You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

659 lines
24 KiB

/****************************************************************************
Copyright (c) 2021-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 "FrameGraph.h"
#include <algorithm>
#include <fstream>
#include "PassNodeBuilder.h"
#include "Resource.h"
#include "base/StringUtil.h"
#include "base/std/container/set.h"
#include "frame-graph/ResourceEntry.h"
namespace cc {
namespace framegraph {
namespace {
// use function scoped static member
// to ensure correct initialization order
StringPool &getStringPool() {
static StringPool pool;
return pool;
}
} // namespace
StringHandle FrameGraph::stringToHandle(const char *const name) {
return getStringPool().stringToHandle(name);
}
const char *FrameGraph::handleToString(const StringHandle &handle) noexcept {
return getStringPool().handleToString(handle);
}
void FrameGraph::present(const TextureHandle &input, gfx::Texture *target, bool useMoveSemantic) {
static const StringHandle PRESENT_PASS{FrameGraph::stringToHandle("Present")};
const ResourceNode &resourceNode{getResourceNode(input)};
CC_ASSERT(resourceNode.writer);
struct PassDataPresent {
TextureHandle input;
};
addPass<PassDataPresent>(
resourceNode.writer->_insertPoint, PRESENT_PASS,
[&](PassNodeBuilder &builder, PassDataPresent &data) {
data.input = builder.read(input);
builder.sideEffect();
if (useMoveSemantic) {
// using a global map here so that the user don't need to worry about importing the targets every frame
static ccstd::unordered_map<uint32_t, std::pair<StringHandle, Texture>> presentTargets;
if (!presentTargets.count(target->getTypedID())) {
auto name = FrameGraph::stringToHandle(StringUtil::format("Present Target %d", target->getTypedID()).c_str());
presentTargets.emplace(std::piecewise_construct, std::forward_as_tuple(target->getTypedID()), std::forward_as_tuple(name, Texture{target}));
}
auto &resourceInfo{presentTargets[target->getTypedID()]};
TextureHandle output{getBlackboard().get(resourceInfo.first)};
if (!output.isValid()) {
output = importExternal(resourceInfo.first, resourceInfo.second);
getBlackboard().put(resourceInfo.first, output);
}
move(data.input, output, 0, 0, 0);
data.input = output;
}
},
[target](const PassDataPresent &data, const DevicePassResourceTable &table) {
auto *cmdBuff = gfx::Device::getInstance()->getCommandBuffer();
gfx::Texture *input = table.getRead(data.input);
if (input && input != target) {
gfx::TextureBlit region;
region.srcExtent.width = input->getWidth();
region.srcExtent.height = input->getHeight();
region.dstExtent.width = target->getWidth();
region.dstExtent.height = target->getHeight();
cmdBuff->blitTexture(input, target, &region, 1, gfx::Filter::POINT);
}
});
}
void FrameGraph::presentLastVersion(const VirtualResource *const virtualResource, gfx::Texture *target, bool useMoveSemantic) {
const auto it = std::find_if(_resourceNodes.rbegin(), _resourceNodes.rend(), [&virtualResource](const ResourceNode &node) {
return node.virtualResource == virtualResource;
});
CC_ASSERT(it != _resourceNodes.rend());
present(TextureHandle(static_cast<Handle::IndexType>(it.base() - _resourceNodes.begin() - 1)), target, useMoveSemantic);
}
void FrameGraph::presentFromBlackboard(const StringHandle &inputName, gfx::Texture *target, bool useMoveSemantic) {
present(TextureHandle(_blackboard.get(inputName)), target, useMoveSemantic);
}
void FrameGraph::compile() {
if (_passNodes.empty()) return;
sort();
cull();
computeResourceLifetime();
if (_merge) {
mergePassNodes();
}
computeStoreActionAndMemoryless();
generateDevicePasses();
}
void FrameGraph::execute() noexcept {
if (_passNodes.empty()) return;
for (auto &pass : _devicePasses) {
pass->execute();
}
}
void FrameGraph::reset() noexcept {
_passNodes.clear();
_resourceNodes.clear();
_virtualResources.clear();
_devicePasses.clear();
_blackboard.clear();
}
void FrameGraph::gc(uint32_t const unusedFrameCount) noexcept {
Buffer::Allocator::getInstance().gc(unusedFrameCount);
Framebuffer::Allocator::getInstance().gc(unusedFrameCount);
RenderPass::Allocator::getInstance().gc(unusedFrameCount);
Texture::Allocator::getInstance().gc(unusedFrameCount);
}
void FrameGraph::move(const TextureHandle from, const TextureHandle to, uint8_t mipmapLevel, uint8_t faceId, uint8_t arrayPosition) noexcept {
const ResourceNode &fromResourceNode = getResourceNode(from);
const ResourceNode &toResourceNode = getResourceNode(to);
CC_ASSERT(!fromResourceNode.virtualResource->isImported());
CC_ASSERT(fromResourceNode.writer);
CC_ASSERT(!toResourceNode.writer);
const gfx::TextureInfo &toTextureDesc = static_cast<ResourceEntry<Texture> *>(toResourceNode.virtualResource)->get().getDesc();
uint32_t const toTextureWidth = toTextureDesc.width >> mipmapLevel;
uint32_t const toTextureHeight = toTextureDesc.height >> mipmapLevel;
uint32_t const toTextureDepth = toTextureDesc.depth >> mipmapLevel;
CC_ASSERT(toTextureWidth && toTextureHeight && toTextureDepth);
CC_ASSERT(toTextureDesc.levelCount > mipmapLevel && toTextureDesc.layerCount > arrayPosition);
CC_ASSERT(toTextureDesc.type == gfx::TextureType::CUBE && faceId < 6 || faceId == 0);
for (ResourceNode &resourceNode : _resourceNodes) {
if (resourceNode.virtualResource == fromResourceNode.virtualResource) {
resourceNode.virtualResource = toResourceNode.virtualResource;
}
}
for (const auto &passNode : _passNodes) {
for (auto &attachment : passNode->_attachments) {
const ResourceNode &attachmentResourceNode = getResourceNode(attachment.textureHandle);
if (attachmentResourceNode.virtualResource == toResourceNode.virtualResource) {
const gfx::TextureInfo &attachmentTextureDesc = static_cast<ResourceEntry<Texture> *>(attachmentResourceNode.virtualResource)->get().getDesc();
CC_ASSERT(attachmentTextureDesc.width >> attachment.level == toTextureWidth &&
attachmentTextureDesc.height >> attachment.level == toTextureHeight &&
attachmentTextureDesc.depth >> attachment.level == toTextureDepth);
attachment.level = mipmapLevel;
attachment.layer = faceId;
attachment.index = arrayPosition;
}
}
}
}
bool FrameGraph::hasPass(StringHandle handle) {
return std::any_of(_passNodes.begin(), _passNodes.end(), [&](const auto &passNode) { return passNode->_name == handle; });
}
Handle FrameGraph::create(VirtualResource *const virtualResource) {
_virtualResources.emplace_back(virtualResource);
return createResourceNode(virtualResource);
}
PassNode &FrameGraph::createPassNode(const PassInsertPoint insertPoint, const StringHandle &name, Executable *const pass) {
_passNodes.emplace_back(ccnew PassNode(insertPoint, name, static_cast<ID>(_passNodes.size()), pass));
return *_passNodes.back();
}
Handle FrameGraph::createResourceNode(VirtualResource *const virtualResource) {
const size_t index = _resourceNodes.size();
ResourceNode resourceNode;
resourceNode.virtualResource = virtualResource;
resourceNode.version = virtualResource->_version;
_resourceNodes.emplace_back(resourceNode);
return Handle{static_cast<Handle::IndexType>(index)};
}
void FrameGraph::sort() noexcept {
std::stable_sort(_passNodes.begin(), _passNodes.end(), [](const auto &x, const auto &y) {
return x->_insertPoint < y->_insertPoint;
});
}
void FrameGraph::cull() {
for (const auto &passNode : _passNodes) {
passNode->_refCount = static_cast<uint32_t>(passNode->_writes.size()) + passNode->_sideEffect;
for (const Handle handle : passNode->_reads) {
CC_ASSERT(handle.isValid());
++_resourceNodes[handle].readerCount;
}
}
static ccstd::vector<const ResourceNode *> stack;
stack.clear();
stack.reserve(_resourceNodes.size());
for (ResourceNode &resourceNode : _resourceNodes) {
if (resourceNode.readerCount == 0 && resourceNode.writer) {
stack.push_back(&resourceNode);
}
}
while (!stack.empty()) {
PassNode *const writerPassNode = stack.back()->writer;
stack.pop_back();
if (writerPassNode) {
CC_ASSERT(writerPassNode->_refCount);
if (--writerPassNode->_refCount == 0) {
CC_ASSERT(!writerPassNode->_sideEffect);
for (const Handle handle : writerPassNode->_reads) {
ResourceNode &resourceNode = _resourceNodes[handle];
if (--resourceNode.readerCount == 0) {
stack.push_back(&resourceNode);
}
}
}
}
}
for (const ResourceNode &resourceNode : _resourceNodes) {
resourceNode.virtualResource->_refCount += resourceNode.readerCount;
}
}
void FrameGraph::computeResourceLifetime() {
for (const auto &passNode : _passNodes) {
if (passNode->_refCount == 0) {
continue;
}
for (const Handle handle : passNode->_reads) {
_resourceNodes[handle].virtualResource->updateLifetime(passNode.get());
}
for (const Handle handle : passNode->_writes) {
_resourceNodes[handle].virtualResource->updateLifetime(passNode.get());
++_resourceNodes[handle].virtualResource->_writerCount;
}
std::sort(passNode->_attachments.begin(), passNode->_attachments.end(), RenderTargetAttachment::Sorter());
}
for (const auto &resource : _virtualResources) {
CC_ASSERT(!resource->_firstUsePass == !resource->_lastUsePass);
if (!resource->_firstUsePass || !resource->_lastUsePass) {
continue;
}
if (resource->_refCount == 0 && !resource->_lastUsePass->getRenderTargetAttachment(*this, resource.get())) {
continue;
}
resource->_firstUsePass->_resourceRequestArray.push_back(resource.get());
resource->_lastUsePass->_resourceReleaseArray.push_back(resource.get());
}
}
void FrameGraph::mergePassNodes() noexcept {
const size_t count = _passNodes.size();
size_t currentPassId = 0;
size_t lastPassId = 0;
while (currentPassId < count) {
const auto &currentPassNode = _passNodes[currentPassId];
if (currentPassNode->_refCount) {
break;
}
++currentPassId;
}
lastPassId = currentPassId;
while (++currentPassId < count) {
const auto &currentPassNode = _passNodes[currentPassId];
if (currentPassNode->_refCount == 0) {
continue;
}
const auto &lastPassNode = _passNodes[lastPassId];
if (lastPassNode->canMerge(*this, *currentPassNode)) {
auto *prevPassNode = lastPassNode.get();
uint16_t distance = 1;
while (prevPassNode->_next) {
prevPassNode = prevPassNode->_next;
++distance;
}
prevPassNode->_next = currentPassNode.get();
currentPassNode->_head = lastPassNode.get();
currentPassNode->_distanceToHead = distance;
currentPassNode->_refCount = 0;
const size_t attachmentCount = lastPassNode->_attachments.size();
for (size_t i = 0; i < attachmentCount; ++i) {
const RenderTargetAttachment &attachmentInLastPassNode = lastPassNode->_attachments[i];
const RenderTargetAttachment &attachmentInCurrentPassNode = currentPassNode->_attachments[i];
ResourceNode &resourceNode = _resourceNodes[attachmentInLastPassNode.textureHandle];
uint16_t &writeCount = resourceNode.virtualResource->_writerCount;
CC_ASSERT_GT(writeCount, 1);
--writeCount;
resourceNode.readerCount += _resourceNodes[attachmentInCurrentPassNode.textureHandle].readerCount;
resourceNode.readerCount -= attachmentInCurrentPassNode.desc.loadOp == gfx::LoadOp::LOAD;
}
} else {
lastPassId = currentPassId;
}
}
}
void FrameGraph::computeStoreActionAndMemoryless() {
ID passId = 0;
bool lastPassSubpassEnable = false;
for (const auto &passNode : _passNodes) {
if (passNode->_refCount == 0) {
continue;
}
ID const oldPassId = passId;
passId += !passNode->_subpass || lastPassSubpassEnable != passNode->_subpass;
passId += oldPassId == passId ? passNode->_hasClearedAttachment * !passNode->_clearActionIgnorable : 0;
passNode->setDevicePassId(passId);
lastPassSubpassEnable = passNode->_subpass && !passNode->_subpassEnd;
}
static ccstd::set<VirtualResource *> renderTargets;
renderTargets.clear();
for (const auto &passNode : _passNodes) {
if (passNode->_refCount == 0) {
continue;
}
for (RenderTargetAttachment &attachment : passNode->_attachments) {
CC_ASSERT(attachment.textureHandle.isValid());
ResourceNode &resourceNode = getResourceNode(attachment.textureHandle);
if (resourceNode.virtualResource->isImported() || resourceNode.readerCount) {
if (passNode->_subpass) {
if (passNode->_devicePassId != resourceNode.virtualResource->_lastUsePass->_devicePassId) {
attachment.storeOp = gfx::StoreOp::STORE;
}
} else {
if (attachment.desc.writeMask) {
attachment.storeOp = gfx::StoreOp::STORE;
}
}
}
if (passNode->_subpass && attachment.desc.loadOp == gfx::LoadOp::LOAD && resourceNode.version > 1) {
ResourceNode *const resourceNodePrevVersion = getResourceNode(resourceNode.virtualResource, resourceNode.version - 1);
CC_ASSERT(resourceNodePrevVersion);
if (resourceNodePrevVersion->writer->_devicePassId == passNode->_devicePassId) {
attachment.desc.loadOp = gfx::LoadOp::DISCARD;
resourceNodePrevVersion->writer->getRenderTargetAttachment(*this, resourceNodePrevVersion->virtualResource)->storeOp = gfx::StoreOp::DISCARD;
}
}
if (attachment.desc.loadOp == gfx::LoadOp::LOAD) {
resourceNode.virtualResource->_neverLoaded = false;
}
if (attachment.storeOp == gfx::StoreOp::STORE) {
resourceNode.virtualResource->_neverStored = false;
}
renderTargets.emplace(resourceNode.virtualResource);
}
}
for (VirtualResource *const renderTarget : renderTargets) {
const gfx::TextureInfo &textureDesc = static_cast<ResourceEntry<Texture> *>(renderTarget)->get().getDesc();
renderTarget->_memoryless = renderTarget->_neverLoaded && renderTarget->_neverStored;
renderTarget->_memorylessMSAA = textureDesc.samples != gfx::SampleCount::X1 && renderTarget->_writerCount < 2;
}
}
void FrameGraph::generateDevicePasses() {
Buffer::Allocator::getInstance().tick();
Framebuffer::Allocator::getInstance().tick();
RenderPass::Allocator::getInstance().tick();
Texture::Allocator::getInstance().tick();
ID passId = 1;
static ccstd::vector<PassNode *> subpassNodes;
subpassNodes.clear();
for (const auto &passNode : _passNodes) {
if (passNode->_refCount == 0) {
continue;
}
if (passId != passNode->_devicePassId) {
_devicePasses.emplace_back(ccnew DevicePass(*this, subpassNodes));
for (PassNode *const p : subpassNodes) {
p->releaseTransientResources();
}
subpassNodes.clear();
passId = passNode->_devicePassId;
}
passNode->requestTransientResources();
subpassNodes.emplace_back(passNode.get());
}
CC_ASSERT(subpassNodes.size() == 1);
_devicePasses.emplace_back(ccnew DevicePass(*this, subpassNodes));
for (PassNode *const p : subpassNodes) {
p->releaseTransientResources();
}
}
// https://dreampuf.github.io/GraphvizOnline/
void FrameGraph::exportGraphViz(const ccstd::string &path) {
std::ofstream out(path, std::ios::out | std::ios::binary);
//out.imbue(std::locale("chs", std::locale::ctype));
if (out.fail()) {
return;
}
out << "digraph framegraph {\n";
out << "rankdir = LR\n";
out << "bgcolor = black\n";
out << "node [shape=rectangle, fontname=\"helvetica\", fontsize=10]\n\n";
for (const auto &node : _passNodes) {
if (node->_head) {
continue;
}
out << "\"P" << node->_id << "\" [label=\"" << node->_name.str();
const PassNode *currPassNode = node.get();
if (currPassNode->_head) {
out << "\\n(merged by pass " << currPassNode->_head->_name.str() << ")";
} else {
while (currPassNode->_next) {
currPassNode = currPassNode->_next;
out << " & " << currPassNode->_name.str();
}
}
out << "\\nrefs: " << node->_refCount
<< "\\nseq: " << node->_id
<< "\\ndevice pass id: " << node->_devicePassId
<< "\", style=filled, fillcolor="
<< (node->_refCount ? "darkorange" : "darkorange4") << "]\n";
}
out << "\n";
for (const ResourceNode &node : _resourceNodes) {
if (node.writer && node.writer->_head) {
continue;
}
out << "\"R" << node.virtualResource->_id << "_" << +node.version << "\""
"[label=\""
<< node.virtualResource->_name.str() << "\\n(version: " << +node.version << ")"
<< "\\nrefs:" << node.virtualResource->_refCount;
if (node.virtualResource->_memoryless) {
out << "\\nMemoryless";
}
if (node.virtualResource->_memorylessMSAA) {
out << "\\nMemorylessMSAA";
}
PassNode *const writer = (node.writer && node.writer->_head) ? node.writer->_head : node.writer;
if (writer) {
out << "\\n";
const RenderTargetAttachment *const attachment = writer->getRenderTargetAttachment(*this, node.virtualResource);
if (attachment) {
switch (attachment->desc.loadOp) {
case gfx::LoadOp::DISCARD:
out << "DontCare";
break;
case gfx::LoadOp::CLEAR:
out << "Clear";
break;
default:
out << "Load";
break;
}
out << ", ";
out << (attachment->storeOp == gfx::StoreOp::DISCARD ? "DontCare" : "Store");
out << "\\nWriteMask: 0x" << std::hex << static_cast<uint32_t>(attachment->desc.writeMask) << std::dec;
} else {
out << "Transfer";
}
}
out << "\", style=filled, fillcolor="
<< ((node.virtualResource->isImported())
? (node.virtualResource->_refCount ? "palegreen" : "palegreen4")
: (node.version == 0 ? "pink" : (node.virtualResource->_refCount ? "skyblue" : "skyblue4")))
<< "]\n";
}
out << "\n";
for (const auto &node : _passNodes) {
if (node->_head) {
continue;
}
out << "P" << node->_id << " -> { ";
for (const Handle writer : node->_writes) {
out << "R" << _resourceNodes[writer].virtualResource->_id << "_" << +_resourceNodes[writer].version << " ";
}
out << "} [color=red]\n";
}
out << "\n";
for (const ResourceNode &node : _resourceNodes) {
if (node.writer && node.writer->_head) {
continue;
}
out << "R" << node.virtualResource->_id << "_" << +node.version << " -> { ";
for (const auto &passNode : _passNodes) {
if (passNode->_head) {
continue;
}
for (Handle read : passNode->_reads) {
const ResourceNode *readResourceNode = &_resourceNodes[read];
if (readResourceNode->writer && readResourceNode->writer->_head) {
const Handle resourceNodeHandlePrevVersion = readResourceNode->writer->_head->getWriteResourceNodeHandle(*this, readResourceNode->virtualResource);
CC_ASSERT(resourceNodeHandlePrevVersion.isValid());
readResourceNode = &getResourceNode(resourceNodeHandlePrevVersion);
}
if (readResourceNode->virtualResource->_id == node.virtualResource->_id &&
readResourceNode->version == node.version) {
out << "P" << passNode->_id << " ";
}
}
}
out << "} [color=green]\n";
}
for (const ResourceNode &node : _resourceNodes) {
if (node.writer && node.writer->_head) {
continue;
}
out << "R" << node.virtualResource->_id << "_" << +node.version << " -> { ";
for (const auto &passNode : _passNodes) {
if (passNode->_head) {
continue;
}
for (const RenderTargetAttachment &attachment : passNode->_attachments) {
const ResourceNode *readResourceNode = &_resourceNodes[attachment.textureHandle];
uint16_t const distanceToHead = readResourceNode->writer->_distanceToHead;
if (readResourceNode->writer && readResourceNode->writer->_head) {
const Handle resourceNodeHandleHead = readResourceNode->writer->_head->getWriteResourceNodeHandle(*this, readResourceNode->virtualResource);
CC_ASSERT(resourceNodeHandleHead.isValid());
readResourceNode = &getResourceNode(resourceNodeHandleHead);
}
if (readResourceNode->virtualResource == node.virtualResource &&
readResourceNode->version == node.version + 1 - distanceToHead) {
out << "P" << passNode->_id << " ";
}
}
}
out << "} [color=red4]\n";
}
out << "}" << std::endl;
out.close();
}
ResourceNode *FrameGraph::getResourceNode(const VirtualResource *const virtualResource, uint8_t version) noexcept {
const auto it = std::find_if(_resourceNodes.begin(), _resourceNodes.end(), [&](const ResourceNode &node) {
return node.version == version && node.virtualResource == virtualResource;
});
return it == _resourceNodes.end() ? nullptr : &(*it);
}
} // namespace framegraph
} // namespace cc