mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 23:07:21 +00:00
494 lines
13 KiB
C
494 lines
13 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 "test.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "../renderers/EGL/texture_buffer.h"
|
|
#include "../renderers/EGL/texture_util.c"
|
|
|
|
struct Trace
|
|
{
|
|
unsigned int nextBuffer;
|
|
unsigned int storage;
|
|
unsigned int map;
|
|
unsigned int unmap;
|
|
unsigned int deleted;
|
|
unsigned int bufferBinds;
|
|
unsigned int textureBinds;
|
|
unsigned int uploads;
|
|
unsigned int fences;
|
|
unsigned int finishes;
|
|
unsigned int flushes;
|
|
GLuint boundPBO;
|
|
GLuint boundTexture;
|
|
GLint unpackAlignment;
|
|
GLint unpackRowLength;
|
|
GLint uploadX;
|
|
GLint uploadY;
|
|
GLsizei uploadWidth;
|
|
GLsizei uploadHeight;
|
|
const void * uploadData;
|
|
bool failMap;
|
|
};
|
|
|
|
static struct Trace trace;
|
|
static uint8_t mappedStorage[4096];
|
|
|
|
struct EGLDynProcs g_egl_dynProcs;
|
|
_Thread_local EGL_StateCache g_eglState;
|
|
|
|
void debug_level(enum DebugLevel level, const char * file, unsigned int line,
|
|
const char * function, const char * format, ...)
|
|
{
|
|
}
|
|
|
|
static void fakeBufferStorage(GLenum target, GLsizeiptr size,
|
|
const void * data, GLbitfield flags)
|
|
{
|
|
CHECK(target == GL_PIXEL_UNPACK_BUFFER);
|
|
CHECK(size > 0);
|
|
CHECK(!data);
|
|
CHECK(flags & GL_MAP_PERSISTENT_BIT_EXT);
|
|
++trace.storage;
|
|
}
|
|
|
|
void glGenBuffers(GLsizei count, GLuint * buffers)
|
|
{
|
|
for (GLsizei i = 0; i < count; ++i)
|
|
buffers[i] = ++trace.nextBuffer;
|
|
}
|
|
|
|
void glBindBuffer(GLenum target, GLuint buffer)
|
|
{
|
|
if (target == GL_PIXEL_UNPACK_BUFFER)
|
|
{
|
|
trace.boundPBO = buffer;
|
|
++trace.bufferBinds;
|
|
}
|
|
}
|
|
|
|
void glActiveTexture(GLenum texture)
|
|
{
|
|
CHECK(texture == GL_TEXTURE0);
|
|
}
|
|
|
|
void glBindTexture(GLenum target, GLuint texture)
|
|
{
|
|
CHECK(target == GL_TEXTURE_2D);
|
|
trace.boundTexture = texture;
|
|
++trace.textureBinds;
|
|
}
|
|
|
|
void glPixelStorei(GLenum pname, GLint param)
|
|
{
|
|
switch (pname)
|
|
{
|
|
case GL_UNPACK_ALIGNMENT:
|
|
trace.unpackAlignment = param;
|
|
break;
|
|
|
|
case GL_UNPACK_ROW_LENGTH:
|
|
trace.unpackRowLength = param;
|
|
break;
|
|
|
|
default:
|
|
CHECK(false);
|
|
}
|
|
}
|
|
|
|
void glTexSubImage2D(GLenum target, GLint level, GLint xoffset,
|
|
GLint yoffset, GLsizei width, GLsizei height, GLenum format,
|
|
GLenum type, const void * pixels)
|
|
{
|
|
CHECK(target == GL_TEXTURE_2D);
|
|
CHECK(level == 0);
|
|
CHECK(format == GL_RGBA);
|
|
CHECK(type == GL_UNSIGNED_BYTE);
|
|
|
|
trace.uploadX = xoffset;
|
|
trace.uploadY = yoffset;
|
|
trace.uploadWidth = width;
|
|
trace.uploadHeight = height;
|
|
trace.uploadData = pixels;
|
|
++trace.uploads;
|
|
}
|
|
|
|
GLsync glFenceSync(GLenum condition, GLbitfield flags)
|
|
{
|
|
CHECK(condition == GL_SYNC_GPU_COMMANDS_COMPLETE);
|
|
CHECK(flags == 0);
|
|
++trace.fences;
|
|
return (GLsync)(uintptr_t)1;
|
|
}
|
|
|
|
void glFinish(void)
|
|
{
|
|
++trace.finishes;
|
|
}
|
|
|
|
void glFlush(void)
|
|
{
|
|
++trace.flushes;
|
|
}
|
|
|
|
void * glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length,
|
|
GLbitfield access)
|
|
{
|
|
CHECK(target == GL_PIXEL_UNPACK_BUFFER);
|
|
CHECK(offset == 0);
|
|
CHECK(length <= (GLsizeiptr)sizeof(mappedStorage));
|
|
CHECK(access & GL_MAP_PERSISTENT_BIT_EXT);
|
|
++trace.map;
|
|
return trace.failMap ? NULL : mappedStorage;
|
|
}
|
|
|
|
GLboolean glUnmapBuffer(GLenum target)
|
|
{
|
|
CHECK(target == GL_PIXEL_UNPACK_BUFFER);
|
|
++trace.unmap;
|
|
return GL_TRUE;
|
|
}
|
|
|
|
void glDeleteBuffers(GLsizei count, const GLuint * buffers)
|
|
{
|
|
trace.deleted += count;
|
|
}
|
|
|
|
void egl_stateInvalidateShared(void)
|
|
{
|
|
memset(&g_eglState, 0, sizeof(g_eglState));
|
|
}
|
|
|
|
const char * gl_getErrorStr(void)
|
|
{
|
|
return "fake";
|
|
}
|
|
|
|
static void reset(void)
|
|
{
|
|
memset(&trace, 0, sizeof(trace));
|
|
memset(&g_eglState, 0, sizeof(g_eglState));
|
|
memset(&g_egl_dynProcs, 0, sizeof(g_egl_dynProcs));
|
|
g_egl_dynProcs.glBufferStorageEXT = fakeBufferStorage;
|
|
}
|
|
|
|
static EGL_TexFormat testFormat(void)
|
|
{
|
|
return (EGL_TexFormat)
|
|
{
|
|
.dataSize = 1024,
|
|
};
|
|
}
|
|
|
|
static void initTexture(TextureBuffer * texture, EGL_TexBufferMode mode)
|
|
{
|
|
memset(texture, 0, sizeof(*texture));
|
|
|
|
texture->base.format.width = 8;
|
|
texture->base.format.height = 4;
|
|
texture->base.format.stride = 10;
|
|
texture->base.format.pitch = 40;
|
|
texture->base.format.bpp = 4;
|
|
texture->base.format.format = GL_RGBA;
|
|
texture->base.format.dataType = GL_UNSIGNED_BYTE;
|
|
texture->base.frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
|
texture->texCount = 1;
|
|
texture->tex[0] = 17;
|
|
texture->buf[0].mode = mode;
|
|
texture->buf[0].pbo =
|
|
mode == EGL_TEX_BUFFER_MODE_PERSISTENT ? 23 : 0;
|
|
texture->buf[0].map = mappedStorage;
|
|
texture->buf[0].size = sizeof(mappedStorage);
|
|
texture->buf[0].updated = true;
|
|
texture->slotToken[0] = 42;
|
|
texture->rIndex = -1;
|
|
LG_LOCK_INIT(texture->copyLock);
|
|
}
|
|
|
|
static void checkLockReleased(TextureBuffer * texture)
|
|
{
|
|
const bool locked = atomic_flag_test_and_set_explicit(
|
|
&texture->copyLock, memory_order_acquire);
|
|
CHECK(!locked);
|
|
atomic_flag_clear_explicit(&texture->copyLock, memory_order_release);
|
|
}
|
|
|
|
static void testDirect(void)
|
|
{
|
|
reset();
|
|
egl_texUtilSetBufferMode(EGL_TEX_BUFFER_MODE_DIRECT);
|
|
|
|
EGL_TexBuffer buffers[2] = { 0 };
|
|
const EGL_TexFormat format = testFormat();
|
|
CHECK(egl_texUtilGenBuffers(&format, buffers, 2));
|
|
CHECK(buffers[0].mode == EGL_TEX_BUFFER_MODE_DIRECT);
|
|
CHECK(buffers[1].mode == EGL_TEX_BUFFER_MODE_DIRECT);
|
|
CHECK(buffers[0].map);
|
|
CHECK(buffers[1].map);
|
|
CHECK(!buffers[0].pbo);
|
|
CHECK(!buffers[1].pbo);
|
|
CHECK(trace.storage == 0);
|
|
CHECK(trace.map == 0);
|
|
|
|
egl_texUtilFreeBuffers(buffers, 2);
|
|
CHECK(!buffers[0].map);
|
|
CHECK(!buffers[1].map);
|
|
CHECK(trace.deleted == 0);
|
|
}
|
|
|
|
static void testPersistent(void)
|
|
{
|
|
reset();
|
|
egl_texUtilSetBufferMode(EGL_TEX_BUFFER_MODE_PERSISTENT);
|
|
|
|
EGL_TexBuffer buffer = { 0 };
|
|
const EGL_TexFormat format = testFormat();
|
|
CHECK(egl_texUtilGenBuffers(&format, &buffer, 1));
|
|
CHECK(buffer.mode == EGL_TEX_BUFFER_MODE_PERSISTENT);
|
|
CHECK(buffer.map == mappedStorage);
|
|
CHECK(buffer.pbo == 1);
|
|
CHECK(trace.storage == 1);
|
|
CHECK(trace.map == 1);
|
|
|
|
egl_texUtilFreeBuffers(&buffer, 1);
|
|
CHECK(!buffer.map);
|
|
CHECK(!buffer.pbo);
|
|
CHECK(trace.unmap == 1);
|
|
CHECK(trace.deleted == 1);
|
|
}
|
|
|
|
static void testFallback(void)
|
|
{
|
|
reset();
|
|
trace.failMap = true;
|
|
egl_texUtilSetBufferMode(EGL_TEX_BUFFER_MODE_PERSISTENT);
|
|
|
|
EGL_TexBuffer buffer = { 0 };
|
|
const EGL_TexFormat format = testFormat();
|
|
CHECK(egl_texUtilGenBuffers(&format, &buffer, 1));
|
|
CHECK(buffer.mode == EGL_TEX_BUFFER_MODE_DIRECT);
|
|
CHECK(buffer.map);
|
|
CHECK(!buffer.pbo);
|
|
CHECK(trace.storage == 1);
|
|
CHECK(trace.map == 1);
|
|
CHECK(trace.unmap == 0);
|
|
CHECK(trace.deleted == 1);
|
|
|
|
egl_texUtilFreeBuffers(&buffer, 1);
|
|
CHECK(!buffer.map);
|
|
|
|
reset();
|
|
EGL_TexBuffer next = { 0 };
|
|
CHECK(egl_texUtilGenBuffers(&format, &next, 1));
|
|
CHECK(next.mode == EGL_TEX_BUFFER_MODE_PERSISTENT);
|
|
CHECK(next.map == mappedStorage);
|
|
CHECK(next.pbo == 1);
|
|
egl_texUtilFreeBuffers(&next, 1);
|
|
}
|
|
|
|
static void testDirectFull(void)
|
|
{
|
|
reset();
|
|
|
|
TextureBuffer texture;
|
|
initTexture(&texture, EGL_TEX_BUFFER_MODE_DIRECT);
|
|
texture.damage[0].full = true;
|
|
|
|
CHECK(egl_texBufferStreamProcess(&texture.base, 42) ==
|
|
EGL_TEX_STATUS_UPDATED);
|
|
CHECK(trace.bufferBinds == 1);
|
|
CHECK(trace.boundPBO == 0);
|
|
CHECK(trace.textureBinds == 1);
|
|
CHECK(trace.boundTexture == texture.tex[0]);
|
|
CHECK(trace.unpackAlignment == 1);
|
|
CHECK(trace.unpackRowLength == (GLint)texture.base.format.stride);
|
|
CHECK(trace.uploads == 1);
|
|
CHECK(trace.uploadX == 0);
|
|
CHECK(trace.uploadY == 0);
|
|
CHECK(trace.uploadWidth == (GLsizei)texture.base.format.width);
|
|
CHECK(trace.uploadHeight == (GLsizei)texture.base.format.height);
|
|
CHECK(trace.uploadData == mappedStorage);
|
|
CHECK(trace.fences == 0);
|
|
CHECK(trace.finishes == 0);
|
|
CHECK(trace.flushes == 0);
|
|
CHECK(!texture.buf[0].updated);
|
|
CHECK(!texture.damage[0].full);
|
|
CHECK(texture.damage[0].count == 0);
|
|
CHECK(texture.rIndex == 0);
|
|
CHECK(texture.bufIndex == 0);
|
|
CHECK(texture.base.frameToken == 42);
|
|
checkLockReleased(&texture);
|
|
}
|
|
|
|
static void testDirectRect(void)
|
|
{
|
|
reset();
|
|
|
|
TextureBuffer texture;
|
|
initTexture(&texture, EGL_TEX_BUFFER_MODE_DIRECT);
|
|
texture.damage[0].count = 1;
|
|
texture.damage[0].rects[0].x = 2;
|
|
texture.damage[0].rects[0].y = 1;
|
|
texture.damage[0].rects[0].width = 3;
|
|
texture.damage[0].rects[0].height = 2;
|
|
|
|
const size_t offset =
|
|
texture.damage[0].rects[0].y * texture.base.format.pitch +
|
|
texture.damage[0].rects[0].x * texture.base.format.bpp;
|
|
|
|
CHECK(egl_texBufferStreamProcess(&texture.base, 42) ==
|
|
EGL_TEX_STATUS_UPDATED);
|
|
CHECK(trace.bufferBinds == 1);
|
|
CHECK(trace.boundPBO == 0);
|
|
CHECK(trace.textureBinds == 1);
|
|
CHECK(trace.boundTexture == texture.tex[0]);
|
|
CHECK(trace.unpackAlignment == 1);
|
|
CHECK(trace.unpackRowLength == (GLint)texture.base.format.stride);
|
|
CHECK(trace.uploads == 1);
|
|
CHECK(trace.uploadX == 2);
|
|
CHECK(trace.uploadY == 1);
|
|
CHECK(trace.uploadWidth == 3);
|
|
CHECK(trace.uploadHeight == 2);
|
|
CHECK(trace.uploadData == mappedStorage + offset);
|
|
CHECK(trace.fences == 0);
|
|
CHECK(trace.finishes == 0);
|
|
CHECK(trace.flushes == 0);
|
|
CHECK(!texture.buf[0].updated);
|
|
CHECK(!texture.damage[0].full);
|
|
CHECK(texture.damage[0].count == 0);
|
|
CHECK(texture.rIndex == 0);
|
|
CHECK(texture.bufIndex == 0);
|
|
CHECK(texture.base.frameToken == 42);
|
|
checkLockReleased(&texture);
|
|
}
|
|
|
|
static void testPersistentFull(void)
|
|
{
|
|
reset();
|
|
|
|
TextureBuffer texture;
|
|
initTexture(&texture, EGL_TEX_BUFFER_MODE_PERSISTENT);
|
|
texture.damage[0].full = true;
|
|
|
|
CHECK(egl_texBufferStreamProcess(&texture.base, 42) ==
|
|
EGL_TEX_STATUS_UPDATED);
|
|
CHECK(trace.bufferBinds == 1);
|
|
CHECK(trace.boundPBO == texture.buf[0].pbo);
|
|
CHECK(trace.textureBinds == 1);
|
|
CHECK(trace.boundTexture == texture.tex[0]);
|
|
CHECK(trace.unpackAlignment == 1);
|
|
CHECK(trace.unpackRowLength == (GLint)texture.base.format.stride);
|
|
CHECK(trace.uploads == 1);
|
|
CHECK(trace.uploadX == 0);
|
|
CHECK(trace.uploadY == 0);
|
|
CHECK(trace.uploadWidth == (GLsizei)texture.base.format.width);
|
|
CHECK(trace.uploadHeight == (GLsizei)texture.base.format.height);
|
|
CHECK(!trace.uploadData);
|
|
CHECK(trace.fences == 1);
|
|
CHECK(trace.finishes == 0);
|
|
CHECK(trace.flushes == 1);
|
|
CHECK(texture.sync[0] == (GLsync)(uintptr_t)1);
|
|
CHECK(!texture.buf[0].updated);
|
|
CHECK(!texture.damage[0].full);
|
|
CHECK(texture.damage[0].count == 0);
|
|
CHECK(texture.rIndex == 0);
|
|
CHECK(texture.bufIndex == 0);
|
|
CHECK(texture.base.frameToken == 42);
|
|
checkLockReleased(&texture);
|
|
}
|
|
|
|
static void testPersistentRect(void)
|
|
{
|
|
reset();
|
|
|
|
TextureBuffer texture;
|
|
initTexture(&texture, EGL_TEX_BUFFER_MODE_PERSISTENT);
|
|
texture.damage[0].count = 1;
|
|
texture.damage[0].rects[0].x = 2;
|
|
texture.damage[0].rects[0].y = 1;
|
|
texture.damage[0].rects[0].width = 3;
|
|
texture.damage[0].rects[0].height = 2;
|
|
|
|
const uintptr_t offset =
|
|
texture.damage[0].rects[0].y * texture.base.format.pitch +
|
|
texture.damage[0].rects[0].x * texture.base.format.bpp;
|
|
|
|
CHECK(egl_texBufferStreamProcess(&texture.base, 42) ==
|
|
EGL_TEX_STATUS_UPDATED);
|
|
CHECK(trace.bufferBinds == 1);
|
|
CHECK(trace.boundPBO == texture.buf[0].pbo);
|
|
CHECK(trace.textureBinds == 1);
|
|
CHECK(trace.boundTexture == texture.tex[0]);
|
|
CHECK(trace.unpackAlignment == 1);
|
|
CHECK(trace.unpackRowLength == (GLint)texture.base.format.stride);
|
|
CHECK(trace.uploads == 1);
|
|
CHECK(trace.uploadX == 2);
|
|
CHECK(trace.uploadY == 1);
|
|
CHECK(trace.uploadWidth == 3);
|
|
CHECK(trace.uploadHeight == 2);
|
|
CHECK(trace.uploadData == (const void *)offset);
|
|
CHECK(trace.fences == 1);
|
|
CHECK(trace.finishes == 0);
|
|
CHECK(trace.flushes == 1);
|
|
CHECK(texture.sync[0] == (GLsync)(uintptr_t)1);
|
|
CHECK(!texture.buf[0].updated);
|
|
CHECK(!texture.damage[0].full);
|
|
CHECK(texture.damage[0].count == 0);
|
|
CHECK(texture.rIndex == 0);
|
|
CHECK(texture.bufIndex == 0);
|
|
CHECK(texture.base.frameToken == 42);
|
|
checkLockReleased(&texture);
|
|
}
|
|
|
|
struct Test
|
|
{
|
|
const char * name;
|
|
void (*run)(void);
|
|
};
|
|
|
|
static const struct Test tests[] =
|
|
{
|
|
{ "direct" , testDirect },
|
|
{ "persistent" , testPersistent },
|
|
{ "fallback" , testFallback },
|
|
{ "direct-full" , testDirectFull },
|
|
{ "direct-rect" , testDirectRect },
|
|
{ "persistent-full" , testPersistentFull },
|
|
{ "persistent-rect" , testPersistentRect },
|
|
};
|
|
|
|
int main(int argc, char ** argv)
|
|
{
|
|
CHECK(argc == 2);
|
|
for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i)
|
|
if (!strcmp(argv[1], tests[i].name))
|
|
{
|
|
tests[i].run();
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
return EXIT_FAILURE;
|
|
}
|