diff --git a/driver.c b/driver.c index 4450282ca6..34edc095aa 100644 --- a/driver.c +++ b/driver.c @@ -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) diff --git a/driver.h b/driver.h index 95c5742463..a2ab606289 100644 --- a/driver.h +++ b/driver.h @@ -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 diff --git a/dynamic.c b/dynamic.c index 2bd8dcb297..eee7d4a85a 100644 --- a/dynamic.c +++ b/dynamic.c @@ -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; diff --git a/general.h b/general.h index 3569495322..7abd504ae1 100644 --- a/general.h +++ b/general.h @@ -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 diff --git a/gfx/gl.c b/gfx/gl.c index 1c1f0f0feb..aa07e754e7 100644 --- a/gfx/gl.c +++ b/gfx/gl.c @@ -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, diff --git a/gfx/gl_common.h b/gfx/gl_common.h index 6ff82cfa2e..5160a17611 100644 --- a/gfx/gl_common.h +++ b/gfx/gl_common.h @@ -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; diff --git a/gfx/thread_wrapper.c b/gfx/thread_wrapper.c index 4688e9f6e4..6e598be4ef 100644 --- a/gfx/thread_wrapper.c +++ b/gfx/thread_wrapper.c @@ -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, diff --git a/libretro-test-gl/Makefile b/libretro-test-gl/Makefile new file mode 100644 index 0000000000..2ec4542b85 --- /dev/null +++ b/libretro-test-gl/Makefile @@ -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 + diff --git a/libretro-test-gl/libretro-test.c b/libretro-test-gl/libretro-test.c new file mode 100644 index 0000000000..36771c5a7e --- /dev/null +++ b/libretro-test-gl/libretro-test.c @@ -0,0 +1,373 @@ +#include "../libretro.h" +#include +#include +#include +#include +#include + +#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 +#else +#include +#include +#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; +} + diff --git a/libretro-test-gl/link.T b/libretro-test-gl/link.T new file mode 100644 index 0000000000..b0c262db9e --- /dev/null +++ b/libretro-test-gl/link.T @@ -0,0 +1,5 @@ +{ + global: retro_*; + local: *; +}; + diff --git a/libretro.h b/libretro.h index 0c39e6edae..b614eb7f3a 100755 --- a/libretro.h +++ b/libretro.h @@ -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. diff --git a/retroarch.c b/retroarch.c index 042b223708..26c167872e 100644 --- a/retroarch.c +++ b/retroarch.c @@ -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);