#include "graphics/gropengl.h" #include "graphics/gropenglextension.h" #include "graphics/gropenglpostprocessing.h" #include "graphics/gropenglshader.h" #include "graphics/gropenglstate.h" #include "graphics/gropengldraw.h" #include "io/timer.h" #include "nebula/neb.h" #include "parse/parselo.h" #include "cmdline/cmdline.h" #include "globalincs/def_files.h" #include "ship/ship.h" #include "freespace2/freespace.h" #include "lighting/lighting.h" extern bool PostProcessing_override; extern int opengl_check_framebuffer(); //Needed to track where the FXAA shaders are size_t fxaa_shader_id; //In case we don't find the shaders at all, this override is needed bool fxaa_unavailable = false; int Fxaa_preset_last_frame; bool zbuffer_saved = false; // lightshaft parameters bool ls_on = false; bool ls_force_off = false; float ls_density = 0.5f; float ls_weight = 0.02f; float ls_falloff = 1.0f; float ls_intensity = 0.5f; float ls_cpintensity = 0.5f * 50 * 0.02f; int ls_samplenum = 50; #define SDR_POST_FLAG_MAIN (1<<0) #define SDR_POST_FLAG_BRIGHT (1<<1) #define SDR_POST_FLAG_BLUR (1<<2) #define SDR_POST_FLAG_PASS1 (1<<3) #define SDR_POST_FLAG_PASS2 (1<<4) #define SDR_POST_FLAG_LIGHTSHAFT (1<<5) static SCP_vector GL_post_shader; struct opengl_shader_file_t { char *vert; char *frag; int flags; int num_uniforms; char* uniforms[MAX_SHADER_UNIFORMS]; int num_attributes; char* attributes[MAX_SDR_ATTRIBUTES]; }; // NOTE: The order of this list *must* be preserved! Additional shaders can be // added, but the first 7 are used with magic numbers so their position // is assumed to never change. static opengl_shader_file_t GL_post_shader_files[] = { // NOTE: the main post-processing shader has any number of uniforms, but // these few should always be present { "post-v.sdr", "post-f.sdr", SDR_POST_FLAG_MAIN, 4, { "tex", "timer", "bloomed", "bloom_intensity" } }, { "post-v.sdr", "blur-f.sdr", SDR_POST_FLAG_BLUR | SDR_POST_FLAG_PASS1, 2, { "tex", "bsize" } }, { "post-v.sdr", "blur-f.sdr", SDR_POST_FLAG_BLUR | SDR_POST_FLAG_PASS2, 2, { "tex", "bsize" } }, { "post-v.sdr", "brightpass-f.sdr", SDR_POST_FLAG_BRIGHT, 1, { "tex" } }, { "fxaa-v.sdr", "fxaa-f.sdr", NULL, 3, { "tex0", "rt_w", "rt_h"} }, { "post-v.sdr", "fxaapre-f.sdr", NULL, 1, { "tex"} }, { "post-v.sdr", "ls-f.sdr", SDR_POST_FLAG_LIGHTSHAFT, 8, { "scene", "cockpit", "sun_pos", "weight", "intensity", "falloff", "density", "cp_intensity" } } }; static const unsigned int Num_post_shader_files = sizeof(GL_post_shader_files) / sizeof(opengl_shader_file_t); typedef struct post_effect_t { SCP_string name; SCP_string uniform_name; SCP_string define_name; float intensity; float default_intensity; float div; float add; bool always_on; post_effect_t() : intensity(0.0f), default_intensity(0.0f), div(1.0f), add(0.0f), always_on(false) { } } post_effect_t; SCP_vector Post_effects; static int Post_initialized = 0; bool Post_in_frame = false; static int Post_active_shader_index = 0; static GLuint Post_framebuffer_id[2] = { 0 }; static GLuint Post_bloom_texture_id[3] = { 0 }; static int Post_texture_width = 0; static int Post_texture_height = 0; static char *opengl_post_load_shader(char *filename, int flags, int flags2); static bool opengl_post_pass_bloom() { if (Cmdline_bloom_intensity <= 0) { return false; } // we need the scissor test disabled GLboolean scissor_test = GL_state.ScissorTest(GL_FALSE); // ------ begin bright pass ------ vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Post_framebuffer_id[0]); // width and height are 1/2 for the bright pass int width = Post_texture_width >> 1; int height = Post_texture_height >> 1; glViewport(0, 0, width, height); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); opengl_shader_set_current( &GL_post_shader[3] ); vglUniform1iARB( opengl_shader_get_uniform("tex"), 0 ); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Scene_color_texture); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, 1.0f); glEnd(); GL_state.Texture.Disable(); // ------ end bright pass ------ // ------ begin blur pass ------ GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); // drop width and height once more for the blur passes width >>= 1; height >>= 1; glViewport(0, 0, width, height); vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Post_framebuffer_id[1]); for (int pass = 0; pass < 2; pass++) { vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, Post_bloom_texture_id[1+pass], 0); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); opengl_shader_set_current( &GL_post_shader[1+pass] ); vglUniform1iARB( opengl_shader_get_uniform("tex"), 0 ); vglUniform1fARB( opengl_shader_get_uniform("bsize"), (pass) ? (float)width : (float)height ); GL_state.Texture.Enable(Post_bloom_texture_id[pass]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, 1.0f); glEnd(); } GL_state.Texture.Disable(); // ------ end blur pass -------- // reset viewport, scissor test and exit glViewport(0, 0, gr_screen.max_w, gr_screen.max_h); GL_state.ScissorTest(scissor_test); vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, opengl_get_rtt_framebuffer()); return true; } void gr_opengl_post_process_begin() { if ( !Post_initialized ) { return; } if (Post_in_frame) { return; } if (PostProcessing_override) { return; } vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Post_framebuffer_id[0]); // vglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, Post_renderbuffer_id); // vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, Post_screen_texture_id, 0); // Assert( !opengl_check_framebuffer() ); GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }; vglDrawBuffers(2, buffers); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); Post_in_frame = true; } void recompile_fxaa_shader() { char *vert = NULL, *frag = NULL; opengl_shader_t *new_shader = &GL_post_shader[fxaa_shader_id]; opengl_shader_file_t *shader_file = &GL_post_shader_files[4]; // choose appropriate files char *vert_name = shader_file->vert; char *frag_name = shader_file->frag; mprintf(("Recompiling FXAA shader with preset %d\n", Cmdline_fxaa_preset)); // read vertex shader vert = opengl_post_load_shader(vert_name, shader_file->flags, NULL); // read fragment shader frag = opengl_post_load_shader(frag_name, shader_file->flags, NULL); Verify( vert != NULL ); Verify( frag != NULL ); new_shader->program_id = opengl_shader_create(vert, frag); if ( !new_shader->program_id ) { } new_shader->flags = shader_file->flags; new_shader->flags2 = NULL; opengl_shader_set_current( new_shader ); new_shader->uniforms.reserve(shader_file->num_uniforms); for (int i = 0; i < shader_file->num_uniforms; i++) { opengl_shader_init_uniform( shader_file->uniforms[i] ); } opengl_shader_set_current(); Fxaa_preset_last_frame = Cmdline_fxaa_preset; } void opengl_post_pass_fxaa() { //If the preset changed, recompile the shader if (Fxaa_preset_last_frame != Cmdline_fxaa_preset) { recompile_fxaa_shader(); } // We only want to draw to ATTACHMENT0 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); // Do a prepass to convert the main shaders' RGBA output into RGBL opengl_shader_set_current( &GL_post_shader[fxaa_shader_id + 1] ); // basic/default uniforms vglUniform1iARB( opengl_shader_get_uniform("tex"), 0 ); vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, Scene_luminance_texture, 0); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Scene_color_texture); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glTexCoord2f(Scene_texture_u_scale, 0.0f); glVertex2f(1.0f, -1.0f); glTexCoord2f(Scene_texture_u_scale, Scene_texture_v_scale); glVertex2f(1.0f, 1.0f); glTexCoord2f(0.0f, Scene_texture_v_scale); glVertex2f(-1.0f, 1.0f); glEnd(); GL_state.Texture.Disable(); // set and configure post shader .. opengl_shader_set_current( &GL_post_shader[fxaa_shader_id] ); vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, Scene_color_texture, 0); // basic/default uniforms vglUniform1iARB( opengl_shader_get_uniform("tex0"), 0 ); vglUniform1fARB( opengl_shader_get_uniform("rt_w"), static_cast(Post_texture_width)); vglUniform1fARB( opengl_shader_get_uniform("rt_h"), static_cast(Post_texture_height)); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Scene_luminance_texture); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glTexCoord2f(Scene_texture_u_scale, 0.0f); glVertex2f(1.0f, -1.0f); glTexCoord2f(Scene_texture_u_scale, Scene_texture_v_scale); glVertex2f(1.0f, 1.0f); glTexCoord2f(0.0f, Scene_texture_v_scale); glVertex2f(-1.0f, 1.0f); glEnd(); GL_state.Texture.Disable(); opengl_shader_set_current(); } extern GLuint shadow_map[2]; extern GLuint Scene_depth_texture; extern GLuint Cockpit_depth_texture; extern bool stars_sun_has_glare(int index); extern float Sun_spot; void gr_opengl_post_process_end() { // state switch just the once (for bloom pass and final render-to-screen) GLboolean depth = GL_state.DepthTest(GL_FALSE); GLboolean depth_mask = GL_state.DepthMask(GL_FALSE); GLboolean light = GL_state.Lighting(GL_FALSE); GLboolean blend = GL_state.Blend(GL_FALSE); GLboolean cull = GL_state.CullFace(GL_FALSE); GL_state.Texture.SetShaderMode(GL_TRUE); // Do FXAA if (Cmdline_fxaa && !fxaa_unavailable && !GL_rendering_to_texture) { opengl_post_pass_fxaa(); } opengl_shader_set_current( &GL_post_shader[6] ); float x,y; // should we even be here? if (!Game_subspace_effect && ls_on && !ls_force_off) { int n_lights = light_get_global_count(); for(int idx=0; idx 0.7f) { x = asin(vm_vec_dot( &light_dir, &Eye_matrix.vec.rvec ))/PI*1.5f+0.5f; //cant get the coordinates right but this works for the limited glare fov y = asin(vm_vec_dot( &light_dir, &Eye_matrix.vec.uvec ))/PI*1.5f*gr_screen.clip_aspect+0.5f; vglUniform2fARB( opengl_shader_get_uniform("sun_pos"), x, y); vglUniform1iARB( opengl_shader_get_uniform("scene"), 0); vglUniform1iARB( opengl_shader_get_uniform("cockpit"), 1); vglUniform1fARB( opengl_shader_get_uniform("density"), ls_density); vglUniform1fARB( opengl_shader_get_uniform("falloff"), ls_falloff); vglUniform1fARB( opengl_shader_get_uniform("weight"), ls_weight); vglUniform1fARB( opengl_shader_get_uniform("intensity"), Sun_spot * ls_intensity); vglUniform1fARB( opengl_shader_get_uniform("cp_intensity"), Sun_spot * ls_cpintensity); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Scene_depth_texture); GL_state.Texture.SetActiveUnit(1); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Cockpit_depth_texture); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); GL_state.Blend(GL_TRUE); GL_state.SetAlphaBlendMode(ALPHA_BLEND_ADDITIVE); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f); glTexCoord2f(Scene_texture_u_scale, 0.0f); glVertex2f(1.0f, -1.0f); glTexCoord2f(Scene_texture_u_scale, Scene_texture_v_scale); glVertex2f(1.0f, 1.0f); glTexCoord2f(0.0f, Scene_texture_v_scale); glVertex2f(-1.0f, 1.0f); glEnd(); GL_state.Blend(GL_FALSE); break; } } } if(zbuffer_saved) { zbuffer_saved = false; gr_zbuffer_set(GR_ZBUFF_FULL); glClear(GL_DEPTH_BUFFER_BIT); gr_zbuffer_set(GR_ZBUFF_NONE); vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, Scene_depth_texture, 0); } // Bind the correct framebuffer. opengl_get_rtt_framebuffer returns 0 if not doing RTT vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, opengl_get_rtt_framebuffer()); // do bloom, hopefully ;) bool bloomed = opengl_post_pass_bloom(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // set and configure post shader ... opengl_shader_set_current( &GL_post_shader[Post_active_shader_index] ); // basic/default uniforms vglUniform1iARB( opengl_shader_get_uniform("tex"), 0 ); vglUniform1fARB( opengl_shader_get_uniform("timer"), static_cast(timer_get_milliseconds() % 100 + 1) ); for (size_t idx = 0; idx < Post_effects.size(); idx++) { if ( GL_post_shader[Post_active_shader_index].flags2 & (1< &names) { size_t idx; for (idx = 0; idx < Post_effects.size(); idx++) { names.push_back(Post_effects[idx].name); } } static bool opengl_post_compile_shader(int flags) { char *vert = NULL, *frag = NULL; bool in_error = false; opengl_shader_t new_shader; opengl_shader_file_t *shader_file = &GL_post_shader_files[0]; int num_main_uniforms = 0; int idx; for (idx = 0; idx < (int)Post_effects.size(); idx++) { if ( flags & (1 << idx) ) { num_main_uniforms++; } } // choose appropriate files char *vert_name = shader_file->vert; char *frag_name = shader_file->frag; mprintf(("POST-PROCESSING: Compiling new post-processing shader with flags %d ... \n", flags)); // read vertex shader if ( (vert = opengl_post_load_shader(vert_name, shader_file->flags, flags)) == NULL ) { in_error = true; goto Done; } // read fragment shader if ( (frag = opengl_post_load_shader(frag_name, shader_file->flags, flags)) == NULL ) { in_error = true; goto Done; } Verify( vert != NULL ); Verify( frag != NULL ); new_shader.program_id = opengl_shader_create(vert, frag); if ( !new_shader.program_id ) { in_error = true; goto Done; } new_shader.flags = shader_file->flags; new_shader.flags2 = flags; opengl_shader_set_current( &new_shader ); new_shader.uniforms.reserve(shader_file->num_uniforms + num_main_uniforms); for (idx = 0; idx < shader_file->num_uniforms; idx++) { opengl_shader_init_uniform( shader_file->uniforms[idx] ); } for (idx = 0; idx < (int)Post_effects.size(); idx++) { if ( flags & (1 << idx) ) { opengl_shader_init_uniform( Post_effects[idx].uniform_name.c_str() ); } } opengl_shader_set_current(); // add it to our list of embedded shaders GL_post_shader.push_back( new_shader ); Done: if (vert != NULL) { vm_free(vert); vert = NULL; } if (frag != NULL) { vm_free(frag); frag = NULL; } return in_error; } void gr_opengl_post_process_set_effect(const char *name, int value) { if ( !Post_initialized ) { return; } if (name == NULL) { return; } size_t idx; int sflags = 0; bool need_change = true; if(!stricmp("lightshafts",name)) { ls_intensity = value / 100.0f; ls_on = !!value; return; } for (idx = 0; idx < Post_effects.size(); idx++) { const char *eff_name = Post_effects[idx].name.c_str(); if ( !stricmp(eff_name, name) ) { Post_effects[idx].intensity = (value / Post_effects[idx].div) + Post_effects[idx].add; break; } } // figure out new flags for (idx = 0; idx < Post_effects.size(); idx++) { if ( Post_effects[idx].always_on || (Post_effects[idx].intensity != Post_effects[idx].default_intensity) ) { sflags |= (1< 0; idx--) { if ( !(GL_post_shader[idx].flags & SDR_POST_FLAG_MAIN) ) { break; } if (GL_post_shader[idx].program_id) { vglDeleteObjectARB(GL_post_shader[idx].program_id); } GL_post_shader[idx].uniforms.clear(); GL_post_shader.pop_back(); } Post_active_shader_index = 0; } extern GLuint Cockpit_depth_texture; void gr_opengl_post_process_save_zbuffer() { if (Post_initialized) { vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, Cockpit_depth_texture, 0); gr_zbuffer_clear(TRUE); zbuffer_saved = true; } else { // If we can't save the z-buffer then just clear it so cockpits are still rendered correctly when // post-processing isn't available/enabled. gr_zbuffer_clear(TRUE); } } static bool opengl_post_init_table() { int rval; bool warned = false; if ( (rval = setjmp(parse_abort)) != 0 ) { mprintf(("Unable to parse 'post_processing.tbl'! Error code = %d.\n", rval)); return false; } if (cf_exists_full("post_processing.tbl", CF_TYPE_TABLES)) read_file_text("post_processing.tbl", CF_TYPE_TABLES); else read_file_text_from_array(defaults_get_file("post_processing.tbl")); reset_parse(); if (optional_string("#Effects")) { while ( !required_string_3("$Name:", "#Ship Effects", "#End") ) { char tbuf[NAME_LENGTH+1] = { 0 }; post_effect_t eff; required_string("$Name:"); stuff_string(tbuf, F_NAME, NAME_LENGTH); eff.name = tbuf; required_string("$Uniform:"); stuff_string(tbuf, F_NAME, NAME_LENGTH); eff.uniform_name = tbuf; required_string("$Define:"); stuff_string(tbuf, F_NAME, NAME_LENGTH); eff.define_name = tbuf; required_string("$AlwaysOn:"); stuff_boolean(&eff.always_on); required_string("$Default:"); stuff_float(&eff.default_intensity); eff.intensity = eff.default_intensity; required_string("$Div:"); stuff_float(&eff.div); required_string("$Add:"); stuff_float(&eff.add); // Post_effects index is used for flag checks, so we can't have more than 32 if (Post_effects.size() < 32) { Post_effects.push_back( eff ); } else if ( !warned ) { mprintf(("WARNING: post_processing.tbl can only have a max of 32 effects! Ignoring extra...\n")); warned = true; } } } //Built-in per-ship effects ship_effect se1; strcpy_s(se1.name, "FS1 Ship select"); se1.shader_effect = 0; se1.disables_rendering = false; se1.invert_timer = false; Ship_effects.push_back(se1); if (optional_string("#Ship Effects")) { while ( !required_string_3("$Name:", "#Light Shafts", "#End") ) { ship_effect se; char tbuf[NAME_LENGTH] = { 0 }; required_string("$Name:"); stuff_string(tbuf, F_NAME, NAME_LENGTH); strcpy_s(se.name, tbuf); required_string("$Shader Effect:"); stuff_int(&se.shader_effect); required_string("$Disables Rendering:"); stuff_boolean(&se.disables_rendering); required_string("$Invert timer:"); stuff_boolean(&se.invert_timer); Ship_effects.push_back(se); } } if (optional_string("#Light Shafts")) { required_string("$AlwaysOn:"); stuff_boolean(&ls_on); required_string("$Density:"); stuff_float(&ls_density); required_string("$Falloff:"); stuff_float(&ls_falloff); required_string("$Weight:"); stuff_float(&ls_weight); required_string("$Intensity:"); stuff_float(&ls_intensity); required_string("$Sample Number:"); stuff_int(&ls_samplenum); ls_cpintensity = ls_weight; for(int i = 1; i < ls_samplenum; i++) ls_cpintensity += ls_weight * pow(ls_falloff, i); ls_cpintensity *= ls_intensity; } required_string("#End"); return true; } static char *opengl_post_load_shader(char *filename, int flags, int flags2) { SCP_string sflags; 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"; } for (size_t idx = 0; idx < Post_effects.size(); idx++) { if ( flags2 & (1 << idx) ) { sflags += "#define "; sflags += Post_effects[idx].define_name.c_str(); sflags += "\n"; } } if (flags & SDR_POST_FLAG_PASS1) { sflags += "#define PASS_0\n"; } else if (flags & SDR_POST_FLAG_PASS2) { sflags += "#define PASS_1\n"; } if (flags & SDR_POST_FLAG_LIGHTSHAFT) { char temp[42]; sprintf(temp, "#define SAMPLE_NUM %d\n", ls_samplenum); sflags += temp; } switch (Cmdline_fxaa_preset) { case 0: sflags += "#define FXAA_QUALITY__PRESET 10\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/6.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/12.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 1: sflags += "#define FXAA_QUALITY__PRESET 11\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/7.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/14.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 2: sflags += "#define FXAA_QUALITY__PRESET 12\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/8.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/16.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 3: sflags += "#define FXAA_QUALITY__PRESET 13\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/9.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/18.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 4: sflags += "#define FXAA_QUALITY__PRESET 14\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/10.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/20.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 5: sflags += "#define FXAA_QUALITY__PRESET 25\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/11.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/22.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 6: sflags += "#define FXAA_QUALITY__PRESET 26\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/12.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/24.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 7: sflags += "#define FXAA_PC 1\n"; sflags += "#define FXAA_QUALITY__PRESET 27\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/13.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/26.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 8: sflags += "#define FXAA_QUALITY__PRESET 28\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/14.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/28.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; case 9: sflags += "#define FXAA_QUALITY__PRESET 39\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD (1.0/15.0)\n"; sflags += "#define FXAA_QUALITY__EDGE_THRESHOLD_MIN (1.0/32.0)\n"; sflags += "#define FXAA_QUALITY__SUBPIX 0.33\n"; break; } const char *shader_flags = sflags.c_str(); int flags_len = strlen(shader_flags); CFILE *cf_shader = cfopen(filename, "rt", CFILE_NORMAL, CF_TYPE_EFFECTS); if (cf_shader != NULL && stricmp(filename, "fxaa-f.sdr") && stricmp(filename, "fxaa-v.sdr") ) { 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; } else { 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); //memset(shader + flags_len, 0, len + 1); return shader; } } static bool opengl_post_init_shader() { char *vert = NULL, *frag = NULL; bool rval = true; int idx, i; int flags2 = 0; int num_main_uniforms = 0; for (idx = 0; idx < (int)Post_effects.size(); idx++) { if (Post_effects[idx].always_on) { flags2 |= (1 << idx); num_main_uniforms++; } } for (idx = 0; idx < (int)Num_post_shader_files; idx++) { bool in_error = false; opengl_shader_t new_shader; opengl_shader_file_t *shader_file = &GL_post_shader_files[idx]; // choose appropriate files char *vert_name = shader_file->vert; char *frag_name = shader_file->frag; mprintf((" Compiling post-processing shader %d ... \n", idx+1)); // read vertex shader if ( (vert = opengl_post_load_shader(vert_name, shader_file->flags, flags2)) == NULL ) { in_error = true; goto Done; } // read fragment shader if ( (frag = opengl_post_load_shader(frag_name, shader_file->flags, flags2)) == NULL ) { in_error = true; goto Done; } Verify( vert != NULL ); Verify( frag != NULL ); new_shader.program_id = opengl_shader_create(vert, frag); if ( !new_shader.program_id ) { in_error = true; goto Done; } new_shader.flags = shader_file->flags; new_shader.flags2 = flags2; opengl_shader_set_current( &new_shader ); new_shader.uniforms.reserve(shader_file->num_uniforms + num_main_uniforms); for (i = 0; i < shader_file->num_uniforms; i++) { opengl_shader_init_uniform( shader_file->uniforms[i] ); } if (idx == 0) { for (i = 0; i < (int)Post_effects.size(); i++) { if ( flags2 & (1 << i) ) { opengl_shader_init_uniform( Post_effects[i].uniform_name.c_str() ); } } flags2 = 0; num_main_uniforms = 0; } opengl_shader_set_current(); // add it to our list of embedded shaders GL_post_shader.push_back( new_shader ); Done: if (vert != NULL) { vm_free(vert); vert = NULL; } if (frag != NULL) { vm_free(frag); frag = NULL; } if (idx == 4) fxaa_shader_id = GL_post_shader.size() - 1; if (in_error) { if (idx == 0) { // only the main/first shader is actually required for post-processing rval = false; break; } else if (idx == 4) { Cmdline_fxaa = false; fxaa_unavailable = true; mprintf(("Error while compiling FXAA shaders. FXAA will be unavailable.\n")); } else if ( shader_file->flags & (SDR_POST_FLAG_BLUR|SDR_POST_FLAG_BRIGHT) ) { // disable bloom if we don't have those shaders available Cmdline_bloom_intensity = 0; } } } mprintf(("\n")); return rval; } // generate and test the framebuffer and textures that we are going to use static bool opengl_post_init_framebuffer() { bool rval = false; // clamp size, if needed Post_texture_width = gr_screen.max_w; Post_texture_height = gr_screen.max_h; if (Post_texture_width > GL_max_renderbuffer_size) { Post_texture_width = GL_max_renderbuffer_size; } if (Post_texture_height > GL_max_renderbuffer_size) { Post_texture_height = GL_max_renderbuffer_size; } if (Cmdline_bloom_intensity > 0) { // two more framebuffers, one each for the two different sized bloom textures vglGenFramebuffersEXT(1, &Post_framebuffer_id[0]); vglGenFramebuffersEXT(1, &Post_framebuffer_id[1]); // need to generate textures for bloom too glGenTextures(3, Post_bloom_texture_id); // half size int width = Post_texture_width >> 1; int height = Post_texture_height >> 1; for (int tex = 0; tex < 3; tex++) { GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Post_bloom_texture_id[tex]); 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); if (tex == 0) { // attach to our bright pass framebuffer and make sure it's ok vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Post_framebuffer_id[0]); vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, Post_bloom_texture_id[tex], 0); // if not then clean up and disable bloom if ( opengl_check_framebuffer() ) { vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); vglDeleteFramebuffersEXT(1, &Post_framebuffer_id[0]); vglDeleteFramebuffersEXT(1, &Post_framebuffer_id[1]); Post_framebuffer_id[0] = 0; Post_framebuffer_id[1] = 0; glDeleteTextures(3, Post_bloom_texture_id); memset(Post_bloom_texture_id, 0, sizeof(Post_bloom_texture_id)); Cmdline_bloom_intensity = 0; break; } // width and height are 1/2 for the bright pass, 1/4 for the blur, so drop down width >>= 1; height >>= 1; } else { // attach to our blur pass framebuffer and make sure it's ok vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Post_framebuffer_id[1]); vglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, Post_bloom_texture_id[tex], 0); // if not then clean up and disable bloom if ( opengl_check_framebuffer() ) { vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); vglDeleteFramebuffersEXT(1, &Post_framebuffer_id[0]); vglDeleteFramebuffersEXT(1, &Post_framebuffer_id[1]); Post_framebuffer_id[0] = 0; Post_framebuffer_id[1] = 0; glDeleteTextures(3, Post_bloom_texture_id); memset(Post_bloom_texture_id, 0, sizeof(Post_bloom_texture_id)); Cmdline_bloom_intensity = 0; break; } } } } vglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); GL_state.Texture.Disable(); rval = true; if ( opengl_check_for_errors("post_init_framebuffer()") ) { rval = false; } return rval; } void opengl_post_process_init() { Post_initialized = 0; //We need to read the tbl first. This is mostly for FRED's benefit, as otherwise the list of post effects for the sexp doesn't get updated. if ( !opengl_post_init_table() ) { mprintf((" Unable to read post-processing table! Disabling post-processing...\n\n")); Cmdline_postprocess = 0; return; } if ( !Cmdline_postprocess ) { return; } if ( !Scene_texture_initialized ) { return; } if ( !Use_GLSL || Cmdline_no_fbo || !Is_Extension_Enabled(OGL_EXT_FRAMEBUFFER_OBJECT) ) { Cmdline_postprocess = 0; return; } // for ease of use we require support for non-power-of-2 textures in one // form or another: // - the NPOT extension // - GL version 2.0+ (which should work for non-reporting ATI cards since we don't use mipmaps) if ( !(Is_Extension_Enabled(OGL_ARB_TEXTURE_NON_POWER_OF_TWO) || (GL_version >= 20)) ) { Cmdline_postprocess = 0; return; } if ( !opengl_post_init_shader() ) { mprintf((" Unable to initialize post-processing shaders! Disabling post-processing...\n\n")); Cmdline_postprocess = 0; return; } if ( !opengl_post_init_framebuffer() ) { mprintf((" Unable to initialize post-processing framebuffer! Disabling post-processing...\n\n")); Cmdline_postprocess = 0; return; } Post_initialized = 1; } void opengl_post_process_shutdown() { if ( !Post_initialized ) { return; } for (size_t i = 0; i < GL_post_shader.size(); i++) { if (GL_post_shader[i].program_id) { vglDeleteObjectARB(GL_post_shader[i].program_id); GL_post_shader[i].program_id = 0; } GL_post_shader[i].uniforms.clear(); } GL_post_shader.clear(); if (Post_bloom_texture_id[0]) { glDeleteTextures(3, Post_bloom_texture_id); memset(Post_bloom_texture_id, 0, sizeof(Post_bloom_texture_id)); } if (Post_framebuffer_id[0]) { vglDeleteFramebuffersEXT(1, &Post_framebuffer_id[0]); Post_framebuffer_id[0] = 0; if (Post_framebuffer_id[1]) { vglDeleteFramebuffersEXT(1, &Post_framebuffer_id[1]); Post_framebuffer_id[1] = 0; } } Post_effects.clear(); Post_in_frame = false; Post_active_shader_index = 0; Post_initialized = 0; }