Merge pull request #169 from Themaister/gl-render

HW GL render for libretro.
This commit is contained in:
Hans-Kristian Arntzen 2013-03-30 06:33:53 -07:00
commit ebd7dcf721
12 changed files with 791 additions and 27 deletions

View File

@ -270,6 +270,24 @@ void driver_set_monitor_refresh_rate(float hz)
}
uintptr_t driver_get_current_framebuffer(void)
{
#ifdef HAVE_FBO
if (driver.video_poke && driver.video_poke->get_current_framebuffer)
return driver.video_poke->get_current_framebuffer(driver.video_data);
else
#endif
return 0;
}
retro_proc_address_t driver_get_proc_address(const char *sym)
{
if (driver.video_poke && driver.video_poke->get_proc_address)
return driver.video_poke->get_proc_address(driver.video_data, sym);
else
return NULL;
}
// Only called once on init and deinit.
// Video and input drivers need to be active (owned)
// before retroarch core starts.
@ -322,6 +340,10 @@ void init_drivers(void)
adjust_system_rates();
init_video_input();
if (g_extern.system.hw_render_callback.context_reset)
g_extern.system.hw_render_callback.context_reset();
init_audio();
}
@ -645,6 +667,12 @@ static void init_filter(bool rgb32)
if (!*g_settings.video.filter_path)
return;
if (g_extern.system.hw_render_callback.context_type)
{
RARCH_WARN("Cannot use CPU filters when hardware rendering is used.\n");
return;
}
RARCH_LOG("Loading bSNES filter from \"%s\"\n", g_settings.video.filter_path);
g_extern.filter.lib = dylib_load(g_settings.video.filter_path);
if (!g_extern.filter.lib)

View File

@ -326,6 +326,8 @@ typedef struct video_poke_interface
#ifdef HAVE_FBO
void (*set_fbo_state)(void *data, unsigned state);
unsigned (*get_fbo_state)(void *data);
uintptr_t (*get_current_framebuffer)(void *data);
retro_proc_address_t (*get_proc_address)(void *data, const char *sym);
#endif
void (*set_aspect_ratio)(void *data, unsigned aspectratio_index);
void (*apply_state_changes)(void *data);
@ -452,6 +454,10 @@ void uninit_audio(void);
void driver_set_monitor_refresh_rate(float hz);
// Used by RETRO_ENVIRONMENT_SET_HW_RENDER.
uintptr_t driver_get_current_framebuffer(void);
retro_proc_address_t driver_get_proc_address(const char *sym);
extern driver_t driver;
//////////////////////////////////////////////// Backends

View File

@ -556,6 +556,46 @@ static bool environment_cb(unsigned cmd, void *data)
g_extern.system.disk_control = *(const struct retro_disk_control_callback*)data;
break;
case RETRO_ENVIRONMENT_SET_HW_RENDER:
{
RARCH_LOG("Environ SET_HW_RENDER.\n");
struct retro_hw_render_callback *cb = (struct retro_hw_render_callback*)data;
switch (cb->context_type)
{
case RETRO_HW_CONTEXT_NONE:
RARCH_LOG("Requesting no HW context.\n");
break;
#if defined(HAVE_OPENGLES2)
case RETRO_HW_CONTEXT_OPENGLES2:
RARCH_LOG("Requesting OpenGLES2 context.\n");
driver.video = &video_gl;
break;
case RETRO_HW_CONTEXT_OPENGL:
RARCH_ERR("Requesting OpenGL context, but RetroArch is compiled against OpenGLES2. Cannot use HW context.\n");
return false;
#elif defined(HAVE_OPENGL)
case RETRO_HW_CONTEXT_OPENGLES2:
RARCH_ERR("Requesting OpenGLES2 context, but RetroArch is compiled against OpenGL. Cannot use HW context.\n");
return false;
case RETRO_HW_CONTEXT_OPENGL:
RARCH_LOG("Requesting OpenGL context.\n");
driver.video = &video_gl;
break;
#endif
default:
RARCH_LOG("Requesting unknown context.\n");
return false;
}
cb->get_current_framebuffer = driver_get_current_framebuffer;
cb->get_proc_address = driver_get_proc_address;
memcpy(&g_extern.system.hw_render_callback, cb, sizeof(*cb));
break;
}
default:
RARCH_LOG("Environ UNSUPPORTED (#%u).\n", cmd);
return false;

View File

@ -392,6 +392,7 @@ struct global
retro_keyboard_event_t key_event;
struct retro_disk_control_callback disk_control;
struct retro_hw_render_callback hw_render_callback;
} system;
struct

221
gfx/gl.c
View File

@ -135,6 +135,11 @@ static PFNGLBINDFRAMEBUFFERPROC pglBindFramebuffer;
static PFNGLFRAMEBUFFERTEXTURE2DPROC pglFramebufferTexture2D;
static PFNGLCHECKFRAMEBUFFERSTATUSPROC pglCheckFramebufferStatus;
static PFNGLDELETEFRAMEBUFFERSPROC pglDeleteFramebuffers;
static PFNGLGENRENDERBUFFERSPROC pglGenRenderbuffers;
static PFNGLBINDRENDERBUFFERPROC pglBindRenderbuffer;
static PFNGLFRAMEBUFFERRENDERBUFFERPROC pglFramebufferRenderbuffer;
static PFNGLRENDERBUFFERSTORAGEPROC pglRenderbufferStorage;
static PFNGLDELETERENDERBUFFERSPROC pglDeleteRenderbuffers;
static bool load_fbo_proc(gl_t *gl)
{
@ -143,9 +148,17 @@ static bool load_fbo_proc(gl_t *gl)
LOAD_GL_SYM(FramebufferTexture2D);
LOAD_GL_SYM(CheckFramebufferStatus);
LOAD_GL_SYM(DeleteFramebuffers);
LOAD_GL_SYM(GenRenderbuffers);
LOAD_GL_SYM(BindRenderbuffer);
LOAD_GL_SYM(FramebufferRenderbuffer);
LOAD_GL_SYM(RenderbufferStorage);
LOAD_GL_SYM(DeleteRenderbuffers);
return pglGenFramebuffers && pglBindFramebuffer && pglFramebufferTexture2D &&
pglCheckFramebufferStatus && pglDeleteFramebuffers;
pglCheckFramebufferStatus && pglDeleteFramebuffers &&
pglGenRenderbuffers && pglBindRenderbuffer &&
pglFramebufferRenderbuffer && pglRenderbufferStorage &&
pglDeleteRenderbuffers;
}
#elif defined(HAVE_OPENGLES2)
#define pglGenFramebuffers glGenFramebuffers
@ -153,6 +166,11 @@ static bool load_fbo_proc(gl_t *gl)
#define pglFramebufferTexture2D glFramebufferTexture2D
#define pglCheckFramebufferStatus glCheckFramebufferStatus
#define pglDeleteFramebuffers glDeleteFramebuffers
#define pglGenRenderbuffers glGenRenderbuffers
#define pglBindRenderbuffer glBindRenderbuffer
#define pglFramebufferRenderbuffer glFramebufferRenderbuffer
#define pglRenderbufferStorage glRenderbufferStorage
#define pglDeleteRenderbuffers glDeleteRenderbuffers
#define load_fbo_proc(gl) (true)
#elif defined(HAVE_OPENGLES)
#define pglGenFramebuffers glGenFramebuffersOES
@ -160,6 +178,11 @@ static bool load_fbo_proc(gl_t *gl)
#define pglFramebufferTexture2D glFramebufferTexture2DOES
#define pglCheckFramebufferStatus glCheckFramebufferStatusOES
#define pglDeleteFramebuffers glDeleteFramebuffersOES
#define pglGenRenderbuffers glGenRenderbuffersOES
#define pglBindRenderbuffer glBindRenderbufferOES
#define pglFramebufferRenderbuffer glFramebufferRenderbufferOES
#define pglRenderbufferStorage glRenderbufferStorageOES
#define pglDeleteRenderbuffers glDeleteRenderbuffersOES
#define GL_FRAMEBUFFER GL_FRAMEBUFFER_OES
#define GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0_EXT
#define GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_COMPLETE_OES
@ -170,6 +193,11 @@ static bool load_fbo_proc(gl_t *gl)
#define pglFramebufferTexture2D glFramebufferTexture2D
#define pglCheckFramebufferStatus glCheckFramebufferStatus
#define pglDeleteFramebuffers glDeleteFramebuffers
#define pglGenRenderbuffers glGenRenderbuffers
#define pglBindRenderbuffer glBindRenderbuffer
#define pglFramebufferRenderbuffer glFramebufferRenderbuffer
#define pglRenderbufferStorage glRenderbufferStorage
#define pglDeleteRenderbuffers glDeleteRenderbuffers
#define load_fbo_proc(gl) (true)
#endif
#endif
@ -703,6 +731,68 @@ void gl_init_fbo(void *data, unsigned width, unsigned height)
gl->fbo_inited = true;
}
bool gl_init_hw_render(gl_t *gl, unsigned width, unsigned height)
{
RARCH_LOG("[GL]: Initializing HW render (%u x %u).\n", width, height);
if (!load_fbo_proc(gl))
return false;
glBindTexture(GL_TEXTURE_2D, 0);
pglGenFramebuffers(TEXTURES, gl->hw_render_fbo);
bool depth = g_extern.system.hw_render_callback.depth;
bool stencil = g_extern.system.hw_render_callback.stencil;
if (depth)
{
pglGenRenderbuffers(TEXTURES, gl->hw_render_depth);
gl->hw_render_depth_init = true;
}
if (stencil)
{
pglGenRenderbuffers(TEXTURES, gl->hw_render_stencil);
gl->hw_render_stencil_init = true;
}
for (unsigned i = 0; i < TEXTURES; i++)
{
pglBindFramebuffer(GL_FRAMEBUFFER, gl->hw_render_fbo[i]);
pglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->texture[i], 0);
if (depth)
{
pglBindRenderbuffer(GL_RENDERBUFFER, gl->hw_render_depth[i]);
pglRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
width, height);
pglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, gl->hw_render_depth[i]);
}
if (stencil)
{
pglBindRenderbuffer(GL_RENDERBUFFER, gl->hw_render_stencil[i]);
pglRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8,
width, height);
pglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, gl->hw_render_stencil[i]);
}
GLenum status = pglCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
RARCH_ERR("[GL]: Failed to create HW render FBO.\n");
return false;
}
}
pglBindFramebuffer(GL_FRAMEBUFFER, 0);
pglBindRenderbuffer(GL_RENDERBUFFER, 0);
gl->hw_render_fbo_init = true;
return true;
}
#endif
void gl_set_projection(void *data, struct gl_ortho *ortho, bool allow_rotate)
@ -981,7 +1071,7 @@ static void gl_update_resize(void *data)
#endif
}
static void gl_update_input_size(void *data, unsigned width, unsigned height, unsigned pitch)
static void gl_update_input_size(void *data, unsigned width, unsigned height, unsigned pitch, bool clear)
{
gl_t *gl = (gl_t*)data;
// Res change. Need to clear out texture.
@ -990,18 +1080,21 @@ static void gl_update_input_size(void *data, unsigned width, unsigned height, un
gl->last_width[gl->tex_index] = width;
gl->last_height[gl->tex_index] = height;
if (clear)
{
#if defined(HAVE_PSGL)
glBufferSubData(GL_TEXTURE_REFERENCE_BUFFER_SCE,
gl->tex_w * gl->tex_h * gl->tex_index * gl->base_size,
gl->tex_w * gl->tex_h * gl->base_size,
gl->empty_buf);
glBufferSubData(GL_TEXTURE_REFERENCE_BUFFER_SCE,
gl->tex_w * gl->tex_h * gl->tex_index * gl->base_size,
gl->tex_w * gl->tex_h * gl->base_size,
gl->empty_buf);
#else
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(width * sizeof(uint32_t)));
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(width * sizeof(uint32_t)));
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, gl->tex_w, gl->tex_h, gl->texture_type,
gl->texture_fmt, gl->empty_buf);
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, gl->tex_w, gl->tex_h, gl->texture_type,
gl->texture_fmt, gl->empty_buf);
#endif
}
GLfloat xamt = (GLfloat)width / gl->tex_w;
GLfloat yamt = (GLfloat)height / gl->tex_h;
@ -1364,20 +1457,50 @@ static bool gl_frame(void *data, const void *frame, unsigned width, unsigned hei
gl->tex_index = (gl->tex_index + 1) & TEXTURES_MASK;
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
gl_update_input_size(gl, width, height, pitch);
#ifdef HAVE_FBO
// Data is already on GPU :) Have to reset some state however incase core changed it.
if (gl->hw_render_fbo_init)
{
gl_update_input_size(gl, width, height, pitch, false);
RARCH_PERFORMANCE_INIT(copy_frame);
RARCH_PERFORMANCE_START(copy_frame);
gl_copy_frame(gl, frame, width, height, pitch);
RARCH_PERFORMANCE_STOP(copy_frame);
if (!gl->fbo_inited)
{
pglBindFramebuffer(GL_FRAMEBUFFER, 0);
gl_set_viewport(gl, gl->win_width, gl->win_height, false, true);
}
}
else
#endif
{
gl_update_input_size(gl, width, height, pitch, true);
RARCH_PERFORMANCE_INIT(copy_frame);
RARCH_PERFORMANCE_START(copy_frame);
gl_copy_frame(gl, frame, width, height, pitch);
RARCH_PERFORMANCE_STOP(copy_frame);
#ifdef IOS // Apparently the viewport is lost each frame, thanks apple.
gl_set_viewport(gl, gl->win_width, gl->win_height, false, true);
gl_set_viewport(gl, gl->win_width, gl->win_height, false, true);
#endif
}
}
else
glBindTexture(GL_TEXTURE_2D, gl->texture[gl->tex_index]);
// Have to reset rendering state which libretro core could easily have overridden.
#ifdef HAVE_FBO
if (gl->hw_render_fbo_init)
{
#ifndef HAVE_OPENGLES
glEnable(GL_TEXTURE_2D);
#endif
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_DITHER);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
#endif
struct gl_tex_info tex_info = {0};
tex_info.tex = gl->texture[gl->tex_index];
tex_info.input_size[0] = width;
@ -1442,6 +1565,23 @@ static bool gl_frame(void *data, const void *frame, unsigned width, unsigned hei
RARCH_PERFORMANCE_STOP(frame_run);
#ifdef HAVE_FBO
// Reset state which could easily mess up libretro core.
if (gl->hw_render_fbo_init)
{
gl_shader_use_func(gl, 0);
glBindTexture(GL_TEXTURE_2D, 0);
#ifndef NO_GL_FF_VERTEX
pglClientActiveTexture(GL_TEXTURE1);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
pglClientActiveTexture(GL_TEXTURE0);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
#endif
}
#endif
#if defined(HAVE_RMENU)
if (lifecycle_mode_state & (1ULL << MODE_MENU_DRAW))
context_rmenu_frame_func(gl);
@ -1453,7 +1593,7 @@ static bool gl_frame(void *data, const void *frame, unsigned width, unsigned hei
if (gl->pbo_readback_enable)
gl_pbo_async_readback(gl);
#endif
return true;
}
@ -1516,6 +1656,14 @@ static void gl_free(void *data)
#ifdef HAVE_FBO
gl_deinit_fbo(gl);
if (gl->hw_render_fbo_init)
pglDeleteFramebuffers(TEXTURES, gl->hw_render_fbo);
if (gl->hw_render_depth)
pglDeleteRenderbuffers(TEXTURES, gl->hw_render_depth);
if (gl->hw_render_stencil)
pglDeleteRenderbuffers(TEXTURES, gl->hw_render_stencil);
gl->hw_render_fbo_init = false;
#endif
context_destroy_func();
@ -1816,11 +1964,6 @@ static void *gl_init(const video_info_t *video, const input_driver_t **input, vo
gl->tex_w = RARCH_SCALE_BASE * video->input_scale;
gl->tex_h = RARCH_SCALE_BASE * video->input_scale;
#ifdef HAVE_FBO
// Set up render to texture.
gl_init_fbo(gl, gl->tex_w, gl->tex_h);
#endif
gl->keep_aspect = video->force_aspect;
// Apparently need to set viewport for passes when we aren't using FBOs.
@ -1840,6 +1983,7 @@ static void *gl_init(const video_info_t *video, const input_driver_t **input, vo
#endif
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_DITHER);
memcpy(gl->tex_coords, tex_coords, sizeof(tex_coords));
@ -1865,6 +2009,25 @@ static void *gl_init(const video_info_t *video, const input_driver_t **input, vo
gl_init_textures(gl, video);
gl_init_textures_data(gl);
#ifdef HAVE_FBO
// Set up render to texture.
gl_init_fbo(gl, gl->tex_w, gl->tex_h);
#ifdef HAVE_OPENGLES2
enum retro_hw_context_type desired = RETRO_HW_CONTEXT_OPENGLES2;
#else
enum retro_hw_context_type desired = RETRO_HW_CONTEXT_OPENGL;
#endif
if (g_extern.system.hw_render_callback.context_type == desired
&& !gl_init_hw_render(gl, gl->tex_w, gl->tex_h))
{
context_destroy_func();
free(gl);
return NULL;
}
#endif
if (input && input_data)
context_input_driver_func(input, input_data);
@ -2317,6 +2480,18 @@ static unsigned gl_get_fbo_state(void *data)
gl_t *gl = (gl_t*)data;
return gl->fbo_inited ? FBO_INIT : FBO_DEINIT;
}
static uintptr_t gl_get_current_framebuffer(void *data)
{
gl_t *gl = (gl_t*)data;
return gl->hw_render_fbo[(gl->tex_index + 1) & TEXTURES_MASK];
}
static retro_proc_address_t gl_get_proc_address(void *data, const char *sym)
{
gl_t *gl = (gl_t*)data;
return gl->ctx_driver->get_proc_address(sym);
}
#endif
static void gl_set_aspect_ratio(void *data, unsigned aspectratio_index)
@ -2382,6 +2557,8 @@ static const video_poke_interface_t gl_poke_interface = {
#ifdef HAVE_FBO
gl_set_fbo_state,
gl_get_fbo_state,
gl_get_current_framebuffer,
gl_get_proc_address,
#endif
gl_set_aspect_ratio,
gl_apply_state_changes,

View File

@ -242,6 +242,13 @@ typedef struct gl
struct gl_fbo_scale fbo_scale[MAX_SHADERS];
int fbo_pass;
bool fbo_inited;
GLuint hw_render_fbo[TEXTURES];
GLuint hw_render_depth[TEXTURES];
GLuint hw_render_stencil[TEXTURES];
bool hw_render_fbo_init;
bool hw_render_depth_init;
bool hw_render_stencil_init;
#endif
bool should_resize;

View File

@ -678,6 +678,8 @@ static const video_poke_interface_t thread_poke = {
#ifdef HAVE_FBO
thread_set_fbo_state,
thread_get_fbo_state,
NULL,
NULL,
#endif
thread_set_aspect_ratio,
thread_apply_state_changes,

61
libretro-test-gl/Makefile Normal file
View File

@ -0,0 +1,61 @@
ifeq ($(platform),)
platform = unix
ifeq ($(shell uname -a),)
platform = win
else ifneq ($(findstring MINGW,$(shell uname -a)),)
platform = win
else ifneq ($(findstring Darwin,$(shell uname -a)),)
platform = osx
else ifneq ($(findstring win,$(shell uname -a)),)
platform = win
endif
endif
ifeq ($(platform), unix)
TARGET := libretro.so
fpic := -fPIC
SHARED := -shared -Wl,--version-script=link.T -Wl,--no-undefined
GL_LIB := -lGL
else ifeq ($(platform), osx)
TARGET := libretro.dylib
fpic := -fPIC
SHARED := -dynamiclib
GL_LIB := -framework OpenGL
else
CC = gcc
TARGET := retro.dll
SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=link.T -Wl,--no-undefined
GL_LIB := -lopengl32
CFLAGS += -I..
endif
ifeq ($(DEBUG), 1)
CFLAGS += -O0 -g
else
CFLAGS += -O3
endif
OBJECTS := libretro-test.o
CFLAGS += -std=gnu99 -Wall -pedantic $(fpic)
ifeq ($(GLES), 1)
CFLAGS += -DGLES
LIBS += -lGLESv2
else
LIBS += $(GL_LIB)
endif
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(CC) $(fpic) $(SHARED) $(INCLUDES) -o $@ $(OBJECTS) $(LIBS) -lm
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJECTS) $(TARGET)
.PHONY: clean

View File

@ -0,0 +1,373 @@
#include "../libretro.h"
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
static struct retro_hw_render_callback hw_render;
#define GL_GLEXT_PROTOTYPES
#if defined(GLES) && 0
#include <GLES2/gl2.h>
#else
#include <GL/gl.h>
#include <GL/glext.h>
#endif
static PFNGLCREATEPROGRAMPROC pglCreateProgram;
static PFNGLCREATESHADERPROC pglCreateShader;
static PFNGLCREATESHADERPROC pglCompileShader;
static PFNGLCREATESHADERPROC pglUseProgram;
static PFNGLSHADERSOURCEPROC pglShaderSource;
static PFNGLATTACHSHADERPROC pglAttachShader;
static PFNGLLINKPROGRAMPROC pglLinkProgram;
static PFNGLBINDFRAMEBUFFERPROC pglBindFramebuffer;
static PFNGLGETUNIFORMLOCATIONPROC pglGetUniformLocation;
static PFNGLUNIFORMMATRIX4FVPROC pglUniformMatrix4fv;
static PFNGLGETATTRIBLOCATIONPROC pglGetAttribLocation;
static PFNGLVERTEXATTRIBPOINTERPROC pglVertexAttribPointer;
static PFNGLENABLEVERTEXATTRIBARRAYPROC pglEnableVertexAttribArray;
static PFNGLDISABLEVERTEXATTRIBARRAYPROC pglDisableVertexAttribArray;
static PFNGLGENVERTEXARRAYSPROC pglGenVertexArrays;
static PFNGLBINDVERTEXARRAYPROC pglBindVertexArray;
static PFNGLDELETEVERTEXARRAYSPROC pglDeleteVertexArray;
static PFNGLGENBUFFERSPROC pglGenBuffers;
static PFNGLBUFFERDATAPROC pglBufferData;
static PFNGLBINDBUFFERPROC pglBindBuffer;
struct gl_proc_map
{
void *proc;
const char *sym;
};
#define PROC_BIND(name) { &(pgl##name), "gl" #name }
static const struct gl_proc_map proc_map[] = {
PROC_BIND(CreateProgram),
PROC_BIND(CreateShader),
PROC_BIND(CompileShader),
PROC_BIND(UseProgram),
PROC_BIND(ShaderSource),
PROC_BIND(AttachShader),
PROC_BIND(LinkProgram),
PROC_BIND(BindFramebuffer),
PROC_BIND(GetUniformLocation),
PROC_BIND(GetAttribLocation),
PROC_BIND(UniformMatrix4fv),
PROC_BIND(VertexAttribPointer),
PROC_BIND(EnableVertexAttribArray),
PROC_BIND(DisableVertexAttribArray),
PROC_BIND(GenVertexArrays),
PROC_BIND(BindVertexArray),
PROC_BIND(DeleteVertexArray),
PROC_BIND(GenBuffers),
PROC_BIND(BufferData),
PROC_BIND(BindBuffer),
};
static void init_gl_proc(void)
{
for (unsigned i = 0; i < ARRAY_SIZE(proc_map); i++)
{
retro_proc_address_t proc = hw_render.get_proc_address(proc_map[i].sym);
if (!proc)
fprintf(stderr, "Symbol %s not found!\n", proc_map[i].sym);
memcpy(proc_map[i].proc, &proc, sizeof(proc));
}
}
static GLuint prog;
static GLuint vao;
static GLuint vbo;
static const GLfloat vertex_data[] = {
-0.5, -0.5,
0.5, -0.5,
-0.5, 0.5,
0.5, 0.5,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 0.0, 1.0,
0.0, 1.0, 1.0, 1.0,
1.0, 0.0, 1.0, 1.0,
};
static const char *vertex_shader[] = {
"uniform mat4 uMVP;",
"attribute vec2 aVertex;",
"attribute vec4 aColor;",
"varying vec4 color;",
"void main() {",
" gl_Position = uMVP * vec4(aVertex, 0.0, 1.0);",
" color = aColor;",
"}",
};
static const char *fragment_shader[] = {
"varying vec4 color;",
"void main() {",
" gl_FragColor = color;",
"}",
};
static void compile_program(void)
{
prog = pglCreateProgram();
GLuint vert = pglCreateShader(GL_VERTEX_SHADER);
GLuint frag = pglCreateShader(GL_FRAGMENT_SHADER);
pglShaderSource(vert, ARRAY_SIZE(vertex_shader), vertex_shader, 0);
pglShaderSource(frag, ARRAY_SIZE(fragment_shader), fragment_shader, 0);
pglCompileShader(vert);
pglCompileShader(frag);
pglAttachShader(prog, vert);
pglAttachShader(prog, frag);
pglLinkProgram(prog);
}
static void setup_vao(void)
{
pglUseProgram(prog);
pglGenVertexArrays(1, &vao);
pglBindVertexArray(vao);
pglGenBuffers(1, &vbo);
pglBindBuffer(GL_ARRAY_BUFFER, vbo);
pglBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW);
int vloc = pglGetAttribLocation(prog, "aVertex");
pglVertexAttribPointer(vloc, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
pglEnableVertexAttribArray(vloc);
int cloc = pglGetAttribLocation(prog, "aColor");
pglVertexAttribPointer(cloc, 4, GL_FLOAT, GL_FALSE, 0, (void*)(8 * sizeof(GLfloat)));
pglEnableVertexAttribArray(cloc);
pglBindBuffer(GL_ARRAY_BUFFER, 0);
pglBindVertexArray(0);
pglUseProgram(0);
}
void retro_init(void)
{}
void retro_deinit(void)
{}
unsigned retro_api_version(void)
{
return RETRO_API_VERSION;
}
void retro_set_controller_port_device(unsigned port, unsigned device)
{
(void)port;
(void)device;
}
void retro_get_system_info(struct retro_system_info *info)
{
memset(info, 0, sizeof(*info));
info->library_name = "TestCore GL";
info->library_version = "v1";
info->need_fullpath = false;
info->valid_extensions = NULL; // Anything is fine, we don't care.
}
void retro_get_system_av_info(struct retro_system_av_info *info)
{
info->timing = (struct retro_system_timing) {
.fps = 60.0,
.sample_rate = 30000.0,
};
info->geometry = (struct retro_game_geometry) {
.base_width = 512,
.base_height = 512,
.max_width = 512,
.max_height = 512,
.aspect_ratio = 4.0 / 3.0,
};
}
static retro_video_refresh_t video_cb;
static retro_audio_sample_t audio_cb;
static retro_audio_sample_batch_t audio_batch_cb;
static retro_environment_t environ_cb;
static retro_input_poll_t input_poll_cb;
static retro_input_state_t input_state_cb;
void retro_set_environment(retro_environment_t cb)
{
environ_cb = cb;
}
void retro_set_audio_sample(retro_audio_sample_t cb)
{
audio_cb = cb;
}
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
{
audio_batch_cb = cb;
}
void retro_set_input_poll(retro_input_poll_t cb)
{
input_poll_cb = cb;
}
void retro_set_input_state(retro_input_state_t cb)
{
input_state_cb = cb;
}
void retro_set_video_refresh(retro_video_refresh_t cb)
{
video_cb = cb;
}
void retro_run(void)
{
input_poll_cb();
pglBindFramebuffer(GL_FRAMEBUFFER, hw_render.get_current_framebuffer());
glClearColor(0.3, 0.4, 0.5, 1.0);
glViewport(0, 0, 512, 512);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
pglUseProgram(prog);
pglBindVertexArray(vao);
glEnable(GL_DEPTH_TEST);
int loc = pglGetUniformLocation(prog, "uMVP");
static unsigned frame_count;
frame_count++;
float angle = frame_count / 100.0;
float cos_angle = cos(angle);
float sin_angle = sin(angle);
const GLfloat mvp[] = {
cos_angle, -sin_angle, 0, 0,
sin_angle, cos_angle, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
};
pglUniformMatrix4fv(loc, 1, GL_FALSE, mvp);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
cos_angle *= 0.5;
sin_angle *= 0.5;
const GLfloat mvp2[] = {
cos_angle, -sin_angle, 0, 0.0,
sin_angle, cos_angle, 0, 0.0,
0, 0, 1, 0,
0.4, 0.4, 0.2, 1,
};
pglUniformMatrix4fv(loc, 1, GL_FALSE, mvp2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
pglUseProgram(0);
pglBindVertexArray(0);
video_cb(RETRO_HW_FRAME_BUFFER_VALID, 512, 512, 0);
}
static void context_reset(void)
{
fprintf(stderr, "Context reset!\n");
init_gl_proc();
compile_program();
setup_vao();
}
bool retro_load_game(const struct retro_game_info *info)
{
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
{
fprintf(stderr, "XRGB8888 is not supported.\n");
return false;
}
#ifdef GLES
hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2;
#else
hw_render.context_type = RETRO_HW_CONTEXT_OPENGL;
#endif
hw_render.context_reset = context_reset;
hw_render.depth = true;
hw_render.stencil = true;
if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
return false;
fprintf(stderr, "Loaded game!\n");
(void)info;
return true;
}
void retro_unload_game(void)
{}
unsigned retro_get_region(void)
{
return RETRO_REGION_NTSC;
}
bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num)
{
(void)type;
(void)info;
(void)num;
return false;
}
size_t retro_serialize_size(void)
{
return 0;
}
bool retro_serialize(void *data, size_t size)
{
(void)data;
(void)size;
return false;
}
bool retro_unserialize(const void *data, size_t size)
{
(void)data;
(void)size;
return false;
}
void *retro_get_memory_data(unsigned id)
{
(void)id;
return NULL;
}
size_t retro_get_memory_size(unsigned id)
{
(void)id;
return 0;
}
void retro_reset(void)
{}
void retro_cheat_reset(void)
{}
void retro_cheat_set(unsigned index, bool enabled, const char *code)
{
(void)index;
(void)enabled;
(void)code;
}

5
libretro-test-gl/link.T Normal file
View File

@ -0,0 +1,5 @@
{
global: retro_*;
local: *;
};

View File

@ -333,6 +333,8 @@ enum retro_mod
RETROKMOD_DUMMY = INT_MAX // Ensure sizeof(enum) == sizeof(int)
};
// If set, this call is not part of the public libretro API yet. It can change or be removed at any time.
#define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000
// Environment commands.
#define RETRO_ENVIRONMENT_SET_ROTATION 1 // const unsigned * --
@ -421,8 +423,52 @@ enum retro_mod
// Sets an interface which frontend can use to eject and insert disk images.
// This is used for games which consist of multiple images and must be manually
// swapped out by the user (e.g. PSX).
#define RETRO_ENVIRONMENT_SET_HW_RENDER (14 | RETRO_ENVIRONMENT_EXPERIMENTAL)
// struct retro_hw_render_callback * --
// NOTE: This call is currently very experimental, and should not be considered part of the public API.
// The interface could be changed or removed at any time.
// Sets an interface to let a libretro core render with hardware acceleration.
// Should be called in retro_load_game().
// If successful, libretro cores will be able to render to a frontend-provided framebuffer.
// The size of this framebuffer will be at least as large as max_width/max_height provided in get_av_info().
// If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or NULL to retro_video_refresh_t.
// Pass this to retro_video_refresh_t if rendering to hardware.
// Passing NULL to retro_video_refresh_t is still a frame dupe as normal.
#define RETRO_HW_FRAME_BUFFER_VALID ((void*)-1)
// Invalidates the current HW context.
// If called, all GPU resources must be reinitialized.
// Usually called when frontend reinits video driver.
// Also called first time video driver is initialized, allowing libretro core to init resources.
typedef void (*retro_hw_context_reset_t)(void);
// Gets current framebuffer which is to be rendered to. Could change every frame potentially.
typedef uintptr_t (*retro_hw_get_current_framebuffer_t)(void);
// Get a symbol from HW context.
typedef void (*retro_proc_address_t)(void);
typedef retro_proc_address_t (*retro_hw_get_proc_address_t)(const char *sym);
enum retro_hw_context_type
{
RETRO_HW_CONTEXT_NONE = 0,
RETRO_HW_CONTEXT_OPENGL, // OpenGL 2.x. Latest version available before 3.x+.
RETRO_HW_CONTEXT_OPENGLES2, // GLES 2.0
RETRO_HW_CONTEXT_DUMMY = INT_MAX
};
struct retro_hw_render_callback
{
enum retro_hw_context_type context_type; // Which API to use. Set by libretro core.
retro_hw_context_reset_t context_reset; // Set by libretro core.
retro_hw_get_current_framebuffer_t get_current_framebuffer; // Set by frontend.
retro_hw_get_proc_address_t get_proc_address; // Set by frontend.
bool depth; // Set if render buffers should have depth component attached.
bool stencil; // Set if render buffers should have stencil component attached.
};
// Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. Called by the frontend in response to keyboard events.
// down is set if the key is being pressed, or false if it is being released.
// keycode is the RETROK value of the char.

View File

@ -141,10 +141,18 @@ static void take_screenshot(void)
bool ret = false;
if (g_settings.video.gpu_screenshot && driver.video->read_viewport && driver.video->viewport_info)
ret = take_screenshot_viewport();
else if (g_extern.frame_cache.data)
ret = take_screenshot_raw();
if (g_extern.frame_cache.data)
{
if ((g_settings.video.gpu_screenshot ||
(g_extern.frame_cache.data == RETRO_HW_FRAME_BUFFER_VALID)) &&
driver.video->read_viewport &&
driver.video->viewport_info)
ret = take_screenshot_viewport();
else if (g_extern.frame_cache.data && (g_extern.frame_cache.data != RETRO_HW_FRAME_BUFFER_VALID))
ret = take_screenshot_raw();
else
RARCH_ERR("Cannot take screenshot. GPU rendering is used and read_viewport is not supported.\n");
}
const char *msg = NULL;
if (ret)
@ -329,10 +337,14 @@ void rarch_render_cached_frame(void)
g_extern.recording = false;
#endif
const void *frame = g_extern.frame_cache.data;
if (frame == RETRO_HW_FRAME_BUFFER_VALID)
frame = NULL; // Dupe
// Not 100% safe, since the library might have
// freed the memory, but no known implementations do this :D
// It would be really stupid at any rate ...
video_frame(g_extern.frame_cache.data,
video_frame(frame,
g_extern.frame_cache.width,
g_extern.frame_cache.height,
g_extern.frame_cache.pitch);
@ -1291,6 +1303,12 @@ static void init_recording(void)
if (!g_extern.recording)
return;
if (!g_settings.video.gpu_record && g_extern.system.hw_render_callback.context_type)
{
RARCH_WARN("Libretro core is hardware rendered. Must use post-shaded FFmpeg recording as well.\n");
return;
}
double fps = g_extern.system.av_info.timing.fps;
double samplerate = g_extern.system.av_info.timing.sample_rate;
RARCH_LOG("Custom timing given: FPS: %.4f, Sample rate: %.4f\n", (float)fps, (float)samplerate);