[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

@@ -194,7 +194,8 @@ bool egl_textureUpdateFromDMA(EGL_Texture * this,
};
/* wait for completion */
framebuffer_wait(frame, this->format.dataSize);
if (unlikely(!framebuffer_wait(frame, this->format.dataSize)))
return false;
return this->ops.update(this, &update);
}

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;
}

View File

@@ -35,7 +35,7 @@ typedef struct TextureBuffer
GLuint tex[EGL_TEX_BUFFER_MAX];
EGL_TexBuffer buf[EGL_TEX_BUFFER_MAX];
int bufFree;
GLsync sync;
GLsync sync[EGL_TEX_BUFFER_MAX];
LG_Lock copyLock;
int bufIndex;
int rIndex;
@@ -55,6 +55,8 @@ bool egl_texBufferStreamInit(EGL_Texture ** texture_, EGL_TexType type,
EGLDisplay * display);
bool egl_texBufferStreamSetup(EGL_Texture * texture_,
const EGL_TexSetup * setup);
/* Returns with copyLock held when the current upload buffer is safe to write. */
bool egl_texBufferStreamLock(TextureBuffer * texture);
EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture_);
EGL_TexStatus egl_texBufferStreamGet(EGL_Texture * texture_, GLuint * tex,
EGL_PixelFormat * fmt);

View File

@@ -255,14 +255,26 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture,
});
}
this->lastIndex = (fdImage == &this->images[0]) ? 0 : 1;
GLsync sync = 0;
INTERLOCKED_SECTION(parent->copyLock,
{
if (fdImage->sync)
glDeleteSync(fdImage->sync);
fdImage->sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
fdImage->sync = sync;
this->lastIndex = (fdImage == &this->images[0]) ? 0 : 1;
});
if (unlikely(!sync))
{
DEBUG_GL_ERROR("Failed to create DMABUF sync");
return false;
}
/* The fence is consumed by the shared render context. Submit it from the
* frame context now so the render context can wait without blocking the CPU. */
glFlush();
return true;
}
@@ -277,48 +289,34 @@ static EGL_TexStatus egl_texDMABUFGet(EGL_Texture * texture, GLuint * tex,
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
if (unlikely(this->lastIndex < 0))
return EGL_TEX_STATUS_NOTREADY;
int index = -1;
int texIndex = -1;
GLsync sync = 0;
struct FdImage *cur = &this->images[this->lastIndex];
GLsync sync = 0;
INTERLOCKED_SECTION(parent->copyLock, {
if (cur->sync) {
INTERLOCKED_SECTION(parent->copyLock,
{
index = this->lastIndex;
if (index >= 0)
{
struct FdImage * cur = &this->images[index];
texIndex = cur->texIndex;
sync = cur->sync;
cur->sync = 0;
}
});
if (unlikely(index < 0))
return EGL_TEX_STATUS_NOTREADY;
if (sync)
{
switch (glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 20000000)) //20ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
glDeleteSync(sync);
break;
case GL_TIMEOUT_EXPIRED:
// Put it back for next try
INTERLOCKED_SECTION(parent->copyLock,
{
if (!cur->sync)
cur->sync = sync;
else
glDeleteSync(sync);
});
return EGL_TEX_STATUS_NOTREADY;
case GL_WAIT_FAILED:
case GL_INVALID_VALUE:
glDeleteSync(sync);
DEBUG_GL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
/* Keep the cross-context dependency on the GPU timeline. Subsequent
* sampling waits for the import without delaying render submission. */
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(sync);
}
*tex = parent->tex[cur->texIndex];
*tex = parent->tex[texIndex];
if (fmt)
*fmt = this->pixFmt;

View File

@@ -85,15 +85,17 @@ static bool egl_texFBUpdate(EGL_Texture * texture, const EGL_TexUpdate * update)
DEBUG_ASSERT(update->type == EGL_TEXTYPE_FRAMEBUFFER);
LG_LOCK(parent->copyLock);
if (!egl_texBufferStreamLock(parent))
return false;
struct TexDamage * damage = this->damage + parent->bufIndex;
bool damageAll = !update->rects || update->rectCount == 0 || damage->count < 0 ||
damage->count + update->rectCount > KVMFR_MAX_DAMAGE_RECTS;
bool complete;
if (damageAll)
{
framebuffer_read(
complete = framebuffer_read(
update->frame,
parent->buf[parent->bufIndex].map,
texture->format.pitch,
@@ -122,7 +124,7 @@ static bool egl_texFBUpdate(EGL_Texture * texture, const EGL_TexUpdate * update)
scaledDamageRects[i] = rect;
}
rectsFramebufferToBuffer(
complete = rectsFramebufferToBuffer(
scaledDamageRects,
damage->count,
texture->format.bpp,
@@ -135,7 +137,7 @@ static bool egl_texFBUpdate(EGL_Texture * texture, const EGL_TexUpdate * update)
}
else
{
rectsFramebufferToBuffer(
complete = rectsFramebufferToBuffer(
damage->rects,
damage->count,
texture->format.bpp,
@@ -148,6 +150,16 @@ static bool egl_texFBUpdate(EGL_Texture * texture, const EGL_TexUpdate * update)
}
}
if (unlikely(!complete))
{
/* The mapped PBO may contain a partial copy. Force a full refresh if the
* caller recovers, and never expose this slot to the render context. */
damage->count = -1;
parent->buf[parent->bufIndex].updated = false;
LG_UNLOCK(parent->copyLock);
return false;
}
parent->buf[parent->bufIndex].updated = true;
for (int i = 0; i < EGL_TEX_BUFFER_MAX; ++i)

View File

@@ -35,7 +35,7 @@ void rectsBufferToFramebuffer(FrameDamageRect * rects, int count, int bpp,
FrameBuffer * frame, int dstPitch, int height,
const uint8_t * src, int srcPitch);
void rectsFramebufferToBuffer(FrameDamageRect * rects, int count, int bpp,
bool rectsFramebufferToBuffer(FrameDamageRect * rects, int count, int bpp,
uint8_t * dst, int dstPitch, int height,
const FrameBuffer * frame, int srcPitch);

View File

@@ -85,10 +85,10 @@ static int cornerCompare(const void * a_, const void * b_)
return 0;
}
inline static void rectsBufferCopy(FrameDamageRect * rects, int count, int bpp,
inline static bool rectsBufferCopy(FrameDamageRect * rects, int count, int bpp,
uint8_t * dst, int dstStride, int height,
const uint8_t * src, int srcStride, void * opaque,
void (*rowCopyStart)(int y, void * opaque),
bool (*rowCopyStart)(int y, void * opaque),
void (*rowCopyFinish)(int y, void * opaque))
{
const int cornerCount = 4 * count;
@@ -138,8 +138,8 @@ inline static void rectsBufferCopy(FrameDamageRect * rects, int count, int bpp,
change[changes++] = (struct Edge) { .x = x, .delta = delta };
}
if (rowCopyStart)
rowCopyStart(y, opaque);
if (rowCopyStart && !rowCopyStart(y, opaque))
return false;
struct Edge * active = active_[activeRow];
int x1 = 0;
@@ -191,6 +191,8 @@ inline static void rectsBufferCopy(FrameDamageRect * rects, int count, int bpp,
rs = re;
activeRow ^= 1;
}
return true;
}
struct ToFramebufferData
@@ -221,18 +223,18 @@ struct FromFramebufferData
int pitch;
};
static void fbRowStart(int y, void * opaque)
static bool fbRowStart(int y, void * opaque)
{
struct FromFramebufferData * data = opaque;
framebuffer_wait(data->frame, y * data->pitch);
return framebuffer_wait(data->frame, y * data->pitch);
}
void rectsFramebufferToBuffer(FrameDamageRect * rects, int count, int bpp,
bool rectsFramebufferToBuffer(FrameDamageRect * rects, int count, int bpp,
uint8_t * dst, int dstPitch, int height,
const FrameBuffer * frame, int srcPitch)
{
struct FromFramebufferData data = { .frame = frame, .pitch = srcPitch };
rectsBufferCopy(rects, count, bpp, dst, dstPitch, height,
return rectsBufferCopy(rects, count, bpp, dst, dstPitch, height,
framebuffer_get_buffer(frame), srcPitch, &data, fbRowStart, NULL);
}