Files
LookingGlass/client/renderers/EGL/texture_dmabuf.c
2026-11-06 14:34:25 +11:00

731 lines
21 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 "model.h"
#include "shader.h"
#include "state.h"
#include "util.h"
#include "common/array.h"
#include "egl_dynprocs.h"
#include "egldebug.h"
#include <string.h>
#include "hdr_compose.vert.h"
#include "downscale_linear.frag.h"
#define EGL_DMABUF_SNAPSHOT_COUNT (LGMP_Q_FRAME_BUFFER_LEN + 1)
struct ExternalImage
{
LG_ExternalFrame * externalFrame;
EGLImage image;
GLuint texture;
};
struct Snapshot
{
GLuint texture;
GLuint framebuffer;
GLsync copySync;
GLsync useSync;
LG_RendererFrameToken frameToken;
LG_ExternalFrame * externalFrame;
LG_FrameReleaseFn releaseFn;
void * releaseOpaque;
uint64_t releaseHandle;
};
typedef struct TexDMABUF
{
TextureBuffer base;
EGLDisplay display;
struct ExternalImage 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;
GLenum intFormat;
GLenum dataType;
}
TexDMABUF;
struct SnapshotRelease
{
LG_ExternalFrame * externalFrame;
LG_FrameReleaseFn fn;
void * opaque;
uint64_t handle;
};
const EGL_TextureOps EGL_TextureDMABUF;
static bool initDone = false;
static bool has24BitSupport = true;
static bool hasImportModifiers = true;
bool egl_texDMABUFSupported(EGLDisplay display, EGLint major, EGLint minor)
{
const char * extensions = eglQueryString(display, EGL_EXTENSIONS);
if (!g_egl_dynProcs.glEGLImageTargetTexture2DOES)
DEBUG_INFO("glEGLImageTargetTexture2DOES unavailable, DMA support disabled");
else if (!g_egl_dynProcs.eglCreateImage || !g_egl_dynProcs.eglDestroyImage)
DEBUG_INFO("eglCreateImage or eglDestroyImage unavailable, DMA support disabled");
else if (!extensions ||
!util_hasGLExt(extensions, "EGL_EXT_image_dma_buf_import"))
DEBUG_INFO("EGL_EXT_image_dma_buf_import unavailable, DMA support disabled");
else if ((major < 1 || (major == 1 && minor < 5)) &&
!util_hasGLExt(extensions, "EGL_KHR_image_base"))
DEBUG_INFO("Need EGL 1.5+ or EGL_KHR_image_base for eglCreateImage(KHR)");
else
return true;
return false;
}
// 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->externalFrame = snapshot->externalFrame;
release->fn = snapshot->releaseFn;
release->opaque = snapshot->releaseOpaque;
release->handle = snapshot->releaseHandle;
snapshot->externalFrame = NULL;
snapshot->releaseFn = NULL;
snapshot->releaseOpaque = NULL;
snapshot->releaseHandle = 0;
}
static void snapshotInvokeReleases(struct SnapshotRelease * releases,
int count)
{
for (int i = 0; i < count; ++i)
{
lgExternalFrameRelease(releases[i].externalFrame);
if (releases[i].fn)
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->externalFrame || 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)
{
g_egl_dynProcs.eglDestroyImage(this->display, this->images[i].image);
this->images[i].image = EGL_NO_IMAGE;
}
if (this->images[i].texture)
glDeleteTextures(1, &this->images[i].texture);
lgExternalFrameRelease(this->images[i].externalFrame);
this->images[i].externalFrame = NULL;
this->images[i].texture = 0;
}
this->renderIndex = -1;
parent->rIndex = -1;
texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
egl_stateInvalidateShared();
LG_UNLOCK(parent->copyLock);
snapshotInvokeReleases(releases, releaseCount);
}
// 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].image = EGL_NO_IMAGE;
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 (!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);
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_modelFree(&this->copyModel);
egl_shaderFree(&this->copyShader);
if (this->copySampler)
glDeleteSamplers(1, &this->copySampler);
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;
}
egl_texDMABUFCleanup(texture);
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;
}
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->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);
}
static EGLImage createImage(EGL_Texture * texture,
const LG_ExternalFrame * externalFrame)
{
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
int fd;
if (!lgExternalFrameQuery(externalFrame, LG_EXTERNAL_FRAME_DMABUF,
&fd, sizeof(fd)))
{
DEBUG_ERROR("Failed to query the DMABUF file descriptor");
return EGL_NO_IMAGE;
}
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);
DEBUG_ASSERT(update->externalFrame);
/* Retire completed copies before deciding whether a snapshot slot must be
* waited on and coalesced. */
egl_texDMABUFPoll(texture);
struct ExternalImage * externalImage = NULL;
for (int i = 0; i < ARRAY_LENGTH(this->images); ++i)
if (this->images[i].externalFrame == update->externalFrame)
{
externalImage = &this->images[i];
break;
}
if (!externalImage)
for (int i = 0; i < ARRAY_LENGTH(this->images); ++i)
if (!this->images[i].externalFrame)
{
externalImage = &this->images[i];
break;
}
if (!externalImage)
{
DEBUG_ERROR("No free DMABUF image slot");
return false;
}
if (unlikely(externalImage->image == EGL_NO_IMAGE))
{
bool setup = false;
if (texture->format.pixFmt == EGL_PF_RGB_24 && has24BitSupport)
{
externalImage->image = createImage(texture, update->externalFrame);
if (externalImage->image == EGL_NO_IMAGE)
{
DEBUG_INFO("Using 24-bit in 32-bit for DMA");
has24BitSupport = false;
setup = true;
}
}
if (!has24BitSupport && setup)
{
if (!texDMABUFSetup(texture))
return false;
externalImage = &this->images[0];
}
if (externalImage->image == EGL_NO_IMAGE)
externalImage->image = createImage(texture, update->externalFrame);
if (unlikely(externalImage->image == EGL_NO_IMAGE))
{
DEBUG_EGL_ERROR("Failed to create EGLImage for DMA transfer");
return false;
}
externalImage->externalFrame =
lgExternalFrameRetain(update->externalFrame);
glGenTextures(1, &externalImage->texture);
egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, externalImage->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, externalImage->image);
}
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)
{
index = i;
break;
}
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(index < 0))
{
LG_UNLOCK(parent->copyLock);
DEBUG_ERROR("No reusable DMABUF snapshot slot");
return false;
}
struct Snapshot * old = &this->snapshots[index];
waitSync(&old->copySync);
waitSync(&old->useSync);
if (old->externalFrame || old->releaseFn)
snapshotRelease(old, &releases[releaseCount++]);
old->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
}
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, externalImage->texture);
egl_shaderUse(this->copyShader);
egl_modelRender(this->copyModel);
snapshot->copySync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (unlikely(!snapshot->copySync))
{
DEBUG_GL_ERROR("Failed to fence DMABUF snapshot copy");
glFinish();
}
snapshot->frameToken = update->frameToken;
snapshot->externalFrame = lgExternalFrameRetain(update->externalFrame);
snapshot->releaseFn = update->releaseFn;
snapshot->releaseOpaque = update->releaseOpaque;
snapshot->releaseHandle = update->releaseHandle;
if (!snapshot->copySync &&
(snapshot->externalFrame || 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->externalFrame || 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,
{
haveImage = this->renderIndex >= 0;
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 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 = index;
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;
INTERLOCKED_SECTION(parent->copyLock,
{
index = this->renderIndex;
});
if (unlikely(index < 0))
return EGL_TEX_STATUS_NOTREADY;
*tex = this->snapshots[index].texture;
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_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);
}
const 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,
.reset = egl_texDMABUFReset,
.poll = egl_texDMABUFPoll,
.markUsed = egl_texDMABUFMarkUsed,
};