Files
fs2open.github.com/code/graphics/gropenglshader.cpp
T

971 lines
29 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 "globalincs/pstypes.h"
#include "globalincs/def_files.h"
#include "graphics/2d.h"
#include "lighting/lighting.h"
#include "graphics/grinternal.h"
#include "graphics/gropengl.h"
#include "graphics/gropenglextension.h"
#include "graphics/gropengltexture.h"
#include "graphics/gropengllight.h"
#include "graphics/gropengltnl.h"
#include "graphics/gropengldraw.h"
#include "graphics/gropenglshader.h"
#include "graphics/gropenglpostprocessing.h"
#include "graphics/gropenglstate.h"
#include "math/vecmat.h"
#include "render/3d.h"
#include "cmdline/cmdline.h"
#include "mod_table/mod_table.h"
SCP_vector<opengl_shader_t> GL_shader;
static char *GLshader_info_log = NULL;
static const int GLshader_info_log_size = 8192;
GLuint Framebuffer_fallback_texture_id = 0;
static int GL_anim_effect_num = 0;
static float GL_anim_timer = 0.0f;
geometry_sdr_params *Current_geo_sdr_params = NULL;
/**
* Static lookup reference for shader uniforms
* When adding a new shader, list all associated uniforms and attributes here
*/
static opengl_shader_type_t GL_shader_types[] = {
{ SDR_TYPE_MODEL, "main-v.sdr", "main-f.sdr", "main-g.sdr", {GL_TRIANGLES, GL_TRIANGLE_STRIP, 3},
0, {}, 0, {}, "Model Rendering" },
{ SDR_TYPE_EFFECT_PARTICLE, "effect-v.sdr", "effect-particle-f.sdr", "effect-screen-g.sdr", {GL_POINTS, GL_TRIANGLE_STRIP, 4},
7, { "baseMap", "depthMap", "window_width", "window_height", "nearZ", "farZ", "linear_depth" }, 1, {"radius"}, "Particle Effects" },
{ SDR_TYPE_EFFECT_DISTORTION, "effect-distort-v.sdr", "effect-distort-f.sdr", 0, { 0, 0, 0 },
6, { "baseMap", "window_width", "window_height", "distMap", "frameBuffer", "use_offset" }, 1, { "radius" }, "Distortion Effects" },
{ SDR_TYPE_POST_PROCESS_MAIN, "post-v.sdr", "post-f.sdr", 0, {0, 0, 0},
5, { "tex", "depth_tex", "timer", "bloomed", "bloom_intensity" }, 0, { NULL }, "Post Processing" },
{ SDR_TYPE_POST_PROCESS_BLUR, "post-v.sdr", "blur-f.sdr", 0, {0, 0, 0},
2, { "tex", "bsize", "debug" }, 0, { NULL }, "Gaussian Blur" },
{ SDR_TYPE_POST_PROCESS_BRIGHTPASS, "post-v.sdr", "brightpass-f.sdr", 0, { 0, 0, 0 },
1, { "tex" }, 0, { NULL }, "Bloom Brightpass" },
{ SDR_TYPE_POST_PROCESS_FXAA, "fxaa-v.sdr", "fxaa-f.sdr", 0, {0, 0, 0},
3, { "tex0", "rt_w", "rt_h" }, 0, { NULL }, "FXAA" },
{ SDR_TYPE_POST_PROCESS_FXAA_PREPASS, "post-v.sdr", "fxaapre-f.sdr", 0, {0, 0, 0},
1, { "tex" }, 0, { NULL }, "FXAA Prepass" },
{ SDR_TYPE_POST_PROCESS_LIGHTSHAFTS, "post-v.sdr", "ls-f.sdr", 0, {0, 0, 0},
8, { "scene", "cockpit", "sun_pos", "weight", "intensity", "falloff", "density", "cp_intensity" }, 0, { NULL }, "Lightshafts" },
{ SDR_TYPE_DEFERRED_LIGHTING, "deferred-v.sdr", "deferred-f.sdr", 0, { 0, 0, 0 },
16, { "scale", "ColorBuffer", "NormalBuffer", "PositionBuffer", "SpecBuffer", "invScreenWidth", "invScreenHeight", "lightType", "lightRadius", "diffuseLightColor",
"specLightColor", "dualCone", "coneDir", "coneAngle", "coneInnerAngle", "specFactor" }, 0, { NULL }, "Deferred Lighting" },
{ SDR_TYPE_DEFERRED_CLEAR, "deferred-clear-v.sdr", "deferred-clear-f.sdr", 0, {0, 0, 0},
0, { NULL }, 0, { NULL }, "Clear Deferred Lighting Buffer" },
{ SDR_TYPE_VIDEO_PROCESS, "video-v.sdr", "video-f.sdr", 0, {0, 0, 0},
3, { "ytex", "utex", "vtex" }, 0, { NULL }, "Video Playback" }
};
/**
* Static lookup reference for shader variant uniforms
* When adding a new shader variant for a shader, list all associated uniforms and attributes here
*/
static opengl_shader_variant_t GL_shader_variants[] = {
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_LIGHT, "FLAG_LIGHT",
2, { "n_lights", "light_factor" }, 0, { NULL },
"Lighting" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_FOG, "FLAG_FOG",
0, { NULL }, 0, { NULL },
"Fog Effect" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_DIFFUSE_MAP, "FLAG_DIFFUSE_MAP",
4, { "sBasemap", "desaturate", "desaturate_clr", "blend_alpha" }, 0, { NULL },
"Diffuse Mapping" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_GLOW_MAP, "FLAG_GLOW_MAP",
1, { "sGlowmap" }, 0, { NULL },
"Glow Mapping" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_SPEC_MAP, "FLAG_SPEC_MAP",
1, { "sSpecmap" }, 0, { NULL },
"Specular Mapping" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_NORMAL_MAP, "FLAG_NORMAL_MAP",
1, { "sNormalmap" }, 0, { NULL },
"Normal Mapping" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_HEIGHT_MAP, "FLAG_HEIGHT_MAP",
1, { "sHeightmap" }, 0, { NULL },
"Parallax Mapping" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_ENV_MAP, "FLAG_ENV_MAP",
3, { "sEnvmap", "alpha_spec", "envMatrix" }, 0, { NULL },
"Environment Mapping" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_ANIMATED, "FLAG_ANIMATED",
5, { "sFramebuffer", "effect_num", "anim_timer", "vpwidth", "vpheight" }, 0, { NULL },
"Animated Effects" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_MISC_MAP, "FLAG_MISC_MAP",
1, { "sMiscmap" }, 0, { NULL },
"Utility mapping" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_TEAMCOLOR, "FLAG_TEAMCOLOR",
2, { "stripe_color", "base_color" }, 0, { NULL },
"Team Colors" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_DEFERRED, "FLAG_DEFERRED",
0, { NULL }, 0, { NULL },
"Deferred lighting" },
{ SDR_TYPE_MODEL, true, SDR_FLAG_MODEL_SHADOW_MAP, "FLAG_SHADOW_MAP",
1, { "shadow_proj_matrix" }, 0, { NULL },
"Shadow Mapping" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_SHADOWS, "FLAG_SHADOWS",
8, { "shadow_map", "shadow_mv_matrix", "shadow_proj_matrix", "model_matrix", "veryneardist", "neardist", "middist", "fardist" }, 0, { NULL },
"Shadows" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_THRUSTER, "FLAG_THRUSTER",
1, { "thruster_scale" }, 0, { NULL },
"Thruster scaling" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_TRANSFORM, "FLAG_TRANSFORM",
2, { "transform_tex", "buffer_matrix_offset" }, 1, { "model_id" },
"Submodel Transforms" },
{ SDR_TYPE_MODEL, false, SDR_FLAG_MODEL_CLIP, "FLAG_CLIP",
4, { "use_clip_plane", "world_matrix", "clip_normal", "clip_position" }, 0, { NULL },
"Clip Plane" },
{ SDR_TYPE_EFFECT_PARTICLE, true, SDR_FLAG_PARTICLE_POINT_GEN, "FLAG_EFFECT_GEOMETRY",
0, { NULL }, 1, { "uvec" },
"Geometry shader point-based particles" },
{ SDR_TYPE_POST_PROCESS_BLUR, false, SDR_FLAG_BLUR_HORIZONTAL, "PASS_0",
0, { NULL }, 0, { NULL },
"Horizontal blur pass" },
{ SDR_TYPE_POST_PROCESS_BLUR, false, SDR_FLAG_BLUR_VERTICAL, "PASS_1",
0, { NULL }, 0, { NULL },
"Vertical blur pass" }
};
static const int GL_num_shader_variants = sizeof(GL_shader_variants) / sizeof(opengl_shader_variant_t);
opengl_shader_t *Current_shader = NULL;
void opengl_shader_check_info_log(GLhandleARB shader_object);
/**
* 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 (shader_obj != NULL) {
if(!Current_shader || (Current_shader->program_id != shader_obj->program_id)) {
Current_shader = shader_obj;
vglUseProgramObjectARB(Current_shader->program_id);
GL_state.Uniform.reset();
#ifndef NDEBUG
if ( opengl_check_for_errors("shader_set_current()") ) {
vglValidateProgramARB(Current_shader->program_id);
GLint obj_status = 0;
vglGetObjectParameterivARB(Current_shader->program_id, GL_OBJECT_VALIDATE_STATUS_ARB, &obj_status);
if ( !obj_status ) {
opengl_shader_check_info_log(Current_shader->program_id);
mprintf(("VALIDATE INFO-LOG:\n"));
if (strlen(GLshader_info_log) > 5) {
mprintf(("%s\n", GLshader_info_log));
} else {
mprintf(("<EMPTY>\n"));
}
}
}
#endif
}
} else {
Current_shader = NULL;
vglUseProgramObjectARB(0);
}
}
void opengl_shader_set_current(int handle)
{
Assert(handle >= 0);
Assert(handle < (int)GL_shader.size());
opengl_shader_set_current(&GL_shader[handle]);
}
/**
* 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 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, unsigned int flags)
{
if (Use_GLSL < 2)
return -1;
size_t idx;
size_t max = GL_shader.size();
for (idx = 0; idx < max; idx++) {
if (GL_shader[idx].shader == shader && GL_shader[idx].flags == flags) {
return idx;
}
}
// If we are here, it means we need to compile a new shader
return opengl_compile_shader(shader, flags);
}
void opengl_delete_shader(int sdr_handle)
{
Assert(sdr_handle >= 0);
Assert(sdr_handle < (int)GL_shader.size());
if (GL_shader[sdr_handle].program_id) {
vglDeleteObjectARB(GL_shader[sdr_handle].program_id);
GL_shader[sdr_handle].program_id = 0;
}
GL_shader[sdr_handle].flags = 0;
GL_shader[sdr_handle].flags2 = 0;
GL_shader[sdr_handle].shader = NUM_SHADER_TYPES;
GL_shader[sdr_handle].uniforms.clear();
GL_shader[sdr_handle].attributes.clear();
GL_shader[sdr_handle].uniform_blocks.clear();
}
/**
* Go through GL_shader and call glDeleteObject() for all created shaders, then clear GL_shader
*/
void opengl_shader_shutdown()
{
size_t i;
if ( !Use_GLSL ) {
return;
}
for (i = 0; i < GL_shader.size(); i++) {
if (GL_shader[i].program_id) {
vglDeleteObjectARB(GL_shader[i].program_id);
GL_shader[i].program_id = 0;
}
GL_shader[i].uniforms.clear();
GL_shader[i].attributes.clear();
GL_shader[i].uniform_blocks.clear();
}
GL_shader.clear();
if (GLshader_info_log != NULL) {
vm_free(GLshader_info_log);
GLshader_info_log = NULL;
}
}
/**
* 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 char *opengl_load_shader(shader_type type_id, char *filename, int flags)
{
SCP_string sflags;
#ifdef __APPLE__
sflags += "#version 120\n";
#endif
if (Use_GLSL >= 4) {
sflags += "#define SHADER_MODEL 4\n";
}
else if (Use_GLSL == 3) {
sflags += "#define SHADER_MODEL 3\n";
}
else {
sflags += "#define SHADER_MODEL 2\n";
}
#ifdef __APPLE__
sflags += "#define APPLE\n";
#endif
if (type_id == SDR_TYPE_POST_PROCESS_MAIN || type_id == SDR_TYPE_POST_PROCESS_LIGHTSHAFTS || type_id == SDR_TYPE_POST_PROCESS_FXAA) {
// ignore looking for variants. main post process, lightshafts, and FXAA shaders need special headers to be hacked in
opengl_post_load_shader(sflags, type_id, flags);
} else {
for (int i = 0; i < GL_num_shader_variants; ++i) {
opengl_shader_variant_t &variant = GL_shader_variants[i];
if (type_id == variant.type_id && flags & variant.flag) {
sflags += "#define " + variant.flag_text + "\n";
}
}
}
const char *shader_flags = sflags.c_str();
int flags_len = strlen(shader_flags);
if (Enable_external_shaders) {
CFILE *cf_shader = cfopen(filename, "rt", CFILE_NORMAL, CF_TYPE_EFFECTS);
if (cf_shader != NULL) {
int len = cfilelength(cf_shader);
char *shader = (char*)vm_malloc(len + flags_len + 1);
strcpy(shader, shader_flags);
memset(shader + flags_len, 0, len + 1);
cfread(shader + flags_len, len + 1, 1, cf_shader);
cfclose(cf_shader);
return shader;
}
}
//If we're still here, proceed with internals
mprintf((" Loading built-in default shader for: %s\n", filename));
char* def_shader = defaults_get_file(filename);
size_t len = strlen(def_shader);
char *shader = (char*)vm_malloc(len + flags_len + 1);
strcpy(shader, shader_flags);
strcat(shader, def_shader);
return 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
*/
int opengl_compile_shader(shader_type sdr, uint flags)
{
int sdr_index = -1;
int empty_idx;
char *vert = NULL, *frag = NULL, *geom = NULL;
bool in_error = false;
opengl_shader_t new_shader;
Assert(sdr < NUM_SHADER_TYPES);
opengl_shader_type_t *sdr_info = &GL_shader_types[sdr];
mprintf(("Compiling new shader:\n"));
mprintf((" %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 != NULL ) {
for (int i = 0; i < GL_num_shader_variants; ++i) {
opengl_shader_variant_t *variant = &GL_shader_variants[i];
if (variant->type_id == sdr && flags & variant->flag && variant->use_geometry_sdr) {
use_geo_sdr = true;
break;
}
}
}
// read vertex shader
if ((vert = opengl_load_shader(sdr_info->type_id, sdr_info->vert, flags)) == NULL) {
in_error = true;
goto Done;
}
// read fragment shader
if ((frag = opengl_load_shader(sdr_info->type_id, sdr_info->frag, flags)) == NULL) {
in_error = true;
goto Done;
}
if ( use_geo_sdr ) {
if (!Is_Extension_Enabled(OGL_EXT_GEOMETRY_SHADER4)) {
goto Done;
}
// read geometry shader
if ((geom = opengl_load_shader(sdr_info->type_id, sdr_info->geo, flags)) == NULL) {
in_error = true;
goto Done;
}
Current_geo_sdr_params = &sdr_info->geo_sdr_info;
}
Verify(vert != NULL);
Verify(frag != NULL);
new_shader.program_id = opengl_shader_create(vert, frag, geom);
if (!new_shader.program_id) {
in_error = true;
goto Done;
}
new_shader.shader = sdr_info->type_id;
new_shader.flags = flags;
opengl_shader_set_current(&new_shader);
// initialize uniforms and attributes
for ( int i = 0; i < sdr_info->num_uniforms; ++i ) {
opengl_shader_init_uniform( sdr_info->uniforms[i] );
}
for (int i = 0; i < sdr_info->num_attributes; ++i) {
opengl_shader_init_attribute(sdr_info->attributes[i]);
}
// if this shader is POST_PROCESS_MAIN, hack in the user-defined flags
if ( sdr_info->type_id == SDR_TYPE_POST_PROCESS_MAIN ) {
opengl_post_init_uniforms(flags);
}
mprintf(("Shader Variant Features:\n"));
// initialize all uniforms and attributes that are specific to this variant
for ( int i = 0; i < GL_num_shader_variants; ++i ) {
opengl_shader_variant_t &variant = GL_shader_variants[i];
if ( sdr_info->type_id == variant.type_id && variant.flag & flags ) {
for (int j = 0; j < variant.num_uniforms; ++j) {
opengl_shader_init_uniform(variant.uniforms[j]);
}
for (int j = 0; j < variant.num_attributes; ++j) {
opengl_shader_init_attribute(variant.attributes[j]);
}
mprintf((" %s\n", variant.description));
}
}
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;
}
}
// then insert it at an empty slot or at the end
if ( empty_idx >= 0 ) {
GL_shader[empty_idx] = new_shader;
sdr_index = empty_idx;
} else {
sdr_index = GL_shader.size();
GL_shader.push_back(new_shader);
}
Done:
if (vert != NULL) {
vm_free(vert);
vert = NULL;
}
if (frag != NULL) {
vm_free(frag);
frag = NULL;
}
if (geom != NULL ) {
vm_free(geom);
geom = NULL;
}
return sdr_index;
}
/**
* 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()
{
if ( !Use_GLSL ) {
return;
}
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);
if (Cmdline_no_glsl_model_rendering) {
Use_GLSL = 1;
}
GL_shader.clear();
// Reserve 32 shader slots. This should cover most use cases in real life.
GL_shader.reserve(32);
// compile effect shaders
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);
// compile deferred lighting shaders
opengl_shader_compile_deferred_light_shader();
mprintf(("\n"));
}
/**
* Retrieve the compilation log for a given shader object, and store it in the GLshader_info_log global variable
*
* @param shader_object OpenGL handle of a shader object
*/
void opengl_shader_check_info_log(GLhandleARB shader_object)
{
if (GLshader_info_log == NULL) {
GLshader_info_log = (char *) vm_malloc(GLshader_info_log_size);
}
memset(GLshader_info_log, 0, GLshader_info_log_size);
vglGetInfoLogARB(shader_object, GLshader_info_log_size-1, 0, GLshader_info_log);
}
/**
* Pass a GLSL shader source to OpenGL and compile it into a usable shader object.
* Prints compilation errors (if any) to the log.
* Note that this will only compile shaders into objects, linking them into executables happens later
*
* @param shader_source GLSL sourcecode for the shader
* @param shader_type OpenGL ID for the type of shader being used, like GL_FRAGMENT_SHADER_ARB, GL_VERTEX_SHADER_ARB
* @return OpenGL handle for the compiled shader object
*/
GLhandleARB opengl_shader_compile_object(const GLcharARB *shader_source, GLenum shader_type)
{
GLhandleARB shader_object = 0;
GLint status = 0;
shader_object = vglCreateShaderObjectARB(shader_type);
vglShaderSourceARB(shader_object, 1, &shader_source, NULL);
vglCompileShaderARB(shader_object);
// check if the compile was successful
vglGetObjectParameterivARB(shader_object, GL_OBJECT_COMPILE_STATUS_ARB, &status);
opengl_shader_check_info_log(shader_object);
// we failed, bail out now...
if (status == 0) {
// basic error check
mprintf(("%s shader failed to compile:\n%s\n", (shader_type == GL_VERTEX_SHADER_ARB) ? "Vertex" : ((shader_type == GL_GEOMETRY_SHADER_EXT) ? "Geometry" : "Fragment"), GLshader_info_log));
// this really shouldn't exist, but just in case
if (shader_object) {
vglDeleteObjectARB(shader_object);
}
return 0;
}
// we succeeded, maybe output warnings too
if (strlen(GLshader_info_log) > 5) {
nprintf(("SHADER-DEBUG", "%s shader compiled with warnings:\n%s\n", (shader_type == GL_VERTEX_SHADER_ARB) ? "Vertex" : ((shader_type == GL_GEOMETRY_SHADER_EXT) ? "Geometry" : "Fragment"), GLshader_info_log));
}
return shader_object;
}
/**
* Link a vertex shader object and a fragment shader object into a usable shader executable.
* Prints linker errors (if any) to the log.
*
* @param vertex_object Compiled vertex shader object
* @param fragment_object Compiled fragment shader object
* @return Shader executable
*/
GLhandleARB opengl_shader_link_object(GLhandleARB vertex_object, GLhandleARB fragment_object, GLhandleARB geometry_object)
{
GLhandleARB shader_object = 0;
GLint status = 0;
shader_object = vglCreateProgramObjectARB();
if (vertex_object) {
vglAttachObjectARB(shader_object, vertex_object);
}
if (fragment_object) {
vglAttachObjectARB(shader_object, fragment_object);
}
if (geometry_object) {
vglAttachObjectARB(shader_object, geometry_object);
if ( Current_geo_sdr_params != NULL) {
#ifdef __APPLE__
vglProgramParameteriEXT((long)shader_object, GL_GEOMETRY_INPUT_TYPE_EXT, Current_geo_sdr_params->input_type);
vglProgramParameteriEXT((long)shader_object, GL_GEOMETRY_OUTPUT_TYPE_EXT, Current_geo_sdr_params->output_type);
vglProgramParameteriEXT((long)shader_object, GL_GEOMETRY_VERTICES_OUT_EXT, Current_geo_sdr_params->vertices_out);
#else
vglProgramParameteriEXT((GLuint)shader_object, GL_GEOMETRY_INPUT_TYPE_EXT, Current_geo_sdr_params->input_type);
vglProgramParameteriEXT((GLuint)shader_object, GL_GEOMETRY_OUTPUT_TYPE_EXT, Current_geo_sdr_params->output_type);
vglProgramParameteriEXT((GLuint)shader_object, GL_GEOMETRY_VERTICES_OUT_EXT, Current_geo_sdr_params->vertices_out);
#endif
}
}
vglLinkProgramARB(shader_object);
// check if the link was successful
vglGetObjectParameterivARB(shader_object, GL_OBJECT_LINK_STATUS_ARB, &status);
opengl_shader_check_info_log(shader_object);
// we failed, bail out now...
if (status == 0) {
mprintf(("Shader failed to link:\n%s\n", GLshader_info_log));
if (shader_object) {
vglDeleteObjectARB(shader_object);
}
return 0;
}
// we succeeded, maybe output warnings too
if (strlen(GLshader_info_log) > 5) {
nprintf(("SHADER-DEBUG", "Shader linked with warnings:\n%s\n", GLshader_info_log));
}
return shader_object;
}
/**
* Creates an executable shader.
*
* @param vs Vertex shader source code
* @param fs Fragment shader source code
* @return Internal ID of the compiled and linked shader as generated by OpenGL
*/
GLhandleARB opengl_shader_create(const char *vs, const char *fs, const char *gs)
{
GLhandleARB vs_o = 0;
GLhandleARB fs_o = 0;
GLhandleARB gs_o = 0;
GLhandleARB program = 0;
if (vs) {
vs_o = opengl_shader_compile_object( (const GLcharARB*)vs, GL_VERTEX_SHADER_ARB );
if ( !vs_o ) {
mprintf(("ERROR! Unable to create vertex shader!\n"));
goto Done;
}
}
if (fs) {
fs_o = opengl_shader_compile_object( (const GLcharARB*)fs, GL_FRAGMENT_SHADER_ARB );
if ( !fs_o ) {
mprintf(("ERROR! Unable to create fragment shader!\n"));
goto Done;
}
}
if (gs) {
gs_o = opengl_shader_compile_object( (const GLcharARB*)gs, GL_GEOMETRY_SHADER_EXT );
if ( !gs_o ) {
mprintf(("ERROR! Unable to create fragment shader!\n"));
goto Done;
}
}
program = opengl_shader_link_object(vs_o, fs_o, gs_o);
if ( !program ) {
mprintf(("ERROR! Unable to create shader program!\n"));
}
Done:
if (vs_o) {
vglDeleteObjectARB(vs_o);
}
if (fs_o) {
vglDeleteObjectARB(fs_o);
}
if (gs_o) {
vglDeleteObjectARB(gs_o);
}
return program;
}
/**
* Initialize a shader attribute. Requires that the Current_shader global variable is valid.
*
* @param attribute_text Name of the attribute to be initialized
*/
void opengl_shader_init_attribute(const char *attribute_text)
{
opengl_shader_uniform_t new_attribute;
if ( ( Current_shader == NULL ) || ( attribute_text == NULL ) ) {
Int3();
return;
}
new_attribute.text_id = attribute_text;
new_attribute.location = vglGetAttribLocationARB(Current_shader->program_id, attribute_text);
if ( new_attribute.location < 0 ) {
nprintf(("SHADER-DEBUG", "WARNING: Unable to get shader attribute location for \"%s\"!\n", attribute_text));
return;
}
Current_shader->attributes.push_back( new_attribute );
}
/**
* Get the internal OpenGL location for a given attribute. Requires that the Current_shader global variable is valid
*
* @param attribute_text Name of the attribute
* @return Internal OpenGL location for the attribute
*/
GLint opengl_shader_get_attribute(const char *attribute_text)
{
if ( (Current_shader == NULL) || (attribute_text == NULL) ) {
Int3();
return -1;
}
SCP_vector<opengl_shader_uniform_t>::iterator attribute;
for (attribute = Current_shader->attributes.begin(); attribute != Current_shader->attributes.end(); ++attribute) {
if ( !attribute->text_id.compare(attribute_text) ) {
return attribute->location;
}
}
return -1;
}
/**
* Initialize a shader uniform. Requires that the Current_shader global variable is valid.
*
* @param uniform_text Name of the uniform to be initialized
*/
void opengl_shader_init_uniform(const char *uniform_text)
{
opengl_shader_uniform_t new_uniform;
if ( (Current_shader == NULL) || (uniform_text == NULL) ) {
Int3();
return;
}
new_uniform.text_id = uniform_text;
new_uniform.location = vglGetUniformLocationARB(Current_shader->program_id, uniform_text);
if (new_uniform.location < 0) {
nprintf(("SHADER-DEBUG", "WARNING: Unable to get shader uniform location for \"%s\"!\n", uniform_text));
return;
}
Current_shader->uniforms.push_back( new_uniform );
}
/**
* Get the internal OpenGL location for a given uniform. Requires that the Current_shader global variable is valid
*
* @param uniform_text Name of the uniform
* @return Internal OpenGL location for the uniform
*/
GLint opengl_shader_get_uniform(const char *uniform_text)
{
if ( (Current_shader == NULL) || (uniform_text == NULL) ) {
Int3();
return -1;
}
SCP_vector<opengl_shader_uniform_t>::iterator uniform;
SCP_vector<opengl_shader_uniform_t>::iterator uniforms_end = Current_shader->uniforms.end();
for (uniform = Current_shader->uniforms.begin(); uniform != uniforms_end; ++uniform) {
if ( !uniform->text_id.compare(uniform_text) ) {
return uniform->location;
}
}
return -1;
}
/**
* Initialize a shader uniform. Requires that the Current_shader global variable is valid.
*
* @param uniform_text Name of the uniform to be initialized
*/
void opengl_shader_init_uniform_block(const char *uniform_text)
{
opengl_shader_uniform_t new_uniform_block;
if ( (Current_shader == NULL) || (uniform_text == NULL) ) {
Int3();
return;
}
new_uniform_block.text_id = uniform_text;
#ifdef __APPLE__
new_uniform_block.location = vglGetUniformBlockIndexARB((long)Current_shader->program_id, uniform_text);
#else
new_uniform_block.location = vglGetUniformBlockIndexARB(Current_shader->program_id, uniform_text);
#endif
if (new_uniform_block.location < 0) {
nprintf(("SHADER-DEBUG", "WARNING: Unable to get shader uniform block location for \"%s\"!\n", uniform_text));
return;
}
Current_shader->uniform_blocks.push_back( new_uniform_block );
}
/**
* Get the internal OpenGL location for a given uniform. Requires that the Current_shader global variable is valid
*
* @param uniform_text Name of the uniform
* @return Internal OpenGL location for the uniform
*/
GLint opengl_shader_get_uniform_block(const char *uniform_text)
{
if ( (Current_shader == NULL) || (uniform_text == NULL) ) {
Int3();
return -1;
}
SCP_vector<opengl_shader_uniform_t>::iterator uniform_block;
for (uniform_block = Current_shader->uniform_blocks.begin(); uniform_block != Current_shader->uniform_blocks.end(); ++uniform_block) {
if ( !uniform_block->text_id.compare(uniform_text) ) {
return uniform_block->location;
}
}
return -1;
}
/**
* Sets the currently active animated effect.
*
* @param effect Effect ID, needs to be implemented and checked for in the shader
* @param timer Timer value to be passed to the shader
*/
void gr_opengl_shader_set_animated_effect(int effect, float timer)
{
GL_anim_effect_num = effect;
GL_anim_timer = timer;
}
/**
* Returns the currently active animated effect ID.
*
* @return Currently active effect ID
*/
int opengl_shader_get_animated_effect()
{
return GL_anim_effect_num;
}
/**
* Get the timer for animated effects.
*/
float opengl_shader_get_animated_timer()
{
return GL_anim_timer;
}
/**
* Compile the deferred light shader.
*/
void opengl_shader_compile_deferred_light_shader()
{
bool in_error = false;
int sdr_handle = gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_LIGHTING, 0);
if ( sdr_handle >= 0 ) {
opengl_shader_set_current(sdr_handle);
GL_state.Uniform.setUniformi("ColorBuffer", 0);
GL_state.Uniform.setUniformi("NormalBuffer", 1);
GL_state.Uniform.setUniformi("PositionBuffer", 2);
GL_state.Uniform.setUniformi("SpecBuffer", 3);
GL_state.Uniform.setUniformf("invScreenWidth", 1.0f / gr_screen.max_w);
GL_state.Uniform.setUniformf("invScreenHeight", 1.0f / gr_screen.max_h);
GL_state.Uniform.setUniformf("specFactor", Cmdline_ogl_spec);
} else {
opengl_shader_set_current();
mprintf(("Failed to compile deferred lighting shader!\n"));
in_error = true;
}
if ( gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_CLEAR, 0) < 0 ) {
mprintf(("Failed to compile deferred lighting buffer clear shader!\n"));
in_error = true;
}
if ( in_error ) {
mprintf((" Shader in_error! Disabling deferred lighting!\n"));
Cmdline_no_deferred_lighting = 1;
}
}