mirror of
https://github.com/gnif/LookingGlass.git
synced 2025-01-10 22:33:58 +00:00
[client] egl: replace monolithic EGLTexture with modular version
The way things were handled in EGLTexture is not only very hard to follow, but broken. This change set breaks up EGLTexture into a modular design making it easier to implement the various versions. Note that DMABUF is currently broken and needs to be re-implemented.
This commit is contained in:
parent
e23144aecd
commit
13d9c84dc9
19 changed files with 910 additions and 583 deletions
|
@ -110,9 +110,9 @@ typedef void (* LG_RendererOnRestart )(void * opaque);
|
||||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate);
|
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate);
|
||||||
typedef bool (* LG_RendererOnMouseShape )(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
typedef bool (* LG_RendererOnMouseShape )(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
||||||
typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visible , const int x, const int y);
|
typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visible , const int x, const int y);
|
||||||
typedef bool (* LG_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format, bool useDMA);
|
typedef bool (* LG_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format);
|
||||||
typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD, const FrameDamageRect * damage, int damageCount);
|
typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD, const FrameDamageRect * damage, int damageCount);
|
||||||
typedef bool (* LG_RendererRenderStartup)(void * opaque);
|
typedef bool (* LG_RendererRenderStartup)(void * opaque, bool useDMA);
|
||||||
typedef bool (* LG_RendererNeedsRender )(void * opaque);
|
typedef bool (* LG_RendererNeedsRender )(void * opaque);
|
||||||
typedef bool (* LG_RendererRender )(void * opaque, LG_RendererRotate rotate, const bool newFrame, const bool invalidateWindow);
|
typedef bool (* LG_RendererRender )(void * opaque, LG_RendererRotate rotate, const bool newFrame, const bool invalidateWindow);
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
|
#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
|
||||||
#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; })
|
#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; })
|
||||||
|
|
||||||
|
#define UPCAST(type, x) \
|
||||||
|
(type *)((uintptr_t)(x) - offsetof(type, base))
|
||||||
|
|
||||||
// reads the specified file into a new buffer
|
// reads the specified file into a new buffer
|
||||||
// the callee must free the buffer
|
// the callee must free the buffer
|
||||||
bool util_fileGetContents(const char * filename, char ** buffer, size_t * length);
|
bool util_fileGetContents(const char * filename, char ** buffer, size_t * length);
|
||||||
|
|
|
@ -36,7 +36,11 @@ add_library(renderer_EGL STATIC
|
||||||
egl.c
|
egl.c
|
||||||
egldebug.c
|
egldebug.c
|
||||||
shader.c
|
shader.c
|
||||||
|
texture_util.c
|
||||||
texture.c
|
texture.c
|
||||||
|
texture_buffer.c
|
||||||
|
texture_framebuffer.c
|
||||||
|
texture_dmabuf.c
|
||||||
model.c
|
model.c
|
||||||
desktop.c
|
desktop.c
|
||||||
cursor.c
|
cursor.c
|
||||||
|
|
|
@ -84,7 +84,7 @@ static bool egl_cursor_tex_init(struct CursorTex * t,
|
||||||
const char * vertex_code , size_t vertex_size,
|
const char * vertex_code , size_t vertex_size,
|
||||||
const char * fragment_code, size_t fragment_size)
|
const char * fragment_code, size_t fragment_size)
|
||||||
{
|
{
|
||||||
if (!egl_texture_init(&t->texture, NULL))
|
if (!egl_texture_init(&t->texture, NULL, EGL_TEXTYPE_BUFFER, false))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to initialize the cursor texture");
|
DEBUG_ERROR("Failed to initialize the cursor texture");
|
||||||
return false;
|
return false;
|
||||||
|
@ -256,7 +256,7 @@ struct CursorState egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rota
|
||||||
|
|
||||||
case LG_CURSOR_COLOR:
|
case LG_CURSOR_COLOR:
|
||||||
{
|
{
|
||||||
egl_texture_setup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false, false);
|
egl_texture_setup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride);
|
||||||
egl_texture_update(cursor->norm.texture, data);
|
egl_texture_update(cursor->norm.texture, data);
|
||||||
egl_model_set_texture(cursor->model, cursor->norm.texture);
|
egl_model_set_texture(cursor->model, cursor->norm.texture);
|
||||||
break;
|
break;
|
||||||
|
@ -280,8 +280,8 @@ struct CursorState egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rota
|
||||||
xor[y * cursor->width + x] = xorMask;
|
xor[y * cursor->width + x] = xorMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
egl_texture_setup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
|
egl_texture_setup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4);
|
||||||
egl_texture_setup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
|
egl_texture_setup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4);
|
||||||
egl_texture_update(cursor->norm.texture, (uint8_t *)and);
|
egl_texture_update(cursor->norm.texture, (uint8_t *)and);
|
||||||
egl_texture_update(cursor->mono.texture, (uint8_t *)xor);
|
egl_texture_update(cursor->mono.texture, (uint8_t *)xor);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -104,7 +104,7 @@ static bool egl_init_desktop_shader(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
|
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display, bool useDMA)
|
||||||
{
|
{
|
||||||
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
|
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
|
||||||
if (!*desktop)
|
if (!*desktop)
|
||||||
|
@ -116,7 +116,8 @@ bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
|
||||||
memset(*desktop, 0, sizeof(EGL_Desktop));
|
memset(*desktop, 0, sizeof(EGL_Desktop));
|
||||||
(*desktop)->display = display;
|
(*desktop)->display = display;
|
||||||
|
|
||||||
if (!egl_texture_init(&(*desktop)->texture, display))
|
if (!egl_texture_init(&(*desktop)->texture, display,
|
||||||
|
useDMA ? EGL_TEXTYPE_DMABUF : EGL_TEXTYPE_FRAMEBUFFER, true))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to initialize the desktop texture");
|
DEBUG_ERROR("Failed to initialize the desktop texture");
|
||||||
return false;
|
return false;
|
||||||
|
@ -209,7 +210,7 @@ void egl_desktop_free(EGL_Desktop ** desktop)
|
||||||
*desktop = NULL;
|
*desktop = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA)
|
bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format)
|
||||||
{
|
{
|
||||||
enum EGL_PixelFormat pixFmt;
|
enum EGL_PixelFormat pixFmt;
|
||||||
switch(format.type)
|
switch(format.type)
|
||||||
|
@ -247,9 +248,7 @@ bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bo
|
||||||
pixFmt,
|
pixFmt,
|
||||||
format.width,
|
format.width,
|
||||||
format.height,
|
format.height,
|
||||||
format.pitch,
|
format.pitch
|
||||||
true, // streaming texture
|
|
||||||
useDMA
|
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to setup the desktop texture");
|
DEBUG_ERROR("Failed to setup the desktop texture");
|
||||||
|
@ -272,13 +271,6 @@ bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dm
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EGL_TexStatus status;
|
|
||||||
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
|
|
||||||
{
|
|
||||||
if (status != EGL_TEX_STATUS_NOTREADY)
|
|
||||||
DEBUG_ERROR("Failed to process the desktop texture");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,6 +281,13 @@ bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
|
||||||
if (!desktop->shader)
|
if (!desktop->shader)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
enum EGL_TexStatus status;
|
||||||
|
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
|
||||||
|
{
|
||||||
|
if (status != EGL_TEX_STATUS_NOTREADY)
|
||||||
|
DEBUG_ERROR("Failed to process the desktop texture");
|
||||||
|
}
|
||||||
|
|
||||||
int scaleAlgo = EGL_SCALE_NEAREST;
|
int scaleAlgo = EGL_SCALE_NEAREST;
|
||||||
|
|
||||||
switch (desktop->scaleAlgo)
|
switch (desktop->scaleAlgo)
|
||||||
|
|
|
@ -36,10 +36,10 @@ enum EGL_DesktopScaleType
|
||||||
struct Option;
|
struct Option;
|
||||||
bool egl_desktop_scale_validate(struct Option * opt, const char ** error);
|
bool egl_desktop_scale_validate(struct Option * opt, const char ** error);
|
||||||
|
|
||||||
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display);
|
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display, bool useDMA);
|
||||||
void egl_desktop_free(EGL_Desktop ** desktop);
|
void egl_desktop_free(EGL_Desktop ** desktop);
|
||||||
|
|
||||||
bool egl_desktop_setup (EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA);
|
bool egl_desktop_setup (EGL_Desktop * desktop, const LG_RendererFormat format);
|
||||||
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd);
|
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd);
|
||||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
|
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
|
||||||
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
||||||
|
|
|
@ -464,7 +464,7 @@ static bool egl_on_mouse_event(void * opaque, const bool visible, const int x, c
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool egl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
|
static bool egl_on_frame_format(void * opaque, const LG_RendererFormat format)
|
||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||||
|
@ -494,7 +494,7 @@ static bool egl_on_frame_format(void * opaque, const LG_RendererFormat format, b
|
||||||
egl_update_scale_type(this);
|
egl_update_scale_type(this);
|
||||||
egl_damage_setup(this->damage, format.width, format.height);
|
egl_damage_setup(this->damage, format.width, format.height);
|
||||||
|
|
||||||
return egl_desktop_setup(this->desktop, format, useDMA);
|
return egl_desktop_setup(this->desktop, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool egl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd,
|
static bool egl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd,
|
||||||
|
@ -603,7 +603,7 @@ static void debugCallback(GLenum source, GLenum type, GLuint id,
|
||||||
DEBUG_PRINT(level, "GL message (source: %s, type: %s): %s", sourceName, typeName, message);
|
DEBUG_PRINT(level, "GL message (source: %s, type: %s): %s", sourceName, typeName, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool egl_render_startup(void * opaque)
|
static bool egl_render_startup(void * opaque, bool useDMA)
|
||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
|
|
||||||
|
@ -767,7 +767,7 @@ static bool egl_render_startup(void * opaque)
|
||||||
|
|
||||||
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
|
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
|
||||||
|
|
||||||
if (!egl_desktop_init(&this->desktop, this->display))
|
if (!egl_desktop_init(&this->desktop, this->display, useDMA))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to initialize the desktop");
|
DEBUG_ERROR("Failed to initialize the desktop");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -221,6 +221,5 @@ void update_uniform_bindings(EGL_Model * model)
|
||||||
if (!model->shader || !model->texture)
|
if (!model->shader || !model->texture)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const int count = egl_texture_count(model->texture);
|
egl_shader_associate_textures(model->shader, 1);
|
||||||
egl_shader_associate_textures(model->shader, count);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,580 +19,116 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "texture.h"
|
#include "texture.h"
|
||||||
#include "common/debug.h"
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "shader.h"
|
||||||
#include "common/framebuffer.h"
|
#include "common/framebuffer.h"
|
||||||
#include "egl_dynprocs.h"
|
|
||||||
#include "egldebug.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <EGL/egl.h>
|
||||||
#include <string.h>
|
#include <EGL/eglext.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdatomic.h>
|
|
||||||
|
|
||||||
/**
|
#include "texture_buffer.h"
|
||||||
* the following comes from drm_fourcc.h and is included here to avoid the
|
|
||||||
* external dependency for the few simple defines we need
|
|
||||||
*/
|
|
||||||
#define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \
|
|
||||||
((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
|
|
||||||
#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4')
|
|
||||||
#define DRM_FORMAT_ABGR8888 fourcc_code('A', 'B', '2', '4')
|
|
||||||
#define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0')
|
|
||||||
#define DRM_FORMAT_ABGR16161616F fourcc_code('A', 'B', '4', 'H')
|
|
||||||
|
|
||||||
/* this must be a multiple of 2 */
|
extern const EGL_TextureOps EGL_TextureBuffer;
|
||||||
#define BUFFER_COUNT 4
|
extern const EGL_TextureOps EGL_TextureBufferStream;
|
||||||
|
extern const EGL_TextureOps EGL_TextureFrameBuffer;
|
||||||
|
extern const EGL_TextureOps EGL_TextureDMABUF;
|
||||||
|
|
||||||
struct Buffer
|
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display,
|
||||||
|
EGL_TexType type, bool streaming)
|
||||||
{
|
{
|
||||||
bool hasPBO;
|
const EGL_TextureOps * ops;
|
||||||
GLuint pbo;
|
|
||||||
void * map;
|
|
||||||
GLsync sync;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BufferState
|
switch(type)
|
||||||
{
|
|
||||||
_Atomic(uint8_t) w, u, s, d;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EGL_Texture
|
|
||||||
{
|
|
||||||
EGLDisplay * display;
|
|
||||||
|
|
||||||
enum EGL_PixelFormat pixFmt;
|
|
||||||
size_t bpp;
|
|
||||||
bool streaming;
|
|
||||||
bool dma;
|
|
||||||
bool ready;
|
|
||||||
|
|
||||||
GLuint sampler;
|
|
||||||
size_t width, height, stride, pitch;
|
|
||||||
GLenum intFormat;
|
|
||||||
GLenum format;
|
|
||||||
GLenum dataType;
|
|
||||||
unsigned int fourcc;
|
|
||||||
size_t pboBufferSize;
|
|
||||||
|
|
||||||
struct BufferState state;
|
|
||||||
int bufferCount;
|
|
||||||
GLuint tex[BUFFER_COUNT];
|
|
||||||
struct Buffer buf[BUFFER_COUNT];
|
|
||||||
|
|
||||||
size_t dmaImageCount;
|
|
||||||
size_t dmaImageUsed;
|
|
||||||
struct
|
|
||||||
{
|
{
|
||||||
int fd;
|
case EGL_TEXTYPE_BUFFER:
|
||||||
EGLImage image;
|
ops = streaming ? &EGL_TextureBufferStream : &EGL_TextureBuffer;
|
||||||
}
|
|
||||||
* dmaImages;
|
|
||||||
|
|
||||||
GLuint dmaFBO;
|
|
||||||
GLuint dmaTex;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display)
|
|
||||||
{
|
|
||||||
*texture = (EGL_Texture *)malloc(sizeof(EGL_Texture));
|
|
||||||
if (!*texture)
|
|
||||||
{
|
|
||||||
DEBUG_ERROR("Failed to malloc EGL_Texture");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(*texture, 0, sizeof(EGL_Texture));
|
|
||||||
(*texture)->display = display;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void egl_texture_free(EGL_Texture ** texture)
|
|
||||||
{
|
|
||||||
if (!*texture)
|
|
||||||
return;
|
|
||||||
|
|
||||||
glDeleteSamplers(1, &(*texture)->sampler);
|
|
||||||
|
|
||||||
for(int i = 0; i < (*texture)->bufferCount; ++i)
|
|
||||||
{
|
|
||||||
struct Buffer * b = &(*texture)->buf[i];
|
|
||||||
if (b->hasPBO)
|
|
||||||
{
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, b->pbo);
|
|
||||||
if ((*texture)->buf[i].map)
|
|
||||||
{
|
|
||||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
|
||||||
(*texture)->buf[i].map = NULL;
|
|
||||||
}
|
|
||||||
glDeleteBuffers(1, &b->pbo);
|
|
||||||
if (b->sync)
|
|
||||||
glDeleteSync(b->sync);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
||||||
glDeleteTextures((*texture)->bufferCount, (*texture)->tex);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < (*texture)->dmaImageUsed; ++i)
|
|
||||||
eglDestroyImage((*texture)->display, (*texture)->dmaImages[i].image);
|
|
||||||
free((*texture)->dmaImages);
|
|
||||||
|
|
||||||
free(*texture);
|
|
||||||
*texture = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
|
|
||||||
{
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
|
|
||||||
texture->buf[i].map = glMapBufferRange(
|
|
||||||
GL_PIXEL_UNPACK_BUFFER,
|
|
||||||
0,
|
|
||||||
texture->pboBufferSize,
|
|
||||||
GL_MAP_WRITE_BIT |
|
|
||||||
GL_MAP_UNSYNCHRONIZED_BIT |
|
|
||||||
GL_MAP_INVALIDATE_BUFFER_BIT |
|
|
||||||
GL_MAP_PERSISTENT_BIT_EXT |
|
|
||||||
GL_MAP_COHERENT_BIT_EXT
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!texture->buf[i].map)
|
|
||||||
{
|
|
||||||
DEBUG_GL_ERROR("glMapBufferRange failed for %d of %lu bytes", i,
|
|
||||||
texture->pboBufferSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
||||||
|
|
||||||
return texture->buf[i].map;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void egl_texture_unmap(EGL_Texture * texture, uint8_t i)
|
|
||||||
{
|
|
||||||
if (!texture->buf[i].map)
|
|
||||||
return;
|
|
||||||
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
|
|
||||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
||||||
texture->buf[i].map = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming, bool useDMA)
|
|
||||||
{
|
|
||||||
if (texture->streaming && !useDMA)
|
|
||||||
{
|
|
||||||
for(int i = 0; i < texture->bufferCount; ++i)
|
|
||||||
{
|
|
||||||
egl_texture_unmap(texture, i);
|
|
||||||
if (texture->buf[i].hasPBO)
|
|
||||||
{
|
|
||||||
glDeleteBuffers(1, &texture->buf[i].pbo);
|
|
||||||
texture->buf[i].hasPBO = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
texture->pixFmt = pixFmt;
|
|
||||||
texture->width = width;
|
|
||||||
texture->height = height;
|
|
||||||
texture->stride = stride;
|
|
||||||
texture->streaming = streaming;
|
|
||||||
texture->bufferCount = streaming ? BUFFER_COUNT : 1;
|
|
||||||
texture->dma = useDMA;
|
|
||||||
texture->ready = false;
|
|
||||||
|
|
||||||
atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed);
|
|
||||||
atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed);
|
|
||||||
atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed);
|
|
||||||
atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed);
|
|
||||||
|
|
||||||
switch(pixFmt)
|
|
||||||
{
|
|
||||||
case EGL_PF_BGRA:
|
|
||||||
texture->bpp = 4;
|
|
||||||
texture->format = GL_BGRA_EXT;
|
|
||||||
texture->intFormat = GL_BGRA_EXT;
|
|
||||||
texture->dataType = GL_UNSIGNED_BYTE;
|
|
||||||
texture->fourcc = DRM_FORMAT_ARGB8888;
|
|
||||||
texture->pboBufferSize = height * stride;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EGL_PF_RGBA:
|
case EGL_TEXTYPE_FRAMEBUFFER:
|
||||||
texture->bpp = 4;
|
assert(streaming);
|
||||||
texture->format = GL_RGBA;
|
ops = &EGL_TextureFrameBuffer;
|
||||||
texture->intFormat = GL_RGBA;
|
|
||||||
texture->dataType = GL_UNSIGNED_BYTE;
|
|
||||||
texture->fourcc = DRM_FORMAT_ABGR8888;
|
|
||||||
texture->pboBufferSize = height * stride;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EGL_PF_RGBA10:
|
case EGL_TEXTYPE_DMABUF:
|
||||||
texture->bpp = 4;
|
assert(streaming);
|
||||||
texture->format = GL_RGBA;
|
ops = &EGL_TextureDMABUF;
|
||||||
texture->intFormat = GL_RGB10_A2;
|
|
||||||
texture->dataType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
|
||||||
texture->fourcc = DRM_FORMAT_BGRA1010102;
|
|
||||||
texture->pboBufferSize = height * stride;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EGL_PF_RGBA16F:
|
|
||||||
texture->bpp = 8;
|
|
||||||
texture->format = GL_RGBA;
|
|
||||||
texture->intFormat = GL_RGBA16F;
|
|
||||||
texture->dataType = GL_HALF_FLOAT;
|
|
||||||
texture->fourcc = DRM_FORMAT_ABGR16161616F;
|
|
||||||
texture->pboBufferSize = height * stride;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
DEBUG_ERROR("Unsupported pixel format");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
texture->pitch = stride / texture->bpp;
|
if (!ops->init(texture, display))
|
||||||
|
return false;
|
||||||
if (texture->tex[0])
|
|
||||||
glDeleteTextures(texture->bufferCount, texture->tex);
|
|
||||||
glGenTextures(texture->bufferCount, texture->tex);
|
|
||||||
|
|
||||||
if (!texture->sampler)
|
|
||||||
{
|
|
||||||
glGenSamplers(1, &texture->sampler);
|
|
||||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
|
||||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useDMA)
|
|
||||||
{
|
|
||||||
if (texture->dmaFBO)
|
|
||||||
glDeleteFramebuffers(1, &texture->dmaFBO);
|
|
||||||
if (texture->dmaTex)
|
|
||||||
glDeleteTextures(1, &texture->dmaTex);
|
|
||||||
glGenFramebuffers(1, &texture->dmaFBO);
|
|
||||||
glGenTextures(1, &texture->dmaTex);
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->tex[0]);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->width,
|
|
||||||
texture->height, 0, texture->format, texture->dataType, NULL);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < texture->dmaImageUsed; ++i)
|
|
||||||
eglDestroyImage(texture->display, texture->dmaImages[i].image);
|
|
||||||
texture->dmaImageUsed = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < texture->bufferCount; ++i)
|
|
||||||
{
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->tex[i]);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->width,
|
|
||||||
texture->height, 0, texture->format, texture->dataType, NULL);
|
|
||||||
}
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
if (!streaming)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for(int i = 0; i < texture->bufferCount; ++i)
|
|
||||||
{
|
|
||||||
glGenBuffers(1, &texture->buf[i].pbo);
|
|
||||||
texture->buf[i].hasPBO = true;
|
|
||||||
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
|
|
||||||
glBufferStorageEXT(
|
|
||||||
GL_PIXEL_UNPACK_BUFFER,
|
|
||||||
texture->pboBufferSize,
|
|
||||||
NULL,
|
|
||||||
GL_MAP_WRITE_BIT |
|
|
||||||
GL_MAP_PERSISTENT_BIT_EXT |
|
|
||||||
GL_MAP_COHERENT_BIT_EXT
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!egl_texture_map(texture, i))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
(*texture)->ops = ops;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void egl_warn_slow(void)
|
void egl_texture_free(EGL_Texture ** tex)
|
||||||
{
|
{
|
||||||
static bool warnDone = false;
|
(*tex)->ops->free(*tex);
|
||||||
if (!warnDone)
|
*tex = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt,
|
||||||
|
size_t width, size_t height, size_t stride)
|
||||||
|
{
|
||||||
|
const struct EGL_TexSetup setup =
|
||||||
{
|
{
|
||||||
warnDone = true;
|
.pixFmt = pixFmt,
|
||||||
DEBUG_BREAK();
|
.width = width,
|
||||||
DEBUG_WARN("The guest is providing updates faster then your computer can display them");
|
.height = height,
|
||||||
DEBUG_WARN("This is a hardware limitation, expect microstutters & frame skips");
|
.stride = stride
|
||||||
DEBUG_BREAK();
|
};
|
||||||
}
|
texture->size = height * stride;
|
||||||
|
return texture->ops->setup(texture, &setup);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
|
bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
|
||||||
{
|
{
|
||||||
if (texture->streaming)
|
const struct EGL_TexUpdate update =
|
||||||
{
|
{
|
||||||
const uint8_t sw =
|
.type = EGL_TEXTYPE_BUFFER,
|
||||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
.buffer = buffer
|
||||||
|
};
|
||||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
return texture->ops->update(texture, &update);
|
||||||
{
|
|
||||||
egl_warn_slow();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t b = sw % BUFFER_COUNT;
|
|
||||||
memcpy(texture->buf[b].map, buffer, texture->pboBufferSize);
|
|
||||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->tex[0]);
|
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->pitch);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height,
|
|
||||||
texture->format, texture->dataType, buffer);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame)
|
bool egl_texture_update_from_frame(EGL_Texture * texture,
|
||||||
|
const FrameBuffer * frame)
|
||||||
{
|
{
|
||||||
if (!texture->streaming)
|
const struct EGL_TexUpdate update =
|
||||||
return false;
|
|
||||||
|
|
||||||
const uint8_t sw =
|
|
||||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
|
||||||
|
|
||||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
|
||||||
{
|
{
|
||||||
egl_warn_slow();
|
.type = EGL_TEXTYPE_FRAMEBUFFER,
|
||||||
return true;
|
.frame = frame
|
||||||
}
|
};
|
||||||
|
return texture->ops->update(texture, &update);
|
||||||
const uint8_t b = sw % BUFFER_COUNT;
|
|
||||||
|
|
||||||
framebuffer_read(
|
|
||||||
frame,
|
|
||||||
texture->buf[b].map,
|
|
||||||
texture->stride,
|
|
||||||
texture->height,
|
|
||||||
texture->width,
|
|
||||||
texture->bpp,
|
|
||||||
texture->stride
|
|
||||||
);
|
|
||||||
|
|
||||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool egl_texture_update_from_dma(EGL_Texture * texture, const FrameBuffer * frame, const int dmaFd)
|
bool egl_texture_update_from_dma(EGL_Texture * texture,
|
||||||
|
const FrameBuffer * frame, const int dmaFd)
|
||||||
{
|
{
|
||||||
if (!texture->streaming)
|
const struct EGL_TexUpdate update =
|
||||||
return false;
|
|
||||||
|
|
||||||
const uint8_t sw =
|
|
||||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
|
||||||
|
|
||||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
|
||||||
{
|
{
|
||||||
egl_warn_slow();
|
.type = EGL_TEXTYPE_DMABUF,
|
||||||
return true;
|
.dmaFD = dmaFd
|
||||||
}
|
};
|
||||||
|
|
||||||
EGLImage image = EGL_NO_IMAGE;
|
|
||||||
|
|
||||||
for (int i = 0; i < texture->dmaImageUsed; ++i)
|
|
||||||
{
|
|
||||||
if (texture->dmaImages[i].fd == dmaFd)
|
|
||||||
{
|
|
||||||
image = texture->dmaImages[i].image;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image == EGL_NO_IMAGE)
|
|
||||||
{
|
|
||||||
EGLAttrib const attribs[] =
|
|
||||||
{
|
|
||||||
EGL_WIDTH , texture->width,
|
|
||||||
EGL_HEIGHT , texture->height,
|
|
||||||
EGL_LINUX_DRM_FOURCC_EXT , texture->fourcc,
|
|
||||||
EGL_DMA_BUF_PLANE0_FD_EXT , dmaFd,
|
|
||||||
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
|
|
||||||
EGL_DMA_BUF_PLANE0_PITCH_EXT , texture->stride,
|
|
||||||
EGL_NONE , EGL_NONE
|
|
||||||
};
|
|
||||||
|
|
||||||
/* create the image backed by the dma buffer */
|
|
||||||
image = eglCreateImage(
|
|
||||||
texture->display,
|
|
||||||
EGL_NO_CONTEXT,
|
|
||||||
EGL_LINUX_DMA_BUF_EXT,
|
|
||||||
(EGLClientBuffer)NULL,
|
|
||||||
attribs
|
|
||||||
);
|
|
||||||
|
|
||||||
if (image == EGL_NO_IMAGE)
|
|
||||||
{
|
|
||||||
DEBUG_EGL_ERROR("Failed to create ELGImage for DMA transfer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (texture->dmaImageUsed == texture->dmaImageCount)
|
|
||||||
{
|
|
||||||
size_t newCount = texture->dmaImageCount * 2 + 2;
|
|
||||||
void * new = realloc(texture->dmaImages, newCount * sizeof *texture->dmaImages);
|
|
||||||
if (!new)
|
|
||||||
{
|
|
||||||
DEBUG_ERROR("Failed to allocate memory");
|
|
||||||
eglDestroyImage(texture->display, image);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
texture->dmaImageCount = newCount;
|
|
||||||
texture->dmaImages = new;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t index = texture->dmaImageUsed++;
|
|
||||||
texture->dmaImages[index].fd = dmaFd;
|
|
||||||
texture->dmaImages[index].image = image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* wait for completion */
|
/* wait for completion */
|
||||||
framebuffer_wait(frame, texture->height * texture->stride);
|
framebuffer_wait(frame, texture->size);
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->dmaTex);
|
return texture->ops->update(texture, &update);
|
||||||
g_egl_dynProcs.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, texture->dmaFBO);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->dmaTex, 0);
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->tex[0]);
|
|
||||||
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texture->width, texture->height);
|
|
||||||
|
|
||||||
GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
||||||
glFlush();
|
|
||||||
|
|
||||||
switch (glClientWaitSync(fence, 0, 10000000)) // 10ms
|
|
||||||
{
|
|
||||||
case GL_ALREADY_SIGNALED:
|
|
||||||
case GL_CONDITION_SATISFIED:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GL_TIMEOUT_EXPIRED:
|
|
||||||
egl_warn_slow();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GL_WAIT_FAILED:
|
|
||||||
case GL_INVALID_VALUE:
|
|
||||||
DEBUG_GL_ERROR("glClientWaitSync failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
glDeleteSync(fence);
|
|
||||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
|
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
|
||||||
{
|
{
|
||||||
if (!texture->streaming)
|
return texture->ops->process(texture);
|
||||||
return EGL_TEX_STATUS_OK;
|
|
||||||
|
|
||||||
const uint8_t su =
|
|
||||||
atomic_load_explicit(&texture->state.u, memory_order_acquire);
|
|
||||||
|
|
||||||
const uint8_t nextu = su + 1;
|
|
||||||
if (
|
|
||||||
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
|
|
||||||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
|
|
||||||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
|
|
||||||
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
|
|
||||||
|
|
||||||
const uint8_t b = su % BUFFER_COUNT;
|
|
||||||
|
|
||||||
/* update the texture */
|
|
||||||
if (!texture->dma)
|
|
||||||
{
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[b].pbo);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->tex[b]);
|
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->pitch);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height,
|
|
||||||
texture->format, texture->dataType, (const void *)0);
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
||||||
|
|
||||||
/* create a fence to prevent usage before the update is complete */
|
|
||||||
texture->buf[b].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
||||||
|
|
||||||
/* we must flush to ensure the sync is in the command buffer */
|
|
||||||
glFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
texture->ready = true;
|
|
||||||
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
|
|
||||||
|
|
||||||
return EGL_TEX_STATUS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
||||||
{
|
{
|
||||||
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
|
return texture->ops->bind(texture);
|
||||||
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
|
|
||||||
GLuint tex = texture->tex[0];
|
|
||||||
|
|
||||||
if (texture->streaming)
|
|
||||||
{
|
|
||||||
if (!texture->ready)
|
|
||||||
return EGL_TEX_STATUS_NOTREADY;
|
|
||||||
|
|
||||||
const uint8_t b = ss % BUFFER_COUNT;
|
|
||||||
if (texture->dma)
|
|
||||||
{
|
|
||||||
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
|
|
||||||
memory_order_release) + 1;
|
|
||||||
}
|
|
||||||
else if (texture->buf[b].sync != 0)
|
|
||||||
{
|
|
||||||
switch(glClientWaitSync(texture->buf[b].sync, 0, 20000000)) // 20ms
|
|
||||||
{
|
|
||||||
case GL_ALREADY_SIGNALED:
|
|
||||||
case GL_CONDITION_SATISFIED:
|
|
||||||
glDeleteSync(texture->buf[b].sync);
|
|
||||||
texture->buf[b].sync = 0;
|
|
||||||
|
|
||||||
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
|
|
||||||
memory_order_release) + 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GL_TIMEOUT_EXPIRED:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GL_WAIT_FAILED:
|
|
||||||
case GL_INVALID_VALUE:
|
|
||||||
glDeleteSync(texture->buf[b].sync);
|
|
||||||
texture->buf[b].sync = 0;
|
|
||||||
DEBUG_GL_ERROR("glClientWaitSync failed");
|
|
||||||
return EGL_TEX_STATUS_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ss != sd && ss != (uint8_t)(sd + 1))
|
|
||||||
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
|
|
||||||
memory_order_release) + 1;
|
|
||||||
|
|
||||||
if (!texture->dma)
|
|
||||||
tex = texture->tex[sd % BUFFER_COUNT];
|
|
||||||
}
|
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tex);
|
|
||||||
glBindSampler(0, texture->sampler);
|
|
||||||
|
|
||||||
return EGL_TEX_STATUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int egl_texture_count(EGL_Texture * texture)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,34 +24,121 @@
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
#include "common/framebuffer.h"
|
#include "common/framebuffer.h"
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
#include <EGL/egl.h>
|
#include <EGL/egl.h>
|
||||||
#include <EGL/eglext.h>
|
#include <EGL/eglext.h>
|
||||||
|
|
||||||
typedef struct EGL_Texture EGL_Texture;
|
struct EGL_TextureOps;
|
||||||
|
|
||||||
enum EGL_PixelFormat
|
typedef struct EGL_Texture
|
||||||
|
{
|
||||||
|
const struct EGL_TextureOps * ops;
|
||||||
|
|
||||||
|
// needed for dmabuf
|
||||||
|
size_t size;
|
||||||
|
}
|
||||||
|
EGL_Texture;
|
||||||
|
|
||||||
|
typedef enum EGL_TexType
|
||||||
|
{
|
||||||
|
EGL_TEXTYPE_BUFFER,
|
||||||
|
EGL_TEXTYPE_FRAMEBUFFER,
|
||||||
|
EGL_TEXTYPE_DMABUF
|
||||||
|
}
|
||||||
|
EGL_TexType;
|
||||||
|
|
||||||
|
typedef enum EGL_PixelFormat
|
||||||
{
|
{
|
||||||
EGL_PF_RGBA,
|
EGL_PF_RGBA,
|
||||||
EGL_PF_BGRA,
|
EGL_PF_BGRA,
|
||||||
EGL_PF_RGBA10,
|
EGL_PF_RGBA10,
|
||||||
EGL_PF_RGBA16F,
|
EGL_PF_RGBA16F
|
||||||
EGL_PF_YUV420
|
}
|
||||||
};
|
EGL_PixelFormat;
|
||||||
|
|
||||||
enum EGL_TexStatus
|
typedef enum EGL_TexStatus
|
||||||
{
|
{
|
||||||
EGL_TEX_STATUS_NOTREADY,
|
EGL_TEX_STATUS_NOTREADY,
|
||||||
EGL_TEX_STATUS_OK,
|
EGL_TEX_STATUS_OK,
|
||||||
EGL_TEX_STATUS_ERROR
|
EGL_TEX_STATUS_ERROR
|
||||||
};
|
}
|
||||||
|
EGL_TexStatus;
|
||||||
|
|
||||||
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display);
|
typedef struct EGL_TexSetup
|
||||||
|
{
|
||||||
|
/* the pixel format of the texture */
|
||||||
|
EGL_PixelFormat pixFmt;
|
||||||
|
|
||||||
|
/* the width of the texture in pixels */
|
||||||
|
size_t width;
|
||||||
|
|
||||||
|
/* the height of the texture in pixels */
|
||||||
|
size_t height;
|
||||||
|
|
||||||
|
/* the stide of the texture in bytes */
|
||||||
|
size_t stride;
|
||||||
|
}
|
||||||
|
EGL_TexSetup;
|
||||||
|
|
||||||
|
typedef struct EGL_TexUpdate
|
||||||
|
{
|
||||||
|
/* the type of this update */
|
||||||
|
EGL_TexType type;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
/* EGL_TEXTURE_BUFFER */
|
||||||
|
const uint8_t * buffer;
|
||||||
|
|
||||||
|
/* EGL_TEXTURE_FRAMEBUFFER */
|
||||||
|
const FrameBuffer * frame;
|
||||||
|
|
||||||
|
/* EGL_TEXTURE_DMABUF */
|
||||||
|
int dmaFD;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EGL_TexUpdate;
|
||||||
|
|
||||||
|
typedef struct EGL_TextureOps
|
||||||
|
{
|
||||||
|
/* allocate & initialize an EGL_Texture */
|
||||||
|
bool (*init)(EGL_Texture ** texture, EGLDisplay * display);
|
||||||
|
|
||||||
|
/* free the EGL_Texture */
|
||||||
|
void (*free)(EGL_Texture * texture);
|
||||||
|
|
||||||
|
/* setup/reconfigure the texture format */
|
||||||
|
bool (*setup)(EGL_Texture * texture, const EGL_TexSetup * setup);
|
||||||
|
|
||||||
|
/* update the texture */
|
||||||
|
bool (*update)(EGL_Texture * texture, const EGL_TexUpdate * update);
|
||||||
|
|
||||||
|
/* called from a background job to prepare the texture for use before bind */
|
||||||
|
enum EGL_TexStatus (*process)(EGL_Texture * texture);
|
||||||
|
|
||||||
|
/* bind the texture for use */
|
||||||
|
enum EGL_TexStatus (*bind)(EGL_Texture * texture);
|
||||||
|
}
|
||||||
|
EGL_TextureOps;
|
||||||
|
|
||||||
|
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display,
|
||||||
|
EGL_TexType type, bool streaming);
|
||||||
void egl_texture_free(EGL_Texture ** tex);
|
void egl_texture_free(EGL_Texture ** tex);
|
||||||
|
|
||||||
bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming, bool useDMA);
|
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt,
|
||||||
bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
|
size_t width, size_t height, size_t stride);
|
||||||
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame);
|
|
||||||
bool egl_texture_update_from_dma (EGL_Texture * texture, const FrameBuffer * frmame, const int dmaFd);
|
bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
|
||||||
|
|
||||||
|
bool egl_texture_update_from_frame(EGL_Texture * texture,
|
||||||
|
const FrameBuffer * frame);
|
||||||
|
|
||||||
|
bool egl_texture_update_from_dma(EGL_Texture * texture,
|
||||||
|
const FrameBuffer * frame, const int dmaFd);
|
||||||
|
|
||||||
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
|
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
|
||||||
enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture);
|
|
||||||
int egl_texture_count (EGL_Texture * texture);
|
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture);
|
||||||
|
|
||||||
|
int egl_texture_count(EGL_Texture * texture);
|
||||||
|
|
278
client/renderers/EGL/texture_buffer.c
Normal file
278
client/renderers/EGL/texture_buffer.c
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright (C) 2017-2021 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 "egldebug.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
// forwards
|
||||||
|
extern const EGL_TextureOps EGL_TextureBuffer;
|
||||||
|
extern const EGL_TextureOps EGL_TextureBufferStream;
|
||||||
|
|
||||||
|
// internal functions
|
||||||
|
|
||||||
|
static void eglTexBuffer_cleanup(TextureBuffer * this)
|
||||||
|
{
|
||||||
|
eglTexUtilFreeBuffers(this->buf, this->texCount);
|
||||||
|
|
||||||
|
if (this->tex[0])
|
||||||
|
glDeleteTextures(this->texCount, this->tex);
|
||||||
|
|
||||||
|
if (this->sampler)
|
||||||
|
glDeleteSamplers(1, &this->sampler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// common functions
|
||||||
|
|
||||||
|
bool eglTexBuffer_init(EGL_Texture ** texture_, EGLDisplay * display)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = (TextureBuffer *)calloc(1, sizeof(*this));
|
||||||
|
if (!this)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Failed to malloc TexB");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*texture_ = &this->base;
|
||||||
|
|
||||||
|
this->display = display;
|
||||||
|
this->texCount = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void eglTexBuffer_free(EGL_Texture * texture_)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
|
||||||
|
eglTexBuffer_cleanup(this);
|
||||||
|
LG_LOCK_FREE(this->copyLock);
|
||||||
|
free(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eglTexBuffer_setup(EGL_Texture * texture_, const EGL_TexSetup * setup)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
|
||||||
|
eglTexBuffer_cleanup(this);
|
||||||
|
|
||||||
|
if (!eglTexUtilGetFormat(setup, &this->format))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
glGenSamplers(1, &this->sampler);
|
||||||
|
glSamplerParameteri(this->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glSamplerParameteri(this->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||||
|
glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
glGenTextures(this->texCount, this->tex);
|
||||||
|
for(int i = 0; i < this->texCount; ++i)
|
||||||
|
{
|
||||||
|
glBindTexture(GL_TEXTURE_2D, this->tex[i]);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
this->format.intFormat,
|
||||||
|
this->format.width,
|
||||||
|
this->format.height,
|
||||||
|
0,
|
||||||
|
this->format.format,
|
||||||
|
this->format.dataType,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
this->rIndex = -1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool eglTexBuffer_update(EGL_Texture * texture_, const EGL_TexUpdate * update)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
assert(update->type == EGL_TEXTYPE_BUFFER);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, this->tex[0]);
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.pitch);
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D,
|
||||||
|
0, 0, 0,
|
||||||
|
this->format.width,
|
||||||
|
this->format.height,
|
||||||
|
this->format.format,
|
||||||
|
this->format.dataType,
|
||||||
|
update->buffer);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGL_TexStatus eglTexBuffer_process(EGL_Texture * texture_)
|
||||||
|
{
|
||||||
|
return EGL_TEX_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGL_TexStatus eglTexBuffer_bind(EGL_Texture * texture_)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, this->tex[0]);
|
||||||
|
glBindSampler(0, this->sampler);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// streaming functions
|
||||||
|
|
||||||
|
bool eglTexBuffer_stream_init(EGL_Texture ** texture_, EGLDisplay * display)
|
||||||
|
{
|
||||||
|
if (!eglTexBuffer_init(texture_, display))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, *texture_);
|
||||||
|
|
||||||
|
this->base.ops = &EGL_TextureBufferStream;
|
||||||
|
this->texCount = 2;
|
||||||
|
LG_LOCK_INIT(this->copyLock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eglTexBuffer_stream_setup(EGL_Texture * texture_, const EGL_TexSetup * setup)
|
||||||
|
{
|
||||||
|
if (!eglTexBuffer_setup(texture_, setup))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
return eglTexUtilGenBuffers(&this->format, this->buf, this->texCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool eglTexBuffer_stream_update(EGL_Texture * texture_,
|
||||||
|
const EGL_TexUpdate * update)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
assert(update->type == EGL_TEXTYPE_BUFFER);
|
||||||
|
|
||||||
|
LG_LOCK(this->copyLock);
|
||||||
|
memcpy(this->buf[this->bufIndex].map, update->buffer,
|
||||||
|
this->format.bufferSize);
|
||||||
|
this->buf[this->bufIndex].updated = true;
|
||||||
|
LG_UNLOCK(this->copyLock);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGL_TexStatus eglTexBuffer_stream_process(EGL_Texture * texture_)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
|
||||||
|
LG_LOCK(this->copyLock);
|
||||||
|
|
||||||
|
GLuint tex = this->tex[this->bufIndex];
|
||||||
|
EGL_TexBuffer * buffer = &this->buf[this->bufIndex];
|
||||||
|
|
||||||
|
if (buffer->updated && buffer->sync == 0)
|
||||||
|
{
|
||||||
|
this->rIndex = this->bufIndex;
|
||||||
|
if (++this->bufIndex == this->texCount)
|
||||||
|
this->bufIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LG_UNLOCK(this->copyLock);
|
||||||
|
|
||||||
|
if (buffer->updated)
|
||||||
|
{
|
||||||
|
buffer->updated = false;
|
||||||
|
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.pitch);
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D,
|
||||||
|
0, 0, 0,
|
||||||
|
this->format.width,
|
||||||
|
this->format.height,
|
||||||
|
this->format.format,
|
||||||
|
this->format.dataType,
|
||||||
|
(const void *)0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
|
|
||||||
|
buffer->sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
glFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return EGL_TEX_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGL_TexStatus eglTexBuffer_stream_bind(EGL_Texture * texture_)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
|
||||||
|
if (this->rIndex == -1)
|
||||||
|
return EGL_TEX_STATUS_NOTREADY;
|
||||||
|
|
||||||
|
EGL_TexBuffer * buffer = &this->buf[this->rIndex];
|
||||||
|
if (buffer->sync)
|
||||||
|
{
|
||||||
|
switch(glClientWaitSync(buffer->sync, 0, 20000000)) // 20ms
|
||||||
|
{
|
||||||
|
case GL_ALREADY_SIGNALED:
|
||||||
|
case GL_CONDITION_SATISFIED:
|
||||||
|
glDeleteSync(buffer->sync);
|
||||||
|
buffer->sync = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GL_TIMEOUT_EXPIRED:
|
||||||
|
return EGL_TEX_STATUS_NOTREADY;
|
||||||
|
|
||||||
|
case GL_WAIT_FAILED:
|
||||||
|
case GL_INVALID_VALUE:
|
||||||
|
glDeleteSync(buffer->sync);
|
||||||
|
buffer->sync = 0;
|
||||||
|
DEBUG_GL_ERROR("glClientWaitSync failed");
|
||||||
|
return EGL_TEX_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, this->tex[this->rIndex]);
|
||||||
|
glBindSampler(0, this->sampler);
|
||||||
|
|
||||||
|
return EGL_TEX_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EGL_TextureOps EGL_TextureBuffer =
|
||||||
|
{
|
||||||
|
.init = eglTexBuffer_init,
|
||||||
|
.free = eglTexBuffer_free,
|
||||||
|
.setup = eglTexBuffer_setup,
|
||||||
|
.update = eglTexBuffer_update,
|
||||||
|
.process = eglTexBuffer_process,
|
||||||
|
.bind = eglTexBuffer_bind
|
||||||
|
};
|
||||||
|
|
||||||
|
const EGL_TextureOps EGL_TextureBufferStream =
|
||||||
|
{
|
||||||
|
.init = eglTexBuffer_stream_init,
|
||||||
|
.free = eglTexBuffer_free,
|
||||||
|
.setup = eglTexBuffer_stream_setup,
|
||||||
|
.update = eglTexBuffer_stream_update,
|
||||||
|
.process = eglTexBuffer_stream_process,
|
||||||
|
.bind = eglTexBuffer_stream_bind
|
||||||
|
};
|
53
client/renderers/EGL/texture_buffer.h
Normal file
53
client/renderers/EGL/texture_buffer.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright (C) 2017-2021 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "texture.h"
|
||||||
|
#include "texture_util.h"
|
||||||
|
#include "common/locking.h"
|
||||||
|
|
||||||
|
typedef struct TextureBuffer
|
||||||
|
{
|
||||||
|
EGL_Texture base;
|
||||||
|
|
||||||
|
EGLDisplay display;
|
||||||
|
EGL_TexFormat format;
|
||||||
|
int texCount;
|
||||||
|
GLuint tex[2];
|
||||||
|
GLuint sampler;
|
||||||
|
EGL_TexBuffer buf[2];
|
||||||
|
LG_Lock copyLock;
|
||||||
|
int bufIndex;
|
||||||
|
int rIndex;
|
||||||
|
}
|
||||||
|
TextureBuffer;
|
||||||
|
|
||||||
|
bool eglTexBuffer_init(EGL_Texture ** texture_, EGLDisplay * display);
|
||||||
|
void eglTexBuffer_free(EGL_Texture * texture_);
|
||||||
|
bool eglTexBuffer_setup(EGL_Texture * texture_, const EGL_TexSetup * setup);
|
||||||
|
EGL_TexStatus eglTexBuffer_process(EGL_Texture * texture_);
|
||||||
|
EGL_TexStatus eglTexBuffer_bind(EGL_Texture * texture_);
|
||||||
|
|
||||||
|
bool eglTexBuffer_stream_init(EGL_Texture ** texture_, EGLDisplay * display);
|
||||||
|
bool eglTexBuffer_stream_setup(EGL_Texture * texture_,
|
||||||
|
const EGL_TexSetup * setup);
|
||||||
|
EGL_TexStatus eglTexBuffer_stream_process(EGL_Texture * texture_);
|
||||||
|
EGL_TexStatus eglTexBuffer_stream_bind(EGL_Texture * texture_);
|
88
client/renderers/EGL/texture_dmabuf.c
Normal file
88
client/renderers/EGL/texture_dmabuf.c
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright (C) 2017-2021 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"
|
||||||
|
|
||||||
|
typedef struct TexDMABUF
|
||||||
|
{
|
||||||
|
EGL_Texture base;
|
||||||
|
}
|
||||||
|
TexDMABUF;
|
||||||
|
|
||||||
|
EGL_TextureOps EGL_TextureDMABUF;
|
||||||
|
|
||||||
|
static bool eglTexDMABUF_init(EGL_Texture ** texture_, EGLDisplay * display)
|
||||||
|
{
|
||||||
|
TexDMABUF * texture = (TexDMABUF *)calloc(sizeof(*texture), 1);
|
||||||
|
*texture_ = &texture->base;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void eglTexDMABUF_free(EGL_Texture * texture_)
|
||||||
|
{
|
||||||
|
TexDMABUF * texture = UPCAST(TexDMABUF, texture_);
|
||||||
|
|
||||||
|
free(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool eglTexDMABUF_setup(EGL_Texture * texture_,
|
||||||
|
const EGL_TexSetup * setup)
|
||||||
|
{
|
||||||
|
TexDMABUF * texture = UPCAST(TexDMABUF, texture_);
|
||||||
|
(void)texture;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool eglTexDMABUF_update(EGL_Texture * texture_,
|
||||||
|
const EGL_TexUpdate * update)
|
||||||
|
{
|
||||||
|
TexDMABUF * texture = UPCAST(TexDMABUF, texture_);
|
||||||
|
(void)texture;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EGL_TexStatus eglTexDMABUF_process(EGL_Texture * texture_)
|
||||||
|
{
|
||||||
|
TexDMABUF * texture = UPCAST(TexDMABUF, texture_);
|
||||||
|
(void)texture;
|
||||||
|
|
||||||
|
return EGL_TEX_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EGL_TexStatus eglTexDMABUF_bind(EGL_Texture * texture_)
|
||||||
|
{
|
||||||
|
TexDMABUF * texture = UPCAST(TexDMABUF, texture_);
|
||||||
|
(void)texture;
|
||||||
|
|
||||||
|
return EGL_TEX_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGL_TextureOps EGL_TextureDMABUF =
|
||||||
|
{
|
||||||
|
.init = eglTexDMABUF_init,
|
||||||
|
.free = eglTexDMABUF_free,
|
||||||
|
.setup = eglTexDMABUF_setup,
|
||||||
|
.update = eglTexDMABUF_update,
|
||||||
|
.process = eglTexDMABUF_process,
|
||||||
|
.bind = eglTexDMABUF_bind
|
||||||
|
};
|
59
client/renderers/EGL/texture_framebuffer.c
Normal file
59
client/renderers/EGL/texture_framebuffer.c
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright (C) 2017-2021 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 <assert.h>
|
||||||
|
|
||||||
|
#include "texture_buffer.h"
|
||||||
|
#include "common/debug.h"
|
||||||
|
|
||||||
|
static bool eglTexFB_update(EGL_Texture * texture_, const EGL_TexUpdate * update)
|
||||||
|
{
|
||||||
|
TextureBuffer * this = UPCAST(TextureBuffer, texture_);
|
||||||
|
assert(update->type == EGL_TEXTYPE_FRAMEBUFFER);
|
||||||
|
|
||||||
|
LG_LOCK(this->copyLock);
|
||||||
|
|
||||||
|
framebuffer_read(
|
||||||
|
update->frame,
|
||||||
|
this->buf[this->bufIndex].map,
|
||||||
|
this->format.stride,
|
||||||
|
this->format.height,
|
||||||
|
this->format.width,
|
||||||
|
this->format.bpp,
|
||||||
|
this->format.stride
|
||||||
|
);
|
||||||
|
|
||||||
|
this->buf[this->bufIndex].updated = true;
|
||||||
|
LG_UNLOCK(this->copyLock);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGL_TextureOps EGL_TextureFrameBuffer =
|
||||||
|
{
|
||||||
|
.init = eglTexBuffer_stream_init,
|
||||||
|
.free = eglTexBuffer_free,
|
||||||
|
.setup = eglTexBuffer_stream_setup,
|
||||||
|
.update = eglTexFB_update,
|
||||||
|
.process = eglTexBuffer_stream_process,
|
||||||
|
.bind = eglTexBuffer_stream_bind
|
||||||
|
};
|
166
client/renderers/EGL/texture_util.c
Normal file
166
client/renderers/EGL/texture_util.c
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright (C) 2017-2021 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_util.h"
|
||||||
|
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
#include <GLES3/gl3.h>
|
||||||
|
#include <GLES2/gl2ext.h>
|
||||||
|
|
||||||
|
#include "egldebug.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the following comes from drm_fourcc.h and is included here to avoid the
|
||||||
|
* external dependency for the few simple defines we need
|
||||||
|
*/
|
||||||
|
#define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \
|
||||||
|
((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
|
||||||
|
#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4')
|
||||||
|
#define DRM_FORMAT_ABGR8888 fourcc_code('A', 'B', '2', '4')
|
||||||
|
#define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0')
|
||||||
|
#define DRM_FORMAT_ABGR16161616F fourcc_code('A', 'B', '4', 'H')
|
||||||
|
|
||||||
|
bool eglTexUtilGetFormat(const EGL_TexSetup * setup, EGL_TexFormat * fmt)
|
||||||
|
{
|
||||||
|
switch(setup->pixFmt)
|
||||||
|
{
|
||||||
|
case EGL_PF_BGRA:
|
||||||
|
fmt->bpp = 4;
|
||||||
|
fmt->format = GL_BGRA_EXT;
|
||||||
|
fmt->intFormat = GL_BGRA_EXT;
|
||||||
|
fmt->dataType = GL_UNSIGNED_BYTE;
|
||||||
|
fmt->fourcc = DRM_FORMAT_ARGB8888;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EGL_PF_RGBA:
|
||||||
|
fmt->bpp = 4;
|
||||||
|
fmt->format = GL_RGBA;
|
||||||
|
fmt->intFormat = GL_RGBA;
|
||||||
|
fmt->dataType = GL_UNSIGNED_BYTE;
|
||||||
|
fmt->fourcc = DRM_FORMAT_ABGR8888;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EGL_PF_RGBA10:
|
||||||
|
fmt->bpp = 4;
|
||||||
|
fmt->format = GL_RGBA;
|
||||||
|
fmt->intFormat = GL_RGB10_A2;
|
||||||
|
fmt->dataType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||||
|
fmt->fourcc = DRM_FORMAT_BGRA1010102;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EGL_PF_RGBA16F:
|
||||||
|
fmt->bpp = 8;
|
||||||
|
fmt->format = GL_RGBA;
|
||||||
|
fmt->intFormat = GL_RGBA16F;
|
||||||
|
fmt->dataType = GL_HALF_FLOAT;
|
||||||
|
fmt->fourcc = DRM_FORMAT_ABGR16161616F;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
DEBUG_ERROR("Unsupported pixel format");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt->width = setup->width;
|
||||||
|
fmt->height = setup->height;
|
||||||
|
fmt->stride = setup->stride;
|
||||||
|
fmt->pitch = setup->stride / fmt->bpp;
|
||||||
|
fmt->bufferSize = setup->height * setup->stride;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eglTexUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers,
|
||||||
|
int count)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
EGL_TexBuffer *buffer = &buffers[i];
|
||||||
|
|
||||||
|
buffer->size = fmt->bufferSize;
|
||||||
|
glGenBuffers(1, &buffer->pbo);
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
|
||||||
|
glBufferStorageEXT(
|
||||||
|
GL_PIXEL_UNPACK_BUFFER,
|
||||||
|
fmt->bufferSize,
|
||||||
|
NULL,
|
||||||
|
GL_MAP_WRITE_BIT |
|
||||||
|
GL_MAP_PERSISTENT_BIT_EXT |
|
||||||
|
GL_MAP_COHERENT_BIT_EXT
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!eglTexUtilMapBuffer(buffer))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void eglTexUtilFreeBuffers(EGL_TexBuffer * buffers, int count)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
EGL_TexBuffer *buffer = &buffers[i];
|
||||||
|
|
||||||
|
if (!buffer->pbo)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
eglTexUtilUnmapBuffer(buffer);
|
||||||
|
glDeleteBuffers(1, &buffer->pbo);
|
||||||
|
if (buffer->sync)
|
||||||
|
{
|
||||||
|
glDeleteSync(buffer->sync);
|
||||||
|
buffer->sync = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer->pbo = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eglTexUtilMapBuffer(EGL_TexBuffer * buffer)
|
||||||
|
{
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
|
||||||
|
buffer->map = glMapBufferRange(
|
||||||
|
GL_PIXEL_UNPACK_BUFFER,
|
||||||
|
0,
|
||||||
|
buffer->size,
|
||||||
|
GL_MAP_WRITE_BIT |
|
||||||
|
GL_MAP_UNSYNCHRONIZED_BIT |
|
||||||
|
GL_MAP_INVALIDATE_BUFFER_BIT |
|
||||||
|
GL_MAP_PERSISTENT_BIT_EXT |
|
||||||
|
GL_MAP_COHERENT_BIT_EXT);
|
||||||
|
|
||||||
|
if (!buffer->map)
|
||||||
|
DEBUG_GL_ERROR("glMapBufferRange failed of %lu bytes", buffer->size);
|
||||||
|
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
|
return buffer->map;
|
||||||
|
}
|
||||||
|
|
||||||
|
void eglTexUtilUnmapBuffer(EGL_TexBuffer * buffer)
|
||||||
|
{
|
||||||
|
if (!buffer->map)
|
||||||
|
return;
|
||||||
|
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
|
||||||
|
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
|
buffer->map = NULL;
|
||||||
|
}
|
54
client/renderers/EGL/texture_util.h
Normal file
54
client/renderers/EGL/texture_util.h
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright (C) 2017-2021 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
typedef struct EGL_TexFormat
|
||||||
|
{
|
||||||
|
size_t bpp;
|
||||||
|
GLenum format;
|
||||||
|
GLenum intFormat;
|
||||||
|
GLenum dataType;
|
||||||
|
unsigned int fourcc;
|
||||||
|
size_t bufferSize;
|
||||||
|
|
||||||
|
size_t width, height;
|
||||||
|
size_t stride, pitch;
|
||||||
|
}
|
||||||
|
EGL_TexFormat;
|
||||||
|
|
||||||
|
typedef struct EGL_TexBuffer
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
GLuint pbo;
|
||||||
|
void * map;
|
||||||
|
GLsync sync;
|
||||||
|
bool updated;
|
||||||
|
}
|
||||||
|
EGL_TexBuffer;
|
||||||
|
|
||||||
|
bool eglTexUtilGetFormat(const EGL_TexSetup * setup, EGL_TexFormat * fmt);
|
||||||
|
bool eglTexUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers,
|
||||||
|
int count);
|
||||||
|
void eglTexUtilFreeBuffers(EGL_TexBuffer * buffers, int count);
|
||||||
|
bool eglTexUtilMapBuffer(EGL_TexBuffer * buffer);
|
||||||
|
void eglTexUtilUnmapBuffer(EGL_TexBuffer * buffer);
|
|
@ -357,7 +357,7 @@ bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool opengl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
|
bool opengl_on_frame_format(void * opaque, const LG_RendererFormat format)
|
||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ bool opengl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool opengl_render_startup(void * opaque)
|
bool opengl_render_startup(void * opaque, bool useDMA)
|
||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,7 @@ static bool fpsTimerFn(void * unused)
|
||||||
|
|
||||||
static int renderThread(void * unused)
|
static int renderThread(void * unused)
|
||||||
{
|
{
|
||||||
if (!g_state.lgr->render_startup(g_state.lgrData))
|
if (!g_state.lgr->render_startup(g_state.lgrData, g_state.useDMA))
|
||||||
{
|
{
|
||||||
g_state.state = APP_STATE_SHUTDOWN;
|
g_state.state = APP_STATE_SHUTDOWN;
|
||||||
|
|
||||||
|
@ -456,13 +456,7 @@ int main_frameThread(void * unused)
|
||||||
LG_RendererFormat lgrFormat;
|
LG_RendererFormat lgrFormat;
|
||||||
|
|
||||||
struct DMAFrameInfo dmaInfo[LGMP_Q_FRAME_LEN] = {0};
|
struct DMAFrameInfo dmaInfo[LGMP_Q_FRAME_LEN] = {0};
|
||||||
const bool useDMA =
|
if (g_state.useDMA)
|
||||||
g_params.allowDMA &&
|
|
||||||
ivshmemHasDMA(&g_state.shm) &&
|
|
||||||
g_state.lgr->supports &&
|
|
||||||
g_state.lgr->supports(g_state.lgrData, LG_SUPPORTS_DMABUF);
|
|
||||||
|
|
||||||
if (useDMA)
|
|
||||||
DEBUG_INFO("Using DMA buffer support");
|
DEBUG_INFO("Using DMA buffer support");
|
||||||
|
|
||||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||||
|
@ -607,7 +601,7 @@ int main_frameThread(void * unused)
|
||||||
frame->rotation);
|
frame->rotation);
|
||||||
|
|
||||||
LG_LOCK(g_state.lgrLock);
|
LG_LOCK(g_state.lgrLock);
|
||||||
if (!g_state.lgr->on_frame_format(g_state.lgrData, lgrFormat, useDMA))
|
if (!g_state.lgr->on_frame_format(g_state.lgrData, lgrFormat))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("renderer failed to configure format");
|
DEBUG_ERROR("renderer failed to configure format");
|
||||||
g_state.state = APP_STATE_SHUTDOWN;
|
g_state.state = APP_STATE_SHUTDOWN;
|
||||||
|
@ -625,7 +619,7 @@ int main_frameThread(void * unused)
|
||||||
core_updatePositionInfo();
|
core_updatePositionInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useDMA)
|
if (g_state.useDMA)
|
||||||
{
|
{
|
||||||
/* find the existing dma buffer if it exists */
|
/* find the existing dma buffer if it exists */
|
||||||
for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i)
|
for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i)
|
||||||
|
@ -674,7 +668,7 @@ int main_frameThread(void * unused)
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)frame) + frame->offset);
|
FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)frame) + frame->offset);
|
||||||
if (!g_state.lgr->on_frame(g_state.lgrData, fb, useDMA ? dma->fd : -1,
|
if (!g_state.lgr->on_frame(g_state.lgrData, fb, g_state.useDMA ? dma->fd : -1,
|
||||||
frame->damageRects, frame->damageRectsCount))
|
frame->damageRects, frame->damageRectsCount))
|
||||||
{
|
{
|
||||||
lgmpClientMessageDone(queue);
|
lgmpClientMessageDone(queue);
|
||||||
|
@ -719,7 +713,7 @@ int main_frameThread(void * unused)
|
||||||
lgmpClientUnsubscribe(&queue);
|
lgmpClientUnsubscribe(&queue);
|
||||||
g_state.lgr->on_restart(g_state.lgrData);
|
g_state.lgr->on_restart(g_state.lgrData);
|
||||||
|
|
||||||
if (useDMA)
|
if (g_state.useDMA)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i)
|
for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i)
|
||||||
if (dmaInfo[i].fd >= 0)
|
if (dmaInfo[i].fd >= 0)
|
||||||
|
@ -931,6 +925,12 @@ static int lg_run(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_state.useDMA =
|
||||||
|
g_params.allowDMA &&
|
||||||
|
ivshmemHasDMA(&g_state.shm) &&
|
||||||
|
g_state.lgr->supports &&
|
||||||
|
g_state.lgr->supports(g_state.lgrData, LG_SUPPORTS_DMABUF);
|
||||||
|
|
||||||
if (!g_state.lgr)
|
if (!g_state.lgr)
|
||||||
{
|
{
|
||||||
DEBUG_INFO("Unable to find a suitable renderer");
|
DEBUG_INFO("Unable to find a suitable renderer");
|
||||||
|
|
|
@ -95,6 +95,7 @@ struct AppState
|
||||||
void * lgrData;
|
void * lgrData;
|
||||||
atomic_int lgrResize;
|
atomic_int lgrResize;
|
||||||
LG_Lock lgrLock;
|
LG_Lock lgrLock;
|
||||||
|
bool useDMA;
|
||||||
|
|
||||||
bool cbAvailable;
|
bool cbAvailable;
|
||||||
SpiceDataType cbType;
|
SpiceDataType cbType;
|
||||||
|
|
Loading…
Reference in a new issue