Commit c4765aa7 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: fix glGetQueryObject not flushing

glGetQueryObject* requires forward progress in the queue regardless of whether we are waiting on the result or busy-looping over whether the results are available. This commit calls flush() if the query has pending work. Additionally, this fixes a race condition where glGetQueryObject* may be accessing a query whose corresponding batch has been submitted but not yet executed. In such a case, the GPU may not have already reset the query, so we have to wait on the fence of that batch to make sure the query results are reliably available. Bug: angleproject:2855 Change-Id: I977909c6526c0778a13722a8b8b73e54ad0202f6 Reviewed-on: https://chromium-review.googlesource.com/c/1279125 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 4163c014
...@@ -193,12 +193,13 @@ class CommandGraphResource : angle::NonCopyable ...@@ -193,12 +193,13 @@ class CommandGraphResource : angle::NonCopyable
// Called when 'this' object changes, but we'd like to start a new command buffer later. // Called when 'this' object changes, but we'd like to start a new command buffer later.
void finishCurrentCommands(RendererVk *renderer); void finishCurrentCommands(RendererVk *renderer);
// Get the current queue serial for this resource. Used to release resources, and for
// queries, to know if the queue they are submitted on has finished execution.
Serial getStoredQueueSerial() const;
protected: protected:
explicit CommandGraphResource(CommandGraphResourceType resourceType); explicit CommandGraphResource(CommandGraphResourceType resourceType);
// Get the current queue serial for this resource. Only used to release resources.
Serial getStoredQueueSerial() const;
private: private:
void startNewCommands(RendererVk *renderer, CommandGraphNodeFunction function); void startNewCommands(RendererVk *renderer, CommandGraphNodeFunction function);
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "libANGLE/renderer/vulkan/QueryVk.h" #include "libANGLE/renderer/vulkan/QueryVk.h"
#include "libANGLE/Context.h" #include "libANGLE/Context.h"
#include "libANGLE/renderer/vulkan/ContextVk.h" #include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "common/debug.h" #include "common/debug.h"
...@@ -66,6 +67,28 @@ angle::Result QueryVk::getResult(const gl::Context *context, bool wait) ...@@ -66,6 +67,28 @@ angle::Result QueryVk::getResult(const gl::Context *context, bool wait)
} }
ContextVk *contextVk = vk::GetImpl(context); ContextVk *contextVk = vk::GetImpl(context);
RendererVk *renderer = contextVk->getRenderer();
// glGetQueryObject* requires an implicit flush of the command buffers to guarantee execution in
// finite time.
if (mQueryHelper.hasPendingWork(renderer))
{
ANGLE_TRY_HANDLE(context, renderer->flush(contextVk));
ASSERT(!mQueryHelper.hasPendingWork(renderer));
}
// If the command buffer this query is being written to is still in flight, its reset command
// may not have been performed by the GPU yet. To avoid a race condition in this case, wait
// for the batch to finish first before querying (or return not-ready if not waiting).
ANGLE_TRY(renderer->checkCompletedCommands(contextVk));
if (mQueryHelper.isResourceInUse(renderer))
{
if (!wait)
{
return angle::Result::Continue();
}
ANGLE_TRY(renderer->finishToSerial(contextVk, mQueryHelper.getStoredQueueSerial()));
}
VkQueryResultFlags flags = (wait ? VK_QUERY_RESULT_WAIT_BIT : 0) | VK_QUERY_RESULT_64_BIT; VkQueryResultFlags flags = (wait ? VK_QUERY_RESULT_WAIT_BIT : 0) | VK_QUERY_RESULT_64_BIT;
...@@ -74,23 +97,28 @@ angle::Result QueryVk::getResult(const gl::Context *context, bool wait) ...@@ -74,23 +97,28 @@ angle::Result QueryVk::getResult(const gl::Context *context, bool wait)
sizeof(mCachedResult), flags); sizeof(mCachedResult), flags);
ANGLE_TRY(result); ANGLE_TRY(result);
if (result == angle::Result::Continue()) // If the results are not ready, do nothing. mCachedResultValid remains false.
if (result == angle::Result::Incomplete())
{ {
mCachedResultValid = true; // If VK_QUERY_RESULT_WAIT_BIT was given, Incomplete() cannot have been returned.
ASSERT(!wait);
return angle::Result::Continue();
}
switch (getType()) // Fix up the results to what OpenGL expects.
{ switch (getType())
case gl::QueryType::AnySamples: {
case gl::QueryType::AnySamplesConservative: case gl::QueryType::AnySamples:
// OpenGL query result in these cases is binary case gl::QueryType::AnySamplesConservative:
mCachedResult = !!mCachedResult; // OpenGL query result in these cases is binary
break; mCachedResult = !!mCachedResult;
default: break;
UNREACHABLE(); default:
break; UNREACHABLE();
} break;
} }
mCachedResultValid = true;
return angle::Result::Continue(); return angle::Result::Continue();
} }
...@@ -124,18 +152,6 @@ gl::Error QueryVk::getResult(const gl::Context *context, GLuint64 *params) ...@@ -124,18 +152,6 @@ gl::Error QueryVk::getResult(const gl::Context *context, GLuint64 *params)
gl::Error QueryVk::isResultAvailable(const gl::Context *context, bool *available) gl::Error QueryVk::isResultAvailable(const gl::Context *context, bool *available)
{ {
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)); ANGLE_TRY(getResult(context, false));
*available = mCachedResultValid; *available = mCachedResultValid;
......
...@@ -881,17 +881,17 @@ void RendererVk::freeAllInFlightResources() ...@@ -881,17 +881,17 @@ void RendererVk::freeAllInFlightResources()
mGarbage.clear(); mGarbage.clear();
} }
angle::Result RendererVk::checkInFlightCommands(vk::Context *context) angle::Result RendererVk::checkCompletedCommands(vk::Context *context)
{ {
int finishedCount = 0; int finishedCount = 0;
for (CommandBatch &batch : mInFlightCommands) for (CommandBatch &batch : mInFlightCommands)
{ {
VkResult result = batch.fence.getStatus(mDevice); angle::Result result = batch.fence.getStatus(context);
if (result == VK_NOT_READY) ANGLE_TRY(result);
if (result == angle::Result::Incomplete())
break; break;
ANGLE_VK_TRY(context, result);
ASSERT(batch.serial > mLastCompletedQueueSerial); ASSERT(batch.serial > mLastCompletedQueueSerial);
mLastCompletedQueueSerial = batch.serial; mLastCompletedQueueSerial = batch.serial;
...@@ -949,7 +949,7 @@ angle::Result RendererVk::submitFrame(vk::Context *context, ...@@ -949,7 +949,7 @@ angle::Result RendererVk::submitFrame(vk::Context *context,
// TODO(jmadill): Overflow check. // TODO(jmadill): Overflow check.
mCurrentQueueSerial = mQueueSerialFactory.generate(); mCurrentQueueSerial = mQueueSerialFactory.generate();
ANGLE_TRY(checkInFlightCommands(context)); ANGLE_TRY(checkCompletedCommands(context));
// Simply null out the command buffer here - it was allocated using the command pool. // Simply null out the command buffer here - it was allocated using the command pool.
commandBuffer.releaseHandle(); commandBuffer.releaseHandle();
...@@ -971,6 +971,40 @@ bool RendererVk::isSerialInUse(Serial serial) const ...@@ -971,6 +971,40 @@ bool RendererVk::isSerialInUse(Serial serial) const
return serial > mLastCompletedQueueSerial; return serial > mLastCompletedQueueSerial;
} }
angle::Result RendererVk::finishToSerial(vk::Context *context, Serial serial)
{
if (!isSerialInUse(serial) || mInFlightCommands.empty())
{
return angle::Result::Continue();
}
// Find the first batch with serial equal to or bigger than given serial (note that
// the batch serials are unique, otherwise upper-bound would have been necessary).
size_t batchIndex = mInFlightCommands.size() - 1;
for (size_t i = 0; i < mInFlightCommands.size(); ++i)
{
if (mInFlightCommands[i].serial >= serial)
{
batchIndex = i;
break;
}
}
const CommandBatch &batch = mInFlightCommands[batchIndex];
// Wait for it finish
constexpr uint64_t kMaxFenceWaitTimeNs = 10'000'000'000llu;
angle::Result result = batch.fence.wait(context, kMaxFenceWaitTimeNs);
if (result == angle::Result::Incomplete())
{
// Wait a maximum of 10s. If that times out, we declare it a failure.
result = angle::Result::Stop();
}
ANGLE_TRY(result);
// Clean up finished batches.
return checkCompletedCommands(context);
}
angle::Result RendererVk::getCompatibleRenderPass(vk::Context *context, angle::Result RendererVk::getCompatibleRenderPass(vk::Context *context,
const vk::RenderPassDesc &desc, const vk::RenderPassDesc &desc,
vk::RenderPass **renderPassOut) vk::RenderPass **renderPassOut)
......
...@@ -101,6 +101,13 @@ class RendererVk : angle::NonCopyable ...@@ -101,6 +101,13 @@ class RendererVk : angle::NonCopyable
} }
} }
// Check to see which batches have finished completion (forward progress for
// mLastCompletedQueueSerial, for example for when the application busy waits on a query
// result).
angle::Result checkCompletedCommands(vk::Context *context);
// Wait for completion of batches until (at least) batch with given serial is finished.
angle::Result finishToSerial(vk::Context *context, Serial serial);
uint32_t getQueueFamilyIndex() const { return mCurrentQueueFamilyIndex; } uint32_t getQueueFamilyIndex() const { return mCurrentQueueFamilyIndex; }
const vk::MemoryProperties &getMemoryProperties() const { return mMemoryProperties; } const vk::MemoryProperties &getMemoryProperties() const { return mMemoryProperties; }
...@@ -180,7 +187,6 @@ class RendererVk : angle::NonCopyable ...@@ -180,7 +187,6 @@ class RendererVk : angle::NonCopyable
angle::Result submitFrame(vk::Context *context, angle::Result submitFrame(vk::Context *context,
const VkSubmitInfo &submitInfo, const VkSubmitInfo &submitInfo,
vk::CommandBuffer &&commandBuffer); vk::CommandBuffer &&commandBuffer);
angle::Result checkInFlightCommands(vk::Context *context);
void freeAllInFlightResources(); void freeAllInFlightResources();
angle::Result flushCommandGraph(vk::Context *context, vk::CommandBuffer *commandBatch); angle::Result flushCommandGraph(vk::Context *context, vk::CommandBuffer *commandBatch);
void initFeatures(); void initFeatures();
......
...@@ -1013,9 +1013,15 @@ angle::Result Fence::init(Context *context, const VkFenceCreateInfo &createInfo) ...@@ -1013,9 +1013,15 @@ angle::Result Fence::init(Context *context, const VkFenceCreateInfo &createInfo)
vkCreateFence(context->getDevice(), &createInfo, nullptr, &mHandle)); vkCreateFence(context->getDevice(), &createInfo, nullptr, &mHandle));
} }
VkResult Fence::getStatus(VkDevice device) const angle::Result Fence::getStatus(Context *context) const
{ {
return vkGetFenceStatus(device, mHandle); ANGLE_VK_TRY_RETURN_ALLOW_NOT_READY(context, vkGetFenceStatus(context->getDevice(), mHandle));
}
angle::Result Fence::wait(Context *context, uint64_t timeout) const
{
ANGLE_VK_TRY_RETURN_ALLOW_TIMEOUT(
context, vkWaitForFences(context->getDevice(), 1, &mHandle, true, timeout));
} }
// MemoryProperties implementation. // MemoryProperties implementation.
......
...@@ -607,7 +607,8 @@ class Fence final : public WrappedObject<Fence, VkFence> ...@@ -607,7 +607,8 @@ class Fence final : public WrappedObject<Fence, VkFence>
using WrappedObject::operator=; using WrappedObject::operator=;
angle::Result init(Context *context, const VkFenceCreateInfo &createInfo); angle::Result init(Context *context, const VkFenceCreateInfo &createInfo);
VkResult getStatus(VkDevice device) const; angle::Result getStatus(Context *context) const;
angle::Result wait(Context *context, uint64_t timeout) const;
}; };
// Similar to StagingImage, for Buffers. // Similar to StagingImage, for Buffers.
...@@ -906,4 +907,7 @@ VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bo ...@@ -906,4 +907,7 @@ VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bo
#define ANGLE_VK_TRY_RETURN_ALLOW_NOT_READY(context, command) \ #define ANGLE_VK_TRY_RETURN_ALLOW_NOT_READY(context, command) \
ANGLE_VK_TRY_RETURN_ALLOW_OTHER(context, command, VK_NOT_READY) ANGLE_VK_TRY_RETURN_ALLOW_OTHER(context, command, VK_NOT_READY)
#define ANGLE_VK_TRY_RETURN_ALLOW_TIMEOUT(context, command) \
ANGLE_VK_TRY_RETURN_ALLOW_OTHER(context, command, VK_TIMEOUT)
#endif // LIBANGLE_RENDERER_VULKAN_VK_UTILS_H_ #endif // LIBANGLE_RENDERER_VULKAN_VK_UTILS_H_
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