mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-07-20 14:22:00 +00:00
[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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user