Files
RetroArch/gfx/drivers/gl3.c
T
LibretroAdmin e5fc0a50e4 apple: the App Store builds compile without warnings, and their uploads stop failing on one bad core
Built the way Xcode builds them - the App Store floors (macOS 12,
iOS/tvOS 15), the projects' warnings, every translation unit - the
tree still warned:

- Delegate methods the SDK deprecates that an older OS still calls
  are no longer implemented in source.  apple_rt_add_method() gives
  the class each one at run time, from +initialize and only on the
  releases that call it: -locationManager:didChangeAuthorizationStatus:
  before macOS 11 / iOS 14 (-locationManagerDidChangeAuthorization:
  from there), -documentPicker:didPickDocumentAtURL: before iOS 11
  (the URL-array method from there), -shouldAutorotateTo-
  InterfaceOrientation: before iOS 6, and NSView's
  -layer:shouldInheritContentsScale:fromWindow: from 10.7.
  class_addMethod is looked up by name, as the 10.4 binary may not
  import it.  CocoaView no longer declares UIDocumentPickerDelegate,
  whose pre-11 SDKs require the method it now adds at run time, and
  -prefersPointerLocked is not built for tvOS, where it is
  unavailable.
- The open panel's file types, the companion's folder icon and the
  cores folder's open are -setAllowedContentTypes:, -iconForContent-
  Type: and -openURL: from macOS 11, the 10.x methods before it, by
  selector where they are not on every release.
- The alert's end selector is looked up by name, and on SDKs older
  than the selectors ui_cocoa.m names (10.6, 10.7, 10.9) they are
  declared in an unimplemented NSObject category, so @selector()
  stays a constant.
- size_t and 64-bit values narrowed without a cast in configuration.c,
  gl3.c, cdrom.c, media_detect_cd.c and vfs_implementation_cdrom.c;
  rchd_read_extent() rejects an extent past the last 32-bit frame
  instead of wrapping into the wrong track.

App Store uploads: one iOS or tvOS core without an unencrypted
LC_ENCRYPTION_INFO_64 - written by Apple's ld, not by other linkers -
fails the whole upload (ITMS-90125).  make-frameworks.sh leaves such a
core out of an App Store build with a warning naming it.  The
Fastfile's check for a build already in TestFlight review passed
Build.all a filter it does not take, so it always failed and the
upload went on to external distribution and stopped there; it asks
App Store Connect for the newest five builds of the platform with
their review submissions instead.
2026-10-07 09:38:29 +02:00

6712 lines
216 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2019 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2016-2019 - Brad Parker
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
/* Modern OpenGL driver.
*
* Minimum version (desktop): OpenGL 3.2+ (2009)
* Minimum version (mobile) : OpenGLES 3.0+ (2012)
*/
#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "../common/gl3_defines.h"
#include "../common/rgba16_pack.h"
#include <encodings/utf.h>
#include <gfx/gl_capabilities.h>
#include <gfx/video_frame.h>
#include <glsym/glsym.h>
#include <string/stdstring.h>
#include <retro_math.h>
#include "../../configuration.h"
#include "../../dynamic.h"
#ifdef HAVE_REWIND
#include "../../state_manager.h"
#endif
#include "../../retroarch.h"
#include "../../verbosity.h"
#ifdef HAVE_THREADS
#include "../video_thread_wrapper.h"
#ifdef HAVE_THREADS
#include "../video_thread_hw.h"
#endif
#endif
#include "../font_driver.h"
#include "../../record/record_driver.h"
#ifdef HAVE_GLSL
#include "../drivers_shader/shader_glsl.h"
#endif
#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif
#ifdef HAVE_GFX_WIDGETS
#include "../gfx_widgets.h"
#ifdef __MACH__
#include <TargetConditionals.h>
#endif
#endif
#define GL3_SET_TEXTURE_COORDS(coords, xamt, yamt) \
coords[2] = xamt; \
coords[6] = xamt; \
coords[5] = yamt; \
coords[7] = yamt
#if defined(HAVE_SLANG)
#define GL3_DEFAULT_SHADER_TYPE RARCH_SHADER_SLANG
#elif defined(HAVE_GLSL)
#define GL3_DEFAULT_SHADER_TYPE RARCH_SHADER_GLSL
#else
#define GL3_DEFAULT_SHADER_TYPE RARCH_SHADER_NONE
#endif
struct gl3_streamed_texture
{
GLuint tex;
unsigned dims;
};
typedef struct gl3
{
const gfx_ctx_driver_t *ctx_driver;
void *ctx_data;
/* The frame's swap: how long it took, and whether it was one that
* waits for the vertical blank (a swap interval set). For
* gl3_get_last_present_wait(). */
retro_time_t present_wait;
bool present_vsynced;
gl3_filter_chain_t *filter_chain;
gl3_filter_chain_t *filter_chain_default;
GLuint *overlay_tex;
float *overlay_vertex_coord;
float *overlay_tex_coord;
uint16_t *overlay_color_coord; /* UNORM16 r, g, b, a per vertex */
GLsync fences[GL_CORE_NUM_FENCES];
void *readback_buffer_screenshot;
struct scaler_ctx pbo_readback_scaler;
video_info_t video_info;
video_viewport_t vp;
struct gl3_viewport filter_chain_vp;
struct gl3_streamed_texture textures[GL_CORE_NUM_TEXTURES];
GLuint vao;
GLuint menu_texture;
/* Sampled by a quad drawn with no texture: solid colour */
GLuint white_texture;
GLuint pbo_readback[GL_CORE_NUM_PBOS];
/* Render chain for non-Slang shaders only. */
struct
{
const shader_backend_t *shader;
void *shader_data;
unsigned num_fbo_passes;
unsigned num_prev_textures;
GLuint fbo_feedback;
unsigned fbo_feedback_pass;
GLuint fbo_feedback_texture;
GLuint fbo[GFX_MAX_SHADERS];
GLuint fbo_texture[GFX_MAX_SHADERS];
struct video_fbo_rect fbo_rect[GFX_MAX_SHADERS];
struct gfx_fbo_scale fbo_scale[GFX_MAX_SHADERS];
struct video_tex_info tex_info;
struct video_tex_info prev_info[GFX_MAX_TEXTURES];
struct video_coords coords;
const float *vertex_ptr;
bool active;
bool mipmap_active;
bool fp_fbo_supported;
bool srgb_fbo_supported;
bool force_srgb_disable;
} chain;
#ifdef HAVE_SLANG
struct
{
GLuint alpha_blend;
GLuint font;
#ifdef HAVE_SHADERPIPELINE
GLuint ribbon;
GLuint ribbon_simple;
GLuint snow_simple;
GLuint snow;
GLuint bokeh;
GLuint snowflake;
#endif /* HAVE_SHADERPIPELINE */
struct gl3_buffer_locations alpha_blend_loc;
struct gl3_buffer_locations font_loc;
/* Half-float (linear scRGB) textures under the scRGB composite:
* alpha_blend_linear.frag, made the first time one is drawn */
GLuint alpha_blend_linear;
struct gl3_buffer_locations alpha_blend_linear_loc;
bool alpha_blend_linear_tried;
#ifdef HAVE_SHADERPIPELINE
struct gl3_buffer_locations ribbon_loc;
struct gl3_buffer_locations ribbon_simple_loc;
struct gl3_buffer_locations snow_simple_loc;
struct gl3_buffer_locations snow_loc;
struct gl3_buffer_locations bokeh_loc;
struct gl3_buffer_locations snowflake_loc;
#endif /* HAVE_SHADERPIPELINE */
GLuint hdr_scrgb;
struct gl3_buffer_locations hdr_scrgb_loc;
/* gfx_display meshes */
GLuint mesh;
struct gl3_buffer_locations mesh_loc;
} pipelines;
#endif /* HAVE_SLANG */
/* scRGB (FP16) default framebuffer support: when the context
* advertises GFX_CTX_FLAGS_SCRGB_FRAMEBUFFER, everything (core
* final pass + all UI) renders into this SDR offscreen, and one
* encode pass writes the scRGB backbuffer at the end of the
* frame. SDR contexts leave all of this at zero and every
* pre-existing path untouched. */
struct
{
GLuint fbo;
GLuint tex;
/* Separate layer for the SDR UI (menu, overlays, widgets, OSD)
* when the content layer is PQ. The two cannot share a target:
* one encode pass cannot treat some pixels as Rec.2020 PQ and
* others as sRGB-ish gamma. Mirrors the Vulkan driver, which
* likewise composites the menu in its own HDR pass. */
GLuint ui_fbo;
GLuint ui_tex;
unsigned dims;
bool active;
/* The SDR content layer, which the menu is drawn into, is FP16:
* always, but while recording, which reads it back each frame
* and keeps it RGBA8 for the copy the driver makes as it is */
bool tex_fp16;
/* The backbuffer is 10-bit Rec.2020 PQ, not FP16 scRGB */
bool pq_out;
/* The HDR settings this frame carried (video_frame_info_t), so the
* thread that draws never reads what the menu writes */
float menu_nits;
float paper_white_nits;
unsigned expand_gamut;
} scrgb;
/* In VIDEO_SCALE_PACK's layout. */
unsigned video_dims;
unsigned overlays;
unsigned version_major;
unsigned version_minor;
unsigned out_vp_dims;
unsigned rotation;
unsigned rotation_raw;
unsigned textures_index;
unsigned scratch_vbo_index;
unsigned fence_count;
unsigned pbo_readback_index;
unsigned hw_render_max_dims;
unsigned menu_texture_dims;
GLuint scratch_vbos[GL_CORE_NUM_VBOS];
/* gfx_display meshes kept on the GPU, by mesh id: vertices, and
* indices where the mesh has them. GL retires a deleted buffer once
* nothing in flight reads it, so the one drawn longest ago simply
* gives way when all are taken. The ribbon pipelines draw from
* here too. */
struct
{
GLuint vbo;
GLuint ibo;
uint64_t last_frame;
uint32_t id;
} meshes[8];
uint64_t mesh_frame;
GLuint hw_render_texture;
GLuint hw_render_fbo;
GLuint hw_render_rb_ds;
/* The threaded wrapper's hardware ring: a target per slot, made in
* the core's context beside the driver's own, which is slot 0; the
* fence the core's thread placed after rendering into each, for the
* frame to wait before reading it. Sync objects are shared between
* the two contexts. */
GLuint hw_ring_texture[3];
GLuint hw_ring_fbo[3];
void *hw_ring_sync[3];
void *hw_ring_done_sync; /* taken ahead of the swap */
float menu_texture_alpha;
math_matrix_4x4 mvp; /* float alignment */
math_matrix_4x4 mvp_yflip;
math_matrix_4x4 mvp_no_rot;
math_matrix_4x4 mvp_no_rot_yflip;
uint32_t flags;
bool pbo_readback_valid[GL_CORE_NUM_PBOS];
bool menu_texture_rgb32;
/* What the last frame said the menu filter should be:
* set_texture_frame() is applied by the video thread in
* thread_update_driver_state(), and reading the setting there races
* the menu writing it. */
bool frame_menu_linear_filter;
} gl3_t;
typedef struct gl3_video_shader_ctx_init
{
const char *path;
const shader_backend_t *shader;
void *data;
void *shader_data;
enum rarch_shader_type shader_type;
} gl3_video_shader_ctx_init_t;
static const struct video_ortho gl3_default_ortho = {0, 1, 0, 1, -1, 1};
static const float gl3_vertexes_flipped[8] = {
0, 1,
1, 1,
0, 0,
1, 0
};
static const float gl3_vertexes[8] = {
0, 0,
1, 0,
0, 1,
1, 1
};
static const float gl3_tex_coords[8] = {
0, 1,
1, 1,
0, 0,
1, 0
};
static const float gl3_colors[16] = {
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
};
/**
* FORWARD DECLARATIONS
*/
static void gl3_set_viewport(gl3_t *gl,
unsigned dims,
bool force_full, bool allow_rotate);
/**
* GL3 COMMON
*/
void gl3_framebuffer_copy(
GLuint fb_id,
GLuint quad_program,
GLuint quad_vbo,
GLint flat_ubo_vertex,
unsigned size_width, unsigned size_height,
GLuint image)
{
glBindFramebuffer(GL_FRAMEBUFFER, fb_id);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, image);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glViewport(0, 0, size_width, size_height);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(quad_program);
if (flat_ubo_vertex >= 0)
{
static float mvp[16] = {
2.0f, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f, 0.0f,
0.0f, 0.0f, 2.0f, 0.0f,
-1.0f,-1.0f, 0.0f, 1.0f
};
glUniform4fv(flat_ubo_vertex, 4, mvp);
}
/* Draw quad */
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, quad_vbo);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void *)((uintptr_t)(0)));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void *)((uintptr_t)(2 * sizeof(float))));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void gl3_framebuffer_copy_partial(
GLuint fb_id,
GLuint quad_program,
GLint flat_ubo_vertex,
unsigned size_width, unsigned size_height,
GLuint image,
float rx, float ry)
{
GLuint vbo;
/* C89: an aggregate initializer must be constant, and rx/ry are
* not; the four corners are set after declaration. */
float quad_data[16] = {
0.0f, 0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 0.0f,
};
quad_data[6] = rx;
quad_data[11] = ry;
quad_data[14] = rx;
quad_data[15] = ry;
glBindFramebuffer(GL_FRAMEBUFFER, fb_id);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, image);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glViewport(0, 0, size_width, size_height);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(quad_program);
if (flat_ubo_vertex >= 0)
{
static float mvp[16] = {
2.0f, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f, 0.0f,
0.0f, 0.0f, 2.0f, 0.0f,
-1.0f,-1.0f, 0.0f, 1.0f
};
glUniform4fv(flat_ubo_vertex, 4, mvp);
}
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
/* A bit crude, but heeeey. */
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_data), quad_data, GL_STREAM_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void *)((uintptr_t)(0)));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void *)((uintptr_t)(2 * sizeof(float))));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &vbo);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
GLuint gl3_compile_shader(GLenum stage, const char *source)
{
GLint status;
GLuint shader = glCreateShader(stage);
const char *ptr = source;
glShaderSource(shader, 1, &ptr, NULL);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (!status)
{
GLint length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
if (length > 0)
{
char *info_log = (char*)malloc(length);
if (info_log)
{
glGetShaderInfoLog(shader, length, &length, info_log);
RARCH_ERR("[GLCore] Failed to compile shader: %s\n", info_log);
free(info_log);
glDeleteShader(shader);
return 0;
}
}
}
return shader;
}
/**
* gl3_parse_version:
* @version : GL_VERSION string (e.g. "4.6", "OpenGL ES 3.0", "OpenGL 4.5 …")
* @major : pointer receiving the major version number
* @minor : pointer receiving the minor version number
*
* Returns true if both major and minor were parsed successfully.
*/
static bool gl3_parse_version(const char *version,
unsigned *major, unsigned *minor)
{
const char *p = version;
unsigned val;
if (!p)
return false;
/* Skip any leading non-digit prefix
* ("OpenGL ES ", "OpenGL ", etc.) */
while (*p && (*p < '0' || *p > '9'))
p++;
if (*p < '0' || *p > '9')
return false;
/* Parse major */
val = 0;
while (*p >= '0' && *p <= '9')
val = val * 10 + (*p++ - '0');
*major = val;
if (*p != '.')
return false;
p++;
if (*p < '0' || *p > '9')
return false;
/* Parse minor */
val = 0;
while (*p >= '0' && *p <= '9')
val = val * 10 + (*p++ - '0');
*minor = val;
return true;
}
#if !defined(HAVE_OPENGLES3) && !defined(HAVE_OPENGLES)
/* Kept local rather than added to the shared glsym headers: some of these
* tokens double as feature tests on GLES class targets, where defining them
* unconditionally would switch on desktop only entry points. */
#ifndef GL_NUM_SHADER_BINARY_FORMATS
#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9
#endif
#ifndef GL_SHADER_BINARY_FORMATS
#define GL_SHADER_BINARY_FORMATS 0x8DF8
#endif
#ifndef GL_SHADER_BINARY_FORMAT_SPIR_V_ARB
#define GL_SHADER_BINARY_FORMAT_SPIR_V_ARB 0x9551
#endif
#ifndef GL_NUM_EXTENSIONS
#define GL_NUM_EXTENSIONS 0x821D
#endif
#endif
/**
* gl3_spirv_binary_supported:
*
* Reports whether SPIR-V modules can be handed straight to the driver via
* GL_ARB_gl_spirv, letting the slang filter chain skip cross compilation to
* GLSL entirely. Desktop GL only; the extension is explicitly not written
* for OpenGL ES.
*
* The result is latched, so this is cheap to call per shader stage. It is
* only ever consulted while a context is current.
*
* Returns: true if glShaderBinary/glSpecializeShader can consume SPIR-V.
*/
#if !defined(HAVE_OPENGLES3) && !defined(HAVE_OPENGLES)
/* The user toggle, latched at init and at every set_shader - both
* blocking wrapper commands, so the read cannot race - which is
* exactly the "takes effect on the next preset load" promise the
* old per-call settings read made, minus the per-call settings read
* from the video thread's lazy pass builds. */
static bool gl3_direct_spirv_enabled;
void gl3_spirv_refresh_direct_toggle(void)
{
settings_t *settings = config_get_ptr();
gl3_direct_spirv_enabled =
settings && settings->bools.video_gl_direct_spirv;
}
#else
void gl3_spirv_refresh_direct_toggle(void) { }
#endif
bool gl3_spirv_binary_supported(void)
{
#if defined(HAVE_OPENGLES3) || defined(HAVE_OPENGLES)
return false;
#else
static int supported = -1;
GLint num_formats = 0;
GLint num_extensions = 0;
GLint i;
unsigned major = 0;
unsigned minor = 0;
bool have_extension = false;
if (!gl3_direct_spirv_enabled)
return false;
if (supported >= 0)
return supported > 0;
supported = 0;
/* A kill switch, for bisecting driver specific breakage without
* having to rebuild. */
if (getenv("RETROARCH_GL3_DISABLE_SPIRV"))
{
RARCH_LOG("[GLCore] SPIR-V shader ingestion disabled by environment.\n");
return false;
}
if (!glShaderBinary || !(glSpecializeShader || glSpecializeShaderARB))
return false;
/* Core since 4.6, otherwise the extension must be advertised. */
if (gl3_parse_version((const char*)glGetString(GL_VERSION), &major, &minor))
have_extension = (major > 4) || (major == 4 && minor >= 6);
if (!have_extension)
{
glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
for (i = 0; i < num_extensions; i++)
{
const char *ext = (const char*)glGetStringi(GL_EXTENSIONS, i);
if (ext && string_is_equal(ext, "GL_ARB_gl_spirv"))
{
have_extension = true;
break;
}
}
}
if (!have_extension)
return false;
/* Advertising the extension is not quite enough: the binary format has
* to actually be accepted by ShaderBinary. */
glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &num_formats);
if (num_formats > 0)
{
GLint *formats = (GLint*)calloc((size_t)num_formats, sizeof(GLint));
if (formats)
{
glGetIntegerv(GL_SHADER_BINARY_FORMATS, formats);
for (i = 0; i < num_formats; i++)
{
if (formats[i] == GL_SHADER_BINARY_FORMAT_SPIR_V_ARB)
{
supported = 1;
break;
}
}
free(formats);
}
}
RARCH_LOG("[GLCore] GL_ARB_gl_spirv: %s.\n",
supported ? "available" : "unavailable");
return supported > 0;
#endif
}
uint32_t gl3_get_cross_compiler_target_version(void)
{
const char *version = (const char*)glGetString(GL_VERSION);
unsigned major = 0;
unsigned minor = 0;
#ifdef HAVE_OPENGLES3
if (!gl3_parse_version(version, &major, &minor))
return 300;
if (major == 2 && minor == 0)
return 100;
#else
if (!gl3_parse_version(version, &major, &minor))
return 150;
if (major == 3)
{
switch (minor)
{
case 2:
return 150;
case 1:
return 140;
case 0:
return 130;
}
}
else if (major == 2)
{
switch (minor)
{
case 1:
return 120;
case 0:
return 110;
}
}
#endif
return 100 * major + 10 * minor;
}
#ifdef HAVE_SLANG
/* The slot whose buffers hold @mesh, uploaded the first time it is
* drawn; -1 when they cannot be had, and the mesh is streamed */
static int gl3_mesh_slot(gl3_t *gl, const gfx_display_mesh_t *mesh)
{
uint64_t oldest = (uint64_t)-1;
int slot = -1;
unsigned i;
for (i = 0; i < ARRAY_SIZE(gl->meshes); i++)
{
if (gl->meshes[i].vbo && gl->meshes[i].id == mesh->id)
return (int)i;
if (!gl->meshes[i].vbo)
{
if (slot < 0 || gl->meshes[slot].vbo)
slot = (int)i;
}
else if ((slot < 0 || gl->meshes[slot].vbo)
&& gl->meshes[i].last_frame < oldest)
{
oldest = gl->meshes[i].last_frame;
slot = (int)i;
}
}
if (slot < 0)
return -1;
if (gl->meshes[slot].vbo)
glDeleteBuffers(1, &gl->meshes[slot].vbo);
if (gl->meshes[slot].ibo)
glDeleteBuffers(1, &gl->meshes[slot].ibo);
gl->meshes[slot].vbo = 0;
gl->meshes[slot].ibo = 0;
gl->meshes[slot].id = 0;
glGenBuffers(1, &gl->meshes[slot].vbo);
if (!gl->meshes[slot].vbo)
return -1;
glBindBuffer(GL_ARRAY_BUFFER, gl->meshes[slot].vbo);
glBufferData(GL_ARRAY_BUFFER,
(GLsizeiptr)mesh->vertex_count * sizeof(gfx_display_mesh_vertex_t),
mesh->vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
if (mesh->indices)
{
glGenBuffers(1, &gl->meshes[slot].ibo);
if (!gl->meshes[slot].ibo)
{
glDeleteBuffers(1, &gl->meshes[slot].vbo);
gl->meshes[slot].vbo = 0;
return -1;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl->meshes[slot].ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
(GLsizeiptr)mesh->index_count * sizeof(uint16_t),
mesh->indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
gl->meshes[slot].id = mesh->id;
return slot;
}
#endif
static void gl3_bind_scratch_vbo(gl3_t *gl, const void *data, size_t len)
{
if (!gl->scratch_vbos[gl->scratch_vbo_index])
glGenBuffers(1, &gl->scratch_vbos[gl->scratch_vbo_index]);
glBindBuffer(GL_ARRAY_BUFFER, gl->scratch_vbos[gl->scratch_vbo_index]);
glBufferData(GL_ARRAY_BUFFER, len, data, GL_STREAM_DRAW);
gl->scratch_vbo_index++;
if (gl->scratch_vbo_index >= GL_CORE_NUM_VBOS)
gl->scratch_vbo_index = 0;
}
/**
* DISPLAY DRIVER
*/
static void *gfx_display_gl3_get_default_mvp(void *data)
{
gl3_t *gl3 = (gl3_t*)data;
if (!gl3)
return NULL;
return &gl3->mvp_no_rot;
}
static const float *gfx_display_gl3_get_default_vertices(void)
{
return &gl3_vertexes[0];
}
static const float *gfx_display_gl3_get_default_tex_coords(void)
{
return &gl3_tex_coords[0];
}
static void gfx_display_gl3_draw_pipeline(
gfx_display_ctx_draw_t *draw,
gfx_display_t *p_disp,
void *data,
unsigned video_dims)
{
unsigned video_width = VIDEO_SCALE_W(video_dims);
unsigned video_height = VIDEO_SCALE_H(video_dims);
#ifdef HAVE_SHADERPIPELINE
float t = p_disp ? p_disp->effect_time : 0.0f;
float yflip = 0.0f;
gl3_t *gl = (gl3_t*)data;
if (!gl || !draw)
return;
draw->pos = VIDEO_POS_PACK(0, 0);
draw->matrix_data = NULL;
if (gl->chain.active)
{
struct uniform_info uniform_param;
if (!(draw->coords = gfx_display_effect_coords(p_disp)))
return;
switch (draw->pipeline_id)
{
case VIDEO_SHADER_MENU:
case VIDEO_SHADER_MENU_2:
glBlendFunc(GL_DST_COLOR, GL_ONE);
break;
default:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
}
switch (draw->pipeline_id)
{
case VIDEO_SHADER_MENU:
case VIDEO_SHADER_MENU_2:
case VIDEO_SHADER_MENU_3:
case VIDEO_SHADER_MENU_4:
case VIDEO_SHADER_MENU_5:
case VIDEO_SHADER_MENU_6:
gl->chain.shader->use(gl, gl->chain.shader_data, draw->pipeline_id,
true);
uniform_param.type = UNIFORM_1F;
uniform_param.enabled = true;
uniform_param.location = 0;
uniform_param.count = 0;
uniform_param.lookup.type = SHADER_PROGRAM_VERTEX;
uniform_param.lookup.ident = "time";
uniform_param.lookup.idx = draw->pipeline_id;
uniform_param.lookup.add_prefix = true;
uniform_param.lookup.enable = true;
uniform_param.result.f.v0 = t;
gl->chain.shader->set_uniform_parameter(gl->chain.shader_data,
&uniform_param, NULL);
break;
}
switch (draw->pipeline_id)
{
case VIDEO_SHADER_MENU_3:
case VIDEO_SHADER_MENU_4:
case VIDEO_SHADER_MENU_5:
case VIDEO_SHADER_MENU_6:
uniform_param.type = UNIFORM_2F;
uniform_param.lookup.ident = "OutputSize";
uniform_param.result.f.v0 = VIDEO_SCALE_W(draw->dims);
uniform_param.result.f.v1 = VIDEO_SCALE_H(draw->dims);
gl->chain.shader->set_uniform_parameter(gl->chain.shader_data,
&uniform_param, NULL);
break;
}
}
#ifdef HAVE_SLANG
else
{
static struct video_coords blank_coords;
static uint8_t ubo_scratch_data[768];
float output_size[2];
output_size[0] = (float)video_width;
output_size[1] = (float)video_height;
switch (draw->pipeline_id)
{
/* Ribbon */
default:
case VIDEO_SHADER_MENU:
case VIDEO_SHADER_MENU_2:
if (!(draw->coords = gfx_display_effect_coords(p_disp)))
return;
draw->backend_data = ubo_scratch_data;
draw->backend_data_size = 2 * sizeof(float);
/* Match UBO layout in shader. */
yflip = 1.0f;
memcpy(ubo_scratch_data, &t, sizeof(t));
memcpy(ubo_scratch_data + sizeof(float), &yflip, sizeof(yflip));
break;
/* Snow simple */
case VIDEO_SHADER_MENU_3:
case VIDEO_SHADER_MENU_4:
case VIDEO_SHADER_MENU_5:
case VIDEO_SHADER_MENU_6:
draw->backend_data = ubo_scratch_data;
draw->backend_data_size = sizeof(math_matrix_4x4)
+ 4 * sizeof(float);
/* Match UBO layout in shader. */
memcpy(ubo_scratch_data,
&gl->mvp_no_rot,
sizeof(math_matrix_4x4));
memcpy(ubo_scratch_data + sizeof(math_matrix_4x4),
output_size,
sizeof(output_size));
if ( draw->pipeline_id == VIDEO_SHADER_MENU_5
|| draw->pipeline_id == VIDEO_SHADER_MENU_6)
yflip = 1.0f;
memcpy(ubo_scratch_data + sizeof(math_matrix_4x4)
+ 2 * sizeof(float), &t, sizeof(t));
memcpy(ubo_scratch_data + sizeof(math_matrix_4x4)
+ 3 * sizeof(float), &yflip, sizeof(yflip));
draw->coords = &blank_coords;
blank_coords.vertices = 4;
break;
}
}
#endif
#endif
}
#if defined(HAVE_SLANG) && defined(HAVE_SHADERPIPELINE)
static GLuint gl3_effect_program(gl3_t *gl, unsigned pipeline_id,
const struct gl3_buffer_locations **loc);
#endif
/* The names of the half-float textures loaded: linear scRGB, which the
* HDR composite shows through the linear program rather than as SDR.
* Loaded, drawn and deleted on the context's own thread, so the table
* needs no lock; it grows with them, so none is ever drawn as SDR for
* want of a place in it. */
static GLuint *gl3_fp16_tex;
static unsigned gl3_fp16_count;
static unsigned gl3_fp16_cap;
static void gl3_fp16_remember(GLuint id)
{
if (gl3_fp16_count == gl3_fp16_cap)
{
unsigned cap = gl3_fp16_cap ? gl3_fp16_cap * 2 : 32;
GLuint *grown = (GLuint*)realloc(gl3_fp16_tex, cap * sizeof(*grown));
if (!grown)
return;
gl3_fp16_tex = grown;
gl3_fp16_cap = cap;
}
gl3_fp16_tex[gl3_fp16_count++] = id;
}
static void gl3_fp16_forget(GLuint id)
{
unsigned i;
for (i = 0; i < gl3_fp16_count; i++)
if (gl3_fp16_tex[i] == id)
{
gl3_fp16_tex[i] = gl3_fp16_tex[--gl3_fp16_count];
return;
}
}
/* With the context: its names are gone with it */
static void gl3_fp16_forget_all(void)
{
free(gl3_fp16_tex);
gl3_fp16_tex = NULL;
gl3_fp16_count = 0;
gl3_fp16_cap = 0;
}
static bool gl3_fp16_is(GLuint id)
{
unsigned i;
for (i = 0; i < gl3_fp16_count; i++)
if (gl3_fp16_tex[i] == id)
return true;
return false;
}
#ifdef HAVE_SLANG
static GLuint gl3_linear_program(gl3_t *gl);
#else
static GLuint gl3_linear_program(gl3_t *gl) { (void)gl; return 0; }
#endif
static void gfx_display_gl3_draw(gfx_display_ctx_draw_t *draw,
void *data, unsigned video_dims)
{
video_coords_t coords;
gl3_t *gl = (gl3_t*)data;
GLuint lin_prog = 0;
if (!gl || !draw)
return;
/* Default the absent streams into a local copy rather than back
* into the caller's struct; see gfx_display_gl2_draw(). */
coords = *draw->coords;
if (!coords.vertex)
coords.vertex = gfx_display_gl3_get_default_vertices();
if (!coords.tex_coord)
coords.tex_coord = &gl3_tex_coords[0];
if (!coords.color)
coords.color = &gl3_colors[0];
glViewport(VIDEO_POS_X(draw->pos), VIDEO_POS_Y(draw->pos),
VIDEO_SCALE_W(draw->dims), VIDEO_SCALE_H(draw->dims));
if (gl->chain.active)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, draw->texture
? (GLuint)draw->texture : gl->white_texture);
gl->chain.shader->set_coords(gl->chain.shader_data, &coords);
gl->chain.shader->set_mvp(gl->chain.shader_data,
draw->matrix_data ? (math_matrix_4x4*)draw->matrix_data
: (math_matrix_4x4*)&gl->mvp_no_rot);
/* Menu draws use a triangle-strip layout. */
glDrawArrays(GL_TRIANGLE_STRIP, 0, coords.vertices);
}
#ifdef HAVE_SLANG
else
{
const struct
gl3_buffer_locations
*loc = NULL;
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, draw->texture
? (GLuint)draw->texture : gl->white_texture);
switch (draw->pipeline_id)
{
case VIDEO_SHADER_MENU:
case VIDEO_SHADER_MENU_2:
glBlendFunc(GL_DST_COLOR, GL_ONE);
break;
default:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
}
switch (draw->pipeline_id)
{
#ifdef HAVE_SHADERPIPELINE
case VIDEO_SHADER_MENU:
case VIDEO_SHADER_MENU_2:
case VIDEO_SHADER_MENU_3:
case VIDEO_SHADER_MENU_4:
case VIDEO_SHADER_MENU_5:
case VIDEO_SHADER_MENU_6:
{
GLuint prog = gl3_effect_program(gl, draw->pipeline_id, &loc);
if (!prog)
return;
glUseProgram(prog);
}
break;
#endif /* HAVE_SHADERPIPELINE */
default:
/* A half-float texture is linear scRGB: under the scRGB
* composite, through the program that inverts its encode */
if ( gl->scrgb.active
&& gl3_fp16_is((GLuint)draw->texture)
&& (lin_prog = gl3_linear_program(gl)))
glUseProgram(lin_prog);
else
glUseProgram(gl->pipelines.alpha_blend);
break;
}
if (loc)
{
if (loc->flat_ubo_vertex >= 0)
glUniform4fv(loc->flat_ubo_vertex,
(GLsizei)((draw->backend_data_size + 15) / 16),
(const GLfloat*)draw->backend_data);
if (loc->flat_ubo_fragment >= 0)
glUniform4fv(loc->flat_ubo_fragment,
(GLsizei)((draw->backend_data_size + 15) / 16),
(const GLfloat*)draw->backend_data);
}
else
{
const math_matrix_4x4 *mat = draw->matrix_data
? (const math_matrix_4x4*)draw->matrix_data
: (const math_matrix_4x4*)&gl->mvp_no_rot;
if (lin_prog)
{
/* The MVP, then the menu nits and the composite's gamut:
* the composite shows a UI texel at the menu nits */
float ubo[20];
memcpy(ubo, mat->data, 16 * sizeof(float));
ubo[16] = gl->scrgb.menu_nits;
ubo[17] = (float)gl->scrgb.expand_gamut;
ubo[18] = 0.0f;
ubo[19] = 0.0f;
if (gl->pipelines.alpha_blend_linear_loc.flat_ubo_vertex >= 0)
glUniform4fv(gl->pipelines.alpha_blend_linear_loc.flat_ubo_vertex,
5, ubo);
if (gl->pipelines.alpha_blend_linear_loc.flat_ubo_fragment >= 0)
glUniform4fv(gl->pipelines.alpha_blend_linear_loc.flat_ubo_fragment,
5, ubo);
}
else if (gl->pipelines.alpha_blend_loc.flat_ubo_vertex >= 0)
glUniform4fv(gl->pipelines.alpha_blend_loc.flat_ubo_vertex,
4, mat->data);
}
#ifdef HAVE_SHADERPIPELINE
/* The two ribbon shaders declare only the position attribute:
* it is bound from the ribbon mesh's own buffer, the others are
* left disabled. */
if ( draw->pipeline_id == VIDEO_SHADER_MENU
|| draw->pipeline_id == VIDEO_SHADER_MENU_2)
{
/* From the ribbon mesh's own buffer, its position only */
const gfx_display_mesh_t *mesh = disp_get_ptr()->effect_mesh;
int slot;
if (mesh && (slot = gl3_mesh_slot(gl, mesh)) >= 0)
{
gl->meshes[slot].last_frame = gl->mesh_frame;
glBindBuffer(GL_ARRAY_BUFFER, gl->meshes[slot].vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(gfx_display_mesh_vertex_t), (void *)(uintptr_t)0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, mesh->vertex_count);
glDisableVertexAttribArray(0);
}
}
else
#endif
{
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
gl3_bind_scratch_vbo(gl, coords.vertex,
2 * sizeof(float) * coords.vertices);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
2 * sizeof(float), (void *)(uintptr_t)0);
gl3_bind_scratch_vbo(gl, coords.tex_coord,
2 * sizeof(float) * coords.vertices);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
2 * sizeof(float), (void *)(uintptr_t)0);
gl3_bind_scratch_vbo(gl, coords.color,
4 * sizeof(float) * coords.vertices);
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE,
4 * sizeof(float), (void *)(uintptr_t)0);
/* See the matching comment in the chain.active branch above:
* every caller passes TRIANGLESTRIP. */
glDrawArrays(GL_TRIANGLE_STRIP, 0, coords.vertices);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
}
}
#endif /* HAVE_SLANG */
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
static void gfx_display_gl3_blend_begin(void *data)
{
gl3_t *gl = (gl3_t*)data;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (gl->chain.active)
gl->chain.shader->use(gl, gl->chain.shader_data, VIDEO_SHADER_STOCK_BLEND, true);
#ifdef HAVE_SLANG
else
glUseProgram(gl->pipelines.alpha_blend);
#endif
}
static void gfx_display_gl3_blend_end(void *data)
{
glDisable(GL_BLEND);
}
#ifdef HAVE_SLANG
static bool gfx_display_gl3_mesh_draw(void *data, unsigned video_dims,
const gfx_display_mesh_t *mesh, const float *mvp,
uintptr_t texture, const float *tint)
{
/* The vertex stage's UBO, flattened: a mat4, then a vec4 */
float ubo[20];
math_matrix_4x4 user, out;
gl3_t *gl = (gl3_t*)data;
GLboolean blend;
int slot;
if (!gl || !mesh || !gl->pipelines.mesh
|| gl->pipelines.mesh_loc.flat_ubo_vertex < 0)
return false;
if ((slot = gl3_mesh_slot(gl, mesh)) < 0)
return false;
gl->meshes[slot].last_frame = ++gl->mesh_frame;
memcpy(user.data, mvp, sizeof(user.data));
matrix_4x4_multiply(out, gl->mvp_no_rot, user);
memcpy(ubo, out.data, sizeof(out.data));
memcpy(ubo + 16, tint, 4 * sizeof(float));
/* The whole display, as draw() sets it for a strip at the origin */
glViewport(0, 0, VIDEO_SCALE_W(video_dims), VIDEO_SCALE_H(video_dims));
/* Meshes are drawn blended, as every driver draws them */
blend = glIsEnabled(GL_BLEND);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(gl->pipelines.mesh);
glUniform4fv(gl->pipelines.mesh_loc.flat_ubo_vertex, 5, ubo);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture ? (GLuint)texture : gl->white_texture);
/* Read as stored: three floats, two 16-bit and four 8-bit
* normalised integers */
glBindBuffer(GL_ARRAY_BUFFER, gl->meshes[slot].vbo);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
sizeof(gfx_display_mesh_vertex_t), (void *)(uintptr_t)0);
glVertexAttribPointer(1, 2, GL_UNSIGNED_SHORT, GL_TRUE,
sizeof(gfx_display_mesh_vertex_t), (void *)(uintptr_t)12);
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE,
sizeof(gfx_display_mesh_vertex_t), (void *)(uintptr_t)16);
if (mesh->indices)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl->meshes[slot].ibo);
glDrawElements(mesh->topology == GFX_MESH_TRIANGLE_STRIP
? GL_TRIANGLE_STRIP : GL_TRIANGLES,
mesh->index_count, GL_UNSIGNED_SHORT, (void *)(uintptr_t)0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
else
glDrawArrays(mesh->topology == GFX_MESH_TRIANGLE_STRIP
? GL_TRIANGLE_STRIP : GL_TRIANGLES, 0, mesh->vertex_count);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
if (!blend)
glDisable(GL_BLEND);
/* The draws after this one may lean on the program blend_begin
* bound */
if (gl->chain.active)
gl->chain.shader->use(gl, gl->chain.shader_data,
VIDEO_SHADER_STOCK_BLEND, true);
else
glUseProgram(gl->pipelines.alpha_blend);
return true;
}
#endif
static void gfx_display_gl3_scissor_begin(void *data, unsigned video_dims,
int x, int y, unsigned dims)
{
unsigned video_height = VIDEO_SCALE_H(video_dims);
unsigned width = VIDEO_SCALE_W(dims);
unsigned height = VIDEO_SCALE_H(dims);
glScissor(x, video_height - y - height, width, height);
glEnable(GL_SCISSOR_TEST);
}
static void gfx_display_gl3_scissor_end(void *data, unsigned video_dims)
{
glDisable(GL_SCISSOR_TEST);
}
/**
* FONT DRIVER
*/
/* TODO: Move viewport side effects to the caller: it's a source of bugs. */
#define GL_CORE_RASTER_FONT_EMIT(c, vx, vy) \
font_vertex[ 2 * (6 * i + c) + 0] = (x + (delta_x + off_x + vx * width) * scale) * inv_win_width; \
font_vertex[ 2 * (6 * i + c) + 1] = (y + (delta_y - off_y - vy * height) * scale) * inv_win_height; \
font_tex_coords[ 2 * (6 * i + c) + 0] = (tex_x + vx * width) * inv_tex_size_x; \
font_tex_coords[ 2 * (6 * i + c) + 1] = (tex_y + vy * height) * inv_tex_size_y
#define MAX_MSG_LEN_CHUNK 64
typedef struct
{
gl3_t *gl;
GLuint tex;
/* The atlas size the texture was made at; the atlas may grow */
unsigned tex_dims;
const font_renderer_driver_t *font_driver;
void *font_data;
struct font_atlas *atlas;
video_font_raster_block_t *block;
/* The chunk a line is built into before it is handed over. Here
* rather than on the stack of the function that fills it: three
* arrays of MAX_MSG_LEN_CHUNK glyphs are twelve kilobytes, and a
* frame that size is three times what this tree allows. One font
* renders at a time on the thread that draws, so one is enough. */
GLfloat font_vertex[2 * 6 * MAX_MSG_LEN_CHUNK];
GLfloat font_tex_coords[2 * 6 * MAX_MSG_LEN_CHUNK];
GLfloat font_color[4 * 6 * MAX_MSG_LEN_CHUNK];
} gl3_raster_t;
static void gl3_raster_font_free(void *data,
bool is_threaded)
{
gl3_raster_t *font = (gl3_raster_t*)data;
if (!font)
return;
if (font->font_driver && font->font_data)
font->font_driver->free(font->font_data);
if (is_threaded)
if (
font->gl &&
font->gl->ctx_driver &&
font->gl->ctx_driver->make_current)
font->gl->ctx_driver->make_current(true);
glDeleteTextures(1, &font->tex);
free(font);
}
static void gl3_raster_font_upload_atlas(gl3_raster_t *font)
{
if (font->tex)
glDeleteTextures(1, &font->tex);
glGenTextures(1, &font->tex);
glBindTexture(GL_TEXTURE_2D, font->tex);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8, font->atlas->width, font->atlas->height);
font->tex_dims = VIDEO_SCALE_PACK(font->atlas->width,
font->atlas->height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
font->atlas->width, font->atlas->height, GL_RED, GL_UNSIGNED_BYTE, font->atlas->buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
}
/* Update only the atlas dirty rectangle in place. glcore/GLES3 both
* guarantee GL_UNPACK_ROW_LENGTH, so the exact sub-rectangle can be
* uploaded without staging or respecifying the (immutable) storage. */
static void gl3_raster_font_update_atlas_region(gl3_raster_t *font,
unsigned xy0, unsigned xy1)
{
unsigned x0 = VIDEO_SCALE_W(xy0);
unsigned y0 = VIDEO_SCALE_H(xy0);
unsigned x1 = VIDEO_SCALE_W(xy1);
unsigned y1 = VIDEO_SCALE_H(xy1);
if ( x1 <= x0 || y1 <= y0
|| x1 > (unsigned)font->atlas->width
|| y1 > (unsigned)font->atlas->height)
return;
glBindTexture(GL_TEXTURE_2D, font->tex);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)font->atlas->width);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glTexSubImage2D(GL_TEXTURE_2D, 0, (GLint)x0, (GLint)y0,
(GLsizei)(x1 - x0), (GLsizei)(y1 - y0),
GL_RED, GL_UNSIGNED_BYTE,
font->atlas->buffer + (size_t)y0 * font->atlas->width + x0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
static void *gl3_raster_font_init(void *data,
const char *font_path, float font_size,
bool is_threaded)
{
gl3_raster_t *font = (gl3_raster_t*)calloc(1, sizeof(*font));
if (!font)
return NULL;
font->gl = (gl3_t*)data;
if (!font_renderer_create_default(
&font->font_driver,
&font->font_data, font_path, font_size, FONT_ATLAS_FORMAT_A8))
{
free(font);
return NULL;
}
if (is_threaded)
if (
font->gl &&
font->gl->ctx_driver &&
font->gl->ctx_driver->make_current)
font->gl->ctx_driver->make_current(false);
font->atlas = font->font_driver->get_atlas(font->font_data);
/* The atlas may grow, up to the largest texture there is */
{
GLint max_tex = 0;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex);
if (max_tex > 0)
font->atlas->max_dims = VIDEO_SCALE_PACK(
(unsigned)max_tex, (unsigned)max_tex);
}
gl3_raster_font_upload_atlas(font);
font->atlas->dirty = false;
return font;
}
static int gl3_raster_font_get_message_width(void *data, const char *msg,
size_t msg_len, float scale)
{
gl3_raster_t *font = (gl3_raster_t*)data;
if (!font)
return 0;
return font_renderer_get_message_width(font->font_driver,
font->font_data, msg, msg_len, scale);
}
static void gl3_raster_font_draw_vertices(gl3_t *gl,
gl3_raster_t *font,
const video_coords_t *coords)
{
if (font->atlas->dirty)
{
/* Immutable storage: an atlas that grew needs a texture of its
* own size */
if ( font->tex
&& font->tex_dims == VIDEO_SCALE_PACK(font->atlas->width,
font->atlas->height))
gl3_raster_font_update_atlas_region(font,
font->atlas->dirty_xy0, font->atlas->dirty_xy1);
else
gl3_raster_font_upload_atlas(font);
font->atlas->dirty = false;
}
if (gl->chain.active)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, font->tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ONE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_ONE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_ONE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED);
gl->chain.shader->set_coords(gl->chain.shader_data, coords);
gl->chain.shader->set_mvp(gl->chain.shader_data, &gl->mvp_no_rot);
glDrawArrays(GL_TRIANGLES, 0, coords->vertices);
}
#ifdef HAVE_SLANG
else
{
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, font->tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);
if (gl->pipelines.font_loc.flat_ubo_vertex >= 0)
glUniform4fv(gl->pipelines.font_loc.flat_ubo_vertex,
4, gl->mvp_no_rot.data);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
gl3_bind_scratch_vbo(gl, coords->vertex,
2 * sizeof(float) * coords->vertices);
glVertexAttribPointer(0, 2,
GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)(uintptr_t)0);
gl3_bind_scratch_vbo(gl, coords->tex_coord,
2 * sizeof(float) * coords->vertices);
glVertexAttribPointer(1, 2,
GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)(uintptr_t)0);
gl3_bind_scratch_vbo(gl, coords->color,
4 * sizeof(float) * coords->vertices);
glVertexAttribPointer(2, 4,
GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)(uintptr_t)0);
glDrawArrays(GL_TRIANGLES, 0, coords->vertices);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
}
#endif
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
#define GL3_RASTER_FONT_FLUSH() \
do \
{ \
coords.tex_coord = font_tex_coords; \
coords.vertex = font_vertex; \
coords.color = font_color; \
coords.vertices = i * 6; \
coords.lut_tex_coord = font_tex_coords; \
if (font->block) \
video_coord_array_append(&font->block->carr, \
&coords, coords.vertices); \
else \
gl3_raster_font_draw_vertices(gl, font, &coords); \
i = 0; \
} while (0)
static void gl3_raster_font_render_message(
gl3_t *gl,
gl3_raster_t *font, const char *msg, size_t msg_len,
GLfloat scale, const GLfloat color[4], GLfloat pos_x,
GLfloat pos_y, unsigned text_align)
{
struct video_coords coords;
GLfloat color_block[4 * 6];
int n;
float line_height;
struct font_line_metrics *line_metrics = NULL;
int i = 0;
int x = 0;
int y = 0;
GLfloat *font_tex_coords = font->font_tex_coords;
GLfloat *font_vertex = font->font_vertex;
GLfloat *font_color = font->font_color;
float inv_tex_size_x = 1.0f / font->atlas->width;
float inv_tex_size_y = 1.0f / font->atlas->height;
float inv_win_width = 1.0f / VIDEO_SCALE_W(gl->vp.dims);
float inv_win_height = 1.0f / VIDEO_SCALE_H(gl->vp.dims);
int pre_x = roundf(pos_x * VIDEO_SCALE_W(gl->vp.dims));
const struct font_glyph* (*get_glyph)(void*, uint32_t)
= font->font_driver->get_glyph;
void *font_data = font->font_data;
const struct font_glyph *glyph_q = get_glyph(font_data, '?');
bool aligned = (text_align == TEXT_ALIGN_RIGHT
|| text_align == TEXT_ALIGN_CENTER);
font->font_driver->get_line_metrics(font->font_data, &line_metrics);
line_height = line_metrics->height * scale / VIDEO_SCALE_H(gl->vp.dims);
for (n = 0; n < 6; n++)
{
color_block[4 * n + 0] = color[0];
color_block[4 * n + 1] = color[1];
color_block[4 * n + 2] = color[2];
color_block[4 * n + 3] = color[3];
}
#define FONT_LAYOUT_ALIGNED aligned
#define FONT_LAYOUT_LINE(line, line_width, count, bytes) \
do \
{ \
(void)(count); \
x = pre_x; \
y = roundf((pos_y - (float)(line) * line_height) \
* VIDEO_SCALE_H(gl->vp.dims)); \
if (text_align == TEXT_ALIGN_RIGHT) \
x -= (int)((line_width) * scale); \
else if (text_align == TEXT_ALIGN_CENTER) \
x -= (int)((line_width) * scale) / 2; \
} while (0)
#define FONT_LAYOUT_GLYPH(glyph, pen_x, pen_y) \
do \
{ \
int off_x = (glyph)->draw_offset_x; \
int off_y = (glyph)->draw_offset_y; \
int tex_x = VIDEO_SCALE_W((glyph)->atlas_pos); \
int tex_y = VIDEO_SCALE_H((glyph)->atlas_pos); \
int width = VIDEO_SCALE_W((glyph)->dims); \
int height = VIDEO_SCALE_H((glyph)->dims); \
int delta_x = (pen_x); \
int delta_y = -(pen_y); \
GL_CORE_RASTER_FONT_EMIT(0, 0, 1); /* Bottom-left */ \
GL_CORE_RASTER_FONT_EMIT(1, 1, 1); /* Bottom-right */ \
GL_CORE_RASTER_FONT_EMIT(2, 0, 0); /* Top-left */ \
GL_CORE_RASTER_FONT_EMIT(3, 1, 0); /* Top-right */ \
GL_CORE_RASTER_FONT_EMIT(4, 0, 0); /* Top-left */ \
GL_CORE_RASTER_FONT_EMIT(5, 1, 1); /* Bottom-right */ \
memcpy(&font_color[4 * 6 * i], color_block, sizeof(color_block)); \
if (++i == MAX_MSG_LEN_CHUNK) \
GL3_RASTER_FONT_FLUSH(); \
} while (0)
#define FONT_LAYOUT_LINE_END() \
do \
{ \
if (i) \
GL3_RASTER_FONT_FLUSH(); \
} while (0)
#include "../font_layout.h"
}
#undef GL3_RASTER_FONT_FLUSH
static void gl3_raster_font_setup_viewport(
gl3_t *gl,
unsigned dims,
gl3_raster_t *font, bool full_screen)
{
gl3_set_viewport(gl, dims, full_screen, false);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
if (gl->chain.active)
{
if (gl->chain.shader && gl->chain.shader->use)
gl->chain.shader->use(gl, gl->chain.shader_data, VIDEO_SHADER_STOCK_BLEND, true);
}
#ifdef HAVE_SLANG
else
glUseProgram(gl->pipelines.font);
#endif
}
static void gl3_raster_font_render_msg(
void *userdata,
void *data,
const char *msg, size_t msg_len,
const struct font_params *params)
{
font_params_resolved_t rp;
GLfloat color[4];
int drop_x, drop_y;
GLfloat x, y, scale, drop_mod, drop_alpha;
enum text_alignment text_align = TEXT_ALIGN_LEFT;
bool full_screen = false;
gl3_raster_t *font = (gl3_raster_t*)data;
gl3_t *gl = (gl3_t*)userdata;
unsigned dims = gl->video_dims;
if (!font || !msg || !*msg || !gl)
return;
/* Asked for before anything is laid out: it may have grown, and the
* texture coordinates are taken from its size */
font->atlas = font->font_driver->get_atlas(font->font_data);
font_driver_resolve_params(params, &rp);
x = rp.x;
y = rp.y;
scale = rp.scale;
full_screen = rp.full_screen;
text_align = rp.text_align;
drop_x = rp.drop_x;
drop_y = rp.drop_y;
drop_mod = rp.drop_mod;
drop_alpha = rp.drop_alpha;
if (rp.color_hp)
{
color[0] = rp.color_hp[0];
color[1] = rp.color_hp[1];
color[2] = rp.color_hp[2];
color[3] = rp.color_hp[3];
}
else
{
color[0] = rp.color[0];
color[1] = rp.color[1];
color[2] = rp.color[2];
color[3] = rp.color[3];
}
/* If alpha is 0.0f, turn it into default 1.0f */
if (color[3] <= 0.0f)
color[3] = 1.0f;
if (font->block)
font->block->fullscreen = full_screen;
else
gl3_raster_font_setup_viewport(gl, dims, font, full_screen);
if ( (msg && *msg)
&& font->font_data && font->font_driver)
{
if (drop_x || drop_y)
{
GLfloat color_dark[4];
color_dark[0] = color[0] * drop_mod;
color_dark[1] = color[1] * drop_mod;
color_dark[2] = color[2] * drop_mod;
color_dark[3] = color[3] * drop_alpha;
gl3_raster_font_render_message(gl, font, msg, msg_len, scale,
color_dark,
x + scale * drop_x / VIDEO_SCALE_W(gl->vp.dims),
y + scale * drop_y / VIDEO_SCALE_H(gl->vp.dims), text_align);
}
gl3_raster_font_render_message(gl, font, msg, msg_len, scale,
color,
x, y, text_align);
}
if (!font->block)
{
glDisable(GL_BLEND);
gl3_set_viewport(gl, dims, false, true);
}
}
static const struct font_glyph *gl3_raster_font_get_glyph(
void *data, uint32_t code)
{
gl3_raster_t *font = (gl3_raster_t*)data;
if (font && font->font_driver)
return font->font_driver->get_glyph((void*)font->font_data, code);
return NULL;
}
static void gl3_raster_font_flush_block(unsigned dims, void *data)
{
gl3_raster_t *font = (gl3_raster_t*)data;
video_font_raster_block_t *block = font ? font->block : NULL;
gl3_t *gl = font ? font->gl : NULL;
if (!font || !block || !block->carr.coords.vertices || !gl)
return;
gl3_raster_font_setup_viewport(gl, dims, font, block->fullscreen);
gl3_raster_font_draw_vertices(gl, font, (video_coords_t*)&block->carr.coords);
glDisable(GL_BLEND);
gl3_set_viewport(gl, dims, block->fullscreen, true);
}
static void gl3_raster_font_bind_block(void *data, void *userdata)
{
gl3_raster_t *font = (gl3_raster_t*)data;
video_font_raster_block_t *block = (video_font_raster_block_t*)userdata;
if (font)
font->block = block;
}
static bool gl3_raster_font_get_line_metrics(void* data, struct font_line_metrics **metrics)
{
gl3_raster_t *font = (gl3_raster_t*)data;
if (font && font->font_driver && font->font_data)
{
font->font_driver->get_line_metrics(font->font_data, metrics);
return true;
}
return false;
}
/**
* VIDEO DRIVER
*/
static void gl3_deinit_fences(gl3_t *gl)
{
size_t i;
for (i = 0; i < gl->fence_count; i++)
{
if (gl->fences[i])
glDeleteSync(gl->fences[i]);
}
gl->fence_count = 0;
memset(gl->fences, 0, sizeof(gl->fences));
}
static bool gl3_init_pbo_readback(gl3_t *gl)
{
int i;
struct scaler_ctx *scaler = NULL;
glGenBuffers(GL_CORE_NUM_PBOS, gl->pbo_readback);
for (i = 0; i < GL_CORE_NUM_PBOS; i++)
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, gl->pbo_readback[i]);
glBufferData(GL_PIXEL_PACK_BUFFER,
VIDEO_SCALE_AREA(gl->vp.dims) * sizeof(uint32_t),
NULL, GL_STREAM_READ);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
scaler = &gl->pbo_readback_scaler;
scaler->in_width = VIDEO_SCALE_W(gl->vp.dims);
scaler->in_height = VIDEO_SCALE_H(gl->vp.dims);
scaler->out_width = VIDEO_SCALE_W(gl->vp.dims);
scaler->out_height = VIDEO_SCALE_H(gl->vp.dims);
scaler->in_stride = VIDEO_SCALE_W(gl->vp.dims) * sizeof(uint32_t);
scaler->out_stride = VIDEO_SCALE_W(gl->vp.dims) * 3;
scaler->in_fmt = SCALER_FMT_ABGR8888;
scaler->out_fmt = SCALER_FMT_BGR24;
scaler->scaler_type = SCALER_TYPE_POINT;
if (!scaler_ctx_gen_filter(scaler))
{
gl->flags &= ~GL3_FLAG_PBO_READBACK_ENABLE;
RARCH_ERR("[GLCore] Failed to initialize pixel conversion for PBO.\n");
glDeleteBuffers(4, gl->pbo_readback);
memset(gl->pbo_readback, 0, sizeof(gl->pbo_readback));
return false;
}
return true;
}
static void gl3_deinit_pbo_readback(gl3_t *gl)
{
int i;
for (i = 0; i < GL_CORE_NUM_PBOS; i++)
if (gl->pbo_readback[i] != 0)
glDeleteBuffers(1, &gl->pbo_readback[i]);
memset(gl->pbo_readback, 0, sizeof(gl->pbo_readback));
scaler_ctx_gen_reset(&gl->pbo_readback_scaler);
}
static void gl3_pbo_async_readback(gl3_t *gl)
{
glBindBuffer(GL_PIXEL_PACK_BUFFER,
gl->pbo_readback[gl->pbo_readback_index++]);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
/* See the screenshot read-back: under scRGB, recording reads the
* pre-encode SDR offscreen, roundtrip-free. */
if (gl->scrgb.active && gl->scrgb.fbo)
glBindFramebuffer(GL_READ_FRAMEBUFFER, gl->scrgb.fbo);
#ifndef HAVE_OPENGLES
else
glReadBuffer(GL_BACK);
#endif
if (gl->pbo_readback_index >= GL_CORE_NUM_PBOS)
gl->pbo_readback_index = 0;
gl->pbo_readback_valid[gl->pbo_readback_index] = true;
glReadPixels(VIDEO_POS_X(gl->vp.pos), VIDEO_POS_Y(gl->vp.pos),
VIDEO_SCALE_W(gl->vp.dims), VIDEO_SCALE_H(gl->vp.dims),
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
if (gl->scrgb.active && gl->scrgb.fbo)
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
static void gl3_fence_iterate(gl3_t *gl, unsigned hard_sync_frames)
{
if (gl->fence_count < GL_CORE_NUM_FENCES)
{
/*
* We need to do some work after the flip, or we risk fencing too early.
* Do as little work as possible.
*/
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, 1, 1);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_SCISSOR_TEST);
gl->fences[gl->fence_count++] = glFenceSync(
GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
while (gl->fence_count > hard_sync_frames)
{
glClientWaitSync(gl->fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000);
glDeleteSync(gl->fences[0]);
gl->fence_count--;
memmove(gl->fences, gl->fences + 1, gl->fence_count * sizeof(GLsync));
}
}
#ifdef HAVE_OVERLAY
static void gl3_free_overlay(gl3_t *gl)
{
if (gl->overlay_tex && !(gl->flags & GL3_FLAG_OVERLAY_BORROWED))
glDeleteTextures(gl->overlays, gl->overlay_tex);
gl->flags &= ~GL3_FLAG_OVERLAY_BORROWED;
/* The three coordinate arrays are views into the overlay_tex block. */
free(gl->overlay_tex);
gl->overlay_tex = NULL;
gl->overlay_vertex_coord = NULL;
gl->overlay_tex_coord = NULL;
gl->overlay_color_coord = NULL;
gl->overlays = 0;
}
static void gl3_free_scratch_vbos(gl3_t *gl)
{
int i;
for (i = 0; i < GL_CORE_NUM_VBOS; i++)
if (gl->scratch_vbos[i])
glDeleteBuffers(1, &gl->scratch_vbos[i]);
for (i = 0; i < (int)ARRAY_SIZE(gl->meshes); i++)
{
if (gl->meshes[i].vbo)
glDeleteBuffers(1, &gl->meshes[i].vbo);
if (gl->meshes[i].ibo)
glDeleteBuffers(1, &gl->meshes[i].ibo);
gl->meshes[i].vbo = 0;
gl->meshes[i].ibo = 0;
gl->meshes[i].id = 0;
}
}
static void gl3_overlay_vertex_geom(void *data,
unsigned image,
float x, float y,
float w, float h)
{
GLfloat *vertex = NULL;
gl3_t *gl = (gl3_t*)data;
if (!gl || !gl->overlay_vertex_coord)
return;
if (image >= gl->overlays)
return;
vertex = (GLfloat*)&gl->overlay_vertex_coord[image * 8];
/* Flipped, so we preserve top-down semantics. */
y = 1.0f - y;
h = -h;
vertex[0] = x;
vertex[1] = y;
vertex[2] = x + w;
vertex[3] = y;
vertex[4] = x;
vertex[5] = y + h;
vertex[6] = x + w;
vertex[7] = y + h;
}
static void gl3_overlay_tex_geom(void *data,
unsigned image,
GLfloat x, GLfloat y,
GLfloat w, GLfloat h)
{
GLfloat *tex = NULL;
gl3_t *gl = (gl3_t*)data;
if (!gl || !gl->overlay_tex_coord)
return;
if (image >= gl->overlays)
return;
tex = (GLfloat*)&gl->overlay_tex_coord[image * 8];
tex[0] = x;
tex[1] = y;
tex[2] = x + w;
tex[3] = y;
tex[4] = x;
tex[5] = y + h;
tex[6] = x + w;
tex[7] = y + h;
}
static void gl3_render_overlay(gl3_t *gl,
unsigned width, unsigned height)
{
size_t i;
if (!gl->overlay_tex || !gl->overlays)
return;
glEnable(GL_BLEND);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
if (gl->flags & GL3_FLAG_OVERLAY_FULLSCREEN)
glViewport(0, 0, width, height);
#ifdef HAVE_SLANG
/* Ensure that we reset the attrib array. */
glUseProgram(gl->pipelines.alpha_blend);
if (gl->pipelines.alpha_blend_loc.flat_ubo_vertex >= 0)
glUniform4fv(gl->pipelines.alpha_blend_loc.flat_ubo_vertex, 4, gl->mvp_no_rot.data);
#endif
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
gl3_bind_scratch_vbo(gl, gl->overlay_vertex_coord,
8 * sizeof(float) * gl->overlays);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
2 * sizeof(float), (void *)(uintptr_t)0);
gl3_bind_scratch_vbo(gl, gl->overlay_tex_coord,
8 * sizeof(float) * gl->overlays);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
2 * sizeof(float), (void *)(uintptr_t)0);
gl3_bind_scratch_vbo(gl, gl->overlay_color_coord,
16 * sizeof(uint16_t) * gl->overlays);
glVertexAttribPointer(2, 4, GL_UNSIGNED_SHORT, GL_TRUE,
4 * sizeof(uint16_t), (void *)(uintptr_t)0);
for (i = 0; i < gl->overlays; i++)
{
/* Name 0 is black only on a core context; a GLES driver may
* sample whatever the unit last held. */
if (!gl->overlay_tex[i])
continue;
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gl->overlay_tex[i]);
glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(4 * i), 4);
}
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, 0);
if (gl->flags & GL3_FLAG_OVERLAY_FULLSCREEN)
glViewport(VIDEO_POS_X(gl->vp.pos), VIDEO_POS_Y(gl->vp.pos), VIDEO_SCALE_W(gl->vp.dims), VIDEO_SCALE_H(gl->vp.dims));
}
#endif
static void gl3_deinit_hw_render(gl3_t *gl)
{
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
{
unsigned i;
for (i = 1; i < 3; i++)
{
if (gl->hw_ring_fbo[i])
glDeleteFramebuffers(1, &gl->hw_ring_fbo[i]);
if (gl->hw_ring_texture[i])
glDeleteTextures(1, &gl->hw_ring_texture[i]);
gl->hw_ring_fbo[i] = 0;
gl->hw_ring_texture[i] = 0;
}
gl->hw_ring_fbo[0] = 0;
gl->hw_ring_texture[0] = 0;
}
if (gl->hw_render_fbo)
glDeleteFramebuffers(1, &gl->hw_render_fbo);
if (gl->hw_render_rb_ds)
glDeleteRenderbuffers(1, &gl->hw_render_rb_ds);
if (gl->hw_render_texture)
glDeleteTextures(1, &gl->hw_render_texture);
gl->hw_render_fbo = 0;
gl->hw_render_rb_ds = 0;
gl->hw_render_texture = 0;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
gl->flags &= ~GL3_FLAG_HW_RENDER_ENABLE;
}
static void gl3_destroy_resources(gl3_t *gl)
{
int i;
if (!gl)
return;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
#ifdef HAVE_SLANG
if (gl->filter_chain)
gl3_filter_chain_free(gl->filter_chain);
#endif
gl->filter_chain = NULL;
#ifdef HAVE_SLANG
if (gl->filter_chain_default)
gl3_filter_chain_free(gl->filter_chain_default);
#endif
gl->filter_chain_default = NULL;
if (gl->chain.shader)
{
gl->chain.shader->deinit(gl->chain.shader_data);
gl->chain.shader = NULL;
gl->chain.shader_data = NULL;
}
if (gl->chain.num_fbo_passes)
{
glDeleteFramebuffers(gl->chain.num_fbo_passes, gl->chain.fbo);
glDeleteTextures(gl->chain.num_fbo_passes, gl->chain.fbo_texture);
gl->chain.num_fbo_passes = 0;
}
if (gl->chain.fbo_feedback)
{
glDeleteFramebuffers(1, &gl->chain.fbo_feedback);
gl->chain.fbo_feedback = 0;
}
if (gl->chain.fbo_feedback_texture)
{
glDeleteTextures(1, &gl->chain.fbo_feedback_texture);
gl->chain.fbo_feedback_texture = 0;
}
/* Nothing was made on a context that never came up, and on the
* null context there are no GL entry points to call. */
if (gl->vao != 0)
{
glBindVertexArray(0);
glDeleteVertexArrays(1, &gl->vao);
}
for (i = 0; i < GL_CORE_NUM_TEXTURES; i++)
{
if (gl->textures[i].tex != 0)
glDeleteTextures(1, &gl->textures[i].tex);
}
memset(gl->textures, 0, sizeof(gl->textures));
if (gl->menu_texture != 0)
glDeleteTextures(1, &gl->menu_texture);
if (gl->white_texture != 0)
glDeleteTextures(1, &gl->white_texture);
#ifdef HAVE_SLANG
if (gl->pipelines.alpha_blend)
{
glDeleteProgram(gl->pipelines.alpha_blend);
gl->pipelines.alpha_blend = 0;
}
if (gl->pipelines.alpha_blend_linear)
{
glDeleteProgram(gl->pipelines.alpha_blend_linear);
gl->pipelines.alpha_blend_linear = 0;
}
gl->pipelines.alpha_blend_linear_tried = false;
if (gl->pipelines.mesh)
{
glDeleteProgram(gl->pipelines.mesh);
gl->pipelines.mesh = 0;
}
if (gl->pipelines.font)
{
glDeleteProgram(gl->pipelines.font);
gl->pipelines.font = 0;
}
if (gl->pipelines.hdr_scrgb)
{
glDeleteProgram(gl->pipelines.hdr_scrgb);
gl->pipelines.hdr_scrgb = 0;
}
if (gl->scrgb.fbo)
{
glDeleteFramebuffers(1, &gl->scrgb.fbo);
gl->scrgb.fbo = 0;
}
if (gl->scrgb.tex)
{
glDeleteTextures(1, &gl->scrgb.tex);
gl->scrgb.tex = 0;
}
if (gl->scrgb.ui_fbo)
{
glDeleteFramebuffers(1, &gl->scrgb.ui_fbo);
gl->scrgb.ui_fbo = 0;
}
if (gl->scrgb.ui_tex)
{
glDeleteTextures(1, &gl->scrgb.ui_tex);
gl->scrgb.ui_tex = 0;
}
#ifdef HAVE_SHADERPIPELINE
if (gl->pipelines.ribbon)
{
glDeleteProgram(gl->pipelines.ribbon);
gl->pipelines.ribbon = 0;
}
if (gl->pipelines.ribbon_simple)
{
glDeleteProgram(gl->pipelines.ribbon_simple);
gl->pipelines.ribbon_simple = 0;
}
if (gl->pipelines.snow_simple)
{
glDeleteProgram(gl->pipelines.snow_simple);
gl->pipelines.snow_simple = 0;
}
if (gl->pipelines.snow)
{
glDeleteProgram(gl->pipelines.snow);
gl->pipelines.snow = 0;
}
if (gl->pipelines.bokeh)
{
glDeleteProgram(gl->pipelines.bokeh);
gl->pipelines.bokeh = 0;
}
if (gl->pipelines.snowflake)
{
glDeleteProgram(gl->pipelines.snowflake);
gl->pipelines.snowflake = 0;
}
#endif /* HAVE_SHADERPIPELINE */
#endif /* HAVE_SLANG */
#ifdef HAVE_OVERLAY
gl3_free_overlay(gl);
gl3_free_scratch_vbos(gl);
#endif
gl3_deinit_fences(gl);
gl3_deinit_pbo_readback(gl);
gl3_deinit_hw_render(gl);
}
static bool gl3_hw_ring_expected(void);
static bool gl3_core_context_is_mains(gl3_t *gl);
static bool gl3_init_hw_render(gl3_t *gl, unsigned width, unsigned height)
{
GLint max_fbo_size;
GLint max_rb_size;
GLenum status;
struct retro_hw_render_callback *hwr = video_driver_get_hw_context();
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
RARCH_LOG("[GLCore] Initializing HW render (%ux%u).\n", width, height);
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_fbo_size);
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &max_rb_size);
RARCH_LOG("[GLCore] Max texture size: %d px, renderbuffer size: %d px.\n",
max_fbo_size, max_rb_size);
if (width > (unsigned)max_fbo_size)
width = max_fbo_size;
if (width > (unsigned)max_rb_size)
width = max_rb_size;
if (height > (unsigned)max_fbo_size)
height = max_fbo_size;
if (height > (unsigned)max_rb_size)
height = max_rb_size;
glGenFramebuffers(1, &gl->hw_render_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, gl->hw_render_fbo);
glGenTextures(1, &gl->hw_render_texture);
glBindTexture(GL_TEXTURE_2D, gl->hw_render_texture);
/* A core that declared RETRO_PIXEL_FORMAT_HDR10_2101010 renders PQ
* Rec.2020 code values here. Narrowing those to 8 bits is much worse
* than it is for SDR: PQ allocates its code space perceptually across
* 10,000 nits, so 8-bit PQ bands heavily in the darks. RGB10_A2 costs
* exactly the same bandwidth as RGBA8 and is the format the pixel
* format names, so there is no reason to make it optional. */
glTexStorage2D(GL_TEXTURE_2D, 1,
gl->video_info.source_hdr10 ? GL_RGB10_A2 : GL_RGBA8,
width, height);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->hw_render_texture, 0);
gl->hw_render_rb_ds = 0;
if (hwr->bottom_left_origin)
gl->flags |= GL3_FLAG_HW_RENDER_BOTTOM_LEFT;
else
gl->flags &= ~GL3_FLAG_HW_RENDER_BOTTOM_LEFT;
if (hwr->depth)
{
glGenRenderbuffers(1, &gl->hw_render_rb_ds);
glBindRenderbuffer(GL_RENDERBUFFER, gl->hw_render_rb_ds);
glRenderbufferStorage(GL_RENDERBUFFER, hwr->stencil ? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT16,
width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
if (hwr->stencil)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gl->hw_render_rb_ds);
else
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gl->hw_render_rb_ds);
}
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
RARCH_ERR("[GLCore] Framebuffer is not complete.\n");
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
return false;
}
if (hwr->depth && hwr->stencil)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
else if (hwr->depth)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
else
glClear(GL_COLOR_BUFFER_BIT);
gl->flags |= GL3_FLAG_HW_RENDER_ENABLE;
gl->hw_render_max_dims = VIDEO_SCALE_PACK(width, height);
/* The ring's other slots: the same target again, sharing the depth
* renderbuffer, which only the core's rendering touches and never
* two slots at once. Slot 0 is the driver's own. */
gl->hw_ring_texture[0] = gl->hw_render_texture;
gl->hw_ring_fbo[0] = gl->hw_render_fbo;
if (gl3_hw_ring_expected())
{
unsigned i;
for (i = 1; i < 3; i++)
{
glGenFramebuffers(1, &gl->hw_ring_fbo[i]);
glBindFramebuffer(GL_FRAMEBUFFER, gl->hw_ring_fbo[i]);
glGenTextures(1, &gl->hw_ring_texture[i]);
glBindTexture(GL_TEXTURE_2D, gl->hw_ring_texture[i]);
glTexStorage2D(GL_TEXTURE_2D, 1,
gl->video_info.source_hdr10 ? GL_RGB10_A2 : GL_RGBA8,
width, height);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, gl->hw_ring_texture[i], 0);
if (gl->hw_render_rb_ds)
{
if (hwr->stencil)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, gl->hw_render_rb_ds);
else
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, gl->hw_render_rb_ds);
}
glClear(GL_COLOR_BUFFER_BIT);
}
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
return true;
}
static const gfx_ctx_driver_t *gl3_get_context(gl3_t *gl)
{
unsigned major;
unsigned minor;
enum gfx_ctx_api api;
gfx_ctx_flags_t flags;
const gfx_ctx_driver_t *gfx_ctx = NULL;
void *ctx_data = NULL;
settings_t *settings = config_get_ptr();
struct retro_hw_render_callback *hwr = video_driver_get_hw_context();
#ifdef HAVE_OPENGLES3
api = GFX_CTX_OPENGL_ES_API;
major = 3;
minor = 0;
if (hwr && hwr->context_type == RETRO_HW_CONTEXT_OPENGLES_VERSION)
{
major = hwr->version_major;
minor = hwr->version_minor;
}
#else
api = GFX_CTX_OPENGL_API;
if (hwr && hwr->context_type != RETRO_HW_CONTEXT_NONE)
{
major = hwr->version_major;
minor = hwr->version_minor;
gl_query_core_context_set(hwr->context_type == RETRO_HW_CONTEXT_OPENGL_CORE);
if (hwr->context_type == RETRO_HW_CONTEXT_OPENGL_CORE)
{
flags.flags = 0;
BIT32_SET(flags.flags, GFX_CTX_FLAGS_GL_CORE_CONTEXT);
video_context_driver_set_flags(&flags);
}
}
else
{
major = 3;
minor = 2;
gl_query_core_context_set(true);
flags.flags = 0;
BIT32_SET(flags.flags, GFX_CTX_FLAGS_GL_CORE_CONTEXT);
video_context_driver_set_flags(&flags);
}
#endif
/* Force shared context. */
if (hwr)
{
if (hwr->context_type != RETRO_HW_CONTEXT_NONE)
gl->flags |= GL3_FLAG_USE_SHARED_CONTEXT;
else
gl->flags &= ~GL3_FLAG_USE_SHARED_CONTEXT;
}
gfx_ctx = video_context_driver_init_first(gl,
settings->arrays.video_context_driver,
api, major, minor,
(gl->flags & GL3_FLAG_USE_SHARED_CONTEXT) ? true : false,
&ctx_data);
if (ctx_data)
gl->ctx_data = ctx_data;
/* Need to force here since video_context_driver_init also checks for global option. */
if (gfx_ctx->bind_hw_render)
gfx_ctx->bind_hw_render(ctx_data, (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT) ? true : false);
return gfx_ctx;
}
static bool gl3_recreate_fbo(
struct video_fbo_rect *fbo_rect,
GLuint fbo,
GLuint* texture)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDeleteTextures(1, texture);
glGenTextures(1, texture);
glBindTexture(GL_TEXTURE_2D, *texture);
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA8,
VIDEO_SCALE_W(fbo_rect->dims),
VIDEO_SCALE_H(fbo_rect->dims),
0, GL_RGBA,
GL_UNSIGNED_BYTE, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
*texture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER)
== GL_FRAMEBUFFER_COMPLETE)
return true;
RARCH_WARN("[GLCore] Failed to reinitialize FBO texture.\n");
return false;
}
static void gl3_set_projection(gl3_t *gl,
const struct video_ortho *ortho, bool allow_rotate)
{
static math_matrix_4x4 rot = {
{ 0.0f, 0.0f, 0.0f, 0.0f ,
0.0f, 0.0f, 0.0f, 0.0f ,
0.0f, 0.0f, 0.0f, 0.0f ,
0.0f, 0.0f, 0.0f, 1.0f }
};
float radians, cosine, sine;
/* Calculate projection. */
matrix_4x4_ortho(gl->mvp_no_rot, ortho->left, ortho->right,
ortho->bottom, ortho->top, ortho->znear, ortho->zfar);
if (!allow_rotate)
{
gl->mvp = gl->mvp_no_rot;
return;
}
radians = M_PI * gl->rotation / 180.0f;
cosine = cosf(radians);
sine = sinf(radians);
MAT_ELEM_4X4(rot, 0, 0) = cosine;
MAT_ELEM_4X4(rot, 0, 1) = -sine;
MAT_ELEM_4X4(rot, 1, 0) = sine;
MAT_ELEM_4X4(rot, 1, 1) = cosine;
matrix_4x4_multiply(gl->mvp, rot, gl->mvp_no_rot);
memcpy(gl->mvp_no_rot_yflip.data, gl->mvp_no_rot.data, sizeof(gl->mvp_no_rot.data));
MAT_ELEM_4X4(gl->mvp_no_rot_yflip, 1, 0) *= -1.0f;
MAT_ELEM_4X4(gl->mvp_no_rot_yflip, 1, 1) *= -1.0f;
MAT_ELEM_4X4(gl->mvp_no_rot_yflip, 1, 2) *= -1.0f;
MAT_ELEM_4X4(gl->mvp_no_rot_yflip, 1, 3) *= -1.0f;
memcpy(gl->mvp_yflip.data, gl->mvp.data, sizeof(gl->mvp.data));
MAT_ELEM_4X4(gl->mvp_yflip, 1, 0) *= -1.0f;
MAT_ELEM_4X4(gl->mvp_yflip, 1, 1) *= -1.0f;
MAT_ELEM_4X4(gl->mvp_yflip, 1, 2) *= -1.0f;
MAT_ELEM_4X4(gl->mvp_yflip, 1, 3) *= -1.0f;
}
static void gl3_set_viewport(gl3_t *gl,
unsigned dims,
bool force_full, bool allow_rotate)
{
gl->vp.full_dims = dims;
video_driver_update_viewport(&gl->vp, force_full,
(gl->flags & GL3_FLAG_KEEP_ASPECT) ? true : false, false);
glViewport(VIDEO_POS_X(gl->vp.pos), VIDEO_POS_Y(gl->vp.pos), VIDEO_SCALE_W(gl->vp.dims), VIDEO_SCALE_H(gl->vp.dims));
gl3_set_projection(gl, &gl3_default_ortho, allow_rotate);
/* Set last backbuffer viewport. */
if (!force_full)
{
gl->out_vp_dims = gl->vp.dims;
}
gl->filter_chain_vp.pos = gl->vp.pos;
gl->filter_chain_vp.dims = gl->vp.dims;
}
#ifdef HAVE_SLANG
static const uint32_t gl3_alpha_blend_vert[] =
#include "vulkan_shaders/alpha_blend.vert.inc"
;
static const uint32_t gl3_alpha_blend_linear_frag[] =
#include "vulkan_shaders/alpha_blend_linear.frag.inc"
;
/* The program for a half-float texture under the scRGB composite, made
* the first time one is drawn; 0 where it cannot be. */
static GLuint gl3_linear_program(gl3_t *gl)
{
if ( !gl->pipelines.alpha_blend_linear
&& !gl->pipelines.alpha_blend_linear_tried)
{
gl->pipelines.alpha_blend_linear_tried = true;
gl->pipelines.alpha_blend_linear = gl3_cross_compile_program(
gl3_alpha_blend_vert, sizeof(gl3_alpha_blend_vert),
gl3_alpha_blend_linear_frag, sizeof(gl3_alpha_blend_linear_frag),
&gl->pipelines.alpha_blend_linear_loc, true);
}
return gl->pipelines.alpha_blend_linear;
}
#ifdef HAVE_SHADERPIPELINE
static const uint32_t gl3_pipeline_ribbon_vert[] =
#include "vulkan_shaders/pipeline_ribbon.vert.inc"
;
static const uint32_t gl3_pipeline_ribbon_frag[] =
#include "vulkan_shaders/pipeline_ribbon.frag.inc"
;
static const uint32_t gl3_pipeline_ribbon_simple_vert[] =
#include "vulkan_shaders/pipeline_ribbon_simple.vert.inc"
;
static const uint32_t gl3_pipeline_ribbon_simple_frag[] =
#include "vulkan_shaders/pipeline_ribbon_simple.frag.inc"
;
static const uint32_t gl3_pipeline_snow_simple_frag[] =
#include "vulkan_shaders/pipeline_snow_simple.frag.inc"
;
static const uint32_t gl3_pipeline_snow_frag[] =
#include "vulkan_shaders/pipeline_snow.frag.inc"
;
static const uint32_t gl3_pipeline_bokeh_frag[] =
#include "vulkan_shaders/pipeline_bokeh.frag.inc"
;
static const uint32_t gl3_pipeline_snowflake_frag[] =
#include "vulkan_shaders/pipeline_snowflake.frag.inc"
;
#endif /* HAVE_SHADERPIPELINE */
#ifdef HAVE_SHADERPIPELINE
/* A menu effect's program, compiled the first time the effect is drawn
* rather than all six at start: at most one is ever on screen. 0 when
* it cannot be had, and the effect is not drawn. */
static GLuint gl3_effect_program(gl3_t *gl, unsigned pipeline_id,
const struct gl3_buffer_locations **loc)
{
const uint32_t *vs, *fs;
size_t vs_size, fs_size;
struct gl3_buffer_locations *l;
GLuint *prog;
switch (pipeline_id)
{
case VIDEO_SHADER_MENU:
prog = &gl->pipelines.ribbon; l = &gl->pipelines.ribbon_loc;
vs = gl3_pipeline_ribbon_vert; vs_size = sizeof(gl3_pipeline_ribbon_vert);
fs = gl3_pipeline_ribbon_frag; fs_size = sizeof(gl3_pipeline_ribbon_frag);
break;
case VIDEO_SHADER_MENU_2:
prog = &gl->pipelines.ribbon_simple; l = &gl->pipelines.ribbon_simple_loc;
vs = gl3_pipeline_ribbon_simple_vert; vs_size = sizeof(gl3_pipeline_ribbon_simple_vert);
fs = gl3_pipeline_ribbon_simple_frag; fs_size = sizeof(gl3_pipeline_ribbon_simple_frag);
break;
case VIDEO_SHADER_MENU_3:
prog = &gl->pipelines.snow_simple; l = &gl->pipelines.snow_simple_loc;
vs = gl3_alpha_blend_vert; vs_size = sizeof(gl3_alpha_blend_vert);
fs = gl3_pipeline_snow_simple_frag; fs_size = sizeof(gl3_pipeline_snow_simple_frag);
break;
case VIDEO_SHADER_MENU_4:
prog = &gl->pipelines.snow; l = &gl->pipelines.snow_loc;
vs = gl3_alpha_blend_vert; vs_size = sizeof(gl3_alpha_blend_vert);
fs = gl3_pipeline_snow_frag; fs_size = sizeof(gl3_pipeline_snow_frag);
break;
case VIDEO_SHADER_MENU_5:
prog = &gl->pipelines.bokeh; l = &gl->pipelines.bokeh_loc;
vs = gl3_alpha_blend_vert; vs_size = sizeof(gl3_alpha_blend_vert);
fs = gl3_pipeline_bokeh_frag; fs_size = sizeof(gl3_pipeline_bokeh_frag);
break;
case VIDEO_SHADER_MENU_6:
prog = &gl->pipelines.snowflake; l = &gl->pipelines.snowflake_loc;
vs = gl3_alpha_blend_vert; vs_size = sizeof(gl3_alpha_blend_vert);
fs = gl3_pipeline_snowflake_frag; fs_size = sizeof(gl3_pipeline_snowflake_frag);
break;
default:
return 0;
}
if (!*prog)
*prog = gl3_cross_compile_program(vs, vs_size, fs, fs_size, l, true);
*loc = l;
return *prog;
}
#endif /* HAVE_SHADERPIPELINE */
#endif /* HAVE_SLANG */
#ifdef HAVE_SLANG
static bool gl3_init_pipelines(gl3_t *gl)
{
static const uint32_t alpha_blend_frag[] =
#include "vulkan_shaders/alpha_blend.frag.inc"
;
static const uint32_t mesh_vert[] =
#include "vulkan_shaders/mesh.vert.inc"
;
static const uint32_t font_frag[] =
#include "vulkan_shaders/font.frag.inc"
;
static const uint32_t hdr_scrgb_vert[] =
#include "vulkan_shaders/hdr_scrgb.vert.inc"
;
static const uint32_t hdr_scrgb_frag[] =
#include "vulkan_shaders/hdr_scrgb.frag.inc"
;
if (!gl->pipelines.alpha_blend)
gl->pipelines.alpha_blend = gl3_cross_compile_program(gl3_alpha_blend_vert, sizeof(gl3_alpha_blend_vert),
alpha_blend_frag, sizeof(alpha_blend_frag),
&gl->pipelines.alpha_blend_loc, true);
if (!gl->pipelines.alpha_blend)
return false;
/* Without it meshes are streamed through the alpha-blend program */
if (!gl->pipelines.mesh)
gl->pipelines.mesh = gl3_cross_compile_program(mesh_vert, sizeof(mesh_vert),
alpha_blend_frag, sizeof(alpha_blend_frag),
&gl->pipelines.mesh_loc, true);
if (!gl->pipelines.font)
gl->pipelines.font = gl3_cross_compile_program(gl3_alpha_blend_vert, sizeof(gl3_alpha_blend_vert),
font_frag, sizeof(font_frag),
&gl->pipelines.font_loc, true);
if (!gl->pipelines.font)
return false;
if (gl->scrgb.active && !gl->pipelines.hdr_scrgb)
gl->pipelines.hdr_scrgb = gl3_cross_compile_program(hdr_scrgb_vert, sizeof(hdr_scrgb_vert),
hdr_scrgb_frag, sizeof(hdr_scrgb_frag),
&gl->pipelines.hdr_scrgb_loc, true);
if (gl->scrgb.active && !gl->pipelines.hdr_scrgb)
return false;
return true;
}
#endif /* HAVE_SLANG */
/**
* gl3_get_fallback_shader_type:
* @type : shader type which should be used if possible
*
* Returns a supported fallback shader type in case the given one is not supported.
* For gl3, shader support is completely defined by the context driver shader flags.
*
* gl3_get_fallback_shader_type(RARCH_SHADER_NONE) returns a default shader type.
* if gl3_get_fallback_shader_type(type) != type, type was not supported.
*
* Returns: A supported shader type.
* If RARCH_SHADER_NONE is returned, no shader backend is supported.
**/
static enum rarch_shader_type gl3_get_fallback_shader_type(enum rarch_shader_type type)
{
#if defined(HAVE_SLANG) || defined(HAVE_GLSL)
int i;
gfx_ctx_flags_t flags;
flags.flags = 0;
video_context_driver_get_flags(&flags);
/* No context driver advertises GFX_CTX_FLAGS_SHADERS_CG for the
* "glcore" video driver -- audited x_ctx.c, wgl_ctx.c, drm_ctx.c,
* wayland_ctx.c, etc. Cg requires the legacy fixed-function / ARB
* asm pipeline that Core Profile contexts don't expose, so no
* RARCH_SHADER_CG path is wired up here. GLSL and slang are both
* Core Profile-compatible and remain valid; in practice context
* drivers advertise SLANG for glcore but the GLSL fallback is kept
* for builds without slang/spirv-cross. */
if (type != RARCH_SHADER_GLSL && type != RARCH_SHADER_SLANG)
{
type = GL3_DEFAULT_SHADER_TYPE;
if (type != RARCH_SHADER_GLSL && type != RARCH_SHADER_SLANG)
type = RARCH_SHADER_SLANG;
}
for (i = 0; i < 2; i++)
{
switch (type)
{
case RARCH_SHADER_GLSL:
#ifdef HAVE_GLSL
if (BIT32_GET(flags.flags, GFX_CTX_FLAGS_SHADERS_GLSL))
return type;
#endif
type = RARCH_SHADER_SLANG;
break;
case RARCH_SHADER_SLANG:
#ifdef HAVE_SLANG
if (BIT32_GET(flags.flags, GFX_CTX_FLAGS_SHADERS_SLANG))
return type;
#endif
type = RARCH_SHADER_GLSL;
break;
default:
return RARCH_SHADER_NONE;
}
}
#endif
return RARCH_SHADER_NONE;
}
#ifdef HAVE_SLANG
static bool gl3_init_default_filter_chain(gl3_t *gl)
{
if (!gl->ctx_driver)
return false;
if (gl->filter_chain_default)
return true;
gl->filter_chain_default = gl3_filter_chain_create_default(
gl->video_info.smooth
? GLSLANG_FILTER_CHAIN_LINEAR
: GLSLANG_FILTER_CHAIN_NEAREST);
if (!gl->filter_chain_default)
{
RARCH_ERR("[GLCore] Failed to create default filter chain.\n");
return false;
}
return true;
}
static bool gl3_init_filter_chain_preset(gl3_t *gl, const char *shader_path)
{
if (!gl->ctx_driver)
return false;
gl->filter_chain = gl3_filter_chain_create_from_preset(
shader_path,
gl->video_info.smooth
? GLSLANG_FILTER_CHAIN_LINEAR
: GLSLANG_FILTER_CHAIN_NEAREST);
if (!gl->filter_chain)
{
RARCH_ERR("[GLCore] Failed to create preset: \"%s\".\n", shader_path);
return false;
}
return true;
}
#endif /* HAVE_SLANG */
static const shader_backend_t *gl3_shader_driver_set_backend(
enum rarch_shader_type type)
{
enum rarch_shader_type fallback = gl3_get_fallback_shader_type(type);
if (fallback != type)
RARCH_ERR("[GLCore] Shader backend %d not supported, falling back to %d.\n", type, fallback);
switch (fallback)
{
#ifdef HAVE_GLSL
case RARCH_SHADER_GLSL:
RARCH_LOG("[GLCore] Using GLSL shader backend.\n");
return &gl_glsl_backend;
#endif
default:
RARCH_LOG("[GLCore] No supported shader backend.\n");
return NULL;
}
}
static bool gl3_shader_driver_init(gl3_video_shader_ctx_init_t *init)
{
void *tmp = NULL;
settings_t *settings = config_get_ptr();
if (!init->shader || !init->shader->init)
{
init->shader = gl3_shader_driver_set_backend(init->shader_type);
if (!init->shader)
return false;
}
tmp = init->shader->init(init->data, init->path);
if (!tmp)
return false;
if (string_is_equal(settings->arrays.menu_driver, "xmb")
&& init->shader->init_menu_shaders)
{
RARCH_LOG("[GLCore] Setting up menu pipeline shaders for XMB...\n");
init->shader->init_menu_shaders(tmp);
}
init->shader_data = tmp;
return true;
}
static void gl3_renderchain_recompute_pass_sizes(
gl3_t *gl,
unsigned width, unsigned height,
unsigned vp_width, unsigned vp_height)
{
size_t i;
bool size_modified = false;
GLint max_size = 0;
unsigned last_dims = VIDEO_SCALE_PACK(width, height);
unsigned last_max_dims = VIDEO_SCALE_PACK(
RARCH_SCALE_BASE * gl->video_info.input_scale,
RARCH_SCALE_BASE * gl->video_info.input_scale);
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_size);
/* Calculate viewports for FBOs. */
for (i = 0; i < gl->chain.num_fbo_passes; i++)
{
struct video_fbo_rect *fbo_rect = &gl->chain.fbo_rect[i];
struct gfx_fbo_scale *fbo_scale = &gl->chain.fbo_scale[i];
/* The axes scale apart from each other -- a pass can be absolute
* on one and relative on the other -- so each is found on its own
* here and the two are joined only when they are stored, which is
* what stops a pass ever holding half an update. */
unsigned img_w = VIDEO_SCALE_W(fbo_rect->img_dims);
unsigned img_h = VIDEO_SCALE_H(fbo_rect->img_dims);
unsigned max_img_w =
VIDEO_SCALE_W(fbo_rect->max_img_dims);
unsigned max_img_h =
VIDEO_SCALE_H(fbo_rect->max_img_dims);
switch (fbo_scale->type_x)
{
case RARCH_SCALE_INPUT:
img_w = fbo_scale->scale_x * VIDEO_SCALE_W(last_dims);
max_img_w = VIDEO_SCALE_W(last_max_dims) * fbo_scale->scale_x;
break;
case RARCH_SCALE_ABSOLUTE:
img_w = max_img_w = fbo_scale->abs_x;
break;
case RARCH_SCALE_VIEWPORT:
if (gl->rotation % 180 == 90)
img_w = max_img_w = fbo_scale->scale_x * vp_height;
else
img_w = max_img_w = fbo_scale->scale_x * vp_width;
break;
}
switch (fbo_scale->type_y)
{
case RARCH_SCALE_INPUT:
img_h = VIDEO_SCALE_H(last_dims) * fbo_scale->scale_y;
max_img_h = VIDEO_SCALE_H(last_max_dims) * fbo_scale->scale_y;
break;
case RARCH_SCALE_ABSOLUTE:
img_h = max_img_h = fbo_scale->abs_y;
break;
case RARCH_SCALE_VIEWPORT:
if (gl->rotation % 180 == 90)
img_h = max_img_h = fbo_scale->scale_y * vp_width;
else
img_h = max_img_h = fbo_scale->scale_y * vp_height;
break;
}
if (img_w > (unsigned)max_size)
{
size_modified = true;
img_w = max_size;
}
if (img_h > (unsigned)max_size)
{
size_modified = true;
img_h = max_size;
}
if (max_img_w > (unsigned)max_size)
{
size_modified = true;
max_img_w = max_size;
}
if (max_img_h > (unsigned)max_size)
{
size_modified = true;
max_img_h = max_size;
}
if (size_modified)
RARCH_WARN("[GLCore] FBO textures exceeded maximum size of GPU (%dx%d). Resizing to fit.\n", max_size, max_size);
fbo_rect->img_dims = VIDEO_SCALE_PACK(img_w, img_h);
fbo_rect->max_img_dims = VIDEO_SCALE_PACK(max_img_w, max_img_h);
last_dims = fbo_rect->img_dims;
last_max_dims = fbo_rect->max_img_dims;
}
}
static void gl3_create_fbo_texture(gl3_t *gl,
unsigned pass, GLuint texture)
{
GLint mag_filter;
GLint min_filter;
bool smooth;
bool fp_fbo = false;
bool srgb_fbo = false;
unsigned tex_w = VIDEO_SCALE_W(gl->chain.fbo_rect[pass].dims);
unsigned tex_h = VIDEO_SCALE_H(gl->chain.fbo_rect[pass].dims);
if (!gl->chain.shader->filter_type(gl->chain.shader_data, pass + 2, &smooth))
smooth = gl->video_info.smooth;
mag_filter = smooth ? GL_LINEAR : GL_NEAREST;
min_filter = gl->chain.shader->mipmap_input(gl->chain.shader_data, pass + 2)
? (smooth ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST)
: mag_filter;
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
fp_fbo = gl->chain.fbo_scale[pass].flags & FBO_SCALE_FLAG_FP_FBO;
if (fp_fbo && !gl->chain.fp_fbo_supported)
{
RARCH_ERR("[GLCore] Floating-point FBO was requested, but is not supported. Falling back to UNORM. Result may band/clip/etc.!\n");
fp_fbo = false;
}
if (fp_fbo)
{
RARCH_LOG("[GLCore] FBO pass #%d is floating-point.\n", pass);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, tex_w, tex_h, 0,
GL_RGBA, GL_FLOAT, NULL);
}
else
{
srgb_fbo = gl->chain.fbo_scale[pass].flags & FBO_SCALE_FLAG_SRGB_FBO;
if (srgb_fbo && !gl->chain.srgb_fbo_supported)
{
RARCH_ERR("[GLCore] sRGB FBO was requested, but is not supported. Falling back to UNORM. Result may band/clip/etc.!\n");
srgb_fbo = false;
}
if (srgb_fbo && !gl->chain.force_srgb_disable)
{
RARCH_LOG("[GLCore] FBO pass #%d is sRGB.\n", pass);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, tex_w, tex_h, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_w, tex_h, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
static bool gl3_create_fbo_targets(gl3_t *gl)
{
unsigned i;
struct gfx_fbo_scale scale, scale_last;
gl->chain.active = true;
gl->chain.mipmap_active = gl->chain.shader->mipmap_input(gl->chain.shader_data, 1);
gl->chain.fp_fbo_supported = gl_check_capability(GL_CAPS_FP_FBO);
gl->chain.srgb_fbo_supported = gl_check_capability(GL_CAPS_SRGB_FBO);
gl->chain.force_srgb_disable = config_get_ptr()->bools.video_force_srgb_disable;
gl->chain.fbo_feedback = 0;
gl->chain.fbo_feedback_pass = 0;
gl->chain.fbo_feedback_texture = 0;
gl->chain.num_fbo_passes = gl->chain.shader->num_shaders(gl->chain.shader_data);
gl->chain.num_prev_textures = gl->chain.shader->get_prev_textures(gl->chain.shader_data);
if (gl->chain.num_prev_textures < 1)
gl->chain.num_prev_textures = 1;
memcpy(gl->chain.tex_info.coord, gl3_vertexes, sizeof(gl->chain.tex_info.coord));
gl->chain.coords.vertex = gl->chain.vertex_ptr;
gl->chain.coords.tex_coord = gl->chain.tex_info.coord;
gl->chain.coords.color = gl3_colors;
gl->chain.coords.lut_tex_coord = gl3_vertexes;
gl->chain.coords.vertices = 4;
for (i = 0; i < gl->chain.num_prev_textures; i++)
{
gl->chain.prev_info[i].tex = gl->textures[0].tex;
gl->chain.prev_info[i].input_size[0] = RARCH_SCALE_BASE * gl->video_info.input_scale;
gl->chain.prev_info[i].input_size[1] = RARCH_SCALE_BASE * gl->video_info.input_scale;
gl->chain.prev_info[i].tex_size[0] = RARCH_SCALE_BASE * gl->video_info.input_scale;
gl->chain.prev_info[i].tex_size[1] = RARCH_SCALE_BASE * gl->video_info.input_scale;
memcpy(gl->chain.prev_info[i].coord, gl3_vertexes, sizeof(gl->chain.prev_info[i].coord));
}
if (gl->flags & GL3_FLAG_HW_RENDER_ENABLE)
{
bool force_smooth;
bool smooth = gl->chain.shader->filter_type(gl->chain.shader_data, 1, &force_smooth)
? force_smooth
: gl->video_info.smooth;
GLint mag_filter = smooth ? GL_LINEAR : GL_NEAREST;
GLint min_filter = gl->chain.mipmap_active
? (smooth ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST)
: mag_filter;
glBindTexture(GL_TEXTURE_2D, gl->hw_render_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
}
if (gl->chain.num_fbo_passes == 0)
return true;
scale_last.flags = 0;
gl->chain.shader->shader_scale(gl->chain.shader_data, gl->chain.num_fbo_passes, &scale_last);
if (!(scale_last.flags & FBO_SCALE_FLAG_VALID) && --gl->chain.num_fbo_passes == 0)
return true;
scale.flags = 0;
gl->chain.shader->shader_scale(gl->chain.shader_data, 1, &scale);
if (!(scale.flags & FBO_SCALE_FLAG_VALID))
{
scale.scale_x = 1.0f;
scale.scale_y = 1.0f;
scale.type_x = RARCH_SCALE_INPUT;
scale.type_y = RARCH_SCALE_INPUT;
scale.flags |= FBO_SCALE_FLAG_VALID;
}
gl->chain.fbo_scale[0] = scale;
for (i = 1; i < gl->chain.num_fbo_passes; i++)
{
gl->chain.shader->shader_scale(gl->chain.shader_data, i + 1, &gl->chain.fbo_scale[i]);
if (!(gl->chain.fbo_scale[i].flags & FBO_SCALE_FLAG_VALID))
{
gl->chain.fbo_scale[i].scale_x = gl->chain.fbo_scale[i].scale_y = 1.0f;
gl->chain.fbo_scale[i].type_x = gl->chain.fbo_scale[i].type_y =
RARCH_SCALE_INPUT;
gl->chain.fbo_scale[i].flags |= FBO_SCALE_FLAG_VALID;
}
}
gl3_renderchain_recompute_pass_sizes(
gl,
RARCH_SCALE_BASE * gl->video_info.input_scale,
RARCH_SCALE_BASE * gl->video_info.input_scale,
VIDEO_SCALE_W(gl->video_dims),
VIDEO_SCALE_H(gl->video_dims));
glGenFramebuffers(gl->chain.num_fbo_passes, gl->chain.fbo);
glGenTextures(gl->chain.num_fbo_passes, gl->chain.fbo_texture);
for (i = 0; i < gl->chain.num_fbo_passes; i++)
{
unsigned img_dims = gl->chain.fbo_rect[i].img_dims;
gl->chain.fbo_rect[i].dims = VIDEO_SCALE_PACK(
next_pow2(VIDEO_SCALE_W(img_dims)),
next_pow2(VIDEO_SCALE_H(img_dims)));
RARCH_LOG("[GLCore] Creating FBO %d @ %ux%u.\n", i,
VIDEO_SCALE_W(gl->chain.fbo_rect[i].dims),
VIDEO_SCALE_H(gl->chain.fbo_rect[i].dims));
if (gl->chain.fbo[i] == 0 || gl->chain.fbo_texture[i] == 0)
goto error;
gl3_create_fbo_texture(gl, i, gl->chain.fbo_texture[i]);
glBindFramebuffer(GL_FRAMEBUFFER, gl->chain.fbo[i]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->chain.fbo_texture[i], 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
goto error;
}
if (gl->chain.shader->get_feedback_pass(gl->chain.shader_data, &gl->chain.fbo_feedback_pass))
{
if (gl->chain.fbo_feedback_pass < gl->chain.num_fbo_passes)
{
unsigned fb_dims =
gl->chain.fbo_rect[gl->chain.fbo_feedback_pass].dims;
RARCH_LOG("[GLCore] Creating feedback FBO %d @ %ux%u.\n", i,
VIDEO_SCALE_W(fb_dims), VIDEO_SCALE_H(fb_dims));
glGenFramebuffers(1, &gl->chain.fbo_feedback);
if (!gl->chain.fbo_feedback)
goto error;
glGenTextures(1, &gl->chain.fbo_feedback_texture);
if (!gl->chain.fbo_feedback_texture)
goto error;
gl3_create_fbo_texture(gl, gl->chain.fbo_feedback_pass, gl->chain.fbo_feedback_texture);
glBindFramebuffer(GL_FRAMEBUFFER, gl->chain.fbo_feedback);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->chain.fbo_feedback_texture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
goto error;
}
else
RARCH_WARN("[GLCore] Tried to create feedback FBO of pass #%u, but there are only %d FBO passes. Will use input texture as feedback texture.\n",
gl->chain.fbo_feedback_pass, gl->chain.num_fbo_passes);
}
return true;
error:
RARCH_ERR("[GLCore] Failed to set up frame buffer objects. Multi-pass shading will not work.\n");
if (gl->chain.fbo_feedback_texture)
{
glDeleteTextures(1, &gl->chain.fbo_feedback_texture);
gl->chain.fbo_feedback_texture = 0;
}
if (gl->chain.fbo_feedback)
{
glDeleteFramebuffers(1, &gl->chain.fbo_feedback);
gl->chain.fbo_feedback = 0;
}
glDeleteTextures(gl->chain.num_fbo_passes, gl->chain.fbo_texture);
glDeleteFramebuffers(gl->chain.num_fbo_passes, gl->chain.fbo);
gl->chain.num_fbo_passes = 0;
return false;
}
static bool gl3_init_filter_chain_with_path(gl3_t *gl, const char *shader_path)
{
enum rarch_shader_type parse_type = video_shader_parse_type(shader_path);
enum rarch_shader_type type = gl3_get_fallback_shader_type(parse_type);
gl->filter_chain = NULL;
gl->chain.shader = NULL;
gl->chain.shader_data = NULL;
gl->chain.num_fbo_passes = 0;
gl->chain.num_prev_textures = 0;
#ifdef HAVE_SLANG
gl->chain.active = false;
#else
gl->chain.active = true;
#endif
gl->chain.mipmap_active = false;
gl->chain.fbo_feedback_texture = 0;
if (type == RARCH_SHADER_NONE)
{
RARCH_ERR("[GLCore] Couldn't find any supported shader backend! Continuing without shaders.\n");
return true;
}
if (type != parse_type)
{
if (shader_path && *shader_path)
RARCH_WARN("[GLCore] Shader preset %s is using unsupported shader type %s, falling back to stock %s.\n",
shader_path, video_shader_type_to_str(parse_type), video_shader_type_to_str(type));
shader_path = NULL;
}
#ifdef HAVE_GLSL
if (type == RARCH_SHADER_GLSL)
gl_glsl_set_context_type(
#ifdef HAVE_OPENGLES
false,
#else
true,
#endif
gl->version_major, gl->version_minor);
#endif
#ifdef HAVE_SLANG
if (type == RARCH_SHADER_SLANG)
{
RARCH_LOG("[GLCore] Using Slang shader backend.\n");
gl->chain.active = false;
if (!gl3_init_pipelines(gl))
{
RARCH_ERR("[GLCore] Failed to cross-compile menu pipelines.\n");
return false;
}
if (!shader_path || !*shader_path)
{
RARCH_LOG("[GLCore] Loading stock shader.\n");
return gl3_init_default_filter_chain(gl);
}
else if (!gl3_init_filter_chain_preset(gl, shader_path))
return gl3_init_default_filter_chain(gl);
else
return true;
}
else
#endif
{
gl3_video_shader_ctx_init_t init_data;
bool ret = false;
gl->chain.active = true;
init_data.shader_type = type;
init_data.shader = NULL;
init_data.shader_data = NULL;
init_data.data = gl;
init_data.path = shader_path;
if (gl3_shader_driver_init(&init_data))
{
gl->chain.shader = init_data.shader;
gl->chain.shader_data = init_data.shader_data;
return gl3_create_fbo_targets(gl);
}
RARCH_ERR("[GLCore] Failed to initialize shader, falling back to stock.\n");
init_data.shader = NULL;
init_data.shader_data = NULL;
init_data.path = NULL;
ret = gl3_shader_driver_init(&init_data);
gl->chain.shader = init_data.shader;
gl->chain.shader_data = init_data.shader_data;
return ret && gl3_create_fbo_targets(gl);
}
}
static bool gl3_init_filter_chain(gl3_t *gl)
{
return gl3_init_filter_chain_with_path(gl, video_shader_get_current_shader_preset());
}
#ifdef GL_DEBUG
#ifdef HAVE_OPENGLES3
#define DEBUG_CALLBACK_TYPE GL_APIENTRY
#define GL_DEBUG_SOURCE_API GL_DEBUG_SOURCE_API_KHR
#define GL_DEBUG_SOURCE_WINDOW_SYSTEM GL_DEBUG_SOURCE_WINDOW_SYSTEM_KHR
#define GL_DEBUG_SOURCE_SHADER_COMPILER GL_DEBUG_SOURCE_SHADER_COMPILER_KHR
#define GL_DEBUG_SOURCE_THIRD_PARTY GL_DEBUG_SOURCE_THIRD_PARTY_KHR
#define GL_DEBUG_SOURCE_APPLICATION GL_DEBUG_SOURCE_APPLICATION_KHR
#define GL_DEBUG_SOURCE_OTHER GL_DEBUG_SOURCE_OTHER_KHR
#define GL_DEBUG_TYPE_ERROR GL_DEBUG_TYPE_ERROR_KHR
#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR
#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR
#define GL_DEBUG_TYPE_PORTABILITY GL_DEBUG_TYPE_PORTABILITY_KHR
#define GL_DEBUG_TYPE_PERFORMANCE GL_DEBUG_TYPE_PERFORMANCE_KHR
#define GL_DEBUG_TYPE_MARKER GL_DEBUG_TYPE_MARKER_KHR
#define GL_DEBUG_TYPE_PUSH_GROUP GL_DEBUG_TYPE_PUSH_GROUP_KHR
#define GL_DEBUG_TYPE_POP_GROUP GL_DEBUG_TYPE_POP_GROUP_KHR
#define GL_DEBUG_TYPE_OTHER GL_DEBUG_TYPE_OTHER_KHR
#define GL_DEBUG_SEVERITY_HIGH GL_DEBUG_SEVERITY_HIGH_KHR
#define GL_DEBUG_SEVERITY_MEDIUM GL_DEBUG_SEVERITY_MEDIUM_KHR
#define GL_DEBUG_SEVERITY_LOW GL_DEBUG_SEVERITY_LOW_KHR
#else
#define DEBUG_CALLBACK_TYPE APIENTRY
#endif
static void DEBUG_CALLBACK_TYPE gl3_debug_cb(GLenum source, GLenum type,
GLuint id, GLenum severity, GLsizei length,
const GLchar *message, void *userParam)
{
const char *src = NULL;
const char *typestr = NULL;
gl3_t *gl = (gl3_t*)userParam; /* Useful for debugger. */
(void)gl;
(void)id;
(void)length;
switch (source)
{
case GL_DEBUG_SOURCE_API:
src = "API";
break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
src = "Window system";
break;
case GL_DEBUG_SOURCE_SHADER_COMPILER:
src = "Shader compiler";
break;
case GL_DEBUG_SOURCE_THIRD_PARTY:
src = "3rd party";
break;
case GL_DEBUG_SOURCE_APPLICATION:
src = "Application";
break;
case GL_DEBUG_SOURCE_OTHER:
src = "Other";
break;
default:
src = "Unknown";
break;
}
switch (type)
{
case GL_DEBUG_TYPE_ERROR:
typestr = "Error";
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
typestr = "Deprecated behavior";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
typestr = "Undefined behavior";
break;
case GL_DEBUG_TYPE_PORTABILITY:
typestr = "Portability";
break;
case GL_DEBUG_TYPE_PERFORMANCE:
typestr = "Performance";
break;
case GL_DEBUG_TYPE_MARKER:
typestr = "Marker";
break;
case GL_DEBUG_TYPE_PUSH_GROUP:
typestr = "Push group";
break;
case GL_DEBUG_TYPE_POP_GROUP:
typestr = "Pop group";
break;
case GL_DEBUG_TYPE_OTHER:
typestr = "Other";
break;
default:
typestr = "Unknown";
break;
}
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH:
RARCH_ERR("[GL debug (High, %s, %s)] %s\n", src, typestr, message);
break;
case GL_DEBUG_SEVERITY_MEDIUM:
RARCH_WARN("[GL debug (Medium, %s, %s)] %s\n", src, typestr, message);
break;
case GL_DEBUG_SEVERITY_LOW:
RARCH_LOG("[GL debug (Low, %s, %s)] %s\n", src, typestr, message);
break;
}
}
static void gl3_begin_debug(gl3_t *gl)
{
if (gl_check_capability(GL_CAPS_DEBUG))
{
#ifdef HAVE_OPENGLES3
glDebugMessageCallbackKHR(gl3_debug_cb, gl);
glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
glEnable(GL_DEBUG_OUTPUT_KHR);
#else
glDebugMessageCallback(gl3_debug_cb, gl);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glEnable(GL_DEBUG_OUTPUT);
#endif
}
else
RARCH_ERR("[GLCore] Neither GL_KHR_debug nor GL_ARB_debug_output are implemented. Cannot start GL debugging.\n");
}
#endif
static void gl3_set_viewport_wrapper(void *data,
unsigned dims,
bool force_full, bool allow_rotate)
{
gl3_t *gl = (gl3_t*)data;
gl3_set_viewport(gl, dims,
force_full, allow_rotate);
}
static void *gl3_init(const video_info_t *video)
{
unsigned full_x, full_y;
settings_t *settings = config_get_ptr();
bool video_gpu_record = settings->bools.video_gpu_record;
bool force_fullscreen = false;
int interval = 0;
unsigned mode_dims = 0;
unsigned win_dims = 0;
unsigned temp_dims = 0;
const char *vendor = NULL;
const char *renderer = NULL;
const char *version = NULL;
char *err_string = NULL;
gl3_t *gl = (gl3_t*)calloc(1, sizeof(gl3_t));
const gfx_ctx_driver_t *ctx_driver = gl3_get_context(gl);
struct retro_hw_render_callback *hwr = video_driver_get_hw_context();
if (!gl || !ctx_driver)
goto error;
/* Latched here, inside the wrapper's blocking CMD_INIT (the main
* thread is parked in send-and-wait, so the settings read is
* race-free), for every later gl3_core_context_is_mains() -
* including the frame path, where main runs free. */
#ifdef HAVE_THREADS
if (video_driver_thread_wrapper_active() && video_thread_hw_allowed())
gl->flags |= GL3_FLAG_HW_RING_EXPECTED;
#endif
/* Seed the raw rotation latch inside the same blocking window;
* set_rotation keeps it current from here on. */
gl->rotation_raw = retroarch_get_rotation();
gl3_spirv_refresh_direct_toggle();
video_context_driver_set(ctx_driver);
gl->ctx_driver = ctx_driver;
gl->video_info = *video;
RARCH_LOG("[GLCore] Found GL context: \"%s\".\n", ctx_driver->ident);
if (gl->ctx_driver->get_video_size)
gl->ctx_driver->get_video_size(gl->ctx_data,
&mode_dims);
if (!video->fullscreen && !gl->ctx_driver->has_windowed)
{
RARCH_DBG("[GLCore] Config requires windowed mode, but context driver does not support it. "
"Forcing fullscreen for this session.\n");
force_fullscreen = true;
}
full_x = VIDEO_SCALE_W(mode_dims);
full_y = VIDEO_SCALE_H(mode_dims);
mode_dims = 0;
interval = 0;
RARCH_LOG("[GLCore] Detecting screen resolution: %ux%u.\n", full_x, full_y);
if (video->vsync)
interval = video->swap_interval;
if (gl->ctx_driver->swap_interval)
{
bool adaptive_vsync_enabled = video_driver_test_all_flags(
GFX_CTX_FLAGS_ADAPTIVE_VSYNC) && video->adaptive_vsync;
if (adaptive_vsync_enabled && interval == 1)
interval = -1;
gl->ctx_driver->swap_interval(gl->ctx_data, interval);
}
gl->present_vsynced = (interval != 0);
win_dims = video->dims;
/* Neither axis set is the whole word clear */
if (video->fullscreen && (win_dims == 0))
win_dims = VIDEO_SCALE_PACK(full_x, full_y);
/* If fullscreen had to be forced, VIDEO_SCALE_W(video->dims)/height is incorrect */
else if (force_fullscreen)
win_dims = VIDEO_SCALE_PACK(settings->uints.video_fullscreen_x,
settings->uints.video_fullscreen_y);
if ( !gl->ctx_driver->set_video_mode
|| !gl->ctx_driver->set_video_mode(gl->ctx_data, win_dims,
(video->fullscreen || force_fullscreen)))
goto error;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
rglgen_resolve_symbols(ctx_driver->get_proc_address);
if (hwr && hwr->context_type != RETRO_HW_CONTEXT_NONE)
gl3_init_hw_render(gl, RARCH_SCALE_BASE * video->input_scale, RARCH_SCALE_BASE * video->input_scale);
#ifdef GL_DEBUG
gl3_begin_debug(gl);
if (gl->flags & GL3_FLAG_HW_RENDER_ENABLE)
{
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
gl3_begin_debug(gl);
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
}
#endif
/* Clear out potential error flags in case we use cached context. */
glGetError();
vendor = (const char*)glGetString(GL_VENDOR);
renderer = (const char*)glGetString(GL_RENDERER);
version = (const char*)glGetString(GL_VERSION);
/* The scRGB / HDR10 encode needs the slang pipelines */
#ifdef HAVE_SLANG
{
gfx_ctx_flags_t ctx_flags;
ctx_flags.flags = 0;
video_context_driver_get_flags(&ctx_flags);
if (BIT32_GET(ctx_flags.flags, GFX_CTX_FLAGS_HDR10_FRAMEBUFFER))
{
/* The same offscreen and encode as scRGB, finished in PQ */
gl->scrgb.active = true;
gl->scrgb.pq_out = true;
RARCH_LOG("[GLCore] HDR10 backbuffer active; the frame will be encoded to Rec.2020 PQ.\n");
}
else if (BIT32_GET(ctx_flags.flags, GFX_CTX_FLAGS_SCRGB_FRAMEBUFFER))
{
gl->scrgb.active = true;
RARCH_LOG("[GLCore] scRGB backbuffer active; SDR content will be encoded for HDR output.\n");
}
}
#endif
/* Whether the source is PQ decides every later composition choice, it
* arrives only through video_info, and getting it wrong is silent --
* the image is merely graded oddly, with no error anywhere. State it
* once at init, as the Vulkan driver does. */
RARCH_LOG("[GLCore] Source is %s (%s), output %s.\n",
video->source_hdr10 ? "HDR10 PQ Rec.2020"
: (video->source_10bit ? "10-bit SDR" : "SDR"),
video->rgb32 ? "32-bit" : "16-bit",
gl->scrgb.active ? "scRGB" : "SDR");
RARCH_LOG("[GLCore] Vendor: %s, Renderer: %s.\n", vendor, renderer);
RARCH_LOG("[GLCore] Version: %s.\n", version);
if (string_is_equal(ctx_driver->ident, "null"))
goto error;
if (version && *version)
gl3_parse_version(version, &gl->version_major, &gl->version_minor);
video_driver_set_gpu_api_version_string(version);
#ifdef _WIN32
if ( string_is_equal(vendor, "Microsoft Corporation"))
if (string_is_equal(renderer, "GDI Generic"))
#ifdef HAVE_OPENGL1
video_driver_force_fallback("gl1");
#else
video_driver_force_fallback("gdi");
#endif
#endif
if (video->vsync)
gl->flags |= GL3_FLAG_VSYNC;
if (video->fullscreen || force_fullscreen)
gl->flags |= GL3_FLAG_FULLSCREEN;
if (video->force_aspect)
gl->flags |= GL3_FLAG_KEEP_ASPECT;
mode_dims = 0;
if (gl->ctx_driver->get_video_size)
gl->ctx_driver->get_video_size(gl->ctx_data,
&mode_dims);
temp_dims = mode_dims;
/* Get real known video size, which might have been altered by context. */
/* One axis alone is not a size, so both have to be set */
if (VIDEO_SCALE_W(temp_dims) != 0 && VIDEO_SCALE_H(temp_dims) != 0)
video_driver_set_output_dims(temp_dims);
else
temp_dims = video_driver_get_output_dims();
gl->video_dims = temp_dims;
RARCH_LOG("[GLCore] Using resolution %ux%u.\n",
VIDEO_SCALE_W(temp_dims), VIDEO_SCALE_H(temp_dims));
/* Set the viewport to fix recording, since it needs to know
* the viewport sizes before we start running. */
gl3_set_viewport_wrapper(gl, temp_dims, false, true);
if (gl->ctx_driver->input_driver)
{
const char *joypad_name = settings->arrays.input_joypad_driver;
gl->ctx_driver->input_driver(
gl->ctx_data, joypad_name);
}
gl->chain.vertex_ptr = hwr && hwr->bottom_left_origin ? gl3_vertexes : gl3_vertexes_flipped;
if (!gl3_init_filter_chain(gl))
{
RARCH_ERR("[GLCore] Failed to init filter chain.\n");
goto error;
}
if (video_gpu_record
&& recording_state_get_ptr()->enable)
{
gl->flags |= GL3_FLAG_PBO_READBACK_ENABLE;
if (gl3_init_pbo_readback(gl))
{
RARCH_LOG("[GLCore] Async PBO readback enabled.\n");
}
}
else
gl->flags &= ~GL3_FLAG_PBO_READBACK_ENABLE;
if (!gl_check_error(&err_string))
{
RARCH_ERR("[GLCore] %s\n", err_string);
free(err_string);
goto error;
}
glGenVertexArrays(1, &gl->vao);
glBindVertexArray(gl->vao);
glBindVertexArray(0);
{
static const uint32_t white = 0xffffffff;
glGenTextures(1, &gl->white_texture);
glBindTexture(GL_TEXTURE_2D, gl->white_texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA,
GL_UNSIGNED_BYTE, &white);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
}
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
return gl;
error:
video_context_driver_free();
gl3_destroy_resources(gl);
if (gl)
free(gl);
return NULL;
}
static unsigned gl3_num_miplevels(unsigned width, unsigned height)
{
unsigned levels = 1;
if (width < height)
width = height;
while (width > 1)
{
levels++;
width >>= 1;
}
return levels;
}
static void video_texture_load_gl3(
const struct texture_image *ti,
enum texture_filter_type filter_type,
GLuint *idptr)
{
/* Generate the OpenGL texture object */
GLuint id;
unsigned levels;
GLenum mag_filter, min_filter;
glGenTextures(1, &id);
*idptr = id;
glBindTexture(GL_TEXTURE_2D, id);
levels = 1;
if (filter_type == TEXTURE_FILTER_MIPMAP_LINEAR || filter_type == TEXTURE_FILTER_MIPMAP_NEAREST)
levels = gl3_num_miplevels(ti->width, ti->height);
/* A pix10 image is XRGB2101010, the core frames' layout: RGB10_A2
* storage, and the R<->B swizzle below serves both, since
* GL_RGBA/UNSIGNED_INT_2_10_10_10_REV reads R from bits 9:0. */
glTexStorage2D(GL_TEXTURE_2D, levels,
ti->fp16 ? GL_RGBA16F
: ti->pix10 ? GL_RGB10_A2 : GL_RGBA8, ti->width, ti->height);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
switch (filter_type)
{
case TEXTURE_FILTER_LINEAR:
mag_filter = GL_LINEAR;
min_filter = GL_LINEAR;
break;
case TEXTURE_FILTER_NEAREST:
mag_filter = GL_NEAREST;
min_filter = GL_NEAREST;
break;
case TEXTURE_FILTER_MIPMAP_NEAREST:
mag_filter = GL_LINEAR;
min_filter = GL_LINEAR_MIPMAP_NEAREST;
break;
case TEXTURE_FILTER_MIPMAP_LINEAR:
default:
mag_filter = GL_LINEAR;
min_filter = GL_LINEAR_MIPMAP_LINEAR;
break;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
ti->width, ti->height, GL_RGBA,
ti->fp16 ? GL_HALF_FLOAT
: ti->pix10 ? GL_UNSIGNED_INT_2_10_10_10_REV
: GL_UNSIGNED_BYTE,
ti->pixels);
if (levels > 1)
glGenerateMipmap(GL_TEXTURE_2D);
/* Half floats are stored R,G,B,A as sampled; the rest are BGRA words
* uploaded as RGBA, which the swizzle puts back. */
if (!ti->fp16)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
else
gl3_fp16_remember(id);
glBindTexture(GL_TEXTURE_2D, 0);
}
#ifdef HAVE_OVERLAY
/* The texture names and the vertex, texture and colour coordinate
* arrays of a page's images come out of one zeroed block, each region
* starting on a 64-byte boundary; overlay_tex owns it. Geometry starts
* as the whole screen, colour as opaque white. Names come from
* @textures when the page shows the pack's textures, else are made
* by the upload that follows. */
static bool gl3_overlay_alloc(gl3_t *gl, unsigned num_images,
const uintptr_t *textures)
{
size_t o_vertex, o_tex, o_color;
unsigned i, j;
gl3_free_overlay(gl);
o_vertex = ((num_images * sizeof(GLuint)) + 63) & ~(size_t)63;
o_tex = o_vertex + ((2 * 4 * num_images * sizeof(GLfloat) + 63) & ~(size_t)63);
o_color = o_tex + ((2 * 4 * num_images * sizeof(GLfloat) + 63) & ~(size_t)63);
gl->overlay_tex = (GLuint*)
calloc(1, o_color + 4 * 4 * num_images * sizeof(uint16_t));
if (!gl->overlay_tex)
return false;
gl->overlay_vertex_coord = (GLfloat*)((uint8_t*)gl->overlay_tex + o_vertex);
gl->overlay_tex_coord = (GLfloat*)((uint8_t*)gl->overlay_tex + o_tex);
gl->overlay_color_coord = (uint16_t*)((uint8_t*)gl->overlay_tex + o_color);
gl->overlays = num_images;
if (textures)
{
for (i = 0; i < num_images; i++)
gl->overlay_tex[i] = (GLuint)textures[i];
gl->flags |= GL3_FLAG_OVERLAY_BORROWED;
}
for (i = 0; i < num_images; i++)
{
gl3_overlay_tex_geom (gl, i, 0, 0, 1, 1);
gl3_overlay_vertex_geom(gl, i, 0, 0, 1, 1);
for (j = 0; j < 16; j++)
gl->overlay_color_coord[16 * i + j] = 0xFFFF;
}
return true;
}
static bool gl3_overlay_load(void *data,
const void *image_data, unsigned num_images)
{
unsigned i;
GLuint id;
gl3_t *gl = (gl3_t*)data;
const struct texture_image *images =
(const struct texture_image*)image_data;
if (!gl || !gl3_overlay_alloc(gl, num_images, NULL))
return false;
for (i = 0; i < num_images; i++)
{
video_texture_load_gl3(&images[i], TEXTURE_FILTER_LINEAR, &id);
gl->overlay_tex[i] = id;
}
return true;
}
/* A page of the pack's textures: no upload, no GL call but the
* geometry setup. The names are gl3_load_texture's, valid on this
* context. */
static bool gl3_overlay_load_textures(void *data,
const uintptr_t *textures, unsigned num_textures)
{
gl3_t *gl = (gl3_t*)data;
if (!gl)
return false;
return gl3_overlay_alloc(gl, num_textures, textures);
}
static void gl3_overlay_enable(void *data, bool state)
{
gl3_t *gl = (gl3_t*)data;
if (!gl)
return;
if (state)
gl->flags |= GL3_FLAG_OVERLAY_ENABLE;
else
gl->flags &= ~GL3_FLAG_OVERLAY_ENABLE;
if ((gl->flags & GL3_FLAG_FULLSCREEN) && gl->ctx_driver->show_mouse)
gl->ctx_driver->show_mouse(gl->ctx_data, state);
}
static void gl3_overlay_full_screen(void *data, bool enable)
{
gl3_t *gl = (gl3_t*)data;
if (gl)
{
if (enable)
gl->flags |= GL3_FLAG_OVERLAY_FULLSCREEN;
else
gl->flags &= ~GL3_FLAG_OVERLAY_FULLSCREEN;
}
}
static void gl3_overlay_set_alpha(void *data, unsigned image, float mod)
{
uint16_t *color = NULL;
uint16_t a;
gl3_t *gl = (gl3_t*)data;
/* As the geometry setters: no page loaded is a NULL array, and an
* index off the end of the page is the neighbouring block. */
if (!gl || !gl->overlay_color_coord || image >= gl->overlays)
return;
a = (uint16_t)rgba16_unorm(mod);
color = &gl->overlay_color_coord[image * 16];
color[ 0 + 3] = a;
color[ 4 + 3] = a;
color[ 8 + 3] = a;
color[12 + 3] = a;
}
static const video_overlay_interface_t gl3_overlay_interface = {
gl3_overlay_enable,
gl3_overlay_load,
gl3_overlay_load_textures,
gl3_overlay_tex_geom,
gl3_overlay_vertex_geom,
gl3_overlay_full_screen,
gl3_overlay_set_alpha,
};
static void gl3_get_overlay_interface(void *data,
const video_overlay_interface_t **iface)
{
(void)data;
*iface = &gl3_overlay_interface;
}
#endif
static void gl3_free(void *data)
{
gl3_t *gl = (gl3_t*)data;
if (!gl)
return;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
gl3_destroy_resources(gl);
gl3_fp16_forget_all();
if (gl->ctx_driver && gl->ctx_driver->destroy)
gl->ctx_driver->destroy(gl->ctx_data);
video_context_driver_free();
free(gl);
}
static bool gl3_alive(void *data)
{
bool ret = false;
bool quit = false;
bool resize = false;
gl3_t *gl = (gl3_t*)data;
unsigned temp_dims = gl->video_dims;
gl->ctx_driver->check_window(gl->ctx_data,
&quit, &resize, &temp_dims);
#ifdef __WINRT__
if (is_running_on_xbox())
{
/* We can set it to 1920x1080 as xbox uwp windowsize is guaranteed
* to be 1920x1080 and currently there is now way to set ANGLE to
* use a variable resolution swapchain so regardless of the size
* the window is always 1080p */
temp_dims = VIDEO_SCALE_PACK(1920,
1080);
}
#endif
if (quit)
gl->flags |= GL3_FLAG_QUITTING;
else if (resize)
gl->flags |= GL3_FLAG_SHOULD_RESIZE;
ret = !(gl->flags & GL3_FLAG_QUITTING);
if (VIDEO_SCALE_W(temp_dims) != 0 && VIDEO_SCALE_H(temp_dims) != 0)
{
video_driver_set_output_dims(temp_dims);
gl->video_dims = temp_dims;
}
return ret;
}
static void gl3_set_nonblock_state(void *data, bool state,
bool adaptive_vsync_enabled,
unsigned swap_interval)
{
int interval = 0;
gl3_t *gl = (gl3_t*)data;
if (!gl)
return;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
if (!state)
interval = swap_interval;
if (gl->ctx_driver->swap_interval)
{
if (adaptive_vsync_enabled && interval == 1)
interval = -1;
gl->ctx_driver->swap_interval(gl->ctx_data, interval);
}
gl->present_vsynced = (interval != 0);
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
}
static bool gl3_suppress_screensaver(void *data, bool enable)
{
bool enabled = enable;
gl3_t *gl = (gl3_t*)data;
if (gl->ctx_data && gl->ctx_driver->suppress_screensaver)
return gl->ctx_driver->suppress_screensaver(gl->ctx_data, enabled);
return false;
}
#ifdef HAVE_SLANG
/* ---- Deferred (per-frame) shader loading for GL Core ---- */
typedef struct gl3_deferred_state
{
gl3_filter_chain_t *new_chain; /* chain being built */
gl3_filter_chain_t *old_chain; /* previous, kept as fallback */
enum glslang_filter_chain_filter filter;
} gl3_deferred_state_t;
static bool gl3_shader_load_begin(void *data,
shader_load_deferred_t *deferred)
{
gl3_t *gl = (gl3_t *)data;
gl3_deferred_state_t *ds;
if (!gl)
return false;
ds = (gl3_deferred_state_t*)calloc(1, sizeof(*ds));
if (!ds)
return false;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
/* Keep old chain alive — it continues rendering
* while the new one is being compiled. */
ds->old_chain = gl->filter_chain;
ds->filter = gl->video_info.smooth
? GLSLANG_FILTER_CHAIN_LINEAR
: GLSLANG_FILTER_CHAIN_NEAREST;
ds->new_chain = gl3_filter_chain_create_deferred(
deferred->preset_path,
ds->filter,
&deferred->total_passes);
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
if (!ds->new_chain)
{
RARCH_ERR("[GLCore] Deferred: failed to create chain for \"%s\".\n",
deferred->preset_path);
free(ds);
return false;
}
RARCH_LOG("[GLCore] Deferred: prepared %u passes for \"%s\".\n",
deferred->total_passes, deferred->preset_path);
deferred->driver_data = ds;
return true;
}
static bool gl3_shader_load_step(void *data,
shader_load_deferred_t *deferred)
{
gl3_t *gl = (gl3_t *)data;
gl3_deferred_state_t *ds = (gl3_deferred_state_t*)deferred->driver_data;
unsigned pass = deferred->current_pass;
if (!gl || !ds)
{
deferred->state = SHADER_LOAD_FAILED;
return false;
}
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
if (pass < deferred->total_passes)
{
RARCH_LOG("[GLCore] Deferred: compiling pass %u/%u...\n",
pass + 1, deferred->total_passes);
if (!gl3_filter_chain_compile_pass(ds->new_chain, pass, ds->filter))
{
RARCH_ERR("[GLCore] Deferred: failed to compile pass %u.\n", pass);
gl3_filter_chain_free(ds->new_chain);
/* Restore old chain */
gl->filter_chain = ds->old_chain;
deferred->state = SHADER_LOAD_FAILED;
goto cleanup;
}
deferred->current_pass++;
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
return true; /* more work remains */
}
/* Check if this is a cancellation (state was changed externally).
* Don't try to finalize a partially compiled chain — just clean up. */
if (deferred->state != SHADER_LOAD_COMPILING)
{
RARCH_LOG("[GLCore] Deferred: cancelled, cleaning up.\n");
gl3_filter_chain_free(ds->new_chain);
gl->filter_chain = ds->old_chain;
deferred->state = SHADER_LOAD_FAILED;
goto cleanup;
}
/* All passes compiled — finalize the chain */
RARCH_LOG("[GLCore] Deferred: finalizing chain...\n");
if (gl3_filter_chain_finalize(ds->new_chain))
{
/* Tear down old chain */
if (ds->old_chain)
gl3_filter_chain_free(ds->old_chain);
/* Swap in the new chain */
gl->filter_chain = ds->new_chain;
deferred->state = SHADER_LOAD_DONE;
RARCH_LOG("[GLCore] Deferred: shader loaded successfully.\n");
}
else
{
RARCH_ERR("[GLCore] Deferred: finalize failed.\n");
gl3_filter_chain_free(ds->new_chain);
gl->filter_chain = ds->old_chain;
deferred->state = SHADER_LOAD_FAILED;
}
cleanup:
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
free(ds);
deferred->driver_data = NULL;
return false; /* no more work */
}
#endif /* HAVE_SLANG */
static bool gl3_set_shader(void *data,
enum rarch_shader_type type, const char *path)
{
gl3_t *gl = (gl3_t *)data;
if (!gl)
return false;
/* Blocking window: refresh the direct-SPIR-V toggle latch, the
* "next preset load" moment its semantics promise. */
gl3_spirv_refresh_direct_toggle();
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
#ifdef HAVE_SLANG
if (gl->filter_chain)
gl3_filter_chain_free(gl->filter_chain);
#endif
if (gl->chain.shader)
gl->chain.shader->deinit(gl->chain.shader_data);
if (gl->chain.num_fbo_passes)
{
glDeleteFramebuffers(gl->chain.num_fbo_passes, gl->chain.fbo);
glDeleteTextures(gl->chain.num_fbo_passes, gl->chain.fbo_texture);
}
if (gl->chain.fbo_feedback)
{
glDeleteFramebuffers(1, &gl->chain.fbo_feedback);
gl->chain.fbo_feedback = 0;
}
if (gl->chain.fbo_feedback_texture)
{
glDeleteTextures(1, &gl->chain.fbo_feedback_texture);
gl->chain.fbo_feedback_texture = 0;
}
if (!gl3_init_filter_chain_with_path(gl, path))
return false;
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
return true;
}
static void gl3_set_rotation(void *data, unsigned rotation)
{
gl3_t *gl = (gl3_t*)data;
if (!gl)
return;
/* Raw 0-3 value alongside the transformed degrees: the frame
* path feeds it to the filter chain, and reading it here -
* written only inside blocking wrapper commands - replaces a
* per-frame retroarch_get_rotation() from the video thread. */
gl->rotation_raw = rotation;
if (video_driver_is_hw_context() && (gl->flags & GL3_FLAG_HW_RENDER_BOTTOM_LEFT))
gl->rotation = 90 * rotation;
else
gl->rotation = 270 * rotation;
gl3_set_projection(gl, &gl3_default_ortho, true);
}
static void gl3_viewport_info(void *data, struct video_viewport *vp)
{
unsigned top_y, top_dist;
gl3_t *gl = (gl3_t*)data;
unsigned width = VIDEO_SCALE_W(gl->video_dims);
unsigned height = VIDEO_SCALE_H(gl->video_dims);
*vp = gl->vp;
vp->full_dims = VIDEO_SCALE_PACK(width, height);
/* Adjust as GL viewport is bottom-up. */
top_y = VIDEO_POS_Y(vp->pos) + VIDEO_SCALE_H(vp->dims);
top_dist = height - top_y;
VIDEO_POS_PUT_Y(vp->pos, top_dist);
}
/* CPU-side scRGB -> PQ helpers for the native HDR read-back; the math
* is verbatim from the vulkan driver's (unit-tested) helpers, minus
* the half decode -- GL hands us floats directly. */
static float gl3_hdr_pq_encode(float v)
{
const float m1 = 0.1593017578125f, m2 = 78.84375f;
const float c1 = 0.8359375f, c2 = 18.8515625f, c3 = 18.6875f;
float yp;
if (v < 0.0f) v = 0.0f;
else if (v > 1.0f) v = 1.0f;
yp = powf(v, m1);
return powf((c1 + c2 * yp) / (1.0f + c3 * yp), m2);
}
static uint16_t gl3_hdr_scrgb_to_pq16(float scrgb)
{
float nits = scrgb * 80.0f;
float pq;
if (nits < 0.0f) nits = 0.0f;
else if (nits > 10000.0f) nits = 10000.0f;
pq = gl3_hdr_pq_encode(nits / 10000.0f);
if (pq < 0.0f) pq = 0.0f;
else if (pq > 1.0f) pq = 1.0f;
return (uint16_t)(pq * 65535.0f + 0.5f);
}
/* Native (no tone-map) HDR read-back: read the encoded FP16 scRGB
* backbuffer as floats, row by row, and convert to 48-bit PQ-coded
* RGB bottom-up (GL rows already come bottom-up, so the buffer is
* filled sequentially). Metadata matches the other drivers' scRGB
* tagging: PQ transfer, BT.709 primaries, D65, measured cLLI and the
* same 1000 / 0.001 nit mastering defaults. */
static bool gl3_read_viewport_hdr(void *data, uint16_t *buffer,
bool is_idle, struct rpng_hdr_metadata *out_meta)
{
gl3_t *gl = (gl3_t*)data;
int vp_x, vp_y;
unsigned w, h, x;
size_t y;
float *row;
float max_cll = 0.0f;
double sum_fall = 0.0;
/* Reads the backbuffer as FP16 scRGB; a PQ one is not */
if (!gl || !(gl->scrgb.active) || gl->scrgb.pq_out || !buffer)
return false;
if (!is_idle)
video_driver_cached_frame();
vp_x = (VIDEO_POS_X(gl->vp.pos) > 0) ? VIDEO_POS_X(gl->vp.pos) : 0;
vp_y = (VIDEO_POS_Y(gl->vp.pos) > 0) ? VIDEO_POS_Y(gl->vp.pos) : 0;
w = MIN(VIDEO_SCALE_W(gl->vp.dims), VIDEO_SCALE_W(gl->video_dims));
h = MIN(VIDEO_SCALE_H(gl->vp.dims), VIDEO_SCALE_H(gl->video_dims));
if (!w || !h)
return false;
row = (float*)malloc((size_t)w * 4 * sizeof(float));
if (!row)
return false;
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
#ifndef HAVE_OPENGLES
glReadBuffer(GL_BACK);
#endif
for (y = 0; y < h; y++)
{
uint16_t *dst = buffer + y * (size_t)w * 3;
glReadPixels(vp_x, vp_y + (int)y, w, 1, GL_RGBA, GL_FLOAT, row);
for (x = 0; x < w; x++)
{
float r = row[4 * x + 0];
float g = row[4 * x + 1];
float b = row[4 * x + 2];
float lvl;
dst[3 * x + 0] = gl3_hdr_scrgb_to_pq16(r);
dst[3 * x + 1] = gl3_hdr_scrgb_to_pq16(g);
dst[3 * x + 2] = gl3_hdr_scrgb_to_pq16(b);
lvl = r; if (g > lvl) lvl = g; if (b > lvl) lvl = b;
lvl *= 80.0f;
if (lvl < 0.0f) lvl = 0.0f;
else if (lvl > 10000.0f) lvl = 10000.0f;
if (lvl > max_cll) max_cll = lvl;
sum_fall += lvl;
}
}
free(row);
if (out_meta)
{
memset(out_meta, 0, sizeof(*out_meta));
out_meta->colour_primaries = 1; /* BT.709 (scRGB) */
out_meta->transfer_function = 16; /* SMPTE ST 2084 (PQ) */
out_meta->matrix_coefficients = 0; /* RGB */
out_meta->video_full_range_flag = 1;
out_meta->max_cll = max_cll;
out_meta->max_fall = (float)(sum_fall / ((double)w * (double)h));
out_meta->write_mdcv = 1;
out_meta->primary_chromaticity[0][0] = 0.640f; out_meta->primary_chromaticity[0][1] = 0.330f;
out_meta->primary_chromaticity[1][0] = 0.300f; out_meta->primary_chromaticity[1][1] = 0.600f;
out_meta->primary_chromaticity[2][0] = 0.150f; out_meta->primary_chromaticity[2][1] = 0.060f;
out_meta->white_point[0] = 0.3127f;
out_meta->white_point[1] = 0.3290f; /* D65 */
out_meta->max_luminance = 1000.0f;
out_meta->min_luminance = 0.001f;
}
return true;
}
static bool gl3_read_viewport(void *data, uint8_t *buffer, bool is_idle)
{
gl3_t *gl = (gl3_t*)data;
size_t num_pixels = 0;
if (!gl)
return false;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
/* Lazy init / reinit: (re)initialize PBO readback when recording
* starts after driver init, or when viewport dimensions change. */
if ( !(gl->flags & GL3_FLAG_PBO_READBACK_ENABLE)
|| (unsigned)gl->pbo_readback_scaler.in_width != VIDEO_SCALE_W(gl->vp.dims)
|| (unsigned)gl->pbo_readback_scaler.in_height != VIDEO_SCALE_H(gl->vp.dims))
{
if (gl->flags & GL3_FLAG_GPU_RECORDING)
{
/* Tear down old PBO resources before reinitializing */
if (gl->flags & GL3_FLAG_PBO_READBACK_ENABLE)
gl3_deinit_pbo_readback(gl);
gl->flags |= GL3_FLAG_PBO_READBACK_ENABLE;
if (gl3_init_pbo_readback(gl))
RARCH_LOG("[GLCore] (Re)initialized async PBO readback for recording.\n");
}
}
num_pixels = VIDEO_SCALE_AREA(gl->vp.dims);
if (gl->flags & GL3_FLAG_PBO_READBACK_ENABLE)
{
const void *ptr = NULL;
struct scaler_ctx *ctx = &gl->pbo_readback_scaler;
/* Don't readback if we're in menu mode.
* We haven't buffered up enough frames yet, come back later. */
if (!gl->pbo_readback_valid[gl->pbo_readback_index])
goto error;
gl->pbo_readback_valid[gl->pbo_readback_index] = false;
glBindBuffer(GL_PIXEL_PACK_BUFFER, gl->pbo_readback[gl->pbo_readback_index]);
ptr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, num_pixels * sizeof(uint32_t), GL_MAP_READ_BIT);
scaler_ctx_scale_direct(ctx, buffer, ptr);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
else
{
/* Use slow synchronous readbacks. Use this with plain screenshots
as we don't really care about performance in this case. */
/* GLES only guarantees GL_RGBA/GL_UNSIGNED_BYTE
* readbacks so do just that.
* GLES also doesn't support reading back data
* from front buffer, so render a cached frame
* and have gl_frame() do the readback while it's
* in the back buffer.
*
* Keep codepath similar for GLES and desktop GL.
*/
gl->readback_buffer_screenshot = (uint32_t*)malloc(num_pixels * sizeof(uint32_t));
if (!gl->readback_buffer_screenshot)
goto error;
if (!is_idle)
video_driver_cached_frame();
{
/* Clamp to the region glReadPixels actually wrote. */
unsigned rb_w = MIN(VIDEO_SCALE_W(gl->vp.dims),
VIDEO_SCALE_W(gl->video_dims));
unsigned rb_h = MIN(VIDEO_SCALE_H(gl->vp.dims),
VIDEO_SCALE_H(gl->video_dims));
video_frame_convert_rgba_to_bgr(
(const void*)gl->readback_buffer_screenshot,
buffer,
rb_w * sizeof(uint32_t),
rb_w * 3,
rb_w,
rb_h);
}
free(gl->readback_buffer_screenshot);
gl->readback_buffer_screenshot = NULL;
}
/* Init leaves the core's context current for context_reset on this
* thread; when the wrapper's ring will drive the core, the main
* thread takes that context itself and this one must not hold it. */
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
return true;
error:
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
return false;
}
static void gl3_update_cpu_texture(gl3_t *gl,
struct gl3_streamed_texture *streamed,
const void *frame, unsigned dims, unsigned pitch)
{
unsigned width = VIDEO_SCALE_W(dims);
unsigned height = VIDEO_SCALE_H(dims);
if (gl->chain.active)
{
/* The input texture is expected to be square with a power of 2 size when not using Slang */
unsigned max = width > height ? width : height;
unsigned pow2_size = next_pow2(max);
width = pow2_size;
height = pow2_size;
dims = VIDEO_SCALE_PACK(width, height);
}
if (dims != streamed->dims)
{
if (streamed->tex != 0)
glDeleteTextures(1, &streamed->tex);
glGenTextures(1, &streamed->tex);
glBindTexture(GL_TEXTURE_2D, streamed->tex);
glTexStorage2D(GL_TEXTURE_2D, 1,
gl->video_info.source_10bit
? GL_RGB10_A2
: (gl->video_info.rgb32
? GL_RGBA8
: GL_RGB565),
width, height);
streamed->dims = dims;
/* Both 32-bit source formats arrive with the red and blue channels
* the other way round from what GL reads back out of the packed
* type (XRGB8888 vs GL_RGBA/UNSIGNED_BYTE, and XRGB2101010 =
* A2R10G10B10 vs GL_RGBA/UNSIGNED_INT_2_10_10_10_REV, which places
* A in 31:30 but R in 9:0), so both want the same view swizzle. */
if (gl->video_info.rgb32 || gl->video_info.source_10bit)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
}
else
glBindTexture(GL_TEXTURE_2D, streamed->tex);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
if (gl->video_info.source_10bit)
{
/* Packed 2-10-10-10, one word per pixel like the 8-bit path. */
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> 2);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
width, height, GL_RGBA,
GL_UNSIGNED_INT_2_10_10_10_REV, frame);
}
else if (gl->video_info.rgb32)
{
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> 2);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
width, height, GL_RGBA, GL_UNSIGNED_BYTE, frame);
}
else
{
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
}
}
#if defined(HAVE_MENU)
static void gl3_draw_menu_texture(gl3_t *gl,
unsigned width, unsigned height)
{
/* C89: the alpha is not a constant; set after declaration. */
float vbo_data[32] = {
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
};
vbo_data[7] = vbo_data[15] = vbo_data[23] = vbo_data[31] = gl->menu_texture_alpha;
glEnable(GL_BLEND);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
if (gl->flags & GL3_FLAG_MENU_TEXTURE_FULLSCREEN)
glViewport(0, 0, width, height);
else
glViewport(VIDEO_POS_X(gl->vp.pos), VIDEO_POS_Y(gl->vp.pos), VIDEO_SCALE_W(gl->vp.dims), VIDEO_SCALE_H(gl->vp.dims));
glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_2D, gl->menu_texture);
if (gl->chain.active)
{
gl->chain.shader->use(gl,
gl->chain.shader_data, VIDEO_SHADER_STOCK_BLEND, true);
gl->chain.coords.vertices = 4;
gl->chain.shader->set_coords(gl->chain.shader_data, &gl->chain.coords);
gl->chain.shader->set_mvp(gl->chain.shader_data, &gl->mvp_no_rot);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
#ifdef HAVE_SLANG
else
{
glUseProgram(gl->pipelines.alpha_blend);
if (gl->pipelines.alpha_blend_loc.flat_ubo_vertex >= 0)
glUniform4fv(gl->pipelines.alpha_blend_loc.flat_ubo_vertex, 4, gl->mvp_no_rot_yflip.data);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
gl3_bind_scratch_vbo(gl, vbo_data, sizeof(vbo_data));
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
8 * sizeof(float), (void *)(uintptr_t)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
8 * sizeof(float), (void *)(uintptr_t)(2 * sizeof(float)));
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE,
8 * sizeof(float), (void *)(uintptr_t)(4 * sizeof(float)));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
#endif
glDisable(GL_BLEND);
}
#endif
/* (Re)create the SDR offscreen the frame renders into under scRGB
* output. Returns the FBO every final-target bind in the frame should
* use: the offscreen when scRGB is active, the backbuffer otherwise. */
/* True when the frame must go through the offscreen + encode pass even
* though there is no scRGB backbuffer: the core supplied PQ, which
* cannot be handed to an SDR presentation path as-is. See
* video_driver_supports_10bit_source() - the frontend has to accept or
* refuse HDR10 before it can know what this context will provide, so it
* accepts and the driver reconciles it here. */
static bool gl3_needs_pq_downconvert(gl3_t *gl)
{
#ifdef HAVE_SLANG
return gl->video_info.source_hdr10 && !gl->scrgb.active;
#else
/* Nothing to downconvert with */
(void)gl;
return false;
#endif
}
/* The SDR content layer's format: FP16 unless recording reads it back
* (see gl3_frame_target_fbo); a PQ source's content layer is RGB10_A2
* and its UI has its own FP16 layer. */
static bool gl3_scrgb_wants_fp16(const gl3_t *gl)
{
return !gl->video_info.source_hdr10
&& !(gl->flags & GL3_FLAG_PBO_READBACK_ENABLE);
}
static GLuint gl3_frame_target_fbo(gl3_t *gl, unsigned dims)
{
if (!gl->scrgb.active && !gl3_needs_pq_downconvert(gl))
return 0;
if ( !gl->scrgb.fbo || gl->scrgb.dims != dims
|| gl->scrgb.tex_fp16 != gl3_scrgb_wants_fp16(gl))
{
unsigned width = VIDEO_SCALE_W(dims);
unsigned height = VIDEO_SCALE_H(dims);
if (gl->scrgb.fbo)
glDeleteFramebuffers(1, &gl->scrgb.fbo);
if (gl->scrgb.tex)
glDeleteTextures(1, &gl->scrgb.tex);
glGenTextures(1, &gl->scrgb.tex);
glBindTexture(GL_TEXTURE_2D, gl->scrgb.tex);
/* PQ content needs 10 bits here for the same reason the hw render
* target does: this offscreen holds the frame between the shader
* chain's viewport pass and the encode, and 8-bit PQ bands. */
if (gl->video_info.source_hdr10)
{
gl->scrgb.tex_fp16 = false;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, width, height, 0,
GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, NULL);
}
/* SDR content, with the menu drawn here too: FP16, so its alpha
* keeps every step and a linear texture's values above menu white
* reach the encode - but RGBA8 while recording, which reads this
* layer back each frame (gl3_pbo_async_readback): RGBA8 into
* GL_UNSIGNED_BYTE is the copy the driver makes without
* converting. The linear path is offered only while it is FP16
* (TEXTURE_GPU_FORMAT_SCRGB), so a recording keeps the tone-mapped
* previews it had. */
else if ((gl->scrgb.tex_fp16 = gl3_scrgb_wants_fp16(gl)))
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0,
GL_RGBA, GL_HALF_FLOAT, NULL);
else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenFramebuffers(1, &gl->scrgb.fbo);
glBindFramebuffer(GL_FRAMEBUFFER, gl->scrgb.fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, gl->scrgb.tex, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER)
!= GL_FRAMEBUFFER_COMPLETE)
{
RARCH_ERR("[GLCore] scRGB offscreen FBO incomplete; falling back to direct rendering.\n");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &gl->scrgb.fbo);
glDeleteTextures(1, &gl->scrgb.tex);
gl->scrgb.fbo = 0;
gl->scrgb.tex = 0;
gl->scrgb.active = false;
return 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
gl->scrgb.dims = dims;
/* Sized and lifetimed with the content offscreen; only the PQ
* path allocates it (SDR keeps the single-target behaviour and
* its screenshot shortcut untouched). */
if (gl->scrgb.ui_fbo)
{
glDeleteFramebuffers(1, &gl->scrgb.ui_fbo);
gl->scrgb.ui_fbo = 0;
}
if (gl->scrgb.ui_tex)
{
glDeleteTextures(1, &gl->scrgb.ui_tex);
gl->scrgb.ui_tex = 0;
}
if (gl->video_info.source_hdr10)
{
glGenTextures(1, &gl->scrgb.ui_tex);
glBindTexture(GL_TEXTURE_2D, gl->scrgb.ui_tex);
/* Half floats: nothing reads this layer back, so its alpha
* keeps every step and a linear texture's values above menu
* white reach the encode at no readback cost */
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0,
GL_RGBA, GL_HALF_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenFramebuffers(1, &gl->scrgb.ui_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, gl->scrgb.ui_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, gl->scrgb.ui_tex, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER)
!= GL_FRAMEBUFFER_COMPLETE)
{
RARCH_ERR("[GLCore] scRGB UI layer FBO incomplete; UI will be composited with the frame.\n");
glDeleteFramebuffers(1, &gl->scrgb.ui_fbo);
glDeleteTextures(1, &gl->scrgb.ui_tex);
gl->scrgb.ui_fbo = 0;
gl->scrgb.ui_tex = 0;
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
return gl->scrgb.fbo;
}
/* Target every UI draw should go to. Under PQ content that is the
* separate UI layer, cleared to transparent once per frame here; the
* encode pass composites it over the decoded content at its own
* brightness. Otherwise it is the frame target, unchanged. */
static GLuint gl3_ui_target_fbo(gl3_t *gl, unsigned dims)
{
GLuint fbo = gl3_frame_target_fbo(gl, dims);
if (gl->video_info.source_hdr10 && gl->scrgb.ui_fbo
&& !gl3_needs_pq_downconvert(gl))
{
glBindFramebuffer(GL_FRAMEBUFFER, gl->scrgb.ui_fbo);
glDisable(GL_SCISSOR_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
return gl->scrgb.ui_fbo;
}
return fbo;
}
static void gl3_update_input_size(gl3_t *gl, unsigned width, unsigned height)
{
float xamt = (float)width / (RARCH_SCALE_BASE * gl->video_info.input_scale);
float yamt = (float)height / (RARCH_SCALE_BASE * gl->video_info.input_scale);
GL3_SET_TEXTURE_COORDS(gl->chain.tex_info.coord, xamt, yamt);
}
static void gl3_renderchain_start_render(
gl3_t *gl)
{
/* Used when rendering to an FBO.
* Texture coords have to be aligned
* with vertex coordinates. */
static const GLfloat fbo_vertexes[] = {
0, 0,
1, 0,
0, 1,
1, 1
};
glBindFramebuffer(GL_FRAMEBUFFER, gl->chain.fbo[0]);
gl3_set_viewport(gl,
gl->chain.fbo_rect[0].img_dims, true, false);
/* Need to preserve the "flipped" state when in FBO
* as well to have consistent texture coordinates.
*
* We will "flip" it in place on last pass. */
gl->chain.coords.vertex = fbo_vertexes;
#ifndef HAVE_OPENGLES
if (gl->chain.srgb_fbo_supported)
glEnable(GL_FRAMEBUFFER_SRGB);
#endif
glBindTexture(GL_TEXTURE_2D, 0);
}
static void gl3_renderchain_render(
gl3_t *gl,
uint64_t frame_count,
const struct video_tex_info *tex_info,
const struct video_tex_info *feedback_info)
{
unsigned i;
video_shader_ctx_params_t params;
static GLfloat fbo_tex_coords[8] = {0.0f};
struct video_tex_info fbo_tex_info[GFX_MAX_SHADERS];
struct video_tex_info *fbo_info = NULL;
const struct video_fbo_rect *prev_rect = NULL;
GLfloat xamt = 0.0f;
GLfloat yamt = 0.0f;
unsigned mip_level = 0;
unsigned fbo_tex_info_cnt = 0;
unsigned width = VIDEO_SCALE_W(gl->video_dims);
unsigned height = VIDEO_SCALE_H(gl->video_dims);
/* Render the rest of our passes. */
gl->chain.coords.tex_coord = fbo_tex_coords;
/* Calculate viewports, texture coordinates etc,
* and render all passes from FBOs, to another FBO. */
for (i = 1; i < gl->chain.num_fbo_passes; i++)
{
const struct video_fbo_rect *rect = &gl->chain.fbo_rect[i];
prev_rect = &gl->chain.fbo_rect[i - 1];
fbo_info = &fbo_tex_info[i - 1];
xamt = (GLfloat)VIDEO_SCALE_W(prev_rect->img_dims)
/ VIDEO_SCALE_W(prev_rect->dims);
yamt = (GLfloat)VIDEO_SCALE_H(prev_rect->img_dims)
/ VIDEO_SCALE_H(prev_rect->dims);
GL3_SET_TEXTURE_COORDS(fbo_tex_coords, xamt, yamt);
fbo_info->tex = gl->chain.fbo_texture[i - 1];
fbo_info->input_size[0] = VIDEO_SCALE_W(prev_rect->img_dims);
fbo_info->input_size[1] = VIDEO_SCALE_H(prev_rect->img_dims);
fbo_info->tex_size[0] = VIDEO_SCALE_W(prev_rect->dims);
fbo_info->tex_size[1] = VIDEO_SCALE_H(prev_rect->dims);
memcpy(fbo_info->coord, fbo_tex_coords, sizeof(fbo_tex_coords));
fbo_tex_info_cnt++;
glBindFramebuffer(GL_FRAMEBUFFER, gl->chain.fbo[i]);
gl->chain.shader->use(gl, gl->chain.shader_data,
i + 1, true);
glBindTexture(GL_TEXTURE_2D, gl->chain.fbo_texture[i - 1]);
mip_level = i + 1;
if (gl->chain.shader->mipmap_input(gl->chain.shader_data, mip_level))
glGenerateMipmap(GL_TEXTURE_2D);
glClear(GL_COLOR_BUFFER_BIT);
/* Render to FBO with certain size. */
gl3_set_viewport(gl, rect->img_dims, true, false);
params.vp_dims = gl->out_vp_dims;
params.dims = prev_rect->img_dims;
params.tex_dims = prev_rect->dims;
params.out_dims = gl->vp.dims;
params.frame_counter = (unsigned int)frame_count;
/* Intermediate passes of the same present: the outer frame's
* count, read from the shared state since video_info does not
* reach this far in. */
params.swap_counter = (unsigned int)video_thread_swap_count();
params.info = tex_info;
params.prev_info = gl->chain.prev_info;
params.feedback_info = feedback_info;
params.fbo_info = fbo_tex_info;
params.fbo_info_cnt = fbo_tex_info_cnt;
gl->chain.shader->set_params(&params, gl->chain.shader_data);
gl->chain.coords.vertices = 4;
gl->chain.shader->set_coords(gl->chain.shader_data, &gl->chain.coords);
gl->chain.shader->set_mvp(gl->chain.shader_data, &gl->mvp);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
#ifndef HAVE_OPENGLES
if (gl->chain.srgb_fbo_supported)
glDisable(GL_FRAMEBUFFER_SRGB);
#endif
/* Render our last FBO texture directly to screen. */
prev_rect = &gl->chain.fbo_rect[gl->chain.num_fbo_passes - 1];
xamt = (GLfloat)VIDEO_SCALE_W(prev_rect->img_dims)
/ VIDEO_SCALE_W(prev_rect->dims);
yamt = (GLfloat)VIDEO_SCALE_H(prev_rect->img_dims)
/ VIDEO_SCALE_H(prev_rect->dims);
GL3_SET_TEXTURE_COORDS(fbo_tex_coords, xamt, yamt);
/* Push final FBO to list. */
fbo_info = &fbo_tex_info[gl->chain.num_fbo_passes - 1];
fbo_info->tex = gl->chain.fbo_texture[gl->chain.num_fbo_passes - 1];
fbo_info->input_size[0] = VIDEO_SCALE_W(prev_rect->img_dims);
fbo_info->input_size[1] = VIDEO_SCALE_H(prev_rect->img_dims);
fbo_info->tex_size[0] = VIDEO_SCALE_W(prev_rect->dims);
fbo_info->tex_size[1] = VIDEO_SCALE_H(prev_rect->dims);
memcpy(fbo_info->coord, fbo_tex_coords, sizeof(fbo_tex_coords));
fbo_tex_info_cnt++;
/* Render our FBO texture to back buffer (or, under scRGB output,
* into the SDR offscreen the end-of-frame encode consumes). */
glBindFramebuffer(GL_FRAMEBUFFER,
gl3_frame_target_fbo(gl, gl->video_dims));
gl->chain.shader->use(gl, gl->chain.shader_data,
gl->chain.num_fbo_passes + 1, true);
glBindTexture(GL_TEXTURE_2D, gl->chain.fbo_texture[gl->chain.num_fbo_passes - 1]);
mip_level = gl->chain.num_fbo_passes + 1;
if (gl->chain.shader->mipmap_input(gl->chain.shader_data, mip_level))
glGenerateMipmap(GL_TEXTURE_2D);
glClear(GL_COLOR_BUFFER_BIT);
gl3_set_viewport(gl, VIDEO_SCALE_PACK(width, height), false, true);
params.vp_dims = gl->out_vp_dims;
params.dims = prev_rect->img_dims;
params.tex_dims = prev_rect->dims;
params.out_dims = gl->vp.dims;
params.frame_counter = (unsigned int)frame_count;
params.swap_counter = (unsigned int)video_thread_swap_count();
params.info = tex_info;
params.prev_info = gl->chain.prev_info;
params.feedback_info = feedback_info;
params.fbo_info = fbo_tex_info;
params.fbo_info_cnt = fbo_tex_info_cnt;
gl->chain.shader->set_params(&params, gl->chain.shader_data);
gl->chain.coords.vertex = gl->chain.vertex_ptr;
gl->chain.coords.vertices = 4;
gl->chain.shader->set_coords(gl->chain.shader_data, &gl->chain.coords);
gl->chain.shader->set_mvp(gl->chain.shader_data, &gl->mvp);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
gl->chain.coords.tex_coord = gl->chain.tex_info.coord;
}
/* Tonemap a PQ frame from the offscreen into the currently bound target
* (the backbuffer). Used only when the core supplied HDR10 but this
* context turned out to have no scRGB backbuffer; the UI is then drawn
* over the converted image in the ordinary SDR way, so none of the
* linear-light compositing applies. */
static void gl3_encode_pq_to_sdr(gl3_t *gl, unsigned width, unsigned height)
{
#ifdef HAVE_SLANG
float ubo_data[24];
static const float quad_pos[8] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
};
static const float quad_tex[8] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
};
static bool warned = false;
if (!gl->scrgb.fbo || !gl->pipelines.hdr_scrgb)
return;
if (!warned)
{
warned = true;
RARCH_WARN("[GLCore] Core supplied HDR10 PQ but this context has no scRGB backbuffer; tonemapping to SDR. Turn on HDR output, or set the core to a 24-bit colour format.\n");
}
memcpy(ubo_data, gl->mvp_no_rot.data, 16 * sizeof(float));
/* Driver-owned, latched by the HDR poke path: the frame path
* (video thread, main running free) reads no live settings. The
* settings null-check this replaces was a vestige of the fetch
* that once supplied the value. */
ubo_data[16] = gl->scrgb.paper_white_nits;
ubo_data[17] = 0.0f;
ubo_data[18] = 2.0f; /* PQ -> SDR */
ubo_data[19] = 0.0f; /* no separate UI layer on this path */
ubo_data[20] = 0.0f; /* an SDR output, never PQ */
ubo_data[21] = 0.0f;
ubo_data[22] = 0.0f;
ubo_data[23] = 0.0f;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, width, height);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
glUseProgram(gl->pipelines.hdr_scrgb);
if (gl->pipelines.hdr_scrgb_loc.flat_ubo_vertex >= 0)
glUniform4fv(gl->pipelines.hdr_scrgb_loc.flat_ubo_vertex, 6, ubo_data);
if (gl->pipelines.hdr_scrgb_loc.flat_ubo_fragment >= 0)
glUniform4fv(gl->pipelines.hdr_scrgb_loc.flat_ubo_fragment, 6, ubo_data);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gl->scrgb.tex);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gl->scrgb.tex);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDisableVertexAttribArray(2);
gl3_bind_scratch_vbo(gl, quad_pos, sizeof(quad_pos));
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
2 * sizeof(float), (void *)(uintptr_t)0);
gl3_bind_scratch_vbo(gl, quad_tex, sizeof(quad_tex));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
2 * sizeof(float), (void *)(uintptr_t)0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glUseProgram(0);
#else
(void)gl;
(void)width;
(void)height;
#endif
}
/* The ring's fence for this frame, taken ahead of the swap: a driver
* that queues swaps completes a fence behind one only once the swap is
* released, at the display's rate. */
static void gl3_hw_ring_drawn(gl3_t *gl)
{
if (!gl3_core_context_is_mains(gl))
return;
if (gl->hw_ring_done_sync)
glDeleteSync((GLsync)gl->hw_ring_done_sync);
gl->hw_ring_done_sync = (void*)glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
static bool gl3_frame(void *data, const void *frame,
unsigned dims,
uint64_t frame_count,
unsigned pitch, const char *msg,
video_frame_info_t *video_info)
{
unsigned frame_width = VIDEO_SCALE_W(dims);
unsigned frame_height = VIDEO_SCALE_H(dims);
struct gl3_filter_chain_texture texture;
struct gl3_streamed_texture *streamed = NULL;
#ifdef HAVE_SLANG
gl3_filter_chain_t *filter_chain = NULL;
#endif
gl3_t *gl = (gl3_t*)data;
unsigned width = VIDEO_SCALE_W(video_info->dims);
unsigned height = VIDEO_SCALE_H(video_info->dims);
struct font_params *osd_params = (struct font_params*)
&video_info->osd_stat_params;
const char *stat_text = video_info->stat_text;
bool statistics_show = video_info->statistics_show;
#if 0
bool msg_bgcolor_enable = video_info->msg_bgcolor_enable;
#endif
int bfi_light_frames;
unsigned i;
unsigned n;
unsigned hard_sync_frames = video_info->hard_sync_frames;
bool input_driver_nonblock_state = video_info->input_driver_nonblock_state;
#ifdef HAVE_MENU
bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false;
#endif
#ifdef HAVE_GFX_WIDGETS
bool widgets_active = video_info->widgets_active;
#endif
bool hard_sync = video_info->hard_sync;
bool overlay_behind_menu = video_info->overlay_behind_menu;
if (!gl)
return false;
/* One a frame without the ring's hand-over left behind */
if (gl->hw_ring_done_sync)
{
glDeleteSync((GLsync)gl->hw_ring_done_sync);
gl->hw_ring_done_sync = NULL;
}
/* Travels with the frame, for set_texture_frame() to read rather
* than the setting the menu writes */
gl->frame_menu_linear_filter = video_info->menu_linear_filter;
/* These travel with the frame, so this thread does not read what the
* main thread writes: the scRGB encode below and gl3_encode_pq_to_sdr()
* read the latched copies. */
gl->scrgb.menu_nits = video_info->hdr_menu_nits;
gl->scrgb.paper_white_nits = video_info->hdr_paper_white_nits;
gl->scrgb.expand_gamut = video_info->hdr_expand_gamut;
/* Whether to read frames back travels with the frame, so this thread
* does not read the recording state the main thread writes. */
if (video_info->gpu_recording)
gl->flags |= GL3_FLAG_GPU_RECORDING;
else
gl->flags &= ~GL3_FLAG_GPU_RECORDING;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
glBindVertexArray(gl->vao);
if (gl->chain.active)
gl->chain.shader->use(gl, gl->chain.shader_data, 1, true);
#if TARGET_OS_IPHONE
/* Apparently the viewport is lost each frame, thanks Apple. */
gl3_set_viewport(gl, VIDEO_SCALE_PACK(width, height), false, true);
#endif
if (frame)
gl->textures_index = (gl->textures_index + 1)
& (GL_CORE_NUM_TEXTURES - 1);
/* Render to texture in first pass. */
if (gl->chain.active && gl->chain.num_fbo_passes != 0)
{
gl3_renderchain_recompute_pass_sizes(gl,
frame_width, frame_height,
VIDEO_SCALE_W(gl->out_vp_dims), VIDEO_SCALE_H(gl->out_vp_dims));
gl3_renderchain_start_render(gl);
}
streamed = &gl->textures[gl->textures_index];
if (frame)
{
if (gl->flags & GL3_FLAG_HW_RENDER_ENABLE)
{
streamed->dims = dims;
}
else
gl3_update_cpu_texture(gl, streamed, frame, dims, pitch);
}
if (gl->flags & GL3_FLAG_SHOULD_RESIZE)
{
if (gl->ctx_driver->set_resize)
gl->ctx_driver->set_resize(gl->ctx_data, video_info->dims);
gl->flags &= ~GL3_FLAG_SHOULD_RESIZE;
if (gl->chain.active && gl->chain.num_fbo_passes != 0)
{
/* On resize, we might have to recreate our FBOs
* due to "Viewport" scale, and set a new viewport. */
/* Check if we have to recreate our FBO textures. */
for (i = 0; i < gl->chain.num_fbo_passes; i++)
{
struct video_fbo_rect *fbo_rect = &gl->chain.fbo_rect[i];
if (fbo_rect)
{
unsigned img_width = VIDEO_SCALE_W(fbo_rect->max_img_dims);
unsigned img_height = VIDEO_SCALE_H(fbo_rect->max_img_dims);
if ( (img_width > VIDEO_SCALE_W(fbo_rect->dims))
|| (img_height > VIDEO_SCALE_H(fbo_rect->dims)))
{
/* Check proactively since we might suddenly
* get sizes of tex_w width or tex_h height. */
unsigned max = img_width > img_height ? img_width : img_height;
unsigned pow2_size = next_pow2(max);
bool update_feedback = gl->chain.fbo_feedback && i == gl->chain.fbo_feedback_pass;
fbo_rect->dims = VIDEO_SCALE_PACK(
pow2_size, pow2_size);
gl3_recreate_fbo(fbo_rect, gl->chain.fbo[i], &gl->chain.fbo_texture[i]);
/* Update feedback texture in-place so we avoid having to
* juggle two different fbo_rect structs since they get updated here. */
if (update_feedback)
{
if (gl3_recreate_fbo(fbo_rect, gl->chain.fbo_feedback,
&gl->chain.fbo_feedback_texture))
{
/* Make sure the feedback textures are cleared
* so we don't feedback noise. */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
}
RARCH_LOG("[GLCore] Recreating FBO texture #%d: %ux%u.\n",
i, VIDEO_SCALE_W(fbo_rect->dims),
VIDEO_SCALE_H(fbo_rect->dims));
}
}
}
/* Go back to what we're supposed to do,
* render to FBO #0. */
gl3_renderchain_start_render(gl);
}
else
gl3_set_viewport(gl, VIDEO_SCALE_PACK(width, height), false, true);
}
/* Can be NULL for frame dupe / NULL render. */
if (frame && gl->chain.active)
{
if (!(gl->flags & GL3_FLAG_HW_RENDER_ENABLE))
gl3_update_input_size(gl, frame_width, frame_height);
}
texture.image = 0;
texture.dims = streamed->dims;
texture.padded_dims = 0;
texture.format = 0;
if (gl->flags & GL3_FLAG_HW_RENDER_ENABLE)
{
texture.image = gl->hw_render_texture;
texture.format = gl->video_info.source_hdr10
? GL_RGB10_A2 : GL_RGBA8;
texture.padded_dims = gl->hw_render_max_dims;
if (!VIDEO_SCALE_W(texture.dims))
VIDEO_SCALE_PUT_W(texture.dims, 1);
if (!VIDEO_SCALE_H(texture.dims))
VIDEO_SCALE_PUT_H(texture.dims, 1);
/* Scissor is global context state, so a core that returns
* with the test still enabled clips every frontend draw that
* follows - the filter chain's final pass, the menu and the
* widgets - to the core's last scissor rectangle. Reset here
* rather than in the chain.active block below, which the
* slang filter chain path never enters. */
glDisable(GL_SCISSOR_TEST);
}
else
{
texture.image = streamed->tex;
texture.format = gl->video_info.source_10bit
? GL_RGB10_A2
: (gl->video_info.rgb32 ? GL_RGBA8 : GL_RGB565);
texture.padded_dims = streamed->dims;
}
/* No point regenerating mipmaps
* if there are no new frames. */
if (frame && gl->chain.active && gl->chain.mipmap_active)
{
glBindTexture(GL_TEXTURE_2D, texture.image);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}
if (gl->chain.active)
{
video_shader_ctx_params_t params;
struct video_tex_info feedback_info;
gl3_update_input_size(gl, frame_width, frame_height);
/* Have to reset rendering state which libretro core
* could easily have overridden. */
if (gl->flags & GL3_FLAG_HW_RENDER_ENABLE)
{
if (gl->chain.num_fbo_passes == 0)
{
glBindFramebuffer(GL_FRAMEBUFFER,
gl3_frame_target_fbo(gl, video_info->dims));
gl3_set_viewport(gl, VIDEO_SCALE_PACK(width, height), false, true);
}
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_DITHER);
glDisable(GL_STENCIL_TEST);
glDisable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);
}
gl->chain.tex_info.tex = texture.image;
gl->chain.tex_info.input_size[0] = frame_width;
gl->chain.tex_info.input_size[1] = frame_height;
gl->chain.tex_info.tex_size[0] = RARCH_SCALE_BASE * gl->video_info.input_scale;
gl->chain.tex_info.tex_size[1] = RARCH_SCALE_BASE * gl->video_info.input_scale;
feedback_info = gl->chain.tex_info;
if (gl->chain.fbo_feedback)
{
const struct video_fbo_rect
*rect = &gl->chain.fbo_rect[gl->chain.fbo_feedback_pass];
GLfloat xamt = (GLfloat)VIDEO_SCALE_W(rect->img_dims)
/ VIDEO_SCALE_W(rect->dims);
GLfloat yamt = (GLfloat)VIDEO_SCALE_H(rect->img_dims)
/ VIDEO_SCALE_H(rect->dims);
feedback_info.tex = gl->chain.fbo_feedback_texture;
feedback_info.input_size[0] = VIDEO_SCALE_W(rect->img_dims);
feedback_info.input_size[1] = VIDEO_SCALE_H(rect->img_dims);
feedback_info.tex_size[0] = VIDEO_SCALE_W(rect->dims);
feedback_info.tex_size[1] = VIDEO_SCALE_H(rect->dims);
GL3_SET_TEXTURE_COORDS(feedback_info.coord, xamt, yamt);
}
params.vp_dims = gl->out_vp_dims;
params.dims = dims;
params.tex_dims = VIDEO_SCALE_PACK(
RARCH_SCALE_BASE * gl->video_info.input_scale,
RARCH_SCALE_BASE * gl->video_info.input_scale);
params.out_dims = gl->vp.dims;
params.frame_counter = (unsigned int)frame_count;
params.swap_counter = (unsigned int)video_info->swap_count;
params.info = &gl->chain.tex_info;
params.prev_info = gl->chain.prev_info;
params.feedback_info = &feedback_info;
params.fbo_info = NULL;
params.fbo_info_cnt = 0;
glBindTexture(GL_TEXTURE_2D, texture.image);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
gl->chain.shader->set_params(&params, gl->chain.shader_data);
gl->chain.coords.vertices = 4;
gl->chain.shader->set_coords(gl->chain.shader_data, &gl->chain.coords);
gl->chain.shader->set_mvp(gl->chain.shader_data, &gl->mvp);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if (gl->chain.num_fbo_passes != 0)
gl3_renderchain_render(gl, frame_count, &gl->chain.tex_info, &feedback_info);
memmove(gl->chain.prev_info + 1, gl->chain.prev_info, sizeof(*gl->chain.prev_info) * (gl->chain.num_prev_textures - 1));
memcpy(&gl->chain.prev_info[0], &gl->chain.tex_info, sizeof(gl->chain.tex_info));
/* Implement feedback by swapping out FBO/textures
* for FBO pass #N and feedbacks. */
if (gl->chain.fbo_feedback)
{
GLuint tmp_fbo = gl->chain.fbo_feedback;
GLuint tmp_tex = gl->chain.fbo_feedback_texture;
gl->chain.fbo_feedback = gl->chain.fbo[gl->chain.fbo_feedback_pass];
gl->chain.fbo_feedback_texture = gl->chain.fbo_texture[gl->chain.fbo_feedback_pass];
gl->chain.fbo[gl->chain.fbo_feedback_pass] = tmp_fbo;
gl->chain.fbo_texture[gl->chain.fbo_feedback_pass] = tmp_tex;
}
glBindTexture(GL_TEXTURE_2D, 0);
}
#ifdef HAVE_SLANG
else
{
/* Fast toggle shader filter chain logic */
filter_chain = gl->filter_chain;
if (!video_info->shader_active && gl->filter_chain != gl->filter_chain_default)
{
if (!gl->filter_chain_default)
gl3_init_default_filter_chain(gl);
if (gl->filter_chain_default)
filter_chain = gl->filter_chain_default;
else
return false;
}
if (!filter_chain && gl->filter_chain_default)
filter_chain = gl->filter_chain_default;
/* Every chain call below dereferences this. If the preset failed to
* build and the default chain is missing too, there is nothing to
* render through, and continuing faults on the first setter rather
* than reporting a dead frame. */
if (!filter_chain)
{
RARCH_ERR("[GLCore] No filter chain available for this frame.\n");
return false;
}
gl3_filter_chain_set_frame_count(filter_chain, frame_count);
gl3_filter_chain_set_swap_count(filter_chain,
video_info->swap_count);
#ifdef HAVE_REWIND
gl3_filter_chain_set_frame_direction(filter_chain, state_manager_frame_is_reversed() ? -1 : 1);
#else
gl3_filter_chain_set_frame_direction(filter_chain, 1);
#endif
gl3_filter_chain_set_frame_time_delta(filter_chain, (uint32_t)video_driver_get_frame_time_delta_usec());
gl3_filter_chain_set_original_fps(filter_chain, video_driver_get_original_fps());
{
uint32_t rot = gl->rotation_raw;
float core_aspect = video_driver_get_core_aspect();
float core_aspect_rot = core_aspect;
gl3_filter_chain_set_rotation(filter_chain, rot);
gl3_filter_chain_set_core_aspect(filter_chain, core_aspect);
/* OriginalAspectRotated: return 1/aspect for 90 and 270 rotated content */
if (rot == 1 || rot == 3)
core_aspect_rot = 1 / core_aspect_rot;
gl3_filter_chain_set_core_aspect_rot(filter_chain, core_aspect_rot);
}
/* Sub-frame info for multiframe shaders (per real content frame).
Should always be 1 for non-use of subframes*/
if (!(gl->flags & GL3_FLAG_FRAME_DUPE_LOCK))
{
if ( video_info->black_frame_insertion
|| video_info->input_driver_nonblock_state
|| video_info->runloop_is_slowmotion
|| video_info->runloop_is_paused
|| (gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE))
gl3_filter_chain_set_shader_subframes(
filter_chain, 1);
else
gl3_filter_chain_set_shader_subframes(
filter_chain, video_info->shader_subframes);
gl3_filter_chain_set_current_shader_subframe(
filter_chain, 1);
}
#ifdef GL3_ROLLING_SCANLINE_SIMULATION
if ( (video_info->shader_subframes > 1)
&& (video_info->scan_subframes)
&& !video_info->black_frame_insertion
&& !video_info->input_driver_nonblock_state
&& !video_info->runloop_is_slowmotion
&& !video_info->runloop_is_paused
&& (!(gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE)))
gl3_filter_chain_set_simulate_scanline(
filter_chain, true);
else
gl3_filter_chain_set_simulate_scanline(
filter_chain, false);
#endif /* GL3_ROLLING_SCANLINE_SIMULATION */
gl3_filter_chain_set_input_texture(filter_chain, &texture);
gl3_filter_chain_build_offscreen_passes(filter_chain,
&gl->filter_chain_vp);
glBindFramebuffer(GL_FRAMEBUFFER,
gl3_frame_target_fbo(gl, video_info->dims));
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
gl3_filter_chain_build_viewport_pass(filter_chain,
&gl->filter_chain_vp,
(gl->flags & GL3_FLAG_HW_RENDER_BOTTOM_LEFT)
? gl->mvp.data
: gl->mvp_yflip.data);
gl3_filter_chain_end_frame(filter_chain);
}
#endif /* HAVE_SLANG */
/* Under scRGB output, re-assert the frame target before any UI
* draws: chain internals (history / feedback / original-history
* copies in end_frame and the GLSL chain, via
* gl3_framebuffer_copy) restore FBO 0 when they finish, which is
* correct for them but would send the menu, overlays, widgets and
* OSD to the raw FP16 backbuffer -- the end-of-frame encode then
* replaces the backbuffer with the offscreen, losing the UI (black
* screen with only the core frame's offscreen content). One
* re-bind at this choke point keeps the everything-in-one-target
* invariant without chasing every restore site. No-op in SDR. */
if (gl->scrgb.active)
glBindFramebuffer(GL_FRAMEBUFFER,
gl3_ui_target_fbo(gl, video_info->dims));
else if (gl3_needs_pq_downconvert(gl))
{
/* Convert the PQ frame to SDR now, straight into the backbuffer,
* so the UI below draws over a displayable image in its own
* space - no UI layer, no linear composite, nothing else about
* the SDR path changes. */
gl3_encode_pq_to_sdr(gl, width, height);
}
#ifdef HAVE_OVERLAY
if ((gl->flags & GL3_FLAG_OVERLAY_ENABLE) && overlay_behind_menu)
gl3_render_overlay(gl, width, height);
#endif
#if defined(HAVE_MENU)
if (gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE)
{
menu_driver_frame(menu_is_alive, video_info);
if (gl->menu_texture)
gl3_draw_menu_texture(gl, width, height);
}
else if (statistics_show)
{
if (osd_params)
font_driver_render_msg(gl, stat_text, video_info->stat_text_len,
(const struct font_params*)osd_params, NULL);
}
#endif
#ifdef HAVE_OVERLAY
if ((gl->flags & GL3_FLAG_OVERLAY_ENABLE) && !overlay_behind_menu)
gl3_render_overlay(gl, width, height);
#endif
#ifdef HAVE_GFX_WIDGETS
if (widgets_active)
gfx_widgets_frame(video_info);
#endif
if (msg && *msg)
font_driver_render_msg(gl, msg, strlen(msg), NULL, NULL);
/* scRGB output: everything above rendered into the SDR offscreen;
* encode it into the FP16 backbuffer in one pass (gamma 2.4
* linearize, gamut handling, paper-white / 80 scaling). Runs before
* the read-back block so screenshots and recording keep observing
* the backbuffer as before. */
#ifdef HAVE_SLANG
if (gl->scrgb.active && gl->scrgb.fbo && gl->pipelines.hdr_scrgb)
{
float ubo_data[24];
bool ui_visible = false;
static const float quad_pos[8] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
};
static const float quad_tex[8] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
};
/* Same semantics as the vulkan composite's is_menu_composite:
* when any UI is composited this frame (menu, overlay, OSD
* message, statistics, widgets), the SDR content is scaled by
* Menu HDR Brightness (video_hdr_menu_nits) instead of paper
* white -- this is the setting that controls menu brightness
* across the HDR drivers. */
#ifdef HAVE_MENU
if (gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE)
ui_visible = true;
#endif
#ifdef HAVE_OVERLAY
if (gl->flags & GL3_FLAG_OVERLAY_ENABLE)
ui_visible = true;
#endif
if ((msg && *msg) || statistics_show)
ui_visible = true;
#ifdef HAVE_GFX_WIDGETS
if (widgets_active)
ui_visible = true;
#endif
{
bool pq = gl->video_info.source_hdr10
&& gl->scrgb.ui_fbo != 0;
float paper_white = gl->scrgb.paper_white_nits;
float menu_nits = gl->scrgb.menu_nits;
memcpy(ubo_data, gl->mvp_no_rot.data, 16 * sizeof(float));
/* SDR content keeps the existing single-target behaviour,
* including scaling the whole frame by Menu HDR Brightness
* when any UI is up. PQ content carries its own absolute
* luminance, so paper white does not apply to it and the UI
* is composited separately at the menu setting instead. */
ubo_data[16] = pq
? paper_white
: (ui_visible ? menu_nits : paper_white);
ubo_data[17] = (float)gl->scrgb.expand_gamut;
/* 0 = SDR -> scRGB, 1 = PQ -> scRGB (2 is the SDR-output
* fallback, issued from gl3_encode_pq_to_sdr instead). */
ubo_data[18] = pq ? 1.0f : 0.0f;
ubo_data[19] = pq ? menu_nits : 0.0f;
/* hdr_out: finish in Rec.2020 PQ for an HDR10 backbuffer */
ubo_data[20] = gl->scrgb.pq_out ? 1.0f : 0.0f;
ubo_data[21] = 0.0f;
ubo_data[22] = 0.0f;
ubo_data[23] = 0.0f;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, width, height);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
glUseProgram(gl->pipelines.hdr_scrgb);
if (gl->pipelines.hdr_scrgb_loc.flat_ubo_vertex >= 0)
glUniform4fv(gl->pipelines.hdr_scrgb_loc.flat_ubo_vertex,
6, ubo_data);
if (gl->pipelines.hdr_scrgb_loc.flat_ubo_fragment >= 0)
glUniform4fv(gl->pipelines.hdr_scrgb_loc.flat_ubo_fragment,
6, ubo_data);
/* The cross-compiled pipelines sample the unit matching the
* SPIR-V binding (shader_gl3.c forces sampler uniform N to
* texture unit N); uTex is binding 1, the same convention the
* alpha_blend / font draws use. Binding the offscreen to unit 0
* left the program sampling whatever unit 1 last held -- the
* OSD font atlas, hence the upside-down red glyphs. */
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gl->scrgb.tex);
/* uUITex is binding 2, hence texture unit 2 (see above). Bound to
* the content texture when there is no separate UI layer: the
* shader will not sample it (hdr_params.w == 0), but leaving a
* stale binding on the unit is what produced the upside-down red
* glyphs the comment above documents. */
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D,
(gl->video_info.source_hdr10 && gl->scrgb.ui_tex)
? gl->scrgb.ui_tex : gl->scrgb.tex);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDisableVertexAttribArray(2);
gl3_bind_scratch_vbo(gl, quad_pos, sizeof(quad_pos));
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
2 * sizeof(float), (void *)(uintptr_t)0);
gl3_bind_scratch_vbo(gl, quad_tex, sizeof(quad_tex));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
2 * sizeof(float), (void *)(uintptr_t)0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glUseProgram(0);
}
#endif
if (gl->ctx_driver->update_window_title)
gl->ctx_driver->update_window_title(gl->ctx_data);
if (gl->readback_buffer_screenshot)
{
/* For screenshots, just do the regular slow readback. */
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
/* Under scRGB output, read the SDR offscreen instead of the
* encoded FP16 backbuffer: it holds the exact pre-encode frame,
* so SDR screenshots are roundtrip-free (no paper-white /
* gamma / gamut inversion at all -- the other HDR drivers can
* only approximate this with a tonemap). */
if (gl->scrgb.active && gl->scrgb.fbo
&& !gl->video_info.source_hdr10)
glBindFramebuffer(GL_READ_FRAMEBUFFER, gl->scrgb.fbo);
#ifndef HAVE_OPENGLES
else
glReadBuffer(GL_BACK);
#else
;
#endif
glReadPixels(
(VIDEO_POS_X(gl->vp.pos) > 0) ? VIDEO_POS_X(gl->vp.pos) : 0,
(VIDEO_POS_Y(gl->vp.pos) > 0) ? VIDEO_POS_Y(gl->vp.pos) : 0,
MIN(VIDEO_SCALE_W(gl->vp.dims), VIDEO_SCALE_W(gl->video_dims)),
MIN(VIDEO_SCALE_H(gl->vp.dims), VIDEO_SCALE_H(gl->video_dims)),
GL_RGBA, GL_UNSIGNED_BYTE,
gl->readback_buffer_screenshot);
if (gl->scrgb.active && gl->scrgb.fbo
&& !gl->video_info.source_hdr10)
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
else if (gl->flags & GL3_FLAG_PBO_READBACK_ENABLE)
{
/* If recording has stopped, tear down PBO readback */
if (!(gl->flags & GL3_FLAG_GPU_RECORDING))
{
gl3_deinit_pbo_readback(gl);
gl->flags &= ~GL3_FLAG_PBO_READBACK_ENABLE;
}
else
{
#ifdef HAVE_MENU
/* Don't readback if we're in menu mode. */
if (!(gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE))
#endif
gl3_pbo_async_readback(gl);
}
}
gl3_hw_ring_drawn(gl);
gl->present_wait = 0;
if (gl->ctx_driver->swap_buffers)
{
/* timed: see gl3_get_last_present_wait() */
retro_time_t swap_at = cpu_features_get_time_usec();
gl->ctx_driver->swap_buffers(gl->ctx_data);
gl->present_wait = cpu_features_get_time_usec() - swap_at;
}
/* Emscripten has to do black frame insertion in its main loop */
#ifndef __EMSCRIPTEN__
/* Disable BFI during fast forward, slow-motion,
* and pause to prevent flicker. */
if (
video_info->black_frame_insertion
&& !video_info->input_driver_nonblock_state
&& !video_info->runloop_is_slowmotion
&& !video_info->runloop_is_paused
&& !(gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE))
{
if (video_info->bfi_dark_frames > video_info->black_frame_insertion)
video_info->bfi_dark_frames = video_info->black_frame_insertion;
/* BFI now handles variable strobe strength, like on-off-off, vs on-on-off for 180hz.
This needs to be done with duping frames instead of increased swap intervals for
a couple reasons. Swap interval caps out at 4 in most all apis as of coding,
and seems to be flat ignored >1 at least in modern Windows for some older APIs. */
bfi_light_frames = video_info->black_frame_insertion - video_info->bfi_dark_frames;
if (bfi_light_frames > 0 && !(gl->flags & GL3_FLAG_FRAME_DUPE_LOCK))
{
gl->flags |= GL3_FLAG_FRAME_DUPE_LOCK;
while (bfi_light_frames > 0)
{
if (!(gl3_frame(gl, NULL, 0, frame_count, 0, msg, video_info)))
{
gl->flags &= ~GL3_FLAG_FRAME_DUPE_LOCK;
return false;
}
--bfi_light_frames;
}
gl->flags &= ~GL3_FLAG_FRAME_DUPE_LOCK;
}
for (n = 0; n < video_info->bfi_dark_frames; ++n)
{
if (!(gl->flags & GL3_FLAG_FRAME_DUPE_LOCK))
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (gl->ctx_driver->swap_buffers)
gl->ctx_driver->swap_buffers(gl->ctx_data);
}
}
}
#endif
/* Frame duping for Shader Subframes, don't combine with swap_interval > 1, BFI.
Also, a major logical use of shader sub-frames will still be shader implemented BFI
or even rolling scan bfi, so we need to protect the menu/ff/etc from bad flickering
from improper settings, and unnecessary performance overhead for ff, screenshots etc. */
if ( (video_info->shader_subframes > 1)
&& !video_info->black_frame_insertion
&& !video_info->input_driver_nonblock_state
&& !video_info->runloop_is_slowmotion
&& !video_info->runloop_is_paused
&& (!(gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE))
&& (!(gl->flags & GL3_FLAG_FRAME_DUPE_LOCK)))
{
gl->flags |= GL3_FLAG_FRAME_DUPE_LOCK;
for (i = 1; i < video_info->shader_subframes; i++)
{
#ifdef HAVE_SLANG
if (!gl->chain.active)
{
gl3_filter_chain_set_shader_subframes(
filter_chain, video_info->shader_subframes);
gl3_filter_chain_set_current_shader_subframe(
filter_chain, i+1);
}
#endif
if (!gl3_frame(gl, NULL, 0, frame_count, 0, msg,
video_info))
{
gl->flags &= ~GL3_FLAG_FRAME_DUPE_LOCK;
return false;
}
}
gl->flags &= ~GL3_FLAG_FRAME_DUPE_LOCK;
}
if ( hard_sync
&& !input_driver_nonblock_state
)
gl3_fence_iterate(gl, hard_sync_frames);
glBindVertexArray(0);
/* Not under the ring: the core's context is current on the main
* thread, and this one has no business taking it. */
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
return true;
}
static uint32_t gl3_get_flags(void *data)
{
uint32_t flags = 0;
#ifdef HAVE_SLANG
gl3_t *gl = (gl3_t*)data;
/* Advertise a 10-bit source path only when there is genuinely
* somewhere to present it: the scRGB FP16 backbuffer. This is what
* SET_PIXEL_FORMAT tests before accepting HDR10_2101010 from a core,
* and unlike the D3D and Vulkan drivers - which have an HDR
* swapchain wherever they have HDR at all - glcore's HDR output
* exists only on contexts that advertise
* GFX_CTX_FLAGS_SCRGB_FRAMEBUFFER. Advertising unconditionally would
* make cores hand PQ frames to an SDR presentation path, which grades
* badly wrong rather than merely coarse.
*
* scrgb.active is cached at init from the context driver's flags;
* querying the context here instead would recurse, since this
* function is called from video_context_driver_get_flags(). */
if (gl && gl->scrgb.active)
BIT32_SET(flags, GFX_CTX_FLAGS_SCREEN_10BPC_SOURCE);
#endif
BIT32_SET(flags, GFX_CTX_FLAGS_HARD_SYNC);
BIT32_SET(flags, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION);
BIT32_SET(flags, GFX_CTX_FLAGS_MENU_FRAME_FILTERING);
BIT32_SET(flags, GFX_CTX_FLAGS_SCREENSHOTS_SUPPORTED);
BIT32_SET(flags, GFX_CTX_FLAGS_OVERLAY_BEHIND_MENU_SUPPORTED);
BIT32_SET(flags, GFX_CTX_FLAGS_SUBFRAME_SHADERS);
BIT32_SET(flags, GFX_CTX_FLAGS_FAST_TOGGLE_SHADERS);
return flags;
}
static void gl3_set_aspect_ratio(void *data, unsigned aspect_ratio_idx)
{
gl3_t *gl = (gl3_t*)data;
if (gl)
gl->flags |= (GL3_FLAG_KEEP_ASPECT | GL3_FLAG_SHOULD_RESIZE);
}
static void gl3_apply_state_changes(void *data)
{
gl3_t *gl = (gl3_t*)data;
if (gl)
gl->flags |= GL3_FLAG_SHOULD_RESIZE;
}
static struct video_shader *gl3_get_current_shader(void *data)
{
gl3_t *gl = (gl3_t*)data;
if (!gl)
return NULL;
if (gl->chain.active)
return gl->chain.shader->get_current_shader(gl->chain.shader_data);
#ifdef HAVE_SLANG
else
{
if (gl->filter_chain)
return gl3_filter_chain_get_preset(gl->filter_chain);
}
#endif
return NULL;
}
#ifdef HAVE_THREADS
typedef struct
{
gl3_t *gl;
void *payload;
uintptr_t handle;
} gl3_texture_cmd_t;
static uintptr_t video_texture_load_wrap_gl3_mipmap(void *data)
{
GLuint id = 0;
gl3_texture_cmd_t *cmd = (gl3_texture_cmd_t*)data;
gl3_t *gl = cmd->gl;
void *image = cmd->payload;
if (gl && gl->ctx_driver->make_current)
gl->ctx_driver->make_current(false);
if (image)
video_texture_load_gl3((struct texture_image*)image,
TEXTURE_FILTER_MIPMAP_LINEAR, &id);
return (int)id;
}
static uintptr_t video_texture_load_wrap_gl3(void *data)
{
GLuint id = 0;
gl3_texture_cmd_t *cmd = (gl3_texture_cmd_t*)data;
gl3_t *gl = cmd->gl;
void *image = cmd->payload;
if (gl && gl->ctx_driver->make_current)
gl->ctx_driver->make_current(false);
if (image)
video_texture_load_gl3((struct texture_image*)image,
TEXTURE_FILTER_LINEAR, &id);
return (int)id;
}
static uintptr_t video_texture_unload_wrap_gl3(void *data)
{
GLuint glid;
gl3_texture_cmd_t *cmd = (gl3_texture_cmd_t*)data;
gl3_t *gl = cmd->gl;
uintptr_t id = (uintptr_t)cmd->payload;
if (gl && gl->ctx_driver->make_current)
gl->ctx_driver->make_current(false);
glid = (GLuint)id;
gl3_fp16_forget(glid);
glDeleteTextures(1, &glid);
return 0;
}
#endif
static uintptr_t gl3_load_texture(void *video_data, void *data,
bool threaded, enum texture_filter_type filter_type)
{
GLuint id = 0;
#ifdef HAVE_THREADS
if (threaded)
{
gl3_texture_cmd_t cmd;
custom_command_method_t func = video_texture_load_wrap_gl3;
cmd.gl = (gl3_t*)video_data;
cmd.payload = data;
switch (filter_type)
{
case TEXTURE_FILTER_MIPMAP_LINEAR:
case TEXTURE_FILTER_MIPMAP_NEAREST:
func = video_texture_load_wrap_gl3_mipmap;
break;
default:
break;
}
return video_thread_texture_handle(&cmd, func);
}
#endif
video_texture_load_gl3((struct texture_image*)data, filter_type, &id);
return id;
}
/* Same-size contents into a texture gl3_load_texture made: the
* immutable storage stays, glTexSubImage2D rewrites level 0. Mip
* levels are not regenerated; streaming textures are loaded with
* TEXTURE_FILTER_LINEAR. */
static void gl3_update_texture_internal(uintptr_t id,
const struct texture_image *ti)
{
glBindTexture(GL_TEXTURE_2D, (GLuint)id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ti->width, ti->height,
GL_RGBA,
ti->fp16 ? GL_HALF_FLOAT
: ti->pix10 ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE,
ti->pixels);
glBindTexture(GL_TEXTURE_2D, 0);
}
#ifdef HAVE_THREADS
static uintptr_t video_texture_update_wrap_gl3(void *data)
{
gl3_texture_cmd_t *cmd = (gl3_texture_cmd_t*)data;
gl3_t *gl = cmd->gl;
if (gl && gl->ctx_driver->make_current)
gl->ctx_driver->make_current(false);
gl3_update_texture_internal(cmd->handle,
(const struct texture_image*)cmd->payload);
return 1;
}
#endif
static enum video_texture_update gl3_update_texture(void *video_data,
uintptr_t id, const struct texture_image *ti, bool threaded)
{
if (!id || !ti || !ti->pixels)
return VIDEO_TEXTURE_UPDATE_REFUSED;
#ifdef HAVE_THREADS
if (threaded)
{
gl3_texture_cmd_t cmd;
cmd.gl = (gl3_t*)video_data;
cmd.payload = (void*)ti;
cmd.handle = id;
video_thread_texture_handle(&cmd, video_texture_update_wrap_gl3);
return VIDEO_TEXTURE_UPDATE_DONE;
}
#endif
gl3_update_texture_internal(id, ti);
return VIDEO_TEXTURE_UPDATE_DONE;
}
static void gl3_unload_texture(void *data, bool threaded,
uintptr_t id)
{
GLuint glid;
if (!id)
return;
#ifdef HAVE_THREADS
if (threaded)
{
gl3_texture_cmd_t cmd;
custom_command_method_t func = video_texture_unload_wrap_gl3;
cmd.gl = (gl3_t*)data;
cmd.payload = (void*)id;
video_thread_texture_handle(&cmd, func);
return;
}
#endif
glid = (GLuint)id;
gl3_fp16_forget(glid);
glDeleteTextures(1, &glid);
}
static void gl3_set_video_mode(void *data, unsigned dims,
bool fullscreen)
{
gl3_t *gl = (gl3_t*)data;
if (gl->ctx_driver->set_video_mode)
gl->ctx_driver->set_video_mode(gl->ctx_data,
dims, fullscreen);
}
static void gl3_show_mouse(void *data, bool state)
{
gl3_t *gl = (gl3_t*)data;
if (gl && gl->ctx_driver->show_mouse)
gl->ctx_driver->show_mouse(gl->ctx_data, state);
}
static void gl3_set_texture_frame(void *data,
const void *frame, bool rgb32, unsigned dims,
float alpha)
{
unsigned base_size = rgb32 ? sizeof(uint32_t) : sizeof(uint16_t);
gl3_t *gl = (gl3_t*)data;
bool recreate;
/* What the last frame carried, not what the setting says now: the
* video thread applies this in thread_update_driver_state(). */
GLenum menu_filter;
if (!gl)
return;
menu_filter = gl->frame_menu_linear_filter
? GL_LINEAR : GL_NEAREST;
if (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
gl->ctx_driver->bind_hw_render(gl->ctx_data, false);
/* glTexStorage2D is immutable — the texture object must be
* recreated whenever dimensions or pixel format change. For
* same-size same-format updates we keep the existing texture
* and just stream new pixel data via glTexSubImage2D. */
recreate = (gl->menu_texture == 0)
|| (gl->menu_texture_dims != dims)
|| (gl->menu_texture_rgb32 != rgb32);
if (recreate)
{
if (gl->menu_texture != 0)
glDeleteTextures(1, &gl->menu_texture);
glGenTextures(1, &gl->menu_texture);
glBindTexture(GL_TEXTURE_2D, gl->menu_texture);
glTexStorage2D(GL_TEXTURE_2D, 1, rgb32
? GL_RGBA8 : GL_RGBA4, VIDEO_SCALE_W(dims), VIDEO_SCALE_H(dims));
gl->menu_texture_dims = dims;
gl->menu_texture_rgb32 = rgb32;
}
else
glBindTexture(GL_TEXTURE_2D, gl->menu_texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, base_size);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
VIDEO_SCALE_W(dims), VIDEO_SCALE_H(dims), GL_RGBA, rgb32
? GL_UNSIGNED_BYTE
: GL_UNSIGNED_SHORT_4_4_4_4, frame);
/* Filter/wrap/swizzle are set every call: the linear-filter menu
* setting can toggle at runtime, and the cost of these calls is
* negligible compared to the pixel upload above. */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, menu_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, menu_filter);
if (rgb32)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
glBindTexture(GL_TEXTURE_2D, 0);
gl->menu_texture_alpha = alpha;
if ( (gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
&& !gl3_core_context_is_mains(gl))
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
}
static void gl3_set_texture_enable(void *data, bool state, bool fullscreen)
{
gl3_t *gl = (gl3_t*)data;
if (!gl)
return;
if (state)
gl->flags |= GL3_FLAG_MENU_TEXTURE_ENABLE;
else
gl->flags &= ~GL3_FLAG_MENU_TEXTURE_ENABLE;
if (fullscreen)
gl->flags |= GL3_FLAG_MENU_TEXTURE_FULLSCREEN;
else
gl->flags &= ~GL3_FLAG_MENU_TEXTURE_FULLSCREEN;
}
/* Whether the threaded wrapper's hardware ring will drive this driver:
* decided at init, when the wrapper is already up, from the core's
* context type and the driver name. */
static bool gl3_hw_ring_expected(void)
{
#ifdef HAVE_THREADS
return video_driver_thread_wrapper_active() && video_thread_hw_allowed();
#else
return false;
#endif
}
/* Whether the core's context belongs to the main thread: it does once
* the wrapper's ring has taken it (the flag), and it will as soon as
* the ring is set up (expected, from init on). Every place this
* thread would take that context for itself asks this, and only this.
* Asking only the flag let a bind during init - the stock shader's
* load is one - take the context here after the ring was decided but
* before it was set up, and the main thread's own bind then failed:
* a core with no current context, and no GL function resolved. */
static bool gl3_core_context_is_mains(gl3_t *gl)
{
/* Both bits are set on the video thread inside blocking command
* handlers (init, ring bring-up) while the main thread is parked
* in the wrapper's send-and-wait, and read here from the frame
* path. The live-settings consultation this replaces read
* settings->arrays.video_driver every frame from the video
* thread with the main thread running free. */
return (gl->flags & (GL3_FLAG_HW_RING | GL3_FLAG_HW_RING_EXPECTED)) != 0;
}
/* --- the threaded wrapper's hardware ring ------------------------------ */
/* ARB_sync names the hardware ring uses; the PowerPC macOS headers
* have none of them. */
#ifndef GL_SYNC_GPU_COMMANDS_COMPLETE
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
#endif
#ifndef GL_SYNC_FLUSH_COMMANDS_BIT
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
#endif
#ifndef GL_TIMEOUT_IGNORED
#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull
#endif
#ifndef GL_TIMEOUT_EXPIRED
#define GL_TIMEOUT_EXPIRED 0x911B
#endif
/* The core's context, current on the caller - the main thread. From
* here on the frame never takes it back. */
static bool gl3_hw_ring_context_new(void *data, void **ctx)
{
gl3_t *gl = (gl3_t*)data;
if (!gl || !ctx || !(gl->flags & GL3_FLAG_USE_SHARED_CONTEXT)
|| !gl->ctx_driver || !gl->ctx_driver->bind_hw_render)
return false;
gl->ctx_driver->bind_hw_render(gl->ctx_data, true);
gl->flags |= GL3_FLAG_HW_RING;
*ctx = gl;
return true;
}
/* Called on the thread that holds the core's context - the main
* thread - before the driver is freed. The context is given up here,
* where it is current; the driver's own bind would make the video
* thread's context current here instead. Without the hook the context
* driver's teardown copes with a still-current context, which GLX and
* WGL define. */
static void gl3_hw_ring_context_free(void *data, void *ctx)
{
gl3_t *gl = (gl3_t*)data;
(void)ctx;
if (!gl)
return;
gl->flags &= ~GL3_FLAG_HW_RING;
if (gl->ctx_driver && gl->ctx_driver->release_current)
gl->ctx_driver->release_current(gl->ctx_data);
}
static uintptr_t gl3_hw_ring_framebuffer(void *data, unsigned slot)
{
gl3_t *gl = (gl3_t*)data;
if (!gl || slot >= 3)
return 0;
return gl->hw_ring_fbo[slot];
}
/* Main thread, the core's context: a fence after the core's rendering
* into the slot, flushed so the other thread's wait can see it. */
static bool gl3_hw_ring_capture(void *data, unsigned slot,
const void *source, unsigned format)
{
gl3_t *gl = (gl3_t*)data;
(void)source; (void)format;
if (!gl || slot >= 3)
return false;
if (gl->hw_ring_sync[slot])
glDeleteSync((GLsync)gl->hw_ring_sync[slot]);
gl->hw_ring_sync[slot] = (void*)glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
return true;
}
/* Video thread: wait the core's fence on the server, and read the
* slot's texture as the frame's HW-render texture. */
static bool gl3_hw_ring_present_slot(void *data, unsigned slot)
{
gl3_t *gl = (gl3_t*)data;
if (!gl || slot >= 3 || !gl->hw_ring_texture[slot])
return false;
if (gl->hw_ring_sync[slot])
{
glWaitSync((GLsync)gl->hw_ring_sync[slot], 0, GL_TIMEOUT_IGNORED);
glDeleteSync((GLsync)gl->hw_ring_sync[slot]);
gl->hw_ring_sync[slot] = NULL;
}
gl->hw_render_texture = gl->hw_ring_texture[slot];
return true;
}
typedef struct { void *sync; } gl3_ring_fence_t;
static bool gl3_hw_ring_fence_new(void *data, void **fence)
{
gl3_ring_fence_t *f;
(void)data;
if (!fence || !(f = (gl3_ring_fence_t*)calloc(1, sizeof(*f))))
return false;
*fence = f;
return true;
}
static void gl3_hw_ring_fence_free(void *data, void *fence)
{
gl3_ring_fence_t *f = (gl3_ring_fence_t*)fence;
(void)data;
if (!f)
return;
if (f->sync)
glDeleteSync((GLsync)f->sync);
free(f);
}
static void gl3_hw_ring_fence_signal(void *data, void *fence)
{
gl3_t *gl = (gl3_t*)data;
gl3_ring_fence_t *f = (gl3_ring_fence_t*)fence;
if (!f)
return;
if (f->sync)
glDeleteSync((GLsync)f->sync);
if (gl && gl->hw_ring_done_sync)
{
f->sync = gl->hw_ring_done_sync;
gl->hw_ring_done_sync = NULL;
}
else
f->sync = (void*)glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
}
static bool gl3_hw_ring_fence_wait(void *data, void *fence, unsigned timeout_us)
{
gl3_ring_fence_t *f = (gl3_ring_fence_t*)fence;
(void)data;
if (!f || !f->sync)
return true;
if (glClientWaitSync((GLsync)f->sync, GL_SYNC_FLUSH_COMMANDS_BIT,
timeout_us == HW_RING_WAIT_FOREVER
? GL_TIMEOUT_IGNORED : (GLuint64)timeout_us * 1000) == GL_TIMEOUT_EXPIRED)
return false;
/* Not deleted here: the video thread made it and may still be in
* the swap that follows it. It goes when the cell is signalled again */
return true;
}
static uintptr_t gl3_get_current_framebuffer(void *data)
{
gl3_t *gl = (gl3_t*)data;
if (gl && (gl->flags & GL3_FLAG_HW_RENDER_ENABLE))
return gl->hw_render_fbo;
return 0;
}
static retro_proc_address_t gl3_get_proc_address(
void *data, const char *sym)
{
gl3_t *gl = (gl3_t*)data;
if (gl && gl->ctx_driver->get_proc_address)
return gl->ctx_driver->get_proc_address(sym);
return NULL;
}
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
#endif
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
#endif
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#endif
#ifndef GL_COMPRESSED_RGBA_BPTC_UNORM
#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C
#endif
static GLenum gl3_gpu_format(enum texture_gpu_format fmt)
{
switch (fmt)
{
case TEXTURE_GPU_FORMAT_BC1: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
case TEXTURE_GPU_FORMAT_BC2: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
case TEXTURE_GPU_FORMAT_BC3: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
case TEXTURE_GPU_FORMAT_BC7: return GL_COMPRESSED_RGBA_BPTC_UNORM;
default: break;
}
return 0;
}
static bool gl3_supports_texture_format(void *data,
enum texture_gpu_format fmt)
{
(void)data;
switch (fmt)
{
/* RGB10_A2 with UNSIGNED_INT_2_10_10_10_REV is core in GL 3.0
* and GLES 3.0, and load and update both take it. */
case TEXTURE_GPU_FORMAT_RGB10A2:
return true;
/* RGBA16F from half floats is core in GL 3.0 and GLES 3.0. */
case TEXTURE_GPU_FORMAT_RGBA16F:
return true;
/* The linear program shows such a texture as linear scRGB while
* the backbuffer is scRGB; in SDR there is no linear light. */
case TEXTURE_GPU_FORMAT_SCRGB:
/* Only where the menu's layer is FP16: an 8-bit one would clip
* the highlights the tone-mapped path rolls off */
if (data)
{
const gl3_t *gl = (const gl3_t*)data;
return gl->scrgb.active && (gl->video_info.source_hdr10
? gl->scrgb.ui_fbo != 0 : gl->scrgb.tex_fp16);
}
return false;
case TEXTURE_GPU_FORMAT_BC1:
case TEXTURE_GPU_FORMAT_BC2:
case TEXTURE_GPU_FORMAT_BC3:
return gl_query_extension("EXT_texture_compression_s3tc")
|| gl_query_extension("ANGLE_texture_compression_dxt5")
|| gl_query_extension("EXT_texture_compression_dxt1");
case TEXTURE_GPU_FORMAT_BC7:
return gl_query_extension("ARB_texture_compression_bptc")
|| gl_query_extension("EXT_texture_compression_bptc");
default:
break;
}
return false;
}
static uintptr_t gl3_upload_texture_compressed(
const struct texture_compressed *tc,
enum texture_filter_type filter_type)
{
GLuint id = 0;
GLenum ifmt;
unsigned i;
GLint minf;
GLint magf;
bool nearest;
if (!tc || tc->num_mips == 0)
return 0;
ifmt = gl3_gpu_format(tc->format);
if (!ifmt)
return 0;
nearest = (filter_type == TEXTURE_FILTER_NEAREST
|| filter_type == TEXTURE_FILTER_MIPMAP_NEAREST);
magf = nearest ? GL_NEAREST : GL_LINEAR;
if (tc->num_mips > 1)
minf = nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR;
else
minf = magf;
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for (i = 0; i < tc->num_mips; i++)
glCompressedTexImage2D(GL_TEXTURE_2D, (GLint)i, ifmt,
(GLsizei)tc->mips[i].width, (GLsizei)tc->mips[i].height, 0,
(GLsizei)tc->mips[i].size, tc->mips[i].data);
#ifdef GL_TEXTURE_MAX_LEVEL
/* Not defined by GLES2 / Orbis (PS4) GL headers; only meaningful when
* the file actually carries a mip chain. */
if (tc->num_mips > 1)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL,
(GLint)(tc->num_mips - 1));
#endif
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minf);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magf);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
return (uintptr_t)id;
}
#ifdef HAVE_THREADS
typedef struct
{
gl3_t *gl;
const struct texture_compressed *tc;
enum texture_filter_type filter;
} gl3_texture_compressed_cmd_t;
static uintptr_t video_texture_load_wrap_gl3_compressed(void *data)
{
gl3_texture_compressed_cmd_t *cmd = (gl3_texture_compressed_cmd_t*)data;
gl3_t *gl = cmd->gl;
if (gl && gl->ctx_driver->make_current)
gl->ctx_driver->make_current(false);
return gl3_upload_texture_compressed(cmd->tc, cmd->filter);
}
#endif
static uintptr_t gl3_load_texture_compressed(void *video_data,
const struct texture_compressed *tc, bool threaded,
enum texture_filter_type filter_type)
{
#ifdef HAVE_THREADS
if (threaded)
{
gl3_texture_compressed_cmd_t cmd;
cmd.gl = (gl3_t*)video_data;
cmd.tc = tc;
cmd.filter = filter_type;
return video_thread_texture_handle(&cmd,
video_texture_load_wrap_gl3_compressed);
}
#else
(void)video_data;
(void)threaded;
#endif
return gl3_upload_texture_compressed(tc, filter_type);
}
/* When the most recent swap reached the display, as the window
* system's context reports it: the gl driver passes this on to the
* threaded presenter, and this one gave it nothing, so with glcore the
* presenter laid its vblanks from its own clock on every platform. */
static retro_time_t gl3_get_last_present_time(void *data)
{
gl3_t *gl = (gl3_t*)data;
if (gl && gl->ctx_driver && gl->ctx_driver->last_present_time)
return gl->ctx_driver->last_present_time(gl->ctx_data);
return 0;
}
/* How long the frame's swap took. With a swap interval set the swap
* waits for the vertical blank and returns when the frame is out, so
* that time is waiting, not drawing, and the frame is shown. */
static retro_time_t gl3_get_last_present_wait(void *data, bool *shown)
{
gl3_t *gl = (gl3_t*)data;
*shown = false;
if (!gl || !gl->present_vsynced)
return 0;
*shown = true;
return gl->present_wait;
}
static const video_poke_interface_t gl3_poke_interface = {
gl3_get_flags,
gl3_load_texture,
gl3_unload_texture,
gl3_set_video_mode,
NULL, /* refresh_rate - handled by display server */
NULL, /* set_filtering */
NULL, /* video_output_size - handled by display server */
NULL, /* video_output_prev - handled by display server */
NULL, /* video_output_next - handled by display server */
gl3_get_current_framebuffer,
gl3_get_proc_address,
gl3_set_aspect_ratio,
gl3_apply_state_changes,
gl3_set_texture_frame,
gl3_set_texture_enable,
font_driver_render_msg,
gl3_show_mouse,
NULL, /* grab_mouse_toggle */
gl3_get_current_shader,
NULL, /* get_current_software_framebuffer */
NULL, /* get_hw_render_interface */
NULL, /* set_hdr_menu_nits */
NULL, /* set_hdr_paper_white_nits */
NULL, /* set_hdr_expand_gamut */
NULL, /* set_hdr_scanlines */
NULL, /* set_hdr_subpixel_layout */
gl3_supports_texture_format,
gl3_load_texture_compressed,
NULL, /* present_last */
gl3_get_last_present_time,
NULL, /* hw_ring_install: Vulkan-shaped */
gl3_hw_ring_fence_new,
gl3_hw_ring_fence_free,
gl3_hw_ring_fence_signal,
gl3_hw_ring_fence_wait,
gl3_hw_ring_capture,
gl3_hw_ring_present_slot,
gl3_hw_ring_context_new,
gl3_hw_ring_context_free,
gl3_hw_ring_framebuffer,
gl3_update_texture,
NULL, /* get_swap_interval_cap */
NULL, /* texture_lend */
NULL, /* texture_lend_ready */
gl3_get_last_present_wait
};
static void gl3_get_poke_interface(void *data,
const video_poke_interface_t **iface)
{
(void)data;
*iface = &gl3_poke_interface;
}
#ifdef HAVE_GFX_WIDGETS
static bool gl3_gfx_widgets_enabled(void *data) { return true; }
#endif
static unsigned gl3_wrap_type_to_enum(enum gfx_wrap_type type)
{
switch (type)
{
case RARCH_WRAP_BORDER:
#ifdef HAVE_OPENGLES3
/* GLES does not support CLAMP_TO_BORDER until GLES 3.2.
* It is a deprecated feature in general. */
return GL_CLAMP_TO_EDGE;
#else
return GL_CLAMP_TO_BORDER;
#endif
case RARCH_WRAP_EDGE:
return GL_CLAMP_TO_EDGE;
case RARCH_WRAP_REPEAT:
return GL_REPEAT;
case RARCH_WRAP_MIRRORED_REPEAT:
return GL_MIRRORED_REPEAT;
default:
break;
}
return 0;
}
static bool gl3_has_windowed(void *data)
{
gl3_t *gl = (gl3_t*)data;
if (gl && gl->ctx_driver)
return gl->ctx_driver->has_windowed;
return false;
}
static bool gl3_focus(void *data)
{
gl3_t *gl = (gl3_t*)data;
if (gl && gl->ctx_driver && gl->ctx_driver->has_focus)
return gl->ctx_driver->has_focus(gl->ctx_data);
return true;
}
static font_renderer_t gl3_raster_font = {
gl3_raster_font_init,
gl3_raster_font_free,
gl3_raster_font_render_msg,
"glcore",
gl3_raster_font_get_glyph,
gl3_raster_font_bind_block,
gl3_raster_font_flush_block,
gl3_raster_font_get_message_width,
gl3_raster_font_get_line_metrics
};
video_driver_t video_gl3 = {
gl3_init,
gl3_frame,
gl3_set_nonblock_state,
gl3_alive,
gl3_focus,
gl3_suppress_screensaver,
gl3_has_windowed,
gl3_set_shader,
gl3_free,
"glcore",
gl3_set_viewport_wrapper,
gl3_set_rotation,
gl3_viewport_info,
gl3_read_viewport,
#ifdef HAVE_OVERLAY
gl3_get_overlay_interface,
#endif
gl3_get_poke_interface,
gl3_wrap_type_to_enum,
#ifdef HAVE_SLANG
gl3_shader_load_begin,
gl3_shader_load_step,
#else
NULL, /* shader_load_begin */
NULL, /* shader_load_step */
#endif
#ifdef HAVE_GFX_WIDGETS
gl3_gfx_widgets_enabled,
#endif
NULL, /* invalidate_hw_render_cache */
gl3_read_viewport_hdr,
&gl3_raster_font
};
gfx_display_ctx_driver_t gfx_display_ctx_gl3 = {
gfx_display_gl3_draw,
gfx_display_gl3_draw_pipeline,
gfx_display_gl3_blend_begin,
gfx_display_gl3_blend_end,
gfx_display_gl3_get_default_mvp,
gfx_display_gl3_get_default_vertices,
gfx_display_gl3_get_default_tex_coords,
&gl3_raster_font,
GFX_VIDEO_DRIVER_OPENGL_CORE,
"glcore",
false,
true,
gfx_display_gl3_scissor_begin,
gfx_display_gl3_scissor_end,
#ifdef HAVE_SLANG
gfx_display_gl3_mesh_draw
#else
NULL
#endif
};