diff --git a/client/include/interface/renderer.h b/client/include/interface/renderer.h index 7b95741e..94efd4ea 100644 --- a/client/include/interface/renderer.h +++ b/client/include/interface/renderer.h @@ -258,10 +258,18 @@ typedef struct LG_RendererOps /* called when there is a new frame. frameToken must remain attached to the * update if the renderer coalesces it, and must be reported by render only - * when that update is consumed. Context: frameThread */ + * when that update is consumed. A non-NULL releaseFn transfers ownership to + * the renderer on success and must be called exactly once when the imported + * frame can no longer be sampled. Context: frameThread */ bool (*onFrame)(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFD, const FrameDamageRect * damage, int damageCount, - LG_RendererFrameToken frameToken); + LG_RendererFrameToken frameToken, + LG_FrameReleaseFn releaseFn, + void * releaseOpaque, uint64_t releaseHandle); + + /* optional frame-thread pump for asynchronous imports while no frame is + * available from the transport */ + void (*onFramePoll)(LG_Renderer * renderer); /* called when the rederer is to startup * Context: renderThread */ diff --git a/client/include/interface/transport.h b/client/include/interface/transport.h index db71bc29..ea631ea6 100644 --- a/client/include/interface/transport.h +++ b/client/include/interface/transport.h @@ -145,6 +145,12 @@ typedef struct LG_TransportFrame int dmaFD; const FrameDamageRect * damageRects; uint32_t damageRectsCount; + + /* A transport may keep the frame payload alive asynchronously after + * onFrame returns. The callback is idempotent and releases that ownership. */ + LG_FrameReleaseFn releaseFn; + void * releaseOpaque; + uint64_t releaseHandle; } LG_TransportFrame; diff --git a/client/renderers/EGL/desktop.c b/client/renderers/EGL/desktop.c index 1c4a2e64..b497062c 100644 --- a/client/renderers/EGL/desktop.c +++ b/client/renderers/EGL/desktop.c @@ -72,7 +72,7 @@ struct EGL_Desktop EGLDisplay * display; EGL_Texture * texture; - struct DesktopShader dmaShader, shader; + struct DesktopShader shader; EGL_DesktopRects * mesh; GLfloat matrix[6]; @@ -194,17 +194,6 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display, return false; } - if (useDMA) - if (!egl_initDesktopShader( - &desktop->dmaShader, - b_shader_desktop_vert , b_shader_desktop_vert_size, - b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size, - true)) - { - DEBUG_ERROR("Failed to initialize the desktop DMA shader"); - return false; - } - app_registerKeybind(KEY_N, toggleNV, desktop, "Toggle night vision mode"); @@ -264,7 +253,6 @@ void egl_desktopFree(EGL_Desktop ** desktop) egl_textureFree (&(*desktop)->texture ); egl_textureFree (&(*desktop)->spiceTexture ); egl_shaderFree (&(*desktop)->shader .shader); - egl_shaderFree (&(*desktop)->dmaShader.shader); egl_desktopRectsFree(&(*desktop)->mesh ); egl_postProcessFree(&(*desktop)->pp); @@ -394,12 +382,14 @@ bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format) bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, LG_RendererFrameToken frameToken, int dmaFd, const FrameDamageRect * damageRects, int damageRectsCount, - uint64_t * waitTimeNs) + uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn, + void * releaseOpaque, uint64_t releaseHandle) { if (likely(desktop->useDMA && dmaFd >= 0)) { if (likely(egl_textureUpdateFromDMA( - desktop->texture, frame, frameToken, dmaFd, waitTimeNs))) + desktop->texture, frame, frameToken, dmaFd, waitTimeNs, + releaseFn, releaseOpaque, releaseHandle))) { atomic_store(&desktop->processFrame, true); return true; @@ -442,11 +432,26 @@ bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, */ if (likely(egl_textureUpdateFromFrame(desktop->texture, frame, frameToken, damageRects, damageRectsCount, waitTimeNs))) + { + if (releaseFn) + releaseFn(releaseOpaque, releaseHandle); return true; + } return false; } +void egl_desktopRestart(EGL_Desktop * desktop) +{ + egl_textureReset(desktop->texture); + atomic_store(&desktop->processFrame, false); +} + +void egl_desktopPoll(EGL_Desktop * desktop) +{ + egl_texturePoll(desktop->texture); +} + void egl_desktopResize(EGL_Desktop * desktop, int width, int height) { atomic_store(&desktop->processFrame, true); @@ -464,21 +469,21 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth, { EGL_Texture * tex; int width, height; - bool dma; + + /* This also retires completed snapshots while the SPICE display is shown. */ + egl_texturePoll(desktop->texture); if (unlikely(desktop->useSpice)) { tex = desktop->spiceTexture; width = desktop->spiceWidth; height = desktop->spiceHeight; - dma = false; } else { tex = desktop->texture; width = desktop->width; height = desktop->height; - dma = desktop->useDMA; } if (unlikely(outputWidth == 0 || outputHeight == 0)) @@ -535,7 +540,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth, const uint64_t effectsStart = nanotime(); const bool postProcessed = egl_postProcessRun( desktop->pp, tex, desktop->mesh, - width, height, outputWidth, outputHeight, dma, + width, height, outputWidth, outputHeight, false, hdr && desktop->hdrPQ, (float)hdrPeak); *effectsTime = nanotime() - effectsStart; @@ -574,6 +579,8 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth, { if (bindStatus != EGL_TEX_STATUS_NOTREADY) DEBUG_ERROR("Failed to bind the desktop texture"); + if (!desktop->useSpice && desktop->useDMA && processFrame) + egl_textureMarkUsed(desktop->texture); return false; } *renderedFrameToken = tex->frameToken; @@ -601,9 +608,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth, scaleAlgo = desktop->scaleAlgo; } - const struct DesktopShader * shader = - desktop->useDMA && texture == desktop->texture ? - &desktop->dmaShader : &desktop->shader; + const struct DesktopShader * shader = &desktop->shader; /* PQ is normalized to 10000 nits, while scRGB defines 1.0 as 80 nits. * Convert either encoding into values relative to the selected SDR peak. */ @@ -628,6 +633,9 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth, desktop->linearComposition); egl_shaderUse(shader->shader); egl_desktopRectsRender(desktop->mesh); + if (!desktop->useSpice && desktop->useDMA && + (processFrame || texture == tex)) + egl_textureMarkUsed(desktop->texture); return true; } diff --git a/client/renderers/EGL/desktop.h b/client/renderers/EGL/desktop.h index 56944431..e2c9a86f 100644 --- a/client/renderers/EGL/desktop.h +++ b/client/renderers/EGL/desktop.h @@ -51,7 +51,10 @@ bool egl_desktopSetup (EGL_Desktop * desktop, const LG_RendererFormat format); bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, LG_RendererFrameToken frameToken, int dmaFd, const FrameDamageRect * damageRects, int damageRectsCount, - uint64_t * waitTimeNs); + uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn, + void * releaseOpaque, uint64_t releaseHandle); +void egl_desktopRestart(EGL_Desktop * desktop); +void egl_desktopPoll(EGL_Desktop * desktop); void egl_desktopResize(EGL_Desktop * desktop, int width, int height); bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth, unsigned int outputHeight, const float x, const float y, diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index 2d8e7252..46fb47d4 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -374,8 +374,13 @@ static void egl_onRestart(LG_Renderer * renderer) { struct Inst * this = UPCAST(struct Inst, renderer); - eglDestroyContext(this->display, this->frameContext); - this->frameContext = NULL; + if (this->frameContext) + { + egl_stateCheckShared(); + egl_desktopRestart(this->desktop); + eglDestroyContext(this->display, this->frameContext); + this->frameContext = NULL; + } INTERLOCKED_SECTION(this->desktopDamageLock, { this->desktopDamage[this->desktopDamageIdx].frameToken = @@ -746,7 +751,8 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFd, const FrameDamageRect * damageRects, int damageRectsCount, - LG_RendererFrameToken frameToken) + LG_RendererFrameToken frameToken, LG_FrameReleaseFn releaseFn, + void * releaseOpaque, uint64_t releaseHandle) { struct Inst * this = UPCAST(struct Inst, renderer); egl_stateCheckShared(); @@ -755,7 +761,8 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, uint64_t waitTimeNs = 0; if (unlikely(!egl_desktopUpdate( this->desktop, frame, frameToken, dmaFd, - damageRects, damageRectsCount, &waitTimeNs))) + damageRects, damageRectsCount, &waitTimeNs, + releaseFn, releaseOpaque, releaseHandle))) { DEBUG_INFO("Failed to to update the desktop"); return false; @@ -790,6 +797,16 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, return true; } +static void egl_onFramePoll(LG_Renderer * renderer) +{ + struct Inst * this = UPCAST(struct Inst, renderer); + if (!this->frameContext) + return; + + egl_stateCheckShared(); + egl_desktopPoll(this->desktop); +} + static void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar * message, const void * userParam) @@ -1835,6 +1852,7 @@ struct LG_RendererOps LGR_EGL = .onMouseEvent = egl_onMouseEvent, .onFrameFormat = egl_onFrameFormat, .onFrame = egl_onFrame, + .onFramePoll = egl_onFramePoll, .renderStartup = egl_renderStartup, .render = egl_render, .capture = egl_capture, diff --git a/client/renderers/EGL/texture.c b/client/renderers/EGL/texture.c index d97bdb9c..35a0575e 100644 --- a/client/renderers/EGL/texture.c +++ b/client/renderers/EGL/texture.c @@ -185,19 +185,23 @@ bool egl_textureUpdateFromFrame(EGL_Texture * this, bool egl_textureUpdateFromDMA(EGL_Texture * this, const FrameBuffer * frame, LG_RendererFrameToken frameToken, - const int dmaFd, uint64_t * waitTimeNs) + const int dmaFd, uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn, + void * releaseOpaque, uint64_t releaseHandle) { const struct EGL_TexUpdate update = { - .type = EGL_TEXTYPE_DMABUF, - .frameToken = frameToken, - .x = 0, - .y = 0, - .width = this->format.width, - .height = this->format.height, - .pitch = this->format.pitch, - .stride = this->format.stride, - .dmaFD = dmaFd + .type = EGL_TEXTYPE_DMABUF, + .frameToken = frameToken, + .x = 0, + .y = 0, + .width = this->format.width, + .height = this->format.height, + .pitch = this->format.pitch, + .stride = this->format.stride, + .dmaFD = dmaFd, + .releaseFn = releaseFn, + .releaseOpaque = releaseOpaque, + .releaseHandle = releaseHandle, }; /* wait for completion */ @@ -208,6 +212,24 @@ bool egl_textureUpdateFromDMA(EGL_Texture * this, return this->ops.update(this, &update); } +void egl_textureReset(EGL_Texture * this) +{ + if (this->ops.reset) + this->ops.reset(this); +} + +void egl_texturePoll(EGL_Texture * this) +{ + if (this->ops.poll) + this->ops.poll(this); +} + +void egl_textureMarkUsed(EGL_Texture * this) +{ + if (this->ops.markUsed) + this->ops.markUsed(this); +} + enum EGL_TexStatus egl_textureProcess(EGL_Texture * this, LG_RendererFrameToken frameTokenLimit, LG_RendererFrameToken * consumedFrameToken) diff --git a/client/renderers/EGL/texture.h b/client/renderers/EGL/texture.h index 02b35a89..1666fced 100644 --- a/client/renderers/EGL/texture.h +++ b/client/renderers/EGL/texture.h @@ -41,9 +41,12 @@ typedef struct EGL_Model EGL_Model; typedef struct EGL_TexUpdate { /* the type of this update */ - EGL_TexType type; - LG_RendererFrameToken frameToken; - uint64_t * waitTimeNs; + EGL_TexType type; + LG_RendererFrameToken frameToken; + uint64_t * waitTimeNs; + LG_FrameReleaseFn releaseFn; + void * releaseOpaque; + uint64_t releaseHandle; int x, y, width, height; @@ -101,6 +104,15 @@ typedef struct EGL_TextureOps /* bind the texture for use */ enum EGL_TexStatus (*bind)(EGL_Texture * texture, GLuint unit); + + /* release queued updates and reset stream state */ + void (*reset)(EGL_Texture * texture); + + /* poll asynchronous work without changing the selected frame */ + void (*poll)(EGL_Texture * texture); + + /* record that the selected texture has been sampled */ + void (*markUsed)(EGL_Texture * texture); } EGL_TextureOps; @@ -135,7 +147,12 @@ bool egl_textureUpdateFromFrame(EGL_Texture * texture, bool egl_textureUpdateFromDMA(EGL_Texture * texture, const FrameBuffer * frame, LG_RendererFrameToken frameToken, - const int dmaFd, uint64_t * waitTimeNs); + const int dmaFd, uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn, + void * releaseOpaque, uint64_t releaseHandle); + +void egl_textureReset(EGL_Texture * texture); +void egl_texturePoll(EGL_Texture * texture); +void egl_textureMarkUsed(EGL_Texture * texture); enum EGL_TexStatus egl_textureProcess(EGL_Texture * texture, LG_RendererFrameToken frameTokenLimit, diff --git a/client/renderers/EGL/texture_dmabuf.c b/client/renderers/EGL/texture_dmabuf.c index e380b668..e3c33871 100644 --- a/client/renderers/EGL/texture_dmabuf.c +++ b/client/renderers/EGL/texture_dmabuf.c @@ -20,6 +20,8 @@ #include "texture.h" #include "texture_buffer.h" +#include "model.h" +#include "shader.h" #include "state.h" #include "util.h" @@ -27,13 +29,30 @@ #include "egl_dynprocs.h" #include "egldebug.h" +#include + +#include "hdr_compose.vert.h" +#include "downscale_linear.frag.h" + +#define EGL_DMABUF_SNAPSHOT_COUNT (LGMP_Q_FRAME_BUFFER_LEN + 1) + struct FdImage { - int fd; - EGLImage image; - GLsync sync; - int texIndex; - LG_RendererFrameToken frameToken; + int fd; + EGLImage image; + GLuint texture; +}; + +struct Snapshot +{ + GLuint texture; + GLuint framebuffer; + GLsync copySync; + GLsync useSync; + LG_RendererFrameToken frameToken; + LG_FrameReleaseFn releaseFn; + void * releaseOpaque; + uint64_t releaseHandle; }; typedef struct TexDMABUF @@ -42,17 +61,29 @@ typedef struct TexDMABUF EGLDisplay display; - struct FdImage images[EGL_TEX_BUFFER_MAX]; - int lastIndex; - int renderIndex; + struct FdImage images[EGL_TEX_BUFFER_MAX]; + struct Snapshot snapshots[EGL_DMABUF_SNAPSHOT_COUNT]; + int renderIndex; + + EGL_Shader * copyShader; + EGL_Model * copyModel; + GLuint copySampler; EGL_PixelFormat pixFmt; unsigned fourcc; unsigned width; - GLuint format; + GLenum intFormat; + GLenum dataType; } TexDMABUF; +struct SnapshotRelease +{ + LG_FrameReleaseFn fn; + void * opaque; + uint64_t handle; +}; + EGL_TextureOps EGL_TextureDMABUF; static bool initDone = false; @@ -61,11 +92,65 @@ static bool hasImportModifiers = true; // internal functions +static void egl_texDMABUFFree(EGL_Texture * texture); +static void egl_texDMABUFPoll(EGL_Texture * texture); + +static void snapshotRelease(struct Snapshot * snapshot, + struct SnapshotRelease * release) +{ + release->fn = snapshot->releaseFn; + release->opaque = snapshot->releaseOpaque; + release->handle = snapshot->releaseHandle; + + snapshot->releaseFn = NULL; + snapshot->releaseOpaque = NULL; + snapshot->releaseHandle = 0; +} + +static void snapshotInvokeReleases(struct SnapshotRelease * releases, + int count) +{ + for (int i = 0; i < count; ++i) + releases[i].fn(releases[i].opaque, releases[i].handle); +} + +static void waitSync(GLsync * sync) +{ + if (!*sync) + return; + + const GLenum result = glClientWaitSync(*sync, 0, GL_TIMEOUT_IGNORED); + if (result == GL_WAIT_FAILED) + DEBUG_FATAL("Failed to wait for DMABUF snapshot fence"); + glDeleteSync(*sync); + *sync = 0; +} + static void egl_texDMABUFCleanup(EGL_Texture * texture) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexDMABUF * this = UPCAST(TexDMABUF , parent); + struct SnapshotRelease releases[EGL_DMABUF_SNAPSHOT_COUNT]; + int releaseCount = 0; + + LG_LOCK(parent->copyLock); + + for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i) + { + struct Snapshot * snapshot = &this->snapshots[i]; + waitSync(&snapshot->copySync); + waitSync(&snapshot->useSync); + if (snapshot->releaseFn) + snapshotRelease(snapshot, &releases[releaseCount++]); + + if (snapshot->framebuffer) + glDeleteFramebuffers(1, &snapshot->framebuffer); + if (snapshot->texture) + glDeleteTextures(1, &snapshot->texture); + memset(snapshot, 0, sizeof(*snapshot)); + } + for(int i = 0; i < ARRAY_LENGTH(this->images); ++i) { if (this->images[i].image != EGL_NO_IMAGE) @@ -73,28 +158,19 @@ static void egl_texDMABUFCleanup(EGL_Texture * texture) g_egl_dynProcs.eglDestroyImage(this->display, this->images[i].image); this->images[i].image = EGL_NO_IMAGE; } - if (this->images[i].sync) - { - glDeleteSync(this->images[i].sync); - this->images[i].sync = 0; - } - this->images[i].fd = -1; - this->images[i].texIndex = -1; - this->images[i].frameToken = LG_RENDERER_FRAME_TOKEN_NONE; + if (this->images[i].texture) + glDeleteTextures(1, &this->images[i].texture); + this->images[i].fd = -1; + this->images[i].texture = 0; } - this->lastIndex = -1; - this->renderIndex = -1; - parent->rIndex = -1; - texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE; + this->renderIndex = -1; + parent->rIndex = -1; + texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE; + egl_stateInvalidateShared(); - egl_texUtilFreeBuffers(parent->buf, parent->texCount); - - if (parent->tex[0]) - { - glDeleteTextures(parent->texCount, parent->tex); - egl_stateInvalidateShared(); - } + LG_UNLOCK(parent->copyLock); + snapshotInvokeReleases(releases, releaseCount); } // dmabuf functions @@ -107,13 +183,9 @@ static bool egl_texDMABUFInit(EGL_Texture ** texture, EGL_TexType type, for(int i = 0; i < ARRAY_LENGTH(this->images); ++i) { - this->images[i].fd = -1; - this->images[i].image = EGL_NO_IMAGE; - this->images[i].sync = 0; - this->images[i].texIndex = -1; - this->images[i].frameToken = LG_RENDERER_FRAME_TOKEN_NONE; + this->images[i].fd = -1; + this->images[i].image = EGL_NO_IMAGE; } - this->lastIndex = -1; this->renderIndex = -1; EGL_Texture * parent = &this->base.base; @@ -126,6 +198,30 @@ static bool egl_texDMABUFInit(EGL_Texture ** texture, EGL_TexType type, this->display = display; + if (!egl_shaderInit(&this->copyShader) || + !egl_shaderCompile(this->copyShader, + b_shader_hdr_compose_vert, b_shader_hdr_compose_vert_size, + b_shader_downscale_linear_frag, b_shader_downscale_linear_frag_size, + true, NULL)) + { + DEBUG_ERROR("Failed to initialize the DMABUF snapshot pass"); + egl_texDMABUFFree(*texture); + *texture = NULL; + return false; + } + + egl_shaderAssocTextures(this->copyShader, 1); + + glGenSamplers(1, &this->copySampler); + glSamplerParameteri(this->copySampler, + GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glSamplerParameteri(this->copySampler, + GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glSamplerParameteri(this->copySampler, + GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(this->copySampler, + GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (!initDone) { const char * client_exts = eglQueryString(this->display, EGL_EXTENSIONS); @@ -143,6 +239,10 @@ static void egl_texDMABUFFree(EGL_Texture * texture) TexDMABUF * this = UPCAST(TexDMABUF , parent); egl_texDMABUFCleanup(texture); + egl_modelFree(&this->copyModel); + egl_shaderFree(&this->copyShader); + if (this->copySampler) + glDeleteSamplers(1, &this->copySampler); egl_texBufferFree(&parent->base); free(this); } @@ -157,12 +257,47 @@ static bool texDMABUFSetup(EGL_Texture * texture) this->pixFmt = EGL_PF_RGB_24_32; this->width = texture->format.pitch / 4; this->fourcc = DRM_FORMAT_ARGB8888; - this->format = GL_BGRA_EXT; } egl_texDMABUFCleanup(texture); - glGenTextures(parent->texCount, parent->tex); + if (!this->copyModel) + { + if (!egl_modelInit(&this->copyModel)) + return false; + egl_modelSetDefault(this->copyModel, false); + egl_modelSetShader(this->copyModel, this->copyShader); + } + + for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i) + { + struct Snapshot * snapshot = &this->snapshots[i]; + + glGenTextures(1, &snapshot->texture); + egl_stateBindTexture(0, GL_TEXTURE_2D, snapshot->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, this->intFormat, + this->width, texture->format.height, 0, + GL_RGBA, this->dataType, NULL); + + glGenFramebuffers(1, &snapshot->framebuffer); + egl_stateBindFramebuffer(snapshot->framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, snapshot->texture, 0); + glDrawBuffers(1, &(GLenum){ GL_COLOR_ATTACHMENT0 }); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + DEBUG_ERROR("Failed to create DMABUF snapshot framebuffer"); + egl_stateBindFramebuffer(0); + egl_texDMABUFCleanup(texture); + return false; + } + } + + egl_stateBindFramebuffer(0); parent->rIndex = -1; return true; @@ -173,10 +308,32 @@ static bool egl_texDMABUFSetup(EGL_Texture * texture, const EGL_TexSetup * setup TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexDMABUF * this = UPCAST(TexDMABUF , parent); - this->pixFmt = texture->format.pixFmt; - this->width = texture->format.width; - this->fourcc = texture->format.fourcc; - this->format = texture->format.format; + this->pixFmt = texture->format.pixFmt; + this->width = texture->format.width; + this->fourcc = texture->format.fourcc; + this->dataType = GL_UNSIGNED_BYTE; + this->intFormat = GL_RGBA8; + + switch (texture->format.pixFmt) + { + case EGL_PF_RGBA10: + this->intFormat = GL_RGB10_A2; + this->dataType = GL_UNSIGNED_INT_2_10_10_10_REV; + break; + + case EGL_PF_RGBA16F: + this->intFormat = GL_RGBA16F; + this->dataType = GL_HALF_FLOAT; + break; + + case EGL_PF_RGB_24: + /* A native RGB888 import is expanded by the snapshot shader. */ + this->pixFmt = EGL_PF_RGBA; + break; + + default: + break; + } return texDMABUFSetup(texture); } @@ -220,6 +377,10 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture, DEBUG_ASSERT(update->type == EGL_TEXTYPE_DMABUF); + /* Retire completed copies before deciding whether a snapshot slot must be + * waited on and coalesced. */ + egl_texDMABUFPoll(texture); + struct FdImage *fdImage = NULL; for (int i = 0; i < ARRAY_LENGTH(this->images); ++i) if (this->images[i].fd == update->dmaFD) @@ -242,14 +403,13 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture, return false; } - EGLImage image = fdImage->image; - if (unlikely(image == EGL_NO_IMAGE)) + if (unlikely(fdImage->image == EGL_NO_IMAGE)) { bool setup = false; if (texture->format.pixFmt == EGL_PF_RGB_24 && has24BitSupport) { - image = createImage(texture, update->dmaFD); - if (image == EGL_NO_IMAGE) + fdImage->image = createImage(texture, update->dmaFD); + if (fdImage->image == EGL_NO_IMAGE) { DEBUG_INFO("Using 24-bit in 32-bit for DMA"); has24BitSupport = false; @@ -258,80 +418,184 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture, } if (!has24BitSupport && setup) - texDMABUFSetup(texture); + { + if (!texDMABUFSetup(texture)) + return false; + fdImage = &this->images[0]; + } - if (image == EGL_NO_IMAGE) - image = createImage(texture, update->dmaFD); + if (fdImage->image == EGL_NO_IMAGE) + fdImage->image = createImage(texture, update->dmaFD); - if (unlikely(image == EGL_NO_IMAGE)) + if (unlikely(fdImage->image == EGL_NO_IMAGE)) { DEBUG_EGL_ERROR("Failed to create EGLImage for DMA transfer"); return false; } fdImage->fd = update->dmaFD; - fdImage->image = image; + glGenTextures(1, &fdImage->texture); + egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, fdImage->texture); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, + GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, + GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, + GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, + GL_TEXTURE_MAG_FILTER, GL_NEAREST); + g_egl_dynProcs.glEGLImageTargetTexture2DOES( + GL_TEXTURE_EXTERNAL_OES, fdImage->image); + } - const int slot = (int)(fdImage - this->images); - fdImage->texIndex = slot; - GLsync sync = 0; - INTERLOCKED_SECTION(parent->copyLock, + struct SnapshotRelease releases[2]; + int releaseCount = 0; + LG_LOCK(parent->copyLock); + + int index = -1; + for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i) + if (this->snapshots[i].frameToken == LG_RENDERER_FRAME_TOKEN_NONE) { - egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, parent->tex[slot]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - g_egl_dynProcs.glEGLImageTargetTexture2DOES( - GL_TEXTURE_EXTERNAL_OES, image); + index = i; + break; + } - sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - fdImage->sync = sync; - }); + if (index < 0) + { + /* Drop the oldest snapshot that is no longer selected. If the GPU is + * still finishing it, wait only when all snapshot slots are saturated. */ + for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i) + if (i != this->renderIndex && + (index < 0 || this->snapshots[i].frameToken < + this->snapshots[index].frameToken)) + index = i; - if (unlikely(!sync)) + if (unlikely(index < 0)) { - DEBUG_GL_ERROR("Failed to create DMABUF sync"); + LG_UNLOCK(parent->copyLock); + DEBUG_ERROR("No reusable DMABUF snapshot slot"); return false; } - /* Publish the texture attachment to the shared render context once. The - * backing memory is synchronized separately by framebuffer_wait(), so - * subsequent frames do not add any GL commands to this context. */ - glFlush(); + struct Snapshot * old = &this->snapshots[index]; + waitSync(&old->copySync); + waitSync(&old->useSync); + if (old->releaseFn) + snapshotRelease(old, &releases[releaseCount++]); + old->frameToken = LG_RENDERER_FRAME_TOKEN_NONE; } - INTERLOCKED_SECTION(parent->copyLock, + struct Snapshot * snapshot = &this->snapshots[index]; + egl_stateBindFramebuffer(snapshot->framebuffer); + egl_stateViewport(0, 0, this->width, texture->format.height); + egl_stateBlend(false); + egl_stateScissor(false); + glDisable(GL_DITHER); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + egl_stateBindSampler(0, this->copySampler); + egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, fdImage->texture); + egl_shaderUse(this->copyShader); + egl_modelRender(this->copyModel); + + snapshot->copySync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + if (unlikely(!snapshot->copySync)) { - fdImage->frameToken = update->frameToken; - this->lastIndex = (int)(fdImage - this->images); - }); + DEBUG_GL_ERROR("Failed to fence DMABUF snapshot copy"); + glFinish(); + } + snapshot->frameToken = update->frameToken; + snapshot->releaseFn = update->releaseFn; + snapshot->releaseOpaque = update->releaseOpaque; + snapshot->releaseHandle = update->releaseHandle; + + if (!snapshot->copySync && snapshot->releaseFn) + snapshotRelease(snapshot, &releases[releaseCount++]); + + glFlush(); + LG_UNLOCK(parent->copyLock); + + snapshotInvokeReleases(releases, releaseCount); return true; } +static void egl_texDMABUFPoll(EGL_Texture * texture) +{ + TextureBuffer * parent = UPCAST(TextureBuffer, texture); + TexDMABUF * this = UPCAST(TexDMABUF , parent); + + struct SnapshotRelease releases[EGL_DMABUF_SNAPSHOT_COUNT]; + int releaseCount = 0; + + LG_LOCK(parent->copyLock); + for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i) + { + struct Snapshot * snapshot = &this->snapshots[i]; + if (snapshot->copySync) + { + const GLenum result = glClientWaitSync(snapshot->copySync, 0, 0); + if (result == GL_ALREADY_SIGNALED || + result == GL_CONDITION_SATISFIED) + { + glDeleteSync(snapshot->copySync); + snapshot->copySync = 0; + if (snapshot->releaseFn) + snapshotRelease(snapshot, &releases[releaseCount++]); + } + else if (result == GL_WAIT_FAILED) + DEBUG_FATAL("Failed to poll DMABUF snapshot copy fence"); + } + + if (snapshot->useSync) + { + const GLenum result = glClientWaitSync(snapshot->useSync, 0, 0); + if (result == GL_ALREADY_SIGNALED || + result == GL_CONDITION_SATISFIED) + { + glDeleteSync(snapshot->useSync); + snapshot->useSync = 0; + } + else if (result == GL_WAIT_FAILED) + DEBUG_FATAL("Failed to poll DMABUF snapshot use fence"); + } + } + LG_UNLOCK(parent->copyLock); + + snapshotInvokeReleases(releases, releaseCount); +} + static EGL_TexStatus egl_texDMABUFProcess(EGL_Texture * texture, LG_RendererFrameToken frameTokenLimit) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexDMABUF * this = UPCAST(TexDMABUF , parent); + egl_texDMABUFPoll(texture); + int index = -1; bool haveImage = false; bool updated = false; INTERLOCKED_SECTION(parent->copyLock, { - index = this->lastIndex; haveImage = this->renderIndex >= 0; - if (index >= 0 && egl_textureFrameAllowed( - this->images[index].frameToken, frameTokenLimit)) + for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i) + if (this->snapshots[i].frameToken != LG_RENDERER_FRAME_TOKEN_NONE && + egl_textureFrameAllowed( + this->snapshots[i].frameToken, frameTokenLimit) && + (index < 0 || this->snapshots[i].frameToken > + this->snapshots[index].frameToken)) + index = i; + + if (index >= 0) { - const struct FdImage * pending = &this->images[index]; + const struct Snapshot * pending = &this->snapshots[index]; if (this->renderIndex != index || texture->frameToken != pending->frameToken) { + if (pending->copySync) + glWaitSync(pending->copySync, 0, GL_TIMEOUT_IGNORED); this->renderIndex = index; - parent->rIndex = pending->texIndex; + parent->rIndex = index; texture->frameToken = pending->frameToken; updated = true; haveImage = true; @@ -350,34 +614,17 @@ static EGL_TexStatus egl_texDMABUFGet(EGL_Texture * texture, GLuint * tex, TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexDMABUF * this = UPCAST(TexDMABUF , parent); - int index = -1; - int texIndex = -1; - GLsync sync = 0; + int index = -1; INTERLOCKED_SECTION(parent->copyLock, { index = this->renderIndex; - 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) - { - /* 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[texIndex]; + *tex = this->snapshots[index].texture; if (fmt) *fmt = this->pixFmt; @@ -393,10 +640,42 @@ static EGL_TexStatus egl_texDMABUFBind(EGL_Texture * texture, GLuint unit) if ((status = egl_texDMABUFGet(texture, &tex, NULL)) != EGL_TEX_STATUS_OK) return status; - egl_stateBindTexture(unit, GL_TEXTURE_EXTERNAL_OES, tex); + egl_stateBindTexture(unit, GL_TEXTURE_2D, tex); return EGL_TEX_STATUS_OK; } +static void egl_texDMABUFReset(EGL_Texture * texture) +{ + TextureBuffer * parent = UPCAST(TextureBuffer, texture); + TexDMABUF * this = UPCAST(TexDMABUF , parent); + + egl_texDMABUFCleanup(texture); + egl_modelFree(&this->copyModel); +} + +static void egl_texDMABUFMarkUsed(EGL_Texture * texture) +{ + TextureBuffer * parent = UPCAST(TextureBuffer, texture); + TexDMABUF * this = UPCAST(TexDMABUF , parent); + + LG_LOCK(parent->copyLock); + if (this->renderIndex >= 0) + { + struct Snapshot * snapshot = &this->snapshots[this->renderIndex]; + if (snapshot->useSync) + glDeleteSync(snapshot->useSync); + snapshot->useSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + if (unlikely(!snapshot->useSync)) + { + DEBUG_GL_ERROR("Failed to fence DMABUF snapshot use"); + glFinish(); + } + else + glFlush(); + } + LG_UNLOCK(parent->copyLock); +} + EGL_TextureOps EGL_TextureDMABUF = { .init = egl_texDMABUFInit, @@ -405,5 +684,8 @@ EGL_TextureOps EGL_TextureDMABUF = .update = egl_texDMABUFUpdate, .process = egl_texDMABUFProcess, .get = egl_texDMABUFGet, - .bind = egl_texDMABUFBind + .bind = egl_texDMABUFBind, + .reset = egl_texDMABUFReset, + .poll = egl_texDMABUFPoll, + .markUsed = egl_texDMABUFMarkUsed, }; diff --git a/client/renderers/OpenGL/opengl.c b/client/renderers/OpenGL/opengl.c index 72d18586..3c7bdcee 100644 --- a/client/renderers/OpenGL/opengl.c +++ b/client/renderers/OpenGL/opengl.c @@ -396,10 +396,19 @@ bool opengl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat format bool opengl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFd, const FrameDamageRect * damage, int damageCount, - LG_RendererFrameToken frameToken) + LG_RendererFrameToken frameToken, LG_FrameReleaseFn releaseFn, + void * releaseOpaque, uint64_t releaseHandle) { struct Inst * this = UPCAST(struct Inst, renderer); + /* The legacy renderer retains the shared framebuffer until render. It does + * not support asynchronous DMA ownership. */ + if (releaseFn) + return false; + + (void)releaseOpaque; + (void)releaseHandle; + LG_LOCK(this->frameLock); this->frame = frame; this->pendingFrameToken = frameToken; diff --git a/client/src/main.c b/client/src/main.c index 6e6fb8b3..1c2e55a4 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -605,7 +605,8 @@ static int renderThread(void * unused) return 1; } - if (g_state.lgr->ops.supports && !RENDERER(supports, LG_SUPPORTS_DMABUF)) + if (!g_state.lgr->ops.supports || + !RENDERER(supports, LG_SUPPORTS_DMABUF)) g_state.useDMA = false; /* start up the fps timer */ @@ -935,7 +936,11 @@ int main_frameThread(void * unused) if (status != LG_TRANSPORT_OK) { if (status == LG_TRANSPORT_TIMEOUT || status == LG_TRANSPORT_UNAVAILABLE) + { + if (g_state.lgr->ops.onFramePoll) + RENDERER(onFramePoll); continue; + } if (status == LG_TRANSPORT_DISCONNECTED) app_setState(APP_STATE_RESTART); else if (status == LG_TRANSPORT_END) @@ -1109,8 +1114,13 @@ int main_frameThread(void * unused) const LG_RendererFrameToken frameToken = frameTimingReserve(); g_state.frameImportTime = 0; g_state.frameImportWaitTime = 0; + + const bool rendererOwnsFrame = frame.dmaFD >= 0 && frame.releaseFn; if (!RENDERER(onFrame, frame.framebuffer, frame.dmaFD, - frame.damageRects, damageCount, frameToken)) + frame.damageRects, damageCount, frameToken, + rendererOwnsFrame ? frame.releaseFn : NULL, + rendererOwnsFrame ? frame.releaseOpaque : NULL, + rendererOwnsFrame ? frame.releaseHandle : 0)) { frameTimingCancel(frameToken); g_state.transportOps->releaseFrame(g_state.transport, &frame); @@ -1119,6 +1129,14 @@ int main_frameThread(void * unused) break; } + /* A DMA snapshot can complete on the render thread immediately after it + * is signalled below, so sample producer timing while this lease is still + * unambiguously owned by the frame thread. */ + LG_TransportFrameTiming timing = {}; + if (g_state.transportOps->getFrameTiming) + g_state.transportOps->getFrameTiming( + g_state.transport, &frame, &timing); + const uint64_t queueStart = nanotime(); atomic_fetch_add_explicit(&g_state.frameCount, 1, memory_order_relaxed); #ifdef ENABLE_TESTS @@ -1142,11 +1160,6 @@ int main_frameThread(void * unused) lgSignalEvent(g_state.frameEvent); } - LG_TransportFrameTiming timing = {}; - if (g_state.transportOps->getFrameTiming) - g_state.transportOps->getFrameTiming( - g_state.transport, &frame, &timing); - if ((frame.flags & LG_TRANSPORT_FRAME_REQUEST_ACTIVATION) && g_params.requestActivation) g_state.ds->requestActivation(); @@ -1165,18 +1178,27 @@ int main_frameThread(void * unused) frameTimingFinishFrame(frameToken, &timing); - g_state.transportOps->releaseFrame(g_state.transport, &frame); + if (!rendererOwnsFrame) + g_state.transportOps->releaseFrame(g_state.transport, &frame); app_useSpiceDisplay(false); } - if (g_state.transportOps->stopFrame) - g_state.transportOps->stopFrame(g_state.transport); - - RENDERER(onRestart); - if (app_getState() != APP_STATE_SHUTDOWN) if (!app_useSpiceDisplay(true)) overlaySplash_show(true); + + /* Queue the fallback source before clearing desktop snapshots. The render + * thread processes this transition under lgrLock before its next render, so + * restart cannot expose an empty desktop frame between the two operations. */ + LG_LOCK(g_state.lgrLock); + RENDERER(onRestart); + LG_UNLOCK(g_state.lgrLock); + + /* Renderer reset requests release for every asynchronous DMA snapshot. + * Drain those requests before the transport unsubscribes or reconnects. */ + if (g_state.transportOps->stopFrame) + g_state.transportOps->stopFrame(g_state.transport); + return 0; } diff --git a/client/transports/LGMP/lgmp.c b/client/transports/LGMP/lgmp.c index 6914e0fc..445f5818 100644 --- a/client/transports/LGMP/lgmp.c +++ b/client/transports/LGMP/lgmp.c @@ -45,28 +45,44 @@ struct DMAFrameInfo int fd; }; +#define LGMP_FRAME_LEASE_COUNT (LGMP_Q_FRAME_LEN + 1) + +struct LGMPFrameLease +{ + PLGMPClientQueue * subscription; + PLGMPClientQueue queue; + const KVMFRFrame * frame; + LG_TransportFrameFormat format; + uint64_t handle; + uint32_t generation; + bool releaseRequested; + bool active; +}; + struct LG_Transport { struct IVSHMEM shm; PLGMPClient client; PLGMPClientQueue frameQueue; PLGMPClientQueue ownerFrameQueue[LGMP_Q_FRAME_LEN]; - PLGMPClientQueue pendingFrameQueue; PLGMPClientQueue pointerQueue; + LG_Lock frameLock; LG_Lock pointerLock; unsigned cursorPollInterval; unsigned framePollInterval; bool allowDMA; bool connected; - bool framePending; + bool frameStopRequested; bool frameScheduleSupported; - const KVMFRFrame * pendingFrame; + uint64_t frameLeaseHandle; + uint32_t frameGeneration; uint32_t clientID; uint32_t frameSerial; bool frameSerialValid; bool formatValid; LG_TransportFrameFormat format; + struct LGMPFrameLease frameLease[LGMP_FRAME_LEASE_COUNT]; struct DMAFrameInfo dma[LGMP_Q_FRAME_BUFFER_LEN]; uint8_t * pointerData; @@ -187,11 +203,18 @@ static bool lgmp_create(LG_Transport ** result) for (unsigned i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i) this->dma[i].fd = -1; + LG_LOCK_INIT(this->frameLock); LG_LOCK_INIT(this->pointerLock); + this->frameGeneration = 1; + this->frameLease[0].subscription = &this->frameQueue; + for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i) + this->frameLease[i + 1].subscription = &this->ownerFrameQueue[i]; + if (!ivshmemOpenDev(&this->shm, option_get_string("lgmp", "shmDevice"))) { + LG_LOCK_FREE(this->frameLock); LG_LOCK_FREE(this->pointerLock); free(this); return false; @@ -203,6 +226,7 @@ static bool lgmp_create(LG_Transport ** result) { DEBUG_ERROR("lgmpClientInit failed: %s", lgmpStatusString(status)); ivshmemClose(&this->shm); + LG_LOCK_FREE(this->frameLock); LG_LOCK_FREE(this->pointerLock); free(this); return false; @@ -212,51 +236,98 @@ static bool lgmp_create(LG_Transport ** result) return true; } -static void lgmp_stopFrame(struct LG_Transport * this) +static void lgmp_unsubscribeFrameLease(struct LGMPFrameLease * lease) { - if (this->framePending && this->pendingFrameQueue) - { - const LGMP_STATUS status = - lgmpClientMessageDone(this->pendingFrameQueue); - if (status != LGMP_OK) - DEBUG_WARN("Failed to release pending LGMP frame: %s", - lgmpStatusString(status)); - } - this->framePending = false; - this->pendingFrame = NULL; - this->pendingFrameQueue = NULL; + if (lease->active || !*lease->subscription) + return; - LGMP_STATUS status = lgmpClientUnsubscribe(&this->frameQueue); + const LGMP_STATUS status = lgmpClientUnsubscribe(lease->subscription); if (status != LGMP_OK) { - DEBUG_WARN("Failed to unsubscribe from the shared LGMP frame queue: %s", - lgmpStatusString(status)); - this->frameQueue = NULL; + if (status != LGMP_ERR_QUEUE_TIMEOUT && + status != LGMP_ERR_QUEUE_UNSUBSCRIBED) + DEBUG_WARN("Failed to unsubscribe from LGMP frame queue: %s", + lgmpStatusString(status)); + *lease->subscription = NULL; } - for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i) +} + +static struct LGMPFrameLease * lgmp_findFrameLeaseLocked( + struct LG_Transport * this, uint64_t handle) +{ + for (unsigned i = 0; i < LGMP_FRAME_LEASE_COUNT; ++i) + if (this->frameLease[i].active && + this->frameLease[i].handle == handle) + return &this->frameLease[i]; + return NULL; +} + +static void lgmp_finishFrameLeaseLocked(struct LG_Transport * this, + struct LGMPFrameLease * lease) +{ + if (lease->generation == this->frameGeneration && lease->queue && + *lease->subscription == lease->queue) { - status = lgmpClientUnsubscribe(&this->ownerFrameQueue[i]); - if (status != LGMP_OK) - { - DEBUG_WARN("Failed to unsubscribe from owner LGMP frame queue %u: %s", - i, lgmpStatusString(status)); - this->ownerFrameQueue[i] = NULL; - } + const LGMP_STATUS status = lgmpClientMessageDone(lease->queue); + if (status == LGMP_ERR_INVALID_SESSION || + status == LGMP_ERR_QUEUE_TIMEOUT || + status == LGMP_ERR_QUEUE_UNSUBSCRIBED) + *lease->subscription = NULL; + else if (status != LGMP_OK) + DEBUG_WARN("Failed to release LGMP frame: %s", + lgmpStatusString(status)); } + lease->queue = NULL; + lease->frame = NULL; + lease->handle = 0; + lease->releaseRequested = false; + lease->active = false; + if (this->frameStopRequested) + lgmp_unsubscribeFrameLease(lease); +} + +static void lgmp_drainFrameLeasesLocked(struct LG_Transport * this) +{ + for (unsigned i = 0; i < LGMP_FRAME_LEASE_COUNT; ++i) + if (this->frameLease[i].active && + this->frameLease[i].releaseRequested) + lgmp_finishFrameLeaseLocked(this, &this->frameLease[i]); +} + +static void lgmp_stopFrameLocked(struct LG_Transport * this) +{ + this->frameStopRequested = true; + /* stopFrame ends every lease by contract. The renderer is drained before + * this call, while direct transport users may still hold a returned frame. */ + for (unsigned i = 0; i < LGMP_FRAME_LEASE_COUNT; ++i) + if (this->frameLease[i].active) + lgmp_finishFrameLeaseLocked(this, &this->frameLease[i]); + for (unsigned i = 0; i < LGMP_FRAME_LEASE_COUNT; ++i) + lgmp_unsubscribeFrameLease(&this->frameLease[i]); + this->frameSerial = 0; this->frameSerialValid = false; this->formatValid = false; } +static void lgmp_stopFrame(struct LG_Transport * this) +{ + LG_LOCK(this->frameLock); + lgmp_stopFrameLocked(this); + LG_UNLOCK(this->frameLock); +} + static void lgmp_stopPointer(struct LG_Transport * this) { LG_LOCK(this->pointerLock); const LGMP_STATUS status = lgmpClientUnsubscribe(&this->pointerQueue); if (status != LGMP_OK) { - DEBUG_WARN("Failed to unsubscribe from the LGMP pointer queue: %s", - lgmpStatusString(status)); + if (status != LGMP_ERR_QUEUE_TIMEOUT && + status != LGMP_ERR_QUEUE_UNSUBSCRIBED) + DEBUG_WARN("Failed to unsubscribe from the LGMP pointer queue: %s", + lgmpStatusString(status)); this->pointerQueue = NULL; } LG_UNLOCK(this->pointerLock); @@ -291,6 +362,7 @@ static void lgmp_destroy(LG_Transport ** transport) free(this->pointerData); lgmpClientFree(&this->client); ivshmemClose(&this->shm); + LG_LOCK_FREE(this->frameLock); LG_LOCK_FREE(this->pointerLock); free(this); *transport = NULL; @@ -388,12 +460,18 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this, case LGMP_OK: if (!lgmp_parseSession(data, size, session)) return LG_TRANSPORT_INVALID_VERSION; + + LG_LOCK(this->frameLock); + if (++this->frameGeneration == 0) + ++this->frameGeneration; this->connected = true; + this->frameStopRequested = false; this->frameScheduleSupported = session->features & LG_TRANSPORT_FEATURE_FRAME_SCHEDULE; this->frameSerial = 0; this->frameSerialValid = false; this->formatValid = false; + LG_UNLOCK(this->frameLock); return LG_TRANSPORT_OK; case LGMP_ERR_INVALID_VERSION: @@ -412,10 +490,19 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this, static void lgmp_disconnect(LG_Transport * this) { lgmp_closeQueues(this); - lgmp_closeDMA(this); + + LG_LOCK(this->frameLock); + if (++this->frameGeneration == 0) + ++this->frameGeneration; + this->frameQueue = NULL; + for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i) + this->ownerFrameQueue[i] = NULL; this->connected = false; this->frameScheduleSupported = false; this->clientID = 0; + LG_UNLOCK(this->frameLock); + + lgmp_closeDMA(this); } static bool lgmp_sessionValid(LG_Transport * this) @@ -459,9 +546,13 @@ static LG_TransportStatus lgmp_subscribe(PLGMPClient client, uint32_t id, } } -static LG_TransportStatus lgmp_process(PLGMPClientQueue queue, +static LG_TransportStatus lgmp_process(PLGMPClientQueue * subscription, unsigned interval, LGMPMessage * message) { + PLGMPClientQueue queue = *subscription; + if (!queue) + return LG_TRANSPORT_TIMEOUT; + LGMP_STATUS status = lgmpClientProcess(queue, message); switch (status) { @@ -471,7 +562,12 @@ static LG_TransportStatus lgmp_process(PLGMPClientQueue queue, if (interval) usleep(interval); return LG_TRANSPORT_TIMEOUT; + case LGMP_ERR_QUEUE_TIMEOUT: + case LGMP_ERR_QUEUE_UNSUBSCRIBED: + *subscription = NULL; + return LG_TRANSPORT_TIMEOUT; case LGMP_ERR_INVALID_SESSION: + *subscription = NULL; return LG_TRANSPORT_DISCONNECTED; default: DEBUG_ERROR("lgmpClientProcess failed: %s", lgmpStatusString(status)); @@ -481,15 +577,21 @@ static LG_TransportStatus lgmp_process(PLGMPClientQueue queue, struct LGMPFrameMessage { - PLGMPClientQueue queue; - LGMPMessage message; - const KVMFRFrame * frame; - bool owner; + PLGMPClientQueue queue; + LGMPMessage message; + const KVMFRFrame * frame; + struct LGMPFrameLease * lease; + bool owner; }; -static LG_TransportStatus lgmp_pollFrameQueue(PLGMPClientQueue queue, +static LG_TransportStatus lgmp_pollFrameQueue( + PLGMPClientQueue * subscription, bool owner, struct LGMPFrameMessage * result) { + PLGMPClientQueue queue = *subscription; + if (!queue) + return LG_TRANSPORT_TIMEOUT; + const LGMP_STATUS advance = lgmpClientAdvanceToLast(queue); switch (advance) { @@ -497,7 +599,12 @@ static LG_TransportStatus lgmp_pollFrameQueue(PLGMPClientQueue queue, break; case LGMP_ERR_QUEUE_EMPTY: break; + case LGMP_ERR_QUEUE_TIMEOUT: + case LGMP_ERR_QUEUE_UNSUBSCRIBED: + *subscription = NULL; + return LG_TRANSPORT_TIMEOUT; case LGMP_ERR_INVALID_SESSION: + *subscription = NULL; return LG_TRANSPORT_DISCONNECTED; default: DEBUG_ERROR("lgmpClientAdvanceToLast failed: %s", @@ -505,7 +612,8 @@ static LG_TransportStatus lgmp_pollFrameQueue(PLGMPClientQueue queue, return LG_TRANSPORT_ERROR; } - const LG_TransportStatus status = lgmp_process(queue, 0, &result->message); + const LG_TransportStatus status = + lgmp_process(subscription, 0, &result->message); if (status == LG_TRANSPORT_OK) { result->queue = queue; @@ -527,13 +635,44 @@ static LG_TransportStatus lgmp_doneFrameMessage( if (status == LGMP_OK) return LG_TRANSPORT_OK; if (status == LGMP_ERR_INVALID_SESSION) + { + *message->lease->subscription = NULL; return LG_TRANSPORT_DISCONNECTED; + } + if (status == LGMP_ERR_QUEUE_TIMEOUT || + status == LGMP_ERR_QUEUE_UNSUBSCRIBED) + { + *message->lease->subscription = NULL; + return LG_TRANSPORT_OK; + } DEBUG_WARN("Failed to release discarded LGMP frame: %s", lgmpStatusString(status)); return LG_TRANSPORT_ERROR; } +static LG_TransportStatus lgmp_donePointerMessage(LG_Transport * this) +{ + const LGMP_STATUS status = lgmpClientMessageDone(this->pointerQueue); + if (status == LGMP_OK) + return LG_TRANSPORT_OK; + if (status == LGMP_ERR_INVALID_SESSION) + { + this->pointerQueue = NULL; + return LG_TRANSPORT_DISCONNECTED; + } + if (status == LGMP_ERR_QUEUE_TIMEOUT || + status == LGMP_ERR_QUEUE_UNSUBSCRIBED) + { + this->pointerQueue = NULL; + return LG_TRANSPORT_OK; + } + + DEBUG_WARN("Failed to release LGMP pointer message: %s", + lgmpStatusString(status)); + return LG_TRANSPORT_ERROR; +} + static void lgmp_mergeFrameStatus(LG_TransportStatus status, LG_TransportStatus * result) { @@ -624,13 +763,17 @@ static int lgmp_getDMA(struct LG_Transport * this, const KVMFRFrame * frame, return dma->fd; } -static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA, +static void lgmp_releaseFrameLease(void * opaque, uint64_t handle); + +static LG_TransportStatus lgmp_nextFrameLocked(LG_Transport * this, + bool useDMA, LG_TransportFrame * result) { + lgmp_drainFrameLeasesLocked(this); if (!this->connected) return LG_TRANSPORT_DISCONNECTED; - if (this->framePending) - return LG_TRANSPORT_ERROR; + + this->frameStopRequested = false; if (this->frameScheduleSupported) for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i) @@ -653,14 +796,16 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA, LG_TransportStatus ownerStatus[LGMP_Q_FRAME_LEN]; /* Select the newest frame across the shared and owner delivery lanes. */ - if (this->frameQueue) - sharedStatus = lgmp_pollFrameQueue(this->frameQueue, false, &shared); + shared.lease = &this->frameLease[0]; + if (this->frameQueue && !shared.lease->active) + sharedStatus = lgmp_pollFrameQueue(&this->frameQueue, false, &shared); for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i) { + owner[i].lease = &this->frameLease[i + 1]; ownerStatus[i] = LG_TRANSPORT_TIMEOUT; - if (this->ownerFrameQueue[i]) + if (this->ownerFrameQueue[i] && !owner[i].lease->active) ownerStatus[i] = lgmp_pollFrameQueue( - this->ownerFrameQueue[i], true, &owner[i]); + &this->ownerFrameQueue[i], true, &owner[i]); } LG_TransportStatus failure = LG_TRANSPORT_OK; @@ -718,11 +863,7 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA, lgmp_selectNewestFrameMessage(&owner[i], &selected); if (!selected) - { - if (this->framePollInterval) - usleep(this->framePollInterval); return malformed ? LG_TRANSPORT_ERROR : LG_TRANSPORT_TIMEOUT; - } if (selected != &shared) releaseFailure = lgmp_doneFrameMessage(&shared); @@ -811,7 +952,9 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA, } this->formatValid = true; } - result->format = format; + struct LGMPFrameLease * lease = selected->lease; + memcpy(&lease->format, format, sizeof(lease->format)); + result->format = &lease->format; result->framebuffer = (const FrameBuffer *)((const uint8_t *)frame + frame->offset); @@ -837,12 +980,33 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA, else if (!fullDamage) DEBUG_WARN("Invalid damage rectangles, forcing a full update"); - this->framePending = true; - this->pendingFrame = frame; - this->pendingFrameQueue = selected->queue; + lease->queue = selected->queue; + lease->frame = frame; + if (++this->frameLeaseHandle == 0) + ++this->frameLeaseHandle; + lease->handle = this->frameLeaseHandle; + lease->generation = this->frameGeneration; + lease->releaseRequested = false; + lease->active = true; + result->releaseFn = lgmp_releaseFrameLease; + result->releaseOpaque = this; + result->releaseHandle = lease->handle; return LG_TRANSPORT_OK; } +static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA, + LG_TransportFrame * result) +{ + LG_LOCK(this->frameLock); + const LG_TransportStatus status = + lgmp_nextFrameLocked(this, useDMA, result); + LG_UNLOCK(this->frameLock); + if ((status == LG_TRANSPORT_TIMEOUT || + status == LG_TRANSPORT_UNAVAILABLE) && this->framePollInterval) + usleep(this->framePollInterval); + return status; +} + static bool lgmp_frameTimingReady(const KVMFRFrame * frame) { return __atomic_load_n(&frame->timingValid, __ATOMIC_ACQUIRE) && @@ -853,38 +1017,70 @@ static void lgmp_getFrameTiming(LG_Transport * this, const LG_TransportFrame * frame, LG_TransportFrameTiming * timing) { memset(timing, 0, sizeof(*timing)); - if (!this->framePending || !this->pendingFrame || - this->pendingFrame->frameSerial != frame->serial) + + LG_LOCK(this->frameLock); + struct LGMPFrameLease * lease = + lgmp_findFrameLeaseLocked(this, frame->releaseHandle); + + if (!lease || + lease->generation != this->frameGeneration || !lease->frame || + lease->frame->frameSerial != frame->serial) + { + LG_UNLOCK(this->frameLock); return; + } /* The producer writes these immediately after publishing FrameBuffer::wp. * nextFrame can observe the header earlier, so briefly observe the * publication tail after onFrame consumes the framebuffer without sleeping * the frame-acquisition thread. */ for (unsigned i = 0; - !lgmp_frameTimingReady(this->pendingFrame) && + !lgmp_frameTimingReady(lease->frame) && i < LGMP_TIMING_SPIN_COUNT; ++i) { } - if (!lgmp_frameTimingReady(this->pendingFrame)) + if (!lgmp_frameTimingReady(lease->frame)) + { + LG_UNLOCK(this->frameLock); return; + } timing->valid = true; - timing->captureTime = this->pendingFrame->captureTime; - timing->postProcessTime = this->pendingFrame->postProcessTime; - timing->copyTime = this->pendingFrame->copyTime; - timing->readyTime = this->pendingFrame->readyTime; + timing->captureTime = lease->frame->captureTime; + timing->postProcessTime = lease->frame->postProcessTime; + timing->copyTime = lease->frame->copyTime; + timing->readyTime = lease->frame->readyTime; + LG_UNLOCK(this->frameLock); +} + +static void lgmp_releaseFrameLease(void * opaque, uint64_t handle) +{ + LG_Transport * this = opaque; + + LG_LOCK(this->frameLock); + struct LGMPFrameLease * lease = lgmp_findFrameLeaseLocked(this, handle); + if (lease) + lease->releaseRequested = true; + LG_UNLOCK(this->frameLock); } static void lgmp_releaseFrame(LG_Transport * this, LG_TransportFrame * frame) { - if (this->framePending && this->pendingFrameQueue) - lgmpClientMessageDone(this->pendingFrameQueue); - this->framePending = false; - this->pendingFrame = NULL; - this->pendingFrameQueue = NULL; + if (frame->releaseFn) + { + LG_LOCK(this->frameLock); + struct LGMPFrameLease * lease = + lgmp_findFrameLeaseLocked(this, frame->releaseHandle); + if (lease) + lgmp_finishFrameLeaseLocked(this, lease); + LG_UNLOCK(this->frameLock); + + frame->releaseFn = NULL; + frame->releaseOpaque = NULL; + frame->releaseHandle = 0; + } memset(frame, 0, sizeof(*frame)); } @@ -910,14 +1106,15 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this, if (status == LG_TRANSPORT_OK) { LGMPMessage message; - status = lgmp_process(this->pointerQueue, this->cursorPollInterval, + status = lgmp_process(&this->pointerQueue, this->cursorPollInterval, &message); if (status == LG_TRANSPORT_OK) { if (message.size < sizeof(KVMFRCursor)) { - lgmpClientMessageDone(this->pointerQueue); - status = LG_TRANSPORT_ERROR; + const LG_TransportStatus done = lgmp_donePointerMessage(this); + status = done == LG_TRANSPORT_DISCONNECTED ? + done : LG_TRANSPORT_ERROR; } else { @@ -930,8 +1127,9 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this, if (shapeSize > message.size - sizeof(*cursor) || transformSize > message.size - sizeof(*cursor) - shapeSize) { - lgmpClientMessageDone(this->pointerQueue); - status = LG_TRANSPORT_ERROR; + const LG_TransportStatus done = lgmp_donePointerMessage(this); + status = done == LG_TRANSPORT_DISCONNECTED ? + done : LG_TRANSPORT_ERROR; } else { @@ -952,7 +1150,9 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this, memcpy(this->pointerData, message.mem, needed); pointerFlags = (uint32_t)message.udata; } - lgmpClientMessageDone(this->pointerQueue); + const LG_TransportStatus done = lgmp_donePointerMessage(this); + if (status == LG_TRANSPORT_OK) + status = done; } } } diff --git a/common/include/common/types.h b/common/include/common/types.h index 2bf7be34..7c063640 100644 --- a/common/include/common/types.h +++ b/common/include/common/types.h @@ -23,6 +23,8 @@ #include +typedef void (*LG_FrameReleaseFn)(void * opaque, uint64_t handle); + struct Point { int x, y;