Commit 20ae6814 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Decouple EGLSync from renderer serial

To support future work where RendererVk functionality is moved to ContextVk. Given multiple contexts, EGLSync can no longer rely on a single serial as it can be used in multiple contexts. Instead, the fence corresponding to the submission in which the EGLSync object signals is kept so it can be waited on. Introduces a `vk::Shared` class that includes a ref-counted reference to a Vulkan object (vk::Fence in this case). This is specially made to `destroy()` object when reference count reaches zero. Bug: angleproject:2464 Change-Id: I68c8229eea8df77974e28fcc2a9563dae5d204f9 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1493131 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill (use @chromium please) <jmadill@google.com> Reviewed-by: 's avatarYuly Novikov <ynovikov@chromium.org>
parent e397949e
......@@ -468,8 +468,9 @@ RendererVk::CommandBatch::CommandBatch() = default;
RendererVk::CommandBatch::~CommandBatch() = default;
RendererVk::CommandBatch::CommandBatch(CommandBatch &&other)
: commandPool(std::move(other.commandPool)), fence(std::move(other.fence)), serial(other.serial)
{}
{
*this = std::move(other);
}
RendererVk::CommandBatch &RendererVk::CommandBatch::operator=(CommandBatch &&other)
{
......@@ -482,7 +483,7 @@ RendererVk::CommandBatch &RendererVk::CommandBatch::operator=(CommandBatch &&oth
void RendererVk::CommandBatch::destroy(VkDevice device)
{
commandPool.destroy(device);
fence.destroy(device);
fence.reset(device);
}
// RendererVk implementation.
......@@ -1371,12 +1372,12 @@ void RendererVk::freeAllInFlightResources()
// On device loss we need to wait for fence to be signaled before destroying it
if (mDeviceLost)
{
VkResult status = batch.fence.wait(mDevice, kMaxFenceWaitTimeNs);
VkResult status = batch.fence.get().wait(mDevice, kMaxFenceWaitTimeNs);
// If wait times out, it is probably not possible to recover from lost device
ASSERT(status == VK_SUCCESS || status == VK_ERROR_DEVICE_LOST);
}
batch.fence.destroy(mDevice);
batch.commandPool.destroy(mDevice);
batch.fence.reset(mDevice);
}
mInFlightCommands.clear();
......@@ -1395,7 +1396,7 @@ angle::Result RendererVk::checkCompletedCommands(vk::Context *context)
for (CommandBatch &batch : mInFlightCommands)
{
VkResult result = batch.fence.getStatus(mDevice);
VkResult result = batch.fence.get().getStatus(mDevice);
if (result == VK_NOT_READY)
{
break;
......@@ -1405,7 +1406,7 @@ angle::Result RendererVk::checkCompletedCommands(vk::Context *context)
ASSERT(batch.serial > mLastCompletedQueueSerial);
mLastCompletedQueueSerial = batch.serial;
batch.fence.destroy(mDevice);
batch.fence.reset(mDevice);
TRACE_EVENT0("gpu.angle", "commandPool.destroy");
batch.commandPool.destroy(mDevice);
++finishedCount;
......@@ -1434,15 +1435,12 @@ angle::Result RendererVk::submitFrame(vk::Context *context,
vk::CommandBuffer &&commandBuffer)
{
TRACE_EVENT0("gpu.angle", "RendererVk::submitFrame");
VkFenceCreateInfo fenceInfo = {};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = 0;
vk::Scoped<CommandBatch> scopedBatch(mDevice);
CommandBatch &batch = scopedBatch.get();
ANGLE_VK_TRY(context, batch.fence.init(mDevice, fenceInfo));
ANGLE_TRY(getSubmitFence(context, &batch.fence));
ANGLE_VK_TRY(context, vkQueueSubmit(mQueue, 1, &submitInfo, batch.fence.getHandle()));
ANGLE_VK_TRY(context, vkQueueSubmit(mQueue, 1, &submitInfo, batch.fence.get().getHandle()));
// Store this command buffer in the in-flight list.
batch.commandPool = std::move(mCommandPool);
......@@ -1450,10 +1448,12 @@ angle::Result RendererVk::submitFrame(vk::Context *context,
mInFlightCommands.emplace_back(scopedBatch.release());
// Make sure a new fence is created for the next submission.
mSubmitFence.reset(mDevice);
// CPU should be throttled to avoid mInFlightCommands from growing too fast. That is done on
// swap() though, and there could be multiple submissions in between (through glFlush() calls),
// so the limit is larger than the expected number of images. The
// InterleavedAttributeDataBenchmark perf test for example issues a large number of flushes.
// so the limit is larger than the expected number of images.
ASSERT(mInFlightCommands.size() <= kInFlightCommandsLimit);
nextSerial();
......@@ -1505,24 +1505,6 @@ bool RendererVk::isSerialInUse(Serial serial) const
angle::Result RendererVk::finishToSerial(vk::Context *context, Serial serial)
{
bool timedOut = false;
angle::Result result = finishToSerialOrTimeout(context, serial, kMaxFenceWaitTimeNs, &timedOut);
// Don't tolerate timeout. If such a large wait time results in timeout, something's wrong.
if (timedOut)
{
result = angle::Result::Stop;
}
return result;
}
angle::Result RendererVk::finishToSerialOrTimeout(vk::Context *context,
Serial serial,
uint64_t timeout,
bool *outTimedOut)
{
*outTimedOut = false;
if (!isSerialInUse(serial) || mInFlightCommands.empty())
{
return angle::Result::Continue;
......@@ -1542,15 +1524,9 @@ angle::Result RendererVk::finishToSerialOrTimeout(vk::Context *context,
const CommandBatch &batch = mInFlightCommands[batchIndex];
// Wait for it finish
VkResult status = batch.fence.wait(mDevice, kMaxFenceWaitTimeNs);
// If timed out, report it as such.
if (status == VK_TIMEOUT)
{
*outTimedOut = true;
return angle::Result::Continue;
}
VkResult status = batch.fence.get().wait(mDevice, kMaxFenceWaitTimeNs);
// Don't tolerate timeout. If such a large wait time results in timeout, something's wrong.
ANGLE_VK_TRY(context, status);
// Clean up finished batches.
......@@ -1714,6 +1690,26 @@ const vk::Semaphore *RendererVk::getSubmitLastSignaledSemaphore(vk::Context *con
return semaphore;
}
angle::Result RendererVk::getSubmitFence(vk::Context *context,
vk::Shared<vk::Fence> *sharedFenceOut)
{
if (!mSubmitFence.isReferenced())
{
vk::Fence fence;
VkFenceCreateInfo fenceCreateInfo = {};
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceCreateInfo.flags = 0;
ANGLE_VK_TRY(context, fence.init(mDevice, fenceCreateInfo));
mSubmitFence.assign(mDevice, std::move(fence));
}
sharedFenceOut->copy(mDevice, mSubmitFence);
return angle::Result::Continue;
}
angle::Result RendererVk::getTimestamp(vk::Context *context, uint64_t *timestampOut)
{
// The intent of this function is to query the timestamp without stalling the GPU. Currently,
......
......@@ -113,11 +113,6 @@ class RendererVk : angle::NonCopyable
// Wait for completion of batches until (at least) batch with given serial is finished.
angle::Result finishToSerial(vk::Context *context, Serial serial);
// A variant of finishToSerial that can time out. Timeout status returned in outTimedOut.
angle::Result finishToSerialOrTimeout(vk::Context *context,
Serial serial,
uint64_t timeout,
bool *outTimedOut);
uint32_t getQueueFamilyIndex() const { return mCurrentQueueFamilyIndex; }
......@@ -163,6 +158,9 @@ class RendererVk : angle::NonCopyable
// by next submission.
const vk::Semaphore *getSubmitLastSignaledSemaphore(vk::Context *context);
// Get (or allocate) the fence that will be signaled on next submission.
angle::Result getSubmitFence(vk::Context *context, vk::Shared<vk::Fence> *sharedFenceOut);
// This should only be called from ResourceVk.
// TODO(jmadill): Keep in ContextVk to enable threaded rendering.
vk::CommandGraph *getCommandGraph();
......@@ -290,7 +288,7 @@ class RendererVk : angle::NonCopyable
void destroy(VkDevice device);
vk::CommandPool commandPool;
vk::Fence fence;
vk::Shared<vk::Fence> fence;
Serial serial;
};
......@@ -328,6 +326,14 @@ class RendererVk : angle::NonCopyable
// A pool of semaphores used to support the aforementioned mid-frame submissions.
vk::DynamicSemaphorePool mSubmitSemaphorePool;
// mSubmitFence is the fence that's going to be signaled at the next submission. This is used
// to support SyncVk objects, which may outlive the context (as EGLSync objects).
//
// TODO(geofflang): this is in preparation for moving RendererVk functionality to ContextVk, and
// is otherwise unnecessary as the SyncVk objects don't actually outlive the renderer currently.
// http://anglebug.com/2701
vk::Shared<vk::Fence> mSubmitFence;
// See CommandGraph.h for a desription of the Command Graph.
vk::CommandGraph mCommandGraph;
......
......@@ -27,6 +27,8 @@ void FenceSyncVk::onDestroy(RendererVk *renderer)
{
renderer->releaseObject(renderer->getCurrentQueueSerial(), &mEvent);
}
mFence.reset(renderer->getDevice());
}
angle::Result FenceSyncVk::initialize(vk::Context *context)
......@@ -43,8 +45,9 @@ angle::Result FenceSyncVk::initialize(vk::Context *context)
vk::Scoped<vk::Event> event(device);
ANGLE_VK_TRY(context, event.get().init(device, eventCreateInfo));
ANGLE_TRY(renderer->getSubmitFence(context, &mFence));
mEvent = event.release();
mSignalSerial = renderer->getCurrentQueueSerial();
renderer->getCommandGraph()->setFenceSync(mEvent);
return angle::Result::Continue;
......@@ -73,23 +76,22 @@ angle::Result FenceSyncVk::clientWait(vk::Context *context,
return angle::Result::Continue;
}
// If asked to flush, only do so if the queue serial hasn't changed (as otherwise the event
// signal is already flushed). If not asked to flush, do the flush anyway! This is because
// there's no cpu-side wait on the event and there's no fence yet inserted to wait on. We could
// test the event in a loop with a sleep, which can only ever not timeout if another thread
// performs the flush. Instead, we perform the flush for simplicity.
if (hasPendingWork(renderer))
if (flushCommands)
{
ANGLE_TRY(renderer->flush(context));
}
// Wait on the fence that's implicitly inserted at the end of every submission.
bool timedOut = false;
angle::Result result =
renderer->finishToSerialOrTimeout(context, mSignalSerial, timeout, &timedOut);
ANGLE_TRY(result);
// Wait on the fence that's expected to be signaled on the first vkQueueSubmit after
// `initialize` was called.
VkResult status = mFence.get().wait(renderer->getDevice(), timeout);
// Check for errors, but don't consider timeout as such.
if (status != VK_TIMEOUT)
{
ANGLE_VK_TRY(context, status);
}
*outResult = timedOut ? VK_TIMEOUT : VK_SUCCESS;
*outResult = status;
return angle::Result::Continue;
}
......@@ -110,11 +112,6 @@ angle::Result FenceSyncVk::getStatus(vk::Context *context, bool *signaled)
return angle::Result::Continue;
}
bool FenceSyncVk::hasPendingWork(RendererVk *renderer)
{
return mSignalSerial == renderer->getCurrentQueueSerial();
}
SyncVk::SyncVk() : SyncImpl() {}
SyncVk::~SyncVk() {}
......
......@@ -41,15 +41,12 @@ class FenceSyncVk
angle::Result getStatus(vk::Context *context, bool *signaled);
private:
bool hasPendingWork(RendererVk *renderer);
// The vkEvent that's signaled on `init` and can be waited on in `serverWait`, or queried with
// `getStatus`.
vk::Event mEvent;
// The serial in which the event was inserted. Used in `clientWait` to know whether flush is
// necessary, and to be able to wait on the vkFence that's automatically inserted at the end of
// each submissions.
Serial mSignalSerial;
// The vkFence that's signaled once the command buffer including the `init` signal is executed.
// `clientWait` waits on this fence.
vk::Shared<vk::Fence> mFence;
};
class SyncVk final : public SyncImpl
......
......@@ -319,6 +319,7 @@ class RefCounted : angle::NonCopyable
RefCounted(RefCounted &&copy) : mRefCount(copy.mRefCount), mObject(std::move(copy.mObject))
{
ASSERT(this != &copy);
copy.mRefCount = 0;
}
......@@ -384,6 +385,76 @@ class BindingPointer final : angle::NonCopyable
private:
RefCounted<T> *mRefCounted;
};
// Helper class to share ref-counted Vulkan objects. Requires that T have a destroy method
// that takes a VkDevice and returns void.
template <typename T>
class Shared final : angle::NonCopyable
{
public:
Shared() : mRefCounted(nullptr) {}
~Shared() { ASSERT(mRefCounted == nullptr); }
Shared(Shared &&other) { *this = std::move(other); }
Shared &operator=(Shared &&other)
{
ASSERT(this != &other);
mRefCounted = other.mRefCounted;
other.mRefCounted = nullptr;
return *this;
}
void set(VkDevice device, RefCounted<T> *refCounted)
{
if (mRefCounted)
{
mRefCounted->releaseRef();
if (!mRefCounted->isReferenced())
{
mRefCounted->get().destroy(device);
SafeDelete(mRefCounted);
}
}
mRefCounted = refCounted;
if (mRefCounted)
{
mRefCounted->addRef();
}
}
void assign(VkDevice device, T &&newObject)
{
set(device, new RefCounted<T>(std::move(newObject)));
}
void copy(VkDevice device, const Shared<T> &other) { set(device, other.mRefCounted); }
void reset(VkDevice device) { set(device, nullptr); }
bool isReferenced() const
{
// If reference is zero, the object should have been deleted. I.e. if the object is not
// nullptr, it should have a reference.
ASSERT(!mRefCounted || mRefCounted->isReferenced());
return mRefCounted != nullptr;
}
T &get()
{
ASSERT(mRefCounted && mRefCounted->isReferenced());
return mRefCounted->get();
}
const T &get() const
{
ASSERT(mRefCounted && mRefCounted->isReferenced());
return mRefCounted->get();
}
private:
RefCounted<T> *mRefCounted;
};
} // namespace vk
// List of function pointers for used extensions.
......
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