blob: 97c35fa9b761fbd71392993780e09fe5c821c26e [file] [log] [blame] [edit]
/*
* Copyright 2022 Rive
*/
#include "testing_window.hpp"
#if defined(RIVE_TOOLS_NO_GL) || defined(__EMSCRIPTEN__)
TestingWindow* TestingWindow::MakeEGL(Backend backend,
const BackendParams& backendParams,
void* platformWindow)
{
return nullptr;
}
#else
#include <cassert>
#include <stdio.h>
#define EGL_EGL_PROTOTYPES 0
#include <EGL/egl.h>
#include <EGL/eglext.h>
// Include after Windows headers due to conflicts with #defines.
#include "common/offscreen_render_target.hpp"
#include "rive/renderer/gl/render_context_gl_impl.hpp"
#include "rive/renderer/gl/render_target_gl.hpp"
#include "rive/renderer.hpp"
#include "rive/renderer/gl/render_target_gl.hpp"
#include "testing_gl_renderer.hpp"
#if defined(_WIN32)
#include <libloaderapi.h>
using PlatformLibraryHandle = HMODULE;
using PlatformProcAddress = FARPROC;
static PlatformLibraryHandle platform_dlopen(const char* libname)
{
PlatformLibraryHandle lib = LoadLibraryA(libname);
if (!lib)
{
WCHAR err[512];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
0,
GetLastError(),
0,
err,
512,
0);
fprintf(stderr, "Failed load %s. %ws\n", libname, err);
}
return lib;
}
static PlatformProcAddress platform_dlsym(PlatformLibraryHandle lib,
const char* symbol)
{
return GetProcAddress(lib, symbol);
}
#else
#include <dlfcn.h>
using PlatformLibraryHandle = void*;
using PlatformProcAddress = void*;
static PlatformLibraryHandle platform_dlopen(const char* libname)
{
std::string filename = libname;
#ifdef __APPLE__
filename.append(".dylib");
#else
filename.append(".so");
#endif
PlatformLibraryHandle lib = dlopen(filename.c_str(), RTLD_LAZY);
if (!lib)
{
fprintf(stderr, "Failed load %s. %s\n", filename.c_str(), dlerror());
}
return lib;
}
static PlatformProcAddress platform_dlsym(PlatformLibraryHandle lib,
const char* symbol)
{
return dlsym(lib, symbol);
}
#endif
static PlatformLibraryHandle s_LibEGL = nullptr;
#define GET_EGL_PROC(eglProc) \
eglProc = (decltype(eglProc))platform_dlsym(s_LibEGL, #eglProc); \
if (!eglProc) \
{ \
fprintf(stderr, "Failed locate " #eglProc " in libEGL.\n"); \
abort(); \
}
static PFNEGLGETDISPLAYPROC eglGetDisplay;
static PFNEGLQUERYSTRINGPROC eglQueryString;
static PFNEGLINITIALIZEPROC eglInitialize;
static PFNEGLCHOOSECONFIGPROC eglChooseConfig;
static PFNEGLCREATECONTEXTPROC eglCreateContext;
static PFNEGLDESTROYCONTEXTPROC eglDestroyContext;
static PFNEGLCREATEPBUFFERSURFACEPROC eglCreatePbufferSurface;
static PFNEGLCREATEWINDOWSURFACEPROC eglCreateWindowSurface;
static PFNEGLQUERYSURFACEPROC eglQuerySurface;
static PFNEGLDESTROYSURFACEPROC eglDestroySurface;
static PFNEGLMAKECURRENTPROC eglMakeCurrent;
static PFNEGLSWAPBUFFERSPROC eglSwapBuffers;
static PFNEGLGETPROCADDRESSPROC eglGetProcAddress;
static void init_egl()
{
if (s_LibEGL)
{
return;
}
s_LibEGL = platform_dlopen("libEGL");
if (!s_LibEGL)
{
fprintf(stderr,
"Failed load libEGL. Did you build ANGLE and copy over libEGL "
"and libGLESv2?\n");
abort();
}
GET_EGL_PROC(eglGetDisplay);
GET_EGL_PROC(eglQueryString);
GET_EGL_PROC(eglInitialize);
GET_EGL_PROC(eglChooseConfig);
GET_EGL_PROC(eglCreateContext);
GET_EGL_PROC(eglDestroyContext);
GET_EGL_PROC(eglCreatePbufferSurface);
GET_EGL_PROC(eglCreateWindowSurface);
GET_EGL_PROC(eglQuerySurface);
GET_EGL_PROC(eglDestroySurface);
GET_EGL_PROC(eglMakeCurrent);
GET_EGL_PROC(eglSwapBuffers);
GET_EGL_PROC(eglGetProcAddress);
}
#ifdef DEBUG
static void GL_APIENTRY err_msg_callback(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam)
{
if (type == GL_DEBUG_TYPE_ERROR_KHR)
{
printf("GL ERROR: %s\n", message);
fflush(stdout);
abort();
}
else if (type == GL_DEBUG_TYPE_PERFORMANCE_KHR)
{
// Don't display GL perf warnings during normal testing.
#if 0
printf("GL PERF: %s\n", message);
#endif
}
else
{
printf("GL MISC: %s\n", message);
}
}
#endif
// Use an offscreen, platform independent window.
class EGLWindow
{
public:
virtual ~EGLWindow() { deleteSurface(); }
operator EGLSurface() const { return m_surface; }
int width() const { return m_width; }
int height() const { return m_height; }
bool isMSAA() const { return m_isMSAA; }
virtual bool isOffscreen() const = 0;
virtual bool resize(int width, int height) = 0;
protected:
EGLWindow(EGLDisplay display, EGLConfig config, int samples) :
m_display(display),
m_config(config),
// "samples == 1" means "msaa mode with 1 sample".
// "samples == 0" means "non-msaa mode".
m_isMSAA(samples > 0)
{}
void deleteSurface()
{
if (m_surface && !eglDestroySurface(m_display, m_surface))
{
fprintf(stderr, "eglDestroySurface failed.\n");
abort();
}
}
const EGLDisplay m_display;
const EGLConfig m_config;
const bool m_isMSAA;
void* m_surface = nullptr;
int m_width = 0;
int m_height = 0;
};
class PbufferWindow : public EGLWindow
{
public:
PbufferWindow(EGLDisplay display, EGLConfig config, int samples) :
EGLWindow(display, config, samples)
{
resize(1, 1);
}
bool isOffscreen() const final { return true; }
bool resize(int width, int height) final
{
deleteSurface();
const EGLint surfaceAttribs[] = {EGL_WIDTH,
width,
EGL_HEIGHT,
height,
EGL_NONE};
m_surface =
eglCreatePbufferSurface(m_display, m_config, surfaceAttribs);
if (!m_surface)
{
fprintf(stderr, "eglCreatePbufferSurface failed.\n");
abort();
}
m_width = width;
m_height = height;
return true;
}
};
class NativeWindow : public EGLWindow
{
public:
NativeWindow(EGLDisplay display,
EGLConfig config,
int samples,
void* platformWindow) :
EGLWindow(display, config, samples)
{
m_surface = eglCreateWindowSurface(
m_display,
m_config,
reinterpret_cast<EGLNativeWindowType>(platformWindow),
nullptr);
if (!m_surface)
{
fprintf(stderr, "eglCreateWindowSurface failed.\n");
abort();
}
EGLint eglInt;
eglQuerySurface(m_display, m_surface, EGL_WIDTH, &eglInt);
m_width = eglInt;
eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &eglInt);
m_height = eglInt;
}
bool isOffscreen() const final { return false; }
bool resize(int width, int height) final
{
return width <= m_width && height <= m_height;
}
};
class TestingWindowEGL : public TestingWindow
{
public:
TestingWindowEGL(const BackendParams& backendParams,
EGLint angleBackend,
void* platformWindow) :
m_renderer(TestingGLRenderer::Make(backendParams))
{
init_egl();
int samples = backendParams.msaa ? 4 : 0;
#ifdef RIVE_DESKTOP_GL
if (angleBackend != EGL_NONE)
{
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT;
GET_EGL_PROC(eglGetPlatformDisplayEXT);
const EGLint displayAttribs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE,
angleBackend,
EGL_NONE};
m_Display = eglGetPlatformDisplayEXT(
EGL_PLATFORM_ANGLE_ANGLE,
reinterpret_cast<void*>(EGL_DEFAULT_DISPLAY),
displayAttribs);
if (!m_Display)
{
fprintf(stderr, "eglGetPlatformDisplayEXT failed.\n");
abort();
}
samples = 0; // Test our offscreen MSAA path on ANGLE.
}
else
#endif
{
assert(angleBackend == EGL_NONE);
m_Display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!m_Display)
{
fprintf(stderr, "eglGetDisplay failed.\n");
abort();
}
}
EGLint majorVersion;
EGLint minorVersion;
if (!eglInitialize(m_Display, &majorVersion, &minorVersion))
{
fprintf(stderr, "eglInitialize failed.\n");
abort();
}
// printf("Initialized EGL version %i.%i\n", majorVersion,
// minorVersion); printf("EGL_VENDOR: %s\n", eglQueryString(m_Display,
// EGL_VENDOR)); printf("EGL_VERSION: %s\n", eglQueryString(m_Display,
// EGL_VERSION));
if (angleBackend != EGL_NONE &&
!strstr(eglQueryString(m_Display, EGL_VERSION), "ANGLE"))
{
fprintf(stderr,
"libEGL does not appear to be ANGLE. Did you build ANGLE "
"and copy over libEGL "
"and libGLESv2?\n");
abort();
}
EGLint numConfigs;
const EGLint configAttribs[] = {EGL_SURFACE_TYPE,
EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES3_BIT,
EGL_COLOR_BUFFER_TYPE,
EGL_RGB_BUFFER,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_DEPTH_SIZE,
samples == 0 ? 0 : 24,
EGL_STENCIL_SIZE,
samples == 0 ? 0 : 8,
EGL_SAMPLE_BUFFERS,
samples == 0 ? 0 : 1,
EGL_SAMPLES,
samples,
EGL_NONE};
EGLConfig config;
if (!eglChooseConfig(m_Display, configAttribs, &config, 1, &numConfigs))
{
fprintf(stderr, "eglChooseConfig failed.\n");
abort();
}
// Create an ES 3.0 context. This very closely mirrors webgl2.
const EGLint contextAttribs[] = {EGL_CONTEXT_MAJOR_VERSION,
3,
EGL_CONTEXT_MINOR_VERSION,
0,
EGL_NONE};
m_Context =
eglCreateContext(m_Display, config, nullptr, contextAttribs);
if (m_Context == EGL_NO_CONTEXT)
{
fprintf(stderr, "eglCreateContext failed.\n");
abort();
}
if (platformWindow != nullptr)
{
m_window = std::make_unique<NativeWindow>(m_Display,
config,
samples,
platformWindow);
}
else
{
m_window =
std::make_unique<PbufferWindow>(m_Display, config, samples);
}
m_width = m_window->width();
m_height = m_window->height();
if (!eglMakeCurrent(m_Display, *m_window, *m_window, m_Context))
{
fprintf(stderr, "eglMakeCurrent failed.\n");
abort();
}
#ifdef RIVE_DESKTOP_GL
// Load the OpenGL API using glad.
if (!gladLoadCustomLoader(
reinterpret_cast<GLADloadfunc>(eglGetProcAddress)))
{
fprintf(stderr, "Failed to initialize glad.\n");
abort();
}
#endif
auto rendererStr =
reinterpret_cast<const char*>(glGetString(GL_RENDERER));
printf("==== EGL GPU: OpenGL %s; %s; %s ====\n",
glGetString(GL_VENDOR),
rendererStr,
glGetString(GL_VERSION));
int extensionCount;
glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount);
#ifdef DEBUG
for (int i = 0; i < extensionCount; ++i)
{
auto* ext =
reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
if (strcmp(ext, "GL_KHR_debug") == 0 &&
// Our shader compiles can take over 30 SECONDS on PowerVR when
// debug output is enabled. Just don't use it.
!strstr(rendererStr, "PowerVR"))
{
glEnable(GL_DEBUG_OUTPUT_KHR);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
auto glDebugMessageControlKHR =
reinterpret_cast<PFNGLDEBUGMESSAGECONTROLKHRPROC>(
eglGetProcAddress("glDebugMessageControlKHR"));
assert(glDebugMessageControlKHR);
glDebugMessageControlKHR(GL_DONT_CARE,
GL_DONT_CARE,
GL_DONT_CARE,
0,
NULL,
GL_TRUE);
auto glDebugMessageCallbackKHR =
reinterpret_cast<PFNGLDEBUGMESSAGECALLBACKKHRPROC>(
eglGetProcAddress("glDebugMessageCallbackKHR"));
assert(glDebugMessageCallbackKHR);
glDebugMessageCallbackKHR(&err_msg_callback, nullptr);
// printf("%s enabled.\n", ext);
continue;
}
}
#endif
m_renderer->init(reinterpret_cast<void*>(eglGetProcAddress));
}
~TestingWindowEGL()
{
eglMakeCurrent(m_Display,
EGL_NO_SURFACE,
EGL_NO_SURFACE,
EGL_NO_CONTEXT);
if (m_Context)
{
eglDestroyContext(m_Display, m_Context);
}
}
rive::Factory* factory() override { return m_renderer->factory(); }
void resize(int width, int height) override
{
if (width == m_width && height == m_height)
{
return;
}
// EXT_shader_pixel_local_storage has issues rendering to an offscreen
// Pbuffer.
bool needsOffscreenWorkaround = !m_window->isMSAA() &&
renderContextGLImpl()
->capabilities()
.EXT_shader_pixel_local_storage &&
m_window->isOffscreen();
if (needsOffscreenWorkaround || !m_window->resize(width, height))
{
// ARM Mali GPUs seem to hang while rendering goldens to a Pbuffer
// with EXT_shader_pixel_local_storage. Rendering to a texture
// instead seems to work.
m_headlessRenderTexture = glutils::Texture();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_headlessRenderTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
}
else
{
m_headlessRenderTexture = glutils::Texture::Zero();
if (!eglMakeCurrent(m_Display, *m_window, *m_window, m_Context))
{
fprintf(stderr, "eglMakeCurrent failed.\n");
abort();
}
}
glViewport(0, 0, width, height);
m_width = width;
m_height = height;
}
std::unique_ptr<rive::Renderer> beginFrame(
const FrameOptions& options) override
{
auto renderer =
m_renderer->reset(m_width, m_height, m_headlessRenderTexture);
m_renderer->beginFrame(options);
return renderer;
}
void endFrame(std::vector<uint8_t>* pixelData) override
{
m_renderer->flush();
if (m_headlessRenderTexture != 0 && !m_window->isOffscreen())
{
// Copy the offscreen texture back to the main window for
// visualization purposes.
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0,
0,
m_width,
m_height,
0,
0,
m_width,
m_height,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
}
if (pixelData)
{
pixelData->resize(m_height * m_width * 4);
static_cast<rive::gpu::RenderTargetGL*>(m_renderer->renderTarget())
->bindDestinationFramebuffer(GL_READ_FRAMEBUFFER);
glReadPixels(0,
0,
m_width,
m_height,
GL_RGBA,
GL_UNSIGNED_BYTE,
pixelData->data());
}
eglSwapBuffers(m_Display, *m_window);
}
rive::gpu::RenderContext* renderContext() const override
{
return m_renderer->renderContext();
}
rive::gpu::RenderContextGLImpl* renderContextGLImpl() const override
{
if (auto ctx = renderContext())
{
return ctx->static_impl_cast<rive::gpu::RenderContextGLImpl>();
}
return nullptr;
}
rive::gpu::RenderTarget* renderTarget() const override
{
return m_renderer->renderTarget();
}
rive::rcp<rive_tests::OffscreenRenderTarget> makeOffscreenRenderTarget(
uint32_t width,
uint32_t height,
bool riveRenderable) const override
{
return rive_tests::OffscreenRenderTarget::MakeGL(renderContextGLImpl(),
width,
height);
}
void flushPLSContext(
rive::gpu::RenderTarget* offscreenRenderTarget) override
{
m_renderer->flushPLSContext(offscreenRenderTarget);
}
private:
const std::unique_ptr<TestingGLRenderer> m_renderer;
void* m_Display;
void* m_Context;
std::unique_ptr<EGLWindow> m_window;
glutils::Texture m_headlessRenderTexture = glutils::Texture::Zero();
};
TestingWindow* TestingWindow::MakeEGL(Backend backend,
const BackendParams& backendParams,
void* platformWindow)
{
EGLint angleBackend = EGL_NONE;
if (backend == Backend::angle)
{
#ifdef __APPLE__
angleBackend = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE;
#elif defined(_WIN32)
angleBackend = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE;
#else
angleBackend = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE;
#endif
}
return new TestingWindowEGL(backendParams, angleBackend, platformWindow);
}
#endif