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
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, ®ion, 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 ¤tPassNode = _passNodes[currentPassId];
|
|
|
|
if (currentPassNode->_refCount) {
|
|
break;
|
|
}
|
|
|
|
++currentPassId;
|
|
}
|
|
|
|
lastPassId = currentPassId;
|
|
|
|
while (++currentPassId < count) {
|
|
const auto ¤tPassNode = _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
|
|
|