Files
LookingGlass/client/renderers/EGL/texture_external.c
2026-11-06 14:37:38 +11:00

417 lines
12 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 "state.h"
#include "egldebug.h"
#include "common/array.h"
#include "common/debug.h"
#include "common/locking.h"
#include <stdlib.h>
#include <string.h>
#define EGL_EXTERNAL_RETIRED_COUNT 4
struct ExternalTextureFrame
{
GLuint texture;
GLsync readySync;
GLsync useSync;
LG_RendererFrameToken frameToken;
LG_ExternalFrame * externalFrame;
LG_FrameReleaseFn releaseFn;
void * releaseOpaque;
uint64_t releaseHandle;
};
struct ExternalTextureRelease
{
LG_ExternalFrame * externalFrame;
LG_FrameReleaseFn releaseFn;
void * releaseOpaque;
uint64_t releaseHandle;
};
typedef struct TexExternal
{
EGL_Texture base;
LG_Lock lock;
struct ExternalTextureFrame pending;
struct ExternalTextureFrame current;
struct ExternalTextureFrame retired[EGL_EXTERNAL_RETIRED_COUNT];
bool deactivateRequested;
}
TexExternal;
static void externalFrameTakeRelease(struct ExternalTextureFrame * frame,
struct ExternalTextureRelease * release)
{
release->externalFrame = frame->externalFrame;
release->releaseFn = frame->releaseFn;
release->releaseOpaque = frame->releaseOpaque;
release->releaseHandle = frame->releaseHandle;
frame->externalFrame = NULL;
frame->releaseFn = NULL;
frame->releaseOpaque = NULL;
frame->releaseHandle = 0;
}
static void externalFrameInvokeReleases(
struct ExternalTextureRelease * releases, int count)
{
for (int i = 0; i < count; ++i)
{
lgExternalFrameRelease(releases[i].externalFrame);
if (releases[i].releaseFn)
releases[i].releaseFn(
releases[i].releaseOpaque, releases[i].releaseHandle);
}
}
static void externalFrameWait(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 an external texture fence");
glDeleteSync(*sync);
*sync = 0;
}
static bool externalFramePending(const struct ExternalTextureFrame * frame)
{
return frame->externalFrame || frame->releaseFn;
}
static int externalFrameCleanupLocked(TexExternal * this,
struct ExternalTextureRelease * releases)
{
int releaseCount = 0;
if (externalFramePending(&this->pending))
externalFrameTakeRelease(
&this->pending, &releases[releaseCount++]);
externalFrameWait(&this->current.useSync);
if (externalFramePending(&this->current))
externalFrameTakeRelease(
&this->current, &releases[releaseCount++]);
for (int i = 0; i < ARRAY_LENGTH(this->retired); ++i)
{
externalFrameWait(&this->retired[i].useSync);
if (externalFramePending(&this->retired[i]))
externalFrameTakeRelease(
&this->retired[i], &releases[releaseCount++]);
}
memset(&this->pending, 0, sizeof(this->pending));
memset(&this->current, 0, sizeof(this->current));
memset(this->retired, 0, sizeof(this->retired));
this->base.frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
this->deactivateRequested = false;
return releaseCount;
}
static void egl_texExternalCleanup(EGL_Texture * texture)
{
TexExternal * this = UPCAST(TexExternal, texture);
struct ExternalTextureRelease releases[EGL_EXTERNAL_RETIRED_COUNT + 2];
int releaseCount;
LG_LOCK(this->lock);
releaseCount = externalFrameCleanupLocked(this, releases);
LG_UNLOCK(this->lock);
externalFrameInvokeReleases(releases, releaseCount);
}
static bool egl_texExternalInit(EGL_Texture ** texture, EGL_TexType type,
EGLDisplay * display)
{
(void)display;
TexExternal * this = calloc(1, sizeof(*this));
if (!this)
return false;
LG_LOCK_INIT(this->lock);
this->base.type = type;
*texture = &this->base;
return true;
}
static void egl_texExternalFree(EGL_Texture * texture)
{
TexExternal * this = UPCAST(TexExternal, texture);
egl_texExternalCleanup(texture);
LG_LOCK_FREE(this->lock);
free(this);
}
static bool egl_texExternalSetup(EGL_Texture * texture,
const EGL_TexSetup * setup)
{
(void)setup;
egl_texExternalCleanup(texture);
return true;
}
static bool egl_texExternalUpdate(EGL_Texture * texture,
const EGL_TexUpdate * update)
{
TexExternal * this = UPCAST(TexExternal, texture);
DEBUG_ASSERT(update->type == EGL_TEXTYPE_EXTERNAL);
DEBUG_ASSERT(update->externalFrame);
LG_ExternalFrameEGLTexture descriptor =
{
.version = LG_EXTERNAL_FRAME_EGL_TEXTURE_VERSION,
.size = sizeof(descriptor),
};
if (!lgExternalFrameQuery(update->externalFrame,
LG_EXTERNAL_FRAME_EGL_TEXTURE, &descriptor, sizeof(descriptor)))
{
DEBUG_ERROR("Failed to query the external EGL texture");
return false;
}
if (descriptor.version != LG_EXTERNAL_FRAME_EGL_TEXTURE_VERSION ||
descriptor.size < sizeof(descriptor) ||
!descriptor.texture || descriptor.target != (uint32_t)GL_TEXTURE_2D)
{
DEBUG_ERROR("Invalid external EGL texture descriptor");
return false;
}
struct ExternalTextureFrame pending =
{
.texture = (GLuint)descriptor.texture,
.readySync = (GLsync)descriptor.readySync,
.frameToken = update->frameToken,
.externalFrame = lgExternalFrameRetain(update->externalFrame),
.releaseFn = update->releaseFn,
.releaseOpaque = update->releaseOpaque,
.releaseHandle = update->releaseHandle,
};
struct ExternalTextureRelease release = {0};
bool haveRelease;
LG_LOCK(this->lock);
this->deactivateRequested = false;
haveRelease = externalFramePending(&this->pending);
if (haveRelease)
externalFrameTakeRelease(&this->pending, &release);
this->pending = pending;
LG_UNLOCK(this->lock);
if (haveRelease)
externalFrameInvokeReleases(&release, 1);
return true;
}
static void egl_texExternalPoll(EGL_Texture * texture)
{
TexExternal * this = UPCAST(TexExternal, texture);
struct ExternalTextureRelease releases[EGL_EXTERNAL_RETIRED_COUNT + 2];
int releaseCount = 0;
LG_LOCK(this->lock);
if (this->deactivateRequested)
{
releaseCount = externalFrameCleanupLocked(this, releases);
LG_UNLOCK(this->lock);
externalFrameInvokeReleases(releases, releaseCount);
return;
}
for (int i = 0; i < ARRAY_LENGTH(this->retired); ++i)
{
struct ExternalTextureFrame * retired = &this->retired[i];
if (!retired->useSync)
continue;
const GLenum result = glClientWaitSync(retired->useSync, 0, 0);
if (result == GL_ALREADY_SIGNALED || result == GL_CONDITION_SATISFIED)
{
glDeleteSync(retired->useSync);
retired->useSync = 0;
externalFrameTakeRelease(retired, &releases[releaseCount++]);
memset(retired, 0, sizeof(*retired));
}
else if (result == GL_WAIT_FAILED)
DEBUG_FATAL("Failed to poll an external texture fence");
}
LG_UNLOCK(this->lock);
externalFrameInvokeReleases(releases, releaseCount);
}
static EGL_TexStatus egl_texExternalProcess(EGL_Texture * texture,
LG_RendererFrameToken frameTokenLimit)
{
TexExternal * this = UPCAST(TexExternal, texture);
struct ExternalTextureRelease releases[2];
int releaseCount = 0;
bool haveTexture;
bool updated = false;
egl_texExternalPoll(texture);
LG_LOCK(this->lock);
if (externalFramePending(&this->pending) &&
egl_textureFrameAllowed(this->pending.frameToken, frameTokenLimit))
{
if (externalFramePending(&this->current))
{
if (this->current.useSync)
{
int index = -1;
for (int i = 0; i < ARRAY_LENGTH(this->retired); ++i)
if (!externalFramePending(&this->retired[i]))
{
index = i;
break;
}
if (index < 0)
{
index = 0;
externalFrameWait(&this->retired[index].useSync);
externalFrameTakeRelease(
&this->retired[index], &releases[releaseCount++]);
}
this->retired[index] = this->current;
}
else
externalFrameTakeRelease(
&this->current, &releases[releaseCount++]);
}
if (this->pending.readySync)
glWaitSync(this->pending.readySync, 0, GL_TIMEOUT_IGNORED);
this->pending.readySync = 0;
this->current = this->pending;
memset(&this->pending, 0, sizeof(this->pending));
texture->frameToken = this->current.frameToken;
updated = true;
}
haveTexture = externalFramePending(&this->current);
LG_UNLOCK(this->lock);
externalFrameInvokeReleases(releases, releaseCount);
if (!haveTexture)
return EGL_TEX_STATUS_NOTREADY;
return updated ? EGL_TEX_STATUS_UPDATED : EGL_TEX_STATUS_OK;
}
static EGL_TexStatus egl_texExternalGet(EGL_Texture * texture, GLuint * tex,
EGL_PixelFormat * format)
{
TexExternal * this = UPCAST(TexExternal, texture);
GLuint selected;
LG_LOCK(this->lock);
selected = this->current.texture;
LG_UNLOCK(this->lock);
if (!selected)
return EGL_TEX_STATUS_NOTREADY;
*tex = selected;
if (format)
*format = texture->format.pixFmt;
return EGL_TEX_STATUS_OK;
}
static EGL_TexStatus egl_texExternalBind(EGL_Texture * texture, GLuint unit)
{
GLuint selected;
EGL_TexStatus status = egl_texExternalGet(texture, &selected, NULL);
if (status != EGL_TEX_STATUS_OK)
return status;
egl_stateBindTexture(unit, GL_TEXTURE_2D, selected);
return EGL_TEX_STATUS_OK;
}
static void egl_texExternalReset(EGL_Texture * texture)
{
egl_texExternalCleanup(texture);
}
static void egl_texExternalDeactivate(EGL_Texture * texture)
{
TexExternal * this = UPCAST(TexExternal, texture);
LG_LOCK(this->lock);
this->deactivateRequested = true;
LG_UNLOCK(this->lock);
}
static void egl_texExternalMarkUsed(EGL_Texture * texture)
{
TexExternal * this = UPCAST(TexExternal, texture);
LG_LOCK(this->lock);
if (externalFramePending(&this->current))
{
if (this->current.useSync)
glDeleteSync(this->current.useSync);
this->current.useSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (!this->current.useSync)
{
DEBUG_GL_ERROR("Failed to fence external texture use");
glFinish();
}
else
glFlush();
}
LG_UNLOCK(this->lock);
}
const EGL_TextureOps EGL_TextureExternal =
{
.init = egl_texExternalInit,
.free = egl_texExternalFree,
.setup = egl_texExternalSetup,
.update = egl_texExternalUpdate,
.process = egl_texExternalProcess,
.get = egl_texExternalGet,
.bind = egl_texExternalBind,
.reset = egl_texExternalReset,
.deactivate = egl_texExternalDeactivate,
.poll = egl_texExternalPoll,
.markUsed = egl_texExternalMarkUsed,
};