#include "gropenglpostprocessing.h" #include "ShaderProgram.h" #include "SmaaAreaTex.h" #include "SmaaSearchTex.h" #include "freespace.h" #include "gropengl.h" #include "gropengldraw.h" #include "gropenglshader.h" #include "gropenglstate.h" #include "cmdline/cmdline.h" #include "def_files/def_files.h" #include "graphics/grinternal.h" #include "graphics/openxr.h" #include "graphics/util/uniform_structs.h" #include "io/timer.h" #include "lighting/lighting.h" #include "lighting/lighting_profiles.h" #include "mod_table/mod_table.h" #include "nebula/neb.h" #include "parse/parselo.h" #include "ship/ship.h" #include "tracing/tracing.h" #ifdef USE_OPENGL_ES #include "es_compatibility.h" #endif extern bool PostProcessing_override; extern int opengl_check_framebuffer(); // Needed to track where the FXAA shaders are // In case we don't find the shaders at all, this override is needed bool fxaa_unavailable = false; bool zbuffer_saved = false; const int MAX_MIP_BLUR_LEVELS = 4; static int Post_initialized = 0; bool Post_in_frame = false; static int Post_active_shader_index = -1; static GLuint Bloom_framebuffer = 0; static GLuint Bloom_textures[2] = { 0 }; static GLuint Post_framebuffer_id[2] = { 0 }; static int Post_texture_width = 0; static int Post_texture_height = 0; static GLuint Smaa_edge_detection_fb = 0; static GLuint Smaa_edges_tex = 0; static GLuint Smaa_blending_weight_fb = 0; static GLuint Smaa_blend_tex = 0; static GLuint Smaa_neighborhood_blending_fb = 0; static GLuint Smaa_output_tex = 0; static GLuint Smaa_search_tex = 0; static GLuint Smaa_area_tex = 0; namespace ltp = lighting_profiles; using namespace ltp; void opengl_post_pass_tonemap() { GR_DEBUG_SCOPE("Tonemapping"); TRACE_SCOPE(tracing::Tonemapping); opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_TONEMAPPING, openxr_enabled() ? SDR_FLAG_TONEMAPPING_LINEAR_OUT : 0)); Current_shader->program->Uniforms.setTextureUniform("tex", 0); opengl_set_generic_uniform_data( [](graphics::generic_data::tonemapping_data* data) { auto ppc = ltp::current_piecewise_intermediates(); auto tn = ltp::current_tonemapper(); data->tonemapper = (int)tn; data->sh_B = ppc.sh_B; data->sh_lnA = ppc.sh_lnA; data->sh_offsetX =ppc.sh_offsetX; data->sh_offsetY = ppc.sh_offsetY; data->toe_B = ppc.toe_B; data->toe_lnA = ppc.toe_lnA; data->x0 = ppc.x0; data->x1 = ppc.x1; data->y0 = ppc.y0; data->exposure = ltp::current_exposure(); }); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Scene_ldr_texture, 0); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_color_texture); opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); } void opengl_post_pass_bloom() { GR_DEBUG_SCOPE("Bloom"); TRACE_SCOPE(tracing::Bloom); // we need the scissor test disabled GLboolean scissor_test = GL_state.ScissorTest(GL_FALSE); // ------ begin bright pass ------ int width, height; { GR_DEBUG_SCOPE("Bloom bright pass"); TRACE_SCOPE(tracing::BloomBrightPass); GL_state.BindFrameBuffer(Bloom_framebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Bloom_textures[0], 0); // width and height are 1/2 for the bright pass width = Post_texture_width >> 1; 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(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BRIGHTPASS, 0)); Current_shader->program->Uniforms.setTextureUniform("tex", 0); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_color_texture); opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f); } // ------ end bright pass ------ // ------ begin blur pass ------ GL_state.Texture.Enable(0, GL_TEXTURE_2D, Bloom_textures[0]); glGenerateMipmap(GL_TEXTURE_2D); for ( int iteration = 0; iteration < 2; iteration++) { for (int pass = 0; pass < 2; pass++) { GR_DEBUG_SCOPE("Bloom iteration step"); TRACE_SCOPE(tracing::BloomIterationStep); GLuint source_tex = Bloom_textures[pass]; GLuint dest_tex = Bloom_textures[1 - pass]; if (pass) { opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BLUR, SDR_FLAG_BLUR_HORIZONTAL)); } else { opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BLUR, SDR_FLAG_BLUR_VERTICAL)); } Current_shader->program->Uniforms.setTextureUniform("tex", 0); GL_state.Texture.Enable(0, GL_TEXTURE_2D, source_tex); for (int mipmap = 0; mipmap < MAX_MIP_BLUR_LEVELS; ++mipmap) { int bloom_width = width >> mipmap; int bloom_height = height >> mipmap; opengl_set_generic_uniform_data( [&](graphics::generic_data::blur_data* data) { data->texSize = (pass) ? 1.0f / i2fl(bloom_width) : 1.0f / i2fl(bloom_height); data->level = mipmap; }); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dest_tex, mipmap); glViewport(0, 0, bloom_width, bloom_height); opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f); } } } // composite blur to the color texture { GR_DEBUG_SCOPE("Bloom composite step"); TRACE_SCOPE(tracing::BloomCompositeStep); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Scene_color_texture, 0); opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BLOOM_COMP, 0)); Current_shader->program->Uniforms.setTextureUniform("tex", 0); opengl_set_generic_uniform_data( [](graphics::generic_data::bloom_composition_data* data) { data->levels = MAX_MIP_BLUR_LEVELS; data->bloom_intensity = gr_bloom_intensity() / 100.0f; }); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Bloom_textures[0]); GL_state.SetAlphaBlendMode(ALPHA_BLEND_ADDITIVE); glViewport(0, 0, gr_screen.max_w, gr_screen.max_h); opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f); GL_state.SetAlphaBlendMode(ALPHA_BLEND_NONE); } // ------ end blur pass -------- // reset viewport, scissor test and exit GL_state.ScissorTest(scissor_test); } void gr_opengl_post_process_begin() { if ( !Post_initialized ) { return; } if (Post_in_frame) { return; } if (PostProcessing_override) { return; } GL_state.PushFramebufferState(); GL_state.BindFrameBuffer(Post_framebuffer_id[0]); glDrawBuffer(GL_COLOR_ATTACHMENT0); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); Post_in_frame = true; } SCP_vector get_aa_shader_types(AntiAliasMode aa_mode) { auto stypes = SCP_vector(); if (gr_is_fxaa_mode(aa_mode)) { stypes.push_back(shader_type::SDR_TYPE_POST_PROCESS_FXAA); } else if (gr_is_smaa_mode(aa_mode)) { stypes.push_back(shader_type::SDR_TYPE_POST_PROCESS_SMAA_BLENDING_WEIGHT); stypes.push_back(shader_type::SDR_TYPE_POST_PROCESS_SMAA_EDGE); stypes.push_back(shader_type::SDR_TYPE_POST_PROCESS_SMAA_NEIGHBORHOOD_BLENDING); } return stypes; } void recompile_aa_shader() { mprintf(("Recompiling AA shader(s)...\n")); for (auto sdr : get_aa_shader_types(Gr_aa_mode_last_frame)) { opengl_delete_shader( gr_opengl_maybe_create_shader(sdr, 0) ); } for (auto sdr : get_aa_shader_types(Gr_aa_mode)) { gr_opengl_maybe_create_shader(sdr, 0); } Gr_aa_mode_last_frame = Gr_aa_mode; } void opengl_post_pass_fxaa() { GR_DEBUG_SCOPE("FXAA"); TRACE_SCOPE(tracing::FXAA); // If the preset changed, recompile the shader if (Gr_aa_mode_last_frame != Gr_aa_mode) { recompile_aa_shader(); } // We only want to draw to ATTACHMENT0 glDrawBuffer(GL_COLOR_ATTACHMENT0); GL_state.ColorMask(true, true, true, true); // Do a prepass to convert the main shaders' RGBA output into RGBL opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_FXAA_PREPASS, 0)); // basic/default uniforms Current_shader->program->Uniforms.setTextureUniform("tex", 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Scene_luminance_texture, 0); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_ldr_texture); opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); // set and configure post shader .. opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_FXAA, 0)); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Scene_ldr_texture, 0); // basic/default uniforms Current_shader->program->Uniforms.setTextureUniform("tex0", 0); opengl_set_generic_uniform_data([](graphics::generic_data::fxaa_data* data) { data->rt_w = i2fl(Post_texture_width); data->rt_h = i2fl(Post_texture_height); }); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_luminance_texture); opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); opengl_shader_set_current(); } static void smaa_detect_edges() { GR_DEBUG_SCOPE("SMAA Detect Edges"); TRACE_SCOPE(tracing::SMAAEdgeDetection); GL_state.BindFrameBuffer(Smaa_edge_detection_fb); GLfloat clearValues[] = {0.0f, 0.0f, 0.0f, 1.0f}; glClearBufferfv(GL_COLOR, 0, clearValues); // Do the edge detection step opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_SMAA_EDGE, 0)); // basic/default uniforms Current_shader->program->Uniforms.setTextureUniform("colorTex", 0); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_ldr_texture); opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); } static void smaa_calculate_blending_weights() { GR_DEBUG_SCOPE("SMAA Blending Weights calculation"); TRACE_SCOPE(tracing::SMAACalculateBlendingWeights); GL_state.BindFrameBuffer(Smaa_blending_weight_fb); GLfloat clearValues[] = {0.0f, 0.0f, 0.0f, 1.0f}; glClearBufferfv(GL_COLOR, 0, clearValues); // Do the edge detection step opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_SMAA_BLENDING_WEIGHT, 0)); // basic/default uniforms Current_shader->program->Uniforms.setTextureUniform("edgesTex", 0); Current_shader->program->Uniforms.setTextureUniform("areaTex", 1); Current_shader->program->Uniforms.setTextureUniform("searchTex", 2); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Smaa_edges_tex); GL_state.Texture.Enable(1, GL_TEXTURE_2D, Smaa_area_tex); GL_state.Texture.Enable(2, GL_TEXTURE_2D, Smaa_search_tex); opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); } static void smaa_neighborhood_blending() { GR_DEBUG_SCOPE("SMAA Neighborhood Blending"); TRACE_SCOPE(tracing::SMAANeighborhoodBlending); GL_state.BindFrameBuffer(Smaa_neighborhood_blending_fb); GLfloat clearValues[] = {0.0f, 0.0f, 0.0f, 1.0f}; glClearBufferfv(GL_COLOR, 0, clearValues); // Do the edge detection step opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_SMAA_NEIGHBORHOOD_BLENDING, 0)); // basic/default uniforms Current_shader->program->Uniforms.setTextureUniform("colorTex", 0); Current_shader->program->Uniforms.setTextureUniform("blendTex", 1); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_ldr_texture); GL_state.Texture.Enable(1, GL_TEXTURE_2D, Smaa_blend_tex); opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); } void smaa_resolve() { GR_DEBUG_SCOPE("SMAA Resolve"); TRACE_SCOPE(tracing::SMAAResolve); opengl_shader_set_passthrough(true, false); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Smaa_output_tex); // Copy SMAA output back to the original framebuffer if (GL_rendering_to_texture) { opengl_draw_textured_quad(0.0f, 0.0f, 0.0f, 0.0f, (float)gr_screen.max_w, (float)gr_screen.max_h, Scene_texture_u_scale, Scene_texture_v_scale); } else { opengl_draw_textured_quad(0.0f, 0.0f, 0.0f, Scene_texture_v_scale, (float)gr_screen.max_w, (float)gr_screen.max_h, Scene_texture_u_scale, 0.0f); } } void opengl_post_pass_smaa() { GR_DEBUG_SCOPE("SMAA"); TRACE_SCOPE(tracing::SMAA); // If the preset changed, recompile the shader if (Gr_aa_mode_last_frame != Gr_aa_mode) { recompile_aa_shader(); } GL_state.PushFramebufferState(); GL_state.ColorMask(true, true, true, true); // All SMAA stages share the same shader data so we only need this once opengl_set_generic_uniform_data([](graphics::generic_data::smaa_data* data) { data->smaa_rt_metrics.x = i2fl(Post_texture_width); data->smaa_rt_metrics.y = i2fl(Post_texture_height); }); smaa_detect_edges(); smaa_calculate_blending_weights(); smaa_neighborhood_blending(); GL_state.PopFramebufferState(); smaa_resolve(); } extern GLuint Shadow_map_depth_texture; extern GLuint Scene_depth_texture; extern GLuint Cockpit_depth_texture; extern GLuint Scene_position_texture; extern GLuint Scene_normal_texture; extern GLuint Scene_specular_texture; extern float Sun_spot; void opengl_post_lightshafts() { GR_DEBUG_SCOPE("Lightshafts"); TRACE_SCOPE(tracing::Lightshafts); opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_LIGHTSHAFTS, 0)); float x, y; // should we even be here? if ( !Game_subspace_effect && gr_sunglare_enabled() && gr_lightshafts_enabled() ) { int n_lights = light_get_global_count(); for ( int idx = 0; idx 0.7f ) { x = asinf_safe(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 = asinf_safe(vm_vec_dot(&light_dir, &Eye_matrix.vec.uvec)) / PI * 1.5f * gr_screen.clip_aspect + 0.5f; opengl_set_generic_uniform_data( [&](graphics::generic_data::lightshaft_data* data) { data->sun_pos.x = x; data->sun_pos.y = y; auto& ls_params = graphics::Post_processing_manager->getLightshaftParams(); data->density = ls_params.density; data->falloff = ls_params.falloff; data->weight = ls_params.weight; data->intensity = Sun_spot * ls_params.intensity; data->cp_intensity = Sun_spot * ls_params.cpintensity; }); Current_shader->program->Uniforms.setTextureUniform("scene", 0); Current_shader->program->Uniforms.setTextureUniform("cockpit", 1); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_depth_texture); GL_state.Texture.Enable(1, GL_TEXTURE_2D, Cockpit_depth_texture); GL_state.Blend(GL_TRUE); GL_state.SetAlphaBlendMode(ALPHA_BLEND_ADDITIVE); opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); GL_state.Blend(GL_FALSE); break; } } } } void gr_opengl_post_process_end() { GR_DEBUG_SCOPE("Draw scene texture"); TRACE_SCOPE(tracing::DrawSceneTexture); // 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 blend = GL_state.Blend(GL_FALSE); GLboolean cull = GL_state.CullFace(GL_FALSE); GL_state.Texture.SetShaderMode(GL_TRUE); GL_state.PushFramebufferState(); // do bloom, hopefully ;) opengl_post_pass_bloom(); // do tone mapping opengl_post_pass_tonemap(); // Do Post processing AA if (!GL_rendering_to_texture) { if (gr_is_smaa_mode(Gr_aa_mode)) { opengl_post_pass_smaa(); } else if (gr_is_fxaa_mode(Gr_aa_mode) && !fxaa_unavailable) { opengl_post_pass_fxaa(); } } // render lightshafts opengl_post_lightshafts(); GR_DEBUG_SCOPE("Draw post effects"); TRACE_SCOPE(tracing::DrawPostEffects); // now write to the previous buffer GL_state.PopFramebufferState(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // set and configure post shader ... int flags = 0; const auto& postEffects = graphics::Post_processing_manager->getPostEffects(); for ( int i = 0; i < (int)postEffects.size(); i++) { if (postEffects[i].always_on) { flags |= (1 << i); } } int post_sdr_handle = Post_active_shader_index; if (post_sdr_handle < 0) { // no active shader index? use the always on shader. post_sdr_handle = gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_MAIN, flags); } opengl_shader_set_current(post_sdr_handle); // basic/default uniforms Current_shader->program->Uniforms.setTextureUniform("tex", 0); GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_ldr_texture); Current_shader->program->Uniforms.setTextureUniform("depth_tex", 1); GL_state.Texture.Enable(1, GL_TEXTURE_2D, Scene_depth_texture); opengl_set_generic_uniform_data([&](graphics::generic_data::post_data* data) { data->timer = i2fl(timer_get_milliseconds() % 100 + 1); for (size_t idx = 0; idx < postEffects.size(); idx++) { if (GL_shader[post_sdr_handle].flags & (1 << idx)) { float value = postEffects[idx].intensity; switch (postEffects[idx].uniform_type) { case graphics::PostEffectUniformType::Invalid: // Invalid name specified, do nothing break; case graphics::PostEffectUniformType::NoiseAmount: data->noise_amount = value; break; case graphics::PostEffectUniformType::Saturation: data->saturation = value; break; case graphics::PostEffectUniformType::Brightness: data->brightness = value; break; case graphics::PostEffectUniformType::Contrast: data->contrast = value; break; case graphics::PostEffectUniformType::FilmGrain: data->film_grain = value; break; case graphics::PostEffectUniformType::TvStripes: data->tv_stripes = value; break; case graphics::PostEffectUniformType::Cutoff: data->cutoff = value; break; case graphics::PostEffectUniformType::Dither: data->dither = value; break; case graphics::PostEffectUniformType::Tint: data->tint = postEffects[idx].rgb; break; case graphics::PostEffectUniformType::CustomEffectVEC3A: data->custom_effect_vec3_a = postEffects[idx].rgb; break; case graphics::PostEffectUniformType::CustomEffectFloatA: data->custom_effect_float_a = value; break; case graphics::PostEffectUniformType::CustomEffectVEC3B: data->custom_effect_vec3_b = postEffects[idx].rgb; break; case graphics::PostEffectUniformType::CustomEffectFloatB: data->custom_effect_float_b = value; break; } } } }); // now render it to the screen ... GL_state.PopFramebufferState(); opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); //Shadow Map debug window //#define SHADOW_DEBUG #ifdef SHADOW_DEBUG opengl_shader_set_current( &GL_post_shader[8] ); GL_state.Texture.SetActiveUnit(0); // GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.SetTarget(GL_TEXTURE_2D_ARRAY); // GL_state.Texture.Enable(Shadow_map_depth_texture); extern GLuint Post_shadow_texture_id; GL_state.Texture.Enable(Shadow_map_depth_texture); glUniform1iARB( opengl_shader_get_uniform("shadow_map"), 0); glUniform1iARB( opengl_shader_get_uniform("index"), 0); //opengl_draw_textured_quad(-1.0f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, Scene_texture_u_scale, Scene_texture_u_scale); //opengl_draw_textured_quad(-1.0f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.5f); opengl_draw_textured_quad(-1.0f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 1.0f, 1.0f); glUniform1iARB( opengl_shader_get_uniform("index"), 1); //opengl_draw_textured_quad(-1.0f, -0.5f, 0.5f, 0.0f, -0.5f, 0.0f, 0.75f, 0.25f); opengl_draw_textured_quad(-1.0f, -0.5f, 0.0f, 0.0f, -0.5f, 0.0f, 1.0f, 1.0f); glUniform1iARB( opengl_shader_get_uniform("index"), 2); opengl_draw_textured_quad(-0.5f, -1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 1.0f, 1.0f); glUniform1iARB( opengl_shader_get_uniform("index"), 3); opengl_draw_textured_quad(-0.5f, -1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 1.0f, 1.0f); opengl_shader_set_current(); #endif /*GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Scene_depth_texture); */ // Done! /*GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Scene_effect_texture); opengl_draw_textured_quad(0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Scene_normal_texture); opengl_draw_textured_quad(-1.0f, -0.0f, 0.0f, 0.0f, 0.0f, 1.0f, Scene_texture_u_scale, Scene_texture_u_scale); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Scene_specular_texture); opengl_draw_textured_quad(0.0f, -0.0f, 0.0f, 0.0f, 1.0f, 1.0f, Scene_texture_u_scale, Scene_texture_u_scale); */ GL_state.Texture.SetShaderMode(GL_FALSE); // reset state GL_state.DepthTest(depth); GL_state.DepthMask(depth_mask); GL_state.Blend(blend); GL_state.CullFace(cull); opengl_shader_set_current(); Post_in_frame = false; } void get_post_process_effect_names(SCP_vector &names) { size_t idx; const auto& postEffects = graphics::Post_processing_manager->getPostEffects(); for (idx = 0; idx < postEffects.size(); idx++) { names.push_back(postEffects[idx].name); } } void gr_opengl_post_process_set_effect(const char *name, int value, const vec3d *rgb) { if ( !Post_initialized ) { return; } if (name == NULL) { return; } size_t idx; int sflags = 0; auto& ls_params = graphics::Post_processing_manager->getLightshaftParams(); if(!stricmp("lightshafts",name)) { ls_params.intensity = value / 100.0f; ls_params.on = !!value; return; } auto& postEffects = graphics::Post_processing_manager->getPostEffects(); for (idx = 0; idx < postEffects.size(); idx++) { const char *eff_name = postEffects[idx].name.c_str(); if ( !stricmp(eff_name, name) ) { postEffects[idx].intensity = (value / postEffects[idx].div) + postEffects[idx].add; if ((rgb != nullptr) && !(vmd_zero_vector == *rgb)) { postEffects[idx].rgb = *rgb; } break; } } // figure out new flags for (idx = 0; idx < postEffects.size(); idx++) { if ( postEffects[idx].always_on || (postEffects[idx].intensity != postEffects[idx].default_intensity) ) { sflags |= (1<getPostEffects(); // reset all effects to their default values for (idx = 0; idx < postEffects.size(); idx++) { postEffects[idx].intensity = postEffects[idx].default_intensity; } Post_active_shader_index = -1; } void gr_opengl_post_process_save_zbuffer() { GR_DEBUG_SCOPE("Save z-Buffer"); if (Post_initialized) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 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); } } void gr_opengl_post_process_restore_zbuffer() { GR_DEBUG_SCOPE("Restore z-Buffer"); if (zbuffer_saved) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, Scene_depth_texture, 0); zbuffer_saved = false; } } static void set_fxaa_defines(SCP_stringstream& sflags) { // Since we require OpenGL 3.2 we always have support for GLSL 130 sflags << "#define FXAA_GLSL_120 0\n"; sflags << "#define FXAA_GLSL_130 1\n"; if (GLSL_version >= 400) { // The gather function became part of the standard with GLSL 4.00 sflags << "#define FXAA_GATHER4_ALPHA 1\n"; } switch (Gr_aa_mode) { case AntiAliasMode::None: 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 AntiAliasMode::FXAA_Low: 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 AntiAliasMode::FXAA_Medium: 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 AntiAliasMode::FXAA_High: 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; default: UNREACHABLE("Unhandled FXAA mode %d!", static_cast(Gr_aa_mode)); } } void set_smaa_defines(SCP_stringstream& sflags) { // Define what GLSL version we use if (GLSL_version >= 400) { sflags << "#define SMAA_GLSL_4\n"; } else { sflags << "#define SMAA_GLSL_3\n"; } switch (Gr_aa_mode) { case AntiAliasMode::SMAA_Low: sflags << "#define SMAA_PRESET_LOW\n"; break; case AntiAliasMode::SMAA_Medium: sflags << "#define SMAA_PRESET_MEDIUM\n"; break; case AntiAliasMode::SMAA_High: sflags << "#define SMAA_PRESET_HIGH\n"; break; case AntiAliasMode::SMAA_Ultra: sflags << "#define SMAA_PRESET_ULTRA\n"; break; default: UNREACHABLE("Unhandled SMAA mode %d!", static_cast(Gr_aa_mode)); } } void opengl_post_shader_header(SCP_stringstream& sflags, shader_type shader_t, int flags) { const auto& postEffects = graphics::Post_processing_manager->getPostEffects(); if (shader_t == SDR_TYPE_POST_PROCESS_MAIN) { for (size_t idx = 0; idx < postEffects.size(); idx++) { if (flags & (1 << idx)) { sflags << "#define "; sflags << postEffects[idx].define_name.c_str(); sflags << "\n"; } } } else if (shader_t == SDR_TYPE_POST_PROCESS_LIGHTSHAFTS) { char temp[64]; const auto& ls_params = graphics::Post_processing_manager->getLightshaftParams(); snprintf(temp, 64, "#define SAMPLE_NUM %d\n", ls_params.samplenum); sflags << temp; } else if (shader_t == SDR_TYPE_POST_PROCESS_FXAA) { set_fxaa_defines(sflags); } else if (shader_t == SDR_TYPE_POST_PROCESS_SMAA_EDGE || shader_t == SDR_TYPE_POST_PROCESS_SMAA_BLENDING_WEIGHT || shader_t == SDR_TYPE_POST_PROCESS_SMAA_NEIGHBORHOOD_BLENDING) { set_smaa_defines(sflags); } } bool opengl_post_init_shaders() { int idx; int flags = 0; // Make sure this is set properly before we actually try to render anything with the post pipeline Gr_aa_mode_last_frame = Gr_aa_mode; const auto& postEffects = graphics::Post_processing_manager->getPostEffects(); // figure out which flags we need for the main post process shader for (idx = 0; idx < (int)postEffects.size(); idx++) { if (postEffects[idx].always_on) { flags |= (1 << idx); } } if ( gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_MAIN, flags) < 0 ) { // only the main shader is actually required for post-processing return false; } if ( gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BRIGHTPASS, 0) < 0 || gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BLUR, SDR_FLAG_BLUR_HORIZONTAL) < 0 || gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BLUR, SDR_FLAG_BLUR_VERTICAL) < 0 || gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_BLOOM_COMP, 0) < 0) { // disable bloom if we don't have those shaders available graphics::Post_processing_manager->setBloomShadersOk(false); } if (gr_is_fxaa_mode(Gr_aa_mode)) { gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_FXAA, 0); gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_FXAA_PREPASS, 0); } if (gr_is_smaa_mode(Gr_aa_mode)) { // Precompile the SMAA shaders if enabled gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_SMAA_EDGE, 0); gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_SMAA_BLENDING_WEIGHT, 0); gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_SMAA_NEIGHBORHOOD_BLENDING, 0); } return true; } void opengl_setup_bloom_textures() { // two more framebuffers, one each for the two different sized bloom textures glGenFramebuffers(1, &Bloom_framebuffer); // need to generate textures for bloom too glGenTextures(2, Bloom_textures); // half size int width = Post_texture_width >> 1; int height = Post_texture_height >> 1; for (int tex = 0; tex < 2; tex++) { GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Bloom_textures[tex]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); glGenerateMipmap(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_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); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, MAX_MIP_BLUR_LEVELS-1); } GL_state.BindFrameBuffer(0); } void setup_smaa_edges_resources() { glGenTextures(1, &Smaa_edges_tex); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Smaa_edges_tex); opengl_set_object_label(GL_TEXTURE, Smaa_edges_tex, "SMAA Edge detection texture"); if (GLAD_GL_ARB_texture_storage) { glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, Post_texture_width, Post_texture_height); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Post_texture_width, Post_texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr); } 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_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glGenFramebuffers(1, &Smaa_edge_detection_fb); GL_state.BindFrameBuffer(Smaa_edge_detection_fb); opengl_set_object_label(GL_FRAMEBUFFER, Smaa_edge_detection_fb, "SMAA Edge detection framebuffer"); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Smaa_edges_tex, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); } void setup_smaa_blending_weight_resources() { glGenTextures(1, &Smaa_blend_tex); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Smaa_blend_tex); opengl_set_object_label(GL_TEXTURE, Smaa_blend_tex, "SMAA Blending weight calculation texture"); if (GLAD_GL_ARB_texture_storage) { glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, Post_texture_width, Post_texture_height); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Post_texture_width, Post_texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr); } 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_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glGenFramebuffers(1, &Smaa_blending_weight_fb); GL_state.BindFrameBuffer(Smaa_blending_weight_fb); opengl_set_object_label(GL_FRAMEBUFFER, Smaa_blending_weight_fb, "SMAA Blending widght calculation framebuffer"); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Smaa_blend_tex, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); } void setup_smaa_neighborhood_blending_resources() { glGenTextures(1, &Smaa_output_tex); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(Smaa_output_tex); opengl_set_object_label(GL_TEXTURE, Smaa_output_tex, "SMAA output texture"); if (GLAD_GL_ARB_texture_storage) { glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, Post_texture_width, Post_texture_height); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Post_texture_width, Post_texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr); } 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_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glGenFramebuffers(1, &Smaa_neighborhood_blending_fb); GL_state.BindFrameBuffer(Smaa_neighborhood_blending_fb); opengl_set_object_label(GL_FRAMEBUFFER, Smaa_neighborhood_blending_fb, "SMAA neighborhood blending framebuffer"); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Smaa_output_tex, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); } static GLuint load_smaa_texture(GLsizei width, GLsizei height, GLenum format, const uint8_t* pixels, const char* name) { GLuint tex; glGenTextures(1, &tex); GL_state.Texture.SetActiveUnit(0); GL_state.Texture.SetTarget(GL_TEXTURE_2D); GL_state.Texture.Enable(tex); opengl_set_object_label(GL_TEXTURE, tex, name); if (GLAD_GL_ARB_texture_storage) { glTexStorage2D(GL_TEXTURE_2D, 1, format, width, height); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexImage2D(GL_TEXTURE_2D, 0, format, Post_texture_width, Post_texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr); } glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format == GL_RG8 ? GL_RG : GL_RED, GL_UNSIGNED_BYTE, pixels); 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_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); return tex; } static void setup_smaa_resources() { GL_state.PushFramebufferState(); Smaa_area_tex = load_smaa_texture(AREATEX_WIDTH, AREATEX_HEIGHT, GL_RG8, areaTexBytes, "SMAA Area Texture"); Smaa_search_tex = load_smaa_texture(SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT, GL_R8, searchTexBytes, "SMAA Search Texture"); setup_smaa_edges_resources(); setup_smaa_blending_weight_resources(); setup_smaa_neighborhood_blending_resources(); GL_state.PopFramebufferState(); } // 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; } opengl_setup_bloom_textures(); // Always set up SMAA resources so the user can switch to an SMAA preset // at runtime even when starting with a non-SMAA AA mode, such as None. //if (Gr_aa_mode != AntiAliasMode::None) { setup_smaa_resources(); //} GL_state.BindFrameBuffer(0); rval = true; if ( opengl_check_for_errors("post_init_framebuffer()") ) { rval = false; } return rval; } void opengl_post_process_shutdown_bloom() { if ( Bloom_textures[0] ) { glDeleteTextures(1, &Bloom_textures[0]); Bloom_textures[0] = 0; } if ( Bloom_textures[1] ) { glDeleteTextures(1, &Bloom_textures[1]); Bloom_textures[1] = 0; } if ( Bloom_framebuffer > 0 ) { glDeleteFramebuffers(1, &Bloom_framebuffer); Bloom_framebuffer = 0; } } void opengl_post_process_init() { // Externally used manager class to expose information about post processing externally graphics::Post_processing_manager.reset(new graphics::PostProcessingManager()); 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 ( !graphics::Post_processing_manager->parse_table() ) { mprintf((" Unable to read post-processing table! Disabling post-processing...\n\n")); Gr_post_processing_enabled = false; return; } if ( !Gr_post_processing_enabled ) { return; } if ( !Scene_texture_initialized ) { return; } if ( Cmdline_no_fbo ) { Gr_post_processing_enabled = false; return; } if ( !opengl_post_init_shaders() ) { mprintf((" Unable to initialize post-processing shaders! Disabling post-processing...\n\n")); Gr_post_processing_enabled = false; return; } if ( !opengl_post_init_framebuffer() ) { mprintf((" Unable to initialize post-processing framebuffer! Disabling post-processing...\n\n")); Gr_post_processing_enabled = false; return; } Post_initialized = 1; } void opengl_post_process_shutdown() { if ( !Post_initialized ) { return; } if (Post_framebuffer_id[0]) { glDeleteFramebuffers(1, &Post_framebuffer_id[0]); Post_framebuffer_id[0] = 0; if (Post_framebuffer_id[1]) { glDeleteFramebuffers(1, &Post_framebuffer_id[1]); Post_framebuffer_id[1] = 0; } } graphics::Post_processing_manager->clear(); graphics::Post_processing_manager = nullptr; opengl_post_process_shutdown_bloom(); Post_in_frame = false; Post_active_shader_index = 0; Post_initialized = 0; }