taisei/src/renderer/gl33/gl33.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,
}
},
};