1533 lines
38 KiB
C
1533 lines
38 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "gl33.h"
|
|
|
|
#include "../api.h"
|
|
#include "../common/backend.h"
|
|
#include "../common/matstack.h"
|
|
#include "../common/sprite_batch_internal.h"
|
|
#include "../glcommon/debug.h"
|
|
#include "../glcommon/vtable.h"
|
|
#include "common_buffer.h"
|
|
#include "framebuffer_async_read.h"
|
|
#include "framebuffer.h"
|
|
#include "index_buffer.h"
|
|
#include "shader_object.h"
|
|
#include "shader_program.h"
|
|
#include "texture.h"
|
|
#include "util/env.h"
|
|
#include "util/glm.h"
|
|
#include "vertex_array.h"
|
|
#include "vertex_buffer.h"
|
|
|
|
// #define GL33_DEBUG_TEXUNITS
|
|
// #define GL33_DRAW_STATS
|
|
|
|
typedef struct TextureUnit {
|
|
LIST_INTERFACE(struct TextureUnit);
|
|
|
|
Texture *active;
|
|
Texture *pending;
|
|
GLuint gl_handle;
|
|
GLenum bind_target;
|
|
GLenum locked_for_target;
|
|
int old_prio;
|
|
} TextureUnit;
|
|
|
|
#define TU_INDEX(unit) ((ptrdiff_t)((unit) - R.texunits.array))
|
|
|
|
static struct {
|
|
struct {
|
|
TextureUnit *array;
|
|
LIST_ANCHOR(TextureUnit) list;
|
|
TextureUnit *active;
|
|
TextureUnit *pending;
|
|
GLint limit;
|
|
} texunits;
|
|
|
|
struct {
|
|
Framebuffer *active;
|
|
Framebuffer *pending;
|
|
} framebuffer;
|
|
|
|
struct {
|
|
GLuint active;
|
|
GLuint pending;
|
|
} buffer_objects[GL33_NUM_BUFFER_BINDINGS];
|
|
|
|
struct {
|
|
GLuint active;
|
|
GLuint pending;
|
|
} vao;
|
|
|
|
struct {
|
|
GLuint gl_prog;
|
|
ShaderProgram *active;
|
|
ShaderProgram *pending;
|
|
} progs;
|
|
|
|
struct {
|
|
struct {
|
|
BlendMode active;
|
|
BlendMode pending;
|
|
} mode;
|
|
bool enabled;
|
|
} blend;
|
|
|
|
struct {
|
|
struct {
|
|
CullFaceMode active;
|
|
CullFaceMode pending;
|
|
} mode;
|
|
} cull_face;
|
|
|
|
struct {
|
|
struct {
|
|
DepthTestFunc active;
|
|
DepthTestFunc pending;
|
|
} func;
|
|
} depth_test;
|
|
|
|
struct {
|
|
r_capability_bits_t active;
|
|
r_capability_bits_t pending;
|
|
} capabilities;
|
|
|
|
struct {
|
|
FloatRect active;
|
|
FloatRect default_framebuffer;
|
|
} viewport;
|
|
|
|
struct {
|
|
IntRect pending;
|
|
IntRect active;
|
|
bool active_enabled;
|
|
} scissor;
|
|
|
|
Color color;
|
|
Color clear_color;
|
|
float clear_depth;
|
|
r_feature_bits_t features;
|
|
|
|
SDL_GLContext *gl_context;
|
|
SDL_Window *window;
|
|
|
|
#ifndef STATIC_GLES3
|
|
bool fb_sRGB_state;
|
|
#endif
|
|
|
|
#ifdef GL33_DRAW_STATS
|
|
struct {
|
|
hrtime_t last_draw;
|
|
hrtime_t draw_time;
|
|
uint draw_calls;
|
|
uint texture_rebinds;
|
|
} stats;
|
|
#endif
|
|
} R;
|
|
|
|
/*
|
|
* Internal functions
|
|
*/
|
|
|
|
static void gl33_set_framebuffer_sRGB_write(bool enable) {
|
|
#ifndef STATIC_GLES3
|
|
if(glext.version.is_es) {
|
|
return;
|
|
}
|
|
|
|
if(enable) {
|
|
if(!R.fb_sRGB_state) {
|
|
glEnable(GL_FRAMEBUFFER_SRGB);
|
|
R.fb_sRGB_state = true;
|
|
}
|
|
} else {
|
|
if(R.fb_sRGB_state) {
|
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
|
R.fb_sRGB_state = false;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static GLenum blendop_to_gl_blendop(BlendOp op) {
|
|
switch(op) {
|
|
case BLENDOP_ADD: return GL_FUNC_ADD;
|
|
case BLENDOP_SUB: return GL_FUNC_SUBTRACT;
|
|
case BLENDOP_REV_SUB: return GL_FUNC_REVERSE_SUBTRACT;
|
|
case BLENDOP_MAX: return GL_MAX;
|
|
case BLENDOP_MIN: return GL_MIN;
|
|
}
|
|
|
|
UNREACHABLE;
|
|
}
|
|
|
|
static GLenum blendfactor_to_gl_blendfactor(BlendFactor factor) {
|
|
switch(factor) {
|
|
case BLENDFACTOR_DST_ALPHA: return GL_DST_ALPHA;
|
|
case BLENDFACTOR_INV_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA;
|
|
case BLENDFACTOR_DST_COLOR: return GL_DST_COLOR;
|
|
case BLENDFACTOR_INV_DST_COLOR: return GL_ONE_MINUS_DST_COLOR;
|
|
case BLENDFACTOR_INV_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA;
|
|
case BLENDFACTOR_INV_SRC_COLOR: return GL_ONE_MINUS_SRC_COLOR;
|
|
case BLENDFACTOR_ONE: return GL_ONE;
|
|
case BLENDFACTOR_SRC_ALPHA: return GL_SRC_ALPHA;
|
|
case BLENDFACTOR_SRC_COLOR: return GL_SRC_COLOR;
|
|
case BLENDFACTOR_ZERO: return GL_ZERO;
|
|
}
|
|
|
|
UNREACHABLE;
|
|
}
|
|
|
|
static inline GLenum r_cull_to_gl_cull(CullFaceMode mode) {
|
|
switch(mode) {
|
|
case CULL_BACK:
|
|
return GL_BACK;
|
|
|
|
case CULL_FRONT:
|
|
return GL_FRONT;
|
|
|
|
case CULL_BOTH:
|
|
return GL_FRONT_AND_BACK;
|
|
|
|
default: UNREACHABLE;
|
|
}
|
|
}
|
|
|
|
static inline void gl33_stats_pre_draw(void) {
|
|
#ifdef GL33_DRAW_STATS
|
|
R.stats.last_draw = time_get();
|
|
R.stats.draw_calls++;
|
|
#endif
|
|
}
|
|
|
|
static inline void gl33_stats_post_draw(void) {
|
|
#ifdef GL33_DRAW_STATS
|
|
R.stats.draw_time += (time_get() - R.stats.last_draw);
|
|
#endif
|
|
}
|
|
|
|
static inline void gl33_stats_post_frame(void) {
|
|
#ifdef GL33_DRAW_STATS
|
|
log_debug("%.1fµs spent in %u draw calls", R.stats.draw_time / (HRTIME_RESOLUTION / 1000000.0) , R.stats.draw_calls);
|
|
log_debug("%u texture rebinds", R.stats.texture_rebinds);
|
|
memset(&R.stats, 0, sizeof(R.stats));
|
|
#endif
|
|
}
|
|
|
|
static void gl33_init_texunits(void) {
|
|
GLint texunits_available, texunits_capped, texunits_max, texunits_min = 8;
|
|
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &texunits_available);
|
|
|
|
if(texunits_available < texunits_min) {
|
|
log_fatal("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS is %i; at least %i is expected.", texunits_available, texunits_min);
|
|
}
|
|
|
|
texunits_max = env_get_int("GL33_MAX_NUM_TEXUNITS", 32);
|
|
|
|
if(texunits_max == 0) {
|
|
texunits_max = texunits_available;
|
|
} else {
|
|
texunits_max = clamp(texunits_max, texunits_min, texunits_available);
|
|
}
|
|
|
|
texunits_capped = min(texunits_max, texunits_available);
|
|
R.texunits.limit = env_get_int("GL33_NUM_TEXUNITS", texunits_capped);
|
|
|
|
if(R.texunits.limit == 0) {
|
|
R.texunits.limit = texunits_available;
|
|
} else {
|
|
R.texunits.limit = clamp(R.texunits.limit, texunits_min, texunits_available);
|
|
}
|
|
|
|
R.texunits.array = ALLOC_ARRAY(R.texunits.limit, typeof(*R.texunits.array));
|
|
R.texunits.active = R.texunits.array;
|
|
|
|
for(int i = 0; i < R.texunits.limit; ++i) {
|
|
TextureUnit *u = R.texunits.array + i;
|
|
alist_append(&R.texunits.list, u);
|
|
}
|
|
|
|
log_info("Using %i texturing units (%i available)", R.texunits.limit, texunits_available);
|
|
}
|
|
|
|
static void gl33_get_viewport(FloatRect *vp) {
|
|
IntRect vp_int;
|
|
glGetIntegerv(GL_VIEWPORT, &vp_int.x);
|
|
vp->x = vp_int.x;
|
|
vp->y = vp_int.y;
|
|
vp->w = vp_int.w;
|
|
vp->h = vp_int.h;
|
|
}
|
|
|
|
static void gl33_set_viewport(const FloatRect *vp) {
|
|
glViewport(vp->x, vp->y, vp->w, vp->h);
|
|
}
|
|
|
|
#ifndef STATIC_GLES3
|
|
static void gl41_get_viewport(FloatRect *vp) {
|
|
glGetFloati_v(GL_VIEWPORT, 0, &vp->x);
|
|
}
|
|
|
|
static void gl41_set_viewport(const FloatRect *vp) {
|
|
glViewportIndexedfv(0, &vp->x);
|
|
}
|
|
#endif
|
|
|
|
static SDL_GLContext gl33_create_context(SDL_Window *window) {
|
|
SDL_GLContext ctx = SDL_GL_CreateContext(window);
|
|
|
|
int gl_profile;
|
|
SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &gl_profile);
|
|
|
|
if(!ctx && gl_profile != SDL_GL_CONTEXT_PROFILE_ES) {
|
|
log_error("Failed to create OpenGL context: %s", SDL_GetError());
|
|
log_warn("Attempting to create a fallback context");
|
|
SDL_GL_ResetAttributes();
|
|
ctx = SDL_GL_CreateContext(window);
|
|
}
|
|
|
|
if(!ctx) {
|
|
log_fatal("Failed to create OpenGL context: %s", SDL_GetError());
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
static void gl33_init_context(SDL_Window *window) {
|
|
R.gl_context = GLVT.create_context(window);
|
|
|
|
glcommon_load_functions();
|
|
glcommon_check_capabilities();
|
|
|
|
if(glcommon_debug_requested()) {
|
|
glcommon_debug_enable();
|
|
}
|
|
|
|
gl33_init_texunits();
|
|
gl33_set_clear_depth(1);
|
|
gl33_set_clear_color(RGBA(0, 0, 0, 0));
|
|
|
|
#ifdef STATIC_GLES3
|
|
GLVT.get_viewport = gl33_get_viewport;
|
|
GLVT.set_viewport = gl33_set_viewport;
|
|
#else
|
|
if(glext.viewport_array) {
|
|
GLVT.get_viewport = gl41_get_viewport;
|
|
GLVT.set_viewport = gl41_set_viewport;
|
|
} else {
|
|
GLVT.get_viewport = gl33_get_viewport;
|
|
GLVT.set_viewport = gl33_set_viewport;
|
|
}
|
|
#endif
|
|
|
|
glFrontFace(GL_CW);
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
GLVT.get_viewport(&R.viewport.default_framebuffer);
|
|
|
|
if(HAVE_GL_FUNC(glReadBuffer)) {
|
|
glReadBuffer(GL_BACK);
|
|
}
|
|
|
|
#ifndef STATIC_GLES3
|
|
if(glext.seamless_cubemap) {
|
|
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
|
}
|
|
#endif
|
|
|
|
R.viewport.active = R.viewport.default_framebuffer;
|
|
|
|
if(glext.instanced_arrays) {
|
|
R.features |= r_feature_bit(RFEAT_DRAW_INSTANCED);
|
|
}
|
|
|
|
if(glext.depth_texture) {
|
|
R.features |= r_feature_bit(RFEAT_DEPTH_TEXTURE);
|
|
}
|
|
|
|
if(glext.draw_buffers) {
|
|
R.features |= r_feature_bit(RFEAT_FRAMEBUFFER_MULTIPLE_OUTPUTS);
|
|
}
|
|
|
|
if(glext.texture_swizzle) {
|
|
R.features |= r_feature_bit(RFEAT_TEXTURE_SWIZZLE);
|
|
}
|
|
|
|
if(!glext.version.is_es || GLES_ATLEAST(3, 0)) {
|
|
R.features |= r_feature_bit(RFEAT_PARTIAL_MIPMAPS);
|
|
}
|
|
|
|
R.features |= r_feature_bit(RFEAT_TEXTURE_BOTTOMLEFT_ORIGIN);
|
|
|
|
if(glext.clear_texture) {
|
|
_r_backend.funcs.texture_clear = gl44_texture_clear;
|
|
}
|
|
}
|
|
|
|
static void gl33_apply_capability(RendererCapability cap, bool value) {
|
|
switch(cap) {
|
|
case RCAP_DEPTH_TEST:
|
|
(value ? glEnable : glDisable)(GL_DEPTH_TEST);
|
|
break;
|
|
|
|
case RCAP_DEPTH_WRITE:
|
|
glDepthMask(value);
|
|
break;
|
|
|
|
case RCAP_CULL_FACE:
|
|
(value ? glEnable : glDisable)(GL_CULL_FACE);
|
|
break;
|
|
|
|
default: UNREACHABLE;
|
|
}
|
|
}
|
|
|
|
static int get_framebuffer_height(Framebuffer *fb) {
|
|
int fb_height = 0;
|
|
|
|
if(fb == NULL) {
|
|
SDL_Window *win = SDL_GL_GetCurrentWindow();
|
|
assume(win != NULL);
|
|
SDL_GL_GetDrawableSize(win, NULL, &fb_height);
|
|
} else {
|
|
for(FramebufferAttachment a = FRAMEBUFFER_ATTACH_COLOR0; a < FRAMEBUFFER_MAX_ATTACHMENTS; ++a) {
|
|
if(fb->attachments[a] != NULL) {
|
|
fb_height = fb->attachments[a]->params.height;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(fb_height > 0);
|
|
return fb_height;
|
|
}
|
|
|
|
static void transform_viewport_origin(Framebuffer *fb, FloatRect *vp) {
|
|
int fb_height = get_framebuffer_height(fb);
|
|
vp->y = fb_height - vp->y - vp->h;
|
|
}
|
|
|
|
static inline FloatRect* get_framebuffer_viewport(Framebuffer *fb) {
|
|
if(fb == NULL) {
|
|
return &R.viewport.default_framebuffer;
|
|
}
|
|
|
|
return &fb->viewport;
|
|
}
|
|
|
|
static void gl33_sync_viewport(void) {
|
|
FloatRect *vp = get_framebuffer_viewport(R.framebuffer.pending);
|
|
|
|
if(memcmp(&R.viewport.active, vp, sizeof(R.viewport.active))) {
|
|
R.viewport.active = *vp;
|
|
GLVT.set_viewport(vp);
|
|
}
|
|
}
|
|
|
|
void gl33_sync_scissor(void) {
|
|
bool pending_enabled =
|
|
R.scissor.pending.w &&
|
|
R.scissor.pending.h;
|
|
|
|
if(!pending_enabled) {
|
|
if(R.scissor.active_enabled) {
|
|
glDisable(GL_SCISSOR_TEST);
|
|
R.scissor.active_enabled = false;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if(!R.scissor.active_enabled) {
|
|
glEnable(GL_SCISSOR_TEST);
|
|
R.scissor.active_enabled = true;
|
|
}
|
|
|
|
IntRect scissor = R.scissor.pending;
|
|
int fb_height = get_framebuffer_height(R.framebuffer.pending);
|
|
scissor.y = fb_height - scissor.y - scissor.h;
|
|
|
|
if(memcmp(&R.scissor.active, &scissor, sizeof(R.scissor.active))) {
|
|
glScissor(scissor.x, scissor.y, scissor.w, scissor.h);
|
|
R.scissor.active = scissor;
|
|
}
|
|
}
|
|
|
|
static IntExtent gl33_get_default_framebuffer_size(void) {
|
|
IntExtent s;
|
|
// TODO: cache this at window creation time and refresh on resize events?
|
|
SDL_GL_GetDrawableSize(R.window, &s.w, &s.h);
|
|
return s;
|
|
}
|
|
|
|
static void gl33_get_output_size(Framebuffer *fb, FramebufferAttachment a, vec2_noalign out) {
|
|
if(a == FRAMEBUFFER_ATTACH_NONE) {
|
|
out[0] = 0;
|
|
out[1] = 0;
|
|
return;
|
|
}
|
|
|
|
FramebufferAttachmentQueryResult q = gl33_framebuffer_query_attachment(fb, a);
|
|
|
|
if(q.texture == NULL) {
|
|
out[0] = 0;
|
|
out[1] = 0;
|
|
return;
|
|
}
|
|
|
|
uint w, h;
|
|
gl33_texture_get_size(q.texture, q.miplevel, &w, &h);
|
|
out[0] = w;
|
|
out[1] = h;
|
|
}
|
|
|
|
static void gl33_sync_magic_uniforms(void) {
|
|
ShaderProgram *shader = NOT_NULL(R.progs.active);
|
|
Framebuffer *fb = R.framebuffer.active;
|
|
Uniform **u = shader->magic_uniforms;
|
|
|
|
r_uniform_mat4(u[UMAGIC_MATRIX_MV], *_r_matrices.modelview.head);
|
|
r_uniform_mat4(u[UMAGIC_MATRIX_PROJ], *_r_matrices.projection.head);
|
|
r_uniform_mat4(u[UMAGIC_MATRIX_TEX], *_r_matrices.texture.head);
|
|
r_uniform_vec4_rgba(u[UMAGIC_COLOR], &R.color);
|
|
r_uniform_vec4_vec(u[UMAGIC_VIEWPORT], (float*)&R.viewport.active);
|
|
|
|
int num_color_out;
|
|
|
|
if(u[UMAGIC_COLOR_OUT_SIZES]) {
|
|
num_color_out = clamp(u[UMAGIC_COLOR_OUT_SIZES]->array_size, 0, FRAMEBUFFER_MAX_OUTPUTS);
|
|
} else {
|
|
num_color_out = 0;
|
|
}
|
|
|
|
if(fb) {
|
|
if(num_color_out > 0) {
|
|
vec2_noalign out[num_color_out];
|
|
|
|
for(int i = 0; i < num_color_out; ++i) {
|
|
gl33_get_output_size(fb, fb->output_mapping[i], out[i]);
|
|
}
|
|
|
|
r_uniform_vec2_array(u[UMAGIC_COLOR_OUT_SIZES], 0, num_color_out, out);
|
|
}
|
|
|
|
if(u[UMAGIC_DEPTH_OUT_SIZE]) {
|
|
vec2_noalign out;
|
|
gl33_get_output_size(fb, FRAMEBUFFER_ATTACH_DEPTH, out);
|
|
r_uniform_vec2_vec(u[UMAGIC_DEPTH_OUT_SIZE], out);
|
|
}
|
|
} else { // default framebuffer
|
|
// TODO: Maybe figure out whether we have the depth buffer?
|
|
// We don't request one for the default framebuffer, so pretend it's not there.
|
|
r_uniform_vec2(u[UMAGIC_DEPTH_OUT_SIZE], 0, 0);
|
|
|
|
if(num_color_out > 0) {
|
|
IntExtent fb_size = gl33_get_default_framebuffer_size();
|
|
vec2 out[num_color_out];
|
|
out[0][0] = fb_size.w;
|
|
out[0][1] = fb_size.h;
|
|
memset(out + 1, 0, sizeof(out) - sizeof(out[0]));
|
|
r_uniform_vec2_array(u[UMAGIC_COLOR_OUT_SIZES], 0, num_color_out, out);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gl33_sync_state(void) {
|
|
gl33_sync_capabilities();
|
|
gl33_sync_framebuffer();
|
|
gl33_sync_shader();
|
|
gl33_sync_viewport();
|
|
gl33_sync_scissor();
|
|
gl33_sync_magic_uniforms();
|
|
gl33_sync_uniforms(R.progs.active);
|
|
gl33_sync_texunits(true);
|
|
gl33_sync_vao();
|
|
gl33_sync_blend_mode();
|
|
|
|
if(R.capabilities.active & r_capability_bit(RCAP_CULL_FACE)) {
|
|
gl33_sync_cull_face_mode();
|
|
}
|
|
|
|
if(R.capabilities.active & r_capability_bit(RCAP_DEPTH_TEST)) {
|
|
gl33_sync_depth_test_func();
|
|
}
|
|
|
|
gl33_set_framebuffer_sRGB_write(R.framebuffer.active != NULL);
|
|
}
|
|
|
|
/*
|
|
* Exported functions
|
|
*/
|
|
|
|
GLenum gl33_prim_to_gl_prim(Primitive prim) {
|
|
static GLenum map[] = {
|
|
[PRIM_POINTS] = GL_POINTS,
|
|
[PRIM_LINE_STRIP] = GL_LINE_STRIP,
|
|
[PRIM_LINES] = GL_LINES,
|
|
[PRIM_TRIANGLE_STRIP] = GL_TRIANGLE_STRIP,
|
|
[PRIM_TRIANGLES] = GL_TRIANGLES,
|
|
};
|
|
|
|
assert((uint)prim < sizeof(map)/sizeof(*map));
|
|
return map[prim];
|
|
}
|
|
|
|
void gl33_sync_capabilities(void) {
|
|
if(R.capabilities.active == R.capabilities.pending) {
|
|
return;
|
|
}
|
|
|
|
for(RendererCapability cap = 0; cap < NUM_RCAPS; ++cap) {
|
|
r_capability_bits_t flag = r_capability_bit(cap);
|
|
bool pending = R.capabilities.pending & flag;
|
|
bool active = R.capabilities.active & flag;
|
|
|
|
if(pending != active) {
|
|
gl33_apply_capability(cap, pending);
|
|
}
|
|
}
|
|
|
|
R.capabilities.active = R.capabilities.pending;
|
|
}
|
|
|
|
attr_nonnull(1)
|
|
static void gl33_activate_texunit(TextureUnit *unit) {
|
|
assert(unit >= R.texunits.array && unit < R.texunits.array + R.texunits.limit);
|
|
|
|
if(R.texunits.active != unit) {
|
|
glActiveTexture(GL_TEXTURE0 + TU_INDEX(unit));
|
|
R.texunits.active = unit;
|
|
#ifdef GL33_DEBUG_TEXUNITS
|
|
log_debug("Activated unit %i", (uint)TU_INDEX(unit));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static int gl33_texunit_priority(TextureUnit *u) {
|
|
if(u->locked_for_target) {
|
|
return 3;
|
|
}
|
|
|
|
if(u->pending) {
|
|
return 2;
|
|
}
|
|
|
|
if(u->active) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gl33_texunit_priority_callback(List *elem) {
|
|
TextureUnit *u = (TextureUnit*)elem;
|
|
return gl33_texunit_priority(u);
|
|
}
|
|
|
|
attr_unused static void texture_str(Texture *tex, char *buf, size_t bufsize) {
|
|
if(tex == NULL) {
|
|
snprintf(buf, bufsize, "None");
|
|
} else {
|
|
snprintf(buf, bufsize, "\"%s\" (#%i; at %p)", tex->debug_label, tex->gl_handle, (void*)tex);
|
|
}
|
|
}
|
|
|
|
attr_unused static void gl33_dump_texunit(TextureUnit *u) {
|
|
char buf1[128], buf2[128];
|
|
texture_str(u->active, buf1, sizeof(buf1));
|
|
texture_str(u->pending, buf2, sizeof(buf2));
|
|
log_debug("[Unit %u | prio=%i | target=0x%x | lock=0x%x] bound: %s; pending: %s",
|
|
(uint)TU_INDEX(u),
|
|
gl33_texunit_priority(u),
|
|
u->bind_target,
|
|
u->locked_for_target,
|
|
buf1,
|
|
buf2
|
|
);
|
|
}
|
|
|
|
attr_unused static void gl33_dump_texunits(void) {
|
|
log_debug("=== BEGIN DUMP ===");
|
|
|
|
for(TextureUnit *u = R.texunits.list.first; u; u = u->next) {
|
|
gl33_dump_texunit(u);
|
|
}
|
|
|
|
log_debug("=== END DUMP ===");
|
|
}
|
|
|
|
static void gl33_relocate_texuint(TextureUnit *unit) {
|
|
int prio = gl33_texunit_priority(unit);
|
|
|
|
if(unit->old_prio == prio) {
|
|
return;
|
|
}
|
|
|
|
// TODO: optimize with a heap-based priority queue?
|
|
|
|
alist_unlink(&R.texunits.list, unit);
|
|
|
|
if(prio > 1) {
|
|
alist_insert_at_priority_tail(&R.texunits.list, unit, prio, gl33_texunit_priority_callback);
|
|
} else {
|
|
alist_insert_at_priority_head(&R.texunits.list, unit, prio, gl33_texunit_priority_callback);
|
|
}
|
|
|
|
unit->old_prio = prio;
|
|
|
|
#ifdef GL33_DEBUG_TEXUNITS
|
|
// gl33_dump_texunits();
|
|
// log_debug("Relocated unit %u", (uint)TU_INDEX(unit));
|
|
#endif
|
|
}
|
|
|
|
attr_nonnull(1)
|
|
static void gl33_set_texunit_binding(TextureUnit *unit, Texture *tex, GLuint lock_target) {
|
|
assert(!unit->locked_for_target);
|
|
|
|
if(tex && lock_target) {
|
|
assert(tex->bind_target == lock_target);
|
|
}
|
|
|
|
if(unit->pending == tex) {
|
|
if(tex) {
|
|
tex->binding_unit = unit;
|
|
}
|
|
|
|
if(lock_target) {
|
|
unit->locked_for_target = lock_target;
|
|
}
|
|
|
|
gl33_relocate_texuint(unit);
|
|
return;
|
|
}
|
|
|
|
if(unit->pending != NULL) {
|
|
// assert(unit->tex2d.pending->binding_unit == unit);
|
|
|
|
if(unit->pending->binding_unit == unit) {
|
|
// FIXME: should we search through the units for a matching binding,
|
|
// just in case another unit had the same texture bound?
|
|
unit->pending->binding_unit = NULL;
|
|
}
|
|
}
|
|
|
|
unit->pending = tex;
|
|
|
|
if(tex) {
|
|
tex->binding_unit = unit;
|
|
}
|
|
|
|
if(lock_target) {
|
|
unit->locked_for_target = lock_target;
|
|
}
|
|
|
|
gl33_relocate_texuint(unit);
|
|
}
|
|
|
|
static void gl33_bind_handle_to_texunit(TextureUnit *u, GLenum target, GLuint handle) {
|
|
/*
|
|
* For hysterical raisins, OpenGL texturing units can technically have multiple textures bound
|
|
* to them — one for each target. However, a single texturing unit can not be accessed by two
|
|
* samplers of a distinct type from a shader, making this "feature" virtually useless.
|
|
*
|
|
* Therefore we maintain only 1 kind of texture per unit here.
|
|
*/
|
|
|
|
#ifdef GL33_DEBUG_TEXUNITS
|
|
if(target != u->bind_target) {
|
|
log_debug("[%u] TYPE CHANGE: 0x%x -> 0x%x", (uint)TU_INDEX(u), u->bind_target, target);
|
|
gl33_dump_texunit(u);
|
|
}
|
|
#endif
|
|
|
|
if(target != u->bind_target && u->bind_target != 0) {
|
|
// If changing target, clear the old binding.
|
|
glBindTexture(u->bind_target, 0);
|
|
|
|
#ifdef GL33_DRAW_STATS
|
|
++R.stats.texture_rebinds;
|
|
#endif
|
|
}
|
|
|
|
glBindTexture(target, handle);
|
|
|
|
#ifdef GL33_DRAW_STATS
|
|
++R.stats.texture_rebinds;
|
|
#endif
|
|
|
|
u->bind_target = target;
|
|
u->gl_handle = handle;
|
|
}
|
|
|
|
void gl33_sync_texunit(TextureUnit *unit, bool prepare_rendering, bool ensure_active) {
|
|
Texture *tex = unit->pending;
|
|
|
|
#ifdef GL33_DEBUG_TEXUNITS
|
|
if(unit->pending != unit->active) {
|
|
attr_unused char buf1[128], buf2[128];
|
|
texture_str(unit->active, buf1, sizeof(buf1));
|
|
texture_str(unit->pending, buf2, sizeof(buf2));
|
|
log_debug("[Unit %u] %s ===> %s (render: %i)", (uint)TU_INDEX(unit), buf1, buf2, prepare_rendering);
|
|
}
|
|
#endif
|
|
|
|
if(tex == NULL) {
|
|
if(unit->gl_handle != 0) {
|
|
gl33_activate_texunit(unit);
|
|
gl33_bind_handle_to_texunit(unit, unit->bind_target, 0);
|
|
unit->active = NULL;
|
|
gl33_relocate_texuint(unit);
|
|
}
|
|
} else if(unit->gl_handle != tex->gl_handle) {
|
|
gl33_activate_texunit(unit);
|
|
gl33_bind_handle_to_texunit(unit, tex->bind_target, tex->gl_handle);
|
|
|
|
bool need_relocate = unit->active == NULL;
|
|
unit->active = tex;
|
|
|
|
if(need_relocate) {
|
|
gl33_relocate_texuint(unit);
|
|
}
|
|
} else if(ensure_active) {
|
|
gl33_activate_texunit(unit);
|
|
}
|
|
|
|
if(prepare_rendering) {
|
|
if(unit->active != NULL && unit->locked_for_target) {
|
|
gl33_texture_prepare(unit->active);
|
|
}
|
|
|
|
unit->locked_for_target = 0;
|
|
gl33_relocate_texuint(unit);
|
|
}
|
|
}
|
|
|
|
void gl33_sync_texunits(bool prepare_rendering) {
|
|
// gl33_dump_texunits();
|
|
for(TextureUnit *u = R.texunits.array; u < R.texunits.array + R.texunits.limit; ++u) {
|
|
gl33_sync_texunit(u, prepare_rendering, false);
|
|
}
|
|
}
|
|
|
|
void gl33_sync_vao(void) {
|
|
if(R.vao.active != R.vao.pending) {
|
|
R.vao.active = R.vao.pending;
|
|
glBindVertexArray(R.vao.active);
|
|
}
|
|
}
|
|
|
|
GLenum gl33_bindidx_to_glenum(BufferBindingIndex bindidx) {
|
|
if(bindidx == GL33_BUFFER_BINDING_ELEMENT_ARRAY) {
|
|
return GL_ELEMENT_ARRAY_BUFFER;
|
|
}
|
|
|
|
static GLenum map[] = {
|
|
[GL33_BUFFER_BINDING_ARRAY] = GL_ARRAY_BUFFER,
|
|
[GL33_BUFFER_BINDING_COPY_WRITE] = GL_COPY_WRITE_BUFFER,
|
|
[GL33_BUFFER_BINDING_PIXEL_UNPACK] = GL_PIXEL_UNPACK_BUFFER,
|
|
[GL33_BUFFER_BINDING_PIXEL_PACK] = GL_PIXEL_PACK_BUFFER,
|
|
};
|
|
|
|
static_assert(sizeof(map) == sizeof(GLenum) * GL33_NUM_BUFFER_BINDINGS, "Fix the lookup table");
|
|
assert((uint)bindidx < GL33_NUM_BUFFER_BINDINGS);
|
|
return map[bindidx];
|
|
}
|
|
|
|
void gl33_sync_buffer(BufferBindingIndex bindidx) {
|
|
assert((uint)bindidx < GL33_NUM_BUFFER_BINDINGS);
|
|
|
|
if(R.buffer_objects[bindidx].active != R.buffer_objects[bindidx].pending) {
|
|
R.buffer_objects[bindidx].active = R.buffer_objects[bindidx].pending;
|
|
glBindBuffer(gl33_bindidx_to_glenum(bindidx), R.buffer_objects[bindidx].active);
|
|
}
|
|
}
|
|
|
|
void gl33_sync_cull_face_mode(void) {
|
|
if(R.cull_face.mode.pending != R.cull_face.mode.active) {
|
|
GLenum glcull = r_cull_to_gl_cull(R.cull_face.mode.pending);
|
|
glCullFace(glcull);
|
|
R.cull_face.mode.active = R.cull_face.mode.pending;
|
|
}
|
|
}
|
|
|
|
void gl33_sync_depth_test_func(void) {
|
|
DepthTestFunc func = R.depth_test.func.pending;
|
|
|
|
static GLenum func_to_glfunc[] = {
|
|
[DEPTH_NEVER] = GL_NEVER,
|
|
[DEPTH_ALWAYS] = GL_ALWAYS,
|
|
[DEPTH_EQUAL] = GL_EQUAL,
|
|
[DEPTH_NOTEQUAL] = GL_NOTEQUAL,
|
|
[DEPTH_LESS] = GL_LESS,
|
|
[DEPTH_LEQUAL] = GL_LEQUAL,
|
|
[DEPTH_GREATER] = GL_GREATER,
|
|
[DEPTH_GEQUAL] = GL_GEQUAL,
|
|
};
|
|
|
|
uint32_t idx = func;
|
|
assert(idx < sizeof(func_to_glfunc)/sizeof(GLenum));
|
|
|
|
if(R.depth_test.func.active != func) {
|
|
glDepthFunc(func_to_glfunc[idx]);
|
|
R.depth_test.func.active = func;
|
|
}
|
|
}
|
|
|
|
static inline GLuint fbo_num(Framebuffer *fb) {
|
|
if(fb == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
assert(fb->gl_fbo != 0);
|
|
return fb->gl_fbo;
|
|
}
|
|
|
|
void gl33_sync_framebuffer(void) {
|
|
if(fbo_num(R.framebuffer.active) != fbo_num(R.framebuffer.pending)) {
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_num(R.framebuffer.pending));
|
|
R.framebuffer.active = R.framebuffer.pending;
|
|
}
|
|
|
|
if(R.framebuffer.active) {
|
|
gl33_framebuffer_prepare(R.framebuffer.active);
|
|
}
|
|
}
|
|
|
|
void gl33_sync_shader(void) {
|
|
if(R.progs.pending && R.progs.gl_prog != R.progs.pending->gl_handle) {
|
|
glUseProgram(R.progs.pending->gl_handle);
|
|
R.progs.gl_prog = R.progs.pending->gl_handle;
|
|
R.progs.active = R.progs.pending;
|
|
}
|
|
}
|
|
|
|
void gl33_sync_blend_mode(void) {
|
|
BlendMode mode = R.blend.mode.pending;
|
|
|
|
if(mode == BLEND_NONE) {
|
|
if(R.blend.enabled) {
|
|
glDisable(GL_BLEND);
|
|
R.blend.enabled = false;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if(!R.blend.enabled) {
|
|
R.blend.enabled = true;
|
|
glEnable(GL_BLEND);
|
|
}
|
|
|
|
if(mode != R.blend.mode.active) {
|
|
static UnpackedBlendMode umode;
|
|
r_blend_unpack(mode, &umode);
|
|
R.blend.mode.active = mode;
|
|
|
|
// TODO: maybe cache the funcs and factors separately,
|
|
// because the blend funcs change a lot less frequently.
|
|
|
|
glBlendEquationSeparate(
|
|
blendop_to_gl_blendop(umode.color.op),
|
|
blendop_to_gl_blendop(umode.alpha.op)
|
|
);
|
|
|
|
glBlendFuncSeparate(
|
|
blendfactor_to_gl_blendfactor(umode.color.src),
|
|
blendfactor_to_gl_blendfactor(umode.color.dst),
|
|
blendfactor_to_gl_blendfactor(umode.alpha.src),
|
|
blendfactor_to_gl_blendfactor(umode.alpha.dst)
|
|
);
|
|
}
|
|
}
|
|
|
|
static TextureUnit *gl33_get_free_texunit(void) {
|
|
TextureUnit *u = R.texunits.list.first;
|
|
|
|
if(
|
|
u->pending &&
|
|
u->pending != u->active
|
|
) {
|
|
log_warn("Ran out of texturing units, expect rendering errors!");
|
|
}
|
|
|
|
assert(!u->locked_for_target);
|
|
return u;
|
|
}
|
|
|
|
uint gl33_bind_texture(Texture *texture, GLuint lock_target, int preferred_unit) {
|
|
if(texture == NULL) {
|
|
TextureUnit *unit = NULL;
|
|
|
|
if(preferred_unit >= 0) {
|
|
assert(preferred_unit < R.texunits.limit);
|
|
unit = &R.texunits.array[preferred_unit];
|
|
|
|
if(
|
|
unit->locked_for_target &&
|
|
(unit->pending != NULL || unit->locked_for_target != lock_target)
|
|
) {
|
|
// someone else already took this unit for rendering
|
|
log_fatal("BUG: can't bind %s to unit %i: unit already locked for rendering!",
|
|
texture->debug_label, preferred_unit);
|
|
}
|
|
}
|
|
|
|
assume(unit != NULL);
|
|
|
|
if(unit->locked_for_target) {
|
|
gl33_relocate_texuint(unit);
|
|
} else {
|
|
gl33_set_texunit_binding(unit, NULL, lock_target);
|
|
}
|
|
|
|
assert(unit->pending == NULL);
|
|
return TU_INDEX(unit);
|
|
}
|
|
|
|
assert(!lock_target || lock_target == texture->bind_target);
|
|
|
|
if(preferred_unit >= 0) {
|
|
assert(preferred_unit < R.texunits.limit);
|
|
TextureUnit *u = &R.texunits.array[preferred_unit];
|
|
|
|
if(u->pending == texture) {
|
|
if(!u->locked_for_target) {
|
|
u->locked_for_target = lock_target;
|
|
} else {
|
|
assert(u->locked_for_target == lock_target);
|
|
}
|
|
} else {
|
|
gl33_set_texunit_binding(u, texture, lock_target);
|
|
}
|
|
|
|
// In this case the texture may be bound to more than one unit.
|
|
// This is fine though, and we just always update binding_unit to the
|
|
// most recent binding.
|
|
texture->binding_unit = u;
|
|
} else if(!texture->binding_unit) {
|
|
gl33_set_texunit_binding(gl33_get_free_texunit(), texture, lock_target);
|
|
} else /* if(for_rendering) */ {
|
|
if(!texture->binding_unit->locked_for_target) {
|
|
texture->binding_unit->locked_for_target = lock_target;
|
|
}
|
|
|
|
gl33_relocate_texuint(texture->binding_unit);
|
|
}
|
|
|
|
assert(texture->binding_unit->pending == texture);
|
|
return TU_INDEX(texture->binding_unit);
|
|
}
|
|
|
|
void gl33_bind_buffer(BufferBindingIndex bindidx, GLuint gl_handle) {
|
|
R.buffer_objects[bindidx].pending = gl_handle;
|
|
}
|
|
|
|
void gl33_bind_vao(GLuint vao) {
|
|
R.vao.pending = vao;
|
|
}
|
|
|
|
GLuint gl33_buffer_current(BufferBindingIndex bindidx) {
|
|
return R.buffer_objects[bindidx].pending;
|
|
}
|
|
|
|
GLuint gl33_vao_current(void) {
|
|
return R.vao.pending;
|
|
}
|
|
|
|
void gl33_texture_deleted(Texture *tex) {
|
|
_r_sprite_batch_texture_deleted(tex);
|
|
gl33_unref_texture_from_samplers(tex);
|
|
|
|
for(TextureUnit *unit = R.texunits.array; unit < R.texunits.array + R.texunits.limit; ++unit) {
|
|
bool bump = false;
|
|
|
|
if(unit->pending == tex) {
|
|
unit->pending = NULL;
|
|
unit->locked_for_target = 0;
|
|
bump = true;
|
|
}
|
|
|
|
if(unit->active == tex) {
|
|
assert(unit->gl_handle == tex->gl_handle);
|
|
unit->active = NULL;
|
|
unit->gl_handle = 0;
|
|
bump = true;
|
|
} else {
|
|
assert(unit->gl_handle != tex->gl_handle);
|
|
}
|
|
|
|
if(bump) {
|
|
gl33_relocate_texuint(unit);
|
|
}
|
|
}
|
|
|
|
if(R.buffer_objects[GL33_BUFFER_BINDING_PIXEL_UNPACK].pending == tex->pbo) {
|
|
R.buffer_objects[GL33_BUFFER_BINDING_PIXEL_UNPACK].pending = 0;
|
|
}
|
|
|
|
if(R.buffer_objects[GL33_BUFFER_BINDING_PIXEL_UNPACK].active == tex->pbo) {
|
|
R.buffer_objects[GL33_BUFFER_BINDING_PIXEL_UNPACK].active = 0;
|
|
}
|
|
}
|
|
|
|
void gl33_texture_pointer_renamed(Texture *pold, Texture *pnew) {
|
|
_r_sprite_batch_texture_deleted(pold);
|
|
gl33_uniforms_handle_texture_pointer_renamed(pold, pnew);
|
|
|
|
for(TextureUnit *unit = R.texunits.array; unit < R.texunits.array + R.texunits.limit; ++unit) {
|
|
if(unit->pending == pold) {
|
|
unit->pending = pnew;
|
|
}
|
|
|
|
if(unit->active == pold) {
|
|
unit->active = pnew;
|
|
}
|
|
}
|
|
}
|
|
|
|
void gl33_framebuffer_deleted(Framebuffer *fb) {
|
|
if(R.framebuffer.pending == fb) {
|
|
R.framebuffer.pending = NULL;
|
|
}
|
|
|
|
if(R.framebuffer.active == fb) {
|
|
R.framebuffer.active = NULL;
|
|
}
|
|
}
|
|
|
|
void gl33_shader_deleted(ShaderProgram *prog) {
|
|
if(R.progs.active == NULL) {
|
|
R.progs.active = NULL;
|
|
}
|
|
|
|
if(R.progs.pending == prog) {
|
|
R.progs.pending = NULL;
|
|
}
|
|
|
|
if(R.progs.gl_prog == prog->gl_handle) {
|
|
R.progs.gl_prog = 0;
|
|
}
|
|
}
|
|
|
|
void gl33_buffer_deleted(CommonBuffer *cbuf) {
|
|
if(cbuf->bindidx == GL33_BUFFER_BINDING_ELEMENT_ARRAY) {
|
|
// TODO: unreference from VAO
|
|
return;
|
|
}
|
|
|
|
if(R.buffer_objects[cbuf->bindidx].active == cbuf->gl_handle) {
|
|
R.buffer_objects[cbuf->bindidx].active = 0;
|
|
}
|
|
|
|
if(R.buffer_objects[cbuf->bindidx].pending == cbuf->gl_handle) {
|
|
R.buffer_objects[cbuf->bindidx].pending = 0;
|
|
}
|
|
}
|
|
|
|
void gl33_vertex_buffer_deleted(VertexBuffer *vbuf) {
|
|
if(R.buffer_objects[GL33_BUFFER_BINDING_ARRAY].active == vbuf->cbuf.gl_handle) {
|
|
R.buffer_objects[GL33_BUFFER_BINDING_ARRAY].active = 0;
|
|
}
|
|
|
|
if(R.buffer_objects[GL33_BUFFER_BINDING_ARRAY].pending == vbuf->cbuf.gl_handle) {
|
|
R.buffer_objects[GL33_BUFFER_BINDING_ARRAY].pending = 0;
|
|
}
|
|
}
|
|
|
|
void gl33_vertex_array_deleted(VertexArray *varr) {
|
|
if(R.vao.active == varr->gl_handle) {
|
|
R.vao.active = 0;
|
|
}
|
|
|
|
if(R.vao.pending == varr->gl_handle) {
|
|
R.vao.pending = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Renderer interface implementation
|
|
*/
|
|
|
|
static void gl33_init(void) {
|
|
SDL_GLprofile profile;
|
|
SDL_GLcontextFlag flags = 0;
|
|
|
|
if(env_get("TAISEI_GL33_CORE_PROFILE", true)) {
|
|
profile = SDL_GL_CONTEXT_PROFILE_CORE;
|
|
} else {
|
|
profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
|
|
}
|
|
|
|
if(env_get("TAISEI_GL33_FORWARD_COMPATIBLE", true)) {
|
|
flags |= SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
|
|
}
|
|
|
|
int major = env_get("TAISEI_GL33_VERSION_MAJOR", 3);
|
|
int minor = env_get("TAISEI_GL33_VERSION_MINOR", 3);
|
|
|
|
glcommon_setup_attributes(profile, major, minor, flags);
|
|
glcommon_load_library();
|
|
}
|
|
|
|
static void gl33_post_init(void) {
|
|
|
|
}
|
|
|
|
static void gl33_shutdown(void) {
|
|
gl33_framebuffer_finalize_read_requests();
|
|
glcommon_unload_library();
|
|
SDL_GL_DeleteContext(R.gl_context);
|
|
}
|
|
|
|
static SDL_Window *gl33_create_window(const char *title, int x, int y, int w, int h, uint32_t flags) {
|
|
SDL_Window *window = SDL_CreateWindow(title, x, y, w, h, flags | SDL_WINDOW_OPENGL);
|
|
|
|
if(!window) {
|
|
log_sdl_error(LOG_FATAL, "SDL_CreateWindow");
|
|
return NULL;
|
|
}
|
|
|
|
if(R.gl_context) {
|
|
SDL_GL_MakeCurrent(window, R.gl_context);
|
|
} else {
|
|
GLVT.init_context(window);
|
|
}
|
|
|
|
R.window = window;
|
|
return window;
|
|
}
|
|
|
|
static r_feature_bits_t gl33_features(void) {
|
|
return R.features;
|
|
}
|
|
|
|
static void gl33_capabilities(r_capability_bits_t capbits) {
|
|
R.capabilities.pending = capbits;
|
|
}
|
|
|
|
static r_capability_bits_t gl33_capabilities_current(void) {
|
|
return R.capabilities.pending;
|
|
}
|
|
|
|
static void gl33_vsync(VsyncMode mode) {
|
|
int interval = 0, result;
|
|
|
|
switch(mode) {
|
|
case VSYNC_NONE: interval = 0; break;
|
|
case VSYNC_NORMAL: interval = 1; break;
|
|
case VSYNC_ADAPTIVE: interval = -1; break;
|
|
default:
|
|
log_fatal("Unknown mode 0x%x", mode);
|
|
}
|
|
|
|
set_interval:
|
|
result = SDL_GL_SetSwapInterval(interval);
|
|
|
|
if(result < 0) {
|
|
log_warn("SDL_GL_SetSwapInterval(%i) failed: %s", interval, SDL_GetError());
|
|
|
|
// if adaptive vsync failed, try normal vsync
|
|
if(interval < 0) {
|
|
interval = 1;
|
|
goto set_interval;
|
|
}
|
|
}
|
|
}
|
|
|
|
static VsyncMode gl33_vsync_current(void) {
|
|
int interval = SDL_GL_GetSwapInterval();
|
|
|
|
if(interval == 0) {
|
|
return VSYNC_NONE;
|
|
}
|
|
|
|
return interval > 0 ? VSYNC_NORMAL : VSYNC_ADAPTIVE;
|
|
}
|
|
|
|
static void gl33_color4(float r, float g, float b, float a) {
|
|
R.color.r = r;
|
|
R.color.g = g;
|
|
R.color.b = b;
|
|
R.color.a = a;
|
|
}
|
|
|
|
static const Color* gl33_color_current(void) {
|
|
return &R.color;
|
|
}
|
|
|
|
void gl33_begin_draw(VertexArray *varr, void **state) {
|
|
gl33_stats_pre_draw();
|
|
r_flush_sprites();
|
|
GLuint prev_vao = gl33_vao_current();
|
|
gl33_bind_vao(varr->gl_handle);
|
|
gl33_sync_state();
|
|
gl33_vertex_array_flush_buffers(varr);
|
|
*state = (void*)(uintptr_t)prev_vao;
|
|
}
|
|
|
|
void gl33_end_draw(void *state) {
|
|
if(R.framebuffer.active) {
|
|
gl33_framebuffer_taint(R.framebuffer.active);
|
|
}
|
|
|
|
gl33_bind_vao((uintptr_t)state);
|
|
gl33_stats_post_draw();
|
|
}
|
|
|
|
static void gl33_draw(VertexArray *varr, Primitive prim, uint firstvert, uint count, uint instances, uint base_instance) {
|
|
assert(count > 0);
|
|
assert(base_instance == 0);
|
|
GLuint gl_prim = gl33_prim_to_gl_prim(prim);
|
|
|
|
void *state;
|
|
gl33_begin_draw(varr, &state);
|
|
|
|
if(instances) {
|
|
glDrawArraysInstanced(gl_prim, firstvert, count, instances);
|
|
} else {
|
|
glDrawArrays(gl_prim, firstvert, count);
|
|
}
|
|
|
|
gl33_end_draw(state);
|
|
}
|
|
|
|
static void gl33_draw_indexed(VertexArray *varr, Primitive prim, uint firstidx, uint count, uint instances, uint base_instance) {
|
|
assert(count > 0);
|
|
assert(base_instance == 0);
|
|
|
|
IndexBuffer *ibuf = NOT_NULL(varr->index_attachment);
|
|
GLuint gl_prim = gl33_prim_to_gl_prim(prim);
|
|
|
|
void *state;
|
|
gl33_begin_draw(varr, &state);
|
|
|
|
uintptr_t iofs = firstidx * ibuf->index_size;
|
|
|
|
if(instances) {
|
|
glDrawElementsInstanced(gl_prim, count, ibuf->index_datatype, (void*)iofs, instances);
|
|
} else {
|
|
glDrawElements(gl_prim, count, ibuf->index_datatype, (void*)iofs);
|
|
}
|
|
|
|
gl33_end_draw(state);
|
|
}
|
|
|
|
static void gl33_framebuffer(Framebuffer *fb) {
|
|
R.framebuffer.pending = fb;
|
|
}
|
|
|
|
static Framebuffer *gl33_framebuffer_current(void) {
|
|
return R.framebuffer.pending;
|
|
}
|
|
|
|
static void gl33_framebuffer_viewport(Framebuffer *fb, FloatRect vp) {
|
|
transform_viewport_origin(fb, &vp);
|
|
memcpy(get_framebuffer_viewport(fb), &vp, sizeof(vp));
|
|
}
|
|
|
|
static void gl33_framebuffer_viewport_current(Framebuffer *fb, FloatRect *out_rect) {
|
|
*out_rect = *get_framebuffer_viewport(fb);
|
|
transform_viewport_origin(fb, out_rect);
|
|
}
|
|
|
|
static IntExtent gl33_framebuffer_get_size(Framebuffer *fb) {
|
|
IntExtent fb_size;
|
|
|
|
if(fb == NULL) {
|
|
fb_size = gl33_get_default_framebuffer_size();
|
|
} else {
|
|
fb_size = gl33_framebuffer_get_effective_size(fb);
|
|
}
|
|
|
|
return fb_size;
|
|
}
|
|
|
|
static void gl33_shader(ShaderProgram *prog) {
|
|
assert(prog->gl_handle != 0);
|
|
|
|
R.progs.pending = prog;
|
|
}
|
|
|
|
static ShaderProgram *gl33_shader_current(void) {
|
|
return R.progs.pending;
|
|
}
|
|
|
|
void gl33_set_clear_color(const Color *color) {
|
|
if(memcmp(&R.clear_color, color, sizeof(*color))) {
|
|
memcpy(&R.clear_color, color, sizeof(*color));
|
|
glClearColor(color->r, color->g, color->b, color->a);
|
|
}
|
|
}
|
|
|
|
void gl33_set_clear_depth(float depth) {
|
|
if(R.clear_depth != depth) {
|
|
R.clear_depth = depth;
|
|
glClearDepth(depth);
|
|
}
|
|
}
|
|
|
|
static void gl33_swap(SDL_Window *window) {
|
|
r_flush_sprites();
|
|
|
|
Framebuffer *prev_fb = r_framebuffer_current();
|
|
r_framebuffer(NULL);
|
|
gl33_sync_framebuffer();
|
|
#ifndef __EMSCRIPTEN__
|
|
SDL_GL_SwapWindow(window);
|
|
#endif
|
|
r_framebuffer(prev_fb);
|
|
|
|
gl33_framebuffer_process_read_requests();
|
|
gl33_stats_post_frame();
|
|
|
|
// We can't rely on viewport being preserved across frames,
|
|
// so force the next frame to set one on the first draw call.
|
|
// The viewport might get updated externally when e.g. going
|
|
// fullscreen, and we can't catch that in the resize event.
|
|
memset(&R.viewport.active, 0, sizeof(R.viewport.active));
|
|
}
|
|
|
|
static void gl33_blend(BlendMode mode) {
|
|
R.blend.mode.pending = mode;
|
|
}
|
|
|
|
static BlendMode gl33_blend_current(void) {
|
|
return R.blend.mode.pending;
|
|
}
|
|
|
|
static void gl33_cull(CullFaceMode mode) {
|
|
R.cull_face.mode.pending = mode;
|
|
}
|
|
|
|
static CullFaceMode gl33_cull_current(void) {
|
|
return R.cull_face.mode.pending;
|
|
}
|
|
|
|
static void gl33_depth_func(DepthTestFunc func) {
|
|
R.depth_test.func.pending = func;
|
|
}
|
|
|
|
static DepthTestFunc gl33_depth_func_current(void) {
|
|
return R.depth_test.func.pending;
|
|
}
|
|
|
|
static void gl33_scissor(IntRect scissor) {
|
|
R.scissor.pending = scissor;
|
|
}
|
|
|
|
static void gl33_scissor_current(IntRect *scissor) {
|
|
*scissor = R.scissor.pending;
|
|
}
|
|
|
|
RendererBackend _r_backend_gl33 = {
|
|
.name = "gl33",
|
|
.funcs = {
|
|
.init = gl33_init,
|
|
.post_init = gl33_post_init,
|
|
.shutdown = gl33_shutdown,
|
|
.create_window = gl33_create_window,
|
|
.features = gl33_features,
|
|
.capabilities = gl33_capabilities,
|
|
.capabilities_current = gl33_capabilities_current,
|
|
.draw = gl33_draw,
|
|
.draw_indexed = gl33_draw_indexed,
|
|
.color4 = gl33_color4,
|
|
.color_current = gl33_color_current,
|
|
.blend = gl33_blend,
|
|
.blend_current = gl33_blend_current,
|
|
.cull = gl33_cull,
|
|
.cull_current = gl33_cull_current,
|
|
.depth_func = gl33_depth_func,
|
|
.depth_func_current = gl33_depth_func_current,
|
|
.shader_language_supported = gl33_shader_language_supported,
|
|
.shader_object_compile = gl33_shader_object_compile,
|
|
.shader_object_destroy = gl33_shader_object_destroy,
|
|
.shader_object_set_debug_label = gl33_shader_object_set_debug_label,
|
|
.shader_object_get_debug_label = gl33_shader_object_get_debug_label,
|
|
.shader_object_transfer = gl33_shader_object_transfer,
|
|
.shader_program_link = gl33_shader_program_link,
|
|
.shader_program_destroy = gl33_shader_program_destroy,
|
|
.shader_program_set_debug_label = gl33_shader_program_set_debug_label,
|
|
.shader_program_get_debug_label = gl33_shader_program_get_debug_label,
|
|
.shader_program_transfer = gl33_shader_program_transfer,
|
|
.shader = gl33_shader,
|
|
.shader_current = gl33_shader_current,
|
|
.shader_uniform = gl33_shader_uniform,
|
|
.uniform = gl33_uniform,
|
|
.uniform_type = gl33_uniform_type,
|
|
.texture_create = gl33_texture_create,
|
|
.texture_get_size = gl33_texture_get_size,
|
|
.texture_get_params = gl33_texture_get_params,
|
|
.texture_get_debug_label = gl33_texture_get_debug_label,
|
|
.texture_set_debug_label = gl33_texture_set_debug_label,
|
|
.texture_set_filter = gl33_texture_set_filter,
|
|
.texture_set_wrap = gl33_texture_set_wrap,
|
|
.texture_destroy = gl33_texture_destroy,
|
|
.texture_invalidate = gl33_texture_invalidate,
|
|
.texture_fill = gl33_texture_fill,
|
|
.texture_fill_region = gl33_texture_fill_region,
|
|
.texture_clear = gl33_texture_clear,
|
|
.texture_type_query = gl33_texture_type_query,
|
|
.texture_dump = gl33_texture_dump,
|
|
.texture_transfer = gl33_texture_transfer,
|
|
.framebuffer_create = gl33_framebuffer_create,
|
|
.framebuffer_destroy = gl33_framebuffer_destroy,
|
|
.framebuffer_attach = gl33_framebuffer_attach,
|
|
.framebuffer_get_debug_label = gl33_framebuffer_get_debug_label,
|
|
.framebuffer_set_debug_label = gl33_framebuffer_set_debug_label,
|
|
.framebuffer_query_attachment = gl33_framebuffer_query_attachment,
|
|
.framebuffer_outputs = gl33_framebuffer_outputs,
|
|
.framebuffer_viewport = gl33_framebuffer_viewport,
|
|
.framebuffer_viewport_current = gl33_framebuffer_viewport_current,
|
|
.framebuffer = gl33_framebuffer,
|
|
.framebuffer_current = gl33_framebuffer_current,
|
|
.framebuffer_clear = gl33_framebuffer_clear,
|
|
.framebuffer_copy = gl33_framebuffer_copy,
|
|
.framebuffer_get_size = gl33_framebuffer_get_size,
|
|
.framebuffer_read_async = gl33_framebuffer_read_async,
|
|
.vertex_buffer_create = gl33_vertex_buffer_create,
|
|
.vertex_buffer_set_debug_label = gl33_vertex_buffer_set_debug_label,
|
|
.vertex_buffer_get_debug_label = gl33_vertex_buffer_get_debug_label,
|
|
.vertex_buffer_destroy = gl33_vertex_buffer_destroy,
|
|
.vertex_buffer_invalidate = gl33_vertex_buffer_invalidate,
|
|
.vertex_buffer_get_stream = gl33_vertex_buffer_get_stream,
|
|
.index_buffer_create = gl33_index_buffer_create,
|
|
.index_buffer_get_capacity = gl33_index_buffer_get_capacity,
|
|
.index_buffer_get_index_size = gl33_index_buffer_get_index_size,
|
|
.index_buffer_get_debug_label = gl33_index_buffer_get_debug_label,
|
|
.index_buffer_set_debug_label = gl33_index_buffer_set_debug_label,
|
|
.index_buffer_set_offset = gl33_index_buffer_set_offset,
|
|
.index_buffer_get_offset = gl33_index_buffer_get_offset,
|
|
.index_buffer_add_indices = gl33_index_buffer_add_indices,
|
|
.index_buffer_invalidate = gl33_index_buffer_invalidate,
|
|
.index_buffer_destroy = gl33_index_buffer_destroy,
|
|
.vertex_array_destroy = gl33_vertex_array_destroy,
|
|
.vertex_array_create = gl33_vertex_array_create,
|
|
.vertex_array_set_debug_label = gl33_vertex_array_set_debug_label,
|
|
.vertex_array_get_debug_label = gl33_vertex_array_get_debug_label,
|
|
.vertex_array_layout = gl33_vertex_array_layout,
|
|
.vertex_array_attach_vertex_buffer = gl33_vertex_array_attach_vertex_buffer,
|
|
.vertex_array_get_vertex_attachment = gl33_vertex_array_get_vertex_attachment,
|
|
.vertex_array_attach_index_buffer = gl33_vertex_array_attach_index_buffer,
|
|
.vertex_array_get_index_attachment = gl33_vertex_array_get_index_attachment,
|
|
.scissor = gl33_scissor,
|
|
.scissor_current = gl33_scissor_current,
|
|
.vsync = gl33_vsync,
|
|
.vsync_current = gl33_vsync_current,
|
|
.swap = gl33_swap,
|
|
},
|
|
.custom = &(GLBackendData) {
|
|
.vtable = {
|
|
.create_context = gl33_create_context,
|
|
.init_context = gl33_init_context,
|
|
}
|
|
},
|
|
};
|