Commit 563fbaa0 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Implement occlusion queries

Begin and end queries insert an execution barrier in the command graph to ensure the commands around them are not reordered w.r.t to the query. Also, these commands cannot be recorded in separate secondary command buffers. Therefore, special-function nodes are created to perform the begin and end query calls on the final primary command buffer. Bug: angleproject:2855 Change-Id: Ie216dfdd6a2009deaaf744fd15d2db6899dd93e9 Reviewed-on: https://chromium-review.googlesource.com/c/1259762Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
parent 120b13f8
......@@ -215,17 +215,6 @@ inline Error NoError()
} \
ANGLE_EMPTY_STATEMENT
#define ANGLE_TRY_RESULT(EXPR, RESULT) \
{ \
auto ANGLE_LOCAL_VAR = EXPR; \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR.isError())) \
{ \
return ANGLE_LOCAL_VAR.getError(); \
} \
RESULT = ANGLE_LOCAL_VAR.getResult(); \
} \
ANGLE_EMPTY_STATEMENT
// TODO(jmadill): Introduce way to store errors to a const Context. http://anglebug.com/2491
#define ANGLE_SWALLOW_ERR(EXPR) \
{ \
......
......@@ -20,6 +20,12 @@ Query::~Query()
SafeDelete(mQuery);
}
Error Query::onDestroy(const Context *context)
{
ASSERT(mQuery);
return mQuery->onDestroy(context);
}
void Query::setLabel(const std::string &label)
{
mLabel = label;
......
......@@ -30,8 +30,8 @@ class Query final : public RefCountObject, public LabeledObject
{
public:
Query(rx::QueryImpl *impl, GLuint id);
void destroy(const gl::Context *context) {}
~Query() override;
Error onDestroy(const Context *context) override;
void setLabel(const std::string &label) override;
const std::string &getLabel() const override;
......
//
// Copyright 2018 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.
//
// QueryImpl.cpp: Defines the abstract rx::QueryImpl classes.
#include "libANGLE/renderer/QueryImpl.h"
namespace rx
{
gl::Error QueryImpl::onDestroy(const gl::Context *context)
{
return gl::NoError();
}
} // namespace rx
......@@ -26,6 +26,8 @@ class QueryImpl : angle::NonCopyable
explicit QueryImpl(gl::QueryType type) : mType(type) {}
virtual ~QueryImpl() {}
virtual gl::Error onDestroy(const gl::Context *context);
virtual gl::Error begin(const gl::Context *context) = 0;
virtual gl::Error end(const gl::Context *context) = 0;
virtual gl::Error queryCounter(const gl::Context *context) = 0;
......
......@@ -29,13 +29,23 @@ enum class CommandGraphResourceType
Buffer,
Framebuffer,
Image,
Query,
};
// Certain functionality cannot be put in secondary command buffers, so they are special-cased in
// the node.
enum class CommandGraphNodeFunction
{
Generic,
BeginQuery,
EndQuery,
};
// Only used internally in the command graph. Kept in the header for better inlining performance.
class CommandGraphNode final : angle::NonCopyable
{
public:
CommandGraphNode();
CommandGraphNode(CommandGraphNodeFunction function);
~CommandGraphNode();
// Immutable queries for when we're walking the commands tree.
......@@ -66,8 +76,12 @@ class CommandGraphNode final : angle::NonCopyable
// frozen forever.
static void SetHappensBeforeDependency(CommandGraphNode *beforeNode,
CommandGraphNode *afterNode);
static void SetHappensBeforeDependencies(const std::vector<CommandGraphNode *> &beforeNodes,
static void SetHappensBeforeDependencies(CommandGraphNode **beforeNodes,
size_t beforeNodesCount,
CommandGraphNode *afterNode);
static void SetHappensBeforeDependencies(CommandGraphNode *beforeNode,
CommandGraphNode **afterNodes,
size_t afterNodesCount);
bool hasParents() const;
bool hasChildren() const { return mHasChildren; }
......@@ -88,6 +102,10 @@ class CommandGraphNode final : angle::NonCopyable
const gl::Rectangle &getRenderPassRenderArea() const;
CommandGraphNodeFunction getFunction() const { return mFunction; }
void setQueryPool(const QueryPool *queryPool, uint32_t queryIndex);
private:
void setHasChildren();
......@@ -100,11 +118,17 @@ class CommandGraphNode final : angle::NonCopyable
gl::Rectangle mRenderPassRenderArea;
gl::AttachmentArray<VkClearValue> mRenderPassClearValues;
// Keep a separate buffers for commands inside and outside a RenderPass.
CommandGraphNodeFunction mFunction;
// Keep 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;
// Special-function additional data:
VkQueryPool mQueryPool;
uint32_t mQueryIndex;
// Parents are commands that must be submitted before 'this' CommandNode can be submitted.
std::vector<CommandGraphNode *> mParents;
......@@ -133,6 +157,9 @@ class CommandGraphResource : angle::NonCopyable
// Returns true if the resource is in use by the renderer.
bool isResourceInUse(RendererVk *renderer) const;
// Returns true if the resource has unsubmitted work pending.
bool hasPendingWork(RendererVk *renderer) const;
// Sets up dependency relations. 'this' resource is the resource being written to.
void addWriteDependency(CommandGraphResource *writingResource);
......@@ -151,7 +178,10 @@ class CommandGraphResource : angle::NonCopyable
const gl::Rectangle &renderArea,
const RenderPassDesc &renderPassDesc,
const std::vector<VkClearValue> &clearValues,
CommandBuffer **commandBufferOut) const;
CommandBuffer **commandBufferOut);
void beginQuery(Context *context, const QueryPool *queryPool, uint32_t queryIndex);
void endQuery(Context *context, const QueryPool *queryPool, uint32_t queryIndex);
// Checks if we're in a RenderPass, returning true if so. Updates serial internally.
// Returns the started command buffer in commandBufferOut.
......@@ -170,11 +200,20 @@ class CommandGraphResource : angle::NonCopyable
Serial getStoredQueueSerial() const;
private:
void startNewCommands(RendererVk *renderer, CommandGraphNodeFunction function);
void onWriteImpl(CommandGraphNode *writingNode, Serial currentSerial);
// Returns true if this node has a current writing node with no children.
bool hasChildlessWritingNode() const
{
// Note: currently, we don't have a resource that can issue both generic and special
// commands. We don't create read/write dependencies between mixed generic/special
// resources either. As such, we expect the function to always be generic here. If such a
// resource is added in the future, this can add a check for function == generic and fail if
// false.
ASSERT(mCurrentWritingNode == nullptr ||
mCurrentWritingNode->getFunction() == CommandGraphNodeFunction::Generic);
return (mCurrentWritingNode != nullptr && !mCurrentWritingNode->hasChildren());
}
......@@ -226,8 +265,9 @@ class CommandGraph final : angle::NonCopyable
// Allocates a new CommandGraphNode and adds it to the list of current open nodes. No ordering
// relations exist in the node by default. Call CommandGraphNode::SetHappensBeforeDependency
// to set up dependency relations.
CommandGraphNode *allocateNode();
// to set up dependency relations. If the node is a barrier, it will automatically add
// dependencies between the previous barrier, the new barrier and all nodes in between.
CommandGraphNode *allocateNode(bool isBarrier, CommandGraphNodeFunction function);
angle::Result submitCommands(Context *context,
Serial serial,
......@@ -236,11 +276,65 @@ class CommandGraph final : angle::NonCopyable
CommandBuffer *primaryCommandBufferOut);
bool empty() const;
CommandGraphNode *getLastBarrierNode(size_t *indexOut);
private:
void dumpGraphDotFile(std::ostream &out) const;
void setNewBarrier(CommandGraphNode *newBarrier);
void addDependenciesToNextBarrier(size_t begin, size_t end, CommandGraphNode *nextBarrier);
std::vector<CommandGraphNode *> mNodes;
bool mEnableGraphDiagnostics;
// A set of nodes (eventually) exist that act as barriers to guarantee submission order. For
// example, a glMemoryBarrier() calls would lead to such a barrier or beginning and ending a
// query. This is because the graph can reorder operations if it sees fit. Let's call a barrier
// node Bi, and the other nodes Ni. The edges between Ni don't interest us. Before a barrier is
// inserted, we have:
//
// N0 N1 ... Na
// \___\__/_/ (dependency egdes, which we don't care about so I'll stop drawing them.
// \/
//
// When the first barrier is inserted, we will have:
//
// ______
// / ____\
// / / \
// / / /\
// N0 N1 ... Na B0
//
// This makes sure all N0..Na are called before B0. From then on, B0 will be the current
// "barrier point" which extends an edge to every next node:
//
// ______
// / ____\
// / / \
// / / /\
// N0 N1 ... Na B0 Na+1 ... Nb
// \/ /
// \______/
//
//
// When the next barrier B1 is met, all nodes between B0 and B1 will add a depenency on B1 as
// well, and the "barrier point" is updated.
//
// ______
// / ____\ ______ ______
// / / \ / \ / \
// / / /\ / /\ / /\
// N0 N1 ... Na B0 Na+1 ... Nb B1 Nb+1 ... Nc B2 ...
// \/ / / \/ / /
// \______/ / \______/ /
// \_______/ \_______/
//
//
// When barrier Bi is introduced, all nodes added since Bi-1 need to add a dependency to Bi
// (including Bi-1). We therefore keep track of the node index of the last barrier that was
// issued.
static constexpr size_t kInvalidNodeIndex = std::numeric_limits<std::size_t>::max();
size_t mLastBarrierIndex;
};
} // namespace vk
} // namespace rx
......
......@@ -169,6 +169,11 @@ void ContextVk::onDestroy(const gl::Context *context)
{
defaultBuffer.destroy(getDevice());
}
for (vk::DynamicQueryPool &queryPool : mQueryPools)
{
queryPool.destroy(getDevice());
}
}
gl::Error ContextVk::getIncompleteTexture(const gl::Context *context,
......@@ -199,6 +204,12 @@ gl::Error ContextVk::initialize()
ANGLE_TRY(mDynamicDescriptorPools[kDriverUniformsDescriptorSetIndex].init(
this, driverUniformsPoolSize));
ANGLE_TRY(mQueryPools[gl::QueryType::AnySamples].init(this, VK_QUERY_TYPE_OCCLUSION,
vk::kDefaultOcclusionQueryPoolSize));
ANGLE_TRY(mQueryPools[gl::QueryType::AnySamplesConservative].init(
this, VK_QUERY_TYPE_OCCLUSION, vk::kDefaultOcclusionQueryPoolSize));
// TODO(syoussefi): Initialize other query pools as they get implemented.
size_t minAlignment = static_cast<size_t>(
mRenderer->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment);
mDriverUniformsBuffer.init(minAlignment, mRenderer);
......@@ -1081,6 +1092,14 @@ vk::DynamicDescriptorPool *ContextVk::getDynamicDescriptorPool(uint32_t descript
return &mDynamicDescriptorPools[descriptorSetIndex];
}
vk::DynamicQueryPool *ContextVk::getQueryPool(gl::QueryType queryType)
{
ASSERT(queryType == gl::QueryType::AnySamples ||
queryType == gl::QueryType::AnySamplesConservative);
ASSERT(mQueryPools[queryType].isValid());
return &mQueryPools[queryType];
}
const VkClearValue &ContextVk::getClearColorValue() const
{
return mClearColorValue;
......
......@@ -12,6 +12,7 @@
#include <vulkan/vulkan.h>
#include "common/PackedEnums.h"
#include "libANGLE/renderer/ContextImpl.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"
......@@ -161,6 +162,7 @@ class ContextVk : public ContextImpl, public vk::Context
void invalidateDefaultAttributes(const gl::AttributesMask &dirtyMask);
vk::DynamicDescriptorPool *getDynamicDescriptorPool(uint32_t descriptorSetIndex);
vk::DynamicQueryPool *getQueryPool(gl::QueryType queryType);
const VkClearValue &getClearColorValue() const;
const VkClearValue &getClearDepthStencilValue() const;
......@@ -251,7 +253,9 @@ class ContextVk : public ContextImpl, public vk::Context
// The descriptor pools are externally sychronized, so cannot be accessed from different
// threads simultaneously. Hence, we keep them in the ContextVk instead of the RendererVk.
// Same with query pools.
vk::DescriptorSetLayoutArray<vk::DynamicDescriptorPool> mDynamicDescriptorPools;
angle::PackedEnumMap<gl::QueryType, vk::DynamicQueryPool> mQueryPools;
// Dirty bits.
DirtyBits mDirtyBits;
......
//
// Copyright 2016 The ANGLE Project Authors. All rights reserved.
// Copyright 2016-2018 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.
//
......@@ -8,30 +8,48 @@
//
#include "libANGLE/renderer/vulkan/QueryVk.h"
#include "libANGLE/Context.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "common/debug.h"
namespace rx
{
QueryVk::QueryVk(gl::QueryType type) : QueryImpl(type)
QueryVk::QueryVk(gl::QueryType type) : QueryImpl(type), mCachedResult(0), mCachedResultValid(false)
{
}
QueryVk::~QueryVk()
QueryVk::~QueryVk() = default;
gl::Error QueryVk::onDestroy(const gl::Context *context)
{
ContextVk *contextVk = vk::GetImpl(context);
contextVk->getQueryPool(getType())->freeQuery(contextVk, &mQueryHelper);
return gl::NoError();
}
gl::Error QueryVk::begin(const gl::Context *context)
{
UNIMPLEMENTED();
return gl::InternalError();
ContextVk *contextVk = vk::GetImpl(context);
ANGLE_TRY(contextVk->getQueryPool(getType())->allocateQuery(contextVk, &mQueryHelper));
mCachedResultValid = false;
mQueryHelper.beginQuery(contextVk, mQueryHelper.getQueryPool(), mQueryHelper.getQuery());
return gl::NoError();
}
gl::Error QueryVk::end(const gl::Context *context)
{
UNIMPLEMENTED();
return gl::InternalError();
ContextVk *contextVk = vk::GetImpl(context);
mQueryHelper.endQuery(contextVk, mQueryHelper.getQueryPool(), mQueryHelper.getQuery());
return gl::NoError();
}
gl::Error QueryVk::queryCounter(const gl::Context *context)
......@@ -40,34 +58,88 @@ gl::Error QueryVk::queryCounter(const gl::Context *context)
return gl::InternalError();
}
angle::Result QueryVk::getResult(const gl::Context *context, bool wait)
{
if (mCachedResultValid)
{
return angle::Result::Continue();
}
ContextVk *contextVk = vk::GetImpl(context);
VkQueryResultFlags flags = (wait ? VK_QUERY_RESULT_WAIT_BIT : 0) | VK_QUERY_RESULT_64_BIT;
angle::Result result = mQueryHelper.getQueryPool()->getResults(
contextVk, mQueryHelper.getQuery(), 1, sizeof(mCachedResult), &mCachedResult,
sizeof(mCachedResult), flags);
ANGLE_TRY(result);
if (result == angle::Result::Continue())
{
mCachedResultValid = true;
switch (getType())
{
case gl::QueryType::AnySamples:
case gl::QueryType::AnySamplesConservative:
// OpenGL query result in these cases is binary
mCachedResult = !!mCachedResult;
break;
default:
UNREACHABLE();
break;
}
}
return angle::Result::Continue();
}
gl::Error QueryVk::getResult(const gl::Context *context, GLint *params)
{
UNIMPLEMENTED();
return gl::InternalError();
ANGLE_TRY(getResult(context, true));
*params = static_cast<GLint>(mCachedResult);
return gl::NoError();
}
gl::Error QueryVk::getResult(const gl::Context *context, GLuint *params)
{
UNIMPLEMENTED();
return gl::InternalError();
ANGLE_TRY(getResult(context, true));
*params = static_cast<GLuint>(mCachedResult);
return gl::NoError();
}
gl::Error QueryVk::getResult(const gl::Context *context, GLint64 *params)
{
UNIMPLEMENTED();
return gl::InternalError();
ANGLE_TRY(getResult(context, true));
*params = static_cast<GLint64>(mCachedResult);
return gl::NoError();
}
gl::Error QueryVk::getResult(const gl::Context *context, GLuint64 *params)
{
UNIMPLEMENTED();
return gl::InternalError();
ANGLE_TRY(getResult(context, true));
*params = mCachedResult;
return gl::NoError();
}
gl::Error QueryVk::isResultAvailable(const gl::Context *context, bool *available)
{
UNIMPLEMENTED();
return gl::InternalError();
ContextVk *contextVk = vk::GetImpl(context);
// Make sure the command buffer for this query is submitted. If not, *available should always
// be false. This is because the reset command is not yet executed (it's only put in the command
// graph), so actually checking the results may return "true" because of a previous submission.
if (mQueryHelper.hasPendingWork(contextVk->getRenderer()))
{
*available = false;
return gl::NoError();
}
ANGLE_TRY(getResult(context, false));
*available = mCachedResultValid;
return gl::NoError();
}
} // namespace rx
......@@ -11,6 +11,7 @@
#define LIBANGLE_RENDERER_VULKAN_QUERYVK_H_
#include "libANGLE/renderer/QueryImpl.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"
namespace rx
{
......@@ -21,6 +22,8 @@ class QueryVk : public QueryImpl
QueryVk(gl::QueryType type);
~QueryVk() override;
gl::Error onDestroy(const gl::Context *context) override;
gl::Error begin(const gl::Context *context) override;
gl::Error end(const gl::Context *context) override;
gl::Error queryCounter(const gl::Context *context) override;
......@@ -29,6 +32,13 @@ class QueryVk : public QueryImpl
gl::Error getResult(const gl::Context *context, GLint64 *params) override;
gl::Error getResult(const gl::Context *context, GLuint64 *params) override;
gl::Error isResultAvailable(const gl::Context *context, bool *available) override;
private:
angle::Result getResult(const gl::Context *context, bool wait);
vk::QueryHelper mQueryHelper;
uint64_t mCachedResult;
bool mCachedResultValid;
};
} // namespace rx
......
......@@ -573,6 +573,10 @@ angle::Result RendererVk::initializeDevice(DisplayVk *displayVk, uint32_t queueF
ANGLE_VK_TRY(displayVk, VerifyExtensionsPresent(deviceExtensionProps, enabledDeviceExtensions));
// Select additional features to be enabled
VkPhysicalDeviceFeatures enabledFeatures = {};
enabledFeatures.inheritedQueries = mPhysicalDeviceFeatures.inheritedQueries;
VkDeviceQueueCreateInfo queueCreateInfo = {};
float zeroPriority = 0.0f;
......@@ -595,7 +599,7 @@ angle::Result RendererVk::initializeDevice(DisplayVk *displayVk, uint32_t queueF
createInfo.enabledExtensionCount = static_cast<uint32_t>(enabledDeviceExtensions.size());
createInfo.ppEnabledExtensionNames =
enabledDeviceExtensions.empty() ? nullptr : enabledDeviceExtensions.data();
createInfo.pEnabledFeatures = nullptr; // TODO(jmadill): features
createInfo.pEnabledFeatures = &enabledFeatures;
ANGLE_VK_TRY(displayVk, vkCreateDevice(mPhysicalDevice, &createInfo, nullptr, &mDevice));
......
......@@ -60,6 +60,10 @@ class RendererVk : angle::NonCopyable
{
return mPhysicalDeviceProperties;
}
const VkPhysicalDeviceFeatures &getPhysicalDeviceFeatures() const
{
return mPhysicalDeviceFeatures;
}
VkQueue getQueue() const { return mQueue; }
VkDevice getDevice() const { return mDevice; }
......@@ -81,6 +85,7 @@ class RendererVk : angle::NonCopyable
uint32_t getMaxActiveTextures();
Serial getCurrentQueueSerial() const { return mCurrentQueueSerial; }
Serial getLastCompletedQueueSerial() const { return mLastCompletedQueueSerial; }
bool isSerialInUse(Serial serial) const;
......
......@@ -48,8 +48,10 @@ void GenerateCaps(const VkPhysicalDeviceProperties &physicalDeviceProperties,
outExtensions->debugMarker = true;
outExtensions->robustness = true;
// TODO: Not implemented yet but exposed so that Chrome can load the query entry points. http://anglebug.com/2855
outExtensions->occlusionQueryBoolean = true;
// We use secondary command buffers almost everywhere and they require a feature to be
// able to execute in the presence of queries. As a result, we won't support queries
// unless that feature is available.
outExtensions->occlusionQueryBoolean = physicalDeviceFeatures.inheritedQueries;
// TODO(lucferron): Eventually remove everything above this line in this function as the caps
// get implemented.
......
......@@ -351,6 +351,133 @@ void DynamicDescriptorPool::setMaxSetsPerPoolForTesting(uint32_t maxSetsPerPool)
mMaxSetsPerPool = maxSetsPerPool;
}
// DynamicQueryPool implementation
DynamicQueryPool::DynamicQueryPool() : mPoolSize(0), mCurrentQueryPool(0), mCurrentFreeQuery(0)
{
}
DynamicQueryPool::~DynamicQueryPool() = default;
angle::Result DynamicQueryPool::init(Context *context, VkQueryType type, uint32_t poolSize)
{
ASSERT(mQueryPools.empty() && mQueryPoolStats.empty());
mPoolSize = poolSize;
mQueryType = type;
ANGLE_TRY(allocateNewPool(context));
return angle::Result::Continue();
}
void DynamicQueryPool::destroy(VkDevice device)
{
for (QueryPool &queryPool : mQueryPools)
{
queryPool.destroy(device);
}
mQueryPools.clear();
mQueryPoolStats.clear();
}
angle::Result DynamicQueryPool::allocateQuery(Context *context, QueryHelper *queryOut)
{
ASSERT(!queryOut->getQueryPool());
if (mCurrentFreeQuery >= mPoolSize)
{
// No more queries left in this pool, create another one.
ANGLE_TRY(allocateNewPool(context));
}
queryOut->init(this, mCurrentQueryPool, mCurrentFreeQuery++);
return angle::Result::Continue();
}
void DynamicQueryPool::freeQuery(Context *context, QueryHelper *query)
{
if (query->getQueryPool())
{
size_t poolIndex = query->getQueryPoolIndex();
ASSERT(query->getQueryPool()->valid() && poolIndex < mQueryPoolStats.size() &&
mQueryPoolStats[poolIndex].freedCount < mPoolSize);
++mQueryPoolStats[poolIndex].freedCount;
query->deinit();
// Take note of the current serial to avoid reallocating a query in the same pool
mQueryPoolStats[poolIndex].serial = context->getRenderer()->getCurrentQueueSerial();
}
}
angle::Result DynamicQueryPool::allocateNewPool(Context *context)
{
// Before allocating a new pool, see if a previously allocated pool is completely freed up.
// In that case, use that older pool instead.
Serial lastCompletedQueueSerial = context->getRenderer()->getLastCompletedQueueSerial();
for (size_t i = 0; i < mQueryPools.size(); ++i)
{
if (mQueryPoolStats[i].freedCount == mPoolSize &&
mQueryPoolStats[i].serial <= lastCompletedQueueSerial)
{
mCurrentQueryPool = i;
mCurrentFreeQuery = 0;
return angle::Result::Continue();
}
}
VkQueryPoolCreateInfo queryPoolInfo = {};
queryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
queryPoolInfo.flags = 0;
queryPoolInfo.queryType = mQueryType;
queryPoolInfo.queryCount = mPoolSize;
queryPoolInfo.pipelineStatistics = 0;
vk::QueryPool queryPool;
ANGLE_TRY(queryPool.init(context, queryPoolInfo));
mQueryPools.push_back(std::move(queryPool));
QueryPoolStats poolStats = {0, Serial()};
mQueryPoolStats.push_back(poolStats);
mCurrentQueryPool = mQueryPools.size() - 1;
mCurrentFreeQuery = 0;
return angle::Result::Continue();
}
// QueryHelper implementation
QueryHelper::QueryHelper()
: CommandGraphResource(CommandGraphResourceType::Query),
mDynamicQueryPool(nullptr),
mQueryPoolIndex(0),
mQuery(0)
{
}
QueryHelper::~QueryHelper()
{
}
void QueryHelper::init(const DynamicQueryPool *dynamicQueryPool,
const size_t queryPoolIndex,
uint32_t query)
{
mDynamicQueryPool = dynamicQueryPool;
mQueryPoolIndex = queryPoolIndex;
mQuery = query;
}
void QueryHelper::deinit()
{
mDynamicQueryPool = nullptr;
mQueryPoolIndex = 0;
mQuery = 0;
}
// LineLoopHelper implementation.
LineLoopHelper::LineLoopHelper(RendererVk *renderer)
: mDynamicIndexBuffer(kLineLoopDynamicBufferUsage, kLineLoopDynamicBufferMinSize)
......
......@@ -101,7 +101,7 @@ class DynamicDescriptorPool final : angle::NonCopyable
DynamicDescriptorPool();
~DynamicDescriptorPool();
// The DynamicDescriptorPool only handles one pool size at at time.
// The DynamicDescriptorPool only handles one pool size at this time.
angle::Result init(Context *context, const VkDescriptorPoolSize &poolSize);
void destroy(VkDevice device);
......@@ -125,6 +125,95 @@ class DynamicDescriptorPool final : angle::NonCopyable
uint32_t mFreeDescriptorSets;
};
// DynamicQueryPool allocates indices out of QueryPool as needed. Once a QueryPool is exhausted,
// another is created. The query pools live permanently, but are recycled as indices get freed.
// This is an arbitrary default size for occlusion query pools.
constexpr uint32_t kDefaultOcclusionQueryPoolSize = 64;
class QueryHelper;
class DynamicQueryPool final : angle::NonCopyable
{
public:
DynamicQueryPool();
~DynamicQueryPool();
angle::Result init(Context *context, VkQueryType type, uint32_t poolSize);
void destroy(VkDevice device);
bool isValid() { return mPoolSize > 0; }
angle::Result allocateQuery(Context *context, QueryHelper *queryOut);
void freeQuery(Context *context, QueryHelper *query);
const QueryPool *getQueryPool(size_t index) const { return &mQueryPools[index]; }
private:
angle::Result allocateNewPool(Context *context);
// Information required to create new query pools
uint32_t mPoolSize;
VkQueryType mQueryType;
// A list of query pools to allocate from
std::vector<QueryPool> mQueryPools;
struct QueryPoolStats
{
// A count corresponding to each query pool indicating how many of its allocated indices
// have been freed. Once that value reaches mPoolSize for each pool, that pool is considered
// free and reusable. While keeping a bitset would allow allocation of each index, the
// slight runtime overhead of finding free indices is not worth the slight memory overhead
// of creating new pools when unnecessary.
uint32_t freedCount;
// When the pool is completely emptied, the serial of the renderer is stored to make sure no
// new allocations are made from the pool until it's not in use.
Serial serial;
};
std::vector<QueryPoolStats> mQueryPoolStats;
// Index into mQueryPools indicating query pool we are currently allocating from
size_t mCurrentQueryPool;
// Bit index inside mQueryPools[mCurrentQueryPool] indicating which index can be allocated next
uint32_t mCurrentFreeQuery;
};
// Queries in vulkan are identified by the query pool and an index for a query within that pool.
// Unlike other pools, such as descriptor pools where an allocation returns an independent object
// from the pool, the query allocations are not done through a Vulkan function and are only an
// integer index.
//
// Furthermore, to support arbitrarily large number of queries, DynamicQueryPool creates query pools
// of a fixed size as needed and allocates indices within those pools.
//
// The QueryHelper class below keeps the pool and index pair together.
class QueryHelper final : public CommandGraphResource
{
public:
QueryHelper();
~QueryHelper();
void init(const DynamicQueryPool *dynamicQueryPool,
const size_t queryPoolIndex,
uint32_t query);
void deinit();
const QueryPool *getQueryPool() const
{
return mDynamicQueryPool ? mDynamicQueryPool->getQueryPool(mQueryPoolIndex) : nullptr;
}
uint32_t getQuery() const { return mQuery; }
// Used only by DynamicQueryPool.
size_t getQueryPoolIndex() const { return mQueryPoolIndex; }
private:
const DynamicQueryPool *mDynamicQueryPool;
size_t mQueryPoolIndex;
uint32_t mQuery;
};
// This class' responsibility is to create index buffers needed to support line loops in Vulkan.
// In the setup phase of drawing, the createIndexBuffer method should be called with the
// current draw call parameters. If an element array buffer is bound for an indexed draw, use
......
......@@ -569,6 +569,24 @@ void CommandBuffer::pushConstants(const PipelineLayout &layout,
vkCmdPushConstants(mHandle, layout.getHandle(), flag, offset, size, data);
}
void CommandBuffer::resetQueryPool(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount)
{
ASSERT(valid());
vkCmdResetQueryPool(mHandle, queryPool, firstQuery, queryCount);
}
void CommandBuffer::beginQuery(VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags)
{
ASSERT(valid());
vkCmdBeginQuery(mHandle, queryPool, query, flags);
}
void CommandBuffer::endQuery(VkQueryPool queryPool, uint32_t query)
{
ASSERT(valid());
vkCmdEndQuery(mHandle, queryPool, query);
}
// Image implementation.
Image::Image()
{
......@@ -1109,6 +1127,44 @@ void StagingBuffer::dumpResources(Serial serial, std::vector<vk::GarbageObject>
mDeviceMemory.dumpResources(serial, garbageQueue);
}
// QueryPool implementation.
QueryPool::QueryPool()
{
}
void QueryPool::destroy(VkDevice device)
{
if (valid())
{
vkDestroyQueryPool(device, mHandle, nullptr);
mHandle = VK_NULL_HANDLE;
}
}
angle::Result QueryPool::init(Context *context, const VkQueryPoolCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkCreateQueryPool(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
}
angle::Result QueryPool::getResults(Context *context,
uint32_t firstQuery,
uint32_t queryCount,
size_t dataSize,
void *data,
VkDeviceSize stride,
VkQueryResultFlags flags) const
{
angle::Result result = angle::Result::Stop();
ANGLE_VK_TRY_ALLOW_NOT_READY(context,
vkGetQueryPoolResults(context->getDevice(), mHandle, firstQuery,
queryCount, dataSize, data, stride, flags),
result);
return result;
}
angle::Result AllocateBufferMemory(vk::Context *context,
VkMemoryPropertyFlags requestedMemoryPropertyFlags,
VkMemoryPropertyFlags *memoryPropertyFlagsOut,
......@@ -1220,6 +1276,9 @@ void GarbageObject::destroy(VkDevice device)
case HandleType::CommandPool:
vkDestroyCommandPool(device, reinterpret_cast<VkCommandPool>(mHandle), nullptr);
break;
case HandleType::QueryPool:
vkDestroyQueryPool(device, reinterpret_cast<VkQueryPool>(mHandle), nullptr);
break;
default:
UNREACHABLE();
break;
......
......@@ -169,7 +169,8 @@ GetImplType<T> *GetImpl(const T *glObject)
FUNC(Sampler) \
FUNC(DescriptorPool) \
FUNC(Framebuffer) \
FUNC(CommandPool)
FUNC(CommandPool) \
FUNC(QueryPool)
#define ANGLE_COMMA_SEP_FUNC(TYPE) TYPE,
......@@ -422,6 +423,10 @@ class CommandBuffer : public WrappedObject<CommandBuffer, VkCommandBuffer>
uint32_t offset,
uint32_t size,
const void *data);
void resetQueryPool(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);
void beginQuery(VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags);
void endQuery(VkQueryPool queryPool, uint32_t query);
};
class Image final : public WrappedObject<Image, VkImage>
......@@ -628,6 +633,22 @@ class StagingBuffer final : angle::NonCopyable
size_t mSize;
};
class QueryPool final : public WrappedObject<QueryPool, VkQueryPool>
{
public:
QueryPool();
void destroy(VkDevice device);
angle::Result init(Context *context, const VkQueryPoolCreateInfo &createInfo);
angle::Result getResults(Context *context,
uint32_t firstQuery,
uint32_t queryCount,
size_t dataSize,
void *data,
VkDeviceSize stride,
VkQueryResultFlags flags) const;
};
template <typename ObjT>
class ObjectAndSerial final : angle::NonCopyable
{
......@@ -769,19 +790,25 @@ VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bo
} \
ANGLE_EMPTY_STATEMENT
#define ANGLE_VK_TRY_ALLOW_INCOMPLETE(context, command, result) \
{ \
auto ANGLE_LOCAL_VAR = command; \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR != VK_SUCCESS && ANGLE_LOCAL_VAR != VK_INCOMPLETE)) \
{ \
context->handleError(ANGLE_LOCAL_VAR, __FILE__, __LINE__); \
return angle::Result::Stop(); \
} \
result = ANGLE_LOCAL_VAR == VK_INCOMPLETE ? angle::Result::Incomplete() \
: angle::Result::Continue(); \
} \
#define ANGLE_VK_TRY_ALLOW_OTHER(context, command, acceptable, result) \
{ \
auto ANGLE_LOCAL_VAR = command; \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR != VK_SUCCESS && ANGLE_LOCAL_VAR != acceptable)) \
{ \
context->handleError(ANGLE_LOCAL_VAR, __FILE__, __LINE__); \
return angle::Result::Stop(); \
} \
result = ANGLE_LOCAL_VAR == VK_SUCCESS ? angle::Result::Continue() \
: angle::Result::Incomplete(); \
} \
ANGLE_EMPTY_STATEMENT
#define ANGLE_VK_TRY_ALLOW_INCOMPLETE(context, command, result) \
ANGLE_VK_TRY_ALLOW_OTHER(context, command, VK_INCOMPLETE, result)
#define ANGLE_VK_TRY_ALLOW_NOT_READY(context, command, result) \
ANGLE_VK_TRY_ALLOW_OTHER(context, command, VK_NOT_READY, result)
#define ANGLE_VK_CHECK(context, test, error) ANGLE_VK_TRY(context, test ? VK_SUCCESS : error)
#define ANGLE_VK_CHECK_MATH(context, result) \
......
......@@ -257,6 +257,7 @@ libangle_sources = [
"src/libANGLE/renderer/PathImpl.h",
"src/libANGLE/renderer/ProgramImpl.h",
"src/libANGLE/renderer/ProgramPipelineImpl.h",
"src/libANGLE/renderer/QueryImpl.cpp",
"src/libANGLE/renderer/QueryImpl.h",
"src/libANGLE/renderer/RenderbufferImpl.h",
"src/libANGLE/renderer/RenderTargetCache.h",
......
......@@ -184,9 +184,9 @@ TEST_P(OcclusionQueriesTest, MultiContext)
!extensionEnabled("GL_EXT_occlusion_query_boolean"));
// Test skipped because the D3D backends cannot support simultaneous queries on multiple
// contexts yet.
// contexts yet. Same with the Vulkan backend.
ANGLE_SKIP_TEST_IF(GetParam() == ES2_D3D9() || GetParam() == ES2_D3D11() ||
GetParam() == ES3_D3D11());
GetParam() == ES3_D3D11() || GetParam() == ES2_VULKAN());
glDepthMask(GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
......@@ -310,4 +310,5 @@ ANGLE_INSTANTIATE_TEST(OcclusionQueriesTest,
ES2_OPENGL(),
ES3_OPENGL(),
ES2_OPENGLES(),
ES3_OPENGLES());
ES3_OPENGLES(),
ES2_VULKAN());
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment