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() ...@@ -215,17 +215,6 @@ inline Error NoError()
} \ } \
ANGLE_EMPTY_STATEMENT 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 // TODO(jmadill): Introduce way to store errors to a const Context. http://anglebug.com/2491
#define ANGLE_SWALLOW_ERR(EXPR) \ #define ANGLE_SWALLOW_ERR(EXPR) \
{ \ { \
......
...@@ -20,6 +20,12 @@ Query::~Query() ...@@ -20,6 +20,12 @@ Query::~Query()
SafeDelete(mQuery); SafeDelete(mQuery);
} }
Error Query::onDestroy(const Context *context)
{
ASSERT(mQuery);
return mQuery->onDestroy(context);
}
void Query::setLabel(const std::string &label) void Query::setLabel(const std::string &label)
{ {
mLabel = label; mLabel = label;
......
...@@ -30,8 +30,8 @@ class Query final : public RefCountObject, public LabeledObject ...@@ -30,8 +30,8 @@ class Query final : public RefCountObject, public LabeledObject
{ {
public: public:
Query(rx::QueryImpl *impl, GLuint id); Query(rx::QueryImpl *impl, GLuint id);
void destroy(const gl::Context *context) {}
~Query() override; ~Query() override;
Error onDestroy(const Context *context) override;
void setLabel(const std::string &label) override; void setLabel(const std::string &label) override;
const std::string &getLabel() const 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 ...@@ -26,6 +26,8 @@ class QueryImpl : angle::NonCopyable
explicit QueryImpl(gl::QueryType type) : mType(type) {} explicit QueryImpl(gl::QueryType type) : mType(type) {}
virtual ~QueryImpl() {} virtual ~QueryImpl() {}
virtual gl::Error onDestroy(const gl::Context *context);
virtual gl::Error begin(const gl::Context *context) = 0; virtual gl::Error begin(const gl::Context *context) = 0;
virtual gl::Error end(const gl::Context *context) = 0; virtual gl::Error end(const gl::Context *context) = 0;
virtual gl::Error queryCounter(const gl::Context *context) = 0; virtual gl::Error queryCounter(const gl::Context *context) = 0;
......
...@@ -29,13 +29,23 @@ enum class CommandGraphResourceType ...@@ -29,13 +29,23 @@ enum class CommandGraphResourceType
Buffer, Buffer,
Framebuffer, Framebuffer,
Image, 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. // Only used internally in the command graph. Kept in the header for better inlining performance.
class CommandGraphNode final : angle::NonCopyable class CommandGraphNode final : angle::NonCopyable
{ {
public: public:
CommandGraphNode(); CommandGraphNode(CommandGraphNodeFunction function);
~CommandGraphNode(); ~CommandGraphNode();
// Immutable queries for when we're walking the commands tree. // Immutable queries for when we're walking the commands tree.
...@@ -66,8 +76,12 @@ class CommandGraphNode final : angle::NonCopyable ...@@ -66,8 +76,12 @@ class CommandGraphNode final : angle::NonCopyable
// frozen forever. // frozen forever.
static void SetHappensBeforeDependency(CommandGraphNode *beforeNode, static void SetHappensBeforeDependency(CommandGraphNode *beforeNode,
CommandGraphNode *afterNode); CommandGraphNode *afterNode);
static void SetHappensBeforeDependencies(const std::vector<CommandGraphNode *> &beforeNodes, static void SetHappensBeforeDependencies(CommandGraphNode **beforeNodes,
size_t beforeNodesCount,
CommandGraphNode *afterNode); CommandGraphNode *afterNode);
static void SetHappensBeforeDependencies(CommandGraphNode *beforeNode,
CommandGraphNode **afterNodes,
size_t afterNodesCount);
bool hasParents() const; bool hasParents() const;
bool hasChildren() const { return mHasChildren; } bool hasChildren() const { return mHasChildren; }
...@@ -88,6 +102,10 @@ class CommandGraphNode final : angle::NonCopyable ...@@ -88,6 +102,10 @@ class CommandGraphNode final : angle::NonCopyable
const gl::Rectangle &getRenderPassRenderArea() const; const gl::Rectangle &getRenderPassRenderArea() const;
CommandGraphNodeFunction getFunction() const { return mFunction; }
void setQueryPool(const QueryPool *queryPool, uint32_t queryIndex);
private: private:
void setHasChildren(); void setHasChildren();
...@@ -100,11 +118,17 @@ class CommandGraphNode final : angle::NonCopyable ...@@ -100,11 +118,17 @@ class CommandGraphNode final : angle::NonCopyable
gl::Rectangle mRenderPassRenderArea; gl::Rectangle mRenderPassRenderArea;
gl::AttachmentArray<VkClearValue> mRenderPassClearValues; 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. // TODO(jmadill): We might not need inside and outside RenderPass commands separate.
CommandBuffer mOutsideRenderPassCommands; CommandBuffer mOutsideRenderPassCommands;
CommandBuffer mInsideRenderPassCommands; CommandBuffer mInsideRenderPassCommands;
// Special-function additional data:
VkQueryPool mQueryPool;
uint32_t mQueryIndex;
// Parents are commands that must be submitted before 'this' CommandNode can be submitted. // Parents are commands that must be submitted before 'this' CommandNode can be submitted.
std::vector<CommandGraphNode *> mParents; std::vector<CommandGraphNode *> mParents;
...@@ -133,6 +157,9 @@ class CommandGraphResource : angle::NonCopyable ...@@ -133,6 +157,9 @@ class CommandGraphResource : angle::NonCopyable
// Returns true if the resource is in use by the renderer. // Returns true if the resource is in use by the renderer.
bool isResourceInUse(RendererVk *renderer) const; 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. // Sets up dependency relations. 'this' resource is the resource being written to.
void addWriteDependency(CommandGraphResource *writingResource); void addWriteDependency(CommandGraphResource *writingResource);
...@@ -151,7 +178,10 @@ class CommandGraphResource : angle::NonCopyable ...@@ -151,7 +178,10 @@ class CommandGraphResource : angle::NonCopyable
const gl::Rectangle &renderArea, const gl::Rectangle &renderArea,
const RenderPassDesc &renderPassDesc, const RenderPassDesc &renderPassDesc,
const std::vector<VkClearValue> &clearValues, 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. // Checks if we're in a RenderPass, returning true if so. Updates serial internally.
// Returns the started command buffer in commandBufferOut. // Returns the started command buffer in commandBufferOut.
...@@ -170,11 +200,20 @@ class CommandGraphResource : angle::NonCopyable ...@@ -170,11 +200,20 @@ class CommandGraphResource : angle::NonCopyable
Serial getStoredQueueSerial() const; Serial getStoredQueueSerial() const;
private: private:
void startNewCommands(RendererVk *renderer, CommandGraphNodeFunction function);
void onWriteImpl(CommandGraphNode *writingNode, Serial currentSerial); void onWriteImpl(CommandGraphNode *writingNode, Serial currentSerial);
// Returns true if this node has a current writing node with no children. // Returns true if this node has a current writing node with no children.
bool hasChildlessWritingNode() const 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()); return (mCurrentWritingNode != nullptr && !mCurrentWritingNode->hasChildren());
} }
...@@ -226,8 +265,9 @@ class CommandGraph final : angle::NonCopyable ...@@ -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 // 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 // relations exist in the node by default. Call CommandGraphNode::SetHappensBeforeDependency
// to set up dependency relations. // to set up dependency relations. If the node is a barrier, it will automatically add
CommandGraphNode *allocateNode(); // dependencies between the previous barrier, the new barrier and all nodes in between.
CommandGraphNode *allocateNode(bool isBarrier, CommandGraphNodeFunction function);
angle::Result submitCommands(Context *context, angle::Result submitCommands(Context *context,
Serial serial, Serial serial,
...@@ -236,11 +276,65 @@ class CommandGraph final : angle::NonCopyable ...@@ -236,11 +276,65 @@ class CommandGraph final : angle::NonCopyable
CommandBuffer *primaryCommandBufferOut); CommandBuffer *primaryCommandBufferOut);
bool empty() const; bool empty() const;
CommandGraphNode *getLastBarrierNode(size_t *indexOut);
private: private:
void dumpGraphDotFile(std::ostream &out) const; void dumpGraphDotFile(std::ostream &out) const;
void setNewBarrier(CommandGraphNode *newBarrier);
void addDependenciesToNextBarrier(size_t begin, size_t end, CommandGraphNode *nextBarrier);
std::vector<CommandGraphNode *> mNodes; std::vector<CommandGraphNode *> mNodes;
bool mEnableGraphDiagnostics; 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 vk
} // namespace rx } // namespace rx
......
...@@ -169,6 +169,11 @@ void ContextVk::onDestroy(const gl::Context *context) ...@@ -169,6 +169,11 @@ void ContextVk::onDestroy(const gl::Context *context)
{ {
defaultBuffer.destroy(getDevice()); defaultBuffer.destroy(getDevice());
} }
for (vk::DynamicQueryPool &queryPool : mQueryPools)
{
queryPool.destroy(getDevice());
}
} }
gl::Error ContextVk::getIncompleteTexture(const gl::Context *context, gl::Error ContextVk::getIncompleteTexture(const gl::Context *context,
...@@ -199,6 +204,12 @@ gl::Error ContextVk::initialize() ...@@ -199,6 +204,12 @@ gl::Error ContextVk::initialize()
ANGLE_TRY(mDynamicDescriptorPools[kDriverUniformsDescriptorSetIndex].init( ANGLE_TRY(mDynamicDescriptorPools[kDriverUniformsDescriptorSetIndex].init(
this, driverUniformsPoolSize)); 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>( size_t minAlignment = static_cast<size_t>(
mRenderer->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment); mRenderer->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment);
mDriverUniformsBuffer.init(minAlignment, mRenderer); mDriverUniformsBuffer.init(minAlignment, mRenderer);
...@@ -1081,6 +1092,14 @@ vk::DynamicDescriptorPool *ContextVk::getDynamicDescriptorPool(uint32_t descript ...@@ -1081,6 +1092,14 @@ vk::DynamicDescriptorPool *ContextVk::getDynamicDescriptorPool(uint32_t descript
return &mDynamicDescriptorPools[descriptorSetIndex]; 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 const VkClearValue &ContextVk::getClearColorValue() const
{ {
return mClearColorValue; return mClearColorValue;
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include "common/PackedEnums.h"
#include "libANGLE/renderer/ContextImpl.h" #include "libANGLE/renderer/ContextImpl.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h" #include "libANGLE/renderer/vulkan/vk_helpers.h"
...@@ -161,6 +162,7 @@ class ContextVk : public ContextImpl, public vk::Context ...@@ -161,6 +162,7 @@ class ContextVk : public ContextImpl, public vk::Context
void invalidateDefaultAttributes(const gl::AttributesMask &dirtyMask); void invalidateDefaultAttributes(const gl::AttributesMask &dirtyMask);
vk::DynamicDescriptorPool *getDynamicDescriptorPool(uint32_t descriptorSetIndex); vk::DynamicDescriptorPool *getDynamicDescriptorPool(uint32_t descriptorSetIndex);
vk::DynamicQueryPool *getQueryPool(gl::QueryType queryType);
const VkClearValue &getClearColorValue() const; const VkClearValue &getClearColorValue() const;
const VkClearValue &getClearDepthStencilValue() const; const VkClearValue &getClearDepthStencilValue() const;
...@@ -251,7 +253,9 @@ class ContextVk : public ContextImpl, public vk::Context ...@@ -251,7 +253,9 @@ class ContextVk : public ContextImpl, public vk::Context
// The descriptor pools are externally sychronized, so cannot be accessed from different // 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. // threads simultaneously. Hence, we keep them in the ContextVk instead of the RendererVk.
// Same with query pools.
vk::DescriptorSetLayoutArray<vk::DynamicDescriptorPool> mDynamicDescriptorPools; vk::DescriptorSetLayoutArray<vk::DynamicDescriptorPool> mDynamicDescriptorPools;
angle::PackedEnumMap<gl::QueryType, vk::DynamicQueryPool> mQueryPools;
// Dirty bits. // Dirty bits.
DirtyBits mDirtyBits; 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 // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// //
...@@ -8,30 +8,48 @@ ...@@ -8,30 +8,48 @@
// //
#include "libANGLE/renderer/vulkan/QueryVk.h" #include "libANGLE/renderer/vulkan/QueryVk.h"
#include "libANGLE/Context.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "common/debug.h" #include "common/debug.h"
namespace rx 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) gl::Error QueryVk::begin(const gl::Context *context)
{ {
UNIMPLEMENTED(); ContextVk *contextVk = vk::GetImpl(context);
return gl::InternalError();
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) gl::Error QueryVk::end(const gl::Context *context)
{ {
UNIMPLEMENTED(); ContextVk *contextVk = vk::GetImpl(context);
return gl::InternalError();
mQueryHelper.endQuery(contextVk, mQueryHelper.getQueryPool(), mQueryHelper.getQuery());
return gl::NoError();
} }
gl::Error QueryVk::queryCounter(const gl::Context *context) gl::Error QueryVk::queryCounter(const gl::Context *context)
...@@ -40,34 +58,88 @@ gl::Error QueryVk::queryCounter(const gl::Context *context) ...@@ -40,34 +58,88 @@ gl::Error QueryVk::queryCounter(const gl::Context *context)
return gl::InternalError(); 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) gl::Error QueryVk::getResult(const gl::Context *context, GLint *params)
{ {
UNIMPLEMENTED(); ANGLE_TRY(getResult(context, true));
return gl::InternalError(); *params = static_cast<GLint>(mCachedResult);
return gl::NoError();
} }
gl::Error QueryVk::getResult(const gl::Context *context, GLuint *params) gl::Error QueryVk::getResult(const gl::Context *context, GLuint *params)
{ {
UNIMPLEMENTED(); ANGLE_TRY(getResult(context, true));
return gl::InternalError(); *params = static_cast<GLuint>(mCachedResult);
return gl::NoError();
} }
gl::Error QueryVk::getResult(const gl::Context *context, GLint64 *params) gl::Error QueryVk::getResult(const gl::Context *context, GLint64 *params)
{ {
UNIMPLEMENTED(); ANGLE_TRY(getResult(context, true));
return gl::InternalError(); *params = static_cast<GLint64>(mCachedResult);
return gl::NoError();
} }
gl::Error QueryVk::getResult(const gl::Context *context, GLuint64 *params) gl::Error QueryVk::getResult(const gl::Context *context, GLuint64 *params)
{ {
UNIMPLEMENTED(); ANGLE_TRY(getResult(context, true));
return gl::InternalError(); *params = mCachedResult;
return gl::NoError();
} }
gl::Error QueryVk::isResultAvailable(const gl::Context *context, bool *available) gl::Error QueryVk::isResultAvailable(const gl::Context *context, bool *available)
{ {
UNIMPLEMENTED(); ContextVk *contextVk = vk::GetImpl(context);
return gl::InternalError();
// 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 } // namespace rx
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#define LIBANGLE_RENDERER_VULKAN_QUERYVK_H_ #define LIBANGLE_RENDERER_VULKAN_QUERYVK_H_
#include "libANGLE/renderer/QueryImpl.h" #include "libANGLE/renderer/QueryImpl.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"
namespace rx namespace rx
{ {
...@@ -21,6 +22,8 @@ class QueryVk : public QueryImpl ...@@ -21,6 +22,8 @@ class QueryVk : public QueryImpl
QueryVk(gl::QueryType type); QueryVk(gl::QueryType type);
~QueryVk() override; ~QueryVk() override;
gl::Error onDestroy(const gl::Context *context) override;
gl::Error begin(const gl::Context *context) override; gl::Error begin(const gl::Context *context) override;
gl::Error end(const gl::Context *context) override; gl::Error end(const gl::Context *context) override;
gl::Error queryCounter(const gl::Context *context) override; gl::Error queryCounter(const gl::Context *context) override;
...@@ -29,6 +32,13 @@ class QueryVk : public QueryImpl ...@@ -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, GLint64 *params) override;
gl::Error getResult(const gl::Context *context, GLuint64 *params) override; gl::Error getResult(const gl::Context *context, GLuint64 *params) override;
gl::Error isResultAvailable(const gl::Context *context, bool *available) 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 } // namespace rx
......
...@@ -573,6 +573,10 @@ angle::Result RendererVk::initializeDevice(DisplayVk *displayVk, uint32_t queueF ...@@ -573,6 +573,10 @@ angle::Result RendererVk::initializeDevice(DisplayVk *displayVk, uint32_t queueF
ANGLE_VK_TRY(displayVk, VerifyExtensionsPresent(deviceExtensionProps, enabledDeviceExtensions)); ANGLE_VK_TRY(displayVk, VerifyExtensionsPresent(deviceExtensionProps, enabledDeviceExtensions));
// Select additional features to be enabled
VkPhysicalDeviceFeatures enabledFeatures = {};
enabledFeatures.inheritedQueries = mPhysicalDeviceFeatures.inheritedQueries;
VkDeviceQueueCreateInfo queueCreateInfo = {}; VkDeviceQueueCreateInfo queueCreateInfo = {};
float zeroPriority = 0.0f; float zeroPriority = 0.0f;
...@@ -595,7 +599,7 @@ angle::Result RendererVk::initializeDevice(DisplayVk *displayVk, uint32_t queueF ...@@ -595,7 +599,7 @@ angle::Result RendererVk::initializeDevice(DisplayVk *displayVk, uint32_t queueF
createInfo.enabledExtensionCount = static_cast<uint32_t>(enabledDeviceExtensions.size()); createInfo.enabledExtensionCount = static_cast<uint32_t>(enabledDeviceExtensions.size());
createInfo.ppEnabledExtensionNames = createInfo.ppEnabledExtensionNames =
enabledDeviceExtensions.empty() ? nullptr : enabledDeviceExtensions.data(); enabledDeviceExtensions.empty() ? nullptr : enabledDeviceExtensions.data();
createInfo.pEnabledFeatures = nullptr; // TODO(jmadill): features createInfo.pEnabledFeatures = &enabledFeatures;
ANGLE_VK_TRY(displayVk, vkCreateDevice(mPhysicalDevice, &createInfo, nullptr, &mDevice)); ANGLE_VK_TRY(displayVk, vkCreateDevice(mPhysicalDevice, &createInfo, nullptr, &mDevice));
......
...@@ -60,6 +60,10 @@ class RendererVk : angle::NonCopyable ...@@ -60,6 +60,10 @@ class RendererVk : angle::NonCopyable
{ {
return mPhysicalDeviceProperties; return mPhysicalDeviceProperties;
} }
const VkPhysicalDeviceFeatures &getPhysicalDeviceFeatures() const
{
return mPhysicalDeviceFeatures;
}
VkQueue getQueue() const { return mQueue; } VkQueue getQueue() const { return mQueue; }
VkDevice getDevice() const { return mDevice; } VkDevice getDevice() const { return mDevice; }
...@@ -81,6 +85,7 @@ class RendererVk : angle::NonCopyable ...@@ -81,6 +85,7 @@ class RendererVk : angle::NonCopyable
uint32_t getMaxActiveTextures(); uint32_t getMaxActiveTextures();
Serial getCurrentQueueSerial() const { return mCurrentQueueSerial; } Serial getCurrentQueueSerial() const { return mCurrentQueueSerial; }
Serial getLastCompletedQueueSerial() const { return mLastCompletedQueueSerial; }
bool isSerialInUse(Serial serial) const; bool isSerialInUse(Serial serial) const;
......
...@@ -48,8 +48,10 @@ void GenerateCaps(const VkPhysicalDeviceProperties &physicalDeviceProperties, ...@@ -48,8 +48,10 @@ void GenerateCaps(const VkPhysicalDeviceProperties &physicalDeviceProperties,
outExtensions->debugMarker = true; outExtensions->debugMarker = true;
outExtensions->robustness = true; outExtensions->robustness = true;
// TODO: Not implemented yet but exposed so that Chrome can load the query entry points. http://anglebug.com/2855 // We use secondary command buffers almost everywhere and they require a feature to be
outExtensions->occlusionQueryBoolean = true; // 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 // TODO(lucferron): Eventually remove everything above this line in this function as the caps
// get implemented. // get implemented.
......
...@@ -351,6 +351,133 @@ void DynamicDescriptorPool::setMaxSetsPerPoolForTesting(uint32_t maxSetsPerPool) ...@@ -351,6 +351,133 @@ void DynamicDescriptorPool::setMaxSetsPerPoolForTesting(uint32_t maxSetsPerPool)
mMaxSetsPerPool = 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 implementation.
LineLoopHelper::LineLoopHelper(RendererVk *renderer) LineLoopHelper::LineLoopHelper(RendererVk *renderer)
: mDynamicIndexBuffer(kLineLoopDynamicBufferUsage, kLineLoopDynamicBufferMinSize) : mDynamicIndexBuffer(kLineLoopDynamicBufferUsage, kLineLoopDynamicBufferMinSize)
......
...@@ -101,7 +101,7 @@ class DynamicDescriptorPool final : angle::NonCopyable ...@@ -101,7 +101,7 @@ class DynamicDescriptorPool final : angle::NonCopyable
DynamicDescriptorPool(); DynamicDescriptorPool();
~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); angle::Result init(Context *context, const VkDescriptorPoolSize &poolSize);
void destroy(VkDevice device); void destroy(VkDevice device);
...@@ -125,6 +125,95 @@ class DynamicDescriptorPool final : angle::NonCopyable ...@@ -125,6 +125,95 @@ class DynamicDescriptorPool final : angle::NonCopyable
uint32_t mFreeDescriptorSets; 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. // 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 // 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 // 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, ...@@ -569,6 +569,24 @@ void CommandBuffer::pushConstants(const PipelineLayout &layout,
vkCmdPushConstants(mHandle, layout.getHandle(), flag, offset, size, data); 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 implementation.
Image::Image() Image::Image()
{ {
...@@ -1109,6 +1127,44 @@ void StagingBuffer::dumpResources(Serial serial, std::vector<vk::GarbageObject> ...@@ -1109,6 +1127,44 @@ void StagingBuffer::dumpResources(Serial serial, std::vector<vk::GarbageObject>
mDeviceMemory.dumpResources(serial, garbageQueue); 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, angle::Result AllocateBufferMemory(vk::Context *context,
VkMemoryPropertyFlags requestedMemoryPropertyFlags, VkMemoryPropertyFlags requestedMemoryPropertyFlags,
VkMemoryPropertyFlags *memoryPropertyFlagsOut, VkMemoryPropertyFlags *memoryPropertyFlagsOut,
...@@ -1220,6 +1276,9 @@ void GarbageObject::destroy(VkDevice device) ...@@ -1220,6 +1276,9 @@ void GarbageObject::destroy(VkDevice device)
case HandleType::CommandPool: case HandleType::CommandPool:
vkDestroyCommandPool(device, reinterpret_cast<VkCommandPool>(mHandle), nullptr); vkDestroyCommandPool(device, reinterpret_cast<VkCommandPool>(mHandle), nullptr);
break; break;
case HandleType::QueryPool:
vkDestroyQueryPool(device, reinterpret_cast<VkQueryPool>(mHandle), nullptr);
break;
default: default:
UNREACHABLE(); UNREACHABLE();
break; break;
......
...@@ -169,7 +169,8 @@ GetImplType<T> *GetImpl(const T *glObject) ...@@ -169,7 +169,8 @@ GetImplType<T> *GetImpl(const T *glObject)
FUNC(Sampler) \ FUNC(Sampler) \
FUNC(DescriptorPool) \ FUNC(DescriptorPool) \
FUNC(Framebuffer) \ FUNC(Framebuffer) \
FUNC(CommandPool) FUNC(CommandPool) \
FUNC(QueryPool)
#define ANGLE_COMMA_SEP_FUNC(TYPE) TYPE, #define ANGLE_COMMA_SEP_FUNC(TYPE) TYPE,
...@@ -422,6 +423,10 @@ class CommandBuffer : public WrappedObject<CommandBuffer, VkCommandBuffer> ...@@ -422,6 +423,10 @@ class CommandBuffer : public WrappedObject<CommandBuffer, VkCommandBuffer>
uint32_t offset, uint32_t offset,
uint32_t size, uint32_t size,
const void *data); 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> class Image final : public WrappedObject<Image, VkImage>
...@@ -628,6 +633,22 @@ class StagingBuffer final : angle::NonCopyable ...@@ -628,6 +633,22 @@ class StagingBuffer final : angle::NonCopyable
size_t mSize; 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> template <typename ObjT>
class ObjectAndSerial final : angle::NonCopyable class ObjectAndSerial final : angle::NonCopyable
{ {
...@@ -769,19 +790,25 @@ VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bo ...@@ -769,19 +790,25 @@ VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bo
} \ } \
ANGLE_EMPTY_STATEMENT ANGLE_EMPTY_STATEMENT
#define ANGLE_VK_TRY_ALLOW_INCOMPLETE(context, command, result) \ #define ANGLE_VK_TRY_ALLOW_OTHER(context, command, acceptable, result) \
{ \ { \
auto ANGLE_LOCAL_VAR = command; \ auto ANGLE_LOCAL_VAR = command; \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR != VK_SUCCESS && ANGLE_LOCAL_VAR != VK_INCOMPLETE)) \ if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR != VK_SUCCESS && ANGLE_LOCAL_VAR != acceptable)) \
{ \ { \
context->handleError(ANGLE_LOCAL_VAR, __FILE__, __LINE__); \ context->handleError(ANGLE_LOCAL_VAR, __FILE__, __LINE__); \
return angle::Result::Stop(); \ return angle::Result::Stop(); \
} \ } \
result = ANGLE_LOCAL_VAR == VK_INCOMPLETE ? angle::Result::Incomplete() \ result = ANGLE_LOCAL_VAR == VK_SUCCESS ? angle::Result::Continue() \
: angle::Result::Continue(); \ : angle::Result::Incomplete(); \
} \ } \
ANGLE_EMPTY_STATEMENT 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(context, test, error) ANGLE_VK_TRY(context, test ? VK_SUCCESS : error)
#define ANGLE_VK_CHECK_MATH(context, result) \ #define ANGLE_VK_CHECK_MATH(context, result) \
......
...@@ -257,6 +257,7 @@ libangle_sources = [ ...@@ -257,6 +257,7 @@ libangle_sources = [
"src/libANGLE/renderer/PathImpl.h", "src/libANGLE/renderer/PathImpl.h",
"src/libANGLE/renderer/ProgramImpl.h", "src/libANGLE/renderer/ProgramImpl.h",
"src/libANGLE/renderer/ProgramPipelineImpl.h", "src/libANGLE/renderer/ProgramPipelineImpl.h",
"src/libANGLE/renderer/QueryImpl.cpp",
"src/libANGLE/renderer/QueryImpl.h", "src/libANGLE/renderer/QueryImpl.h",
"src/libANGLE/renderer/RenderbufferImpl.h", "src/libANGLE/renderer/RenderbufferImpl.h",
"src/libANGLE/renderer/RenderTargetCache.h", "src/libANGLE/renderer/RenderTargetCache.h",
......
...@@ -184,9 +184,9 @@ TEST_P(OcclusionQueriesTest, MultiContext) ...@@ -184,9 +184,9 @@ TEST_P(OcclusionQueriesTest, MultiContext)
!extensionEnabled("GL_EXT_occlusion_query_boolean")); !extensionEnabled("GL_EXT_occlusion_query_boolean"));
// Test skipped because the D3D backends cannot support simultaneous queries on multiple // 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() || ANGLE_SKIP_TEST_IF(GetParam() == ES2_D3D9() || GetParam() == ES2_D3D11() ||
GetParam() == ES3_D3D11()); GetParam() == ES3_D3D11() || GetParam() == ES2_VULKAN());
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
...@@ -310,4 +310,5 @@ ANGLE_INSTANTIATE_TEST(OcclusionQueriesTest, ...@@ -310,4 +310,5 @@ ANGLE_INSTANTIATE_TEST(OcclusionQueriesTest,
ES2_OPENGL(), ES2_OPENGL(),
ES3_OPENGL(), ES3_OPENGL(),
ES2_OPENGLES(), 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