Files
fs2open.github.com/code/graphics/opengl/gropenglpostprocessing.cpp
asarium 4568172287 Make the OpenGL backend an optional component
This can be used for producing even leaner builds for standalone
servers. It also provides a nice architecture verification since now
using internal OpenGL functions will cause a compilation or linking
failure.
2020-08-14 18:52:14 +02:00

1189 lines
36 KiB
C++

#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/util/uniform_structs.h"
#include "io/timer.h"
#include "lighting/lighting.h"
#include "mod_table/mod_table.h"
#include "nebula/neb.h"
#include "parse/parselo.h"
#include "ship/ship.h"
#include "tracing/tracing.h"
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;
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, 0));
Current_shader->program->Uniforms.setTextureUniform("tex", 0);
opengl_set_generic_uniform_data<graphics::generic_data::tonemapping_data>(
[](graphics::generic_data::tonemapping_data* data) { data->exposure = 4.0f; });
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>(
[&](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>(
[](graphics::generic_data::bloom_composition_data* data) {
data->levels = MAX_MIP_BLUR_LEVELS;
data->bloom_intensity = Cmdline_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<shader_type> get_aa_shader_types(AntiAliasMode aa_mode) {
auto stypes = SCP_vector<shader_type>();
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>([](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>([](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 bool stars_sun_has_glare(int index);
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_lightshafts_enabled() ) {
int n_lights = light_get_global_count();
for ( int idx = 0; idx<n_lights; idx++ ) {
vec3d light_dir;
vec3d local_light_dir;
light_get_global_dir(&light_dir, idx);
vm_vec_rotate(&local_light_dir, &light_dir, &Eye_matrix);
if ( !stars_sun_has_glare(idx) ) {
continue;
}
float dot;
if ( (dot = vm_vec_dot(&light_dir, &Eye_matrix.vec.fvec)) > 0.7f ) {
x = asinf(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(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>(
[&](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>([&](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;
}
}
}
});
// 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 Shadow_map_texture;
extern GLuint Post_shadow_texture_id;
GL_state.Texture.Enable(Shadow_map_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<SCP_string> &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<<idx);
}
}
Post_active_shader_index = gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_MAIN, sflags);
}
void gr_opengl_post_process_set_defaults()
{
size_t idx;
if ( !Post_initialized ) {
return;
}
auto& postEffects = graphics::Post_processing_manager->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_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_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!");
}
}
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!");
}
}
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();
sprintf(temp, "#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
Cmdline_bloom_intensity = 0;
}
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();
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;
}