Files
Shivansps 49f4b1d9c7 Support GLES in internal shaders and Vulkan software clip distance fallback (#7489)
* GLES Shaders and Vulkan Clip fallback

* clip fallback: change defined name, set from engine side and invert usage

* Proposed changes and fixes

Fixed:
-The OpenGL ES option stays on when OpenGL is off (issue #7984)
-Missing precision in copy-f, volumetric-f and smaa-resolve-f
-msaa-f including z-compress before precision quantification
-missing float literals in volumetric-f
-Fixed decals blending
-Restored glBindFragDataLocation() for use in legacy external shaders
-Enabled GL_ARB_explicit_attrib_location ext for GLSL version below 330 in GL
-Guarded enabling/disabling of GL_CLIP_DISTANCE0_EXT and GL_DEPTH_CLAMP
-Restored PI in lighting.sdr
-removed "External shaders only" indication from cmakelists.txt
2026-11-03 12:20:21 +09:00

1350 lines
43 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#ifdef _WIN32
#include <windows.h>
#endif
#include "globalincs/alphacolors.h"
#include "globalincs/systemvars.h"
#include "ShaderProgram.h"
#include "gropengldeferred.h"
#include "gropengldraw.h"
#include "gropenglshader.h"
#include "gropenglstate.h"
#include "gropengltexture.h"
#include "gropengltnl.h"
#include "cmdline/cmdline.h"
#include "def_files/def_files.h"
#include "graphics/2d.h"
#include "graphics/grinternal.h"
#include "graphics/light.h"
#include "graphics/material.h"
#include "graphics/matrix.h"
#include "graphics/shadows.h"
#include "graphics/util/uniform_structs.h"
#include "mod_table/mod_table.h"
#include "lighting/lighting.h"
#include "math/vecmat.h"
#include "options/Option.h"
#include "particle/particle.h"
#include "render/3d.h"
#include "weapon/trails.h"
#ifdef USE_OPENGL_ES
#include "es_compatibility.h"
#endif
#define MODEL_SDR_FLAG_MODE_CPP
#include "def_files/data/effects/model_shader_flags.h"
extern int GLOWMAP;
extern int SPECMAP;
extern int SPECGLOSSMAP;
extern int NORMMAP;
extern int MISCMAP;
extern int HEIGHTMAP;
extern int G3_user_clip;
extern vec3d G3_user_clip_normal;
extern vec3d G3_user_clip_point;
extern bool Envmap_override;
extern bool Shadow_override;
size_t GL_vertex_data_in = 0;
GLint GL_max_elements_vertices = 4096;
GLint GL_max_elements_indices = 4096;
GLuint Shadow_map_depth_texture = 0;
GLuint shadow_fbo = 0;
int Shadow_texture_size = 0;
gr_buffer_handle Transform_buffer_handle;
SCP_unordered_map<vertex_layout, GLuint> Stored_vertex_arrays;
static opengl_vertex_bind GL_array_binding_data[] =
{
{ vertex_format_data::POSITION4, 4, GL_FLOAT, GL_FALSE, opengl_vert_attrib::POSITION },
{ vertex_format_data::POSITION3, 3, GL_FLOAT, GL_FALSE, opengl_vert_attrib::POSITION },
{ vertex_format_data::POSITION2, 2, GL_FLOAT, GL_FALSE, opengl_vert_attrib::POSITION },
{ vertex_format_data::COLOR3, 3, GL_UNSIGNED_BYTE, GL_TRUE, opengl_vert_attrib::COLOR },
{ vertex_format_data::COLOR4, 4, GL_UNSIGNED_BYTE, GL_TRUE, opengl_vert_attrib::COLOR },
{ vertex_format_data::COLOR4F, 4, GL_FLOAT, GL_FALSE, opengl_vert_attrib::COLOR },
{ vertex_format_data::TEX_COORD2, 2, GL_FLOAT, GL_FALSE, opengl_vert_attrib::TEXCOORD },
{ vertex_format_data::TEX_COORD4, 4, GL_FLOAT, GL_FALSE, opengl_vert_attrib::TEXCOORD },
{ vertex_format_data::NORMAL, 3, GL_FLOAT, GL_FALSE, opengl_vert_attrib::NORMAL },
{ vertex_format_data::TANGENT, 4, GL_FLOAT, GL_FALSE, opengl_vert_attrib::TANGENT },
{ vertex_format_data::MODEL_ID, 1, GL_FLOAT, GL_FALSE, opengl_vert_attrib::MODEL_ID },
{ vertex_format_data::RADIUS, 1, GL_FLOAT, GL_FALSE, opengl_vert_attrib::RADIUS },
{ vertex_format_data::UVEC, 3, GL_FLOAT, GL_FALSE, opengl_vert_attrib::UVEC },
{ vertex_format_data::MATRIX4, 16, GL_FLOAT, GL_FALSE, opengl_vert_attrib::MODEL_MATRIX },
};
struct opengl_buffer_object {
GLuint buffer_id;
GLenum type;
GLenum gl_usage;
BufferUsageHint usage;
size_t size;
GLuint texture; // for texture buffer objects
};
static SCP_vector<opengl_buffer_object> GL_buffer_objects;
static int GL_vertex_buffers_in_use = 0;
static GLenum convertBufferType(BufferType type) {
switch (type) {
case BufferType::Vertex:
return GL_ARRAY_BUFFER;
case BufferType::Index:
return GL_ELEMENT_ARRAY_BUFFER;
case BufferType::Uniform:
return GL_UNIFORM_BUFFER;
default:
UNREACHABLE("Unhandled enum value %d!", static_cast<int>(type));
return GL_INVALID_ENUM;
}
}
static GLenum convertUsageHint(BufferUsageHint usage) {
switch(usage) {
case BufferUsageHint::Static:
return GL_STATIC_DRAW;
case BufferUsageHint::Dynamic:
return GL_DYNAMIC_DRAW;
case BufferUsageHint::Streaming:
return GL_STREAM_DRAW;
case BufferUsageHint::PersistentMapping:
return GL_NONE; // Dummy value
default:
UNREACHABLE("Unhandled enum value %d!", static_cast<int>(usage));
return GL_INVALID_ENUM;
}
}
static GLenum convertStencilOp(const StencilOperation stencil_op) {
switch (stencil_op) {
case StencilOperation::Keep:
return GL_KEEP;
case StencilOperation::Zero:
return GL_ZERO;
case StencilOperation::Replace:
return GL_REPLACE;
case StencilOperation::Increment:
return GL_INCR;
case StencilOperation::IncrementWrap:
return GL_INCR_WRAP;
case StencilOperation::Decrement:
return GL_DECR;
case StencilOperation::DecrementWrap:
return GL_DECR_WRAP;
case StencilOperation::Invert:
return GL_INVERT;
default:
UNREACHABLE("Unhandled enum value %d encountered!", static_cast<int>(stencil_op));
return GL_NONE;
}
}
static GLenum convertComparisionFunction(ComparisionFunction func) {
GLenum mode;
switch (func) {
case ComparisionFunction::Always:
mode = GL_ALWAYS;
break;
case ComparisionFunction::Equal:
mode = GL_EQUAL;
break;
case ComparisionFunction::Greater:
mode = GL_GREATER;
break;
case ComparisionFunction::GreaterOrEqual:
mode = GL_GEQUAL;
break;
case ComparisionFunction::Less:
mode = GL_LESS;
break;
case ComparisionFunction::LessOrEqual:
mode = GL_LEQUAL;
break;
case ComparisionFunction::Never:
mode = GL_NEVER;
break;
case ComparisionFunction::NotEqual:
mode = GL_NOTEQUAL;
break;
default:
UNREACHABLE("Unhandled comparision function value %d!", static_cast<int>(func));
mode = GL_ALWAYS;
break;
}
return mode;
}
gr_buffer_handle opengl_create_buffer_object(GLenum type, GLenum gl_usage, BufferUsageHint usage)
{
GR_DEBUG_SCOPE("Create buffer object");
Assertion(usage != BufferUsageHint::PersistentMapping || GLAD_GL_ARB_buffer_storage != 0,
"Persistent mapping is not supported by this OpenGL implementation!");
opengl_buffer_object buffer_obj;
buffer_obj.gl_usage = gl_usage;
buffer_obj.usage = usage;
buffer_obj.type = type;
buffer_obj.size = 0;
glGenBuffers(1, &buffer_obj.buffer_id);
GL_buffer_objects.push_back(buffer_obj);
return gr_buffer_handle((int)(GL_buffer_objects.size() - 1));
}
void opengl_bind_buffer_object(gr_buffer_handle handle)
{
GR_DEBUG_SCOPE("Bind buffer handle");
Assert(handle.isValid());
Assert((size_t)handle.value() < GL_buffer_objects.size());
opengl_buffer_object &buffer_obj = GL_buffer_objects[handle.value()];
switch ( buffer_obj.type ) {
case GL_ARRAY_BUFFER:
GL_state.Array.BindArrayBuffer(buffer_obj.buffer_id);
break;
case GL_ELEMENT_ARRAY_BUFFER:
GL_state.Array.BindElementBuffer(buffer_obj.buffer_id);
break;
case GL_TEXTURE_BUFFER:
GL_state.Array.BindTextureBuffer(buffer_obj.buffer_id);
break;
case GL_UNIFORM_BUFFER:
GL_state.Array.BindUniformBuffer(buffer_obj.buffer_id);
break;
default:
Int3();
return;
break;
}
}
GLuint opengl_buffer_get_id(GLenum expected_type, gr_buffer_handle handle)
{
Assert(handle.isValid());
Assert((size_t)handle.value() < GL_buffer_objects.size());
opengl_buffer_object& buffer_obj = GL_buffer_objects[handle.value()];
Assertion(expected_type == buffer_obj.type, "Expected buffer type did not match the actual buffer type!");
return buffer_obj.buffer_id;
}
void gr_opengl_update_buffer_data(gr_buffer_handle handle, size_t size, const void* data)
{
// This has to be verified by the caller or else we will run into OPenGL errors
Assertion(size > 0, "Buffer updates must include some data!");
GR_DEBUG_SCOPE("Update buffer data");
Assert(handle.isValid());
Assert((size_t)handle.value() < GL_buffer_objects.size());
opengl_buffer_object &buffer_obj = GL_buffer_objects[handle.value()];
opengl_bind_buffer_object(handle);
if (buffer_obj.usage == BufferUsageHint::PersistentMapping) {
Assertion(buffer_obj.size == 0, "Tried to resize a buffer for persistent mapping! This is not allowed.");
Assertion(GLAD_GL_ARB_buffer_storage != 0, "Persistent mapping was used when it wasn't supported!");
glBufferStorage(buffer_obj.type, size, data, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT);
} else {
glBufferData(buffer_obj.type, size, data, buffer_obj.gl_usage);
}
GL_vertex_data_in -= buffer_obj.size;
buffer_obj.size = size;
GL_vertex_data_in += buffer_obj.size;
}
void gr_opengl_update_buffer_data_offset(gr_buffer_handle handle, size_t offset, size_t size, const void* data)
{
GR_DEBUG_SCOPE("Update buffer data with offset");
Assert(handle.isValid());
Assert((size_t)handle.value() < GL_buffer_objects.size());
opengl_buffer_object &buffer_obj = GL_buffer_objects[handle.value()];
Assertion(buffer_obj.usage != BufferUsageHint::PersistentMapping,
"Persistently mapped buffers may not be updated!");
opengl_bind_buffer_object(handle);
glBufferSubData(buffer_obj.type, offset, size, data);
}
void* gr_opengl_map_buffer(gr_buffer_handle handle)
{
GR_DEBUG_SCOPE("Map buffer");
Assert(handle.isValid());
Assert((size_t)handle.value() < GL_buffer_objects.size());
opengl_buffer_object &buffer_obj = GL_buffer_objects[handle.value()];
Assertion(buffer_obj.usage == BufferUsageHint::PersistentMapping,
"Buffer mapping is only supported for persistently mapped buffers!");
Assertion(GLAD_GL_ARB_buffer_storage != 0, "Persistent mapping is not available in this OpenGL context!");
opengl_bind_buffer_object(handle);
return glMapBufferRange(buffer_obj.type, 0, buffer_obj.size,
GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
}
void gr_opengl_flush_mapped_buffer(gr_buffer_handle handle, size_t offset, size_t size)
{
GR_DEBUG_SCOPE("Flush mapped buffer");
Assert(handle.isValid());
Assert((size_t)handle.value() < GL_buffer_objects.size());
opengl_buffer_object &buffer_obj = GL_buffer_objects[handle.value()];
Assertion(buffer_obj.usage == BufferUsageHint::PersistentMapping,
"Buffer mapping is only supported for persistently mapped buffers!");
Assertion(GLAD_GL_ARB_buffer_storage != 0, "Persistent mapping is not available in this OpenGL context!");
opengl_bind_buffer_object(handle);
glFlushMappedBufferRange(buffer_obj.type, offset, size);
}
void gr_opengl_delete_buffer(gr_buffer_handle handle)
{
if (GL_buffer_objects.size() == 0) return;
GR_DEBUG_SCOPE("Deleting buffer");
Assert(handle.isValid());
Assert((size_t)handle.value() < GL_buffer_objects.size());
opengl_buffer_object &buffer_obj = GL_buffer_objects[handle.value()];
// de-bind the buffer point so we can clear the recorded state.
switch ( buffer_obj.type ) {
case GL_ARRAY_BUFFER:
GL_state.Array.BindArrayBuffer(0);
break;
case GL_ELEMENT_ARRAY_BUFFER:
GL_state.Array.BindElementBuffer(0);
break;
case GL_TEXTURE_BUFFER:
GL_state.Array.BindTextureBuffer(0);
break;
case GL_UNIFORM_BUFFER:
GL_state.Array.BindUniformBuffer(0);
break;
default:
Int3();
return;
break;
}
if ( buffer_obj.type == GL_TEXTURE_BUFFER ) {
glDeleteTextures(1, &buffer_obj.texture);
}
GL_vertex_data_in -= buffer_obj.size;
glDeleteBuffers(1, &buffer_obj.buffer_id);
}
gr_buffer_handle gr_opengl_create_buffer(BufferType type, BufferUsageHint usage, bool /*rt_capable*/)
{
// rt_capable is a Vulkan-only concept (acceleration structure build input
// usage flags); OpenGL has no raytracing backend, so it is ignored here.
return opengl_create_buffer_object(convertBufferType(type), convertUsageHint(usage), usage);
}
void gr_opengl_bind_uniform_buffer(uniform_block_type bind_point, size_t offset, size_t size, gr_buffer_handle buffer) {
GR_DEBUG_SCOPE("Bind uniform buffer range");
GLuint buffer_handle = 0;
if (buffer.isValid()) {
Assert((size_t)buffer.value() < GL_buffer_objects.size());
opengl_buffer_object &buffer_obj = GL_buffer_objects[buffer.value()];
Assertion(buffer_obj.type == GL_UNIFORM_BUFFER, "Only uniform buffers are valid for this function!");
buffer_handle = buffer_obj.buffer_id;
}
glBindBufferRange(GL_UNIFORM_BUFFER, static_cast<GLuint>(bind_point), buffer_handle, static_cast<GLintptr>(offset),
static_cast<GLsizeiptr>(size));
// glBindBufferRange also modifies the generic GL_UNIFORM_BUFFER binding point.
// Sync the state tracker to prevent stale-state skips in subsequent
// opengl_bind_buffer_object calls (which rely on BindUniformBuffer's cache).
GL_state.Array.BindUniformBuffer(buffer_handle);
}
gr_buffer_handle opengl_create_texture_buffer_object()
{
// create the buffer
auto buffer_object_handle =
opengl_create_buffer_object(GL_TEXTURE_BUFFER, GL_DYNAMIC_DRAW, BufferUsageHint::Dynamic);
opengl_check_for_errors();
opengl_buffer_object& buffer_obj = GL_buffer_objects[buffer_object_handle.value()];
// create the texture
glGenTextures(1, &buffer_obj.texture);
glBindTexture(GL_TEXTURE_BUFFER, buffer_obj.texture);
gr_opengl_update_buffer_data(buffer_object_handle, 100, NULL);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, buffer_obj.buffer_id);
opengl_check_for_errors();
return buffer_object_handle;
}
void gr_opengl_update_transform_buffer(void* data, size_t size)
{
if (!Transform_buffer_handle.isValid() || size <= 0) {
return;
}
gr_opengl_update_buffer_data(Transform_buffer_handle, size, data);
opengl_buffer_object &buffer_obj = GL_buffer_objects[Transform_buffer_handle.value()];
// need to rebind the buffer object to the texture buffer after it's been updated.
// didn't have to do this on AMD and Nvidia drivers but Intel drivers seem to want it.
glBindTexture(GL_TEXTURE_BUFFER, buffer_obj.texture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, buffer_obj.buffer_id);
}
GLuint opengl_get_transform_buffer_texture()
{
if (!Transform_buffer_handle.isValid()) {
return 0;
}
return GL_buffer_objects[Transform_buffer_handle.value()].texture;
}
void opengl_destroy_all_buffers()
{
for ( uint i = 0; i < GL_buffer_objects.size(); i++ ) {
gr_opengl_delete_buffer(gr_buffer_handle(i));
}
GL_vertex_buffers_in_use = 0;
}
static bool opengl_init_shadow_framebuffer(int size)
{
mprintf(("Trying to create %dx%d 24-bit shadow framebuffer\n", size, size));
glGenFramebuffers(1, &shadow_fbo);
GL_state.BindFrameBuffer(shadow_fbo);
glGenTextures(1, &Shadow_map_depth_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D_ARRAY);
GL_state.Texture.Enable(Shadow_map_depth_texture);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT24, size, size, Num_shadow_cascades + Num_cockpit_shadow_cascades, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, Shadow_map_depth_texture, 0);
auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
GL_state.BindFrameBuffer(0);
if (status == GL_FRAMEBUFFER_COMPLETE) {
// Everything is fine
mprintf(("Shadow framebuffer created successfully.\n"));
Shadow_texture_size = size;
return true;
}
// Clean up resources
glDeleteTextures(1, &Shadow_map_depth_texture);
glDeleteFramebuffers(1, &shadow_fbo);
Shadow_map_depth_texture = 0;
shadow_fbo = 0;
const char* error;
switch (status) {
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
error = "Incomplete framebuffer attachment";
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
error = "Framebuffer is missing an attachment";
break;
case GL_FRAMEBUFFER_UNSUPPORTED:
error = "Framebuffer configuration is unsupported";
break;
default:
error = "Unknown framebuffer status";
break;
}
mprintf(("Failed to create framebuffer: %s\n", error));
return false;
}
void opengl_tnl_init()
{
Transform_buffer_handle = opengl_create_texture_buffer_object();
if (Shadow_quality != ShadowQuality::Disabled) {
int size;
switch (Shadow_quality) {
case ShadowQuality::Low:
size = 512;
break;
case ShadowQuality::Medium:
size = 1024;
break;
case ShadowQuality::High:
size = 2048;
break;
case ShadowQuality::Ultra:
size = 4096;
break;
default:
size = 256;
break;
}
if (!opengl_init_shadow_framebuffer(size)) {
mprintf(("Failed to create either shadow framebuffer. Disabling shadow support.\n"));
Shadow_quality = ShadowQuality::Disabled;
}
if (Shadow_quality != ShadowQuality::Disabled) {
shadow_cascade_params_init();
}
}
gr_opengl_deferred_init();
}
void opengl_tnl_shutdown()
{
for (auto& vao_entry : Stored_vertex_arrays) {
glDeleteVertexArrays(1, &vao_entry.second);
}
Stored_vertex_arrays.clear();
gr_opengl_deferred_shutdown();
shadow_cascade_params_shutdown();
if ( Shadow_map_depth_texture ) {
glDeleteTextures(1, &Shadow_map_depth_texture);
Shadow_map_depth_texture = 0;
}
opengl_destroy_all_buffers();
}
void opengl_render_model_program(model_material* material_info, indexed_vertex_source *vert_source, vertex_buffer* bufferp, buffer_data *datap)
{
GL_state.Texture.SetShaderMode(GL_TRUE);
opengl_tnl_set_model_material(material_info);
auto ibuffer = reinterpret_cast<GLubyte*>(vert_source->Index_offset);
GLenum element_type = (datap->flags & VB_FLAG_LARGE_INDEX) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
Assert(vert_source);
Assertion(vert_source->Vbuffer_handle.isValid(), "The vertex data must be located in a GPU buffer!");
Assertion(vert_source->Ibuffer_handle.isValid(), "The index values must be located in a GPU buffer!");
// basic setup of all data
opengl_bind_vertex_layout(bufferp->layout,
opengl_buffer_get_id(GL_ARRAY_BUFFER, vert_source->Vbuffer_handle),
opengl_buffer_get_id(GL_ELEMENT_ARRAY_BUFFER, vert_source->Ibuffer_handle));
if (Cmdline_drawelements) {
glDrawElementsBaseVertex(GL_TRIANGLES,
(GLsizei) datap->n_verts,
element_type,
ibuffer + datap->index_offset,
(GLint) (vert_source->Base_vertex_offset + bufferp->vertex_num_offset));
} else {
glDrawRangeElementsBaseVertex(GL_TRIANGLES,
datap->i_first,
datap->i_last,
(GLsizei) datap->n_verts,
element_type,
ibuffer + datap->index_offset,
(GLint) (vert_source->Base_vertex_offset + bufferp->vertex_num_offset));
}
GL_state.Texture.SetShaderMode(GL_FALSE);
}
void gr_opengl_render_model(model_material* material_info, indexed_vertex_source *vert_source, vertex_buffer* bufferp, size_t texi)
{
Verify(bufferp != NULL);
GL_CHECK_FOR_ERRORS("start of render_buffer()");
buffer_data *datap = &bufferp->tex_buf[texi];
opengl_render_model_program(material_info, vert_source, bufferp, datap);
GL_CHECK_FOR_ERRORS("end of render_buffer()");
}
void gr_opengl_render_shadow_draw(gr_buffer_handle ubo_handle, size_t ubo_offset, size_t ubo_size,
vertex_buffer* buffer, indexed_vertex_source* vert_src, size_t texi)
{
auto* datap = &buffer->tex_buf[texi];
if (datap->n_verts == 0) {
return;
}
int shader_handle = gr_opengl_maybe_create_shader(SDR_TYPE_SHADOW_MAP_GEN, gr_is_capable(gr_capability::CAPABILITY_FAST_SHADOWS) ? 0 : SDR_FLAG_SHADOW_FALLBACK);
opengl_shader_set_current(shader_handle);
GL_state.Texture.SetShaderMode(GL_TRUE);
gr_bind_uniform_buffer(uniform_block_type::ShadowMapData, ubo_offset, ubo_size, ubo_handle);
// Bind the transform texture buffer so the vertex shader can read model transforms
Current_shader->program->Uniforms.setTextureUniform("transform_tex", 10);
GL_state.Texture.Enable(10, GL_TEXTURE_BUFFER, opengl_get_transform_buffer_texture());
GL_state.SetAlphaBlendMode(ALPHA_BLEND_NONE);
gr_zbuffer_set(ZBUFFER_TYPE_FULL);
gr_set_cull(1);
gr_zbias(-1024);
gr_set_fill_mode(GR_FILL_MODE_SOLID);
GL_state.ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
GL_state.FrontFaceValue(gr_screen.rendering_to_texture != -1 ? GL_CCW : GL_CW);
// Enable clip distance; actual clipping is gated by the use_clip_plane uniform in the shader
GL_state.ClipDistance(0, true);
opengl_bind_vertex_layout(buffer->layout,
opengl_buffer_get_id(GL_ARRAY_BUFFER, vert_src->Vbuffer_handle),
opengl_buffer_get_id(GL_ELEMENT_ARRAY_BUFFER, vert_src->Ibuffer_handle));
auto ibuffer = reinterpret_cast<GLubyte*>(vert_src->Index_offset);
GLenum element_type = (datap->flags & VB_FLAG_LARGE_INDEX) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
auto base_vertex = static_cast<GLint>(vert_src->Base_vertex_offset + buffer->vertex_num_offset);
//Funnily enough, both the modern shadow rendering (using shader_viewport_layer_array), and the super-old fallback without shader5 use the same instanced draw call
if (gr_is_capable(gr_capability::CAPABILITY_FAST_SHADOWS) || !GLAD_GL_ARB_gpu_shader5) {
glDrawElementsInstancedBaseVertex(GL_TRIANGLES,
(GLsizei)datap->n_verts,
element_type,
ibuffer + datap->index_offset,
Shadow_cascade_count,
base_vertex);
}
else {
glDrawElementsBaseVertex(GL_TRIANGLES,
(GLsizei)datap->n_verts,
element_type,
ibuffer + datap->index_offset,
base_vertex);
}
GL_state.Texture.SetShaderMode(GL_FALSE);
}
extern GLuint Framebuffer_fallback_texture_id;
extern GLuint Scene_depth_texture;
extern GLuint Scene_position_texture;
extern GLuint Distortion_texture[2];
extern int Distortion_switch;
void opengl_create_perspective_projection_matrix(matrix4 *out, float left, float right, float bottom, float top, float near_dist, float far_dist)
{
memset(out, 0, sizeof(matrix4));
out->a1d[0] = 2.0f * near_dist / (right - left);
out->a1d[5] = 2.0f * near_dist / (top - bottom);
out->a1d[8] = (right + left) / (right - left);
out->a1d[9] = (top + bottom) / (top - bottom);
out->a1d[10] = -(far_dist + near_dist) / (far_dist - near_dist);
out->a1d[11] = -1.0f;
out->a1d[14] = -2.0f * far_dist * near_dist / (far_dist - near_dist);
}
void opengl_create_orthographic_projection_matrix(matrix4* out, float left, float right, float bottom, float top, float near_dist, float far_dist)
{
memset(out, 0, sizeof(matrix4));
out->a1d[0] = 2.0f / (right - left);
out->a1d[5] = 2.0f / (top - bottom);
out->a1d[10] = -2.0f / (far_dist - near_dist);
out->a1d[12] = -(right + left) / (right - left);
out->a1d[13] = -(top + bottom) / (top - bottom);
out->a1d[14] = -(far_dist + near_dist) / (far_dist - near_dist);
out->a1d[15] = 1.0f;
}
extern bool Glowpoint_override;
bool Glowpoint_override_save;
extern bool gr_htl_projection_matrix_set;
void gr_opengl_shadow_map_start(matrix4 *shadow_view_matrix, const matrix *light_orient, vec3d* eye_pos, bool first_pass)
{
if (Shadow_quality == ShadowQuality::Disabled)
return;
if (first_pass) {
GL_state.PushFramebufferState();
GL_state.BindFrameBuffer(shadow_fbo);
glDrawBuffers(0, nullptr);
glClear(GL_DEPTH_BUFFER_BIT);
#ifdef USE_OPENGL_ES
if (GLAD_GL_EXT_depth_clamp) {
glEnable(GL_DEPTH_CLAMP);
}
#else
glEnable(GL_DEPTH_CLAMP);
#endif
Glowpoint_override_save = Glowpoint_override;
Glowpoint_override = true;
gr_htl_projection_matrix_set = true;
} else {
gr_end_view_matrix();
}
gr_set_view_matrix(eye_pos, light_orient);
*shadow_view_matrix = gr_view_matrix;
glViewport(0, 0, Shadow_texture_size, Shadow_texture_size);
}
void gr_opengl_shadow_map_end()
{
gr_end_view_matrix();
#ifdef USE_OPENGL_ES
if (GLAD_GL_EXT_depth_clamp) {
glDisable(GL_DEPTH_CLAMP);
}
#else
glDisable(GL_DEPTH_CLAMP);
#endif
gr_zbuffer_set(ZBUFFER_TYPE_FULL);
GL_state.PopFramebufferState();
Glowpoint_override = Glowpoint_override_save;
gr_htl_projection_matrix_set = false;
glViewport(gr_screen.offset_x, (gr_screen.max_h - gr_screen.offset_y - gr_screen.clip_height), gr_screen.clip_width, gr_screen.clip_height);
glScissor(gr_screen.offset_x, (gr_screen.max_h - gr_screen.offset_y - gr_screen.clip_height), gr_screen.clip_width, gr_screen.clip_height);
}
void opengl_tnl_set_material(material* material_info, bool set_base_map, bool set_clipping)
{
int shader_handle = material_info->get_shader_handle();
int base_map = material_info->get_texture_map(TM_BASE_TYPE);
vec4 clr = material_info->get_color();
Assert(shader_handle >= 0);
opengl_shader_set_current(shader_handle);
if (material_info->has_buffer_blend_modes()) {
Assertion(GLAD_GL_ARB_draw_buffers_blend != 0,
"Buffer blend modes are not supported at the moment! Query the capability before using this feature.");
auto enable_blend = false;
for (auto i = 0; i < (int) material::NUM_BUFFER_BLENDS; ++i) {
auto mode = material_info->get_blend_mode(i);
GL_state.SetAlphaBlendModei(i, mode);
enable_blend = enable_blend || mode != ALPHA_BLEND_NONE;
}
GL_state.Blend(enable_blend ? GL_TRUE : GL_FALSE);
} else {
GL_state.SetAlphaBlendMode(material_info->get_blend_mode());
}
GL_state.SetZbufferType(material_info->get_depth_mode());
gr_set_cull(material_info->get_cull_mode() ? 1 : 0);
gr_zbias(material_info->get_depth_bias());
gr_set_fill_mode(material_info->get_fill_mode());
gr_set_texture_addressing(material_info->get_texture_addressing());
if (set_clipping) {
// Only set the clipping state if explicitly requested by the caller to avoid unnecessary state changes
auto& clip_params = material_info->get_clip_plane();
if (!clip_params.enabled) {
GL_state.ClipDistance(0, false);
} else {
Assertion(Current_shader != nullptr && (Current_shader->shader == SDR_TYPE_MODEL
|| Current_shader->shader == SDR_TYPE_DEFAULT_MATERIAL),
"Clip planes are not supported by this shader!");
GL_state.ClipDistance(0, true);
}
}
GL_state.StencilMask(material_info->get_stencil_mask());
auto& stencilFunc = material_info->get_stencil_func();
GL_state.StencilFunc(convertComparisionFunction(stencilFunc.compare), stencilFunc.ref, stencilFunc.mask);
auto& frontStencilOp = material_info->get_front_stencil_op();
GL_state.StencilOpSeparate(GL_FRONT,
convertStencilOp(frontStencilOp.stencilFailOperation),
convertStencilOp(frontStencilOp.depthFailOperation),
convertStencilOp(frontStencilOp.successOperation));
auto& backStencilOp = material_info->get_back_stencil_op();
GL_state.StencilOpSeparate(GL_BACK,
convertStencilOp(backStencilOp.stencilFailOperation),
convertStencilOp(backStencilOp.depthFailOperation),
convertStencilOp(backStencilOp.successOperation));
GL_state.StencilTest(material_info->is_stencil_enabled() ? GL_TRUE : GL_FALSE);
auto& color_mask = material_info->get_color_mask();
GL_state.ColorMask(color_mask.x, color_mask.y, color_mask.z, color_mask.w);
// This is only needed for the passthrough shader
uint32_t array_index = 0;
if ( set_base_map && base_map >= 0 ) {
float u_scale, v_scale;
if ( !gr_opengl_tcache_set(base_map, material_info->get_texture_type(), &u_scale, &v_scale, &array_index) ) {
mprintf(("WARNING: Error setting bitmap texture (%i)!\n", base_map));
}
}
if ( Current_shader->shader == SDR_TYPE_DEFAULT_MATERIAL ) {
opengl_shader_set_default_material(base_map >= 0,
material_info->get_texture_type() == TCACHE_TYPE_AABITMAP,
&clr,
material_info->get_color_scale(),
array_index,
material_info->get_clip_plane());
}
}
void opengl_tnl_set_model_material(model_material *material_info)
{
float u_scale, v_scale;
opengl_tnl_set_material(material_info, false, false);
if ( GL_state.CullFace() ) {
// Model rendering uses CW front faces in the default backbuffer path.
// Render-to-texture flips the projection vertically, which flips winding,
// so we must invert the front-face definition there.
GL_state.FrontFaceValue(gr_screen.rendering_to_texture != -1 ? GL_CCW : GL_CW);
}
gr_set_center_alpha(material_info->get_center_alpha());
Assert( Current_shader->shader == SDR_TYPE_MODEL );
GL_state.Texture.SetShaderMode(GL_TRUE);
if (material_info->is_clipped()) {
GL_state.ClipDistance(0, true);
} else {
GL_state.ClipDistance(0, false);
}
uint32_t array_index;
if (!material_info->is_shadow_casting()) {
// An observant reader might, upon seeing this, ask themselves "Hang on, why are we setting these uniforms
// without putting anything in them". This is an entirely fair question.
// The answer, dear reader, is divergent behaviour in GL implementations. Nvidia, at time of writing (04.01.2023)
// doesn't care; AMD will report conflicting bindings and basically give up.
// While this technically invites undefined behaviour (texture reads from unbound texture units can do anything),
// it is uncritical at this time as texture reads are gated behind feature flags in the shader.
// This will be fixed in future cleanups, where we plan to introduce engine-generated default textures to substitute
// if the material doesn't provide anything.
const bool setAllUniforms = gr_is_capable(gr_capability::CAPABILITY_LARGE_SHADER);
const int flags = material_info->get_shader_runtime_early_flags() | material_info->get_shader_runtime_flags();
if (setAllUniforms || (flags & MODEL_SDR_FLAG_DIFFUSE))
Current_shader->program->Uniforms.setTextureUniform("sBasemap", 0);
if (setAllUniforms || (flags & MODEL_SDR_FLAG_GLOW))
Current_shader->program->Uniforms.setTextureUniform("sGlowmap", 1);
if (setAllUniforms || (flags & MODEL_SDR_FLAG_SPEC))
Current_shader->program->Uniforms.setTextureUniform("sSpecmap", 2);
if (setAllUniforms || (flags & MODEL_SDR_FLAG_NORMAL))
Current_shader->program->Uniforms.setTextureUniform("sNormalmap", 4);
if (setAllUniforms || (flags & MODEL_SDR_FLAG_AMBIENT))
Current_shader->program->Uniforms.setTextureUniform("sAmbientmap", 6);
if (setAllUniforms || (flags & MODEL_SDR_FLAG_MISC))
Current_shader->program->Uniforms.setTextureUniform("sMiscmap", 7);
if (setAllUniforms || (flags & MODEL_SDR_FLAG_SHADOWS))
Current_shader->program->Uniforms.setTextureUniform("shadow_map", 8);
Current_shader->program->Uniforms.setTextureUniform("sFramebuffer", 9);
if (setAllUniforms || (flags & MODEL_SDR_FLAG_TRANSFORM))
Current_shader->program->Uniforms.setTextureUniform("transform_tex", 10);
//No shader ever defines this, so don't push it.
//Current_shader->program->Uniforms.setTextureUniform("sHeightmap", 5);
if (material_info->get_texture_map(TM_BASE_TYPE) >= 0) {
gr_opengl_tcache_set(material_info->get_texture_map(TM_BASE_TYPE),
TCACHE_TYPE_NORMAL,
&u_scale,
&v_scale,
&array_index,
0);
}
if (material_info->get_texture_map(TM_GLOW_TYPE) >= 0) {
gr_opengl_tcache_set(material_info->get_texture_map(TM_GLOW_TYPE),
TCACHE_TYPE_NORMAL,
&u_scale,
&v_scale,
&array_index,
1);
}
if (material_info->get_texture_map(TM_SPECULAR_TYPE) >= 0 ||
material_info->get_texture_map(TM_SPEC_GLOSS_TYPE) >= 0) {
if (material_info->get_texture_map(TM_SPEC_GLOSS_TYPE) >= 0) {
gr_opengl_tcache_set(material_info->get_texture_map(TM_SPEC_GLOSS_TYPE),
TCACHE_TYPE_NORMAL,
&u_scale,
&v_scale,
&array_index,
2);
} else {
gr_opengl_tcache_set(material_info->get_texture_map(TM_SPECULAR_TYPE),
TCACHE_TYPE_NORMAL,
&u_scale,
&v_scale,
&array_index,
2);
}
}
if (material_info->get_texture_map(TM_NORMAL_TYPE) >= 0) {
gr_opengl_tcache_set(material_info->get_texture_map(TM_NORMAL_TYPE),
TCACHE_TYPE_NORMAL,
&u_scale,
&v_scale,
&array_index,
4);
}
if (material_info->get_texture_map(TM_HEIGHT_TYPE) >= 0) {
gr_opengl_tcache_set(material_info->get_texture_map(TM_HEIGHT_TYPE),
TCACHE_TYPE_NORMAL,
&u_scale,
&v_scale,
&array_index,
5);
}
if (material_info->get_texture_map(TM_AMBIENT_TYPE) >= 0) {
gr_opengl_tcache_set(material_info->get_texture_map(TM_AMBIENT_TYPE),
TCACHE_TYPE_NORMAL,
&u_scale,
&v_scale,
&array_index,
6);
}
if (material_info->get_texture_map(TM_MISC_TYPE) >= 0) {
gr_opengl_tcache_set(material_info->get_texture_map(TM_MISC_TYPE),
TCACHE_TYPE_NORMAL,
&u_scale,
&v_scale,
&array_index,
7);
}
if (material_info->is_shadow_receiving()) {
GL_state.Texture.Enable(8, GL_TEXTURE_2D_ARRAY, Shadow_map_depth_texture);
}
if (material_info->get_animated_effect() >= 0) {
if (Scene_framebuffer_in_frame) {
GL_state.Texture.Enable(9, GL_TEXTURE_2D, Scene_composite_texture);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
} else {
GL_state.Texture.Enable(9, GL_TEXTURE_2D, Framebuffer_fallback_texture_id);
}
}
}
if ( material_info->is_batched() ) {
GL_state.Texture.Enable(10, GL_TEXTURE_BUFFER, opengl_get_transform_buffer_texture());
}
}
void opengl_tnl_set_material_particle(particle_material * material_info)
{
opengl_tnl_set_material(material_info, true);
gr_matrix_set_uniforms();
opengl_set_generic_uniform_data<graphics::generic_data::effect_data>(
[&](graphics::generic_data::effect_data* data) {
data->window_width = (float)gr_screen.max_w;
data->window_height = (float)gr_screen.max_h;
data->nearZ = Min_draw_distance;
data->farZ = Max_draw_distance;
data->srgb = High_dynamic_range ? 1 : 0;
data->blend_alpha = material_info->get_blend_mode() != ALPHA_BLEND_ADDITIVE;
if (!light_deferred_enabled()) {
data->linear_depth = 0;
} else {
data->linear_depth = 1;
}
});
Current_shader->program->Uniforms.setTextureUniform("baseMap", 0);
Current_shader->program->Uniforms.setTextureUniform("depthMap", 1);
if (light_deferred_enabled()) {
Assert(Scene_position_texture != 0);
GL_state.Texture.Enable(1, GL_TEXTURE_2D, Scene_position_texture);
} else {
Assert(Scene_depth_texture != 0);
GL_state.Texture.Enable(1, GL_TEXTURE_2D, Scene_depth_texture);
}
}
void opengl_tnl_set_material_batched(batched_bitmap_material* material_info)
{
// This material assumes that the array index is supplied via the vertex attributes
opengl_tnl_set_material(material_info, true);
opengl_set_generic_uniform_data<graphics::generic_data::batched_data>(
[&](graphics::generic_data::batched_data* data) {
data->intensity = material_info->get_color_scale();
data->color = material_info->get_color();
});
gr_matrix_set_uniforms();
Current_shader->program->Uniforms.setTextureUniform("baseMap", 0);
}
void opengl_tnl_set_material_distortion(distortion_material* material_info)
{
opengl_tnl_set_material(material_info, true);
gr_matrix_set_uniforms();
Current_shader->program->Uniforms.setTextureUniform("baseMap", 0);
Current_shader->program->Uniforms.setTextureUniform("depthMap", 1);
opengl_set_generic_uniform_data<graphics::generic_data::effect_distort_data>(
[&](graphics::generic_data::effect_distort_data* data) {
data->window_width = (float)gr_screen.max_w;
data->window_height = (float)gr_screen.max_h;
if (material_info->get_thruster_rendering()) {
data->use_offset = 1.0f;
} else {
data->use_offset = 0.0f;
}
});
Current_shader->program->Uniforms.setTextureUniform("frameBuffer", 2);
GL_state.Texture.Enable(2, GL_TEXTURE_2D, Scene_composite_texture);
Current_shader->program->Uniforms.setTextureUniform("distMap", 3);
if (material_info->get_thruster_rendering()) {
GL_state.Texture.Enable(3, GL_TEXTURE_2D, Distortion_texture[!Distortion_switch]);
} else {
// Disable this texture unit
GL_state.Texture.Enable(3, GL_TEXTURE_2D, 0);
}
Assert(Scene_depth_texture != 0);
GL_state.Texture.Enable(1, GL_TEXTURE_2D, Scene_depth_texture);
}
void opengl_tnl_set_material_movie(movie_material* material_info) {
opengl_tnl_set_material(material_info, false);
gr_matrix_set_uniforms();
auto uniform_buffer = gr_get_uniform_buffer(uniform_block_type::MovieData, 1);
auto& aligner = uniform_buffer.aligner();
auto movie_data = aligner.addTypedElement<graphics::movie_uniforms>();
movie_data->alpha = material_info->get_color().xyzw.w;
uniform_buffer.submitData();
gr_bind_uniform_buffer(uniform_block_type::MovieData,
uniform_buffer.getBufferOffset(0),
sizeof(graphics::movie_uniforms),
uniform_buffer.bufferHandle());
Current_shader->program->Uniforms.setTextureUniform("ytex", 0);
Current_shader->program->Uniforms.setTextureUniform("utex", 1);
Current_shader->program->Uniforms.setTextureUniform("vtex", 2);
float u_scale, v_scale;
uint32_t index;
if ( !gr_opengl_tcache_set(material_info->getYtex(), material_info->get_texture_type(), &u_scale, &v_scale, &index, 0) ) {
mprintf(("WARNING: Error setting bitmap texture (%i)!\n", material_info->getYtex()));
}
if ( !gr_opengl_tcache_set(material_info->getUtex(), material_info->get_texture_type(), &u_scale, &v_scale, &index, 1) ) {
mprintf(("WARNING: Error setting bitmap texture (%i)!\n", material_info->getUtex()));
}
if ( !gr_opengl_tcache_set(material_info->getVtex(), material_info->get_texture_type(), &u_scale, &v_scale, &index, 2) ) {
mprintf(("WARNING: Error setting bitmap texture (%i)!\n", material_info->getVtex()));
}
}
void opengl_tnl_set_material_nanovg(nanovg_material* material_info) {
opengl_tnl_set_material(material_info, true);
Current_shader->program->Uniforms.setTextureUniform("nvg_tex", 0);
}
void opengl_tnl_set_material_decal(decal_material* material_info) {
opengl_tnl_set_material(material_info, false);
#ifdef USE_OPENGL_ES
// ES decal outputs use G-buffer locations instead of packed draw-buffer indices.
GL_state.SetAlphaBlendModei(2, material_info->get_blend_mode(1));
GL_state.SetAlphaBlendModei(4, material_info->get_blend_mode(2));
#endif
float u_scale, v_scale;
uint32_t array_index;
if (gr_opengl_tcache_set(material_info->get_texture_map(TM_BASE_TYPE),
material_info->get_texture_type(),
&u_scale,
&v_scale,
&array_index,
0)) {
Current_shader->program->Uniforms.setTextureUniform("diffuseMap", 0);
}
if (gr_opengl_tcache_set(material_info->get_texture_map(TM_GLOW_TYPE),
material_info->get_texture_type(),
&u_scale,
&v_scale,
&array_index,
1)) {
Current_shader->program->Uniforms.setTextureUniform("glowMap", 1);
}
if (gr_opengl_tcache_set(material_info->get_texture_map(TM_NORMAL_TYPE),
material_info->get_texture_type(),
&u_scale,
&v_scale,
&array_index,
2)) {
Current_shader->program->Uniforms.setTextureUniform("normalMap", 2);
}
GL_state.Texture.Enable(3, GL_TEXTURE_2D, Scene_depth_texture);
Current_shader->program->Uniforms.setTextureUniform("gDepthBuffer", 3);
if (Current_shader->flags & SDR_FLAG_DECAL_USE_NORMAL_MAP) {
GL_state.Texture.Enable(4, GL_TEXTURE_2D, Scene_normal_texture);
Current_shader->program->Uniforms.setTextureUniform("gNormalBuffer", 4);
}
}
void opengl_tnl_set_rocketui_material(interface_material* material_info)
{
opengl_tnl_set_material(material_info, false);
Current_shader->program->Uniforms.setTextureUniform("baseMap", 0);
uint32_t baseMapIndex = 0;
if (material_info->is_textured()) {
float u_scale, v_scale;
if (!gr_opengl_tcache_set(material_info->get_texture_map(TM_BASE_TYPE), material_info->get_texture_type(),
&u_scale, &v_scale, &baseMapIndex)) {
mprintf(("WARNING: Error setting bitmap texture (%i)!\n", material_info->get_texture_map(TM_BASE_TYPE)));
}
}
const vec2d& offset = material_info->get_offset();
opengl_set_generic_uniform_data<graphics::generic_data::rocketui_data>(
[&](graphics::generic_data::rocketui_data* data) {
data->projMatrix = gr_projection_matrix;
data->offset = offset;
data->textured = material_info->is_textured() ? GL_TRUE : GL_FALSE;
data->baseMapIndex = baseMapIndex;
data->horizontalSwipeOffset = material_info->get_horizontal_swipe();
});
}
void gr_opengl_set_viewport(int x, int y, int width, int height) {
glViewport(x, y, width, height);
}
void opengl_bind_vertex_component(const vertex_format_data &vert_component, size_t base_offset)
{
opengl_vertex_bind &bind_info = GL_array_binding_data[vert_component.format_type];
opengl_vert_attrib &attrib_info = GL_vertex_attrib_info[bind_info.attribute_id];
Assert(bind_info.attribute_id == attrib_info.attribute_id);
GLubyte *data_src = reinterpret_cast<GLubyte*>(base_offset) + vert_component.offset;
if ( Current_shader != NULL ) {
// grabbing a vertex attribute is dependent on what current shader has been set. i hope no one calls opengl_bind_vertex_layout before opengl_set_current_shader
GLint index = attrib_info.attribute_id;
if ( index >= 0 ) {
GL_state.Array.EnableVertexAttrib(index);
GL_state.Array.VertexAttribPointer(index, bind_info.size, bind_info.data_type, bind_info.normalized, (GLsizei)vert_component.stride, data_src);
}
}
}
void opengl_bind_dynamic_layout(vertex_layout& layout, size_t base_offset) {
GL_state.BindVertexArray(GL_vao);
GL_state.Array.BindPointersBegin();
size_t num_vertex_bindings = layout.get_num_vertex_components();
for ( size_t i = 0; i < num_vertex_bindings; ++i ) {
opengl_bind_vertex_component(*layout.get_vertex_component(i), base_offset);
}
GL_state.Array.BindPointersEnd();
}
void opengl_bind_vertex_array(const vertex_layout& layout) {
auto iter = Stored_vertex_arrays.find(layout);
if (iter != Stored_vertex_arrays.end()) {
// Found existing vertex array!
GL_state.BindVertexArray(iter->second);
return;
}
GR_DEBUG_SCOPE("Create Vertex array");
GLuint vao;
glGenVertexArrays(1, &vao);
GL_state.BindVertexArray(vao);
for (size_t i = 0; i < layout.get_num_vertex_components(); ++i) {
auto component = layout.get_vertex_component(i);
auto& bind_info = GL_array_binding_data[component->format_type];
auto& attrib_info = GL_vertex_attrib_info[bind_info.attribute_id];
auto attribIndex = attrib_info.attribute_id;
GLuint add_val_index = 0;
for (GLint size = bind_info.size; size > 0; size -=4) {
glEnableVertexAttribArray(attribIndex + add_val_index);
glVertexAttribFormat(attribIndex + add_val_index,
std::min(size, 4),
bind_info.data_type,
bind_info.normalized,
static_cast<GLuint>(component->offset) + add_val_index * 16);
glVertexAttribBinding(attribIndex + add_val_index, static_cast<GLuint>(component->buffer_number));
add_val_index++;
}
if (component->divisor != 0) {
glVertexBindingDivisor(static_cast<GLuint>(component->buffer_number), static_cast<GLuint>(component->divisor));
}
}
Stored_vertex_arrays.insert(std::make_pair(layout, vao));
}
void opengl_bind_vertex_layout(vertex_layout &layout, GLuint vertexBuffer, GLuint indexBuffer, size_t base_offset)
{
GR_DEBUG_SCOPE("Bind vertex layout");
if (!GLAD_GL_ARB_vertex_attrib_binding) {
// We don't have support for the new vertex binding functions so fall back to the single VAO implementation
GL_state.Array.BindArrayBuffer(vertexBuffer);
GL_state.Array.BindElementBuffer(indexBuffer);
opengl_bind_dynamic_layout(layout, base_offset);
return;
}
opengl_bind_vertex_array(layout);
GL_state.Array.BindVertexBuffer(0,
vertexBuffer,
static_cast<GLintptr>(base_offset),
static_cast<GLsizei>(layout.get_vertex_stride()));
GL_state.Array.BindElementBuffer(indexBuffer);
}
void opengl_bind_vertex_layout_multiple(vertex_layout &layout, const SCP_vector<GLuint>& vertexBuffer, GLuint indexBuffer, size_t base_offset) {
GR_DEBUG_SCOPE("Bind vertex layout");
if (!GLAD_GL_ARB_vertex_attrib_binding) {
/*
* This will mean that decals don't render.
* It's possible, but way too much effort to support non-instanced fallback rendering here.
* By my estimation, every GPU you might still run FSO run supports this.
* per mesamatrix.net, even the least-extension supporting mesa drivers all support this extension
* */
return;
}
opengl_bind_vertex_array(layout);
GLuint i = 0;
for(const auto& buffer : vertexBuffer) {
GL_state.Array.BindVertexBuffer(i,
buffer,
static_cast<GLintptr>(base_offset),
static_cast<GLsizei>(layout.get_vertex_stride(i)));
i++;
}
GL_state.Array.BindElementBuffer(indexBuffer);
}