mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 17:52:08 -10:00
* 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
789 lines
25 KiB
C++
789 lines
25 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.
|
|
*
|
|
*/
|
|
|
|
#include "graphics/opengl/gropenglshader.h"
|
|
|
|
#include "ShaderProgram.h"
|
|
|
|
#include "cfile/cfile.h"
|
|
#include "graphics/shader_preprocess.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "def_files/def_files.h"
|
|
#include "graphics/2d.h"
|
|
#include "graphics/grinternal.h"
|
|
#include "graphics/matrix.h"
|
|
#include "graphics/opengl/gropengldraw.h"
|
|
#include "graphics/opengl/gropenglpostprocessing.h"
|
|
#include "graphics/opengl/gropenglstate.h"
|
|
#include "graphics/opengl/gropengltexture.h"
|
|
#include "graphics/opengl/gropengltnl.h"
|
|
#include "graphics/shader_types.h"
|
|
#include "graphics/util/uniform_structs.h"
|
|
#include "lighting/lighting.h"
|
|
#include "math/vecmat.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "render/3d.h"
|
|
#ifdef USE_OPENGL_ES
|
|
#include "es_compatibility.h"
|
|
#endif
|
|
|
|
#include <jansson.h>
|
|
#include <md5.h>
|
|
|
|
SCP_vector<opengl_shader_t> GL_shader;
|
|
|
|
typedef std::pair<int, uint32_t> shader_descriptor_t;
|
|
|
|
struct key_hasher
|
|
{
|
|
size_t operator()(const shader_descriptor_t &obj) const
|
|
{
|
|
return obj.first ^ obj.second;
|
|
}
|
|
};
|
|
|
|
SCP_unordered_map<shader_descriptor_t, size_t, key_hasher> GL_shader_map;
|
|
|
|
GLuint Framebuffer_fallback_texture_id = 0;
|
|
|
|
SCP_vector<opengl_vert_attrib> GL_vertex_attrib_info =
|
|
{
|
|
{ opengl_vert_attrib::POSITION, "vertPosition", {{{ 0.0f, 0.0f, 0.0f, 1.0f }}} },
|
|
{ opengl_vert_attrib::COLOR, "vertColor", {{{ 1.0f, 1.0f, 1.0f, 1.0f }}} },
|
|
{ opengl_vert_attrib::TEXCOORD, "vertTexCoord", {{{ 1.0f, 1.0f, 1.0f, 1.0f }}} },
|
|
{ opengl_vert_attrib::NORMAL, "vertNormal", {{{ 0.0f, 0.0f, 1.0f, 0.0f }}} },
|
|
{ opengl_vert_attrib::TANGENT, "vertTangent", {{{ 1.0f, 0.0f, 0.0f, 0.0f }}} },
|
|
{ opengl_vert_attrib::MODEL_ID, "vertModelID", {{{ 0.0f, 0.0f, 0.0f, 0.0f }}} },
|
|
{ opengl_vert_attrib::RADIUS, "vertRadius", {{{ 1.0f, 0.0f, 0.0f, 0.0f }}} },
|
|
{ opengl_vert_attrib::UVEC, "vertUvec", {{{ 0.0f, 1.0f, 0.0f, 0.0f }}} },
|
|
{ opengl_vert_attrib::MODEL_MATRIX, "vertModelMatrix", {{{ 1.0f, 0.0f, 0.0f, 0.0f }}} },
|
|
};
|
|
|
|
struct opengl_uniform_block_binding {
|
|
uniform_block_type block_type;
|
|
const char* name;
|
|
};
|
|
|
|
opengl_uniform_block_binding GL_uniform_blocks[] = {
|
|
{uniform_block_type::Lights, "lightData"},
|
|
{uniform_block_type::ModelData, "modelData"},
|
|
{uniform_block_type::ShadowMapData, "shadowMapData"},
|
|
{uniform_block_type::NanoVGData, "NanoVGUniformData"},
|
|
{uniform_block_type::DecalInfo, "decalInfoData"},
|
|
{uniform_block_type::DecalGlobals, "decalGlobalData"},
|
|
{uniform_block_type::DeferredGlobals, "globalDeferredData"},
|
|
{uniform_block_type::Matrices, "matrixData"},
|
|
{uniform_block_type::MovieData, "movieData"},
|
|
{uniform_block_type::GenericData, "genericData"},
|
|
{uniform_block_type::ShadowCascadeParams, "shadowCascadeParams"},
|
|
};
|
|
|
|
/**
|
|
* Static lookup reference for shader uniforms
|
|
* When adding a new shader, list all associated uniforms and attributes here
|
|
*/
|
|
|
|
opengl_shader_t *Current_shader = NULL;
|
|
|
|
opengl_shader_t::opengl_shader_t() : shader(SDR_TYPE_NONE), flags(0), flags2(0)
|
|
{}
|
|
|
|
/**
|
|
* Set the currently active shader
|
|
* @param shader_obj Pointer to an opengl_shader_t object. This function calls glUseProgramARB with parameter 0 if shader_obj is NULL or if function is called without parameters, causing OpenGL to revert to fixed-function processing
|
|
*/
|
|
void opengl_shader_set_current(opengl_shader_t *shader_obj)
|
|
{
|
|
if (Current_shader != shader_obj) {
|
|
GR_DEBUG_SCOPE("Set shader");
|
|
|
|
GL_state.Array.ResetVertexAttribs();
|
|
|
|
if(shader_obj) {
|
|
shader_obj->program->use();
|
|
} else {
|
|
GL_state.UseProgram(0);
|
|
}
|
|
|
|
Current_shader = shader_obj;
|
|
}
|
|
}
|
|
|
|
void opengl_shader_set_current(int handle)
|
|
{
|
|
Assert(handle >= 0);
|
|
Assert(handle < (int)GL_shader.size());
|
|
|
|
opengl_shader_set_current(&GL_shader[handle]);
|
|
}
|
|
|
|
size_t opengl_get_shader_idx(shader_type shader_t, unsigned int flags)
|
|
{
|
|
auto found = GL_shader_map.find(shader_descriptor_t(shader_t, flags));
|
|
if (found != GL_shader_map.end()) {
|
|
return found->second;
|
|
}
|
|
return GL_shader.size();
|
|
}
|
|
|
|
/**
|
|
* Given a set of flags, determine whether a shader with these flags exists within the GL_shader vector. If no shader with the requested flags exists, attempt to compile one.
|
|
*
|
|
* @param shader_t shader_type variable, a reference to the shader program needed
|
|
* @param flags Integer variable, holding a combination of SDR_* flags
|
|
* @return Index into GL_shader, referencing a valid shader, or -1 if shader compilation failed
|
|
*/
|
|
int gr_opengl_maybe_create_shader(shader_type shader_t, unsigned int flags)
|
|
{
|
|
size_t idx = opengl_get_shader_idx(shader_t, flags);
|
|
|
|
if (idx < GL_shader.size())
|
|
return (int)idx;
|
|
|
|
// If we are here, it means we need to compile a new shader
|
|
return opengl_compile_shader(shader_t, flags);
|
|
}
|
|
|
|
void opengl_delete_shader(int sdr_handle)
|
|
{
|
|
Assert(sdr_handle >= 0);
|
|
Assert(sdr_handle < (int)GL_shader.size());
|
|
opengl_shader_t &victim = GL_shader[sdr_handle];
|
|
GL_shader_map.erase(shader_descriptor_t(victim.shader, victim.flags));
|
|
|
|
GL_shader[sdr_handle].program.reset();
|
|
|
|
GL_shader[sdr_handle].flags = 0;
|
|
GL_shader[sdr_handle].flags2 = 0;
|
|
GL_shader[sdr_handle].shader = NUM_SHADER_TYPES;
|
|
}
|
|
|
|
/**
|
|
* Go through GL_shader and call glDeleteObject() for all created shaders, then clear GL_shader
|
|
*/
|
|
void opengl_shader_shutdown()
|
|
{
|
|
GL_shader.clear();
|
|
GL_shader_map.clear();
|
|
}
|
|
|
|
static SCP_string opengl_shader_get_header(shader_type type_id, int flags, bool has_geo_shader) {
|
|
SCP_stringstream sflags;
|
|
|
|
#ifndef USE_OPENGL_ES
|
|
sflags << "#version " << GLSL_version << " core\n";
|
|
if (GLSL_version < 330) {
|
|
sflags << "#extension GL_ARB_explicit_attrib_location : enable\n";
|
|
}
|
|
#else
|
|
sflags << "#version " << GLSL_version << " es\n";
|
|
#endif
|
|
sflags << "#define OPENGL\n";
|
|
|
|
#ifdef USE_OPENGL_ES
|
|
if (!GLAD_GL_EXT_clip_cull_distance) {
|
|
sflags << "#define SDR_FLAG_SW_CLIP_FALLBACK\n";
|
|
}
|
|
#endif
|
|
|
|
sflags << shader_get_shadow_cascade_defines();
|
|
sflags << shader_get_rt_shadow_light_limit_define();
|
|
|
|
if (Detail.lighting < 3) {
|
|
sflags << "#define FLAG_LIGHT_MODEL_BLINN_PHONG\n";
|
|
}
|
|
|
|
if (has_geo_shader) {
|
|
// If there is a geometry shader then we define a special preprocessor symbol to make writing shaders easier
|
|
sflags << "#define HAS_GEOMETRY_SHADER\n";
|
|
}
|
|
|
|
if (type_id == SDR_TYPE_POST_PROCESS_MAIN || type_id == SDR_TYPE_POST_PROCESS_LIGHTSHAFTS ||
|
|
type_id == SDR_TYPE_POST_PROCESS_FXAA || type_id == SDR_TYPE_POST_PROCESS_SMAA_EDGE ||
|
|
type_id == SDR_TYPE_POST_PROCESS_SMAA_BLENDING_WEIGHT ||
|
|
type_id == SDR_TYPE_POST_PROCESS_SMAA_NEIGHBORHOOD_BLENDING) {
|
|
// ignore looking for variants. main post process, lightshafts, and FXAA shaders need special headers to be hacked in
|
|
opengl_post_shader_header(sflags, type_id, flags);
|
|
} else {
|
|
sflags << shader_build_variant_defines(type_id, flags);
|
|
}
|
|
|
|
return sflags.str();
|
|
}
|
|
|
|
/**
|
|
* Load a shader file from disc or from the builtin defaults in def_files.cpp if none can be found.
|
|
* This function will also create a list of preprocessor defines for the GLSL compiler based on the shader flags
|
|
* and the supported GLSL version as reported by the GPU driver.
|
|
*
|
|
* @param shader shader_type enum defined with which shader we're loading
|
|
* @param filename C-string holding the filename (with extension) of the shader file
|
|
* @param flags integer variable holding a combination of SDR_* flags
|
|
* @return C-string holding the complete shader source code
|
|
*/
|
|
static SCP_vector<SCP_string>
|
|
opengl_get_shader_content(shader_type type_id, const char* filename, int flags, bool has_geo_shader, bool spirv_shader)
|
|
{
|
|
SCP_vector<SCP_string> parts;
|
|
if (spirv_shader) {
|
|
// No need to add a header here or handle includes since the original compiler did that
|
|
parts.push_back(shader_load_source(filename));
|
|
} else {
|
|
parts.push_back(opengl_shader_get_header(type_id, flags, has_geo_shader));
|
|
|
|
parts.push_back(shader_preprocess_defines(filename, shader_preprocess_includes(filename, shader_load_source(filename))));
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
|
|
static void add_shader_parts(MD5& md5, const SCP_vector<SCP_string>& parts) {
|
|
for (auto& part : parts) {
|
|
md5.update(part.c_str(), (MD5::size_type) part.size());
|
|
}
|
|
}
|
|
|
|
static SCP_string get_shader_hash(const SCP_vector<SCP_string>& vert,
|
|
const SCP_vector<SCP_string>& geom_content,
|
|
const SCP_vector<SCP_string>& frag) {
|
|
MD5 md5;
|
|
add_shader_parts(md5, vert);
|
|
add_shader_parts(md5, geom_content);
|
|
add_shader_parts(md5, frag);
|
|
|
|
// Add the attribute locations so that changes get detected
|
|
for (uint32_t i = 0; i < (uint32_t)GL_vertex_attrib_info.size(); ++i) {
|
|
md5.update(GL_vertex_attrib_info[i].name.c_str(), (MD5::size_type) GL_vertex_attrib_info[i].name.size());
|
|
md5.update(reinterpret_cast<const char*>(&i), sizeof(i));
|
|
}
|
|
|
|
md5.update(GL_implementation_id.data(),
|
|
(MD5::size_type)GL_implementation_id.size() * sizeof(SCP_string::value_type));
|
|
|
|
md5.finalize();
|
|
|
|
return md5.hexdigest();
|
|
}
|
|
|
|
static bool do_shader_caching() {
|
|
if (!GLAD_GL_ARB_get_program_binary) {
|
|
// Not supported until OpenGL 4.1
|
|
return false;
|
|
}
|
|
if (Cmdline_noshadercache) {
|
|
// Shader cache is disabled
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool load_cached_shader_binary(opengl::ShaderProgram* program, const SCP_string& hash) {
|
|
if (!do_shader_caching()) {
|
|
return false;
|
|
}
|
|
|
|
auto base_filename = SCP_string("ogl_shader-") + hash;
|
|
|
|
auto metadata = base_filename + ".json";
|
|
auto binary = base_filename + ".bin";
|
|
|
|
auto metadata_fp = cfopen(metadata.c_str(), "rb", CF_TYPE_CACHE, false,
|
|
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
|
if (!metadata_fp) {
|
|
nprintf(("ShaderCache", "Metadata file does not exist.\n"));
|
|
return false;
|
|
}
|
|
|
|
auto size = cfilelength(metadata_fp);
|
|
SCP_string metadata_content;
|
|
metadata_content.resize((size_t) size);
|
|
cfread(&metadata_content[0], 1, size, metadata_fp);
|
|
|
|
cfclose(metadata_fp);
|
|
|
|
auto metadata_root = json_loads(metadata_content.c_str(), 0, nullptr);
|
|
if (!metadata_root) {
|
|
mprintf(("Loading of cache metadata failed! Falling back to GLSL shader...\n"));
|
|
return false;
|
|
}
|
|
|
|
json_int_t format;
|
|
if (json_unpack(metadata_root, "{sI}", "format", &format) != 0) {
|
|
mprintf(("Failed to unpack values from metadata JSON! Falling back to GLSL shader...\n"));
|
|
return false;
|
|
}
|
|
auto binary_format = (GLenum) format;
|
|
json_decref(metadata_root);
|
|
|
|
bool supported = false;
|
|
for (auto supported_fmt : GL_binary_formats) {
|
|
if ((GLenum)supported_fmt == binary_format) {
|
|
supported = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!supported) {
|
|
// This can happen in case an implementation stops supporting a particular binary format
|
|
nprintf(("ShaderCache", "Unsupported binary format %d encountered in shader cache.\n", binary_format));
|
|
return false;
|
|
}
|
|
|
|
auto binary_fp = cfopen(binary.c_str(), "rb", CF_TYPE_CACHE, false,
|
|
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
|
if (!binary_fp) {
|
|
nprintf(("ShaderCache", "Binary file does not exist.\n"));
|
|
return false;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("Loading cached shader");
|
|
|
|
SCP_vector<uint8_t> buffer;
|
|
int length = cfilelength(binary_fp);
|
|
buffer.resize((size_t) length);
|
|
cfread(&buffer[0], 1, length, binary_fp);
|
|
|
|
cfclose(binary_fp);
|
|
|
|
// Load the data!
|
|
glProgramBinary(program->getShaderHandle(), binary_format, buffer.data(), (GLsizei) buffer.size());
|
|
|
|
// Check the status...
|
|
GLint status;
|
|
glGetProgramiv(program->getShaderHandle(), GL_LINK_STATUS, &status);
|
|
|
|
return status == GL_TRUE;
|
|
}
|
|
static int json_write_callback(const char *buffer, size_t size, void *data) {
|
|
CFILE* cfp = (CFILE*)data;
|
|
|
|
if ((size_t)cfwrite(buffer, 1, (int)size, cfp) != size) {
|
|
return -1; // Error
|
|
} else {
|
|
return 0; // Success
|
|
}
|
|
}
|
|
|
|
static void cache_program_binary(GLuint program, const SCP_string& hash) {
|
|
if (!do_shader_caching()) {
|
|
return;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("Saving shader binary");
|
|
|
|
GLint size;
|
|
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &size);
|
|
|
|
if (size <= 0) {
|
|
// No binary available (I'm looking at you Mesa...)
|
|
return;
|
|
}
|
|
|
|
SCP_vector<uint8_t> binary;
|
|
binary.resize((size_t) size);
|
|
GLenum binary_fmt;
|
|
GLsizei length;
|
|
glGetProgramBinary(program, (GLsizei) binary.size(), &length, &binary_fmt, binary.data());
|
|
if (length == 0) {
|
|
return;
|
|
}
|
|
|
|
auto base_filename = SCP_string("ogl_shader-") + hash;
|
|
|
|
auto metadata_name = base_filename + ".json";
|
|
auto binary_name = base_filename + ".bin";
|
|
|
|
auto metadata_fp = cfopen(metadata_name.c_str(), "wb", CF_TYPE_CACHE, false,
|
|
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
|
if (!metadata_fp) {
|
|
mprintf(("Could not open shader cache metadata file!\n"));
|
|
return;
|
|
}
|
|
|
|
auto metadata = json_pack("{sI}", "format", (json_int_t)binary_fmt);
|
|
if (json_dump_callback(metadata, json_write_callback, metadata_fp, 0) != 0) {
|
|
mprintf(("Failed to write shader cache metadata file!\n"));
|
|
cfclose(metadata_fp);
|
|
return;
|
|
}
|
|
cfclose(metadata_fp);
|
|
json_decref(metadata);
|
|
|
|
auto binary_fp = cfopen(binary_name.c_str(), "wb", CF_TYPE_CACHE, false,
|
|
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
|
if (!binary_fp) {
|
|
mprintf(("Could not open shader cache binary file!\n"));
|
|
return;
|
|
}
|
|
cfwrite(binary.data(), 1, (int) binary.size(), binary_fp);
|
|
cfclose(binary_fp);
|
|
}
|
|
|
|
static void opengl_set_default_uniforms(const opengl_shader_t& sdr) {
|
|
switch (sdr.shader) {
|
|
case SDR_TYPE_DEFERRED_LIGHTING:
|
|
Current_shader->program->Uniforms.setTextureUniform("ColorBuffer", 0);
|
|
Current_shader->program->Uniforms.setTextureUniform("NormalBuffer", 1);
|
|
Current_shader->program->Uniforms.setTextureUniform("PositionBuffer", 2);
|
|
Current_shader->program->Uniforms.setTextureUniform("SpecBuffer", 3);
|
|
Current_shader->program->Uniforms.setTextureUniform("shadow_map", 4);
|
|
break;
|
|
|
|
case SDR_TYPE_PASSTHROUGH_RENDER:
|
|
Current_shader->program->Uniforms.setTextureUniform("baseMap", 0);
|
|
Current_shader->program->Uniforms.setTextureUniform("clipEnabled", 0);
|
|
break;
|
|
|
|
default:
|
|
// No default values for this shader type.
|
|
break;
|
|
}
|
|
}
|
|
|
|
void opengl_compile_shader_actual(shader_type sdr, const uint &flags, opengl_shader_t &new_shader)
|
|
{
|
|
const ShaderTypeInfo *sdr_info = shader_get_type_info(sdr);
|
|
|
|
Assert(sdr_info != nullptr);
|
|
nprintf(("shaders","Compiling new shader:\n"));
|
|
nprintf(("shaders"," %s\n", sdr_info->description));
|
|
|
|
// figure out if the variant requested needs a geometry shader
|
|
bool use_geo_sdr = false;
|
|
|
|
// do we even have a geometry shader?
|
|
if (sdr_info->geo != nullptr) {
|
|
shader_for_each_active_variant(sdr, flags, [&](const ShaderVariantInfo& variant) {
|
|
if (variant.use_geometry_sdr) {
|
|
use_geo_sdr = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
auto vert_content =
|
|
opengl_get_shader_content(sdr, sdr_info->vert, flags, use_geo_sdr, sdr_info->spirv_shader);
|
|
auto frag_content =
|
|
opengl_get_shader_content(sdr, sdr_info->frag, flags, use_geo_sdr, sdr_info->spirv_shader);
|
|
SCP_vector<SCP_string> geom_content;
|
|
|
|
if (use_geo_sdr) {
|
|
// read geometry shader
|
|
geom_content =
|
|
opengl_get_shader_content(sdr, sdr_info->geo, flags, use_geo_sdr, sdr_info->spirv_shader);
|
|
}
|
|
|
|
auto shader_hash = get_shader_hash(vert_content, geom_content, frag_content);
|
|
std::unique_ptr<opengl::ShaderProgram> program(new opengl::ShaderProgram(sdr_info->description));
|
|
|
|
if (!load_cached_shader_binary(program.get(), shader_hash)) {
|
|
GR_DEBUG_SCOPE("Compiling shader code");
|
|
try {
|
|
program->addShaderCode(opengl::STAGE_VERTEX, sdr_info->vert, vert_content);
|
|
program->addShaderCode(opengl::STAGE_FRAGMENT, sdr_info->frag, frag_content);
|
|
if (use_geo_sdr) {
|
|
program->addShaderCode(opengl::STAGE_GEOMETRY, sdr_info->geo, geom_content);
|
|
}
|
|
|
|
for (size_t i = 0; i < GL_vertex_attrib_info.size(); ++i) {
|
|
// Check that the enum values match the position in the vector to make accessing that information more efficient
|
|
Assertion(GL_vertex_attrib_info[i].attribute_id == i, "Mistmatch between enum values and attribute vector detected!");
|
|
|
|
// assign vert attribute binding locations before we link the shader
|
|
glBindAttribLocation(program->getShaderHandle(), (GLint)i, GL_vertex_attrib_info[i].name.c_str());
|
|
}
|
|
|
|
// bind fragment data locations before we link the shader
|
|
glBindFragDataLocation(program->getShaderHandle(), 0, "fragOut0");
|
|
glBindFragDataLocation(program->getShaderHandle(), 1, "fragOut1");
|
|
glBindFragDataLocation(program->getShaderHandle(), 2, "fragOut2");
|
|
glBindFragDataLocation(program->getShaderHandle(), 3, "fragOut3");
|
|
glBindFragDataLocation(program->getShaderHandle(), 4, "fragOut4");
|
|
|
|
if (do_shader_caching()) {
|
|
// Enable shader caching
|
|
glProgramParameteri(program->getShaderHandle(), GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
|
|
}
|
|
|
|
program->linkProgram();
|
|
}
|
|
catch (const std::exception&) {
|
|
// Since all shaders are required a compilation failure is a fatal error
|
|
Error(LOCATION, "A shader failed to compile! %s. Check the debug log for more information.", sdr_info->description );
|
|
}
|
|
|
|
cache_program_binary(program->getShaderHandle(), shader_hash);
|
|
}
|
|
|
|
new_shader.shader = sdr;
|
|
new_shader.flags = flags;
|
|
new_shader.program = std::move(program);
|
|
|
|
opengl_shader_set_current(&new_shader);
|
|
|
|
// initialize the attributes
|
|
for (auto& attr : sdr_info->attributes) {
|
|
new_shader.program->initAttribute(GL_vertex_attrib_info[attr].name, GL_vertex_attrib_info[attr].default_value);
|
|
}
|
|
|
|
for (auto& uniform_block : GL_uniform_blocks) {
|
|
auto blockIndex = glGetUniformBlockIndex(new_shader.program->getShaderHandle(), uniform_block.name);
|
|
|
|
if (blockIndex != GL_INVALID_INDEX) {
|
|
glUniformBlockBinding(new_shader.program->getShaderHandle(), blockIndex, static_cast<GLuint>(uniform_block.block_type));
|
|
}
|
|
}
|
|
|
|
nprintf(("shaders","Shader Variant Features:\n"));
|
|
|
|
// initialize extra attributes from active variants
|
|
shader_for_each_active_variant(sdr, flags, [&](const ShaderVariantInfo& v) {
|
|
for (auto& attr : v.attributes) {
|
|
new_shader.program->initAttribute(GL_vertex_attrib_info[attr].name, GL_vertex_attrib_info[attr].default_value);
|
|
}
|
|
nprintf(("shaders"," %s\n", v.description));
|
|
});
|
|
|
|
opengl_set_default_uniforms(new_shader);
|
|
}
|
|
|
|
/**
|
|
* Compiles a new shader, and creates an opengl_shader_t that will be put into the GL_shader vector
|
|
* if compilation is successful.
|
|
*
|
|
* @param sdr Identifier defined with the program we wish to compile
|
|
* @param flags Combination of SDR_* flags
|
|
* @param replacement_idx The index of the shader this replaces. If -1, the newly compiled shader will be appended to the GL_shader vector
|
|
* or inserted at the first available empty slot
|
|
*/
|
|
int opengl_compile_shader(shader_type sdr, uint flags)
|
|
{
|
|
GR_DEBUG_SCOPE("Creating new shader");
|
|
|
|
int sdr_index = -1;
|
|
int empty_idx;
|
|
opengl_shader_t new_shader;
|
|
|
|
Assert(sdr < NUM_SHADER_TYPES);
|
|
|
|
opengl_compile_shader_actual(sdr, flags, new_shader);
|
|
|
|
opengl_shader_set_current();
|
|
|
|
// add it to our list of embedded shaders
|
|
// see if we have empty shader slots
|
|
empty_idx = -1;
|
|
for (int i = 0; i < (int)GL_shader.size(); ++i) {
|
|
if (GL_shader[i].shader == NUM_SHADER_TYPES) {
|
|
empty_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int new_shader_shader = new_shader.shader;
|
|
uint32_t new_shader_flags = new_shader.flags;
|
|
// then insert it at an empty slot or at the end
|
|
if ( empty_idx >= 0 ) {
|
|
GL_shader[empty_idx] = std::move(new_shader);
|
|
sdr_index = empty_idx;
|
|
} else {
|
|
sdr_index = (int)GL_shader.size();
|
|
GL_shader.push_back(std::move(new_shader));
|
|
}
|
|
GL_shader_map[shader_descriptor_t(new_shader_shader, new_shader_flags)] = sdr_index;
|
|
return sdr_index;
|
|
}
|
|
|
|
void gr_opengl_recompile_all_shaders(const std::function<void(size_t, size_t)>& progress_callback)
|
|
{
|
|
for (auto sdr = GL_shader.begin(); sdr != GL_shader.end(); ++sdr)
|
|
{
|
|
if (progress_callback)
|
|
progress_callback(std::distance(GL_shader.begin(), sdr), GL_shader.size());
|
|
sdr->program.reset();
|
|
opengl_compile_shader_actual(sdr->shader, sdr->flags, *sdr);
|
|
}
|
|
}
|
|
|
|
static void opengl_purge_shader_cache_type(const char* ext) {
|
|
|
|
SCP_string filter("*.");
|
|
filter += ext;
|
|
|
|
// Previously the cache files were stored in the mod directory. Since we have a better system now, those files
|
|
// should be cleaned out. This is only needed if we have a mod directory since otherwise we would delete the actual
|
|
// cache files
|
|
if (Cmdline_mod != nullptr && strlen(Cmdline_mod) > 0) {
|
|
SCP_vector<SCP_string> cache_files;
|
|
cf_get_file_list(cache_files, CF_TYPE_CACHE, filter.c_str(), CF_SORT_NONE, nullptr,
|
|
CF_LOCATION_TYPE_PRIMARY_MOD | CF_LOCATION_TYPE_SECONDARY_MODS);
|
|
|
|
for (auto& file : cache_files) {
|
|
cf_delete((file + "." + ext).c_str(), CF_TYPE_CACHE,
|
|
CF_LOCATION_TYPE_PRIMARY_MOD | CF_LOCATION_TYPE_SECONDARY_MODS);
|
|
}
|
|
}
|
|
|
|
SCP_vector<SCP_string> cache_files;
|
|
SCP_vector<file_list_info> file_info;
|
|
cf_get_file_list(cache_files, CF_TYPE_CACHE, filter.c_str(), CF_SORT_NONE, &file_info,
|
|
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
|
|
|
Assertion(cache_files.size() == file_info.size(),
|
|
"cf_get_file_list returned different sizes for file names and file informations!");
|
|
|
|
const auto TIMEOUT = 2.0 * 30.0 * 24.0 * 60.0 * 60.0; // purge timeout in seconds which is ~2 months
|
|
const SCP_string PREFIX = "ogl_shader-";
|
|
|
|
auto now = std::time(nullptr);
|
|
for (size_t i = 0; i < cache_files.size(); ++i) {
|
|
auto& name = cache_files[i];
|
|
auto write_time = file_info[i].write_time;
|
|
|
|
if (name.compare(0, PREFIX.size(), PREFIX) != 0) {
|
|
// Not an OpenGL cache file
|
|
continue;
|
|
}
|
|
|
|
auto diff = std::difftime(now, write_time);
|
|
|
|
if (diff > TIMEOUT) {
|
|
auto full_name = name + "." + ext;
|
|
|
|
cf_delete(full_name.c_str(), CF_TYPE_CACHE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void opengl_purge_old_shader_cache()
|
|
{
|
|
opengl_purge_shader_cache_type("json");
|
|
opengl_purge_shader_cache_type("bin");
|
|
}
|
|
|
|
/**
|
|
* Initializes the shader system. Creates a 1x1 texture that can be used as a fallback texture when framebuffer support is missing.
|
|
* Also compiles the shaders used for particle rendering.
|
|
*/
|
|
void opengl_shader_init()
|
|
{
|
|
glGenTextures(1,&Framebuffer_fallback_texture_id);
|
|
GL_state.Texture.SetActiveUnit(0);
|
|
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
|
|
GL_state.Texture.Enable(Framebuffer_fallback_texture_id);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
|
GLuint pixels[4] = {0,0,0,0};
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, &pixels);
|
|
|
|
GL_shader.clear();
|
|
GL_shader_map.clear();
|
|
|
|
// Reserve 32 shader slots. This should cover most use cases in real life.
|
|
GL_shader.reserve(32);
|
|
|
|
opengl_purge_old_shader_cache();
|
|
|
|
// compile effect shaders
|
|
if (gr_opengl_is_capable(gr_capability::CAPABILITY_SOFT_PARTICLES)) {
|
|
// only compile soft particle shaders if they are supported, as to avoid introducing geo shaders if they aren't needed
|
|
gr_opengl_maybe_create_shader(SDR_TYPE_EFFECT_PARTICLE, 0);
|
|
gr_opengl_maybe_create_shader(SDR_TYPE_EFFECT_PARTICLE, SDR_FLAG_PARTICLE_POINT_GEN);
|
|
}
|
|
gr_opengl_maybe_create_shader(SDR_TYPE_EFFECT_DISTORTION, 0);
|
|
|
|
gr_opengl_maybe_create_shader(SDR_TYPE_SHIELD_DECAL, 0);
|
|
|
|
// compile deferred lighting shaders
|
|
gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_LIGHTING, 0);
|
|
gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_CLEAR, 0);
|
|
|
|
// compile passthrough shader
|
|
nprintf(("shaders","Compiling passthrough shader...\n"));
|
|
gr_opengl_maybe_create_shader(SDR_TYPE_PASSTHROUGH_RENDER, 0);
|
|
|
|
nprintf(("shaders","\n"));
|
|
}
|
|
|
|
void opengl_shader_set_passthrough(bool textured, bool hdr)
|
|
{
|
|
opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_PASSTHROUGH_RENDER, 0));
|
|
|
|
gr_matrix_set_uniforms();
|
|
|
|
opengl_set_generic_uniform_data<graphics::generic_data::passthrough_data>(
|
|
[=](graphics::generic_data::passthrough_data* data) {
|
|
data->noTexturing = textured ? 0 : 1;
|
|
data->srgb = hdr ? 1 : 0;
|
|
});
|
|
}
|
|
|
|
void opengl_shader_set_default_material(bool textured, bool alpha, vec4* clr, float color_scale, uint32_t array_index,
|
|
const material::clip_plane& clip_plane)
|
|
{
|
|
Current_shader->program->Uniforms.setTextureUniform("baseMap", 0);
|
|
|
|
opengl_set_generic_uniform_data<genericData_default_material_v_sdr>(
|
|
[=](genericData_default_material_v_sdr* data) {
|
|
if (textured) {
|
|
data->noTexturing = 0;
|
|
data->baseMapIndex = array_index;
|
|
} else {
|
|
data->noTexturing = 1;
|
|
// array_index is probably not valid here
|
|
data->baseMapIndex = 0;
|
|
}
|
|
|
|
if (alpha) {
|
|
data->alphaTexture = 1;
|
|
} else {
|
|
data->alphaTexture = 0;
|
|
}
|
|
|
|
if (High_dynamic_range) {
|
|
data->srgb = 1;
|
|
data->intensity = color_scale;
|
|
} else {
|
|
data->srgb = 0;
|
|
data->intensity = 1.0f;
|
|
}
|
|
|
|
data->alphaThreshold = GL_alpha_threshold;
|
|
|
|
if (clr != nullptr) {
|
|
data->color = *clr;
|
|
} else {
|
|
data->color.xyzw.x = 1.0f;
|
|
data->color.xyzw.y = 1.0f;
|
|
data->color.xyzw.z = 1.0f;
|
|
data->color.xyzw.w = 1.0f;
|
|
}
|
|
|
|
if (clip_plane.enabled) {
|
|
data->clipEnabled = 1;
|
|
|
|
vec4 clip_equation;
|
|
clip_equation.xyzw.x = clip_plane.normal.xyz.x;
|
|
clip_equation.xyzw.y = clip_plane.normal.xyz.y;
|
|
clip_equation.xyzw.z = clip_plane.normal.xyz.z;
|
|
clip_equation.xyzw.w = -vm_vec_dot(&clip_plane.normal, &clip_plane.position);
|
|
|
|
data->clipEquation = clip_equation;
|
|
data->modelMatrix = gr_model_matrix_stack.get_transform();
|
|
} else {
|
|
data->clipEnabled = 0;
|
|
}
|
|
});
|
|
|
|
gr_matrix_set_uniforms();
|
|
}
|