diff options
Diffstat (limited to 'Runtime/Filters')
86 files changed, 22340 insertions, 0 deletions
diff --git a/Runtime/Filters/AABBUtility.cpp b/Runtime/Filters/AABBUtility.cpp new file mode 100644 index 0000000..cf7eccf --- /dev/null +++ b/Runtime/Filters/AABBUtility.cpp @@ -0,0 +1,136 @@ +#include "UnityPrefix.h" +#include "AABBUtility.h" +#include "Renderer.h" +#include "Runtime/Filters/Mesh/LodMeshFilter.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Filters/Mesh/SpriteRenderer.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Filters/Deformation/SkinnedMeshFilter.h" +#include "Runtime/BaseClasses/GameObject.h" + +inline bool HasAABB (Renderer& renderer) +{ + return renderer.IsActive () && renderer.GetVisible (); +} + +bool CalculateWorldAABB (GameObject& go, AABB* aabb) +{ + Renderer* renderer = go.QueryComponent (Renderer); + if (renderer && HasAABB (*renderer)) + { + renderer->GetWorldAABB ( *aabb ); + return true; + } + + MeshFilter *lmf = go.QueryComponent (MeshFilter); + if (lmf) + { + Mesh *lm = lmf->GetSharedMesh (); + if (lm) + { + *aabb = lm->GetBounds (); + Matrix4x4f matrix; + go.GetComponent (Transform).CalculateTransformMatrix (matrix); + TransformAABB (*aabb, matrix, *aabb); + return true; + } + } + +#if ENABLE_SPRITES + SpriteRenderer* sprite = go.QueryComponent (SpriteRenderer); + if (sprite) + { + Sprite* frame = sprite->GetSprite(); + if (frame) + { + *aabb = frame->GetBounds(); + Matrix4x4f matrix; + go.GetComponent (Transform).CalculateTransformMatrix (matrix); + TransformAABB (*aabb, matrix, *aabb); + return true; + } + } +#endif + + aabb->SetCenterAndExtent( Vector3f::zero, Vector3f::zero ); + return false; +} + +bool CalculateLocalAABB (GameObject& go, AABB* aabb) +{ + Renderer* renderer = go.QueryComponent (Renderer); + if (renderer && HasAABB (*renderer)) + { + const TransformInfo& info = renderer->GetTransformInfo(); + + Matrix4x4f transformWorldToLocal = renderer->GetComponent(Transform).GetWorldToLocalMatrix(); + Matrix4x4f rendererLocalToTransformLocal; + MultiplyMatrices4x4(&transformWorldToLocal, &info.worldMatrix, &rendererLocalToTransformLocal); + + TransformAABB(info.localAABB, rendererLocalToTransformLocal, *aabb); + + return true; + } + + MeshFilter *lmf = go.QueryComponent (MeshFilter); + if (lmf) + { + Mesh *lm = lmf->GetSharedMesh (); + if (lm) + { + *aabb = lm->GetBounds (); + return true; + } + } + + aabb->SetCenterAndExtent( Vector3f::zero, Vector3f::zero ); + return false; +} + +bool CalculateAABBCornerVertices (GameObject& go, Vector3f* vertices) +{ + Renderer* renderer = go.QueryComponent (Renderer); + if (renderer && HasAABB (*renderer)) + { + Transform const& transform = renderer->GetTransform(); + + AABB aabb; + if (dynamic_pptr_cast<SkinnedMeshRenderer*> (renderer)) + { + renderer->GetWorldAABB( aabb ); + aabb.GetVertices (vertices); + } + else + { + renderer->GetLocalAABB( aabb ); + + aabb.GetVertices (vertices); + TransformPoints3x4 (transform.GetLocalToWorldMatrix (), vertices, vertices, 8); + } + + return true; + } + + Transform* transform = go.QueryComponent (Transform); + MeshFilter *lmf = go.QueryComponent (MeshFilter); + if (transform && lmf) + { + Mesh *lm = lmf->GetSharedMesh (); + if (lm) + { + lm->GetBounds ().GetVertices (vertices); + TransformPoints3x4 (transform->GetLocalToWorldMatrix (), vertices, vertices, 8); + + return true; + } + } + + return false; +} + +AABB CalculateWorldAABB (GameObject& go) +{ + AABB aabb; + CalculateWorldAABB (go, &aabb); + return aabb; +} diff --git a/Runtime/Filters/AABBUtility.h b/Runtime/Filters/AABBUtility.h new file mode 100644 index 0000000..a6d01e6 --- /dev/null +++ b/Runtime/Filters/AABBUtility.h @@ -0,0 +1,18 @@ +#ifndef AABBUTILITY_H +#define AABBUTILITY_H + +#include "Runtime/Geometry/AABB.h" + +class Transform; +class Vector3f; +namespace Unity { class GameObject; } + +bool EXPORT_COREMODULE CalculateLocalAABB (Unity::GameObject& go, AABB* aabb); + +AABB CalculateWorldAABB (Unity::GameObject& go); +bool CalculateWorldAABB (Unity::GameObject& go, AABB* aabb); + +bool CalculateAABBSkinned (Transform& transform, AABB& aabb); +bool CalculateAABBCornerVertices (Unity::GameObject& go, Vector3f* vertices); + +#endif diff --git a/Runtime/Filters/Deformation/BlendShapeAnimationBinding.cpp b/Runtime/Filters/Deformation/BlendShapeAnimationBinding.cpp new file mode 100644 index 0000000..484297f --- /dev/null +++ b/Runtime/Filters/Deformation/BlendShapeAnimationBinding.cpp @@ -0,0 +1,141 @@ +#include "UnityPrefix.h" +#include "Runtime/Animation/GenericAnimationBindingCache.h" +#include "Runtime/Animation/BoundCurve.h" +#include "SkinnedMeshFilter.h" +#include "Runtime/Filters/Mesh/MeshBlendShape.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Interfaces/IAnimationBinding.h" +#include "Runtime/mecanim/generic/crc32.h" + +const char* kBlendShapePrefix = "blendShape."; +const char* kBlendShapeSerializedPropertyPath = "m_BlendShapeWeights.Array.data["; + +static int BlendShapeNameToIndex (SkinnedMeshRenderer& renderer, BindingHash hash) +{ + const Mesh* mesh = renderer.GetMesh(); + if (mesh == NULL) + return -1; + + const BlendShapeData& blendShapes = mesh->GetBlendShapeData(); + int index = GetChannelIndex (blendShapes, hash); + + return index; +} + +static std::string BlendShapeIndexToName (Object& targetObject, int index) +{ + SkinnedMeshRenderer* renderer = dynamic_pptr_cast<SkinnedMeshRenderer*> (&targetObject); + + if (renderer == NULL) + return std::string(); + const Mesh* mesh = renderer->GetMesh(); + if (mesh == NULL) + return std::string(); + + const BlendShapeData& blendShapeData = mesh->GetBlendShapeData(); + if (index < mesh->GetBlendShapeChannelCount ()) + return std::string(kBlendShapePrefix) + GetChannelName (blendShapeData, index); + else + return std::string(); +} + +static bool CalculateBlendShapeHash (const char* name, BindingHash& hash) +{ + // Special case support for blendshape weights. We have to call a function. + const char* shapeName = ParsePrefixedName (name, kBlendShapePrefix); + if (shapeName == NULL) + return false; + + hash = mecanim::processCRC32(shapeName); + return true; +} + +class BlendshapePropertyBinding : public IAnimationBinding +{ +#if UNITY_EDITOR + virtual void GetAllAnimatableProperties (Object& targetObject, std::vector<EditorCurveBinding>& outProperties) const + { + SkinnedMeshRenderer& renderer = static_cast<SkinnedMeshRenderer&> (targetObject); + Mesh* mesh = renderer.GetMesh(); + if (mesh == NULL) + return; + + for (int i = 0; i < mesh->GetBlendShapeChannelCount (); ++i) + AddBinding (outProperties, ClassID(SkinnedMeshRenderer), BlendShapeIndexToName (targetObject, i)); + } +#endif + + virtual float GetFloatValue (const UnityEngine::Animation::BoundCurve& bind) const + { + SkinnedMeshRenderer* renderer = reinterpret_cast<SkinnedMeshRenderer*>(bind.targetObject); + const int shapeIndex = reinterpret_cast<int> (bind.targetPtr); + return renderer->GetBlendShapeWeight(shapeIndex); + } + + virtual void SetFloatValue (const UnityEngine::Animation::BoundCurve& bind, float value) const + { + SkinnedMeshRenderer* renderer = reinterpret_cast<SkinnedMeshRenderer*>(bind.targetObject); + + const int shapeIndex = reinterpret_cast<int> (bind.targetPtr); + renderer->SetBlendShapeWeight(shapeIndex, value); + } + + virtual void SetPPtrValue (const UnityEngine::Animation::BoundCurve& bound, SInt32 value) const { } + + virtual SInt32 GetPPtrValue (const UnityEngine::Animation::BoundCurve& bound) const { return 0;} + + virtual bool GenerateBinding (const UnityStr& attribute, bool pptrCurve, UnityEngine::Animation::GenericBinding& outputBinding) const + { + BindingHash hash; + if (pptrCurve || !CalculateBlendShapeHash (attribute.c_str(), hash)) + return false; + + outputBinding.attribute = hash; + return true; + } + + virtual ClassIDType BindValue (Object& target, const UnityEngine::Animation::GenericBinding& inputBinding, UnityEngine::Animation::BoundCurve& bound) const + { + int index = BlendShapeNameToIndex (static_cast<SkinnedMeshRenderer&> (target), inputBinding.attribute); + if (index == -1) + return ClassID(Undefined); + + bound.targetPtr = (void*)index; + return ClassID(float); + } + + virtual std::string SerializedPropertyPathToCurveAttribute (Object& target, const char* propertyPath) const + { + Assert (target.GetClassID () == ClassID(SkinnedMeshRenderer)); + + if (BeginsWith (propertyPath, kBlendShapeSerializedPropertyPath)) + { + int index = StringToInt (propertyPath + strlen(kBlendShapeSerializedPropertyPath)); + return BlendShapeIndexToName (target, index); + } + else + return string(); + } + + virtual std::string CurveAttributeToSerializedPath (const UnityEngine::Animation::BoundCurve& bound) const + { + const int shapeIndex = reinterpret_cast<int> (bound.targetPtr); + Assert (shapeIndex != -1); + return kBlendShapeSerializedPropertyPath + IntToString(shapeIndex) + ']'; + } + +}; + +static BlendshapePropertyBinding* gBinding = NULL; + +void InitializeBlendShapeAnimationBindingInterface () +{ + gBinding = UNITY_NEW (BlendshapePropertyBinding, kMemAnimation); + UnityEngine::Animation::GetGenericAnimationBindingCache ().RegisterIAnimationBinding (ClassID(SkinnedMeshRenderer), UnityEngine::Animation::kBlendShapeWeightBinding, gBinding); +} + +void CleanupBlendShapeAnimationBindingInterface () +{ + UNITY_DELETE (gBinding, kMemAnimation); +} + diff --git a/Runtime/Filters/Deformation/BlendShapeAnimationBinding.h b/Runtime/Filters/Deformation/BlendShapeAnimationBinding.h new file mode 100644 index 0000000..7857b28 --- /dev/null +++ b/Runtime/Filters/Deformation/BlendShapeAnimationBinding.h @@ -0,0 +1,4 @@ +#pragma once + +void InitializeBlendShapeAnimationBindingInterface (); +void CleanupBlendShapeAnimationBindingInterface ();
\ No newline at end of file diff --git a/Runtime/Filters/Deformation/SkinnedMeshFilter.cpp b/Runtime/Filters/Deformation/SkinnedMeshFilter.cpp new file mode 100644 index 0000000..a6ad95e --- /dev/null +++ b/Runtime/Filters/Deformation/SkinnedMeshFilter.cpp @@ -0,0 +1,1535 @@ +#include "UnityPrefix.h" +#include "SkinnedMeshFilter.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/GfxDevice/ChannelAssigns.h" +#include "Runtime/Filters/Mesh/MeshSkinning.h" +#include "Runtime/Animation/Animation.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Misc/QualitySettings.h" +#include "Runtime/Misc/GameObjectUtility.h" +#include "Runtime/Filters/Mesh/MeshRenderer.h" +#include "Runtime/Filters/Mesh/MeshBlendShaping.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Filters/Mesh/MeshUtility.h" +#include "Runtime/Graphics/DrawUtil.h" +#include "Runtime/GameCode/CallDelayed.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/BaseClasses/SupportedMessageOptimization.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Threads/JobScheduler.h" +#include "Runtime/Dynamics/SkinnedCloth.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/BaseClasses/CleanupManager.h" +#include "Runtime/BaseClasses/EventIDs.h" +#include "Runtime/Misc/PlayerSettings.h" +#include "Runtime/Interfaces/IPhysics.h" +#include "Runtime/Interfaces/IAnimation.h" +#include "BlendShapeAnimationBinding.h" +#if UNITY_PS3 +#include "Runtime/GfxDevice/ps3/GfxGCMVBO.h" +#include "Runtime/Filters/Mesh/MeshPartitioner.h" +#endif + +PROFILER_INFORMATION(gMeshSkinningUpdate, "MeshSkinning.Update", kProfilerRender) +PROFILER_INFORMATION(gMeshSkinningUpdateImmediate, "MeshSkinning.UpdateImmediate", kProfilerRender) +PROFILER_INFORMATION(gMeshSkinningPrepare, "MeshSkinning.Prepare", kProfilerRender) +PROFILER_INFORMATION(gMeshSkinningRender, "MeshSkinning.Render", kProfilerRender) +PROFILER_INFORMATION(gMeshSkinningWait, "MeshSkinning.WaitForSkinThreads", kProfilerRender) +PROFILER_INFORMATION(gMeshSkinningSkinGPU, "MeshSkinning.SkinOnGPU", kProfilerRender) + + +#if UNITY_EDITOR +#define SET_CACHED_SURFACE_AREA_DIRTY() m_CachedSurfaceArea = -1.0f; +#else +#define SET_CACHED_SURFACE_AREA_DIRTY() // do nothing +#endif + +typedef List< ListNode<SkinnedMeshRenderer> > SkinnedMeshList; +static SkinnedMeshList gActiveSkinnedMeshes; + +/* +JOE: + * TODO: Do we really need this. -> Test cloth + +MIRCEA: + + * PS3 version for fallback skinning ( so mesh particle emitters & skinned cloth still works ) + +*/ + +SkinnedMeshRenderer::SkinnedMeshRenderer (MemLabelId label, ObjectCreationMode mode) +: Super(kRendererSkinnedMesh, label, mode) +, m_BlendShapeWeights(0, label) +, m_CachedAnimator(NULL) +, m_CachedBlendShapeCount (0) +, m_SkinNode(this) +, m_MeshNode(this) +, m_AABB(Vector3f::zero, Vector3f::zero) +, m_MemExportInfo(0) +{ + m_Visible = false; + m_UpdateBeforeRendering = false; + m_SourceMeshDirty = false; + m_DirtyAABB = true; + m_CachedMesh = NULL; + m_ChannelsInVBO = 0; + m_Cloth = NULL; + m_VBO = NULL; + SET_CACHED_SURFACE_AREA_DIRTY(); +} + +SkinnedMeshRenderer::~SkinnedMeshRenderer () +{ + Assert(m_CachedAnimator == NULL); + + if(m_MemExportInfo) + GetGfxDevice().DeleteGPUSkinningInfo(m_MemExportInfo); + + if (m_VBO) + { + GetGfxDevice().DeleteVBO(m_VBO); + m_VBO = NULL; + } +} + +void SkinnedMeshRenderer::Setup (Mesh* mesh, const dynamic_array<PPtr<Transform> >& state) +{ + m_Bones = state; + m_Mesh = mesh; + UpdateCachedMesh (); + SetDirty(); +} + +void SkinnedMeshRenderer::SetMesh (Mesh* mesh) +{ + m_Mesh = mesh; + UpdateCachedMesh (); + SetDirty(); +} + +Mesh* SkinnedMeshRenderer::GetMesh () +{ + return m_Mesh; +} + +void SkinnedMeshRenderer::SetBones (const dynamic_array<PPtr<Transform> >& bones) +{ + m_Bones = bones; + SetDirty(); + if (!bones.empty()) + ClearCachedAnimatorBinding(); // switch to non-optimized mode, no binding is needed anymore +} + +void SkinnedMeshRenderer::Reset() +{ + Super::Reset(); + m_Quality = 0; + m_UpdateWhenOffscreen = false; + m_AABB = AABB(Vector3f::zero, Vector3f::zero); +} + + +bool SkinnedMeshRenderer::DoesQualifyForMemExport() const +{ + bool qualifies = (m_Cloth == 0) && (m_MemExportInfo); + qualifies = qualifies && GetPlayerSettings().GetGPUSkinning(); + + return qualifies; +} + +bool SkinnedMeshRenderer::CalculateBoneBasedBounds (const Matrix4x4f* poseMatrices, size_t size, MinMaxAABB& output) +{ + if (m_CachedMesh == NULL) + return false; + + if (m_CachedMesh->GetMaxBoneIndex() >= size) + return false; + + const Mesh::AABBContainer& bounds = m_CachedMesh->GetCachedBonesBounds (); + if (size > bounds.size()) + return false; + + MinMaxAABB minMaxAABB; + for(int i=0;i<size;i++) + { + AABB result; + + ///@TODO: OPTIMIZATION: DO this precomputed. + if (!bounds[i].IsValid()) + continue; + + AABB aabb = bounds[i]; + + TransformAABB(aabb, poseMatrices[i], result); + // TransformAABBSlow (aabb, animatedPose[i], result); + + minMaxAABB.Encapsulate(result.GetMin()); + minMaxAABB.Encapsulate(result.GetMax()); + } + + output = minMaxAABB; + return true; +} + +#if UNITY_EDITOR +bool SkinnedMeshRenderer::CalculateVertexBasedBounds (const Matrix4x4f* poseMatrices, MinMaxAABB& output) +{ + if (m_CachedMesh == NULL) + return false; + + int boneCount = m_CachedMesh->GetBindposeCount (); + + Matrix4x4f* fullMatrices = NULL; + ALLOC_TEMP(fullMatrices, Matrix4x4f, boneCount); + + const Matrix4x4f* bindPoses = m_CachedMesh->GetBindposes(); + for (int i=0;i<boneCount;i++) + { + MultiplyMatrices4x4(&poseMatrices[i], &bindPoses[i], &fullMatrices[i]); + } + + MinMaxAABB minMaxAABB; + + // could actually use only the vertices which are affected by bones which are actually animated + StrideIterator<Vector3f> vertices = m_CachedMesh->GetVertexBegin(); + const BoneInfluence* boneInfluences = m_CachedMesh->GetBoneWeights (); + int count = m_CachedMesh->GetVertexCount(); + if (count == 0) + minMaxAABB.Encapsulate(Vector3f::zero); + for (int i = 0; i < count; ++i, ++vertices) + { + const BoneInfluence& boneInfluence = boneInfluences[i]; + Vector3f v(Vector3f::zero); + + float w = 0; + + // This always calculates bounding box as if it was 4 bone skinning + // 2 bone or 1 bone skinning could produce slightly different results + for (int j = 0; j < 4; ++j) + { + const int boneIndex = boneInfluence.boneIndex[j]; + Assert(boneIndex < boneCount); + + v += fullMatrices[boneIndex].MultiplyPoint3(*vertices) * boneInfluence.weight[j]; + + w += boneInfluence.weight[j]; + } + + Assert(w > 0.99f && w < 1.01f); + + minMaxAABB.Encapsulate(v); + } + + output = minMaxAABB; + + return true; +} +#endif + +bool SkinnedMeshRenderer::CalculateAnimatedPosesWithRoot (const Matrix4x4f& rootMatrix, Matrix4x4f* poses, size_t size) +{ + if (!CalculateAnimatedPoses(poses, size)) + return false; + + for (int i=0;i<size;i++) + { + Matrix4x4f temp; + MultiplyMatrices4x4(&rootMatrix, poses + i, &temp); + poses[i] = temp; + } + return true; +} + +bool SkinnedMeshRenderer::CalculateSkinningMatrices (const Matrix4x4f& rootPose, Matrix4x4f* skinnedPoses, size_t size) +{ + Assert(m_CachedMesh != NULL); + DebugAssert (size > 0); + + if (!CalculateAnimatedPoses(skinnedPoses, size)) + return false; + + MultiplyMatrixArrayWithBase4x4 (&rootPose, skinnedPoses, m_CachedMesh->GetBindposes(), skinnedPoses, size); + + return true; +} + +bool SkinnedMeshRenderer::CalculateAnimatedPoses (Matrix4x4f* poses, size_t size) +{ + if (IsOptimized()) + { + const dynamic_array<UInt16>& skeletonIndices = GetSkeletonIndices(); + if (!skeletonIndices.empty()) // implies: GetAnimator() && GetAnimationInterface() + return GetAnimationInterface()->CalculateWorldSpaceMatricesMainThread(*GetAnimator(), skeletonIndices.begin(), size, poses); + else + return false; + } + else + { + if (size > m_Bones.size()) + return false; + + bool hasAnyBones = false; + for (int i=0;i<size;i++) + { + Transform* transform = m_Bones[i]; + if (transform) + { + poses[i] = transform->GetLocalToWorldMatrix (); + hasAnyBones = true; + } + else + poses[i].SetIdentity (); + } + + return hasAnyBones; + } +} + +const dynamic_array<UInt16>& SkinnedMeshRenderer::GetSkeletonIndices() +{ + Assert(IsOptimized()); + + if (m_SkeletonIndices.empty()) + CreateCachedAnimatorBinding (); + + + return m_SkeletonIndices; +} + +int SkinnedMeshRenderer::GetBindposeCount () const +{ + return m_CachedMesh ? m_CachedMesh->GetBindposeCount() : 0; +} + +bool SkinnedMeshRenderer::PrepareVBO(bool hasSkin, bool hasBlendshape, bool doMemExport, int flags) +{ + if (!hasSkin && !hasBlendshape) + return false; + + // Right now, it can be false for 2 cases: + // 1. GetSkinnedVerticesAndNormal + // In this case, SF_ReadbackBack will also be set. + // SkinMeshInfo.outVertices will be allocated in SkinnedMeshRenderer.m_SkinnedVertices. + // 2. Bake mesh + // In this case, SkinMeshInfo.outVertices will be the vertex data of the output Mesh. + bool doNeedVBO = !(flags & SF_NoUpdateVBO); + bool newVBO = false; + + if (hasBlendshape) + { + // Handle cases: + // hasSkin==true, hasBlendshape==true or + // hasSkin==false, hasBlendshape==true + // + // Hardware skinning is not applicable + if (!m_VBO && doNeedVBO) + { + m_VBO = GetGfxDevice().CreateVBO(); + m_VBO->SetVertexStreamMode(0, VBO::kStreamModeDynamic); + m_VBO->SetIndicesDynamic(false); + newVBO = true; + } + } + else + { + // Handle case: hasSkin==ture, hasBlendshape==false + bool canMapVBO = m_VBO && (m_VBO->GetVertexStreamMode(0) != VBO::kStreamModeNoAccess); + + // Check if mem-export qualification changes. See SkinnedMeshRenderer::PrepareSkinXenon + // Re-create VBO if it changes. + if (m_VBO && (doMemExport == canMapVBO)) + { + GetGfxDevice().DeleteVBO(m_VBO); + m_VBO = NULL; + if(m_MemExportInfo) + m_MemExportInfo->SetDestVBO(NULL); + } + + if (!m_VBO && doNeedVBO) + { + m_VBO = GetGfxDevice().CreateVBO(); + if (!doMemExport) + { + m_VBO->SetVertexStreamMode(0, VBO::kStreamModeDynamic); + m_VBO->SetIndicesDynamic(false); + } + else + m_VBO->UseAsStreamOutput(); + newVBO = true; + } + } + + if (doMemExport) + { + // Rebuild the VBO in case mem-export qualification is gained. This could use cleaning up. + // OR if source is dirty. + if (newVBO || (doNeedVBO && m_SourceMeshDirty)) + m_CachedMesh->CopyToVBO(m_ChannelsInVBO, *m_VBO); + return true; + } + + if (newVBO || (doNeedVBO && (m_SourceMeshDirty || m_VBO->IsVertexBufferLost()))) + { + // fill the VBO + m_VBO->SetMappedFromRenderThread(!m_Cloth); + m_CachedMesh->CopyToVBO(m_ChannelsInVBO, *m_VBO); + m_SourceMeshDirty = false; + } + + return true; +} + + +bool SkinnedMeshRenderer::PrepareSkinCommon(UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask) +{ + m_UpdateBeforeRendering = false; + if (!m_CachedMesh || (m_CachedMesh->GetSubMeshCount () == 0)) + return false; + DebugAssertIf(m_CachedMesh != m_Mesh); + + int bindposeCount = GetBindposeCount(); + int blendShapeCount = GetValidBlendShapeWeightCount (); + + if (bindposeCount > 0 && m_CachedMesh->GetMaxBoneIndex() >= bindposeCount) + { + ErrorStringObject("Bone influences do not match bones.", this); + return false; + } + + // We are not monitoring whether the bones have changed + // so we always have to make cached surface area dirty, + // as it could have changed. + SET_CACHED_SURFACE_AREA_DIRTY(); + + bool hasSkin = (bindposeCount > 0) && !m_CachedMesh->GetSkin().empty(); + + // For skinned meshes we only care about active blend shapes. + // For non-skinned meshes we need to take the blend shape code path + // even if all the shapes are zero weight. There is no optimized path + // to render the undeformed source mesh (case 557165). + // TODO: Write an optimized path for no skin, no active blend shapes. + bool hasBlendshape = false; + if (hasSkin) + hasBlendshape = blendShapeCount > 0; + else + hasBlendshape = m_CachedBlendShapeCount > 0; + + bool doMemExport = DoesQualifyForMemExport() && + (flags & SF_AllowMemExport) && hasSkin && !hasBlendshape; + + m_CachedMesh->InitVertexBufferData(requiredChannels); + m_ChannelsInVBO = m_CachedMesh->GetAvailableChannels(); + + if (!PrepareVBO(hasSkin, hasBlendshape, doMemExport, flags)) + return false; + + // Fill SkinMeshInfo + skin.boneCount = bindposeCount; + skin.blendshapeCount = blendShapeCount; + skin.vertexCount = m_CachedMesh->GetVertexCount(); + skin.memExport = doMemExport; + skin.Allocate(); + + if (hasSkin) + { + skin.bonesPerVertex = GetBonesPerVertexCount(); + skin.compactSkin = m_CachedMesh->GetSkinInfluence(skin.bonesPerVertex); + + Matrix4x4f rootPose; + if (!(flags & SF_ClothPlaying)) + rootPose = GetActualRootBone().GetWorldToLocalMatrixNoScale (); + else + // clothed skins are simulated using world space rotation, so rotating the character will affect the cloth simulation. + // translation is applied using forces in the cloth, which is smoother. + rootPose.SetTranslate (-GetActualRootBone().GetPosition()); + + bool canCalcSkinMatricesInMT = false; + if (calcSkinMatricesTask && IsOptimized()) + { + const dynamic_array<UInt16>& skeletonIndices = GetSkeletonIndices(); + if (skeletonIndices.empty()) + return false; + else // implies GetAnimationInterface() && GetAnimator() + { + const void* skeletonPose = GetAnimationInterface()->GetGlobalSpaceSkeletonPose(*GetAnimator()); + if (skeletonPose) + { + canCalcSkinMatricesInMT = true; + + calcSkinMatricesTask->skeletonPose = skeletonPose; + calcSkinMatricesTask->skeletonIndices = skeletonIndices.begin(); + calcSkinMatricesTask->rootPose = rootPose; + calcSkinMatricesTask->bindPoseCount = bindposeCount; + calcSkinMatricesTask->bindPose = m_CachedMesh->GetBindposes(); + + calcSkinMatricesTask->outPose = skin.cachedPose; + } + } + } + + if (!canCalcSkinMatricesInMT) + { + // slow code path + if (!CalculateSkinningMatrices(rootPose, skin.cachedPose, bindposeCount)) + return false; + } + } + else + { + skin.cachedPose = NULL; + skin.compactSkin = NULL; + } + + if (hasBlendshape) + { + Assert (skin.blendshapeCount <= m_CachedMesh->GetBlendShapeChannelCount()); + memcpy (skin.blendshapeWeights, m_BlendShapeWeights.begin(), skin.blendshapeCount * sizeof(float)); + skin.blendshapes = &m_CachedMesh->GetBlendShapeData(); + } + +#if UNITY_PS3 + if ((NULL == m_Cloth) && (!m_CachedMesh->m_PartitionInfos.empty())) + { + m_ChannelsInVBO = m_CachedMesh->GetAvailableChannels(); + return true; + } +#endif + + const VertexData& vertexData = m_CachedMesh->GetVertexData(); + const StreamInfo streamInfo = vertexData.GetStream(0); + skin.inVertices = vertexData.GetDataPtr() + streamInfo.offset; + skin.inStride = streamInfo.stride; + skin.outStride = streamInfo.stride; +#if !UNITY_FLASH + if (streamInfo.channelMask & ~VERTEX_FORMAT3(Vertex, Normal, Tangent)) + ErrorString(Format("Skinned mesh stream should contain only positions, normals and tangents. channelMask was %lx", streamInfo.channelMask)); +#endif + + if (skin.memExport) + return true; + + // Skin vertices into vbo + const ChannelInfo& normalInfo = vertexData.GetChannel(kShaderChannelNormal); + const ChannelInfo& tangentInfo = vertexData.GetChannel(kShaderChannelTangent); + +#if !UNITY_PS3 // Stream layout for skinned models is different on PS3 + DebugAssert(!normalInfo.IsValid() || (normalInfo.stream == 0 && normalInfo.format == kChannelFormatFloat && normalInfo.dimension == 3)); + DebugAssert(!tangentInfo.IsValid() || (tangentInfo.stream == 0 && tangentInfo.format == kChannelFormatFloat && tangentInfo.dimension == 4)); +#endif + + skin.skinNormals = normalInfo.IsValid(); + skin.normalOffset = normalInfo.offset; + skin.skinTangents = tangentInfo.IsValid(); + skin.tangentOffset = tangentInfo.offset; + + if (flags & SF_ReadbackBuffer) + { + // Allocate a temporary buffer to read back from + m_SkinnedVertices.resize_uninitialized(skin.outStride * skin.vertexCount); + skin.outVertices = &m_SkinnedVertices[0]; + } + return true; +} + +#if UNITY_PS3 +bool SkinnedMeshRenderer::PrepareSkinPS3( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask ) +{ + if (!m_CachedMesh || m_CachedMesh->GetSkin().empty ()) + return false; + + skin.vertexData = NULL; + + if (!PrepareSkinCommon( requiredChannels, flags, skin, calcSkinMatricesTask )) + return false; + + skin.vertexData = &m_CachedMesh->GetVertexData(); + + if(m_CachedMesh->m_PartitionInfos.empty() || m_Cloth) + return true; + + if(m_SourceMeshDirty) + { + VertexBufferData vertexBuffer; + IndexBufferData indexBuffer; + + m_CachedMesh->GetVertexBufferData( vertexBuffer, m_ChannelsInVBO ); + m_CachedMesh->GetIndexBufferData( indexBuffer ); + + vertexBuffer.inflPerVertex = skin.bonesPerVertex; + vertexBuffer.numInfluences = m_CachedMesh->GetSkin().size(); + vertexBuffer.influences = skin.compactSkin; + vertexBuffer.numBones = skin.boneCount; + vertexBuffer.bones = skin.cachedPose; + + m_VBO->UpdateVertexData( vertexBuffer ); + m_VBO->UpdateIndexData( indexBuffer ); + + m_SourceMeshDirty = false; + } + else + ((GfxGCMVBO*)m_VBO)->UpdateBones(skin.boneCount, skin.cachedPose); + + skin.Release(); + return false; +} +#endif + +bool SkinnedMeshRenderer::PrepareSkinGPU( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask ) +{ + if (!PrepareSkinCommon( requiredChannels, flags, skin, calcSkinMatricesTask )) + return false; + + if (skin.memExport) + { + Assert(m_ChannelsInVBO != 0); + Assert(skin.inStride == skin.outStride); + + + // TODO: Are there any situations where this might change on the fly? In the editor? + if(!m_MemExportInfo->GetDestVBO() || m_SourceMeshDirty) + { + VertexBufferData vertexBuffer; + const StreamInfo& skinStream = vertexBuffer.streams[0]; + + m_CachedMesh->GetVertexBufferData(vertexBuffer, m_ChannelsInVBO); + + // Source data + const size_t dataBufferSize = skinStream.stride * skin.vertexCount; + const void* dataBufferPtr = vertexBuffer.buffer + skinStream.offset; + + // Skin + Assert(sizeof(BoneInfluence) == 32); + const int skinBufferSize = m_CachedMesh->GetVertexCount() * sizeof(BoneInfluence); + // const BoneInfluence* skinBufferPtr = m_CachedMesh->GetBoneWeights(); + void *skinBufferPtr = m_CachedMesh->GetSkinInfluence(skin.bonesPerVertex); + + m_MemExportInfo->SetVertexCount(skin.vertexCount); + m_MemExportInfo->SetChannelMap(skinStream.channelMask); + m_MemExportInfo->SetStride(skin.outStride); + m_MemExportInfo->SetDestVBO(m_VBO); + m_MemExportInfo->SetBonesPerVertex(skin.bonesPerVertex); + + GetGfxDevice().UpdateSkinSourceData(m_MemExportInfo, dataBufferPtr, (const BoneInfluence *)skinBufferPtr, m_SourceMeshDirty); + } + + + // Bones (uses 4 weights, ignores quality settings) + Assert(skin.boneCount > 0); + + skin.mei = m_MemExportInfo; + m_SourceMeshDirty = false; + } + else + { + Assert(m_SourceMeshDirty == false); + } + + return true; +} + +bool SkinnedMeshRenderer::PrepareSkin( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask ) +{ +#if UNITY_PS3 + return PrepareSkinPS3(requiredChannels, flags, skin, calcSkinMatricesTask); +#else + return PrepareSkinGPU(requiredChannels, flags, skin, calcSkinMatricesTask); +#endif +} + +#if UNITY_EDITOR +void SkinnedMeshRenderer::UpdateClothDataForEditing(const SkinMeshInfo& skin) +{ + // update cloth vertices in edit mode for the cloth vertex editor + if (m_Cloth != NULL && !IsWorldPlaying()) + { + dynamic_array<Vector3f> &vertices = m_Cloth->GetVertices(); + dynamic_array<Vector3f> &normals = m_Cloth->GetNormals(); + GetSkinnedVerticesAndNormals (&vertices, &normals); + } +} +#endif + +bool SkinnedMeshRenderer::SkinMesh( SkinMeshInfo& skin, bool lastMemExportThisFrame, UInt32 cpuFence, int flags ) +{ + GfxDevice& device = GetGfxDevice(); + if (skin.memExport) + { + GetGfxDevice().UpdateSkinBonePoses(m_MemExportInfo, skin.boneCount, skin.cachedPose); + skin.Release(); + + // Issue GPU skinning requests in sync. Cloth is done on CPU in parallel. + PROFILER_AUTO(gMeshSkinningSkinGPU, this) + device.SkinOnGPU(m_MemExportInfo, lastMemExportThisFrame); + device.GetFrameStats().AddDrawCall (skin.vertexCount, skin.vertexCount); + GPU_TIMESTAMP(); + return true; + } + else + { +#if ENABLE_MULTITHREADED_CODE + m_CachedMesh->SetCurrentCPUFence(cpuFence); +#endif + return GetGfxDevice().SkinMesh(skin, (flags & SF_ReadbackBuffer) ? NULL : m_VBO); + } +} + +bool SkinnedMeshRenderer::SkinMeshImmediate( UInt32 requiredChannels ) +{ + GfxDevice& device = GetGfxDevice(); + // Double check there are no fences inserted during skinning + UInt32 expectedFence = device.GetNextCPUFence(); + device.BeginSkinning(1); + SkinMeshInfo skin; + int flags = SF_AllowMemExport; + bool success = PrepareSkin(requiredChannels, flags, skin); + if (success) + { + SkinMesh(skin, true, expectedFence, flags); +#if UNITY_EDITOR + UpdateClothDataForEditing(skin); +#endif + } + device.EndSkinning(); + // Insert fence after all skinning is complete + UInt32 fence = device.InsertCPUFence(); + + return success; +} + +#if UNITY_EDITOR +float SkinnedMeshRenderer::GetCachedSurfaceArea () +{ + if (m_CachedSurfaceArea >= 0.0f) + return m_CachedSurfaceArea; + + Mesh* mesh = m_CachedMesh; + if (!mesh) + { + m_CachedSurfaceArea = 1.0f; + return m_CachedSurfaceArea; + } + + Matrix4x4f objectToWorld = GetTransformInfo ().worldMatrix; + + Mesh::TemporaryIndexContainer triangles; + mesh->GetTriangles (triangles); + + dynamic_array<Vector3f> vertices; + if (GetSkinnedVerticesAndNormals (&vertices, NULL)) // this may fail and return empty array as is the case in repro for bug 505751. + { + m_CachedSurfaceArea = CalculateSurfaceArea (objectToWorld, triangles, vertices); + } + else + { + m_CachedSurfaceArea = 1.0f; // to avoid repeat invocation of failing GetCachedSurfaceArea + } + return m_CachedSurfaceArea; +} + +bool SkinnedMeshRenderer::GetSkinnedVerticesAndNormals (dynamic_array<Vector3f>* vertices, dynamic_array<Vector3f>* normals) +{ + UInt32 requiredChannels = (1 << kShaderChannelVertex); + if (normals) + requiredChannels |= (1 << kShaderChannelNormal); + + SkinMeshInfo skin; + if (!PrepareSkinCommon(requiredChannels, SF_ReadbackBuffer | SF_NoUpdateVBO, skin)) + return false; + + DeformSkinnedMesh(skin); + skin.Release(); + + bool hasVertices = m_ChannelsInVBO & (1 << kShaderChannelVertex); + bool hasNormals = skin.skinNormals; + + if (vertices && hasVertices) + { + vertices->resize_uninitialized (skin.vertexCount); + + for (int i = 0; i < skin.vertexCount; i++) + { + char* vertex = ((char*)skin.outVertices + i * skin.outStride); + (*vertices)[i] = *((Vector3f*)vertex); + } + } + + if (normals && hasNormals) + { + normals->resize_uninitialized (skin.vertexCount); + + for (int i = 0; i < skin.vertexCount; i++) + { + char* normal = ((char*)skin.outVertices + i * skin.outStride + skin.normalOffset); + (*normals)[i] = *((Vector3f*)normal); + } + } + return true; +} +#endif + + +int SkinnedMeshRenderer::GetBonesPerVertexCount () +{ + if (m_Quality == 0) + return GetQualitySettings().GetCurrent().blendWeights; + else + return m_Quality; +} + +template<class TransferFunction> inline +void SkinnedMeshRenderer::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + transfer.SetVersion(2); + + TRANSFER_SIMPLE (m_Quality); + TRANSFER_SIMPLE (m_UpdateWhenOffscreen); + transfer.Align(); + + transfer.Transfer (m_Mesh, "m_Mesh"); + + transfer.Transfer (m_Bones, "m_Bones", kHideInEditorMask); + transfer.Align(); + + transfer.Transfer(m_BlendShapeWeights, "m_BlendShapeWeights"); + + transfer.Transfer(m_RootBone, "m_RootBone"); + + transfer.Transfer(m_AABB, "m_AABB"); + transfer.Transfer(m_DirtyAABB, "m_DirtyAABB", kHideInEditorMask); + transfer.Align(); +} + +IMPLEMENT_CLASS_HAS_INIT (SkinnedMeshRenderer) +IMPLEMENT_OBJECT_SERIALIZE (SkinnedMeshRenderer) + +void SkinnedMeshRenderer::InitializeClass () +{ + REGISTER_MESSAGE_VOID (SkinnedMeshRenderer, kBecameVisible, BecameVisible); + REGISTER_MESSAGE_VOID (SkinnedMeshRenderer, kBecameInvisible, BecameInvisible); + + REGISTER_MESSAGE_VOID(SkinnedMeshRenderer, kDidDeleteMesh, DidDeleteMesh); + REGISTER_MESSAGE_VOID(SkinnedMeshRenderer, kDidModifyMesh, DidModifyMesh); + RegisterAllowNameConversion (SkinnedMeshRenderer::GetClassStringStatic(), "m_LodMesh", "m_Mesh"); + RegisterAllowNameConversion (SkinnedMeshRenderer::GetClassStringStatic(), "m_Animation", "m_DisableAnimationWhenOffscreen"); + + + InitializeBlendShapeAnimationBindingInterface (); + +} + +void SkinnedMeshRenderer::CleanupClass () +{ + CleanupBlendShapeAnimationBindingInterface (); +} + +void SkinnedMeshRenderer::DidDeleteMesh () +{ + m_CachedMesh = NULL; + m_CachedBlendShapeCount = 0; +} + +void SkinnedMeshRenderer::DidModifyMesh () +{ + m_SourceMeshDirty = true; +} + +void SkinnedMeshRenderer::UpdateCachedMesh () +{ + Mesh* mesh = m_Mesh; + if (mesh != m_CachedMesh) + { + m_SourceMeshDirty = true; + + m_CachedMesh = mesh; + BoundsChanged(); + m_TransformDirty = true; + SET_CACHED_SURFACE_AREA_DIRTY(); + + m_MeshNode.RemoveFromList(); + + if (m_CachedMesh) + m_CachedMesh->AddObjectUser( m_MeshNode ); + } + + if (m_CachedMesh != NULL) + m_CachedBlendShapeCount = GetBlendShapeChannelCount(m_CachedMesh->GetBlendShapeData()); + else + m_CachedBlendShapeCount = 0; + + ClearCachedAnimatorBinding(); +} + + + +void SkinnedMeshRenderer::UpdateRenderer() +{ + if (GetEnabled() && IsActive()) + { + // Force update bounding volumes when we have a root bone or we have to + bool recalculateBoundingVolumeEveryFrame = ShouldRecalculateBoundingVolumeEveryFrame(); + if (recalculateBoundingVolumeEveryFrame) + { + // Make we continously get this callback every frame so that we can make sure that skinned meshes are put in the gActiveSkinnedMeshes queue + // depending on their visibility + UpdateManagerState(true); + + // The root transform can move every frame and there is no way for us to track it. + TransformChanged (Transform::kPositionChanged | Transform::kRotationChanged | Transform::kScaleChanged); + } + } + + UpdateVisibleSkinnedMeshQueue(IsActive()); + + Super::UpdateRenderer(); +} + +void SkinnedMeshRenderer::UpdateVisibleSkinnedMeshQueue (bool active) +{ + bool needsUpdate = m_Visible && (GetEnabled() && active); + if (needsUpdate == m_SkinNode.IsInList()) + return; + + if (needsUpdate) + gActiveSkinnedMeshes.push_back(m_SkinNode); + else + m_SkinNode.RemoveFromList(); +} + +void SkinnedMeshRenderer::Deactivate (DeactivateOperation operation) +{ + Super::Deactivate(operation); + UpdateVisibleSkinnedMeshQueue(false); + ClearCachedAnimatorBinding(); +} + +void SkinnedMeshRenderer::SetUpdateWhenOffscreen (bool onlyIfVisible) +{ + m_UpdateWhenOffscreen = onlyIfVisible; + + // We might have to start firing UpdateRenderer events every frame... + UpdateManagerState (IsActive()); + BoundsChanged (); + + SetDirty(); +} + +void SkinnedMeshRenderer::BecameVisible () +{ + m_Visible = true; + + // When using LOD we might have a skinned mesh, the animation component might sample during OnBecameVisible since it wasn't visible before. + // In that case the root bone might change, thus we force a transformDirty in on became visible as well as every single frame. + if (ShouldRecalculateBoundingVolumeEveryFrame()) + m_TransformDirty = true; + + UpdateVisibleSkinnedMeshQueue (IsActive()); + + m_UpdateBeforeRendering = true; +} + +void SkinnedMeshRenderer::BecameInvisible () +{ + m_Visible = false; + UpdateVisibleSkinnedMeshQueue (IsActive()); +} + + +static GPUSkinningInfo* CreateGPUSkinningIfAvailable() +{ + if (!GetBuildSettings().hasAdvancedVersion) + return NULL; + + return GetGfxDevice().CreateGPUSkinningInfo(); +} + + +void SkinnedMeshRenderer::AwakeFromLoad(AwakeFromLoadMode awakeMode) +{ + if (!m_MemExportInfo) + m_MemExportInfo = CreateGPUSkinningIfAvailable(); + + #if UNITY_EDITOR || SUPPORT_REPRODUCE_LOG + HandleOldSkinnedFilter (); + #endif + + Super::AwakeFromLoad(awakeMode); + + UpdateCachedMesh (); + + // Make sure we are added to the visibile queue if we the renderer is active + UpdateVisibleSkinnedMeshQueue (IsActive()); + + // The root transform pptr might have changed and thus worldTransform needs to be updated + TransformChanged(Transform::kPositionChanged | Transform::kRotationChanged | Transform::kScaleChanged | Transform::kParentingChanged); + + m_BlendShapeWeights.resize_initialized(m_CachedBlendShapeCount, 0.0F); +} + +void SkinnedMeshRenderer::SetQuality (int quality) +{ + m_Quality = quality; SetDirty(); +} + +void SkinnedMeshRenderer::Render (int subsetIndex, const ChannelAssigns& channels) +{ + PROFILER_AUTO(gMeshSkinningRender, this) + + if (m_CachedMesh) + { + if (m_CachedMesh->GetSkin().empty() && m_CachedBlendShapeCount == 0) + { + ErrorStringObject("SkinnedMeshRenderer requires a mesh with skinning or blendshape information.", this); + return; + } + + UInt32 requiredChannels = channels.GetSourceMap(); + // Skinned cloth prefers to have normals, even if shader does not need them + if (m_Cloth) + requiredChannels |= (1<<kShaderChannelNormal); + + // Just in time update + if (m_UpdateBeforeRendering || m_SourceMeshDirty || !m_VBO || m_VBO->IsVertexBufferLost()) + { + PROFILER_BEGIN(gMeshSkinningUpdateImmediate, this) + bool success = SkinMeshImmediate(requiredChannels); + PROFILER_END + + // Mesh skinning can fail (Bone indices out of bounds, bone transforms missing etc) + if (!success) + return; + } + + if (m_CustomProperties) + GetGfxDevice().SetMaterialProperties (*m_CustomProperties); + DrawUtil::DrawVBOMeshRaw (*m_VBO, *m_CachedMesh, channels, subsetIndex, m_ChannelsInVBO); + } + + GPU_TIMESTAMP(); +} + +void SkinnedMeshRenderer::SetLocalAABB(const AABB& bounds) +{ + m_AABB = bounds; + m_DirtyAABB = false; + SetDirty(); +} + +void SkinnedMeshRenderer::ReadSkinningDataForCloth(const SkinMeshInfo& skin) +{ +#if ENABLE_CLOTH + void *normalPointer = (char*)skin.outVertices + skin.normalOffset; + if (!skin.skinNormals) + normalPointer = NULL; + void *tangentPointer = (char*)skin.outVertices + skin.tangentOffset; + if (!skin.skinTangents) + tangentPointer = NULL; + + GetIPhysics()->SetUpSkinnedBuffersOnSkinnedCloth(*m_Cloth, skin.outVertices, normalPointer, tangentPointer, skin.outStride); +#endif +} + +void SkinnedMeshRenderer::UpdateAllSkinnedMeshes(UpdateType updateType, dynamic_array<SkinnedMeshRenderer*>* outMeshes) +{ + PROFILER_AUTO(gMeshSkinningUpdate, NULL) + + // TODO: we should submit skinning jobs, go do something else (e.g. update particles, movies, whatnot), + // and wait/reintegrate jobs after that is done. + + int flags = SF_None; + if (updateType == kUpdateCloth) + { + flags |= SF_ReadbackBuffer; + if (IsWorldPlaying()) + flags |= SF_ClothPlaying; + } + else + { + flags = SF_AllowMemExport; + } + + size_t skinCount = 0; + size_t maxCount = gActiveSkinnedMeshes.size_slow(); + dynamic_array<SkinnedMeshRenderer*> skinMeshes(maxCount, kMemTempAlloc); + dynamic_array<SkinMeshInfo> skinInfos(maxCount, kMemTempAlloc); + dynamic_array<CalculateSkinMatricesTask> calculateSkinMatricesTasks(maxCount, kMemTempAlloc); + + PROFILER_BEGIN(gMeshSkinningPrepare, NULL); + // Find out which renderers to skin this frame + const SkinMeshInfo* lastMemExport = 0; + SkinnedMeshList::iterator next; + int skinMatrixTaskCount = 0; + for (SkinnedMeshList::iterator i=gActiveSkinnedMeshes.begin();i != gActiveSkinnedMeshes.end();i=next) + { + SkinnedMeshRenderer& skin = **i; + next = i; + next++; + UpdateType type = (skin.m_Cloth && IsWorldPlaying())? kUpdateCloth : kUpdateNonCloth; + if (type != updateType) + continue; + SkinMeshInfo& info = skinInfos[skinCount]; + memset(&info, 0, sizeof(SkinMeshInfo)); + + calculateSkinMatricesTasks[skinMatrixTaskCount].skeletonPose = NULL; // mark invalid task + if (skin.PrepareSkin(skin.m_ChannelsInVBO, flags, info, &calculateSkinMatricesTasks[skinMatrixTaskCount])) + { + if (calculateSkinMatricesTasks[skinMatrixTaskCount].skeletonPose != NULL) + skinMatrixTaskCount++; // valid task + skinMeshes[skinCount] = &skin; + skinCount++; + if (info.memExport) + lastMemExport = &info; + } + } + PROFILER_END; + + if (skinMatrixTaskCount) + { +#if ENABLE_MULTITHREADED_CODE + JobScheduler& scheduler = GetJobScheduler(); + JobScheduler::JobGroupID jobGroup; +#define CALC_SKIN_MATRICES_LOOP(x,list,size) \ + { \ + size_t jobCount = size; \ + jobGroup = scheduler.BeginGroup(jobCount); \ + for (size_t i = 0; i < jobCount; ++i) \ + { CalculateSkinMatricesTask& task = list[i]; scheduler.SubmitJob (jobGroup, x, &task, NULL); } \ + scheduler.WaitForGroup (jobGroup); \ + } +#else +#define CALC_SKIN_MATRICES_LOOP(x,list,size) \ + for (size_t i=0;i<size;i++) \ + { CalculateSkinMatricesTask& task = list[i]; x (&task); } +#endif + + // Do it in multiple threads + if (GetAnimationInterface()) + { + /// @TODO: Simplify this. No need for having an interface that returns a callback... Doh + CalculateAnimatorSkinMatricesFunc calculateAnimatorSkinMatricesFunc = + GetAnimationInterface()->GetCalculateAnimatorSkinMatricesFunc(); + if (calculateAnimatorSkinMatricesFunc) + CALC_SKIN_MATRICES_LOOP(calculateAnimatorSkinMatricesFunc, calculateSkinMatricesTasks, skinMatrixTaskCount) + } + } + + if (skinCount == 0) + return; + + skinMeshes.resize_uninitialized(skinCount); + skinInfos.resize_uninitialized(skinCount); + + // Double check there are no fences inserted during skinning + GfxDevice& device = GetGfxDevice(); + UInt32 expectedFence = device.GetNextCPUFence(); + + // Now we know exactly which renderers to skin and which one is last + device.BeginSkinning(skinCount); + for (int i = 0; i < skinCount; i++) + { + SkinMeshInfo& info = skinInfos[i]; + SkinnedMeshRenderer& skin = *skinMeshes[i]; + bool lastMemExportThisFrame = (&info == lastMemExport); + skin.SkinMesh(info, lastMemExportThisFrame, expectedFence, flags); + } + + PROFILER_BEGIN(gMeshSkinningWait, NULL) + device.EndSkinning(); + PROFILER_END; + + // Insert fence after all skinning is complete + UInt32 fence = device.InsertCPUFence(); + Assert(fence == expectedFence); + + // Read back vertices for cloth + // It's fine to do this after EndSkinning() since we own the buffer (m_SkinnedVertices) + if (updateType == kUpdateCloth) + { + for (int i = 0; i < skinCount; i++) + { + SkinnedMeshRenderer& skin = *skinMeshes[i]; + skin.ReadSkinningDataForCloth(skinInfos[i]); + } + } + + if (outMeshes) + outMeshes->assign(skinMeshes.begin(), skinMeshes.end()); +} + +void SkinnedMeshRenderer::UploadSkinnedClothes(const dynamic_array<SkinnedMeshRenderer*>& skinnedMeshes) +{ + int skinCount = skinnedMeshes.size(); + for (int i = 0; i < skinCount; i++) + { + SkinnedMeshRenderer& skin = *skinnedMeshes[i]; + + if (skin.m_SkinnedVertices.empty()) + continue; + + VertexStreamData mappedVSD; + if (skin.m_VBO->MapVertexStream(mappedVSD, 0)) + { + memcpy(mappedVSD.buffer, skin.m_SkinnedVertices.data(), skin.m_SkinnedVertices.size()); + skin.m_VBO->UnmapVertexStream(0); + } + } +} + +Transform& SkinnedMeshRenderer::GetActualRootBone () +{ + Transform* rootBone = m_RootBone; + if (rootBone != NULL) + // Not optimized mode && m_RootBone != NULL + return *rootBone; + else + // Optimized mode + // Not optimized mode && m_RootBone == NULL + return GetTransform(); +} + + +bool SkinnedMeshRenderer::CalculateRootLocalSpaceBounds (MinMaxAABB& minMaxAAbb) +{ + Matrix4x4f* poses; + int poseCount = GetBindposeCount(); + ALLOC_TEMP(poses, Matrix4x4f, poseCount); + + Transform& rootBone = GetActualRootBone(); + + if (CalculateAnimatedPosesWithRoot(rootBone.GetWorldToLocalMatrix(), poses, poseCount) && CalculateBoneBasedBounds(poses, poseCount, minMaxAAbb)) + return true; + else + return false; +} + +void SkinnedMeshRenderer::UpdateTransformInfo () +{ + Transform& rootBone = GetActualRootBone(); + + Vector3f pos; + Quaternionf rot; + TransformType transformType = rootBone.GetPositionAndRotationWithTransformType (pos, rot); + + const bool hasSkin = !m_CachedMesh ? true : !m_CachedMesh->GetSkin().empty(); + if (hasSkin || IsNoScaleTransform(transformType)) + { + m_TransformInfo.transformType = transformType & kOddNegativeScaleTransform; + m_TransformInfo.worldMatrix.SetTR (pos, rot); + m_TransformInfo.invScale = 1.0f; + } + // This codepath only exists for blendshapes. Skinned meshes will always use only position & rotation (no scale) + // For blendshapes the skinning code does no bone or matrix deformation thus there is no good way to plug scale in there. + else + { + float uniformScale = 1.0f; + m_TransformInfo.worldMatrix = rootBone.GetLocalToWorldMatrix(); + m_TransformInfo.transformType = transformType = ComputeTransformType(m_TransformInfo.worldMatrix, uniformScale); + m_TransformInfo.invScale = 1.0f / uniformScale; + } + +#if UNITY_SUPPORTS_VFP || (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING) + // NOTE: optimized VFP routines do not do any normalization + // instead we rely on GPU to do that + if (GetBonesPerVertexCount () != 1) + m_TransformInfo.transformType |= kNonUniformScaleTransform; +#endif + + // Compute world space bounding volume from each bone to get a very accurate bounding volume + if (m_UpdateWhenOffscreen && hasSkin) + { + Matrix4x4f* poses; + int poseCount = GetBindposeCount(); + ALLOC_TEMP(poses, Matrix4x4f, poseCount); + + MinMaxAABB accurateMinMax; + if (CalculateAnimatedPoses(poses, poseCount) && CalculateBoneBasedBounds(poses, poseCount, accurateMinMax)) + { + m_TransformInfo.worldAABB = accurateMinMax; + InverseTransformAABB(m_TransformInfo.worldAABB, pos, rot, m_TransformInfo.localAABB); + return; + } + } + + // Calculate m_AABB from the current pose and undirty the AABB + if (m_DirtyAABB) + { + MinMaxAABB accurateMinMax; + if (!hasSkin && m_CachedMesh) + { + accurateMinMax = m_CachedMesh->GetLocalAABB(); + SetLocalAABB(accurateMinMax); + } + else if (CalculateRootLocalSpaceBounds(accurateMinMax)) + { + SetLocalAABB(accurateMinMax); + } + else + { + ////@TOOD: figure out something logicalll.... + m_AABB = AABB(Vector3f::zero, Vector3f::zero); + } + } + + MinMaxAABB localAABB = m_AABB; + + if (IsNoScaleTransform(transformType)) + { + m_TransformInfo.localAABB = localAABB; + TransformAABB (localAABB, pos, rot, m_TransformInfo.worldAABB); + } + else + { + // Calculate world space bounding volume (Transform from root space to world space) + Matrix4x4f rootToWorldMatrix; + rootBone.CalculateTransformMatrix (rootToWorldMatrix); + TransformAABB (localAABB, rootToWorldMatrix, m_TransformInfo.worldAABB); + + + // Calculate local space bounding volume (Transform from root space to SkinnedMeshRenderer space) + // When we have scaled objects we have to + // The m_TransformInfo.localAABB is the local space bounding volume relative to the root position and rotation, excluding scale. + // Thus the following code simply exists to add scale to the stored m_AABB. + Matrix4x4f inverseWorldMatrix; + inverseWorldMatrix.SetTRInverse (pos, rot); + + Matrix4x4f rootToLocal; + MultiplyMatrices4x4(&inverseWorldMatrix, &rootToWorldMatrix, &rootToLocal); + + TransformAABB (localAABB, rootToLocal, m_TransformInfo.localAABB); + } +} + + +void SkinnedMeshRenderer::GetSkinnedMeshLocalAABB (AABB& bounds) +{ + // Make sure the localAABB & m_LocalAABB is up to date + const TransformInfo& info = GetTransformInfo(); + + // Extract from world space bounding volume + if (m_UpdateWhenOffscreen) + bounds = info.localAABB; + // Return precomputed local bounding volume + else + bounds = m_AABB; + +} + +void SkinnedMeshRenderer::BakeMesh (Mesh& mesh) +{ + if (m_CachedMesh == NULL) + return; + + // Making the mesh relative to the root bone is very unintutive when using it as a SkinnedMesh. + // So don't do that when exporting the mesh. + PPtr<Transform> oldRootBone = m_RootBone; + m_RootBone = NULL; + + SkinMeshInfo skin; +#if UNITY_PS3 + if (false == m_CachedMesh->m_Partitions.empty()) + { + WarningString(Format("Optimized skinned meshes cannot be baked (%s).", m_CachedMesh->GetName())); + return; + } + + skin.vertexData = &m_CachedMesh->GetVertexData(); +#endif + + if (PrepareSkinCommon(m_CachedMesh->GetAvailableChannels(), SF_NoUpdateVBO, skin)) + { + mesh.WaitOnRenderThreadUse(); + + mesh.SetBoneWeights(NULL, 0); + + const VertexData& skinVertexData = m_CachedMesh->GetVertexData(); + const VertexStreamsLayout& skinStreamsLayout = skinVertexData.GetStreamsLayout(); + const VertexChannelsLayout& skinChannelsLayout = skinVertexData.GetChannelsLayout(); + mesh.ResizeVertices (skin.vertexCount, m_ChannelsInVBO, skinStreamsLayout, skinChannelsLayout); + + skin.outVertices = mesh.GetVertexDataPointer(); + + DeformSkinnedMesh(skin); + skin.Release(); + + // Skinning only updates vertex data in "hot" stream (zero). Have to copy + // all the other data like UVs. + const UInt32 channelsToCopy = m_ChannelsInVBO & (~skinStreamsLayout.channelMasks[0]); + CopyVertexDataChannels (skin.vertexCount, channelsToCopy, skinVertexData, mesh.GetVertexData()); + + mesh.GetIndexBuffer() = m_CachedMesh->GetIndexBuffer(); + mesh.GetSubMeshes() = m_CachedMesh->GetSubMeshes(); + mesh.SetVertexColorsSwizzled(m_CachedMesh->GetVertexColorsSwizzled()); + + mesh.SetChannelsDirty(true, true); + + // Calculate bounding volume + Matrix4x4f rootPose = GetActualRootBone().GetWorldToLocalMatrixNoScale (); + MinMaxAABB accurateMinMax; + Matrix4x4f* poses; + int poseCount = GetBindposeCount(); + ALLOC_TEMP(poses, Matrix4x4f, poseCount); + if (CalculateAnimatedPosesWithRoot(rootPose, poses, poseCount) && CalculateBoneBasedBounds(poses, poseCount, accurateMinMax)) + mesh.SetLocalAABB(accurateMinMax); + } + + m_RootBone = oldRootBone; +} + +////@TODO: Write integration test for this! +#if UNITY_EDITOR || SUPPORT_REPRODUCE_LOG + +void SkinnedMeshRenderer::HandleOldSkinnedFilter () +{ + /// Backwards compatibility for old school skinned mesh filters + /// - Remove mesh renderer + /// - copy over materials from renderer + Renderer* meshRenderer = NULL; + if (GetGameObjectPtr()) + meshRenderer = QueryComponent(MeshRenderer); + + if (meshRenderer) + { + #if UNITY_EDITOR + GetCleanupManager ().MarkForDeletion (meshRenderer, "Obsolete"); + #endif + SetMaterialArray(meshRenderer->GetMaterialArray(), meshRenderer->GetSubsetIndices()); + } +} + +#endif + + +#if ENABLE_PROFILER +int SkinnedMeshRenderer::GetVisibleSkinnedMeshRendererCount () +{ + return gActiveSkinnedMeshes.size_slow(); +} +#endif + +size_t SkinnedMeshRenderer::GetValidBlendShapeWeightCount () const +{ + size_t size = std::min<UInt32>(m_CachedBlendShapeCount, m_BlendShapeWeights.size()); + + for (int i=size-1;i >= 0;i--) + { + if (HasValidWeight(m_BlendShapeWeights[i])) + return i + 1; + } + return 0; +} + +float SkinnedMeshRenderer::GetBlendShapeWeight(UInt32 index) const +{ + size_t size = std::min<UInt32>(m_CachedBlendShapeCount, m_BlendShapeWeights.size()); + if (index >= size) + return 0.0F; + else + return m_BlendShapeWeights[index]; +} + +void SkinnedMeshRenderer::SetBlendShapeWeight(UInt32 index, float weight) +{ + if (index >= m_CachedBlendShapeCount) + { + ErrorStringMsg("Array index (%d) is out of bounds (size=%d)", (int)index, (int)m_BlendShapeWeights.size()); + return; + } + + // The m_BlendShapeWeights array is stored seperately from the mesh. + // Thus it can go out of sync. + // It is bad to fix dependent serialized data in Awake (eg. SkinnedMeshRenderer in scenes could change based on changes in an asset) + // Thus we resize to the correct size when the user sets a blendshape weight. + // We can never assume that m_BlendShapeWeights.size() already matches the amount of blendshapes in the used mesh when this function is called. + if (index >= m_BlendShapeWeights.size()) + m_BlendShapeWeights.resize_initialized(m_CachedBlendShapeCount, 0.0F); + + m_BlendShapeWeights[index] = weight; + SetDirty(); +} + +/* + * Make sure m_BlendShapeWeights is not accessed directly anywhere.. It is not the size of the number of blendshapes.. + * Wrong: bool ShouldRecalculateBoundingVolumeEveryFrame () { return m_UpdateWhenOffscreen || m_RootBone.GetInstanceID() != 0 || !m_BlendShapeWeights.empty (); } + * Wrong: BlendShape bounding volume calculation needs serious review + * Extract & Store blendshape default values in skinned mesh renderer from fbx file + * Review code for multithreaded rendering integration when changing blendshape weights. Probably should also hook into mesh changed for recalculating blendshape count... + * Fix animation window editing being abysmal slow +*/ +void SkinnedMeshRenderer::UnloadVBOFromGfxDevice() +{ + if (m_VBO) + { + GetGfxDevice().DeleteVBO (m_VBO); + m_VBO = NULL; + } + if (m_MemExportInfo) + { + GetGfxDevice().DeleteGPUSkinningInfo(m_MemExportInfo); + m_MemExportInfo = NULL; + } + + m_SourceMeshDirty = true; +} + +void SkinnedMeshRenderer::ReloadVBOToGfxDevice() +{ + if (!m_MemExportInfo) + m_MemExportInfo = CreateGPUSkinningIfAvailable(); +} + +Unity::Component* SkinnedMeshRenderer::GetAnimator() +{ + if (m_CachedAnimator == NULL) + CreateCachedAnimatorBinding (); + + return m_CachedAnimator; +} + +void SkinnedMeshRenderer::CreateCachedAnimatorBinding() +{ + ClearCachedAnimatorBinding(); + + if (!m_CachedMesh) + return; + + const dynamic_array<BindingHash>& bonePathHashes = m_CachedMesh->GetBonePathHashes(); + if (bonePathHashes.size() != GetBindposeCount()) + { + ErrorStringObject("Bones do not match bindpose.", m_CachedMesh); + return; + } + + m_CachedAnimator = FindAncestorComponentExactTypeImpl(GetGameObject(), ClassID(Animator)); + if (m_CachedAnimator == NULL || GetAnimationInterface() == NULL) + return; + + m_SkeletonIndices.resize_uninitialized(bonePathHashes.size()); + if (!GetAnimationInterface()->PathHashesToIndices(*m_CachedAnimator, bonePathHashes.begin(), bonePathHashes.size(), m_SkeletonIndices.begin())) + m_SkeletonIndices.clear(); + + Assert(!m_CachedAnimator->HasEvent(AnimatorModifiedCallback, this)); + m_CachedAnimator->AddEvent(AnimatorModifiedCallback, this); +} + +void SkinnedMeshRenderer::ClearCachedAnimatorBinding() +{ + if (m_CachedAnimator == NULL) + return; + + m_CachedAnimator->RemoveEvent(AnimatorModifiedCallback, this); + m_SkeletonIndices.clear(); + m_CachedAnimator = NULL; +} + +void SkinnedMeshRenderer::AnimatorModifiedCallback(void* userData, void* sender, int eventID) +{ + SkinnedMeshRenderer& skinnedMeshRenderer = *reinterpret_cast<SkinnedMeshRenderer*>(userData); + + if (eventID == kAnimatorClearEvent) + skinnedMeshRenderer.ClearCachedAnimatorBinding(); +} + diff --git a/Runtime/Filters/Deformation/SkinnedMeshFilter.h b/Runtime/Filters/Deformation/SkinnedMeshFilter.h new file mode 100644 index 0000000..d20d304 --- /dev/null +++ b/Runtime/Filters/Deformation/SkinnedMeshFilter.h @@ -0,0 +1,213 @@ +#ifndef SKINNEDMESHFILTER_H +#define SKINNEDMESHFILTER_H + +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Filters/Mesh/Mesh.h" +#include "Runtime/Geometry/AABB.h" +#include "Configuration/UnityConfigure.h" +#include "Runtime/GfxDevice/GfxDeviceTypes.h" +#include "Runtime/Utilities/LinkedList.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/GfxDevice/GPUSkinningInfo.h" +#include "Runtime/Modules/ExportModules.h" +#include "Runtime/BaseClasses/NamedObject.h" + +class Mesh; +class Animation; +class VBO; +namespace Unity { class SkinnedCloth; } +struct SkinMeshInfo; +struct CalculateSkinMatricesTask; + +class EXPORT_COREMODULE SkinnedMeshRenderer : public Renderer +{ + PPtr<Mesh> m_Mesh; + Mesh* m_CachedMesh; + + // Bones for non-optimized mode + dynamic_array<PPtr<Transform> > m_Bones; + PPtr<Transform> m_RootBone; + // Bones for optimized mode + dynamic_array<UInt16> m_SkeletonIndices; + + Unity::Component* m_CachedAnimator; + + UInt32 m_CachedBlendShapeCount; + dynamic_array<float> m_BlendShapeWeights; + + AABB m_AABB; + int m_Quality; ///< enum { Auto = 0, 1 Bone = 1, 2 Bones = 2, 4 Bones = 4 } Number of bones to use per vertex during skinning. + bool m_UpdateWhenOffscreen;///< Unity will calculate an accurate bounding volume representation every frame. + bool m_DirtyAABB; + bool m_Visible; + + VBO* m_VBO; + dynamic_array<UInt8> m_SkinnedVertices; + UInt32 m_ChannelsInVBO; + bool m_SourceMeshDirty; + bool m_UpdateBeforeRendering; + SkinnedCloth* m_Cloth; + + GPUSkinningInfo* m_MemExportInfo; + + ListNode<Object> m_MeshNode; + +#if UNITY_EDITOR + float m_CachedSurfaceArea; +#endif + +#if UNITY_EDITOR || SUPPORT_REPRODUCE_LOG + PPtr<Animation> m_DeprecatedDisableAnimationWhenOffscreen; +#endif + + ListNode<SkinnedMeshRenderer> m_SkinNode; + void DirtyAndClearCache (); + +public: + + int GetBonesPerVertexCount (); + + REGISTER_DERIVED_CLASS (SkinnedMeshRenderer, Renderer) + DECLARE_OBJECT_SERIALIZE (SkinnedMeshRenderer) + + SkinnedMeshRenderer (MemLabelId label, ObjectCreationMode mode); + // ~SkinnedMeshRenderer (); declared-by-macro + + virtual void Reset (); + + static void InitializeClass (); + static void CleanupClass (); + virtual void AwakeFromLoad(AwakeFromLoadMode mode); + virtual void Deactivate (DeactivateOperation operation); + + void Setup (Mesh* lodmesh, const dynamic_array<PPtr<Transform> >& state); + + enum SkinningFlags + { + SF_None = 0, + SF_NoUpdateVBO = 1 << 0, + SF_ReadbackBuffer = 1 << 1, + SF_ClothPlaying = 1 << 2, + SF_AllowMemExport = 1 << 3, + }; + + enum UpdateType + { + kUpdateCloth, + kUpdateNonCloth + }; + + bool PrepareSkinCommon( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask=NULL ); + bool PrepareSkinPS3( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask=NULL ); + bool PrepareSkinGPU( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask=NULL ); + bool PrepareSkin( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask=NULL ); + + bool SkinMesh( SkinMeshInfo& skin, bool lastMemExportThisFrame, UInt32 cpuFence, int flags ); + bool SkinMeshImmediate( UInt32 requiredChannels ); + + bool CalculateAnimatedPoses (Matrix4x4f* poses, size_t size); + +#if UNITY_EDITOR + float GetCachedSurfaceArea (); + bool GetSkinnedVerticesAndNormals (dynamic_array<Vector3f>* vertices, dynamic_array<Vector3f>* normals); + bool CalculateVertexBasedBounds (const Matrix4x4f* poseMatrices, MinMaxAABB& output); +#endif + + void SetUpdateWhenOffscreen (bool onlyIfVisible); + bool GetUpdateWhenOffscreen () { return m_UpdateWhenOffscreen; } + + void SetDisableAnimationWhenOffscreen (Animation* animation); + Animation* GetDisableAnimationWhenOffscreen (); + + const dynamic_array<PPtr<Transform> >& GetBones () const { return m_Bones; } + void SetBones (const dynamic_array<PPtr<Transform> >& bones); + + bool IsOptimized () { return m_Bones.size()==0 && GetBindposeCount()>0; } + // Before invoking this function, please make sure the SkinnedMeshRenderer is in optimized mode. + const dynamic_array<UInt16>& GetSkeletonIndices(); + + int GetBindposeCount () const; + int GetBoneCount () const { return m_Bones.size(); } + + void SetQuality (int quality); + int GetQuality () { return m_Quality; } + + void SetMesh (Mesh* mesh); + Mesh* GetMesh (); + + void SetCloth (SkinnedCloth *value) { m_Cloth = value; } + void SetRootBone (Transform* rootBone) { m_RootBone = rootBone; } + Transform* GetRootBone () { return m_RootBone; } + + Transform& GetActualRootBone (); + + static void UpdateAllSkinnedMeshes(UpdateType updateType, dynamic_array<SkinnedMeshRenderer*>* outMeshes = NULL); + static void UploadSkinnedClothes(const dynamic_array<SkinnedMeshRenderer*>& skinnedMeshes); + void ReadSkinningDataForCloth(const SkinMeshInfo& skin); +#if UNITY_EDITOR + void UpdateClothDataForEditing(const SkinMeshInfo& skin); +#endif + + /// Handlers so we only skin when somebody sees us. + virtual void BecameVisible (); + virtual void BecameInvisible (); + + #if UNITY_EDITOR || SUPPORT_REPRODUCE_LOG + void HandleOldSkinnedFilter (); + #endif + + virtual void UpdateTransformInfo(); + + void SetLocalAABB(const AABB& bounds); + void GetSkinnedMeshLocalAABB(AABB& bounds); + + void DidDeleteMesh (); + void DidModifyMesh (); + void UpdateCachedMesh (); + + void BakeMesh (Mesh& mesh); + + virtual void Render (int materialIndex, const ChannelAssigns& channels); + + float GetBlendShapeWeight(UInt32 index) const; + void SetBlendShapeWeight(UInt32 index, float weight); + +#if ENABLE_PROFILER + static int GetVisibleSkinnedMeshRendererCount (); +#endif + + bool DoesQualifyForMemExport() const; + + bool CalculateRootLocalSpaceBounds (MinMaxAABB& minMaxAAbb); + + void UnloadVBOFromGfxDevice(); + void ReloadVBOToGfxDevice(); + +protected: + + size_t GetValidBlendShapeWeightCount () const; + + bool ShouldRecalculateBoundingVolumeEveryFrame () { return m_UpdateWhenOffscreen || m_RootBone.GetInstanceID() != 0; } + + + // Puts the renderer into the queue of all visible skinned meshes depending on if it is currently visible. + void UpdateVisibleSkinnedMeshQueue (bool active); + + bool CalculateAnimatedPosesWithRoot (const Matrix4x4f& rootMatrix, Matrix4x4f* poses, size_t size); + // Main thread only. + bool CalculateSkinningMatrices (const Matrix4x4f& rootPose, Matrix4x4f* poses, size_t size); + + bool PrepareVBO (bool hasSkin, bool hasBlendshape, bool doMemExport, int flags); + + virtual void UpdateRenderer(); + + Unity::Component* GetAnimator (); + void CreateCachedAnimatorBinding (); + void ClearCachedAnimatorBinding (); + bool CalculateBoneBasedBounds (const Matrix4x4f* animatedPoses, size_t size, MinMaxAABB& output); + + static void AnimatorModifiedCallback(void* userData, void* sender, int eventID); +}; + +#endif diff --git a/Runtime/Filters/Mesh/CompressedMesh.cpp b/Runtime/Filters/Mesh/CompressedMesh.cpp new file mode 100644 index 0000000..02cc74c --- /dev/null +++ b/Runtime/Filters/Mesh/CompressedMesh.cpp @@ -0,0 +1,755 @@ +#include "UnityPrefix.h" +#include "CompressedMesh.h" +#include "LodMesh.h" +#include "Runtime/Animation/AnimationCurveUtility.h" + + +#define sqr(x) ((x)*(x)) + +void PackedFloatVector::PackFloats(float *data, int itemCountInChunk, int chunkStride, int numChunks, int bitSize, bool adjustBitSize) +{ + float maxf = -std::numeric_limits<float>::infinity(); + float minf = std::numeric_limits<float>::infinity(); + float* end = Stride (data, numChunks * chunkStride); + for(float* it = data; it != end; it = Stride (it, chunkStride)) + { + for (int i=0; i<itemCountInChunk; ++i) + { + if(maxf < it[i]) + maxf = it[i]; + if(minf > it[i]) + minf = it[i]; + } + } + + m_Range = maxf-minf; + + if(adjustBitSize) + bitSize += int(ceilf(Log2(m_Range))); + if(bitSize > 32) + bitSize = 32; + + m_Start = minf; + m_NumItems = numChunks * itemCountInChunk; + m_BitSize = bitSize; + m_Data.resize((m_NumItems * bitSize + 7)/8, 0); + + + float scale = 1.0/m_Range; + + int indexPos = 0; + int bitPos = 0; + + for(float* it = data; it != end; it = Stride (it, chunkStride)) + { + for(int i=0; i<itemCountInChunk; ++i) + { + float scaled = (it[i] - m_Start) * scale; + if(scaled < 0) scaled = 0; + if(scaled > 1) scaled = 1; + + UInt32 x = UInt32(scaled * ((1 << (m_BitSize)) - 1)); + + int bits = 0; + while(bits < m_BitSize) + { + m_Data[indexPos] |= (x >> bits) << bitPos; + int num = std::min( m_BitSize-bits, 8-bitPos); + bitPos += num; + bits += num; + if(bitPos == 8) + { + indexPos++; + bitPos = 0; + } + } + } + } +} + +void PackedFloatVector::UnpackFloats(float *data, int itemCountInChunk, int chunkStride, int start, int numChunks) +{ + int bitPos = m_BitSize*start; + int indexPos = bitPos/8; + bitPos %= 8; + + float scale = 1.0/m_Range; + if (numChunks == -1) + numChunks = m_NumItems / itemCountInChunk; + + for(float* end = Stride (data, chunkStride * numChunks); data != end; data = Stride (data, chunkStride)) + { + for (int i=0; i<itemCountInChunk; ++i) + { + UInt32 x = 0; + + int bits = 0; + while(bits < m_BitSize) + { + x |= (m_Data[indexPos] >> bitPos) << bits; + int num = std::min( m_BitSize-bits, 8-bitPos); + bitPos += num; + bits += num; + if(bitPos == 8) + { + indexPos++; + bitPos = 0; + } + } + x &= (1 << m_BitSize) - 1; + data[i] = (x / (scale * ((1 << (m_BitSize)) - 1))) + m_Start; + } + } +} + +template <class IntSize> void PackedIntVector::PackInts(IntSize *data, int numItems) +{ + // make sure that the intsize is an unsigned type + Assert( (IntSize)0 < (IntSize)-1 ); + + UInt32 maxi = 0; + for(int i=0; i<numItems; i++) + if(maxi < data[i]) + maxi = data[i]; + + m_NumItems = numItems; + //Prevent overflow + m_BitSize = UInt8(maxi == 0xFFFFFFFF ? 32 : ceilf(Log2(maxi+1))); + m_Data.resize((numItems * m_BitSize + 7)/8, 0); + + + int indexPos = 0; + int bitPos = 0; + for(int i=0; i<numItems; i++) + { + int bits = 0; + while(bits < m_BitSize) + { + m_Data[indexPos] |= (data[i] >> bits) << bitPos; + int num = std::min( m_BitSize-bits, 8-bitPos); + bitPos += num; + bits += num; + if(bitPos == 8) + { + indexPos++; + bitPos = 0; + } + } + } +} + +template <class IntSize> void PackedIntVector::UnpackInts(IntSize *data) +{ + int indexPos = 0; + int bitPos = 0; + for(int i=0; i<m_NumItems; i++) + { + int bits = 0; + data[i] = 0; + while(bits < m_BitSize) + { + data[i] |= (m_Data[indexPos] >> bitPos) << bits; + int num = std::min( m_BitSize-bits, 8-bitPos); + bitPos += num; + bits += num; + if(bitPos == 8) + { + indexPos++; + bitPos = 0; + } + } + data[i] &= (1ULL << m_BitSize) - 1; + } +} + + +void PackedQuatVector::PackQuats(Quaternionf *data, int numItems) +{ + m_NumItems = numItems; + m_Data.resize(numItems * (32/8), 0); + + int indexPos = 0; + int bitPos = 0; + + for(int i=0; i<numItems; i++) + { + Quaternionf &q = data[i]; + UInt8 flags = q.x<0? 4:0; + + float max=fabs(q.x); + if(fabs(q.y) > max) + { + max = fabs(q.y); + flags = 1; + if(q.y<0) + flags |= 4; + } + if(fabs(q.z) > max) + { + max = fabs(q.z); + flags = 2; + if(q.z<0) + flags |= 4; + } + if(fabs(q.w) > max) + { + max = fabs(q.w); + flags = 3; + if(q.w<0) + flags |= 4; + } + int bits = 0; + while(bits < 3) + { + m_Data[indexPos] |= (flags >> bits) << bitPos; + int num = std::min( 3-bits, 8-bitPos); + bitPos += num; + bits += num; + if(bitPos == 8) + { + indexPos++; + bitPos = 0; + } + } + for(int j=0;j<4;j++) + { + if((flags&3) != j) + { + int bitSize = (((flags&3)+1)%4 == j)?9:10; + float scaled = (q[j] + 1) * 0.5; + if(scaled < 0) scaled = 0; + if(scaled > 1) scaled = 1; + + UInt32 x = UInt32(scaled * ((1 << bitSize) - 1)); + + bits = 0; + while(bits < bitSize) + { + m_Data[indexPos] |= (x >> bits) << bitPos; + int num = std::min( bitSize-bits, 8-bitPos); + bitPos += num; + bits += num; + if(bitPos == 8) + { + indexPos++; + bitPos = 0; + } + } + } + } + } +} + +void PackedQuatVector::UnpackQuats(Quaternionf *data) +{ + int indexPos = 0; + int bitPos = 0; + + for(int i=0; i<m_NumItems; i++) + { + UInt32 flags = 0; + + int bits = 0; + while(bits < 3) + { + flags |= (m_Data[indexPos] >> bitPos) << bits; + int num = std::min( 3-bits, 8-bitPos); + bitPos += num; + bits += num; + if(bitPos == 8) + { + indexPos++; + bitPos = 0; + } + } + flags &= 7; + + + Quaternionf &q = data[i]; + float sum = 0; + for(int j=0;j<4;j++) + { + if((flags&3) != j) + { + int bitSize = (((flags&3)+1)%4 == j)?9:10; + UInt32 x = 0; + + bits = 0; + while(bits < bitSize) + { + x |= (m_Data[indexPos] >> bitPos) << bits; + int num = std::min( bitSize-bits, 8-bitPos); + bitPos += num; + bits += num; + if(bitPos == 8) + { + indexPos++; + bitPos = 0; + } + } + x &= (1 << bitSize) - 1; + q[j] = (x / (0.5 * ((1 << (bitSize)) - 1))) - 1; + sum += sqr(q[j]); + } + } + + int lastComponent = flags&3; + q[lastComponent] = FastSqrt(1 - sum); + if(flags & 4) + q[lastComponent] = -q[lastComponent]; + } +} + +void CompressedMesh::Compress(Mesh &src, int compression) +{ + int numVertices = src.GetVertexCount(); + + int vertexBits = 0; + switch(compression) + { + case kMeshCompressionHigh: vertexBits = 10; break; + case kMeshCompressionMed: vertexBits = 16; break; + case kMeshCompressionLow: vertexBits = 20; break; + } + m_Vertices.PackFloats((float*)src.GetChannelPointer(kShaderChannelVertex), 3, src.GetStride (kShaderChannelVertex), numVertices, vertexBits, false); + + //Possible optimization: use Edgebreaker algorithm + //for 1.8 bits per triangle connectivity information + //http://www.gvu.gatech.edu/~jarek/edgebreaker/eb/ + + int numIndices = src.m_IndexBuffer.size(); + numIndices/=2; + + m_Triangles.PackInts<UInt16>((UInt16*)&src.m_IndexBuffer[0],numIndices); + + if(src.IsAvailable(kShaderChannelTexCoord0)) + { + int uvBits = 0; + switch(compression) + { + case kMeshCompressionHigh: uvBits = 8; break; + case kMeshCompressionMed: uvBits = 10; break; + case kMeshCompressionLow: uvBits = 16; break; + } + if(src.IsAvailable(kShaderChannelTexCoord1)) + { + Vector2f *uv12 = new Vector2f[numVertices*2]; + src.ExtractUvArray(0, uv12); + src.ExtractUvArray(1, uv12 + numVertices); + m_UV.PackFloats(&uv12->x, 2, sizeof(Vector2f), numVertices*2, uvBits, true); + delete[] uv12; + } + else + m_UV.PackFloats((float*)src.GetChannelPointer (kShaderChannelTexCoord0), 2, src.GetStride (kShaderChannelTexCoord0), numVertices, uvBits, true); + } + else if(src.IsAvailable(kShaderChannelTexCoord1)) + ErrorString( "Mesh compression doesn't work on Meshes wich only have a UV1 channel but no UV0 channel. UVs will be dropped." ); + + if(src.IsAvailable (kShaderChannelNormal)) + { + int normalBits = 0; + switch(compression) + { + case kMeshCompressionHigh: normalBits = 6; break; + case kMeshCompressionMed: normalBits = 8; break; + case kMeshCompressionLow: normalBits = 8; break; + } + + float *normals = new float[numVertices*2]; + UInt32 *signs = new UInt32[numVertices]; + StrideIterator<Vector3f> n = src.GetNormalBegin (); + for(int i=0;i<numVertices; ++i, ++n) + { + normals[i*2+0] = n->x; + normals[i*2+1] = n->y; + signs[i] = n->z>0?1:0; + } + m_Normals.PackFloats(normals, 2, sizeof (float) * 2, numVertices, normalBits, false); + m_NormalSigns.PackInts(signs, numVertices); + delete[] normals; + delete[] signs; + } + + if(src.IsAvailable (kShaderChannelTangent)) + { + int normalBits = 0; + switch(compression) + { + case kMeshCompressionHigh: normalBits = 6; break; + case kMeshCompressionMed: normalBits = 8; break; + case kMeshCompressionLow: normalBits = 8; break; + } + + float *tangents = new float[numVertices*2]; + UInt32 *signs = new UInt32[numVertices*2]; + StrideIterator<Vector4f> t = src.GetTangentBegin (); + for(int i=0;i<numVertices; ++i, ++t) + { + tangents[i*2+0] = t->x; + tangents[i*2+1] = t->y; + signs[i*2+0] = t->z>0?1:0; + signs[i*2+1] = t->w>0?1:0; + } + m_Tangents.PackFloats(tangents, 2, sizeof (float) * 2, numVertices, normalBits, false); + m_TangentSigns.PackInts(signs, numVertices*2); + delete[] tangents; + delete[] signs; + } + + // TODO: do an actual compression + if(src.IsAvailable (kShaderChannelColor)) + { + dynamic_array<UInt32> tempColors (numVertices, kMemTempAlloc); + std::transform (src.GetColorBegin (), src.GetColorEnd (), tempColors.begin (), OpColorRGBA32ToUInt32()); + m_Colors.PackInts<UInt32> (tempColors.data (), tempColors.size ()); + } + + BoneInfluence* influence = src.GetBoneWeights(); + if(influence) + { + UInt32 *weights = new UInt32[numVertices*3]; + UInt32 *indices = new UInt32[numVertices*4]; + int weightPos = 0; + int boneIndexPos = 0; + for(int i=0;i<numVertices;i++) + { + int j; + int sum = 0; + + //As all four bone weights always add up to 1, we can always calculate the fourth one + // by subtracting the other three from 1. So we don't need to store it. + + //Furthermore, once the weights we stored add up to 1, we don't need to store further + //weights or indices, as these will necessarily be zero. This is often the case, as many + //vertices have only the first weight set to one, and all others to zero. + + //find last non-zero entry -- we don't need to store those after this. + int lastNonZero; + for(lastNonZero=3;lastNonZero>0&&influence[i].weight[lastNonZero]==0;lastNonZero--) + {} + + + for(j=0;j<3 && j<=lastNonZero && sum<31;j++) + { + weights[weightPos] = UInt32(influence[i].weight[j] * 31); + indices[boneIndexPos++] = influence[i].boneIndex[j]; + sum += weights[weightPos++]; + } + if(lastNonZero<3) + { + //we stored less then 3 weights, but they don't add up to one, due to quantization + //inprecision. + //Add the difference, so the math works out on decompression. + if(sum<31) + weights[weightPos-1] += 31-sum; + } + + //we stored three weights, but they don't add up to one. we don't need to store the fourth weight + //(as it can be calculated from the other three), but we need the bone index. + else if(sum<31) + indices[boneIndexPos++] = influence[i].boneIndex[j]; + } + + m_Weights.PackInts(weights, weightPos); + m_BoneIndices.PackInts(indices, boneIndexPos); + + delete[] weights; + delete[] indices; + } +} + +void CompressedMesh::Decompress(Mesh &src) +{ + int numIndices = m_Triangles.Count(); + src.m_IndexBuffer.resize(numIndices * 2); + m_Triangles.UnpackInts<UInt16>((UInt16*)&src.m_IndexBuffer[0]); + + int numVertices = m_Vertices.Count()/3; + unsigned decompressedFormat = 0; + if (m_Vertices.Count ()) decompressedFormat |= VERTEX_FORMAT1(Vertex); + if (m_Normals.Count()) decompressedFormat |= VERTEX_FORMAT1(Normal); + if (m_UV.Count()) decompressedFormat |= VERTEX_FORMAT1(TexCoord0); + if (m_UV.Count() == numVertices * 4) decompressedFormat |= VERTEX_FORMAT1(TexCoord1); + if (m_Tangents.Count()) decompressedFormat |= VERTEX_FORMAT1(Tangent); + if (m_Colors.Count()) decompressedFormat |= VERTEX_FORMAT1(Color); + + src.ResizeVertices(numVertices, decompressedFormat); + Assert (src.GetVertexCount () == numVertices); + + m_Vertices.UnpackFloats((float*)src.GetChannelPointer (kShaderChannelVertex), 3, src.GetStride (kShaderChannelVertex)); + + if(m_UV.Count()) + { + m_UV.UnpackFloats((float*)src.GetChannelPointer (kShaderChannelTexCoord0), 2, src.GetStride (kShaderChannelTexCoord0), 0, numVertices); + + if(m_UV.Count()==numVertices * 4) + { + m_UV.UnpackFloats((float*)src.GetChannelPointer (kShaderChannelTexCoord1), 2, src.GetStride (kShaderChannelTexCoord1), numVertices*2, numVertices); + } + } + + // TODO: This never gets written. Unity 3.4 and 3.5 never wrote this data. + // Most likely no version ever did. Remove code and bindpose serialization. + if(m_BindPoses.Count()) + { + src.m_Bindpose.resize_initialized(m_BindPoses.Count()/16); + m_BindPoses.UnpackFloats(src.m_Bindpose[0].m_Data, 16, sizeof(float) * 16); + } + + if(m_Normals.Count()) + { + float *normalData = new float[m_Normals.Count()]; + UInt32 *signs = new UInt32[m_NormalSigns.Count()]; + + m_Normals.UnpackFloats(normalData, 2, sizeof(float) * 2); + m_NormalSigns.UnpackInts(signs); + + StrideIterator<Vector3f> n = src.GetNormalBegin (); + for(int i=0;i<m_Normals.Count()/2; ++i, ++n) + { + n->x = normalData[i*2+0]; + n->y = normalData[i*2+1]; + float zsqr = 1 - sqr(n->x) - sqr(n->y); + if(zsqr >= 0) + n->z = FastSqrt( zsqr ); + else + { + n->z = 0; + *n = Normalize(*n); + } + if(signs[i]==0) + n->z = -n->z; + } + + delete[] normalData; + delete[] signs; + } + + if(m_Tangents.Count()) + { + float *tangentData = new float[m_Tangents.Count()]; + UInt32 *signs = new UInt32[m_TangentSigns.Count()]; + + m_Tangents.UnpackFloats(tangentData, 2, sizeof(float) * 2); + m_TangentSigns.UnpackInts(signs); + + StrideIterator<Vector4f> t = src.GetTangentBegin (); + for(int i=0;i<m_Tangents.Count()/2; ++i, ++t) + { + t->x = tangentData[i*2+0]; + t->y = tangentData[i*2+1]; + float zsqr = 1-sqr(tangentData[i*2+0])-sqr(tangentData[i*2+1]); + if(zsqr >= 0.0f) + t->z = FastSqrt( zsqr ); + else + { + t->z = 0; + *(Vector3f*)(&*t) = Normalize(*(Vector3f*)(&*t)); + } + if(signs[i*2+0]==0) + t->z = -t->z; + + t->w = signs[i*2+1]?1.0:-1.0; + } + + delete[] tangentData; + delete[] signs; + } + + // TODO: do an actual compression + if (m_Colors.Count()) + { + dynamic_array<UInt32> tempColors (m_Colors.Count (), kMemTempAlloc); + m_Colors.UnpackInts<UInt32> (tempColors.data ()); + Assert (tempColors.size () == src.GetVertexCount ()); + strided_copy ((ColorRGBA32*)tempColors.begin (), (ColorRGBA32*)tempColors.end (), src.GetColorBegin ()); + } + + if(m_Weights.Count()) + { + UInt32 *weights = new UInt32[m_Weights.Count()]; + m_Weights.UnpackInts(weights); + UInt32 *boneIndices = new UInt32[m_BoneIndices.Count()]; + m_BoneIndices.UnpackInts(boneIndices); + src.m_Skin.resize_uninitialized(numVertices); + int bonePos = 0; + int boneIndexPos = 0; + int j=0; + int sum = 0; + + for(int i=0;i<m_Weights.Count();i++) + { + //read bone index and weight. + src.m_Skin[bonePos].weight[j] = weights[i]/31.0; + src.m_Skin[bonePos].boneIndex[j] = boneIndices[boneIndexPos++]; + j++; + sum += weights[i]; + + //the weights add up to one. fill the rest for this vertex with zero, and continue with next one. + if(sum >= 31) + { + for(;j<4;j++) + { + src.m_Skin[bonePos].weight[j] = 0; + src.m_Skin[bonePos].boneIndex[j] = 0; + } + bonePos++; + j = 0; + sum = 0; + } + //we read three weights, but they don't add up to one. calculate the fourth one, and read + //missing bone index. continue with next vertex. + else if(j==3) + { + src.m_Skin[bonePos].weight[j] = (31-sum)/31.0; + src.m_Skin[bonePos].boneIndex[j] = boneIndices[boneIndexPos++]; + bonePos++; + j = 0; + sum = 0; + } + } + + delete[] weights; + delete[] boneIndices; + } +} + +template <class T> void CompressedAnimationCurve::CompressTimeKeys(AnimationCurveTpl<T> &src) +{ + int numKeys = src.GetKeyCount(); + + float minTime=0; + for(int i=0;i<numKeys;i++) + { + float t = src.GetKey(i).time; + if(t < minTime) + { + //negative time key. offset all keys by this, so math doesn't break - but it's still wrong. + minTime = t; + } + } + + + UInt32 *times = new UInt32[numKeys]; + UInt32 t=0; + for(int i=0;i<numKeys;i++) + { + times[i] = UInt32((src.GetKey(i).time - minTime) * 100); + times[i] -= t; + t += times[i]; + } + + m_Times.PackInts(times, numKeys); + + delete[] times; +} + +template <class T> void CompressedAnimationCurve::DecompressTimeKeys(AnimationCurveTpl<T> &src) +{ + int numKeys = m_Times.Count(); + UInt32 *times = new UInt32[numKeys]; + m_Times.UnpackInts(times); + + UInt32 t=0; + + src.ResizeUninitialized(numKeys); + + for(int i=0;i<numKeys;i++) + { + t+=times[i]; + src.GetKey(i).time = t*0.01; + } + delete[] times; +} + +void CompressedAnimationCurve::CompressQuatCurve(AnimationClip::QuaternionCurve &src) +{ + CompressTimeKeys(src.curve); + int numKeys = src.curve.GetKeyCount(); + + Quaternionf *qkeys = new Quaternionf[numKeys]; + for(int i=0;i<numKeys;i++) + qkeys[i] = src.curve.GetKey(i).value; + m_Values.PackQuats(qkeys, numKeys); + + delete[] qkeys; + + bool same = true; + + for(int i=0;i<numKeys && same;i++) + { + Quaternionf &q1 = src.curve.GetKey(i).inSlope; + Quaternionf &q2 = src.curve.GetKey(i).inSlope; + if(q1.x!=q2.x) + same = false; + if(q1.y!=q2.y) + same = false; + if(q1.z!=q2.z) + same = false; + if(q1.w!=q2.w) + same = false; + } + + float *keys = new float[numKeys*8]; + for(int i=0;i<numKeys;i++) + { + Quaternionf q = src.curve.GetKey(i).inSlope; + keys[i*4+0] = q.x; + keys[i*4+1] = q.y; + keys[i*4+2] = q.z; + keys[i*4+3] = q.w; + q = src.curve.GetKey(i).outSlope; + keys[(i+numKeys)*4+0] = q.x; + keys[(i+numKeys)*4+1] = q.y; + keys[(i+numKeys)*4+2] = q.z; + keys[(i+numKeys)*4+3] = q.w; + } + + //if in and out slopes are all the same, pack only the first of the two. + if(same) + m_Slopes.PackFloats(keys, 1, sizeof(float), numKeys * 4, 6, false); + else + m_Slopes.PackFloats(keys, 1, sizeof(float), numKeys * 8, 6, false); + + delete[] keys; + + m_PreInfinity = src.curve.GetPreInfinityInternal(); + m_PostInfinity = src.curve.GetPostInfinityInternal(); + m_Path = src.path; +} + +void CompressedAnimationCurve::DecompressQuatCurve(AnimationClip::QuaternionCurve &src) +{ + DecompressTimeKeys(src.curve); + int numKeys = m_Values.Count(); + + Quaternionf *qkeys = new Quaternionf[numKeys]; + m_Values.UnpackQuats(qkeys); + for(int i=0;i<numKeys;i++) + src.curve.GetKey(i).value = qkeys[i]; + delete[] qkeys; + + float *keys = new float[numKeys*8]; + m_Slopes.UnpackFloats(keys, 1, sizeof(float)); + + //are there seperate in and out slopes? + int offs = 0; + if(m_Slopes.Count() == numKeys*8) + offs = numKeys; + for(int i=0;i<numKeys;i++) + { + src.curve.GetKey(i).inSlope.x = keys[i*4+0]; + src.curve.GetKey(i).inSlope.y = keys[i*4+1]; + src.curve.GetKey(i).inSlope.z = keys[i*4+2]; + src.curve.GetKey(i).inSlope.w = keys[i*4+3]; + src.curve.GetKey(i).outSlope.x = keys[(i+offs)*4+0]; + src.curve.GetKey(i).outSlope.y = keys[(i+offs)*4+1]; + src.curve.GetKey(i).outSlope.z = keys[(i+offs)*4+2]; + src.curve.GetKey(i).outSlope.w = keys[(i+offs)*4+3]; + } + delete[] keys; + + src.curve.SetPreInfinityInternal( m_PreInfinity ); + src.curve.SetPostInfinityInternal( m_PostInfinity ); + src.path = m_Path; +} diff --git a/Runtime/Filters/Mesh/CompressedMesh.h b/Runtime/Filters/Mesh/CompressedMesh.h new file mode 100644 index 0000000..cf2f01c --- /dev/null +++ b/Runtime/Filters/Mesh/CompressedMesh.h @@ -0,0 +1,175 @@ +#ifndef COMPRESSEDMESH_H +#define COMPRESSEDMESH_H + +#include "Runtime/Serialize/SerializeUtility.h" +#include "Runtime/Animation/AnimationClip.h" +class Mesh; +class AnimationClip; + +enum +{ + kMeshCompressionOff = 0, + kMeshCompressionLow = 1, + kMeshCompressionMed = 2, + kMeshCompressionHigh = 3, +}; + +typedef std::vector<UInt8> DataVector; + +class PackedFloatVector +{ +public: + DECLARE_SERIALIZE (PackedBitVector) + + PackedFloatVector() { m_NumItems = 0; m_Range = 0; m_Start = 0; m_BitSize = 0; } + + void PackFloats(float *data, int chunkSize, int chunkStride, int chunkCount, int bitSize, bool adjustBitSize); + void UnpackFloats(float *data, int chunkSize, int chunkStride, int start = 0, int count = -1); + int Count() {return m_NumItems;} + +private: + UInt32 m_NumItems; + float m_Range; + float m_Start; + UInt8 m_BitSize; + std::vector<UInt8> m_Data; +}; + +class PackedIntVector +{ +public: + DECLARE_SERIALIZE (PackedBitVector) + + PackedIntVector() { m_NumItems = 0; m_BitSize = 0; } + + template <class IntSize> void PackInts(IntSize *data, int numItems); + template <class IntSize> void UnpackInts(IntSize *data); + int Count() {return m_NumItems;} + +private: + UInt32 m_NumItems; + UInt8 m_BitSize; + std::vector<UInt8> m_Data; +}; + +class PackedQuatVector +{ +public: + DECLARE_SERIALIZE (PackedBitVector) + + PackedQuatVector() {m_NumItems = 0;} + + void PackQuats(Quaternionf *data, int numItems); + void UnpackQuats(Quaternionf *data); + int Count() {return m_NumItems;} + +private: + UInt32 m_NumItems; + std::vector<UInt8> m_Data; +}; + +class CompressedMesh +{ +public: + DECLARE_SERIALIZE (CompressedMesh) + + void Compress(Mesh &src, int quality); + void Decompress(Mesh &src); + +private: + PackedFloatVector m_Vertices; + PackedFloatVector m_UV; + + // TODO: This never gets written. Unity 3.4 and 3.5 never wrote this data. + // Most likely no version ever did. Remove code and bindpose serialization. + PackedFloatVector m_BindPoses; + + PackedFloatVector m_Normals; + PackedIntVector m_NormalSigns; + PackedFloatVector m_Tangents; + PackedIntVector m_TangentSigns; + PackedIntVector m_Weights; + PackedIntVector m_BoneIndices; + PackedIntVector m_Triangles; + PackedIntVector m_Colors; +}; + +template<class TransferFunc> +void PackedFloatVector::Transfer (TransferFunc& transfer) { + TRANSFER ( m_NumItems ); + TRANSFER( m_Range ); + TRANSFER( m_Start ); + TRANSFER( m_Data ); + TRANSFER( m_BitSize ); + transfer.Align(); +} + +template<class TransferFunc> +void PackedIntVector::Transfer (TransferFunc& transfer) { + TRANSFER( m_NumItems ); + TRANSFER( m_Data ); + TRANSFER( m_BitSize ); + transfer.Align(); +} + +template<class TransferFunc> +void PackedQuatVector::Transfer (TransferFunc& transfer) { + TRANSFER( m_NumItems ); + TRANSFER( m_Data ); + transfer.Align(); +} + +template<class TransferFunc> +void CompressedMesh::Transfer (TransferFunc& transfer) { + TRANSFER( m_Vertices ); + TRANSFER( m_UV ); + TRANSFER( m_BindPoses ); + TRANSFER( m_Normals ); + TRANSFER( m_Tangents ); + TRANSFER( m_Weights ); + TRANSFER( m_NormalSigns ); + TRANSFER( m_TangentSigns ); + TRANSFER( m_BoneIndices ); + TRANSFER( m_Triangles ); + TRANSFER( m_Colors ); +} + +class CompressedAnimationCurve +{ +public: + DECLARE_SERIALIZE (CompressedAnimationCurve) + + CompressedAnimationCurve() { m_PreInfinity = 0; m_PostInfinity = 0; } + + void CompressQuatCurve(AnimationClip::QuaternionCurve &src); + void DecompressQuatCurve(AnimationClip::QuaternionCurve &src); + +private: + + template <class T> void CompressTimeKeys(AnimationCurveTpl<T> &src); + template <class T> void DecompressTimeKeys(AnimationCurveTpl<T> &src); + + PackedIntVector m_Times; + PackedQuatVector m_Values; + PackedFloatVector m_Slopes; + + int m_PreInfinity; + int m_PostInfinity; + + UnityStr m_Path; +}; + +template<class TransferFunc> +void CompressedAnimationCurve::Transfer (TransferFunc& transfer) { + + TRANSFER( m_Path ); + + TRANSFER( m_Times ); + TRANSFER( m_Values ); + TRANSFER( m_Slopes ); + + TRANSFER( m_PreInfinity ); + TRANSFER( m_PostInfinity ); +} + +#endif diff --git a/Runtime/Filters/Mesh/LodMesh.cpp b/Runtime/Filters/Mesh/LodMesh.cpp new file mode 100644 index 0000000..fc5dca8 --- /dev/null +++ b/Runtime/Filters/Mesh/LodMesh.cpp @@ -0,0 +1,2344 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" +#include "LodMesh.h" +#include "Runtime/Utilities/vector_utility.h" +#include "Runtime/Utilities/Utility.h" +#include "Runtime/Math/FloatConversion.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Serialize/PersistentManager.h" +#include "Runtime/Graphics/TriStripper.h" +#include "MeshUtility.h" +#include "Runtime/Geometry/TangentSpaceCalculation.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Shaders/VBO.h" +#include "Runtime/Serialize/TransferUtility.h" +#include "Runtime/Serialize/SwapEndianArray.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/Camera/IntermediateRenderer.h" +#include "Runtime/Filters/Mesh/MeshRenderer.h" +#include "Runtime/Allocator/MemoryMacros.h" +#include "Runtime/Misc/Allocator.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Camera/Camera.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Threads/Thread.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Utilities/UniqueIDGenerator.h" +#if UNITY_XENON +#include "PlatformDependent/Xbox360/Source/GfxDevice/GfxXenonVBO.h" +#endif +#include "Runtime/GfxDevice/GfxDeviceConfigure.h" + +#if UNITY_FLASH +#include <limits.h> +#define FLT_MAX __FLT_MAX__ +#define FLT_MIN __FLT_MIN__ +#endif + +#if UNITY_EDITOR +# include "Editor/Src/BuildPipeline/PrepareMeshDataForBuildTarget.h" +# include "Runtime/Camera/RenderLoops/RenderLoopPrivate.h" +# include "Runtime/Misc/Player.h" +#endif + + +///* Checkbox in mesh importer that allows you have mesh access (Done) +///* Default for new importers is to have mesh access enabled (done) +///* Error Messages when acessing data although you shouldn't be allowed (--) +///* MeshColliders / SkinnedMeshes / non-uniform scale. Forces meshes to be non-readable. (Done) + + +///* MeshCollider with no-access allowed. Does it work / no errors +///* MeshCollider with no-access allowed, mesh is assigned from script. Does it give an error in editor & player +///* MeshCollider with no-access allowed, mesh is scaled at runtime does it give an error +///* MeshCollider with no-access allowed, mesh is scaled in scene. Does it work without errors. +///* Mesh data accessed from script, does it give an error. + + + +static char const* kMeshAPIErrorMessage = +"Mesh.%s is out of bounds. The supplied array needs to be the same size as the Mesh.vertices array."; + + +static UniqueIDGenerator s_MeshIDGenerator; + + +// The Mesh class contains one of these for every Material that is bound to it. +struct DeprecatedMeshData +{ + std::vector<Face> faces; // Indices for specific faces + std::vector <unsigned short> strips; // A list of triangle strips + int triangleCount; + DECLARE_SERIALIZE_NO_PPTR (MeshData) +}; + +template<class TransferFunc> +void DeprecatedMeshData::Transfer (TransferFunc& transfer) +{ + TRANSFER (faces); + TRANSFER (strips); + TRANSFER(triangleCount); +} + +struct DeprecatedLOD +{ + vector<DeprecatedMeshData> m_MeshData; + + DECLARE_SERIALIZE (LOD) +}; + +template<class TransferFunction> +void DeprecatedLOD::Transfer (TransferFunction& transfer) +{ + TRANSFER (m_MeshData); +} + +static void LoadDeprecatedMeshData (Mesh& mesh, vector<DeprecatedLOD> &lods) +{ + mesh.GetIndexBuffer().clear(); + mesh.GetSubMeshes().clear(); + + if (lods.empty()) + return; + + DeprecatedLOD& lod = lods.front(); + + mesh.SetSubMeshCount(lod.m_MeshData.size()); + for (int i=0;i<lod.m_MeshData.size();i++) + { + DeprecatedMeshData& oldMeshData = lod.m_MeshData[i]; + if (oldMeshData.faces.size()) + mesh.SetIndicesComplex (&oldMeshData.faces[0].v1, oldMeshData.faces.size()*3, i, kPrimitiveTriangles, Mesh::k16BitIndices); + else + { + UNITY_TEMP_VECTOR(UInt16) triangles; + Destripify(&oldMeshData.strips[0], oldMeshData.strips.size(), triangles); + mesh.SetIndicesComplex (&triangles[0], triangles.size(), i, kPrimitiveTriangles, Mesh::k16BitIndices); + } + } +} + + +using namespace std; + +Mesh::Mesh (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_ChannelsInVBO(0) +, m_VerticesDirty(true) +, m_IndicesDirty(true) +, m_IsDynamic(false) +, m_HideFromRuntimeStats(false) +, m_VertexColorsSwizzled(false) +, m_MeshUsageFlags(0) +, m_LocalAABB(Vector3f::zero, Vector3f::zero) +, m_VBO(NULL) +, m_InternalMeshID (0) +, m_Skin (label) +, m_CachedSkin2 (label) +, m_CachedSkin1 (label) +, m_CachedBonesAABB(label) +, m_Bindpose(label) +, m_BonePathHashes(label) +, m_RootBonePathHash(0) +{ + m_MaxBoneIndex = -1; + SubMesh sub; + m_SubMeshes.push_back(sub); + + m_MeshCompression = kMeshCompressionOff; + m_StreamCompression = kStreamCompressionDefault; + m_IsReadable = true; + m_KeepVertices = false; + m_KeepIndices = false; + +#if UNITY_EDITOR + m_MeshOptimized = false; +#endif + +#if ENABLE_MULTITHREADED_CODE + m_CurrentCPUFence = 0; + m_WaitOnCPUFence = false; +#endif + + m_InternalMeshID = 0; +} + +Mesh::~Mesh () +{ + MainThreadCleanup (); +} + +bool Mesh::MainThreadCleanup () +{ + WaitOnRenderThreadUse(); + NotifyObjectUsers( kDidDeleteMesh ); + m_IntermediateUsers.Notify( kImNotifyAssetDeleted ); + + m_CollisionMesh.Cleanup(); + + if (m_VBO) + { + GetGfxDevice().DeleteVBO(m_VBO); + m_VBO = NULL; + } + + if (m_InternalMeshID != 0) + { + s_MeshIDGenerator.RemoveID (m_InternalMeshID); + m_InternalMeshID = 0; + } + + return true; +} + +void Mesh::LoadDeprecatedTangentData (Mesh& mesh, DeprecatedTangentsArray &inTangents) +{ + int count = inTangents.size(); + unsigned needChannels = m_VertexData.GetChannelMask () | VERTEX_FORMAT2(Normal, Tangent); + if (count != GetVertexCount () || m_VertexData.GetChannelMask () != needChannels) + ResizeVertices (count, needChannels); + + Assert (GetVertexCount () == count); + + StrideIterator<Vector3f> normals = GetNormalBegin (); + StrideIterator<Vector4f> tangents = GetTangentBegin (); + + for(int i=0;i<count; ++i, ++normals, ++tangents) + { + *normals = inTangents[i].normal; + *tangents = Vector4f(inTangents[i].tangent.x,inTangents[i].tangent.y,inTangents[i].tangent.z,inTangents[i].handedness); + } +} + +void Mesh::SwizzleVertexColorsIfNeeded () +{ + // Early out if color are already in the right format + if (gGraphicsCaps.needsToSwizzleVertexColors == m_VertexColorsSwizzled) + return; + + // Due to runtime GfxDevice switching we might need to unswizzle vertex colors (case 562695) + if (m_VertexColorsSwizzled) + { + std::transform(GetColorBegin(), GetColorEnd(), GetColorBegin(), UnswizzleColorForPlatform); + m_VertexColorsSwizzled = false; + } + else + { + std::transform(GetColorBegin(), GetColorEnd(), GetColorBegin(), SwizzleColorForPlatform); + m_VertexColorsSwizzled = true; + } +} + +void Mesh::ExtractVertexArray (Vector3f* destination) const +{ + StrideIterator<Vector3f> v = GetVertexBegin (); + for (Vector3f* end = destination + GetVertexCount(); destination != end; ++v, ++destination) + *destination = *v; +} + +void Mesh::ExtractNormalArray (Vector3f* destination) const +{ + StrideIterator<Vector3f> n = GetNormalBegin (); + for (Vector3f* end = destination + GetVertexCount(); destination != end; ++n, ++destination) + *destination = *n; +} + +void Mesh::ExtractColorArray (ColorRGBA32* destination) const +{ + if (m_VertexColorsSwizzled) + std::transform(GetColorBegin(), GetColorEnd(), destination, UnswizzleColorForPlatform); + else + std::copy(GetColorBegin(), GetColorEnd(), destination); +} + +void Mesh::ExtractColorArrayConverting (ColorRGBAf* destination) const +{ + if (m_VertexColorsSwizzled) + std::transform(GetColorBegin(), GetColorEnd(), destination, UnswizzleColorForPlatform); + else + std::copy(GetColorBegin(), GetColorEnd(), destination); +} + +void Mesh::ExtractUvArray (int uvIndex, Vector2f* destination) const +{ + StrideIterator<Vector2f> uv = GetUvBegin (uvIndex); + for (Vector2f* end = destination + GetVertexCount(); destination != end; ++uv, ++destination) + *destination = *uv; +} + +void Mesh::ExtractTangentArray (Vector4f* destination) const +{ + StrideIterator<Vector4f> t = GetTangentBegin (); + for (Vector4f* end = destination + GetVertexCount(); destination != end; ++t, ++destination) + *destination = *t; +} + + +UInt32 Mesh::ResizeVertices (size_t count, UInt32 shaderChannels, const VertexStreamsLayout& streams, const VertexChannelsLayout& channels) +{ + Assert (count <= std::numeric_limits<UInt16>::max()); + + UInt32 prevChannels = m_VertexData.GetChannelMask(); + + if (m_VertexData.GetVertexCount() != count || + m_VertexData.GetChannelMask() != shaderChannels || + !m_VertexData.ConformsToStreamsLayout(streams) || + !m_VertexData.ConformsToChannelsLayout(channels)) + { + WaitOnRenderThreadUse(); + + SET_ALLOC_OWNER(this); + m_VertexData.Resize(count, shaderChannels, streams, channels); + + if (!m_Skin.empty ()) + m_Skin.resize_initialized (count, BoneInfluence()); + } + + return m_VertexData.GetChannelMask() & ~prevChannels; +} + + +UInt32 Mesh::FormatVertices (UInt32 shaderChannels) +{ + return ResizeVertices(GetVertexCount(), shaderChannels); +} + +void Mesh::InitChannelsToDefault (unsigned begin, unsigned count, unsigned shaderChannels) +{ + if (shaderChannels & VERTEX_FORMAT1(Vertex)) + std::fill (GetVertexBegin () + begin, GetVertexBegin () + begin + count, Vector3f (0,0,0)); + if (shaderChannels & VERTEX_FORMAT1(Normal)) + std::fill (GetNormalBegin () + begin, GetNormalBegin () + begin + count, Vector3f (0,0,0)); + if (shaderChannels & VERTEX_FORMAT1(Color)) + std::fill (GetColorBegin () + begin, GetColorBegin () + begin + count, ColorRGBA32 (0xffffffff)); + if (shaderChannels & VERTEX_FORMAT1(TexCoord0)) + std::fill (GetUvBegin (0) + begin, GetUvBegin (0) + begin + count, Vector2f (0,0)); + if (shaderChannels & VERTEX_FORMAT1(Tangent)) + std::fill (GetTangentBegin () + begin, GetTangentBegin () + begin + count, Vector4f (0,0,0,0)); + + if (shaderChannels & VERTEX_FORMAT1(TexCoord1)) + { + if( GetAvailableChannels () & VERTEX_FORMAT1(TexCoord0) ) + std::copy (GetUvBegin (0) + begin, GetUvBegin (0) + begin + count, GetUvBegin (1) + begin); + else + std::fill (GetUvBegin (1) + begin, GetUvBegin (1) + begin + count, Vector2f (0,0)); + } +} + +namespace +{ + bool IsStripValid(const Mesh::TemporaryIndexContainer& triangles, const Mesh::TemporaryIndexContainer& newStrip) + { + int invalidTriangleCount = 0; + for (int j = 0; j < triangles.size(); j += 3) + { + int i0 = triangles[j + 0]; + int i1 = triangles[j + 1]; + int i2 = triangles[j + 2]; + + bool found = false; + for (int k = 0; k < newStrip.size() - 2; ++k) + { + int s0 = newStrip[k + 0]; + int s1 = newStrip[k + 1]; + int s2 = newStrip[k + 2]; + + if (k&1) + std::swap(s1, s2); + + if ((s0 == i0 && s1 == i1 && s2 == i2) || + (s0 == i1 && s1 == i2 && s2 == i0) || + (s0 == i2 && s1 == i0 && s2 == i1)) + { + found = true; + break; + } + } + + if (!found) + ++invalidTriangleCount; + } + + AssertMsg(invalidTriangleCount == 0, "Mesh strip is missing %d triangles", invalidTriangleCount); + return invalidTriangleCount == 0; + } +} + +void Mesh::RecalculateBoundsInternal () +{ + MinMaxAABB minmax; + minmax.Init (); + for (StrideIterator<Vector3f> it = GetVertexBegin (), end = GetVertexEnd (); it != end; ++it) + minmax.Encapsulate (*it); + + // Apply all blendshape targets to bounding volumes + if (!m_Shapes.vertices.empty()) + { + StrideIterator<Vector3f> verts = GetVertexBegin (); + + for (int i=0;i<m_Shapes.vertices.size();i++) + { + Vector3f pos = verts[m_Shapes.vertices[i].index] + m_Shapes.vertices[i].vertex; + minmax.Encapsulate (pos); + } + } + + AABB aabb; + if (GetVertexCount ()) + aabb = minmax; + else + aabb = AABB (Vector3f::zero, Vector3f::zero); + + m_LocalAABB = aabb; + + for (int submesh = 0; submesh < m_SubMeshes.size(); ++submesh) + RecalculateSubmeshBoundsInternal (submesh); +} + +void Mesh::RecalculateSubmeshBoundsInternal (unsigned submesh) +{ + MinMaxAABB minmax; + minmax.Init (); + + const UInt16* indices = GetSubMeshBuffer16(submesh); + StrideIterator<Vector3f> vertices = GetVertexBegin (); + for (unsigned int i = 0; i < GetSubMeshFast(submesh).indexCount; i++) + minmax.Encapsulate (vertices[indices[i]]); + + AABB aabb; + if (GetSubMeshFast(submesh).indexCount > 0) + aabb = minmax; + else + aabb = AABB (Vector3f::zero, Vector3f::zero); + + GetSubMeshFast(submesh).localAABB = aabb; +} + + +void Mesh::RecalculateBounds () +{ + RecalculateBoundsInternal (); + + SetDirty(); + NotifyObjectUsers( kDidModifyBounds ); + m_IntermediateUsers.Notify( kImNotifyBoundsChanged ); +} + +void Mesh::RecalculateSubmeshBounds (unsigned submesh) +{ + RecalculateSubmeshBoundsInternal (submesh); + + SetDirty(); + NotifyObjectUsers( kDidModifyBounds ); + m_IntermediateUsers.Notify( kImNotifyBoundsChanged ); +} + + +void Mesh::Clear (bool keepVertexLayout) +{ + WaitOnRenderThreadUse(); + + m_SubMeshes.clear(); + SubMesh sub; + m_SubMeshes.push_back(sub); + + ClearBlendShapes (m_Shapes); + + m_IndexBuffer.clear(); +#if UNITY_EDITOR + m_MeshOptimized = false; +#endif + +#if UNITY_PS3 || UNITY_EDITOR + m_PartitionInfos.clear(); + m_Partitions.clear(); +#endif + + unsigned prevFormat = m_VertexData.GetChannelMask(); + + if (m_VertexData.GetVertexCount() > 0) + { + // keepVertexLayout added in Unity 3.5.3; keep previous behaviour + // for older content for safety. + if (keepVertexLayout && IS_CONTENT_NEWER_OR_SAME (kUnityVersion3_5_3_a1)) + { + ResizeVertices (0, prevFormat); + } + else + { + VertexData tempVD; + swap (tempVD, m_VertexData); + } + } + + if (!m_Skin.empty()) + { + m_Skin.clear(); + } + + m_VertexColorsSwizzled = false; + ClearSkinCache(); + + SetChannelsDirty( prevFormat, true ); +} + +IMPLEMENT_CLASS (Mesh) +IMPLEMENT_OBJECT_SERIALIZE (Mesh) + +template <typename Index> +static void GetVertexBufferRange(const Index* indices, int indexCount, UInt32& fromVertex, UInt32& toVertex) +{ + Index a = Index(INT_MAX); + Index b = 0; + const Index* indicesEnd = indices + indexCount; + for (const Index* index = indices; index < indicesEnd; ++index) + { + a = std::min(a, *index); + b = std::max(b, *index); + } + fromVertex = a; + toVertex = b; +} + +void Mesh::ByteSwapIndices () +{ + SwapEndianArray (&m_IndexBuffer[0], kVBOIndexSize, GetTotalndexCount()); +} + +template<class T> +bool ShouldSerializeForBigEndian (T& transfer) +{ + bool bigEndian = UNITY_BIG_ENDIAN; + if (transfer.ConvertEndianess()) + bigEndian = !bigEndian; + return bigEndian; +} + +void Mesh::DestripifyIndices () +{ + if (m_IndexBuffer.empty() || m_SubMeshes.empty()) + return; + + int submeshCount = m_SubMeshes.size(); + bool anyStripped = false; + for (size_t i = 0; i < submeshCount; ++i) + { + if (m_SubMeshes[i].topology == kPrimitiveTriangleStripDeprecated) + { + anyStripped = true; + break; + } + } + if(!anyStripped) + return; + + // destripify the stripped submeshes + typedef UNITY_TEMP_VECTOR(UInt16) TemporaryIndexContainer; + + std::vector<TemporaryIndexContainer> submeshIndices; + submeshIndices.resize(submeshCount); + for(int i=0;i<submeshCount;i++) + { + SubMesh& sm = m_SubMeshes[i]; + if (sm.topology == kPrimitiveTriangleStripDeprecated) + Destripify (GetSubMeshBuffer16(i), sm.indexCount, submeshIndices[i]); + else + { + submeshIndices[i].resize(sm.indexCount); + memcpy(&submeshIndices[i][0], GetSubMeshBuffer16(i), sm.indexCount << 1); + } + } + + SetSubMeshCount(0); + SetSubMeshCount(submeshCount); + + for(int i=0;i<submeshCount;i++) + SetIndices(&submeshIndices[i][0], submeshIndices[i].size(), i, kPrimitiveTriangles); +} + +bool Mesh::CanAccessFromScript() const +{ +#if UNITY_EDITOR + // Allow editor scripts access even if not allowed in runtime + if (!IsInsidePlayerLoop() && !IsInsideRenderLoop()) + return true; +#endif + return m_IsReadable; +} + + +template<class TransferFunction> +void Mesh::Transfer (TransferFunction& transfer) +{ + #if SUPPORT_SERIALIZED_TYPETREES + // See TransferWorkaround35SerializeFuckup below for comments. + // Remove when we can break backwards-compatiblity. + if (transfer.GetFlags() & kWorkaround35MeshSerializationFuckup) + { + TransferWorkaround35SerializeFuckup (transfer); + return; + } + #endif + + Super::Transfer (transfer); + transfer.SetVersion (8); + + #if UNITY_EDITOR + const UInt32 supportedChannels = transfer.IsWritingGameReleaseData() ? transfer.GetBuildUsage().meshSupportedChannels : 0; + const UInt32 meshUsageFlags = transfer.IsWritingGameReleaseData() ? transfer.GetBuildUsage().meshUsageFlags : 0; + PrepareMeshDataForBuildTarget prepareMesh(*this, transfer.GetBuildingTarget().platform, supportedChannels, meshUsageFlags); + #endif + + bool reswizzleColors = false; + if (m_VertexColorsSwizzled) + { + // Unswizzle colors before serializing + std::transform(GetColorBegin(), GetColorEnd(), GetColorBegin(), UnswizzleColorForPlatform); + m_VertexColorsSwizzled = false; + reswizzleColors = true; + } + + transfer.Transfer (m_SubMeshes, "m_SubMeshes", kHideInEditorMask); + transfer.Transfer (m_Shapes, "m_Shapes", kHideInEditorMask); + transfer.Transfer (m_Bindpose, "m_BindPose", kHideInEditorMask); + transfer.Transfer (m_BonePathHashes, "m_BoneNameHashes", kHideInEditorMask); + transfer.Transfer (m_RootBonePathHash, "m_RootBoneNameHash", kHideInEditorMask); + + transfer.Transfer (m_MeshCompression, "m_MeshCompression", kHideInEditorMask); + transfer.Transfer (m_StreamCompression, "m_StreamCompression", kHideInEditorMask); + transfer.Transfer (m_IsReadable, "m_IsReadable", kHideInEditorMask); + transfer.Transfer (m_KeepVertices, "m_KeepVertices", kHideInEditorMask); + transfer.Transfer (m_KeepIndices, "m_KeepIndices", kHideInEditorMask); + transfer.Align(); + + // Notice the two codepaths for serialization here. + // It is very important to keep both codepaths in sync, otherwise SafeBinaryRead serialization will crash. + // Look at kSerializeForPrefabSystem to disable compression when using Transfer to instantiate a Mesh. + // Changes to compression can break web content if we recompress at runtime. (case 546159) + bool doCompression = m_MeshCompression && !(transfer.GetFlags() & kSerializeForPrefabSystem); + if (!doCompression) + { + if (transfer.ConvertEndianess() && transfer.IsWriting ()) + ByteSwapIndices(); + + transfer.Transfer (m_IndexBuffer, "m_IndexBuffer", kHideInEditorMask); + + if (transfer.ConvertEndianess() && (transfer.IsWriting () || transfer.IsReading ())) + ByteSwapIndices(); + + transfer.Transfer (m_Skin, "m_Skin", kHideInEditorMask); + + if (transfer.IsVersionSmallerOrEqual (5)) + { + dynamic_array<Vector4f> tangents; + dynamic_array<Vector3f> vertices, normals; + dynamic_array<Vector2f> uvs, uvs1; + dynamic_array<ColorRGBA32> colors; + + + transfer.Transfer (vertices, "m_Vertices", kHideInEditorMask); + transfer.Transfer (uvs, "m_UV", kHideInEditorMask); + transfer.Transfer (uvs1, "m_UV1", kHideInEditorMask); + transfer.Transfer (tangents, "m_Tangents", kHideInEditorMask); + transfer.Transfer (normals, "m_Normals", kHideInEditorMask); + transfer.Transfer (colors, "m_Colors", kHideInEditorMask); + + unsigned format = 0; + if (!vertices.empty ()) format |= VERTEX_FORMAT1(Vertex); + if (!tangents.empty ()) format |= VERTEX_FORMAT1(Tangent); + if (!normals.empty ()) format |= VERTEX_FORMAT1(Normal); + if (!uvs.empty ()) format |= VERTEX_FORMAT1(TexCoord0); + if (!uvs1.empty ()) format |= VERTEX_FORMAT1(TexCoord1); + if (!colors.empty ()) format |= VERTEX_FORMAT1(Color); + + size_t vertexCount = vertices.size (); + if (GetVertexCount () != vertexCount || GetAvailableChannels () != format) + ResizeVertices (vertexCount, format); + + strided_copy (vertices.begin (), vertices.begin () + std::min (vertices.size (), vertexCount), GetVertexBegin ()); + strided_copy (normals.begin (), normals.begin () + std::min (normals.size (), vertexCount), GetNormalBegin ()); + strided_copy (uvs.begin (), uvs.begin () + std::min (uvs.size (), vertexCount), GetUvBegin (0)); + strided_copy (uvs1.begin (), uvs1.begin () + std::min (uvs1.size (), vertexCount), GetUvBegin (1)); + strided_copy (tangents.begin (), tangents.begin () + std::min (tangents.size (), vertexCount), GetTangentBegin ()); + strided_copy (colors.begin (), colors.begin () + std::min (colors.size (), vertexCount), GetColorBegin ()); + } + else + { + // version 6 introduces interleaved buffer + if (transfer.ConvertEndianess() && transfer.IsWriting ()) + m_VertexData.SwapEndianess (); + + transfer.Transfer (m_VertexData, "m_VertexData", kHideInEditorMask); + + if (transfer.ConvertEndianess() && (transfer.IsWriting () || transfer.IsReading ())) + m_VertexData.SwapEndianess (); + } + } + // Notice the two codepaths for serialization here. + // It is very important to keep both codepaths in sync, otherwise SafeBinaryRead serialization will crash. + else + { + BoneInfluenceContainer dummySkin; + VertexData dummyVertexData; + IndexContainer dummyIndexContainer; + + transfer.Transfer (dummyIndexContainer, "m_IndexBuffer", kHideInEditorMask); + transfer.Transfer (dummySkin, "m_Skin", kHideInEditorMask); + transfer.Transfer (dummyVertexData, "m_VertexData", kHideInEditorMask); + } + + { + // only keep the compressed mesh in memory while needed + CompressedMesh m_CompressedMesh; + transfer.Align(); + // Check both IsWriting() and IsReading() since both are true when reading with SafeBinaryRead + if (doCompression && transfer.IsWriting()) + m_CompressedMesh.Compress(*this, m_MeshCompression); + + transfer.Transfer (m_CompressedMesh, "m_CompressedMesh", kHideInEditorMask); + + if (doCompression && transfer.DidReadLastProperty ()) + m_CompressedMesh.Decompress(*this); + } + + #if !GFX_SUPPORTS_TRISTRIPS + if (transfer.IsReading()) + DestripifyIndices (); + #endif + + // Reswizzle colors after serializing + if (reswizzleColors) + { + std::transform(GetColorBegin(), GetColorEnd(), GetColorBegin(), SwizzleColorForPlatform); + m_VertexColorsSwizzled = true; + } + + transfer.Transfer (m_LocalAABB, "m_LocalAABB", kHideInEditorMask); + + #if UNITY_EDITOR + // When building player we precalcuate mesh usage based on who uses the different MeshColliders in different scenes. + if (transfer.IsWritingGameReleaseData()) + { + int buildMeshUsageFlags = transfer.GetBuildUsage().meshUsageFlags; + transfer.Transfer (buildMeshUsageFlags, "m_MeshUsageFlags", kHideInEditorMask); + } + else + transfer.Transfer (m_MeshUsageFlags, "m_MeshUsageFlags", kHideInEditorMask); + #else + transfer.Transfer (m_MeshUsageFlags, "m_MeshUsageFlags", kHideInEditorMask); + #endif + + m_CollisionMesh.Transfer(transfer, *this); + + if (transfer.IsOldVersion(1)) + { + vector<DeprecatedLOD> lod; + transfer.Transfer (lod, "m_LODData", kHideInEditorMask); + LoadDeprecatedMeshData(*this, lod); + } + + if (transfer.IsVersionSmallerOrEqual(4)) + { + for (int sm = 0; sm < m_SubMeshes.size(); ++sm) + { + UpdateSubMeshVertexRange (sm); + RecalculateSubmeshBoundsInternal (sm); + } + } + + if (transfer.IsOldVersion(2) || transfer.IsOldVersion(1)) + { + DeprecatedTangentsArray m_TangentSpace; + transfer.Transfer (m_TangentSpace, "m_TangentSpace", kHideInEditorMask); + if(transfer.IsReading()) + LoadDeprecatedTangentData(*this,m_TangentSpace); + } + + if (transfer.IsVersionSmallerOrEqual(7)) + { + DestripifySubmeshOnTransferInternal(); + } + TRANSFER_EDITOR_ONLY_HIDDEN(m_MeshOptimized); + +#if UNITY_EDITOR || UNITY_PS3 + TransferPS3Data(transfer); +#endif +} + +#if SUPPORT_SERIALIZED_TYPETREES +// Except for some dead-path removal and a change to the ResizeVertices call to account for an +// API change, this is an exact copy of the Mesh::Transfer function as it shipped in 3.5.0 final. +// This path exists solely to work around the issue with compressed mesh serialization in 3.5.0 +// which produced different serializations for compressed and uncompressed meshes while using the +// same type tree for either case. This makes it impossible for SafeBinaryRead to sort things out. +// +// By having the exact same transfer path, we end up with identical type trees compared to version +// 3.5.0 and thus automatically end up on the StreamedBinaryRead codepath. Also, as long as this +// separate path here is preserved, we can read the faulty 3.5.0 streams without having to worry +// about it in the normal transfer path. +template<class TransferFunction> +void Mesh::TransferWorkaround35SerializeFuckup (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.SetVersion (6); + + if (m_VertexColorsSwizzled) + { + // Unswizzle colors before serializing + std::transform(GetColorBegin(), GetColorEnd(), GetColorBegin(), UnswizzleColorForPlatform); + m_VertexColorsSwizzled = false; + } + + transfer.Transfer (m_SubMeshes, "m_SubMeshes", kHideInEditorMask); + + if (!transfer.IsVersionSmallerOrEqual(3)) + transfer.Transfer (m_MeshCompression, "m_MeshCompression", kHideInEditorMask); + else + m_MeshCompression = kMeshCompressionOff; + + transfer.Align(); + if (m_MeshCompression == kMeshCompressionOff) + { + if (transfer.ConvertEndianess() && transfer.IsWriting ()) + ByteSwapIndices(); + + transfer.Transfer (m_IndexBuffer, "m_IndexBuffer", kHideInEditorMask); + + if (transfer.ConvertEndianess() && (transfer.IsWriting () || transfer.IsReading ())) + ByteSwapIndices(); + + transfer.Transfer (m_Skin, "m_Skin", kHideInEditorMask); + transfer.Transfer (m_Bindpose, "m_BindPose", kHideInEditorMask); + + if (transfer.IsVersionSmallerOrEqual (5)) + { + dynamic_array<Vector4f> tangents; + dynamic_array<Vector3f> vertices, normals; + dynamic_array<Vector2f> uvs, uvs1; + dynamic_array<ColorRGBA32> colors; + + + transfer.Transfer (vertices, "m_Vertices", kHideInEditorMask); + transfer.Transfer (uvs, "m_UV", kHideInEditorMask); + transfer.Transfer (uvs1, "m_UV1", kHideInEditorMask); + transfer.Transfer (tangents, "m_Tangents", kHideInEditorMask); + transfer.Transfer (normals, "m_Normals", kHideInEditorMask); + transfer.Transfer (colors, "m_Colors", kHideInEditorMask); + + unsigned format = 0; + if (!vertices.empty ()) format |= VERTEX_FORMAT1(Vertex); + if (!tangents.empty ()) format |= VERTEX_FORMAT1(Tangent); + if (!normals.empty ()) format |= VERTEX_FORMAT1(Normal); + if (!uvs.empty ()) format |= VERTEX_FORMAT1(TexCoord0); + if (!uvs1.empty ()) format |= VERTEX_FORMAT1(TexCoord1); + if (!colors.empty ()) format |= VERTEX_FORMAT1(Color); + + size_t vertexCount = vertices.size (); + if (GetVertexCount () != vertexCount || GetAvailableChannels () != format) + ResizeVertices (vertexCount, format); + + strided_copy (vertices.begin (), vertices.begin () + std::min (vertices.size (), vertexCount), GetVertexBegin ()); + strided_copy (normals.begin (), normals.begin () + std::min (normals.size (), vertexCount), GetNormalBegin ()); + strided_copy (uvs.begin (), uvs.begin () + std::min (uvs.size (), vertexCount), GetUvBegin (0)); + strided_copy (uvs1.begin (), uvs1.begin () + std::min (uvs1.size (), vertexCount), GetUvBegin (1)); + strided_copy (tangents.begin (), tangents.begin () + std::min (tangents.size (), vertexCount), GetTangentBegin ()); + strided_copy (colors.begin (), colors.begin () + std::min (colors.size (), vertexCount), GetColorBegin ()); + } + else + { + // version 6 introduces interleaved buffer + if (transfer.ConvertEndianess() && transfer.IsWriting ()) + m_VertexData.SwapEndianess (); + + transfer.Transfer (m_VertexData, "m_VertexData", kHideInEditorMask); + + if (transfer.ConvertEndianess() && (transfer.IsWriting () || transfer.IsReading ())) + m_VertexData.SwapEndianess (); + } + } + else + { + vector<Vector4f> emptyVector4; + vector<Vector3f> emptyVector3; + vector<Vector2f> emptyVector2; + vector<BoneInfluence> emptyBones; + vector<UInt8> emptyIndices; + vector<ColorRGBA32> emptyColors; + + transfer.Transfer (emptyIndices, "m_IndexBuffer", kHideInEditorMask); + transfer.Transfer (emptyVector3, "m_Vertices", kHideInEditorMask); + transfer.Transfer (emptyBones, "m_Skin", kHideInEditorMask); + transfer.Transfer (m_Bindpose, "m_BindPose", kHideInEditorMask); + transfer.Transfer (emptyVector2, "m_UV", kHideInEditorMask); + transfer.Transfer (emptyVector2, "m_UV1", kHideInEditorMask); + transfer.Transfer (emptyVector4, "m_Tangents", kHideInEditorMask); + transfer.Transfer (emptyVector3, "m_Normals", kHideInEditorMask); + transfer.Transfer (emptyColors, "m_Colors", kHideInEditorMask); + } + + CompressedMesh m_CompressedMesh; + transfer.Align(); + if (transfer.IsWriting() && m_MeshCompression) + m_CompressedMesh.Compress(*this, m_MeshCompression); + + printf_console( "Reading compressed mesh...\n" ); + transfer.Transfer (m_CompressedMesh, "m_CompressedMesh", kHideInEditorMask); + + if (transfer.DidReadLastProperty () && m_MeshCompression) + m_CompressedMesh.Decompress(*this); + + +#if !GFX_SUPPORTS_TRISTRIPS + if (transfer.IsReading()) + DestripifyIndices (); +#endif + + transfer.Transfer (m_LocalAABB, "m_LocalAABB", kHideInEditorMask); + transfer.Transfer (m_MeshUsageFlags, "m_MeshUsageFlags", kHideInEditorMask); + + m_CollisionMesh.Transfer(transfer, *this); + + if (transfer.IsOldVersion(1)) + { + vector<DeprecatedLOD> lod; + transfer.Transfer (lod, "m_LODData", kHideInEditorMask); + LoadDeprecatedMeshData(*this, lod); + } + + if (transfer.IsVersionSmallerOrEqual(4)) + { + for (int sm = 0; sm < m_SubMeshes.size(); ++sm) + { + UpdateSubMeshVertexRange (sm); + RecalculateSubmeshBoundsInternal (sm); + } + } + + if (transfer.IsOldVersion(2) || transfer.IsOldVersion(1)) + { + DeprecatedTangentsArray m_TangentSpace; + transfer.Transfer (m_TangentSpace, "m_TangentSpace", kHideInEditorMask); + if(transfer.IsReading()) + LoadDeprecatedTangentData(*this,m_TangentSpace); + } + + if (transfer.IsReading()) + DestripifySubmeshOnTransferInternal(); +} +#endif + +#if UNITY_EDITOR || UNITY_PS3 +template<class TransferFunction> +void Mesh::TransferPS3Data (TransferFunction& transfer) +{ + if (UNITY_PS3 || (kBuildPS3 == transfer.GetBuildingTarget().platform)) + { + transfer.Transfer(m_Partitions, "m_Partitions", kHideInEditorMask); + transfer.Transfer(m_PartitionInfos, "m_PartitionInfos", kHideInEditorMask); + } +} +#endif + + +void Mesh::UpdateSubMeshVertexRange (int index) +{ + SubMesh& submesh = m_SubMeshes[index]; + if (submesh.indexCount > 0) + { + UInt32 lastVertex = 0; + GetVertexBufferRange(GetSubMeshBuffer16(index), submesh.indexCount, submesh.firstVertex, lastVertex); + Assert(lastVertex < GetVertexCount ()); + Assert(submesh.firstVertex <= lastVertex); + submesh.vertexCount = lastVertex - submesh.firstVertex + 1; + } + else + { + submesh.firstVertex = 0; + submesh.vertexCount = 0; + } +} + +static bool CheckOutOfBounds (unsigned max, const UInt16* p, unsigned count) +{ + for (int i=0;i<count;i++) + { + if (p[i] >= max) + return false; + } + return true; +} + +static bool CheckOutOfBounds (unsigned max, const UInt32* p, unsigned count) +{ + for (int i=0;i<count;i++) + { + if (p[i] >= max) + return false; + } + return true; +} + +bool Mesh::ValidateVertexCount (unsigned newVertexCount, const void* newTriangles, unsigned indexCount) +{ + if (newTriangles) + { + return CheckOutOfBounds (newVertexCount, reinterpret_cast<const UInt16*>(newTriangles), indexCount); + } + else + { + return CheckOutOfBounds(newVertexCount, reinterpret_cast<const UInt16*>(&m_IndexBuffer[0]), GetTotalndexCount()); + } +} + +int Mesh::GetTotalndexCount () const +{ + return m_IndexBuffer.size () / kVBOIndexSize; +} + +void Mesh::SetVertices (Vector3f const* data, size_t count) +{ + if (m_StreamCompression) + return; + + if (count > std::numeric_limits<UInt16>::max()) + { + ErrorString("Mesh.vertices is too large. A mesh may not have more than 65000 vertices."); + return; + } + + size_t prevCount = GetVertexCount (); + if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion3_5_3_a1) && count < prevCount && !ValidateVertexCount(count, NULL, 0)) + { + ErrorString("Mesh.vertices is too small. The supplied vertex array has less vertices than are referenced by the triangles array."); + return; + } + + WaitOnRenderThreadUse(); + +#if UNITY_PS3 + if(m_Skin.empty() || (!(m_Skin.empty() || m_PartitionInfos.empty()))) + { + // mircea@info: sadly for us GPU renders from pointers, so we need to create a new instance when something changes....(fixes nasty bug #434226) + SET_ALLOC_OWNER(this); + VertexData vertexData(m_VertexData, GetAvailableChannels(), GetStreamsLayout(), GetChannelsLayout()); + swap(vertexData, m_VertexData); + } +#endif + + if (prevCount != count) + { + unsigned prevChannels = GetAvailableChannels (); + ResizeVertices (count, prevChannels | VERTEX_FORMAT1(Vertex)); + + // In case there were other channels present, initialize the newly created values of + // the expanded buffer to something meaningful. + if (prevCount != 0 && count > prevCount && (prevChannels & ~VERTEX_FORMAT1(Vertex))) + { + InitChannelsToDefault (prevCount, count - prevCount, prevChannels & ~VERTEX_FORMAT1(Vertex)); + } + } + + // Make sure we'll not be overrunning the buffer + if (GetVertexCount () < count) + count = GetVertexCount (); + + strided_copy (data, data + count, GetVertexBegin ()); + SetChannelsDirty (VERTEX_FORMAT1(Vertex), false); + + // We do not recalc the bounds automatically when re-writing existing vertices + if (prevCount != count) + RecalculateBounds (); +} + +void Mesh::SetNormals (Vector3f const* data, size_t count) +{ + if (m_StreamCompression) + return; + WaitOnRenderThreadUse(); + + if (count == 0 || !data) + { + FormatVertices (GetAvailableChannels () & ~VERTEX_FORMAT1(Normal)); + SetChannelsDirty (VERTEX_FORMAT1(Normal), false); + return; + } + + if (count != GetVertexCount ()) + { + ErrorStringMsg(kMeshAPIErrorMessage, "normals"); + return; + } + + if (!IsAvailable (kShaderChannelNormal)) + FormatVertices (GetAvailableChannels () | VERTEX_FORMAT1(Normal)); + + strided_copy (data, data + count, GetNormalBegin ()); + + SetChannelsDirty (VERTEX_FORMAT1(Normal), false); +} + +void Mesh::SetTangents (Vector4f const* data, size_t count) +{ + if (m_StreamCompression) + return; + WaitOnRenderThreadUse(); + + if (count == 0 || !data) + { + FormatVertices (GetAvailableChannels () & ~VERTEX_FORMAT1(Tangent)); + SetChannelsDirty (VERTEX_FORMAT1(Tangent), false); + return; + } + + if (count != GetVertexCount ()) + { + ErrorStringMsg(kMeshAPIErrorMessage, "tangents"); + return; + } + + if (!IsAvailable (kShaderChannelTangent)) + FormatVertices (GetAvailableChannels () | VERTEX_FORMAT1(Tangent)); + + strided_copy (data, data + count, GetTangentBegin ()); + SetChannelsDirty( VERTEX_FORMAT1(Tangent), false ); +} + +void Mesh::SetUv (int uvIndex, Vector2f const* data, size_t count) +{ + Assert (uvIndex <= 1); + if (m_StreamCompression) + return; + WaitOnRenderThreadUse(); + + ShaderChannel texCoordChannel = static_cast<ShaderChannel>(kShaderChannelTexCoord0 + uvIndex); + unsigned texCoordMask = 1 << texCoordChannel; + if (count == 0 || !data) + { + FormatVertices (GetAvailableChannels () & ~texCoordMask); + SetChannelsDirty (texCoordMask, false); + return; + } + + if (count != GetVertexCount ()) + { + const char* uvName = uvIndex == 1 ? "uv2" : "uv"; + ErrorStringMsg(kMeshAPIErrorMessage, uvName); + return; + } + + if (!IsAvailable (texCoordChannel)) + FormatVertices (GetAvailableChannels () | texCoordMask); + + strided_copy (data, data + count, GetUvBegin (uvIndex)); + SetChannelsDirty (texCoordMask, false); +} + +void Mesh::SetColors (ColorRGBA32 const* data, size_t count) +{ + if (m_StreamCompression) + return; + WaitOnRenderThreadUse(); + + if (count == 0 || !data) + { + FormatVertices (GetAvailableChannels () & ~VERTEX_FORMAT1(Color)); + SetChannelsDirty( VERTEX_FORMAT1(Color), false ); + return; + } + + if (count != GetVertexCount ()) + { + ErrorStringMsg(kMeshAPIErrorMessage, "colors"); + return; + } + + if (!IsAvailable (kShaderChannelColor)) + { + FormatVertices (GetAvailableChannels () | VERTEX_FORMAT1(Color)); + } + m_VertexColorsSwizzled = gGraphicsCaps.needsToSwizzleVertexColors; + + if (m_VertexColorsSwizzled) + std::transform(data, data + count, GetColorBegin(), SwizzleColorForPlatform); + else + std::copy(data, data + count, GetColorBegin()); + + SetChannelsDirty( VERTEX_FORMAT1(Color), false ); +} + +void Mesh::SetColorsConverting (ColorRGBAf const* data, size_t count) +{ + if (m_StreamCompression) + return; + WaitOnRenderThreadUse(); + + if (count == 0 || !data) + { + FormatVertices (GetAvailableChannels () & ~VERTEX_FORMAT1(Color)); + SetChannelsDirty( VERTEX_FORMAT1(Color), false ); + return; + } + + if (count != GetVertexCount ()) + { + ErrorStringMsg(kMeshAPIErrorMessage, "colors"); + return; + } + + if (!IsAvailable (kShaderChannelColor)) + { + FormatVertices (GetAvailableChannels () | VERTEX_FORMAT1(Color)); + } + m_VertexColorsSwizzled = gGraphicsCaps.needsToSwizzleVertexColors; + + if (m_VertexColorsSwizzled) + std::transform(data, data + count, GetColorBegin(), SwizzleColorForPlatform); + else + strided_copy_convert(data, data + count, GetColorBegin()); + + SetChannelsDirty( VERTEX_FORMAT1(Color), false ); +} + + +void Mesh::GetTriangles (Mesh::TemporaryIndexContainer& triangles) const +{ + triangles.clear(); + for (unsigned m=0;m<GetSubMeshCount();m++) + AppendTriangles(triangles, m); +} + +void Mesh::GetTriangles (Mesh::TemporaryIndexContainer& triangles, unsigned submesh) const +{ + triangles.clear(); + AppendTriangles(triangles, submesh); +} + +void QuadsToTriangles(const UInt16* quads, const int indexCount, Mesh::TemporaryIndexContainer& triangles) +{ + DebugAssert (indexCount%4 == 0); + triangles.resize((indexCount/2)*3); + for (int q = 0, t = 0; q < indexCount; q += 4, t +=6) + { + triangles[t] = quads[q]; + triangles[t + 1] = quads[q + 1]; + triangles[t + 2] = quads[q + 2]; + + triangles[t + 3] = quads[q]; + triangles[t + 4] = quads[q + 2]; + triangles[t + 5] = quads[q + 3]; + } +} + + +void Mesh::AppendTriangles (Mesh::TemporaryIndexContainer& triangles, unsigned submesh) const +{ + if (submesh >= GetSubMeshCount()) + { + ErrorString("Failed getting triangles. Submesh index is out of bounds."); + return; + } + + int topology = GetSubMeshFast(submesh).topology; + if (topology == kPrimitiveTriangleStripDeprecated) + Destripify(GetSubMeshBuffer16(submesh), GetSubMeshFast(submesh).indexCount, triangles); + else if (topology == kPrimitiveQuads) + QuadsToTriangles (GetSubMeshBuffer16 (submesh), GetSubMeshFast (submesh).indexCount, triangles); + else if (topology == kPrimitiveTriangles) + triangles.insert(triangles.end(), GetSubMeshBuffer16(submesh), GetSubMeshBuffer16(submesh) + GetSubMeshFast(submesh).indexCount); + else + ErrorString("Failed getting triangles. Submesh topology is lines or points."); +} + +void Mesh::GetStrips (Mesh::TemporaryIndexContainer& triangles, unsigned submesh) const +{ + triangles.clear(); + if (submesh >= GetSubMeshCount()) + { + ErrorString("Failed getting triangles. Submesh index is out of bounds."); + return; + } + + if (GetSubMeshFast(submesh).topology != kPrimitiveTriangleStripDeprecated) + return; + + triangles.assign(GetSubMeshBuffer16(submesh), GetSubMeshBuffer16(submesh) + GetSubMeshFast(submesh).indexCount); +} + +void Mesh::GetIndices (TemporaryIndexContainer& triangles, unsigned submesh) const +{ + triangles.clear(); + if (submesh >= GetSubMeshCount()) + { + ErrorString("Failed getting indices. Submesh index is out of bounds."); + return; + } + triangles.assign(GetSubMeshBuffer16(submesh), GetSubMeshBuffer16(submesh) + GetSubMeshFast(submesh).indexCount); +} + + +bool Mesh::SetIndices (const UInt32* indices, unsigned count, unsigned submesh, GfxPrimitiveType topology) +{ + int mask = kRebuildCollisionTriangles; + return SetIndicesComplex (indices, count, submesh, topology, mask); +} + +bool Mesh::SetIndices (const UInt16* indices, unsigned count, unsigned submesh, GfxPrimitiveType topology) +{ + int mask = kRebuildCollisionTriangles | k16BitIndices; + return SetIndicesComplex (indices, count, submesh, topology, mask); +} + + +bool Mesh::SetIndicesComplex (const void* indices, unsigned count, unsigned submesh, GfxPrimitiveType topology, int mode) +{ + WaitOnRenderThreadUse(); + + if (indices == NULL && count != 0 && (mode & kDontAssignIndices) == 0) + { + ErrorString("failed setting triangles. triangles is NULL"); + return false; + } + + if (submesh >= GetSubMeshCount()) + { + ErrorString("Failed setting triangles. Submesh index is out of bounds."); + return false; + } + + if ((topology == kPrimitiveTriangles) && (count % 3 != 0)) + { + ErrorString("Failed setting triangles. The number of supplied triangle indices must be a multiple of 3."); + return false; + } + + if ((mode & kDontAssignIndices) == 0) + { + bool valid; + if (mode & k16BitIndices) + valid = CheckOutOfBounds (GetVertexCount(), reinterpret_cast<const UInt16*>(indices), count); + else + valid = CheckOutOfBounds (GetVertexCount(), reinterpret_cast<const UInt32*>(indices), count); + + if (!valid) + { + ErrorString("Failed setting triangles. Some indices are referencing out of bounds vertices."); + return false; + } + } + + SetIndexData(submesh, count, indices, topology, mode); + + if (mode & Mesh::kDontSupportSubMeshVertexRanges) + { + Assert(m_SubMeshes.size () == 1); + m_SubMeshes[0].firstVertex = 0; + m_SubMeshes[0].vertexCount = GetVertexCount(); + m_SubMeshes[0].localAABB = m_LocalAABB; + } + else + { + // Update vertex range + UpdateSubMeshVertexRange (submesh); + RecalculateSubmeshBounds(submesh); + } + + if (mode & kRebuildCollisionTriangles) + RebuildCollisionTriangles(); + + SetChannelsDirty( 0, true ); + + return true; +} + +void Mesh::DestripifySubmeshOnTransferInternal() +{ + if (m_IndexBuffer.empty() || m_SubMeshes.empty()) + return; + + int submeshCount = m_SubMeshes.size(); + typedef UNITY_TEMP_VECTOR(UInt16) TemporaryIndexContainer; + + std::vector<TemporaryIndexContainer> submeshIndices; + submeshIndices.resize(submeshCount); + + // We have to do this in two batches, as SetIndexData seems to have a bug that causes + // triangle windings to get screwed up if we attempt to modify the submeshes in-place. + + for (size_t i = 0; i < submeshCount; ++i) + { + SubMesh& sm = m_SubMeshes[i]; + if (sm.topology == kPrimitiveTriangleStripDeprecated) + { + Destripify (GetSubMeshBuffer16(i), sm.indexCount, submeshIndices[i]); + } + else + { + submeshIndices[i].resize(sm.indexCount); + memcpy(&submeshIndices[i][0], GetSubMeshBuffer16(i), sm.indexCount << 1); + } + } + + for(size_t i = 0; i < submeshCount; ++i) + { + SetIndexData(i, submeshIndices[i].size(), &submeshIndices[i][0], kPrimitiveTriangles, kRebuildCollisionTriangles | k16BitIndices); + } +} + +void Mesh::SetIndexData(int submeshIndex, int indexCount, const void* indices, GfxPrimitiveType topology, int mode) +{ + int newByteSize = indexCount * kVBOIndexSize; + int oldSubmeshSize = GetSubMeshBufferByteSize (submeshIndex); + int insertedBytes = newByteSize - GetSubMeshBufferByteSize (submeshIndex); + int oldFirstByte = m_SubMeshes[submeshIndex].firstByte; + // Growing the buffer + if (insertedBytes > 0) + { + m_IndexBuffer.insert(m_IndexBuffer.begin() + oldFirstByte + oldSubmeshSize, insertedBytes, 0); + } + // Shrinking the buffer + else + { + m_IndexBuffer.erase(m_IndexBuffer.begin() + oldFirstByte, m_IndexBuffer.begin() + oldFirstByte - insertedBytes); + } + +#if UNITY_PS3 + + // mircea@info: sadly for us GPU renders from pointers, so we need to create a new instance when something changes....(fixes nasty bug #434226) + IndexContainer newIndexContainer; + newIndexContainer.resize(m_IndexBuffer.size()); + m_IndexBuffer.swap(newIndexContainer); + +#endif + + // Update the sub mesh + m_SubMeshes[submeshIndex].indexCount = indexCount; + m_SubMeshes[submeshIndex].topology = topology; + + // Synchronize subsequent sub meshes + for (int i=submeshIndex+1;i<m_SubMeshes.size();i++) + { + m_SubMeshes[i].firstByte = m_SubMeshes[i-1].firstByte + m_SubMeshes[i-1].indexCount * kVBOIndexSize; + } + + // Write indices into the allocated data + if ((mode & kDontAssignIndices) == 0) + { + if (mode & k16BitIndices) + { + const UInt16* src = reinterpret_cast<const UInt16*>(indices); + UInt16* dst = GetSubMeshBuffer16(submeshIndex); + for (int i=0;i<indexCount;i++) + dst[i] = src[i]; + } + else + { + const UInt32* src = reinterpret_cast<const UInt32*>(indices); + UInt16* dst = GetSubMeshBuffer16(submeshIndex); + for (int i=0;i<indexCount;i++) + dst[i] = src[i]; + } + } + + return; +} + +const UInt16* Mesh::GetSubMeshBuffer16 (int submesh) const +{ + return m_IndexBuffer.size() > 0 && m_SubMeshes[submesh].firstByte < m_IndexBuffer.size() ? reinterpret_cast<const UInt16*> (&m_IndexBuffer[m_SubMeshes[submesh].firstByte]) : NULL; +} +UInt16* Mesh::GetSubMeshBuffer16 (int submesh) +{ + return m_IndexBuffer.size() > 0 && m_SubMeshes[submesh].firstByte < m_IndexBuffer.size() ? reinterpret_cast<UInt16*> (&m_IndexBuffer[m_SubMeshes[submesh].firstByte]) : NULL; +} + +void Mesh::SetBindposes (const Matrix4x4f* bindposes, int count) +{ + m_Bindpose.assign(bindposes, bindposes + count); + SetDirty(); +} + +void Mesh::SetBounds (const AABB& aabb) +{ + m_LocalAABB = aabb; + SetDirty(); + NotifyObjectUsers( kDidModifyBounds ); + m_IntermediateUsers.Notify( kImNotifyBoundsChanged ); +} + +void Mesh::SetBounds (unsigned submesh, const AABB& aabb) +{ + GetSubMeshFast(submesh).localAABB = aabb; + SetDirty(); + NotifyObjectUsers( kDidModifyBounds ); + m_IntermediateUsers.Notify( kImNotifyBoundsChanged ); +} + +void Mesh::NotifyObjectUsers(const MessageIdentifier& msg) +{ + ASSERT_RUNNING_ON_MAIN_THREAD; + + MessageData data; + data.SetData (this, ClassID (Mesh)); + + ObjectList::iterator next; + for( ObjectList::iterator i = m_ObjectUsers.begin(); i != m_ObjectUsers.end(); i=next ) + { + next = i; + ++next; + Object& target = **i; + SendMessageDirect(target, msg, data); + } +} + +void Mesh::WaitOnRenderThreadUse() +{ +#if ENABLE_MULTITHREADED_CODE + if (m_WaitOnCPUFence) + { + GetGfxDevice().WaitOnCPUFence(m_CurrentCPUFence); + m_WaitOnCPUFence = false; + } +#endif +} + +void Mesh::RebuildCollisionTriangles() +{ + m_CollisionMesh.VertexDataHasChanged (); +} + +PROFILER_INFORMATION(gRecalculateNormals, "Mesh.RecalculateNormals", kProfilerOther) + +void Mesh::RecalculateNormals() +{ + if (m_StreamCompression) + return; + WaitOnRenderThreadUse(); + + PROFILER_AUTO(gRecalculateNormals, this); + + if (int vertexCount = GetVertexCount()) + { + unsigned newChannels = m_VertexData.GetChannelMask () | VERTEX_FORMAT1(Normal); + if (newChannels != m_VertexData.GetChannelMask ()) + FormatVertices (newChannels); + + TemporaryIndexContainer triangles; + GetTriangles (triangles); + + CalculateNormals( GetVertexBegin (), &triangles[0], vertexCount, triangles.size()/3, GetNormalBegin () ); + } + + SetChannelsDirty( VERTEX_FORMAT1(Normal), false ); +} + + +void Mesh::SetSubMeshCount (unsigned int count) +{ + WaitOnRenderThreadUse(); + + if (count == 0) + { + m_IndexBuffer.clear(); + m_SubMeshes.clear(); + return; + } + + // Remove elements + if (count < m_SubMeshes.size ()) + { + m_IndexBuffer.resize(m_SubMeshes[count].firstByte); + m_SubMeshes.resize(count); + } + // Append elements + else if (count > m_SubMeshes.size ()) + { + SubMesh data; + data.firstByte = m_IndexBuffer.size(); + data.indexCount = 0; + data.topology = kPrimitiveTriangles; + data.firstVertex = 0; + data.vertexCount = 0; + data.localAABB = AABB (Vector3f::zero, Vector3f::zero); + m_SubMeshes.resize(count, data); + RecalculateBounds(); + } +} + +size_t Mesh::GetSubMeshCount () const +{ + return m_SubMeshes.size(); +} + +int Mesh::GetPrimitiveCount() const +{ + int submeshes = GetSubMeshCount(); + int count = 0; + for( int m = 0; m < submeshes; ++m ) { + const SubMesh& sub = m_SubMeshes[m]; + count += ::GetPrimitiveCount(sub.indexCount, sub.topology, false); + } + return count; +} + +int Mesh::CalculateTriangleCount() const +{ + int submeshes = GetSubMeshCount(); + int count = 0; + for( int m = 0; m < submeshes; ++m ) + { + const SubMesh& sub = m_SubMeshes[m]; + if (sub.topology == kPrimitiveTriangleStripDeprecated) + { + const UInt16* indices = GetSubMeshBuffer16(m); + int triCount = CountTrianglesInStrip (indices, sub.indexCount); + count += triCount; + } + else if (sub.topology == kPrimitiveTriangles) + { + count += sub.indexCount / 3; + } + } + return count; +} + +Mesh& Mesh::GetInstantiatedMesh (Mesh* mesh, Object& owner) +{ + if (NULL != mesh && mesh->m_Owner == PPtr<Object> (&owner)) + return *mesh; + + if (!IsWorldPlaying()) + ErrorStringObject("Instantiating mesh due to calling MeshFilter.mesh during edit mode. This will leak meshes. Please use MeshFilter.sharedMesh instead.", &owner); + + if (mesh == NULL || !mesh->HasVertexData ()) + { + if (!mesh) + mesh = NEW_OBJECT (Mesh); + mesh->Reset(); + + mesh->SetName(owner.GetName()); + mesh->m_Owner = &owner; + + mesh->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + return *mesh; + } + + Mesh* instance = NEW_OBJECT (Mesh); + CopySerialized(*mesh, *instance); + instance->SetNameCpp (Append (mesh->GetName (), " Instance")); + instance->m_Owner = &owner; + return *instance; +} + +const VertexStreamsLayout& Mesh::GetStreamsLayout() const +{ + if (!m_Skin.empty() || GetBlendShapeChannelCount() != 0) + return VertexDataInfo::kVertexStreamsSkinnedHotColdSplit; + else + return VertexDataInfo::kVertexStreamsDefault; +} + +const VertexChannelsLayout& Mesh::GetChannelsLayout() const +{ + UInt8 compressed = m_StreamCompression; +#if !UNITY_EDITOR + // Editor only does build step for compression and never draws float16 vertices + if (!gGraphicsCaps.has16BitFloatVertex) + { + compressed = kStreamCompressionDefault; + } +#endif + switch (compressed) + { + default: // fall through + case kStreamCompressionDefault: + return VertexDataInfo::kVertexChannelsDefault; + case kStreamCompressionCompressed: + return VertexDataInfo::kVertexChannelsCompressed; + case kStreamCompressionCompressedAggressive: + return VertexDataInfo::kVertexChannelsCompressedAggressive; + } +} + +void Mesh::InitVertexBufferData( UInt32 wantedChannels ) +{ +#if GFX_CAN_UNLOAD_MESH_DATA + // If data was uploaded and freed we cannot update it. + if (!HasVertexData()) + return; +#endif + UInt32 presentChannels = GetAvailableChannels (); + + // Modify the vertex buffer before fetching any channel pointers, as modifying the format reallocates the buffer and pointers + // are invalidated. Due to possible format changes, also fetch the stride sizes only after buffer reformatting. + unsigned initChannels = 0; + + // Silently create an all-white color array if shader wants colors, but mesh does not have them. + // On D3D, some runtime/driver combinations will crash if a vertex shader wants colors but does not + // have them (e.g. Vista drivers for Intel 965). In other cases it will default to white for fixed function + // pipe, and to undefined value for vertex shaders, which is not good either. + if( (wantedChannels & VERTEX_FORMAT1(Color)) && !(presentChannels & VERTEX_FORMAT1(Color)) ) + initChannels |= VERTEX_FORMAT1(Color); + +#if UNITY_PEPPER + // Pepper OpenGL implementation fails to draw anything if any channel is missing. + if( (wantedChannels & VERTEX_FORMAT1(Tangent)) && !(presentChannels & VERTEX_FORMAT1(Tangent)) ) + initChannels |= VERTEX_FORMAT1(Tangent); +#endif + + if ((initChannels & presentChannels) != initChannels) + { + FormatVertices (presentChannels | initChannels); + InitChannelsToDefault (0, GetVertexCount (), initChannels); + } +} + +void Mesh::GetVertexBufferData( VertexBufferData& buffer, UInt32 wantedChannels ) +{ + InitVertexBufferData(wantedChannels); + + for (int i = 0; i < kShaderChannelCount; i++) + buffer.channels[i] = m_VertexData.GetChannel(i); + + for (int i = 0; i < kMaxVertexStreams; i++) + buffer.streams[i] = m_VertexData.GetStream(i); + + int srcTexcoord = kShaderChannelNone; + for (int i = kShaderChannelTexCoord0; i <= kShaderChannelTexCoord1; i++) + { + if (buffer.channels[i].IsValid()) + { + // We have a valid texcoord + srcTexcoord = i; + continue; + } + UInt32 channelMask = 1 << i; + if (srcTexcoord != kShaderChannelNone) + { + // Replicate last valid texture coord + const ChannelInfo& srcChannel = buffer.channels[srcTexcoord]; + buffer.channels[i] = srcChannel; + buffer.streams[srcChannel.stream].channelMask |= channelMask; + } + } + + // Data pointer can be NULL if we are only updating declaration of uploaded VBO + buffer.buffer = m_VertexData.GetDataPtr(); + buffer.bufferSize = m_VertexData.GetDataSize(); + buffer.vertexCount = GetVertexCount(); + +#if UNITY_EDITOR + #define LogStringObjectEditor(x) LogStringObject(Format(x, GetName()),this) + + if (Camera::ShouldShowChannelErrors(GetCurrentCameraPtr())) + { + const ChannelInfo* channels = buffer.channels; + + if ((wantedChannels & VERTEX_FORMAT1(Tangent)) && !channels[kShaderChannelTangent].IsValid()) + LogStringObjectEditor ("Shader wants tangents, but the mesh %s doesn't have them"); + + if ((wantedChannels & VERTEX_FORMAT1(Normal)) && !channels[kShaderChannelNormal].IsValid()) + LogStringObjectEditor ("Shader wants normals, but the mesh %s doesn't have them"); + + if ((wantedChannels & VERTEX_FORMAT1(TexCoord0)) && !channels[kShaderChannelTexCoord0].IsValid()) + LogStringObjectEditor ("Shader wants texture coordinates, but the mesh %s doesn't have them"); + + if ((wantedChannels & VERTEX_FORMAT1(TexCoord1)) && !channels[kShaderChannelTexCoord1].IsValid()) + LogStringObjectEditor ("Shader wants secondary texture coordinates, but the mesh %s doesn't have any"); + + if ((wantedChannels & VERTEX_FORMAT1(Color)) && !channels[kShaderChannelColor].IsValid()) + LogStringObjectEditor ("Shader wants vertex colors, and failed to create a vertex color array"); + } + #undef LogStringObjectEditor +#endif + +#if UNITY_PS3 + if(m_PartitionInfos.empty()) + { + int submeshCount = m_SubMeshes.size(); + for (int submesh=0; submesh<submeshCount; submesh++) + { + SubMesh& sm = GetSubMeshFast(submesh); + + MeshPartitionInfo partInfo; + partInfo.submeshStart = submesh; + partInfo.partitionCount = 1; + buffer.partInfo.push_back(partInfo); + + MeshPartition part; + part.vertexCount = sm.vertexCount; + part.vertexOffset = 0; + part.indexCount = sm.indexCount; + part.indexByteOffset = sm.firstByte; + buffer.partitions.push_back(part);; + } + } + else + { + buffer.partInfo = m_PartitionInfos; + buffer.partitions = m_Partitions; + } + +#endif + + buffer.vertexCount = GetVertexCount (); +} + +void Mesh::GetIndexBufferData (IndexBufferData& buffer) +{ + DebugAssert (!m_IndexBuffer.empty()); + buffer.indices = m_IndexBuffer.empty() ? NULL : (void*)&m_IndexBuffer[0]; + + ///@TODO: HACK for now to get index buffers working, without changing a lot of vbo code + // We should be passing the byte size not the number of indices + buffer.count = GetTotalndexCount(); + buffer.hasTopologies = 0; + for (size_t i = 0, n = m_SubMeshes.size(); i < n; ++i) + { + buffer.hasTopologies |= (1<<m_SubMeshes[i].topology); + } +} + +PROFILER_INFORMATION(gCreateVBOProfile, "Mesh.CreateVBO", kProfilerRender); +PROFILER_INFORMATION(gAwakeFromLoadMesh, "Mesh.AwakeFromLoad", kProfilerLoading); +PROFILER_INFORMATION(gUploadMeshDataMesh, "Mesh.UploadMeshData", kProfilerLoading); + +VBO* Mesh::GetSharedVBO( UInt32 wantedChannels ) +{ + // Some badly written shaders have no Bind statements in the vertex shaders parts; + // and only happened to work before by accident. If requiredChannels turns out to be + // zero, let's pretend it did request at least position. + if (wantedChannels == 0) + wantedChannels = (1<<kShaderChannelVertex); + + UInt32 newChannels = wantedChannels | m_ChannelsInVBO; + bool addedChannels = newChannels != m_ChannelsInVBO; + +#if GFX_CAN_UNLOAD_MESH_DATA + if (!m_IsReadable && !m_KeepVertices && m_VBO) + { + // Everything is already prepared, just return VBO + return m_VBO; + } +#endif + + if ((GFX_ALL_BUFFERS_CAN_BECOME_LOST || m_IsDynamic) && m_VBO && m_VBO->IsVertexBufferLost()) + m_VerticesDirty = true; + if (GFX_ALL_BUFFERS_CAN_BECOME_LOST && m_VBO && m_VBO->IsIndexBufferLost()) + m_IndicesDirty = true; + + if (addedChannels || m_VerticesDirty || m_IndicesDirty) + CreateSharedVBO(wantedChannels); + + return m_VBO; +} + +void Mesh::CreateSharedVBO( UInt32 wantedChannels ) +{ + if (m_IndexBuffer.empty()) + { + if (m_VBO) + { + GetGfxDevice().DeleteVBO(m_VBO); + m_VBO = NULL; + } + return; + } + + PROFILER_BEGIN(gCreateVBOProfile, this) + SET_ALLOC_OWNER(this); + + if (!m_VBO) + { + m_VBO = GetGfxDevice().CreateVBO(); + m_VBO->SetHideFromRuntimeStats(m_HideFromRuntimeStats); + } + + UInt32 newChannels = wantedChannels | m_ChannelsInVBO; + if (m_VerticesDirty || newChannels != m_ChannelsInVBO) + { + if (m_IsDynamic) + m_VBO->SetVertexStreamMode(0, VBO::kStreamModeDynamic); + + VertexBufferData vertexBuffer; + GetVertexBufferData (vertexBuffer, newChannels); + m_VBO->UpdateVertexData (vertexBuffer); + } + + if (m_IndicesDirty) + { + // TODO: probably add separate script access to set vertex/index dynamic + if (m_IsDynamic) + m_VBO->SetIndicesDynamic(true); + + IndexBufferData indexBuffer; + GetIndexBufferData (indexBuffer); + m_VBO->UpdateIndexData (indexBuffer); + } + + m_VerticesDirty = false; + m_IndicesDirty = false; + m_ChannelsInVBO = newChannels; + + PROFILER_END +} + +bool Mesh::CopyToVBO ( UInt32 wantedChannels, VBO& vbo ) +{ + if( m_IndexBuffer.empty() ) + return false; + + PROFILER_BEGIN(gCreateVBOProfile, this) + + VertexBufferData vertexBuffer; + GetVertexBufferData( vertexBuffer, wantedChannels ); + vbo.UpdateVertexData( vertexBuffer ); + + IndexBufferData indexBuffer; + GetIndexBufferData (indexBuffer); + vbo.UpdateIndexData (indexBuffer); +#if UNITY_XENON + if( m_VBO ) + vbo.CopyExtraUvChannels( m_VBO ); +#endif + PROFILER_END + + return true; +} + + +void Mesh::UnloadVBOFromGfxDevice() +{ + if (m_VBO) + { + WaitOnRenderThreadUse(); + GetGfxDevice().DeleteVBO (m_VBO); + } + m_VBO = NULL; + m_ChannelsInVBO = 0; + m_VerticesDirty = m_IndicesDirty = true; +#if ENABLE_MULTITHREADED_CODE + m_CurrentCPUFence = 0; + m_WaitOnCPUFence = false; +#endif +} + +void Mesh::ReloadVBOToGfxDevice() +{ + const bool needReloadFromDisk = (!m_IsReadable && !HasVertexData()); + if (needReloadFromDisk) + { + GetPersistentManager().ReloadFromDisk(this); + } + else + { + m_ChannelsInVBO = 0; + m_VerticesDirty = m_IndicesDirty = true; + } + SwizzleVertexColorsIfNeeded(); +} + + +bool Mesh::ExtractTriangle (UInt32 face, UInt32* indices) const +{ + ///@TODO: OPTIMIZE this away + TemporaryIndexContainer triangles; + GetTriangles(triangles); + if (face * 3 > triangles.size ()) + return false; + + indices[0] = triangles[face * 3 + 0]; + indices[1] = triangles[face * 3 + 1]; + indices[2] = triangles[face * 3 + 2]; + return true; +} + +static void TransformNormals (const Matrix3x3f& invTranspose, StrideIterator<Vector3f> inNormals, StrideIterator<Vector3f> inNormalsEnd, StrideIterator<Vector3f> outNormals) +{ + for (; inNormals != inNormalsEnd; ++inNormals, ++outNormals) + *outNormals = NormalizeSafe (invTranspose.MultiplyVector3 (*inNormals)); +} + +static void TransformTangents (const Matrix3x3f& invTranspose, StrideIterator<Vector4f> inTangents, StrideIterator<Vector4f> inTangentsEnd, StrideIterator<Vector4f> outTangents) +{ + for ( ; inTangents != inTangentsEnd; ++inTangents, ++outTangents) + { + Vector3f tangent = Vector3f(inTangents->x,inTangents->y,inTangents->z); + Vector3f normalized = NormalizeSafe (invTranspose.MultiplyVector3 (tangent)); + *outTangents = Vector4f(normalized.x, normalized.y ,normalized.z, inTangents->w); + } +} + +void Mesh::CopyTransformed (const Mesh& mesh, const Matrix4x4f& transform) +{ + int vertexCount = mesh.GetVertexCount(); + unsigned outVertexFormat = mesh.GetAvailableChannelsForRendering (); + + ResizeVertices(mesh.GetVertexCount (), outVertexFormat); + + if (outVertexFormat & VERTEX_FORMAT1(Vertex)) + TransformPoints3x4 (transform, + (Vector3f*)mesh.GetChannelPointer (kShaderChannelVertex), mesh.GetStride (kShaderChannelVertex), + (Vector3f*)GetChannelPointer (kShaderChannelVertex), GetStride (kShaderChannelVertex), + vertexCount); + + Matrix3x3f invTranspose3x3 = Matrix3x3f(transform); invTranspose3x3.InvertTranspose (); + + if (outVertexFormat & VERTEX_FORMAT1(Normal)) + TransformNormals (invTranspose3x3, mesh.GetNormalBegin (), mesh.GetNormalEnd (), GetNormalBegin ()); + if (outVertexFormat & VERTEX_FORMAT1(Tangent)) + TransformTangents (invTranspose3x3, mesh.GetTangentBegin (), mesh.GetTangentEnd (), GetTangentBegin ()); + + m_IndexBuffer = mesh.m_IndexBuffer; + m_SubMeshes = mesh.m_SubMeshes; + m_Skin = mesh.m_Skin; + if (outVertexFormat & VERTEX_FORMAT1(TexCoord0)) + strided_copy (mesh.GetUvBegin (0), mesh.GetUvEnd (0), GetUvBegin (0)); + if (outVertexFormat & VERTEX_FORMAT1(TexCoord1)) + strided_copy (mesh.GetUvBegin (1), mesh.GetUvEnd (1), GetUvBegin (1)); + if (outVertexFormat & VERTEX_FORMAT1(Color)) + strided_copy (mesh.GetColorBegin (), mesh.GetColorEnd (), GetColorBegin ()); + m_VertexColorsSwizzled = mesh.m_VertexColorsSwizzled; + m_LocalAABB = mesh.m_LocalAABB; + + SetChannelsDirty( outVertexFormat, true ); + ClearSkinCache(); +} + + +void Mesh::SetChannelsDirty (unsigned vertexChannelsChanged, bool indices) +{ + SetDirty(); + + m_VerticesDirty |= vertexChannelsChanged != 0; + m_IndicesDirty |= indices; + + // We should regenreate physics mesh only if verex data have changed + if ((vertexChannelsChanged & VERTEX_FORMAT1(Vertex)) || indices) + { + m_CollisionMesh.VertexDataHasChanged(); + m_CachedBonesAABB.clear(); + } + NotifyObjectUsers( kDidModifyMesh ); +} + +bool Mesh::SetBoneWeights (const BoneInfluence* v, int count) +{ + WaitOnRenderThreadUse(); + ClearSkinCache(); + if (count == 0) + { + m_Skin.clear(); + UpdateVertexFormat(); + return true; + } + + if (count != GetVertexCount ()) + { + ErrorString("Mesh.boneWeights is out of bounds. The supplied array needs to be the same size as the Mesh.vertices array."); + return false; + } + m_Skin.assign(v, v + count); + SetChannelsDirty (0, false); + UpdateVertexFormat(); + + return true; +} + +static void ComputeBoneBindPoseAABB (const Matrix4x4f* bindPoses, size_t bindPoseCount, const StrideIterator<Vector3f> vertices, const BoneInfluence* influences, size_t vertexCount, const BlendShapeVertices& blendShapeVertices, MinMaxAABB* outputBounds) +{ + if (blendShapeVertices.empty()) + { + for(int v=0;v<vertexCount;v++) + { + const Vector3f& vert = vertices[v]; + for (int i = 0; i < 4; i++) + { + if(influences[v].weight[i] > 0.0f) + { + const UInt32 boneIndex = influences[v].boneIndex[i]; + + outputBounds[boneIndex].Encapsulate(bindPoses[boneIndex].MultiplyPoint3(vert)); + } + } + } + } + else + { + Vector3f* minVertices; + ALLOC_TEMP(minVertices, Vector3f, vertexCount); + Vector3f* maxVertices; + ALLOC_TEMP(maxVertices, Vector3f, vertexCount); + + strided_copy(vertices, vertices + vertexCount, minVertices); + strided_copy(vertices, vertices + vertexCount, maxVertices); + + for (int i=0;i<blendShapeVertices.size();i++) + { + int index = blendShapeVertices[i].index; + Vector3f pos = blendShapeVertices[i].vertex + vertices[index]; + maxVertices[index] = max (maxVertices[index], pos); + minVertices[index] = min (minVertices[index], pos); + } + + for(int v=0;v<vertexCount;v++) + { + for (int i = 0; i < 4; i++) + { + if(influences[v].weight[i] > 0.0f) + { + const UInt32 boneIndex = influences[v].boneIndex[i]; + outputBounds[boneIndex].Encapsulate(bindPoses[boneIndex].MultiplyPoint3(minVertices[v])); + outputBounds[boneIndex].Encapsulate(bindPoses[boneIndex].MultiplyPoint3(maxVertices[v])); + } + } + } + } +} + +const Mesh::AABBContainer& Mesh::GetCachedBonesBounds() +{ + // Use cached result if it has the correct size (including empty) + if (m_CachedBonesAABB.size() == m_Bindpose.size()) + return m_CachedBonesAABB; + + Assert(GetMaxBoneIndex() < m_Bindpose.size()); + + m_CachedBonesAABB.resize_initialized(m_Bindpose.size(), MinMaxAABB()); + + ComputeBoneBindPoseAABB (GetBindposes(), m_CachedBonesAABB.size(), GetVertexBegin(), m_Skin.begin(), GetVertexCount(), m_Shapes.vertices, &m_CachedBonesAABB[0]); + + return m_CachedBonesAABB; +} + +void Mesh::ClearSkinCache () +{ + m_CachedBonesAABB.clear(); + m_CachedSkin2.clear(); + m_CachedSkin1.clear(); + m_MaxBoneIndex = -1; +} + +int Mesh::GetMaxBoneIndex () +{ + if (m_MaxBoneIndex != -1) + return m_MaxBoneIndex; + + m_MaxBoneIndex = 0; + for (int i=0;i<m_Skin.size();i++) + { + m_MaxBoneIndex = max(m_MaxBoneIndex, m_Skin[i].boneIndex[0]); + m_MaxBoneIndex = max(m_MaxBoneIndex, m_Skin[i].boneIndex[1]); + m_MaxBoneIndex = max(m_MaxBoneIndex, m_Skin[i].boneIndex[2]); + m_MaxBoneIndex = max(m_MaxBoneIndex, m_Skin[i].boneIndex[3]); + } + + return m_MaxBoneIndex; +} + +void* Mesh::GetSkinInfluence (int count) +{ + if (!m_Skin.empty()) + { + BoneInfluence* bones4 = &m_Skin[0]; + if (count == 1) + { + if (!m_CachedSkin1.empty()) + return &m_CachedSkin1[0]; + + // Cache 1 bone skin weights + int size = m_Skin.size(); + m_CachedSkin1.resize_uninitialized(size); + + int* bones1 = &m_CachedSkin1[0]; + for (int i=0;i<size;i++) + bones1[i] = bones4[i].boneIndex[0]; + return bones1; + + } + else if (count == 2) + { + if (!m_CachedSkin2.empty ()) + return &m_CachedSkin2[0]; + + // Cache 2 bone skin weights + int size = m_Skin.size(); + m_CachedSkin2.resize_uninitialized(size); + + BoneInfluence2* bones2 = &m_CachedSkin2[0]; + for (int i=0;i<size;i++) + { + bones2[i].boneIndex[0] = bones4[i].boneIndex[0]; + bones2[i].boneIndex[1] = bones4[i].boneIndex[1]; + + float invSum = 1.0F / (bones4[i].weight[0] + bones4[i].weight[1]); + bones2[i].weight[0] = bones4[i].weight[0] * invSum; + bones2[i].weight[1] = bones4[i].weight[1] * invSum; + } + return bones2; + } + else if (count == 4) + { + return bones4; + } + else + { + return NULL; + } + } + else + { + return NULL; + } +} + + +int Mesh::GetRuntimeMemorySize () const +{ + int size = Super::GetRuntimeMemorySize(); + + #if ENABLE_PROFILER + if (m_VBO) + size += m_VBO->GetRuntimeMemorySize(); + #endif + + return size; +} + + +void* Mesh::GetSharedNxMesh () +{ + return m_CollisionMesh.GetSharedNxMesh (*this); +} + +void* Mesh::GetSharedNxConvexMesh () +{ + return m_CollisionMesh.GetSharedNxConvexMesh (*this); +} + +void Mesh::UploadMeshData(bool markNoLongerReadable) +{ + if(markNoLongerReadable) + m_IsReadable = false; + + ClearSkinCache(); + UpdateVertexFormat(); + + // prepare VBO + UInt32 channelMask = GetAvailableChannelsForRendering(); + + // Create color channel in case it's needed by shader (and we can't patch it) +#if GFX_CAN_UNLOAD_MESH_DATA + bool unloadData = !m_IsReadable && m_Skin.empty(); + if (unloadData && !m_KeepVertices) + channelMask |= VERTEX_FORMAT1(Color); +#endif + + // Shared VBO is not required for skinned meshes (unless used as non-skinned) + if (m_Skin.empty()) + CreateSharedVBO(channelMask); + +#if GFX_CAN_UNLOAD_MESH_DATA + if (unloadData) + { + if (!m_KeepVertices && m_VBO && !m_VBO->IsUsingSourceVertices()) + { + Assert(m_Skin.empty()); + m_VertexData.Deallocate(); + m_VBO->UnloadSourceVertices(); + } + if (!m_KeepIndices && m_VBO && !m_VBO->IsUsingSourceIndices()) + { +#if UNITY_METRO + m_IndexBuffer.clear(); + m_IndexBuffer.shrink_to_fit(); +#else + // On Metro this throws "Expression: vector containers incompatible for swap" when compiling in VS 2013, works okay if compiling in VS 2012 + // Case 568418 + IndexContainer emptyIndices; + m_IndexBuffer.swap(emptyIndices); +#endif + } + } +#endif +} + +void Mesh::AwakeFromLoad(AwakeFromLoadMode awakeMode) +{ + PROFILER_AUTO(gAwakeFromLoadMesh, this) + + Super::AwakeFromLoad(awakeMode); + m_CollisionMesh.AwakeFromLoad(awakeMode); + + UploadMeshData(!m_IsReadable); + + if (m_InternalMeshID == 0) + m_InternalMeshID = s_MeshIDGenerator.AllocateID (); +} + +void Mesh::AwakeFromLoadThreaded() +{ + Super::AwakeFromLoadThreaded(); + m_CollisionMesh.AwakeFromLoadThreaded(*this); +} + +void Mesh::MarkDynamic() +{ + // Optimize for frequent updates + m_IsDynamic = true; +} + +void Mesh::UpdateVertexFormat() +{ + // Make sure vertex streams are in the format we want for rendering + // This will also handle decompression of unsupported vertex formats + FormatVertices(GetAvailableChannels()); + SwizzleVertexColorsIfNeeded(); +} + +bool Mesh::ShouldIgnoreInGarbageDependencyTracking () +{ + return true; +} + +UInt32 Mesh::GetAvailableChannels() const +{ + return m_VertexData.GetChannelMask (); +} + +UInt32 Mesh::GetAvailableChannelsForRendering() const +{ + unsigned availChannels = m_VertexData.GetChannelMask (); + return availChannels; +} + +bool Mesh::IsSuitableSizeForDynamicBatching () const +{ + // If any submesh has too many vertices, don't keep mesh data for batching + for (size_t i = 0; i < GetSubMeshCount(); i++) + { + if (m_SubMeshes[i].vertexCount > kDynamicBatchingVerticesThreshold) + return false; + } + return true; +} + +void Mesh::CheckConsistency() +{ + Super::CheckConsistency(); + + for (int i = 0; i < m_SubMeshes.size(); ++i) + { + Assert(m_SubMeshes[i].topology != kPrimitiveTriangleStripDeprecated); + } +} + +void Mesh::SwapBlendShapeData (BlendShapeData& shapes) +{ + WaitOnRenderThreadUse(); + +// swap (m_Shapes, shapes); + m_Shapes = shapes; + + NotifyObjectUsers( kDidModifyMesh ); +} diff --git a/Runtime/Filters/Mesh/LodMesh.h b/Runtime/Filters/Mesh/LodMesh.h new file mode 100644 index 0000000..41fcf74 --- /dev/null +++ b/Runtime/Filters/Mesh/LodMesh.h @@ -0,0 +1,509 @@ +#ifndef LODMESH_H +#define LODMESH_H + +#include "Runtime/BaseClasses/NamedObject.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Vector4.h" +#include "Mesh.h" +#include "Runtime/Math/Color.h" +#include <string> +#include <vector> +#include "Runtime/BaseClasses/MessageIdentifier.h" +#include "Runtime/Shaders/VBO.h" +#include "CompressedMesh.h" +#include "VertexData.h" +#include "Runtime/Dynamics/CollisionMeshData.h" +#include "MeshBlendShape.h" +#include "Runtime/Misc/Allocator.h" +#include "Runtime/Camera/IntermediateUsers.h" + +class IntermediateRenderer; + +struct SubMesh +{ + UInt32 firstByte; + UInt32 indexCount; + GfxPrimitiveType topology; + + UInt32 firstVertex; + UInt32 vertexCount; + AABB localAABB; + + SubMesh () + { + firstByte = 0; + indexCount = 0; + topology = kPrimitiveTriangles; + firstVertex = 0; + vertexCount = 0; + localAABB = AABB (Vector3f::zero, Vector3f::zero); + } + + DECLARE_SERIALIZE_NO_PPTR (SubMesh) + +#if SUPPORT_SERIALIZED_TYPETREES + template<class TransferFunction> + void TransferWorkaround35SerializationFuckup (TransferFunction& transfer); +#endif +}; + +/// typedef for tangent space lighting rotations +typedef std::vector<DeprecatedTangent, STL_ALLOCATOR(kMemGeometry, DeprecatedTangent) > DeprecatedTangentsArray; + +template<class TransferFunc> +void SubMesh::Transfer (TransferFunc& transfer) +{ + #if SUPPORT_SERIALIZED_TYPETREES + if (transfer.GetFlags() & kWorkaround35MeshSerializationFuckup) + { + TransferWorkaround35SerializationFuckup (transfer); + return; + } + #endif + + transfer.SetVersion (2); + TRANSFER(firstByte); + TRANSFER(indexCount); + TRANSFER_ENUM(topology); + TRANSFER(firstVertex); + TRANSFER(vertexCount); + TRANSFER(localAABB); + if (transfer.IsOldVersion(1)) + { + UInt32 triStrip; + transfer.Transfer (triStrip, "isTriStrip"); + topology = triStrip ? kPrimitiveTriangleStripDeprecated : kPrimitiveTriangles; + } +} + +#if SUPPORT_SERIALIZED_TYPETREES +template<class TransferFunc> +void SubMesh::TransferWorkaround35SerializationFuckup (TransferFunc& transfer) +{ + TRANSFER(firstByte); + TRANSFER(indexCount); + + UInt32 triStrip; + transfer.Transfer (triStrip, "isTriStrip"); + topology = triStrip ? kPrimitiveTriangleStripDeprecated : kPrimitiveTriangles; + + UInt32 triangleCount; + transfer.Transfer (triangleCount, "triangleCount"); + + TRANSFER(firstVertex); + TRANSFER(vertexCount); + TRANSFER(localAABB); +} +#endif + +template<class TransferFunc> +void MeshPartition::Transfer (TransferFunc& transfer) +{ + TRANSFER(vertexCount); + TRANSFER(vertexOffset); + TRANSFER(indexCount); + TRANSFER(indexByteOffset); +} + +template<class TransferFunc> +void MeshPartitionInfo::Transfer (TransferFunc& transfer) +{ + TRANSFER(submeshStart); + TRANSFER(partitionCount); +} + +class EXPORT_COREMODULE Mesh : public NamedObject +{ +public: + enum + { + #if UNITY_IPHONE || UNITY_ANDROID || UNITY_BB10 || UNITY_TIZEN + alignBoneContainer = 16, + #else + alignBoneContainer = kDefaultMemoryAlignment, + #endif + }; + + //mircea@INFO PS3 doesn't render from VBOs hence m_VertexData and m_IndexBuffer *have* to be allocated with kMemVertexData. + typedef UNITY_VECTOR(kMemVertexData, UInt8) IndexContainer; + typedef UNITY_VECTOR(kMemGeometry, SubMesh) SubMeshContainer; + typedef dynamic_array<Matrix4x4f> MatrixContainer; + typedef dynamic_array<int> SkinContainer; + typedef UNITY_VECTOR(kMemGeometry, UInt32) CollisionTriangleContainer; + typedef dynamic_array<MinMaxAABB> AABBContainer; + + typedef dynamic_array<BoneInfluence, alignBoneContainer> BoneInfluenceContainer; + typedef dynamic_array<BoneInfluence2, alignBoneContainer> BoneInfluence2Container; + + typedef UNITY_TEMP_VECTOR(UInt32) TemporaryIndexContainer; + +#if UNITY_PS3 || UNITY_EDITOR + typedef UNITY_VECTOR(kMemVertexData, MeshPartition) MeshPartitionContainer; + typedef UNITY_VECTOR(kMemVertexData, MeshPartitionInfo) MeshPartitionInfoContainer; +#endif + + REGISTER_DERIVED_CLASS (Mesh, NamedObject) + DECLARE_OBJECT_SERIALIZE (Mesh) + + Mesh (MemLabelId label, ObjectCreationMode mode); + // ~Mesh (); declared-by-macro + +public: + + virtual int GetRuntimeMemorySize () const; + + VBO* GetSharedVBO( UInt32 wantedChannels ); + bool CopyToVBO ( UInt32 wantedChannels, VBO& vbo ); + void InitVertexBufferData ( UInt32 wantedChannels ); + void GetVertexBufferData ( VertexBufferData& buffer, UInt32 wantedChannels ); + void GetIndexBufferData (IndexBufferData& buffer); + void UnloadVBOFromGfxDevice(); + void ReloadVBOToGfxDevice(); + + + void AwakeFromLoad(AwakeFromLoadMode mode); + void AwakeFromLoadThreaded(); + void UploadMeshData(bool markNoLongerReadable); + + virtual bool MainThreadCleanup (); + + void MarkDynamic(); + void UpdateVertexFormat(); + + void SetBounds (const AABB& aabb ); + const AABB& GetBounds () const { return m_LocalAABB; } + + void SetBounds (unsigned submesh, const AABB& aabb ); + const AABB& GetBounds (unsigned submesh) const + { + DebugAssertIf(submesh >= m_SubMeshes.size()); + return m_SubMeshes[submesh].localAABB; + } + + void Clear (bool keepVertexLayout); + + /// Recalculate the bounding volume + void RecalculateBounds (); + void RecalculateSubmeshBounds (unsigned submesh); + + // Recalculate normals + void RecalculateNormals(); + void RecalculateNormalsWithHardAngle( float hardAngle ); + + // Validate that there are no out of bounds indices in the triangles + bool ValidateVertexCount (unsigned newVertexCount, const void* newTriangles, unsigned indexCount); + + int GetVertexCount () const { return m_VertexData.GetVertexCount (); } + + // Gets count in all submeshes. + int GetPrimitiveCount() const; + int CalculateTriangleCount() const; // ignores degenerates in strips + + // NOTE: make sure to call SetChannelDirty and RecalculateBounds when changing the geometry! + StrideIterator<Vector3f> GetVertexBegin () const { return m_VertexData.MakeStrideIterator<Vector3f> (kShaderChannelVertex); } + StrideIterator<Vector3f> GetVertexEnd () const { return m_VertexData.MakeEndIterator<Vector3f> (kShaderChannelVertex); } + + StrideIterator<Vector3f> GetNormalBegin () const { return m_VertexData.MakeStrideIterator<Vector3f> (kShaderChannelNormal); } + StrideIterator<Vector3f> GetNormalEnd () const { return m_VertexData.MakeEndIterator<Vector3f> (kShaderChannelNormal); } + + StrideIterator<ColorRGBA32> GetColorBegin () const { return m_VertexData.MakeStrideIterator<ColorRGBA32> (kShaderChannelColor); } + StrideIterator<ColorRGBA32> GetColorEnd () const { return m_VertexData.MakeEndIterator<ColorRGBA32> (kShaderChannelColor); } + + StrideIterator<Vector2f> GetUvBegin (int uvIndex = 0) const { return m_VertexData.MakeStrideIterator<Vector2f> ((ShaderChannel)(kShaderChannelTexCoord0 + uvIndex)); } + StrideIterator<Vector2f> GetUvEnd (int uvIndex = 0) const { return m_VertexData.MakeEndIterator<Vector2f> ((ShaderChannel)(kShaderChannelTexCoord0 + uvIndex)); } + + StrideIterator<Vector4f> GetTangentBegin () const { return m_VertexData.MakeStrideIterator<Vector4f> (kShaderChannelTangent); } + StrideIterator<Vector4f> GetTangentEnd () const { return m_VertexData.MakeEndIterator<Vector4f> (kShaderChannelTangent); } + + void ExtractVertexArray (Vector3f* destination) const; + void ExtractNormalArray (Vector3f* destination) const; + void ExtractColorArray (ColorRGBA32* destination) const; + void ExtractColorArrayConverting (ColorRGBAf* destination) const; + void ExtractUvArray (int uvIndex, Vector2f* destination) const; + void ExtractTangentArray (Vector4f* destination) const; + + void SetVertices (Vector3f const* data, size_t count); + void SetNormals (Vector3f const* data, size_t count); + void SetTangents (Vector4f const* data, size_t count); + void SetUv (int uvIndex, Vector2f const* data, size_t count); + void SetColors (ColorRGBA32 const* data, size_t count); + void SetColorsConverting (ColorRGBAf const* data, size_t count); + + bool GetVertexColorsSwizzled() const { return m_VertexColorsSwizzled; } + void SetVertexColorsSwizzled(bool flag) { m_VertexColorsSwizzled = flag; } + bool HasVertexData () const { return m_VertexData.GetDataPtr () != NULL; } + void* GetVertexDataPointer () const { return m_VertexData.GetDataPtr (); } + size_t GetVertexDataSize () const { return m_VertexData.GetDataSize (); } + size_t GetVertexSize () const { return m_VertexData.GetVertexSize(); } + + const void* GetChannelPointer (ShaderChannel channel) const { return m_VertexData.GetDataPtr () + m_VertexData.GetChannelOffset (channel); } + void* GetChannelPointer (ShaderChannel channel) { return m_VertexData.GetDataPtr () + m_VertexData.GetChannelOffset (channel); } + void* GetChannelPointer (ShaderChannel channel, size_t offsetInElements) { return m_VertexData.GetDataPtr () + m_VertexData.GetChannelOffset (channel) + offsetInElements * m_VertexData.GetChannelStride(channel); } + size_t GetStride (ShaderChannel channel) const { return m_VertexData.GetChannelStride(channel); } + + bool IsAvailable (ShaderChannel channel) const { return m_VertexData.HasChannel (channel); } + // returns a bitmask of a newly created channels + UInt32 ResizeVertices (size_t count, UInt32 shaderChannels, const VertexStreamsLayout& streams, const VertexChannelsLayout& channels); + UInt32 ResizeVertices (size_t count, UInt32 shaderChannels) { return ResizeVertices(count, shaderChannels, GetStreamsLayout(), GetChannelsLayout()); } + + // returns a bitmask of a newly created channels + UInt32 FormatVertices (UInt32 shaderChannels); + // initializes the specified channels to default values + void InitChannelsToDefault (unsigned begin, unsigned count, unsigned shaderChannels); + + bool SetBoneWeights (const BoneInfluence* v, int count); + const BoneInfluence* GetBoneWeights () const { return m_Skin.empty() ? NULL : &m_Skin[0]; } + BoneInfluence* GetBoneWeights () { return m_Skin.empty() ? NULL : &m_Skin[0]; } + void ClearSkinCache (); + int GetMaxBoneIndex (); + + const Matrix4x4f* GetBindposes () const { return m_Bindpose.empty() ? NULL : &m_Bindpose[0]; } + int GetBindposeCount () const { return m_Bindpose.size(); } + void SetBindposes (const Matrix4x4f* bindposes, int count); + + bool SetIndices (const UInt32* indices, unsigned count, unsigned submesh, GfxPrimitiveType topology); + bool SetIndices (const UInt16* indices, unsigned count, unsigned submesh, GfxPrimitiveType topology); + + void GetTriangles (TemporaryIndexContainer& triangles, unsigned submesh) const; + void GetTriangles (TemporaryIndexContainer& triangles) const; + void AppendTriangles (TemporaryIndexContainer& triangles, unsigned submesh) const; + void GetStrips (TemporaryIndexContainer& triangles, unsigned submesh) const; + void GetIndices (TemporaryIndexContainer& triangles, unsigned submesh) const; + + enum { + k16BitIndices = 1 << 0, + kRebuildCollisionTriangles = 1 << 2, + kDontAssignIndices = 1 << 3, + kDontSupportSubMeshVertexRanges = 1 << 4 + }; + bool SetIndicesComplex (const void* indices, unsigned count, unsigned submesh, GfxPrimitiveType topology, int mode); + + bool ExtractTriangle (UInt32 face, UInt32* indices) const; + + void SetSubMeshCount (unsigned int count); + size_t GetSubMeshCount () const; + + void UpdateSubMeshVertexRange (int index); + + void AddObjectUser( ListNode<Object>& node ) { m_ObjectUsers.push_back(node); } + void AddIntermediateUser( ListNode<IntermediateRenderer>& node ) { m_IntermediateUsers.AddUser(node); } + + const BlendShapeData& GetBlendShapeData() const { return m_Shapes; } + size_t GetBlendShapeChannelCount() const { return m_Shapes.channels.size(); } + void SwapBlendShapeData (BlendShapeData& shapes); + + + BlendShapeData& GetWriteBlendShapeDataInternal() { return m_Shapes; } + + + void CheckConsistency(); + +#if ENABLE_MULTITHREADED_CODE + void SetCurrentCPUFence( UInt32 fence ) { m_CurrentCPUFence = fence; m_WaitOnCPUFence = true; } +#endif + + void WaitOnRenderThreadUse(); + + static Mesh& GetInstantiatedMesh (Mesh* mesh, Object& owner); + + void CopyTransformed (const Mesh& mesh, const Matrix4x4f& transform); + + void SetChannelsDirty (unsigned vertexChannelsChanged, bool indices); + + void* GetSharedNxMesh (); + void* GetSharedNxConvexMesh (); + + void RebuildCollisionTriangles(); + + const SubMesh& GetSubMeshFast (unsigned int submesh) const + { + DebugAssertIf(submesh >= m_SubMeshes.size()); + return m_SubMeshes[submesh]; + } + SubMesh& GetSubMeshFast (unsigned int submesh) + { + DebugAssertIf(submesh >= m_SubMeshes.size()); + return m_SubMeshes[submesh]; + } + + const UInt16* GetSubMeshBuffer16 (int submesh) const; + UInt16* GetSubMeshBuffer16 (int submesh); + + int GetSubMeshBufferByteSize (int submesh) const { return kVBOIndexSize * m_SubMeshes[submesh].indexCount; } + + // The number of indices contained in the index buffer (all submeshes) + int GetTotalndexCount () const; + + void ByteSwapIndices (); + + /// 4, 2, 1 bone influence (BoneInfluence, BoneInfluence2, int) + void* GetSkinInfluence (int count); + + int GetMeshUsageFlags () const { return m_MeshUsageFlags; } + + virtual bool ShouldIgnoreInGarbageDependencyTracking (); + + UInt32 GetAvailableChannels() const; + // May return only a subset of channels that are present in the mesh + UInt32 GetAvailableChannelsForRendering() const; + UInt32 GetChannelsInVBO() const { return m_ChannelsInVBO; } + + bool IsSuitableSizeForDynamicBatching () const; + + // Calculate cached bone bounds per bone by calculating the bounding volume in bind pose space. + // This is used by the SkinnedMeshRenderer to compute an accurate world space bounding volume quickly. + const AABBContainer& GetCachedBonesBounds(); + + void DestripifyIndices (); + void SetHideFromRuntimeStats(bool flag) { m_HideFromRuntimeStats = flag; } + + bool IsSharedPhysicsMeshDirty () { return m_CollisionMesh.IsSharedPhysicsMeshDirty(); } + + bool CanAccessFromScript() const; + + const VertexData& GetVertexData() const { return m_VertexData; } + VertexData& GetVertexData() { return m_VertexData; } + + UInt8 GetMeshCompression() const { return m_MeshCompression; } + void SetMeshCompression(UInt8 mc) { m_MeshCompression = mc; } + + enum + { + kStreamCompressionDefault = 0, + kStreamCompressionCompressed, + kStreamCompressionCompressedAggressive + }; + + UInt8 GetStreamCompression() const { return m_StreamCompression; } + void SetStreamCompression(UInt8 cs) { m_StreamCompression = cs; } + bool GetIsReadable() const { return m_IsReadable; } + void SetIsReadable(bool readable) { m_IsReadable = readable; } + + + bool GetKeepVertices() const { return m_KeepVertices; } + void SetKeepVertices(bool keep) { m_KeepVertices = keep; } + + bool GetKeepIndices() const { return m_KeepIndices; } + void SetKeepIndices(bool keep) { m_KeepIndices = keep; } + + const IndexContainer& GetIndexBuffer() const { return m_IndexBuffer; } + IndexContainer& GetIndexBuffer() { return m_IndexBuffer; } + + const SubMeshContainer& GetSubMeshes() const { return m_SubMeshes; } + SubMeshContainer& GetSubMeshes() { return m_SubMeshes; } + + const MatrixContainer& GetBindpose() const { return m_Bindpose; } + MatrixContainer& GetBindpose() { return m_Bindpose; } + + const dynamic_array<BindingHash>& GetBonePathHashes() const { return m_BonePathHashes; } + dynamic_array<BindingHash>& GetBonePathHashes() { return m_BonePathHashes; } + BindingHash GetRootBonePathHash() const { return m_RootBonePathHash; } + void SetRootBonePathHash(BindingHash val) { m_RootBonePathHash = val; } + + const BoneInfluenceContainer& GetSkin() const { return m_Skin; } + BoneInfluenceContainer& GetSkin() { return m_Skin; } + + const AABB& GetLocalAABB() const { return m_LocalAABB; } + void SetLocalAABB(const AABB& aabb) { m_LocalAABB = aabb; } + +#if UNITY_PS3 || UNITY_EDITOR + MeshPartitionContainer m_Partitions; + MeshPartitionInfoContainer m_PartitionInfos; +#endif + + +#if UNITY_EDITOR + void SetMeshOptimized(bool meshOptimized) { m_MeshOptimized = meshOptimized; } + bool GetMeshOptimized() const { return m_MeshOptimized; } +#endif + + UInt32 GetInternalMeshID() const { Assert(m_InternalMeshID); return m_InternalMeshID; } + +private: + void CreateSharedVBO( UInt32 wantedChannels ); + void NotifyObjectUsers( const MessageIdentifier& msg ); + void RecalculateSubmeshBoundsInternal (unsigned submesh); + void RecalculateBoundsInternal (); + void LoadDeprecatedTangentData (Mesh& mesh, DeprecatedTangentsArray &tangents); + void SwizzleVertexColorsIfNeeded (); + + const VertexStreamsLayout& GetStreamsLayout() const; + const VertexChannelsLayout& GetChannelsLayout() const; + + void DestripifySubmeshOnTransferInternal(); + void SetIndexData(int submeshIndex, int indexCount, const void* indices, GfxPrimitiveType topology, int mode); + +#if SUPPORT_SERIALIZED_TYPETREES + template<class TransferFunction> + void TransferWorkaround35SerializeFuckup (TransferFunction& transfer); +#endif + +#if UNITY_EDITOR || UNITY_PS3 + template<class TransferFunction> + void TransferPS3Data (TransferFunction& transfer); +#endif +#if UNITY_EDITOR + bool m_MeshOptimized; +#endif + + VertexData m_VertexData; + + UInt8 m_MeshCompression; + UInt8 m_StreamCompression; + bool m_IsReadable; + bool m_KeepVertices; + bool m_KeepIndices; + UInt32 m_InternalMeshID; + + int m_MeshUsageFlags; + + IndexContainer m_IndexBuffer; + SubMeshContainer m_SubMeshes; + MatrixContainer m_Bindpose; + BlendShapeData m_Shapes; + + dynamic_array<BindingHash> m_BonePathHashes; + BindingHash m_RootBonePathHash; + + AABBContainer m_CachedBonesAABB; + + BoneInfluenceContainer m_Skin; + BoneInfluence2Container m_CachedSkin2; + SkinContainer m_CachedSkin1; + + int m_MaxBoneIndex; + + AABB m_LocalAABB; + + CollisionMeshData m_CollisionMesh; + + typedef List< ListNode<Object> > ObjectList; + ObjectList m_ObjectUsers; // Object-derived users of this mesh + + IntermediateUsers m_IntermediateUsers; // IntermediateRenderer users of this mesh + + #if ENABLE_MULTITHREADED_CODE + UInt32 m_CurrentCPUFence; + bool m_WaitOnCPUFence; + #endif + + PPtr<Object> m_Owner; + VBO* m_VBO; + + + UInt32 m_ChannelsInVBO; + bool m_VerticesDirty; + bool m_IndicesDirty; + bool m_IsDynamic; + bool m_HideFromRuntimeStats; + bool m_VertexColorsSwizzled; + + friend class MeshFilter; + friend class ClothAnimator; + friend class CompressedMesh; + friend void PartitionSubmeshes (Mesh& m); + friend void OptimizeReorderVertexBuffer (Mesh& mesh); +}; + +#endif diff --git a/Runtime/Filters/Mesh/LodMeshFilter.cpp b/Runtime/Filters/Mesh/LodMeshFilter.cpp new file mode 100644 index 0000000..512f153 --- /dev/null +++ b/Runtime/Filters/Mesh/LodMeshFilter.cpp @@ -0,0 +1,96 @@ +#include "UnityPrefix.h" +#include "LodMeshFilter.h" +#include "LodMesh.h" +#include "MeshRenderer.h" +#include "Runtime/Filters/Particles/MeshParticleEmitter.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h" + +MeshFilter::MeshFilter (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_Mesh = NULL; +} + +MeshFilter::~MeshFilter () +{ +} + +void MeshFilter::OnDidAddMesh () +{ + AssignMeshToRenderer (); +} + +void MeshFilter::AssignMeshToRenderer () +{ + if (GetGameObjectPtr()) + { + MeshRenderer* renderer = QueryComponent(MeshRenderer); + if (renderer && renderer->GetSharedMesh() != m_Mesh) + renderer->SetSharedMesh(m_Mesh); + + MeshParticleEmitter* emitter = QueryComponent(MeshParticleEmitter); + if (emitter && emitter->GetMesh() != m_Mesh) + emitter->SetMesh(m_Mesh); + } +} + +void MeshFilter::SetSharedMesh (PPtr<Mesh> mesh) +{ + m_Mesh = mesh; + + MeshRenderer* renderer = QueryComponent(MeshRenderer); + if (renderer) + renderer->SetSharedMesh(m_Mesh); + + MeshParticleEmitter* emitter = QueryComponent(MeshParticleEmitter); + if (emitter) + emitter->SetMesh(m_Mesh); + + SetDirty (); +} + +PPtr<Mesh> MeshFilter::GetSharedMesh () +{ + return m_Mesh; +} + +Mesh* MeshFilter::GetInstantiatedMesh () +{ + Mesh* instantiated = &Mesh::GetInstantiatedMesh (m_Mesh, *this); + if (PPtr<Mesh> (instantiated) != m_Mesh) + { + SetSharedMesh(instantiated); + } + + return instantiated; +} + +void MeshFilter::SetInstantiatedMesh (Mesh* mesh) +{ + SetSharedMesh(mesh); +} + +IMPLEMENT_CLASS_HAS_INIT (MeshFilter) +IMPLEMENT_OBJECT_SERIALIZE (MeshFilter) + +template<class TransferFunction> inline +void MeshFilter::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.Transfer (m_Mesh, "m_Mesh", kSimpleEditorMask); +} + +void MeshFilter::InitializeClass () +{ + RegisterAllowNameConversion(GetClassStringStatic(), "m_LodMesh", "m_Mesh"); + RegisterAllowTypeNameConversion ("PPtr<LodMesh>", "PPtr<Mesh>"); + + REGISTER_MESSAGE_VOID(MeshFilter, kDidAddComponent, OnDidAddMesh); +} + +void MeshFilter::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + AssignMeshToRenderer (); +} diff --git a/Runtime/Filters/Mesh/LodMeshFilter.h b/Runtime/Filters/Mesh/LodMeshFilter.h new file mode 100644 index 0000000..ff6273b --- /dev/null +++ b/Runtime/Filters/Mesh/LodMeshFilter.h @@ -0,0 +1,38 @@ +#ifndef LODMESHFILTER_H +#define LODMESHFILTER_H + +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Modules/ExportModules.h" + +class Mesh; + +class EXPORT_COREMODULE MeshFilter : public Unity::Component +{ +public: + REGISTER_DERIVED_CLASS (MeshFilter, Unity::Component) + DECLARE_OBJECT_SERIALIZE (MeshFilter) + + MeshFilter (MemLabelId label, ObjectCreationMode mode); + + void SetSharedMesh (PPtr<Mesh> mesh); + PPtr<Mesh> GetSharedMesh (); + + Mesh* GetInstantiatedMesh (); + void SetInstantiatedMesh (Mesh* mesh); + + static void InitializeClass (); + static void CleanupClass () {} + + void OnDidAddMesh (); + +protected: + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + + +private: + void AssignMeshToRenderer (); + + PPtr<Mesh> m_Mesh; +}; + +#endif diff --git a/Runtime/Filters/Mesh/Mesh.h b/Runtime/Filters/Mesh/Mesh.h new file mode 100644 index 0000000..e6b58dc --- /dev/null +++ b/Runtime/Filters/Mesh/Mesh.h @@ -0,0 +1,76 @@ +#ifndef MESH_H +#define MESH_H + +#include <vector> +#include "Runtime/Serialize/SerializeUtility.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Misc/Allocator.h" + +class Quaternionf; + +/// A face in the mesh. +struct Face { + UInt16 v1, v2, v3; + Face (UInt16 vert1, UInt16 vert2, UInt16 vert3) + {v1 = vert1; v2 = vert2; v3 = vert3;} + Face () {} + + UInt16 &operator[] (int i) { return (&v1)[i]; } + UInt16 operator[] (int i) const { return (&v1)[i]; } + + DECLARE_SERIALIZE_OPTIMIZE_TRANSFER (Face) +}; + +template<class TransferFunc> +void Face::Transfer (TransferFunc& transfer) +{ + TRANSFER (v1); + TRANSFER (v2); + TRANSFER (v3); +} + +struct DeprecatedTangent +{ + Vector3f normal; + Vector3f tangent; + float handedness; + DECLARE_SERIALIZE_OPTIMIZE_TRANSFER (Tangent) +}; + +template<class TransferFunc> +void DeprecatedTangent::Transfer (TransferFunc& transfer) +{ + TRANSFER (normal); + TRANSFER (tangent); + TRANSFER (handedness); +} + +struct BoneInfluence +{ + float weight[4]; + int boneIndex[4]; + + DECLARE_SERIALIZE_OPTIMIZE_TRANSFER (BoneInfluence) +}; + +struct BoneInfluence2 +{ + float weight[2]; + int boneIndex[2]; +}; + +template<class TransferFunc> +void BoneInfluence::Transfer (TransferFunc& transfer) +{ + TRANSFER (weight[0]); + TRANSFER (weight[1]); + TRANSFER (weight[2]); + TRANSFER (weight[3]); + + TRANSFER (boneIndex[0]); + TRANSFER (boneIndex[1]); + TRANSFER (boneIndex[2]); + TRANSFER (boneIndex[3]); +} + +#endif diff --git a/Runtime/Filters/Mesh/MeshBlendShape.cpp b/Runtime/Filters/Mesh/MeshBlendShape.cpp new file mode 100644 index 0000000..c7588e2 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshBlendShape.cpp @@ -0,0 +1,234 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" +#include "MeshBlendShape.h" +#include "Runtime/mecanim/generic/crc32.h" + +static const float kVertexDeltaEpsilon = 1e-5f; +static const float kNormalDeltaEpsilon = 1e-5f; + +void SetBlendShapeVertices(const std::vector<Vector3f>& deltaVertices, const std::vector<Vector3f>& deltaNormals, const std::vector<Vector3f>& deltaTangents, BlendShapeVertices& sharedSparceVertices, BlendShape& frame) +{ + Assert(deltaNormals.empty() || deltaVertices.size() == deltaNormals.size()); + Assert(deltaTangents.empty() || deltaVertices.size() == deltaTangents.size()); + + frame.firstVertex = sharedSparceVertices.size(); + + // Converting blend shape in to sparse blend shape + sharedSparceVertices.reserve(sharedSparceVertices.size() + deltaVertices.size()); + + frame.hasNormals = frame.hasTangents = false; + + for (int j = 0; j < deltaVertices.size(); ++j) + { + const bool vertexHasNormal = (!deltaNormals.empty() && Magnitude(deltaNormals[j]) > kNormalDeltaEpsilon); + const bool vertexHasTangent = (!deltaTangents.empty() && Magnitude(deltaTangents[j]) > kNormalDeltaEpsilon); + + frame.hasNormals = frame.hasNormals || vertexHasNormal; + frame.hasTangents = frame.hasTangents || vertexHasTangent; + + if (Magnitude(deltaVertices[j]) > kVertexDeltaEpsilon || vertexHasNormal || vertexHasTangent) + { + BlendShapeVertex v; + + v.vertex = deltaVertices[j]; + if (!deltaNormals.empty()) + v.normal = deltaNormals[j]; + if (!deltaTangents.empty()) + v.tangent = deltaTangents[j]; + + v.index = j; + sharedSparceVertices.push_back(v); + } + } + + frame.vertexCount = sharedSparceVertices.size() - frame.firstVertex; +} + +void BlendShape::UpdateFlags(const BlendShapeVertices& sharedSparceVertices) +{ + hasNormals = hasTangents = false; + + for (int j = 0; j < vertexCount; ++j) + { + const BlendShapeVertex& v = sharedSparceVertices[firstVertex + j]; + const bool vertexHasNormal = Magnitude(v.normal) > kNormalDeltaEpsilon; + const bool vertexHasTangent = Magnitude(v.tangent) > kNormalDeltaEpsilon; + + hasNormals = hasNormals || vertexHasNormal; + hasTangents = hasTangents || vertexHasTangent; + } +} + +void InitializeChannel (const UnityStr& inName, int frameIndex, int frameCount, BlendShapeChannel& channel) +{ + channel.name.assign(inName.c_str(), kMemGeometry); + channel.nameHash = mecanim::processCRC32(inName.c_str()); + channel.frameIndex = frameIndex; + channel.frameCount = frameCount; +} + +const char* GetChannelName (const BlendShapeData& data, int index) +{ + return data.channels[index].name.c_str(); +} + +int GetChannelIndex (const BlendShapeData& data, const char* name) +{ + for (int i=0;i<data.channels.size();i++) + { + if (name == data.channels[i].name) + return i; + } + return -1; +} + +int GetChannelIndex (const BlendShapeData& data, BindingHash name) +{ + for (int i=0;i<data.channels.size();i++) + { + if (name == data.channels[i].nameHash) + return i; + } + return -1; +} + +void ClearBlendShapes (BlendShapeData& data) +{ + data.vertices.clear(); + data.shapes.clear(); + data.channels.clear(); + data.fullWeights.clear(); +} + +/* + +STRUCT BlendShapeChannel + +// BlendShape vertex class. +STRUCT Vertex +// Vertex delta. +CSRAW public Vector3 vertex; + +// Normal delta. +CSRAW public Vector3 normal; + +// Tangent delta. +CSRAW public Vector3 tangent; + +// Index to [[Mesh]] vertex data. +CSRAW public int index; +END + +// A class representing a single BlendShape (also called morph-target). +STRUCT BlendShape + +// The weight of the frame +CSRAW public float weight; + +// Sparse vertex data. +CSRAW public Vertex[] vertices; +END + +// Name of the BlendShape. +CSRAW public string name; + +// The frames making up a blendshape animation. +// Each frame has a weight, based on the weight of the BlendShape in the SkinnedMeshRenderer, Unity will apply 1 or 2 frames. +CSRAW public BlendShape[] shapes; +END + + +C++RAW +/* + struct MonoMeshBlendShape + { + ScriptingStringPtr name; + ScriptingArrayPtr vertices; + }; + + void BlendShapeVertexToMono (const BlendShapeVertex &src, MonoBlendShapeVertex &dest) { + dest.vertex = src.vertex; + dest.normal = src.normal; + dest.tangent = src.tangent; + dest.index = src.index; + } + void BlendShapeVertexToCpp (const MonoBlendShapeVertex &src, BlendShapeVertex &dest) { + dest.vertex = src.vertex; + dest.normal = src.normal; + dest.tangent = src.tangent; + dest.index = src.index; + } + + class MeshBlendShapeToMono + { + public: + MeshBlendShapeToMono(const BlendShapeVertices& sharedVertices_) : sharedVertices(sharedVertices_) {} + + void operator() (const MeshBlendShape &src, MonoMeshBlendShape &dest) + { + dest.name = scripting_string_new(src.m_Name); + const BlendShapeVertices vertices(sharedVertices.begin() + src.firstVertex, sharedVertices.begin() + src.firstVertex + src.vertexCount); + + ScriptingTypePtr classVertex = GetScriptingTypeRegistry().GetType("UnityEngine", "BlendShapeVertex"); + dest.vertices = VectorToScriptingStructArray<BlendShapeVertex, MonoBlendShapeVertex>(vertices, classVertex, BlendShapeVertexToMono); + } + + private: + const BlendShapeVertices& sharedVertices; + }; + + class MeshBlendShapeToCpp + { + public: + MeshBlendShapeToCpp(int meshVertexCount_, BlendShapeVertices& sharedVertices_) : meshVertexCount(meshVertexCount_), sharedVertices(sharedVertices_) {} + + void operator() (MonoMeshBlendShape &src, MeshBlendShape &dest) + { + dest.weight = src.weight; + + const BlendShapeVertex* vertices = Scripting::GetScriptingArrayStart<BlendShapeVertex> (src.vertices); + sharedVertices.insert(sharedVertices.end(), vertices, vertices + GetScriptingArraySize(src.vertices)); + + for (BlendShapeVertices::iterator it = vertices.begin(), end = vertices.end(); it != end; ++it) + { + BlendShapeVertex& v = *it; + if (v.index < 0 || v.index >= meshVertexCount) + { + ErrorStringMsg("Value (%d) of BlendShapeVertex.index #%d is out of bounds (Mesh vertex count: %d) on BlendShape '%s'. It will be reset to 0.", v.index, it - vertices.begin(), meshVertexCount, dest.m_Name.c_str()); + v.index = 0; + } + } + + dest.firstVertex = sharedVertices.size(); + dest.vertexCount = vertices.size(); + + sharedVertices.insert(sharedVertices.end(), vertices.begin(), vertices.end()); + dest.UpdateFlags(sharedVertices); + } + + private: + int meshVertexCount; + BlendShapeVertices& sharedVertices; + }; + + + + ---------------- + + // BlendShapes for this mesh. + CUSTOM_PROP BlendShapeChannel[] blendShapes + { + // ScriptingTypePtr classBlendShape = GetScriptingTypeRegistry().GetType("UnityEngine", "MeshBlendShape"); + // return VectorToScriptingStructArray<MeshBlendShape, MonoMeshBlendShape>(self->GetShapesVector(), classBlendShape, MeshBlendShapeToMono(self->GetShapeVertexVector())); + return SCRIPTING_NULL; + } + { + // Mesh::MeshBlendShapeContainer shapes; + // self->GetShapeVertexVector().clear(); + // ScriptingStructArrayToVector<MeshBlendShape, MonoMeshBlendShape>(value, shapes, MeshBlendShapeToCpp(self->GetVertexCount(), self->GetShapeVertexVector())); + // self->SwapShapesVector(shapes); + } + + + + */ diff --git a/Runtime/Filters/Mesh/MeshBlendShape.h b/Runtime/Filters/Mesh/MeshBlendShape.h new file mode 100644 index 0000000..d4d0f41 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshBlendShape.h @@ -0,0 +1,115 @@ +#ifndef MESHBLENDSHAPES_H +#define MESHBLENDSHAPES_H + +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Serialize/SerializeUtility.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Containers/ConstantString.h" +#include "Runtime/Containers/ConstantStringSerialization.h" + +typedef UInt32 BindingHash; + +struct BlendShapeVertex +{ + // vertex, normal & tangent are stored as deltas + Vector3f vertex; + Vector3f normal; + Vector3f tangent; + UInt32 index; + + BlendShapeVertex() : vertex(Vector3f::zero), normal(Vector3f::zero), tangent(Vector3f::zero), index(0) {} + + DECLARE_SERIALIZE_NO_PPTR (BlendShapeVertex) +}; +typedef dynamic_array<BlendShapeVertex> BlendShapeVertices; + +struct BlendShapeChannel +{ + ConstantString name; + BindingHash nameHash; + + int frameIndex; + int frameCount; + + DECLARE_SERIALIZE_NO_PPTR(MeshBlendShapeChannel) +}; + +struct BlendShape +{ + BlendShape() : firstVertex(0), vertexCount(0), hasNormals(false), hasTangents(false) {} + + UInt32 firstVertex; + UInt32 vertexCount; + + bool hasNormals; + bool hasTangents; + + + ///@TODO: MOve + // updates hasNormals and hasTangents based on data in vertices + void UpdateFlags(const BlendShapeVertices& sharedSparceVertices); + + DECLARE_SERIALIZE_NO_PPTR (MeshBlendShape) +}; + +struct BlendShapeData +{ + BlendShapeVertices vertices; + dynamic_array<BlendShape> shapes; + std::vector<BlendShapeChannel> channels; + dynamic_array<float> fullWeights; + + DECLARE_SERIALIZE_NO_PPTR(BlendShapeData) +}; + + +// Convert between blendshape name and index +const char* GetChannelName (const BlendShapeData& data, int index); +inline size_t GetBlendShapeChannelCount (const BlendShapeData& data) { return data.channels.size(); } +int GetChannelIndex (const BlendShapeData& data, const char* name); +int GetChannelIndex (const BlendShapeData& data, BindingHash name); + +// data is passed as non-sparce arrays, i.e. deltaVertices.size() has to be the same as vertex count on the Mesh +void SetBlendShapeVertices(const std::vector<Vector3f>& deltaVertices, const std::vector<Vector3f>& deltaNormals, const std::vector<Vector3f>& deltaTangents, BlendShapeVertices& sharedSparceVertices, BlendShape& frame); +void InitializeChannel (const UnityStr& inName, int frameIndex, int frameCount, BlendShapeChannel& channel); +void ClearBlendShapes (BlendShapeData& data); + +template<class TransferFunc> +void BlendShape::Transfer (TransferFunc& transfer) +{ + TRANSFER(firstVertex); + TRANSFER(vertexCount); + TRANSFER(hasNormals); + TRANSFER(hasTangents); + transfer.Align(); +} + +template<class TransferFunc> +void BlendShapeData::Transfer (TransferFunc& transfer) +{ + TRANSFER (vertices); + TRANSFER (shapes); + TRANSFER (channels); + TRANSFER (fullWeights); +} + +template<class TransferFunc> +void BlendShapeVertex::Transfer (TransferFunc& transfer) +{ + TRANSFER(vertex); + TRANSFER(normal); + TRANSFER(tangent); + TRANSFER(index); +} + +template<class TransferFunc> +void BlendShapeChannel::Transfer (TransferFunc& transfer) +{ + TransferConstantString (name, "name", kNoTransferFlags, kMemGeometry, transfer); + TRANSFER (nameHash); + TRANSFER (frameIndex); + TRANSFER (frameCount); +} + +#endif diff --git a/Runtime/Filters/Mesh/MeshBlendShaping.cpp b/Runtime/Filters/Mesh/MeshBlendShaping.cpp new file mode 100644 index 0000000..a86a24d --- /dev/null +++ b/Runtime/Filters/Mesh/MeshBlendShaping.cpp @@ -0,0 +1,184 @@ +#include "UnityPrefix.h" +#include "MeshBlendShaping.h" +#include "MeshSkinning.h" +#include "MeshBlendShape.h" + +template<bool skinNormal, bool skinTangent> +void ApplyBlendShapeTmpl (const BlendShapeVertex* vertices, size_t vertexCount, size_t dstVertexCount, float weight, int normalOffset, int tangentOffset, int inStride, UInt8* dst) +{ + for (int i = 0; i < vertexCount; ++i) + { + const BlendShapeVertex& blendShapeVertex = vertices[i]; + + int offset = inStride * blendShapeVertex.index; + + *reinterpret_cast<Vector3f*>(dst + offset) += blendShapeVertex.vertex * weight; + if (skinNormal) + { + DebugAssert (offset + normalOffset < inStride * dstVertexCount); + *reinterpret_cast<Vector3f*>(dst + offset + normalOffset) += blendShapeVertex.normal * weight; + } + if (skinTangent) + { + DebugAssert (offset + tangentOffset < inStride * dstVertexCount); + *reinterpret_cast<Vector3f*>(dst + offset + tangentOffset) += blendShapeVertex.tangent * weight; + } + } +} + + +void ApplyBlendShape (const BlendShape& target, const BlendShapeVertices& vertices, float weight, const SkinMeshInfo& info, UInt8* dst) +{ + if (!HasValidWeight(weight)) + return; + + weight = std::min(weight, 1.0F); + + const BlendShapeVertex* v = vertices.begin() + target.firstVertex; + + if (info.skinNormals && info.skinTangents && target.hasNormals && target.hasTangents) + ApplyBlendShapeTmpl<true, true> (v, target.vertexCount, info.vertexCount, weight, info.normalOffset, info.tangentOffset, info.inStride, dst); + else if (info.skinNormals && target.hasNormals) + ApplyBlendShapeTmpl<true, false> (v, target.vertexCount, info.vertexCount, weight, info.normalOffset, info.tangentOffset, info.inStride, dst); + else + ApplyBlendShapeTmpl<false, false> (v, target.vertexCount, info.vertexCount, weight, info.normalOffset, info.tangentOffset, info.inStride, dst); +} + +static int FindFrame (const float* weights, size_t count, float targetWeight) +{ + // Find frame (left index) + int frame = 0; + while (frame < count-1 && targetWeight > weights[frame+1]) + frame++; + + return frame; +} + +void ApplyBlendShapes (SkinMeshInfo& info, UInt8* dst) +{ + DebugAssert (info.blendshapeCount != 0); + Assert (info.inStride == info.outStride); + const int inStride = info.inStride; + const int count = info.vertexCount; + + Assert (dst); + memcpy (dst, info.inVertices, inStride * count); + + const BlendShapeData& blendShapeData = *info.blendshapes; + + for (int c = 0; c < info.blendshapeCount; ++c) + { + const float targetWeight = info.blendshapeWeights[c]; + + if (!HasValidWeight (targetWeight)) + continue; + + const BlendShapeChannel& channel = blendShapeData.channels[c]; + Assert(channel.frameCount != 0); + + const BlendShape* blendShapeFrames = &blendShapeData.shapes[channel.frameIndex]; + const float* weights = &blendShapeData.fullWeights[channel.frameIndex]; + + // The first blendshape does not need to do any blending. Just fade it in. + if (targetWeight < weights[0] || channel.frameCount == 1) + { + float lhsShapeWeight = weights[0]; + ApplyBlendShape (blendShapeFrames[0], blendShapeData.vertices, targetWeight / lhsShapeWeight, info, dst); + } + // We are blending with two frames + else + { + // Find the frame we are blending with + int frame = FindFrame(weights, channel.frameCount, targetWeight); + + float lhsShapeWeight = weights[frame + 0]; + float rhsShapeWeight = weights[frame + 1]; + + float relativeWeight = (targetWeight - lhsShapeWeight) / (rhsShapeWeight - lhsShapeWeight); + + ApplyBlendShape (blendShapeFrames[frame + 0], blendShapeData.vertices, 1.0F - relativeWeight, info, dst); + ApplyBlendShape (blendShapeFrames[frame + 1], blendShapeData.vertices, relativeWeight, info, dst); + } + } +} + +///@TODO: How do we deal with resizing vertex count once mesh blendshapes have been created??? + +/* + template<bool skinNormal, bool skinTangent> + static void ApplyBlendShapesTmpl (SkinMeshInfo& info, UInt8* dst) + { + DebugAssert (info.blendshapeCount != 0); + Assert (info.inStride == info.outStride); + const int inStride = info.inStride; + const int count = info.vertexCount; + + Assert (dst); + memcpy (dst, info.inVertices, inStride * count); + + const int normalOffset = info.normalOffset; + const int tangentOffset = info.tangentOffset; + + #if BLEND_DIRECT_NORMALS + if (skinNormal) + { // figure out how what fraction of original normal should be used + float totalBlendshapeWeight = 0.0f; + for (int i = 0; i < info.blendshapeCount; ++i) + totalBlendshapeWeight += info.blendshapeWeights[i]; + Assert (totalBlendshapeWeight >= 0.0f); + if (totalBlendshapeWeight > 0.0f) + { + for (int i = 0; i < count; ++i) + *reinterpret_cast<Vector3f*>(dst + i*inStride + normalOffset) *= max(0.0f, (1.0f - totalBlendshapeWeight)); + } + } + + bool atLeastOneSparseBlendshape = false; + #endif + for (int bs = 0; bs < info.blendshapeCount; ++bs) + { + const float w = info.blendshapeWeights[bs]; + + if (HasWeight(w)) + { + const MeshBlendShape& blendShape = info.blendshapes[bs]; + + const BlendShapeVertex* vertices = info.blendshapesVertices + blendShape.firstVertex; + for (int i = 0; i < blendShape.vertexCount; ++i) + { + const BlendShapeVertex& blendShapeVertex = vertices[i]; + + int offset = inStride * blendShapeVertex.index; + Assert (offset < inStride * count); + *reinterpret_cast<Vector3f*>(dst + offset) += blendShapeVertex.vertex * w; + if (skinNormal) + { + Assert (offset + normalOffset < inStride * count); + *reinterpret_cast<Vector3f*>(dst + offset + normalOffset) += blendShapeVertex.normal * w; + } + if (skinTangent) + { + Assert (offset + tangentOffset < inStride * count); + *reinterpret_cast<Vector3f*>(dst + offset + tangentOffset) += blendShapeVertex.tangent * w; + } + } + + #if BLEND_DIRECT_NORMALS + if (vertices.size () < count) + atLeastOneSparseBlendshape = true; + #endif + } + } + + #if BLEND_DIRECT_NORMALS + if (atLeastOneSparseBlendshape && skinNormal) // we might need to take larger fraction from original normal + for (int i = 0; i < count; ++i) + { + Vector3f const& srcNormal = *reinterpret_cast<Vector3f*>((UInt8*)info.inVertices + i*inStride + normalOffset); + Vector3f* dstNormal = reinterpret_cast<Vector3f*>(dst + i*inStride + normalOffset); + const float missingFractionOfNormal = max (0.0f, 1.0f - Magnitude (*dstNormal)); + *dstNormal += srcNormal * missingFractionOfNormal; + } + #endif + } +*/
\ No newline at end of file diff --git a/Runtime/Filters/Mesh/MeshBlendShaping.h b/Runtime/Filters/Mesh/MeshBlendShaping.h new file mode 100644 index 0000000..7b39f26 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshBlendShaping.h @@ -0,0 +1,12 @@ +#pragma once + +struct SkinMeshInfo; + +// Does "mesh skinning" logic for BlendShapes +void ApplyBlendShapes (SkinMeshInfo& info, UInt8* dst); + +inline bool HasValidWeight(const float w) +{ + const float kWeightEpsilon = 1e-4f; + return w > kWeightEpsilon; +} diff --git a/Runtime/Filters/Mesh/MeshCombiner.cpp b/Runtime/Filters/Mesh/MeshCombiner.cpp new file mode 100644 index 0000000..1bf93e5 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshCombiner.cpp @@ -0,0 +1,502 @@ +#include "UnityPrefix.h" +#include "MeshCombiner.h" +#include "Runtime/Graphics/TriStripper.h" +#include "Runtime/Shaders/GraphicsCaps.h" +#include "Runtime/Profiler/Profiler.h" +#include <limits> + + +#define sqr(x) ((x)*(x)) + +PROFILER_INFORMATION(gCombineMeshesProfile, "CombineMeshes", kProfilerRender) +PROFILER_INFORMATION(gCombineVerticesProfile, "CombineVertices", kProfilerRender) +PROFILER_INFORMATION(gCombineIndicesProfile, "CombineIndices", kProfilerRender) + +static void CombineBoneSkinning (const CombineInstances &in, Mesh& outCombinedMesh); + + +size_t ExtractMeshIndices(Mesh::TemporaryIndexContainer& srcIndices, const CombineInstance& in, bool useVertexOffsets, size_t& inoutTotalVertexOffset, UInt16* dstIndices) +{ + srcIndices.clear(); + + if (in.subMeshIndex < 0 || in.subMeshIndex >= in.mesh->GetSubMeshCount()) + return 0; + + const int subMeshIndex = in.subMeshIndex; + const int vertexOffset = useVertexOffsets ? in.vertexOffset : inoutTotalVertexOffset; + inoutTotalVertexOffset += in.mesh->GetVertexCount(); + + in.mesh->GetTriangles( srcIndices, subMeshIndex ); + + size_t numIndices = srcIndices.size(); + if (Dot (Cross(in.transform.GetAxisX(), in.transform.GetAxisY()), in.transform.GetAxisZ()) >= 0) + { + for ( size_t k=0; k!=numIndices; ++k ) + dstIndices[k] = srcIndices[k] + vertexOffset; + } + else + { + // if trilist, then + // reverse Cull order by reversing indices + for ( size_t k=0; k!=numIndices; ++k ) + dstIndices[k] = srcIndices[numIndices-k-1] + vertexOffset; + } + + return numIndices; +} + +static bool IsMeshBatchable (const Mesh* mesh, int subMeshIndex) +{ + return mesh && mesh->HasVertexData() && subMeshIndex >= 0 && subMeshIndex < mesh->GetSubMeshCount(); +} + + +void CombineMeshIndicesForStaticBatching(const CombineInstances& in, Mesh& inoutMesh, bool mergeSubMeshes, bool useVertexOffsets) +{ + PROFILER_AUTO(gCombineIndicesProfile, &inoutMesh); + + size_t size = in.size(); + + UInt32 maxIndices = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const UInt32 numTris = in[i].mesh->GetSubMeshFast( in[i].subMeshIndex ).indexCount; + if (mergeSubMeshes) + maxIndices += numTris; + else + maxIndices = std::max( maxIndices, numTris ); + } + } + + UInt16* dstIndices = new UInt16[maxIndices+1]; + Mesh::TemporaryIndexContainer srcIndices; + srcIndices.reserve( maxIndices+1 ); + + size_t totalVertexOffset = 0; + if (mergeSubMeshes) + { + inoutMesh.SetSubMeshCount( 1 ); + size_t totalNumIndices = 0; + for ( size_t s=0; s!=size; ++s ) + { + if (in[s].mesh) + { + size_t numIndices = ExtractMeshIndices (srcIndices, in[s], useVertexOffsets, totalVertexOffset, dstIndices+totalNumIndices); + + totalNumIndices += numIndices; + Assert(totalNumIndices <= (maxIndices+1)); + } + } + int mask = Mesh::k16BitIndices; + inoutMesh.SetIndicesComplex (dstIndices, totalNumIndices, 0, kPrimitiveTriangles, mask); + } + else + { + inoutMesh.SetSubMeshCount( in.size() ); + for ( size_t s=0; s!=size; ++s ) + { + if (in[s].mesh) + { + size_t numIndices = ExtractMeshIndices (srcIndices, in[s], useVertexOffsets, totalVertexOffset, dstIndices); + Assert(numIndices <= (maxIndices+1)); + + int mask = Mesh::k16BitIndices; + inoutMesh.SetIndicesComplex (dstIndices, numIndices, s, kPrimitiveTriangles, mask); + } + } + } + + delete []dstIndices; +} + +void CombineMeshVerticesForStaticBatching ( const CombineInstances& in, const string& combinedMeshName, Mesh& outCombinedMesh, bool useTransforms ) +{ + PROFILER_AUTO(gCombineVerticesProfile, &outCombinedMesh); + + int vertexCount = 0; + size_t size = in.size(); + for( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + vertexCount += in[i].mesh->GetVertexCount(); + } + + bool hasNormals = false; + bool hasTangents = false; + bool hasUV0 = false; + bool hasUV1 = false; + bool hasColors = false; + bool hasSkin = false; + int bindposeCount = 0; + + for( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + const UInt32 channels = mesh->GetAvailableChannels(); + hasNormals |= (channels & (1<<kShaderChannelNormal)) != 0; + hasTangents |= (channels & (1<<kShaderChannelTangent)) != 0; + hasUV0 |= (channels & (1<<kShaderChannelTexCoord0)) != 0; + hasUV1 |= (channels & (1<<kShaderChannelTexCoord1)) != 0 || (in[i].lightmapTilingOffset != Vector4f(1, 1, 0, 0)); + hasColors |= (channels & (1<<kShaderChannelColor)) != 0; + hasSkin |= mesh->GetSkin().size() && mesh->GetBindpose().size(); + bindposeCount += mesh->GetBindpose().size(); + } + } + + UInt32 channels = 1<<kShaderChannelVertex; + if ( hasNormals ) channels |= 1<<kShaderChannelNormal; + if ( hasTangents ) channels |= 1<<kShaderChannelTangent; + if ( hasUV0 ) channels |= 1<<kShaderChannelTexCoord0; + if ( hasUV1 ) channels |= 1<<kShaderChannelTexCoord1; + if ( hasColors ) channels |= 1<<kShaderChannelColor; + + outCombinedMesh.Clear(true); + outCombinedMesh.ResizeVertices( vertexCount, channels ); + outCombinedMesh.SetName( combinedMeshName.c_str() ); + // Input meshes are already swizzled correctly, so we can copy colors directly + outCombinedMesh.SetVertexColorsSwizzled(gGraphicsCaps.needsToSwizzleVertexColors); + + if ( hasSkin ) + { + outCombinedMesh.GetSkin().resize_initialized(vertexCount); + outCombinedMesh.GetBindpose().resize_initialized(bindposeCount); + outCombinedMesh.GetBonePathHashes().resize_uninitialized(bindposeCount); + } + + // avoid doing twice (in worst case) + Matrix4x4f* normalMatrices; + bool* isNonUniformScaleTransform; + ALLOC_TEMP (normalMatrices, Matrix4x4f, size); + ALLOC_TEMP (isNonUniformScaleTransform, bool, size); + if ( hasNormals || hasTangents ) + { + for( size_t i=0; i!=size; ++i ) + { + float uniformScale; + TransformType type = ComputeTransformType(in[i].transform, uniformScale); + Matrix4x4f m; + isNonUniformScaleTransform[i] = IsNonUniformScaleTransform(type); + if (isNonUniformScaleTransform[i]) + { + Matrix4x4f::Invert_General3D( in[i].transform, normalMatrices[i] ); + normalMatrices[i].Transpose(); + } + else + { + normalMatrices[i] = Matrix3x3f(in[i].transform); + // Scale matrix to keep normals normalized + normalMatrices[i].Scale(Vector3f::one * (1.0f/uniformScale)); + } + } + } + + int offset = 0; + for( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Matrix4x4f& transform = in[i].transform; + const Mesh* mesh = in[i].mesh; + if (useTransforms) + TransformPoints3x4 (transform, + (Vector3f const*)mesh->GetChannelPointer (kShaderChannelVertex), + mesh->GetStride (kShaderChannelVertex), + (Vector3f*)outCombinedMesh.GetChannelPointer (kShaderChannelVertex, offset), + outCombinedMesh.GetStride (kShaderChannelVertex), + mesh->GetVertexCount()); + else + strided_copy (mesh->GetVertexBegin (), mesh->GetVertexEnd (), outCombinedMesh.GetVertexBegin () + offset); + offset += mesh->GetVertexCount(); + } + } + + if ( hasNormals ) + { + offset = 0; + for( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + int vertexCount = mesh->GetVertexCount (); + if (!mesh->IsAvailable (kShaderChannelNormal)) + std::fill(outCombinedMesh.GetNormalBegin () + offset, outCombinedMesh.GetNormalBegin () + offset + vertexCount, Vector3f(0.0f,1.0f,0.0f)); + else + { + const Matrix4x4f& transform = normalMatrices[i]; + + StrideIterator<Vector3f> outNormal = outCombinedMesh.GetNormalBegin () + offset; + if (useTransforms) + { + if (isNonUniformScaleTransform[i]) + { + for (StrideIterator<Vector3f> it = mesh->GetNormalBegin (), end = mesh->GetNormalEnd (); it != end; ++it, ++outNormal) + *outNormal = Normalize( transform.MultiplyVector3( *it) ); + } + else + { + for (StrideIterator<Vector3f> it = mesh->GetNormalBegin (), end = mesh->GetNormalEnd (); it != end; ++it, ++outNormal) + *outNormal = transform.MultiplyVector3( *it); + } + } + else + strided_copy (mesh->GetNormalBegin (), mesh->GetNormalEnd (), outCombinedMesh.GetNormalBegin () + offset); + } + offset += vertexCount; + } + } + } + + if ( hasTangents ) + { + offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + int vertexCount = mesh->GetVertexCount (); + if (!mesh->IsAvailable (kShaderChannelTangent)) + std::fill(outCombinedMesh.GetTangentBegin () + offset, outCombinedMesh.GetTangentBegin () + offset + vertexCount, Vector4f(1.0f,0.0f,0.0f,1.0f)); + else + { + const Matrix4x4f& transform = normalMatrices[i]; + + StrideIterator<Vector4f> outTanget = outCombinedMesh.GetTangentBegin () + offset; + if (useTransforms) + { + if (isNonUniformScaleTransform[i]) + { + for (StrideIterator<Vector4f> it = mesh->GetTangentBegin (), end = mesh->GetTangentEnd (); it != end; ++it, ++outTanget) + { + Vector3f t3 = Normalize(transform.MultiplyVector3(Vector3f(it->x, it->y, it->z))); + *outTanget = Vector4f(t3.x,t3.y,t3.z,it->w); + } + } + else + { + for (StrideIterator<Vector4f> it = mesh->GetTangentBegin (), end = mesh->GetTangentEnd (); it != end; ++it, ++outTanget) + { + Vector3f t3 = transform.MultiplyVector3(Vector3f(it->x, it->y, it->z)); + *outTanget = Vector4f(t3.x,t3.y,t3.z,it->w); + } + } + } + else + strided_copy (mesh->GetTangentBegin (), mesh->GetTangentEnd (), outCombinedMesh.GetTangentBegin () + offset); + } + offset += vertexCount; + } + } + } + + if ( hasUV0 ) + { + offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + int vertexCount = mesh->GetVertexCount (); + if (!mesh->IsAvailable (kShaderChannelTexCoord0)) + std::fill (outCombinedMesh.GetUvBegin (0) + offset, outCombinedMesh.GetUvBegin (0) + offset + vertexCount, Vector2f(0.0f,0.0f)); + else + strided_copy (mesh->GetUvBegin (0), mesh->GetUvEnd (0), outCombinedMesh.GetUvBegin (0) + offset); + offset += vertexCount; + } + } + } + + if ( hasUV1 ) + { + offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + const int uvIndex = (mesh->GetAvailableChannels() & (1<<kShaderChannelTexCoord1))!=0? 1 : 0; + StrideIterator<Vector2f> it = in[i].mesh->GetUvBegin( uvIndex ); + StrideIterator<Vector2f> end = in[i].mesh->GetUvEnd( uvIndex ); + + int vertexCount = mesh->GetVertexCount (); + if ( it == end) + std::fill (outCombinedMesh.GetUvBegin (1) + offset, outCombinedMesh.GetUvBegin (1) + offset + vertexCount, Vector2f(0.0f,0.0f)); + else + { + // we have to apply lightmap UV scale and offset factors + // callee is responsible to reset lightmapTilingOffset on the Renderer afterwards + const Vector4f uvScaleOffset = in[i].lightmapTilingOffset; + if ( uvScaleOffset != Vector4f(1, 1, 0, 0) ) + { + StrideIterator<Vector2f> outUV = outCombinedMesh.GetUvBegin (1) + offset; + for (; it != end; ++it, ++outUV) + { + outUV->x = it->x * uvScaleOffset.x + uvScaleOffset.z; + outUV->y = it->y * uvScaleOffset.y + uvScaleOffset.w; + } + } + else + strided_copy (it, end, outCombinedMesh.GetUvBegin (1) + offset); + } + offset += vertexCount; + } + } + } + + if ( hasColors ) + { + offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + int vertexCount = mesh->GetVertexCount (); + if (!mesh->IsAvailable (kShaderChannelColor)) + std::fill (outCombinedMesh.GetColorBegin () + offset, outCombinedMesh.GetColorBegin () + offset + vertexCount, ColorRGBA32(255,255,255,255)); + else + { + DebugAssert(mesh->GetVertexColorsSwizzled() == outCombinedMesh.GetVertexColorsSwizzled()); + strided_copy (mesh->GetColorBegin (), mesh->GetColorEnd (), outCombinedMesh.GetColorBegin () + offset); + } + offset += vertexCount; + } + } + } + + if ( hasSkin ) + { + CombineBoneSkinning (in, outCombinedMesh); + } +} + +static void CalculateRootBonePathHash (const CombineInstances &in, Mesh& outCombinedMesh) +{ + // We always pick the root bone path hash of the first combine instance. + // This is because anything else gives unpredictable behaviour and makes it impossible for the user + // to setup the skinned mesh renderer T/R/S correctly. + outCombinedMesh.SetRootBonePathHash(in[0].mesh->GetRootBonePathHash()); + + // If we made it so that the skinnedmeshrenderer always used the default pose from the Avatar + // Then it would be possible to pick the root bone from the mesh with the most bones instead. +#if 0 + size_t size = in.size(); + + BindingHash rootBonePathHash = 0; + int boneCount = 0; + for (size_t i=0; i<size; ++i) + { + } + } + if (rootBonePathHash) + outCombinedMesh.SetRootBonePathHash(rootBonePathHash); +#endif +} + +static void CombineBoneSkinning (const CombineInstances &in, Mesh& outCombinedMesh) +{ + size_t size = in.size(); + + int boneOffset = 0; + int offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (!IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + continue; + + const Mesh* mesh = in[i].mesh; + Mesh::BoneInfluenceContainer& outSkin = outCombinedMesh.GetSkin(); + const Mesh::BoneInfluenceContainer& inSkin = mesh->GetSkin(); + int vertexCount = mesh->GetVertexCount (); + if (inSkin.empty()) + { + for(int i=0; i<vertexCount;i++) + { + outSkin[offset+i].weight[0] = 0; + outSkin[offset+i].weight[1] = 0; + outSkin[offset+i].weight[2] = 0; + outSkin[offset+i].weight[3] = 0; + outSkin[offset+i].boneIndex[0] = 0; + outSkin[offset+i].boneIndex[1] = 0; + outSkin[offset+i].boneIndex[2] = 0; + outSkin[offset+i].boneIndex[3] = 0; + } + } + else + { + for(int i=0; i<vertexCount;i++) + { + outSkin[offset+i].weight[0] = inSkin[i].weight[0]; + outSkin[offset+i].weight[1] = inSkin[i].weight[1]; + outSkin[offset+i].weight[2] = inSkin[i].weight[2]; + outSkin[offset+i].weight[3] = inSkin[i].weight[3]; + outSkin[offset+i].boneIndex[0] = inSkin[i].boneIndex[0]+boneOffset; + outSkin[offset+i].boneIndex[1] = inSkin[i].boneIndex[1]+boneOffset; + outSkin[offset+i].boneIndex[2] = inSkin[i].boneIndex[2]+boneOffset; + outSkin[offset+i].boneIndex[3] = inSkin[i].boneIndex[3]+boneOffset; + } + } + + offset += vertexCount; + + int poseCount = mesh->GetBindpose().size(); + int bindingHashCount = mesh->GetBonePathHashes().size(); + + memcpy(outCombinedMesh.GetBindpose().begin() + boneOffset, mesh->GetBindpose().begin(), poseCount*sizeof(Matrix4x4f)); + + // Old asset bundles might not have bindingHashCount in sync with bind poses. + if (poseCount == bindingHashCount) + memcpy(outCombinedMesh.GetBonePathHashes().begin () + boneOffset, mesh->GetBonePathHashes().begin(), poseCount*sizeof(BindingHash)); + else + memset(outCombinedMesh.GetBonePathHashes().begin () + boneOffset, 0, poseCount*sizeof(BindingHash)); + + boneOffset += poseCount; + } + + CalculateRootBonePathHash (in, outCombinedMesh); +} + + +void CombineMeshes (const CombineInstances &in, Mesh& out, bool mergeSubMeshes, bool useTransforms) +{ + if (!out.CanAccessFromScript()) + { + ErrorStringMsg("Cannot combine into mesh that does not allow access: %s", out.GetName()); + return; + } + for (size_t i = 0; i < in.size(); ++i) + { + Mesh* mesh = in[i].mesh; + if (!mesh) + { + WarningStringMsg("Combine mesh instance %" PRINTF_SIZET_FORMAT " is null.", i); + } + if (mesh && (in[i].subMeshIndex < 0 || in[i].subMeshIndex >= mesh->GetSubMeshCount())) + { + WarningStringMsg("Submesh index %d is invalid for mesh %s.", in[i].subMeshIndex, mesh->GetName()); + } + if (mesh && !mesh->CanAccessFromScript()) + { + ErrorStringMsg("Cannot combine mesh that does not allow access: %s", mesh->GetName()); + return; + } + if (mesh == &out) + { + ErrorStringMsg("Cannot combine into a mesh that is also in the CombineInstances input: %s", mesh->GetName()); + return; + } + } + + CombineMeshVerticesForStaticBatching (in, out.GetName(), out, useTransforms); + CombineMeshIndicesForStaticBatching (in, out, mergeSubMeshes, false); + + out.RecalculateBounds(); + out.UpdateVertexFormat(); +} + diff --git a/Runtime/Filters/Mesh/MeshCombiner.h b/Runtime/Filters/Mesh/MeshCombiner.h new file mode 100644 index 0000000..a6975a9 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshCombiner.h @@ -0,0 +1,33 @@ +#ifndef MESHCOMBINER_H +#define MESHCOMBINER_H + +#include "LodMesh.h" + +class Renderer; + +struct CombineInstance +{ + Mesh *mesh; + int subMeshIndex; + Matrix4x4f transform; + + Vector4f lightmapTilingOffset; + int vertexOffset; + + CombineInstance() : + mesh(NULL), + subMeshIndex(0), + lightmapTilingOffset(1, 1, 0, 0), + vertexOffset(0) + {} +}; + +typedef std::vector<CombineInstance> CombineInstances; + +void CombineMeshes (const CombineInstances &in, Mesh& out, bool mergeSubMeshes, bool useTransforms); +// takes an array of meshes(their vertex data) and merges them into 1 combined mesh. +void CombineMeshVerticesForStaticBatching ( const CombineInstances& in, const string& combinedMeshName, Mesh& outCombinedMesh, bool useTransforms = true ); +// takes an array of meshes(their indices) and merges them in 1 mesh (setups subsets) +void CombineMeshIndicesForStaticBatching (const CombineInstances& in, Mesh& inoutMesh, bool mergeSubMeshes, bool useVertexOffsets); + +#endif diff --git a/Runtime/Filters/Mesh/MeshOptimizer.cpp b/Runtime/Filters/Mesh/MeshOptimizer.cpp new file mode 100644 index 0000000..068dc53 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshOptimizer.cpp @@ -0,0 +1,359 @@ +#include "UnityPrefix.h" +#include "MeshOptimizer.h" +#include <vector> + +//@TODO: + +// Step 1 + +//* bool ExtractCollisionData (Mesh& mesh, UNITY_TEMP_VECTOR(kMemGeometry, Vector3f)& vertices, UNITY_TEMP_VECTOR(kMemGeometry, UInt32)& triangles); +// -> make it return welded vertices and triangle array +//* Enable Deformablemesh code and make it work with welding code and check that cloth works visually... + +// Testing: +//* Check mesh collision detection code to work visually correct. +// * run functional test suite +// * run lightmapper tests in the integration test suite. They have a complete test for the lightmap uv coordinates picking up lightmap values... + + +// Step 2: +//* Verify vertex cache performance on iPad1 / Wii / intel integrated graphics +//* Switch to default gpu optimized mode and update all model importer templates + + + +template<typename T, const int CACHE_SIZE> +class VertexCacheOptimizer +{ + UInt32* m_cacheEntries; + UInt32 m_cacheSize; + + mutable UInt32 m_cacheMisses; + mutable UInt32 m_cacheHits; + + UInt32 GetInCache(UInt32 lIndex, const char* vertexInCache) const + { + return vertexInCache[lIndex] ? 1 : 0; + } + + void AddToCache(UInt32 lIndex, char* vertexInCache) + { + if(m_cacheEntries[0]!=-1) + vertexInCache[m_cacheEntries[0]]=0; + + for(UInt32 i=0; i<m_cacheSize-1; i++) + m_cacheEntries[i]=m_cacheEntries[i+1]; + + m_cacheEntries[m_cacheSize-1]=lIndex; + vertexInCache[lIndex]=1; + } + +public: + + VertexCacheOptimizer () : m_cacheSize(CACHE_SIZE) + { + m_cacheEntries=new UInt32 [m_cacheSize]; + + m_cacheHits = m_cacheMisses = 0; + for(UInt32 i=0; i<m_cacheSize; i++) + m_cacheEntries[i]=(UInt32)-1; + } + + ~VertexCacheOptimizer() { delete m_cacheEntries; } + + UInt32 GetCacheMisses() { return m_cacheMisses; } + UInt32 GetCacheHits() { return m_cacheHits; } + + void OptimizeTriangles(T* pdstTris, UInt32 numVertices, const T* srcTris, UInt32 numTriangles) + { + UInt32 cachedVerts=0; + char* triangleUsed=new char [numTriangles]; + char* vertexInCache=new char [numVertices]; + memset(triangleUsed,0,numTriangles); + memset(vertexInCache,0,numVertices); + + bool foundTriangle=true; + while (foundTriangle) + { + foundTriangle=false; + UInt32 bestCandidate=0; + UInt32 bestCacheValue=0; + for (UInt32 i = 0; i < numTriangles; i++) + { + if (triangleUsed[i]) + continue; + + foundTriangle=true; + UInt32 i1=srcTris[i*3+0]; + UInt32 i2=srcTris[i*3+1]; + UInt32 i3=srcTris[i*3+2]; + + UInt32 lCacheValue=GetInCache(i1,vertexInCache)+GetInCache(i2,vertexInCache)+GetInCache(i3,vertexInCache)+1; + if (lCacheValue > bestCacheValue) + { + bestCandidate=i; + bestCacheValue=lCacheValue; + if (bestCacheValue == 4) + break; + } + } + if(foundTriangle) + { + triangleUsed[bestCandidate]=1; + UInt32 i1=srcTris[bestCandidate*3+0]; + UInt32 i2=srcTris[bestCandidate*3+1]; + UInt32 i3=srcTris[bestCandidate*3+2]; + *pdstTris++=(T)i1; + *pdstTris++=(T)i2; + *pdstTris++=(T)i3; + if (!GetInCache(i1,vertexInCache)) { AddToCache(i1,vertexInCache); cachedVerts++; m_cacheMisses++; } else m_cacheHits++; + if (!GetInCache(i2,vertexInCache)) { AddToCache(i2,vertexInCache); cachedVerts++; m_cacheMisses++; } else m_cacheHits++; + if (!GetInCache(i3,vertexInCache)) { AddToCache(i3,vertexInCache); cachedVerts++; m_cacheMisses++; } else m_cacheHits++; + } + } + delete[] triangleUsed; + delete[] vertexInCache; + } +}; + +inline bool CompareBlendShapeVertexIndex (const BlendShapeVertex& lhs, const BlendShapeVertex& rhs) +{ + return lhs.index < rhs.index; +} + +void OptimizeReorderVertexBuffer (Mesh& mesh) +{ + const int submeshCount = mesh.GetSubMeshCount(); + const int vertexCount = mesh.GetVertexCount(); + + // backup required data + VertexData backupVertexData(mesh.m_VertexData, mesh.GetAvailableChannels(), mesh.GetVertexData().GetStreamsLayout(), mesh.GetVertexData().GetChannelsLayout()); + + Mesh::BoneInfluenceContainer backupSkin; + if (!mesh.m_Skin.empty()) + backupSkin.swap(mesh.m_Skin); + + // reorder the vertices so they come in increasing order + dynamic_array<UInt32> oldToNew; + dynamic_array<UInt32> newToOld; + newToOld.resize_initialized(vertexCount, 0xFFFFFFFF); + oldToNew.resize_initialized(vertexCount, 0xFFFFFFFF); + + Mesh::TemporaryIndexContainer dstIndices; + int newVertexCount = 0; + for (int submesh = 0; submesh < submeshCount; submesh++) + { + Mesh::TemporaryIndexContainer indices; + mesh.GetTriangles (indices, submesh); + + const int indexCount = indices.size(); + dstIndices.resize(indexCount); + for (int index=0; index < indexCount; index++) + { + int vertex = indices[index]; + AssertBreak(vertex >= 0); + AssertBreak(vertex < vertexCount); + + if (oldToNew[vertex] == 0xFFFFFFFF) + { + oldToNew[vertex]=newVertexCount; + newToOld[newVertexCount]=vertex; + newVertexCount++; + } + dstIndices[index] = oldToNew[vertex]; + } + + mesh.SetIndices (&dstIndices[0], dstIndices.size(), submesh, kPrimitiveTriangles); + } + + mesh.ResizeVertices(newVertexCount, backupVertexData.GetChannelMask()); + + if (!backupSkin.empty()) + mesh.m_Skin.resize_initialized(newVertexCount); + + for (int vertex=0; vertex < newVertexCount; vertex++) + { + UInt32 remapNew = newToOld[vertex]; + Assert(remapNew != 0xFFFFFFFF); + + if (!backupSkin.empty()) + mesh.m_Skin[vertex] = backupSkin[remapNew]; + + mesh.GetVertexBegin()[vertex] = backupVertexData.MakeStrideIterator<Vector3f> (kShaderChannelVertex)[remapNew]; + + if (backupVertexData.HasChannel(kShaderChannelNormal)) + mesh.GetNormalBegin()[vertex] = backupVertexData.MakeStrideIterator<Vector3f> (kShaderChannelNormal)[remapNew]; + + if (backupVertexData.HasChannel(kShaderChannelColor)) + mesh.GetColorBegin()[vertex] = backupVertexData.MakeStrideIterator<ColorRGBA32> (kShaderChannelColor)[remapNew]; + + if (backupVertexData.HasChannel(kShaderChannelTexCoord0)) + mesh.GetUvBegin(0)[vertex] = backupVertexData.MakeStrideIterator<Vector2f> (kShaderChannelTexCoord0)[remapNew]; + + if (backupVertexData.HasChannel(kShaderChannelTexCoord1)) + mesh.GetUvBegin(1)[vertex] = backupVertexData.MakeStrideIterator<Vector2f> (kShaderChannelTexCoord1)[remapNew]; + + if (backupVertexData.HasChannel(kShaderChannelTangent)) + mesh.GetTangentBegin()[vertex] = backupVertexData.MakeStrideIterator<Vector4f> (kShaderChannelTangent)[remapNew]; + } + + // Remap vertex indices stored in blend shapes + BlendShapeData& blendShapeData = mesh.GetWriteBlendShapeDataInternal(); + BlendShapeVertices& blendShapeVertices = blendShapeData.vertices; + for (BlendShapeVertices::iterator itv = blendShapeVertices.begin(), endv = blendShapeVertices.end(); itv != endv; ++itv) + { + BlendShapeVertex& bsv = *itv; + bsv.index = oldToNew[bsv.index]; + } + + // Sort each shape's vertices by index so the blending writes to memory as linearly as possible + for (int shapeIndex = 0; shapeIndex < blendShapeData.shapes.size(); shapeIndex++) + { + const BlendShape& shape = blendShapeData.shapes[shapeIndex]; + BlendShapeVertex* vertices = &blendShapeVertices[shape.firstVertex]; + std::sort(vertices, vertices + shape.vertexCount, CompareBlendShapeVertexIndex); + } + + mesh.SetChannelsDirty(mesh.GetAvailableChannels(), true); +} + +void OptimizeIndexBuffers (Mesh& mesh) +{ + const int submeshCount = mesh.GetSubMeshCount(); + const int vertexCount = mesh.GetVertexCount(); + + // first optimize the indices for each submesh + for (int submesh = 0; submesh < submeshCount; submesh++) + { + Mesh::TemporaryIndexContainer unoptimizedIndices; + mesh.GetTriangles (unoptimizedIndices, submesh); + + Mesh::TemporaryIndexContainer optimizedIndices; + optimizedIndices.resize(unoptimizedIndices.size()); + + VertexCacheOptimizer<UInt32, 16> vertexCacheOptimizer; + vertexCacheOptimizer.OptimizeTriangles(&optimizedIndices[0], vertexCount, &unoptimizedIndices[0], unoptimizedIndices.size() / 3); + // LogString(Format("[Optimize] mesh: %s: submesh: %d hits: %d misses: %d\n", mesh.GetName(), submesh, vertexCacheOptimizer.GetCacheHits(), vertexCacheOptimizer.GetCacheMisses())); + + mesh.SetIndices (&optimizedIndices[0], optimizedIndices.size(), submesh, kPrimitiveTriangles); + } +} + + +template<typename T, const int CACHE_SIZE> +class VertexCacheDeOptimizer +{ + UInt32* m_cacheEntries; + UInt32 m_cacheSize; + + mutable UInt32 m_cacheMisses; + mutable UInt32 m_cacheHits; + + UInt32 GetInCache(UInt32 lIndex, const char* vertexInCache) const + { + return vertexInCache[lIndex] ? 1 : 0; + } + + void AddToCache(UInt32 lIndex, char* vertexInCache) + { + if(m_cacheEntries[0]!=-1) + vertexInCache[m_cacheEntries[0]]=0; + + for(UInt32 i=0; i<m_cacheSize-1; i++) + m_cacheEntries[i]=m_cacheEntries[i+1]; + + m_cacheEntries[m_cacheSize-1]=lIndex; + vertexInCache[lIndex]=1; + } + +public: + + VertexCacheDeOptimizer () : m_cacheSize(CACHE_SIZE) + { + m_cacheEntries=new UInt32 [m_cacheSize]; + + m_cacheHits = m_cacheMisses = 0; + for(UInt32 i=0; i<m_cacheSize; i++) + m_cacheEntries[i]=(UInt32)-1; + } + + ~VertexCacheDeOptimizer() { delete m_cacheEntries; } + + UInt32 GetCacheMisses() { return m_cacheMisses; } + UInt32 GetCacheHits() { return m_cacheHits; } + + void DeOptimizeTriangles(T* pdstTris, UInt32 numVertices, const T* srcTris, UInt32 numTriangles) + { + UInt32 cachedVerts=0; + char* triangleUsed=new char [numTriangles]; + char* vertexInCache=new char [numVertices]; + memset(triangleUsed,0,numTriangles); + memset(vertexInCache,0,numVertices); + + bool foundTriangle=true; + while (foundTriangle) + { + foundTriangle=false; + UInt32 bestCandidate=0; + UInt32 bestCacheValue=4; + for (UInt32 i = 0; i < numTriangles; i++) + { + if (triangleUsed[i]) + continue; + + foundTriangle=true; + UInt32 i1=srcTris[i*3+0]; + UInt32 i2=srcTris[i*3+1]; + UInt32 i3=srcTris[i*3+2]; + + UInt32 lCacheValue=GetInCache(i1,vertexInCache)+GetInCache(i2,vertexInCache)+GetInCache(i3,vertexInCache)+1; + if (lCacheValue <= bestCacheValue) + { + bestCandidate=i; + bestCacheValue=lCacheValue; + if (bestCacheValue == 1) + break; + } + } + if(foundTriangle) + { + triangleUsed[bestCandidate]=1; + UInt32 i1=srcTris[bestCandidate*3+0]; + UInt32 i2=srcTris[bestCandidate*3+1]; + UInt32 i3=srcTris[bestCandidate*3+2]; + *pdstTris++=(T)i1; + *pdstTris++=(T)i2; + *pdstTris++=(T)i3; + if (!GetInCache(i1,vertexInCache)) { AddToCache(i1,vertexInCache); cachedVerts++; m_cacheMisses++; } else m_cacheHits++; + if (!GetInCache(i2,vertexInCache)) { AddToCache(i2,vertexInCache); cachedVerts++; m_cacheMisses++; } else m_cacheHits++; + if (!GetInCache(i3,vertexInCache)) { AddToCache(i3,vertexInCache); cachedVerts++; m_cacheMisses++; } else m_cacheHits++; + } + } + delete triangleUsed; + delete vertexInCache; + } +}; + +void DeOptimizeIndexBuffers (Mesh& mesh) +{ + const int submeshCount = mesh.GetSubMeshCount(); + const int vertexCount = mesh.GetVertexCount(); + + // first optimize the indices for each submesh + for (int submesh = 0; submesh < submeshCount; submesh++) + { + Mesh::TemporaryIndexContainer unoptimizedIndices; + mesh.GetTriangles (unoptimizedIndices, submesh); + + Mesh::TemporaryIndexContainer deOptimizedIndices; + deOptimizedIndices.resize(unoptimizedIndices.size()); + + VertexCacheDeOptimizer<UInt32, 16> vertexCacheDeOptimizer; + vertexCacheDeOptimizer.DeOptimizeTriangles(&deOptimizedIndices[0], vertexCount, &unoptimizedIndices[0], unoptimizedIndices.size() / 3); + + //LogString(Format("[Deoptimize] mesh: %s: submesh: %d hits: %d misses: %d\n", mesh.GetName(), submesh, vertexCacheDeOptimizer.GetCacheHits(), vertexCacheDeOptimizer.GetCacheMisses())); + + mesh.SetIndices (&deOptimizedIndices[0], deOptimizedIndices.size(), submesh, kPrimitiveTriangles); + } +} + diff --git a/Runtime/Filters/Mesh/MeshOptimizer.h b/Runtime/Filters/Mesh/MeshOptimizer.h new file mode 100644 index 0000000..8964edf --- /dev/null +++ b/Runtime/Filters/Mesh/MeshOptimizer.h @@ -0,0 +1,13 @@ +#pragma once + +#ifndef __importmeshoptimizer_h_included__ +#define __importmeshoptimizer_h_included__ + +#include "Runtime/Filters/Mesh/LodMesh.h" + +void DeOptimizeIndexBuffers (Mesh& mesh); +void OptimizeIndexBuffers (Mesh& mesh); +void OptimizeReorderVertexBuffer (Mesh& mesh); + + +#endif //__importmeshoptimizer_h_included__ diff --git a/Runtime/Filters/Mesh/MeshPartitioner.cpp b/Runtime/Filters/Mesh/MeshPartitioner.cpp new file mode 100644 index 0000000..9ec9f87 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshPartitioner.cpp @@ -0,0 +1,346 @@ + +#include "UnityPrefix.h" +#include "MeshPartitioner.h" +#include "Runtime/Filters/Mesh/LodMesh.h" + +#if UNITY_EDITOR + +static const UInt32 ComponentStride[] = { 12, 12, 4, 8, 8, 16, sizeof(BoneInfluence) }; + +static int CalcDMABatchSize(int totalVerts, int stride, const int sizeRestriction, bool padded) +{ + const int alignmentRestriction = 16; // DMA transfers address must be a multiple of 16 + int a = alignmentRestriction; + + if(a>stride) + { + if(a % stride == 0) + return sizeRestriction; + while(a % stride) { a+=alignmentRestriction; } + } + else + { + if(stride % a == 0) + return sizeRestriction; + while(stride % a) { a+=alignmentRestriction; } + } + + int batchMultiple = a / stride; + totalVerts = (totalVerts < sizeRestriction) ? totalVerts : sizeRestriction; + if(padded) + totalVerts += batchMultiple - 1; + totalVerts /= batchMultiple; + totalVerts *= batchMultiple; + return totalVerts; +}; + +static int CalcBestFitBatchSize(const UInt32 availableChannels, int vertexCount, int maxVerts, bool padded = false) +{ + int bestFit = INT_MAX; + for(int i=0;i<=kShaderChannelCount;i++) + { + if (availableChannels & (1<<i)) + { + int maxVCount = CalcDMABatchSize(vertexCount, ComponentStride[i], maxVerts, padded); + bestFit = (bestFit > maxVCount) ? maxVCount : bestFit; + } + } + return bestFit; +} + +template<typename T> +struct TempPartition +{ + dynamic_array<Vector3f> m_Vertices; + dynamic_array<Vector2f> m_UV; + dynamic_array<Vector2f> m_UV1; + dynamic_array<ColorRGBA32> m_Colors; + dynamic_array<Vector3f> m_Normals; + dynamic_array<Vector4f> m_Tangents; + dynamic_array<BoneInfluence> m_Skin; + dynamic_array<T> indexBuffer; + dynamic_array<T> newToOld; + int vertexCount; + // + void InitRemapping(int numVertices) + { + newToOld.resize_uninitialized(numVertices); + memset(&newToOld[0],(T)-1,numVertices*sizeof(T)); + } + void RemapVertices(Mesh& mesh, int actualVertexCount) + { + m_Vertices.resize_uninitialized(vertexCount); + const UInt32 channels = mesh.GetAvailableChannels(); + if(channels&(1<<kShaderChannelNormal)) + m_Normals.resize_uninitialized(vertexCount); + if(channels&(1<<kShaderChannelTexCoord0)) + m_UV.resize_uninitialized(vertexCount); + if(channels&(1<<kShaderChannelTexCoord1)) + m_UV1.resize_uninitialized(vertexCount); + if(channels&(1<<kShaderChannelTangent)) + m_Tangents.resize_uninitialized(vertexCount); + if(channels&(1<<kShaderChannelColor)) + m_Colors.resize_uninitialized(vertexCount); + if(!mesh.GetSkin().empty()) + m_Skin.resize_uninitialized(vertexCount); + + T remapNew = 0; + for(int vertex=0; vertex<vertexCount; vertex++) + { + if((T)-1 != newToOld[vertex]) + remapNew = newToOld[vertex]; + m_Vertices[vertex]=mesh.GetVertexBegin()[remapNew]; + if(channels&(1<<kShaderChannelNormal)) + m_Normals[vertex]=mesh.GetNormalBegin()[remapNew]; + if(channels&(1<<kShaderChannelTexCoord0)) + m_UV[vertex]=mesh.GetUvBegin(0)[remapNew]; + if(channels&(1<<kShaderChannelTexCoord1)) + m_UV1[vertex]=mesh.GetUvBegin(1)[remapNew]; + if(channels&(1<<kShaderChannelTangent)) + m_Tangents[vertex]=mesh.GetTangentBegin()[remapNew]; + if(channels&(1<<kShaderChannelColor)) + m_Colors[vertex]=mesh.GetColorBegin()[remapNew]; + if(!mesh.GetSkin().empty()) + m_Skin[vertex]=mesh.GetSkin()[remapNew]; + } + } +}; + +template<typename T> +struct SegmentedMesh +{ + std::vector<TempPartition<T> > m_Partitions; + void Clear() { m_Partitions.clear(); } +}; + +template<typename T> +static void CreateFromSubMesh(std::vector< SegmentedMesh<T> >& segments, Mesh& mesh, int submesh) +{ + SubMesh& sm = mesh.GetSubMeshFast(submesh); + + T vertexCount = 0; + const int numIndices = sm.indexCount; + const int numTriangles = numIndices / 3; + + AssertBreak((numTriangles * 3) == numIndices); + + UInt32 maxComponentStride = 0; + const UInt32 availableChannels = mesh.GetAvailableChannels() | (mesh.GetSkin().empty() ? 0 : (1<<kShaderChannelCount)); + for(int i=0;i<=kShaderChannelCount;i++) + { + if(availableChannels & (1<<i)) + { + if(maxComponentStride < ComponentStride[i]) + maxComponentStride = ComponentStride[i]; + } + } + + const UInt32 maxDMATransferSize = 16 * 1024; + const UInt32 numVerts = (numIndices + 15) & (~15); + const UInt32 maxVerts = std::min(numVerts, maxDMATransferSize / maxComponentStride); + const UInt32 batchSize = CalcBestFitBatchSize(availableChannels, numVerts, maxVerts); + + const int maxPartitions = (numIndices + batchSize-1) / batchSize; + const int numVertices = (sm.indexCount + 2*maxPartitions); + + const T* srcIndices = reinterpret_cast<const T*> (&mesh.GetIndexBuffer()[sm.firstByte]); + + int startTriangle = 0; + int startVertex = 0; + std::vector<T> oldToNew; + oldToNew.resize(mesh.GetVertexCount()); + std::vector<TempPartition<T> > & partitions = segments[submesh].m_Partitions; + while(startTriangle != numTriangles) + { + TempPartition<T> p; + p.indexBuffer.clear(); + p.vertexCount = 0; + p.InitRemapping(batchSize+3); + dynamic_array<T>& dstIndices = p.indexBuffer; + memset(&oldToNew[0],(T)-1,oldToNew.size()*sizeof(T)); + for(int i=startTriangle; i<numTriangles; i++) + { + startTriangle = numTriangles; + T lastVertexCount = vertexCount; // undo stack + for(int j=0;j<3;j++) + { + int index = i*3+j; + int vertex = srcIndices[index]; + AssertBreak(vertex >= 0); + AssertBreak(vertex < mesh.GetVertexCount()); + AssertBreak(lastVertexCount-startVertex+j < p.newToOld.size()); + AssertBreak(p.newToOld[lastVertexCount-startVertex+j] == (T)-1); + if(oldToNew[vertex]==(T)-1) + { + AssertBreak(vertexCount < numVertices); + oldToNew[vertex]=vertexCount-startVertex; + p.newToOld[vertexCount-startVertex]=vertex; + vertexCount++; + } + dstIndices.push_back(oldToNew[vertex]); + } + if((vertexCount-startVertex) > batchSize) + { + //undo the last one in the partition + for(int j=0;j<3;j++) + { + p.newToOld[lastVertexCount-startVertex+j] = -1;; + dstIndices.pop_back(); + } + startTriangle = i; + vertexCount = lastVertexCount; + break; + } + } + const int actualVertexCount = vertexCount - startVertex; + p.vertexCount = maxVerts;//CalcBestFitBatchSize(availableChannels, actualVertexCount, maxVerts, true); // FIXME!!! This needs to find the next "best fit" that will still keep alignment restrictions.. + p.RemapVertices(mesh, actualVertexCount); + partitions.push_back(p); + startVertex = vertexCount; + } + oldToNew.clear(); +} + +// mircea: todo: this would be awesome!!! +// spuInOut: +// m_Vertices +// m_Normals +// m_Tangents +// spuIn: +// m_Skin + +// rsxDirect +// m_UV +// m_UV1 +// m_Colors +// m_IndexBuffer + +void PartitionSubmeshes(Mesh& m) +{ + typedef UInt16 T; + + const int submeshCount = m.m_SubMeshes.size(); + + m.m_PartitionInfos.clear(); + m.m_Partitions.clear(); + + // skinned meshes cannot be partitioned if the optimization flag is not set because partitioning changes the vertex/index buffers + if (!m.GetMeshOptimized() || m.GetSkin().empty()) + return; + + // destripify if needed + m.DestripifyIndices (); + + // need to fixup the indices first so they are not relative to the partition start anymore. + Mesh::MeshPartitionInfoContainer& partInfos = m.m_PartitionInfos; + for(int pi=0; pi<partInfos.size(); pi++) + { + const MeshPartitionInfo& partInfo = m.m_PartitionInfos[pi]; + + for(int s=0; s<partInfo.partitionCount; s++) + { + const MeshPartition& p = m.m_Partitions[partInfo.submeshStart + s]; + IndexBufferData indexBufferData; + m.GetIndexBufferData(indexBufferData); + UInt16* indices = (UInt16*)(&m.m_IndexBuffer[0] + p.indexByteOffset); + for(int i=0;i<p.indexCount;i++) + indices[i] += p.vertexOffset; + } + } + + // make a segment for each submesh + std::vector< SegmentedMesh<T> > segments; + segments.resize(submeshCount); + for(int submesh=0;submesh<submeshCount;submesh++) + CreateFromSubMesh<T>(segments, m, submesh); + + /////////////////////////////////////////////////////////////////////////////// + // combine the segments to get the script accessible buffers + + UInt32 availableChannels = m.GetAvailableChannels(); + + m.Clear(false); + m.SetMeshOptimized(true); //mircea@ m.Clear will set the optimized mesh to false. Being here means we are partitioning an optimized mesh so restore the flag. + m.SetSubMeshCount(submeshCount); + + UInt32 vertexOffset = 0; + UInt32 indexOffset = 0; + + for(int submesh=0;submesh<submeshCount;submesh++) + { + int indexCount = 0; + SegmentedMesh<T>& seg = segments[submesh]; + + MeshPartitionInfo partInfo; + partInfo.submeshStart = m.m_Partitions.size(); + partInfo.partitionCount = seg.m_Partitions.size(); + m.m_PartitionInfos.push_back(partInfo); + + // create partitions & build the mesh buffers + for(int s=0;s<seg.m_Partitions.size();s++) + { + MeshPartition part; + TempPartition<T>& p = seg.m_Partitions[s]; + part.vertexCount = p.vertexCount; + part.vertexOffset = vertexOffset; + part.indexCount = p.indexBuffer.size(); + part.indexByteOffset = indexOffset; + AssertBreak(0 == (part.vertexOffset & 15)); + m.m_Partitions.push_back(part);; + indexCount += part.indexCount; + indexOffset += p.indexBuffer.size() * sizeof(T); + vertexOffset += p.vertexCount; + } + } + + // fill in the partitioned data back into the mesh. + m.ResizeVertices(vertexOffset, availableChannels); + + for(int submesh=0;submesh<submeshCount;submesh++) + { + const SegmentedMesh<T>& seg = segments[submesh]; + const MeshPartitionInfo& partInfo = m.m_PartitionInfos[submesh]; + for(int s=0;s<seg.m_Partitions.size();s++) + { + const TempPartition<T>& p = seg.m_Partitions[s]; + const MeshPartition& part = m.m_Partitions[partInfo.submeshStart + s]; + strided_copy (p.m_Vertices.begin (), p.m_Vertices.end(), m.GetVertexBegin () + part.vertexOffset); + if(!p.m_Normals.empty()) + strided_copy (p.m_Normals.begin (), p.m_Normals.end(), m.GetNormalBegin () + part.vertexOffset); + if(!p.m_UV.empty()) + strided_copy (p.m_UV.begin (), p.m_UV.end (), m.GetUvBegin (0) + part.vertexOffset); + if(!p.m_UV1.empty()) + strided_copy (p.m_UV1.begin (), p.m_UV1.end (), m.GetUvBegin (1) + part.vertexOffset); + if(!p.m_Tangents.empty()) + strided_copy (p.m_Tangents.begin (), p.m_Tangents.end (), m.GetTangentBegin () + part.vertexOffset); + if(!p.m_Colors.empty()) + strided_copy (p.m_Colors.begin (), p.m_Colors.end (), m.GetColorBegin() + part.vertexOffset); + if(!p.m_Skin.empty()) + m.GetSkin().insert(m.GetSkin().end(), p.m_Skin.begin(), p.m_Skin.end()); + } + + std::vector<T> indices; + for(int s=0;s<partInfo.partitionCount;s++) + { + const MeshPartition& p = m.m_Partitions[partInfo.submeshStart+s]; + const TempPartition<T>& tp = seg.m_Partitions[s]; + for(int i=0;i<p.indexCount;i++) + { + int index = tp.indexBuffer[i]; + AssertBreak( (index>=0) && (index < (p.vertexCount))); + #if DEBUG_PARTITIONING + index += p.vertexOffset; + #endif + indices.push_back(index); + } + } + m.SetIndices (&indices[0], indices.size(), submesh, kPrimitiveTriangles); + } +} + +void PartitionMesh(Mesh* m) +{ + PartitionSubmeshes(*m); +} + +#endif //UNITY_EDITOR diff --git a/Runtime/Filters/Mesh/MeshPartitioner.h b/Runtime/Filters/Mesh/MeshPartitioner.h new file mode 100644 index 0000000..95a0d98 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshPartitioner.h @@ -0,0 +1,5 @@ +#pragma once + +#define DEBUG_PARTITIONING 0 +class Mesh; +void PartitionMesh(Mesh* m); diff --git a/Runtime/Filters/Mesh/MeshRenderer.cpp b/Runtime/Filters/Mesh/MeshRenderer.cpp new file mode 100644 index 0000000..08dfbae --- /dev/null +++ b/Runtime/Filters/Mesh/MeshRenderer.cpp @@ -0,0 +1,664 @@ +#include "UnityPrefix.h" +#include "MeshRenderer.h" +#include "Runtime/Graphics/Transform.h" +#include "LodMesh.h" +#include "Runtime/Filters/Mesh/MeshUtility.h" +#include "Runtime/Graphics/DrawUtil.h" +#include "Runtime/GfxDevice/BatchRendering.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Profiler/ExternalGraphicsProfiler.h" +#include "Runtime/Utilities/BitUtility.h" +#include "Runtime/GfxDevice/GfxDevice.h" + +#include "Runtime/GfxDevice/ChannelAssigns.h" +#include "External/shaderlab/Library/properties.h" +#include "External/shaderlab/Library/shaderlab.h" + +#include "Runtime/Camera/Renderqueue.h" +#include "Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h" +#include "Runtime/GfxDevice/BatchRendering.h" + +#include "Runtime/Profiler/TimeHelper.h" +#include "Runtime/GfxDevice/GfxDeviceStats.h" +#include "Runtime/Misc/BuildSettings.h" + + +PROFILER_INFORMATION(gMeshRenderProfile, "MeshRenderer.Render", kProfilerRender) +PROFILER_INFORMATION(gMeshRenderScaledProfile, "MeshRenderer.ComputeScaledMesh", kProfilerRender) +PROFILER_INFORMATION(gMeshRenderStaticBatch, "MeshRenderer.RenderStaticBatch", kProfilerRender) +PROFILER_INFORMATION(gMeshRenderDynamicBatch, "MeshRenderer.RenderDynamicBatch", kProfilerRender) + + +#if UNITY_EDITOR +#define SET_CACHED_SURFACE_AREA_DIRTY() m_CachedSurfaceArea = -1.0f; +#else +#define SET_CACHED_SURFACE_AREA_DIRTY() //do nothing +#endif + +IMPLEMENT_CLASS_INIT_ONLY (MeshRenderer) + +MeshRenderer::MeshRenderer (MemLabelId label, ObjectCreationMode mode) +: Super(kRendererMesh, label, mode) +, m_MeshNode (this) +{ + m_ScaledMeshDirty = true; + m_MeshWasModified = false; + + m_CachedMesh = NULL; + m_ScaledMesh = NULL; + SET_CACHED_SURFACE_AREA_DIRTY(); +} + +MeshRenderer::~MeshRenderer () +{ + FreeScaledMesh (); +} + +void MeshRenderer::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + UpdateCachedMesh (); +} + +void MeshRenderer::Deactivate (DeactivateOperation operation) +{ + Super::Deactivate (operation); + FreeScaledMesh (); +} + +void MeshRenderer::InitializeClass () +{ + REGISTER_MESSAGE (MeshRenderer, kTransformChanged, TransformChanged, int); + + REGISTER_MESSAGE_VOID(MeshRenderer, kDidModifyBounds, DidModifyMeshBounds); + REGISTER_MESSAGE_VOID(MeshRenderer, kDidDeleteMesh, DidDeleteMesh); + REGISTER_MESSAGE_VOID(MeshRenderer, kDidModifyMesh, DidModifyMesh); +} + +void MeshRenderer::TransformChanged (int changeMask) +{ + if (changeMask & Transform::kScaleChanged) + { + SET_CACHED_SURFACE_AREA_DIRTY(); + m_ScaledMeshDirty = true; + } + Super::TransformChanged (changeMask); +} + +void MeshRenderer::UpdateLocalAABB() +{ + DebugAssertIf( m_CachedMesh != m_Mesh ); + if( m_CachedMesh ) + { + if (HasSubsetIndices()) + { + if (GetMaterialCount() == 1) + m_TransformInfo.localAABB = m_CachedMesh->GetBounds(GetSubsetIndex(0)); + else + { + MinMaxAABB minMaxAABB; + for (int m = 0; m < GetMaterialCount(); ++m) + minMaxAABB.Encapsulate(m_CachedMesh->GetBounds(GetSubsetIndex(m))); + m_TransformInfo.localAABB = minMaxAABB; + } + } + else + { + m_TransformInfo.localAABB = m_CachedMesh->GetBounds(); + } + } + else + m_TransformInfo.localAABB.SetCenterAndExtent( Vector3f::zero, Vector3f::zero ); +} + +void MeshRenderer::SetSubsetIndex(int subsetIndex, int index) +{ + Renderer::SetSubsetIndex(subsetIndex, index); + + // Reset scaled mesh if this renderer is now statically batched. + // Mesh scaling should never be used with static batching (case 551504). + FreeScaledMesh(); +} + +int MeshRenderer::GetStaticBatchIndex() const +{ + // Wrap non-virtual version in a virtual call + return GetMeshStaticBatchIndex(); +} + +int MeshRenderer::GetMeshStaticBatchIndex() const +{ + return IsPartOfStaticBatch() ? m_CachedMesh->GetInstanceID(): 0; +} + +UInt32 MeshRenderer::GetMeshIDSmall() const +{ + return m_CachedMesh ? m_CachedMesh->GetInternalMeshID(): 0; +} + + +Mesh* MeshRenderer::GetCachedMesh () +{ + DebugAssertIf(m_CachedMesh != m_Mesh); + return m_CachedMesh; +} + + +Mesh* MeshRenderer::GetMeshUsedForRendering () +{ + Mesh* cachedMesh = GetCachedMesh (); + + if (cachedMesh != NULL) + { + // NOTE: staticaly batched geometry already has scale applied + // therefore we skip mesh scaling + if (!m_ScaledMeshDirty || IsPartOfStaticBatch()) + return m_ScaledMesh == NULL ? cachedMesh : m_ScaledMesh->mesh; + + m_ScaledMeshDirty = false; + + float unused2; + Matrix4x4f unused; + Matrix4x4f scalematrix; + TransformType type = GetTransform().CalculateTransformMatrixDisableNonUniformScale (unused, scalematrix, unused2); + // Check if no scale is needed or we can't access vertices anyway to transform them correctly + DebugAssert(!IsNonUniformScaleTransform(type) || cachedMesh->HasVertexData()); + if (!IsNonUniformScaleTransform(type) || !cachedMesh->HasVertexData()) + { + // Cleanup scaled mesh + FreeScaledMesh(); + m_MeshWasModified = false; + + return cachedMesh; + } + // Need scaled mesh + else + { + // Early out if the mesh scale hasn't actually changed + if (m_ScaledMesh != NULL && CompareApproximately(scalematrix, m_ScaledMesh->matrix) && !m_MeshWasModified) + return m_ScaledMesh->mesh; + + // Scale has changed, maybe generated a new scaled mesh + PROFILER_AUTO(gMeshRenderScaledProfile, this) + + // Allocate scaled mesh + if (m_ScaledMesh == NULL) + { + m_ScaledMesh = new ScaledMesh (); + m_ScaledMesh->mesh = NEW_OBJECT (Mesh); + m_ScaledMesh->mesh->Reset(); + m_ScaledMesh->mesh->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + m_ScaledMesh->mesh->SetHideFlags(kHideAndDontSave); + } + + m_MeshWasModified = false; + + // Rescale mesh + m_ScaledMesh->matrix = scalematrix; + m_ScaledMesh->mesh->CopyTransformed(*cachedMesh, scalematrix); + return m_ScaledMesh->mesh; + } + } + else + { + return NULL; + } +} + +static SubMesh const& GetSubMesh(Mesh& mesh, int subsetIndex) +{ + const int subMeshCount = mesh.GetSubMeshCount()? mesh.GetSubMeshCount()-1 : 0; + const int subMeshIndex = std::min<unsigned int>(subsetIndex, subMeshCount); + return mesh.GetSubMeshFast(subMeshIndex); +} + + +void MeshRenderer::Render (int subsetIndex, const ChannelAssigns& channels) +{ + PROFILER_AUTO(gMeshRenderProfile, this); + + Mesh* mesh = GetMeshUsedForRendering (); + if (!mesh) + return; + if (m_CustomProperties) + GetGfxDevice().SetMaterialProperties (*m_CustomProperties); + DrawUtil::DrawMeshRaw (channels, *mesh, subsetIndex); +} + + +#if UNITY_EDITOR + +void MeshRenderer::GetRenderStats (RenderStats& renderStats) +{ + ///@TODO: This does not work with static batching fixor it. + memset(&renderStats, 0, sizeof(renderStats)); + + Mesh* mesh = m_Mesh; + if (mesh) + { + for (int i=0;i<GetMaterialCount();i++) + { + const SubMesh& submesh = GetSubMesh (*mesh, GetSubsetIndex(i)); + + renderStats.triangleCount += GetPrimitiveCount(submesh.indexCount, submesh.topology, false); + renderStats.vertexCount += submesh.vertexCount; + renderStats.submeshCount++; + } + } +} + +float MeshRenderer::GetCachedSurfaceArea () +{ + if (m_CachedSurfaceArea >= 0.0f) + return m_CachedSurfaceArea; + + Mesh* mesh = GetCachedMesh (); + if (!mesh) + { + m_CachedSurfaceArea = 1.0f; + return m_CachedSurfaceArea; + } + + Matrix4x4f objectToWorld; + GetComponent (Transform).CalculateTransformMatrix (objectToWorld); + + Mesh::TemporaryIndexContainer triangles; + mesh->GetTriangles (triangles); + + dynamic_array<Vector3f> vertices (mesh->GetVertexCount(), kMemTempAlloc); + mesh->ExtractVertexArray (vertices.begin ()); + + m_CachedSurfaceArea = CalculateSurfaceArea (objectToWorld, triangles, vertices); + + return m_CachedSurfaceArea; +} +#endif + +void MeshRenderer::DidModifyMeshBounds () +{ + SET_CACHED_SURFACE_AREA_DIRTY(); + m_TransformDirty = true; + BoundsChanged (); +} + +void MeshRenderer::DidModifyMesh () +{ + m_MeshWasModified = true; + m_ScaledMeshDirty = true; + m_TransformDirty = true; + BoundsChanged(); +} + +void MeshRenderer::DidDeleteMesh () +{ + m_CachedMesh = NULL; +} + +void MeshRenderer::SetSharedMesh (PPtr<Mesh> mesh) +{ + SET_CACHED_SURFACE_AREA_DIRTY(); + m_Mesh = mesh; + UpdateCachedMesh (); +} + +PPtr<Mesh> MeshRenderer::GetSharedMesh () +{ + return m_Mesh; +} + +void MeshRenderer::UpdateCachedMesh () +{ + Mesh* mesh = m_Mesh; + if (mesh != m_CachedMesh) + { + // In order to make sure we are not using old subset indices referring to the previous mesh + // we clear them here, assuming that the correct subset indices will be set subsequently. + // We only do this if there was a previous mesh that the new mesh is replacing, since some + // code paths are transferring in the values and then call this function. In that case we do + // not want to mess with the indices. + if (m_CachedMesh) ClearSubsetIndices(); + m_ScaledMeshDirty = true; + m_MeshWasModified = true; + m_CachedMesh = mesh; + m_TransformDirty = true; + BoundsChanged(); + m_MeshNode.RemoveFromList(); + if (m_CachedMesh) + m_CachedMesh->AddObjectUser( m_MeshNode ); + } +} + +void MeshRenderer::FreeScaledMesh () +{ + if (m_ScaledMesh) + { + DestroySingleObject (m_ScaledMesh->mesh); + delete m_ScaledMesh; + m_ScaledMesh = NULL; + m_ScaledMeshDirty = false; + } +} + +#if GFX_ENABLE_DRAW_CALL_BATCHING + +PROFILER_INFORMATION(gDrawStaticBatchProfile, "Batch.DrawStatic", kProfilerRender) +PROFILER_INFORMATION(gDrawDynamicBatchProfile, "Batch.DrawDynamic", kProfilerRender) + +static bool RenderStaticBatch (Mesh& mesh, VBO& vbo, + BatchInstanceData const* instances, size_t count, const ChannelAssigns& channels) +{ + if (count <= 1) + return false; + IndexBufferData indexBuffer; + mesh.GetIndexBufferData (indexBuffer); + if (!indexBuffer.indices) + return false; + + PROFILER_AUTO(gMeshRenderStaticBatch, &mesh) + + const SubMesh& firstSubmesh = GetSubMesh (mesh, instances[0].subsetIndex); + GfxPrimitiveType topology = firstSubmesh.topology; + const Matrix4x4f& xform = instances[0].xform; + int xformType = instances[0].xformType; + + GfxDevice& device = GetGfxDevice(); + device.BeginStaticBatching(channels, topology); + + // Concat SubMeshes + for (BatchInstanceData const* it = instances; it < instances + count; ++it) + { + const SubMesh& submesh = GetSubMesh (mesh, it->subsetIndex); + device.StaticBatchMesh(submesh.firstVertex, submesh.vertexCount, indexBuffer, submesh.firstByte, submesh.indexCount); + + Assert(topology == submesh.topology); + Assert(xformType == it->xformType); + } + + device.EndStaticBatching(vbo, xform, TransformType(xformType), mesh.GetChannelsInVBO()); + GPU_TIMESTAMP(); + +#if ENABLE_MULTITHREADED_CODE + // Make sure renderer is done before mesh is changed or deleted + UInt32 cpuFence = device.InsertCPUFence(); + mesh.SetCurrentCPUFence(cpuFence); +#endif + + return true; +} + +static bool RenderDynamicBatch (BatchInstanceData const* instances, size_t count, size_t maxVertices, size_t maxIndices, const ChannelAssigns& shaderChannels, UInt32 availableChannels, GfxPrimitiveType topology) +{ + if (count <= 1) + return false; + + if (gGraphicsCaps.buggyDynamicVBOWithTangents && (shaderChannels.GetSourceMap() & (1<<kShaderChannelTangent))) + return false; + + PROFILER_AUTO(gMeshRenderDynamicBatch, NULL) + + DebugAssert (topology != -1); + + GfxDevice& device = GetGfxDevice(); + UInt32 expectedFence = device.GetNextCPUFence(); + device.BeginDynamicBatching(shaderChannels, availableChannels, maxVertices, maxIndices, topology); + + // Transform on CPU + int xformType = -1; + + + for (BatchInstanceData const* it = instances; it < instances + count; ++it) + { + Assert(it->renderer); + Assert(it->renderer->GetRendererType() == kRendererMesh); + MeshRenderer* meshRenderer = (MeshRenderer*)it->renderer; + Mesh* mesh = meshRenderer->GetMeshUsedForRendering(); + if (!mesh) + continue; + + SubMesh const& submesh = GetSubMesh (*mesh, it->subsetIndex); + + Assert(topology == ~0UL || topology == submesh.topology); + Assert(xformType == -1 || xformType == it->xformType); + xformType = it->xformType; + + VertexBufferData vbData; + mesh->GetVertexBufferData(vbData, availableChannels); + IndexBufferData ibData; + mesh->GetIndexBufferData(ibData); + + // Make sure renderer is done before mesh is changed or deleted +#if ENABLE_MULTITHREADED_CODE + mesh->SetCurrentCPUFence(expectedFence); +#endif + + device.DynamicBatchMesh(it->xform, vbData, submesh.firstVertex, submesh.vertexCount, ibData, submesh.firstByte, submesh.indexCount); + } + + // Draw + Assert(xformType != -1); + Assert(topology != ~0UL); + + // We transformed all geometry into the world (Identity) space already. + // However, we did not normalize the normals. + // In fixed function, most GfxDevices (e.g. OpenGL & D3D) will try to figure out uniform + // scale directly from the matrix, and hence will not scale our normals. + // Therefore we upgrade normalization mode to "full normalize" to make them transform properly. + if (xformType & kUniformScaleTransform) + { + xformType &= ~kUniformScaleTransform; + xformType |= kNonUniformScaleTransform; + } + + // Caveat: we do pass identity matrix when batching + // currently normals handling in vprog is: + // xform * (normalize(normal) * unity_Scale.w); + // as we pass identity matrix (no scale) we need NOT apply inv_scale + device.SetInverseScale(1.0f); + device.EndDynamicBatching(TransformType(xformType)); + + // Insert fence after batching is complete + UInt32 fence = device.InsertCPUFence(); + Assert(fence == expectedFence); + + GPU_TIMESTAMP(); + + return true; +} + +void MeshRenderer::RenderMultiple (BatchInstanceData const* instances, size_t count, const ChannelAssigns& channels) +{ + Assert(count > 0); + + GfxDevice& device = GetGfxDevice(); + const float invScale = device.GetBuiltinParamValues().GetInstanceVectorParam(kShaderInstanceVecScale).w; + + const MaterialPropertyBlock* customProps = instances[0].renderer->GetCustomProperties(); + if (customProps) + device.SetMaterialProperties (*customProps); + + const UInt32 wantedChannels = channels.GetSourceMap(); + const bool enableDynamicBatching = GetBuildSettings().enableDynamicBatching; + + BatchInstanceData const* instancesEnd = instances + count; + for (BatchInstanceData const* iBatchBegin = instances; iBatchBegin != instancesEnd; ) + { + Assert(iBatchBegin->renderer->GetRendererType() == kRendererMesh); + MeshRenderer* meshRenderer = (MeshRenderer*)iBatchBegin->renderer; + Mesh* mesh = meshRenderer->GetMeshUsedForRendering (); + VBO* vbo = mesh ? mesh->GetSharedVBO (wantedChannels) : NULL; + if (!vbo) + { + // Skip mesh + ++iBatchBegin; + continue; + } + + const UInt32 availableChannels = mesh->GetChannelsInVBO() & wantedChannels; + const int staticBatchIndex = meshRenderer->GetMeshStaticBatchIndex (); + const int xformType = iBatchBegin->xformType; + + const SubMesh& firstSubMesh = GetSubMesh(*mesh, iBatchBegin->subsetIndex); + const GfxPrimitiveType topology = firstSubMesh.topology; + size_t batchVertexCount = firstSubMesh.vertexCount; + size_t batchIndexCount = firstSubMesh.indexCount; + + // For first strip take 1 connecting (degenerate) triangles into account + if (topology == kPrimitiveTriangleStripDeprecated) + batchIndexCount += 1; + + BatchInstanceData const* iBatchEnd = iBatchBegin + 1; + + // static batching + if (staticBatchIndex != 0) + { + Assert(topology == kPrimitiveTriangles || topology == kPrimitiveTriangleStripDeprecated); + const int maxIndices = GetGfxDevice().GetMaxStaticBatchIndices(); + + for (; iBatchEnd != instancesEnd; ++iBatchEnd) + { + if (xformType != iBatchEnd->xformType) + break; + + Assert(iBatchEnd->renderer->GetRendererType() == kRendererMesh); + MeshRenderer* meshRenderer = (MeshRenderer*)iBatchEnd->renderer; + if (staticBatchIndex != meshRenderer->GetMeshStaticBatchIndex()) + break; + + Mesh* nextMesh = meshRenderer->GetMeshUsedForRendering (); + if (!nextMesh) + break; + + const SubMesh& submesh = GetSubMesh(*nextMesh, iBatchEnd->subsetIndex); + if (submesh.topology != topology) + break; + + VBO* nextVbo = nextMesh->GetSharedVBO (wantedChannels); + if (nextVbo != vbo) // also a NULL check since vbo is non-NULL + break; + + UInt32 nextAvailableChannels = nextMesh->GetChannelsInVBO() & wantedChannels; + if (availableChannels != nextAvailableChannels) + break; + + UInt32 requiredIndexCount = batchIndexCount + submesh.indexCount; + if (topology == kPrimitiveTriangleStripDeprecated) + requiredIndexCount += 3; // take 3 connecting (degenerate) triangles into account + + if (requiredIndexCount > maxIndices) + break; + + batchIndexCount = requiredIndexCount; + } + + if (mesh && vbo) + if (RenderStaticBatch (*mesh, *vbo, iBatchBegin, iBatchEnd - iBatchBegin, channels)) + iBatchBegin = iBatchEnd; + } + else if (vbo && enableDynamicBatching) + // dynamic batching + { + const int firstVertexCount = batchVertexCount; + const int firstIndexCount = batchIndexCount; + + // after moving to fully strided meshes we were hit by the issue that we might have different channels + // in src and dst data, so our optimized asm routines doesn't quite work. + // we will move to support vertex streams (this will solve lots of issues after skinning/batching asm rewrite ;-)) + // but for now let just play safe + + if (CanUseDynamicBatching(*mesh, wantedChannels, firstVertexCount) && + firstIndexCount < kDynamicBatchingIndicesThreshold && + topology != kPrimitiveLineStrip) + { + for (; iBatchEnd != instancesEnd; ++iBatchEnd) + { + if (xformType != iBatchEnd->xformType) + break; + + Assert(iBatchEnd->renderer->GetRendererType() == kRendererMesh); + MeshRenderer* meshRenderer = (MeshRenderer*)iBatchEnd->renderer; + if (meshRenderer->IsPartOfStaticBatch()) + break; + + Mesh* nextMesh = meshRenderer->GetMeshUsedForRendering (); + if (!nextMesh) + break; + + const SubMesh& submesh = GetSubMesh(*nextMesh, iBatchEnd->subsetIndex); + if (submesh.topology != topology) + break; + + if (!CanUseDynamicBatching(*nextMesh, wantedChannels, submesh.vertexCount)) + break; + + UInt32 requiredVertexCount = batchVertexCount + submesh.vertexCount; + UInt32 requiredIndexCount = batchIndexCount + submesh.indexCount; + if (topology == kPrimitiveTriangleStripDeprecated) + requiredIndexCount += 3; // take 3 connecting (degenerate) triangles into account + + if (requiredVertexCount > 0xffff) + break; + + if (requiredIndexCount > kDynamicBatchingIndicesThreshold) + break; + + VBO* nextVbo = nextMesh->GetSharedVBO (wantedChannels); + if (!nextVbo) + break; + + const UInt32 nextAvailableChannels = nextMesh->GetChannelsInVBO() & wantedChannels; + if (availableChannels != nextAvailableChannels) + break; + + batchVertexCount = requiredVertexCount; + batchIndexCount = requiredIndexCount; + } + + // Skip batch if batchVertexCount == 0 or batchIndexCount == 0 + if (batchVertexCount == 0 || batchIndexCount == 0 || RenderDynamicBatch (iBatchBegin, iBatchEnd - iBatchBegin, batchVertexCount, batchIndexCount, channels, availableChannels, topology)) + iBatchBegin = iBatchEnd; + } + } + + // old-school rendering for anything left + for (; iBatchBegin != iBatchEnd; ++iBatchBegin) + { + BatchInstanceData const* it = iBatchBegin; + Assert(iBatchBegin->renderer->GetRendererType() == kRendererMesh); + MeshRenderer* meshRenderer = (MeshRenderer*)iBatchBegin->renderer; + Mesh* mesh = meshRenderer->GetMeshUsedForRendering (); + if (!mesh) + continue; + + VBO* vbo = mesh->GetSharedVBO (wantedChannels); + if (!vbo) + continue; + + if (customProps) + device.SetMaterialProperties (*customProps); + + // Batched rendering above will have set inverse scale to 1.0 (since everything is transformed + // to identity). For remaining meshes that aren't batched, we have to setup the original scale + // back. + device.SetInverseScale(invScale); + SetupObjectMatrix (it->xform, it->xformType); + DrawUtil::DrawVBOMeshRaw (*vbo, *mesh, channels, it->subsetIndex); + } + + Assert(iBatchBegin == iBatchEnd); // everything was rendered successfully + } +} + +bool MeshRenderer::CanUseDynamicBatching(const Mesh& mesh, UInt32 wantedChannels, int vertexCount) +{ + if (mesh.GetStreamCompression() != Mesh::kStreamCompressionDefault || + mesh.GetIndexBuffer().empty() || + vertexCount > kDynamicBatchingVerticesThreshold || + vertexCount * BitsInMask(wantedChannels) > kDynamicBatchingVertsByChannelThreshold) + return false; + return true; +} + +#endif // #if GFX_ENABLE_DRAW_CALL_BATCHING + diff --git a/Runtime/Filters/Mesh/MeshRenderer.h b/Runtime/Filters/Mesh/MeshRenderer.h new file mode 100644 index 0000000..d42c22e --- /dev/null +++ b/Runtime/Filters/Mesh/MeshRenderer.h @@ -0,0 +1,87 @@ +#ifndef MESHRENDERER_H +#define MESHRENDERER_H + +#include "Runtime/Filters/Renderer.h" + +class Mesh; + + + +class MeshRenderer : public Renderer { + public: + MeshRenderer (MemLabelId label, ObjectCreationMode mode); + // ~MeshRenderer (); declared-by-macro + REGISTER_DERIVED_CLASS (MeshRenderer, Renderer) + static void InitializeClass (); + + // Tag class as sealed, this makes QueryComponent faster. + static bool IsSealedClass () { return true; } + + static void RenderMultiple (const BatchInstanceData* instances, size_t count, const ChannelAssigns& channels); + virtual void Render (int materialIndex, const ChannelAssigns& channels); + + virtual void UpdateLocalAABB(); + + virtual void SetSubsetIndex(int subsetIndex, int index); + + virtual int GetStaticBatchIndex() const; + virtual UInt32 GetMeshIDSmall() const; + int GetMeshStaticBatchIndex() const; + + void TransformChanged (int changeMask); + void AwakeFromLoad(AwakeFromLoadMode mode); + virtual void Deactivate (DeactivateOperation operation); + + void SetSharedMesh (PPtr<Mesh> mesh); + PPtr<Mesh> GetSharedMesh (); + + Mesh& GetInstantiatedMesh (); + void SetInstantiatedMesh (Mesh* mesh); + + Mesh* GetMeshUsedForRendering(); + + void DidModifyMeshBounds (); + void DidModifyMeshValidity (); + void DidModifyMesh (); + void DidDeleteMesh (); + #if UNITY_EDITOR + float GetCachedSurfaceArea (); + virtual void GetRenderStats (RenderStats& renderStats); + #endif + + static bool CanUseDynamicBatching(const Mesh& mesh, UInt32 wantedChannels, int vertexCount); + + private: + + Mesh* GetCachedMesh (); + + ListNode<Object> m_MeshNode; + void UpdateCachedMesh (); + + void FreeScaledMesh (); + + Mesh* m_CachedMesh; + PPtr<Mesh> m_Mesh; + + struct ScaledMesh + { + Matrix4x4f matrix; + Mesh* mesh; + }; + + ScaledMesh* m_ScaledMesh; + + // as we have padding anyway, we can add more flags here + UInt8 m_ScaledMeshDirty; + // setted on responce to event to properly handle vertices changing on non-uniform scale + UInt8 m_MeshWasModified; + // for future + UInt16 m_Padding16; + + #if UNITY_EDITOR + float m_CachedSurfaceArea; + #endif + +}; + +#endif diff --git a/Runtime/Filters/Mesh/MeshSkinning.cpp b/Runtime/Filters/Mesh/MeshSkinning.cpp new file mode 100644 index 0000000..7d01667 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinning.cpp @@ -0,0 +1,165 @@ +#include "UnityPrefix.h" +#include "MeshSkinning.h" +#if UNITY_OSX +#include <alloca.h> // this is really deprecated and should be exchanged for stdlib.h +#else +#include <stdlib.h> +#endif +#include "Runtime/Utilities/Utility.h" +#include "Runtime/Utilities/LogAssert.h" +#include "Runtime/Utilities/OptimizationUtility.h" +#include "Runtime/Misc/Allocator.h" +#include "Runtime/Utilities/Prefetch.h" +#include "Runtime/Profiler/TimeHelper.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Misc/CPUInfo.h" +#include "Runtime/Allocator/MemoryMacros.h" +#include "Runtime/Filters/Mesh/LodMesh.h" + +PROFILER_INFORMATION(gMeshSkinningProfile, "MeshSkinning.Skin", kProfilerRender) +PROFILER_INFORMATION(gMeshSkinningSlowpath, "MeshSkinning.SlowPath", kProfilerRender) + +#include "MeshSkinningMobile.h" +#include "MeshSkinningSSE2.h" +#include "SkinGeneric.h" +#include "MeshBlendShaping.h" + + +//=========================================================================================================================================== + + +void SkinMesh(SkinMeshInfo& info) +{ + const TransformInstruction NormalizeTransformInstruction = +#if (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING) || UNITY_SUPPORTS_VFP + // NOTE: optimized NEON/VFP routines do not do any normalization + // instead we rely on GPU to do that + kNoNormalize; +#else + //@TODO: fix that "Fast" & "Fastest" crap. Right now "Fastest" is actually a win on PC (1ms saved in Dark Unity) + // so I'm leaving it there for now. + kNormalizeFastest; +#endif + + // Instantiates the right skinning template depending on the bone per vertex count + #define PERMUTE_BONES(skinNormal,skinTangent) { \ + if (info.bonesPerVertex == 1) \ + SkinGeneric<NormalizeTransformInstruction, 1, skinNormal, skinTangent> (info); \ + else if (info.bonesPerVertex == 2) \ + SkinGeneric<NormalizeTransformInstruction, 2, skinNormal, skinTangent> (info); \ + else if (info.bonesPerVertex == 4) \ + SkinGeneric<NormalizeTransformInstruction, 4, skinNormal, skinTangent> (info); \ + } + + if (info.skinNormals && info.skinTangents) + PERMUTE_BONES(true, true) + else if (info.skinNormals) + PERMUTE_BONES(true, false) + else + PERMUTE_BONES(false, false) +} + + +static void ApplyMeshSkinning (SkinMeshInfo& info) +{ + #if UNITY_WII + SkinMeshWii(info); + #else + + PROFILER_AUTO(gMeshSkinningProfile, NULL); + + if (SkinMeshOptimizedMobile(info)) + return; + + if (SkinMeshOptimizedSSE2(info)) + return; + + // fallback to slow generic implementation + { + PROFILER_AUTO(gMeshSkinningSlowpath, NULL); + SkinMesh(info); + } + #endif +} + +void DeformSkinnedMesh (SkinMeshInfo& info) +{ + const bool hasBlendShapes = info.blendshapeCount != 0; + const bool hasSkin = info.boneCount != 0; + + // No actual skinning can be done. Just copy vertex stream. + // TODO: This code can be removed if we render the undeformed mesh in SkinnedMeshRenderer + // when there is no skin and no active blend shapes. See case 557165. + if (!hasBlendShapes && !hasSkin) + { + memcpy (info.outVertices, info.inVertices, info.inStride * info.vertexCount); + return; + } + + UInt8* tmpBlendShapes = NULL; + + // blend shapes + if (hasBlendShapes) + { + // The final destination might be write-combined memory which is insanely slow to read + // or randomly access, so always allocate a temp buffer for blend shapes (case 554830). + // Skinning can write directly to a VB since it always writes sequentially to memory. + size_t bufferSize = info.inStride * info.vertexCount; + tmpBlendShapes = ALLOC_TEMP_MANUAL(UInt8, bufferSize); + + ApplyBlendShapes (info, tmpBlendShapes); + + if (hasSkin) + info.inVertices = tmpBlendShapes; + else + memcpy(info.outVertices, tmpBlendShapes, bufferSize); + } + + // skinning + if (hasSkin) + ApplyMeshSkinning (info); + + if (tmpBlendShapes) + FREE_TEMP_MANUAL(tmpBlendShapes); +} + + +void* DeformSkinnedMeshJob (void* rawData) +{ + SkinMeshInfo* data = reinterpret_cast<SkinMeshInfo*>(rawData); + DeformSkinnedMesh (*data); + return NULL; +} + + +SkinMeshInfo::SkinMeshInfo() +{ + memset(this, 0, sizeof(SkinMeshInfo)); +} + +void SkinMeshInfo::Allocate() +{ + size_t size = boneCount * sizeof(Matrix4x4f) + sizeof(float) * blendshapeCount; + if (size == 0) + return; + + allocatedBuffer = (UInt8*)UNITY_MALLOC_ALIGNED(kMemSkinning, size, 64); + + UInt8* head = allocatedBuffer; + if (boneCount != 0) + { + cachedPose = reinterpret_cast<Matrix4x4f*> (head); + head += sizeof(Matrix4x4f) * boneCount; + } + + if (blendshapeCount != 0) +{ + blendshapeWeights = reinterpret_cast<float*> (head); + } +} + +void SkinMeshInfo::Release() const +{ + if (allocatedBuffer) + UNITY_FREE(kMemSkinning, allocatedBuffer); +} diff --git a/Runtime/Filters/Mesh/MeshSkinning.h b/Runtime/Filters/Mesh/MeshSkinning.h new file mode 100644 index 0000000..b56efa9 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinning.h @@ -0,0 +1,64 @@ +#ifndef MESHSKINNING_H +#define MESHSKINNING_H + +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Quaternion.h" +#include "Mesh.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/GfxDevice/GfxDeviceTypes.h" +#include <vector> +#include <list> + +class GPUSkinningInfo; + +typedef std::vector<BoneInfluence> CompactSkin; +struct BlendShapeData; + +enum TransformInstruction { kNormalizeFastest = 0, kNormalizeFast = 1, kNoNormalize = 3 }; +class VertexData; + +struct SkinMeshInfo +{ + int bonesPerVertex; + + void* compactSkin; + int boneCount; + + const void* inVertices; + void* outVertices; + int inStride; + int outStride; + + int normalOffset; + int tangentOffset; + bool skinNormals; + bool skinTangents; + + int vertexCount; + + // This is instance data and must be double buffered so the render thread can work in paralell. + UInt8* allocatedBuffer; + Matrix4x4f* cachedPose; + float* blendshapeWeights; + + int blendshapeCount; + const BlendShapeData* blendshapes; + + bool memExport; // Is set up for memexport (Xbox) or streamout (DX11) + +#if UNITY_PS3 + const VertexData* vertexData; +#endif + + GPUSkinningInfo *mei; + + SkinMeshInfo(); + + void Allocate(); + void Release () const; +}; + +void DeformSkinnedMesh (SkinMeshInfo& info); +void* DeformSkinnedMeshJob (void* rawData); + +#endif diff --git a/Runtime/Filters/Mesh/MeshSkinningGenericSIMD.h b/Runtime/Filters/Mesh/MeshSkinningGenericSIMD.h new file mode 100644 index 0000000..0b17b42 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningGenericSIMD.h @@ -0,0 +1,212 @@ +#if 0 + +/* + mircea@INFO: this doesn't do normalization. + */ + +#include "Runtime/Math/Simd/Matrix4x4Simd.h" + +template<TransformInstruction transformInstruction, int bonesPerVertexCount, +bool skinNormal, bool skinTangent, bool copy8BytesAt24Offset> +void SkinGenericSimd (SkinMeshInfo& info) +{ + DebugAssertIf( copy8BytesAt24Offset && (!info.skinNormals || info.normalOffset != 12) ); + const int* influence1 = reinterpret_cast<const int*> (info.compactSkin); + const BoneInfluence2* influence2 = reinterpret_cast<const BoneInfluence2*> (info.compactSkin); + const BoneInfluence* influence4 = reinterpret_cast<const BoneInfluence*> (info.compactSkin); + + const Matrix4x4f* bones4x4 = info.cachedPose; + + const int inStride = info.inStride; + int outStride = info.outStride; + int count = info.vertexCount; + + const int normalOffset = (copy8BytesAt24Offset ? 12 : info.normalOffset) >> 2; + const int tangentOffset = info.tangentOffset >> 2; + + const UInt8* inputVertex = (const UInt8*)info.inVertices; + UInt8* outputVertex = (UInt8*)info.outVertices; + + Simd128 pose0, pose1, pose2, pose3; + + for( int v = 0; v < count; v++ ) + { + ALIGN_LOOP_OPTIMIZATION + + // Blend the matrices first, then transform everything with this + // blended matrix. Gives a small speed boost on XCode/Intel (11.3 to 12.00 FPS + // in skin4 bench), and a good boost on MSVC/Windows (9.6 to 12.4 FPS). + if (bonesPerVertexCount == 1) + { + const float* maddr = bones4x4[*influence1].m_Data; + + Prefetch(maddr); + + pose0 = V4LoadUnaligned( maddr, 0x0 ); + pose1 = V4LoadUnaligned( maddr, 0x4 ); + pose2 = V4LoadUnaligned( maddr, 0x8 ); + pose3 = V4LoadUnaligned( maddr, 0xC ); + } + else if (bonesPerVertexCount == 2) + { + Prefetch(influence2); + + Simd128 weights = {influence2->weight[0], influence2->weight[1], 0, 0}; + + const float* maddr0 = bones4x4[influence2->boneIndex[0]].m_Data; + const float* maddr1 = bones4x4[influence2->boneIndex[1]].m_Data; + + Prefetch(maddr0); + Prefetch(maddr1); + + Simd128 weight0 = V4Splat(weights, 0); + Simd128 weight1 = V4Splat(weights, 1); + + Simd128 mat00 = V4LoadUnaligned( maddr0, 0x0 ); + Simd128 mat01 = V4LoadUnaligned( maddr0, 0x4 ); + Simd128 mat02 = V4LoadUnaligned( maddr0, 0x8 ); + Simd128 mat03 = V4LoadUnaligned( maddr0, 0xC ); + + Simd128 mat10 = V4LoadUnaligned( maddr1, 0x0 ); + Simd128 mat11 = V4LoadUnaligned( maddr1, 0x4 ); + Simd128 mat12 = V4LoadUnaligned( maddr1, 0x8 ); + Simd128 mat13 = V4LoadUnaligned( maddr1, 0xC ); + + pose0 = V4Mul(mat00, weight0); + pose1 = V4Mul(mat01, weight0); + pose2 = V4Mul(mat02, weight0); + pose3 = V4Mul(mat03, weight0); + + pose0 = V4MulAdd(mat10, weight1, pose0); + pose1 = V4MulAdd(mat11, weight1, pose1); + pose2 = V4MulAdd(mat12, weight1, pose2); + pose3 = V4MulAdd(mat13, weight1, pose3); + } + else if (bonesPerVertexCount == 4) + { + Prefetch(influence4); + + Simd128 weights = {influence4->weight[0], influence4->weight[1], influence4->weight[2], influence4->weight[3]}; + + const float* maddr0 = bones4x4[influence4->boneIndex[0]].m_Data; + const float* maddr1 = bones4x4[influence4->boneIndex[1]].m_Data; + const float* maddr2 = bones4x4[influence4->boneIndex[2]].m_Data; + const float* maddr3 = bones4x4[influence4->boneIndex[3]].m_Data; + + Prefetch(maddr0); + Prefetch(maddr1); + Prefetch(maddr2); + Prefetch(maddr3); + + Simd128 weight0 = V4Splat(weights, 0); + Simd128 weight1 = V4Splat(weights, 1); + Simd128 weight2 = V4Splat(weights, 2); + Simd128 weight3 = V4Splat(weights, 3); + + Simd128 mat00 = V4LoadUnaligned( maddr0, 0x0 ); + Simd128 mat01 = V4LoadUnaligned( maddr0, 0x4 ); + Simd128 mat02 = V4LoadUnaligned( maddr0, 0x8 ); + Simd128 mat03 = V4LoadUnaligned( maddr0, 0xC ); + + Simd128 mat10 = V4LoadUnaligned( maddr1, 0x0 ); + Simd128 mat11 = V4LoadUnaligned( maddr1, 0x4 ); + Simd128 mat12 = V4LoadUnaligned( maddr1, 0x8 ); + Simd128 mat13 = V4LoadUnaligned( maddr1, 0xC ); + + Simd128 mat20 = V4LoadUnaligned( maddr2, 0x0 ); + Simd128 mat21 = V4LoadUnaligned( maddr2, 0x4 ); + Simd128 mat22 = V4LoadUnaligned( maddr2, 0x8 ); + Simd128 mat23 = V4LoadUnaligned( maddr2, 0xC ); + + Simd128 mat30 = V4LoadUnaligned( maddr3, 0x0 ); + Simd128 mat31 = V4LoadUnaligned( maddr3, 0x4 ); + Simd128 mat32 = V4LoadUnaligned( maddr3, 0x8 ); + Simd128 mat33 = V4LoadUnaligned( maddr3, 0xC ); + + pose0 = V4Mul(mat00, weight0); + pose1 = V4Mul(mat01, weight0); + pose2 = V4Mul(mat02, weight0); + pose3 = V4Mul(mat03, weight0); + + pose0 = V4MulAdd(mat10, weight1, pose0); + pose1 = V4MulAdd(mat11, weight1, pose1); + pose2 = V4MulAdd(mat12, weight1, pose2); + pose3 = V4MulAdd(mat13, weight1, pose3); + + pose0 = V4MulAdd(mat20, weight2, pose0); + pose1 = V4MulAdd(mat21, weight2, pose1); + pose2 = V4MulAdd(mat22, weight2, pose2); + pose3 = V4MulAdd(mat23, weight2, pose3); + + pose0 = V4MulAdd(mat30, weight3, pose0); + pose1 = V4MulAdd(mat31, weight3, pose1); + pose2 = V4MulAdd(mat32, weight3, pose2); + pose3 = V4MulAdd(mat33, weight3, pose3); + } + + Prefetch(inputVertex); + + Simd128 vpos = V4LoadUnaligned((const float*)inputVertex, 0); + TransformPoint3NATIVE(pose0, pose1, pose2, pose3, vpos, vpos); + + Simd128 vnor, vtan, ndot, tdot; + + // remember... this is a template and skinNormal & skinTangent are consts + if(skinNormal || skinTangent) + { + Simd128 vlen; + if( skinNormal ) + { + vnor = V4LoadUnaligned((const float*)inputVertex, normalOffset); + TransformVector3NATIVE(pose0, pose1, pose2, pose3, vnor, vnor); + ndot = V3Dot(vnor, vnor); + } + else + { + ndot = V4Zero(); + } + + if( skinTangent ) + { + vtan = V4LoadUnaligned((const float*)inputVertex, tangentOffset); + TransformVector3NATIVE(pose0, pose1, pose2, pose3, vtan, vtan); + tdot = V3Dot(vtan, vtan); + } + else + { + tdot = V4Zero(); + } + + vlen = V4MergeH(ndot, tdot); + vlen = V4Rsqrt(vlen); + + if(skinNormal) { + vnor = V4Mul(vnor, V4Splat(vlen, 0)); + V3StoreUnaligned(vnor, (float*)outputVertex, normalOffset); + } + + if(skinTangent) { + vtan = V4Mul(vtan, V4Splat(vlen, 1)); + V3StoreUnaligned(vtan, (float*)outputVertex, tangentOffset); + } + } + + V3StoreUnaligned(vpos, (float*)outputVertex, 0); + + if( skinTangent ) + { + *reinterpret_cast<float*>( outputVertex + (tangentOffset<<2) + sizeof(Vector3f) ) = *reinterpret_cast<const float*>( inputVertex + (tangentOffset<<2) + sizeof(Vector3f) ); + } + + outputVertex += outStride; + inputVertex += inStride; + + if (bonesPerVertexCount == 1) + influence1++; + else if (bonesPerVertexCount == 2) + influence2++; + if (bonesPerVertexCount == 4) + influence4++; + } +} +#endif diff --git a/Runtime/Filters/Mesh/MeshSkinningMobile.h b/Runtime/Filters/Mesh/MeshSkinningMobile.h new file mode 100644 index 0000000..f6efc54 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningMobile.h @@ -0,0 +1,160 @@ +#if UNITY_SUPPORTS_VFP + +#if UNITY_ANDROID || UNITY_BB10 || UNITY_TIZEN +#define s_SkinVertices_VFP _s_SkinVertices_VFP +#define s_SkinVertices_NoNormals_VFP _s_SkinVertices_NoNormals_VFP +#define s_SkinVertices_Tangents_VFP _s_SkinVertices_Tangents_VFP + +#define s_SkinVertices2Bones_VFP _s_SkinVertices2Bones_VFP +#define s_SkinVertices2Bones_NoNormals_VFP _s_SkinVertices2Bones_NoNormals_VFP +#define s_SkinVertices2Bones_Tangents_VFP _s_SkinVertices2Bones_Tangents_VFP + +#define s_SkinVertices4Bones_VFP _s_SkinVertices4Bones_VFP +#define s_SkinVertices4Bones_NoNormals_VFP _s_SkinVertices4Bones_NoNormals_VFP +#define s_SkinVertices4Bones_Tangents_VFP _s_SkinVertices4Bones_Tangents_VFP +#endif // UNITY_ANDROID || UNITY_BB10 || UNITY_TIZEN + +extern "C" +{ + void s_SkinVertices_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence1, void* dstVertData); + void s_SkinVertices_NoNormals_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence1, void* dstVertData); + void s_SkinVertices_Tangents_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence1, void* dstVertData); + + void s_SkinVertices2Bones_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence2, void* dstVertData); + void s_SkinVertices2Bones_NoNormals_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence2, void* dstVertData); + void s_SkinVertices2Bones_Tangents_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence2, void* dstVertData); + + void s_SkinVertices4Bones_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence4, void* dstVertData); + void s_SkinVertices4Bones_NoNormals_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence4, void* dstVertData); + void s_SkinVertices4Bones_Tangents_VFP(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const void* srcBoneInfluence4, void* dstVertData); +} +#endif + +#if (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING) + +#if UNITY_ANDROID || UNITY_WINRT || UNITY_BB10 || UNITY_TIZEN +#define s_SkinVertices_NEON _s_SkinVertices_NEON +#define s_SkinVertices_NoNormals_NEON _s_SkinVertices_NoNormals_NEON +#define s_SkinVertices_Tangents_NEON _s_SkinVertices_Tangents_NEON + +#define s_SkinVertices2Bones_NEON _s_SkinVertices2Bones_NEON +#define s_SkinVertices2Bones_NoNormals_NEON _s_SkinVertices2Bones_NoNormals_NEON +#define s_SkinVertices2Bones_Tangents_NEON _s_SkinVertices2Bones_Tangents_NEON + +#define s_SkinVertices4Bones_NEON _s_SkinVertices4Bones_NEON +#define s_SkinVertices4Bones_NoNormals_NEON _s_SkinVertices4Bones_NoNormals_NEON +#define s_SkinVertices4Bones_Tangents_NEON _s_SkinVertices4Bones_Tangents_NEON + +#endif // UNITY_ANDROID || UNITY_WINRT || UNITY_BB10 || UNITY_TIZEN + +extern "C" +{ + void s_SkinVertices_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const int* srcBoneInfluence1, void* dstVertData); + void s_SkinVertices_NoNormals_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const int* srcBoneInfluence1, void* dstVertData); + void s_SkinVertices_Tangents_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const int* srcBoneInfluence1, void* dstVertData); + + void s_SkinVertices2Bones_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const BoneInfluence2* srcBoneInfluence2, void* dstVertData); + void s_SkinVertices2Bones_NoNormals_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const BoneInfluence2* srcBoneInfluence2, void* dstVertData); + void s_SkinVertices2Bones_Tangents_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const BoneInfluence2* srcBoneInfluence2, void* dstVertData); + + void s_SkinVertices4Bones_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const BoneInfluence* srcBoneInfluences, void* dstVertData); + void s_SkinVertices4Bones_NoNormals_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const BoneInfluence* srcBoneInfluences, void* dstVertData); + void s_SkinVertices4Bones_Tangents_NEON(const Matrix4x4f* bones4x4, const void* srcVertData, const void* srcVertDataEnd, const BoneInfluence* srcBoneInfluences, void* dstVertData); +} +#endif + +#if UNITY_SUPPORTS_VFP || (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING) + +bool SkinMeshOptimizedMobile(SkinMeshInfo& info) +{ + static const size_t kPrefetchSizeBones = 4096; + static const size_t kPrefetchSizeVertex = 512; + + const int bonesPerVertexCount = info.bonesPerVertex; + const bool skinNormal = info.skinNormals; + const bool skinTangent = info.skinTangents; + + const int* influence1 = reinterpret_cast<const int*> (info.compactSkin); + const BoneInfluence2* influence2 = reinterpret_cast<const BoneInfluence2*> (info.compactSkin); + const BoneInfluence* influence4 = reinterpret_cast<const BoneInfluence*> (info.compactSkin); + + const Matrix4x4f* bones4x4 = info.cachedPose; + + const int inStride = info.inStride; + int count = info.vertexCount; + + const UInt8* inputVertex = (const UInt8*)info.inVertices; + UInt8* outputVertex = (UInt8*)info.outVertices; + + if (skinTangent && !skinNormal) + return false; + + if( !UNITY_SUPPORTS_VFP && !CPUInfo::HasNEONSupport() ) + { + ErrorString("non-NEON path not enabled!"); + return false; + } + +#if !ENABLE_MULTITHREADED_SKINNING + PROFILER_AUTO_THREAD_SAFE(gMeshSkinningOptimized, NULL); +#endif + + Prefetch(bones4x4, std::min<size_t>(info.boneCount * sizeof(Matrix4x4f), kPrefetchSizeBones)); + Prefetch(inputVertex + inStride, std::min<size_t>(inStride * (count-1), kPrefetchSizeVertex)); + +#if UNITY_SUPPORTS_NEON && UNITY_SUPPORTS_VFP +#define CALL_SKIN_FUNC( name, influence ) \ +do \ +{ \ +if (CPUInfo::HasNEONSupport()) \ + name##_NEON(bones4x4, inputVertex, (UInt8*)inputVertex + (inStride * count), influence, outputVertex); \ +else \ + name##_VFP(bones4x4, inputVertex, (UInt8*)inputVertex + (inStride * count), influence, outputVertex); \ +} \ +while(0) +#endif +#if UNITY_SUPPORTS_NEON && !UNITY_SUPPORTS_VFP +#define CALL_SKIN_FUNC( name, influence ) name##_NEON(bones4x4, inputVertex, (UInt8*)inputVertex + (inStride * count), influence, outputVertex) +#endif +#if UNITY_SUPPORTS_VFP && !UNITY_SUPPORTS_NEON +#define CALL_SKIN_FUNC( name, influence ) name##_VFP(bones4x4, inputVertex, (UInt8*)inputVertex + (inStride * count), influence, outputVertex) +#endif + + if (bonesPerVertexCount == 1 ) + { + if (skinNormal && skinTangent) + CALL_SKIN_FUNC(s_SkinVertices_Tangents, influence1); + else if( skinNormal ) + CALL_SKIN_FUNC(s_SkinVertices, influence1); + else + CALL_SKIN_FUNC(s_SkinVertices_NoNormals, influence1); + } + else if (bonesPerVertexCount == 2) + { + if (skinNormal && skinTangent) + CALL_SKIN_FUNC(s_SkinVertices2Bones_Tangents, influence2); + else if( skinNormal ) + CALL_SKIN_FUNC(s_SkinVertices2Bones, influence2); + else + CALL_SKIN_FUNC(s_SkinVertices2Bones_NoNormals, influence2); + } + else if (bonesPerVertexCount == 4) + { + if (skinNormal && skinTangent) + CALL_SKIN_FUNC(s_SkinVertices4Bones_Tangents, influence4); + else if (skinNormal) + CALL_SKIN_FUNC(s_SkinVertices4Bones, influence4); + else + CALL_SKIN_FUNC(s_SkinVertices4Bones_NoNormals, influence4); + } + + return true; +} +#else +bool SkinMeshOptimizedMobile(SkinMeshInfo& info) +{ + return false; +} +#endif // UNITY_SUPPORTS_VFP || UNITY_SUPPORTS_NEON + + diff --git a/Runtime/Filters/Mesh/MeshSkinningNEON.asm b/Runtime/Filters/Mesh/MeshSkinningNEON.asm new file mode 100644 index 0000000..494b397 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningNEON.asm @@ -0,0 +1,527 @@ + AREA .text, CODE + + EXPORT _s_SkinVertices_NEON + EXPORT _s_SkinVertices_NoNormals_NEON + EXPORT _s_SkinVertices_Tangents_NEON + EXPORT _s_SkinVertices2Bones_NEON + EXPORT _s_SkinVertices2Bones_NoNormals_NEON + EXPORT _s_SkinVertices2Bones_Tangents_NEON + EXPORT _s_SkinVertices4Bones_NEON + EXPORT _s_SkinVertices4Bones_NoNormals_NEON + EXPORT _s_SkinVertices4Bones_Tangents_NEON + +|_s_SkinVertices_NEON| PROC + mov ip, sp + vpush {d8-d10} + stmdb sp!, {r4, r5, r6, r7, r8} + ldr.w r4, [ip] + mov.w r8, #12 + ldr.w r5, [r3], #4 + add.w r7, r0, r5, lsl #6 + +|_s_SkinVertices_NEON_loop| + vld1.32 {d24-d27}, [r7@128]! + vld1.32 {d28-d31}, [r7@128] + vld1.32 {d6-d8}, [r1@64]! + vmul.f32 q0, q12, d6[0] + vmul.f32 q1, q12, d7[1] + cmp r1, r2 + pld [r1, #256] ; 0x100 + vmla.f32 q0, q13, d6[1] + vmla.f32 q1, q13, d8[0] + it cc + ldrcc.w r5, [r3], #4 + add.w r7, r0, r5, lsl #6 + vmla.f32 q0, q14, d7[0] + vmla.f32 q1, q14, d8[1] + pld [r7] + vadd.f32 q0, q0, q15 + vst1.32 {d0-d1}, [r4], r8 + vst1.32 {d2-d3}, [r4], r8 + bcc.w |_s_SkinVertices_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8} + vpop {d8-d10} + bx lr + ENDP + + +|_s_SkinVertices_NoNormals_NEON| PROC + mov ip, sp + vpush {d8-d10} + stmdb sp!, {r4, r5, r6, r7, r8} + ldr.w r4, [ip] + mov.w r8, #12 + ldr.w r5, [r3], #4 + add.w r7, r0, r5, lsl #6 + +|_s_SkinVertices_NoNormals_NEON_loop| + vld1.32 {d24-d27}, [r7@128]! + vld1.32 {d28-d31}, [r7@128] + vld1.32 {d6-d7}, [r1], r8 + vmul.f32 q0, q12, d6[0] + cmp r1, r2 + pld [r1, #256] ; 0x100 + vmla.f32 q0, q13, d6[1] + it cc + ldrcc.w r5, [r3], #4 + add.w r7, r0, r5, lsl #6 + vmla.f32 q0, q14, d7[0] + pld [r7] + vadd.f32 q0, q0, q15 + vst1.32 {d0-d1}, [r4], r8 + bcc.w |_s_SkinVertices_NoNormals_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8} + vpop {d8-d10} + bx lr + ENDP + + +|_s_SkinVertices_Tangents_NEON| PROC + mov ip, sp + vpush {d8-d10} + stmdb sp!, {r4, r5, r6, r7, r8} + ldr.w r4, [ip] + mov.w r8, #12 + ldr.w r5, [r3], #4 + add.w r7, r0, r5, lsl #6 + +|_s_SkinVertices_Tangents_NEON_loop| + vld1.32 {d24-d27}, [r7@128]! + vld1.32 {d28-d31}, [r7@128] + vld1.32 {d6-d8}, [r1@64]! + vld1.32 {d9-d10}, [r1@64]! + vmul.f32 q0, q12, d6[0] + vmul.f32 q1, q12, d7[1] + vmul.f32 q2, q12, d9[0] + cmp r1, r2 + pld [r1, #256] ; 0x100 + vmla.f32 q0, q13, d6[1] + vmla.f32 q1, q13, d8[0] + vmla.f32 q2, q13, d9[1] + it cc + ldrcc.w r5, [r3], #4 + add.w r7, r0, r5, lsl #6 + vmla.f32 q0, q14, d7[0] + vmla.f32 q1, q14, d8[1] + vmla.f32 q2, q14, d10[0] + pld [r7] + vadd.f32 q0, q0, q15 + vmov.f32 s11, s21 + vst1.32 {d0-d1}, [r4], r8 + vst1.32 {d2-d3}, [r4], r8 + vst1.32 {d4-d5}, [r4]! + bcc.w |_s_SkinVertices_Tangents_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8} + vpop {d8-d10} + bx lr + ENDP + + +|_s_SkinVertices2Bones_NEON| PROC + mov ip, sp + vpush {d8-d11} + stmdb sp!, {r4, r5, r6, r7, r8, sl} + ldr.w r4, [ip] + vld1.32 {d11}, [r3]! + ldmia r3!, {r5, r6} + add.w r7, r0, r5, lsl #6 + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q12, q8, d11[0] + vmul.f32 q13, q9, d11[0] + vld1.32 {d20-d23}, [r7@128] + add.w r7, r0, r6, lsl #6 + vmul.f32 q14, q10, d11[0] + vmul.f32 q15, q11, d11[0] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q12, q8, d11[1] + vmla.f32 q13, q9, d11[1] + ldr r5, [r3, #8] + mov.w r8, #12 + sub.w sl, r2, #24 + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q14, q10, d11[1] + nop + +|_s_SkinVertices2Bones_NEON_loop| + cmp r1, sl + add.w r7, r0, r5, lsl #6 + it cc + ldrcc r6, [r3, #12] + vld1.32 {d6-d8}, [r1@64]! + vmla.f32 q15, q11, d11[1] + vmul.f32 q0, q12, d6[0] + vld1.32 {d16-d19}, [r7@128]! + cmp r1, sl + vmul.f32 q1, q12, d7[1] + vld1.32 {d11}, [r3] + vmul.f32 q12, q8, d11[0] + pld [r1, #256] ; 0x100 + vmla.f32 q0, q13, d6[1] + vld1.32 {d20-d23}, [r7@128] + add.w r7, r0, r6, lsl #6 + vmla.f32 q1, q13, d8[0] + it cc + ldrcc r5, [r3, #24] + vmul.f32 q13, q9, d11[0] + vmla.f32 q0, q14, d7[0] + cmp r1, r2 + vmla.f32 q1, q14, d8[1] + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q14, q10, d11[0] + vadd.f32 q0, q0, q15 + vmul.f32 q15, q11, d11[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d11[1] + vst1.32 {d0-d1}, [r4], r8 + vmla.f32 q13, q9, d11[1] + vst1.32 {d2-d3}, [r4], r8 + add.w r3, r3, #16 + vmla.f32 q14, q10, d11[1] + bcc.w |_s_SkinVertices2Bones_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, sl} + vpop {d8-d11} + bx lr + ENDP + + +|_s_SkinVertices2Bones_NoNormals_NEON| PROC + mov ip, sp + vpush {d8-d11} + stmdb sp!, {r4, r5, r6, r7, r8, sl} + ldr.w r4, [ip] + vld1.32 {d11}, [r3]! + ldmia r3!, {r5, r6} + add.w r7, r0, r5, lsl #6 + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q12, q8, d11[0] + vmul.f32 q13, q9, d11[0] + vld1.32 {d20-d23}, [r7@128] + add.w r7, r0, r6, lsl #6 + vmul.f32 q14, q10, d11[0] + vmul.f32 q15, q11, d11[0] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q12, q8, d11[1] + vmla.f32 q13, q9, d11[1] + ldr r5, [r3, #8] + mov.w r8, #12 + sub.w sl, r2, #12 + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q14, q10, d11[1] + nop + nop.w + +|_s_SkinVertices2Bones_NoNormals_NEON_loop| + cmp r1, sl + add.w r7, r0, r5, lsl #6 + it cc + ldrcc r6, [r3, #12] + vld1.32 {d6-d7}, [r1], r8 + vmla.f32 q15, q11, d11[1] + vmul.f32 q0, q12, d6[0] + vld1.32 {d16-d19}, [r7@128]! + cmp r1, sl + vld1.32 {d11}, [r3] + vmul.f32 q12, q8, d11[0] + pld [r1, #256] ; 0x100 + vmla.f32 q0, q13, d6[1] + vld1.32 {d20-d23}, [r7@128] + add.w r7, r0, r6, lsl #6 + it cc + ldrcc r5, [r3, #24] + vmul.f32 q13, q9, d11[0] + vmla.f32 q0, q14, d7[0] + cmp r1, r2 + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q14, q10, d11[0] + vadd.f32 q0, q0, q15 + vmul.f32 q15, q11, d11[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d11[1] + vst1.32 {d0-d1}, [r4], r8 + vmla.f32 q13, q9, d11[1] + add.w r3, r3, #16 + vmla.f32 q14, q10, d11[1] + bcc.w |_s_SkinVertices2Bones_NoNormals_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, sl} + vpop {d8-d11} + bx lr + ENDP + + +|_s_SkinVertices2Bones_Tangents_NEON| PROC + mov ip, sp + vpush {d8-d11} + stmdb sp!, {r4, r5, r6, r7, r8, sl} + ldr.w r4, [ip] + vld1.32 {d11}, [r3]! + ldmia r3!, {r5, r6} + add.w r7, r0, r5, lsl #6 + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q12, q8, d11[0] + vmul.f32 q13, q9, d11[0] + vld1.32 {d20-d23}, [r7@128] + add.w r7, r0, r6, lsl #6 + vmul.f32 q14, q10, d11[0] + vmul.f32 q15, q11, d11[0] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q12, q8, d11[1] + vmla.f32 q13, q9, d11[1] + ldr r5, [r3, #8] + mov.w r8, #12 + sub.w sl, r2, #40 ; 0x28 + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q14, q10, d11[1] + nop + nop.w + +|_s_SkinVertices2Bones_Tangents_NEON_loop| + cmp r1, sl + add.w r7, r0, r5, lsl #6 + it cc + ldrcc r6, [r3, #12] + vld1.32 {d6-d8}, [r1@64]! + vmla.f32 q15, q11, d11[1] + vld1.32 {d9-d10}, [r1@64]! + vmul.f32 q0, q12, d6[0] + vld1.32 {d16-d19}, [r7@128]! + cmp r1, sl + vmul.f32 q1, q12, d7[1] + vmul.f32 q2, q12, d9[0] + vld1.32 {d11}, [r3] + vmul.f32 q12, q8, d11[0] + pld [r1, #256] ; 0x100 + vmla.f32 q0, q13, d6[1] + vld1.32 {d20-d23}, [r7@128] + add.w r7, r0, r6, lsl #6 + vmla.f32 q1, q13, d8[0] + vmla.f32 q2, q13, d9[1] + it cc + ldrcc r5, [r3, #24] + vmul.f32 q13, q9, d11[0] + vmla.f32 q0, q14, d7[0] + cmp r1, r2 + vmla.f32 q1, q14, d8[1] + vmla.f32 q2, q14, d10[0] + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q14, q10, d11[0] + vadd.f32 q0, q0, q15 + vmov.f32 s11, s21 + vmul.f32 q15, q11, d11[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d11[1] + vst1.32 {d0-d1}, [r4], r8 + vmla.f32 q13, q9, d11[1] + vst1.32 {d2-d3}, [r4], r8 + add.w r3, r3, #16 + vmla.f32 q14, q10, d11[1] + vst1.32 {d4-d5}, [r4]! + bcc.w |_s_SkinVertices2Bones_Tangents_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, sl} + vpop {d8-d11} + bx lr + ENDP + + +|_s_SkinVertices4Bones_NEON| PROC + mov ip, sp + vpush {d8-d12} + stmdb sp!, {r4, r5, r6, r7, r8} + ldr.w r4, [ip] + vld1.32 {d11-d12}, [r3]! + ldmia r3!, {r5, r6} + add.w r7, r0, r5, lsl #6 + vld1.32 {d16-d19}, [r7@128]! + vld1.32 {d20-d23}, [r7@128] + mov.w r8, #12 + nop.w + nop.w + nop.w + +|_s_SkinVertices4Bones_NEON_loop| + vmul.f32 q12, q8, d11[0] + vld1.32 {d6-d8}, [r1@64]! + vmul.f32 q13, q9, d11[0] + add.w r7, r0, r6, lsl #6 + vmul.f32 q14, q10, d11[0] + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q15, q11, d11[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d11[1] + ldmia r3!, {r5, r6} + vmla.f32 q13, q9, d11[1] + add.w r7, r0, r5, lsl #6 + cmp r1, r2 + vmla.f32 q14, q10, d11[1] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q15, q11, d11[1] + pld [r3, #256] ; 0x100 + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d12[0] + add.w r7, r0, r6, lsl #6 + vmla.f32 q13, q9, d12[0] + vmla.f32 q14, q10, d12[0] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q15, q11, d12[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d12[1] + vmla.f32 q13, q9, d12[1] + vmla.f32 q14, q10, d12[1] + vmla.f32 q15, q11, d12[1] + pld [r1, #256] ; 0x100 + vmul.f32 q0, q12, d6[0] + vld1.32 {d11-d12}, [r3]! + vmul.f32 q1, q12, d7[1] + it cc + ldmiacc r3!, {r5, r6} + vmla.f32 q0, q13, d6[1] + add.w r7, r0, r5, lsl #6 + vmla.f32 q1, q13, d8[0] + vldmia r7, {d16-d23} + vmla.f32 q0, q14, d7[0] + vmla.f32 q1, q14, d8[1] + vadd.f32 q0, q0, q15 + vst1.32 {d0-d1}, [r4], r8 + vst1.32 {d2-d3}, [r4], r8 + bcc.w |_s_SkinVertices4Bones_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8} + vpop {d8-d12} + bx lr + ENDP + + +|_s_SkinVertices4Bones_NoNormals_NEON| PROC + mov ip, sp + vpush {d8-d12} + stmdb sp!, {r4, r5, r6, r7, r8} + ldr.w r4, [ip] + vld1.32 {d11-d12}, [r3]! + ldmia r3!, {r5, r6} + add.w r7, r0, r5, lsl #6 + vld1.32 {d16-d19}, [r7@128]! + vld1.32 {d20-d23}, [r7@128] + mov.w r8, #12 + nop + nop.w + +|_s_SkinVertices4Bones_NoNormals_NEON_loop| + vmul.f32 q12, q8, d11[0] + vld1.32 {d6-d7}, [r1], r8 + vmul.f32 q13, q9, d11[0] + add.w r7, r0, r6, lsl #6 + vmul.f32 q14, q10, d11[0] + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q15, q11, d11[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d11[1] + ldmia r3!, {r5, r6} + vmla.f32 q13, q9, d11[1] + add.w r7, r0, r5, lsl #6 + cmp r1, r2 + vmla.f32 q14, q10, d11[1] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q15, q11, d11[1] + pld [r3, #256] ; 0x100 + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d12[0] + add.w r7, r0, r6, lsl #6 + vmla.f32 q13, q9, d12[0] + vmla.f32 q14, q10, d12[0] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q15, q11, d12[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d12[1] + vmla.f32 q13, q9, d12[1] + vmla.f32 q14, q10, d12[1] + vmla.f32 q15, q11, d12[1] + pld [r1, #256] ; 0x100 + vmul.f32 q0, q12, d6[0] + vld1.32 {d11-d12}, [r3]! + it cc + ldmiacc r3!, {r5, r6} + vmla.f32 q0, q13, d6[1] + add.w r7, r0, r5, lsl #6 + vldmia r7, {d16-d23} + vmla.f32 q0, q14, d7[0] + vadd.f32 q0, q0, q15 + vst1.32 {d0-d1}, [r4], r8 + bcc.w |_s_SkinVertices4Bones_NoNormals_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8} + vpop {d8-d12} + bx lr + ENDP + + +|_s_SkinVertices4Bones_Tangents_NEON| PROC + mov ip, sp + vpush {d8-d12} + stmdb sp!, {r4, r5, r6, r7, r8} + ldr.w r4, [ip] + vld1.32 {d11-d12}, [r3]! + ldmia r3!, {r5, r6} + add.w r7, r0, r5, lsl #6 + vld1.32 {d16-d19}, [r7@128]! + vld1.32 {d20-d23}, [r7@128] + mov.w r8, #12 + nop + nop.w + +|_s_SkinVertices4Bones_Tangents_NEON_loop| + vmul.f32 q12, q8, d11[0] + vld1.32 {d6-d8}, [r1@64]! + vmul.f32 q13, q9, d11[0] + vld1.32 {d9-d10}, [r1@64]! + add.w r7, r0, r6, lsl #6 + vmul.f32 q14, q10, d11[0] + vld1.32 {d16-d19}, [r7@128]! + vmul.f32 q15, q11, d11[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d11[1] + ldmia r3!, {r5, r6} + vmla.f32 q13, q9, d11[1] + add.w r7, r0, r5, lsl #6 + cmp r1, r2 + vmla.f32 q14, q10, d11[1] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q15, q11, d11[1] + pld [r3, #256] ; 0x100 + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d12[0] + add.w r7, r0, r6, lsl #6 + vmla.f32 q13, q9, d12[0] + vmla.f32 q14, q10, d12[0] + vld1.32 {d16-d19}, [r7@128]! + vmla.f32 q15, q11, d12[0] + vld1.32 {d20-d23}, [r7@128] + vmla.f32 q12, q8, d12[1] + vmla.f32 q13, q9, d12[1] + vmla.f32 q14, q10, d12[1] + vmla.f32 q15, q11, d12[1] + pld [r1, #256] ; 0x100 + vmul.f32 q0, q12, d6[0] + vld1.32 {d11-d12}, [r3]! + vmul.f32 q1, q12, d7[1] + vmul.f32 q2, q12, d9[0] + it cc + ldmiacc r3!, {r5, r6} + vmla.f32 q0, q13, d6[1] + add.w r7, r0, r5, lsl #6 + vmla.f32 q1, q13, d8[0] + vmla.f32 q2, q13, d9[1] + vldmia r7, {d16-d23} + vmla.f32 q0, q14, d7[0] + vmla.f32 q1, q14, d8[1] + vmla.f32 q2, q14, d10[0] + vadd.f32 q0, q0, q15 + vmov.f32 s11, s21 + vst1.32 {d0-d1}, [r4], r8 + vst1.32 {d2-d3}, [r4], r8 + vst1.32 {d4-d5}, [r4]! + bcc.w |_s_SkinVertices4Bones_Tangents_NEON_loop| + ldmia.w sp!, {r4, r5, r6, r7, r8} + vpop {d8-d12} + bx lr + nop + ENDP + + + END diff --git a/Runtime/Filters/Mesh/MeshSkinningNEON.s b/Runtime/Filters/Mesh/MeshSkinningNEON.s new file mode 100644 index 0000000..e94542d --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningNEON.s @@ -0,0 +1,183 @@ +#define UNITY_ASSEMBLER +#include "Configuration/PrefixConfigure.h" + +#if (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING) + +.set device,0 +.set device,__arm__ + +.if device + +//.code32 + +.globl _s_SkinVertices_NEON +.globl _s_SkinVertices_NoNormals_NEON +.globl _s_SkinVertices_Tangents_NEON + +.globl _s_SkinVertices2Bones_NEON +.globl _s_SkinVertices2Bones_NoNormals_NEON +.globl _s_SkinVertices2Bones_Tangents_NEON + +.globl _s_SkinVertices4Bones_NEON +.globl _s_SkinVertices4Bones_NoNormals_NEON +.globl _s_SkinVertices4Bones_Tangents_NEON + +#if UNITY_ANDROID +.hidden _s_SkinVertices_NEON +.hidden _s_SkinVertices_NoNormals_NEON +.hidden _s_SkinVertices_Tangents_NEON + +.hidden _s_SkinVertices2Bones_NEON +.hidden _s_SkinVertices2Bones_NoNormals_NEON +.hidden _s_SkinVertices2Bones_Tangents_NEON + +.hidden _s_SkinVertices4Bones_NEON +.hidden _s_SkinVertices4Bones_NoNormals_NEON +.hidden _s_SkinVertices4Bones_Tangents_NEON +#endif + + +//=========================================================================================================================================== + +#define SKIN_POS 1 +#define SKIN_POS_NRM 2 +#define SKIN_POS_NRM_TAN 3 + + +#define SKIN_2BONES 0 +#define SKIN_4BONES 0 + +_s_SkinVertices_NEON: + +#define SKIN_1BONE SKIN_POS_NRM +#define VERTEX_SZ 24 +#define LOOP_NAME _s_SkinVertices_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_1BONE + +_s_SkinVertices_NoNormals_NEON: + +#define SKIN_1BONE SKIN_POS +#define VERTEX_SZ 12 +#define LOOP_NAME _s_SkinVertices_NoNormals_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_1BONE + +_s_SkinVertices_Tangents_NEON: + +#define SKIN_1BONE SKIN_POS_NRM_TAN +#define VERTEX_SZ 40 +#define LOOP_NAME _s_SkinVertices_Tangents_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_1BONE + +#undef SKIN_4BONES +#undef SKIN_2BONES + +//=========================================================================================================================================== + +#define SKIN_1BONE 0 +#define SKIN_4BONES 0 + +_s_SkinVertices2Bones_NEON: + +#define SKIN_2BONES SKIN_POS_NRM +#define VERTEX_SZ 24 +#define LOOP_NAME _s_SkinVertices2Bones_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_2BONES + +_s_SkinVertices2Bones_NoNormals_NEON: + +#define SKIN_2BONES SKIN_POS +#define VERTEX_SZ 12 +#define LOOP_NAME _s_SkinVertices2Bones_NoNormals_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_2BONES + +_s_SkinVertices2Bones_Tangents_NEON: + +#define SKIN_2BONES SKIN_POS_NRM_TAN +#define VERTEX_SZ 40 +#define LOOP_NAME _s_SkinVertices2Bones_Tangents_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_2BONES + +#undef SKIN_4BONES +#undef SKIN_1BONE + + +//=========================================================================================================================================== + +#define SKIN_1BONE 0 +#define SKIN_2BONES 0 + +_s_SkinVertices4Bones_NEON: + +#define SKIN_4BONES SKIN_POS_NRM +#define VERTEX_SZ 24 +#define LOOP_NAME _s_SkinVertices4Bones_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_4BONES + +_s_SkinVertices4Bones_NoNormals_NEON: + +#define SKIN_4BONES SKIN_POS +#define VERTEX_SZ 12 +#define LOOP_NAME _s_SkinVertices4Bones_NoNormals_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_4BONES + +_s_SkinVertices4Bones_Tangents_NEON: + +#define SKIN_4BONES SKIN_POS_NRM_TAN +#define VERTEX_SZ 40 +#define LOOP_NAME _s_SkinVertices4Bones_Tangents_NEON_loop + +#include "MeshSkinningNeon_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_4BONES + + +#undef SKIN_2BONES +#undef SKIN_1BONE + +//=========================================================================================================================================== + +.endif + +#endif diff --git a/Runtime/Filters/Mesh/MeshSkinningNeon_Loop.h b/Runtime/Filters/Mesh/MeshSkinningNeon_Loop.h new file mode 100644 index 0000000..8e584da --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningNeon_Loop.h @@ -0,0 +1,487 @@ + +// defines +// SKIN_1BONE +// SKIN_2BONES +// SKIN_4BONES +// LOOP_NAME +// VERTEX_SZ + +// skin types +// SKIN_POS +// SKIN_POS_NRM +// SKIN_POS_NRM_TAN + + + +//r0: const void* bones4x4 +//r1: const void* srcVertData +//r2: const void* srcVertDataEnd +//r3: const BoneInfluence4* srcBoneInfluence4 +//[sp+0] -> r4: const void* dstVertData + +// r5, r6: index +// r7: matrix address +// r8: 12 (offset for vector3) + +// q0 <- output: pos +// q1 <- output: nrm +// q2 <- output: tan +// q3 <- input: pos +// q4 <- input: nrm +// q5 <- input: tan +// d11,d12 <- weights +// q12-q15 (blended matrix) +// q8-q11 (cur matrix) + + +// input: +// d6[0], d6[1], d7[0] <- pos +// d7[1], d8[0], d8[1] <- nrm +// d9[0], d9[1], d10[0], d10[1] <- tan +// q3 <- pos.x, pos.y, pos.z, nrm.x +// q4 <- nrm.y, nrm.z, tan.x, tan.y +// q5 <- tan.z, tan.w, w0, w1 + + +//=========================================================================================================================================== +// +// Common + +#define CALC_POS_1 vmul.f32 q0, q12, d6[0] +#define CALC_POS_2 vmla.f32 q0, q13, d6[1] +#define CALC_POS_3 vmla.f32 q0, q14, d7[0] +#define CALC_POS_4 vadd.f32 q0, q15 + +#define STORE_POS vst1.32 {d0, d1}, [r4], r8 + +#if (SKIN_1BONE == SKIN_POS_NRM) || (SKIN_1BONE == SKIN_POS_NRM_TAN) \ + || (SKIN_2BONES == SKIN_POS_NRM) || (SKIN_2BONES == SKIN_POS_NRM_TAN) \ + || (SKIN_4BONES == SKIN_POS_NRM) || (SKIN_4BONES == SKIN_POS_NRM_TAN) + + #define LOAD_POS_NRM vld1.32 {d6, d7, d8}, [r1, :64]! + #define STORE_NRM vst1.32 {d2, d3}, [r4], r8 + #define CALC_NRM_1 vmul.f32 q1, q12, d7[1] + #define CALC_NRM_2 vmla.f32 q1, q13, d8[0] + #define CALC_NRM_3 vmla.f32 q1, q14, d8[1] +#else + #define LOAD_POS_NRM vld1.32 {d6, d7}, [r1], r8 + #define STORE_NRM + #define CALC_NRM_1 + #define CALC_NRM_2 + #define CALC_NRM_3 +#endif + +#if (SKIN_1BONE == SKIN_POS_NRM_TAN) || (SKIN_2BONES == SKIN_POS_NRM_TAN) || (SKIN_4BONES == SKIN_POS_NRM_TAN) + #define LOAD_TAN vld1.32 {d9, d10}, [r1, :64]! + #define STORE_TAN vst1.32 {d4, d5}, [r4]! + #define CALC_TAN_1 vmul.f32 q2, q12, d9[0] + #define CALC_TAN_2 vmla.f32 q2, q13, d9[1] + #define CALC_TAN_3 vmla.f32 q2, q14, d10[0] + #define CALC_TAN_4 vmov.f32 s11, s21 +#else + #define LOAD_TAN + #define STORE_TAN + #define CALC_TAN_1 + #define CALC_TAN_2 + #define CALC_TAN_3 + #define CALC_TAN_4 +#endif + +// right after vertex-data will be copy-data stream, so be careful to not overwrite anything +#if (SKIN_1BONE == SKIN_POS) || (SKIN_2BONES == SKIN_POS) || (SKIN_4BONES == SKIN_POS) +#define STORE_POS_LAST1 vst1.32 {d0}, [r4]! +#define STORE_POS_LAST2 vst1.32 {d1[0]}, [r4]! +#else +#define STORE_POS_LAST1 STORE_POS +#define STORE_POS_LAST2 +#endif + +#if (SKIN_1BONE == SKIN_POS_NRM) || (SKIN_2BONES == SKIN_POS_NRM) || (SKIN_4BONES == SKIN_POS_NRM) +#define STORE_NRM_LAST1 vst1.32 {d2}, [r4]! +#define STORE_NRM_LAST2 vst1.32 {d3[0]}, [r4]! +#else +#define STORE_NRM_LAST1 STORE_NRM +#define STORE_NRM_LAST2 +#endif + +#define __NAME_EPILOGUE(x) x ## EPILOGUE +#define _NAME_EPILOGUE(x) __NAME_EPILOGUE(x) +#define LOOP_EPILOGUE _NAME_EPILOGUE(LOOP_NAME) + + + +#if (SKIN_1BONE == SKIN_POS) || (SKIN_1BONE == SKIN_POS_NRM) || (SKIN_1BONE == SKIN_POS_NRM_TAN) + #define LOAD_M_12 vld1.32 {q12,q13}, [r7,:128]! + #define LOAD_M_34 vld1.32 {q14,q15}, [r7,:128] +#else + #define LOAD_M_12 vld1.32 {q8,q9}, [r7,:128]! + #define LOAD_M_34 vld1.32 {q10,q11}, [r7,:128] +#endif + +#define WEIGHT_MATRIX_1(op,r) op.f32 q12, q8, r +#define WEIGHT_MATRIX_2(op,r) op.f32 q13, q9, r +#define WEIGHT_MATRIX_3(op,r) op.f32 q14, q10, r +#define WEIGHT_MATRIX_4(op,r) op.f32 q15, q11, r + +#define WEIGHT_M0_1 WEIGHT_MATRIX_1(vmul, d11[0]) +#define WEIGHT_M0_2 WEIGHT_MATRIX_2(vmul, d11[0]) +#define WEIGHT_M0_3 WEIGHT_MATRIX_3(vmul, d11[0]) +#define WEIGHT_M0_4 WEIGHT_MATRIX_4(vmul, d11[0]) + +#define WEIGHT_M1_1 WEIGHT_MATRIX_1(vmla, d11[1]) +#define WEIGHT_M1_2 WEIGHT_MATRIX_2(vmla, d11[1]) +#define WEIGHT_M1_3 WEIGHT_MATRIX_3(vmla, d11[1]) +#define WEIGHT_M1_4 WEIGHT_MATRIX_4(vmla, d11[1]) + +#define WEIGHT_M2_1 WEIGHT_MATRIX_1(vmla, d12[0]) +#define WEIGHT_M2_2 WEIGHT_MATRIX_2(vmla, d12[0]) +#define WEIGHT_M2_3 WEIGHT_MATRIX_3(vmla, d12[0]) +#define WEIGHT_M2_4 WEIGHT_MATRIX_4(vmla, d12[0]) + +#define WEIGHT_M3_1 WEIGHT_MATRIX_1(vmla, d12[1]) +#define WEIGHT_M3_2 WEIGHT_MATRIX_2(vmla, d12[1]) +#define WEIGHT_M3_3 WEIGHT_MATRIX_3(vmla, d12[1]) +#define WEIGHT_M3_4 WEIGHT_MATRIX_4(vmla, d12[1]) + + +//=========================================================================================================================================== +// +// 1 bone skinning + +#if (SKIN_1BONE == SKIN_POS) || (SKIN_1BONE == SKIN_POS_NRM) || (SKIN_1BONE == SKIN_POS_NRM_TAN) + +mov ip, sp + +vpush {d8-d10} +stmfd sp!, {r4-r8} + +ldr r4, [ip, #0] +mov r8, #12 + + ldr r5, [r3], #4 + add r7, r0, r5, lsl #6 + +LOOP_NAME: + + + +LOAD_M_12 +LOAD_M_34 + + +LOAD_POS_NRM +LOAD_TAN + +CALC_POS_1 +CALC_NRM_1 +CALC_TAN_1 + + cmp r1, r2 + pld [r1, #256] + +CALC_POS_2 +CALC_NRM_2 +CALC_TAN_2 + + ldrcc r5, [r3], #4 + add r7, r0, r5, lsl #6 + +CALC_POS_3 +CALC_NRM_3 +CALC_TAN_3 + + pld [r7] + +CALC_POS_4 +CALC_TAN_4 + +beq LOOP_EPILOGUE + +STORE_POS +STORE_NRM +STORE_TAN + +bcc LOOP_NAME + +LOOP_EPILOGUE: +STORE_POS_LAST1 +STORE_POS_LAST2 +STORE_NRM_LAST1 +STORE_NRM_LAST2 +STORE_TAN + + +ldmfd sp!, {r4-r8} +vpop {d8-d10} + +bx lr + + +//=========================================================================================================================================== +// +// 2 bones skinning + +#elif (SKIN_2BONES == SKIN_POS || SKIN_2BONES == SKIN_POS_NRM || SKIN_2BONES == SKIN_POS_NRM_TAN) + +mov ip, sp + +vpush {d8-d11} +stmfd sp!, {r4,r5,r6,r7,r8,r10} + +ldr r4, [ip, #0] + +vld1.32 {d11}, [r3,:64]! // wgt -> +ldmia r3!, {r5-r6} // idx -> + +add r7, r0, r5, lsl #6 // M0 .. +LOAD_M_12 // M0 +WEIGHT_M0_1 +WEIGHT_M0_2 + +LOAD_M_34 // M0 +add r7, r0, r6, lsl #6 // M1 .. +WEIGHT_M0_3 +WEIGHT_M0_4 + +LOAD_M_12 // M1 +WEIGHT_M1_1 +WEIGHT_M1_2 + +ldr r5, [r3, #8] // idx0 + +mov r8, #12 +sub r10, r2, #VERTEX_SZ + +LOAD_M_34 // M1 + +WEIGHT_M1_3 + +.align 4 +LOOP_NAME: + + cmp r1, r10 + + add r7, r0, r5, lsl #6 // M0 .. + ldrcc r6, [r3, #12] // idx1 +LOAD_POS_NRM + +WEIGHT_M1_4 + +LOAD_TAN + +CALC_POS_1 +LOAD_M_12 // M0 + cmp r1, r10 +CALC_NRM_1 +CALC_TAN_1 +vld1.32 {d11}, [r3,:64] // wgt -> + +WEIGHT_M0_1 + pld [r1,#256] + +CALC_POS_2 +LOAD_M_34 // M0 + add r7, r0, r6, lsl #6 // M1 .. +CALC_NRM_2 +CALC_TAN_2 + ldrcc r5, [r3, #24] // idx0 +WEIGHT_M0_2 +CALC_POS_3 + + cmp r1, r2 +CALC_NRM_3 +CALC_TAN_3 +LOAD_M_12 // M1 + + +WEIGHT_M0_3 + +CALC_POS_4 +CALC_TAN_4 + +WEIGHT_M0_4 +LOAD_M_34 // M1 + +beq LOOP_EPILOGUE + +WEIGHT_M1_1 +STORE_POS + +WEIGHT_M1_2 +STORE_NRM + add r3, r3, #16 +WEIGHT_M1_3 +STORE_TAN + +bcc LOOP_NAME + +LOOP_EPILOGUE: +STORE_POS_LAST1 +STORE_POS_LAST2 +STORE_NRM_LAST1 +STORE_NRM_LAST2 +STORE_TAN + + +ldmfd sp!, {r4,r5,r6,r7,r8,r10} +vpop {d8-d11} +bx lr + + +//=========================================================================================================================================== +// +// 4 bones skinning + +#elif (SKIN_4BONES == SKIN_POS || SKIN_4BONES == SKIN_POS_NRM || SKIN_4BONES == SKIN_POS_NRM_TAN) + + +mov ip, sp + +vpush {d8-d12} +stmfd sp!, {r4-r8} + +ldr r4, [ip, #0] + +vld1.32 {d11,d12}, [r3,:128]! // wgt -> +ldmia r3!, {r5-r6} // idx' -> + +add r7, r0, r5, lsl #6 // M0 .. +LOAD_M_12 // M0 +LOAD_M_34 // M0 + +mov r8, #12 + +.align 4 +LOOP_NAME: + +WEIGHT_M0_1 +LOAD_POS_NRM + +WEIGHT_M0_2 +LOAD_TAN + add r7, r0, r6, lsl #6 // M1 .. + + +WEIGHT_M0_3 +LOAD_M_12 // M1 + +WEIGHT_M0_4 +LOAD_M_34 // M1 + +WEIGHT_M1_1 + ldmia r3!, {r5-r6} // idx'' -> + +WEIGHT_M1_2 + add r7, r0, r5, lsl #6 // M2 .. + cmp r1, r2 + +WEIGHT_M1_3 +LOAD_M_12 // M2 + +WEIGHT_M1_4 + pld [r3, #256] +LOAD_M_34 // M2 + +WEIGHT_M2_1 + add r7, r0, r6, lsl #6 // M3 .. +WEIGHT_M2_2 +WEIGHT_M2_3 +LOAD_M_12 // M3 +WEIGHT_M2_4 + +LOAD_M_34 // M3 +WEIGHT_M3_1 +WEIGHT_M3_2 +WEIGHT_M3_3 +WEIGHT_M3_4 + pld [r1, #256] + +CALC_POS_1 +vld1.32 {d11,d12}, [r3,:128]! // wgt -> + +CALC_NRM_1 +CALC_TAN_1 + ldmcc r3!, {r5-r6} // idx -> + +CALC_POS_2 + add r7, r0, r5, lsl #6 // M0 .. +CALC_NRM_2 +CALC_TAN_2 +vldmia r7, {q8-q11} // M0 -> + +CALC_POS_3 +CALC_NRM_3 +CALC_TAN_3 + +CALC_POS_4 +CALC_TAN_4 + +beq LOOP_EPILOGUE + +STORE_POS +STORE_NRM +STORE_TAN + +bcc LOOP_NAME + +LOOP_EPILOGUE: +STORE_POS_LAST1 +STORE_POS_LAST2 +STORE_NRM_LAST1 +STORE_NRM_LAST2 +STORE_TAN + + +ldmfd sp!, {r4-r8} +vpop {d8-d12} +bx lr + + +//=========================================================================================================================================== + +#endif + +#undef __NAME_EPILOGUE +#undef _NAME_EPILOGUE +#undef LOOP_EPILOGUE +#undef CALC_POS_1 +#undef CALC_POS_2 +#undef CALC_POS_3 +#undef STORE_POS +#undef STORE_POS_LAST1 +#undef STORE_POS_LAST2 +#undef LOAD_POS_NRM +#undef STORE_NRM +#undef STORE_NRM_LAST1 +#undef STORE_NRM_LAST2 +#undef CALC_NRM_1 +#undef CALC_NRM_2 +#undef CALC_NRM_3 +#undef LOAD_TAN +#undef STORE_TAN +#undef CALC_TAN_1 +#undef CALC_TAN_2 +#undef CALC_TAN_3 +#undef CALC_TAN_4 +#undef LOAD_M_12 +#undef LOAD_M_34 +#undef WEIGHT_MATRIX_1 +#undef WEIGHT_MATRIX_2 +#undef WEIGHT_MATRIX_3 +#undef WEIGHT_MATRIX_4 +#undef WEIGHT_M0_1 +#undef WEIGHT_M0_2 +#undef WEIGHT_M0_3 +#undef WEIGHT_M0_4 +#undef WEIGHT_M1_1 +#undef WEIGHT_M1_2 +#undef WEIGHT_M1_3 +#undef WEIGHT_M1_4 +#undef WEIGHT_M2_1 +#undef WEIGHT_M2_2 +#undef WEIGHT_M2_3 +#undef WEIGHT_M2_4 +#undef WEIGHT_M3_1 +#undef WEIGHT_M3_2 +#undef WEIGHT_M3_3 +#undef WEIGHT_M3_4 diff --git a/Runtime/Filters/Mesh/MeshSkinningSSE2.asm b/Runtime/Filters/Mesh/MeshSkinningSSE2.asm new file mode 100644 index 0000000..395bf16 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningSSE2.asm @@ -0,0 +1,323 @@ +;; SkinSSE2.s +;; +;; Created by Kaspar Daugaard on 1/12/11. +;; Copyright 2011 Unity Technologies. All rights reserved. + +bits 32 + +section .text align=32 + +%define normalOffset 12 +%define tangentOffset 24 + +%macro SkinSSE2_Generic 3 + ; %1 numBones + ; %2 hasNormals + ; %3 hasTangents + ; [ebp + 8] inVertices + ; [ebp + 12] outVertices + ; [ebp + 16] numVertices + ; [ebp + 20] boneMatrices + ; [ebp + 24] weightsAndIndices + ; [ebp + 28] inputStride + ; [ebp + 32] outputStride + + push ebp + mov ebp, esp + pushad + + ; Local variables (32 byte aligned) + ; [esp + 0] MaskW + ; [esp + 16] MaskVec3 + ; [esp + 32] savedEcx + sub esp, 16*3 + and esp, ~31 + + ; Create bitmasks on stack + sub eax, eax + mov [esp + 0], eax ; MaskW + mov [esp + 4], eax + mov [esp + 8], eax + dec eax + mov [esp + 12], eax + mov [esp + 16], eax ; MaskVec3 + mov [esp + 20], eax + mov [esp + 24], eax + inc eax + mov [esp + 28], eax + + mov esi, [ebp + 8] ; inVertices + mov edi, [ebp + 12] ; outVertices + mov ecx, [ebp + 16] ; numVertices + mov edx, [ebp + 24] ; weightsAndIndices + + ; Prefetch vertices + prefetchnta [edx] + prefetchnta [esi] + prefetchnta [esi + 32] + + align 32 + +%%SkinSSE2_loop: + prefetchnta [esi + 64] + + mov ebx, [ebp + 20] ; boneMatrices + mov [esp + 32], ecx ; savedEcx + + ; Load first bone index +%if %1 == 1 + ; Single bone, no weight + mov eax, [edx] + shl eax, 6 +%else + ; Indices come after weights + mov eax, [edx + %1*4] + shl eax, 6 + prefetchnta [ebx + eax] + prefetchnta [ebx + eax + 32] + + ; Load second bone index + mov ecx, [edx + %1*4 + 4] + shl ecx, 6 + prefetchnta [ebx + ecx] + prefetchnta [ebx + ecx + 32] + + ; Load all weights to xmm0 + movups xmm0, [edx] +%endif + + ; Load first matrix to xmm4-xmm7 + movaps xmm4, [ebx + eax] + movaps xmm5, [ebx + eax + 16] + movaps xmm6, [ebx + eax + 32] + movaps xmm7, [ebx + eax + 48] + +%if %1 >= 2 + ; Multiply first matrix with first weight + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0x00 + mulps xmm4, xmm1 + mulps xmm5, xmm1 + mulps xmm6, xmm1 + mulps xmm7, xmm1 +%endif + +%if %1 >= 3 + ; Load third bone index + mov eax, [edx + %1*4 + 8] + shl eax, 6 + prefetchnta [ebx + eax] + prefetchnta [ebx + eax + 32] +%endif + +%if %1 >= 2 + ; Load first two rows of the second matrix to xmm2-xmm3 + movaps xmm2, [ebx + ecx] + movaps xmm3, [ebx + ecx + 16] + ; Shuffle second weight to all elements of xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0x55 + ; Multiply two first rows of second matrix with second weight + mulps xmm2, xmm1 + mulps xmm3, xmm1 + ; Add + addps xmm4, xmm2 + addps xmm5, xmm3 + + ; Load last two rows of the second matrix to xmm2-xmm3 + movaps xmm2, [ebx + ecx + 32] + movaps xmm3, [ebx + ecx + 48] + ; Multiply two last rows of the second matri with second weight + mulps xmm2, xmm1 + mulps xmm3, xmm1 + ; Add + addps xmm6, xmm2 + addps xmm7, xmm3 +%endif + +%if %1 >= 4 + ; Load fourth bone index + mov ecx, [edx + %1*4 + 12] + shl ecx, 6 + prefetchnta [ebx + ecx] + prefetchnta [ebx + ecx + 32] +%endif + +%if %1 >= 3 + ; Load first two rows of the third matrix to xmm2-xmm3 + movaps xmm2, [ebx + eax] + movaps xmm3, [ebx + eax + 16] + ; Shuffle third weight to all elements of xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0xaa + ; Multiply first two rows of third matrix with third weight + mulps xmm2, xmm1 + mulps xmm3, xmm1 + ; Add + addps xmm4, xmm2 + addps xmm5, xmm3 + + ; Load last two rows of the third matrix to xmm2-xmm3 + movaps xmm2, [ebx + eax + 32] + movaps xmm3, [ebx + eax + 48] + ; Multiply last two rows of third matrix with third weight + mulps xmm2, xmm1 + mulps xmm3, xmm1 + ; Add + addps xmm6, xmm2 + addps xmm7, xmm3 +%endif + +%if %1 >= 4 + ; Load first two rows of the fourth matrix into xmm2-xmm3 + movaps xmm2, [ebx + ecx] + movaps xmm3, [ebx + ecx + 16] + ; Shuffle fourth weight to all elements of xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0xff + ; Multiply first two rows of the fourth matrix with fourth weight + mulps xmm2, xmm1 + mulps xmm3, xmm1 + ; Add + addps xmm4, xmm2 + addps xmm5, xmm3 + + ; Load last two rows of the fourth matrix to xmm2-xmm3 + movaps xmm2, [ebx + ecx + 32] + movaps xmm3, [ebx + ecx + 48] + ; Multiply last two rows of the fourth matrix with fourth weight + mulps xmm2, xmm1 + mulps xmm3, xmm1 + ; Add + addps xmm6, xmm2 + addps xmm7, xmm3 +%endif + + ; Matrix is in xmm4-xmm7 + ; Transform position by 4x4 matrix in xmm4-xmm7 + movups xmm0, [esi] + movaps xmm1, xmm0 + movaps xmm2, xmm0 + shufps xmm1, xmm1, 0x55 + shufps xmm2, xmm2, 0xaa + shufps xmm0, xmm0, 0x00 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + mulps xmm0, xmm4 + addps xmm1, xmm2 + addps xmm0, xmm7 + addps xmm0, xmm1 + ; Store vertex position in outvert + movaps xmm7, [esp + 16] ; MaskVec3 + maskmovdqu xmm0, xmm7 + +%if %2 ; Has normal + ; Transform vector by 3x3 matrix in xmm4-xmm6 + movups xmm0, [esi + normalOffset] + movaps xmm1, xmm0 + movaps xmm2, xmm0 + shufps xmm1, xmm1, 0x55 + shufps xmm2, xmm2, 0xaa + shufps xmm0, xmm0, 0x00 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + mulps xmm0, xmm4 + addps xmm1, xmm2 + addps xmm0, xmm1 +%endif + +%if %3 ; Has tangent + ; Transform vector by 3x3 matrix in xmm4-xmm6 + movups xmm1, [esi + tangentOffset] + movaps xmm2, xmm1 + movaps xmm3, xmm1 + shufps xmm2, xmm2, 0x55 + shufps xmm3, xmm3, 0xaa + mulps xmm2, xmm5 + mulps xmm3, xmm6 + movaps xmm6, xmm1 ; Save original tangent's W in xmm6 + shufps xmm1, xmm1, 0x00 + andps xmm6, [esp + 0] ; MaskW + mulps xmm1, xmm4 + addps xmm2, xmm3 + addps xmm1, xmm2 +%endif + +%if %2 || %3 ; Has normal or tangent + ; Calculate lengths and normalize + movaps xmm2, xmm0 + movaps xmm5, xmm1 + mulps xmm2, xmm2 + mulps xmm5, xmm5 + movaps xmm3, xmm2 + movaps xmm4, xmm2 + shufps xmm3, xmm5, 0x55 + shufps xmm4, xmm5, 0xaa + shufps xmm2, xmm5, 0x00 + addps xmm3, xmm4 + addps xmm2, xmm3 + sqrtps xmm2, xmm2 + rcpps xmm2, xmm2 + movaps xmm3, xmm2 + shufps xmm2, xmm2, 0x00 + shufps xmm3, xmm3, 0xaa + mulps xmm0, xmm2 + mulps xmm1, xmm3 +%endif + +%if %2 ; Write normal + add edi, normalOffset + maskmovdqu xmm0, xmm7 ; MaskVec3 + sub edi, normalOffset +%endif + +%if %3 ; Write tangent + andps xmm1, xmm7 ; MaskVec3 + orps xmm1, xmm6 ; Restore original W + movups [edi + tangentOffset], xmm1 +%endif + +%if %1 == 1 + ; Indices only + add edx, 4 +%else + ; Indices and weights + add edx, %1 * 8 +%endif + + add esi, [ebp + 28] ; inputStride + add edi, [ebp + 32] ; outputStride + mov ecx, [esp + 32] ; savedEcx + dec ecx + jnz %%SkinSSE2_loop + + ; Remove local variables from stack + lea esp, [ebp-32] + + popad + pop ebp + ret + align 16 +%endmacro + + +global SkinSSE2_1Bone_Pos +global SkinSSE2_2Bones_Pos +global SkinSSE2_4Bones_Pos +global SkinSSE2_1Bone_PosNormal +global SkinSSE2_2Bones_PosNormal +global SkinSSE2_4Bones_PosNormal +global SkinSSE2_1Bone_PosNormalTan +global SkinSSE2_2Bones_PosNormalTan +global SkinSSE2_4Bones_PosNormalTan + + +SkinSSE2_1Bone_Pos: SkinSSE2_Generic 1, 0, 0 +SkinSSE2_2Bones_Pos: SkinSSE2_Generic 2, 0, 0 +SkinSSE2_4Bones_Pos: SkinSSE2_Generic 4, 0, 0 +SkinSSE2_1Bone_PosNormal: SkinSSE2_Generic 1, 1, 0 +SkinSSE2_2Bones_PosNormal: SkinSSE2_Generic 2, 1, 0 +SkinSSE2_4Bones_PosNormal: SkinSSE2_Generic 4, 1, 0 +SkinSSE2_1Bone_PosNormalTan: SkinSSE2_Generic 1, 1, 1 +SkinSSE2_2Bones_PosNormalTan: SkinSSE2_Generic 2, 1, 1 +SkinSSE2_4Bones_PosNormalTan: SkinSSE2_Generic 4, 1, 1 diff --git a/Runtime/Filters/Mesh/MeshSkinningSSE2.h b/Runtime/Filters/Mesh/MeshSkinningSSE2.h new file mode 100644 index 0000000..c085309 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningSSE2.h @@ -0,0 +1,129 @@ +#if UNITY_SUPPORTS_SSE && !UNITY_64 + +#if UNITY_OSX || UNITY_LINUX +#define __cdecl +#endif + +#define SKIN_SSE2_PARAMS \ + const void* inVertices, \ + void* outVertices, \ + int numVertices, \ + const void* boneMatrices, \ + const void* weightsAndIndices, \ + int inputStride, \ + int outputStride + +typedef void (__cdecl *SkinSSE2_Function)(SKIN_SSE2_PARAMS); + +extern "C" +{ + void __cdecl SkinSSE2_1Bone_Pos(SKIN_SSE2_PARAMS); + void __cdecl SkinSSE2_2Bones_Pos(SKIN_SSE2_PARAMS); + void __cdecl SkinSSE2_4Bones_Pos(SKIN_SSE2_PARAMS); + void __cdecl SkinSSE2_1Bone_PosNormal(SKIN_SSE2_PARAMS); + void __cdecl SkinSSE2_2Bones_PosNormal(SKIN_SSE2_PARAMS); + void __cdecl SkinSSE2_4Bones_PosNormal(SKIN_SSE2_PARAMS); + void __cdecl SkinSSE2_1Bone_PosNormalTan(SKIN_SSE2_PARAMS); + void __cdecl SkinSSE2_2Bones_PosNormalTan(SKIN_SSE2_PARAMS); + void __cdecl SkinSSE2_4Bones_PosNormalTan(SKIN_SSE2_PARAMS); +} + + +bool SkinMeshOptimizedSSE2(SkinMeshInfo& info) +{ + if (!CPUInfo::HasSSE2Support()) + { + return false; + } + + SkinSSE2_Function skinFunc = NULL; + + if (!info.skinNormals && !info.skinTangents) + { + switch (info.bonesPerVertex) + { + DebugAssert(info.inStride == sizeof(Vector3f)); + case 1: + skinFunc = &SkinSSE2_1Bone_Pos; + break; + case 2: + skinFunc = &SkinSSE2_2Bones_Pos; + break; + case 4: + skinFunc = &SkinSSE2_4Bones_Pos; + break; + + } + } + else if (info.skinNormals && !info.skinTangents) + { + DebugAssert(info.inStride == sizeof(Vector3f) + sizeof(Vector3f)); + switch (info.bonesPerVertex) + { + case 1: + skinFunc = &SkinSSE2_1Bone_PosNormal; + break; + case 2: + skinFunc = &SkinSSE2_2Bones_PosNormal; + break; + case 4: + skinFunc = &SkinSSE2_4Bones_PosNormal; + break; + + } + } + else if (info.skinNormals && info.skinTangents) + { + DebugAssert(info.inStride == sizeof(Vector3f) + sizeof(Vector3f) + sizeof(Vector4f)); + switch (info.bonesPerVertex) + { + case 1: + skinFunc = &SkinSSE2_1Bone_PosNormalTan; + break; + case 2: + skinFunc = &SkinSSE2_2Bones_PosNormalTan; + break; + case 4: + skinFunc = &SkinSSE2_4Bones_PosNormalTan; + break; + + } + } + + if (skinFunc == NULL) + return false; + + // Skin all vertices apart from last one! + if (info.vertexCount > 1) + { + (*skinFunc)(info.inVertices, info.outVertices, info.vertexCount - 1,info.cachedPose, info.compactSkin, info.inStride, info.outStride); + } + // Copy last vertex to stack to avoid reading/writing past end of buffer + if (info.vertexCount > 0) + { + const int maxStride = 2 * sizeof(Vector3f) + sizeof(Vector4f) + 4; + Assert(info.inStride <= maxStride && info.outStride <= maxStride); + // Need 4 bytes padding to access Vec3 as Vec4 + char vertexCopyIn[maxStride + 4]; + char vertexCopyOut[maxStride + 4]; + int skinStride = (info.bonesPerVertex == 4) ? sizeof(BoneInfluence) : + (info.bonesPerVertex == 2) ? sizeof(BoneInfluence2) : + (info.bonesPerVertex == 1) ? sizeof(int) : 0; + Assert(skinStride != 0); + int index = info.vertexCount - 1; + const char* compactSkin = static_cast<const char*>(info.compactSkin) + index * skinStride; + const char* inVertex = static_cast<const char*>(info.inVertices) + index * info.inStride; + char* outVertex = static_cast<char*>(info.outVertices) + index * info.outStride; + memcpy(vertexCopyIn, inVertex, info.inStride); + (*skinFunc)(vertexCopyIn, vertexCopyOut, 1, info.cachedPose, compactSkin, info.inStride, info.outStride); + memcpy(outVertex, vertexCopyOut, info.outStride); + } + + return true; +} +#else +inline bool SkinMeshOptimizedSSE2(SkinMeshInfo& info) +{ + return false; +} +#endif diff --git a/Runtime/Filters/Mesh/MeshSkinningTests.cpp b/Runtime/Filters/Mesh/MeshSkinningTests.cpp new file mode 100644 index 0000000..407729b --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningTests.cpp @@ -0,0 +1,228 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" + +#if ENABLE_UNIT_TESTS && UNITY_SUPPORTS_SSE && !UNITY_64 + +#include "Runtime/Filters/Mesh/MeshSkinning.h" +#include "External/UnitTest++/src/UnitTest++.h" +#include "Runtime/Allocator/MemoryMacros.h" +#include "Runtime/Math/Random/rand.h" +#include "Runtime/Math/Matrix4x4.h" + +bool SkinMeshOptimizedSSE2(SkinMeshInfo& info); +void SkinMesh(SkinMeshInfo& info); + +Vector3f RandomVector3InUnitBox(Rand& rnd) +{ + return Vector3f(rnd.GetSignedFloat(), + rnd.GetSignedFloat(), + rnd.GetSignedFloat()); +} + +SUITE (MeshSkinningTests) +{ +TEST(MeshSkinning_AllFeatures) +{ + int failedPositions = 0; + int failedNormals = 0; + int failedTangents = 0; + int failedTangentSigns = 0; + int failedVertexCopies = 0; + + const int minVertices = 1; + const int maxVertices = 100; + const int positionSize = 3*sizeof(float); + const int normalSize = 3*sizeof(float); + const int tangentSize = 4*sizeof(float); + const int maxStride = positionSize + normalSize + tangentSize; + const int trailingBytes = 128; + + UInt8 inVertices[maxVertices * maxStride]; + UInt8 outVerticesRef[maxVertices * maxStride + trailingBytes]; + UInt8 outVerticesSimd[maxVertices * maxStride + trailingBytes]; + + SkinMeshInfo info; + memset(&info, 0, sizeof(info)); + info.inVertices = inVertices; + info.vertexCount = minVertices; + info.normalOffset = positionSize; + info.tangentOffset = positionSize + normalSize; + + // Try a large offset so AABBs don't contain (0,0,0) + Vector3f posOffset(-2000, 0, 2000); + + const int numBones = 64; + Matrix4x4f *cachedPose; + ALLOC_TEMP_ALIGNED(cachedPose, Matrix4x4f, numBones, 32); + info.cachedPose = cachedPose; + for (int i = 0; i < numBones; i++) + { + Matrix4x4f mat; + mat.SetScale(Vector3f(1.0 + 0.5f*sin(i*0.3f), + 1.0 + 0.5f*sin(i*0.5f), + 1.0 + 0.5f*sin(i*0.7f))); + mat.SetPosition(Vector3f(100.0f*sin(i*1.0f), + 100.0f*sin(i*2.5f), + 100.0f*sin(i*3.3f)) + posOffset); + cachedPose[i] = mat; + } + info.boneCount = numBones; + + Rand rnd(123); + + int boneIndices[maxVertices]; + BoneInfluence2 boneInfl2[maxVertices]; + BoneInfluence boneInfl4[maxVertices]; + for (int i = 0; i < maxVertices; i++) + { + boneIndices[i] = i%numBones; + + BoneInfluence2& b2 = boneInfl2[i]; + b2.boneIndex[0] = (i)%numBones; + b2.boneIndex[1] = (i/2+10)%numBones; + b2.weight[0] = rnd.GetFloat(); + b2.weight[1] = 1.0f - b2.weight[0]; + + BoneInfluence& b4 = boneInfl4[i]; + b4.boneIndex[0] = (i)%numBones; + b4.boneIndex[1] = (i/2+10)%numBones; + b4.boneIndex[2] = (i/3+20)%numBones; + b4.boneIndex[3] = (i/4+30)%numBones; + float weightLeft = 1.0f; + for (int j=0; j<3; j++) + { + b4.weight[j] = weightLeft * rnd.GetFloat(); + weightLeft -= b4.weight[j]; + } + b4.weight[3] = weightLeft; + } + + for (info.bonesPerVertex = 1; info.bonesPerVertex <= 4; info.bonesPerVertex++) + { + if (info.bonesPerVertex == 3) continue; + + switch (info.bonesPerVertex) + { + case 1: + info.compactSkin = boneIndices; + break; + case 2: + info.compactSkin = boneInfl2; + break; + case 4: + info.compactSkin = boneInfl4; + break; + } + + for (int skinNormals = 0; skinNormals <= 1; skinNormals++) + { + info.skinNormals = (skinNormals != 0); + + for (int skinTangents = 0; skinTangents <= 1; skinTangents++) + { + if (!skinNormals && skinTangents) continue; + info.skinTangents = (skinTangents != 0); + + // Randomize vertex count and stride + info.vertexCount += 7; + while (info.vertexCount > maxVertices) info.vertexCount -= (maxVertices - minVertices); + info.inStride = positionSize; + info.inStride += skinNormals ? normalSize : 0; + info.inStride += skinTangents ? tangentSize : 0; + info.outStride = info.inStride; + + UInt8* inVert = inVertices; + for (int i = 0; i < info.vertexCount; i++) + { + Vector3f* nextVec = (Vector3f*)inVert; + Vector3f pos = RandomVector3InUnitBox(rnd); + pos *= 1000.0f; + *nextVec++ = pos; + if (info.skinNormals) + { + Vector3f normal = RandomVector3InUnitBox(rnd); + normal = NormalizeSafe(normal); + *nextVec++ = normal; + } + + if (info.skinTangents) + { + Vector3f tangent = RandomVector3InUnitBox(rnd); + tangent = NormalizeSafe(tangent); + *nextVec++ = tangent; + float* tangentSign = (float*)nextVec; + *tangentSign = (rnd.GetSignedFloat() < 0.0f) ? -1.0f : 1.0f; + } + inVert += info.inStride; + } + + int outSize = info.vertexCount * info.outStride; + memset(outVerticesRef, 0xcc, outSize + trailingBytes); + memset(outVerticesSimd, 0xdd, outSize + trailingBytes); + + info.outVertices = outVerticesRef; + SkinMesh(info); + + info.outVertices = outVerticesSimd; + bool successSimd = SkinMeshOptimizedSSE2(info); + CHECK(successSimd); + + // Check if we wrote past end of buffer + for (int i = 0; i < trailingBytes; i++) + { + CHECK_EQUAL(0xcc, outVerticesRef[outSize + i]); + CHECK_EQUAL(0xdd, outVerticesSimd[outSize + i]); + } + + inVert = inVertices; + UInt8* vertRef = outVerticesRef; + UInt8* vertSimd = outVerticesSimd; + for (int i = 0; i < info.vertexCount; i++) + { + Vector3f* posRef = (Vector3f*)vertRef; + Vector3f* posSimd = (Vector3f*)vertRef; + if (!CompareApproximately(*posRef, *posSimd)) + { + failedPositions++; + } + if (info.skinNormals) + { + Vector3f* normalRef = (Vector3f*)(vertRef + info.normalOffset); + Vector3f* normalSimd = (Vector3f*)(vertRef + info.normalOffset); + if (!CompareApproximately(*normalRef, *normalSimd)) + { + failedNormals++; + } + } + if (info.skinTangents) + { + Vector3f* tangentRef = (Vector3f*)(vertRef + info.tangentOffset); + Vector3f* tangentSimd = (Vector3f*)(vertRef + info.tangentOffset); + if (!CompareApproximately(*tangentRef, *tangentSimd)) + { + failedTangents++; + } + float* tangentSignRef = (float*)(vertRef + info.tangentOffset + sizeof(Vector3f)); + float* tangentSignSimd = (float*)(vertRef + info.tangentOffset + sizeof(Vector3f)); + if (*tangentSignRef != *tangentSignSimd) + { + failedTangentSigns++; + } + } + + inVert += info.inStride; + vertRef += info.outStride; + vertSimd += info.outStride; + } + } + } + } + + CHECK_EQUAL(0, failedPositions); + CHECK_EQUAL(0, failedNormals); + CHECK_EQUAL(0, failedTangents); + CHECK_EQUAL(0, failedTangentSigns); + CHECK_EQUAL(0, failedVertexCopies); +} +} +#endif diff --git a/Runtime/Filters/Mesh/MeshSkinningVFP.s b/Runtime/Filters/Mesh/MeshSkinningVFP.s new file mode 100644 index 0000000..8829981 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningVFP.s @@ -0,0 +1,187 @@ +#define UNITY_ASSEMBLER +#include "Configuration/PrefixConfigure.h" +#include "Runtime/Utilities/VFPUtility.h" + +#if UNITY_SUPPORTS_VFP + +.syntax unified + +.set device,0 +.set device,__arm__ + +.if device + +//.code32 +.globl _s_SkinVertices_VFP +.globl _s_SkinVertices_NoNormals_VFP +.globl _s_SkinVertices_Tangents_VFP + +.globl _s_SkinVertices2Bones_VFP +.globl _s_SkinVertices2Bones_NoNormals_VFP +.globl _s_SkinVertices2Bones_Tangents_VFP + +.globl _s_SkinVertices4Bones_VFP +.globl _s_SkinVertices4Bones_Copy4Ints_VFP +.globl _s_SkinVertices4Bones_NoNormals_VFP +.globl _s_SkinVertices4Bones_NoNormals_Copy4Ints_VFP +.globl _s_SkinVertices4Bones_Tangents_VFP +.globl _s_SkinVertices4Bones_Tangents_Copy4Ints_VFP + +#if UNITY_ANDROID +.hidden _s_SkinVertices_VFP +.hidden _s_SkinVertices_NoNormals_VFP +.hidden _s_SkinVertices_Tangents_VFP + +.hidden _s_SkinVertices2Bones_VFP +.hidden _s_SkinVertices2Bones_NoNormals_VFP +.hidden _s_SkinVertices2Bones_Tangents_VFP + +.hidden _s_SkinVertices4Bones_VFP +.hidden _s_SkinVertices4Bones_NoNormals_VFP +.hidden _s_SkinVertices4Bones_Tangents_VFP +#endif + + +//=========================================================================================================================================== + + +#define SKIN_POS 1 +#define SKIN_POS_NRM 2 +#define SKIN_POS_NRM_TAN 3 + + +#define SKIN_2BONES 0 +#define SKIN_4BONES 0 + +_s_SkinVertices_VFP: + +#define SKIN_1BONE SKIN_POS_NRM +#define VERTEX_SZ 24 +#define LOOP_NAME _s_SkinVertices_VFP_loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_1BONE + +_s_SkinVertices_NoNormals_VFP: + +#define SKIN_1BONE SKIN_POS +#define VERTEX_SZ 12 +#define LOOP_NAME _s_SkinVertices_NoNormals_VFP_loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_1BONE + +_s_SkinVertices_Tangents_VFP: + +#define SKIN_1BONE SKIN_POS_NRM_TAN +#define VERTEX_SZ 40 +#define LOOP_NAME _s_SkinVertices_Tangents_VFP_loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_1BONE + +#undef SKIN_4BONES +#undef SKIN_2BONES + + +//=========================================================================================================================================== + +#define SKIN_1BONE 0 +#define SKIN_4BONES 0 + +_s_SkinVertices2Bones_VFP: + +#define SKIN_2BONES SKIN_POS_NRM +#define VERTEX_SZ 24 +#define LOOP_NAME _s_SkinVertices2Bones_VFP_Loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_2BONES + +_s_SkinVertices2Bones_NoNormals_VFP: + +#define SKIN_2BONES SKIN_POS +#define VERTEX_SZ 12 +#define LOOP_NAME _s_SkinVertices2Bones_NoNormals_VFP_Loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_2BONES + +_s_SkinVertices2Bones_Tangents_VFP: + +#define SKIN_2BONES SKIN_POS_NRM_TAN +#define VERTEX_SZ 40 +#define LOOP_NAME _s_SkinVertices2Bones_Tangents_VFP_loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_2BONES + +#undef SKIN_4BONES +#undef SKIN_1BONE + +//=========================================================================================================================================== + +#define SKIN_1BONE 0 +#define SKIN_2BONES 0 + +_s_SkinVertices4Bones_VFP: + +#define SKIN_4BONES SKIN_POS_NRM +#define VERTEX_SZ 24 +#define LOOP_NAME _s_SkinVertices4Bones_VFP_loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_4BONES + +_s_SkinVertices4Bones_NoNormals_VFP: + +#define SKIN_4BONES SKIN_POS +#define VERTEX_SZ 12 +#define LOOP_NAME _s_SkinVertices4Bones_NoNormals_VFP_loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_4BONES + +_s_SkinVertices4Bones_Tangents_VFP: + +#define SKIN_4BONES SKIN_POS_NRM_TAN +#define VERTEX_SZ 40 +#define LOOP_NAME _s_SkinVertices4Bones_Tangents_VFP_loop + +#include "MeshSkinningVFP_Loop.h" + +#undef LOOP_NAME +#undef VERTEX_SZ +#undef SKIN_4BONES + +#undef SKIN_2BONES +#undef SKIN_1BONE + +//=========================================================================================================================================== + +.endif +#endif diff --git a/Runtime/Filters/Mesh/MeshSkinningVFP_Loop.h b/Runtime/Filters/Mesh/MeshSkinningVFP_Loop.h new file mode 100644 index 0000000..3b7400f --- /dev/null +++ b/Runtime/Filters/Mesh/MeshSkinningVFP_Loop.h @@ -0,0 +1,335 @@ + +// defines +// SKIN_1BONE +// SKIN_2BONES +// SKIN_4BONES +// LOOP_NAME +// VERTEX_SZ + +// skin types +// SKIN_POS +// SKIN_POS_NRM +// SKIN_POS_NRM_TAN + +//r0: const void* bones4x4 +//r1: const void* srcVertData +//r2: const void* srcVertDataEnd +//r3: const BoneInfluence4* srcBoneInfluence4 +//[sp+0] -> r4: const void* dstVertData + +// s0,s1,s2 <- output: pos +// s3,s4,s5 <- output: nrm +// s6,s7,s8,s9 <- output: tan +// s10,s11,s12 <- input: pos +// s13,s14,s15 <- input: nrm +// s16,s17,s18,s19 <- input: tan +// s20-s31 <- matrix [3x4] last row loaded directly to output pos + +//=========================================================================================================================================== +// +// Common + +#define CALC_POS_2 FMACS3 (0,1,2, 20,21,22, 10,10,10) +#define CALC_POS_3 FMACS3 (0,1,2, 24,25,26, 11,11,11) +#define CALC_POS_4 FMACS3 (0,1,2, 28,29,30, 12,12,12) + + +#if (SKIN_1BONE == SKIN_POS_NRM) || (SKIN_1BONE == SKIN_POS_NRM_TAN) \ + || (SKIN_2BONES == SKIN_POS_NRM) || (SKIN_2BONES == SKIN_POS_NRM_TAN) \ + || (SKIN_4BONES == SKIN_POS_NRM) || (SKIN_4BONES == SKIN_POS_NRM_TAN) + + #define LOAD_POS_NRM vldmia.32 r1!, {s10-s15} + #define STORE_POS_NRM vstmia.32 r4!, {s0-s5} + #define CALC_NRM_1 FMULS3 (3,4,5, 20,21,22, 13,13,13) + #define CALC_NRM_2 FMACS3 (3,4,5, 24,25,26, 14,14,14) + #define CALC_NRM_3 FMACS3 (3,4,5, 28,29,30, 15,15,15) +#else + #define LOAD_POS_NRM vldmia.32 r1!, {s10-s12} + #define STORE_POS_NRM vstmia.32 r4!, {s0-s2} + #define CALC_NRM_1 + #define CALC_NRM_2 + #define CALC_NRM_3 +#endif + +#if (SKIN_1BONE == SKIN_POS_NRM_TAN) || (SKIN_2BONES == SKIN_POS_NRM_TAN) || (SKIN_4BONES == SKIN_POS_NRM_TAN) + #define LOAD_TAN vldmia.32 r1!, {s16-s19} + #define STORE_TAN vstmia.32 r4!, {s6-s9} + #define CALC_TAN_1 FMULS3 (6,7,8, 20,21,22, 16,16,16) + #define CALC_TAN_2 FMACS3 (6,7,8, 24,25,26, 17,17,17) + #define CALC_TAN_3 FMACS3 (6,7,8, 28,29,30, 18,18,18) + #define CALC_TAN_4 fcpys s9, s19 +#else + #define LOAD_TAN + #define STORE_TAN + #define CALC_TAN_1 + #define CALC_TAN_2 + #define CALC_TAN_3 + #define CALC_TAN_4 +#endif + + + + +//=========================================================================================================================================== +// +// 1 bone skinning + +#if (SKIN_1BONE == SKIN_POS) || (SKIN_1BONE == SKIN_POS_NRM) || (SKIN_1BONE == SKIN_POS_NRM_TAN) + +mov ip, sp +vpush {d7-d15} +stmfd sp!, {r4,r5,r6,r7,r8,r10,r11} + +ldr r4, [ip, #0] + +ldr r5, [r3], #4 +add r5, r0, r5, lsl #6 +add r6, r5, #48 + +vldmia.32 r6, {s0-s2} +vldmia.32 r5!, {s20-s23} +vldmia.32 r5!, {s24-s27} + +.align 4 +LOOP_NAME: + +LOAD_POS_NRM + +CALC_POS_2 +CALC_NRM_1 + ldr r6, [r3], #4 // next matrix index +vldmia.32 r5, {s28-s30} // bone matrix + add r5, r0, r6, lsl #6 // next matrix addr + + +CALC_POS_3 +CALC_NRM_2 + +LOAD_TAN + add r6, r5, #48 + cmp r1, r2 + +CALC_TAN_1 + vldmiacc.32 r5!, {s20-s23} // next bone matrix + + +CALC_POS_4 + +CALC_TAN_2 +CALC_NRM_3 + vldmiacc.32 r5!, {s24-s27} // next bone matrix + +CALC_TAN_3 +CALC_TAN_4 + + pld [r1, #1024] + + +STORE_POS_NRM +STORE_TAN + + vldmiacc.32 r6, {s0-s2} + +bcc LOOP_NAME + +ldmfd sp!, {r4,r5,r6,r7,r8,r10,r11} +vpop {d7-d15} +bx lr + + +//=========================================================================================================================================== + +#elif (SKIN_2BONES == SKIN_POS) || (SKIN_2BONES == SKIN_POS_NRM) || (SKIN_2BONES == SKIN_POS_NRM_TAN) + +mov ip, sp +vpush {d7-d15} +stmfd sp!, {r4,r5,r6,r7,r8,r10,r11} + +ldr r4, [ip, #0] + + +.align 4 +LOOP_NAME: + +vldmia.32 r3!, {s3,s4} // w + ldmia r3!, {r5-r6} // idx + + add r5, r0, r5, lsl #6 // M0 + add r6, r0, r6, lsl #6 // M1 + + +vldmia.64 r5!, {d4,d5} // M0[0] + +vldmia.64 r6!, {d6,d7} // M1[0] +FMULS3 (20,21,22, 8,9,10, 3,3,3) // M0[0] * w + +vldmia.64 r5!, {d4,d5} // M0[1] +FMACS3 (20,21,22, 12,13,14, 4,4,4) // + M1[0] * w + +vldmia.64 r6!, {d6,d7} // M1[1] +FMULS3 (24,25,26, 8,9,10, 3,3,3) // M0[1] * w + +vldmia.64 r5!, {d4,d5} // M0[2] +FMACS3 (24,25,26, 12,13,14, 4,4,4) // + M1[1] * w + +vldmia.64 r6!, {d6,d7} // M1[2] +FMULS3 (28,29,30, 8,9,10, 3,3,3) // M0[2] * w + +vldmia.64 r5!, {d4,d5} // M0[3] +FMACS3 (28,29,30, 12,13,14, 4,4,4) // + M1[2] * w + +vldmia.64 r6!, {d6,d7} // M1[3] +FMULS3 (0,1,2, 8,9,10, 3,3,3) // M0[3] * w + +FMACS3 (0,1,2, 12,13,14, 4,4,4) // + M1[3] * w + + +LOAD_POS_NRM +LOAD_TAN + +CALC_POS_2 +CALC_NRM_1 +CALC_TAN_1 + +CALC_POS_3 +CALC_NRM_2 +CALC_TAN_2 + pld [r1, #1024] + cmp r1, r2 +CALC_POS_4 +CALC_NRM_3 +CALC_TAN_3 + +CALC_TAN_4 + + +STORE_POS_NRM +STORE_TAN + +bcc LOOP_NAME + +ldmfd sp!, {r4,r5,r6,r7,r8,r10,r11} +vpop {d7-d15} +bx lr + + + +//=========================================================================================================================================== + +#elif (SKIN_4BONES == SKIN_POS) || (SKIN_4BONES == SKIN_POS_NRM) || (SKIN_4BONES == SKIN_POS_NRM_TAN) + +mov ip, sp +vpush {d7-d15} +stmfd sp!, {r4,r5,r6,r7,r8} + +ldr r4, [ip, #0] + + +.align 4 +LOOP_NAME: + +vldmia.32 r3!, {s3-s6} // w + ldmia r3!, {r5-r8} // idx + + add r5, r0, r5, lsl #6 // M0 + add r6, r0, r6, lsl #6 // M1 + add r7, r0, r7, lsl #6 // M2 + add r8, r0, r8, lsl #6 // M3 + + +vldmia.64 r5!, {d4,d5} // M0[0] + +vldmia.64 r6!, {d6,d7} // M1[0] +FMULS3 (20,21,22, 8,9,10, 3,3,3) // M0[0] * w + +vldmia.64 r7!, {d4,d5} // M2[0] +FMACS3 (20,21,22, 12,13,14, 4,4,4) // + M1[0] * w + +vldmia.64 r8!, {d6,d7} // M3[0] +FMACS3 (20,21,22, 8,9,10, 5,5,5) // + M2[0] * w + +vldmia.64 r5!, {d4,d5} // M0[1] +FMACS3 (20,21,22, 12,13,14, 6,6,6) // + M3[0] * w + +vldmia.64 r6!, {d6,d7} // M1[1] +FMULS3 (24,25,26, 8,9,10, 3,3,3) // M0[1] * w + +vldmia.64 r7!, {d4,d5} // M2[1] +FMACS3 (24,25,26, 12,13,14, 4,4,4) // + M1[1] * w + +vldmia.64 r8!, {d6,d7} // M3[1] +FMACS3 (24,25,26, 8,9,10, 5,5,5) // + M2[1] * w + +vldmia.64 r5!, {d4,d5} // M0[2] +FMACS3 (24,25,26, 12,13,14, 6,6,6) // + M3[1] * w + +vldmia.64 r6!, {d6,d7} // M1[2] +FMULS3 (28,29,30, 8,9,10, 3,3,3) // M0[2] * w + +vldmia.64 r7!, {d4,d5} // M2[2] +FMACS3 (28,29,30, 12,13,14, 4,4,4) // + M1[2] * w + +vldmia.64 r8!, {d6,d7} // M3[2] +FMACS3 (28,29,30, 8,9,10, 5,5,5) // + M2[2] * w + +vldmia.64 r5!, {d4,d5} // M0[3] +FMACS3 (28,29,30, 12,13,14, 6,6,6) // + M3[2] * w + +vldmia.64 r6!, {d6,d7} // M1[3] +FMULS3 (0,1,2, 8,9,10, 3,3,3) // M0[3] * w + +vldmia.64 r7!, {d4,d5} // M2[3] +FMACS3 (0,1,2, 12,13,14, 4,4,4) // + M1[3] * w + +vldmia.64 r8!, {d6,d7} // M3[3] +FMACS3 (0,1,2, 8,9,10, 5,5,5) // + M2[3] * w + +FMACS3 (0,1,2, 12,13,14, 6,6,6) // + M3[3] * w + + +LOAD_POS_NRM +LOAD_TAN + +CALC_POS_2 +CALC_NRM_1 +CALC_TAN_1 + +CALC_POS_3 +CALC_NRM_2 +CALC_TAN_2 + pld [r1, #1024] + cmp r1, r2 +CALC_POS_4 +CALC_NRM_3 +CALC_TAN_3 + +CALC_TAN_4 + + +STORE_POS_NRM +STORE_TAN + +bcc LOOP_NAME + +ldmfd sp!, {r4,r5,r6,r7,r8} +vpop {d7-d15} +bx lr + +#endif + +//=========================================================================================================================================== + +#undef CALC_POS_1 +#undef CALC_POS_2 +#undef CALC_POS_3 +#undef STORE_POS_NRM +#undef LOAD_POS_NRM +#undef CALC_NRM_1 +#undef CALC_NRM_2 +#undef CALC_NRM_3 +#undef LOAD_TAN +#undef STORE_TAN +#undef CALC_TAN_1 +#undef CALC_TAN_2 +#undef CALC_TAN_3 +#undef CALC_TAN_4 diff --git a/Runtime/Filters/Mesh/MeshUtility.cpp b/Runtime/Filters/Mesh/MeshUtility.cpp new file mode 100644 index 0000000..75d8e7f --- /dev/null +++ b/Runtime/Filters/Mesh/MeshUtility.cpp @@ -0,0 +1,58 @@ +#include "UnityPrefix.h" +#include "MeshUtility.h" +#include "Runtime/Geometry/Plane.h" +#include "Mesh.h" + +using namespace std; + +void CalculateNormals (StrideIterator<Vector3f> verts, const UInt32* indices, int vertexCount, int triangleCount, StrideIterator<Vector3f> outNormals) +{ + std::fill_n (outNormals, vertexCount, Vector3f(0,0,0)); + + // Add normals from faces + int idx = 0; + for( int i = 0; i < triangleCount; ++i ) + { + UInt32 index0 = indices[idx+0]; + UInt32 index1 = indices[idx+1]; + UInt32 index2 = indices[idx+2]; + Vector3f faceNormal = CalcRawNormalFromTriangle( verts[index0], verts[index1], verts[index2] ); + outNormals[index0] += faceNormal; + outNormals[index1] += faceNormal; + outNormals[index2] += faceNormal; + idx += 3; + } + + // Normalize + for (StrideIterator<Vector3f> end = outNormals + vertexCount; outNormals != end; ++outNormals ) + { + *outNormals = NormalizeFast (*outNormals); + } +} + + +float CalculateSurfaceArea ( + const Matrix4x4f& objectToWorld, + const Mesh::TemporaryIndexContainer& triangles, + dynamic_array<Vector3f>& vertices) +{ + // transform the vertices to world space, + // do it in place since they are a copy + for (int i = 0; i < vertices.size (); i++) + vertices[i] = objectToWorld.MultiplyPoint3 (vertices[i]); + + // calculate the area + float cachedSurfaceArea = 0; + for (int i = 0; i < triangles.size () / 3; i++) + { + DebugAssert (triangles[3 * i] < vertices.size ()); + DebugAssert (triangles[3 * i + 1] < vertices.size ()); + DebugAssert (triangles[3 * i + 2] < vertices.size ()); + Vector3f a = vertices[triangles[3 * i]]; + Vector3f b = vertices[triangles[3 * i + 1]]; + Vector3f c = vertices[triangles[3 * i + 2]]; + cachedSurfaceArea += Magnitude (Cross (b - a, c - a)) * 0.5f; + } + + return cachedSurfaceArea; +} diff --git a/Runtime/Filters/Mesh/MeshUtility.h b/Runtime/Filters/Mesh/MeshUtility.h new file mode 100644 index 0000000..748c874 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshUtility.h @@ -0,0 +1,42 @@ +#ifndef MESHUTILITY_H +#define MESHUTILITY_H + +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Matrix4x4.h" +#include "Runtime/Math/Quaternion.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Utilities/StrideIterator.h" +#include "Runtime/Utilities/dynamic_array.h" + +struct Tangent; + +// Calculate normals for the mesh, given vertex array and triangle list (3 indices per triangle). +void CalculateNormals( StrideIterator<Vector3f> verts, const UInt32* indices, int vertexCount, int triangleCount, StrideIterator<Vector3f> outNormals ); + +float CalculateSurfaceArea (const Matrix4x4f& objectToWorld, const Mesh::TemporaryIndexContainer& triangles, dynamic_array<Vector3f>& vertices); + +// Use this to generate a normal from an tangent basis quickly +inline Vector3f NormalFromQuatTangentBasis (const Quaternionf& lhs) +{ + float x = lhs.x * 2.0F; + float y = lhs.y * 2.0F; + float z = lhs.z * 2.0F; + float xx = lhs.x * x; + float yy = lhs.y * y; + float xz = lhs.x * z; + float yz = lhs.y * z; + float wx = lhs.w * x; + float wy = lhs.w * y; + + Vector3f res; + res.x = xz - wy; + res.y = yz + wx; + res.z = 1.0f - xx - yy; + AssertIf (!CompareApproximately (res, RotateVectorByQuat(Inverse (lhs), Vector3f::zAxis))); + return res; +} + +//bool HasDegenerateTriangles (const Vector3f* verts, const MeshData &meshData, float degenerateArea = 0.0001); + + +#endif diff --git a/Runtime/Filters/Mesh/SkinGeneric.h b/Runtime/Filters/Mesh/SkinGeneric.h new file mode 100644 index 0000000..ef30d81 --- /dev/null +++ b/Runtime/Filters/Mesh/SkinGeneric.h @@ -0,0 +1,338 @@ +#ifndef SKINGENERIC_H +#define SKINGENERIC_H + +#include "Runtime/Filters/Mesh/VertexData.h" + +#if UNITY_PS3 +template<TransformInstruction transformInstruction, int bonesPerVertexCount, +bool skinNormal, bool skinTangent> +void SkinGenericStreamed (SkinMeshInfo& info) +{ + const int* influence1 = reinterpret_cast<const int*> (info.compactSkin); + const BoneInfluence2* influence2 = reinterpret_cast<const BoneInfluence2*> (info.compactSkin); + const BoneInfluence* influence4 = reinterpret_cast<const BoneInfluence*> (info.compactSkin); + + const Matrix4x4f* bones4x4 = info.cachedPose; + + int count = info.vertexCount; + + int vertexOffset = info.vertexData->GetStream(0).offset; + const int vertexStride = info.vertexData->GetStream(0).stride; + + int normalOffset = info.vertexData->GetStream(1).offset; + const int normalStride = info.vertexData->GetStream(1).stride; + + int tangentOffset = info.vertexData->GetStream(2).offset; + const int tangentStride = info.vertexData->GetStream(2).stride; + + const int copyDataOffset = info.vertexData->GetStream(3).offset; + const int copyDataSize = info.vertexData->GetStream(3).stride * info.vertexCount; + + const UInt8* inputVertex = (const UInt8*)info.inVertices; + UInt8* outputVertex = (UInt8*)info.outVertices; + + Matrix4x4f poseBlended; + const Matrix4x4f* poseToUse; + + for( int v = 0; v < count; v++ ) + { + ALIGN_LOOP_OPTIMIZATION + + Prefetch(inputVertex + 256); + + // Blend the matrices first, then transform everything with this + // blended matrix. Gives a small speed boost on XCode/Intel (11.3 to 12.00 FPS + // in skin4 bench), and a good boost on MSVC/Windows (9.6 to 12.4 FPS). + if (bonesPerVertexCount == 1) + { + poseToUse = &bones4x4[*influence1]; + } + else if (bonesPerVertexCount == 2) + { + float weight0 = influence2->weight[0]; + float weight1 = influence2->weight[1]; + const float* b4x40 = bones4x4[influence2->boneIndex[0]].m_Data; + const float* b4x41 = bones4x4[influence2->boneIndex[1]].m_Data; + // we need only 12 components of the matrix + poseBlended.m_Data[ 0] = b4x40[ 0] * weight0 + b4x41[ 0] * weight1; + poseBlended.m_Data[ 1] = b4x40[ 1] * weight0 + b4x41[ 1] * weight1; + poseBlended.m_Data[ 2] = b4x40[ 2] * weight0 + b4x41[ 2] * weight1; + poseBlended.m_Data[ 4] = b4x40[ 4] * weight0 + b4x41[ 4] * weight1; + poseBlended.m_Data[ 5] = b4x40[ 5] * weight0 + b4x41[ 5] * weight1; + poseBlended.m_Data[ 6] = b4x40[ 6] * weight0 + b4x41[ 6] * weight1; + poseBlended.m_Data[ 8] = b4x40[ 8] * weight0 + b4x41[ 8] * weight1; + poseBlended.m_Data[ 9] = b4x40[ 9] * weight0 + b4x41[ 9] * weight1; + poseBlended.m_Data[10] = b4x40[10] * weight0 + b4x41[10] * weight1; + poseBlended.m_Data[12] = b4x40[12] * weight0 + b4x41[12] * weight1; + poseBlended.m_Data[13] = b4x40[13] * weight0 + b4x41[13] * weight1; + poseBlended.m_Data[14] = b4x40[14] * weight0 + b4x41[14] * weight1; + poseToUse = &poseBlended; + } + else if (bonesPerVertexCount == 4) + { + float weight0 = influence4->weight[0]; + float weight1 = influence4->weight[1]; + float weight2 = influence4->weight[2]; + float weight3 = influence4->weight[3]; + + const float* b4x40 = bones4x4[influence4->boneIndex[0]].m_Data; + const float* b4x41 = bones4x4[influence4->boneIndex[1]].m_Data; + const float* b4x42 = bones4x4[influence4->boneIndex[2]].m_Data; + const float* b4x43 = bones4x4[influence4->boneIndex[3]].m_Data; + // we need only 12 components of the matrix, so unroll + poseBlended.m_Data[ 0] = b4x40[ 0] * weight0 + b4x41[ 0] * weight1 + b4x42[ 0] * weight2 + b4x43[ 0] * weight3; + poseBlended.m_Data[ 1] = b4x40[ 1] * weight0 + b4x41[ 1] * weight1 + b4x42[ 1] * weight2 + b4x43[ 1] * weight3; + poseBlended.m_Data[ 2] = b4x40[ 2] * weight0 + b4x41[ 2] * weight1 + b4x42[ 2] * weight2 + b4x43[ 2] * weight3; + poseBlended.m_Data[ 4] = b4x40[ 4] * weight0 + b4x41[ 4] * weight1 + b4x42[ 4] * weight2 + b4x43[ 4] * weight3; + poseBlended.m_Data[ 5] = b4x40[ 5] * weight0 + b4x41[ 5] * weight1 + b4x42[ 5] * weight2 + b4x43[ 5] * weight3; + poseBlended.m_Data[ 6] = b4x40[ 6] * weight0 + b4x41[ 6] * weight1 + b4x42[ 6] * weight2 + b4x43[ 6] * weight3; + poseBlended.m_Data[ 8] = b4x40[ 8] * weight0 + b4x41[ 8] * weight1 + b4x42[ 8] * weight2 + b4x43[ 8] * weight3; + poseBlended.m_Data[ 9] = b4x40[ 9] * weight0 + b4x41[ 9] * weight1 + b4x42[ 9] * weight2 + b4x43[ 9] * weight3; + poseBlended.m_Data[10] = b4x40[10] * weight0 + b4x41[10] * weight1 + b4x42[10] * weight2 + b4x43[10] * weight3; + poseBlended.m_Data[12] = b4x40[12] * weight0 + b4x41[12] * weight1 + b4x42[12] * weight2 + b4x43[12] * weight3; + poseBlended.m_Data[13] = b4x40[13] * weight0 + b4x41[13] * weight1 + b4x42[13] * weight2 + b4x43[13] * weight3; + poseBlended.m_Data[14] = b4x40[14] * weight0 + b4x41[14] * weight1 + b4x42[14] * weight2 + b4x43[14] * weight3; + poseToUse = &poseBlended; + } + + // skin components + Vector3f outVertex, outNormal, outTangent; + const Vector3f* vertex = reinterpret_cast<const Vector3f*>( inputVertex + vertexOffset); + const Vector3f* normal = reinterpret_cast<const Vector3f*>( inputVertex + normalOffset ); + const Vector3f* tangent = reinterpret_cast<const Vector3f*>( inputVertex + tangentOffset ); + poseToUse->MultiplyPoint3( *vertex, outVertex ); + if( skinNormal ) + { + poseToUse->MultiplyVector3( *normal, outNormal ); + if (transformInstruction == kNormalizeFastest) + { + float sqr1 = SqrMagnitude( outNormal ); + float invsqrt1 = FastestInvSqrt (sqr1); + outNormal *= invsqrt1; + } + else if (transformInstruction == kNormalizeFast) + { + float sqr1 = SqrMagnitude( outNormal ); + float invsqrt1 = FastInvSqrt (sqr1); + outNormal *= invsqrt1; + } + } + if( skinTangent ) + { + poseToUse->MultiplyVector3( *tangent, outTangent ); + if (transformInstruction == kNormalizeFastest) + { + float sqr1 = SqrMagnitude( outTangent ); + float invsqrt1 = FastestInvSqrt (sqr1); + outTangent *= invsqrt1; + } + else if (transformInstruction == kNormalizeFast) + { + float sqr1 = SqrMagnitude( outTangent ); + float invsqrt1 = FastInvSqrt (sqr1); + outTangent *= invsqrt1; + } + } + + // write data out + *reinterpret_cast<Vector3f*> (outputVertex + vertexOffset) = outVertex; + if( skinNormal ) + { + *reinterpret_cast<Vector3f*>( outputVertex + normalOffset ) = outNormal; + } + if( skinTangent ) + { + *reinterpret_cast<Vector3f*>( outputVertex + tangentOffset ) = outTangent; + *reinterpret_cast<float*>( outputVertex + tangentOffset + sizeof(Vector3f) ) = *reinterpret_cast<const float*>( inputVertex + tangentOffset + sizeof(Vector3f) ); + } + + vertexOffset += vertexStride; + normalOffset += normalStride; + tangentOffset += tangentStride; + + if (bonesPerVertexCount == 1) + influence1++; + else if (bonesPerVertexCount == 2) + influence2++; + if (bonesPerVertexCount == 4) + influence4++; + } + + // copy + const UInt8* copyDataSrc = inputVertex + copyDataOffset; + UInt8* copyDataDst = outputVertex + copyDataOffset; + memcpy(copyDataDst, copyDataSrc, copyDataSize); +} +#endif + +template<TransformInstruction transformInstruction, int bonesPerVertexCount, + bool skinNormal, bool skinTangent> +void SkinGeneric (SkinMeshInfo& info); + +template<TransformInstruction transformInstruction, int bonesPerVertexCount, + bool skinNormal, bool skinTangent> +void SkinGeneric (SkinMeshInfo& info) +{ +#if UNITY_PS3 + if(info.vertexData && (info.vertexData->GetActiveStreamCount() > 2)) + return SkinGenericStreamed<transformInstruction, bonesPerVertexCount, skinNormal, skinTangent>(info); +#endif + const int* influence1 = reinterpret_cast<const int*> (info.compactSkin); + const BoneInfluence2* influence2 = reinterpret_cast<const BoneInfluence2*> (info.compactSkin); + const BoneInfluence* influence4 = reinterpret_cast<const BoneInfluence*> (info.compactSkin); + + const Matrix4x4f* bones4x4 = info.cachedPose; + + const int inStride = info.inStride; + int outStride = info.outStride; + int count = info.vertexCount; + + const int normalOffset = info.normalOffset; + const int tangentOffset = info.tangentOffset; + + const UInt8* inputVertex = (const UInt8*)info.inVertices; + UInt8* outputVertex = (UInt8*)info.outVertices; + + Matrix4x4f poseBlended; + const Matrix4x4f* poseToUse; + + +#if !ENABLE_MULTITHREADED_SKINNING + PROFILER_AUTO(gMeshSkinningSlowpath, NULL); +#endif + + //;;printf_console("bonesPerVertexCount: %d, skinNormal: %d, normalOffset: %d, inStride: %d, copyDataSizeInts: %d, count: %d, boneCount: %d, outputVertex: %d\n", + // bonesPerVertexCount, (int)skinNormal, normalOffset, inStride, copyDataSizeInts, count, info.boneCount, (int)outputVertex); + //;;uint64_t delta = mach_absolute_time(); + + for( int v = 0; v < count; v++ ) + { + ALIGN_LOOP_OPTIMIZATION + + Prefetch(inputVertex + 256); + + // Blend the matrices first, then transform everything with this + // blended matrix. Gives a small speed boost on XCode/Intel (11.3 to 12.00 FPS + // in skin4 bench), and a good boost on MSVC/Windows (9.6 to 12.4 FPS). + if (bonesPerVertexCount == 1) + { + poseToUse = &bones4x4[*influence1]; + } + else if (bonesPerVertexCount == 2) + { + float weight0 = influence2->weight[0]; + float weight1 = influence2->weight[1]; + const float* b4x40 = bones4x4[influence2->boneIndex[0]].m_Data; + const float* b4x41 = bones4x4[influence2->boneIndex[1]].m_Data; + // we need only 12 components of the matrix + poseBlended.m_Data[ 0] = b4x40[ 0] * weight0 + b4x41[ 0] * weight1; + poseBlended.m_Data[ 1] = b4x40[ 1] * weight0 + b4x41[ 1] * weight1; + poseBlended.m_Data[ 2] = b4x40[ 2] * weight0 + b4x41[ 2] * weight1; + poseBlended.m_Data[ 4] = b4x40[ 4] * weight0 + b4x41[ 4] * weight1; + poseBlended.m_Data[ 5] = b4x40[ 5] * weight0 + b4x41[ 5] * weight1; + poseBlended.m_Data[ 6] = b4x40[ 6] * weight0 + b4x41[ 6] * weight1; + poseBlended.m_Data[ 8] = b4x40[ 8] * weight0 + b4x41[ 8] * weight1; + poseBlended.m_Data[ 9] = b4x40[ 9] * weight0 + b4x41[ 9] * weight1; + poseBlended.m_Data[10] = b4x40[10] * weight0 + b4x41[10] * weight1; + poseBlended.m_Data[12] = b4x40[12] * weight0 + b4x41[12] * weight1; + poseBlended.m_Data[13] = b4x40[13] * weight0 + b4x41[13] * weight1; + poseBlended.m_Data[14] = b4x40[14] * weight0 + b4x41[14] * weight1; + poseToUse = &poseBlended; + } + else if (bonesPerVertexCount == 4) + { + float weight0 = influence4->weight[0]; + float weight1 = influence4->weight[1]; + float weight2 = influence4->weight[2]; + float weight3 = influence4->weight[3]; + + const float* b4x40 = bones4x4[influence4->boneIndex[0]].m_Data; + const float* b4x41 = bones4x4[influence4->boneIndex[1]].m_Data; + const float* b4x42 = bones4x4[influence4->boneIndex[2]].m_Data; + const float* b4x43 = bones4x4[influence4->boneIndex[3]].m_Data; + // we need only 12 components of the matrix, so unroll + poseBlended.m_Data[ 0] = b4x40[ 0] * weight0 + b4x41[ 0] * weight1 + b4x42[ 0] * weight2 + b4x43[ 0] * weight3; + poseBlended.m_Data[ 1] = b4x40[ 1] * weight0 + b4x41[ 1] * weight1 + b4x42[ 1] * weight2 + b4x43[ 1] * weight3; + poseBlended.m_Data[ 2] = b4x40[ 2] * weight0 + b4x41[ 2] * weight1 + b4x42[ 2] * weight2 + b4x43[ 2] * weight3; + poseBlended.m_Data[ 4] = b4x40[ 4] * weight0 + b4x41[ 4] * weight1 + b4x42[ 4] * weight2 + b4x43[ 4] * weight3; + poseBlended.m_Data[ 5] = b4x40[ 5] * weight0 + b4x41[ 5] * weight1 + b4x42[ 5] * weight2 + b4x43[ 5] * weight3; + poseBlended.m_Data[ 6] = b4x40[ 6] * weight0 + b4x41[ 6] * weight1 + b4x42[ 6] * weight2 + b4x43[ 6] * weight3; + poseBlended.m_Data[ 8] = b4x40[ 8] * weight0 + b4x41[ 8] * weight1 + b4x42[ 8] * weight2 + b4x43[ 8] * weight3; + poseBlended.m_Data[ 9] = b4x40[ 9] * weight0 + b4x41[ 9] * weight1 + b4x42[ 9] * weight2 + b4x43[ 9] * weight3; + poseBlended.m_Data[10] = b4x40[10] * weight0 + b4x41[10] * weight1 + b4x42[10] * weight2 + b4x43[10] * weight3; + poseBlended.m_Data[12] = b4x40[12] * weight0 + b4x41[12] * weight1 + b4x42[12] * weight2 + b4x43[12] * weight3; + poseBlended.m_Data[13] = b4x40[13] * weight0 + b4x41[13] * weight1 + b4x42[13] * weight2 + b4x43[13] * weight3; + poseBlended.m_Data[14] = b4x40[14] * weight0 + b4x41[14] * weight1 + b4x42[14] * weight2 + b4x43[14] * weight3; + poseToUse = &poseBlended; + } + + // skin components + Vector3f outVertex, outNormal, outTangent; + const Vector3f* vertex = reinterpret_cast<const Vector3f*>( inputVertex ); + const Vector3f* normal = reinterpret_cast<const Vector3f*>( inputVertex + normalOffset ); + const Vector3f* tangent = reinterpret_cast<const Vector3f*>( inputVertex + tangentOffset ); + poseToUse->MultiplyPoint3( *vertex, outVertex ); + if( skinNormal ) + { + poseToUse->MultiplyVector3( *normal, outNormal ); + if (transformInstruction == kNormalizeFastest) + { + float sqr1 = SqrMagnitude( outNormal ); + float invsqrt1 = FastestInvSqrt (sqr1); + outNormal *= invsqrt1; + } + else if (transformInstruction == kNormalizeFast) + { + float sqr1 = SqrMagnitude( outNormal ); + float invsqrt1 = FastInvSqrt (sqr1); + outNormal *= invsqrt1; + } + } + if( skinTangent ) + { + poseToUse->MultiplyVector3( *tangent, outTangent ); + if (transformInstruction == kNormalizeFastest) + { + float sqr1 = SqrMagnitude( outTangent ); + float invsqrt1 = FastestInvSqrt (sqr1); + outTangent *= invsqrt1; + } + else if (transformInstruction == kNormalizeFast) + { + float sqr1 = SqrMagnitude( outTangent ); + float invsqrt1 = FastInvSqrt (sqr1); + outTangent *= invsqrt1; + } + } + + // write data out + *reinterpret_cast<Vector3f*> (outputVertex) = outVertex; + if( skinNormal ) + { + *reinterpret_cast<Vector3f*>( outputVertex + normalOffset ) = outNormal; + } + + if( skinTangent ) + { + *reinterpret_cast<Vector3f*>( outputVertex + tangentOffset ) = outTangent; + *reinterpret_cast<float*>( outputVertex + tangentOffset + sizeof(Vector3f) ) = *reinterpret_cast<const float*>( inputVertex + tangentOffset + sizeof(Vector3f) ); + } + + outputVertex += outStride; + inputVertex += inStride; + + if (bonesPerVertexCount == 1) + influence1++; + else if (bonesPerVertexCount == 2) + influence2++; + if (bonesPerVertexCount == 4) + influence4++; + } + + //;;static int frameCount = 0; frameCount++; + //delta = mach_absolute_time() - delta; + //;;static uint64_t deltaAccum = 0; deltaAccum += (int)(delta); + //;;printf_console("skin-c: %d %d\n", (int)(deltaAccum / frameCount), (int)delta); +} + +#endif diff --git a/Runtime/Filters/Mesh/SpriteRenderer.cpp b/Runtime/Filters/Mesh/SpriteRenderer.cpp new file mode 100644 index 0000000..4ce85a1 --- /dev/null +++ b/Runtime/Filters/Mesh/SpriteRenderer.cpp @@ -0,0 +1,338 @@ +#include "UnityPrefix.h" +#include "SpriteRenderer.h" + +#if ENABLE_SPRITES + +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Graphics/SpriteFrame.h" +#include "Runtime/Graphics/Texture.h" +#include "Runtime/Graphics/Texture2D.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Profiler/ExternalGraphicsProfiler.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Shaders/ShaderNameRegistry.h" +#include "Runtime/Shaders/VBO.h" +#include "Runtime/Filters/Mesh/TransformVertex.h" +#include "Runtime/GfxDevice/BatchRendering.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" +#include "Runtime/Misc/ResourceManager.h" +#include "Runtime/BaseClasses/Tags.h" +#include "SpriteRendererAnimationBinding.h" + + +PROFILER_INFORMATION(gSpriteRenderSingleProfile, "SpriteRenderer.RenderSingle", kProfilerRender) +PROFILER_INFORMATION(gSpriteRenderBatchProfile, "SpriteRenderer.RenderBatch", kProfilerRender) +PROFILER_INFORMATION(gSpriteRenderSubmitVBO, "Mesh.SubmitVBO", kProfilerRender) + +const float kSpriteScaleEpsilon = 0.0001f; +#define kMaxNumSpriteTrianglesPerBatch (2*1024) + +static const char* const kDefaultSpriteShader = "Sprites/Default"; +static const char* const kDefaultSpriteMaterial = "Sprites-Default.mat"; + +static SHADERPROP (MainTex); +static SHADERPROP (MainTex_TexelSize); +static Material* gSpriteDefaultMaterial = NULL; + +static void InitDefaultSpriteMaterial() +{ + Assert(gSpriteDefaultMaterial == NULL); + gSpriteDefaultMaterial = GetBuiltinResource<Material>(kDefaultSpriteMaterial); +} + +IMPLEMENT_CLASS_HAS_INIT (SpriteRenderer) +IMPLEMENT_OBJECT_SERIALIZE (SpriteRenderer) + +SpriteRenderer::SpriteRenderer (MemLabelId label, ObjectCreationMode mode) +: Super(kRendererSprite, label, mode) +, m_Color(1.0F, 1.0F, 1.0F, 1.0F) +{ + m_CastShadows = false; + m_ReceiveShadows = false; +} + +SpriteRenderer::~SpriteRenderer () +{ +} + +inline ColorRGBA32 GetDeviceColor (const ColorRGBAf& color, GfxDevice& device) +{ + if (GetActiveColorSpace () == kLinearColorSpace) + return device.ConvertToDeviceVertexColor(GammaToActiveColorSpace(color)); + else + return device.ConvertToDeviceVertexColor(color); +} + +void SpriteRenderer::InitializeClass () +{ + REGISTER_GLOBAL_CALLBACK(initializedEngineGraphics, InitDefaultSpriteMaterial()); + InitializeSpriteRendererAnimationBindingInterface(); +} + +void SpriteRenderer::CleanupClass () +{ + CleanupSpriteRendererAnimationBindingInterface (); + gSpriteDefaultMaterial = NULL; +} + +template<class TransferFunction> +void SpriteRenderer::Transfer(TransferFunction& transfer) +{ + Super::Transfer (transfer); + TRANSFER (m_Sprite); + TRANSFER (m_Color); +} + +void SpriteRenderer::UpdateLocalAABB () +{ + if (m_Sprite.IsValid()) + { + //TODO: calculate AABB from RenderData. + m_TransformInfo.localAABB = m_Sprite->GetBounds(); + } + else + { + m_TransformInfo.localAABB.SetCenterAndExtent(Vector3f::zero, Vector3f::zero); + } +} + +void SpriteRenderer::UpdateTransformInfo () +{ + Transform const& transform = GetTransform(); + if (m_TransformDirty) + { + // will return a cached matrix most of the time + TransformType type = transform.CalculateTransformMatrix (m_TransformInfo.worldMatrix); + + // Always treat sprites has having a non-uniform scale. Will make them batch better + // (since we break batches on transform type changes). And does not have any negative effects + // since uniform vs. non-uniform scale only affects fixed function vertex normals, which + // aren't relevant here. + type &= ~kUniformScaleTransform; + type |= kNonUniformScaleTransform; + m_TransformInfo.transformType = type; + + // Likewise, treat inverse scale as always being 1. + m_TransformInfo.invScale = 1.0f; + } + + if (m_BoundsDirty) + UpdateLocalAABB(); + + TransformAABBSlow(m_TransformInfo.localAABB, m_TransformInfo.worldMatrix, m_TransformInfo.worldAABB); +} + +void SpriteRenderer::SetSprite(PPtr<Sprite> sprite) +{ + if (m_Sprite != sprite) + { + m_Sprite = sprite; + BoundsChanged(); + SetupMaterialProperties(); + + SetDirty(); + } +} + +void SpriteRenderer::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad(awakeMode); + BoundsChanged(); + SetupMaterialProperties(); +} + +void SpriteRenderer::SmartReset () +{ + SetMaterialCount(1); + SetMaterial(GetDefaultSpriteMaterial(), 0); +} + +void SpriteRenderer::SetupMaterialProperties() +{ + if (m_Sprite.IsNull()) + return; + + // Patch sprite texture and apply material property block + MaterialPropertyBlock& block = GetPropertyBlockRememberToUpdateHash (); + SetupMaterialPropertyBlock(block, GetSpriteRenderDataInContext(m_Sprite)->texture); + ComputeCustomPropertiesHash (); +} + +void SpriteRenderer::SetupMaterialPropertyBlock(MaterialPropertyBlock& block, const Texture2D* spriteTexture) +{ + const TextureID id = spriteTexture ? spriteTexture->GetTextureID() : TextureID(0); + const Vector4f texelSize = spriteTexture ? Vector4f(spriteTexture->GetTexelSizeX(), spriteTexture->GetTexelSizeY(), spriteTexture->GetGLWidth(), spriteTexture->GetGLHeight()) : Vector4f(0, 0, 0, 0); + + block.ReplacePropertyTexture(kSLPropMainTex, kTexDim2D, id); + block.ReplacePropertyVector(kSLPropMainTex_TexelSize, texelSize); +} + +const SpriteRenderData* SpriteRenderer::GetSpriteRenderDataInContext(const PPtr<Sprite>& frame) +{ + //@Note: this is here for a possible contextual atlas implementation. + return &frame->GetRenderDataForPlayMode(); +} + +void SpriteRenderer::Render (int materialIndex, const ChannelAssigns& channels) +{ + GfxDevice& device = GetGfxDevice(); + + Assert(materialIndex == 0); + if (m_Sprite.IsNull()) + return; + + const SpriteRenderData* rd = GetSpriteRenderDataInContext(m_Sprite); + Assert(rd->texture.IsValid()); + + PROFILER_AUTO_GFX(gSpriteRenderSingleProfile, this); + + // Get VBO chunk for a rectangle or mesh + UInt32 numIndices, numVertices; + GetGeometrySize(numIndices, numVertices); + if (!numIndices) + return; + + const UInt32 channelMask = (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor); + + DynamicVBO& vbo = device.GetDynamicVBO(); + UInt8* __restrict vbPtr; + UInt16* __restrict ibPtr; + if ( !vbo.GetChunk(channelMask, numVertices, numIndices, DynamicVBO::kDrawIndexedTriangles, (void**)&vbPtr, (void**)&ibPtr) ) + return; + + TransformSprite (vbPtr, ibPtr, NULL, rd, GetDeviceColor (m_Color, device), 0); + vbo.ReleaseChunk(numVertices, numIndices); + + // Draw + if (m_CustomProperties) + device.SetMaterialProperties(*m_CustomProperties); + + PROFILER_BEGIN(gSpriteRenderSubmitVBO, this) + vbo.DrawChunk(channels); + GPU_TIMESTAMP(); + PROFILER_END +} + +void SpriteRenderer::GetGeometrySize(UInt32& indexCount, UInt32& vertexCount) +{ + if (m_Sprite.IsValid()) + { + const SpriteRenderData* rd = GetSpriteRenderDataInContext(m_Sprite); + if (rd->indices.size() > 0) + { + indexCount = rd->indices.size(); + vertexCount = rd->vertices.size(); + return; + } + } + + indexCount = 0; + vertexCount = 0; +} + +#if GFX_ENABLE_DRAW_CALL_BATCHING +void SpriteRenderer::RenderBatch (const BatchInstanceData* instances, size_t count, size_t numIndices, size_t numVertices, const ChannelAssigns& channels) +{ + DebugAssert(numIndices); + DebugAssert(numVertices); + PROFILER_AUTO_GFX(gSpriteRenderBatchProfile, 0); + + GfxDevice& device = GetGfxDevice(); + const MaterialPropertyBlock* customProps = count > 0 ? instances[0].renderer->GetCustomProperties() : NULL; + if (customProps) + device.SetMaterialProperties (*customProps); + + UInt32 expectedFence = device.GetNextCPUFence(); + const UInt32 channelMask = (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor);; + device.BeginDynamicBatching(channels, channelMask, numVertices, numIndices, kPrimitiveTriangles); + + for (BatchInstanceData const* it = instances; it < instances + count; ++it) + { + UInt32 numIndices, numVertices; + + Assert(it->renderer); + Assert(it->renderer->GetRendererType() == kRendererSprite); + SpriteRenderer* renderer = (SpriteRenderer*)it->renderer; + renderer->GetGeometrySize(numIndices, numVertices); + if (!numIndices) + continue; + + const SpriteRenderData *rd = renderer->GetSpriteRenderDataInContext(renderer->m_Sprite); + Assert(rd->texture.IsValid()); + +#if ENABLE_MULTITHREADED_CODE + renderer->m_Sprite->SetCurrentCPUFence(expectedFence); +#endif + device.DynamicBatchSprite(&it->xform, rd, GetDeviceColor(renderer->m_Color, device)); + } + device.SetInverseScale(1.0f); + device.EndDynamicBatching(TransformType(kNoScaleTransform)); + + // Insert fence after batching is complete + UInt32 fence = device.InsertCPUFence(); + Assert(fence == expectedFence); + GPU_TIMESTAMP(); +} + +void SpriteRenderer::RenderMultiple (const BatchInstanceData* instances, size_t count, const ChannelAssigns& channels) +{ + size_t numIndicesBatch = 0; + size_t numVerticesBatch = 0; + + BatchInstanceData const* instancesEnd = instances + count; + BatchInstanceData const* iBatchBegin = instances; + BatchInstanceData const* iBatchEnd = instances; + while (iBatchEnd != instancesEnd) + { + Assert(iBatchEnd->renderer->GetRendererType() == kRendererSprite); + SpriteRenderer* renderer = (SpriteRenderer*)iBatchEnd->renderer; + + if (renderer->GetSprite().IsNull()) + { + iBatchEnd++; + continue; + } + + UInt32 numIndices, numVertices; + renderer->GetGeometrySize(numIndices, numVertices); + + if ((numIndicesBatch + numIndices) <= kMaxNumSpriteTrianglesPerBatch) + { + numIndicesBatch += numIndices; + numVerticesBatch += numVertices; + iBatchEnd++; + } + else + { + if (numIndicesBatch) + { + RenderBatch(iBatchBegin, iBatchEnd - iBatchBegin, numIndicesBatch, numVerticesBatch, channels); + numIndicesBatch = 0; + numVerticesBatch = 0; + iBatchBegin = iBatchEnd; + } + else // Can't fit in one draw call + { + RenderBatch(iBatchEnd, 1, numIndices, numVertices, channels); + iBatchEnd++; + iBatchBegin = iBatchEnd; + } + } + } + + if ((iBatchBegin != iBatchEnd) && numIndicesBatch) + { + RenderBatch(iBatchBegin, iBatchEnd - iBatchBegin, numIndicesBatch, numVerticesBatch, channels); + } +} +#endif + +Material* SpriteRenderer::GetDefaultSpriteMaterial () +{ + Assert(gSpriteDefaultMaterial); + return gSpriteDefaultMaterial; +} + +#endif // ENABLE_SPRITES diff --git a/Runtime/Filters/Mesh/SpriteRenderer.h b/Runtime/Filters/Mesh/SpriteRenderer.h new file mode 100644 index 0000000..0bf47b9 --- /dev/null +++ b/Runtime/Filters/Mesh/SpriteRenderer.h @@ -0,0 +1,60 @@ +#ifndef SPRITERENDERER_H +#define SPRITERENDERER_H +#include "Configuration/UnityConfigure.h" + +#if ENABLE_SPRITES + +#include "Runtime/GfxDevice/ChannelAssigns.h" +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Graphics/SpriteFrame.h" + +class SpriteRenderer : public Renderer +{ +public: + REGISTER_DERIVED_CLASS (SpriteRenderer, Renderer) + DECLARE_OBJECT_SERIALIZE (SpriteRenderer) + + SpriteRenderer (MemLabelId label, ObjectCreationMode mode); + // ~SpriteRenderer (); declared-by-macro + + static bool IsSealedClass () { return true; } + static void InitializeClass (); + static void CleanupClass (); + + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + virtual void SmartReset (); + + virtual void UpdateTransformInfo(); + virtual void UpdateLocalAABB (); + virtual void Render (int materialIndex, const ChannelAssigns& channels); +#if GFX_ENABLE_DRAW_CALL_BATCHING + static void RenderMultiple (const BatchInstanceData* instances, size_t count, const ChannelAssigns& channels); +#endif + PPtr<Sprite> GetSprite() const { return m_Sprite; } + void SetSprite(PPtr<Sprite> sprite); + + ColorRGBAf GetColor() const { return m_Color; } + void SetColor(const ColorRGBAf& color) { m_Color = color; } + + static void SetupMaterialPropertyBlock(MaterialPropertyBlock& block, const Texture2D* spriteTexture); + + static Material* GetDefaultSpriteMaterial(); + +private: + PPtr<Sprite> m_Sprite; + ColorRGBAf m_Color; + + void SetupMaterialProperties(); + void GetGeometrySize(UInt32& indexCount, UInt32& vertexCount); + +#if GFX_ENABLE_DRAW_CALL_BATCHING + static void RenderBatch (const BatchInstanceData* instances, size_t count, size_t numIndices, size_t numVertices, const ChannelAssigns& channels); +#endif + // Context + const SpriteRenderData* GetSpriteRenderDataInContext(const PPtr<Sprite>& frame); +}; + +#endif //ENABLE_SPRITES + +#endif diff --git a/Runtime/Filters/Mesh/SpriteRendererAnimationBinding.cpp b/Runtime/Filters/Mesh/SpriteRendererAnimationBinding.cpp new file mode 100644 index 0000000..a36406f --- /dev/null +++ b/Runtime/Filters/Mesh/SpriteRendererAnimationBinding.cpp @@ -0,0 +1,68 @@ +#include "UnityPrefix.h" +#include "Runtime/Animation/GenericAnimationBindingCache.h" +#include "Runtime/Animation/AnimationClipBindings.h" +#include "SpriteRenderer.h" +#include "Runtime/Interfaces/IAnimationBinding.h" + +#if ENABLE_SPRITES + +static const char* kSpriteFrame = "m_Sprite"; + +class SpriteRendererAnimationBinding : public IAnimationBinding +{ +public: + +#if UNITY_EDITOR + virtual void GetAllAnimatableProperties (Object& targetObject, std::vector<EditorCurveBinding>& outProperties) const + { + AddPPtrBinding (outProperties, ClassID(SpriteRenderer), kSpriteFrame); + } +#endif + + virtual float GetFloatValue (const UnityEngine::Animation::BoundCurve& bind) const { return 0.0F; } + virtual void SetFloatValue (const UnityEngine::Animation::BoundCurve& bind, float value) const { } + + virtual void SetPPtrValue (const UnityEngine::Animation::BoundCurve& bound, SInt32 value) const + { + SpriteRenderer* renderer = reinterpret_cast<SpriteRenderer*>(bound.targetObject); + renderer->SetSprite(PPtr<Sprite> (value)); + } + + virtual SInt32 GetPPtrValue (const UnityEngine::Animation::BoundCurve& bound) const + { + SpriteRenderer* renderer = reinterpret_cast<SpriteRenderer*>(bound.targetObject); + return renderer->GetSprite().GetInstanceID(); + } + + virtual bool GenerateBinding (const UnityStr& attribute, bool pptrCurve, UnityEngine::Animation::GenericBinding& outputBinding) const + { + if (attribute == kSpriteFrame && pptrCurve) + { + outputBinding.attribute = 0; + return true; + } + + return false; + } + + virtual ClassIDType BindValue (Object& target, const UnityEngine::Animation::GenericBinding& inputBinding, UnityEngine::Animation::BoundCurve& bound) const + { + return ClassID(Sprite); + } +}; + +static SpriteRendererAnimationBinding* gSpriteRendererBinding = NULL; + +void InitializeSpriteRendererAnimationBindingInterface () +{ + Assert(gSpriteRendererBinding == NULL); + gSpriteRendererBinding = UNITY_NEW (SpriteRendererAnimationBinding, kMemAnimation); + UnityEngine::Animation::GetGenericAnimationBindingCache ().RegisterIAnimationBinding (ClassID(SpriteRenderer), UnityEngine::Animation::kSpriteRendererPPtrBinding, gSpriteRendererBinding); +} + +void CleanupSpriteRendererAnimationBindingInterface () +{ + UNITY_DELETE (gSpriteRendererBinding, kMemAnimation); +} + +#endif
\ No newline at end of file diff --git a/Runtime/Filters/Mesh/SpriteRendererAnimationBinding.h b/Runtime/Filters/Mesh/SpriteRendererAnimationBinding.h new file mode 100644 index 0000000..63e2731 --- /dev/null +++ b/Runtime/Filters/Mesh/SpriteRendererAnimationBinding.h @@ -0,0 +1,2 @@ +void InitializeSpriteRendererAnimationBindingInterface (); +void CleanupSpriteRendererAnimationBindingInterface ();
\ No newline at end of file diff --git a/Runtime/Filters/Mesh/TransformVertex.cpp b/Runtime/Filters/Mesh/TransformVertex.cpp new file mode 100644 index 0000000..e9bebc1 --- /dev/null +++ b/Runtime/Filters/Mesh/TransformVertex.cpp @@ -0,0 +1,205 @@ +#include "UnityPrefix.h" +#include "TransformVertex.h" + +#include "Runtime/Math/Matrix4x4.h" +#include "Runtime/Math/Vector4.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Color.h" + +#include "Runtime/Misc/CPUInfo.h" + +void +TransformVerticesStridedREF( StrideIterator<Vector3f> inPos, StrideIterator<Vector3f> inNormal, + StrideIterator<ColorRGBA32> inColor, StrideIterator<Vector2f> inTexCoord0, StrideIterator<Vector2f> inTexCoord1, + StrideIterator<Vector4f> inTangent, + UInt8* dstData, const Matrix4x4f& m, unsigned vertexCount, bool multiStream ) +{ + // NOTE: kill this code once all shaders normalize normals & tangents! + // + // We batch uniformly scaled objects, so derive the "normal matrix" here by scaling world matrix axes. + // On reference code seems much cheaper than full normalization of normal/tangent vectors. + // Test with scene of 200k vertices on Core i7 2600K: no handling of scale 3.77ms, normalization 8.00ms, + // using scaled normal matrix 3.80ms. + // + // Note that ARM NEON/VFP transformation code does not handle this, but it's not needed on GLES platforms + // since shaders always normalize normal & tangent. Might be needed on WinRT+ARM though (or just disable + // dynamic batching with tangents there). + Matrix4x4f nm; + CopyMatrix(m.GetPtr(), nm.GetPtr()); + const float axisLen = Magnitude (m.GetAxisX()); + float scale = axisLen > 1.0e-6f ? 1.0f / axisLen : 1.0f; + nm.Get (0, 0) *= scale; + nm.Get (1, 0) *= scale; + nm.Get (2, 0) *= scale; + nm.Get (0, 1) *= scale; + nm.Get (1, 1) *= scale; + nm.Get (2, 1) *= scale; + nm.Get (0, 2) *= scale; + nm.Get (1, 2) *= scale; + nm.Get (2, 2) *= scale; + + while (vertexCount --> 0) + { + Vector3f* outPos = reinterpret_cast<Vector3f*> (dstData); + m.MultiplyPoint3(*inPos, *outPos); + dstData += sizeof(Vector3f); + ++inPos; + + if (inNormal.GetPointer()) + { + Vector3f* outNormal = reinterpret_cast<Vector3f*> (dstData); + nm.MultiplyVector3(*inNormal, *outNormal); + dstData += sizeof(Vector3f); + ++inNormal; + } + + if (inColor.GetPointer()) + { + memcpy(dstData, inColor.GetPointer(), sizeof(ColorRGBA32)); + dstData += sizeof(ColorRGBA32); + ++inColor; + } + + if (inTexCoord0.GetPointer()) + { + memcpy(dstData, inTexCoord0.GetPointer(), sizeof(Vector2f)); + dstData += sizeof(Vector2f); + ++inTexCoord0; + } + + if (inTexCoord1.GetPointer()) + { + memcpy(dstData, inTexCoord1.GetPointer(), sizeof(Vector2f)); + dstData += sizeof(Vector2f); + ++inTexCoord1; + } + + if (inTangent.GetPointer()) + { + Vector4f* outTangent = reinterpret_cast<Vector4f*> (dstData); + Vector3f* outTangentXYZ = reinterpret_cast<Vector3f*> (outTangent); + nm.MultiplyVector3(reinterpret_cast<const Vector3f&>(*inTangent), *outTangentXYZ); + outTangent->w = inTangent->w; + dstData += sizeof(Vector4f); + ++inTangent; + } + } +} + + + +#if (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING) || UNITY_SUPPORTS_VFP + +typedef void (*TransformFunc)( const void*, const void*, const void*, const float*, void*, int ); +typedef void (*TransformFuncWithTangents)( const void*, const void*, const void*, const float*, void*, int, const void* ); + + +#if UNITY_SUPPORTS_NEON +namespace TransformNEON +{ + #define TRANSFORM_FUNC(prefix, addData) s_TransformVertices_Strided_##prefix##_##addData##_NEON + + TransformFunc TransformXYZ[] = + { + TRANSFORM_FUNC(XYZ,0), TRANSFORM_FUNC(XYZ,1), TRANSFORM_FUNC(XYZ,2), TRANSFORM_FUNC(XYZ,3), TRANSFORM_FUNC(XYZ,4), TRANSFORM_FUNC(XYZ,5) + }; + + TransformFunc TransformXYZN[] = + { + TRANSFORM_FUNC(XYZN,0), TRANSFORM_FUNC(XYZN,1), TRANSFORM_FUNC(XYZN,2), TRANSFORM_FUNC(XYZN,3), TRANSFORM_FUNC(XYZN,4), TRANSFORM_FUNC(XYZN,5) + }; + + TransformFuncWithTangents TransformXYZNT[] = + { + TRANSFORM_FUNC(XYZNT,0), TRANSFORM_FUNC(XYZNT,1), TRANSFORM_FUNC(XYZNT,2), TRANSFORM_FUNC(XYZNT,3), TRANSFORM_FUNC(XYZNT,4), TRANSFORM_FUNC(XYZNT,5) + }; + + #undef TRANSFORM_FUNC +} +#endif // UNITY_SUPPORTS_NEON + + +#if UNITY_SUPPORTS_VFP +namespace TransformVFP +{ + #define TRANSFORM_FUNC(prefix, addData) s_TransformVertices_Strided_##prefix##_##addData##_VFP + + TransformFunc TransformXYZ[] = + { + TRANSFORM_FUNC(XYZ,0), TRANSFORM_FUNC(XYZ,1), TRANSFORM_FUNC(XYZ,2), TRANSFORM_FUNC(XYZ,3), TRANSFORM_FUNC(XYZ,4), TRANSFORM_FUNC(XYZ,5) + }; + + TransformFunc TransformXYZN[] = + { + TRANSFORM_FUNC(XYZN,0), TRANSFORM_FUNC(XYZN,1), TRANSFORM_FUNC(XYZN,2), TRANSFORM_FUNC(XYZN,3), TRANSFORM_FUNC(XYZN,4), TRANSFORM_FUNC(XYZN,5) + }; + + TransformFuncWithTangents TransformXYZNT[] = + { + TRANSFORM_FUNC(XYZNT,0), TRANSFORM_FUNC(XYZNT,1), TRANSFORM_FUNC(XYZNT,2), TRANSFORM_FUNC(XYZNT,3), TRANSFORM_FUNC(XYZNT,4), TRANSFORM_FUNC(XYZNT,5) + }; + + #undef TRANSFORM_FUNC +} +#endif // UNITY_SUPPORTS_VFP + +void +TransformVerticesStridedARM( StrideIterator<Vector3f> inPos, StrideIterator<Vector3f> inNormal, + StrideIterator<ColorRGBA32> inColor, StrideIterator<Vector2f> inTexCoord0, StrideIterator<Vector2f> inTexCoord1, + StrideIterator<Vector4f> inTangent, + UInt8* dstData, const Matrix4x4f& m, unsigned vertexCount, bool multiStream ) +{ + int addDataSize = 0; + if( inColor.GetPointer() ) addDataSize += 1; + if( inTexCoord0.GetPointer() ) addDataSize += 2; + if( inTexCoord1.GetPointer() ) addDataSize += 2; + + const void* addDataSrc = 0; + if( inColor.GetPointer() ) addDataSrc = inColor.GetPointer(); + else if( inTexCoord0.GetPointer() ) addDataSrc = inTexCoord0.GetPointer(); + else if( inTexCoord1.GetPointer() ) addDataSrc = inTexCoord1.GetPointer(); + + // slow path determination + if( (inColor.GetPointer() && inTexCoord1.GetPointer() && !inTexCoord0.GetPointer()) + || (inTangent.GetPointer() && !inNormal.GetPointer()) || multiStream ) + { + TransformVerticesStridedREF(inPos, inNormal, inColor, inTexCoord0, inTexCoord1, inTangent, dstData, m, vertexCount, multiStream); + return; + } + + int stride = inPos.GetStride(); + const UInt8* inDataBegin = static_cast<const UInt8*>(inPos.GetPointer()); + const UInt8* inDataEnd = inDataBegin + vertexCount * stride; + +#if UNITY_SUPPORTS_NEON + if (CPUInfo::HasNEONSupport()) + { + using namespace TransformNEON; + if( inNormal.GetPointer() && inTangent.GetPointer() ) + TransformXYZNT[addDataSize]( inDataBegin, inDataEnd, addDataSrc, m.m_Data, dstData, stride, inTangent.GetPointer() ); + else if( inNormal.GetPointer() ) + TransformXYZN[addDataSize]( inDataBegin, inDataEnd, addDataSrc, m.m_Data, dstData, stride ); + else + TransformXYZ[addDataSize]( inDataBegin, inDataEnd, addDataSrc, m.m_Data, dstData, stride ); + } + else +#endif +#if UNITY_SUPPORTS_VFP + { + using namespace TransformVFP; + if( inNormal.GetPointer() && inTangent.GetPointer() ) + TransformXYZNT[addDataSize]( inDataBegin, inDataEnd, addDataSrc, m.m_Data, dstData, stride, inTangent.GetPointer() ); + else if( inNormal.GetPointer() ) + TransformXYZN[addDataSize]( inDataBegin, inDataEnd, addDataSrc, m.m_Data, dstData, stride ); + else + TransformXYZ[addDataSize]( inDataBegin, inDataEnd, addDataSrc, m.m_Data, dstData, stride ); + } +#else + { + ErrorString("non-NEON path not enabled!"); + } +#endif +} +#endif + diff --git a/Runtime/Filters/Mesh/TransformVertex.h b/Runtime/Filters/Mesh/TransformVertex.h new file mode 100644 index 0000000..fe7aa77 --- /dev/null +++ b/Runtime/Filters/Mesh/TransformVertex.h @@ -0,0 +1,175 @@ +#ifndef TRANSFORM_VERTEX_H_ +#define TRANSFORM_VERTEX_H_ + +#include "Configuration/PrefixConfigure.h" +#include "Runtime/Utilities/StrideIterator.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Vector4.h" +#include "Runtime/Math/Color.h" + +class Matrix4x4f; + + +//============================================================================== + +#define DECL_TRANSFORM_VERTICES_STRIDED(code, num, postfix) \ + void s_TransformVertices_Strided_##code##_##num##_##postfix( const void* srcData, const void* srcDataEnd, const void* addData, \ + const float* xform, void* outData, int stride \ + ); + +#define DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(num, postfix) \ + void s_TransformVertices_Strided_XYZNT_##num##_##postfix( const void* srcData, const void* srcDataEnd, const void* addData, \ + const float* xform, void* outData, int stride, const void* srcTangent \ + ); + + +#if UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING + +extern "C" +{ +#if UNITY_ANDROID || UNITY_WINRT || UNITY_BB10 || UNITY_TIZEN + #define s_TransformVertices_Strided_XYZ_0_NEON _s_TransformVertices_Strided_XYZ_0_NEON + #define s_TransformVertices_Strided_XYZ_1_NEON _s_TransformVertices_Strided_XYZ_1_NEON + #define s_TransformVertices_Strided_XYZ_2_NEON _s_TransformVertices_Strided_XYZ_2_NEON + #define s_TransformVertices_Strided_XYZ_3_NEON _s_TransformVertices_Strided_XYZ_3_NEON + #define s_TransformVertices_Strided_XYZ_4_NEON _s_TransformVertices_Strided_XYZ_4_NEON + #define s_TransformVertices_Strided_XYZ_5_NEON _s_TransformVertices_Strided_XYZ_5_NEON + + #define s_TransformVertices_Strided_XYZN_0_NEON _s_TransformVertices_Strided_XYZN_0_NEON + #define s_TransformVertices_Strided_XYZN_1_NEON _s_TransformVertices_Strided_XYZN_1_NEON + #define s_TransformVertices_Strided_XYZN_2_NEON _s_TransformVertices_Strided_XYZN_2_NEON + #define s_TransformVertices_Strided_XYZN_3_NEON _s_TransformVertices_Strided_XYZN_3_NEON + #define s_TransformVertices_Strided_XYZN_4_NEON _s_TransformVertices_Strided_XYZN_4_NEON + #define s_TransformVertices_Strided_XYZN_5_NEON _s_TransformVertices_Strided_XYZN_5_NEON + + #define s_TransformVertices_Strided_XYZNT_0_NEON _s_TransformVertices_Strided_XYZNT_0_NEON + #define s_TransformVertices_Strided_XYZNT_1_NEON _s_TransformVertices_Strided_XYZNT_1_NEON + #define s_TransformVertices_Strided_XYZNT_2_NEON _s_TransformVertices_Strided_XYZNT_2_NEON + #define s_TransformVertices_Strided_XYZNT_3_NEON _s_TransformVertices_Strided_XYZNT_3_NEON + #define s_TransformVertices_Strided_XYZNT_4_NEON _s_TransformVertices_Strided_XYZNT_4_NEON + #define s_TransformVertices_Strided_XYZNT_5_NEON _s_TransformVertices_Strided_XYZNT_5_NEON +#if ENABLE_SPRITES +#define s_TransformVertices_Sprite_NEON _s_TransformVertices_Sprite_NEON +#endif + +#endif // UNITY_ANDROID || UNITY_WINRT || UNITY_BB10 || UNITY_TIZEN + + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,0,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,1,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,2,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,3,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,4,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,5,NEON); + + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,0,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,1,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,2,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,3,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,4,NEON); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,5,NEON); + + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(0,NEON); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(1,NEON); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(2,NEON); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(3,NEON); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(4,NEON); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(5,NEON); +#if ENABLE_SPRITES + void s_TransformVertices_Sprite_NEON(const void* srcData, const void* srcDataEnd, const void* addData, const float* xform, void* outData, int stride, unsigned int color); +#endif +} + +#endif + + +#if UNITY_SUPPORTS_VFP + +extern "C" +{ +#if UNITY_ANDROID || UNITY_BB10 || UNITY_TIZEN + #define s_TransformVertices_Strided_XYZ_0_VFP _s_TransformVertices_Strided_XYZ_0_VFP + #define s_TransformVertices_Strided_XYZ_1_VFP _s_TransformVertices_Strided_XYZ_1_VFP + #define s_TransformVertices_Strided_XYZ_2_VFP _s_TransformVertices_Strided_XYZ_2_VFP + #define s_TransformVertices_Strided_XYZ_3_VFP _s_TransformVertices_Strided_XYZ_3_VFP + #define s_TransformVertices_Strided_XYZ_4_VFP _s_TransformVertices_Strided_XYZ_4_VFP + #define s_TransformVertices_Strided_XYZ_5_VFP _s_TransformVertices_Strided_XYZ_5_VFP + + #define s_TransformVertices_Strided_XYZN_0_VFP _s_TransformVertices_Strided_XYZN_0_VFP + #define s_TransformVertices_Strided_XYZN_1_VFP _s_TransformVertices_Strided_XYZN_1_VFP + #define s_TransformVertices_Strided_XYZN_2_VFP _s_TransformVertices_Strided_XYZN_2_VFP + #define s_TransformVertices_Strided_XYZN_3_VFP _s_TransformVertices_Strided_XYZN_3_VFP + #define s_TransformVertices_Strided_XYZN_4_VFP _s_TransformVertices_Strided_XYZN_4_VFP + #define s_TransformVertices_Strided_XYZN_5_VFP _s_TransformVertices_Strided_XYZN_5_VFP + + #define s_TransformVertices_Strided_XYZNT_0_VFP _s_TransformVertices_Strided_XYZNT_0_VFP + #define s_TransformVertices_Strided_XYZNT_1_VFP _s_TransformVertices_Strided_XYZNT_1_VFP + #define s_TransformVertices_Strided_XYZNT_2_VFP _s_TransformVertices_Strided_XYZNT_2_VFP + #define s_TransformVertices_Strided_XYZNT_3_VFP _s_TransformVertices_Strided_XYZNT_3_VFP + #define s_TransformVertices_Strided_XYZNT_4_VFP _s_TransformVertices_Strided_XYZNT_4_VFP + #define s_TransformVertices_Strided_XYZNT_5_VFP _s_TransformVertices_Strided_XYZNT_5_VFP +#if ENABLE_SPRITES + #define s_TransformVertices_Sprite_VFP _s_TransformVertices_Sprite_VFP +#endif +#endif // UNITY_ANDROID || UNITY_BB10 || UNITY_TIZEN + + + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,0,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,1,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,2,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,3,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,4,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZ,5,VFP); + + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,0,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,1,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,2,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,3,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,4,VFP); + DECL_TRANSFORM_VERTICES_STRIDED(XYZN,5,VFP); + + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(0,VFP); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(1,VFP); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(2,VFP); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(3,VFP); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(4,VFP); + DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS(5,VFP); +#if ENABLE_SPRITES + void s_TransformVertices_Sprite_VFP (const void* srcData, const void* srcDataEnd, const void* addData, const float* xform, void* outData, int stride, unsigned int color); +#endif +} + +#endif + + +#undef DECL_TRANSFORM_VERTICES_STRIDED_TANGENTS +#undef DECL_TRANSFORM_VERTICES_STRIDED + + +//============================================================================== + +void +TransformVerticesStridedREF( StrideIterator<Vector3f> inPos, StrideIterator<Vector3f> inNormal, + StrideIterator<ColorRGBA32> inColor, StrideIterator<Vector2f> inTexCoord0, StrideIterator<Vector2f> inTexCoord1, + StrideIterator<Vector4f> inTangent, + UInt8* dstData, const Matrix4x4f& m, unsigned vertexCount, bool multiStream ); + +#if (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING) || UNITY_SUPPORTS_VFP +void +TransformVerticesStridedARM( StrideIterator<Vector3f> inPos, StrideIterator<Vector3f> inNormal, + StrideIterator<ColorRGBA32> inColor, StrideIterator<Vector2f> inTexCoord0, StrideIterator<Vector2f> inTexCoord1, + StrideIterator<Vector4f> inTangent, + UInt8* dstData, const Matrix4x4f& m, unsigned vertexCount, bool multiStream ); +#endif + + +#if (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING) || UNITY_SUPPORTS_VFP + #define TransformVerticesStrided TransformVerticesStridedARM +#else + #define TransformVerticesStrided TransformVerticesStridedREF +#endif + + +//============================================================================== + +#endif // TRANSFORM_VERTEX_H_ diff --git a/Runtime/Filters/Mesh/TransformVertexNEON.asm b/Runtime/Filters/Mesh/TransformVertexNEON.asm new file mode 100644 index 0000000..7db462b --- /dev/null +++ b/Runtime/Filters/Mesh/TransformVertexNEON.asm @@ -0,0 +1,694 @@ + AREA .text, CODE + + EXPORT _s_TransformVertices_Strided_XYZ_0_NEON + EXPORT _s_TransformVertices_Strided_XYZ_1_NEON + EXPORT _s_TransformVertices_Strided_XYZ_2_NEON + EXPORT _s_TransformVertices_Strided_XYZ_3_NEON + EXPORT _s_TransformVertices_Strided_XYZ_4_NEON + EXPORT _s_TransformVertices_Strided_XYZ_5_NEON + EXPORT _s_TransformVertices_Strided_XYZN_0_NEON + EXPORT _s_TransformVertices_Strided_XYZN_1_NEON + EXPORT _s_TransformVertices_Strided_XYZN_2_NEON + EXPORT _s_TransformVertices_Strided_XYZN_3_NEON + EXPORT _s_TransformVertices_Strided_XYZN_4_NEON + EXPORT _s_TransformVertices_Strided_XYZN_5_NEON + EXPORT _s_TransformVertices_Strided_XYZNT_0_NEON + EXPORT _s_TransformVertices_Strided_XYZNT_1_NEON + EXPORT _s_TransformVertices_Strided_XYZNT_2_NEON + EXPORT _s_TransformVertices_Strided_XYZNT_3_NEON + EXPORT _s_TransformVertices_Strided_XYZNT_4_NEON + EXPORT _s_TransformVertices_Strided_XYZNT_5_NEON + +|_s_TransformVertices_Strided_XYZ_0_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + +|TransformVertices_Strided_XYZ_0_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d6-d7}, [r0], r4 + vmla.f32 q0, q12, d6[0] + vmul.f32 q1, q13, d6[1] + vmul.f32 q2, q14, d7[0] + vadd.f32 q0, q0, q1 + vadd.f32 q0, q0, q2 + cmp r0, r1 + vst1.32 {d0-d1}, [r3], r6 + vorr q0, q15, q15 + bcc.w |TransformVertices_Strided_XYZ_0_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZ_1_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + nop.w + nop.w + +|TransformVertices_Strided_XYZ_1_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d6-d7}, [r0], r4 + vmla.f32 q0, q12, d6[0] + vmul.f32 q1, q13, d6[1] + vmul.f32 q2, q14, d7[0] + vadd.f32 q0, q0, q1 + vld1.32 {d9}, [r2], r4 + vadd.f32 q0, q0, q2 + cmp r0, r1 + vst1.32 {d0-d1}, [r3], r6 + vorr q0, q15, q15 + vst1.32 {d9[0]}, [r3]! + bcc.w |TransformVertices_Strided_XYZ_1_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZ_2_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + +|TransformVertices_Strided_XYZ_2_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d6-d7}, [r0], r4 + vmla.f32 q0, q12, d6[0] + vmul.f32 q1, q13, d6[1] + vmul.f32 q2, q14, d7[0] + vadd.f32 q0, q0, q1 + vld1.32 {d9}, [r2], r4 + vadd.f32 q0, q0, q2 + cmp r0, r1 + vst1.32 {d0-d1}, [r3], r6 + vorr q0, q15, q15 + vst1.32 {d9}, [r3]! + bcc.w |TransformVertices_Strided_XYZ_2_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZ_3_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + +|TransformVertices_Strided_XYZ_3_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d6-d7}, [r0], r4 + vmla.f32 q0, q12, d6[0] + vmul.f32 q1, q13, d6[1] + vmul.f32 q2, q14, d7[0] + vadd.f32 q0, q0, q1 + vld1.32 {d9-d10}, [r2], r4 + vadd.f32 q0, q0, q2 + cmp r0, r1 + vst1.32 {d0-d1}, [r3], r6 + vorr q0, q15, q15 + vst1.32 {d9}, [r3]! + vst1.32 {d10[0]}, [r3]! + bcc.w |TransformVertices_Strided_XYZ_3_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZ_4_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + +|TransformVertices_Strided_XYZ_4_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d6-d7}, [r0], r4 + vmla.f32 q0, q12, d6[0] + vmul.f32 q1, q13, d6[1] + vmul.f32 q2, q14, d7[0] + vadd.f32 q0, q0, q1 + vld1.32 {d9-d10}, [r2], r4 + vadd.f32 q0, q0, q2 + cmp r0, r1 + vst1.32 {d0-d1}, [r3], r6 + vorr q0, q15, q15 + vst1.32 {d9-d10}, [r3]! + bcc.w |TransformVertices_Strided_XYZ_4_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZ_5_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + +|TransformVertices_Strided_XYZ_5_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d6-d7}, [r0], r4 + vmla.f32 q0, q12, d6[0] + vmul.f32 q1, q13, d6[1] + vmul.f32 q2, q14, d7[0] + vadd.f32 q0, q0, q1 + vld1.32 {d9-d11}, [r2], r4 + vadd.f32 q0, q0, q2 + cmp r0, r1 + vst1.32 {d0-d1}, [r3], r6 + vorr q0, q15, q15 + vst1.32 {d9-d10}, [r3]! + vst1.32 {d11[0]}, [r3]! + bcc.w |TransformVertices_Strided_XYZ_5_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZN_0_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + +|TransformVertices_Strided_XYZN_0_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + bcc.w |TransformVertices_Strided_XYZN_0_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZN_1_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + +|TransformVertices_Strided_XYZN_1_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vld1.32 {d9}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9[0]}, [r3]! + bcc.w |TransformVertices_Strided_XYZN_1_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZN_2_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + nop.w + nop.w + +|TransformVertices_Strided_XYZN_2_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vld1.32 {d9}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9}, [r3]! + bcc.w |TransformVertices_Strided_XYZN_2_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZN_3_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + nop.w + nop.w + +|TransformVertices_Strided_XYZN_3_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vld1.32 {d9-d10}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9}, [r3]! + vst1.32 {d10[0]}, [r3]! + bcc.w |TransformVertices_Strided_XYZN_3_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZN_4_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + nop.w + +|TransformVertices_Strided_XYZN_4_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vld1.32 {d9-d10}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9-d10}, [r3]! + bcc.w |TransformVertices_Strided_XYZN_4_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZN_5_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + nop + nop.w + nop.w + nop.w + +|TransformVertices_Strided_XYZN_5_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vld1.32 {d9-d11}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9-d10}, [r3]! + vst1.32 {d11[0]}, [r3]! + bcc.w |TransformVertices_Strided_XYZN_5_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZNT_0_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + ldr.w r8, [ip, #8] + mov.w r9, #12 + mov.w sl, #4 + nop + nop.w + nop.w + nop.w + +|TransformVertices_Strided_XYZNT_0_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vld1.32 {d7-d8}, [r8], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vmul.f32 q11, q12, d7[0] + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q11, q13, d7[1] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vmla.f32 q11, q14, d8[0] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vtrn.32 d8, d7 + vst1.32 {d22-d23}, [r3], r9 + vst1.32 {d7[0]}, [r3], sl + bcc.w |TransformVertices_Strided_XYZNT_0_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZNT_1_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + ldr.w r8, [ip, #8] + mov.w r9, #12 + mov.w sl, #4 + nop + nop.w + nop.w + nop.w + +|TransformVertices_Strided_XYZNT_1_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vld1.32 {d7-d8}, [r8], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vmul.f32 q11, q12, d7[0] + vld1.32 {d9}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q11, q13, d7[1] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vmla.f32 q11, q14, d8[0] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9[0]}, [r3]! + vtrn.32 d8, d7 + vst1.32 {d22-d23}, [r3], r9 + vst1.32 {d7[0]}, [r3], sl + bcc.w |TransformVertices_Strided_XYZNT_1_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZNT_2_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + ldr.w r8, [ip, #8] + mov.w r9, #12 + mov.w sl, #4 + nop + nop.w + +|TransformVertices_Strided_XYZNT_2_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vld1.32 {d7-d8}, [r8], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vmul.f32 q11, q12, d7[0] + vld1.32 {d9}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q11, q13, d7[1] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vmla.f32 q11, q14, d8[0] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9}, [r3]! + vtrn.32 d8, d7 + vst1.32 {d22-d23}, [r3], r9 + vst1.32 {d7[0]}, [r3], sl + bcc.w |TransformVertices_Strided_XYZNT_2_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZNT_3_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + ldr.w r8, [ip, #8] + mov.w r9, #12 + mov.w sl, #4 + nop + nop.w + +|TransformVertices_Strided_XYZNT_3_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vld1.32 {d7-d8}, [r8], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vmul.f32 q11, q12, d7[0] + vld1.32 {d9-d10}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q11, q13, d7[1] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vmla.f32 q11, q14, d8[0] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9}, [r3]! + vst1.32 {d10[0]}, [r3]! + vtrn.32 d8, d7 + vst1.32 {d22-d23}, [r3], r9 + vst1.32 {d7[0]}, [r3], sl + bcc.w |TransformVertices_Strided_XYZNT_3_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZNT_4_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + ldr.w r8, [ip, #8] + mov.w r9, #12 + mov.w sl, #4 + nop + +|TransformVertices_Strided_XYZNT_4_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vld1.32 {d7-d8}, [r8], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vmul.f32 q11, q12, d7[0] + vld1.32 {d9-d10}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q11, q13, d7[1] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vmla.f32 q11, q14, d8[0] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9-d10}, [r3]! + vtrn.32 d8, d7 + vst1.32 {d22-d23}, [r3], r9 + vst1.32 {d7[0]}, [r3], sl + bcc.w |TransformVertices_Strided_XYZNT_4_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + ENDP + + +|_s_TransformVertices_Strided_XYZNT_5_NEON| PROC + mov ip, sp + vpush {s0-s15} + stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vldmia r3!, {d24-d31} + mov.w r6, #12 + ldr.w r3, [ip] + ldr.w r4, [ip, #4] + vorr q0, q15, q15 + ldr.w r8, [ip, #8] + mov.w r9, #12 + mov.w sl, #4 + nop + nop.w + +|TransformVertices_Strided_XYZNT_5_Loop| + pld [r0, #512] ; 0x200 + vld1.32 {d4-d6}, [r0], r4 + vld1.32 {d7-d8}, [r8], r4 + vmla.f32 q0, q12, d4[0] + vmul.f32 q1, q12, d5[1] + vmul.f32 q11, q12, d7[0] + vld1.32 {d9-d11}, [r2], r4 + vmla.f32 q0, q13, d4[1] + vmla.f32 q1, q13, d6[0] + vmla.f32 q11, q13, d7[1] + vmla.f32 q0, q14, d5[0] + vmla.f32 q1, q14, d6[1] + vmla.f32 q11, q14, d8[0] + vst1.32 {d0-d1}, [r3], r6 + cmp r0, r1 + vorr q0, q15, q15 + vst1.32 {d2-d3}, [r3], r6 + vst1.32 {d9-d10}, [r3]! + vst1.32 {d11[0]}, [r3]! + vtrn.32 d8, d7 + vst1.32 {d22-d23}, [r3], r9 + vst1.32 {d7[0]}, [r3], sl + bcc.w |TransformVertices_Strided_XYZNT_5_Loop| + ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, fp} + vpop {s0-s15} + bx lr + nop.w + nop.w + nop.w + ENDP + + + END diff --git a/Runtime/Filters/Mesh/TransformVertexNEON.s b/Runtime/Filters/Mesh/TransformVertexNEON.s new file mode 100644 index 0000000..e21a554 --- /dev/null +++ b/Runtime/Filters/Mesh/TransformVertexNEON.s @@ -0,0 +1,224 @@ +#define UNITY_ASSEMBLER +#include "Configuration/PrefixConfigure.h" + +#if UNITY_SUPPORTS_NEON + +.set device,0 +.set device,__arm__ + +.if device + +//.code32 + + +.globl _s_TransformVertices_Strided_XYZ_0_NEON +.globl _s_TransformVertices_Strided_XYZ_1_NEON +.globl _s_TransformVertices_Strided_XYZ_2_NEON +.globl _s_TransformVertices_Strided_XYZ_3_NEON +.globl _s_TransformVertices_Strided_XYZ_4_NEON +.globl _s_TransformVertices_Strided_XYZ_5_NEON + +.globl _s_TransformVertices_Strided_XYZN_0_NEON +.globl _s_TransformVertices_Strided_XYZN_1_NEON +.globl _s_TransformVertices_Strided_XYZN_2_NEON +.globl _s_TransformVertices_Strided_XYZN_3_NEON +.globl _s_TransformVertices_Strided_XYZN_4_NEON +.globl _s_TransformVertices_Strided_XYZN_5_NEON + +.globl _s_TransformVertices_Strided_XYZNT_0_NEON +.globl _s_TransformVertices_Strided_XYZNT_1_NEON +.globl _s_TransformVertices_Strided_XYZNT_2_NEON +.globl _s_TransformVertices_Strided_XYZNT_3_NEON +.globl _s_TransformVertices_Strided_XYZNT_4_NEON +.globl _s_TransformVertices_Strided_XYZNT_5_NEON + +.globl _s_TransformVertices_Sprite_NEON + + +#define STRIDED_INPUT 1 + + +#define LOOP_XYZ 1 +#define LOOP_XYZN 0 +#define LOOP_XYZNT 0 +#define LOOP_SPRITE 0 + + +_s_TransformVertices_Strided_XYZ_0_NEON: +#define COPY_DATA_SZ 0 +#define LOOP_NAME TransformVertices_Strided_XYZ_0_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_1_NEON: +#define COPY_DATA_SZ 1 +#define LOOP_NAME TransformVertices_Strided_XYZ_1_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_2_NEON: +#define COPY_DATA_SZ 2 +#define LOOP_NAME TransformVertices_Strided_XYZ_2_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_3_NEON: +#define COPY_DATA_SZ 3 +#define LOOP_NAME TransformVertices_Strided_XYZ_3_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_4_NEON: +#define COPY_DATA_SZ 4 +#define LOOP_NAME TransformVertices_Strided_XYZ_4_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_5_NEON: +#define COPY_DATA_SZ 5 +#define LOOP_NAME TransformVertices_Strided_XYZ_5_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + + +#undef LOOP_XYZ +#undef LOOP_XYZN +#undef LOOP_XYZNT +#undef LOOP_SPRITE + + +#define LOOP_XYZ 0 +#define LOOP_XYZN 1 +#define LOOP_XYZNT 0 +#define LOOP_SPRITE 0 + + +_s_TransformVertices_Strided_XYZN_0_NEON: +#define COPY_DATA_SZ 0 +#define LOOP_NAME TransformVertices_Strided_XYZN_0_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_1_NEON: +#define COPY_DATA_SZ 1 +#define LOOP_NAME TransformVertices_Strided_XYZN_1_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_2_NEON: +#define COPY_DATA_SZ 2 +#define LOOP_NAME TransformVertices_Strided_XYZN_2_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_3_NEON: +#define COPY_DATA_SZ 3 +#define LOOP_NAME TransformVertices_Strided_XYZN_3_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_4_NEON: +#define COPY_DATA_SZ 4 +#define LOOP_NAME TransformVertices_Strided_XYZN_4_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_5_NEON: +#define COPY_DATA_SZ 5 +#define LOOP_NAME TransformVertices_Strided_XYZN_5_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + + +#undef LOOP_XYZ +#undef LOOP_XYZN +#undef LOOP_XYZNT +#undef LOOP_SPRITE + + +#define LOOP_XYZ 0 +#define LOOP_XYZN 0 +#define LOOP_XYZNT 1 +#define LOOP_SPRITE 0 + + +_s_TransformVertices_Strided_XYZNT_0_NEON: +#define COPY_DATA_SZ 0 +#define LOOP_NAME TransformVertices_Strided_XYZNT_0_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_1_NEON: +#define COPY_DATA_SZ 1 +#define LOOP_NAME TransformVertices_Strided_XYZNT_1_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_2_NEON: +#define COPY_DATA_SZ 2 +#define LOOP_NAME TransformVertices_Strided_XYZNT_2_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_3_NEON: +#define COPY_DATA_SZ 3 +#define LOOP_NAME TransformVertices_Strided_XYZNT_3_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_4_NEON: +#define COPY_DATA_SZ 4 +#define LOOP_NAME TransformVertices_Strided_XYZNT_4_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_5_NEON: +#define COPY_DATA_SZ 5 +#define LOOP_NAME TransformVertices_Strided_XYZNT_5_Loop +#include "TransformVertexNEON_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + + +#undef LOOP_XYZ +#undef LOOP_XYZN +#undef LOOP_XYZNT +#undef LOOP_SPRITE + +#define LOOP_XYZ 0 +#define LOOP_XYZN 0 +#define LOOP_XYZNT 0 +#define LOOP_SPRITE 1 + +_s_TransformVertices_Sprite_NEON: +#define LOOP_NAME TransformVertices_Sprite_Loop +#include "TransformVertexNEON_Loop.h" +#undef LOOP_NAME + +#undef LOOP_XYZ +#undef LOOP_XYZN +#undef LOOP_XYZNT +#undef LOOP_SPRITE + +#undef STRIDED_INPUT + +.endif + +#endif
\ No newline at end of file diff --git a/Runtime/Filters/Mesh/TransformVertexNEON_Loop.h b/Runtime/Filters/Mesh/TransformVertexNEON_Loop.h new file mode 100644 index 0000000..d84a516 --- /dev/null +++ b/Runtime/Filters/Mesh/TransformVertexNEON_Loop.h @@ -0,0 +1,254 @@ +// TODO: SOA + +// defines +// LOOP_XYZ +// LOOP_XYZN +// LOOP_XYZNT +// LOOP_NAME +// COPY_DATA_SZ +// STRIDED_INPUT + +#if STRIDED_INPUT + +//r0: const void* srcData +//r1: const void* srcDataEnd +//r2: const void* addData +//r3: const void* xform +//[sp+0]: void* dstData +//[sp+4]: const int stride + +mov ip, sp + +vpush {d0-d15} +stmfd sp!, {r4-r11} + +vldmia r3!, {q12-q15} + +// r3:dstData +// r4: stride +// r6: proper offset for out ptr (pos, normal) + +mov r6, #12 + +ldr r3, [ip, #0] +ldr r4, [ip, #4] + +// overlap calculation + +vmov.32 q0, q15 // pos.w (1.0) + + +#if LOOP_XYZ + +.align 4 +LOOP_NAME: + +pld [r0, #512] // prefetch + +vld1.32 {d6,d7}, [r0], r4 // load pos + +vmla.f32 q0, q12, d6[0] // pos.x +vmul.f32 q1, q13, d6[1] // pos.y +vmul.f32 q2, q14, d7[0] // pos.z + +vadd.f32 q0, q0, q1 + // load additional data +#if COPY_DATA_SZ == 1 +vld1.32 {d9}, [r2], r4 +#elif COPY_DATA_SZ == 2 +vld1.32 {d9}, [r2], r4 +#elif COPY_DATA_SZ == 3 +vld1.32 {d9,d10}, [r2], r4 +#elif COPY_DATA_SZ == 4 +vld1.32 {d9,d10}, [r2], r4 +#elif COPY_DATA_SZ == 5 +vld1.32 {d9,d10,d11}, [r2], r4 +#endif + +vadd.f32 q0, q0, q2 +cmp r0, r1 // check cycle + +vst1.32 {d0,d1}, [r3], r6 + +vmov.32 q0, q15 // pos.w (1.0) + // save additional data +#if COPY_DATA_SZ == 1 +vst1.32 {d9[0]}, [r3]! +#elif COPY_DATA_SZ == 2 +vst1.32 {d9}, [r3]! +#elif COPY_DATA_SZ == 3 +vst1.32 {d9}, [r3]! +vst1.32 {d10[0]}, [r3]! +#elif COPY_DATA_SZ == 4 +vst1.32 {d9,d10}, [r3]! +#elif COPY_DATA_SZ == 5 +vst1.32 {d9,d10}, [r3]! +vst1.32 {d11[0]}, [r3]! +#endif + +bcc LOOP_NAME + + +#elif LOOP_XYZN + + +.align 4 +LOOP_NAME: + +pld [r0, #512] // prefetch + +vld1.32 {d4,d5,d6}, [r0], r4 // load pos + normal + +vmla.f32 q0, q12, d4[0] // pos.x +vmul.f32 q1, q12, d5[1] // normal.x + + // load additional data +#if COPY_DATA_SZ == 1 +vld1.32 {d9}, [r2], r4 +#elif COPY_DATA_SZ == 2 +vld1.32 {d9}, [r2], r4 +#elif COPY_DATA_SZ == 3 +vld1.32 {d9,d10}, [r2], r4 +#elif COPY_DATA_SZ == 4 +vld1.32 {d9,d10}, [r2], r4 +#elif COPY_DATA_SZ == 5 +vld1.32 {d9,d10,d11}, [r2], r4 +#endif + +vmla.f32 q0, q13, d4[1] // pos.y +vmla.f32 q1, q13, d6[0] // normal.y + +vmla.f32 q0, q14, d5[0] // pos.z +vmla.f32 q1, q14, d6[1] // normal.z + +vst1.32 {d0,d1}, [r3], r6 + +cmp r0, r1 // check cycle +vmov.32 q0, q15 // pos.w (1.0) +vst1.32 {d2,d3}, [r3], r6 + // save additional data +#if COPY_DATA_SZ == 1 +vst1.32 {d9[0]}, [r3]! +#elif COPY_DATA_SZ == 2 +vst1.32 {d9}, [r3]! +#elif COPY_DATA_SZ == 3 +vst1.32 {d9}, [r3]! +vst1.32 {d10[0]}, [r3]! +#elif COPY_DATA_SZ == 4 +vst1.32 {d9,d10}, [r3]! +#elif COPY_DATA_SZ == 5 +vst1.32 {d9,d10}, [r3]! +vst1.32 {d11[0]}, [r3]! +#endif + + +bcc LOOP_NAME + + +#elif LOOP_XYZNT + +//[sp+8]: const void* tangent +//r8: tangent + +ldr r8, [ip, #8] + +mov r9, #12 +mov r10, #4 + +.align 4 +LOOP_NAME: + +pld [r0, #512] // prefetch + +vld1.32 {d4,d5,d6}, [r0], r4 // load pos + normal +vld1.32 {d7,d8}, [r8], r4 // load tangent + +vmla.f32 q0, q12, d4[0] // pos.x +vmul.f32 q1, q12, d5[1] // normal.x +vmul.f32 q11, q12, d7[0] // tangent.x + + // load additional data +#if COPY_DATA_SZ == 1 +vld1.32 {d9}, [r2], r4 +#elif COPY_DATA_SZ == 2 +vld1.32 {d9}, [r2], r4 +#elif COPY_DATA_SZ == 3 +vld1.32 {d9,d10}, [r2], r4 +#elif COPY_DATA_SZ == 4 +vld1.32 {d9,d10}, [r2], r4 +#elif COPY_DATA_SZ == 5 +vld1.32 {d9,d10,d11}, [r2], r4 +#endif + +vmla.f32 q0, q13, d4[1] // pos.y +vmla.f32 q1, q13, d6[0] // normal.y +vmla.f32 q11, q13, d7[1] // tangent.y + +vmla.f32 q0, q14, d5[0] // pos.z +vmla.f32 q1, q14, d6[1] // normal.z +vmla.f32 q11, q14, d8[0] // tangent.z + +vst1.32 {d0,d1}, [r3], r6 + +cmp r0, r1 // check cycle +vmov.32 q0, q15 // pos.w (1.0) +vst1.32 {d2,d3}, [r3], r6 + // save additional data +#if COPY_DATA_SZ == 1 +vst1.32 {d9[0]}, [r3]! +#elif COPY_DATA_SZ == 2 +vst1.32 {d9}, [r3]! +#elif COPY_DATA_SZ == 3 +vst1.32 {d9}, [r3]! +vst1.32 {d10[0]}, [r3]! +#elif COPY_DATA_SZ == 4 +vst1.32 {d9,d10}, [r3]! +#elif COPY_DATA_SZ == 5 +vst1.32 {d9,d10}, [r3]! +vst1.32 {d11[0]}, [r3]! +#endif + + +// TODO: less stupid way + +vtrn.32 d8, d7 +vst1.32 {d22,d23}, [r3], r9 +vst1.32 {d7[0]}, [r3], r10 + +bcc LOOP_NAME +#elif LOOP_SPRITE +.align 4 +ldr r7, [ip, #8] // load color32 +vmov.32 d10[0], r7 +LOOP_NAME: + +pld [r0, #512] // prefetch + +vld1.32 {d6,d7}, [r0], r4 // load pos + +vmla.f32 q0, q12, d6[0] // pos.x +vmul.f32 q1, q13, d6[1] // pos.y +vmul.f32 q2, q14, d7[0] // pos.z +vadd.f32 q0, q0, q1 +// load data +vld1.32 {d9}, [r2], r4 + +vadd.f32 q0, q0, q2 +cmp r0, r1 // check cycle + +vst1.32 {d0,d1}, [r3], r6 + +vmov.32 q0, q15 // pos.w (1.0) +// save data +vst1.32 {d10[0]}, [r3]! +vst1.32 {d9}, [r3]! + + +bcc LOOP_NAME +#endif + +ldmfd sp!, {r4-r11} +vpop {d0-d15} +bx lr + +#endif diff --git a/Runtime/Filters/Mesh/TransformVertexVFP.s b/Runtime/Filters/Mesh/TransformVertexVFP.s new file mode 100644 index 0000000..114afc6 --- /dev/null +++ b/Runtime/Filters/Mesh/TransformVertexVFP.s @@ -0,0 +1,250 @@ +#define UNITY_ASSEMBLER +#include "Configuration/PrefixConfigure.h" +#include "Runtime/Utilities/VFPUtility.h" + +#if UNITY_SUPPORTS_VFP + +.syntax unified + +.set device,0 +.set device,__arm__ + +.if device + +//.code32 + + +.globl _s_TransformVertices_Strided_XYZ_0_VFP +.globl _s_TransformVertices_Strided_XYZ_1_VFP +.globl _s_TransformVertices_Strided_XYZ_2_VFP +.globl _s_TransformVertices_Strided_XYZ_3_VFP +.globl _s_TransformVertices_Strided_XYZ_4_VFP +.globl _s_TransformVertices_Strided_XYZ_5_VFP + +.globl _s_TransformVertices_Strided_XYZN_0_VFP +.globl _s_TransformVertices_Strided_XYZN_1_VFP +.globl _s_TransformVertices_Strided_XYZN_2_VFP +.globl _s_TransformVertices_Strided_XYZN_3_VFP +.globl _s_TransformVertices_Strided_XYZN_4_VFP +.globl _s_TransformVertices_Strided_XYZN_5_VFP + +.globl _s_TransformVertices_Strided_XYZNT_0_VFP +.globl _s_TransformVertices_Strided_XYZNT_1_VFP +.globl _s_TransformVertices_Strided_XYZNT_2_VFP +.globl _s_TransformVertices_Strided_XYZNT_3_VFP +.globl _s_TransformVertices_Strided_XYZNT_4_VFP +.globl _s_TransformVertices_Strided_XYZNT_5_VFP + +.globl _s_TransformVertices_Sprite_VFP + + +#if UNITY_ANDROID +.hidden _s_TransformVertices_Strided_XYZ_0_VFP +.hidden _s_TransformVertices_Strided_XYZ_1_VFP +.hidden _s_TransformVertices_Strided_XYZ_2_VFP +.hidden _s_TransformVertices_Strided_XYZ_3_VFP +.hidden _s_TransformVertices_Strided_XYZ_4_VFP +.hidden _s_TransformVertices_Strided_XYZ_5_VFP + +.hidden _s_TransformVertices_Strided_XYZN_0_VFP +.hidden _s_TransformVertices_Strided_XYZN_1_VFP +.hidden _s_TransformVertices_Strided_XYZN_2_VFP +.hidden _s_TransformVertices_Strided_XYZN_3_VFP +.hidden _s_TransformVertices_Strided_XYZN_4_VFP +.hidden _s_TransformVertices_Strided_XYZN_5_VFP + +.hidden _s_TransformVertices_Strided_XYZNT_0_VFP +.hidden _s_TransformVertices_Strided_XYZNT_1_VFP +.hidden _s_TransformVertices_Strided_XYZNT_2_VFP +.hidden _s_TransformVertices_Strided_XYZNT_3_VFP +.hidden _s_TransformVertices_Strided_XYZNT_4_VFP +.hidden _s_TransformVertices_Strided_XYZNT_5_VFP + +.hidden _s_TransformVertices_Sprite_VFP +#endif + +#define STRIDED_INPUT 1 + + +#define LOOP_XYZ 1 +#define LOOP_XYZN 0 +#define LOOP_XYZNT 0 +#define LOOP_SPRITE 0 + +_s_TransformVertices_Strided_XYZ_0_VFP: +#define COPY_DATA_SZ 0 +#define LOOP_NAME TransformVertices_Strided_XYZ_0_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_1_VFP: +#define COPY_DATA_SZ 1 +#define LOOP_NAME TransformVertices_Strided_XYZ_1_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_2_VFP: +#define COPY_DATA_SZ 2 +#define LOOP_NAME TransformVertices_Strided_XYZ_2_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_3_VFP: +#define COPY_DATA_SZ 3 +#define LOOP_NAME TransformVertices_Strided_XYZ_3_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_4_VFP: +#define COPY_DATA_SZ 4 +#define LOOP_NAME TransformVertices_Strided_XYZ_4_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZ_5_VFP: +#define COPY_DATA_SZ 5 +#define LOOP_NAME TransformVertices_Strided_XYZ_5_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + + +#undef LOOP_XYZ +#undef LOOP_XYZN +#undef LOOP_XYZNT +#undef LOOP_SPRITE + + +#define LOOP_XYZ 0 +#define LOOP_XYZN 1 +#define LOOP_XYZNT 0 +#define LOOP_SPRITE 0 + + +_s_TransformVertices_Strided_XYZN_0_VFP: +#define COPY_DATA_SZ 0 +#define LOOP_NAME TransformVertices_Strided_XYZN_0_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_1_VFP: +#define COPY_DATA_SZ 1 +#define LOOP_NAME TransformVertices_Strided_XYZN_1_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_2_VFP: +#define COPY_DATA_SZ 2 +#define LOOP_NAME TransformVertices_Strided_XYZN_2_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_3_VFP: +#define COPY_DATA_SZ 3 +#define LOOP_NAME TransformVertices_Strided_XYZN_3_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_4_VFP: +#define COPY_DATA_SZ 4 +#define LOOP_NAME TransformVertices_Strided_XYZN_4_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZN_5_VFP: +#define COPY_DATA_SZ 5 +#define LOOP_NAME TransformVertices_Strided_XYZN_5_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + + +#undef LOOP_XYZ +#undef LOOP_XYZN +#undef LOOP_XYZNT +#undef LOOP_SPRITE + + +#define LOOP_XYZ 0 +#define LOOP_XYZN 0 +#define LOOP_XYZNT 1 +#define LOOP_SPRITE 0 + + +_s_TransformVertices_Strided_XYZNT_0_VFP: +#define COPY_DATA_SZ 0 +#define LOOP_NAME TransformVertices_Strided_XYZNT_0_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_1_VFP: +#define COPY_DATA_SZ 1 +#define LOOP_NAME TransformVertices_Strided_XYZNT_1_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_2_VFP: +#define COPY_DATA_SZ 2 +#define LOOP_NAME TransformVertices_Strided_XYZNT_2_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_3_VFP: +#define COPY_DATA_SZ 3 +#define LOOP_NAME TransformVertices_Strided_XYZNT_3_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_4_VFP: +#define COPY_DATA_SZ 4 +#define LOOP_NAME TransformVertices_Strided_XYZNT_4_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +_s_TransformVertices_Strided_XYZNT_5_VFP: +#define COPY_DATA_SZ 5 +#define LOOP_NAME TransformVertices_Strided_XYZNT_5_Loop +#include "TransformVertexVFP_Loop.h" +#undef COPY_DATA_SZ +#undef LOOP_NAME + +#undef LOOP_XYZ +#undef LOOP_XYZN +#undef LOOP_XYZNT +#undef LOOP_SPRITE + +#define LOOP_XYZ 0 +#define LOOP_XYZN 0 +#define LOOP_XYZNT 0 +#define LOOP_SPRITE 1 + +_s_TransformVertices_Sprite_VFP: +#define LOOP_NAME TransformVerties_Sprite_Loop +#include "TransformVertexVFP_Loop.h" +#undef LOOP_NAME + +#undef LOOP_XYZ +#undef LOOP_XYZN +#undef LOOP_XYZNT +#undef LOOP_SPRITE + +#undef STRIDED_INPUT + +.endif + +#endif diff --git a/Runtime/Filters/Mesh/TransformVertexVFP_Loop.h b/Runtime/Filters/Mesh/TransformVertexVFP_Loop.h new file mode 100644 index 0000000..48193c8 --- /dev/null +++ b/Runtime/Filters/Mesh/TransformVertexVFP_Loop.h @@ -0,0 +1,252 @@ +// defines +// LOOP_XYZ +// LOOP_XYZN +// LOOP_XYZNT +// LOOP_SPRITE +// LOOP_NAME +// COPY_DATA_SZ +// STRIDED_INPUT + +#if STRIDED_INPUT + +//r0: const void* srcData +//r1: const void* srcDataEnd +//r2: const void* addData +//r3: const void* xform +//[sp+0]: void* dstData +//[sp+4]: const int stride +//[sp+8]: const void* tangent + +mov ip, sp + +vpush {d0-d15} +stmfd sp!, {r4-r11} + +// {s16-s31} xform + +vldmia.32 r3!, {s16-s31} + +// r3: dstData +// r4: stride +//r11: tangent +ldr r3, [ip, #0] +ldr r4, [ip, #4] + +#if LOOP_XYZNT +ldr r11, [ip, #8] +#endif + +#if LOOP_SPRITE +//r6: color +ldr r6, [ip, #8] +#endif + + +mov ip, r0 +// VFP_VECTOR_LENGTH(3) +mov r0, ip + + +#if LOOP_XYZ + +.align 4 +LOOP_NAME: + +mov r5, r0 +pld [r0, #512] // prefetch + +vldmia.32 r5!, {s0-s2} // load pos +FCPYS4 (8,9,10,11, 28,29,30,31) // pos.w + +FMACS4 (8,9,10,11, 16,17,18,19, 0,0,0,0) // pos.x +#if COPY_DATA_SZ == 1 +ldmia r2, {r6} // load additional data +#elif COPY_DATA_SZ == 2 +ldmia r2, {r6-r7} // load additional data +#elif COPY_DATA_SZ == 3 +ldmia r2, {r6-r8} // load additional data +#elif COPY_DATA_SZ == 4 +ldmia r2, {r6-r9} // load additional data +#elif COPY_DATA_SZ == 5 +ldmia r2, {r6-r10} // load additional data +#endif + +FMACS4 (8,9,10,11, 20,21,22,23, 1,1,1,1) // pos.y +add r0, r0, r4 // inc srcData + +FMACS4 (8,9,10,11, 24,25,26,27, 2,2,2,2) // pos.z +add r2, r2, r4 // inc srcAddData + +vstmia.32 r3!, {s8-s10} // store pos +cmp r0, r1 // check cycle + +#if COPY_DATA_SZ == 1 +stmia r3!, {r6} // save additional data +#elif COPY_DATA_SZ == 2 +stmia r3!, {r6-r7} // save additional data +#elif COPY_DATA_SZ == 3 +stmia r3!, {r6-r8} // save additional data +#elif COPY_DATA_SZ == 4 +stmia r3!, {r6-r9} // save additional data +#elif COPY_DATA_SZ == 5 +stmia r3!, {r6-r10} // save additional data +#endif + +bcc LOOP_NAME + + +#elif LOOP_XYZN + +.align 4 +LOOP_NAME: + +mov r5, r0 +pld [r0, #512] // prefetch + +vldmia.32 r5!, {s0-s2} // load pos +FCPYS4 (8,9,10,11, 28,29,30,31) // pos.w + +vldmia.32 r5!, {s3-s5} // load normal +FMACS4 (8,9,10,11, 16,17,18,19, 0,0,0,0) // pos.x + +FMULS4 (12,13,14,15, 16,17,18,19, 3,3,3,3) // normal.x +FMACS4 (8,9,10,11, 20,21,22,23, 1,1,1,1) // pos.y + +#if COPY_DATA_SZ == 1 +ldmia r2, {r6} // load additional data +#elif COPY_DATA_SZ == 2 +ldmia r2, {r6-r7} // load additional data +#elif COPY_DATA_SZ == 3 +ldmia r2, {r6-r8} // load additional data +#elif COPY_DATA_SZ == 4 +ldmia r2, {r6-r9} // load additional data +#elif COPY_DATA_SZ == 5 +ldmia r2, {r6-r10} // load additional data +#endif +FMACS4 (8,9,10,11, 24,25,26,27, 2,2,2,2) // pos.z + +FMACS4 (12,13,14,15, 20,21,22,23, 4,4,4,4) // normal.y +vstmia.32 r3!, {s8-s10} // store pos + +FMACS4 (12,13,14,15, 24,25,26,27, 5,5,5,5) // normal.z +add r0, r0, r4 // inc srcData + +vstmia.32 r3!, {s12-s14} // store normal +add r2, r2, r4 // inc srcAddData + +cmp r0, r1 // check cycle +#if COPY_DATA_SZ == 1 +stmia r3!, {r6} // save additional data +#elif COPY_DATA_SZ == 2 +stmia r3!, {r6-r7} // save additional data +#elif COPY_DATA_SZ == 3 +stmia r3!, {r6-r8} // save additional data +#elif COPY_DATA_SZ == 4 +stmia r3!, {r6-r9} // save additional data +#elif COPY_DATA_SZ == 5 +stmia r3!, {r6-r10} // save additional data +#endif + +bcc LOOP_NAME + +#elif LOOP_XYZNT + +.align 4 +LOOP_NAME: + +mov r5, r0 +pld [r0, #512] // prefetch + +vldmia.32 r5!, {s0-s2} // load pos +FCPYS4 (8,9,10,11, 28,29,30,31) // pos.w + +vldmia.32 r5!, {s3-s5} // load normal +FMACS4 (8,9,10,11, 16,17,18,19, 0,0,0,0) // pos.x + +FMULS4 (12,13,14,15, 16,17,18,19, 3,3,3,3) // normal.x +FMACS4 (8,9,10,11, 20,21,22,23, 1,1,1,1) // pos.y + +#if COPY_DATA_SZ == 1 +ldmia r2, {r6} // load additional data +#elif COPY_DATA_SZ == 2 +ldmia r2, {r6-r7} // load additional data +#elif COPY_DATA_SZ == 3 +ldmia r2, {r6-r8} // load additional data +#elif COPY_DATA_SZ == 4 +ldmia r2, {r6-r9} // load additional data +#elif COPY_DATA_SZ == 5 +ldmia r2, {r6-r10} // load additional data +#endif +FMACS4 (8,9,10,11, 24,25,26,27, 2,2,2,2) // pos.z + +FMACS4 (12,13,14,15, 20,21,22,23, 4,4,4,4) // normal.y +vstmia.32 r3!, {s8-s10} // store pos + +FMACS4 (12,13,14,15, 24,25,26,27, 5,5,5,5) // normal.z +vldmia.32 r11, {s0-s3} // load tangent + +add r0, r0, r4 // inc srcData +FMULS4 (8,9,10,11, 16,17,18,19, 0,0,0,0) // tangent.x + +vstmia.32 r3!, {s12-s14} // store normal +FMACS4 (8,9,10,11, 20,21,22,23, 1,1,1,1) // tangent.y + +cmp r0, r1 // check cycle +FMACS4 (8,9,10,11, 24,25,26,27, 2,2,2,2) // tangent.z + +#if COPY_DATA_SZ == 1 +stmia r3!, {r6} // save additional data +#elif COPY_DATA_SZ == 2 +stmia r3!, {r6-r7} // save additional data +#elif COPY_DATA_SZ == 3 +stmia r3!, {r6-r8} // save additional data +#elif COPY_DATA_SZ == 4 +stmia r3!, {r6-r9} // save additional data +#elif COPY_DATA_SZ == 5 +stmia r3!, {r6-r10} // save additional data +#endif +fcpys s11, s3 // copy tangent.w + +vstmia.32 r3!, {s8-s11} // store tangent +add r2, r2, r4 // inc srcAddData + +add r11, r11, r4 // inc srcTangent +bcc LOOP_NAME + +#elif LOOP_SPRITE + +.align 4 +LOOP_NAME: + +mov r5, r0 +pld [r0, #512] // prefetch + +vldmia.32 r5!, {s0-s2} // load pos +FCPYS4 (8,9,10,11, 28,29,30,31) // pos.w + +FMACS4 (8,9,10,11, 16,17,18,19, 0,0,0,0) // pos.x + + +ldmia r2, {r7-r8} // load uv + +FMACS4 (8,9,10,11, 20,21,22,23, 1,1,1,1) // pos.y +add r0, r0, r4 // inc srcData + +FMACS4 (8,9,10,11, 24,25,26,27, 2,2,2,2) // pos.z +add r2, r2, r4 // inc srcAddData + +vstmia.32 r3!, {s8-s10} // store pos +cmp r0, r1 // check cycle + +stmia r3!, {r6-r8} // save color and uv + +bcc LOOP_NAME +#endif + +// VFP_VECTOR_LENGTH_ZERO + +ldmfd sp!, {r4-r11} +vpop {d0-d15} +bx lr + +#endif // STRIDED_INPUT diff --git a/Runtime/Filters/Mesh/VertexData.cpp b/Runtime/Filters/Mesh/VertexData.cpp new file mode 100644 index 0000000..b922805 --- /dev/null +++ b/Runtime/Filters/Mesh/VertexData.cpp @@ -0,0 +1,559 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" +#include "VertexData.h" +#include "Runtime/Shaders/VBO.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Serialize/TransferUtility.h" +#include "Runtime/Serialize/SwapEndianArray.h" +#include <algorithm> + +/* + On most platforms, for skinning/non-uniform-scaling of meshes you would want to split your data into + a hot data stream (position, normal and tangent) and a cold data stream (diffuse and uvs) in order to maximize CPU cache access patterns and + reduce bandwidth and computation ( you won't need to copy the cold data ) +*/ + +VertexStreamsLayout VertexDataInfo::kVertexStreamsDefault = {{ kShaderChannelsAll, 0, 0, 0 }}; +#if UNITY_PS3 + VertexStreamsLayout VertexDataInfo::kVertexStreamsSkinnedHotColdSplit = {{ VERTEX_FORMAT1(Vertex), VERTEX_FORMAT1(Normal), VERTEX_FORMAT1(Tangent), kShaderChannelsCold }}; +#else + VertexStreamsLayout VertexDataInfo::kVertexStreamsSkinnedHotColdSplit = {{ kShaderChannelsHot, kShaderChannelsCold, 0, 0 }}; +# if UNITY_EDITOR + VertexStreamsLayout VertexDataInfo::kVertexStreamsSkinnedHotColdSplitPS3 = {{ VERTEX_FORMAT1(Vertex), VERTEX_FORMAT1(Normal), VERTEX_FORMAT1(Tangent), kShaderChannelsCold }}; +# endif +#endif + +#define MAKE_CHANNEL(fmt, dim) VertexChannelsLayout::Channel(kChannelFormat##fmt, dim) +VertexChannelsLayout VertexDataInfo::kVertexChannelsDefault = +{{ // Array wrapped by struct requires double braces + MAKE_CHANNEL(Float, 3), // position + MAKE_CHANNEL(Float, 3), // normal + MAKE_CHANNEL(Color, 1), // color + MAKE_CHANNEL(Float, 2), // texcoord0 + MAKE_CHANNEL(Float, 2), // texcoord1 + MAKE_CHANNEL(Float, 4) // tangent +}}; +VertexChannelsLayout VertexDataInfo::kVertexChannelsCompressed = +{{ // Array wrapped by struct requires double braces + MAKE_CHANNEL(Float, 3), // position + MAKE_CHANNEL(Float16, 4), // normal + MAKE_CHANNEL(Color, 1), // color + MAKE_CHANNEL(Float16, 2), // texcoord0 + MAKE_CHANNEL(Float16, 2), // texcoord1 + MAKE_CHANNEL(Float16, 4) // tangent +}}; +VertexChannelsLayout VertexDataInfo::kVertexChannelsCompressedAggressive = +{{ // Array wrapped by struct requires double braces + MAKE_CHANNEL(Float, 3), // position + MAKE_CHANNEL(Byte, 4), // normal + MAKE_CHANNEL(Color, 1), // color + MAKE_CHANNEL(Float16, 2), // texcoord0 + MAKE_CHANNEL(Float16, 2), // texcoord1 + MAKE_CHANNEL(Byte, 4) // tangent +}}; +#undef MAKE_CHANNEL + +static const UInt8 kVertexChannelFormatSizes[kChannelFormatCount] = { + 4, // kChannelFormatFloat + 2, // kChannelFormatFloat16 + 4, // kChannelFormatColor + 1 // kChannelFormatByte +}; + +size_t GetChannelFormatSize(UInt8 format) +{ + Assert (format < kChannelFormatCount); + return kVertexChannelFormatSizes[format]; +} + +static bool operator == (const VertexStreamsLayout& lhs, const VertexStreamsLayout& rhs) +{ + return CompareArrays(lhs.channelMasks, rhs.channelMasks, kMaxVertexStreams); +} + +template<class TransferFunction> +void VertexData::Transfer (TransferFunction& transfer) +{ + #if SUPPORT_SERIALIZED_TYPETREES + if (transfer.GetFlags() & kWorkaround35MeshSerializationFuckup) + { + TransferWorkaround35SerializationFuckup (transfer); + return; + } + #endif + + transfer.Transfer (m_CurrentChannels, "m_CurrentChannels", kHideInEditorMask); + transfer.Transfer (m_VertexCount, "m_VertexCount", kHideInEditorMask); + + dynamic_array<ChannelInfo> channels; + dynamic_array<StreamInfo> streams; + if (transfer.IsWriting ()) + { + channels.resize_uninitialized (kShaderChannelCount); + streams.resize_uninitialized (kMaxVertexStreams); + std::copy (m_Channels, m_Channels + kShaderChannelCount, channels.begin ()); + std::copy (m_Streams, m_Streams + kMaxVertexStreams, streams.begin ()); + } + transfer.Transfer (channels, "m_Channels", kHideInEditorMask); + transfer.Transfer (streams, "m_Streams", kHideInEditorMask); + + if (transfer.IsReading ()) + { + // For compatibility do this even if channels/streams info didn't exist (case 558604) + // In the past there was only a channels mask, UpdateStreams() generates the info from that + if (channels.size () == kShaderChannelCount) + std::copy (channels.begin (), channels.begin () + kShaderChannelCount, m_Channels); + if (streams.size () == kMaxVertexStreams) + std::copy (streams.begin (), streams.begin () + kMaxVertexStreams, m_Streams); + else + std::fill (m_Streams, m_Streams + kMaxVertexStreams, StreamInfo()); + + UInt32 channelsInStreams = 0; + for (int i = 0; i < kMaxVertexStreams ; i++) + channelsInStreams |= m_Streams[i].channelMask; + if (channelsInStreams) + UpdateStreams(channelsInStreams, m_VertexCount, GetStreamsLayout (), GetChannelsLayout ()); + else + UpdateStreams(m_CurrentChannels, m_VertexCount, kVertexStreamsDefault, kVertexChannelsDefault); + } + + transfer.TransferTypeless (&m_DataSize, "m_DataSize", kHideInEditorMask); + if (transfer.DidReadLastProperty ()) + { + if (m_Data) + UNITY_FREE (kMemVertexData, m_Data); + m_Data = (UInt8*)UNITY_MALLOC_ALIGNED (kMemVertexData, VertexData::GetAllocateDataSize (m_DataSize), kVertexDataAlign); + } + + transfer.TransferTypelessData (m_DataSize, m_Data); +} + +#if SUPPORT_SERIALIZED_TYPETREES +template<class TransferFunction> +void VertexData::TransferWorkaround35SerializationFuckup (TransferFunction& transfer) +{ + UInt32 currentChannels = m_CurrentChannels; + transfer.Transfer (currentChannels, "m_CurrentChannels", kHideInEditorMask); + transfer.Transfer (m_VertexCount, "m_VertexCount", kHideInEditorMask); + + TRANSFER(m_Streams[0]); + TRANSFER(m_Streams[1]); + TRANSFER(m_Streams[2]); + TRANSFER(m_Streams[3]); + + if (transfer.IsReading ()) + { + if(m_VertexCount && (currentChannels == 0)) + { + for(int i=0;i<kMaxVertexStreams;i++) + currentChannels |= m_Streams[i].channelMask; + } + UpdateStreams(currentChannels, m_VertexCount); + //GetComponentInfo(m_Components, currentChannels); + m_CurrentChannels = currentChannels; + } + + transfer.TransferTypeless (&m_DataSize, "m_DataSize", kHideInEditorMask); + + if (transfer.IsReading ()) + { + if (m_Data) + UNITY_FREE (kMemVertexData, m_Data); + m_Data = (UInt8*)UNITY_MALLOC_ALIGNED (kMemVertexData, VertexData::GetAllocateDataSize (m_DataSize), kVertexDataAlign); + } + + transfer.TransferTypelessData (m_DataSize, m_Data); +} +#endif + +INSTANTIATE_TEMPLATE_TRANSFER(VertexData) + +void VertexDataInfo::UpdateStreams(unsigned newChannelMask, size_t newVertexCount, const VertexStreamsLayout& streams, const VertexChannelsLayout& channels) +{ + m_VertexCount = newVertexCount; + m_CurrentChannels = 0; + m_VertexSize = 0; + size_t streamOffset = 0; + for (int s = 0; s < kMaxVertexStreams; s++) + { + StreamInfo& stream = m_Streams[s]; + m_Streams[s].Reset(); + stream.channelMask = streams.channelMasks[s] & newChannelMask; + if (stream.channelMask == 0) + continue; + m_CurrentChannels |= stream.channelMask; + for (int c = 0; c < kShaderChannelCount; c++) + { + if (stream.channelMask & (1 << c)) + { + ChannelInfo& channel = m_Channels[c]; + const VertexChannelsLayout::Channel& srcChannel = channels.channels[c]; + channel.stream = s; + channel.offset = stream.stride; + channel.format = srcChannel.format; + channel.dimension = srcChannel.dimension; + stream.stride += channel.dimension * GetChannelFormatSize(channel.format); + } + } + streamOffset = AlignStreamSize(streamOffset); + stream.offset = streamOffset; + streamOffset += stream.stride * newVertexCount; + m_VertexSize += stream.stride; + } + for (int c = 0; c < kShaderChannelCount; c++) + { + // Reset channels that were removed + if (!(m_CurrentChannels & (1 << c))) + m_Channels[c].Reset(); + } + m_DataSize = streamOffset; +} + +size_t VertexDataInfo::GetActiveStreamCount() const +{ + size_t activeStreamCount = 0; + for (int i=0; i<kMaxVertexStreams; i++) + { + if(m_Streams[i].channelMask != 0) + activeStreamCount++; + } + return activeStreamCount; +} + +size_t VertexDataInfo::GetStreamIndex(ShaderChannel channel) const +{ + UInt32 channelMask = 1 << channel; + for (int i=0; i<kMaxVertexStreams; i++) + { + if(m_Streams[i].channelMask & channelMask) + return i; + } + return -1; +} + +VertexStreamsLayout VertexDataInfo::GetStreamsLayout() const +{ + VertexStreamsLayout result; + for (int i = 0; i < kMaxVertexStreams; i++) + result.channelMasks[i] = m_Streams[i].channelMask; + return result; +} + +VertexChannelsLayout VertexDataInfo::GetChannelsLayout() const +{ + VertexChannelsLayout result; + for (int i = 0; i < kShaderChannelCount; i++) + { + result.channels[i] = VertexChannelsLayout::Channel(m_Channels[i].format, m_Channels[i].dimension); + } + return result; +} + +bool VertexDataInfo::ConformsToStreamsLayout(const VertexStreamsLayout& streams) const +{ + for (int i = 0; i < kMaxVertexStreams; i++) + { + // Fail if we have a channel that's not in the layout + if (m_Streams[i].channelMask & ~streams.channelMasks[i]) + return false; + } + return true; +} + +bool VertexDataInfo::ConformsToChannelsLayout(const VertexChannelsLayout& channels) const +{ + for (int i = 0; i < kShaderChannelCount; i++) + { + if (m_Channels[i].IsValid()) + { + const VertexChannelsLayout::Channel& channel = channels.channels[i]; + if (m_Channels[i].format != channel.format || + m_Channels[i].dimension != channel.dimension) + return false; + } + } + return true; +} + +signed char f32_to_s8(float fval) +{ + return ((fval * 255.0f) - 1.0f) / 2.0f; +} + +float s8_to_f32(signed char val) +{ + return (2*(val/255.0f)-1.0f); +} + +static void ConvertCopyChannel(size_t vertexCount, + const UInt8* srcPtr, UInt8 srcStride, UInt8 srcType, UInt8 srcDim, + UInt8* dstPtr, UInt8 dstStride, UInt8 dstType, UInt8 dstDim) +{ + UInt8 minDim = std::min(srcDim, dstDim); + if (srcType == kChannelFormatFloat16 && dstType == kChannelFormatFloat) + { + // decompressing + for (size_t i = 0; i < vertexCount; i++) + { + UInt8 comp = 0; + for ( ; comp < minDim; comp++) + HalfToFloat(reinterpret_cast<const UInt16*>(srcPtr)[comp], reinterpret_cast<float*>(dstPtr)[comp]); + for ( ; comp < dstDim; comp++) + reinterpret_cast<float*>(dstPtr)[comp] = 0.0f; + srcPtr += srcStride; + dstPtr += dstStride; + } + } + else if (srcType == kChannelFormatByte && dstType == kChannelFormatFloat) + { + // decompressing + for (size_t i = 0; i < vertexCount; i++) + { + UInt8 comp = 0; + for ( ; comp < minDim; comp++) + reinterpret_cast<float*>(dstPtr)[comp] = s8_to_f32(reinterpret_cast<const SInt8*>(srcPtr)[comp]); + for ( ; comp < dstDim; comp++) + reinterpret_cast<float*>(dstPtr)[comp] = 0.0f; + srcPtr += srcStride; + dstPtr += dstStride; + } + } +#if UNITY_EDITOR + else if (srcType == kChannelFormatFloat && dstType == kChannelFormatFloat16) + { + // compressing + for (size_t i = 0; i < vertexCount; i++) + { + UInt8 comp = 0; + for ( ; comp < minDim; comp++) + g_FloatToHalf.Convert(reinterpret_cast<const float*>(srcPtr)[comp], reinterpret_cast<UInt16*>(dstPtr)[comp]); + for ( ; comp < dstDim; comp++) + reinterpret_cast<UInt16*>(dstPtr)[comp] = 0; + srcPtr += srcStride; + dstPtr += dstStride; + } + } + else if (srcType == kChannelFormatFloat && dstType == kChannelFormatByte) + { + // compressing + for (size_t i = 0; i < vertexCount; i++) + { + UInt8 comp = 0; + for ( ; comp < minDim; comp++) + reinterpret_cast<SInt8*>(dstPtr)[comp] = f32_to_s8(reinterpret_cast<const float*>(srcPtr)[comp]); + for ( ; comp < dstDim; comp++) + reinterpret_cast<SInt8*>(dstPtr)[comp] = 0; + srcPtr += srcStride; + dstPtr += dstStride; + } + } +#endif + else + ErrorString("Unsupported conversion of vertex formats"); +} + +static void CopyChannels (size_t vertexCount, unsigned copyChannels, + const StreamInfoArray srcStreams, const ChannelInfoArray srcChannels, const UInt8* srcData, + const StreamInfoArray dstStreams, const ChannelInfoArray dstChannels, UInt8* dstData) +{ + for (unsigned chan = copyChannels, i = 0; chan && (i < kShaderChannelCount); i++, chan >>= 1) + { + if (0 == (chan & 1)) + continue; + + const ChannelInfo& srcChannel = srcChannels[i]; + const ChannelInfo& dstChannel = dstChannels[i]; + + const UInt8* srcPtr = srcData + srcChannel.CalcOffset(srcStreams); + UInt8* dstPtr = dstData + dstChannel.CalcOffset(dstStreams); + UInt8 srcStride = srcChannel.CalcStride(srcStreams); + UInt8 dstStride = dstChannel.CalcStride(dstStreams); + + if(srcChannel.format == dstChannel.format) + { + size_t copySize = srcChannel.dimension * GetChannelFormatSize(srcChannel.format); + switch (copySize) + { + case 4: + { + for (size_t i=0; i<vertexCount; ++i) + { + *(reinterpret_cast<UInt32*> (dstPtr) + 0) = *(reinterpret_cast<const UInt32*> (srcPtr) + 0); + srcPtr += srcStride; + dstPtr += dstStride; + } + break; + } + case 8: + { + for (size_t i=0; i<vertexCount; ++i) + { + *(reinterpret_cast<UInt32*> (dstPtr) + 0) = *(reinterpret_cast<const UInt32*> (srcPtr) + 0); + *(reinterpret_cast<UInt32*> (dstPtr) + 1) = *(reinterpret_cast<const UInt32*> (srcPtr) + 1); + srcPtr += srcStride; + dstPtr += dstStride; + } + break; + } + case 12: + { + for (size_t i=0; i<vertexCount; ++i) + { + *(reinterpret_cast<UInt32*> (dstPtr) + 0) = *(reinterpret_cast<const UInt32*> (srcPtr) + 0); + *(reinterpret_cast<UInt32*> (dstPtr) + 1) = *(reinterpret_cast<const UInt32*> (srcPtr) + 1); + *(reinterpret_cast<UInt32*> (dstPtr) + 2) = *(reinterpret_cast<const UInt32*> (srcPtr) + 2); + srcPtr += srcStride; + dstPtr += dstStride; + } + break; + } + default: + { + for (size_t i=0; i<vertexCount; ++i) + { + memcpy (dstPtr, srcPtr, copySize); + srcPtr += srcStride; + dstPtr += dstStride; + } + break; + } + } + } + else + { + ConvertCopyChannel(vertexCount, srcPtr, srcStride, srcChannel.format, srcChannel.dimension, dstPtr, dstStride, dstChannel.format, dstChannel.dimension); + } + } +} + +VertexDataInfo::VertexDataInfo () +: m_Data(NULL) +, m_DataSize(0) +, m_VertexCount(0) +, m_VertexSize(0) +, m_CurrentChannels(0) +{ + // Channels and streams have default constructors +} + +VertexData::VertexData (VertexData const& src, unsigned copyChannels, const VertexStreamsLayout& streams, const VertexChannelsLayout& channels) +{ + // We do not support inserting new channels that are not present in the source + Assert ((copyChannels & src.GetChannelMask()) == copyChannels); + + UpdateStreams(copyChannels, src.m_VertexCount, streams, channels); + m_Data = (UInt8*) UNITY_MALLOC_ALIGNED (kMemVertexData, VertexData::GetAllocateDataSize (m_DataSize), kVertexDataAlign); + + const VertexData& dest = *this; + if (m_DataSize == src.m_DataSize && + copyChannels == src.GetChannelMask() && + CompareMemory(dest.m_Channels, src.m_Channels) && + CompareMemory(dest.m_Streams, src.m_Streams)) + { + // Simple copy if the format didn't change + memcpy (m_Data, src.m_Data, m_DataSize); + } + else + CopyChannels (m_VertexCount, copyChannels, src.m_Streams, src.m_Channels, src.m_Data, m_Streams, m_Channels, m_Data); +} + +VertexData::~VertexData () +{ + Deallocate(); +} + +void VertexData::Deallocate () +{ + if (m_Data) + UNITY_FREE(kMemVertexData, m_Data); + m_Data = NULL; +} + +void VertexData::Resize (size_t vertexCount, unsigned channelMask, const VertexStreamsLayout& streams, const VertexChannelsLayout& channels) +{ + ChannelInfoArray srcChannels; + StreamInfoArray srcStreams; + memcpy(srcChannels, m_Channels, sizeof(srcChannels)); + memcpy(srcStreams, m_Streams, sizeof(srcStreams)); + UInt32 srcChannelMask = m_CurrentChannels; + UInt32 srcVertexCount = m_VertexCount; + UInt8* srcData = m_Data; + + UpdateStreams(channelMask, vertexCount, streams, channels); + + // In case the streams and channels don't change, simply reallocate the buffer and return + // Note that this will rarely be true with multiple streams since the stream offsets change + if (m_Data && CompareMemory(srcChannels, m_Channels) && CompareMemory(srcStreams, m_Streams)) + { + m_Data = (UInt8*)UNITY_REALLOC_ALIGNED(kMemVertexData, m_Data, VertexData::GetAllocateDataSize(m_DataSize), kVertexDataAlign); + return; + } + + m_Data = (UInt8*)UNITY_MALLOC_ALIGNED(kMemVertexData, VertexData::GetAllocateDataSize(m_DataSize), kVertexDataAlign); + // copy over the old data + if (srcData) + { + unsigned copyChannels = srcChannelMask & m_CurrentChannels; + size_t toCopyCount = std::min<size_t>(srcVertexCount, m_VertexCount); + CopyChannels(toCopyCount, copyChannels, srcStreams, srcChannels, srcData, m_Streams, m_Channels, m_Data); + UNITY_FREE(kMemVertexData, srcData); + } +} + + +void VertexData::SwapEndianess () +{ + unsigned const kChannelSwapMask = VERTEX_FORMAT5(Vertex, Normal, TexCoord0, TexCoord1, Tangent); + for (int s = 0; s < kMaxVertexStreams; s++) + { + if (m_Streams[s].stride) + { + StreamInfo& stream = m_Streams[s]; + size_t stride = stream.stride; + UInt8* dataStart = m_Data + stream.offset; + UInt8* dataEnd = dataStart + stream.stride * m_VertexCount; + UInt32 channelMask = stream.channelMask; + for (UInt8* p = dataStart, *end = dataEnd; p != end; p += stride) + { + // counting from LSb, 1 denotes that a value should be endian-swapped + int localOffset = 0; + for (unsigned i=0, chan = channelMask, swap = kChannelSwapMask; i<kShaderChannelCount; ++i, chan >>= 1, swap >>= 1) + { + if (chan & 1) + { + size_t componentCount = m_Channels[i].dimension; + size_t componentSize = GetChannelFormatSize(m_Channels[i].format); + if(swap & 1) + { + Assert (m_Channels [i].IsValid()); + SwapEndianArray (p + localOffset, componentSize, componentCount); + } + localOffset += componentCount * componentSize; + } + } + } + } + } +} + +void swap (VertexData& a, VertexData& b) +{ + std::swap_ranges (a.m_Channels, a.m_Channels + kShaderChannelCount, b.m_Channels); + std::swap_ranges (a.m_Streams, a.m_Streams + kMaxVertexStreams, b.m_Streams); + std::swap (a.m_CurrentChannels, b.m_CurrentChannels); + std::swap (a.m_VertexSize, b.m_VertexSize); + std::swap (a.m_VertexCount, b.m_VertexCount); + std::swap (a.m_DataSize, b.m_DataSize); + std::swap (a.m_Data, b.m_Data); +} + +void CopyVertexDataChannels (size_t vertexCount, unsigned copyChannels, const VertexData& srcData, VertexData& dstData) +{ + Assert (vertexCount <= srcData.GetVertexCount() && vertexCount <= dstData.GetVertexCount()); + Assert ((srcData.GetChannelMask() & copyChannels) == copyChannels); + Assert ((dstData.GetChannelMask() & copyChannels) == copyChannels); + CopyChannels (vertexCount, copyChannels, + srcData.GetStreams(), srcData.GetChannels(), srcData.GetDataPtr(), + dstData.GetStreams(), dstData.GetChannels(), dstData.GetDataPtr()); +} + diff --git a/Runtime/Filters/Mesh/VertexData.h b/Runtime/Filters/Mesh/VertexData.h new file mode 100644 index 0000000..7cc6c98 --- /dev/null +++ b/Runtime/Filters/Mesh/VertexData.h @@ -0,0 +1,253 @@ +#ifndef VERTEX_DATA_H_ +#define VERTEX_DATA_H_ + +#include "Runtime/Utilities/StrideIterator.h" +#include "Runtime/GfxDevice/GfxDeviceTypes.h" +#include "Runtime/BaseClasses/ObjectDefines.h" +#include "Runtime/Serialize/SerializeUtility.h" +#include "Runtime/Serialize/TransferFunctionFwd.h" + +class VertexData; + +void swap (VertexData& a, VertexData& b); + +typedef struct StreamInfo +{ + enum { kDividerOpDivide=0, kDividerOpModulo }; + + UInt32 channelMask; + UInt32 offset; + UInt16 frequency; + UInt8 stride; + UInt8 dividerOp; + + // We use default constructors instead of memset() + StreamInfo() : channelMask(0), offset(0), frequency(0), stride(0), dividerOp(kDividerOpDivide) {} + void Reset() { *this = StreamInfo(); } + + bool operator == (const StreamInfo& rhs) const { return (channelMask == rhs.channelMask) && (offset == rhs.offset) && (frequency == rhs.frequency) && (stride == rhs.stride) && (dividerOp == rhs.dividerOp); } + bool operator != (const StreamInfo& rhs) const { return !(*this == rhs); } + + DECLARE_SERIALIZE_NO_PPTR (StreamInfo); + +#if SUPPORT_SERIALIZED_TYPETREES + template<class TransferFunction> + void TransferWorkaround35SerializationFuckup (TransferFunction& transfer); +#endif + +} StreamInfoArray [kMaxVertexStreams]; + +struct VertexStreamsLayout +{ + UInt32 channelMasks[kMaxVertexStreams]; +}; + +typedef struct ALIGN_TYPE(4) ChannelInfo +{ + UInt8 stream; + UInt8 offset; + UInt8 format; + UInt8 dimension; + + enum { kInvalidDimension = 0 }; + + // We use default constructors instead of memset() + ChannelInfo() : stream(0), offset(0), format(0), dimension(kInvalidDimension) {} + + UInt32 CalcOffset(const StreamInfoArray streams) const { return streams[stream].offset + offset; } + UInt32 CalcStride(const StreamInfoArray streams) const { return streams[stream].stride; } + bool IsValid() const { return (kInvalidDimension != dimension); } + void Reset() { *this = ChannelInfo(); } + + bool operator == (const ChannelInfo& rhs) const { return (stream == rhs.stream) && (offset == rhs.offset) && (format == rhs.format) && (dimension == rhs.dimension); } + bool operator != (const ChannelInfo& rhs) const { return !(*this == rhs); } + + DECLARE_SERIALIZE_NO_PPTR (ChannelInfo); + +} ChannelInfoArray [kShaderChannelCount]; + +struct VertexChannelsLayout +{ + struct Channel + { + Channel(UInt8 fmt, UInt8 dim) : format(fmt), dimension(dim) {} + Channel() : format(0), dimension(0) {} + UInt8 format; + UInt8 dimension; + }; + Channel channels[kShaderChannelCount]; +}; + + +template<class TransferFunc> +void StreamInfo::Transfer (TransferFunc& transfer) +{ + #if SUPPORT_SERIALIZED_TYPETREES + if (transfer.GetFlags() & kWorkaround35MeshSerializationFuckup) + { + TransferWorkaround35SerializationFuckup (transfer); + return; + } + #endif + + transfer.Transfer (channelMask, "channelMask", kHideInEditorMask); + transfer.Transfer (offset, "offset", kHideInEditorMask); + transfer.Transfer (stride, "stride", kHideInEditorMask); + transfer.Transfer (dividerOp, "dividerOp", kHideInEditorMask); + transfer.Transfer (frequency, "frequency", kHideInEditorMask); +} + +#if SUPPORT_SERIALIZED_TYPETREES +template<class TransferFunc> +void StreamInfo::TransferWorkaround35SerializationFuckup (TransferFunc& transfer) +{ + transfer.Transfer (channelMask, "channelMask", kHideInEditorMask); + transfer.Transfer (offset, "offset", kHideInEditorMask); + + UInt32 align; + UInt32 stride32bit; + transfer.Transfer (stride32bit, "stride", kHideInEditorMask); + transfer.Transfer (align, "align", kHideInEditorMask); + + stride = (UInt8) stride32bit; +} +#endif + +template<class TransferFunc> +void ChannelInfo::Transfer (TransferFunc& transfer) +{ + transfer.Transfer (stream, "stream", kHideInEditorMask); + transfer.Transfer (offset, "offset", kHideInEditorMask); + transfer.Transfer (format, "format", kHideInEditorMask); + transfer.Transfer (dimension, "dimension", kHideInEditorMask); +} + +// Information about all vertex data, but does not own the memory +class VertexDataInfo +{ +public: + enum + { + kVertexDataAlign = 32, + kVertexStreamAlign = 16, + kVertexDataPadding = 16 + }; + + static VertexStreamsLayout kVertexStreamsDefault; + static VertexStreamsLayout kVertexStreamsSkinnedHotColdSplit; + static VertexChannelsLayout kVertexChannelsDefault; + static VertexChannelsLayout kVertexChannelsCompressed; + static VertexChannelsLayout kVertexChannelsCompressedAggressive; +#if UNITY_EDITOR + static VertexStreamsLayout kVertexStreamsSkinnedHotColdSplitPS3; +#endif + + static size_t AlignStreamSize (size_t size) { return (size + (kVertexStreamAlign-1)) & ~(kVertexStreamAlign-1); } + + friend void ::swap (VertexData& a, VertexData& b); + + VertexDataInfo (); + + bool HasChannel (ShaderChannel shaderChannelIndex) const + { + Assert ((m_Channels[shaderChannelIndex].dimension != 0) == (((m_CurrentChannels & (1 << shaderChannelIndex)) != 0))); + return m_Channels[shaderChannelIndex].dimension != 0; + } + + void UpdateStreams(unsigned newChannelMask, size_t newVertexCount, const VertexStreamsLayout& streams = kVertexStreamsDefault, const VertexChannelsLayout& channels = kVertexChannelsDefault); + + size_t GetActiveStreamCount() const ; + size_t GetStreamIndex(ShaderChannel channel) const ; + const StreamInfo* GetStreams() const { return m_Streams; } + const StreamInfo& GetStream(int index) const { return m_Streams[index]; } + + const ChannelInfo* GetChannels() const { return m_Channels; } + const ChannelInfo& GetChannel(int index) const { return m_Channels[index]; } + + VertexStreamsLayout GetStreamsLayout() const; + VertexChannelsLayout GetChannelsLayout() const; + + bool ConformsToStreamsLayout(const VertexStreamsLayout& streams) const; + bool ConformsToChannelsLayout(const VertexChannelsLayout& channels) const; + + unsigned GetChannelMask () const { return m_CurrentChannels; } + size_t GetDataSize () const { return m_DataSize; } + size_t GetVertexSize () const { return m_VertexSize; } + size_t GetVertexCount () const { return m_VertexCount; } + size_t GetChannelOffset (unsigned channel) const { return m_Channels[channel].CalcOffset(m_Streams); } + size_t GetChannelStride (unsigned channel) const { return m_Channels[channel].CalcStride(m_Streams); } + UInt8* GetDataPtr () const { return m_Data; } + + template<class T> + StrideIterator<T> MakeStrideIterator (ShaderChannel shaderChannelIndex) const + { + Assert (shaderChannelIndex < kShaderChannelCount); + void* p = m_Data + GetChannelOffset(shaderChannelIndex); + return HasChannel (shaderChannelIndex) ? StrideIterator<T> (p, GetChannelStride (shaderChannelIndex)) : StrideIterator<T> (NULL, GetChannelStride (shaderChannelIndex)); + } + + template<class T> + StrideIterator<T> MakeEndIterator (ShaderChannel shaderChannelIndex) const + { + T* end = GetEndPointer<T> (shaderChannelIndex); + return StrideIterator<T> (end, GetChannelStride (shaderChannelIndex)); + } + + template<class T> + T* GetEndPointer (ShaderChannel shaderChannelIndex) const + { + Assert (shaderChannelIndex < kShaderChannelCount); + void* p = HasChannel (shaderChannelIndex) ? (m_Data + GetChannelOffset(shaderChannelIndex) + m_VertexCount * GetChannelStride (shaderChannelIndex)) : NULL; + return reinterpret_cast<T*> (p); + } + +protected: + ChannelInfoArray m_Channels; + StreamInfoArray m_Streams; + + size_t m_VertexSize; // must match m_CurrentChannels + UInt8* m_Data; + + // The following are being serialized. Their size must match in both 32 and 64 bit platforms + UInt32 m_CurrentChannels; // kShaderChannel bitmask + UInt32 m_VertexCount; + unsigned m_DataSize; +}; + + +// Owns the vertex memory +class VertexData : public VertexDataInfo +{ +public: + + DECLARE_SERIALIZE (VertexData) + + VertexData () : VertexDataInfo() { } + VertexData (VertexData const& src, unsigned copyChannels, const VertexStreamsLayout& streams = kVertexStreamsDefault, const VertexChannelsLayout& channels = kVertexChannelsDefault); + ~VertexData (); + + static size_t GetAllocateDataSize (size_t accesibleBufferSize) { return accesibleBufferSize + kVertexDataPadding; } + + void Deallocate (); + void Resize (size_t vertexCount, unsigned channelMask, const VertexStreamsLayout& streams = kVertexStreamsDefault, const VertexChannelsLayout& channels = kVertexChannelsDefault); + void SwapEndianess (); + +private: + VertexData (const VertexData& o); + void operator= (const VertexData& o); + VertexData (const VertexDataInfo& o); + void operator= (const VertexDataInfo& o); + +#if SUPPORT_SERIALIZED_TYPETREES + template<class TransferFunction> + void TransferWorkaround35SerializationFuckup (TransferFunction& transfer); +#endif +}; + + +void CopyVertexDataChannels (size_t vertexCount, unsigned copyChannels, const VertexData& srcData, VertexData& dstData); +size_t GetChannelFormatSize(UInt8 format); + + + +#endif diff --git a/Runtime/Filters/Misc/DynamicFontFreeType.cpp b/Runtime/Filters/Misc/DynamicFontFreeType.cpp new file mode 100644 index 0000000..042e328 --- /dev/null +++ b/Runtime/Filters/Misc/DynamicFontFreeType.cpp @@ -0,0 +1,510 @@ +#include "UnityPrefix.h" +#include "Font.h" + +#if UNITY_WIN +#include "PlatformDependent/Win/Registry.h" +#endif + +#include "Runtime/Graphics/ScreenManager.h" +#include "Runtime/Graphics/Image.h" +#include "Runtime/Misc/ResourceManager.h" +#include FT_BITMAP_H + + +DynamicFontData::DynamicFontData() +{ +} + +DynamicFontData::~DynamicFontData () +{ + for (FaceMap::iterator i=m_Faces.begin(); i!=m_Faces.end(); i++) + FT_Done_Face(i->second); +} + +static FT_Library g_ftLib; +static bool g_ftLibInit = false; +static FT_Bitmap g_bitmap8bpp; +static bool g_bitmap8bppInit = false; + +struct OSFont +{ + OSFont () : + index(0) + {} + + OSFont (std::string _path, int _index) : + path(_path), + index(_index) + {} + + bool operator != (const OSFont& other) const + { + return index != other.index || path != other.path; + } + + std::string path; + int index; +}; +typedef std::map<FontRef,OSFont> OSFontMap; +static OSFontMap* gOSFontMap = NULL; // Maps family names to files + + +namespace DynamicFontMap +{ + void StaticInitialize () + { + gOSFontMap = UNITY_NEW (OSFontMap, kMemFont); + } + void StaticDestroy () + { + UNITY_DELETE (gOSFontMap, kMemFont); + } +} +static float ConvertFixed26(long val) +{ + float f = (float)(val >> 6); + val = val & 0x3F; + f += (float)val/(2^6 - 1); + return f; +} + +static float ConvertFixed16(long val) +{ + float f = (float)(val >> 16); + val = val & 0xFFFF; + f += (float)val/(2^16 - 1); + return f; +} + +static long ConvertFloat26(float val) +{ + return (long)(val * (1 << 6)); +} + +static long ConvertFloat16(float val) +{ + return (long)(val * (1 << 16)); +} + +static unsigned int FreeTypeStyleToUnity (int style_flags) +{ + unsigned int style = 0; + if (style_flags & FT_STYLE_FLAG_ITALIC) + style |= kStyleFlagItalic; + if (style_flags & FT_STYLE_FLAG_BOLD) + style |= kStyleFlagBold; + return style; +} + +static bool IsExpectedFontStyle(const char* style_name, unsigned int style) +{ + switch (style) + { + case kStyleDefault: + return (strcmp(style_name, "Regular") == 0); + case kStyleFlagItalic: + return (strcmp(style_name, "Italic") == 0); + case kStyleFlagBold: + return (strcmp(style_name, "Bold") == 0); + case kStyleFlagBold | kStyleFlagItalic: + return (strcmp(style_name, "Bold Italic") == 0); + } + return false; +} + +bool GetFontMetadata(const std::string& path, std::string& family_name, std::string& style_name, unsigned& style_flags, unsigned& face_flags, int *faceIndex) +{ + std::string shortName = GetFileNameWithoutExtension(path); + + // Check, maybe we already have font metadata in our preset table + if (GetFontMetadataPreset(shortName, family_name, style_name, style_flags, face_flags)) + return true; + + bool res = false; + FT_Face face; + if(FT_New_Face(g_ftLib, path.c_str(), *faceIndex, &face) == 0) + { + *faceIndex = face->num_faces; + if(face->family_name != NULL) + { + //Uncomment this line when font metadata needs to be rebuilt for newer OS/hw versions + //printf_console("\tgFontMetadata[\"%s\"] = (_FontInfo){\"%s\", \"%s\", 0x%x, 0x%x};\n", shortName.c_str(), face->family_name, face->style_name, face->style_flags, face->face_flags); + + family_name = face->family_name; + style_name = face->style_name; + style_flags = face->style_flags; + face_flags = face->face_flags; + res = true; + } + + FT_Done_Face(face); + } + + return res; +} + +void ReadFontFiles() +{ + DynamicFontMap::StaticInitialize(); + + std::vector<std::string> paths; + GetFontPaths(paths); + + for(int folderIndex = 0; folderIndex < paths.size(); ++folderIndex) + { + + std::string &path = paths[folderIndex]; + + std::string family_name, style_name; + unsigned style_flags, face_flags; + int numFaces = 1; + for (int fontIndex=0; fontIndex<numFaces; fontIndex++) + { + int faceIndex = fontIndex; + if (GetFontMetadata(path, family_name, style_name, style_flags, face_flags, &faceIndex)) + { + numFaces = faceIndex; + if((face_flags & FT_FACE_FLAG_SCALABLE) == 0) + continue; + + FontRef r(family_name.c_str(), FreeTypeStyleToUnity(style_flags)); + OSFont font (path, fontIndex); + OSFontMap::iterator i = gOSFontMap->find(r); + if (i != gOSFontMap->end()) + { + // There already is a font for this family and style + if (i->second != font) + { + // Try to pick the one with the expected style name. + // This way we don't accidentally catch fonts like "Arial Black" when we want "Arial", + // as both will show with family_name=="Arial" and style_flags==0. In such cases, pick + // the one with the style name matching what we'd expect from the style flags, if possible. + if (IsExpectedFontStyle(style_name.c_str(), r.style)) + (*gOSFontMap)[r] = font; + } + } + else + (*gOSFontMap)[r] = font; + } + } + } +} + +static OSFont SelectFont(FontRef &r) +{ + // Initialize font map. Do this on demand here + // as it takes a significant amount of time to go through + // all of the system's fonts. + if (!gOSFontMap) + { + ReadFontFiles(); + Assert (gOSFontMap); + } + + OSFontMap::iterator i = gOSFontMap->find(r); + if (i != gOSFontMap->end()) + return i->second; + return OSFont(); +} + +FT_Face DynamicFontData::GetFaceForFontRef (FontRef &r, FontFallbacks &fallbacks) +{ + FaceMap::iterator i = m_Faces.find(r); + if (i != m_Faces.end()) + return (i->second); + else + { + // First see if any of the fallback fonts in the project contain the needed font. + for (FontFallbacks::iterator j=fallbacks.begin(); j != fallbacks.end(); j++) + { + if (j->IsValid()) + { + i = (**j).m_DynamicData.m_Faces.find(r); + if (i != (**j).m_DynamicData.m_Faces.end()) + return (i->second); + } + } + + // Then look at the system font files. + OSFont font = SelectFont(r); + if (!font.path.empty()) + { + FT_New_Face(g_ftLib, font.path.c_str(), font.index, &m_Faces[r]); + return m_Faces[r]; + } + } + return NULL; +} + +FT_Face DynamicFontData::GetFaceForCharacterIfAvailableInFont (FontRef &r, FontFallbacks &fallbacks, unsigned int unicodeChar) +{ + // Do we have a font for the requested name and style which has the character? + FT_Face face = GetFaceForFontRef(r, fallbacks); + if (face != NULL) + { + if (FT_Get_Char_Index(face, unicodeChar)) + return face; + } + + // If not, try with default style (if we had something else requested). + if (r.style) + { + FontRef r2 = r; + r2.style = 0; + face = GetFaceForFontRef(r2, fallbacks); + if (face != NULL) + { + if (FT_Get_Char_Index(face, unicodeChar)) + return face; + } + } + return NULL; +} + +FT_Face DynamicFontData::GetFaceForCharacter (FontNames &fonts, FontFallbacks &fallbacks, unsigned int style, unsigned int unicodeChar) +{ + // Check if any of the fonts in the fallback names serialized with the font has the character + for (FontNames::iterator font = fonts.begin();font != fonts.end();font++) + { + // First check if we find the font by it's full name (as that + // is what's used for the embedded ttf data, if any) + unsigned int st = style; + std::string name = *font; + FontRef r (name, st); + FT_Face face = GetFaceForCharacterIfAvailableInFont (r, fallbacks, unicodeChar); + if (face != NULL) + return face; + + // If that did not find anything, remove and parse potential style names, as OS fonts + // are identified by family name and style flags. + size_t pos = name.find(" Bold"); + if (pos != std::string::npos) + { + name = name.substr(0,pos)+name.substr(pos+5); + st |= kStyleFlagBold; + } + pos = name.find(" Italic"); + if (pos != std::string::npos) + { + name = name.substr(0,pos)+name.substr(pos+7); + st |= kStyleFlagItalic; + } + r = FontRef(name, st); + face = GetFaceForCharacterIfAvailableInFont (r, fallbacks, unicodeChar); + if (face != NULL) + return face; + } + // If not, fall back to the global fallbacks. + FontNames &globalFallbacks = GetFallbacks(); + for (FontNames::iterator font = globalFallbacks.begin();font != globalFallbacks.end();font++) + { + FontRef r (*font, style); + FT_Face face = GetFaceForCharacterIfAvailableInFont (r, fallbacks, unicodeChar); + if (face != NULL) + return face; + } + + return NULL; +} + +bool Font::HasCharacterDynamic (unsigned int unicodeChar) +{ + return unicodeChar >= 32; +} + +int GetLoadTarget (int fontsize, int mode) +{ + switch (mode) + { + case kFontRenderingModeSmooth: + return FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; + case kFontRenderingModeHintedSmooth: + return FT_LOAD_TARGET_NORMAL; + case kFontRenderingModeHintedRaster: + return FT_LOAD_TARGET_MONO; + case kFontRenderingModeOSDefault: + #if UNITY_WINRT + return FT_LOAD_TARGET_NORMAL; + #elif UNITY_WIN + { + static bool smoothing = registry::getString( "Control Panel\\Desktop", "FontSmoothing", "2" ) == "2"; + if (smoothing) + return FT_LOAD_TARGET_NORMAL; + else + return FT_LOAD_TARGET_MONO; + } + #elif UNITY_OSX + static int antiAliasingTreshold = -1; + if (antiAliasingTreshold == -1) + { + Boolean exists; + antiAliasingTreshold = CFPreferencesGetAppIntegerValue(CFSTR("AppleAntiAliasingThreshold"),CFSTR("Apple Global Domain"),&exists); + if (!exists) + antiAliasingTreshold = 4; + } + if (fontsize <= antiAliasingTreshold) + return FT_LOAD_TARGET_MONO; + else + return FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; + #else + return FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; + #endif + default: + ErrorString("Unknown font rendering mode."); + return FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; + } +} + +UInt8 *Font::GetCharacterBitmap(unsigned int &charWidth, unsigned int &charHeight, unsigned int &bufferWidth, Rectf &vert, float &advance, unsigned int unicodeChar, int size, unsigned int style) +{ + if(size == 0) + size = m_FontSize; + + FT_Face face = m_DynamicData.GetFaceForCharacter(m_FontNames, m_FallbackFonts, style, unicodeChar); + if (face == NULL) + { + // If we don't find a fallback Font in the OS, try built-in default font. + // Needed for Platforms without access to OS fonts (like NaCl, which has no file system access). + Font *builtinFont = GetBuiltinResource<Font> (kDefaultFontName); + if (builtinFont) + face = builtinFont->m_DynamicData.GetFaceForCharacter(m_FontNames, builtinFont->m_FallbackFonts, style, unicodeChar); + + if (face == NULL) + return NULL; + } + + unsigned int faceStyle = FreeTypeStyleToUnity(face->style_flags); + // Perform transformations needed for bold/italic styles if the font does not natively support it. + FT_Matrix m; + if (!(faceStyle & kStyleFlagBold) && style & kStyleFlagBold) + m.xx = ConvertFloat16(1.25f); + else + m.xx = ConvertFloat16(1); + if (!(faceStyle & kStyleFlagItalic) && style & kStyleFlagItalic) + m.xy = ConvertFloat16(0.25f); + else + m.xy = ConvertFloat16(0); + m.yy = ConvertFloat16(1); + m.yx = ConvertFloat16(0); + FT_Set_Transform(face, &m, NULL); + + FT_Set_Char_Size(face, 0, ConvertFloat26(size), 72, 72); + + FT_UInt glyph = FT_Get_Char_Index(face, unicodeChar); + if(glyph != 0) + { + int loadTarget = FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; + loadTarget = GetLoadTarget(size, m_FontRenderingMode); + if( FT_Load_Glyph(face, glyph, loadTarget ) == 0 ) + { + bool glyphRendered = true; + if(face->glyph->format != FT_GLYPH_FORMAT_BITMAP ) + glyphRendered = ( FT_Render_Glyph(face->glyph, FT_LOAD_TARGET_MODE(loadTarget) ) == 0 ); + + if(glyphRendered) + { + FT_Bitmap *srcBitmap = 0; + FT_Bitmap& bitmap =face->glyph->bitmap; + + if(bitmap.pixel_mode != FT_PIXEL_MODE_GRAY) + { + if(!g_bitmap8bppInit) + { + FT_Bitmap_New(&g_bitmap8bpp); + g_bitmap8bppInit = true; + } + FT_Bitmap_Convert(g_ftLib, &bitmap, &g_bitmap8bpp, 4); + srcBitmap = &g_bitmap8bpp; + if (srcBitmap->num_grays != 256) + { + float factor = 1.0f/(srcBitmap->num_grays-1) * 255; + for (int i=0; i<srcBitmap->pitch*srcBitmap->rows; i++) + srcBitmap->buffer[i] *= factor; + } + } + else + { + srcBitmap = &bitmap; + } + + charWidth = srcBitmap->width; + charHeight = srcBitmap->rows; + bufferWidth = srcBitmap->pitch; + vert = Rectf(face->glyph->bitmap_left, + face->glyph->bitmap_top - m_Ascent, + (float)charWidth, + -(float)charHeight); + advance = Roundf(face->glyph->metrics.horiAdvance / 64.0); + + if (srcBitmap->width*srcBitmap->rows == 0) + return NULL; + + return srcBitmap->buffer; + } + } + } + + return NULL; +} + +void Font::SetupDynamicFont () +{ + Assert(g_ftLibInit); + + if(!m_FontData.empty()) + { + FT_Face face = NULL; + if(FT_New_Memory_Face(g_ftLib, (const FT_Byte*)&m_FontData[0], m_FontData.size(), 0, &face) != 0) + { + ErrorString("Failed to load font from memory"); + } + else + { + // So we don't crash if we have a font where FreeType does not understand the name. + // Unity 4.x will refuse to import such fonts, but it can happen with content made with 3.x, + // which did not use FT to import the font. + if (face->family_name == NULL) + face->family_name = (FT_String*)"Unreadeable font name."; + + // Make sure the name of the memory font is the first in the list of fonts to use. + if (strcmp(m_FontNames[0].c_str(), face->family_name) != 0) + m_FontNames.insert(m_FontNames.begin(), face->family_name); + + FontRef r (face->family_name, FreeTypeStyleToUnity(face->style_flags)); + m_DynamicData.m_Faces[r] = face; + if (r.style != 0) + { + r.style = 0; + if (FT_New_Memory_Face(g_ftLib, (const FT_Byte*)&m_FontData[0], m_FontData.size(), 0, &face) == 0) + m_DynamicData.m_Faces[r] = face; + } + } + } +} + +void Font::InitializeClass() +{ + GetFontsManager::StaticInitialize(); + if(FT_Init_FreeType( &g_ftLib ) != 0) + { + ErrorString("Could not initialize FreeType"); + } + g_ftLibInit = true; +} + +void Font::CleanupClass() +{ + if(g_bitmap8bppInit) + { + FT_Bitmap_Done(g_ftLib, &g_bitmap8bpp); + g_bitmap8bppInit = false; + } + if(g_ftLibInit) + { + FT_Done_FreeType(g_ftLib); + g_ftLibInit = false; + } + DynamicFontMap::StaticDestroy(); + GetFontsManager::StaticDestroy(); +} + diff --git a/Runtime/Filters/Misc/DynamicFontFreeType.h b/Runtime/Filters/Misc/DynamicFontFreeType.h new file mode 100644 index 0000000..4bfdb35 --- /dev/null +++ b/Runtime/Filters/Misc/DynamicFontFreeType.h @@ -0,0 +1,47 @@ +#ifndef DYNAMICFONTFREETYPE_H +#define DYNAMICFONTFREETYPE_H + +#if DYNAMICFONTMODE == kDynamicFontModeFreeType + +#include "External/freetype2/include/ft2build.h" +#include FT_FREETYPE_H +//#include FT_GLYPH_H + +namespace DynamicFontMap +{ + void StaticInitialize(); + void StaticDestroy(); +} + +struct FontRef +{ + std::string family; + unsigned int style; + + FontRef(const std::string& _family, unsigned int _style) : family(_family), style(_style) {} + + bool operator < (const FontRef& other) const { + if (family < other.family) + return true; + else if (family > other.family) + return false; + return style < other.style; + } +}; + +typedef std::map<FontRef, FT_Face> FaceMap; + +struct DynamicFontData { + DynamicFontData(); + ~DynamicFontData(); + + FT_Face GetFaceForFontRef (FontRef &r, FontFallbacks &fallbacks); + FT_Face GetFaceForCharacter (FontNames &fonts, FontFallbacks &fallbacks, unsigned int style, unsigned int unicodeChar); + FT_Face GetFaceForCharacterIfAvailableInFont (FontRef &r, FontFallbacks &fallbacks, unsigned int unicodeChar); + + FaceMap m_Faces; +}; + +#endif + +#endif diff --git a/Runtime/Filters/Misc/Font.cpp b/Runtime/Filters/Misc/Font.cpp new file mode 100644 index 0000000..57d84f3 --- /dev/null +++ b/Runtime/Filters/Misc/Font.cpp @@ -0,0 +1,845 @@ +#include "UnityPrefix.h" +#include "Font.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Shaders/Material.h" +#include "TextMesh.h" +#if UNITY_EDITOR +#include "Editor/Src/AssetPipeline/TrueTypeFontImporter.h" +#endif +#include "Runtime/Graphics/Image.h" +#include "Runtime/Graphics/Texture.h" +#include "Runtime/Utilities/Word.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Graphics/Texture2D.h" +#include "Runtime/IMGUI/TextMeshGenerator2.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Utilities/BitUtility.h" +#include "Runtime/Scripting/ScriptingUtility.h" +#include "Runtime/Scripting/ScriptingManager.h" +#include "Runtime/Scripting/Scripting.h" + +using namespace std; + +// on android we can recreate gles context and loose gpu-side texture copy +// on editor we want to have it to write it later on build +#define NEEDS_SYSTEM_MEM_COPY UNITY_ANDROID || UNITY_EDITOR + +PROFILER_INFORMATION(gFontTextureCacheProfile, "Font.CacheFontForText", kProfilerRender) + +unsigned int Font::s_FrameCount = 0; + +Font::Font(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + // dynamic font stuff + m_TexWidth = 256; + m_TexHeight = 256; + m_SubImageSize = 1; + m_CharacterSpacing = 1; + m_CharacterPadding = 0; + m_FontSize = 0; + m_Ascent = 0.0f; + m_DefaultStyle = kStyleDefault; + m_FontRenderingMode = kFontRenderingModeSmooth; + + m_PixelScale = 0.1f; + + m_AsciiCharacterRects.clear(); + m_AsciiCharacterRects.resize (256); + + m_TexturePositions.insert (TexturePosition (0, 0)); + m_TexturePositionsSearchPosition = m_TexturePositions.begin(); +} + +Font::~Font () +{ +} + +void Font::Reset () +{ + Super::Reset(); + + m_Kerning = 1.0F; + m_LineSpacing = 0.1F; + m_AsciiStartOffset = 0; + m_ConvertCase = 0; +} + +unsigned int Font::GetGlyphNo (unsigned int charCode) const +{ + if (m_ConvertCase == kUpperCase) + return ToUpper ((char)charCode) - m_AsciiStartOffset; + else if (m_ConvertCase == kLowerCase) + return ToLower ((char)charCode) - m_AsciiStartOffset; + else + return charCode - m_AsciiStartOffset; +} + +void Font::GetCharacterRenderInfo( unsigned int charCode, Rectf& verts, Rectf& uvs, bool &flipped ) const +{ + GetCharacterRenderInfo(charCode, 0, 0, verts, uvs, flipped); +} + +void Font::GetCharacterRenderInfo( unsigned int charCode, int size, unsigned int style, Rectf& verts, Rectf& uvs, bool &flipped ) const +{ + unsigned int charNo = GetGlyphNo (charCode); + if (size == m_FontSize) + size = 0; + + if (m_ConvertCase != kDynamicFont && (size != 0 || style != kStyleDefault)) + { + ErrorString ("Font size and style overrides are only supported for dynamic fonts."); + size = 0; + style = kStyleDefault; + } + + if (charNo < 256 && size == 0 && style == kStyleDefault) + { + verts = m_AsciiCharacterRects[charNo].vert; + uvs = m_AsciiCharacterRects[charNo].uv; + flipped = m_AsciiCharacterRects[charNo].flipped; + } + else + { + CharacterInfo proxy; + proxy.index = charNo; + proxy.size = size; + proxy.style = style; + vector_set<CharacterInfo>::const_iterator found = m_UnicodeCharacterRects.find(proxy); + if (found != m_UnicodeCharacterRects.end()) + { + verts = found->vert; + uvs = found->uv; + flipped = found->flipped; + } + else + { + verts = Rectf( 0, 0, 0, 0 ); + uvs = Rectf( 0, 0, 0, 0 ); + flipped = false; + } + } +} + +/// Does this font have a definition for a specific character? +bool Font::HasCharacterInTexture (unsigned int unicodeChar, int size, unsigned int style) +{ + unsigned int charNo = GetGlyphNo (unicodeChar); + if (size == m_FontSize) + size = 0; + + if (m_ConvertCase != kDynamicFont && (size != 0 || style != kStyleDefault)) + { + ErrorString ("Font size and style overrides are only supported for dynamic fonts."); + size = 0; + style = kStyleDefault; + } + + // This uses the character advancement - We're making the assumption that all characters have a width + if (charNo < 256 && size == 0 && style == kStyleDefault) + { + if (m_AsciiCharacterRects[charNo].width != 0.0f) + { + m_AsciiCharacterRects[charNo].lastUsedInFrame = s_FrameCount; + return true; + } + } + + CharacterInfo proxy; + proxy.index = charNo; + proxy.size = size; + proxy.style = style; + vector_set<CharacterInfo>::iterator found = m_UnicodeCharacterRects.find(proxy); + if (found != m_UnicodeCharacterRects.end()) + { + found->lastUsedInFrame = s_FrameCount; + return true; + } + return false; +} + +bool Font::HasCharacter (unsigned int unicodeChar, int size, unsigned int style) +{ + if (m_ConvertCase == kDynamicFont) + return HasCharacterDynamic (unicodeChar); + else + return HasCharacterInTexture (unicodeChar, size, style); +} + +float Font::GetCharacterWidth( unsigned int charCode, int size, unsigned int style ) const +{ + if (size == m_FontSize) + size = 0; + + if (m_ConvertCase != kDynamicFont && (size != 0 || style != kStyleDefault)) + { + ErrorString ("Font size and style overrides are only supported for dynamic fonts."); + size = 0; + style = kStyleDefault; + } + + unsigned int charNo = GetGlyphNo (charCode); + if (charNo < 256 && size == 0 && style == kStyleDefault) + return m_AsciiCharacterRects[charNo].width * m_Kerning; + else + { + CharacterInfo proxy; + proxy.index = charNo; + proxy.size = size; + proxy.style = style; + vector_set<CharacterInfo>::const_iterator found = m_UnicodeCharacterRects.find(proxy); + if (found != m_UnicodeCharacterRects.end()) + return found->width * m_Kerning; + else + return 0.0F; + } +} + +float Font::GetTabWidth() const +{ + return GetCharacterWidth(' '); +} + +template<class TransferFunction> inline +void Font::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.SetVersion (4); + + TRANSFER(m_AsciiStartOffset); + TRANSFER(m_Kerning); + TRANSFER(m_LineSpacing); + TRANSFER(m_CharacterSpacing); + TRANSFER(m_CharacterPadding); + TRANSFER(m_ConvertCase); + TRANSFER(m_DefaultMaterial); + + if (m_ConvertCase != kDynamicFont) + { + transfer.Transfer(m_CharacterRects, "m_CharacterRects"); + } + else + { + // These are generated dynamically for dynamic fonts. + UNITY_TEMP_VECTOR(CharacterInfo) emptyCharacterInfo; + transfer.Transfer(emptyCharacterInfo, "m_CharacterRects"); + } + + transfer.Transfer(m_Texture, "m_Texture", kHideInEditorMask); + + transfer.Transfer(m_KerningValues, "m_KerningValues", kHideInEditorMask); + + // In version 1.5.0 line spacing is multiplicative instead of additive + if (transfer.IsOldVersion(1)) + { + m_LineSpacing = 1.0F + m_LineSpacing; + } + + transfer.Transfer(m_PixelScale, "m_PixelScale", kHideInEditorMask); + + // Legacy Grid Font support + if (transfer.IsVersionSmallerOrEqual(3)) + { + bool gridFont; + transfer.Transfer(gridFont, "m_GridFont"); + + if (gridFont) + { + int fontCountX; + int fontCountY; + transfer.Transfer(fontCountX, "m_FontCountX"); + transfer.Transfer(fontCountY, "m_FontCountY"); + + m_PixelScale = -fontCountX; + + PerCharacterKerning perCharacterKerning; + + transfer.Transfer(perCharacterKerning, "m_PerCharacterKerning"); + + for (int charNo=0; charNo< fontCountX*fontCountY; charNo++ ) + { + CharacterInfo info; + info.index = charNo; + + info.vert = Rectf(0.0F, 0.0F, 1.0, -1.0); + short charCol = charNo % fontCountX; + short charRow = charNo / fontCountX; + float charUVSizeX = 1.0F / (float)fontCountX; + float charUVSizeY = 1.0F / (float)fontCountY; + Vector2f charUVOffset = Vector2f((float)charCol * charUVSizeX, (float)charRow * charUVSizeY); + info.uv = MinMaxRect ( charUVOffset.x, 1.0F - charUVOffset.y - charUVSizeY, charUVOffset.x + charUVSizeX, 1.0F - charUVOffset.y ); + info.width = 1.0; + for (PerCharacterKerning::iterator i=perCharacterKerning.begin ();i != perCharacterKerning.end ();i++) + { + if (i->first - m_AsciiStartOffset == charNo) + info.width = i->second; + } + m_CharacterRects.push_back(info); + } + } + } + + transfer.Align(); + transfer.Transfer(m_FontData, "m_FontData", kHideInEditorMask); + transfer.Align(); + float fontSize = m_FontSize; + transfer.Transfer(fontSize, "m_FontSize", kHideInEditorMask); + m_FontSize = (int)fontSize; + transfer.Transfer(m_Ascent, "m_Ascent", kHideInEditorMask); + transfer.Transfer(m_DefaultStyle, "m_DefaultStyle", kHideInEditorMask); + transfer.Transfer(m_FontNames, "m_FontNames", kHideInEditorMask); + +#if UNITY_EDITOR + if (transfer.IsWritingGameReleaseData ()) + { + // Make sure we have references to all other fonts in the project + // which may be used as fallbacks in the build. + TrueTypeFontImporter::GetFallbackFontReferences (this); + } +#endif + + transfer.Transfer(m_FallbackFonts, "m_FallbackFonts", kHideInEditorMask); + transfer.Align(); + TRANSFER(m_FontRenderingMode); +} + +template<class TransferFunction> inline +void Font::CharacterInfo::Transfer (TransferFunction& transfer) +{ + transfer.SetVersion(2); + TRANSFER(index); + TRANSFER(uv); + TRANSFER(vert); + TRANSFER(width); + TRANSFER(flipped); + transfer.Align(); + if( !transfer.IsCurrentVersion() ) + width = vert.Width(); +} + +void ApplyToMeshes () +{ + vector<TextMesh*> meshes; + Object::FindObjectsOfType (&meshes); + for (int i=0;i<meshes.size ();i++) + { + meshes[i]->ApplyToMesh (); + } +} + +void Font::AwakeFromLoad(AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + +#if UNITY_EDITOR + // Make sure we have references to all other fonts in the project + // which may be used as fallbacks in the build. + TrueTypeFontImporter::GetFallbackFontReferences (this); + + if ((awakeMode & kDidLoadFromDisk) && (m_ConvertCase == kDynamicFont) && !m_Texture.IsValid()) + ErrorStringObject(Format("Font texture for dynamic font %s is missing. Please reimport the Font. All dynamic fonts created with earlier Unity 3.0 betas need to be reimported.", GetName()), this); +#endif + + // m_PixelScale is set to -fontCountX for legacy gridfonts + if(m_PixelScale < 0.f) + { + // Load related material or texture to apply the scaling to the CharacterRects + Texture *tex = GetTexture (); + if (!tex) { + Material *mat = GetMaterial(); + if (mat) + tex = mat->GetTexture (ShaderLab::Property("_MainTex")); + } + if (tex) + m_PixelScale = -m_PixelScale / tex->GetDataWidth (); + else + m_PixelScale = 1.0f; + + for (int charNo=0; charNo< m_CharacterRects.size(); charNo++ ) + { + CharacterInfo& info = m_CharacterRects[charNo]; + info.vert = Rectf(0.0F, 0.0F, 1.0/m_PixelScale, -1.0/m_PixelScale); + info.width /= m_PixelScale; + } + m_LineSpacing /= m_PixelScale; + } + + CacheRects(); + + if (m_ConvertCase == kDynamicFont) + { + if (m_FontNames.empty()) + { + ErrorString ("Font does not contain font names!"); + m_FontNames.push_back("Arial"); + } + SetupDynamicFont (); + ResetCachedTexture(); + } + + if ((awakeMode & kDidLoadFromDisk) == 0) + ApplyToMeshes(); +} + +void Font::AddCharacterInfoEntry( const Rectf& uv, const Rectf& vert, float width, int character, bool flipped, int size, unsigned int style) +{ + character -= m_AsciiStartOffset; + AssertIf( character < 0 ); + + CharacterInfo inf; + inf.uv = uv; + inf.vert = vert; + inf.width = width; + inf.index = character; + if (size == m_FontSize) + inf.size = 0; + else + inf.size = size; + inf.style = style; + inf.lastUsedInFrame = s_FrameCount; + inf.flipped = flipped; + m_CharacterRects.push_back(inf); + AddRectToCache (inf); +} + +void Font::AddRectToCache(CharacterInfo& info) +{ + // We cache ascii characters into a direct lookup buffer, for optimal performance on the most common characters + if (info.index < 256 && info.size == 0 && info.style == kStyleDefault) + m_AsciiCharacterRects[info.index] = info; + // And a set of characters for the rest. + else + m_UnicodeCharacterRects.insert(info); +} + +void Font::CacheRects () +{ + m_AsciiCharacterRects.clear(); + m_AsciiCharacterRects.resize (256); + m_UnicodeCharacterRects.clear(); + + for (int i=0;i<m_CharacterRects.size();i++) + { + CharacterInfo& info = m_CharacterRects[i]; + // Older version didn't have the index. So derive it from i + if (info.index == -1) + info.index = i; + + AddRectToCache(info); + } +} + + + +// Dynamic font stuff +// ================== + +static bool Equals (FontNames& list1, FontNames& list2) +{ + if (list1.size() != list2.size()) + return false; + + for (unsigned i=0; i<list1.size (); ++i) + if (list1[i] != list2[i]) + return false; + + return true; +} + +void Font::SetFontNames (FontNames &names) +{ + if (m_ConvertCase == kDynamicFont) + { + if (Equals (names, m_FontNames)) + return; + + m_FontNames = names; +// TODO! +// DestroyDynamicFont (); +// SetupDynamicFont (); + ResetCachedTexture(); + } + else + ErrorString ("Font.names can only be set for dynamic fonts."); +} + +void Font::ResetPackingData () +{ + m_TexturePositions.clear(); + m_IntRects.clear(); + m_TexturePositions.insert (TexturePosition (0, 0)); + m_TexturePositionsSearchPosition = m_TexturePositions.begin(); +} + +bool Font::ResetCachedTexture () +{ + if (m_ConvertCase != kDynamicFont) + return true; // nothing to do for static fonts + + m_CharacterRects.clear(); + CacheRects (); + + int maxSize = gGraphicsCaps.maxTextureSize; + + // Some windows setups apparently crash when allocating textures > 4096^2. So, don't do it. + maxSize = std::min(maxSize, 4096); + + if (m_TexWidth > maxSize || m_TexHeight > maxSize) + { + ErrorString ("Failed to generate dynamic font texture, because all the needed characters do not fit onto a single texture. Try using less text or a smaller font size."); + m_TexWidth = maxSize; + m_TexHeight = maxSize; + return false; + } + + Texture2D *tex; + if (!GetTexture().IsValid()) + return false; + else + tex = dynamic_pptr_cast<Texture2D*>(GetTexture()); + + if (gGraphicsCaps.disableSubTextureUpload || (NEEDS_SYSTEM_MEM_COPY && !UNITY_EDITOR)) + tex->SetIsReadable(true); + else + tex->SetIsUnreloadable (true); + +#if UNITY_EDITOR + tex->SetEditorDontWriteTextureData(true); +#endif + + if (tex->GetDataWidth() != m_TexWidth || tex->GetDataHeight() != m_TexHeight || !tex->GetIsUploaded()) + { + if (!tex->InitTexture (m_TexWidth, m_TexHeight, kTexFormatAlpha8, Texture2D::kNoMipmap)) + return false; + tex->UpdateImageData (); + } + + { + UInt8* texData; + ALLOC_TEMP(texData, UInt8, m_TexWidth * m_TexHeight); + memset (texData, 0, m_TexWidth * m_TexHeight); + int dataSize = m_TexWidth * m_TexHeight; + + if (!gGraphicsCaps.disableSubTextureUpload) + GetGfxDevice().UploadTextureSubData2D( tex->GetTextureID(), texData, dataSize, 0, 0, 0, m_TexWidth, m_TexHeight, kTexFormatAlpha8, tex->GetActiveTextureColorSpace() ); + if (gGraphicsCaps.disableSubTextureUpload || NEEDS_SYSTEM_MEM_COPY) + { + ImageReference texImg; + if (tex->GetWriteImageReference ( &texImg, 0, 0 )) + { + ImageReference data (m_TexWidth, m_TexHeight, m_TexWidth, kTexFormatAlpha8, texData); + texImg.BlitImage( data ); + } + if (gGraphicsCaps.disableSubTextureUpload) + tex->UpdateImageData(); + } + } + + ResetPackingData (); + + m_SubImageIndex = 0; + m_SubImageSize = std::max(m_SubImageSize, (unsigned int)NextPowerOfTwo(8 * m_FontSize)); + m_SubImageSize = std::min(m_SubImageSize, m_TexWidth); + + return true; +} + +bool Font::IsRectFree(const IntRect &r) const +{ + if (r.x < 0 || r.y < 0 || r.x + r.width > m_SubImageSize || r.y+r.height > m_SubImageSize) + return false; + + for (UNITY_VECTOR(kMemFont,IntRect)::const_iterator i = m_IntRects.begin(); i != m_IntRects.end(); i++) + { + if (r.Intersects(*i)) + return false; + } + + return true; +} + +bool Font::AddCharacterToTexture (unsigned int unicodeChar, int size, unsigned int style) +{ + Rectf vert; + + unsigned int charWidth = 0; + unsigned int charHeight = 0; + unsigned int bufferWidth = 0; + float advance = 0; + // returns a pointer to static vector data + UInt8* bitmap = GetCharacterBitmap (charWidth, charHeight, bufferWidth, vert, advance, unicodeChar, size, style | m_DefaultStyle); + UNITY_TEMP_VECTOR(UInt8) flippedBitmap; + bool flipped = false; + + if (bitmap == NULL && charHeight*charWidth != 0) + { + charWidth = 0; + charHeight = 0; + advance = 0; + } + + if (charWidth > charHeight) + { + // flip glyphs with >1 aspect ratios for better packing results. + flipped = true; + flippedBitmap.resize (charWidth * charHeight); + for (int x = 0; x<charWidth; x++) + { + for (int y = 0; y<charHeight; y++) + flippedBitmap[charHeight-1-y + (charWidth-1-x)*charHeight] = bitmap[x + y*bufferWidth]; + } + bitmap = &flippedBitmap[0]; + bufferWidth = charHeight; + charHeight = charWidth; + charWidth = bufferWidth; + } + else if (bufferWidth > charWidth) + { + flippedBitmap.resize (charWidth * charHeight); + for (int x = 0; x<charWidth; x++) + { + for (int y = 0; y<charHeight; y++) + flippedBitmap[x + y*charWidth] = bitmap[x + y*bufferWidth]; + } + bitmap = &flippedBitmap[0]; + bufferWidth = charWidth; + } + + vert.x -= m_CharacterPadding; + vert.y += m_CharacterPadding; + vert.width += 2*m_CharacterPadding; + vert.height -= 2*m_CharacterPadding; + + while (true) + { + for (UNITY_SET(kMemFont,TexturePosition)::iterator i = m_TexturePositionsSearchPosition; i != m_TexturePositions.end(); i++) + { + IntRect r (i->x, i->y, charWidth+m_CharacterSpacing+2*m_CharacterPadding, charHeight+m_CharacterSpacing+2*m_CharacterPadding); + if (IsRectFree (r)) + { + IntRect r2 = r; + r2.x--; + while (IsRectFree (r2) && r2.x > 0) + { + r = r2; + r2.x--; + } + r2 = r; + r2.y--; + while (IsRectFree (r2) && r2.y > 0) + { + r = r2; + r2.y--; + } + + m_IntRects.push_back(r); + m_TexturePositionsSearchPosition = i; + m_TexturePositionsSearchPosition++; + m_TexturePositions.erase(i); + m_TexturePositions.insert( TexturePosition(r.x + r.width, r.y)); + m_TexturePositions.insert( TexturePosition(r.x, r.y + r.height)); + + // Offset sub image position to get actual texture position + int subImagePos = m_SubImageIndex * m_SubImageSize; + r.x += subImagePos % m_TexWidth; + r.y += (subImagePos / m_TexWidth) * m_SubImageSize; + + if (bitmap) + { + int dataSize = bufferWidth * charHeight; + Texture2D *tex = dynamic_pptr_cast<Texture2D*>(GetTexture()); + + if (!gGraphicsCaps.disableSubTextureUpload) + GetGfxDevice().UploadTextureSubData2D( tex->GetTextureID(), bitmap, dataSize, 0, r.x+m_CharacterPadding, r.y+m_CharacterPadding, bufferWidth, charHeight, kTexFormatAlpha8, tex->GetActiveTextureColorSpace() ); + if (gGraphicsCaps.disableSubTextureUpload || NEEDS_SYSTEM_MEM_COPY) + { + ImageReference texImg; + if (tex->GetWriteImageReference ( &texImg, 0, 0 )) + { + ImageReference destRect = texImg.ClipImage( r.x, r.y, bufferWidth, charHeight ); + + ImageReference data (bufferWidth, charHeight, bufferWidth, kTexFormatAlpha8, bitmap); + destRect.BlitImage( data ); + } + } + } + + float width = m_TexWidth; + float height = m_TexHeight; + + Rectf uv (r.x/width, (r.y+charHeight+2*m_CharacterPadding)/height, (charWidth+2*m_CharacterPadding)/width, -((charHeight+2*m_CharacterPadding)/height)); + AddCharacterInfoEntry (uv, vert, advance, unicodeChar, flipped, size, style); + return true; + } + } + if (m_TexturePositionsSearchPosition != m_TexturePositions.begin()) + m_TexturePositionsSearchPosition = m_TexturePositions.begin(); + else + { + if (m_SubImageIndex+1 < (m_TexWidth/m_SubImageSize) * (m_TexHeight/m_SubImageSize)) + { + // This sub image is full. move to the next one. + m_SubImageIndex++; + ResetPackingData (); + } + else + return false; + } + } + return false; +} + +void Font::GrowTexture (int maxFontSize) +{ + // If we couldn't fit all characters by repainting the texture, enlarge it. + if (m_TexWidth < m_TexHeight) + m_TexWidth *= 2; + else + m_TexHeight *= 2; + // Make sure that we fit the largest characters in the string into a single sub image. + m_SubImageSize = std::max(m_SubImageSize, (unsigned int)NextPowerOfTwo(4 * maxFontSize)); + m_SubImageSize = std::min(m_SubImageSize, m_TexWidth); +} + +UInt16 *Font::CollectAllUsedCharacters (UInt16 *chars, int &length, int *&sizes, unsigned int *&styles) +{ + // We have to create a new texture. Make sure to add all characters which have been used in this frame. + int usedThisFrame = 0; + for (UNITY_VECTOR(kMemFont,CharacterInfo)::iterator ch = m_CharacterRects.begin(); ch != m_CharacterRects.end(); ch++) + { + if (ch->lastUsedInFrame == s_FrameCount) + usedThisFrame++; + } + UInt16 *newchars = new UInt16[usedThisFrame + length]; + sizes = new int[usedThisFrame + length]; + styles = new unsigned int[usedThisFrame + length]; + // string being currently cached; without size/style override + for (int ch=0; ch<length; ch++) + { + newchars[ch] = chars[ch]; + sizes[ch] = -1; + styles[ch] = -1; + } + // put in characters used in this frame, with their original size/style + for (UNITY_VECTOR(kMemFont,CharacterInfo)::iterator ch = m_CharacterRects.begin(); ch != m_CharacterRects.end(); ch++) + { + if (ch->lastUsedInFrame == s_FrameCount) + { + newchars[length] = ch->index; + sizes[length] = ch->size; + styles[length] = ch->style; + length++; + } + } + return newchars; +} + +bool Font::CacheFontForText (UInt16 *chars, int length, int size, unsigned int style, std::vector<TextFormatChange> format) +{ + if (m_ConvertCase != kDynamicFont) + return true; + + PROFILER_AUTO(gFontTextureCacheProfile, NULL) + + if (!GetTexture().IsValid() && !ResetCachedTexture ()) + return false; + + bool didAdd = false; + int *sizes = NULL; + unsigned int *styles = NULL; + int maxFontSize = 0; + bool didNotFit = false; + do { + didNotFit = false; + FormatStack formatStack(0xffffffff, size, style); + int formatChange = 0; + for (int i=0; i<length; i++) + { + while (formatChange < format.size() && i >= format[formatChange].startPosition) + { + i += format[formatChange].skipCharacters; + formatStack.PushFormat(format[formatChange]); + formatChange++; + } + // Recheck range after skipping format changes + if (i >= length) + break; + + int thisSize = formatStack.Current().size; + int thisStyle = formatStack.Current().style; + if (sizes && sizes[i] != -1) + { + // Normally, we just add characters with the computed size/style. + // But when we need to recreate the texture, then we need to make sure all used characters + // with all used sizes are in there, so we index size & style from an array. + thisSize = sizes[i]; + thisStyle = styles[i]; + } + if (thisSize == 0) + thisSize = m_FontSize; + if (thisSize > maxFontSize) + maxFontSize = thisSize; + UInt16 thisChar = chars[i]; + if (HasCharacterDynamic (thisChar) && !HasCharacterInTexture (chars[i], thisSize, thisStyle)) + { + if (!AddCharacterToTexture (thisChar, thisSize, thisStyle)) + { + if (sizes != NULL) + GrowTexture(maxFontSize); + else + chars = CollectAllUsedCharacters (chars, length, sizes, styles); + didNotFit = true; + if (!ResetCachedTexture()) + return false; + break; + } + didAdd = true; + } + } + } while(didNotFit); + + if (didAdd && gGraphicsCaps.disableSubTextureUpload) + dynamic_pptr_cast<Texture2D*>(GetTexture())->UpdateImageData(); + + if (sizes != NULL) + { + delete[] chars; + delete[] sizes; + delete[] styles; + + #if ENABLE_SCRIPTING + // Make sure we don't call InvokeFontTextureRebuildCallback_Internal repeatedly due to + // ApplyToMeshes adding characters. + static int recursionDepth = 0; + recursionDepth++; + TextMeshGenerator2::Flush(); + + ApplyToMeshes(); + recursionDepth--; + + if (recursionDepth == 0) + { + ScriptingObjectPtr instance = Scripting::ScriptingWrapperFor(this); + if (instance) + { + ScriptingInvocation invocation(GetScriptingManager().GetCommonClasses().font_InvokeFontTextureRebuildCallback_Internal); + invocation.object = instance; + invocation.Invoke(); + } + } + #endif + } + + return true; +} + +float Font::GetLineSpacing (int size) const +{ + if (size == 0 || m_FontSize == 0) + return m_LineSpacing; + else + return m_LineSpacing * (float)size/m_FontSize; +} + +IMPLEMENT_CLASS_HAS_INIT (Font) +IMPLEMENT_OBJECT_SERIALIZE (Font) diff --git a/Runtime/Filters/Misc/Font.h b/Runtime/Filters/Misc/Font.h new file mode 100644 index 0000000..5004709 --- /dev/null +++ b/Runtime/Filters/Misc/Font.h @@ -0,0 +1,318 @@ +#ifndef FONT_H +#define FONT_H + +#include "Runtime/BaseClasses/NamedObject.h" +#include "Runtime/Utilities/vector_map.h" +#include "Runtime/Utilities/vector_set.h" +#include "Runtime/Math/Rect.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Misc/UTF8.h" +#include "Runtime/IMGUI/TextFormatting.h" + +class Font; + +typedef UNITY_VECTOR(kMemFont,UnityStr) FontNames; +typedef UNITY_VECTOR (kMemFont, PPtr<Font> ) FontFallbacks; + +#include "DynamicFontFreeType.h" + +namespace Unity { class Material; } +using namespace Unity; + +enum { + kFontRenderingModeSmooth, + kFontRenderingModeHintedSmooth, + kFontRenderingModeHintedRaster, + kFontRenderingModeOSDefault +}; + +class Texture; + +class Font : public NamedObject +{ + public: + + struct KerningCompare : std::binary_function<std::pair<char, char>, std::pair<char, char>, std::size_t> + { + bool operator()(const std::pair<char, char> lhs, const std::pair<char, char> rhs) const + { + if (lhs.first != rhs.first) + return lhs.first < rhs.first; + else + return lhs.second < rhs.second; + } + }; + + // TrueType fonts: information for a character. + struct CharacterInfo { + unsigned int index; + Rectf uv; ///< UV coordinates for this glyph. + Rectf vert; ///< Rectangle for where to render the glyph. + float width; + int size; + unsigned int style; + unsigned int lastUsedInFrame; + bool flipped; + DECLARE_SERIALIZE_NO_PPTR (CharacterInfo) + + CharacterInfo() : + vert(0.0F,0.0F,0.0F,0.0F), + uv(0.0F,0.0F,0.0F,0.0F), + index (-1), + width (0.0F), + size (0), + style (kStyleDefault), + lastUsedInFrame (0), + flipped (false) + { } + + friend bool operator < (const CharacterInfo& lhs, const CharacterInfo& rhs) + { + if (lhs.index == rhs.index) + { + if (lhs.size < rhs.size) + return true; + else if (lhs.size > rhs.size) + return false; + return lhs.style < rhs.style; + } + return lhs.index < rhs.index; + } + }; + typedef UNITY_VECTOR(kMemFont,CharacterInfo) CharacterInfos; + + + REGISTER_DERIVED_CLASS (Font, NamedObject) + DECLARE_OBJECT_SERIALIZE (Font) + + Font (MemLabelId label, ObjectCreationMode mode); + virtual void AwakeFromLoad(AwakeFromLoadMode mode); + virtual void Reset(); + + static void InitializeClass (); + static void CleanupClass (); + + /// Get the kerning of the font. + /// Kerning is letter-spacing + float GetKerning () const { return m_Kerning; } + + /// Get the uv & vertex info for unicode character charCode into 'verts' & 'uvs' + void GetCharacterRenderInfo( unsigned int charCode, Rectf& verts, Rectf& uvs, bool &flipped ) const; + void GetCharacterRenderInfo( unsigned int charCode, int size, unsigned int style, Rectf& verts, Rectf& uvs, bool &flipped ) const; + + + /// Get the width of a character + float GetCharacterWidth (unsigned int charCode, int size = 0, unsigned int style = kStyleDefault) const; + + /// Get the width of the tab character + float GetTabWidth () const; + + /// Get the material of this font. + PPtr<Material> GetMaterial () const { return m_DefaultMaterial; } + + /// Set the default material + void SetMaterial (PPtr<Material> material) {m_DefaultMaterial = material;} + + /// Get the texture of this font... + PPtr<Texture> GetTexture () const { return m_Texture; } + + /// Set the texture + void SetTexture(PPtr<Texture> texture) {m_Texture = texture;} + + /// Get the line spacing of this font + float GetLineSpacing (int size = 0) const; + + /// Get the line spacing of this font + void SetLineSpacing (float spacing) { m_LineSpacing = spacing; } + + /// Adds character info (only for non grid fonts) + void AddCharacterInfoEntry( const Rectf& uv, const Rectf& vert, float width, int character, bool flipped, int size = 0, unsigned int style = kStyleDefault); + + /// Set Ascii Start Offset + void SetAsciiStartOffset(short val) {m_AsciiStartOffset = val;} + + // Old "Grid fonts" where scaled differently when using non-pixel correct rendering then + // normal fonts. So we need this to emulate the effect. + float GetDeprecatedPixelScale () const { return m_PixelScale; } + + /// Set the Convert Case property + enum { kDynamicFont = -2, kUnicodeSet = -1, kDontConvertCase = 0, kUpperCase = 1, kLowerCase = 2, kCustomSet = 3 }; + void SetConvertCase(int val) {m_ConvertCase = val;} + int GetConvertCase() { return m_ConvertCase; } + + typedef vector_map<std::pair<UnicodeChar, UnicodeChar>, float, KerningCompare> KerningValues; + /// Get the kerning values for modifying in place + KerningValues &GetKerningValues() { return m_KerningValues; } + + /// Set the font size, called by the truetype font importer + void SetFontSize(int val) { m_FontSize = val; } + + /// Get the font size, used to find pixel height of the chars in the font + int GetFontSize() const { return m_FontSize; } + + void SetAscent(float val) { m_Ascent = val; } + float GetAscent() const { return m_Ascent; } + + void SetCharacterSpacing (int val) { m_CharacterSpacing = val; } + void SetCharacterPadding (int val) { m_CharacterPadding = val; } + + /// Does this font have a definition for a specific character? + bool HasCharacter (unsigned int unicodeChar, int size = 0, unsigned int style = kStyleDefault); + + bool HasCharacterInTexture (unsigned int unicodeChar, int size, unsigned int style); + + /// Dynamic Font stuff: + bool CacheFontForText (UInt16 *chars, int length, int size = 0, unsigned int style = kStyleDefault, std::vector<TextFormatChange> format = std::vector<TextFormatChange>()); + + FontNames &GetFontNames () { return m_FontNames; } + FontFallbacks &GetFontFallbacks () { return m_FallbackFonts; } + void SetFontNames (FontNames &names); + + UNITY_VECTOR(kMemFont,char) &GetFontData () { return m_FontData; } + void SetFontDefaultStyle (int style) { m_DefaultStyle = style; } + static void FrameComplete () { s_FrameCount++; } + bool ResetCachedTexture (); + + const CharacterInfos &GetCharacterInfos() { return m_CharacterRects; } + void SetCharacterInfos (CharacterInfos &infos) { m_CharacterRects = infos; CacheRects(); } + + void SetFontRenderingMode(int val) { m_FontRenderingMode = val; } + int GetFontRenderingMode() const { return m_FontRenderingMode; } +#if UNITY_EDITOR + void SetMinimalFontTextureSize (int size) { m_TexWidth = size; m_TexHeight = size; } +#endif + +protected: + + void AddRectToCache(CharacterInfo& info); + void CacheRects(); + + /// Helper: Get the glyph code from a character, remapping cases, etc... + unsigned int GetGlyphNo( unsigned int charCode ) const; + + // The kerning map + KerningValues m_KerningValues; + + // These are only used in grid fonts + typedef std::pair<int, float> IntFloatPair; + typedef UNITY_VECTOR(kMemFont,IntFloatPair) PerCharacterKerning; + + float m_Kerning;///< Kerning of space between characters (Smaller than 1.0 pulls them together, Larger pushes them out) + float m_LineSpacing; ///< Spacing between lines as multiplum of height of a character. + int m_CharacterSpacing; + int m_CharacterPadding; + int m_AsciiStartOffset; ///< What is the first ascii character in the texture. + int m_FontSize; + + int m_ConvertCase; ///< enum { Don't change case, Convert to upper case characters, Convert to lower case characters } + PPtr<Material> m_DefaultMaterial; + PPtr<Texture> m_Texture; + + // Legacy Grid font support + float m_PixelScale; + + struct TexturePosition { + int x, y; + + TexturePosition (int _x, int _y) : x(_x), y(_y) {} + + friend bool operator < (const TexturePosition& lhs, const TexturePosition& rhs) + { + if (lhs.x + lhs.y != rhs.x + rhs.y) + return lhs.x + lhs.y < rhs.x + rhs.y; + else + return lhs.x < rhs.x; + } + }; + + struct IntRect { + int x, y, width, height; + + IntRect (int _x, int _y, int _width, int _height) : x(_x), y(_y), width(_width), height(_height) {} + + inline bool Intersects (const IntRect &r) const + { + return r.x+r.width > x && r.y+r.height > y && r.x < x + width && r.y < y + height; + } + }; + + UNITY_VECTOR(kMemFont,CharacterInfo) m_CharacterRects; + vector_set<CharacterInfo> m_UnicodeCharacterRects; + UNITY_VECTOR(kMemFont,CharacterInfo) m_AsciiCharacterRects; + + // dynamic font stuff: + void ResetPackingData (); + void GrowTexture (int maxFontSize); + UInt16 *CollectAllUsedCharacters (UInt16 *chars, int &length, int *&sizes, unsigned int *&styles); + void SetupDynamicFont (); + bool HasCharacterDynamic (unsigned int unicodeChar); + + bool AddCharacterToTexture (unsigned int unicodeChar, int size, unsigned int style); + bool IsRectFree (const IntRect &r) const; + UInt8 *GetCharacterBitmap(unsigned int &charWidth, unsigned int &charHeight, unsigned int &bufferWidth, Rectf &vert, float &advance, unsigned int unicodeChar, int size, unsigned int style); + + UNITY_VECTOR(kMemFont,char) m_FontData; + FontNames m_FontNames; + FontFallbacks m_FallbackFonts; + UNITY_VECTOR(kMemFont,IntRect) m_IntRects; + UNITY_SET(kMemFont,TexturePosition) m_TexturePositions; + UNITY_SET(kMemFont,TexturePosition)::iterator m_TexturePositionsSearchPosition; + + unsigned int m_TexWidth; + unsigned int m_TexHeight; + unsigned int m_TexMargin; + unsigned int m_SubImageSize; + unsigned int m_SubImageIndex; + static unsigned int s_FrameCount; + unsigned int m_DefaultStyle; + float m_Ascent; + int m_FontRenderingMode; + + DynamicFontData m_DynamicData; + + friend struct DynamicFontData; +}; + +namespace GetFontsManager +{ + void StaticInitialize(); + void StaticDestroy(); +} + +void GetFontPaths (std::vector<std::string> &paths); +FontNames &GetFallbacks (); +bool GetFontMetadataPreset(const std::string& name, std::string& family_name, std::string& style_name, unsigned& style_flags, unsigned& face_flags); + + +struct ScriptingCharacterInfo +{ + int index; + Rectf uv, vert; + float width; + int size, style; + bool flipped; + + void CopyFrom(const Font::CharacterInfo& inData) + { + index = inData.index; + uv = inData.uv; + vert = inData.vert; + width = inData.width; + size = inData.size; + style = inData.style; + flipped = inData.flipped; + } + void CopyTo(Font::CharacterInfo& outData) + { + outData.index = index; + outData.uv = uv; + outData.vert = vert; + outData.width = width; + outData.size = size; + outData.style = style; + outData.flipped = flipped; + } +}; + +#endif diff --git a/Runtime/Filters/Misc/GetFonts.cpp b/Runtime/Filters/Misc/GetFonts.cpp new file mode 100644 index 0000000..37bdb11 --- /dev/null +++ b/Runtime/Filters/Misc/GetFonts.cpp @@ -0,0 +1,338 @@ +#include "UnityPrefix.h" +#include "Font.h" +#include "Runtime/Utilities/File.h" +#if UNITY_LINUX +#include <ftw.h> +#endif + +#if DYNAMICFONTMODE == kDynamicFontModeFreeType || DYNAMICFONTMODE == kDynamicFontModeStb + + +typedef std::vector<UnityStr> FontDirs; +static FontDirs* gFontDirs = NULL; + +struct _FontInfo +{ + const char* family_name; + const char* style_name; + unsigned style_flags; + unsigned face_flags; +}; +typedef std::map<std::string, _FontInfo> FontMetadataMap; +static FontMetadataMap* gFontMetadata = NULL; + +static FontNames* gFontFallbacks = NULL; + +namespace GetFontsManager +{ + void StaticInitialize() + { + gFontDirs = UNITY_NEW(FontDirs, kMemFont); + gFontMetadata = UNITY_NEW(FontMetadataMap, kMemFont); + gFontFallbacks = UNITY_NEW(FontNames, kMemFont); + } + + void StaticDestroy() + { + UNITY_DELETE(gFontDirs , kMemFont); + UNITY_DELETE(gFontMetadata, kMemFont); + UNITY_DELETE(gFontFallbacks, kMemFont); + } +} + + +FontNames &GetFallbacks () +{ + if (gFontFallbacks->empty()) + { + // Make Arial first fallback for consistency, as it widely available. + gFontFallbacks->push_back("Arial"); + // Arial Unicode MS covers almost all unicode scripts, and is available on OS X (>=10.5). + gFontFallbacks->push_back("Arial Unicode MS"); + // This should catch Unicode scripts on Windows, excluding Asian scripts + gFontFallbacks->push_back("Microsoft Sans Serif"); + // This should catch Chinese on windows + gFontFallbacks->push_back("Microsoft YaHei"); + // This should catch Korean on windows + gFontFallbacks->push_back("Gulim"); + // This should catch Japanese on windows + gFontFallbacks->push_back("MS Gothic"); +#if UNITY_ANDROID + // Android system font + gFontFallbacks->push_back("Roboto"); + gFontFallbacks->push_back("NanumGothic"); + gFontFallbacks->push_back("Droid Sans"); + gFontFallbacks->push_back("Droid Sans Japanese"); + gFontFallbacks->push_back("Droid Sans Fallback"); +#elif UNITY_IPHONE + gFontFallbacks->push_back("Hiragino Kaku Gothic ProN"); + gFontFallbacks->push_back("Heiti TC"); + gFontFallbacks->push_back("AppleGothic"); + gFontFallbacks->push_back(".LastResort"); +#elif UNITY_WP8 + gFontFallbacks->push_back("Yu Gothic"); // Japanese + gFontFallbacks->push_back("Microsoft NeoGothic"); // Korean + gFontFallbacks->push_back("SimSun"); // Chinese simplified + gFontFallbacks->push_back("Microsoft Mhei"); // Chinese traditional + gFontFallbacks->push_back("Urdu Typesetting"); // Arabic +#elif UNITY_TIZEN + gFontFallbacks->push_back("Tizen Sans"); + gFontFallbacks->push_back("Tizen Sans Japanese"); + gFontFallbacks->push_back("Tizen Sans Fallback"); +#endif +#if UNITY_LINUX + gFontFallbacks->push_back("FreeSans"); + gFontFallbacks->push_back("WenQuanYi Micro Hei"); +#endif + // Unicode debugging fallback: http://en.wikipedia.org/wiki/Fallback_font + gFontFallbacks->push_back("LastResort"); + } + return *gFontFallbacks; +} + +#if UNITY_LINUX +int callback(const char *fpath, const struct stat *sb, int typeflag) +{ + if (typeflag == FTW_D) + { + gFontDirs->push_back (fpath); + } + return 0; +} +#endif + +void GetFontPaths (std::vector<std::string> &paths) +{ + paths.clear(); + + // paths should not be garbaged by the repetitive + // content accumulation in the dirs vector + gFontDirs->clear(); + +// Xbox and Wii do not have GetFolderContentsAtPath(). +#if !UNITY_XENON && !UNITY_WII +#if UNITY_OSX + gFontDirs->push_back ("/System/Library/Fonts"); + gFontDirs->push_back ("/Library/Fonts"); + string homeDir = getenv ("HOME"); + gFontDirs->push_back (homeDir + "/Library/Fonts"); + +#elif UNITY_WINRT + gFontDirs->push_back ("C:\\Windows\\Fonts"); +#elif UNITY_WIN && !UNITY_WINRT + // It must be noted that Windows installation does not necessarily have to reside on C: disk + std::string win_dir; + win_dir.resize( MAX_PATH ); + UINT const dir_len = GetWindowsDirectoryA( &win_dir.front(), win_dir.size() ); + if( 0u == dir_len ) // The function has failed, so a default is as good as any other choice + { + gFontDirs->push_back ("C:\\Windows\\Fonts"); + } + else + { + std::string::size_type old_win_dir_size = win_dir.size(); + win_dir.resize( dir_len ); + + if ( dir_len > old_win_dir_size ) + { + // Absolutely unlikely, but possible; in such a case where the previous buffer was not enough + // to hold the path to the windows directory, we simply increase the size of the buffer + // and try again to fetch the directory name. + UINT const dir_len2 = GetWindowsDirectoryA( &win_dir.front(), win_dir.size() ); + if( (dir_len2 + 1u) == win_dir.size() ) + { + win_dir.pop_back(); // Remove the embedded null terminator + } + else + { + win_dir = "C:\\Windows"; // Seriously screwed up + } + } + + gFontDirs->push_back( PlatformAppendPathName(win_dir, "Fonts") ); + } + +#elif UNITY_LINUX + ftw ("/usr/share/fonts", callback, 16); +#elif UNITY_ANDROID + gFontDirs->push_back ("/system/fonts"); +#elif UNITY_IPHONE + #if TARGET_IPHONE_SIMULATOR + gFontDirs->push_back ("/Library/Fonts"); + #else + gFontDirs->push_back ("/System/Library/Fonts/Cache"); + #endif +#elif UNITY_TIZEN + gFontDirs->push_back ("/usr/share/fonts"); + gFontDirs->push_back ("/usr/share/fallback_fonts"); +#endif + + for(int i = 0; i < gFontDirs->size(); ++i) + { + std::set<std::string> dirPaths; + if ( GetFolderContentsAtPath( (*gFontDirs)[i], dirPaths ) ) + { + for (std::set<std::string>::iterator j = dirPaths.begin(); j != dirPaths.end(); j++) + { + std::string extension = GetPathNameExtension(*j); + ToLowerInplace(extension); + if (!StrCmp(extension.c_str(), "ttf") || !StrCmp(extension.c_str(), "ttc") || !StrCmp(extension.c_str(), "otf") || !StrCmp(extension.c_str(), "dfont")) + paths.push_back(*j); + } + } + } +#endif +} + + + +static void InitFontMetadataPreset(); +bool GetFontMetadataPreset(const std::string& name, std::string& family_name, std::string& style_name, unsigned& style_flags, unsigned& face_flags) +{ + if (gFontMetadata->empty()) + { + InitFontMetadataPreset(); + } + + FontMetadataMap::iterator it = gFontMetadata->find(name); + if (it != gFontMetadata->end()) + { + family_name = it->second.family_name; + style_name = it->second.style_name; + style_flags = it->second.style_flags; + face_flags = it->second.face_flags; + return true; + } + + return false; +} + +static void InitFontMetadataPreset() +{ +#if UNITY_IPHONE + // Reading font metada on iOS devices might take few seconds when missing OS cache, so keeping preset known font table there + // iOS 4.3 + (*gFontMetadata)["AppleColorEmoji"] = (_FontInfo){"Apple Color Emoji", "Regular", 0x0, 0x19}; + (*gFontMetadata)["AppleGothic"] = (_FontInfo){"AppleGothic", "Regular", 0x0, 0x39}; + (*gFontMetadata)["Arial"] = (_FontInfo){"Arial", "Regular", 0x0, 0x59}; + (*gFontMetadata)["ArialBold"] = (_FontInfo){"Arial", "Bold", 0x2, 0x59}; + (*gFontMetadata)["ArialBoldItalic"] = (_FontInfo){"Arial", "Bold Italic", 0x3, 0x59}; + (*gFontMetadata)["ArialHB"] = (_FontInfo){"Arial Hebrew", "Regular", 0x0, 0x19}; + (*gFontMetadata)["ArialHBBold"] = (_FontInfo){"Arial Hebrew", "Bold", 0x2, 0x19}; + (*gFontMetadata)["ArialItalic"] = (_FontInfo){"Arial", "Italic", 0x1, 0x59}; + (*gFontMetadata)["ArialRoundedMTBold"] = (_FontInfo){"Arial Rounded MT Bold", "Regular", 0x0, 0x59}; + (*gFontMetadata)["BanglaSangamMN"] = (_FontInfo){"Bangla Sangam MN", "Regular", 0x0, 0x19}; + (*gFontMetadata)["CourierNew"] = (_FontInfo){"Courier New", "Regular", 0x0, 0x1f}; + (*gFontMetadata)["CourierNewBold"] = (_FontInfo){"Courier New", "Bold", 0x2, 0x1d}; + (*gFontMetadata)["CourierNewBoldItalic"] = (_FontInfo){"Courier New", "Bold Italic", 0x3, 0x1d}; + (*gFontMetadata)["CourierNewItalic"] = (_FontInfo){"Courier New", "Italic", 0x1, 0x1d}; + (*gFontMetadata)["DB_LCD_Temp-Black"] = (_FontInfo){"DB LCD Temp", "Black", 0x0, 0x19}; + (*gFontMetadata)["DevanagariSangamMN"] = (_FontInfo){"Devanagari Sangam MN", "Regular", 0x0, 0x59}; + (*gFontMetadata)["Fallback"] = (_FontInfo){".PhoneFallback", "Regular", 0x0, 0x19}; + (*gFontMetadata)["GeezaPro"] = (_FontInfo){"Geeza Pro", "Regular", 0x0, 0x19}; + (*gFontMetadata)["GeezaProBold"] = (_FontInfo){"Geeza Pro", "Bold", 0x0, 0x19}; + (*gFontMetadata)["Georgia"] = (_FontInfo){"Georgia", "Regular", 0x0, 0x19}; + (*gFontMetadata)["GeorgiaBold"] = (_FontInfo){"Georgia", "Bold", 0x2, 0x19}; + (*gFontMetadata)["GeorgiaBoldItalic"] = (_FontInfo){"Georgia", "Bold Italic", 0x3, 0x19}; + (*gFontMetadata)["GeorgiaItalic"] = (_FontInfo){"Georgia", "Italic", 0x1, 0x19}; + (*gFontMetadata)["GujaratiSangamMN"] = (_FontInfo){"Gujarati Sangam MN", "Regular", 0x0, 0x19}; + (*gFontMetadata)["GurmukhiMN"] = (_FontInfo){"Gurmukhi MN", "Regular", 0x0, 0x59}; + (*gFontMetadata)["HKGPW3UI"] = (_FontInfo){".HKGPW3UI", "Regular", 0x0, 0x59}; + (*gFontMetadata)["HiraginoKakuGothicProNW3"] = (_FontInfo){"Hiragino Kaku Gothic ProN", "W3", 0x0, 0x39}; + (*gFontMetadata)["HiraginoKakuGothicProNW6"] = (_FontInfo){"Hiragino Kaku Gothic ProN", "W6", 0x2, 0x39}; + (*gFontMetadata)["Kailasa"] = (_FontInfo){"Kailasa", "Bold", 0x0, 0x59}; + (*gFontMetadata)["KannadaSangamMN"] = (_FontInfo){"Kannada Sangam MN", "Regular", 0x0, 0x19}; + (*gFontMetadata)["LastResort"] = (_FontInfo){".LastResort", "Regular", 0x0, 0x19}; + (*gFontMetadata)["LockClock"] = (_FontInfo){".Lock Clock", "Light", 0x0, 0x59}; + (*gFontMetadata)["MalayalamSangamMN"] = (_FontInfo){"Malayalam Sangam MN", "Regular", 0x0, 0x19}; + (*gFontMetadata)["OriyaSangamMN"] = (_FontInfo){"Oriya Sangam MN", "Regular", 0x0, 0x19}; + (*gFontMetadata)["PhoneKeyCaps"] = (_FontInfo){".PhoneKeyCaps", "Regular", 0x2, 0x59}; + (*gFontMetadata)["PhoneKeyCapsTwo"] = (_FontInfo){".PhoneKeyCapsTwo", "Regular", 0x2, 0x59}; + (*gFontMetadata)["PhonepadTwo"] = (_FontInfo){".PhonepadTwo", "Regular", 0x0, 0x59}; + (*gFontMetadata)["STHeiti-Light"] = (_FontInfo){"Heiti TC", "Light", 0x0, 0x1b}; + (*gFontMetadata)["STHeiti-Medium"] = (_FontInfo){"Heiti TC", "Medium", 0x2, 0x19}; + (*gFontMetadata)["SinhalaSangamMN"] = (_FontInfo){"Sinhala Sangam MN", "Regular", 0x0, 0x59}; + (*gFontMetadata)["TamilSangamMN"] = (_FontInfo){"Tamil Sangam MN", "Regular", 0x0, 0x19}; + (*gFontMetadata)["TeluguSangamMN"] = (_FontInfo){"Telugu Sangam MN", "Regular", 0x0, 0x19}; + (*gFontMetadata)["Thonburi"] = (_FontInfo){"Thonburi", "Regular", 0x0, 0x19}; + (*gFontMetadata)["ThonburiBold"] = (_FontInfo){"Thonburi", "Bold", 0x2, 0x19}; + (*gFontMetadata)["TimesNewRoman"] = (_FontInfo){"Times New Roman", "Regular", 0x0, 0x59}; + (*gFontMetadata)["TimesNewRomanBold"] = (_FontInfo){"Times New Roman", "Bold", 0x2, 0x59}; + (*gFontMetadata)["TimesNewRomanBoldItalic"] = (_FontInfo){"Times New Roman", "Bold Italic", 0x3, 0x59}; + (*gFontMetadata)["TimesNewRomanItalic"] = (_FontInfo){"Times New Roman", "Italic", 0x1, 0x59}; + (*gFontMetadata)["TrebuchetMS"] = (_FontInfo){"Trebuchet MS", "Regular", 0x0, 0x59}; + (*gFontMetadata)["TrebuchetMSBold"] = (_FontInfo){"Trebuchet MS", "Bold", 0x2, 0x59}; + (*gFontMetadata)["TrebuchetMSBoldItalic"] = (_FontInfo){"Trebuchet MS", "Bold Italic", 0x3, 0x59}; + (*gFontMetadata)["TrebuchetMSItalic"] = (_FontInfo){"Trebuchet MS", "Italic", 0x1, 0x59}; + (*gFontMetadata)["Verdana"] = (_FontInfo){"Verdana", "Regular", 0x0, 0x59}; + (*gFontMetadata)["VerdanaBold"] = (_FontInfo){"Verdana", "Bold", 0x2, 0x19}; + (*gFontMetadata)["VerdanaBoldItalic"] = (_FontInfo){"Verdana", "Bold Italic", 0x3, 0x19}; + (*gFontMetadata)["VerdanaItalic"] = (_FontInfo){"Verdana", "Italic", 0x1, 0x19}; + (*gFontMetadata)["Zapfino"] = (_FontInfo){"Zapfino", "Regular", 0x1, 0x19}; + (*gFontMetadata)["_H_AmericanTypewriter"] = (_FontInfo){"American Typewriter", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_Baskerville"] = (_FontInfo){"Baskerville", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_ChalkboardSE"] = (_FontInfo){"Chalkboard SE", "Light", 0x0, 0x59}; + (*gFontMetadata)["_H_Cochin"] = (_FontInfo){"Cochin", "Regular", 0x0, 0x5b}; + (*gFontMetadata)["_H_Courier"] = (_FontInfo){"Courier", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_Futura"] = (_FontInfo){"Futura", "Medium", 0x0, 0x19}; + (*gFontMetadata)["_H_Helvetica"] = (_FontInfo){"Helvetica", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_HelveticaNeue"] = (_FontInfo){"Helvetica Neue", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_HelveticaNeueExtras"] = (_FontInfo){"Helvetica Neue", "Light", 0x0, 0x19}; + (*gFontMetadata)["_H_MarkerFeltThin"] = (_FontInfo){"Marker Felt", "Thin", 0x0, 0x59}; + (*gFontMetadata)["_H_MarkerFeltWide"] = (_FontInfo){"Marker Felt", "Wide", 0x2, 0x59}; + (*gFontMetadata)["_H_Noteworthy"] = (_FontInfo){"Noteworthy", "Light", 0x0, 0x59}; + (*gFontMetadata)["_H_Palatino"] = (_FontInfo){"Palatino", "Regular", 0x0, 0x59}; + (*gFontMetadata)["_H_SnellRoundhand"] = (_FontInfo){"Snell Roundhand", "Regular", 0x0, 0x59}; + (*gFontMetadata)["_H__PO_Bodoni-Ornaments"] = (_FontInfo){"Bodoni Ornaments", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_Bodoni72-Book-SmallCaps"] = (_FontInfo){"Bodoni 72 Smallcaps", "Book", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_Bodoni72-OldStyle"] = (_FontInfo){"Bodoni 72 Oldstyle", "Book", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_Bodoni72"] = (_FontInfo){"Bodoni 72", "Book", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_BradleyHand-Bold"] = (_FontInfo){"Bradley Hand", "Bold", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_Didot"] = (_FontInfo){"Didot", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_GillSans"] = (_FontInfo){"Gill Sans", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_HoeflerText"] = (_FontInfo){"Hoefler Text", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_Optima"] = (_FontInfo){"Optima", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_PartyLET"] = (_FontInfo){"Party LET", "Plain", 0x0, 0x19}; + (*gFontMetadata)["_H__PO_ZapfDingbats"] = (_FontInfo){"Zapf Dingbats", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_PO_AcademyEngraved"] = (_FontInfo){"Academy Engraved LET", "Plain", 0x0, 0x19}; + (*gFontMetadata)["_PO_Chalkduster"] = (_FontInfo){"Chalkduster", "Regular", 0x0, 0x59}; + (*gFontMetadata)["_PO_Copperplate"] = (_FontInfo){"Copperplate", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_PO_HiraginoMinchoProNW3"] = (_FontInfo){"Hiragino Mincho ProN", "W3", 0x0, 0x39}; + (*gFontMetadata)["_PO_HiraginoMinchoProNW6"] = (_FontInfo){"Hiragino Mincho ProN", "W6", 0x2, 0x39}; + (*gFontMetadata)["_PO_Papyrus"] = (_FontInfo){"Papyrus", "Regular", 0x0, 0x19}; +// iOS 5.0 + (*gFontMetadata)["AcademyEngraved"] = (_FontInfo){"Academy Engraved LET", "Plain", 0x0, 0x19}; + (*gFontMetadata)["Chalkduster"] = (_FontInfo){"Chalkduster", "Regular", 0x0, 0x59}; + (*gFontMetadata)["Copperplate"] = (_FontInfo){"Copperplate", "Regular", 0x0, 0x19}; + (*gFontMetadata)["EuphemiaCAS"] = (_FontInfo){"Euphemia UCAS", "Regular", 0x0, 0x19}; + (*gFontMetadata)["HiraginoMinchoProNW3"] = (_FontInfo){"Hiragino Mincho ProN", "W3", 0x0, 0x39}; + (*gFontMetadata)["HiraginoMinchoProNW6"] = (_FontInfo){"Hiragino Mincho ProN", "W6", 0x2, 0x39}; + (*gFontMetadata)["Papyrus"] = (_FontInfo){"Papyrus", "Regular", 0x0, 0x19}; + (*gFontMetadata)["STFangsongCore"] = (_FontInfo){".STFangsongCore", "Regular", 0x0, 0x39}; + (*gFontMetadata)["STKaitiCore"] = (_FontInfo){".STKaitiCore", "Regular", 0x0, 0x39}; + (*gFontMetadata)["STSongCore"] = (_FontInfo){".STSongCore", "Regular", 0x0, 0x39}; + (*gFontMetadata)["_H_Bodoni-Ornaments"] = (_FontInfo){"Bodoni Ornaments", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_Bodoni72-Book-SmallCaps"] = (_FontInfo){"Bodoni 72 Smallcaps", "Book", 0x0, 0x19}; + (*gFontMetadata)["_H_Bodoni72-OldStyle"] = (_FontInfo){"Bodoni 72 Oldstyle", "Book", 0x0, 0x19}; + (*gFontMetadata)["_H_Bodoni72"] = (_FontInfo){"Bodoni 72", "Book", 0x0, 0x19}; + (*gFontMetadata)["_H_BradleyHand-Bold"] = (_FontInfo){"Bradley Hand", "Bold", 0x0, 0x19}; + (*gFontMetadata)["_H_Didot"] = (_FontInfo){"Didot", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_GillSans"] = (_FontInfo){"Gill Sans", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_HoeflerText"] = (_FontInfo){"Hoefler Text", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_Marion"] = (_FontInfo){"Marion", "Regular", 0x0, 0x59}; + (*gFontMetadata)["_H_Optima"] = (_FontInfo){"Optima", "Regular", 0x0, 0x19}; + (*gFontMetadata)["_H_PartyLET"] = (_FontInfo){"Party LET", "Plain", 0x0, 0x19}; + (*gFontMetadata)["_H_ZapfDingbats"] = (_FontInfo){"Zapf Dingbats", "Regular", 0x0, 0x19}; +// iOS 6.0 + (*gFontMetadata)["AppleColorEmoji@2x"] = (_FontInfo){"Apple Color Emoji", "Regular", 0x0, 0x39}; + (*gFontMetadata)["AppleSDGothicNeoBold"] = (_FontInfo){"Apple SD Gothic Neo", "Bold", 0x0, 0x39}; + (*gFontMetadata)["AppleSDGothicNeoMedium"] = (_FontInfo){"Apple SD Gothic Neo", "Medium", 0x0, 0x39}; + (*gFontMetadata)["Symbol"] = (_FontInfo){"Symbol", "Regular", 0x0, 0x1b}; + (*gFontMetadata)["_H_Avenir"] = (_FontInfo){"Avenir", "Book", 0x0, 0x59}; + (*gFontMetadata)["_H_AvenirNext"] = (_FontInfo){"Avenir Next", "Bold", 0x2, 0x59}; + (*gFontMetadata)["_H_AvenirNextCondensed"] = (_FontInfo){"Avenir Next Condensed", "Bold", 0x2, 0x59}; + +#endif +} + +#endif diff --git a/Runtime/Filters/Misc/LineBuilder.cpp b/Runtime/Filters/Misc/LineBuilder.cpp new file mode 100644 index 0000000..5ef855e --- /dev/null +++ b/Runtime/Filters/Misc/LineBuilder.cpp @@ -0,0 +1,95 @@ +#include "UnityPrefix.h" +#include "LineBuilder.h" +#include "Runtime/Math/Matrix4x4.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Shaders/GraphicsCaps.h" +#include "Runtime/GfxDevice/GfxDevice.h" + +inline Vector2f Calculate2DLineExtrusionAverage (const Vector3f& p0, const Vector3f& delta, const Vector3f& delta2, float halfWidth) +{ + Vector2f dif; + dif.x = p0.y * delta.z - p0.z * delta.y; + dif.y = p0.z * delta.x - p0.x * delta.z; +// dif = NormalizeFast(dif); + + Vector2f dif2; + dif2.x = p0.y * delta2.z - p0.z * delta2.y; + dif2.y = p0.z * delta2.x - p0.x * delta2.z; +// dif2 = NormalizeFast(dif2); + + dif += dif2; + dif = NormalizeFast(dif); + + dif.x *= halfWidth; + dif.y *= halfWidth; + + return dif; +/* Vector2f dif; + dif.x = p0.y * delta.z - p0.z * delta.y; + dif.y = p0.z * delta.x - p0.x * delta.z; +*/ +} + +/// \todo have optional input lengths for speed +/// \todo optimize the dif cross product (z is unused) +void Build3DLine( LineParameters *param, const Vector3f *inVertices, int vertexCount ) +{ + Assert(vertexCount > 1); + Assert(param->outVertices && param->outAABB); + + LineVertex *outVertices = param->outVertices; + Matrix4x4f matrix = param->cameraTransform; + + // As Gradient->GetFixed() needs an unnormalized position in 16.16 format + // (upper 16 color index, lower 16 - how far between), the max value is + // (nr of gradient colors - 1)*2^16 - 1 + float fixedMult = (float)(((k3DLineGradientSize - 1) << 16) - 1); + + GfxDevice& device = GetGfxDevice(); + + // Skip last vertex + Vector3f delta = matrix.MultiplyPoint3 (inVertices[0]) - matrix.MultiplyPoint3 (inVertices[1]); + for (int i=0;i<vertexCount;i++) + { + // Don't accumulate by adding deltaU, as the rounding error accumulates as well. + // Calculate u each time anew instead. + float u = i/(float)(vertexCount - 1); + + // Calculate width and figure a cross section that faces the camera + Vector3f p0 = matrix.MultiplyPoint3 (inVertices[i]); + + if (i+1 != vertexCount) + { + Vector3f p1 = matrix.MultiplyPoint3 (inVertices[i+1]); + delta = p0 - p1; + } + + float width = Lerp(param->startWidth, param->endWidth, u); + Vector2f dif = Calculate2DLineExtrusion (p0, delta, width * 0.5F); + + ColorRGBA32 color; + if(param->gradient) + color = param->gradient->GetFixed (UInt32(u*fixedMult)); + else + // TODO: rewrite Gradient, so that we can elegantly use it here as well + color = Lerp((ColorRGBAf)param->color1, (ColorRGBAf)param->color2, u); + + // Swizzle color of the renderer requires it + color = device.ConvertToDeviceVertexColor(color); + + // One vertex + outVertices->vert.Set( p0.x - dif.x, p0.y - dif.y, p0.z ); + outVertices->color = color; + outVertices->uv.Set( u, 1.0f ); + ++outVertices; + + // And another vertex + outVertices->vert.Set( p0.x + dif.x, p0.y + dif.y, p0.z ); + outVertices->color = color; + outVertices->uv.Set( u, 0.0f ); + ++outVertices; + + param->outAABB->Encapsulate (inVertices[i]); + } + param->outAABB->Encapsulate (inVertices[vertexCount-1]); +} diff --git a/Runtime/Filters/Misc/LineBuilder.h b/Runtime/Filters/Misc/LineBuilder.h new file mode 100644 index 0000000..ffc46a8 --- /dev/null +++ b/Runtime/Filters/Misc/LineBuilder.h @@ -0,0 +1,88 @@ +#ifndef LINEBUILDER_H +#define LINEBUILDER_H + +class MinMaxAABB; + +#include "Runtime/Math/Gradient.h" +#include "Runtime/Math/Matrix4x4.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Color.h" + +enum { k3DLineGradientSize = 5 }; + +struct LineVertex { + Vector3f vert; + ColorRGBA32 color; + Vector2f uv; +}; + +// Settings for the Build3DLine function +// Instead of passing lots of parameters, make one of these and use that instead. +struct LineParameters +{ + DECLARE_SERIALIZE (LineParameters) + + LineVertex* outVertices; // Output vertices; 2 * input vertices size + class MinMaxAABB *outAABB; // AABB to be generated + + // ptr to the gradient used for color generation + GradientDeprecated<k3DLineGradientSize> *gradient; + ColorRGBA32 color1; + ColorRGBA32 color2; + Matrix4x4f cameraTransform; + + float startWidth; ///< The width (in worldspace) at the line start. + float endWidth; ///< The width (in worldspace) at the line end. + + LineParameters () : + outVertices (NULL), outAABB (NULL), + gradient (NULL), startWidth (1), endWidth (1), + color1 (0), color2 (0) { cameraTransform = Matrix4x4f::identity; } +}; + +template<class TransferFunction> +inline void LineParameters::Transfer (TransferFunction& transfer) { + TRANSFER_SIMPLE (startWidth); + TRANSFER_SIMPLE (endWidth); + transfer.Transfer (color1, "m_StartColor", kSimpleEditorMask); + transfer.Transfer (color2, "m_EndColor", kSimpleEditorMask); +} + +/// build the mesh for a 3D line segement seen from the current camera +/// @param param generation parameters. +/// @param in ptr to the input vertices +/// @param vertexCount the number of vertices in inVertices +void Build3DLine (LineParameters *param, const Vector3f *in, int vertexCount); + +/// Calculates the 2D line extrusion, so that the line is halfWidth * 2 wide and always faces the viewer. +/// The start point is p0, the endpoint is p0 + delta +/// The points are expected to be in camera space. +inline Vector2f Calculate2DLineExtrusion (const Vector3f& p0, const Vector3f& delta, float halfWidth) +{ + #if 1 + Vector2f dif; + dif.x = p0.y * delta.z - p0.z * delta.y; + dif.y = p0.z * delta.x - p0.x * delta.z; + + dif = NormalizeFast(dif); + + dif.x *= halfWidth; + dif.y *= halfWidth; + + return dif; + + #else + + Vector3f dif = Cross (p0, delta); + dif = NormalizeFast (dif); + + dif.x *= halfWidth; + dif.y *= halfWidth; + + return Vector2f (dif.x, dif.y); + + #endif +} + +#endif diff --git a/Runtime/Filters/Misc/LineRenderer.cpp b/Runtime/Filters/Misc/LineRenderer.cpp new file mode 100644 index 0000000..1bab153 --- /dev/null +++ b/Runtime/Filters/Misc/LineRenderer.cpp @@ -0,0 +1,195 @@ +#include "UnityPrefix.h" +#include "LineRenderer.h" +#include "Runtime/Mono/MonoManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Camera/Camera.h" +#include "Runtime/Shaders/VBO.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Profiler/Profiler.h" + +IMPLEMENT_CLASS_INIT_ONLY (LineRenderer) +IMPLEMENT_OBJECT_SERIALIZE (LineRenderer) + +LineRenderer::LineRenderer (MemLabelId label, ObjectCreationMode mode) +: Super(kRendererLine, label, mode) +{ + SetVisible (false); +} + +LineRenderer::~LineRenderer () +{ +} + +void LineRenderer::InitializeClass () +{ + RegisterAllowNameConversion (LineRenderer::GetClassStringStatic(), "m_WorldSpace", "m_UseWorldSpace"); +} + + +void LineRenderer::SetVertexCount(int count) +{ + if(count < 0) + { + count = 0; + ErrorString ("LineRenderer.SetVertexCount: Vertex count can't be set to negative value!"); + } + UpdateManagerState( true ); + m_Positions.resize(count); + SetVisible (m_Positions.size() >= 2); + SetDirty(); + BoundsChanged(); +} + +void LineRenderer::AwakeFromLoad(AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad(awakeMode); + SetVisible (m_Positions.size() >= 2); + + if ((awakeMode & kDidLoadFromDisk) == 0) + BoundsChanged(); +} + +void LineRenderer::SetPosition (int index, const Vector3f& position) +{ + SetDirty(); + UpdateManagerState( true ); + if (index < m_Positions.size() && index >= 0) + m_Positions[index] = position; + else + ErrorString("LineRenderer.SetPosition index out of bounds!"); + BoundsChanged(); +} + +PROFILER_INFORMATION(gSubmitVBOProfileLine, "Mesh.SubmitVBO", kProfilerRender) + + +void LineRenderer::Render (int subsetIndex, const ChannelAssigns& channels) +{ + if( m_Positions.size() < 2 ) + return; + + Vector3f* lineInVerts = NULL; + ALLOC_TEMP(lineInVerts, Vector3f, m_Positions.size()); + + MinMaxAABB mmAABB = MinMaxAABB(Vector3f::zero, Vector3f::zero); + + if (m_UseWorldSpace) + { + memcpy (lineInVerts, &m_Positions[0], m_Positions.size()*sizeof(Vector3f)); + } + else + { + Transform& tc = GetComponent(Transform); + int idx = 0; + for (PositionVector::iterator j = m_Positions.begin(); j != m_Positions.end(); ++j, ++idx) + { + lineInVerts[idx] = tc.TransformPoint(*j); + } + } + + // Get VBO chunk + int stripCount = m_Positions.size() * 2; + GfxDevice& device = GetGfxDevice(); + DynamicVBO& vbo = device.GetDynamicVBO(); + LineVertex* vbPtr; + if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor), + stripCount, 0, + DynamicVBO::kDrawTriangleStrip, + (void**)&vbPtr, NULL ) ) + { + return; + } + + // Generate line into the chunk + m_Parameters.outVertices = vbPtr; + m_Parameters.outAABB = &mmAABB; + m_Parameters.cameraTransform = GetCurrentCamera().GetWorldToCameraMatrix(); + Build3DLine (&m_Parameters, lineInVerts, m_Positions.size()); + + vbo.ReleaseChunk( stripCount, 0 ); + + // We can't set the view matrix since that breaks shadow maps (case 490315) + // Set the world matrix instead so it cancels out the usual view matrix + // i.e. it transforms from camera space back to world space + device.SetWorldMatrix(GetCurrentCamera().GetCameraToWorldMatrix().GetPtr()); + + if (m_CustomProperties) + device.SetMaterialProperties (*m_CustomProperties); + + PROFILER_BEGIN(gSubmitVBOProfileLine, this) + vbo.DrawChunk (channels); + GPU_TIMESTAMP(); + PROFILER_END +} + +void LineRenderer::UpdateTransformInfo () +{ + const Transform& transform = GetTransform(); + if (m_TransformDirty) + { + m_TransformInfo.invScale = 1.0f; + // will return a cached matrix most of the time + m_TransformInfo.transformType = transform.CalculateTransformMatrix (m_TransformInfo.worldMatrix);; + } + + if (m_BoundsDirty) + { + MinMaxAABB minmax; + minmax.Init(); + for (PositionVector::const_iterator i = m_Positions.begin(), itEnd = m_Positions.end(); i != itEnd; ++i) + minmax.Encapsulate (*i); + + if (m_UseWorldSpace) + { + m_TransformInfo.worldAABB = minmax; + TransformAABB (m_TransformInfo.worldAABB, transform.GetWorldToLocalMatrix(), m_TransformInfo.localAABB); + } + else + { + m_TransformInfo.localAABB = minmax; + TransformAABB (m_TransformInfo.localAABB, transform.GetLocalToWorldMatrix(), m_TransformInfo.worldAABB); + } + } +} + + + +void LineRenderer::UpdateRenderer() +{ + Super::UpdateRenderer(); + if (m_BoundsDirty) + { + BoundsChanged(); + } +} + +void LineRenderer::Reset() +{ + Super::Reset(); + m_UseWorldSpace = true; + m_Positions.clear(); + m_Positions.push_back (Vector3f (0,0,0)); + m_Positions.push_back (Vector3f (0,0,1)); + m_Parameters.color1 = ColorRGBA32(255, 255, 255, 255); + m_Parameters.color2 = ColorRGBA32(255, 255, 255, 255); + SetVisible (true); +} + +void LineRenderer::SetUseWorldSpace (bool space) +{ + m_UseWorldSpace = space; + SetDirty(); + UpdateManagerState( true ); + BoundsChanged(); +} + +template<class TransferFunction> inline +void LineRenderer::Transfer (TransferFunction& transfer) { + Super::Transfer (transfer); + TRANSFER_SIMPLE (m_Positions); + TRANSFER_SIMPLE (m_Parameters); + TRANSFER (m_UseWorldSpace); +} diff --git a/Runtime/Filters/Misc/LineRenderer.h b/Runtime/Filters/Misc/LineRenderer.h new file mode 100644 index 0000000..3d05758 --- /dev/null +++ b/Runtime/Filters/Misc/LineRenderer.h @@ -0,0 +1,61 @@ +#ifndef LINERENDERER_H +#define LINERENDERER_H + +#include <vector> +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Geometry/AABB.h" +#include "LineBuilder.h" + + + +// Renders a freeform texture/colored line in 3D space. +// (heavily based on TrailRenderer code, so most comments apply to both) +class LineRenderer : public Renderer { +public: + REGISTER_DERIVED_CLASS (LineRenderer, Renderer) + DECLARE_OBJECT_SERIALIZE (LineRenderer) + + LineRenderer (MemLabelId label, ObjectCreationMode mode); + + virtual void Reset (); + + virtual void Render (int materialIndex, const ChannelAssigns& channels); + + // Can operate in either local or world space, so we need to fill whole transform info ourselves + virtual void UpdateTransformInfo(); + + void SetPosition (int index, const Vector3f& position); + + void SetVertexCount(int count); + + void SetColors(const ColorRGBAf& c0, const ColorRGBAf& c1) { m_Parameters.color1 = c0; m_Parameters.color2 = c1; SetDirty(); } + + void SetWidth(float startWidth,float endWidth) + { + m_Parameters.startWidth = startWidth; + m_Parameters.endWidth = endWidth; + BoundsChanged(); + SetDirty(); + } + + bool GetUseWorldSpace () { return m_UseWorldSpace; } + void SetUseWorldSpace (bool space); + + void AwakeFromLoad(AwakeFromLoadMode mode); + static void InitializeClass (); + +protected: + // from Renderer + virtual void UpdateRenderer(); + +private: +// bool m_BoundsDirty; + bool m_UseWorldSpace; ///< Draw lines in worldspace (or localspace) + LineParameters m_Parameters; + typedef UNITY_VECTOR(kMemRenderer,Vector3f) PositionVector; + PositionVector m_Positions; +}; + +#endif diff --git a/Runtime/Filters/Misc/MiniCoreText.h b/Runtime/Filters/Misc/MiniCoreText.h new file mode 100644 index 0000000..1027645 --- /dev/null +++ b/Runtime/Filters/Misc/MiniCoreText.h @@ -0,0 +1,38 @@ +#if !defined(MINICORETEXT_H) +#define MINICORETEXT_H + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef float CGFloat; +typedef const struct __CTFont * CTFontRef; +typedef const struct __CTFontDescriptor * CTFontDescriptorRef; +typedef uint32_t CTFontOrientation; +typedef const struct __CTLine * CTLineRef; + +enum { + kCTFontDefaultOrientation = 0, +}; + +extern const CFStringRef kCTForegroundColorAttributeName WEAK_IMPORT_ATTRIBUTE; +extern const CFStringRef kCTFontAttributeName WEAK_IMPORT_ATTRIBUTE; + + +CTFontRef CTFontCreateWithName(CFStringRef name, CGFloat size, const CGAffineTransform *matrix) WEAK_IMPORT_ATTRIBUTE; +CTFontRef CTFontCreateWithGraphicsFont(CGFontRef graphicsFont, CGFloat size, const CGAffineTransform *matrix, CTFontDescriptorRef attributes) WEAK_IMPORT_ATTRIBUTE; +CGFloat CTFontGetAscent(CTFontRef font) WEAK_IMPORT_ATTRIBUTE; +CGFloat CTFontGetDescent(CTFontRef font) WEAK_IMPORT_ATTRIBUTE; +Boolean CTFontGetGlyphsForCharacters(CTFontRef font, const UniChar characters[], CGGlyph glyphs[], CFIndex count) WEAK_IMPORT_ATTRIBUTE; +CGRect CTFontGetBoundingRectsForGlyphs(CTFontRef font, CTFontOrientation orientation, const CGGlyph glyphs[], CGRect boundingRects[], CFIndex count ) WEAK_IMPORT_ATTRIBUTE; +double CTFontGetAdvancesForGlyphs(CTFontRef font, CTFontOrientation orientation, const CGGlyph glyphs[], CGSize advances[], CFIndex count) WEAK_IMPORT_ATTRIBUTE; +CTLineRef CTLineCreateWithAttributedString(CFAttributedStringRef string) WEAK_IMPORT_ATTRIBUTE; +void CTLineDraw(CTLineRef line, CGContextRef context) WEAK_IMPORT_ATTRIBUTE; +CTFontRef CTFontCreateForString(CTFontRef currentFont, CFStringRef string, CFRange range) WEAK_IMPORT_ATTRIBUTE; +CGFontRef CGFontCreateWithDataProvider(CGDataProviderRef provider) WEAK_IMPORT_ATTRIBUTE; + +#if defined(__cplusplus) +} +#endif + +#endif
\ No newline at end of file diff --git a/Runtime/Filters/Misc/TextMesh.cpp b/Runtime/Filters/Misc/TextMesh.cpp new file mode 100644 index 0000000..80b6b76 --- /dev/null +++ b/Runtime/Filters/Misc/TextMesh.cpp @@ -0,0 +1,298 @@ +#include "UnityPrefix.h" +#include "TextMesh.h" +#include "Runtime/Filters/Mesh/MeshRenderer.h" +#include "Runtime/IMGUI/TextMeshGenerator2.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Font.h" +#include "Runtime/Misc/ResourceManager.h" +#include "Runtime/Filters/Mesh/LodMesh.h" + +using namespace std; +namespace TextMesh_Static +{ +static Font* gDefaultFont = NULL; +} + + +TextMesh::TextMesh (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_Mesh = NULL; + m_FontSize = 0; + m_FontStyle = 0; + m_RichText = true; + m_Color = 0xffffffff; +} + +TextMesh::~TextMesh () +{ + DestroySingleObject(m_Mesh); +} + +Mesh* TextMesh::GetMesh () +{ + if (m_Mesh) + return m_Mesh; + else + { + m_Mesh = NEW_OBJECT (Mesh); + m_Mesh->Reset(); + m_Mesh->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + + m_Mesh->SetHideFlags(kHideAndDontSave); + return m_Mesh; + } +} + +void TextMesh::Reset () { + Super::Reset(); + + m_OffsetZ = 0.0f; + m_CharacterSize = 1.0f; + m_Anchor = kUpperLeft; + m_Alignment = kLeft; + m_LineSpacing = 1.0F; + m_TabSize = 4.0F; +} + +Font * TextMesh::GetFont () const { + using namespace TextMesh_Static; + Font *f = m_Font; + if (!f) { + if (!gDefaultFont) + gDefaultFont = GetBuiltinResource<Font> (kDefaultFontName); + return gDefaultFont; + } + else { + return f; + } +} + +void TextMesh::AwakeFromLoad(AwakeFromLoadMode awakeMode) { + Super::AwakeFromLoad (awakeMode); + + if (IsActive()) + { + SetupMeshRenderer (); + ApplyToMesh (); + } +} + +void TextMesh::SetText (const string& text) { + if (m_Text != text) + { + m_Text = text; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetFont (PPtr<Font> font) +{ + if (m_Font != font) + { + m_Font = font; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetFontSize (int size) +{ + if (m_FontSize != size) + { + m_FontSize = size; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetFontStyle (int style) +{ + if (m_FontStyle != style) + { + m_FontStyle = style; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetAlignment(short alignment) +{ + if (m_Alignment != alignment) + { + m_Alignment = alignment; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetOffsetZ(float offset) +{ + if (m_OffsetZ != offset) + { + m_OffsetZ = offset; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetAnchor(short anchor) +{ + if (m_Anchor != anchor) + { + m_Anchor = anchor; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetCharacterSize(float characterSize) +{ + if (m_CharacterSize != characterSize) + { + m_CharacterSize = characterSize; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetLineSpacing(float lineSpacing) +{ + if (m_LineSpacing != lineSpacing) + { + m_LineSpacing = lineSpacing; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetTabSize(float tabSize) +{ + if (m_TabSize != tabSize) + { + m_TabSize = tabSize; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetRichText(bool richText) +{ + if (m_RichText != richText) + { + m_RichText = richText; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetColor(const ColorRGBA32 color) +{ + if (m_Color != color) + { + m_Color = color; + ApplyToMesh(); + } + SetDirty(); +} + +void TextMesh::SetupMeshRenderer () { + if (IsActive ()) + { + MeshRenderer* renderer = QueryComponent(MeshRenderer); + if (renderer) + renderer->SetSharedMesh(GetMesh()); + } +} + +void TextMesh::DidAddComponent () { + if (IsActive ()) + { + MeshRenderer* renderer = QueryComponent(MeshRenderer); + if (renderer) + renderer->SetSharedMesh(GetMesh()); + } +} + +void TextMesh::ApplyToMesh () +{ + Mesh * mesh = GetMesh(); + // Setup textmesh generator + TextMeshGenerator2 &tmgen = TextMeshGenerator2::Get (UTF16String(m_Text.c_str()), GetFont (), (TextAnchor)m_Anchor, (TextAlignment)m_Alignment, 0, m_TabSize, m_LineSpacing, m_RichText, false, m_Color, m_FontSize, m_FontStyle); + + Vector2f size = tmgen.GetSize (); + Vector2f offset = tmgen.GetTextOffset (Rectf (0, 0, -size.x, size.y * 2)); + switch (m_Alignment) + { + case kRight: offset.x += size.x; break; + case kCenter: offset.x += size.x * 0.5f; break; + } + + Mesh* srcMesh = tmgen.GetMesh (); + Matrix4x4f m; + Vector3f scale(m_CharacterSize, -m_CharacterSize, m_CharacterSize); + scale *= GetFont()->GetDeprecatedPixelScale (); + m.SetTranslate (Vector3f(offset.x * scale.x, offset.y * -scale.y, m_OffsetZ)); + m.Scale(scale); + mesh->CopyTransformed(*srcMesh, m); + + // Mesh CopyTransformed does not update local AABB! Kind of scared to change it there, + // so instead manually transform the AABB here. + const AABB& bounds = mesh->GetLocalAABB(); + AABB xformBounds; + TransformAABB (bounds, m, xformBounds); + mesh->SetLocalAABB (xformBounds); + + MeshRenderer* meshRenderer = QueryComponent(MeshRenderer); + if (meshRenderer) + meshRenderer->SetSharedMesh(mesh); +} + +IMPLEMENT_CLASS_HAS_INIT (TextMesh) +IMPLEMENT_OBJECT_SERIALIZE (TextMesh) + +template<class TransferFunction> inline +void TextMesh::Transfer (TransferFunction& transfer) +{ + transfer.SetVersion (3); + Super::Transfer (transfer); + TRANSFER(m_Text); + TRANSFER(m_OffsetZ); + TRANSFER(m_CharacterSize); + TRANSFER(m_LineSpacing); + TRANSFER(m_Anchor); + TRANSFER(m_Alignment); + TRANSFER(m_TabSize); + + TRANSFER(m_FontSize); + TRANSFER(m_FontStyle); + + TRANSFER(m_RichText); + + transfer.Align(); + + TRANSFER (m_Font); + TRANSFER (m_Color); + + + #if UNITY_EDITOR + // Renamed m_Settings to m_Font in version 1.2.2 + if (transfer.IsOldVersion(1)) + { + transfer.Transfer(m_Font, "m_Settings"); + } + + // In version 1.5.0 line spacing is multiplicative instead of additive + if (transfer.IsOldVersion(1) || transfer.IsOldVersion(2)) + { + Font* font = GetFont(); + m_LineSpacing = (font->GetLineSpacing() + m_LineSpacing) / font->GetLineSpacing(); + } + #endif +} + +void TextMesh::InitializeClass () +{ + REGISTER_MESSAGE_VOID (TextMesh, kDidAddComponent, DidAddComponent); +} diff --git a/Runtime/Filters/Misc/TextMesh.h b/Runtime/Filters/Misc/TextMesh.h new file mode 100644 index 0000000..1a71c8b --- /dev/null +++ b/Runtime/Filters/Misc/TextMesh.h @@ -0,0 +1,99 @@ +#ifndef TEXTMESH_H +#define TEXTMESH_H + +#include "Runtime/Filters/Mesh/Mesh.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Geometry/AABB.h" +#include <string> +#include <vector> + +using std::vector; +class Font; +class Mesh; + + + + +class TextMesh : public Unity::Component { + public: + REGISTER_DERIVED_CLASS (TextMesh, Component) + DECLARE_OBJECT_SERIALIZE (TextMesh) + + TextMesh (MemLabelId label, ObjectCreationMode mode); + // ~TextMesh (); declared-by-macro + + const UnityStr& GetText () const { return m_Text; } + void SetText (const std::string& text); + + void SetFont (PPtr<Font> font); + Font * GetFont () const; + + void SetFontSize (int size); + int GetFontSize() const { return m_FontSize; } + + void SetFontStyle (int style); + int GetFontStyle() const { return m_FontStyle; } + + void SetOffsetZ(float offset); + float GetOffsetZ(){ return m_OffsetZ; } + + void SetAlignment(short alignment); + short GetAlignment(){ return m_Alignment; } + + void SetAnchor(short anchor); + short GetAnchor(){ return m_Anchor; } + + void SetCharacterSize(float characterSize); + float GetCharacterSize(){ return m_CharacterSize; } + + void SetLineSpacing(float lineSpacing); + float GetLineSpacing(){ return m_LineSpacing; } + + void SetTabSize(float tabSize); + float GetTabSize(){ return m_TabSize; } + + void SetRichText(bool richText); + bool GetRichText() { return m_RichText; } + + void SetColor(const ColorRGBA32 color); + ColorRGBA32 GetColor() const { return m_Color; } + + void AwakeFromLoad(AwakeFromLoadMode mode); + + virtual void Reset (); + void DidAddComponent (); + + static void InitializeClass(); + static void CleanupClass() {} + + void ApplyToMesh (); + private: + + void SetupMeshRenderer(); + + UnityStr m_Text; + + PPtr<Font> m_Font; + + float m_OffsetZ; ///< How much to offset the generated mesh from the Z-position=0. + short m_Alignment; ///< enum { left, center, right } + short m_Anchor; ///< Where the text-mesh is anchored related to local origo. enum { upper left, upper center, upper right, middle left, middle center, middle right, lower left, lower center, lower right } + float m_CharacterSize; ///< Size of one character (as its height, since Aspect may change its width) + float m_LineSpacing; ///< Spacing between lines as multiplum of height of a character. + float m_TabSize; ///< Length of one tab + + int m_FontSize; ///<The font size to use. Set to 0 to use default font size. Only applicable for dynamic fonts. + int m_FontStyle; ///<The font style to use. Only applicable for dynamic fonts. enum { Normal, Bold, Italic, Bold and Italic } + + ColorRGBA32 m_Color; + bool m_RichText; + + Mesh* GetMesh (); + + Mesh* m_Mesh; +}; + +#endif diff --git a/Runtime/Filters/Misc/TrailRenderer.cpp b/Runtime/Filters/Misc/TrailRenderer.cpp new file mode 100644 index 0000000..c6208ee --- /dev/null +++ b/Runtime/Filters/Misc/TrailRenderer.cpp @@ -0,0 +1,197 @@ +#include "UnityPrefix.h" +#include "TrailRenderer.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Camera/Camera.h" +#include "Runtime/GameCode/DestroyDelayed.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Shaders/VBO.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Profiler/Profiler.h" + +const float kMinSqrDistance = 0.1f * 0.1f; + +IMPLEMENT_CLASS_INIT_ONLY (TrailRenderer) +IMPLEMENT_OBJECT_SERIALIZE (TrailRenderer) + +void TrailRenderer::InitializeClass () +{ + REGISTER_MESSAGE (TrailRenderer, kTransformChanged, TransformChanged, int); +} + +TrailRenderer::TrailRenderer (MemLabelId label, ObjectCreationMode mode) +: Super(kRendererTrail, label, mode) +, m_TransformChanged(false) +, m_WasRendered(false) +, m_CurrentLength(0) +, m_Time(0) +, m_MinVertexDistance(0) +, m_Autodestruct(false) +{ + m_AABB = MinMaxAABB (Vector3f::zero, Vector3f::zero); +} + +TrailRenderer::~TrailRenderer () +{ +} + +void TrailRenderer::Reset () { + Super::Reset (); + m_Colors[0].Set (255,255,255,255); + m_Colors[1].Set (255,255,255,255); + m_Colors[2].Set (255,255,255,255); + m_Colors[3].Set (255,255,255,255); + m_Colors[4].Set (255,255,255,0); + m_Time = 5.0f; + m_TransformChanged = true; + m_MinVertexDistance = 0.1F; + m_Positions.clear(); + m_TimeStamps.clear(); +} + +void TrailRenderer::UpdateRenderer() +{ + Super::UpdateRenderer(); + + float now = GetCurTime (); + // Remove last vertrices if neccessary + while (!m_TimeStamps.empty() && now > m_TimeStamps.back() + m_Time) { + m_Positions.pop_back(); + m_TimeStamps.pop_back(); + } + + // Add a vertex to the object + if (m_TransformChanged) { + Vector3f position = GetComponent (Transform).GetPosition (); + if( m_Positions.empty () || SqrMagnitude (m_Positions.front () - position) > m_MinVertexDistance*m_MinVertexDistance ) + { + m_Positions.push_front (position); + m_TimeStamps.push_front (now); + } + + float halfWidth = GetHalfMaxLineWidth (); + AABB newPosAABB (m_Positions.front (), Vector3f(halfWidth, halfWidth, halfWidth)); + + // Expand the BBox with the new transform position. + m_AABB.Encapsulate (newPosAABB); + BoundsChanged (); + } + + if (m_Positions.size() < 2) { + if (m_Autodestruct && m_WasRendered && IsWorldPlaying ()) + DestroyObjectDelayed (GetGameObjectPtr()); + } + else + m_WasRendered = true; + + // Important: update manager state after calling any SetVisible() above. Fixes an issue + // where trails would stop be rendered when object is disabled or stops moving. + UpdateManagerState( true ); + + m_TransformChanged = false; +} + +float TrailRenderer::GetHalfMaxLineWidth () const +{ + return std::max (m_LineParameters.endWidth, m_LineParameters.startWidth) * 0.5f; +} + +PROFILER_INFORMATION(gTrailRenderProfile, "TrailRenderer.Render", kProfilerRender) +PROFILER_INFORMATION(gSubmitVBOProfileTrail, "Mesh.SubmitVBO", kProfilerRender) + +void TrailRenderer::Render (int materialIndex, const ChannelAssigns& channels) +{ + PROFILER_AUTO(gTrailRenderProfile, this) + + int size = m_Positions.size(); + if( size < 2 ) + return; + + Vector3f* trailInVerts = NULL; + ALLOC_TEMP(trailInVerts, Vector3f, size); + int idx = 0; + for (std::list<Vector3f>::iterator j = m_Positions.begin(); j != m_Positions.end(); ++j, ++idx) + { + trailInVerts[idx] = *j; + } + trailInVerts[0] = GetComponent(Transform).GetPosition(); + + // Get VBO chunk + int stripCount = size * 2; + GfxDevice& device = GetGfxDevice(); + DynamicVBO& vbo = device.GetDynamicVBO(); + LineVertex* vbPtr; + if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor), + stripCount, 0, + DynamicVBO::kDrawTriangleStrip, + (void**)&vbPtr, NULL ) ) + { + return; + } + + // Generate line into the chunk + MinMaxAABB aabb; + m_LineParameters.outVertices = vbPtr; + m_LineParameters.gradient = &m_Colors; + m_LineParameters.cameraTransform = GetCurrentCamera().GetWorldToCameraMatrix(); + m_LineParameters.outAABB = &aabb; + Build3DLine( &m_LineParameters, trailInVerts, size ); + + vbo.ReleaseChunk( stripCount, 0 ); + + aabb.Expand (GetHalfMaxLineWidth ()); + + if (!CompareMemory (m_AABB, aabb)) + { + m_AABB = aabb; + BoundsChanged (); + } + + // We can't set the view matrix since that breaks shadow maps (case 490315) + // Set the world matrix instead so it cancels out the usual view matrix + // i.e. it transforms from camera space back to world space + device.SetWorldMatrix(GetCurrentCamera().GetCameraToWorldMatrix().GetPtr()); + + if (m_CustomProperties) + device.SetMaterialProperties (*m_CustomProperties); + + PROFILER_BEGIN(gSubmitVBOProfileTrail, this) + vbo.DrawChunk (channels); + GPU_TIMESTAMP(); + PROFILER_END +} + +void TrailRenderer::TransformChanged (int changeMask) +{ + Renderer::TransformChanged (changeMask); + m_TransformChanged = true; +} + +void TrailRenderer::UpdateTransformInfo() +{ + const Transform& t = GetComponent (Transform); + + TransformType type = t.CalculateTransformMatrix (m_TransformInfo.worldMatrix); + m_TransformInfo.transformType = type; + m_TransformInfo.invScale = 1.0f; + + m_TransformInfo.worldAABB = m_AABB; + InverseTransformAABB( m_TransformInfo.worldAABB, t.GetPosition(), t.GetRotation(), m_TransformInfo.localAABB ); +} + +template<class TransferFunction> inline +void TrailRenderer::Transfer (TransferFunction& transfer) { + Super::Transfer (transfer); + transfer.Transfer (m_Time, "m_Time", kSimpleEditorMask); + transfer.Transfer (m_LineParameters.startWidth, "m_StartWidth", kSimpleEditorMask); + transfer.Transfer (m_LineParameters.endWidth, "m_EndWidth", kSimpleEditorMask); + TRANSFER_SIMPLE (m_Colors); + + TRANSFER(m_MinVertexDistance); + + transfer.Transfer (m_Autodestruct, "m_Autodestruct"); + if (transfer.IsReading () && !m_Autodestruct) + m_WasRendered = false; +} diff --git a/Runtime/Filters/Misc/TrailRenderer.h b/Runtime/Filters/Misc/TrailRenderer.h new file mode 100644 index 0000000..4535d64 --- /dev/null +++ b/Runtime/Filters/Misc/TrailRenderer.h @@ -0,0 +1,62 @@ +#ifndef TRAILRENDERER_H +#define TRAILRENDERER_H + +#include <list> +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Math/Gradient.h" +#include "Runtime/Math/Vector3.h" +#include "LineBuilder.h" +#include "Runtime/Geometry/AABB.h" + + + +// Renders a trail after an object. +// This is good for smoke trails after missiles or a visual FX after strong lights. +// @todo make it work so we track movement over a Cameras screen space. +class TrailRenderer : public Renderer { +public: + REGISTER_DERIVED_CLASS (TrailRenderer, Renderer) + DECLARE_OBJECT_SERIALIZE (TrailRenderer) + + TrailRenderer (MemLabelId label, ObjectCreationMode mode); + + virtual void Reset (); + + virtual void Render (int materialIndex, const ChannelAssigns& channels); + + // Hook up to TransformChanged + static void InitializeClass (); + + // TransformChanged message handler + void TransformChanged (int changeMask); + + virtual void UpdateTransformInfo(); + + GET_SET_DIRTY(float, Time, m_Time) + GET_SET_DIRTY(float, MinVertexDistance, m_MinVertexDistance) + GET_SET_DIRTY(float, StartWidth, m_LineParameters.startWidth) + GET_SET_DIRTY(float, EndWidth, m_LineParameters.endWidth) + GET_SET_DIRTY(bool, Autodestruct, m_Autodestruct) + +protected: + // from Renderer + virtual void UpdateRenderer(); + +private: + float GetHalfMaxLineWidth () const; + + bool m_TransformChanged; // Has the transform changed since last render? + bool m_WasRendered; // Trail was rendered so enable autodestruct + std::list<Vector3f> m_Positions; // The positions for each of the centers + std::list<float> m_TimeStamps; // The timestamp for each position + int m_CurrentLength; // The current length in vertices + MinMaxAABB m_AABB; // The size in world coords + + GradientDeprecated<k3DLineGradientSize> m_Colors; + LineParameters m_LineParameters; + float m_Time; ///< How long the tail should be (seconds). { 0, infinity} + float m_MinVertexDistance; ///< The minimum distance to spawn a new point on the trail range { 0, infinity} + bool m_Autodestruct; ///< Destroy GameObject when there is no trail? +}; + +#endif diff --git a/Runtime/Filters/Particles/EllipsoidParticleEmitter.cpp b/Runtime/Filters/Particles/EllipsoidParticleEmitter.cpp new file mode 100644 index 0000000..0401fa2 --- /dev/null +++ b/Runtime/Filters/Particles/EllipsoidParticleEmitter.cpp @@ -0,0 +1,110 @@ +#include "UnityPrefix.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" +#include "EllipsoidParticleEmitter.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" + +using namespace std; + +Rand gEllipsoidEmitterRand (3); + +EllipsoidParticleEmitter::EllipsoidParticleEmitter (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_Ellipsoid = Vector3f (1, 1, 1); + m_MinEmitterRange = 0.0F; +} + +EllipsoidParticleEmitter::~EllipsoidParticleEmitter () +{ +} + +static void ResetEllipsoidEmitterRand () +{ + gEllipsoidEmitterRand.SetSeed (3); +} + +void EllipsoidParticleEmitter::InitializeClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Register(ResetEllipsoidEmitterRand); +} + +void EllipsoidParticleEmitter::CleanupClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Unregister(ResetEllipsoidEmitterRand); +} + + +void EllipsoidParticleEmitter::SetupParticles ( + ParticleArray& particles, + const Vector3f& velocityOffset, + const Matrix3x3f& rotation, + int firstIndex) +{ + float deltaTime = GetDeltaTime (); + MinMaxAABB& aabb = m_PrivateInfo.aabb; + for (int i = firstIndex;i<particles.size ();i++) + { + SetupParticle (particles[i], velocityOffset, rotation, deltaTime); + aabb.Encapsulate (particles[i].position); + } +} + +void EllipsoidParticleEmitter::SetupParticle ( + Particle& p, + const Vector3f& velocityOffset, + const Matrix3x3f& rotation, + float deltaTime) +{ + InitParticleEnergy(gEllipsoidEmitterRand, p, deltaTime); + + // Set particle starting position + p.position = m_PreviousEmitterPos; + p.position += velocityOffset * RangedRandom (gEllipsoidEmitterRand, 0.0F, deltaTime); + p.position += (m_EmitterPos - m_PreviousEmitterPos) * Random01 (gEllipsoidEmitterRand); + Vector3f insideEllipsoidPosition = RandomPointBetweenEllipsoid (gEllipsoidEmitterRand, m_Ellipsoid, m_MinEmitterRange); + p.position += rotation.MultiplyVector3 (insideEllipsoidPosition); + + // Set velocity + p.velocity = velocityOffset; + p.velocity += rotation.MultiplyVector3 (RandomPointInsideEllipsoid (gEllipsoidEmitterRand, m_RndVelocity)); + + p.rotation = m_RndInitialRotations ? RangedRandom (gEllipsoidEmitterRand, 0.0f, 2*kPI):0.0F; + float angularVelocity = m_AngularVelocity; +#if SUPPORT_REPRODUCE_LOG + if (m_RndAngularVelocity > Vector3f::epsilon) +#endif + angularVelocity += RangedRandom (gEllipsoidEmitterRand, -m_RndAngularVelocity, m_RndAngularVelocity); + p.angularVelocity = Deg2Rad(angularVelocity); + +// Vector3f ellipsoidRelativeDirection = NormalizeSafe (insideEllipsoidPosition); +// p.velocity += rotation.MultiplyVector3 (Cross (ellipsoidRelativeDirection, info.tangentVelocity)); + + Vector3f uTangent (insideEllipsoidPosition.z, 0.0F, -insideEllipsoidPosition.x); + Vector3f vTangent (insideEllipsoidPosition.x, 0.0F, -insideEllipsoidPosition.y); + uTangent = NormalizeSafe (uTangent); + vTangent = NormalizeSafe (vTangent); + Vector3f normal = NormalizeSafe (insideEllipsoidPosition); + p.velocity += rotation.MultiplyVector3 (uTangent * m_TangentVelocity.x); + p.velocity += rotation.MultiplyVector3 (vTangent * m_TangentVelocity.y); + p.velocity += rotation.MultiplyVector3 (normal * m_TangentVelocity.z); + + p.color = ColorRGBA32 (255, 255, 255, 255); + + // Set size + p.size = RangedRandom (gEllipsoidEmitterRand, m_MinSize, m_MaxSize); +} + +IMPLEMENT_CLASS_HAS_INIT (EllipsoidParticleEmitter) +IMPLEMENT_OBJECT_SERIALIZE (EllipsoidParticleEmitter) + +template<class TransferFunction> inline +void EllipsoidParticleEmitter::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.Align(); + TRANSFER_SIMPLE (m_Ellipsoid); + TRANSFER (m_MinEmitterRange); +} diff --git a/Runtime/Filters/Particles/EllipsoidParticleEmitter.h b/Runtime/Filters/Particles/EllipsoidParticleEmitter.h new file mode 100644 index 0000000..8a93dda --- /dev/null +++ b/Runtime/Filters/Particles/EllipsoidParticleEmitter.h @@ -0,0 +1,35 @@ +#ifndef ELLIPSOIDPARTICLEEMITTER_H +#define ELLIPSOIDPARTICLEEMITTER_H + +#include "ParticleEmitter.h" +#include "Runtime/Math/Vector3.h" +#include "ParticleStruct.h" + +class Matrix4x4f; + + + +class EllipsoidParticleEmitter : public ParticleEmitter +{ +public: + REGISTER_DERIVED_CLASS (EllipsoidParticleEmitter, ParticleEmitter) + DECLARE_OBJECT_SERIALIZE (EllipsoidParticleEmitter) + EllipsoidParticleEmitter (MemLabelId label, ObjectCreationMode mode); + + static void InitializeClass (); + static void CleanupClass (); + +private: + + void SetupParticle (Particle& p, const Vector3f& velocityOffset, const Matrix3x3f& rotation, float deltaTime); + virtual void SetupParticles (ParticleArray& particles, const Vector3f& velocityOffset, + const Matrix3x3f& rotation, int firstIndex); + +public: + Vector3f m_Ellipsoid; ///< Size of emission area + float m_MinEmitterRange;///< [0...1] relative range to maxEmitterSize where particles will not be spawned + // 0 means that a full ellipsoid will be filled with particles + // 1 means only the outline will be filled with particles +}; + +#endif diff --git a/Runtime/Filters/Particles/MeshParticleEmitter.cpp b/Runtime/Filters/Particles/MeshParticleEmitter.cpp new file mode 100644 index 0000000..16ea50c --- /dev/null +++ b/Runtime/Filters/Particles/MeshParticleEmitter.cpp @@ -0,0 +1,353 @@ +#include "UnityPrefix.h" +#include "MeshParticleEmitter.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Filters/Mesh/MeshUtility.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" + +using namespace std; + +static Rand gMeshEmitterRand (4); + +MeshParticleEmitter::MeshParticleEmitter (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_VertexIndex = false; +} + +MeshParticleEmitter::~MeshParticleEmitter () +{ +} + +void MeshParticleEmitter::Reset () +{ + Super::Reset(); + m_InterpolateTriangles = false; + m_Systematic = false; + m_MinNormalVelocity = 0.0F; + m_MaxNormalVelocity = 0.0F; +} + +void MeshParticleEmitter::SetupParticles ( + ParticleArray& particles, + const Vector3f& velocityOffset, + const Matrix3x3f& rotation, + int firstIndex) +{ + Mesh* mesh = m_Mesh; + + MinMaxAABB& aabb = m_PrivateInfo.aabb; + + Matrix4x4f scale; + GetComponent (Transform).CalculateTransformMatrixScaleDelta (scale); + + Matrix3x3f rotationAndScale = Matrix3x3f(scale); + rotationAndScale = rotation * rotationAndScale; + rotationAndScale.InvertTranspose (); + + float deltaTime = GetDeltaTime (); + + // If there's an invalid mesh, then just emit from the center + if (mesh == NULL || mesh->GetSubMeshCount () == 0 || mesh->GetSubMeshFast (0).indexCount == 0 || !mesh->HasVertexData()) + { + Vector3f v; + StrideIterator<Vector3f> vertices(&v, 0); + StrideIterator<Vector3f> normals; + + for (int i = firstIndex;i<particles.size ();i++) + { + SetupParticle (0, particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals); + aabb.Encapsulate (particles[i].position); + } + return; + } + + SubMesh& submesh = mesh->GetSubMeshFast (0); + int vertexCount = mesh->GetVertexCount(); + + StrideIterator<Vector3f> vertices = mesh->GetVertexBegin(); + StrideIterator<Vector3f> normals = mesh->GetNormalBegin(); + const UInt16* buffer = mesh->GetSubMeshBuffer16(0); + + if (!m_Systematic) + { + if (m_InterpolateTriangles) + { + if (submesh.topology == kPrimitiveTriangleStripDeprecated) + { + for (int i = firstIndex;i<particles.size ();i++) + { + SetupParticleStrip (particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals, buffer, submesh.indexCount); + aabb.Encapsulate (particles[i].position); + } + } + else if (submesh.topology == kPrimitiveTriangles) + { + for (int i = firstIndex;i<particles.size ();i++) + { + SetupParticleTri (particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals, buffer, submesh.indexCount/3); + aabb.Encapsulate (particles[i].position); + } + } + } + else { + if (vertexCount != 0) + { + for (int i = firstIndex;i<particles.size ();i++) { + SetupParticle (RangedRandom (gMeshEmitterRand, 0, vertexCount), particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals); + aabb.Encapsulate (particles[i].position); + } + } + } + } + else { + if (vertexCount != 0) + { + // Just in case the mesh changed while the particle emitter was running + if (m_VertexIndex >= vertexCount) + m_VertexIndex = 0; + + for (int i = firstIndex;i<particles.size ();i++) { + SetupParticle (m_VertexIndex, particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals); + aabb.Encapsulate (particles[i].position); + + m_VertexIndex++; + if (m_VertexIndex >= vertexCount) + m_VertexIndex = 0; + } + } + } +} + +void MeshParticleEmitter::SetupParticle ( + int vertexIndex, + Particle& p, + const Vector3f& velocityOffset, + const Matrix4x4f& scale, + const Matrix3x3f& rotation, + const Matrix3x3f& normalTransform, + float deltaTime, + StrideIterator<Vector3f> vertices, + StrideIterator<Vector3f> normals) +{ + InitParticleEnergy(gMeshEmitterRand, p, deltaTime); + + // position/normal of particle is vertex/vertex normal from mesh + Vector3f positionOnMesh = vertices[vertexIndex]; + positionOnMesh = scale.MultiplyPoint3 (positionOnMesh); + + Vector3f normal; + if (!normals.IsNull ()) + { + normal = normalTransform.MultiplyVector3 (normals[vertexIndex]); + normal = NormalizeFast (normal); + } + else + normal = Vector3f(0,0,0); + + + // Set particle starting position + p.position = m_PreviousEmitterPos; + p.position += velocityOffset * RangedRandom (gMeshEmitterRand, 0.0F, deltaTime); + p.position += (m_EmitterPos - m_PreviousEmitterPos) * Random01 (gMeshEmitterRand); + p.position += rotation.MultiplyVector3 (positionOnMesh); + + // Set velocity + p.velocity = velocityOffset + normal * RangedRandom (gMeshEmitterRand, m_MinNormalVelocity, m_MaxNormalVelocity); + p.velocity += rotation.MultiplyVector3 (RandomPointInsideEllipsoid (gMeshEmitterRand, m_RndVelocity)); + + p.rotation = m_RndInitialRotations ? RangedRandom (gMeshEmitterRand, 0.0f, 2*kPI):0.0F; + float angularVelocity = m_AngularVelocity; +#if SUPPORT_REPRODUCE_LOG + if (m_RndAngularVelocity > Vector3f::epsilon) +#endif + angularVelocity += RangedRandom (gMeshEmitterRand, -m_RndAngularVelocity, m_RndAngularVelocity); + p.angularVelocity = Deg2Rad(angularVelocity); + + p.color = ColorRGBA32 (255, 255, 255, 255); + + // Set size + p.size = RangedRandom (gMeshEmitterRand, m_MinSize, m_MaxSize); +} + +void MeshParticleEmitter::SetupParticleTri ( + Particle& p, + const Vector3f& velocityOffset, + const Matrix4x4f& scale, + const Matrix3x3f& rotation, + const Matrix3x3f& normalTransform, + float deltaTime, + StrideIterator<Vector3f> vertices, + StrideIterator<Vector3f> normals, + const UInt16* indices, + int triCount) +{ + InitParticleEnergy(gMeshEmitterRand, p, deltaTime); + + int triIndex = RangedRandom (gMeshEmitterRand, 0, (int)triCount); + const UInt16* face = indices + triIndex * 3; + Vector3f barycenter = RandomBarycentricCoord (gMeshEmitterRand); + + // Interpolate vertex with barycentric coordinate + Vector3f positionOnMesh = barycenter.x * vertices[face[0]] + barycenter.y * vertices[face[1]] + barycenter.z * vertices[face[2]]; + positionOnMesh = scale.MultiplyPoint3 (positionOnMesh); + + Vector3f normal; + if (!normals.IsNull ()) + { + // Interpolate normal with barycentric coordinate + Vector3f const& normal1 = normals[face[0]]; + Vector3f const& normal2 = normals[face[1]]; + Vector3f const& normal3 = normals[face[2]]; + normal = barycenter.x * normal1 + barycenter.y * normal2 + barycenter.z * normal3; + normal = normalTransform.MultiplyVector3 (normal); + normal = NormalizeFast (normal); + } + else + normal = Vector3f(0,0,0); + + // Set particle starting position + p.position = m_PreviousEmitterPos; + p.position += velocityOffset * RangedRandom (gMeshEmitterRand, 0.0F, deltaTime); + p.position += (m_EmitterPos - m_PreviousEmitterPos) * Random01 (gMeshEmitterRand); + p.position += rotation.MultiplyVector3 (positionOnMesh); + + // Set velocity + p.velocity = velocityOffset + normal * RangedRandom (gMeshEmitterRand, m_MinNormalVelocity, m_MaxNormalVelocity); + p.velocity += rotation.MultiplyVector3 (RandomPointInsideEllipsoid (gMeshEmitterRand, m_RndVelocity)); + + p.rotation = m_RndInitialRotations ? RangedRandom (gMeshEmitterRand, 0.0f, 2*kPI):0.0F; + float angularVelocity = m_AngularVelocity; +#if SUPPORT_REPRODUCE_LOG + if (m_RndAngularVelocity > Vector3f::epsilon) +#endif + angularVelocity += RangedRandom (gMeshEmitterRand, -m_RndAngularVelocity, m_RndAngularVelocity); + p.angularVelocity = Deg2Rad(angularVelocity); + + p.color = ColorRGBA32 (255, 255, 255, 255); + + // Set size + p.size = RangedRandom (gMeshEmitterRand, m_MinSize, m_MaxSize); +} + +void MeshParticleEmitter::SetupParticleStrip ( + Particle& p, + const Vector3f& velocityOffset, + const Matrix4x4f& scale, + const Matrix3x3f& rotation, + const Matrix3x3f& normalTransform, + float deltaTime, + StrideIterator<Vector3f> vertices, + StrideIterator<Vector3f> normals, + const UInt16* strip, + int stripSize) +{ + InitParticleEnergy(gMeshEmitterRand, p, deltaTime); + + // Extract indices from tristrip + int stripIndex = RangedRandom (gMeshEmitterRand, 2, stripSize); + UInt16 a = strip[stripIndex-2]; + UInt16 b = strip[stripIndex-1]; + UInt16 c = strip[stripIndex]; + // Ignore degenerate triangles + while (a == b || a == c || b == c) + { + stripIndex = RangedRandom (gMeshEmitterRand, 2, stripSize); + a = strip[stripIndex-2]; + b = strip[stripIndex-1]; + c = strip[stripIndex]; + while (a == b || a == c || b == c) + { + stripIndex++; + if (stripIndex >= stripSize) + break; + a = strip[stripIndex-2]; + b = strip[stripIndex-1]; + c = strip[stripIndex]; + } + } + + Vector3f barycenter = RandomBarycentricCoord (gMeshEmitterRand); + + // Interpolate vertex with barycentric coordinate + Vector3f positionOnMesh = barycenter.x * vertices[a] + barycenter.y * vertices[b] + barycenter.z * vertices[c]; + positionOnMesh = scale.MultiplyPoint3 (positionOnMesh); + + + Vector3f normal; + if (!normals.IsNull ()) + { + // Interpolate normal with barycentric coordinate + Vector3f normal1 = normals[a]; + Vector3f normal2 = normals[b]; + Vector3f normal3 = normals[c]; + normal = barycenter.x * normal1 + barycenter.y * normal2 + barycenter.z * normal3; + normal = normalTransform.MultiplyVector3 (normal); + normal = NormalizeFast (normal); + } + else + { + normal = Vector3f(0,0,0); + } + + // Set particle starting position + p.position = m_PreviousEmitterPos; + p.position += velocityOffset * RangedRandom (gMeshEmitterRand, 0.0F, deltaTime); + p.position += (m_EmitterPos - m_PreviousEmitterPos) * Random01 (gMeshEmitterRand); + p.position += rotation.MultiplyVector3 (positionOnMesh); + + // Set velocity + p.velocity = velocityOffset + normal * RangedRandom (gMeshEmitterRand, m_MinNormalVelocity, m_MaxNormalVelocity); + p.velocity += rotation.MultiplyVector3 (RandomPointInsideEllipsoid (gMeshEmitterRand, m_RndVelocity)); + + p.rotation = m_RndInitialRotations ? RangedRandom (gMeshEmitterRand, 0.0f, 2*kPI):0.0F; + float angularVelocity = m_AngularVelocity; +#if SUPPORT_REPRODUCE_LOG + if (m_RndAngularVelocity > Vector3f::epsilon) +#endif + angularVelocity += RangedRandom (gMeshEmitterRand, -m_RndAngularVelocity, m_RndAngularVelocity); + p.angularVelocity = Deg2Rad(angularVelocity); + + p.color = ColorRGBA32 (255, 255, 255, 255); + + // Set size + p.size = RangedRandom (gMeshEmitterRand, m_MinSize, m_MaxSize); +} + +static void ResetRandSeedForMeshParticleEmitter () +{ + gMeshEmitterRand.SetSeed (4); +} + +void MeshParticleEmitter::InitializeClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Register(ResetRandSeedForMeshParticleEmitter); +} + +void MeshParticleEmitter::CleanupClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Unregister(ResetRandSeedForMeshParticleEmitter); +} + +IMPLEMENT_CLASS_HAS_INIT (MeshParticleEmitter) +IMPLEMENT_OBJECT_SERIALIZE (MeshParticleEmitter) + +template<class TransferFunction> inline +void MeshParticleEmitter::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + TRANSFER (m_InterpolateTriangles); + TRANSFER (m_Systematic); + transfer.Align(); + TRANSFER (m_MinNormalVelocity); + TRANSFER (m_MaxNormalVelocity); + TRANSFER (m_Mesh); +} + +void MeshParticleEmitter::SetMesh (PPtr<Mesh> mesh) +{ + m_Mesh = mesh; + SetDirty(); +} diff --git a/Runtime/Filters/Particles/MeshParticleEmitter.h b/Runtime/Filters/Particles/MeshParticleEmitter.h new file mode 100644 index 0000000..6ac74c1 --- /dev/null +++ b/Runtime/Filters/Particles/MeshParticleEmitter.h @@ -0,0 +1,42 @@ +#ifndef MESHPARTICLEEMITTER_H +#define MESHPARTICLEEMITTER_H + +#include "ParticleEmitter.h" +#include "ParticleStruct.h" +#include "Runtime/Filters/Mesh/LodMesh.h" + + + +class MeshParticleEmitter : public ParticleEmitter +{ +public: + REGISTER_DERIVED_CLASS (MeshParticleEmitter, ParticleEmitter) + DECLARE_OBJECT_SERIALIZE (MeshParticleEmitter) + MeshParticleEmitter (MemLabelId label, ObjectCreationMode mode); + + void SetMesh (PPtr<Mesh> mesh); + PPtr<Mesh> GetMesh () { return m_Mesh; } + + virtual void Reset (); + + static void InitializeClass (); + static void CleanupClass (); + +private: + void SetupParticle (int vertexIndex, Particle& p, const Vector3f& velocityOffset, const Matrix4x4f& scale, const Matrix3x3f& rotation, const Matrix3x3f& normalTransform, float deltaTime, StrideIterator<Vector3f> vertices, StrideIterator<Vector3f> normals); + void SetupParticleTri (Particle& p, const Vector3f& velocityOffset, const Matrix4x4f& scale, const Matrix3x3f& rotation, const Matrix3x3f& normalTransform, float deltaTime, StrideIterator<Vector3f> vertices, StrideIterator<Vector3f> normals, const UInt16* faces, int triCount); + void SetupParticleStrip (Particle& p, const Vector3f& velocityOffset, const Matrix4x4f& scale, const Matrix3x3f& rotation, const Matrix3x3f& normalTransform, float deltaTime, StrideIterator<Vector3f> vertices, StrideIterator<Vector3f> normals, const UInt16* strip, int stripSize); + + virtual void SetupParticles (ParticleArray& particles, const Vector3f& velocityOffset, + const Matrix3x3f& rotation, int firstIndex); + +private: + bool m_InterpolateTriangles; + bool m_Systematic; + float m_MinNormalVelocity; + float m_MaxNormalVelocity; + int m_VertexIndex; + PPtr<Mesh> m_Mesh; +}; + +#endif diff --git a/Runtime/Filters/Particles/ParticleAnimator.cpp b/Runtime/Filters/Particles/ParticleAnimator.cpp new file mode 100644 index 0000000..95ceb3a --- /dev/null +++ b/Runtime/Filters/Particles/ParticleAnimator.cpp @@ -0,0 +1,216 @@ +#include "UnityPrefix.h" +#include "ParticleAnimator.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Input/TimeManager.h" +#include "ParticleStruct.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/GameCode/DestroyDelayed.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" + +static Rand gParticleAnimRand (2); + +void ParticleAnimator::GetColorAnimation(ColorRGBAf *col) const +{ + for(int i=0;i<kColorKeys;i++) + col[i] = m_ColorAnimation[i]; +} + +void ParticleAnimator::SetColorAnimation(ColorRGBAf *col) +{ + for(int i=0;i<kColorKeys;i++) + m_ColorAnimation[i] = col[i]; +} + +void ParticleAnimator::UpdateAnimator( ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime ) +{ + // Quit updating if no new emissions and no old particles + if( particles.empty() ) + { + m_EnergylossFraction = 0.0F; + + if( WillAutoDestructIfNoParticles( privateInfo ) ) + DestroyObjectDelayed (GetGameObjectPtr()); + return; + } + if( m_Autodestruct == 1 ) + m_Autodestruct = 2; + + if( !m_StopSimulation ) + { + privateInfo.aabb.Init (); + UpdateParticles( particles, privateInfo, deltaTime ); + + //Particle size is multipled to calculate the maximum size + //Handle cases where grow size is negative also + float endSize = privateInfo.maxEmitterParticleSize * pow (1.0F + m_SizeGrow, privateInfo.maxEnergy); + privateInfo.maxParticleSize = m_SizeGrow < 0.0f ? privateInfo.maxEmitterParticleSize : endSize; + } +} + +bool ParticleAnimator::WillAutoDestructIfNoParticles( const PrivateParticleInfo& privateInfo ) const +{ + // Autodestruct destroys the GO if enabled AND either of those: + // * Particles have been emitted, but are all dead now. + // * Emit was once On, but now is Off. + if( m_Autodestruct != 0 && IsWorldPlaying() ) + { + if( m_Autodestruct == 2 || (privateInfo.hadEverEmitOn && !privateInfo.isEmitOn) ) + return true; + } + return false; +} + + +void ParticleAnimator::UpdateParticles (ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime) const +{ + Vector3f dtForce = m_Force * deltaTime; + Vector3f rotationAxis; + if (privateInfo.useWorldSpace) + rotationAxis = (m_WorldRotationAxis + GetComponent(Transform).TransformDirection(m_LocalRotationAxis)) * deltaTime; + else + rotationAxis = (GetComponent(Transform).InverseTransformDirection(m_WorldRotationAxis) + m_LocalRotationAxis) * deltaTime; + + float damping = pow (m_Damping, deltaTime); + Vector3f rndForce = m_RndForce * deltaTime; + + + const float kColorScale = kColorKeys - 1.0f; + + // The color keys are inverted + ColorRGBA32 colorAnimation[kColorKeys] = { m_ColorAnimation[4], m_ColorAnimation[3], m_ColorAnimation[2], m_ColorAnimation[1], m_ColorAnimation[0] }; + float sizeScale = pow (1.0F + m_SizeGrow, deltaTime); + float maxColorAnimationT = (float)kColorKeys - 1.0F - Vector3f::epsilon; + + bool doesAnimateColor = m_DoesAnimateColor; + int particleSize = particles.size (); + int i = 0; + while (i < particleSize) + { + Particle& p = particles[i]; + + // Update Alpha + p.energy -= deltaTime; + + if (p.energy <= 0.0f) { + // Kill particle (replace particle i with last, and continue updating the moved particle) + KillParticle (particles, i); + --particleSize; + continue; + } + + if( doesAnimateColor ) + { + // [0..kColorKeys-1] for [0..maxEnergy] + float t = p.energy * kColorScale / p.startEnergy; + t = FloatMin (t, maxColorAnimationT); + int baseColor = FloorfToIntPos (t); + DebugAssertIf( baseColor < 0 || baseColor >= kColorKeys - 1 ); + float frac = t - (float)baseColor; + int intFrac = RoundfToIntPos (frac * 255.0F); + p.color = Lerp (colorAnimation[baseColor], colorAnimation[baseColor + 1], intFrac); + } + + // Update Velocity + p.velocity *= damping; + + p.velocity += dtForce; + + p.velocity += RandomPointInsideCube (gParticleAnimRand, rndForce); + + p.velocity += Cross (rotationAxis, p.velocity); + + // Update position + p.position += p.velocity * deltaTime; + + // Update Size + p.size *= sizeScale; + + // Update Bounding Box + privateInfo.aabb.Encapsulate (p.position); + + p.rotation += p.angularVelocity * deltaTime; + + Prefetch(&particles[i] + 8); + Prefetch(&particles[particleSize-1]); + + i++; + } +} + +ParticleAnimator::ParticleAnimator(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_ColorAnimation[0] = ColorRGBA32 (255, 255, 255, 10); + m_ColorAnimation[1] = ColorRGBA32 (255, 255, 255, 180); + m_ColorAnimation[2] = ColorRGBA32 (255, 255, 255, 255); + m_ColorAnimation[3] = ColorRGBA32 (255, 255, 255, 180); + m_ColorAnimation[4] = ColorRGBA32 (255, 255, 255, 10); + + m_WorldRotationAxis = Vector3f::zero; + m_LocalRotationAxis = Vector3f::zero; + m_DoesAnimateColor = true; + m_RndForce = Vector3f::zero; + m_Force = Vector3f::zero; + m_SizeGrow = 0.0f; + m_Damping = 1.0f; + m_Autodestruct = 0; + m_StopSimulation = false; + m_EnergylossFraction = 0.0F; +} + +ParticleAnimator::~ParticleAnimator () +{ +} + +IMPLEMENT_CLASS_HAS_INIT (ParticleAnimator) +IMPLEMENT_OBJECT_SERIALIZE (ParticleAnimator) + +static void ResetRandSeed () +{ + gParticleAnimRand.SetSeed (2); +} + +void ParticleAnimator::InitializeClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Register(ResetRandSeed); +} + +void ParticleAnimator::CleanupClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Unregister(ResetRandSeed); +} + +template<class TransferFunction> inline +void ParticleAnimator::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + transfer.Transfer (m_DoesAnimateColor, "Does Animate Color?"); + transfer.Align(); + transfer.Transfer( m_ColorAnimation[0], "colorAnimation[0]", kSimpleEditorMask ); + transfer.Transfer( m_ColorAnimation[1], "colorAnimation[1]", kSimpleEditorMask ); + transfer.Transfer( m_ColorAnimation[2], "colorAnimation[2]", kSimpleEditorMask ); + transfer.Transfer( m_ColorAnimation[3], "colorAnimation[3]", kSimpleEditorMask ); + transfer.Transfer( m_ColorAnimation[4], "colorAnimation[4]", kSimpleEditorMask ); + DebugAssertIf (kColorKeys != 5); + + transfer.Transfer( m_WorldRotationAxis, "worldRotationAxis" ); + transfer.Transfer( m_LocalRotationAxis, "localRotationAxis" ); + transfer.Transfer( m_SizeGrow, "sizeGrow", kSimpleEditorMask ); + transfer.Transfer( m_RndForce, "rndForce", kSimpleEditorMask ); + transfer.Transfer( m_Force, "force", kSimpleEditorMask ); + transfer.Transfer( m_Damping, "damping", kSimpleEditorMask ); m_Damping = clamp<float> (m_Damping, 0.0f, 1.0f); + transfer.Transfer( m_StopSimulation, "stopSimulation", kHideInEditorMask ); + + bool autodestruct = m_Autodestruct; + transfer.Transfer (autodestruct, "autodestruct"); + if (transfer.IsReading ()) + { + if (autodestruct == 0) + m_Autodestruct = 0; + else if (m_Autodestruct == 0) + m_Autodestruct = 1; + } +} diff --git a/Runtime/Filters/Particles/ParticleAnimator.h b/Runtime/Filters/Particles/ParticleAnimator.h new file mode 100644 index 0000000..dde7b9d --- /dev/null +++ b/Runtime/Filters/Particles/ParticleAnimator.h @@ -0,0 +1,57 @@ +#ifndef PARTICLEANIMATOR_H +#define PARTICLEANIMATOR_H + +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Math/Color.h" +#include "ParticleStruct.h" + + + +class ParticleAnimator : public Unity::Component +{ +public: + REGISTER_DERIVED_CLASS (ParticleAnimator, Unity::Component) + DECLARE_OBJECT_SERIALIZE (ParticleAnimator) + + ParticleAnimator(MemLabelId label, ObjectCreationMode mode); + + static void InitializeClass (); + static void CleanupClass (); + + enum { kColorKeys = 5 }; + + GET_SET_DIRTY (Vector3f, WorldRotationAxis, m_WorldRotationAxis) + GET_SET_DIRTY (Vector3f, LocalRotationAxis, m_LocalRotationAxis) + GET_SET_DIRTY (Vector3f, RndForce, m_RndForce) + GET_SET_DIRTY (Vector3f, Force, m_Force) + GET_SET_DIRTY (float, Damping, m_Damping) + GET_SET_DIRTY (float, SizeGrow, m_SizeGrow) + GET_SET_DIRTY (bool, Autodestruct, m_Autodestruct) + GET_SET_DIRTY (bool, DoesAnimateColor, m_DoesAnimateColor) + GET_SET_DIRTY (bool, stopSimulation, m_StopSimulation) + + void GetColorAnimation(ColorRGBAf *col) const; + void SetColorAnimation(ColorRGBAf *col); + + void UpdateAnimator( ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime ); + + bool WillAutoDestructIfNoParticles( const PrivateParticleInfo& privateInfo ) const; + +private: + void UpdateParticles (ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime) const; + +private: + Vector3f m_WorldRotationAxis; // axis around which the particle rotates, worldspace + Vector3f m_LocalRotationAxis; // axis around which the particle rotates, localspace + Vector3f m_RndForce;// rnd force + Vector3f m_Force; // gravity value + float m_Damping; // damping value + float m_SizeGrow; // Grows the size of the particle by sizeGrow per second. A value of 1.0 doubles the particle size every second. + ColorRGBA32 m_ColorAnimation[kColorKeys]; // Animates the color through the color keys + int m_Autodestruct; + bool m_DoesAnimateColor; + bool m_StopSimulation; + float m_EnergylossFraction; +}; + +#endif diff --git a/Runtime/Filters/Particles/ParticleEmitter.cpp b/Runtime/Filters/Particles/ParticleEmitter.cpp new file mode 100644 index 0000000..e6507e7 --- /dev/null +++ b/Runtime/Filters/Particles/ParticleEmitter.cpp @@ -0,0 +1,455 @@ +#include "UnityPrefix.h" +#include "ParticleEmitter.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Serialize/PersistentManager.h" +#include "ParticleAnimator.h" +#include "WorldParticleCollider.h" +#include "ParticleRenderer.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" + + +using namespace std; + +static Rand gEmitterRand (5); + +enum { kMaxParticleCount = 65000 / 4 }; +const float kMaxEnergy = 1e20f; + +typedef List< ListNode<ParticleEmitter> > ParticleEmitterList; +static ParticleEmitterList gActiveEmitters; + + +ParticleEmitter::ParticleEmitter (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_EmittersListNode( this ) +{ + m_EmissionFrac = 0.0F; + m_Emit = true; + m_Enabled = true; + m_OneShot = false; + m_PrivateInfo.hadEverEmitOn = false; + m_PrivateInfo.isEmitOn = false; + + m_MinSize = 0.1F; + m_MaxSize = 0.1F; + m_MinEnergy = 3.0F; + m_MaxEnergy = 3.0F; + m_MinEmission = 50.0F; + m_MaxEmission = 50.0F; + m_UseWorldSpace = true; + m_EmitterVelocityScale = 0.05F; + m_WorldVelocity = Vector3f::zero; + m_LocalVelocity = Vector3f::zero; + m_TangentVelocity = Vector3f::zero; + m_RndVelocity = Vector3f::zero; + m_RndAngularVelocity = 0.0F; + m_AngularVelocity = 0.0F; + m_RndInitialRotations = false; + m_FirstFrame = true; + m_PrivateInfo.aabb.Init (); +} + +ParticleEmitter::~ParticleEmitter () +{ +} + +void ParticleEmitter::UpdateManagerState( bool updateParticles ) +{ + if( updateParticles == m_EmittersListNode.IsInList() ) + return; + if( updateParticles ) + gActiveEmitters.push_back(m_EmittersListNode); + else + m_EmittersListNode.RemoveFromList(); +} + +void ParticleEmitter::UpdateAllParticleSystems() +{ + const float deltaTimeEpsilon = 0.0001f; + float deltaTime = GetDeltaTime(); + if(deltaTime < deltaTimeEpsilon) + return; + + ParticleEmitterList::iterator next; + for( ParticleEmitterList::iterator i = gActiveEmitters.begin(); i != gActiveEmitters.end(); i = next ) + { + next = i; + next++; + ParticleEmitter& emitter = **i; + emitter.UpdateParticleSystem(deltaTime); + } +} + + + +void ParticleEmitter::ResetEmitterPos () +{ + if (m_UseWorldSpace) + m_EmitterPos = GetComponent (Transform).GetPosition (); + else + m_EmitterPos = Vector3f::zero; + m_PreviousEmitterPos = m_EmitterPos; +} + + +void ParticleEmitter::Deactivate( DeactivateOperation operation ) +{ + Super::Deactivate(operation); + + m_PrivateInfo.hadEverEmitOn = false; + UpdateManagerState( false ); +} + +PROFILER_INFORMATION(gParticleEmitterProfile, "Particle.Update", kProfilerParticles) + +void ParticleEmitter::UpdateParticleSystem(float deltaTime) +{ + PROFILER_AUTO(gParticleEmitterProfile, this) + + if( !IsActive() ) + { + AssertStringObject( "UpdateParticle system should not happen on disabled GO", this ); + return; + } + + //@TODO: REMOVE FROM LIST PROPERLY TO SAVE ITERATING THROUGH ALL PARTICLES + if (m_Enabled) + { + m_PrivateInfo.maxParticleSize = m_MaxSize; + m_PrivateInfo.maxEmitterParticleSize = m_MaxSize; + m_PrivateInfo.maxEnergy = clamp( m_MaxEnergy, 0.0f, kMaxEnergy ); + m_PrivateInfo.useWorldSpace = m_UseWorldSpace; + if( m_Emit ) + m_PrivateInfo.hadEverEmitOn = true; + m_PrivateInfo.isEmitOn = m_Emit; + + // + // Emit particles. + + ParticleAnimator* animator = QueryComponent(ParticleAnimator); + if( IsEmitting() ) + { + // We don't want to emit anymore if we have OneShot and auto destruct + // in particle animator is true. Otherwise we'd emit when we have no particles, and + // there will never be zero particles, hence no auto destruct ever! + bool willAutoDestruct = false; + if( animator && m_OneShot && m_Particles.empty() ) + willAutoDestruct = animator->WillAutoDestructIfNoParticles( m_PrivateInfo ); + if( !willAutoDestruct ) + TimedEmit( deltaTime ); + } + + // + // Update particle animator + + if( animator ) + animator->UpdateAnimator( m_Particles, m_PrivateInfo, deltaTime ); + + // + // Update world particle collider + + WorldParticleCollider* collider = QueryComponent(WorldParticleCollider); + if( collider ) + collider->UpdateParticleCollider( m_Particles, m_PrivateInfo, deltaTime ); + + // + // Update renderer + + ParticleRenderer* renderer = QueryComponent(ParticleRenderer); + if( renderer ) + renderer->UpdateParticleRenderer(); + } +} + +void ParticleEmitter::SetEmit (bool emit) +{ + if (m_Emit == emit) + return; + m_Emit = emit; + UpdateManagerState( IsActive() ); + if (emit) { + ResetEmitterPos (); + } +} + +void ParticleEmitter::SetEnabled (bool enabled) +{ + if (m_Enabled != enabled) + { + m_Enabled = enabled; + SetDirty(); + } +} + +void ParticleEmitter::Emit (unsigned int newParticleCount, float invDeltaTime) +{ + if (newParticleCount <= 0) + return; + + if (m_FirstFrame) + { + ResetEmitterPos(); + m_FirstFrame = false; + } + + unsigned int firstNewIndex = m_Particles.size (); + newParticleCount = min<unsigned int> (newParticleCount + firstNewIndex, kMaxParticleCount); + + if (newParticleCount != firstNewIndex) + { + m_Particles.resize (newParticleCount); + + Vector3f velocityOffset; + Matrix3x3f localRotation; + + CalcOffsets (&velocityOffset, &localRotation, invDeltaTime); + // Setup particles + SetupParticles (m_Particles, velocityOffset, localRotation, firstNewIndex); + } +} + + +void ParticleEmitter::Emit (const Vector3f &pos, const Vector3f &dir, float size, float energy, const ColorRGBA32 &color, float rotation, float angularVelocity) { + + if (m_Particles.size() >= kMaxParticleCount) + return; + + Particle p; + p.position = pos; + p.velocity = dir; + p.size = size; + p.rotation = Deg2Rad(rotation); + p.angularVelocity = Deg2Rad(angularVelocity); +// p.energy = SecondsToEnergy (energy + GetDeltaTime ()) + 1; + p.energy = energy; + p.startEnergy = energy; + p.color = color; + m_Particles.push_back (p); + m_PrivateInfo.aabb.Encapsulate( pos ); + UpdateManagerState( IsActive() ); +} + + +void ParticleEmitter::ClearParticles () +{ + m_Particles.clear(); + UpdateManagerState( IsActive() ); +} + +void ParticleEmitter::TimedEmit (float deltaTime) +{ + // Reserve enough memory to hold all particles that could ever be emitted with the current settings + int maxParticles = 0; + if (!m_OneShot) + { + maxParticles = CeilfToIntPos( min<float>(m_MaxEmission * m_MaxEnergy, kMaxParticleCount) ); + } + else + { + maxParticles = RoundfToIntPos( min<float>(m_MaxEmission, kMaxParticleCount) ); + } + + m_Particles.reserve (maxParticles); + + // Calculate how many new particles to emit + // never emit more particles than the capacity. + // the capacity can sometimes be too small if deltaTime is very large + // or minEnergy == maxEnergy or minEmissions == maxEmissions + int newParticleCount = 0; + + float emission = min<float> ( RangedRandom (gEmitterRand, m_MinEmission, m_MaxEmission), maxParticles ); + if (!m_OneShot) + { + float newParticleCountf = emission * deltaTime + m_EmissionFrac; + newParticleCount = FloorfToIntPos (newParticleCountf); + m_EmissionFrac = newParticleCountf - (float)newParticleCount; + } + else if(m_Particles.size () == 0) + { + newParticleCount = RoundfToIntPos(emission); + } + + newParticleCount = min<int> (m_Particles.capacity () - m_Particles.size (), newParticleCount); + + // Calculate velocity and rotation that is used to setup the particles + // We do this here so we don't get confused by calls to Emit from outside the filter loop. + if (m_UseWorldSpace) + { + m_PreviousEmitterPos = m_EmitterPos; + m_EmitterPos = GetComponent (Transform).GetPosition (); + } + else + { + m_PreviousEmitterPos = Vector3f::zero; + m_EmitterPos = Vector3f::zero; + } + + if (newParticleCount > 0) + { + Emit (newParticleCount, CalcInvDeltaTime(deltaTime)); + } +} + +void ParticleEmitter::CalcOffsets (Vector3f *velocityOffset, Matrix3x3f *localRotation, float invDeltaTime) { + Transform& t = GetComponent (Transform); + if (m_UseWorldSpace) + { + m_EmitterPos = t.GetPosition (); + + QuaternionToMatrix (t.GetRotation (), *localRotation); + + *velocityOffset = localRotation->MultiplyVector3 (m_LocalVelocity); + *velocityOffset += m_WorldVelocity; + *velocityOffset += (m_EmitterPos - m_PreviousEmitterPos) * invDeltaTime * m_EmitterVelocityScale; + } + else { + localRotation->SetIdentity (); + + *velocityOffset = m_LocalVelocity; + *velocityOffset += t.InverseTransformDirection (m_WorldVelocity); + } +} + +template<class TransferFunction> +void ParticleEmitter::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.SetVersion(2); + transfer.Transfer (m_Enabled, "m_Enabled", kHideInEditorMask); + TRANSFER_SIMPLE (m_Emit); + + transfer.Align(); + transfer.Transfer (m_MinSize, "minSize", kSimpleEditorMask); + transfer.Transfer (m_MaxSize, "maxSize", kSimpleEditorMask); + // WARNING: energy/emission might be inf + // when using fastmath on ps3 or equivalent on other platform - these will be handled incorrectly + // in that case we need to patch them on build here + transfer.Transfer (m_MinEnergy, "minEnergy", kSimpleEditorMask); + transfer.Transfer (m_MaxEnergy, "maxEnergy", kSimpleEditorMask); + transfer.Transfer (m_MinEmission, "minEmission", kSimpleEditorMask); + transfer.Transfer (m_MaxEmission, "maxEmission", kSimpleEditorMask); + transfer.Transfer (m_WorldVelocity, "worldVelocity", kSimpleEditorMask); + transfer.Transfer (m_LocalVelocity, "localVelocity", kSimpleEditorMask); + transfer.Transfer (m_RndVelocity, "rndVelocity", kSimpleEditorMask); + transfer.Transfer (m_EmitterVelocityScale, "emitterVelocityScale"); + // Emitter velocity scale was not frame rate independent! + if (transfer.IsOldVersion(1)) + m_EmitterVelocityScale /= 40.0F; + + transfer.Transfer (m_TangentVelocity, "tangentVelocity"); + transfer.Transfer (m_AngularVelocity, "angularVelocity", kSimpleEditorMask); + transfer.Transfer (m_RndAngularVelocity, "rndAngularVelocity", kSimpleEditorMask); + transfer.Transfer (m_RndInitialRotations, "rndRotation", kSimpleEditorMask); + transfer.Transfer (m_UseWorldSpace, "Simulate in Worldspace?"); + transfer.Transfer (m_OneShot, "m_OneShot"); +} + +void ParticleEmitter::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + if (IsActive()) + ResetEmitterPos(); + if (m_OneShot) + ClearParticles (); + UpdateManagerState( IsActive() ); +} + +void ParticleEmitter::ReadParticles( SimpleParticle* __restrict particle, int base, int count ) const +{ + if (base < 0 || base + count > m_Particles.size()) + { + ErrorString("Reading out of bounds particles"); + return; + } + + count += base; + for (int i=0;i<count;++i) + { + SimpleParticle& output = particle[i]; + const Particle& input = m_Particles[i + base]; + output.position = input.position; + output.velocity = input.velocity; + output.size = input.size; + output.rotation = Rad2Deg (input.rotation); + output.angularVelocity = Rad2Deg (input.angularVelocity); + output.energy =input.energy; + output.startEnergy = input.startEnergy; + output.color = input.color; + + Prefetch(&particle[i] + 8); + Prefetch(&particle[i + base] + 8); + } +} + +void ParticleEmitter::WriteParticles( const SimpleParticle* __restrict particle, /*int base,*/ int count ) +{ + if (count > kMaxParticleCount) + { + ErrorString(Format("You are assigning more than %d particles", kMaxParticleCount)); + count = kMaxParticleCount; + } + + MinMaxAABB& aabb = m_PrivateInfo.aabb; + aabb.Init(); + + m_Particles.resize(count); + + int newSize = 0; + for (int i=0;i<count;++i) + { + const SimpleParticle& input = particle[i]; + Particle& output = m_Particles[newSize]; + + output.position = input.position; + aabb.Encapsulate( input.position ); + output.velocity = input.velocity; + output.size = input.size; + output.rotation = Deg2Rad(input.rotation); + output.angularVelocity = Deg2Rad(input.angularVelocity); + output.energy = input.energy; + output.startEnergy = FloatMax(input.startEnergy, input.energy); + output.color = input.color; + + if (output.energy > 0.0f ) + newSize++; + + Prefetch(&particle[i] + 8); + } + + m_Particles.resize(newSize); +} + + +void ParticleEmitter::InitParticleEnergy(Rand& r, Particle& p, float dt) +{ + // if any if energies is infinity - both energy and startEnergy will be infinity too. + // that would be fine, but we're not quite ready to handle Inf properly + + + // add deltatime - it will be removed in particle animator + p.startEnergy = clamp ( RangedRandom (r, m_MinEnergy, m_MaxEnergy), 0.0f, kMaxEnergy ); + p.energy = p.startEnergy + dt + kTimeEpsilon; +} + +static void ResetRandSeedForEmitter () +{ + gEmitterRand.SetSeed (5); +} + +void ParticleEmitter::InitializeClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Register(ResetRandSeedForEmitter); +} + +void ParticleEmitter::CleanupClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Unregister(ResetRandSeedForEmitter); +} + +IMPLEMENT_CLASS_HAS_INIT (ParticleEmitter) +IMPLEMENT_OBJECT_SERIALIZE (ParticleEmitter) +INSTANTIATE_TEMPLATE_TRANSFER (ParticleEmitter) diff --git a/Runtime/Filters/Particles/ParticleEmitter.h b/Runtime/Filters/Particles/ParticleEmitter.h new file mode 100644 index 0000000..3622fe2 --- /dev/null +++ b/Runtime/Filters/Particles/ParticleEmitter.h @@ -0,0 +1,151 @@ +#ifndef PARTICLEEMITTER_H +#define PARTICLEEMITTER_H + +#include "Runtime/BaseClasses/GameObject.h" +#include "ParticleStruct.h" +#include "Runtime/Utilities/LinkedList.h" + + +class Rand; + +#define GET_SET_DIRTY_MIN(TYPE,PROP_NAME,VAR_NAME,MIN_VAL) void Set##PROP_NAME (TYPE val) { VAR_NAME = std::max(val,MIN_VAL); SetDirty(); } TYPE Get##PROP_NAME () const { return (TYPE)VAR_NAME; } + + + +class ParticleEmitter : public Unity::Component +{ +public: + REGISTER_DERIVED_ABSTRACT_CLASS (ParticleEmitter, Unity::Component) + DECLARE_OBJECT_SERIALIZE (ParticleEmitter) + + ParticleEmitter (MemLabelId label, ObjectCreationMode mode); + + void SetEmit (bool emit); + bool IsEmitting () const { return m_Emit; } + + GET_SET_DIRTY_MIN (float, MinSize, m_MinSize, 0.0f); + GET_SET_DIRTY_MIN (float, MaxSize, m_MaxSize, 0.0f); + GET_SET_DIRTY_MIN (float, MinEnergy, m_MinEnergy, 0.0f); + GET_SET_DIRTY_MIN (float, MaxEnergy, m_MaxEnergy, 0.0f); + GET_SET_DIRTY_MIN (float, MinEmission, m_MinEmission, 0.0f); + GET_SET_DIRTY_MIN (float, MaxEmission, m_MaxEmission, 0.0f); + + GET_SET_DIRTY (float, EmitterVelocityScale, m_EmitterVelocityScale); + GET_SET_DIRTY (Vector3f, WorldVelocity, m_WorldVelocity); + GET_SET_DIRTY (Vector3f, LocalVelocity, m_LocalVelocity); + GET_SET_DIRTY (Vector3f, RndVelocity, m_RndVelocity); + GET_SET_DIRTY (bool, RndRotation, m_RndInitialRotations); + GET_SET_DIRTY (float, AngularVelocity, m_AngularVelocity); + + float GetRndAngularVelocity () const { return m_RndAngularVelocity; } + void SetRndAngularVelocity (float val) { if (val < 0) val = 0; m_RndAngularVelocity = val; SetDirty(); } + + bool GetUseWorldSpace () const { return m_UseWorldSpace; } + void SetUseWorldSpace (bool val) { m_UseWorldSpace = val; } // TODO: set dirty? + + // Emit particleCount particles + void Emit (unsigned int particleCount, float invDeltaTime); + + // This is needed, so that subsequent calls to Emit() will each only emit at the current location, and not along the line + // between the current and last location. + void EmitResetEmitterPos (int particleCount, float invDeltaTime) { ResetEmitterPos(); Emit (particleCount, invDeltaTime); } + + // Emit one particle + void Emit (const Vector3f &pos, const Vector3f &dir, float size, float energy, const ColorRGBA32 &color, float rotation, float angularVelocity); + + void Deactivate (DeactivateOperation operation); + void AwakeFromLoad (AwakeFromLoadMode awakeMode); + + // clear all particles. + void ClearParticles (); + + void ReadParticles( SimpleParticle*__restrict particle, int baseIndex, int count ) const; + void WriteParticles( const SimpleParticle* __restrict particle, int count ); + + int GetParticleCount() const { return m_Particles.size(); } + + static void UpdateAllParticleSystems(); + void UpdateParticleSystem(float deltaTime); + + const ParticleArray& GetParticles() const { return m_Particles; } + ParticleArray& GetParticles() { return m_Particles; } + const PrivateParticleInfo& GetPrivateInfo() const { return m_PrivateInfo; } + + bool GetEnabled () const { return m_Enabled; } + void SetEnabled (bool enabled); + + static void InitializeClass (); + static void CleanupClass (); + +protected: + // Sets "does this emitter have to be updated next frame" flags. + void UpdateManagerState( bool updateParticles ); + +private: + // Subclasses have to override this function to place particles + virtual void SetupParticles (ParticleArray& particles, const Vector3f& velocityOffset, + const Matrix3x3f& rotation, int firstIndex) = 0; + + // Emit particles + void TimedEmit (float deltaTime); + + void ResetEmitterPos (); + void CalcOffsets (Vector3f *velocityOffset, Matrix3x3f *localRotation, float invDeltaTime); + +protected: + Vector3f m_EmitterPos; + Vector3f m_PreviousEmitterPos; + +protected: + ParticleArray m_Particles; + PrivateParticleInfo m_PrivateInfo; + float m_EmissionFrac; + + float m_MinSize; ///< minimum size range {0, infinity } + float m_MaxSize; ///< maximum size range {0, infinity } + float m_MinEnergy; ///< minimum energy range { 0, infinity } + float m_MaxEnergy; ///< maximum energy range { 0, infinity } + + float m_MinEmission; ///< minimum emissions per second range { 0, infinity } + float m_MaxEmission; ///< maximum emissions per second range { 0, infinity } + + float m_EmitterVelocityScale;///< Scales velocity of the emitter + + Vector3f m_WorldVelocity; + + Vector3f m_LocalVelocity; + + Vector3f m_TangentVelocity; + + Vector3f m_RndVelocity; + + bool m_UseWorldSpace; ///< Are particles simulated in WorldSpace or in LocalSpace? + + bool m_RndInitialRotations; ///< Should initial particle rotations be randomized? + float m_RndAngularVelocity; + float m_AngularVelocity; + + bool m_Enabled; + bool m_Emit; + bool m_OneShot; + bool m_FirstFrame; + +protected: + + void InitParticleEnergy(Rand& r, Particle& p, float dt); + + #if DOXYGEN + ///////// @TODO: DO EASIER BACKWARDS COMPATIBILITY: THEN WE DONT NEED THIS CRAP + float minSize; ///< minimum size range {0, infinity } + float maxSize; ///< minimum size range {0, infinity } + float minEnergy; ///< minimum size range {0, infinity } + float maxEnergy; ///< minimum size range {0, infinity } + float minEmission; ///< minimum size range {0, infinity } + float maxEmission; ///< minimum size range {0, infinity } + #endif + +private: + ListNode<ParticleEmitter> m_EmittersListNode; +}; + +#endif diff --git a/Runtime/Filters/Particles/ParticleRenderer.cpp b/Runtime/Filters/Particles/ParticleRenderer.cpp new file mode 100644 index 0000000..3a638e9 --- /dev/null +++ b/Runtime/Filters/Particles/ParticleRenderer.cpp @@ -0,0 +1,630 @@ +#include "UnityPrefix.h" +#include "ParticleRenderer.h" +#include "ParticleEmitter.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Filters/Misc/LineBuilder.h" +#include "Runtime/Camera/Camera.h" +#include "ParticleStruct.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Shaders/Shader.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Shaders/VBO.h" +#include "Runtime/Shaders/GraphicsCaps.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Profiler/ExternalGraphicsProfiler.h" + +IMPLEMENT_CLASS (ParticleRenderer) +IMPLEMENT_OBJECT_SERIALIZE (ParticleRenderer) + +// The distance of particle Corner from the center +// sin (45 deg), or sqrt(0.5) +#define kMaxParticleSizeFactor 0.707106781186548f + +ParticleRenderer::ParticleRenderer (MemLabelId label, ObjectCreationMode mode) +: Super(kRendererParticle, label, mode) +{ + SetVisible (false); + m_UVFrames = NULL; +} + +ParticleRenderer::~ParticleRenderer () +{ + if (m_UVFrames) + UNITY_FREE(kMemParticles, m_UVFrames); +} + +struct ParticleVertex { + Vector3f vert; + Vector3f normal; + ColorRGBA32 color; + Vector2f uv; +}; + +void ParticleRenderer::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + ParticleRenderer::GenerateUVFrames (); +} + +void ParticleRenderer::SetUVFrames (const Rectf *uvFrames, int numFrames) +{ + m_NumUVFrames = numFrames; + + if (m_UVFrames) + UNITY_FREE(kMemParticles, m_UVFrames); + + size_t size; + size = sizeof(Rectf) * m_NumUVFrames; + + if (m_NumUVFrames != 0 && m_NumUVFrames != size / sizeof(Rectf)) + { + m_UVFrames = NULL; + m_NumUVFrames = 0; + } + else + { + m_UVFrames = (Rectf*)UNITY_MALLOC(kMemParticles, size); + memcpy(m_UVFrames, uvFrames, numFrames*sizeof(Rectf)); + } +} + +void ParticleRenderer::GenerateUVFrames () +{ + if(m_UVAnimation.xTile < 1) + m_UVAnimation.xTile = 1; + if(m_UVAnimation.yTile < 1) + m_UVAnimation.yTile = 1; + + m_NumUVFrames = (m_UVAnimation.xTile * m_UVAnimation.yTile); + float animUScale = 1.0f / m_UVAnimation.xTile; + float animVScale = 1.0f / m_UVAnimation.yTile; + + if (m_UVFrames) + UNITY_FREE(kMemParticles, m_UVFrames); + + if(m_NumUVFrames == 1) + m_NumUVFrames = 0; + SET_ALLOC_OWNER(this); + m_UVFrames = (Rectf*)UNITY_MALLOC(kMemParticles, m_NumUVFrames * sizeof(Rectf)); + + for(int index=0;index<m_NumUVFrames;index++) + { + int vIdx = index / m_UVAnimation.xTile; + int uIdx = index - vIdx * m_UVAnimation.xTile; // slightly faster than index % m_UVAnimation.xTile + float uOffset = (float)uIdx * animUScale; + float vOffset = 1.0f - animVScale - (float)vIdx * animVScale; + + m_UVFrames[index] = Rectf(uOffset, vOffset, animUScale, animVScale); + } +} + +void ParticleRenderer::SetUVAnimationXTile (int v) +{ + v = std::max(v,1); + if (v == m_UVAnimation.xTile) + return; + m_UVAnimation.xTile = v; + SetDirty (); + GenerateUVFrames (); +} +void ParticleRenderer::SetUVAnimationYTile (int v) +{ + v = std::max(v,1); + if (v == m_UVAnimation.yTile) + return; + m_UVAnimation.yTile = v; + SetDirty (); + GenerateUVFrames (); +} +void ParticleRenderer::SetUVAnimationCycles (float v) +{ + v = std::max(v,0.0f); + if (v == m_UVAnimation.cycles) + return; + m_UVAnimation.cycles = v; + SetDirty (); +} + + + +PROFILER_INFORMATION(gParticlesProfile, "ParticleRenderer.Render", kProfilerParticles) +PROFILER_INFORMATION(gSubmitVBOProfileParticle, "Mesh.SubmitVBO", kProfilerRender) + +#pragma message ("Optimize particle systems to use templates for inner loops and build optimized inner loops without ifs") + +void ParticleRenderer::Render (int/* materialIndex*/, const ChannelAssigns& channels) +{ + + ParticleEmitter* emitter = QueryComponent(ParticleEmitter); + if( !emitter ) + return; + + PROFILER_AUTO_GFX(gParticlesProfile, this) + + GfxDevice& device = GetGfxDevice(); + Matrix4x4f matrix; + + ParticleArray& particles = emitter->GetParticles(); + const PrivateParticleInfo& privateInfo = emitter->GetPrivateInfo(); + if( particles.empty() ) + return; + + Camera& cam = GetCurrentCamera (); + + Vector3f xSpan, ySpan; + if(m_StretchParticles == kBillboardFixedHorizontal) + { + xSpan = Vector3f(1.0f,0.0f,0.0f); + ySpan = Vector3f(0.0f,0.0f,1.0f); + } + + if(m_StretchParticles == kBillboardFixedVertical) + { + ySpan = Vector3f(0.0f,1.0f,0.0f); + Vector3f zSpan = RotateVectorByQuat (cam.GetComponent (Transform).GetRotation(), Vector3f(0.0f,0.0f,1.0f)); + xSpan = NormalizeSafe( Cross(ySpan, zSpan) ); + } + + Vector3f cameraVelocity; + if (privateInfo.useWorldSpace) + { + CopyMatrix (device.GetViewMatrix (), matrix.GetPtr()); + cameraVelocity = cam.GetVelocity (); + } + else + { + Matrix4x4f mat, temp; + CopyMatrix (device.GetViewMatrix (), temp.GetPtr()); + mat = GetComponent (Transform).GetLocalToWorldMatrixNoScale(); + MultiplyMatrices4x4 (&temp, &mat, &matrix); + cameraVelocity = GetComponent (Transform).InverseTransformDirection (cam.GetVelocity ()); + } + + // Constrain the size to be a fraction of the viewport size. + // In perspective case, max size is (z*factorA). In ortho case, max size is just factorB. To have both + // without branches, we do (z*factorA+factorB) and set one of factors to zero. + float maxPlaneScale; + float maxOrthoSize; + if (!cam.GetOrthographic()) { + maxPlaneScale = -cam.CalculateFarPlaneWorldSpaceLength () * m_MaxParticleSize / cam.GetFar (); + maxOrthoSize = 0.0f; + } else { + maxPlaneScale = 0.0f; + maxOrthoSize = cam.CalculateFarPlaneWorldSpaceLength () * m_MaxParticleSize; + } + + /// Sort the particles + if (m_StretchParticles == kSortedBillboard && !particles.empty ()) + { + static vector<float> s_Dist; + if (s_Dist.size() < particles.size()) { + s_Dist.resize (0); + s_Dist.resize (particles.size()); + } + + // Calculate all distances + int i; + Vector3f distFactor = Vector3f (matrix.Get (2, 0), matrix.Get (2, 1), + matrix.Get (2, 2)); + for (i = 0; i < particles.size(); i++) + s_Dist[i] = Dot (distFactor, particles[i].position); + + // Bubblesort them + i = 0; + while (i < particles.size() - 1) + { + if (s_Dist[i] > s_Dist[i + 1]) + { + std::swap (s_Dist[i], s_Dist[i + 1]); + std::swap (particles[i], particles[i + 1]); + if (i > 0) + i -= 2; + } + i++; + } + } + + // Calculate parameters for UV animation + const int animFullTexCount = int(m_NumUVFrames * m_UVAnimation.cycles); + + // Get VBO chunk + const int particleCount = particles.size(); + DynamicVBO& vbo = device.GetDynamicVBO(); + ParticleVertex* vbPtr; + if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor), + particleCount * 4, 0, + DynamicVBO::kDrawQuads, + (void**)&vbPtr, NULL ) ) + { + return; + } + + int billboardRenderMode = m_StretchParticles; + + if (billboardRenderMode == kSortedBillboard) + billboardRenderMode = kBillboard; + if (billboardRenderMode == kBillboardFixedVertical) + billboardRenderMode = kBillboardFixedHorizontal; + + // Fill vertex buffer with particles + for( int i = 0; i < particleCount; ++i ) + { + const Particle& p = particles[i]; + Vector3f vert[4]; + Vector2f uv[4]; + + if (p.rotation != 0) + { + if (billboardRenderMode == kBillboard) + billboardRenderMode = kBillboardRotated; + else if (billboardRenderMode == kBillboardFixedHorizontal) + billboardRenderMode = kBillboardFixedRotated; + } + // Positions + // @TODO: get rid of the branch here + switch (billboardRenderMode) { + case kBillboard: + { + // Project point and create quad + Vector3f center = matrix.MultiplyPoint3 (p.position); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = center.z * maxPlaneScale + maxOrthoSize; + float size = std::min (p.size, maxWorldSpaceLength); + float halfSize = size * 0.5f; + + vert[0].Set( center.x - halfSize, center.y + halfSize, center.z ); + vert[1].Set( center.x + halfSize, center.y + halfSize, center.z ); + vert[2].Set( center.x + halfSize, center.y - halfSize, center.z ); + vert[3].Set( center.x - halfSize, center.y - halfSize, center.z ); + } + break; + case kBillboardFixedHorizontal: + { + // Project point and create quad + Vector3f center = matrix.MultiplyPoint3 (p.position); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = (center.z - p.size * kMaxParticleSizeFactor) * maxPlaneScale + maxOrthoSize; + float size = std::min (p.size, maxWorldSpaceLength); + float halfSize = size * 0.5f; + + vert[0] = matrix.MultiplyPoint3 ( p.position - halfSize * xSpan + halfSize * ySpan ); + vert[1] = matrix.MultiplyPoint3 ( p.position + halfSize * xSpan + halfSize * ySpan ); + vert[2] = matrix.MultiplyPoint3 ( p.position + halfSize * xSpan - halfSize * ySpan ); + vert[3] = matrix.MultiplyPoint3 ( p.position - halfSize * xSpan - halfSize * ySpan ); + } + break; + case kBillboardRotated: + { + // Project point and create quad + // Particles can be rotated by animation curve and width & height can be animated individually using an animation curve + Vector3f center = matrix.MultiplyPoint3 (p.position); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = center.z * maxPlaneScale + maxOrthoSize; + + float s = Sin (p.rotation); + float c = Cos (p.rotation); + + float x00 = c; + float x01 = s; + float x10 = -s; + float x11 = c; + + float size = std::min (p.size, maxWorldSpaceLength); + float halfSize = size * 0.5f; + + #define MUL(w,h) center.x + x00 * w + x01 * h, center.y + x10 * w + x11 * h, center.z + + vert[0].Set(MUL(-halfSize, halfSize)); + vert[1].Set(MUL( halfSize, halfSize)); + vert[2].Set(MUL( halfSize, -halfSize)); + vert[3].Set(MUL(-halfSize, -halfSize)); + #undef MUL + } + break; + case kBillboardFixedRotated: + { + // Project point and create quad + Vector3f center = matrix.MultiplyPoint3 (p.position); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = center.z * maxPlaneScale + maxOrthoSize; + + float s = Sin (p.rotation); + float c = Cos (p.rotation); + + float size = std::min (p.size, maxWorldSpaceLength); + float halfSize = size * 0.5f; + + vert[0] = matrix.MultiplyPoint3 ( p.position + halfSize * xSpan * c + halfSize * ySpan * s ); + vert[1] = matrix.MultiplyPoint3 ( p.position + halfSize * ySpan * c - halfSize * xSpan * s ); + vert[2] = matrix.MultiplyPoint3 ( p.position - halfSize * xSpan * c - halfSize * ySpan * s ); + vert[3] = matrix.MultiplyPoint3 ( p.position - halfSize * ySpan * c + halfSize * xSpan * s ); + } + break; + + case kStretch3D: + { + Vector3f velocity = p.velocity - cameraVelocity * m_CameraVelocityScale; + float sqrVelocity = SqrMagnitude (velocity); + + float size = p.size; + + float lineScale; + if (sqrVelocity > Vector3f::epsilon) + lineScale = m_VelocityScale + FastInvSqrt (sqrVelocity) * (m_LengthScale * size); + else + lineScale = 0.0F; + + Vector3f lineDelta = velocity * lineScale; + Vector3f begProj = matrix.MultiplyPoint3 (p.position); + Vector3f endProj = matrix.MultiplyPoint3 (p.position - lineDelta); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = endProj.z * maxPlaneScale + maxOrthoSize; + size = std::min (size, maxWorldSpaceLength); + float halfSize = size * 0.5F; + + Vector2f delta = Calculate2DLineExtrusion(begProj, begProj - endProj, halfSize); + + vert[0].Set( begProj.x + delta.x, begProj.y + delta.y, begProj.z ); + vert[1].Set( endProj.x + delta.x, endProj.y + delta.y, endProj.z ); + vert[2].Set( endProj.x - delta.x, endProj.y - delta.y, endProj.z ); + vert[3].Set( begProj.x - delta.x, begProj.y - delta.y, begProj.z ); + } + break; + case kStretch2D: + { + Vector3f velocity = p.velocity - cameraVelocity * m_CameraVelocityScale; + float sqrVelocity = SqrMagnitude (velocity); + + float size = p.size; + + float lineScale; + if (sqrVelocity > Vector3f::epsilon) + lineScale = m_VelocityScale + FastInvSqrt (sqrVelocity) * (m_LengthScale * size); + else + lineScale = 0.0F; + + Vector3f lineDelta = velocity * lineScale; + Vector3f begProj = matrix.MultiplyPoint3 (p.position); + Vector3f endProj = matrix.MultiplyPoint3 (p.position + lineDelta); + + float dx = endProj.x - begProj.x; + float dy = endProj.y - begProj.y; + + float sqrProjectedLength = dx*dx + dy*dy; + + if (sqrProjectedLength < Vector3f::epsilon) + { + dx = 1.0F; + dy = 0.0F; + sqrProjectedLength = 1.0F; + } + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = endProj.z * maxPlaneScale + maxOrthoSize; + size = std::min (size, maxWorldSpaceLength); + float halfSize = size * 0.5F; + + float lengthInv = halfSize * FastInvSqrt (sqrProjectedLength); + // Orthogonal 2D-vector to sdx0 + float sdx1 = -dy * lengthInv; + float sdy1 = dx * lengthInv; + + // scale with velocity + vert[0].Set( begProj.x + sdx1, begProj.y + sdy1, begProj.z ); + vert[1].Set( endProj.x + sdx1, endProj.y + sdy1, endProj.z ); + vert[2].Set( endProj.x - sdx1, endProj.y - sdy1, endProj.z ); + vert[3].Set( begProj.x - sdx1, begProj.y - sdy1, begProj.z ); + } + break; + } + + + // UVs + if(m_NumUVFrames > 0) + { + const float alpha = 1.0f - clamp01 (p.energy / p.startEnergy); + unsigned int index = (unsigned int)(alpha * animFullTexCount); + + Rectf *r = m_UVFrames+(index % m_NumUVFrames); + uv[0].Set( r->x, r->y + r->height ); + uv[1].Set( r->x + r->width, r->y + r->height ); + uv[2].Set( r->x + r->width, r->y ); + uv[3].Set( r->x, r->y ); + } + else + { + uv[0].Set( 0, 1 ); + uv[1].Set( 1, 1 ); + uv[2].Set( 1, 0 ); + uv[3].Set( 0, 0 ); + } + + + // Swizzle color of the renderer requires it + ColorRGBA32 color = p.color; + color = device.ConvertToDeviceVertexColor(color); + + // Now, write out the vertex structures sequentially (important when writing into dynamic VBO) + // @TODO: this place seems to be heavy in the profile (even more than branches above), optimize somehow! + vbPtr[0].vert = vert[0]; + vbPtr[0].normal.Set( 0.0f, 0.0f, 1.0f ); + vbPtr[0].color = color; + vbPtr[0].uv = uv[0]; + + vbPtr[1].vert = vert[1]; + vbPtr[1].normal.Set( 0.0f, 0.0f, 1.0f ); + vbPtr[1].color = color; + vbPtr[1].uv = uv[1]; + + vbPtr[2].vert = vert[2]; + vbPtr[2].normal.Set( 0.0f, 0.0f, 1.0f ); + vbPtr[2].color = color; + vbPtr[2].uv = uv[2]; + + vbPtr[3].vert = vert[3]; + vbPtr[3].normal.Set( 0.0f, 0.0f, 1.0f ); + vbPtr[3].color = color; + vbPtr[3].uv = uv[3]; + + // Next four vertices + vbPtr += 4; + } + + vbo.ReleaseChunk( particleCount * 4, 0 ); + + // Draw + float matView[16]; + CopyMatrix(device.GetViewMatrix(), matView); + device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity + + if (m_CustomProperties) + device.SetMaterialProperties (*m_CustomProperties); + + PROFILER_BEGIN(gSubmitVBOProfileParticle, this) + vbo.DrawChunk (channels); + GPU_TIMESTAMP(); + PROFILER_END + + device.SetViewMatrix(matView); +} + +void ParticleRenderer::AdjustBoundsForStretch( const ParticleEmitter& emitter, MinMaxAABB& aabb ) const +{ + Assert(m_StretchParticles == kStretch3D); + + const ParticleArray& particles = emitter.GetParticles(); + const size_t particleCount = particles.size(); + const float velocityScale = m_VelocityScale; + const float lengthScale = m_LengthScale; + const Particle* p = &particles[0]; + + for( size_t i = 0; i < particleCount; ++i, ++p ) { + float sqrVelocity = SqrMagnitude (p->velocity); + if (sqrVelocity > Vector3f::epsilon) { + float scale = velocityScale + FastInvSqrt (sqrVelocity) * lengthScale * p->size; + aabb.Encapsulate( p->position - p->velocity * scale ); + } + } +} + +void ParticleRenderer::UpdateTransformInfo () +{ + const Transform& transform = GetTransform(); + if (m_TransformDirty) + { + m_TransformInfo.invScale = 1.0f; + // will return a cached matrix most of the time + m_TransformInfo.transformType = transform.CalculateTransformMatrix (m_TransformInfo.worldMatrix);; + } + + if (m_BoundsDirty) + { + ParticleEmitter* emitter = QueryComponent(ParticleEmitter); + if (!emitter) + { + m_TransformInfo.localAABB.SetCenterAndExtent(Vector3f::zero, Vector3f::zero); + m_TransformInfo.worldAABB.SetCenterAndExtent(Vector3f::zero, Vector3f::zero); + return; + } + + const PrivateParticleInfo& info = emitter->GetPrivateInfo(); + MinMaxAABB aabb = info.aabb; + if (m_StretchParticles == kStretch3D) + AdjustBoundsForStretch (*emitter, aabb); + aabb.Expand (info.maxParticleSize * kMaxParticleSizeFactor); + + if (info.useWorldSpace) + { + m_TransformInfo.worldAABB = aabb; + InverseTransformAABB (m_TransformInfo.worldAABB, transform.GetPosition(), transform.GetRotation(), m_TransformInfo.localAABB); + } + else + { + m_TransformInfo.localAABB = aabb; + TransformAABB (m_TransformInfo.localAABB, transform.GetPosition(), transform.GetRotation(), m_TransformInfo.worldAABB); + } + } +} + + +void ParticleRenderer::UpdateRenderer() +{ + ParticleEmitter* emitter = QueryComponent(ParticleEmitter); + if( emitter ) + { + bool empty = emitter->GetParticles().empty(); + SetVisible( !empty ); + if( !empty ) + BoundsChanged(); + } + else + { + UpdateManagerState( false ); + } + + Super::UpdateRenderer(); +} + +void ParticleRenderer::UpdateParticleRenderer() +{ + UpdateManagerState( true ); +} + + +template<class TransferFunction> inline +void ParticleRenderer::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.SetVersion (2); + TRANSFER (m_CameraVelocityScale); + TRANSFER_SIMPLE (m_StretchParticles); + TRANSFER_SIMPLE (m_LengthScale); + TRANSFER (m_VelocityScale); + TRANSFER (m_MaxParticleSize); + + if (transfer.IsCurrentVersion()) { + transfer.Transfer (m_UVAnimation, "UV Animation"); + } else { + transfer.Transfer (m_UVAnimation.xTile, "m_AnimatedTextureCount"); + } +} + +void ParticleRenderer::CheckConsistency () +{ + Super::CheckConsistency (); + m_MaxParticleSize = std::max (0.0F, m_MaxParticleSize); + m_UVAnimation.xTile = std::max (1, m_UVAnimation.xTile); + m_UVAnimation.yTile = std::max (1, m_UVAnimation.yTile); + m_UVAnimation.cycles = std::max (0.0F, m_UVAnimation.cycles); +} + +void ParticleRenderer::Reset () +{ + Super::Reset (); + m_StretchParticles = kBillboard; + m_LengthScale = 2.0F; + m_VelocityScale = 0.0F; + m_MaxParticleSize = 0.25F; + m_UVAnimation.xTile = 1; + m_UVAnimation.yTile = 1; + m_UVAnimation.cycles = 1; + m_CameraVelocityScale = 0.0; +} diff --git a/Runtime/Filters/Particles/ParticleRenderer.h b/Runtime/Filters/Particles/ParticleRenderer.h new file mode 100644 index 0000000..d63661c --- /dev/null +++ b/Runtime/Filters/Particles/ParticleRenderer.h @@ -0,0 +1,103 @@ +#ifndef PARTICLERENDERER_H +#define PARTICLERENDERER_H + +#include <vector> +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Math/Rect.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/AnimationCurve.h" +using std::vector; +class ParticleEmitter; +class MinMaxAABB; + + + + +enum ParticleRenderMode { + kBillboard = 0, + kStretch2D = 1, + kStretch3D = 3, + kSortedBillboard = 2, + kBillboardFixedHorizontal = 4, + kBillboardFixedVertical = 5, + + /// Internal modes + kBillboardRotated = 1000, + kBillboardFixedRotated = 1001 +}; + +class ParticleRenderer : public Renderer { +public: + REGISTER_DERIVED_CLASS (ParticleRenderer, Renderer) + DECLARE_OBJECT_SERIALIZE (ParticleRenderer) + + ParticleRenderer (MemLabelId label, ObjectCreationMode mode); + // ~ParticleRenderer(); declared-by-macro + + virtual void Render (int materialIndex, const ChannelAssigns& channels); + + // Can operate in either local or world space, so we need to fill whole transform info ourselves + virtual void UpdateTransformInfo(); + + virtual void CheckConsistency (); + virtual void Reset (); + + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + + GET_SET_DIRTY (ParticleRenderMode, RenderMode, m_StretchParticles) ; + GET_SET_DIRTY (float, LengthScale, m_LengthScale) ; + GET_SET_DIRTY (float, VelocityScale, m_VelocityScale) ; + GET_SET_DIRTY (float, CameraVelocityScale, m_CameraVelocityScale) ; + GET_SET_DIRTY (float, MaxParticleSize, m_MaxParticleSize) ; + int GetUVAnimationXTile() const { return m_UVAnimation.xTile; } + int GetUVAnimationYTile() const { return m_UVAnimation.yTile; } + float GetUVAnimationCycles() const { return m_UVAnimation.cycles; } + void SetUVAnimationXTile (int v); + void SetUVAnimationYTile (int v); + void SetUVAnimationCycles (float v); + + void UpdateParticleRenderer(); + + Rectf *GetUVFrames() {return m_UVFrames;}; + int GetNumUVFrames() {return m_NumUVFrames;}; + + void SetUVFrames(const Rectf *uvFrames, int numFrames); + +private: + // from Renderer + virtual void UpdateRenderer(); + + void AdjustBoundsForStretch( const ParticleEmitter& emitter, MinMaxAABB& aabb ) const; + +protected: + struct UVAnimation { + int xTile; ///< Number of texture tiles in the X direction. + int yTile; ///< Number of texture tiles in the Y direction. + float cycles; ///< Number of cycles over a particle's life span. + DECLARE_SERIALIZE (UVAnimation) + }; + void SetBufferSize (int particleCount); + void GenerateUVFrames (); + + int m_StretchParticles; ///< enum { Billboard = 0, Stretched = 3, Sorted Billboard = 2, Horizontal Billboard = 4, Vertical Billboard = 5 } Should the particles be stretched along their velocity? + float m_LengthScale; ///< When Stretch Particles is enabled, defines the length of the particle compared to its width. + float m_VelocityScale; ///< When Stretch Particles is enabled, defines the length of the particle compared to its velocity. + float m_MaxParticleSize; ///< How large is a particle allowed to be on screen at most? 1 is entire viewport. 0.5 is half viewport. + UVAnimation m_UVAnimation; ///< Tiled UV settings. + + float m_CameraVelocityScale; ///< How much the camera motion is factored in when determining particle stretching + + int m_NumUVFrames; //uv tiles for uv animation + Rectf *m_UVFrames; +}; + +template<class TransferFunc> +void ParticleRenderer::UVAnimation::Transfer (TransferFunc& transfer) { + transfer.Transfer (xTile, "x Tile", kSimpleEditorMask); + transfer.Transfer (yTile, "y Tile", kSimpleEditorMask); + TRANSFER_SIMPLE (cycles); +} + +#endif diff --git a/Runtime/Filters/Particles/ParticleStruct.h b/Runtime/Filters/Particles/ParticleStruct.h new file mode 100644 index 0000000..cafd2dc --- /dev/null +++ b/Runtime/Filters/Particles/ParticleStruct.h @@ -0,0 +1,59 @@ +#ifndef PARTICLESTRUCT_H +#define PARTICLESTRUCT_H + +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Geometry/AABB.h" +#if UNITY_WII +#include "Runtime/Misc/Allocator.h" +#endif + +struct Particle +{ + Vector3f position; //12 + Vector3f velocity; //12 + float size; //4 + float rotation; //4 + float angularVelocity; //4 + float energy; //4 + float startEnergy; //4 + ColorRGBA32 color; //4 +}; + +struct SimpleParticle +{ + Vector3f position; + Vector3f velocity; + float size; + float rotation; + float angularVelocity; + float energy; + float startEnergy; + ColorRGBAf color; +}; + + +// 24 bytes +typedef UNITY_VECTOR(kMemParticles, Particle) ParticleArray; + +struct PrivateParticleInfo +{ + MinMaxAABB aabb; + float maxEmitterParticleSize;// Maximum size of any particle of emitted particles + float maxParticleSize;// max particle size of any particle after particle animation is done + float maxEnergy; + bool useWorldSpace; + bool hadEverEmitOn; // had "emit" flag ever set? + bool isEmitOn; // is "emit" flag currently set? +}; + + +const float kTimeEpsilon = 0.001f; + +inline void KillParticle (ParticleArray& array, int i) +{ + array[i] = array.back (); + array.pop_back (); +} + +#endif diff --git a/Runtime/Filters/Particles/WorldParticleCollider.cpp b/Runtime/Filters/Particles/WorldParticleCollider.cpp new file mode 100644 index 0000000..a209036 --- /dev/null +++ b/Runtime/Filters/Particles/WorldParticleCollider.cpp @@ -0,0 +1,197 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" +#include "WorldParticleCollider.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Geometry/Ray.h" +#include "Runtime/Input/TimeManager.h" +#include "ParticleStruct.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Interfaces/IRaycast.h" + +using namespace std; + +#pragma message ("Support collides with") +///@TODO: SUPPORT COLLIDES WITH + +WorldParticleCollider::WorldParticleCollider (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + +WorldParticleCollider::~WorldParticleCollider () +{ +} + +void WorldParticleCollider::Reset () +{ + Super::Reset (); + m_BounceFactor = 0.5; + m_MinKillVelocity = 0.0F; + m_CollisionEnergyLoss = 0.0F; + + m_CollidesWith.m_Bits = -1; + m_SendCollisionMessage = false; +} + +IMPLEMENT_CLASS (WorldParticleCollider) +IMPLEMENT_OBJECT_SERIALIZE (WorldParticleCollider) + +template<class T> inline +void WorldParticleCollider::Transfer (T& transfer) +{ + Super::Transfer (transfer); + TRANSFER (m_BounceFactor); + TRANSFER (m_CollisionEnergyLoss); + TRANSFER (m_CollidesWith); + TRANSFER (m_SendCollisionMessage); + transfer.Align(); + TRANSFER (m_MinKillVelocity); +} + +void WorldParticleCollider::UpdateParticleCollider( ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime ) +{ + int particleCount = particles.size (); + + float sqrMinKillVelocity = m_MinKillVelocity * m_MinKillVelocity; + // not sure if multiplication of infinity works on every platform + AssertIf (std::numeric_limits<float>::infinity () * std::numeric_limits<float>::infinity () != std::numeric_limits<float>::infinity ()); + + if (privateInfo.useWorldSpace) + { + for (int i=0;i<particleCount;i++) + { + Vector3f& position = particles[i].position; + Vector3f& velocity = particles[i].velocity; + Vector3f delta = velocity * deltaTime; + + float psize = 0.5f * particles[i].size; + float poffset = 0.51f * particles[i].size; + + Ray ray; + ray.SetOrigin (position - delta); + float deltaLength = Magnitude (delta); + if (deltaLength < Vector3f::epsilon) + continue; + + ray.SetDirection (delta / deltaLength); + + float checkLen = deltaLength + psize; + float t = checkLen; + + #if ENABLE_PHYSICS + HitInfo hit; + IRaycast *raycast = GetRaycastInterface(); + if(raycast) + { + if(raycast->Raycast(ray, t, m_CollidesWith.m_Bits, hit)) + { + Particle& particle = particles[i]; + + // Reflect velocity and apply velocity * time left for particle to position + // Test and factor changes into other particle collider + velocity = ReflectVector (m_BounceFactor * velocity, hit.normal); + float fractionLeftForReflection = (checkLen - t) / deltaLength; + + // Place particle slightly above the surface. Otherwise in next frame raycast can + // detect collision again and reflect particle back! + position = hit.intersection + hit.normal * poffset + velocity * (deltaTime * fractionLeftForReflection); + + if (m_SendCollisionMessage) + { + AssertIf (hit.colliderInstanceID == 0); + PPtr<Component> collider_pptr(hit.colliderInstanceID); + Component* collider = collider_pptr; + SendMessage (kParticleCollisionEvent, &collider->GetGameObject (), ClassID (GameObject)); + collider->SendMessage (kParticleCollisionEvent, &GetGameObject (), ClassID (GameObject)); + } + // Update energy + particle.energy -= m_CollisionEnergyLoss; + float sqrVelocity = SqrMagnitude (velocity); + if (particle.energy <= 0.0f || sqrVelocity < sqrMinKillVelocity) { + // Kill particle (replace particle i with last, and continue updating the moved particle) + KillParticle (particles, i); + particleCount = particles.size (); + i--; + continue; + } + privateInfo.aabb.Encapsulate (position); + } + } + + #endif // ENABLE_PHYSICS + } + } + else + { + Matrix4x4f localToWorld = GetComponent (Transform).GetLocalToWorldMatrixNoScale (); + for (int i=0;i<particleCount;i++) + { + Vector3f& position = particles[i].position; + Vector3f& velocity = particles[i].velocity; + + float psize = 0.5f * particles[i].size; + float poffset = 0.51f * particles[i].size; + + Vector3f worldSpaceDelta = localToWorld.MultiplyVector3 (velocity * deltaTime); + Vector3f worldPosition = localToWorld.MultiplyPoint3 (position); + + Ray ray; + ray.SetOrigin (worldPosition - worldSpaceDelta); + float deltaLength = Magnitude (worldSpaceDelta); + if (deltaLength < Vector3f::epsilon) + continue; + + ray.SetDirection (worldSpaceDelta / deltaLength); + + float checkLen = deltaLength + psize; + float t = checkLen; + + #if ENABLE_PHYSICS + HitInfo hit; + IRaycast *raycast = GetRaycastInterface(); + if(raycast) + { + if(raycast->Raycast(ray, t, m_CollidesWith.m_Bits, hit)) + { + Particle& particle = particles[i]; + + // Reflect velocity and apply velocity * time left for particle to position + Vector3f worldVelocity = localToWorld.MultiplyVector3 (velocity); + // Test and factor changes into other particle collider + worldVelocity = ReflectVector (m_BounceFactor * worldVelocity, hit.normal); + float fractionLeftForReflection = (checkLen - t) / deltaLength; + + // Place particle slightly above the surface. Otherwise in next frame raycast can + // detect collision again and reflect particle back! + worldPosition = hit.intersection + hit.normal * poffset + worldVelocity * (deltaTime * fractionLeftForReflection); + + position = localToWorld.InverseMultiplyPoint3Affine (worldPosition); + velocity = localToWorld.InverseMultiplyVector3Affine (worldVelocity); + + if (m_SendCollisionMessage) + { + AssertIf (hit.colliderInstanceID == 0); + PPtr<Component> collider_pptr(hit.colliderInstanceID); + Component* collider = collider_pptr; + SendMessage (kParticleCollisionEvent, &collider->GetGameObject (), ClassID (GameObject)); + collider->SendMessage (kParticleCollisionEvent, &GetGameObject (), ClassID (GameObject)); + } + + // Update energy + particle.energy -= m_CollisionEnergyLoss; + float sqrVelocity = SqrMagnitude (velocity); + if (particle.energy <= 0.0f || sqrVelocity < sqrMinKillVelocity) { + // Kill particle (replace particle i with last, and continue updating the moved particle) + KillParticle (particles, i); + particleCount = particles.size (); + i--; + continue; + } + + privateInfo.aabb.Encapsulate (position); + } + } + #endif // ENABLE_PHYSICS + } + } +} diff --git a/Runtime/Filters/Particles/WorldParticleCollider.h b/Runtime/Filters/Particles/WorldParticleCollider.h new file mode 100644 index 0000000..f6db8af --- /dev/null +++ b/Runtime/Filters/Particles/WorldParticleCollider.h @@ -0,0 +1,33 @@ +#ifndef WORLDPARTICLECOLLIDER_H +#define WORLDPARTICLECOLLIDER_H + +#include "Runtime/BaseClasses/GameObject.h" +#include "ParticleStruct.h" + + + +class WorldParticleCollider : public Unity::Component +{ +public: + REGISTER_DERIVED_CLASS (WorldParticleCollider, Unity::Component) + DECLARE_OBJECT_SERIALIZE (WorldParticleCollider) + + WorldParticleCollider (MemLabelId label, ObjectCreationMode mode); + + virtual void Reset (); + + void UpdateParticleCollider( ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime ); + +private: + /// The velocity at which a particle is killed after the bounced velocity is calculated + float m_MinKillVelocity; + float m_BounceFactor; + /// Seconds of energy a particle loses when colliding + float m_CollisionEnergyLoss; + /// Collides the particles with every collider whose layerMask & m_CollidesWith != 0 + BitField m_CollidesWith; + /// Should we send out a collision message for every particle that has collided? + bool m_SendCollisionMessage; +}; + +#endif diff --git a/Runtime/Filters/Pipeline.cpp b/Runtime/Filters/Pipeline.cpp new file mode 100644 index 0000000..cb554e6 --- /dev/null +++ b/Runtime/Filters/Pipeline.cpp @@ -0,0 +1,20 @@ +#include "UnityPrefix.h" +#include "Pipeline.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" + +IMPLEMENT_CLASS (Pipeline) +IMPLEMENT_OBJECT_SERIALIZE (Pipeline) + +Pipeline::Pipeline(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + +Pipeline::~Pipeline () +{ +} + +template<class TransferFunction> +void Pipeline::Transfer (TransferFunction& transfer) { + Super::Transfer (transfer); +} diff --git a/Runtime/Filters/Pipeline.h b/Runtime/Filters/Pipeline.h new file mode 100644 index 0000000..a580c19 --- /dev/null +++ b/Runtime/Filters/Pipeline.h @@ -0,0 +1,16 @@ +#ifndef PIPELINE_H +#define PIPELINE_H + +#include "Runtime/BaseClasses/GameObject.h" + + +class Pipeline : public Unity::Component { +public: + REGISTER_DERIVED_CLASS (Pipeline, Component) + DECLARE_OBJECT_SERIALIZE (Pipeline) + + Pipeline(MemLabelId label, ObjectCreationMode mode); + +}; + +#endif diff --git a/Runtime/Filters/Renderer.cpp b/Runtime/Filters/Renderer.cpp new file mode 100644 index 0000000..33a0b5b --- /dev/null +++ b/Runtime/Filters/Renderer.cpp @@ -0,0 +1,663 @@ +#include "UnityPrefix.h" +#include "Renderer.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Shaders/MaterialProperties.h" +#include "Runtime/Camera/UnityScene.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Camera/Camera.h" +#include "Runtime/Camera/Culler.h" +#include "Runtime/BaseClasses/SupportedMessageOptimization.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Camera/LODGroup.h" +#include "Runtime/BaseClasses/EventIDs.h" +#include "Runtime/Misc/GameObjectUtility.h" +#include "Runtime/Modules/ExportModules.h" +#include "RendererAnimationBinding.h" + +using namespace Unity; + +IMPLEMENT_CLASS_HAS_POSTINIT (Renderer) +IMPLEMENT_OBJECT_SERIALIZE (Renderer) +INSTANTIATE_TEMPLATE_TRANSFER_EXPORTED (Renderer) + +typedef List< ListNode<Renderer> > RendererList; +static RendererList gRenderersToUpdate; + + +static Transform *gIdentityTransform = NULL; + +Transform* GetIdentityTransform() { return gIdentityTransform; } + +Renderer::Renderer (RendererType type, MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, BaseRenderer(type) +, m_LODGroup (NULL) +, m_SceneHandle(kInvalidSceneHandle) +, m_RenderersListNode(this) +, m_Enabled(true) +, m_Visible(true) +//, m_Materials (Renderer::MaterialArray::allocator_type (*baseAllocator)) +#if UNITY_EDITOR +, m_SelectedWireframeHidden(false) +#endif +, m_UseLightProbes(false) +, m_LastLightProbeTetIndex(-1) +, m_SortingLayer(0) +, m_SortingOrder(0) +#if UNITY_EDITOR +, m_SortingLayerID(0) +#endif +{ + +} + +void Renderer::SmartReset () +{ + Super::SmartReset(); + SetMaterialCount (1); +} + +Renderer::~Renderer () +{ + DebugAssert (!IsInScene()); + delete m_CustomProperties; + + if (m_LODGroup) + m_LODGroup->RemoveFromCachedRenderers(this); +} + +void Renderer::CleanupClass() +{ + Assert(gIdentityTransform != NULL); + gIdentityTransform = NULL; + + CleanupRendererAnimationBindingInterface (); +} + +void Renderer::InitializeClass() +{ + REGISTER_MESSAGE (Renderer, kTransformChanged, TransformChanged, int); + REGISTER_MESSAGE_VOID (Renderer, kLayerChanged, LayerChanged); + + InitializeRendererAnimationBindingInterface (); +} + +void Renderer::PostInitializeClass() +{ + Assert(gIdentityTransform == NULL); + + GameObject* go = CreateObjectFromCode<GameObject> (); + gIdentityTransform = CreateObjectFromCode<Transform> (); + GameObject::AddComponentInternal(*go, *gIdentityTransform); + go->SetHideFlags(kHideAndDontSave); + Assert(!go->IsActive()); +} + +bool Renderer::IsVisibleInScene () const +{ + Assert ((m_IsVisibleInScene && IsInScene()) == m_IsVisibleInScene); + return m_IsVisibleInScene; +} + +void Renderer::RendererBecameVisible() +{ + BaseRenderer::RendererBecameVisible (); + + InvokeEvent (kBecameVisibleEvent); + + SendMessage (kBecameVisible); +} + +void Renderer::RendererBecameInvisible() +{ + BaseRenderer::RendererBecameInvisible (); + + SendMessage (kBecameInvisible); + + InvokeEvent (kBecameInvisibleEvent); +} + +int Renderer::GetLayer() const +{ + return GetGameObject().GetLayer(); +} + + +void Renderer::SetVisible (bool visible) +{ + m_Visible = visible; + + bool shouldBeInScene = ShouldBeInScene(); + if (shouldBeInScene == IsInScene()) + return; + + if (!shouldBeInScene) + { + // Remove from scene immediately + RemoveFromScene (); + UpdateManagerState( false ); + + InvokeEvent(kBecameInvisibleEvent); + } + else + { + // Add to scene in renderers update + UpdateManagerState( true ); + } +} + +void Renderer::BoundsChanged () +{ + m_BoundsDirty = true; + if (IsInScene()) + GetScene().SetDirtyAABB(m_SceneHandle); +} + +void Renderer::LayerMaskChanged () +{ + if (IsInScene()) + GetScene().SetRendererLayer(m_SceneHandle, GetLayer()); +} + +void Renderer::UpdateManagerState( bool needsUpdate ) +{ + if( needsUpdate == m_RenderersListNode.IsInList() ) + return; + if( needsUpdate ) + gRenderersToUpdate.push_front(m_RenderersListNode); + else + m_RenderersListNode.RemoveFromList(); +} + +void Renderer::UpdateAllRenderersInternal() +{ + // Update the renderers from the update list: + // - before updating each, remove from the list + // - a renderer can add itself again, so only process the original list length + RendererList::iterator next, listEnd = gRenderersToUpdate.end(); + for( RendererList::iterator i = gRenderersToUpdate.begin(); i != listEnd; i = next ) + { + next = i; + next++; + Renderer& renderer = **i; + renderer.m_RenderersListNode.RemoveFromList(); + renderer.UpdateRenderer(); + } +} + +void Renderer::NotifySceneHandleChange (SceneHandle handle) +{ + m_SceneHandle = handle; +} + +void Renderer::UpdateLODGroup () +{ + if (!IsInScene()) + return; + Unity::Scene& scene = GetScene(); + DebugAssert (scene.GetRendererNode(m_SceneHandle).renderer == this); + + UInt32 lodGroup = 0; + UInt32 lodIndexMask = 0; + if (m_LODGroup != NULL) + { + m_LODGroup->GetLODGroupIndexAndMask(this, &lodGroup, &lodIndexMask); + } + scene.SetRendererLODGroup(m_SceneHandle, lodGroup); + scene.SetRendererLODIndexMask(m_SceneHandle, lodIndexMask); +} + +void Renderer::UpdateSceneHandle () +{ + if (!IsInScene()) + return; + Unity::Scene& scene = GetScene(); + DebugAssert (scene.GetRendererNode(m_SceneHandle).renderer == this); + + AABB worldAABB; + GetWorldAABB (worldAABB); + scene.SetRendererAABB(m_SceneHandle, worldAABB); + + bool needsCullCallback = (GetGameObject().GetSupportedMessages() & kHasOnWillRenderObject) != 0; + scene.SetRendererNeedsCullCallback(m_SceneHandle, needsCullCallback); + scene.SetRendererLayer(m_SceneHandle, GetLayer()); + + UpdateLODGroup(); +} + +void Renderer::UpdateTransformInfo () +{ + Transform const& transform = GetTransform(); + if(m_TransformDirty) + { + m_TransformInfo.invScale = 1.0f; + // will return a cached matrix most of the time + m_TransformInfo.transformType = transform.CalculateTransformMatrix (m_TransformInfo.worldMatrix); + } + + if(m_BoundsDirty) + UpdateLocalAABB (); + + TransformAABB( m_TransformInfo.localAABB, m_TransformInfo.worldMatrix, m_TransformInfo.worldAABB ); + + if (IsNoScaleTransform(m_TransformInfo.transformType)) + return; + + // run slow path for non uniform scale + Matrix4x4f scaleOnly; + float scale; + TransformType type = transform.CalculateTransformMatrixDisableNonUniformScale (m_TransformInfo.worldMatrix, scaleOnly, scale); + Assert (type == m_TransformInfo.transformType); + + m_TransformInfo.invScale = 1.0F / scale; + + if (IsNonUniformScaleTransform(type)) + { + // must recalculate this since it is changed with the following transform + UpdateLocalAABB (); + TransformAABB (m_TransformInfo.localAABB, scaleOnly, m_TransformInfo.localAABB); + } +} + +void Renderer::UpdateRenderer () +{ + if (ShouldBeInScene ()) + { + if (!IsInScene()) + { + m_SceneHandle = GetScene().AddRenderer (this); + } + Assert (m_SceneHandle != kInvalidSceneHandle); + UpdateSceneHandle (); + } + else + { + // This should not be necessary but fixes a weird bug where a mesh is + // being leaked when disabled before loading a scene. Happens in the fps tutorial. + RemoveFromScene (); + } +} + +void Renderer::SetPropertyBlock (const MaterialPropertyBlock& block) +{ + delete m_CustomProperties; + m_CustomProperties = new MaterialPropertyBlock (block); + ComputeCustomPropertiesHash(); +} + +void Renderer::GetPropertyBlock (MaterialPropertyBlock& outBlock) +{ + if (!m_CustomProperties) + { + outBlock.Clear(); + return; + } + outBlock = *m_CustomProperties; +} + +void Renderer::ClearPropertyBlock () +{ + delete m_CustomProperties; + m_CustomProperties = NULL; + ComputeCustomPropertiesHash(); +} + +MaterialPropertyBlock& Renderer::GetPropertyBlockRememberToUpdateHash () +{ + if (!m_CustomProperties) + m_CustomProperties = new MaterialPropertyBlock (); + return *m_CustomProperties; +} + + +void Renderer::SetEnabled (bool newEnabled) +{ + m_Enabled = newEnabled; + SetDirty(); + SetVisible(m_Visible); +} + +void Renderer::Deactivate (DeactivateOperation operation) +{ + RemoveFromScene (); + UpdateManagerState( false ); + Super::Deactivate (operation); +} + +/// Set how many materials this renderer uses +/// @param size the number of materials. +void Renderer::SetMaterialCount (int size) +{ + const size_t oldSize = m_Materials.size (); + + if (size != (int)oldSize) + { + Assert(m_SubsetIndices.empty() || m_SubsetIndices.size() == m_Materials.size()); + + resize_trimmed (m_Materials, size); + HealSubsetIndices(); + + SetDirty (); + BoundsChanged(); + } +} + +void Renderer::HealSubsetIndices() +{ + // We are using batching and our subset indices have gone out of sync + if (!m_SubsetIndices.empty() && m_SubsetIndices.size() != m_Materials.size()) + { + int oldSize = m_SubsetIndices.size(); + resize_trimmed (m_SubsetIndices, m_Materials.size()); + // All new subset indices get the + for (size_t q = oldSize; q < m_SubsetIndices.size(); ++q) + m_SubsetIndices[q] = (UInt32)q; + BoundsChanged(); + } +} + +void Renderer::RemoveFromScene () +{ + if (!IsInScene()) + return; + + bool wasVisible = IsVisibleInScene (); + + BaseRenderer* remRenderer = GetScene ().RemoveRenderer (m_SceneHandle); + Assert(remRenderer == this); + + m_SceneHandle = kInvalidSceneHandle; + + if (wasVisible) + RendererBecameInvisible (); +} + +void Renderer::CheckConsistency() +{ + Super::CheckConsistency(); + + HealSubsetIndices(); +} + +void Renderer::ClearSubsetIndices() +{ + m_SubsetIndices.clear(); + SetDirty (); + BoundsChanged(); +} + +void Renderer::SetSubsetIndex (int index, int subsetIndex) +{ + if (m_SubsetIndices.empty()) + { + resize_trimmed (m_SubsetIndices, m_Materials.size()); + for (size_t q = 0; q < m_Materials.size(); ++q) + m_SubsetIndices[q] = (UInt32)q; + } + + Assert(index < m_SubsetIndices.size()); + Assert (m_Materials.size() == m_SubsetIndices.size()); + m_SubsetIndices[index] = subsetIndex; + SetDirty (); + BoundsChanged(); +} + +/// Set a given Material. +/// @param material the material to assign +/// @param index the index to assign the material to. +void Renderer::SetMaterial (PPtr<Material> material, int index) +{ + Assert (index < m_Materials.size()); + m_Materials[index] = material; + + /* + #if !DEPLOY_OPTIMIZED + Material* materialPtr = material; + if (materialPtr && materialPtr->GetOwner ().GetInstanceID () != 0 && materialPtr->GetOwner() != PPtr<Object> (this)) + { + ErrorString("Assigning an instantiated material is not a good idea. Since the material is owned by another game object, it will be destroyed when the game object is destroyed.\nYou probably want to explicitly instantiate the material."); + } + #endif + */ + + SetDirty (); +} + +void Renderer::SetMaterialArray( const MaterialArray& m, const IndexArray& i ) +{ + m_Materials = m; + m_SubsetIndices = i; +} + +Material* Renderer::GetAndAssignInstantiatedMaterial(int i, bool allowFromEditMode) +{ + // Grab shared material + Material* material = NULL; + if (GetMaterialCount () > i) + material = GetMaterial (i); + + // instantiate material if necessary + Material* instantiated = &Material::GetInstantiatedMaterial (material, *this, allowFromEditMode); + + // Assign material + if (material != instantiated) + { + SetMaterialCount (std::max(GetMaterialCount (), i + 1)); + SetMaterial (instantiated, i); + } + + return instantiated; +} + +Transform& Renderer::GetTransform() +{ + if (!IsPartOfStaticBatch()) + return GetComponent(Transform); + + if (!m_StaticBatchRoot.IsNull()) + return *m_StaticBatchRoot; + + return *gIdentityTransform; +} + +Transform const& Renderer::GetTransform() const +{ + return const_cast<Renderer*>(this)->GetTransform(); +} + +Matrix4x4f Renderer::GetWorldToLocalMatrix () const +{ + return GetTransform().GetWorldToLocalMatrix(); +} + +Matrix4x4f Renderer::GetLocalToWorldMatrix () const +{ + return GetTransform().GetLocalToWorldMatrix(); +} + + +// Receiver for the TransformChanged message. +// Updates the scene info with the new bounds. +void Renderer::TransformChanged (int changeMask) +{ + m_TransformDirty = true; + BoundsChanged (); +} + +// Receiver for the LayerChanged message. +// Updates the scene info with the new layerMask. +void Renderer::LayerChanged () +{ + LayerMaskChanged (); +} + +void Renderer::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + + // Materials might have been changed eg. from the property editor + // When loading from disk the materials are only being restored not changed + if ((awakeMode & kDidLoadFromDisk) == 0) + { + // m_Enabled might have changed - update visibility status + SetVisible(m_Visible); + } + + UpdateManagerState( IsActive() ); + SetupSortingOverride(); +} + +void Renderer::SupportedMessagesDidChange (int mask) +{ + Super::SupportedMessagesDidChange(mask); + + if (IsInScene()) + { + bool needsCullCallback = (GetGameObject().GetSupportedMessages() & kHasOnWillRenderObject) != 0; + GetScene().SetRendererNeedsCullCallback(m_SceneHandle, needsCullCallback); + } +} + +void Renderer::SetLightmapIndexInt(int index) +{ + UInt8 oldIndex = m_LightmapIndex; + SetLightmapIndexIntNoDirty (index); + if (oldIndex != m_LightmapIndex) + SetDirty(); +} + +void Renderer::SetLightmapST( const Vector4f& st ) +{ + if (st != m_LightmapST) + { + m_LightmapST = st; + SetDirty(); + } +} + + +#if UNITY_EDITOR +bool Renderer::CanSelectedWireframeBeRendered () const +{ + return m_Enabled && m_Visible && IsInScene() && !m_SelectedWireframeHidden; +} +#endif + +template<class TransferFunction> +void Renderer::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.Transfer(m_Enabled, "m_Enabled", kHideInEditorMask); + transfer.Transfer(m_CastShadows, "m_CastShadows"); + transfer.Transfer(m_ReceiveShadows, "m_ReceiveShadows"); + transfer.Transfer(m_LightmapIndex, "m_LightmapIndex", kHideInEditorMask | kDontAnimate); + transfer.Transfer(m_LightmapST, "m_LightmapTilingOffset", kHideInEditorMask | kDontAnimate); + transfer.Transfer (m_Materials, "m_Materials"); + transfer.Transfer (m_SubsetIndices, "m_SubsetIndices", kHideInEditorMask); + transfer.Transfer (m_StaticBatchRoot, "m_StaticBatchRoot", kHideInEditorMask); + TRANSFER (m_UseLightProbes); + transfer.Align(); + TRANSFER (m_LightProbeAnchor); + #if UNITY_EDITOR + if (!transfer.IsSerializingForGameRelease()) + transfer.Transfer (m_ScaleInLightmap, "m_ScaleInLightmap", kHideInEditorMask | kDontAnimate); + #endif + transfer.Align(); + + transfer.Transfer (m_SortingLayer, "m_SortingLayer", kHideInEditorMask); + transfer.Transfer (m_SortingOrder, "m_SortingOrder", kHideInEditorMask); + // In the editor, we also transfer global sorting layer ID, so we can derive final sorting value + // in case layers are reordered / etc. + TRANSFER_EDITOR_ONLY_HIDDEN(m_SortingLayerID); +} + +Vector3f Renderer::GetLightProbeInterpolationPosition (const AABB& worldBounds) +{ + Transform* anchor = m_LightProbeAnchor; + if (anchor != NULL) + return anchor->GetPosition(); + + return worldBounds.GetCenter(); +} + + +Vector3f Renderer::GetLightProbeInterpolationPosition () +{ + Transform* anchor = m_LightProbeAnchor; + if (anchor != NULL) + return anchor->GetPosition(); + + AABB aabb; + GetWorldAABB(aabb); + if (aabb.IsValid()) + return aabb.GetCenter(); + + return Vector3f(0,0,0); +} + + + +int Renderer::GetSortingLayerUserID() const +{ + if (m_SortingLayer == 0) + return 0; + return ::GetSortingLayerUserIDFromValue(m_SortingLayer); +} + +void Renderer::SetSortingLayerUserID(int id) +{ + int layerValue = GetSortingLayerValueFromUserID(id); + if (m_SortingLayer == layerValue) + return; + +# if UNITY_EDITOR + if (layerValue == 0) + m_SortingLayerID = 0; + else + m_SortingLayerID = ::GetSortingLayerUniqueIDFromValue(layerValue); +# endif + + m_SortingLayer = layerValue; + SetupSortingOverride(); + SetDirty(); +} + +std::string Renderer::GetSortingLayerName() const +{ + if (m_SortingLayer == 0) + return std::string(); + return ::GetSortingLayerNameFromValue(m_SortingLayer); +} + +void Renderer::SetSortingLayerName(const std::string& name) +{ + int layerValue = ::GetSortingLayerValueFromName(name); + int layerUserID = ::GetSortingLayerUserIDFromValue(layerValue); + SetSortingLayerUserID (layerUserID); +} + + +void Renderer::SetSortingOrder(SInt16 newValue) +{ + if (m_SortingOrder == newValue) + return; + + m_SortingOrder = newValue; + SetupSortingOverride(); +} + +void Renderer::SetupSortingOverride() +{ + // In editor, make sure our final layer sorting value is up to date + // (might have changed behind our backs by global layer reordering). +# if UNITY_EDITOR + m_SortingLayer = GetSortingLayerValueFromUniqueID(m_SortingLayerID); +# endif // if UNITY_EDITOR + + GlobalLayeringData gld = GlobalLayeringDataCleared(); + gld.layer = m_SortingLayer; + gld.order = m_SortingOrder; + SetGlobalLayeringData(gld); +} diff --git a/Runtime/Filters/Renderer.h b/Runtime/Filters/Renderer.h new file mode 100644 index 0000000..69bd87a --- /dev/null +++ b/Runtime/Filters/Renderer.h @@ -0,0 +1,234 @@ +#ifndef RENDERER_H +#define RENDERER_H + +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Camera/BaseRenderer.h" +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Utilities/LinkedList.h" +#include <vector> +#include "Runtime/Modules/ExportModules.h" +#include "Runtime/Camera/SceneNode.h" + +class Animation; +class LODGroup; +class Renderer; +struct VisibleNode; + +struct RenderStats +{ + int triangleCount; + int vertexCount; + int submeshCount; +}; + +struct EventEntry; + +enum { + // @TBD: tweak to ideal values per platform / device / rendering API + kDynamicBatchingVerticesThreshold = 300, // verts (needed at build time since required channels isn't known) + kDynamicBatchingVertsByChannelThreshold = 300 * 3, // verts * channels + kDynamicBatchingIndicesThreshold = 32000 // >32k causes a slowdown on MBPs with AMD cards (Case 394520) +}; + + +class EXPORT_COREMODULE Renderer : public Unity::Component, public BaseRenderer +{ +public: + + typedef UNITY_VECTOR(kMemRenderer, PPtr<Material>) MaterialArray; + typedef UNITY_VECTOR(kMemRenderer, UInt32) IndexArray; + + REGISTER_DERIVED_ABSTRACT_CLASS (Renderer, Unity::Component) + DECLARE_OBJECT_SERIALIZE (Renderer) + + static void InitializeClass (); + static void PostInitializeClass (); + static void CleanupClass (); + + Renderer (RendererType type, MemLabelId label, ObjectCreationMode mode); + // ~Renderer (); declared-in-macro + + virtual void SmartReset (); + virtual void CheckConsistency(); + + // BaseRenderer + virtual void RendererBecameVisible(); + virtual void RendererBecameInvisible(); + virtual int GetLayer() const; + virtual void UpdateTransformInfo(); + + // UpdateLocalAABB only needed for derived classes that don't implement UpdateTransformInfo + virtual void UpdateLocalAABB() {Assert(false);} + + virtual int GetMaterialCount() const { return m_Materials.size (); } + virtual PPtr<Material> GetMaterial(int i) const { return m_Materials[i]; } + Material* GetAndAssignInstantiatedMaterial(int i, bool allowFromEditMode); + + virtual int GetSubsetIndex(int i) const { return (m_SubsetIndices.empty())? i: m_SubsetIndices[i]; } + virtual void SetSubsetIndex(int subsetIndex, int index); + + // Set the visibility of a renderer. + // If false, the renderer doesn't register itself in the scene, and never get any render calls. + void SetVisible (bool isVisible); + bool GetVisible () const { return m_Visible; } + + bool GetEnabled () const { return m_Enabled; } + void SetEnabled( bool v ); + + void SetMaterialCount (int size); + void SetMaterial (PPtr<Material> material, int index); + void ClearSubsetIndices(); + + PPtr<Transform> GetStaticBatchRoot() const { return m_StaticBatchRoot; } + void SetStaticBatchRoot (PPtr<Transform> root) { m_StaticBatchRoot = root; } + + // Due to batching GameObject and attached Renderer component transforms can differ + // User following methods to access Renderer transforms or matrix + Transform const& GetTransform () const; + Transform& GetTransform (); + Matrix4x4f GetWorldToLocalMatrix () const; + Matrix4x4f GetLocalToWorldMatrix () const; + + // from Component + virtual void Deactivate (DeactivateOperation operation); + + void TransformChanged (int changeMask); + void LayerChanged (); + + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + + void SetLightmapIndexInt(int index); + + void SetLightmapST( const Vector4f& st ); + + void SetCastShadows( bool f ) { m_CastShadows = f; SetDirty(); } + void SetReceiveShadows( bool f ) { m_ReceiveShadows = f; SetDirty(); } + + void SetMaterialArray( const MaterialArray& m, const IndexArray& i ); + const MaterialArray& GetMaterialArray() { return m_Materials; } + const IndexArray& GetSubsetIndices() { return m_SubsetIndices; } + + static void UpdateAllRenderersInternal(); + + #if UNITY_EDITOR + bool CanSelectedWireframeBeRendered () const; + void SetSelectedWireframeHidden (bool isHidden) { m_SelectedWireframeHidden = isHidden; }; + virtual void GetRenderStats (RenderStats& renderStats) { memset(&renderStats, 0, sizeof(renderStats)); } + #endif + + bool GetUseLightProbes() const { return m_UseLightProbes; } + void SetUseLightProbes(bool useLightProbes) { m_UseLightProbes = useLightProbes; SetDirty(); } + int GetLastLightProbeTetIndex() const { return m_LastLightProbeTetIndex; } + void SetLastLightProbeTetIndex(int index) { m_LastLightProbeTetIndex = index; } + PPtr<Transform> GetLightProbeAnchor() const { return m_LightProbeAnchor; } + void SetLightProbeAnchor (PPtr<Transform> anchor) { m_LightProbeAnchor = anchor; SetDirty(); } + + // Tries to use the light probe anchor, or the RendererNode's AABB center or the WorldAABB's center; + // likes to get the RendererNode, as that's the fastest + Vector3f GetLightProbeInterpolationPosition (); + Vector3f GetLightProbeInterpolationPosition (const AABB& worldBounds); + + + void SetLODGroup(LODGroup* ptr) { m_LODGroup = ptr; } + LODGroup* GetLODGroup() { return m_LODGroup; } + + // A callback from the scene to notify + // that the pointer to the cullnode associated with the renderer has changed + void NotifySceneHandleChange (SceneHandle cullNode); + + SceneHandle GetSceneHandle() { return m_SceneHandle; } + + bool IsInScene() const { return m_SceneHandle != kInvalidSceneHandle; } + + bool IsVisibleInScene () const; + + void UpdateLODGroup (); + + const MaterialPropertyBlock* GetPropertyBlock () const { return m_CustomProperties; } + void GetPropertyBlock (MaterialPropertyBlock& outBlock); + void SetPropertyBlock (const MaterialPropertyBlock& block); + + bool HasPropertyBlock() const { return m_CustomProperties != NULL; } + void ClearPropertyBlock (); + MaterialPropertyBlock& GetPropertyBlockRememberToUpdateHash (); + + int GetSortingLayer() const { return m_SortingLayer; } + int GetSortingLayerUserID() const; + void SetSortingLayerUserID(int id); + std::string GetSortingLayerName() const; + void SetSortingLayerName(const std::string& name); + SInt16 GetSortingOrder() const { return m_SortingOrder; } + void SetSortingOrder(SInt16 newValue); + void SetupSortingOverride(); + +protected: + + void UpdateSceneHandle (); + + // Update the scene info for the renderer. + // Registers/unregisters the renderer in the scene. + // Override in child classes to do custom stuff. + virtual void UpdateRenderer(); + + void HealSubsetIndices(); + + // Should this renderer be in the scene? + bool ShouldBeInScene() const { return m_Enabled && m_Visible && IsActive (); } + + void SupportedMessagesDidChange (int mask); + + void UpdateManagerState( bool needsUpdate ); + + // Inform a renderer that the aabb has changed. + void BoundsChanged (); + + // Inform a renderer that the layerMask has changed. + void LayerMaskChanged (); + + // Check if material indices are equal to subset indices + void ValidateSubsetIndexArray (); + + bool HasSubsetIndices() const { return !m_SubsetIndices.empty(); } + bool IsPartOfStaticBatch() const { return HasSubsetIndices(); } + + void RemoveFromScene(); + +protected: + SceneHandle m_SceneHandle; + ListNode<Renderer> m_RenderersListNode; + MaterialArray m_Materials; ///< List of materials to use when rendering. + +private: + IndexArray m_SubsetIndices; + PPtr<Transform> m_StaticBatchRoot; + + LODGroup* m_LODGroup; + + bool m_Enabled; + bool m_Visible; + + bool m_UseLightProbes; + PPtr<Transform> m_LightProbeAnchor; ///< Light probe lighting is interpolated at the center of the renderer's bounds or at the position of the anchor, if assigned. + int m_LastLightProbeTetIndex; // Last light probe tetrahedron this renderer was in, used as a guess for the next frame; don't serialize. + + // Global sorting layer index. Zero is "Default" layer which is always there. + // Layers before the default one get negative numbers; after default one + // get positive ones. + // + // When the layers are reordered in the inspector, all alive renderers + // get this updated. Otherwise, at load time the proper sorting layer + // is fetched from m_SortingLayerID (each layer gets an unique one). + SInt16 m_SortingLayer; + SInt16 m_SortingOrder; + +# if UNITY_EDITOR + // Unique ID of our sorting layer, or zero for default one. Needed to resolve + // proper final m_SortingLayer value, when the layers were reordered. + UInt32 m_SortingLayerID; + bool m_SelectedWireframeHidden; +# endif // if UNITY_EDITOR +}; + +Transform* GetIdentityTransform(); + +#endif diff --git a/Runtime/Filters/RendererAnimationBinding.cpp b/Runtime/Filters/RendererAnimationBinding.cpp new file mode 100644 index 0000000..fef77e7 --- /dev/null +++ b/Runtime/Filters/RendererAnimationBinding.cpp @@ -0,0 +1,407 @@ +#include "UnityPrefix.h" +#include "Runtime/Animation/GenericAnimationBindingCache.h" +#include "Runtime/Animation/AnimationClipBindings.h" +#include "Renderer.h" +#include "External/shaderlab/Library/FastPropertyName.h" +#include "External/shaderlab/Library/properties.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Shaders/MaterialProperties.h" +#include "Runtime/Interfaces/IAnimationBinding.h" + +#define MATERIAL_ANIMATION 1 + +class RendererAnimationBinding : public IAnimationBinding +{ +#if UNITY_EDITOR + virtual void GetAllAnimatableProperties (Object& targetObject, std::vector<EditorCurveBinding>& outProperties) const + { + Renderer& renderer = static_cast<Renderer&> (targetObject); + + for (int i=0;i<renderer.GetMaterialCount();i++) + AddPPtrBinding (outProperties, targetObject.GetClassID(), Format("m_Materials.Array.data[%d]", i)); + } +#endif + + virtual float GetFloatValue (const UnityEngine::Animation::BoundCurve& bind) const + { + AssertString("unsupported"); return 0.0F; + } + + virtual void SetFloatValue (const UnityEngine::Animation::BoundCurve& bound, float value) const + { + AssertString("unsupported"); + } + + virtual void SetPPtrValue (const UnityEngine::Animation::BoundCurve& bound, SInt32 value) const + { + Renderer& renderer = *static_cast<Renderer*> (bound.targetObject); + int index = reinterpret_cast<int> (bound.targetPtr); + + if (index < renderer.GetMaterialCount ()) + renderer.SetMaterial(PPtr<Material> (value), index); + } + + virtual SInt32 GetPPtrValue (const UnityEngine::Animation::BoundCurve& bound) const + { + Renderer& renderer = *static_cast<Renderer*> (bound.targetObject); + int index = reinterpret_cast<int> (bound.targetPtr); + + if (index < renderer.GetMaterialCount ()) + return renderer.GetMaterial(index).GetInstanceID(); + else + return 0; + } + + virtual bool GenerateBinding (const UnityStr& attribute, bool pptrCurve, UnityEngine::Animation::GenericBinding& outputBinding) const + { + int index = ParseIndexAttributeIndex (attribute, "m_Materials.Array.data["); + if (index != -1 && pptrCurve) + { + outputBinding.attribute = index; + return true; + } + + return false; + } + + virtual ClassIDType BindValue (Object& target, const UnityEngine::Animation::GenericBinding& outputBinding, UnityEngine::Animation::BoundCurve& bound) const + { + bound.targetPtr = reinterpret_cast<void*> (outputBinding.attribute); + return ClassID(Material); + } +}; + +#if MATERIAL_ANIMATION + +const char* kMaterialPrefix = "material."; +class RendererMaterialAnimationBinding : public IAnimationBinding +{ +public: + +#if UNITY_EDITOR + virtual void GetAllAnimatableProperties (Object& targetObject, std::vector<EditorCurveBinding>& outProperties) const + { + Renderer& renderer = static_cast<Renderer&> (targetObject); + if (renderer.GetMaterialCount() == 0) + return; + + int startIndex = outProperties.size(); + for (int i=0;i<renderer.GetMaterialCount();i++) + { + Material* material = renderer.GetMaterial(i); + if (material == NULL) + continue; + + ExtractAllMaterialAnimatableAttributes (*material, targetObject.GetClassID(), outProperties, startIndex); + } + } + + static void ExtractAllMaterialAnimatableAttributes (Material& targetObject, int classID, std::vector<EditorCurveBinding>& outProperties, int startIndex) + { + Material& material = static_cast<Material&> (targetObject); + + const ShaderLab::PropertySheet& properties = material.GetProperties(); + + const string materialPrefix = kMaterialPrefix; + + // Get all float properties + const ShaderLab::PropertySheet::Floats& floats = properties.GetFloatsMap(); + for (ShaderLab::PropertySheet::Floats::const_iterator i=floats.begin();i != floats.end();i++) + { + AddBindingCheckUnique (outProperties, startIndex, classID, materialPrefix + i->first.GetName()); + } + + // Get all vector properties + const ShaderLab::PropertySheet::Vectors& vectors = properties.GetVectorMap(); + for (ShaderLab::PropertySheet::Vectors::const_iterator i=vectors.begin();i != vectors.end();i++) + { + string prefix = materialPrefix + i->first.GetName(); + if (properties.GetColorTag(i->first)) + { + AddBindingCheckUnique (outProperties, startIndex, classID, prefix + ".r"); + AddBindingCheckUnique (outProperties, startIndex, classID, prefix + ".g"); + AddBindingCheckUnique (outProperties, startIndex, classID, prefix + ".b"); + AddBindingCheckUnique (outProperties, startIndex, classID, prefix + ".a"); + } + else + { + AddBindingCheckUnique (outProperties, startIndex, classID, prefix + ".x"); + AddBindingCheckUnique (outProperties, startIndex, classID, prefix + ".y"); + AddBindingCheckUnique (outProperties, startIndex, classID, prefix + ".z"); + AddBindingCheckUnique (outProperties, startIndex, classID, prefix + ".w"); + } + + } + } +#endif + + struct MaterialBinding + { + enum { kFloat4, kColor4, kFloat1 }; + + UInt32 propertyName : 28; + UInt32 colIndex : 2; + UInt32 type : 2; + }; + + static UInt32 BindingToUInt32 (MaterialBinding binding) + { + UInt32 data; + data = binding.propertyName; + data |= binding.colIndex << 28; + data |= binding.type << 30; + return data; + } + + static MaterialBinding UInt32ToBinding (UInt32 data) + { + MaterialBinding binding; + binding.propertyName = data & 0x3FFFFFFF; + binding.colIndex = (data >> 28) & 0x3; + binding.type = (data >> 30) & 0x3; + return binding; + } + + virtual float GetFloatValue (const UnityEngine::Animation::BoundCurve& bind) const + { + Renderer& renderer = *static_cast<Renderer*> (bind.targetObject); + MaterialBinding binding = *reinterpret_cast<const MaterialBinding*> (&bind.targetPtr); + + ShaderLab::FastPropertyName name; + name.index = binding.propertyName; + + const MaterialPropertyBlock* block = renderer.GetPropertyBlock(); + + // Extract from material property block + if (block) + { + if (binding.type == MaterialBinding::kFloat1) + { + const float* value = block->FindFloat (name); + if (value != NULL) + return *value; + } + else if (binding.type == MaterialBinding::kFloat4) + { + const Vector4f* value = block->FindVector (name); + if (value != NULL) + return value->GetPtr()[binding.colIndex]; + } + else if (binding.type == MaterialBinding::kColor4) + { + ColorRGBAf color; + if (block->GetColor (name, color)) + return color.GetPtr()[binding.colIndex]; + } + } + + // Extract from material + for (int i=0;i<renderer.GetMaterialCount ();i++) + { + Material* material = renderer.GetMaterial(0); + if (material == NULL) + continue; + if (!material->HasProperty (name)) + continue; + + if (binding.type == MaterialBinding::kFloat1) + return material->GetFloat(name); + else if (binding.type == MaterialBinding::kColor4) + return material->GetColor(name).GetPtr()[binding.colIndex]; + else if (binding.type == MaterialBinding::kFloat4) + return material->GetColor(name).GetPtr()[binding.colIndex]; + } + + return 0.0F; + } + + virtual void SetFloatValue (const UnityEngine::Animation::BoundCurve& bound, float value) const + { + Renderer& renderer = *static_cast<Renderer*> (bound.targetObject); + MaterialBinding binding = *reinterpret_cast<const MaterialBinding*> (&bound.targetPtr); + + MaterialPropertyBlock& block = renderer.GetPropertyBlockRememberToUpdateHash(); + + ShaderLab::FastPropertyName name; + name.index = binding.propertyName; + + if (binding.type == MaterialBinding::kFloat1) + { + block.ReplacePropertyFloat (name, value); + } + else if (binding.type == MaterialBinding::kFloat4) + { + block.ReplacePartialFloatProperty (name, value, 4, binding.colIndex); + } + else if (binding.type == MaterialBinding::kColor4) + { + block.ReplacePartialFloatColorProperty (name, value, 4, binding.colIndex); + } + + renderer.ComputeCustomPropertiesHash(); + + // Force a repaint + #if UNITY_EDITOR + renderer.SetDirty(); + #endif + } + + virtual void SetPPtrValue (const UnityEngine::Animation::BoundCurve& bound, SInt32 value) const { } + + virtual SInt32 GetPPtrValue (const UnityEngine::Animation::BoundCurve& bound) const { return 0; } + + virtual bool GenerateBinding (const UnityStr& attributeStr, bool pptrCurve, UnityEngine::Animation::GenericBinding& outputBinding) const + { + if (pptrCurve) + return false; + + if (!BeginsWith(attributeStr, kMaterialPrefix)) + return false; + + MaterialBinding binding; + + const char* attribute = attributeStr.c_str() + strlen(kMaterialPrefix); + const char* a = attribute; + + // Parse this: + // mainColor.r + // mainColor.x + // mainColor.y + // floatPropertyName + + // Find shader propertyname + int dotIndex = -1; + const char* lastCharacter; + while (*a != 0) + { + if (*a == '.' && dotIndex == -1) + dotIndex = a - attribute; + a++; + } + lastCharacter = a - 1; + + // No '.' found, thus it must be a float property + if (dotIndex == -1) + { + binding.propertyName = ShaderLab::GenerateFastPropertyName28BitHash(attribute); + binding.type = MaterialBinding::kFloat1; + binding.colIndex = 0; + } + // Calculate different property types + else + { + binding.propertyName = ShaderLab::GenerateFastPropertyName28BitHash(string(attribute, attribute + dotIndex).c_str()); + + // There must be exactly one property ('r', 'g' etc after the .) + if (dotIndex + 2 != strlen(attribute)) + return false; + + binding.type = MaterialBinding::kFloat4; + switch (*lastCharacter) + { + case 'r': + case 'g': + case 'b': + case 'a': + binding.type = MaterialBinding::kColor4; + } + + switch (*lastCharacter) + { + // r color or x vector + case 'r': + case 'x': + binding.colIndex = 0; + break; + + // g color or y vector + case 'g': + case 'y': + binding.colIndex = 1; + break; + + // b or z vector + case 'b': + case 'z': + binding.colIndex = 2; + break; + + // alpha or w vector + case 'a': + case 'w': + binding.colIndex = 3; + break; + default: + return false; + } + } + + outputBinding.attribute = BindingToUInt32 (binding); + return true; + } + + virtual ClassIDType BindValue (Object& target, const UnityEngine::Animation::GenericBinding& outputBinding, UnityEngine::Animation::BoundCurve& bound) const + { + MaterialBinding materialBinding = UInt32ToBinding(outputBinding.attribute); + + ShaderLab::FastPropertyName prop; + prop.InitBy28BitHash (materialBinding.propertyName); + materialBinding.propertyName = prop.index; + + bound.targetPtr = *reinterpret_cast<void**> (&materialBinding); + return ClassID(float); + } +}; +#endif + + +static RendererAnimationBinding* gRendererBinding = NULL; +#if MATERIAL_ANIMATION +static RendererMaterialAnimationBinding* gMaterialBinding = NULL; +#endif + +void InitializeRendererAnimationBindingInterface () +{ + gRendererBinding = UNITY_NEW (RendererAnimationBinding, kMemAnimation); + UnityEngine::Animation::GetGenericAnimationBindingCache ().RegisterIAnimationBinding (ClassID(Renderer), UnityEngine::Animation::kRendererMaterialPPtrBinding, gRendererBinding); + + #if MATERIAL_ANIMATION + gMaterialBinding = UNITY_NEW (RendererMaterialAnimationBinding, kMemAnimation); + UnityEngine::Animation::GetGenericAnimationBindingCache ().RegisterIAnimationBinding (ClassID(Renderer), UnityEngine::Animation::kRendererMaterialPropertyBinding, gMaterialBinding); + #endif +} + +void CleanupRendererAnimationBindingInterface () +{ + UNITY_DELETE (gRendererBinding, kMemAnimation); + #if MATERIAL_ANIMATION + UNITY_DELETE (gMaterialBinding, kMemAnimation); + #endif +} + + +#if ENABLE_UNIT_TESTS +#include "External/UnitTest++/src/UnitTest++.h" + +SUITE (MaterialBindingTests) +{ + TEST (MaterialBindingUInt32Conversion) + { + RendererMaterialAnimationBinding::MaterialBinding binding; + binding.propertyName = 12345678; + binding.colIndex = 3; + binding.type = 3; + + RendererMaterialAnimationBinding::MaterialBinding converted = RendererMaterialAnimationBinding::UInt32ToBinding (RendererMaterialAnimationBinding::BindingToUInt32 (binding)); + CHECK_EQUAL (converted.propertyName, binding.propertyName); + CHECK_EQUAL (converted.colIndex, binding.colIndex); + CHECK_EQUAL (converted.type, binding.type); + } + + TEST (MaterialBindingCorrectlyEncodesAllBits) + { + CHECK_EQUAL ( RendererMaterialAnimationBinding::BindingToUInt32(RendererMaterialAnimationBinding::UInt32ToBinding (0xFFFFFFFF)), 0xFFFFFFFF); + CHECK_EQUAL ( RendererMaterialAnimationBinding::BindingToUInt32(RendererMaterialAnimationBinding::UInt32ToBinding (0)), 0); + } +} +#endif
\ No newline at end of file diff --git a/Runtime/Filters/RendererAnimationBinding.h b/Runtime/Filters/RendererAnimationBinding.h new file mode 100644 index 0000000..4096ed8 --- /dev/null +++ b/Runtime/Filters/RendererAnimationBinding.h @@ -0,0 +1,2 @@ +void InitializeRendererAnimationBindingInterface (); +void CleanupRendererAnimationBindingInterface (); |