[retro-gtk] Introduce RetroGLRenderer



commit 2d91cc5013265ba2b78a90c5273952628f774b6e
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Fri Jan 31 22:34:00 2020 +0500

    Introduce RetroGLRenderer
    
    This implements an OpenGL renderer. Use EGL to get a context, this should
    work on both Wayland and X11, except maybe some old drivers that only
    support GLX.
    
    Use a rather hacky way to get epoxy to provide pointers to OpenGL
    functions, as it uses epoxy_gl* names internally, but it's better than
    listing them all manually.
    
    Unfortunately, for now we have to flip the scene on CPU if
    bottom_left_origin=true, later we'll need to pass the framebuffer as is
    with a flag, and instead flip it on UI process side by just drawing the
    rectangle upside down for RetroGLDisplay, and applying a scale
    transformation for RetroCairoDisplay.

 retro-runner/meson.build                 |   2 +
 retro-runner/retro-gl-renderer-private.h |  22 ++
 retro-runner/retro-gl-renderer.c         | 374 +++++++++++++++++++++++++++++++
 3 files changed, 398 insertions(+)
---
diff --git a/retro-runner/meson.build b/retro-runner/meson.build
index a826c4c..dcb6c3e 100644
--- a/retro-runner/meson.build
+++ b/retro-runner/meson.build
@@ -7,6 +7,7 @@ retro_runner_sources = [
   'retro-core.c',
   'retro-environment.c',
   'retro-game-info.c',
+  'retro-gl-renderer.c',
   'retro-input-descriptor.c',
   'retro-main-loop-source.c',
   'retro-module.c',
@@ -17,6 +18,7 @@ retro_runner_sources = [
 ]
 
 retro_runner_deps = [
+  epoxy,
   gio,
   gio_unix,
   gmodule,
diff --git a/retro-runner/retro-gl-renderer-private.h b/retro-runner/retro-gl-renderer-private.h
new file mode 100644
index 0000000..f07e045
--- /dev/null
+++ b/retro-runner/retro-gl-renderer-private.h
@@ -0,0 +1,22 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#pragma once
+
+#if !defined(__RETRO_GTK_INSIDE__) && !defined(RETRO_GTK_COMPILATION)
+# error "Only <retro-gtk.h> can be included directly."
+#endif
+
+#include <glib-object.h>
+#include "retro-core.h"
+#include "retro-renderer-private.h"
+
+G_BEGIN_DECLS
+
+#define RETRO_TYPE_GL_RENDERER (retro_gl_renderer_get_type())
+
+G_DECLARE_FINAL_TYPE (RetroGLRenderer, retro_gl_renderer, RETRO, GL_RENDERER, GObject)
+
+RetroRenderer *retro_gl_renderer_new (RetroCore             *core,
+                                      RetroHWRenderCallback *callback);
+
+G_END_DECLS
diff --git a/retro-runner/retro-gl-renderer.c b/retro-runner/retro-gl-renderer.c
new file mode 100644
index 0000000..b6cff09
--- /dev/null
+++ b/retro-runner/retro-gl-renderer.c
@@ -0,0 +1,374 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#include "retro-gl-renderer-private.h"
+
+#include <gio/gio.h>
+#include "epoxy/egl.h"
+
+#define MAX_EGL_ATTRS 30
+
+struct _RetroGLRenderer
+{
+  GObject parent_instance;
+  RetroCore *core;
+  RetroHWRenderCallback *callback;
+
+  EGLDisplay display;
+  EGLContext context;
+  GModule *gl_module;
+
+  guint framebuffer;
+  guint renderbuffer;
+  guint texture;
+
+  guint8 *buf_flip;
+  gsize last_size;
+};
+
+static void retro_renderer_interface_init (RetroRendererInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (RetroGLRenderer, retro_gl_renderer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (RETRO_TYPE_RENDERER,
+                                                retro_renderer_interface_init))
+
+static void
+check_egl_errors (const gchar *msg)
+{
+  EGLint err;
+
+  while ((err = eglGetError ()) != EGL_SUCCESS)
+    g_critical ("EGL error 0x%x at %s()", err, msg);
+}
+
+static void
+check_gl_errors (const gchar *msg)
+{
+  GLenum err;
+
+  while ((err = glGetError ()) != GL_NO_ERROR)
+    g_critical ("OpenGL error 0x%x at %s", err, msg);
+}
+
+static void
+init_framebuffer (RetroGLRenderer *self,
+                  guint            width,
+                  guint            height)
+{
+  GLenum status;
+  GLint max_fbo_size, max_rb_size;
+
+  glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_fbo_size);
+  glGetIntegerv (GL_MAX_RENDERBUFFER_SIZE, &max_rb_size);
+
+  width = MIN (width, MIN (max_fbo_size, max_rb_size));
+  height = MIN (height, MIN (max_fbo_size, max_rb_size));
+
+  glGenFramebuffers (1, &self->framebuffer);
+  glBindFramebuffer (GL_FRAMEBUFFER, self->framebuffer);
+
+  glGenTextures (1, &self->texture);
+  glBindTexture (GL_TEXTURE_2D, self->texture);
+  glTexStorage2D (GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
+  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                          GL_TEXTURE_2D, self->texture, 0);
+
+  if (self->callback->depth) {
+    glGenRenderbuffers (1, &self->renderbuffer);
+    glBindRenderbuffer (GL_RENDERBUFFER, self->renderbuffer);
+    if (self->callback->stencil)
+      glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
+    else
+      glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
+    glBindRenderbuffer (GL_RENDERBUFFER, 0);
+
+    if (self->callback->stencil)
+      glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
+                                 GL_RENDERBUFFER, self->renderbuffer);
+    else
+      glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                                 GL_RENDERBUFFER, self->renderbuffer);
+  } else {
+    self->renderbuffer = 0;
+  }
+
+  check_gl_errors ("init_framebuffer");
+
+  status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
+  if (status != GL_FRAMEBUFFER_COMPLETE)
+     g_critical ("Framebuffer not complete: %d", status);
+
+  glClearColor (0, 0, 0, 1);
+  if (self->callback->depth && self->callback->stencil)
+    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+  else if (self->callback->depth)
+    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  else
+    glClear (GL_COLOR_BUFFER_BIT);
+
+  glBindTexture (GL_TEXTURE_2D, 0);
+  glBindFramebuffer (GL_FRAMEBUFFER, 0);
+}
+
+static void
+retro_gl_renderer_realize (RetroRenderer *renderer,
+                           guint          width,
+                           guint          height)
+{
+  RetroGLRenderer *self = RETRO_GL_RENDERER (renderer);
+
+  eglMakeCurrent (self->display, EGL_NO_SURFACE, EGL_NO_SURFACE, self->context);
+  check_egl_errors ("eglMakeCurrent");
+
+  init_framebuffer (self, width, height);
+
+  self->callback->context_reset ();
+}
+
+static RetroProcAddress
+retro_gl_renderer_get_proc_address (RetroRenderer *renderer,
+                                    const gchar   *sym)
+{
+  RetroGLRenderer *self = RETRO_GL_RENDERER (renderer);
+  RetroProcAddress *out = NULL;
+  g_autofree gchar *sym_mangled = NULL;
+
+  sym_mangled = g_strdup_printf ("epoxy_%s", sym);
+  g_module_symbol (self->gl_module, sym_mangled, (gpointer) &out);
+
+  if (!out)
+    return NULL;
+
+  return *out;
+}
+
+static guintptr
+retro_gl_renderer_get_current_framebuffer (RetroRenderer *renderer)
+{
+  RetroGLRenderer *self = RETRO_GL_RENDERER (renderer);
+
+  return self->framebuffer;
+}
+
+static void
+retro_gl_renderer_snapshot (RetroRenderer    *renderer,
+                            RetroPixelFormat  pixel_format,
+                            guint             width,
+                            guint             height,
+                            gsize             rowstride,
+                            guint8           *data)
+{
+  RetroGLRenderer *self = RETRO_GL_RENDERER (renderer);
+  gsize size;
+  GLenum format, type;
+
+  if (!retro_pixel_format_to_gl (pixel_format, &format, &type, NULL))
+    return;
+
+  size = rowstride * height;
+
+  if (!self->framebuffer)
+    return;
+
+  if (self->callback->bottom_left_origin && size != self->last_size) {
+    g_clear_pointer (&self->buf_flip, g_free);
+    self->buf_flip = g_malloc0 (size);
+    self->last_size = size;
+  }
+
+  glBindFramebuffer (GL_FRAMEBUFFER, self->framebuffer);
+  glReadBuffer(GL_COLOR_ATTACHMENT0);
+  glReadnPixels (0, 0, width, height, format, type, size,
+                 self->callback->bottom_left_origin ? self->buf_flip : data);
+  glBindFramebuffer (GL_FRAMEBUFFER, 0);
+
+  check_gl_errors ("snapshot");
+
+  if (self->callback->bottom_left_origin) {
+    gsize i;
+
+    for (i = 0; i < size; i += rowstride)
+      memcpy (&data[i], &self->buf_flip[size - i - rowstride], rowstride);
+  }
+
+  eglSwapBuffers (self->display, self->context);
+}
+
+static void
+retro_gl_renderer_finalize (GObject *object)
+{
+  RetroGLRenderer *self = RETRO_GL_RENDERER (object);
+
+  /* Ideally we need to cleanly deinit the core and call this earlier,
+   * so that it actually has a chance to run */
+  self->callback->context_destroy ();
+
+  if (self->texture) {
+    glDeleteTextures (1, &self->texture);
+    self->texture = 0;
+  }
+
+  if (self->renderbuffer) {
+    glDeleteRenderbuffers (1, &self->renderbuffer);
+    self->renderbuffer = 0;
+  }
+
+  if (self->framebuffer) {
+    glDeleteFramebuffers (1, &self->framebuffer);
+    self->framebuffer = 0;
+  }
+
+  eglDestroyContext (self->display, self->context);
+  self->context = EGL_NO_CONTEXT;
+
+  eglTerminate (self->display);
+  self->display = EGL_NO_DISPLAY;
+
+  g_module_close (self->gl_module);
+  g_clear_object (&self->gl_module);
+
+  self->core = NULL;
+  self->callback = NULL;
+
+  g_clear_pointer (&self->buf_flip, g_free);
+  self->last_size = 0;
+
+  G_OBJECT_CLASS (retro_gl_renderer_parent_class)->finalize (object);
+}
+
+static void
+retro_gl_renderer_class_init (RetroGLRendererClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = retro_gl_renderer_finalize;
+}
+
+static void
+retro_gl_renderer_init (RetroGLRenderer *self)
+{
+}
+
+static void
+retro_renderer_interface_init (RetroRendererInterface *iface)
+{
+  iface->realize = retro_gl_renderer_realize;
+  iface->get_proc_address = retro_gl_renderer_get_proc_address;
+  iface->get_current_framebuffer = retro_gl_renderer_get_current_framebuffer;
+  iface->snapshot = retro_gl_renderer_snapshot;
+}
+
+static EGLConfig
+get_egl_config (EGLDisplay  display)
+{
+  g_autofree EGLConfig *configs = NULL;
+  EGLint count;
+  EGLint attrs[] = {
+    EGL_SURFACE_TYPE,      EGL_WINDOW_BIT,
+    EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
+    EGL_RED_SIZE,          1,
+    EGL_GREEN_SIZE,        1,
+    EGL_BLUE_SIZE,         1,
+    EGL_ALPHA_SIZE,        0,
+    EGL_DEPTH_SIZE,        0,
+    EGL_RENDERABLE_TYPE,   EGL_OPENGL_BIT,
+    EGL_NONE
+  };
+
+  if (!eglChooseConfig (display, attrs, NULL, 0, &count) || count < 1)
+    return NULL;
+
+  configs = g_new (EGLConfig, count);
+
+  if (!eglChooseConfig (display, attrs, configs, count, &count) || count < 1)
+    return NULL;
+
+  return configs[0];
+}
+
+RetroRenderer *
+retro_gl_renderer_new (RetroCore             *core,
+                       RetroHWRenderCallback *callback)
+{
+  RetroGLRenderer *self = NULL;
+  EGLConfig config;
+  EGLint context_attribs[MAX_EGL_ATTRS];
+  gboolean is_opengl_es;
+  gboolean use_compat_profile;
+  gint major_version, minor_version, i;
+
+  g_return_val_if_fail (RETRO_IS_CORE (core), NULL);
+  g_return_val_if_fail (callback != NULL, NULL);
+
+  self = g_object_new (RETRO_TYPE_GL_RENDERER, NULL);
+  self->core = core;
+  self->callback = callback;
+
+  is_opengl_es = FALSE;
+  use_compat_profile = FALSE;
+  minor_version = 0;
+
+  switch (callback->context_type) {
+  case RETRO_HW_CONTEXT_OPENGL:
+    use_compat_profile = TRUE;
+    major_version = 2;
+    break;
+  case RETRO_HW_CONTEXT_OPENGL_CORE:
+    major_version = callback->version_major;
+    minor_version = callback->version_minor;
+    break;
+  case RETRO_HW_CONTEXT_OPENGLES2:
+    is_opengl_es = TRUE;
+    major_version = 2;
+    break;
+  case RETRO_HW_CONTEXT_OPENGLES3:
+    is_opengl_es = TRUE;
+    major_version = 3;
+    break;
+  case RETRO_HW_CONTEXT_OPENGLES_VERSION:
+    is_opengl_es = TRUE;
+    major_version = callback->version_major;
+    minor_version = callback->version_minor;
+    break;
+  default:
+    g_assert_not_reached ();
+  }
+
+  self->display = eglGetDisplay (EGL_DEFAULT_DISPLAY);
+  check_egl_errors ("eglGetDisplay");
+
+  eglInitialize (self->display, NULL, NULL);
+  check_egl_errors ("eglInitialize");
+
+  config = get_egl_config (self->display);
+  check_egl_errors ("get_egl_config");
+
+  if (!config)
+    g_error ("Cannot find EGL config");
+
+  eglBindAPI (is_opengl_es ? EGL_OPENGL_ES_API : EGL_OPENGL_API);
+  check_egl_errors ("eglBindAPI");
+
+  i = 0;
+  context_attribs[i++] = EGL_CONTEXT_MAJOR_VERSION;
+  context_attribs[i++] = major_version;
+  context_attribs[i++] = EGL_CONTEXT_MINOR_VERSION;
+  context_attribs[i++] = minor_version;
+  context_attribs[i++] = EGL_CONTEXT_OPENGL_PROFILE_MASK;
+  context_attribs[i++] = use_compat_profile ?
+    EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT :
+    EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT;
+
+  if (callback->debug_context) {
+    context_attribs[i++] = EGL_CONTEXT_FLAGS_KHR;
+    context_attribs[i++] = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
+  }
+
+  context_attribs[i++] = EGL_NONE;
+
+  self->context = eglCreateContext (self->display, config, EGL_NO_CONTEXT, context_attribs);
+  check_egl_errors ("eglCreateContext");
+
+  self->gl_module = g_module_open (NULL, G_MODULE_BIND_LAZY);
+
+  return RETRO_RENDERER (self);
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]