/**************************************************************************** Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. http://www.cocos.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "2d/renderer/Batcher2d.h" #include "application/ApplicationManager.h" #include "base/TypeDef.h" #include "core/Root.h" #include "core/scene-graph/Scene.h" #include "editor-support/MiddlewareManager.h" #include "renderer/pipeline/Define.h" #include "scene/Pass.h" namespace cc { Batcher2d::Batcher2d() : Batcher2d(nullptr) { } Batcher2d::Batcher2d(Root* root) : _drawBatchPool([]() { return ccnew scene::DrawBatch2D(); }, [](auto* obj) { delete obj; }, 10U) { if (root == nullptr) { root = Root::getInstance(); } _root = root; _device = _root->getDevice(); _stencilManager = StencilManager::getInstance(); } Batcher2d::~Batcher2d() { // NOLINT _drawBatchPool.destroy(); for (auto iter : _descriptorSetCache) { delete iter.second; } for (auto* drawBatch : _batches) { delete drawBatch; } _attributes.clear(); if (_maskClearModel != nullptr) { Root::getInstance()->destroyModel(_maskClearModel); _maskClearModel = nullptr; } if (_maskModelMesh != nullptr) { _maskModelMesh->destroy(); _maskModelMesh = nullptr; } _maskClearMtl = nullptr; _maskAttributes.clear(); } void Batcher2d::syncMeshBuffersToNative(uint16_t accId, ccstd::vector&& buffers) { _meshBuffersMap[accId] = std::move(buffers); } UIMeshBuffer* Batcher2d::getMeshBuffer(uint16_t accId, uint16_t bufferId) { // NOLINT(bugprone-easily-swappable-parameters) const auto& map = _meshBuffersMap[accId]; return map[bufferId]; } gfx::Device* Batcher2d::getDevice() { if (_device == nullptr) { _device = Root::getInstance()->getDevice(); } return _device; } void Batcher2d::updateDescriptorSet() { } void Batcher2d::syncRootNodesToNative(ccstd::vector&& rootNodes) { _rootNodeArr = std::move(rootNodes); } void Batcher2d::fillBuffersAndMergeBatches() { size_t index = 0; for (auto* rootNode : _rootNodeArr) { // _batches will add by generateBatch walk(rootNode, 1); generateBatch(_currEntity, _currDrawInfo); auto* scene = rootNode->getScene()->getRenderScene(); size_t const count = _batches.size(); for (size_t i = index; i < count; i++) { scene->addBatch(_batches.at(i)); } index = count; } } void Batcher2d::walk(Node* node, float parentOpacity) { // NOLINT(misc-no-recursion) if (!node->isActiveInHierarchy()) { return; } bool breakWalk = false; auto* entity = static_cast(node->getUserData()); if (entity) { if (entity->getColorDirty()) { float localOpacity = entity->getLocalOpacity(); float localColorAlpha = entity->getColorAlpha(); entity->setOpacity(parentOpacity * localOpacity * localColorAlpha); entity->setColorDirty(false); entity->setVBColorDirty(true); } if (math::isEqualF(entity->getOpacity(), 0)) { breakWalk = true; } else if (entity->isEnabled()) { uint32_t size = entity->getRenderDrawInfosSize(); for (uint32_t i = 0; i < size; i++) { auto* drawInfo = entity->getRenderDrawInfoAt(i); handleDrawInfo(entity, drawInfo, node); } entity->setVBColorDirty(false); } if (entity->getRenderEntityType() == RenderEntityType::CROSSED) { breakWalk = true; } } if (!breakWalk) { const auto& children = node->getChildren(); float thisOpacity = entity ? entity->getOpacity() : parentOpacity; for (const auto& child : children) { // we should find parent opacity recursively upwards if it doesn't have an entity. walk(child, thisOpacity); } } // post assembler if (_stencilManager->getMaskStackSize() > 0 && entity && entity->isEnabled()) { handlePostRender(entity); } } void Batcher2d::handlePostRender(RenderEntity* entity) { bool isMask = entity->getIsMask(); if (isMask) { generateBatch(_currEntity, _currDrawInfo); resetRenderStates(); _stencilManager->exitMask(); } } CC_FORCE_INLINE void Batcher2d::handleComponentDraw(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node) { ccstd::hash_t dataHash = drawInfo->getDataHash(); if (drawInfo->getIsMeshBuffer()) { dataHash = 0; } // may slow bool isMask = entity->getIsMask(); if (isMask) { // Mask subComp insertMaskBatch(entity); } else { entity->setEnumStencilStage(_stencilManager->getStencilStage()); } auto tempStage = static_cast(entity->getStencilStage()); if (_currHash != dataHash || dataHash == 0 || _currMaterial != drawInfo->getMaterial() || _currStencilStage != tempStage) { // Generate a batch if not batching generateBatch(_currEntity, _currDrawInfo); if (!drawInfo->getIsMeshBuffer()) { UIMeshBuffer* buffer = drawInfo->getMeshBuffer(); if (_currMeshBuffer != buffer) { _currMeshBuffer = buffer; _indexStart = _currMeshBuffer->getIndexOffset(); } } _currHash = dataHash; _currMaterial = drawInfo->getMaterial(); _currStencilStage = tempStage; _currLayer = entity->getNode()->getLayer(); _currEntity = entity; _currDrawInfo = drawInfo; _currTexture = drawInfo->getTexture(); _currSampler = drawInfo->getSampler(); if (_currSampler == nullptr) { _currSamplerHash = 0; } else { _currSamplerHash = _currSampler->getHash(); } } if (!drawInfo->getIsMeshBuffer()) { if (node->getChangedFlags() || drawInfo->getVertDirty()) { fillVertexBuffers(entity, drawInfo); drawInfo->setVertDirty(false); } if (entity->getVBColorDirty()) { fillColors(entity, drawInfo); } fillIndexBuffers(drawInfo); } if (isMask) { _stencilManager->enableMask(); } } CC_FORCE_INLINE void Batcher2d::handleModelDraw(RenderEntity* entity, RenderDrawInfo* drawInfo) { generateBatch(_currEntity, _currDrawInfo); resetRenderStates(); // stencil stage gfx::DepthStencilState* depthStencil = nullptr; ccstd::hash_t dssHash = 0; Material* renderMat = drawInfo->getMaterial(); bool isMask = entity->getIsMask(); if (isMask) { // Mask Comp insertMaskBatch(entity); } else { entity->setEnumStencilStage(_stencilManager->getStencilStage()); } StencilStage entityStage = entity->getEnumStencilStage(); depthStencil = _stencilManager->getDepthStencilState(entityStage, renderMat); dssHash = _stencilManager->getStencilHash(entityStage); // Model auto* model = drawInfo->getModel(); if (model == nullptr) return; auto stamp = CC_CURRENT_ENGINE()->getTotalFrames(); model->updateTransform(stamp); model->updateUBOs(stamp); const auto& subModelList = model->getSubModels(); for (const auto& submodel : subModelList) { auto* curdrawBatch = _drawBatchPool.alloc(); curdrawBatch->setVisFlags(entity->getNode()->getLayer()); curdrawBatch->setModel(model); curdrawBatch->setInputAssembler(submodel->getInputAssembler()); curdrawBatch->setDescriptorSet(submodel->getDescriptorSet()); curdrawBatch->fillPass(renderMat, depthStencil, dssHash, &(submodel->getPatches())); _batches.push_back(curdrawBatch); } if (isMask) { _stencilManager->enableMask(); } } CC_FORCE_INLINE void Batcher2d::handleMiddlewareDraw(RenderEntity* entity, RenderDrawInfo* drawInfo) { auto layer = entity->getNode()->getLayer(); Material* material = drawInfo->getMaterial(); auto* texture = drawInfo->getTexture(); auto* sampler = drawInfo->getSampler(); auto* meshBuffer = drawInfo->getMeshBuffer(); // check for merge draw auto enableBatch = !entity->getUseLocal(); if (enableBatch && _currTexture == texture && _currMeshBuffer == meshBuffer && !_currEntity->getUseLocal() && material->getHash() == _currMaterial->getHash() && drawInfo->getIndexOffset() == _currDrawInfo->getIndexOffset() + _currDrawInfo->getIbCount() && layer == _currLayer) { auto ibCount = _currDrawInfo->getIbCount(); _currDrawInfo->setIbCount(ibCount + drawInfo->getIbCount()); } else { generateBatch(_currEntity, _currDrawInfo); _currLayer = layer; _currMaterial = material; _currTexture = texture; _currMeshBuffer = meshBuffer; _currEntity = entity; _currDrawInfo = drawInfo; _currHash = 0; } } CC_FORCE_INLINE void Batcher2d::handleSubNode(RenderEntity* entity, RenderDrawInfo* drawInfo) { // NOLINT if (drawInfo->getSubNode()) { walk(drawInfo->getSubNode(), entity->getOpacity()); } } CC_FORCE_INLINE void Batcher2d::handleDrawInfo(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node) { // NOLINT(misc-no-recursion) CC_ASSERT(entity); CC_ASSERT(drawInfo); RenderDrawInfoType drawInfoType = drawInfo->getEnumDrawInfoType(); switch (drawInfoType) { case RenderDrawInfoType::COMP: handleComponentDraw(entity, drawInfo, node); break; case RenderDrawInfoType::MODEL: handleModelDraw(entity, drawInfo); break; case RenderDrawInfoType::MIDDLEWARE: handleMiddlewareDraw(entity, drawInfo); break; case RenderDrawInfoType::SUB_NODE: handleSubNode(entity, drawInfo); break; default: break; } } void Batcher2d::generateBatch(RenderEntity* entity, RenderDrawInfo* drawInfo) { if (drawInfo == nullptr) { return; } if (drawInfo->getEnumDrawInfoType() == RenderDrawInfoType::MIDDLEWARE) { generateBatchForMiddleware(entity, drawInfo); return; } if (_currMaterial == nullptr) { return; } gfx::InputAssembler* ia = nullptr; uint32_t indexOffset = 0; uint32_t indexCount = 0; if (drawInfo->getIsMeshBuffer()) { // Todo MeshBuffer RenderData ia = drawInfo->requestIA(getDevice()); indexOffset = drawInfo->getIndexOffset(); indexCount = drawInfo->getIbCount(); _meshRenderDrawInfo.emplace_back(drawInfo); } else { UIMeshBuffer* currMeshBuffer = drawInfo->getMeshBuffer(); currMeshBuffer->setDirty(true); ia = currMeshBuffer->requireFreeIA(getDevice()); indexCount = currMeshBuffer->getIndexOffset() - _indexStart; if (ia == nullptr) { return; } indexOffset = _indexStart; _indexStart = currMeshBuffer->getIndexOffset(); } _currMeshBuffer = nullptr; // stencilStage gfx::DepthStencilState* depthStencil = nullptr; ccstd::hash_t dssHash = 0; StencilStage entityStage = entity->getEnumStencilStage(); depthStencil = _stencilManager->getDepthStencilState(entityStage, _currMaterial); dssHash = _stencilManager->getStencilHash(entityStage); auto* curdrawBatch = _drawBatchPool.alloc(); curdrawBatch->setVisFlags(_currLayer); curdrawBatch->setInputAssembler(ia); curdrawBatch->setFirstIndex(indexOffset); curdrawBatch->setIndexCount(indexCount); curdrawBatch->fillPass(_currMaterial, depthStencil, dssHash); const auto& pass = curdrawBatch->getPasses().at(0); if (entity->getUseLocal()) { drawInfo->updateLocalDescriptorSet(entity->getRenderTransform(), pass->getLocalSetLayout()); curdrawBatch->setDescriptorSet(drawInfo->getLocalDes()); } else { curdrawBatch->setDescriptorSet(getDescriptorSet(_currTexture, _currSampler, pass->getLocalSetLayout())); } _batches.push_back(curdrawBatch); } void Batcher2d::generateBatchForMiddleware(RenderEntity* entity, RenderDrawInfo* drawInfo) { auto layer = entity->getNode()->getLayer(); auto* material = drawInfo->getMaterial(); auto* texture = drawInfo->getTexture(); auto* sampler = drawInfo->getSampler(); auto* meshBuffer = drawInfo->getMeshBuffer(); // set meshbuffer offset auto indexOffset = drawInfo->getIndexOffset(); auto indexCount = drawInfo->getIbCount(); indexOffset += indexCount; if (meshBuffer->getIndexOffset() < indexOffset) { meshBuffer->setIndexOffset(indexOffset); } meshBuffer->setDirty(true); gfx::InputAssembler* ia = meshBuffer->requireFreeIA(getDevice()); // stencilstage auto stencilStage = _stencilManager->getStencilStage(); gfx::DepthStencilState* depthStencil = _stencilManager->getDepthStencilState(stencilStage, material); ccstd::hash_t dssHash = _stencilManager->getStencilHash(stencilStage); auto* curdrawBatch = _drawBatchPool.alloc(); curdrawBatch->setVisFlags(_currLayer); curdrawBatch->setInputAssembler(ia); curdrawBatch->setFirstIndex(drawInfo->getIndexOffset()); curdrawBatch->setIndexCount(drawInfo->getIbCount()); curdrawBatch->fillPass(material, depthStencil, dssHash); const auto& pass = curdrawBatch->getPasses().at(0); if (entity->getUseLocal()) { drawInfo->updateLocalDescriptorSet(entity->getNode(), pass->getLocalSetLayout()); curdrawBatch->setDescriptorSet(drawInfo->getLocalDes()); } else { curdrawBatch->setDescriptorSet(getDescriptorSet(texture, sampler, pass->getLocalSetLayout())); } _batches.push_back(curdrawBatch); // make sure next generateBatch return. resetRenderStates(); _currMeshBuffer = nullptr; } void Batcher2d::resetRenderStates() { _currMaterial = nullptr; _currTexture = nullptr; _currSampler = nullptr; _currSamplerHash = 0; _currLayer = 0; _currEntity = nullptr; _currDrawInfo = nullptr; } gfx::DescriptorSet* Batcher2d::getDescriptorSet(gfx::Texture* texture, gfx::Sampler* sampler, const gfx::DescriptorSetLayout* dsLayout) { ccstd::hash_t hash = 2; size_t textureHash; if (texture != nullptr) { textureHash = boost::hash_value(texture); ccstd::hash_combine(hash, textureHash); } if (sampler != nullptr) { ccstd::hash_combine(hash, sampler->getHash()); } auto iter = _descriptorSetCache.find(hash); if (iter != _descriptorSetCache.end()) { if (texture != nullptr && sampler != nullptr) { iter->second->bindTexture(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), texture); iter->second->bindSampler(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), sampler); } iter->second->forceUpdate(); return iter->second; } _dsInfo.layout = dsLayout; auto* ds = getDevice()->createDescriptorSet(_dsInfo); if (texture != nullptr && sampler != nullptr) { ds->bindTexture(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), texture); ds->bindSampler(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), sampler); } ds->update(); _descriptorSetCache.emplace(hash, ds); return ds; } void Batcher2d::releaseDescriptorSetCache(gfx::Texture* texture, gfx::Sampler* sampler) { ccstd::hash_t hash = 2; size_t textureHash; if (texture != nullptr) { textureHash = boost::hash_value(texture); ccstd::hash_combine(hash, textureHash); } if (sampler != nullptr) { ccstd::hash_combine(hash, sampler->getHash()); } auto iter = _descriptorSetCache.find(hash); if (iter != _descriptorSetCache.end()) { delete iter->second; _descriptorSetCache.erase(hash); } } bool Batcher2d::initialize() { _isInit = true; return _isInit; } void Batcher2d::update() { fillBuffersAndMergeBatches(); resetRenderStates(); } void Batcher2d::uploadBuffers() { if (_batches.empty()) { return; } for (auto& meshRenderData : _meshRenderDrawInfo) { meshRenderData->uploadBuffers(); } for (auto& map : _meshBuffersMap) { for (auto& buffer : map.second) { buffer->uploadBuffers(); buffer->reset(); } } updateDescriptorSet(); } void Batcher2d::reset() { for (auto& batch : _batches) { batch->clear(); _drawBatchPool.free(batch); } _batches.clear(); for (auto& meshRenderData : _meshRenderDrawInfo) { meshRenderData->resetMeshIA(); } _meshRenderDrawInfo.clear(); // meshDataArray for (auto& map : _meshBuffersMap) { for (auto& buffer : map.second) { if (buffer) { buffer->resetIA(); } } } // meshBuffer cannot clear because it is not transported at every frame. _currMeshBuffer = nullptr; _indexStart = 0; _currHash = 0; _currLayer = 0; _currMaterial = nullptr; _currTexture = nullptr; _currSampler = nullptr; // stencilManager } void Batcher2d::insertMaskBatch(RenderEntity* entity) { generateBatch(_currEntity, _currDrawInfo); resetRenderStates(); createClearModel(); _maskClearModel->setNode(entity->getNode()); _maskClearModel->setTransform(entity->getNode()); _stencilManager->pushMask(); auto stage = _stencilManager->clear(entity); gfx::DepthStencilState* depthStencil = nullptr; ccstd::hash_t dssHash = 0; if (_maskClearMtl != nullptr) { depthStencil = _stencilManager->getDepthStencilState(stage, _maskClearMtl); dssHash = _stencilManager->getStencilHash(stage); } // Model if (_maskClearModel == nullptr) return; auto stamp = CC_CURRENT_ENGINE()->getTotalFrames(); _maskClearModel->updateTransform(stamp); _maskClearModel->updateUBOs(stamp); const auto& subModelList = _maskClearModel->getSubModels(); for (const auto& submodel : subModelList) { auto* curdrawBatch = _drawBatchPool.alloc(); curdrawBatch->setVisFlags(entity->getNode()->getLayer()); curdrawBatch->setModel(_maskClearModel); curdrawBatch->setInputAssembler(submodel->getInputAssembler()); curdrawBatch->setDescriptorSet(submodel->getDescriptorSet()); curdrawBatch->fillPass(_maskClearMtl, depthStencil, dssHash, &(submodel->getPatches())); _batches.push_back(curdrawBatch); } _stencilManager->enterLevel(entity); } void Batcher2d::createClearModel() { if (_maskClearModel == nullptr) { _maskClearMtl = BuiltinResMgr::getInstance()->get(ccstd::string("default-clear-stencil")); _maskClearModel = Root::getInstance()->createModel(); uint32_t stride = 12; // vfmt auto* vertexBuffer = _device->createBuffer({ gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST, gfx::MemoryUsageBit::DEVICE, 4 * stride, stride, }); const float vertices[] = {-1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0}; vertexBuffer->update(vertices); auto* indexBuffer = _device->createBuffer({ gfx::BufferUsageBit::INDEX | gfx::BufferUsageBit::TRANSFER_DST, gfx::MemoryUsageBit::DEVICE, 6 * sizeof(uint16_t), sizeof(uint16_t), }); const uint16_t indices[] = {0, 2, 1, 2, 1, 3}; indexBuffer->update(indices); gfx::BufferList vbReference; vbReference.emplace_back(vertexBuffer); _maskModelMesh = ccnew RenderingSubMesh(vbReference, _maskAttributes, _primitiveMode, indexBuffer); _maskModelMesh->setSubMeshIdx(0); _maskClearModel->initSubModel(0, _maskModelMesh, _maskClearMtl); } } } // namespace cc