Make finish flush procs work with GL.
This will be required for async readback support.
This is implemented using sync objects when available and otherwise
calls glFinish.
Relaxes the unit test requirement that providing a callback with no
work to flush always calls the proc immediately.
Bug: skia:8962
Change-Id: Ieefcab6dccc3924e50260343f01904e7303bb12b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/212198
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 98d6f21..112e2aa 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -270,6 +270,14 @@
////////////////////////////////////////////////////////////////////////////////
+void GrContext::checkAsyncWorkCompletion() {
+ if (fGpu) {
+ fGpu->checkFinishProcs();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
void GrContext::storeVkPipelineCacheData() {
if (fGpu) {
fGpu->storeVkPipelineCacheData();
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index a64a74e..5b90f05 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -319,6 +319,8 @@
virtual void insertSemaphore(sk_sp<GrSemaphore> semaphore) = 0;
virtual void waitSemaphore(sk_sp<GrSemaphore> semaphore) = 0;
+ virtual void checkFinishProcs() = 0;
+
/**
* Put this texture in a safe and known state for use across multiple GrContexts. Depending on
* the backend, this may return a GrSemaphore. If so, other contexts should wait on that
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 0a044b6..ec2f370 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -4278,15 +4278,35 @@
const GrFlushInfo& info) {
// If we inserted semaphores during the flush, we need to call GLFlush.
bool insertedSemaphore = info.fNumSemaphores > 0 && this->caps()->fenceSyncSupport();
- if (insertedSemaphore) {
- GL_CALL(Flush());
- }
- if (info.fFlags & kSyncCpu_GrFlushFlag) {
+ // We call finish if the client told us to sync or if we have a finished proc but don't support
+ // GLsync objects.
+ bool finish = (info.fFlags & kSyncCpu_GrFlushFlag) ||
+ (info.fFinishedProc && !this->caps()->fenceSyncSupport());
+ if (finish) {
GL_CALL(Finish());
- }
- // TODO: We should have GL actually wait until the GPU has finished work on the GPU.
- if (info.fFinishedProc) {
- info.fFinishedProc(info.fFinishedContext);
+ // After a finish everything previously sent to GL is done.
+ for (const auto& cb : fFinishCallbacks) {
+ cb.fCallback(cb.fContext);
+ this->deleteSync(cb.fSync);
+ }
+ fFinishCallbacks.clear();
+ if (info.fFinishedProc) {
+ info.fFinishedProc(info.fFinishedContext);
+ }
+ } else {
+ if (info.fFinishedProc) {
+ FinishCallback callback;
+ callback.fCallback = info.fFinishedProc;
+ callback.fContext = info.fFinishedContext;
+ callback.fSync = (GrGLsync)this->insertFence();
+ fFinishCallbacks.push_back(callback);
+ GL_CALL(Flush());
+ } else if (insertedSemaphore) {
+ // Must call flush after semaphores in case they are waited on another GL context.
+ GL_CALL(Flush());
+ }
+ // See if any previously inserted finish procs are good to go.
+ this->checkFinishProcs();
}
}
@@ -4308,10 +4328,15 @@
return (GrFence)sync;
}
-bool GrGLGpu::waitFence(GrFence fence, uint64_t timeout) {
+bool GrGLGpu::waitSync(GrGLsync sync, uint64_t timeout, bool flush) {
+ GrGLbitfield flags = flush ? GR_GL_SYNC_FLUSH_COMMANDS_BIT : 0;
GrGLenum result;
- GL_CALL_RET(result, ClientWaitSync((GrGLsync)fence, GR_GL_SYNC_FLUSH_COMMANDS_BIT, timeout));
- return (GR_GL_CONDITION_SATISFIED == result);
+ GL_CALL_RET(result, ClientWaitSync(sync, flags, timeout));
+ return (GR_GL_CONDITION_SATISFIED == result || GR_GL_ALREADY_SIGNALED == result);
+}
+
+bool GrGLGpu::waitFence(GrFence fence, uint64_t timeout) {
+ return this->waitSync((GrGLsync)fence, timeout, /* flush = */ true);
}
void GrGLGpu::deleteFence(GrFence fence) const {
@@ -4344,6 +4369,16 @@
GL_CALL(WaitSync(glSem->sync(), 0, GR_GL_TIMEOUT_IGNORED));
}
+void GrGLGpu::checkFinishProcs() {
+ // Bail after the first unfinished sync since we expect they signal in the order inserted.
+ while (!fFinishCallbacks.empty() && this->waitSync(fFinishCallbacks.front().fSync,
+ /* timeout = */ 0, /* flush = */ false)) {
+ fFinishCallbacks.front().fCallback(fFinishCallbacks.front().fContext);
+ this->deleteSync(fFinishCallbacks.front().fSync);
+ fFinishCallbacks.pop_front();
+ }
+}
+
void GrGLGpu::deleteSync(GrGLsync sync) const {
GL_CALL(DeleteSync(sync));
}
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 68fcc7e..b2821e4 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -8,6 +8,7 @@
#ifndef GrGLGpu_DEFINED
#define GrGLGpu_DEFINED
+#include <list>
#include "include/core/SkTypes.h"
#include "include/private/SkTArray.h"
#include "src/core/SkLRUCache.h"
@@ -167,6 +168,8 @@
void insertSemaphore(sk_sp<GrSemaphore> semaphore) override;
void waitSemaphore(sk_sp<GrSemaphore> semaphore) override;
+ void checkFinishProcs() override;
+
sk_sp<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override;
void deleteSync(GrGLsync) const;
@@ -295,6 +298,8 @@
void onFinishFlush(GrSurfaceProxy*, SkSurface::BackendSurfaceAccess access,
const GrFlushInfo&) override;
+ bool waitSync(GrGLsync, uint64_t timeout, bool flush);
+
bool copySurfaceAsDraw(GrSurface* dst, GrSurfaceOrigin dstOrigin,
GrSurface* src, GrSurfaceOrigin srcOrigin,
const SkIRect& srcRect, const SkIPoint& dstPoint);
@@ -665,6 +670,12 @@
std::unique_ptr<GrGLGpuRTCommandBuffer> fCachedRTCommandBuffer;
std::unique_ptr<GrGLGpuTextureCommandBuffer> fCachedTexCommandBuffer;
+ struct FinishCallback {
+ GrGpuFinishedProc fCallback;
+ GrGpuFinishedContext fContext;
+ GrGLsync fSync;
+ };
+ std::list<FinishCallback> fFinishCallbacks;
friend class GrGLPathRendering; // For accessing setTextureUnit.
typedef GrGpu INHERITED;
diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h
index 15074a1..57d8f7a 100644
--- a/src/gpu/mock/GrMockGpu.h
+++ b/src/gpu/mock/GrMockGpu.h
@@ -47,6 +47,8 @@
void submit(GrGpuCommandBuffer* buffer) override;
+ void checkFinishProcs() override {}
+
private:
GrMockGpu(GrContext* context, const GrMockOptions&, const GrContextOptions&);
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index cb723a5..d16ae2d 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -116,6 +116,8 @@
GrWrapOwnership ownership) override { return nullptr; }
void insertSemaphore(sk_sp<GrSemaphore> semaphore) override {}
void waitSemaphore(sk_sp<GrSemaphore> semaphore) override {}
+ // We currently call finish procs immediately in onFinishFlush().
+ void checkFinishProcs() override {}
sk_sp<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override { return nullptr; }
// When the Metal backend actually uses indirect command buffers, this function will actually do
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index 2cc3725..2321f9a 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -164,6 +164,8 @@
// command buffer to the gpu.
void addDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler> drawable);
+ void checkFinishProcs() override { fResourceProvider.checkCommandBuffers(); }
+
sk_sp<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override;
void copyBuffer(GrVkBuffer* srcBuffer, GrVkBuffer* dstBuffer, VkDeviceSize srcOffset,