mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
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.
6712 lines
216 KiB
C
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(¶ms, 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(¶ms, 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(¶ms, 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
|
|
};
|