mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 22:31:39 +00:00
Correlate received frames with the texture update actually consumed by the renderer and retain damage until its token becomes renderable. Split client latency into dispatch, import, queue, preparation, setup, effects, desktop, composition, and swap stages in FRAME LATENCY. Exclude diagnostic overlay work from composition and preserve client wait time when producer timing is unavailable. Publish joined samples through a bounded token queue, avoid sleeping while producer timing is finalized, and correct Wayland photon units.
393 lines
11 KiB
C
393 lines
11 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "texture.h"
|
|
#include "texture_buffer.h"
|
|
#include "state.h"
|
|
#include "util.h"
|
|
|
|
#include "common/array.h"
|
|
#include "egl_dynprocs.h"
|
|
#include "egldebug.h"
|
|
|
|
struct FdImage
|
|
{
|
|
int fd;
|
|
EGLImage image;
|
|
GLsync sync;
|
|
int texIndex;
|
|
LG_RendererFrameToken frameToken;
|
|
};
|
|
|
|
typedef struct TexDMABUF
|
|
{
|
|
TextureBuffer base;
|
|
|
|
EGLDisplay display;
|
|
|
|
struct FdImage images[2];
|
|
int lastIndex;
|
|
int renderIndex;
|
|
|
|
EGL_PixelFormat pixFmt;
|
|
unsigned fourcc;
|
|
unsigned width;
|
|
GLuint format;
|
|
}
|
|
TexDMABUF;
|
|
|
|
EGL_TextureOps EGL_TextureDMABUF;
|
|
|
|
static bool initDone = false;
|
|
static bool has24BitSupport = true;
|
|
static bool hasImportModifiers = true;
|
|
|
|
// internal functions
|
|
|
|
static void egl_texDMABUFCleanup(EGL_Texture * texture)
|
|
{
|
|
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
|
|
TexDMABUF * this = UPCAST(TexDMABUF , parent);
|
|
|
|
for(int i = 0; i < ARRAY_LENGTH(this->images); ++i)
|
|
{
|
|
if (this->images[i].image != EGL_NO_IMAGE)
|
|
{
|
|
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;
|
|
}
|
|
|
|
this->lastIndex = -1;
|
|
this->renderIndex = -1;
|
|
parent->rIndex = -1;
|
|
texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
|
|
|
egl_texUtilFreeBuffers(parent->buf, parent->texCount);
|
|
|
|
if (parent->tex[0])
|
|
{
|
|
glDeleteTextures(parent->texCount, parent->tex);
|
|
egl_stateInvalidateShared();
|
|
}
|
|
}
|
|
|
|
// dmabuf functions
|
|
|
|
static bool egl_texDMABUFInit(EGL_Texture ** texture, EGL_TexType type,
|
|
EGLDisplay * display)
|
|
{
|
|
TexDMABUF * this = calloc(1, sizeof(*this));
|
|
*texture = &this->base.base;
|
|
|
|
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->lastIndex = -1;
|
|
this->renderIndex = -1;
|
|
|
|
EGL_Texture * parent = &this->base.base;
|
|
if (!egl_texBufferStreamInit(&parent, type, display))
|
|
{
|
|
free(this);
|
|
*texture = NULL;
|
|
return false;
|
|
}
|
|
|
|
this->display = display;
|
|
|
|
if (!initDone)
|
|
{
|
|
const char * client_exts = eglQueryString(this->display, EGL_EXTENSIONS);
|
|
hasImportModifiers =
|
|
util_hasGLExt(client_exts, "EGL_EXT_image_dma_buf_import_modifiers");
|
|
initDone = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void egl_texDMABUFFree(EGL_Texture * texture)
|
|
{
|
|
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
|
|
TexDMABUF * this = UPCAST(TexDMABUF , parent);
|
|
|
|
egl_texDMABUFCleanup(texture);
|
|
egl_texBufferFree(&parent->base);
|
|
free(this);
|
|
}
|
|
|
|
static bool texDMABUFSetup(EGL_Texture * texture)
|
|
{
|
|
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
|
|
TexDMABUF * this = UPCAST(TexDMABUF , parent);
|
|
|
|
if (texture->format.pixFmt == EGL_PF_RGB_24 && !has24BitSupport)
|
|
{
|
|
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);
|
|
parent->rIndex = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
|
|
return texDMABUFSetup(texture);
|
|
}
|
|
|
|
static EGLImage createImage(EGL_Texture * texture, int fd)
|
|
{
|
|
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
|
|
TexDMABUF * this = UPCAST(TexDMABUF , parent);
|
|
|
|
const uint64_t modifier = DRM_FORMAT_MOD_LINEAR;
|
|
EGLAttrib attribs[] =
|
|
{
|
|
EGL_WIDTH , this->width ,
|
|
EGL_HEIGHT , texture->format.height,
|
|
EGL_LINUX_DRM_FOURCC_EXT , this->fourcc,
|
|
EGL_DMA_BUF_PLANE0_FD_EXT , fd,
|
|
EGL_DMA_BUF_PLANE0_OFFSET_EXT , 0,
|
|
EGL_DMA_BUF_PLANE0_PITCH_EXT , texture->format.pitch,
|
|
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, (modifier & 0xffffffff),
|
|
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (modifier >> 32),
|
|
EGL_NONE , EGL_NONE
|
|
};
|
|
|
|
if (!hasImportModifiers)
|
|
attribs[12] = attribs[13] =
|
|
attribs[14] = attribs[15] = EGL_NONE;
|
|
|
|
return g_egl_dynProcs.eglCreateImage(
|
|
this->display,
|
|
EGL_NO_CONTEXT,
|
|
EGL_LINUX_DMA_BUF_EXT,
|
|
(EGLClientBuffer)NULL,
|
|
attribs);
|
|
}
|
|
|
|
static bool egl_texDMABUFUpdate(EGL_Texture * texture,
|
|
const EGL_TexUpdate * update)
|
|
{
|
|
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
|
|
TexDMABUF * this = UPCAST(TexDMABUF , parent);
|
|
|
|
DEBUG_ASSERT(update->type == EGL_TEXTYPE_DMABUF);
|
|
|
|
struct FdImage *fdImage =
|
|
(this->images[0].fd == update->dmaFD) ? &this->images[0] :
|
|
(this->images[1].fd == update->dmaFD) ? &this->images[1] :
|
|
(this->images[0].fd == -1) ? &this->images[0] :
|
|
&this->images[1];
|
|
EGLImage image = fdImage->image;
|
|
if (unlikely(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)
|
|
{
|
|
DEBUG_INFO("Using 24-bit in 32-bit for DMA");
|
|
has24BitSupport = false;
|
|
setup = true;
|
|
}
|
|
}
|
|
|
|
if (!has24BitSupport && setup)
|
|
texDMABUFSetup(texture);
|
|
|
|
if (image == EGL_NO_IMAGE)
|
|
image = createImage(texture, update->dmaFD);
|
|
|
|
if (unlikely(image == EGL_NO_IMAGE))
|
|
{
|
|
DEBUG_EGL_ERROR("Failed to create EGLImage for DMA transfer");
|
|
return false;
|
|
}
|
|
|
|
fdImage->fd = update->dmaFD;
|
|
fdImage->image = image;
|
|
|
|
int slot = (fdImage == &this->images[0]) ? 0 : 1;
|
|
fdImage->texIndex = slot;
|
|
GLsync sync = 0;
|
|
INTERLOCKED_SECTION(parent->copyLock,
|
|
{
|
|
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);
|
|
|
|
sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
fdImage->sync = sync;
|
|
});
|
|
|
|
if (unlikely(!sync))
|
|
{
|
|
DEBUG_GL_ERROR("Failed to create DMABUF sync");
|
|
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();
|
|
}
|
|
|
|
INTERLOCKED_SECTION(parent->copyLock,
|
|
{
|
|
fdImage->frameToken = update->frameToken;
|
|
this->lastIndex = (fdImage == &this->images[0]) ? 0 : 1;
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
static EGL_TexStatus egl_texDMABUFProcess(EGL_Texture * texture,
|
|
LG_RendererFrameToken frameTokenLimit)
|
|
{
|
|
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
|
|
TexDMABUF * this = UPCAST(TexDMABUF , parent);
|
|
|
|
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))
|
|
{
|
|
const struct FdImage * pending = &this->images[index];
|
|
if (this->renderIndex != index ||
|
|
texture->frameToken != pending->frameToken)
|
|
{
|
|
this->renderIndex = index;
|
|
parent->rIndex = pending->texIndex;
|
|
texture->frameToken = pending->frameToken;
|
|
updated = true;
|
|
haveImage = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (unlikely(!haveImage))
|
|
return EGL_TEX_STATUS_NOTREADY;
|
|
return updated ? EGL_TEX_STATUS_UPDATED : EGL_TEX_STATUS_OK;
|
|
}
|
|
|
|
static EGL_TexStatus egl_texDMABUFGet(EGL_Texture * texture, GLuint * tex,
|
|
EGL_PixelFormat * fmt)
|
|
{
|
|
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
|
|
TexDMABUF * this = UPCAST(TexDMABUF , parent);
|
|
|
|
int index = -1;
|
|
int texIndex = -1;
|
|
GLsync sync = 0;
|
|
|
|
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];
|
|
|
|
if (fmt)
|
|
*fmt = this->pixFmt;
|
|
|
|
return EGL_TEX_STATUS_OK;
|
|
}
|
|
|
|
static EGL_TexStatus egl_texDMABUFBind(EGL_Texture * texture, GLuint unit)
|
|
{
|
|
GLuint tex;
|
|
EGL_TexStatus status;
|
|
|
|
if ((status = egl_texDMABUFGet(texture, &tex, NULL)) != EGL_TEX_STATUS_OK)
|
|
return status;
|
|
|
|
egl_stateBindTexture(unit, GL_TEXTURE_EXTERNAL_OES, tex);
|
|
return EGL_TEX_STATUS_OK;
|
|
}
|
|
|
|
EGL_TextureOps EGL_TextureDMABUF =
|
|
{
|
|
.init = egl_texDMABUFInit,
|
|
.free = egl_texDMABUFFree,
|
|
.setup = egl_texDMABUFSetup,
|
|
.update = egl_texDMABUFUpdate,
|
|
.process = egl_texDMABUFProcess,
|
|
.get = egl_texDMABUFGet,
|
|
.bind = egl_texDMABUFBind
|
|
};
|