/****************************************************************************** * Spine Runtimes License Agreement * Last updated January 1, 2020. Replaces all prior versions. * * Copyright (c) 2013-2020, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #include "SkeletonCacheAnimation.h" #include "2d/renderer/RenderDrawInfo.h" #include "2d/renderer/RenderEntity.h" #include "MiddlewareMacro.h" #include "SharedBufferManager.h" #include "SkeletonCacheMgr.h" #include "base/TypeDef.h" #include "base/memory/Memory.h" #include "gfx-base/GFXDef.h" #include "math/Math.h" #include "renderer/core/MaterialInstance.h" USING_NS_MW; // NOLINT(google-build-using-namespace) using namespace cc; // NOLINT(google-build-using-namespace) using namespace cc::gfx; // NOLINT(google-build-using-namespace) static const std::string TECH_STAGE = "opaque"; static const std::string TEXTURE_KEY = "texture"; namespace spine { SkeletonCacheAnimation::SkeletonCacheAnimation(const std::string &uuid, bool isShare) { if (isShare) { _skeletonCache = SkeletonCacheMgr::getInstance()->buildSkeletonCache(uuid); _skeletonCache->addRef(); } else { _skeletonCache = new SkeletonCache(); _skeletonCache->addRef(); _skeletonCache->initWithUUID(uuid); } // store global TypedArray begin and end offset _sharedBufferOffset = new IOTypedArray(se::Object::TypedArrayType::UINT32, sizeof(uint32_t) * 2); } SkeletonCacheAnimation::~SkeletonCacheAnimation() { if (_sharedBufferOffset) { delete _sharedBufferOffset; _sharedBufferOffset = nullptr; } if (_skeletonCache) { _skeletonCache->release(); _skeletonCache = nullptr; } while (!_animationQueue.empty()) { auto *ani = _animationQueue.front(); _animationQueue.pop(); delete ani; } for (auto *draw : _drawInfoArray) { CC_SAFE_DELETE(draw); } for (auto &item : _materialCaches) { CC_SAFE_DELETE(item.second); } stopSchedule(); } void SkeletonCacheAnimation::update(float dt) { if (_paused) return; auto gTimeScale = SkeletonAnimation::GlobalTimeScale; dt *= _timeScale * gTimeScale; if (_isAniComplete) { if (_animationQueue.empty() && !_headAnimation) { if (_animationData && !_animationData->isComplete()) { _skeletonCache->updateToFrame(_animationName); } return; } if (!_headAnimation) { _headAnimation = _animationQueue.front(); _animationQueue.pop(); } if (!_headAnimation) { return; } _accTime += dt; if (_accTime > _headAnimation->delay) { std::string name = _headAnimation->animationName; bool loop = _headAnimation->loop; delete _headAnimation; _headAnimation = nullptr; setAnimation(name, loop); return; } } if (!_animationData) return; if (_accTime <= 0.00001 && _playCount == 0) { if (_startListener) { _startListener(_animationName); } } _accTime += dt; int frameIdx = floor(_accTime / SkeletonCache::FrameTime); if (!_animationData->isComplete()) { _skeletonCache->updateToFrame(_animationName, frameIdx); } int finalFrameIndex = static_cast(_animationData->getFrameCount()) - 1; if (_animationData->isComplete() && frameIdx >= finalFrameIndex) { _playCount++; _accTime = 0.0F; if (_playTimes > 0 && _playCount >= _playTimes) { frameIdx = finalFrameIndex; _playCount = 0; _isAniComplete = true; } else { frameIdx = 0; } if (_endListener) { _endListener(_animationName); } if (_completeListener) { _completeListener(_animationName); } } _curFrameIndex = frameIdx; } void SkeletonCacheAnimation::render(float /*dt*/) { if (!_animationData) return; SkeletonCache::FrameData *frameData = _animationData->getFrameData(_curFrameIndex); if (!frameData) return; auto *entity = _entity; entity->clearDynamicRenderDrawInfos(); const auto &segments = frameData->getSegments(); const auto &colors = frameData->getColors(); if (segments.empty() || colors.empty()) return; auto *mgr = MiddlewareManager::getInstance(); if (!mgr->isRendering) return; _sharedBufferOffset->reset(); _sharedBufferOffset->clear(); auto *attachMgr = mgr->getAttachInfoMgr(); auto *attachInfo = attachMgr->getBuffer(); if (!attachInfo) return; // store attach info offset _sharedBufferOffset->writeUint32(static_cast(attachInfo->getCurPos()) / sizeof(uint32_t)); auto vertexFormat = _useTint ? VF_XYZUVCC : VF_XYZUVC; middleware::MeshBuffer *mb = mgr->getMeshBuffer(vertexFormat); middleware::IOBuffer &vb = mb->getVB(); middleware::IOBuffer &ib = mb->getIB(); const auto &srcVB = frameData->vb; const auto &srcIB = frameData->ib; // vertex size int bytes with one color int vbs1 = sizeof(V3F_T2F_C4B); // vertex size in floats with one color int vs1 = static_cast(vbs1 / sizeof(float)); // vertex size int bytes with two color int vbs2 = sizeof(V3F_T2F_C4B_C4B); // vertex size in floats with two color int vs2 = static_cast(vbs2 / sizeof(float)); int vs = _useTint ? vs2 : vs1; int vbs = _useTint ? vbs2 : vbs1; auto &nodeWorldMat = entity->getNode()->getWorldMatrix(); int colorOffset = 0; SkeletonCache::ColorData *nowColor = colors[colorOffset++]; auto maxVFOffset = nowColor->vertexFloatOffset; Color4B finalColor; Color4B darkColor; float tempR = 0.0F; float tempG = 0.0F; float tempB = 0.0F; float tempA = 0.0F; float multiplier = 1.0F; int srcVertexBytesOffset = 0; int srcVertexBytes = 0; int vertexBytes = 0; int vertexFloats = 0; int tintBytes = 0; int srcIndexBytesOffset = 0; int indexBytes = 0; double effectHash = 0; int blendMode = 0; int dstVertexOffset = 0; int dstIndexOffset = 0; float *dstVertexBuffer = nullptr; unsigned int *dstColorBuffer = nullptr; uint16_t *dstIndexBuffer = nullptr; bool needColor = false; int curBlendSrc = -1; int curBlendDst = -1; cc::Texture2D *curTexture = nullptr; RenderDrawInfo *curDrawInfo = nullptr; if (abs(_nodeColor.r - 1.0F) > 0.0001F || abs(_nodeColor.g - 1.0F) > 0.0001F || abs(_nodeColor.b - 1.0F) > 0.0001F || abs(_nodeColor.a - 1.0F) > 0.0001F || _premultipliedAlpha) { needColor = true; } auto handleColor = [&](SkeletonCache::ColorData *colorData) { tempA = colorData->finalColor.a * _nodeColor.a; multiplier = _premultipliedAlpha ? tempA / 255 : 1; tempR = _nodeColor.r * multiplier; tempG = _nodeColor.g * multiplier; tempB = _nodeColor.b * multiplier; finalColor.r = static_cast(std::round(colorData->finalColor.r * tempR)); finalColor.g = static_cast(std::round(colorData->finalColor.g * tempG)); finalColor.b = static_cast(std::round(colorData->finalColor.b * tempB)); finalColor.a = static_cast(std::round(tempA)); darkColor.r = static_cast(std::round(colorData->darkColor.r * tempR)); darkColor.g = static_cast(std::round(colorData->darkColor.g * tempG)); darkColor.b = static_cast(std::round(colorData->darkColor.b * tempB)); darkColor.a = _premultipliedAlpha ? 255 : 0; }; handleColor(nowColor); int segmentCount = 0; for (auto *segment : segments) { srcVertexBytes = static_cast(segment->vertexFloatCount * sizeof(float)); if (!_useTint) { tintBytes = static_cast(segment->vertexFloatCount / vs2 * sizeof(float)); vertexBytes = srcVertexBytes - tintBytes; vertexFloats = static_cast(vertexBytes / sizeof(float)); } else { vertexBytes = srcVertexBytes; vertexFloats = segment->vertexFloatCount; } curDrawInfo = requestDrawInfo(segmentCount++); entity->addDynamicRenderDrawInfo(curDrawInfo); // fill new texture index curTexture = static_cast(segment->getTexture()->getRealTexture()); gfx::Texture *texture = curTexture->getGFXTexture(); gfx::Sampler *sampler = curTexture->getGFXSampler(); curDrawInfo->setTexture(texture); curDrawInfo->setSampler(sampler); blendMode = segment->blendMode; switch (blendMode) { case BlendMode_Additive: curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); curBlendDst = static_cast(BlendFactor::ONE); break; case BlendMode_Multiply: curBlendSrc = static_cast(BlendFactor::DST_COLOR); curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); break; case BlendMode_Screen: curBlendSrc = static_cast(BlendFactor::ONE); curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_COLOR); break; default: curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); } // fill new blend src and dst auto *material = requestMaterial(curBlendSrc, curBlendDst); curDrawInfo->setMaterial(material); // fill vertex buffer vb.checkSpace(vertexBytes, true); dstVertexOffset = static_cast(vb.getCurPos()) / vbs; dstVertexBuffer = reinterpret_cast(vb.getCurBuffer()); dstColorBuffer = reinterpret_cast(vb.getCurBuffer()); if (!_useTint) { char *srcBuffer = reinterpret_cast(srcVB.getBuffer()) + srcVertexBytesOffset; for (std::size_t srcBufferIdx = 0; srcBufferIdx < srcVertexBytes; srcBufferIdx += vbs2) { vb.writeBytes(srcBuffer + srcBufferIdx, vbs); } } else { vb.writeBytes(reinterpret_cast(srcVB.getBuffer()) + srcVertexBytesOffset, vertexBytes); } // batch handle if (_enableBatch) { cc::Vec3 *point = nullptr; for (auto posIndex = 0; posIndex < vertexFloats; posIndex += vs) { point = reinterpret_cast(dstVertexBuffer + posIndex); point->z = 0; point->transformMat4(*point, nodeWorldMat); } } // handle vertex color if (needColor) { int srcVertexFloatOffset = static_cast(srcVertexBytesOffset / sizeof(float)); if (_useTint) { for (auto colorIndex = 0; colorIndex < vertexFloats; colorIndex += vs, srcVertexFloatOffset += vs2) { if (srcVertexFloatOffset >= maxVFOffset) { nowColor = colors[colorOffset++]; handleColor(nowColor); maxVFOffset = nowColor->vertexFloatOffset; } memcpy(dstColorBuffer + colorIndex + 5, &finalColor, sizeof(finalColor)); memcpy(dstColorBuffer + colorIndex + 6, &darkColor, sizeof(darkColor)); } } else { for (auto colorIndex = 0; colorIndex < vertexFloats; colorIndex += vs, srcVertexFloatOffset += vs2) { if (srcVertexFloatOffset >= maxVFOffset) { nowColor = colors[colorOffset++]; handleColor(nowColor); maxVFOffset = nowColor->vertexFloatOffset; } memcpy(dstColorBuffer + colorIndex + 5, &finalColor, sizeof(finalColor)); } } } // move src vertex buffer offset srcVertexBytesOffset += srcVertexBytes; // fill index buffer indexBytes = static_cast(segment->indexCount * sizeof(uint16_t)); ib.checkSpace(indexBytes, true); dstIndexOffset = static_cast(ib.getCurPos() / sizeof(uint16_t)); dstIndexBuffer = reinterpret_cast(ib.getCurBuffer()); ib.writeBytes(reinterpret_cast(srcIB.getBuffer()) + srcIndexBytesOffset, indexBytes); for (auto indexPos = 0; indexPos < segment->indexCount; indexPos++) { dstIndexBuffer[indexPos] += dstVertexOffset; } srcIndexBytesOffset += indexBytes; // fill new index and vertex buffer id UIMeshBuffer *uiMeshBuffer = mb->getUIMeshBuffer(); curDrawInfo->setMeshBuffer(uiMeshBuffer); // fill new index offset curDrawInfo->setIndexOffset(dstIndexOffset); // fill new indice segamentation count curDrawInfo->setIbCount(segment->indexCount); } if (_useAttach) { const auto &bonesData = frameData->getBones(); auto boneCount = frameData->getBoneCount(); for (std::size_t i = 0, n = boneCount; i < n; i++) { auto *bone = bonesData[i]; attachInfo->checkSpace(sizeof(cc::Mat4), true); attachInfo->writeBytes(reinterpret_cast(&bone->globalTransformMatrix), sizeof(cc::Mat4)); } } } Skeleton *SkeletonCacheAnimation::getSkeleton() const { return _skeletonCache->getSkeleton(); } void SkeletonCacheAnimation::setTimeScale(float scale) { _timeScale = scale; } float SkeletonCacheAnimation::getTimeScale() const { return _timeScale; } void SkeletonCacheAnimation::paused(bool value) { _paused = value; } Bone *SkeletonCacheAnimation::findBone(const std::string &boneName) const { return _skeletonCache->findBone(boneName); } Slot *SkeletonCacheAnimation::findSlot(const std::string &slotName) const { return _skeletonCache->findSlot(slotName); } void SkeletonCacheAnimation::setSkin(const std::string &skinName) { _skeletonCache->setSkin(skinName); _skeletonCache->resetAllAnimationData(); } void SkeletonCacheAnimation::setSkin(const char *skinName) { _skeletonCache->setSkin(skinName); _skeletonCache->resetAllAnimationData(); } Attachment *SkeletonCacheAnimation::getAttachment(const std::string &slotName, const std::string &attachmentName) const { return _skeletonCache->getAttachment(slotName, attachmentName); } bool SkeletonCacheAnimation::setAttachment(const std::string &slotName, const std::string &attachmentName) { auto ret = _skeletonCache->setAttachment(slotName, attachmentName); _skeletonCache->resetAllAnimationData(); return ret; } bool SkeletonCacheAnimation::setAttachment(const std::string &slotName, const char *attachmentName) { auto ret = _skeletonCache->setAttachment(slotName, attachmentName); _skeletonCache->resetAllAnimationData(); return ret; } void SkeletonCacheAnimation::setColor(float r, float g, float b, float a) { _nodeColor.r = r / 255.0F; _nodeColor.g = g / 255.0F; _nodeColor.b = b / 255.0F; _nodeColor.a = a / 255.0F; } void SkeletonCacheAnimation::setBatchEnabled(bool enabled) { if (_enableBatch != enabled) { for (auto &item : _materialCaches) { CC_SAFE_DELETE(item.second); } _materialCaches.clear(); _enableBatch = enabled; } } void SkeletonCacheAnimation::setOpacityModifyRGB(bool value) { _premultipliedAlpha = value; } bool SkeletonCacheAnimation::isOpacityModifyRGB() const { return _premultipliedAlpha; } void SkeletonCacheAnimation::beginSchedule() { MiddlewareManager::getInstance()->addTimer(this); } void SkeletonCacheAnimation::stopSchedule() { MiddlewareManager::getInstance()->removeTimer(this); if (_sharedBufferOffset) { _sharedBufferOffset->reset(); _sharedBufferOffset->clear(); } } void SkeletonCacheAnimation::onEnable() { beginSchedule(); } void SkeletonCacheAnimation::onDisable() { stopSchedule(); } void SkeletonCacheAnimation::setUseTint(bool enabled) { // cache mode default enable use tint // _useTint = enabled; } void SkeletonCacheAnimation::setAttachEnabled(bool enabled) { _useAttach = enabled; } void SkeletonCacheAnimation::setAnimation(const std::string &name, bool loop) { _playTimes = loop ? 0 : 1; _animationName = name; _animationData = _skeletonCache->buildAnimationData(_animationName); _isAniComplete = false; _accTime = 0.0F; _playCount = 0; _curFrameIndex = 0; } void SkeletonCacheAnimation::addAnimation(const std::string &name, bool loop, float delay) { auto *aniInfo = new AniQueueData(); aniInfo->animationName = name; aniInfo->loop = loop; aniInfo->delay = delay; _animationQueue.push(aniInfo); } Animation *SkeletonCacheAnimation::findAnimation(const std::string &name) const { return _skeletonCache->findAnimation(name); } void SkeletonCacheAnimation::setStartListener(const CacheFrameEvent &listener) { _startListener = listener; } void SkeletonCacheAnimation::setEndListener(const CacheFrameEvent &listener) { _endListener = listener; } void SkeletonCacheAnimation::setCompleteListener(const CacheFrameEvent &listener) { _completeListener = listener; } void SkeletonCacheAnimation::updateAnimationCache(const std::string &animationName) { _skeletonCache->resetAnimationData(animationName); } void SkeletonCacheAnimation::updateAllAnimationCache() { _skeletonCache->resetAllAnimationData(); } se_object_ptr SkeletonCacheAnimation::getSharedBufferOffset() const { if (_sharedBufferOffset) { return _sharedBufferOffset->getTypeArray(); } return nullptr; } void SkeletonCacheAnimation::setToSetupPose() { if (_skeletonCache) { _skeletonCache->setToSetupPose(); } } void SkeletonCacheAnimation::setBonesToSetupPose() { if (_skeletonCache) { _skeletonCache->setBonesToSetupPose(); } } void SkeletonCacheAnimation::setSlotsToSetupPose() { if (_skeletonCache) { _skeletonCache->setSlotsToSetupPose(); } } void SkeletonCacheAnimation::setRenderEntity(cc::RenderEntity *entity) { _entity = entity; } void SkeletonCacheAnimation::setMaterial(cc::Material *material) { _material = material; for (auto &item : _materialCaches) { CC_SAFE_DELETE(item.second); } _materialCaches.clear(); } cc::RenderDrawInfo *SkeletonCacheAnimation::requestDrawInfo(int idx) { if (_drawInfoArray.size() < idx + 1) { cc::RenderDrawInfo *draw = new cc::RenderDrawInfo(); draw->setDrawInfoType(static_cast(RenderDrawInfoType::MIDDLEWARE)); _drawInfoArray.push_back(draw); } return _drawInfoArray[idx]; } cc::Material *SkeletonCacheAnimation::requestMaterial(uint16_t blendSrc, uint16_t blendDst) { uint32_t key = static_cast(blendSrc) << 16 | static_cast(blendDst); if (_materialCaches.find(key) == _materialCaches.end()) { const IMaterialInstanceInfo info{ (Material *)_material, 0}; MaterialInstance *materialInstance = new MaterialInstance(info); PassOverrides overrides; BlendStateInfo stateInfo; stateInfo.blendColor = gfx::Color{1.0F, 1.0F, 1.0F, 1.0F}; BlendTargetInfo targetInfo; targetInfo.blendEq = gfx::BlendOp::ADD; targetInfo.blendAlphaEq = gfx::BlendOp::ADD; targetInfo.blendSrc = (gfx::BlendFactor)blendSrc; targetInfo.blendDst = (gfx::BlendFactor)blendDst; targetInfo.blendSrcAlpha = (gfx::BlendFactor)blendSrc; targetInfo.blendDstAlpha = (gfx::BlendFactor)blendDst; BlendTargetInfoList targetList{targetInfo}; stateInfo.targets = targetList; overrides.blendState = stateInfo; materialInstance->overridePipelineStates(overrides); const MacroRecord macros{{"TWO_COLORED", _useTint}, {"USE_LOCAL", !_enableBatch}}; materialInstance->recompileShaders(macros); _materialCaches[key] = materialInstance; } return _materialCaches[key]; } } // namespace spine