diff --git a/client/renderers/EGL/texture.c b/client/renderers/EGL/texture.c index 653b96d5..8b09b491 100644 --- a/client/renderers/EGL/texture.c +++ b/client/renderers/EGL/texture.c @@ -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); } diff --git a/client/renderers/EGL/texture_buffer.c b/client/renderers/EGL/texture_buffer.c index 9d880aa4..535163fc 100644 --- a/client/renderers/EGL/texture_buffer.c +++ b/client/renderers/EGL/texture_buffer.c @@ -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; } diff --git a/client/renderers/EGL/texture_buffer.h b/client/renderers/EGL/texture_buffer.h index e4df56d0..a689c417 100644 --- a/client/renderers/EGL/texture_buffer.h +++ b/client/renderers/EGL/texture_buffer.h @@ -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); diff --git a/client/renderers/EGL/texture_dmabuf.c b/client/renderers/EGL/texture_dmabuf.c index f17ba887..88568cce 100644 --- a/client/renderers/EGL/texture_dmabuf.c +++ b/client/renderers/EGL/texture_dmabuf.c @@ -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; diff --git a/client/renderers/EGL/texture_framebuffer.c b/client/renderers/EGL/texture_framebuffer.c index f51d9c96..84ce4257 100644 --- a/client/renderers/EGL/texture_framebuffer.c +++ b/client/renderers/EGL/texture_framebuffer.c @@ -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) diff --git a/common/include/common/rects.h b/common/include/common/rects.h index 67ce448e..e409ee54 100644 --- a/common/include/common/rects.h +++ b/common/include/common/rects.h @@ -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); diff --git a/common/src/rects.c b/common/src/rects.c index 5a532106..53eae242 100644 --- a/common/src/rects.c +++ b/common/src/rects.c @@ -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); }