diff --git a/driver.c b/driver.c index 4450282ca6..afeaa146cb 100644 --- a/driver.c +++ b/driver.c @@ -270,6 +270,16 @@ 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; +} + // Only called once on init and deinit. // Video and input drivers need to be active (owned) // before retroarch core starts. diff --git a/driver.h b/driver.h index ff7228b874..850b26ff42 100644 --- a/driver.h +++ b/driver.h @@ -323,6 +323,7 @@ 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); #endif void (*set_aspect_ratio)(void *data, unsigned aspectratio_index); void (*apply_state_changes)(void *data); @@ -446,6 +447,9 @@ 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); + extern driver_t driver; //////////////////////////////////////////////// Backends diff --git a/dynamic.c b/dynamic.c index 2bd8dcb297..4d2ede5512 100644 --- a/dynamic.c +++ b/dynamic.c @@ -556,6 +556,15 @@ 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; + cb->get_current_framebuffer = driver_get_current_framebuffer; + 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 6481daee05..29b2fca8b9 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 c647755ce9..0de4969a61 100644 --- a/gfx/gl.c +++ b/gfx/gl.c @@ -699,6 +699,27 @@ void gl_init_fbo(void *data, unsigned width, unsigned height) gl->fbo_inited = true; } + +void gl_init_hw_render(gl_t *gl, unsigned width, unsigned height) +{ + RARCH_LOG("[GL]: Initializing HW render (%u x %u).\n", width, height); + + glBindTexture(GL_TEXTURE_2D, 0); + + pglGenFramebuffers(TEXTURES, gl->hw_render_fbo); + + 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); + GLenum status = pglCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) + RARCH_ERR("[GL]: Failed to create HW render FBO.\n"); + } + + pglBindFramebuffer(GL_FRAMEBUFFER, 0); + gl->hw_render_fbo_init = true; +} #endif void gl_set_projection(void *data, struct gl_ortho *ortho, bool allow_rotate) @@ -977,7 +998,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. @@ -986,18 +1007,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; @@ -1360,16 +1384,36 @@ 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); +#ifndef HAVE_OPENGLES + glEnable(GL_TEXTURE_2D); +#endif + glDisable(GL_DEPTH_TEST); + glDisable(GL_DITHER); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - 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]); @@ -1514,6 +1558,10 @@ 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); + gl->hw_render_fbo_init = false; #endif context_destroy_func(); @@ -1814,11 +1862,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. @@ -1863,6 +1906,14 @@ 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); + + if (g_extern.system.hw_render_callback.context_type == RETRO_HW_CONTEXT_OPENGL) + gl_init_hw_render(gl, gl->tex_w, gl->tex_h); +#endif + if (input && input_data) context_input_driver_func(input, input_data); @@ -1881,6 +1932,11 @@ static void *gl_init(const video_info_t *video, const input_driver_t **input, vo return NULL; } +#ifdef HAVE_FBO + if (gl->hw_render_fbo_init) + g_extern.system.hw_render_callback.context_reset(); +#endif + return gl; } @@ -2313,6 +2369,12 @@ 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]; +} #endif static void gl_set_aspect_ratio(void *data, unsigned aspectratio_index) @@ -2371,6 +2433,7 @@ static const video_poke_interface_t gl_poke_interface = { #ifdef HAVE_FBO gl_set_fbo_state, gl_get_fbo_state, + gl_get_current_framebuffer, #endif gl_set_aspect_ratio, gl_apply_state_changes, diff --git a/gfx/gl_common.h b/gfx/gl_common.h index 6ff82cfa2e..814ba5611e 100644 --- a/gfx/gl_common.h +++ b/gfx/gl_common.h @@ -242,6 +242,9 @@ typedef struct gl struct gl_fbo_scale fbo_scale[MAX_SHADERS]; int fbo_pass; bool fbo_inited; + + GLuint hw_render_fbo[TEXTURES]; + bool hw_render_fbo_init; #endif bool should_resize; diff --git a/gfx/thread_wrapper.c b/gfx/thread_wrapper.c index 4688e9f6e4..7e02400008 100644 --- a/gfx/thread_wrapper.c +++ b/gfx/thread_wrapper.c @@ -678,6 +678,7 @@ static const video_poke_interface_t thread_poke = { #ifdef HAVE_FBO thread_set_fbo_state, thread_get_fbo_state, + 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..dd4bf79d17 --- /dev/null +++ b/libretro-test-gl/Makefile @@ -0,0 +1,53 @@ + +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 + LIBS := -lGL +else ifeq ($(platform), osx) + TARGET := libretro.dylib + fpic := -fPIC + SHARED := -dynamiclib + LIBS := -framework OpenGL +else + CC = gcc + TARGET := retro.dll + SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=link.T -Wl,--no-undefined + LIBS := -lopengl32 +endif + +ifeq ($(DEBUG), 1) + CFLAGS += -O0 -g +else + CFLAGS += -O3 +endif + +OBJECTS := libretro-test.o +CFLAGS += -std=gnu99 -Wall -pedantic $(fpic) + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) $(fpic) $(SHARED) $(LIBS) $(INCLUDES) -o $@ $(OBJECTS) -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..581b14529b --- /dev/null +++ b/libretro-test-gl/libretro-test.c @@ -0,0 +1,193 @@ +#include "../libretro.h" +#include +#include +#include +#include +#include +#include +#include + +static uint16_t *frame_buf; + +void retro_init(void) +{ + frame_buf = calloc(320 * 240, sizeof(uint16_t)); +} + +void retro_deinit(void) +{ + free(frame_buf); + frame_buf = NULL; +} + +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 = 320, + .base_height = 240, + .max_width = 320, + .max_height = 240, + .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; +} + +static struct retro_hw_render_callback hw_render; + +void retro_run(void) +{ + input_poll_cb(); + static unsigned frame_count = 0; + frame_count = (frame_count + 1) % 60; + glBindFramebuffer(GL_FRAMEBUFFER, hw_render.get_current_framebuffer()); + glViewport(0, 0, 320, 240); + glClearColor(frame_count / 120.0, frame_count / 60.0, frame_count / 60.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + video_cb(RETRO_HW_FRAME_BUFFER_VALID, 320, 240, 0); +} + + +static void context_reset(void) +{ + fprintf(stderr, "Context reset!\n"); +} + +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; + } + + hw_render.context_type = RETRO_HW_CONTEXT_OPENGL; + hw_render.context_reset = context_reset; + if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) + return false; + + (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..47e9d56dac 100755 --- a/libretro.h +++ b/libretro.h @@ -421,8 +421,44 @@ 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 0x10000 + // 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); + +enum retro_hw_context_type +{ + RETRO_HW_CONTEXT_NONE = 0, + RETRO_HW_CONTEXT_OPENGL, + + 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. +}; + // 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.