mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-11 04:31:35 -10:00
The shadow-mapped path's penumbra width was a fixed per-cascade value (shadow_smoothness_scale()'s ceiling), so every occluder blurred its shadow by the same amount regardless of how close it actually was to the receiver -- unlike the raytraced path, which gets that for free from traceShadowRayCone()'s real occluder distance. Close that gap with PCSS: search nearby texels for occluders, then scale the filter radius by how far away they were. The blocker search (pcssBlockerSearch() in shadows.sdr) needs raw, uncompared depth reads, which the existing PCF sampler can't do -- it's bound with a compare mode for the hardware shadow-compare instructions the filtered sampling itself relies on. So shadow maps now get a second sampler, shadow_map_raw, bound to the same texture with compare mode off; on OpenGL that needs a second sampler object and GL 3.3 (shadow_contact_hardening_supported()), Vulkan can always bind it. Where the raw sampler isn't available, or the new Graphics.ShadowContactHardening option is off, shadows_start_render() sets Shadow_penumbra_scale[cascade] to -1 as a sentinel and pcssPenumbraRadius() (shadows.sdr) falls straight back to the fixed radius, bit-identical to the pre-contact-hardening behavior and skipping the extra texture reads. When it is on, shadows_start_render() computes each cascade's penumbra scale from its own frustum extents and the sun's tanθ; the shader turns that plus the blocker search's average depth into a per-pixel radius, clamped to [1 texel, the old fixed ceiling]. A blocker search that finds nothing doesn't fall back to "no shadow" -- a sparse 8-tap search can miss a thin occluder, so it instead runs the full filter at the hardest (1-texel) radius, trading a touch of extra blur for not popping a real shadow to nothing. Drops RT_SHADOW_SUN_SIZE_CALIBRATION accordingly: that constant existed only to fake a blocker-distance-driven penumbra on the shadow-mapped path by pre-scaling the raytraced cone to match it, and the shadow-mapped path now has a real blocker search of its own, so the raytraced cone goes back to its physically correct size.
1415 lines
47 KiB
C++
1415 lines
47 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;
|
|
|
|
// Running total of the bytes held by live vertex and index buffers, for the profiler overlay's
|
|
// memory panel (see opengl_get_geometry_bytes()). Uniform buffers and texture buffers stay out of
|
|
// this total, which keeps it equal to the "geometry" group of the Vulkan backend.
|
|
static size_t GL_geometry_bytes_used = 0;
|
|
|
|
// Tells if a buffer of this OpenGL type counts as geometry for GL_geometry_bytes_used.
|
|
static bool opengl_buffer_is_geometry(GLenum type)
|
|
{
|
|
return (type == GL_ARRAY_BUFFER) || (type == GL_ELEMENT_ARRAY_BUFFER);
|
|
}
|
|
|
|
size_t opengl_get_geometry_bytes()
|
|
{
|
|
return GL_geometry_bytes_used;
|
|
}
|
|
|
|
GLint GL_max_elements_vertices = 4096;
|
|
GLint GL_max_elements_indices = 4096;
|
|
|
|
GLuint Shadow_map_depth_texture = 0;
|
|
GLuint Shadow_map_raw_sampler = 0;
|
|
GLuint shadow_fbo = 0;
|
|
int Shadow_texture_size = 0;
|
|
|
|
// Number of layers in the shadow map texture array, kept from the moment
|
|
// opengl_init_shadow_framebuffer() created the array. opengl_get_shadow_map_bytes() uses it, thus
|
|
// a later change of the cascade counts cannot make that value wrong.
|
|
static int Shadow_texture_layers = 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);
|
|
}
|
|
|
|
if (opengl_buffer_is_geometry(buffer_obj.type)) {
|
|
GL_geometry_bytes_used -= buffer_obj.size;
|
|
GL_geometry_bytes_used += size;
|
|
}
|
|
buffer_obj.size = 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);
|
|
}
|
|
|
|
if (opengl_buffer_is_geometry(buffer_obj.type)) {
|
|
GL_geometry_bytes_used -= buffer_obj.size;
|
|
}
|
|
// The size must go to 0 here. opengl_destroy_all_buffers() calls this function for every
|
|
// handle, thus a buffer that the game already deleted comes through a second time.
|
|
buffer_obj.size = 0;
|
|
|
|
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;
|
|
Shadow_texture_layers = Num_shadow_cascades + Num_cockpit_shadow_cascades;
|
|
|
|
// Second sampler for Shadow_map_depth_texture, compare mode off, so PCSS blocker
|
|
// search (shadows.sdr) can read raw depth off the same texture that the compare
|
|
// sampler reads filtered visibility off -- see shadow_contact_hardening_supported()
|
|
// for why this needs a sampler object rather than a second glTexParameteri call.
|
|
if (shadow_contact_hardening_supported()) {
|
|
glGenSamplers(1, &Shadow_map_raw_sampler);
|
|
// GL_NEAREST: the blocker search wants the actual stored per-texel depth, not a
|
|
// hardware-filtered blend across the compare boundary -- deliberately different
|
|
// from the compare sampler's GL_LINEAR.
|
|
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
|
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
size_t opengl_get_shadow_map_bytes()
|
|
{
|
|
if (Shadow_map_depth_texture == 0) {
|
|
return 0;
|
|
}
|
|
|
|
// opengl_init_shadow_framebuffer() gives the texture array the internal format
|
|
// GL_DEPTH_COMPONENT24, which the driver holds in 4 bytes for each texel.
|
|
constexpr size_t DEPTH_COMPONENT24_BYTES = 4;
|
|
|
|
const auto size = static_cast<size_t>(Shadow_texture_size);
|
|
const auto layers = static_cast<size_t>(Shadow_texture_layers);
|
|
|
|
return size * size * layers * DEPTH_COMPONENT24_BYTES;
|
|
}
|
|
|
|
void opengl_tnl_init()
|
|
{
|
|
Transform_buffer_handle = opengl_create_texture_buffer_object();
|
|
|
|
if (Shadow_quality != ShadowQuality::Disabled) {
|
|
const int size = shadows_map_resolution();
|
|
|
|
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_raw_sampler ) {
|
|
// Clear the tracked bindings before deleting -- glDeleteSamplers silently unbinds
|
|
// the sampler from every unit on the GL side, which would leave GL_state's tracking
|
|
// stale (BindSampler() would then skip a rebind of a since-deleted sampler name).
|
|
GL_state.Texture.BindSampler(7, 0);
|
|
GL_state.Texture.BindSampler(11, 0);
|
|
glDeleteSamplers(1, &Shadow_map_raw_sampler);
|
|
Shadow_map_raw_sampler = 0;
|
|
}
|
|
|
|
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);
|
|
// Unit 11 is reserved for this -- see the raw-sampler bind alongside "shadow_map"
|
|
// below. Set unconditionally (even when contact hardening is unsupported) for the
|
|
// same AMD unbound-uniform reason as the rest of this block; the shader's own
|
|
// penumbra_scale sentinel check is what prevents the read at runtime.
|
|
Current_shader->program->Uniforms.setTextureUniform("shadow_map_raw", 11);
|
|
}
|
|
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 (Shadow_map_raw_sampler) {
|
|
GL_state.Texture.Enable(11, GL_TEXTURE_2D_ARRAY, Shadow_map_depth_texture);
|
|
GL_state.Texture.BindSampler(11, Shadow_map_raw_sampler);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|