misc

Async downloads in blocking APIs

Conceptually, we want to:

Abstract approach

OpenGL ES 3+ example class

class AsyncReadback {
   GLsizeiptr mSize;
   GLuint mBuffer;
   GLuint mFence;

public:
   AsyncReadback(GLenum srcTarget, GLintptr readOffset, GLsizeiptr size) {
      mSize = size;
      glGenBuffers(1, &mBuffer);
      glBindBuffer(GL_COPY_WRITE_BUFFER, mBuffer);
      glBufferData(GL_COPY_WRITE_BUFFER, size, nullptr, GL_STREAM_READ);
      glCopyBufferSubData(srcTarget, GL_COPY_WRITE_BUFFER, readOffset, 0, size);
      mFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
      // NB: This isn't guaranteed to become signaled unless you glFlush! (or similar)
   }

   ~AsyncReadback() {
      glDeleteSync(mFence);
      glDeleteBuffers(1, &mBuffer);
   }

   const void* TryMap(GLenum target) {
      if (mFence) {
         GLenum status;
         glGetSynciv(mFence, GL_SYNC_STATUS, 1, nullptr, (GLint*)&status);
         if (status != GL_SIGNALED)
            return nullptr;
         glDeleteSync(mFence);
         mFence = 0;
      }
      glBindBuffer(target, mBuffer);
      return (const void*)glMapBufferRange(target, 0, mSize, GL_MAP_READ_BIT);
   }

   void Unmap(GLenum target) {
      glBindBuffer(target, mBuffer);
      glUnmapBuffer(target);
   }
};

Takeaways

Notes

All references to GPU-side and CPU-side can be replaced by the more generic remote/non-mappable and local/mappable. This approach is not non-UMA or discrete-GPU specific. A multithreaded driver/implementation (especially a cross-process one) would benefit from this even on a UMA device.

In WebGL, we can warn when an app messes this up and incurs a pipeline stall. This warning was implemented in Firefox in bug 1425488.