blob: c63b81a931ad6a30803921ce580ae246563c0b3e [file] [log] [blame]
//
// Copyright 2017 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// CommandGraph:
// Deferred work constructed by GL calls, that will later be flushed to Vulkan.
//
#include "libANGLE/renderer/vulkan/CommandGraph.h"
#include "libANGLE/renderer/vulkan/RenderTargetVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "libANGLE/renderer/vulkan/vk_format_utils.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"
namespace rx
{
namespace vk
{
namespace
{
Error InitAndBeginCommandBuffer(VkDevice device,
const CommandPool &commandPool,
const VkCommandBufferInheritanceInfo &inheritanceInfo,
VkCommandBufferUsageFlags flags,
CommandBuffer *commandBuffer)
{
ASSERT(!commandBuffer->valid());
VkCommandBufferAllocateInfo createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
createInfo.pNext = nullptr;
createInfo.commandPool = commandPool.getHandle();
createInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
createInfo.commandBufferCount = 1;
ANGLE_TRY(commandBuffer->init(device, createInfo));
VkCommandBufferBeginInfo beginInfo;
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.pNext = nullptr;
beginInfo.flags = flags | VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.pInheritanceInfo = &inheritanceInfo;
ANGLE_TRY(commandBuffer->begin(beginInfo));
return NoError();
}
} // anonymous namespace
class CommandGraphNode final : angle::NonCopyable
{
public:
CommandGraphNode();
~CommandGraphNode();
// Immutable queries for when we're walking the commands tree.
CommandBuffer *getOutsideRenderPassCommands();
CommandBuffer *getInsideRenderPassCommands();
// For outside the render pass (copies, transitions, etc).
Error beginOutsideRenderPassRecording(VkDevice device,
const CommandPool &commandPool,
CommandBuffer **commandsOut);
// For rendering commands (draws).
Error beginInsideRenderPassRecording(RendererVk *renderer, CommandBuffer **commandsOut);
// storeRenderPassInfo and append*RenderTarget store info relevant to the RenderPass.
void storeRenderPassInfo(const Framebuffer &framebuffer,
const gl::Rectangle renderArea,
const vk::RenderPassDesc &renderPassDesc,
const std::vector<VkClearValue> &clearValues);
// Dependency commands order node execution in the command graph.
// Once a node has commands that must happen after it, recording is stopped and the node is
// frozen forever.
static void SetHappensBeforeDependency(CommandGraphNode *beforeNode,
CommandGraphNode *afterNode);
static void SetHappensBeforeDependencies(const std::vector<CommandGraphNode *> &beforeNodes,
CommandGraphNode *afterNode);
bool hasParents() const;
bool hasChildren() const;
// Commands for traversing the node on a flush operation.
VisitedState visitedState() const;
void visitParents(std::vector<CommandGraphNode *> *stack);
Error visitAndExecute(VkDevice device,
Serial serial,
RenderPassCache *renderPassCache,
CommandBuffer *primaryCommandBuffer);
const gl::Rectangle &getRenderPassRenderArea() const;
private:
void setHasChildren();
// Used for testing only.
bool isChildOf(CommandGraphNode *parent);
// Only used if we need a RenderPass for these commands.
RenderPassDesc mRenderPassDesc;
Framebuffer mRenderPassFramebuffer;
gl::Rectangle mRenderPassRenderArea;
gl::AttachmentArray<VkClearValue> mRenderPassClearValues;
// Keep a separate buffers for commands inside and outside a RenderPass.
// TODO(jmadill): We might not need inside and outside RenderPass commands separate.
CommandBuffer mOutsideRenderPassCommands;
CommandBuffer mInsideRenderPassCommands;
// Parents are commands that must be submitted before 'this' CommandNode can be submitted.
std::vector<CommandGraphNode *> mParents;
// If this is true, other commands exist that must be submitted after 'this' command.
bool mHasChildren;
// Used when traversing the dependency graph.
VisitedState mVisitedState;
};
// CommandGraphResource implementation.
CommandGraphResource::CommandGraphResource() : mCurrentWritingNode(nullptr)
{
}
CommandGraphResource::~CommandGraphResource() = default;
void CommandGraphResource::updateQueueSerial(Serial queueSerial)
{
ASSERT(queueSerial >= mStoredQueueSerial);
if (queueSerial > mStoredQueueSerial)
{
mCurrentWritingNode = nullptr;
mCurrentReadingNodes.clear();
mStoredQueueSerial = queueSerial;
}
}
Serial CommandGraphResource::getQueueSerial() const
{
return mStoredQueueSerial;
}
bool CommandGraphResource::hasChildlessWritingNode() const
{
return (mCurrentWritingNode != nullptr && !mCurrentWritingNode->hasChildren());
}
CommandGraphNode *CommandGraphResource::getNewWritingNode(RendererVk *renderer)
{
CommandGraphNode *newCommands = renderer->getCommandGraph()->allocateNode();
onWriteImpl(newCommands, renderer->getCurrentQueueSerial());
return newCommands;
}
bool CommandGraphResource::hasStartedWriteResource() const
{
return hasChildlessWritingNode() &&
mCurrentWritingNode->getOutsideRenderPassCommands()->valid();
}
Error CommandGraphResource::beginWriteResource(RendererVk *renderer,
CommandBuffer **commandBufferOut)
{
CommandGraphNode *commands = getNewWritingNode(renderer);
VkDevice device = renderer->getDevice();
ANGLE_TRY(commands->beginOutsideRenderPassRecording(device, renderer->getCommandPool(),
commandBufferOut));
return NoError();
}
Error CommandGraphResource::appendWriteResource(RendererVk *renderer,
CommandBuffer **commandBufferOut)
{
if (!hasChildlessWritingNode())
{
return beginWriteResource(renderer, commandBufferOut);
}
CommandBuffer *outsideRenderPassCommands = mCurrentWritingNode->getOutsideRenderPassCommands();
if (!outsideRenderPassCommands->valid())
{
ANGLE_TRY(mCurrentWritingNode->beginOutsideRenderPassRecording(
renderer->getDevice(), renderer->getCommandPool(), commandBufferOut));
}
else
{
*commandBufferOut = outsideRenderPassCommands;
}
return NoError();
}
void CommandGraphResource::appendToRenderPass(class CommandBuffer **commandBufferOut) const
{
ASSERT(hasStartedRenderPass());
*commandBufferOut = mCurrentWritingNode->getInsideRenderPassCommands();
}
bool CommandGraphResource::hasStartedRenderPass() const
{
return hasChildlessWritingNode() && mCurrentWritingNode->getInsideRenderPassCommands()->valid();
}
const gl::Rectangle &CommandGraphResource::getRenderPassRenderArea() const
{
ASSERT(hasStartedRenderPass());
return mCurrentWritingNode->getRenderPassRenderArea();
}
Error CommandGraphResource::beginRenderPass(RendererVk *renderer,
const Framebuffer &framebuffer,
const gl::Rectangle &renderArea,
const RenderPassDesc &renderPassDesc,
const std::vector<VkClearValue> &clearValues,
CommandBuffer **commandBufferOut) const
{
// Hard-code RenderPass to clear the first render target to the current clear value.
// TODO(jmadill): Proper clear value implementation. http://anglebug.com/2361
mCurrentWritingNode->storeRenderPassInfo(framebuffer, renderArea, renderPassDesc, clearValues);
return mCurrentWritingNode->beginInsideRenderPassRecording(renderer, commandBufferOut);
}
void CommandGraphResource::onResourceChanged(RendererVk *renderer)
{
getNewWritingNode(renderer);
}
void CommandGraphResource::addWriteDependency(CommandGraphResource *writingResource)
{
CommandGraphNode *writingNode = writingResource->mCurrentWritingNode;
ASSERT(writingNode);
onWriteImpl(writingNode, writingResource->getQueueSerial());
}
void CommandGraphResource::onWriteImpl(CommandGraphNode *writingNode, Serial currentSerial)
{
updateQueueSerial(currentSerial);
// Make sure any open reads and writes finish before we execute 'writingNode'.
if (!mCurrentReadingNodes.empty())
{
CommandGraphNode::SetHappensBeforeDependencies(mCurrentReadingNodes, writingNode);
mCurrentReadingNodes.clear();
}
if (mCurrentWritingNode && mCurrentWritingNode != writingNode)
{
CommandGraphNode::SetHappensBeforeDependency(mCurrentWritingNode, writingNode);
}
mCurrentWritingNode = writingNode;
}
void CommandGraphResource::addReadDependency(CommandGraphResource *readingResource)
{
updateQueueSerial(readingResource->getQueueSerial());
CommandGraphNode *readingNode = readingResource->mCurrentWritingNode;
ASSERT(readingNode);
if (hasChildlessWritingNode())
{
// Ensure 'readingNode' happens after the current writing node.
CommandGraphNode::SetHappensBeforeDependency(mCurrentWritingNode, readingNode);
}
// Add the read node to the list of nodes currently reading this resource.
mCurrentReadingNodes.push_back(readingNode);
}
bool CommandGraphResource::checkResourceInUseAndRefreshDeps(RendererVk *renderer)
{
if (!renderer->isResourceInUse(*this) ||
(renderer->getCurrentQueueSerial() > mStoredQueueSerial))
{
mCurrentReadingNodes.clear();
mCurrentWritingNode = nullptr;
return false;
}
else
{
return true;
}
}
// CommandGraphNode implementation.
CommandGraphNode::CommandGraphNode()
: mRenderPassClearValues{}, mHasChildren(false), mVisitedState(VisitedState::Unvisited)
{
}
CommandGraphNode::~CommandGraphNode()
{
mRenderPassFramebuffer.setHandle(VK_NULL_HANDLE);
// Command buffers are managed by the command pool, so don't need to be freed.
mOutsideRenderPassCommands.releaseHandle();
mInsideRenderPassCommands.releaseHandle();
}
CommandBuffer *CommandGraphNode::getOutsideRenderPassCommands()
{
ASSERT(!mHasChildren);
return &mOutsideRenderPassCommands;
}
CommandBuffer *CommandGraphNode::getInsideRenderPassCommands()
{
ASSERT(!mHasChildren);
return &mInsideRenderPassCommands;
}
Error CommandGraphNode::beginOutsideRenderPassRecording(VkDevice device,
const CommandPool &commandPool,
CommandBuffer **commandsOut)
{
ASSERT(!mHasChildren);
VkCommandBufferInheritanceInfo inheritanceInfo;
inheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
inheritanceInfo.pNext = nullptr;
inheritanceInfo.renderPass = VK_NULL_HANDLE;
inheritanceInfo.subpass = 0;
inheritanceInfo.framebuffer = VK_NULL_HANDLE;
inheritanceInfo.occlusionQueryEnable = VK_FALSE;
inheritanceInfo.queryFlags = 0;
inheritanceInfo.pipelineStatistics = 0;
ANGLE_TRY(InitAndBeginCommandBuffer(device, commandPool, inheritanceInfo, 0,
&mOutsideRenderPassCommands));
*commandsOut = &mOutsideRenderPassCommands;
return NoError();
}
Error CommandGraphNode::beginInsideRenderPassRecording(RendererVk *renderer,
CommandBuffer **commandsOut)
{
ASSERT(!mHasChildren);
// Get a compatible RenderPass from the cache so we can initialize the inheritance info.
// TODO(jmadill): Support query for compatible/conformant render pass. htto://anglebug.com/2361
RenderPass *compatibleRenderPass;
ANGLE_TRY(renderer->getCompatibleRenderPass(mRenderPassDesc, &compatibleRenderPass));
VkCommandBufferInheritanceInfo inheritanceInfo;
inheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
inheritanceInfo.pNext = nullptr;
inheritanceInfo.renderPass = compatibleRenderPass->getHandle();
inheritanceInfo.subpass = 0;
inheritanceInfo.framebuffer = mRenderPassFramebuffer.getHandle();
inheritanceInfo.occlusionQueryEnable = VK_FALSE;
inheritanceInfo.queryFlags = 0;
inheritanceInfo.pipelineStatistics = 0;
ANGLE_TRY(InitAndBeginCommandBuffer(
renderer->getDevice(), renderer->getCommandPool(), inheritanceInfo,
VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, &mInsideRenderPassCommands));
*commandsOut = &mInsideRenderPassCommands;
return NoError();
}
void CommandGraphNode::storeRenderPassInfo(const Framebuffer &framebuffer,
const gl::Rectangle renderArea,
const vk::RenderPassDesc &renderPassDesc,
const std::vector<VkClearValue> &clearValues)
{
mRenderPassDesc = renderPassDesc;
mRenderPassFramebuffer.setHandle(framebuffer.getHandle());
mRenderPassRenderArea = renderArea;
std::copy(clearValues.begin(), clearValues.end(), mRenderPassClearValues.begin());
}
// static
void CommandGraphNode::SetHappensBeforeDependency(CommandGraphNode *beforeNode,
CommandGraphNode *afterNode)
{
ASSERT(beforeNode != afterNode && !beforeNode->isChildOf(afterNode));
afterNode->mParents.emplace_back(beforeNode);
beforeNode->setHasChildren();
}
// static
void CommandGraphNode::SetHappensBeforeDependencies(
const std::vector<CommandGraphNode *> &beforeNodes,
CommandGraphNode *afterNode)
{
afterNode->mParents.insert(afterNode->mParents.end(), beforeNodes.begin(), beforeNodes.end());
// TODO(jmadill): is there a faster way to do this?
for (CommandGraphNode *beforeNode : beforeNodes)
{
beforeNode->setHasChildren();
ASSERT(beforeNode != afterNode && !beforeNode->isChildOf(afterNode));
}
}
bool CommandGraphNode::hasParents() const
{
return !mParents.empty();
}
void CommandGraphNode::setHasChildren()
{
mHasChildren = true;
}
bool CommandGraphNode::hasChildren() const
{
return mHasChildren;
}
// Do not call this in anything but testing code, since it's slow.
bool CommandGraphNode::isChildOf(CommandGraphNode *parent)
{
std::set<CommandGraphNode *> visitedList;
std::vector<CommandGraphNode *> openList;
openList.insert(openList.begin(), mParents.begin(), mParents.end());
while (!openList.empty())
{
CommandGraphNode *current = openList.back();
openList.pop_back();
if (visitedList.count(current) == 0)
{
if (current == parent)
{
return true;
}
visitedList.insert(current);
openList.insert(openList.end(), current->mParents.begin(), current->mParents.end());
}
}
return false;
}
VisitedState CommandGraphNode::visitedState() const
{
return mVisitedState;
}
void CommandGraphNode::visitParents(std::vector<CommandGraphNode *> *stack)
{
ASSERT(mVisitedState == VisitedState::Unvisited);
stack->insert(stack->end(), mParents.begin(), mParents.end());
mVisitedState = VisitedState::Ready;
}
Error CommandGraphNode::visitAndExecute(VkDevice device,
Serial serial,
RenderPassCache *renderPassCache,
CommandBuffer *primaryCommandBuffer)
{
if (mOutsideRenderPassCommands.valid())
{
mOutsideRenderPassCommands.end();
primaryCommandBuffer->executeCommands(1, &mOutsideRenderPassCommands);
}
if (mInsideRenderPassCommands.valid())
{
// Pull a compatible RenderPass from the cache.
// TODO(jmadill): Insert real ops and layout transitions.
RenderPass *renderPass = nullptr;
ANGLE_TRY(
renderPassCache->getCompatibleRenderPass(device, serial, mRenderPassDesc, &renderPass));
mInsideRenderPassCommands.end();
VkRenderPassBeginInfo beginInfo;
beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
beginInfo.pNext = nullptr;
beginInfo.renderPass = renderPass->getHandle();
beginInfo.framebuffer = mRenderPassFramebuffer.getHandle();
beginInfo.renderArea.offset.x = static_cast<uint32_t>(mRenderPassRenderArea.x);
beginInfo.renderArea.offset.y = static_cast<uint32_t>(mRenderPassRenderArea.y);
beginInfo.renderArea.extent.width = static_cast<uint32_t>(mRenderPassRenderArea.width);
beginInfo.renderArea.extent.height = static_cast<uint32_t>(mRenderPassRenderArea.height);
beginInfo.clearValueCount = mRenderPassDesc.attachmentCount();
beginInfo.pClearValues = mRenderPassClearValues.data();
primaryCommandBuffer->beginRenderPass(beginInfo,
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
primaryCommandBuffer->executeCommands(1, &mInsideRenderPassCommands);
primaryCommandBuffer->endRenderPass();
}
mVisitedState = VisitedState::Visited;
return NoError();
}
const gl::Rectangle &CommandGraphNode::getRenderPassRenderArea() const
{
return mRenderPassRenderArea;
}
// CommandGraph implementation.
CommandGraph::CommandGraph() = default;
CommandGraph::~CommandGraph()
{
ASSERT(empty());
}
CommandGraphNode *CommandGraph::allocateNode()
{
// TODO(jmadill): Use a pool allocator for the CPU node allocations.
CommandGraphNode *newCommands = new CommandGraphNode();
mNodes.emplace_back(newCommands);
return newCommands;
}
Error CommandGraph::submitCommands(VkDevice device,
Serial serial,
RenderPassCache *renderPassCache,
CommandPool *commandPool,
CommandBuffer *primaryCommandBufferOut)
{
VkCommandBufferAllocateInfo primaryInfo;
primaryInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
primaryInfo.pNext = nullptr;
primaryInfo.commandPool = commandPool->getHandle();
primaryInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
primaryInfo.commandBufferCount = 1;
ANGLE_TRY(primaryCommandBufferOut->init(device, primaryInfo));
if (mNodes.empty())
{
return NoError();
}
std::vector<CommandGraphNode *> nodeStack;
VkCommandBufferBeginInfo beginInfo;
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.pNext = nullptr;
beginInfo.flags = 0;
beginInfo.pInheritanceInfo = nullptr;
ANGLE_TRY(primaryCommandBufferOut->begin(beginInfo));
for (CommandGraphNode *topLevelNode : mNodes)
{
// Only process commands that don't have child commands. The others will be pulled in
// automatically. Also skip commands that have already been visited.
if (topLevelNode->hasChildren() || topLevelNode->visitedState() != VisitedState::Unvisited)
continue;
nodeStack.push_back(topLevelNode);
while (!nodeStack.empty())
{
CommandGraphNode *node = nodeStack.back();
switch (node->visitedState())
{
case VisitedState::Unvisited:
node->visitParents(&nodeStack);
break;
case VisitedState::Ready:
ANGLE_TRY(node->visitAndExecute(device, serial, renderPassCache,
primaryCommandBufferOut));
nodeStack.pop_back();
break;
case VisitedState::Visited:
nodeStack.pop_back();
break;
default:
UNREACHABLE();
break;
}
}
}
ANGLE_TRY(primaryCommandBufferOut->end());
// TODO(jmadill): Use pool allocation so we don't need to deallocate command graph.
for (CommandGraphNode *node : mNodes)
{
delete node;
}
mNodes.clear();
return NoError();
}
bool CommandGraph::empty() const
{
return mNodes.empty();
}
} // namespace vk
} // namespace rx