mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-09-01 19:53:54 +00:00
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
Add LGProtocol as a pinned submodule and consume its KVMFR protocol definitions throughout the client, host, IDD, OBS, and profiler. Keep Looking Glass framebuffer helpers local while removing duplicated protocol headers and migrating users to KVMFR-scoped types.
567 lines
14 KiB
C
567 lines
14 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_buffer.h"
|
|
#include "state.h"
|
|
|
|
#include "egldebug.h"
|
|
|
|
#include <string.h>
|
|
|
|
// forwards
|
|
extern const EGL_TextureOps EGL_TextureBuffer;
|
|
extern const EGL_TextureOps EGL_TextureBufferStream;
|
|
|
|
// internal functions
|
|
|
|
static uint64_t egl_texBufferDamageArea(const KVMFRFrameDamageRect * rect)
|
|
{
|
|
return (uint64_t)rect->width * rect->height;
|
|
}
|
|
|
|
static KVMFRFrameDamageRect egl_texBufferDamageUnion(
|
|
const KVMFRFrameDamageRect * a, const KVMFRFrameDamageRect * b)
|
|
{
|
|
const uint32_t left = min(a->x, b->x);
|
|
const uint32_t top = min(a->y, b->y);
|
|
const uint32_t right = max(a->x + a->width, b->x + b->width);
|
|
const uint32_t bottom = max(a->y + a->height, b->y + b->height);
|
|
|
|
return (KVMFRFrameDamageRect)
|
|
{
|
|
.x = left,
|
|
.y = top,
|
|
.width = right - left,
|
|
.height = bottom - top,
|
|
};
|
|
}
|
|
|
|
static bool egl_texBufferDamageTouches(const KVMFRFrameDamageRect * a,
|
|
const KVMFRFrameDamageRect * b)
|
|
{
|
|
return a->x <= b->x + b->width && b->x <= a->x + a->width &&
|
|
a->y <= b->y + b->height && b->y <= a->y + a->height;
|
|
}
|
|
|
|
static void egl_texBufferDamageAdd(TextureBuffer * this, int index,
|
|
const EGL_TexUpdate * update)
|
|
{
|
|
EGL_TexBufferDamage * damage = &this->damage[index];
|
|
const EGL_TexFormat * format = &this->base.format;
|
|
|
|
if (damage->full)
|
|
return;
|
|
|
|
if (update->x == 0 && update->y == 0 &&
|
|
update->width == format->width &&
|
|
update->height == format->height)
|
|
{
|
|
damage->full = true;
|
|
damage->count = 0;
|
|
return;
|
|
}
|
|
|
|
KVMFRFrameDamageRect rect =
|
|
{
|
|
.x = update->x,
|
|
.y = update->y,
|
|
.width = update->width,
|
|
.height = update->height,
|
|
};
|
|
|
|
for (;;)
|
|
{
|
|
for (int i = 0; i < damage->count;)
|
|
{
|
|
if (!egl_texBufferDamageTouches(&rect, &damage->rects[i]))
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
rect = egl_texBufferDamageUnion(&rect, &damage->rects[i]);
|
|
damage->rects[i] = damage->rects[--damage->count];
|
|
i = 0;
|
|
}
|
|
|
|
if (damage->count < EGL_TEX_BUFFER_DAMAGE_MAX)
|
|
break;
|
|
|
|
int best = 0;
|
|
uint64_t bestCost = UINT64_MAX;
|
|
for (int i = 0; i < damage->count; ++i)
|
|
{
|
|
const KVMFRFrameDamageRect merged =
|
|
egl_texBufferDamageUnion(&rect, &damage->rects[i]);
|
|
const uint64_t cost = egl_texBufferDamageArea(&merged) -
|
|
egl_texBufferDamageArea(&damage->rects[i]);
|
|
if (cost < bestCost)
|
|
{
|
|
best = i;
|
|
bestCost = cost;
|
|
}
|
|
}
|
|
|
|
rect = egl_texBufferDamageUnion(&rect, &damage->rects[best]);
|
|
damage->rects[best] = damage->rects[--damage->count];
|
|
}
|
|
|
|
damage->rects[damage->count++] = rect;
|
|
|
|
uint64_t area = 0;
|
|
for (int i = 0; i < damage->count; ++i)
|
|
area += egl_texBufferDamageArea(&damage->rects[i]);
|
|
|
|
const uint64_t fullArea = (uint64_t)format->width * format->height;
|
|
if (area * 4 >= fullArea * 3)
|
|
{
|
|
damage->full = true;
|
|
damage->count = 0;
|
|
}
|
|
}
|
|
|
|
static void egl_texBufferDamageReset(EGL_TexBufferDamage * damage)
|
|
{
|
|
damage->full = false;
|
|
damage->count = 0;
|
|
}
|
|
|
|
static void egl_texBuffer_cleanup(TextureBuffer * this)
|
|
{
|
|
egl_texUtilFreeBuffers(this->buf, this->texCount);
|
|
|
|
if (this->tex[0])
|
|
{
|
|
glDeleteTextures(this->texCount, this->tex);
|
|
egl_stateInvalidateShared();
|
|
}
|
|
|
|
for (int i = 0; i < EGL_TEX_BUFFER_MAX; ++i)
|
|
{
|
|
if (!this->sync[i])
|
|
continue;
|
|
|
|
glDeleteSync(this->sync[i]);
|
|
this->sync[i] = 0;
|
|
}
|
|
}
|
|
|
|
// common functions
|
|
|
|
bool egl_texBufferInit(EGL_Texture ** texture, EGL_TexType type,
|
|
EGLDisplay * display)
|
|
{
|
|
TextureBuffer * this;
|
|
if (!*texture)
|
|
{
|
|
this = calloc(1, sizeof(*this));
|
|
if (!this)
|
|
{
|
|
DEBUG_ERROR("Failed to malloc TexB");
|
|
return false;
|
|
}
|
|
*texture = &this->base;
|
|
this->free = true;
|
|
}
|
|
else
|
|
this = UPCAST(TextureBuffer, *texture);
|
|
|
|
this->texCount = 1;
|
|
return true;
|
|
}
|
|
|
|
void egl_texBufferFree(EGL_Texture * texture)
|
|
{
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
|
|
egl_texBuffer_cleanup(this);
|
|
LG_LOCK_FREE(this->copyLock);
|
|
|
|
if (this->free)
|
|
free(this);
|
|
}
|
|
|
|
bool egl_texBufferSetup(EGL_Texture * texture, const EGL_TexSetup * setup)
|
|
{
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
|
|
egl_texBuffer_cleanup(this);
|
|
|
|
glGenTextures(this->texCount, this->tex);
|
|
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
for(int i = 0; i < this->texCount; ++i)
|
|
{
|
|
egl_stateBindTexture(0, GL_TEXTURE_2D, this->tex[i]);
|
|
glTexImage2D(GL_TEXTURE_2D,
|
|
0,
|
|
texture->format.intFormat,
|
|
texture->format.width,
|
|
texture->format.height,
|
|
0,
|
|
texture->format.format,
|
|
texture->format.dataType,
|
|
NULL);
|
|
}
|
|
|
|
this->bufIndex = 0;
|
|
this->rIndex = -1;
|
|
texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
|
|
|
for (int i = 0; i < this->texCount; ++i)
|
|
{
|
|
this->buf[i].updated = false;
|
|
this->slotToken[i] = LG_RENDERER_FRAME_TOKEN_NONE;
|
|
egl_texBufferDamageReset(&this->damage[i]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool egl_texBufferUpdate(EGL_Texture * texture, const EGL_TexUpdate * update)
|
|
{
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
DEBUG_ASSERT(update->type == EGL_TEXTYPE_BUFFER);
|
|
|
|
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
egl_stateBindTexture(0, GL_TEXTURE_2D, this->tex[0]);
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, update->stride);
|
|
glTexSubImage2D(GL_TEXTURE_2D,
|
|
0,
|
|
update->x,
|
|
update->y,
|
|
update->width,
|
|
update->height,
|
|
texture->format.format,
|
|
texture->format.dataType,
|
|
update->buffer);
|
|
return true;
|
|
}
|
|
|
|
EGL_TexStatus egl_texBufferProcess(EGL_Texture * texture,
|
|
LG_RendererFrameToken frameTokenLimit)
|
|
{
|
|
(void)frameTokenLimit;
|
|
return EGL_TEX_STATUS_OK;
|
|
}
|
|
|
|
EGL_TexStatus egl_texBufferGet(EGL_Texture * texture, GLuint * tex,
|
|
EGL_PixelFormat * fmt)
|
|
{
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
*tex = this->tex[0];
|
|
return EGL_TEX_STATUS_OK;
|
|
}
|
|
|
|
// streaming functions
|
|
|
|
bool egl_texBufferStreamInit(EGL_Texture ** texture, EGL_TexType type,
|
|
EGLDisplay * display)
|
|
{
|
|
if (!egl_texBufferInit(texture, type, display))
|
|
return false;
|
|
|
|
TextureBuffer * this = UPCAST(TextureBuffer, *texture);
|
|
|
|
switch(type)
|
|
{
|
|
case EGL_TEXTYPE_BUFFER_STREAM:
|
|
case EGL_TEXTYPE_FRAMEBUFFER:
|
|
this->texCount = 2;
|
|
break;
|
|
|
|
case EGL_TEXTYPE_DMABUF:
|
|
this->texCount = LGMP_Q_FRAME_BUFFER_LEN;
|
|
break;
|
|
|
|
case EGL_TEXTYPE_BUFFER_MAP:
|
|
this->texCount = 1;
|
|
break;
|
|
|
|
default:
|
|
DEBUG_UNREACHABLE();
|
|
}
|
|
|
|
LG_LOCK_INIT(this->copyLock);
|
|
return true;
|
|
}
|
|
|
|
bool egl_texBufferStreamSetup(EGL_Texture * texture, const EGL_TexSetup * setup)
|
|
{
|
|
if (!egl_texBufferSetup(texture, setup))
|
|
return false;
|
|
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
return egl_texUtilGenBuffers(&texture->format, this->buf, this->texCount);
|
|
}
|
|
|
|
bool egl_texBufferStreamLock(TextureBuffer * this)
|
|
{
|
|
for (;;)
|
|
{
|
|
LG_LOCK(this->copyLock);
|
|
|
|
GLsync sync = this->sync[this->bufIndex];
|
|
if (!sync)
|
|
return true;
|
|
|
|
/* The slot cannot be submitted again until this function marks it
|
|
* updated, so ownership of its fence can be transferred while unlocked. */
|
|
this->sync[this->bufIndex] = 0;
|
|
LG_UNLOCK(this->copyLock);
|
|
|
|
GLenum result = glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
|
|
glDeleteSync(sync);
|
|
|
|
switch(result)
|
|
{
|
|
case GL_ALREADY_SIGNALED:
|
|
case GL_CONDITION_SATISFIED:
|
|
break;
|
|
|
|
case GL_TIMEOUT_EXPIRED:
|
|
DEBUG_ERROR("Upload buffer sync unexpectedly timed out");
|
|
return false;
|
|
|
|
case GL_WAIT_FAILED:
|
|
case GL_INVALID_VALUE:
|
|
DEBUG_GL_ERROR("glClientWaitSync failed while reusing upload buffer");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool egl_texBufferStreamUpdate(EGL_Texture * texture,
|
|
const EGL_TexUpdate * update)
|
|
{
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
DEBUG_ASSERT(update->type == EGL_TEXTYPE_BUFFER);
|
|
|
|
if (!egl_texBufferStreamLock(this))
|
|
return false;
|
|
|
|
uint8_t * dst = this->buf[this->bufIndex].map +
|
|
texture->format.pitch * update->y +
|
|
update->x * texture->format.bpp;
|
|
|
|
if (update->topDown)
|
|
{
|
|
const uint8_t * src = update->buffer;
|
|
for(int y = 0; y < update->height; ++y)
|
|
{
|
|
memcpy(dst, src, update->width * texture->format.bpp);
|
|
dst += texture->format.pitch;
|
|
src += update->pitch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const uint8_t * src = update->buffer + update->pitch * update->height;
|
|
for(int y = 0; y < update->height; ++y)
|
|
{
|
|
src -= update->pitch;
|
|
memcpy(dst, src, update->width * texture->format.bpp);
|
|
dst += texture->format.pitch;
|
|
}
|
|
}
|
|
|
|
this->slotToken[this->bufIndex] = update->frameToken;
|
|
this->buf[this->bufIndex].updated = true;
|
|
egl_texBufferDamageAdd(this, this->bufIndex, update);
|
|
LG_UNLOCK(this->copyLock);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool egl_texBufferStreamFill(EGL_Texture * texture,
|
|
int x, int y, int width, int height, uint32_t color)
|
|
{
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
|
|
DEBUG_ASSERT(texture->format.bpp == sizeof(color));
|
|
if (texture->format.bpp != sizeof(color))
|
|
return false;
|
|
|
|
if (!egl_texBufferStreamLock(this))
|
|
return false;
|
|
|
|
uint8_t * row = this->buf[this->bufIndex].map +
|
|
texture->format.pitch * y + x * texture->format.bpp;
|
|
for (int dy = 0; dy < height; ++dy)
|
|
{
|
|
uint32_t * dst = (uint32_t *)row;
|
|
for (int dx = 0; dx < width; ++dx)
|
|
dst[dx] = color;
|
|
row += texture->format.pitch;
|
|
}
|
|
|
|
const EGL_TexUpdate update =
|
|
{
|
|
.x = x,
|
|
.y = y,
|
|
.width = width,
|
|
.height = height,
|
|
};
|
|
|
|
this->slotToken[this->bufIndex] = LG_RENDERER_FRAME_TOKEN_NONE;
|
|
this->buf[this->bufIndex].updated = true;
|
|
egl_texBufferDamageAdd(this, this->bufIndex, &update);
|
|
LG_UNLOCK(this->copyLock);
|
|
|
|
return true;
|
|
}
|
|
|
|
EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture,
|
|
LG_RendererFrameToken frameTokenLimit)
|
|
{
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
|
|
LG_LOCK(this->copyLock);
|
|
|
|
const int index = this->bufIndex;
|
|
GLuint tex = this->tex[index];
|
|
EGL_TexBuffer * buffer = &this->buf[index];
|
|
|
|
if (!buffer->updated ||
|
|
!egl_textureFrameAllowed(this->slotToken[index], frameTokenLimit))
|
|
{
|
|
LG_UNLOCK(this->copyLock);
|
|
return EGL_TEX_STATUS_OK;
|
|
}
|
|
|
|
DEBUG_ASSERT(!this->sync[index]);
|
|
|
|
this->rIndex = index;
|
|
texture->frameToken = this->slotToken[index];
|
|
if (++this->bufIndex == this->texCount)
|
|
this->bufIndex = 0;
|
|
buffer->updated = false;
|
|
|
|
/* With more than one slot the producer can fill the next PBO while this
|
|
* upload is queued. A single-slot texture must remain locked until its
|
|
* fence exists so the mapped buffer cannot be overwritten concurrently. */
|
|
const bool keepLocked = this->texCount == 1;
|
|
if (!keepLocked)
|
|
LG_UNLOCK(this->copyLock);
|
|
|
|
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
|
|
egl_stateBindTexture(0, GL_TEXTURE_2D, tex);
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->format.stride);
|
|
|
|
EGL_TexBufferDamage * damage = &this->damage[index];
|
|
if (this->texCount > 1 || damage->full || !damage->count)
|
|
glTexSubImage2D(GL_TEXTURE_2D,
|
|
0, 0, 0,
|
|
texture->format.width,
|
|
texture->format.height,
|
|
texture->format.format,
|
|
texture->format.dataType,
|
|
(const void *)0);
|
|
else
|
|
for (int i = 0; i < damage->count; ++i)
|
|
{
|
|
const KVMFRFrameDamageRect * rect = &damage->rects[i];
|
|
const uintptr_t offset =
|
|
(uintptr_t)rect->y * texture->format.pitch +
|
|
(uintptr_t)rect->x * texture->format.bpp;
|
|
glTexSubImage2D(GL_TEXTURE_2D,
|
|
0,
|
|
rect->x,
|
|
rect->y,
|
|
rect->width,
|
|
rect->height,
|
|
texture->format.format,
|
|
texture->format.dataType,
|
|
(const void *)offset);
|
|
}
|
|
|
|
/* Keep damage intact until all of its upload commands have been issued. */
|
|
egl_texBufferDamageReset(damage);
|
|
this->sync[index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
if (unlikely(!this->sync[index]))
|
|
{
|
|
DEBUG_GL_ERROR("Failed to create upload buffer sync");
|
|
glFinish();
|
|
if (keepLocked)
|
|
LG_UNLOCK(this->copyLock);
|
|
return EGL_TEX_STATUS_ERROR;
|
|
}
|
|
|
|
/* A different shared context may wait when this PBO is reused. Flushing
|
|
* here makes the fence visible without making the render thread wait. */
|
|
glFlush();
|
|
|
|
if (keepLocked)
|
|
LG_UNLOCK(this->copyLock);
|
|
|
|
return EGL_TEX_STATUS_UPDATED;
|
|
}
|
|
|
|
EGL_TexStatus egl_texBufferStreamGet(EGL_Texture * texture, GLuint * tex,
|
|
EGL_PixelFormat * fmt)
|
|
{
|
|
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
|
|
|
if (this->rIndex == -1)
|
|
return EGL_TEX_STATUS_NOTREADY;
|
|
|
|
/* Upload and sampling are ordered in the same context. Waiting here would
|
|
* only stall submission of the draw and delay presentation. */
|
|
*tex = this->tex[this->rIndex];
|
|
return EGL_TEX_STATUS_OK;
|
|
}
|
|
|
|
EGL_TexStatus egl_texBufferBind(EGL_Texture * texture, GLuint unit)
|
|
{
|
|
GLuint tex;
|
|
EGL_TexStatus status;
|
|
|
|
if ((status = texture->ops.get(texture, &tex, NULL)) != EGL_TEX_STATUS_OK)
|
|
return status;
|
|
|
|
egl_stateBindTexture(unit, GL_TEXTURE_2D, tex);
|
|
return EGL_TEX_STATUS_OK;
|
|
}
|
|
|
|
|
|
const EGL_TextureOps EGL_TextureBuffer =
|
|
{
|
|
.init = egl_texBufferInit,
|
|
.free = egl_texBufferFree,
|
|
.setup = egl_texBufferSetup,
|
|
.update = egl_texBufferUpdate,
|
|
.process = egl_texBufferProcess,
|
|
.get = egl_texBufferGet,
|
|
.bind = egl_texBufferBind
|
|
};
|
|
|
|
const EGL_TextureOps EGL_TextureBufferStream =
|
|
{
|
|
.init = egl_texBufferStreamInit,
|
|
.free = egl_texBufferFree,
|
|
.setup = egl_texBufferStreamSetup,
|
|
.update = egl_texBufferStreamUpdate,
|
|
.process = egl_texBufferStreamProcess,
|
|
.get = egl_texBufferStreamGet,
|
|
.bind = egl_texBufferBind
|
|
};
|