[client] egl: remove blocking waits from frame submission

Track PBO fences per upload slot and wait only when a slot is reused,
allowing rendering to proceed without a CPU stall. Use GPU-side waits
for cross-context DMABUF synchronization.

Also propagate KVMFR write-completion failures so incomplete
shared-memory frames are never submitted, while retaining incremental
damage-copy waits to avoid added latency.
This commit is contained in:
Geoffrey McRae
2026-07-17 22:02:05 +10:00
parent e3616ed4b9
commit b2c40a80af
7 changed files with 161 additions and 101 deletions

View File

@@ -37,10 +37,13 @@ static void egl_texBuffer_cleanup(TextureBuffer * this)
if (this->tex[0])
glDeleteTextures(this->texCount, this->tex);
if (this->sync)
for (int i = 0; i < EGL_TEX_BUFFER_MAX; ++i)
{
glDeleteSync(this->sync);
this->sync = 0;
if (!this->sync[i])
continue;
glDeleteSync(this->sync[i]);
this->sync[i] = 0;
}
}
@@ -101,8 +104,12 @@ bool egl_texBufferSetup(EGL_Texture * texture, const EGL_TexSetup * setup)
}
glBindTexture(GL_TEXTURE_2D, 0);
this->bufIndex = 0;
this->rIndex = -1;
for (int i = 0; i < this->texCount; ++i)
this->buf[i].updated = false;
return true;
}
@@ -179,13 +186,50 @@ bool egl_texBufferStreamSetup(EGL_Texture * texture, const EGL_TexSetup * setup)
return egl_texUtilGenBuffers(&texture->format, this->buf, this->texCount);
}
bool egl_texBufferStreamLock(TextureBuffer * this)
{
for (;;)
{
LG_LOCK(this->copyLock);
GLsync sync = this->sync[this->bufIndex];
if (!sync)
return true;
/* The slot cannot be submitted again until this function marks it
* updated, so ownership of its fence can be transferred while unlocked. */
this->sync[this->bufIndex] = 0;
LG_UNLOCK(this->copyLock);
GLenum result = glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(sync);
switch(result)
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
break;
case GL_TIMEOUT_EXPIRED:
DEBUG_ERROR("Upload buffer sync unexpectedly timed out");
return false;
case GL_WAIT_FAILED:
case GL_INVALID_VALUE:
DEBUG_GL_ERROR("glClientWaitSync failed while reusing upload buffer");
return false;
}
}
}
static bool egl_texBufferStreamUpdate(EGL_Texture * texture,
const EGL_TexUpdate * update)
{
TextureBuffer * this = UPCAST(TextureBuffer, texture);
DEBUG_ASSERT(update->type == EGL_TEXTYPE_BUFFER);
LG_LOCK(this->copyLock);
if (!egl_texBufferStreamLock(this))
return false;
uint8_t * dst = this->buf[this->bufIndex].map +
texture->format.pitch * update->y +
@@ -224,39 +268,61 @@ EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture)
LG_LOCK(this->copyLock);
GLuint tex = this->tex[this->bufIndex];
EGL_TexBuffer * buffer = &this->buf[this->bufIndex];
const int index = this->bufIndex;
GLuint tex = this->tex[index];
EGL_TexBuffer * buffer = &this->buf[index];
if (buffer->updated && this->sync == 0)
if (!buffer->updated)
{
this->rIndex = this->bufIndex;
if (++this->bufIndex == this->texCount)
this->bufIndex = 0;
LG_UNLOCK(this->copyLock);
return EGL_TEX_STATUS_OK;
}
LG_UNLOCK(this->copyLock);
DEBUG_ASSERT(!this->sync[index]);
if (buffer->updated)
this->rIndex = index;
if (++this->bufIndex == this->texCount)
this->bufIndex = 0;
buffer->updated = false;
/* With more than one slot the producer can fill the next PBO while this
* upload is queued. A single-slot texture must remain locked until its
* fence exists so the mapped buffer cannot be overwritten concurrently. */
const bool keepLocked = this->texCount == 1;
if (!keepLocked)
LG_UNLOCK(this->copyLock);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
glBindTexture(GL_TEXTURE_2D, tex);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->format.stride);
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0,
texture->format.width,
texture->format.height,
texture->format.format,
texture->format.dataType,
(const void *)0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
this->sync[index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (unlikely(!this->sync[index]))
{
buffer->updated = false;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
glBindTexture(GL_TEXTURE_2D, tex);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->format.stride);
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0,
texture->format.width,
texture->format.height,
texture->format.format,
texture->format.dataType,
(const void *)0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
this->sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
DEBUG_GL_ERROR("Failed to create upload buffer sync");
glFinish();
if (keepLocked)
LG_UNLOCK(this->copyLock);
return EGL_TEX_STATUS_ERROR;
}
/* A different shared context may wait when this PBO is reused. Flushing
* here makes the fence visible without making the render thread wait. */
glFlush();
if (keepLocked)
LG_UNLOCK(this->copyLock);
return EGL_TEX_STATUS_OK;
}
@@ -268,29 +334,8 @@ EGL_TexStatus egl_texBufferStreamGet(EGL_Texture * texture, GLuint * tex,
if (this->rIndex == -1)
return EGL_TEX_STATUS_NOTREADY;
if (this->sync)
{
switch(glClientWaitSync(
this->sync, GL_SYNC_FLUSH_COMMANDS_BIT, 40000000)) //40ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
glDeleteSync(this->sync);
this->sync = 0;
break;
case GL_TIMEOUT_EXPIRED:
return EGL_TEX_STATUS_NOTREADY;
case GL_WAIT_FAILED:
case GL_INVALID_VALUE:
glDeleteSync(this->sync);
this->sync = 0;
DEBUG_GL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
}
/* Upload and sampling are ordered in the same context. Waiting here would
* only stall submission of the draw and delay presentation. */
*tex = this->tex[this->rIndex];
return EGL_TEX_STATUS_OK;
}