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

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();
}