Files
fs2open.github.com/code/graphics/opengl/gropengldraw.cpp
the-eandClaude Opus 5 4c0ff8414d Grow the scene/post render targets when the viewport does
The offscreen targets backing the scene texture and post-processing were
allocated once from gr_screen at gr_init() and never revisited. Anything drawn
while gr_screen was larger got clipped to their edge and stretched back over
the viewport. The game hits this on the SDL window-resize path
(osapi.cpp calls gr_screen_resize()); qtFRED hits it constantly, since its 3D
viewport is a resizable dock widget.

Add gf_resize_render_targets, called from gr_screen_resize(), and implement it
for OpenGL: grow the targets to cover the new gr_screen, rebuilding only the
resolution-dependent resources. The post-processing table, the compiled
shaders and the SMAA lookup textures are resolution-independent and stay
alive, which is what makes this cheap enough to run off a window drag. This
mirrors what the Vulkan backend already does in VulkanPostProcessor::resize();
Vulkan leaves the hook unset because recreateSwapChain() owns it there.

The allocation only ever grows: gr_screen_resize() is called every frame by
qtFRED and repeatedly by the briefing map widget, so tracking the high-water
mark avoids thrashing, and the shrunk state is already handled by
Scene_texture_u_scale/v_scale. The size is also clamped to the hardware limit
up front, so a viewport past that limit stops asking to be resized instead of
rebuilding every frame. If the larger allocation fails outright -- most likely
precisely when growing -- the resize stops before rebuilding the
post-processing targets on top of scene textures that no longer exist.

This replaces Gr_min_render_target_w/h, which sized the targets for the
largest attached display up front -- on a 4K display at 2x scaling that was
over a gigabyte of VRAM, allocated at launch, for a feature that is off by
default and may never be switched on.

Deletions now go through GL_state.Texture.Delete(): a freed texture name the
state cache still holds would make a later Enable() of the recycled name a
silent no-op. This mattered little when teardown only ran at shutdown. While
there, the scene teardown now releases everything setup allocates -- it was
leaking Scene_ldr/composite/luminance/Cockpit_depth and all six MSAA targets --
and post-processing shutdown releases the SMAA lookup textures.

Separately, fix the sampling extents that the above makes reachable:

 - deferred-f.sdr turns gl_FragCoord into a G-buffer coordinate using
   invScreenWidth/Height, which described gr_screen rather than the G-buffer.
   Fixed in both backends; it is currently a no-op under Vulkan, where
   resize() keeps the extent equal to gr_screen, but states the requirement
   instead of relying on that.
 - the MSAA scene-colour copy, the MSAA resolve and the fog pass sampled the
   full [0,1] range of targets that are only filled to the u/v scale.
 - fxaa-v.sdr derived its texcoord from vertPosition, ignoring the
   sub-rectangle the draw call asked for.

Those extents now come from opengl_draw_full_screen_scene_texture() rather
than being open-coded, so a new pass cannot quietly reintroduce the bug. The
volumetric nebula pass is deliberately left unscaled and commented: it uses
fragTexCoord both to reconstruct a ray direction and to sample, which needs a
shader change to separate.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:45:48 +01:00

1450 lines
50 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "gropengldraw.h"
#include "globalincs/pstypes.h"
#include "ShaderProgram.h"
#include "freespace.h"
#include "gropengl.h"
#include "gropengldeferred.h"
#include "gropenglpostprocessing.h"
#include "gropenglshader.h"
#include "gropengltexture.h"
#include "gropengltnl.h"
#include "cmdline/cmdline.h"
#include "graphics/light.h"
#include "graphics/matrix.h"
#include "graphics/paths/PathRenderer.h"
#include "graphics/util/uniform_structs.h"
#include "lighting/lighting.h"
#include "render/3d.h"
#include "tracing/tracing.h"
#ifdef USE_OPENGL_ES
#include "es_compatibility.h"
#endif
GLuint Scene_framebuffer;
GLuint Scene_framebuffer_ms;
GLuint Scene_ldr_texture;
GLuint Scene_color_texture;
GLuint Scene_position_texture;
GLuint Scene_normal_texture;
GLuint Scene_specular_texture;
GLuint Scene_emissive_texture;
GLuint Scene_color_texture_ms;
GLuint Scene_position_texture_ms;
GLuint Scene_normal_texture_ms;
GLuint Scene_specular_texture_ms;
GLuint Scene_emissive_texture_ms;
GLuint Scene_composite_texture;
GLuint Scene_luminance_texture;
GLuint Scene_depth_texture;
GLuint Scene_depth_texture_ms;
GLuint Cockpit_depth_texture;
GLuint Back_framebuffer;
GLuint Back_texture;
GLuint Back_depth_texture;
GLuint GammaBlit_framebuffer;
GLuint GammaBlit_texture;
GLuint Distortion_framebuffer = 0;
GLuint Distortion_texture[2];
int Distortion_switch = 0;
int Scene_texture_initialized;
int Scene_texture_width;
int Scene_texture_height;
GLfloat Scene_texture_u_scale = 1.0f;
GLfloat Scene_texture_v_scale = 1.0f;
// Render targets are torn down and rebuilt mid-session by gr_opengl_resize_render_targets(), not
// just at shutdown, so deletion has to go through the state cache: the driver is free to hand a
// freed name straight back out, and a cache entry still holding that name would make a later
// Enable() of the recycled texture a no-op.
void opengl_delete_render_texture(GLuint& tex)
{
if ( !tex ) {
return;
}
GL_state.Texture.Delete(tex);
glDeleteTextures(1, &tex);
tex = 0;
}
// Callers must have bound something else first (the resize path binds 0); the framebuffer cache
// has no equivalent of Texture.Delete() to unbind through.
void opengl_delete_render_framebuffer(GLuint& fbo)
{
if ( !fbo ) {
return;
}
glDeleteFramebuffers(1, &fbo);
fbo = 0;
}
inline GLenum opengl_primitive_type(primitive_type prim_type)
{
switch ( prim_type ) {
case PRIM_TYPE_POINTS:
return GL_POINTS;
case PRIM_TYPE_TRIS:
return GL_TRIANGLES;
case PRIM_TYPE_TRISTRIP:
return GL_TRIANGLE_STRIP;
case PRIM_TYPE_TRIFAN:
return GL_TRIANGLE_FAN;
case PRIM_TYPE_LINES:
return GL_LINES;
case PRIM_TYPE_LINESTRIP:
return GL_LINE_STRIP;
default:
return GL_TRIANGLE_FAN;
}
}
void gr_opengl_sphere(material* material_def, float /*rad*/)
{
opengl_tnl_set_material(material_def, true);
opengl_draw_sphere();
}
extern int opengl_check_framebuffer();
void opengl_setup_scene_textures(int width, int height)
{
Scene_texture_initialized = 0;
if ( Cmdline_no_fbo ) {
Gr_post_processing_enabled = false;
Gr_enable_soft_particles = false;
Gr_framebuffer_effects = {};
Scene_ldr_texture = 0;
Scene_color_texture = 0;
Scene_depth_texture = 0;
return;
}
Scene_texture_width = width;
Scene_texture_height = height;
// clamp size, if needed
if ( Scene_texture_width > GL_max_renderbuffer_size ) {
Scene_texture_width = GL_max_renderbuffer_size;
}
if ( Scene_texture_height > GL_max_renderbuffer_size) {
Scene_texture_height = GL_max_renderbuffer_size;
}
mprintf((" Scene textures: %dx%d (screen %dx%d, max renderbuffer %d)\n",
Scene_texture_width,
Scene_texture_height,
gr_screen.max_w,
gr_screen.max_h,
GL_max_renderbuffer_size));
// create framebuffer
glGenFramebuffers(1, &Scene_framebuffer);
GL_state.BindFrameBuffer(Scene_framebuffer);
opengl_set_object_label(GL_FRAMEBUFFER, Scene_framebuffer, "Scene framebuffer");
// setup main render texture
// setup high dynamic range color texture
glGenTextures(1, &Scene_color_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_color_texture);
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_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Scene_color_texture, 0);
opengl_set_object_label(GL_TEXTURE, Scene_color_texture, "Scene color texture");
// setup low dynamic range color texture
glGenTextures(1, &Scene_ldr_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_ldr_texture);
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, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
opengl_set_object_label(GL_TEXTURE, Scene_ldr_texture, "Scene LDR texture");
// setup position render texture
glGenTextures(1, &Scene_position_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_position_texture);
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_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
opengl_set_object_label(GL_TEXTURE, Scene_position_texture, "Scene Position texture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, Scene_position_texture, 0);
// setup normal render texture
glGenTextures(1, &Scene_normal_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_normal_texture);
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_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
opengl_set_object_label(GL_TEXTURE, Scene_normal_texture, "Scene Normal texture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, Scene_normal_texture, 0);
// setup specular render texture
glGenTextures(1, &Scene_specular_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_specular_texture);
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, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
opengl_set_object_label(GL_TEXTURE, Scene_specular_texture, "Scene Specular texture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, Scene_specular_texture, 0);
// setup emissive render texture
glGenTextures(1, &Scene_emissive_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_emissive_texture);
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_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glGenerateMipmap(GL_TEXTURE_2D);
opengl_set_object_label(GL_TEXTURE, Scene_emissive_texture, "Scene Emissive texture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT4, GL_TEXTURE_2D, Scene_emissive_texture, 0);
// setup compositing render texture
glGenTextures(1, &Scene_composite_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_composite_texture);
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_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
opengl_set_object_label(GL_TEXTURE, Scene_composite_texture, "Scene Composite texture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT5, GL_TEXTURE_2D, Scene_composite_texture, 0);
//Set up luminance texture (used as input for FXAA)
// also used as a light accumulation buffer during the deferred pass
glGenTextures(1, &Scene_luminance_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_luminance_texture);
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_RGBA16F, Scene_texture_width, Scene_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
opengl_set_object_label(GL_TEXTURE, Scene_luminance_texture, "Scene Luminance texture");
// setup cockpit depth texture
glGenTextures(1, &Cockpit_depth_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Cockpit_depth_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
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_COMPARE_MODE, GL_NONE);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_DEPTH24_STENCIL8,
Scene_texture_width,
Scene_texture_height,
0,
GL_DEPTH_COMPONENT,
GL_FLOAT,
nullptr);
opengl_set_object_label(GL_TEXTURE, Cockpit_depth_texture, "Cockpit depth texture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, Cockpit_depth_texture, 0);
gr_zbuffer_set(GR_ZBUFF_FULL);
glClear(GL_DEPTH_BUFFER_BIT);
// setup main depth texture
glGenTextures(1, &Scene_depth_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Scene_depth_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
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_COMPARE_MODE, GL_NONE);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_DEPTH24_STENCIL8,
Scene_texture_width,
Scene_texture_height,
0,
GL_DEPTH_COMPONENT,
GL_FLOAT,
nullptr);
opengl_set_object_label(GL_TEXTURE, Scene_depth_texture, "Scene depth texture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, Scene_depth_texture, 0);
glReadBuffer(GL_COLOR_ATTACHMENT0);
if ( opengl_check_framebuffer() ) {
GL_state.BindFrameBuffer(0);
opengl_delete_render_framebuffer(Scene_framebuffer);
opengl_delete_render_texture(Scene_color_texture);
opengl_delete_render_texture(Scene_position_texture);
opengl_delete_render_texture(Scene_normal_texture);
opengl_delete_render_texture(Scene_specular_texture);
opengl_delete_render_texture(Scene_emissive_texture);
opengl_delete_render_texture(Scene_depth_texture);
opengl_delete_render_texture(Scene_luminance_texture);
Gr_post_processing_enabled = false;
Gr_enable_soft_particles = false;
return;
}
#ifdef USE_OPENGL_ES
if (Cmdline_msaa_enabled > 0) {
Cmdline_msaa_enabled = 0;
Warning(LOCATION, "MSAA is not currently supported under OpenGL ES. Disabling MSAA.");
}
#endif
if (Cmdline_msaa_enabled > 0) {
glEnable(GL_MULTISAMPLE);
// Make sure our MSAA setting are valid
int maxSamples;
glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
//Disallow >16 samples due to manual sorting networks in shader
if (maxSamples > 16)
maxSamples = 16;
if (maxSamples < Cmdline_msaa_enabled) {
Warning(LOCATION, "Requested MSAA level of %d is not supported. Max MSAA level is %d.", Cmdline_msaa_enabled, maxSamples);
Cmdline_msaa_enabled = maxSamples;
}
else if (Cmdline_msaa_enabled & (Cmdline_msaa_enabled - 1)) {
int newLevel = 1 << (sizeof(int) * 8 - 2);
while ((newLevel & Cmdline_msaa_enabled) == 0)
newLevel = newLevel >> 1;
Warning(LOCATION, "Requested MSAA level of %d is not a power of 2. Setting to %d.", Cmdline_msaa_enabled, newLevel);
Cmdline_msaa_enabled = newLevel;
}
// create framebuffer
glGenFramebuffers(1, &Scene_framebuffer_ms);
GL_state.BindFrameBuffer(Scene_framebuffer_ms);
opengl_set_object_label(GL_FRAMEBUFFER, Scene_framebuffer_ms, "Scene framebuffer multisampling");
// setup high dynamic range color texture
glGenTextures(1, &Scene_color_texture_ms);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D_MULTISAMPLE);
GL_state.Texture.Enable(Scene_color_texture_ms);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
Cmdline_msaa_enabled,
GL_RGBA16F,
Scene_texture_width,
Scene_texture_height,
GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D_MULTISAMPLE,
Scene_color_texture_ms,
0);
opengl_set_object_label(GL_TEXTURE, Scene_color_texture_ms, "Scene color texture multisampling");
// setup position render texture
glGenTextures(1, &Scene_position_texture_ms);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D_MULTISAMPLE);
GL_state.Texture.Enable(Scene_position_texture_ms);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
Cmdline_msaa_enabled,
GL_RGBA16F,
Scene_texture_width,
Scene_texture_height,
GL_TRUE);
opengl_set_object_label(GL_TEXTURE, Scene_position_texture_ms, "Scene Position texture multisampling");
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT1,
GL_TEXTURE_2D_MULTISAMPLE,
Scene_position_texture_ms,
0);
// setup normal render texture
glGenTextures(1, &Scene_normal_texture_ms);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D_MULTISAMPLE);
GL_state.Texture.Enable(Scene_normal_texture_ms);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
Cmdline_msaa_enabled,
GL_RGBA16F,
Scene_texture_width,
Scene_texture_height,
GL_TRUE);
opengl_set_object_label(GL_TEXTURE, Scene_normal_texture_ms, "Scene Normal texture multisampling");
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT2,
GL_TEXTURE_2D_MULTISAMPLE,
Scene_normal_texture_ms,
0);
// setup specular render texture
glGenTextures(1, &Scene_specular_texture_ms);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D_MULTISAMPLE);
GL_state.Texture.Enable(Scene_specular_texture_ms);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
Cmdline_msaa_enabled,
GL_RGBA8,
Scene_texture_width,
Scene_texture_height,
GL_TRUE);
opengl_set_object_label(GL_TEXTURE, Scene_specular_texture_ms, "Scene Specular texture multisample");
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT3,
GL_TEXTURE_2D_MULTISAMPLE,
Scene_specular_texture_ms,
0);
// setup emissive render texture
glGenTextures(1, &Scene_emissive_texture_ms);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D_MULTISAMPLE);
GL_state.Texture.Enable(Scene_emissive_texture_ms);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
Cmdline_msaa_enabled,
GL_RGBA16F,
Scene_texture_width,
Scene_texture_height,
GL_TRUE);
opengl_set_object_label(GL_TEXTURE, Scene_emissive_texture_ms, "Scene Emissive texture multisample");
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT4,
GL_TEXTURE_2D_MULTISAMPLE,
Scene_emissive_texture_ms,
0);
// setup main depth texture
glGenTextures(1, &Scene_depth_texture_ms);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D_MULTISAMPLE);
GL_state.Texture.Enable(Scene_depth_texture_ms);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
Cmdline_msaa_enabled,
GL_DEPTH24_STENCIL8,
Scene_texture_width,
Scene_texture_height,
GL_TRUE);
opengl_set_object_label(GL_TEXTURE, Scene_depth_texture_ms, "Scene depth texture multisample");
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D_MULTISAMPLE,
Scene_depth_texture_ms,
0);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
}
if (Cmdline_window_res) {
//Gen Framebuffer
glGenFramebuffers(1, &Back_framebuffer);
GL_state.BindFrameBuffer(Back_framebuffer);
opengl_set_object_label(GL_FRAMEBUFFER, Back_framebuffer, "Backbuffer");
// setup main render texture
// setup high dynamic range color texture
glGenTextures(1, &Back_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Back_texture);
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_RGBA16F, gr_screen.max_w, gr_screen.max_h, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Back_texture, 0);
opengl_set_object_label(GL_TEXTURE, Back_texture, "Backbuffer texture");
glGenTextures(1, &Back_depth_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Back_depth_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
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_COMPARE_MODE, GL_NONE);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_DEPTH24_STENCIL8,
gr_screen.max_w,
gr_screen.max_h,
0,
GL_DEPTH_STENCIL,
GL_UNSIGNED_INT_24_8,
nullptr);
opengl_set_object_label(GL_TEXTURE, Back_depth_texture, "Backbuffer depth texture");
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, Back_depth_texture, 0);
gr_zbuffer_set(GR_ZBUFF_FULL);
glClear(GL_DEPTH_BUFFER_BIT);
// Intermediate target for the gamma-correction pass. The gamma pass samples Back_texture and writes
// here (an offscreen FBO->FBO draw, same as every other post-process pass); the result is then
// presented to the window via glBlitFramebuffer. Some drivers show flicker when a shader draw that
// samples a just-rendered texture targets the window-system default framebuffer directly, so the
// final presentation step always goes through a blit instead.
glGenFramebuffers(1, &GammaBlit_framebuffer);
GL_state.BindFrameBuffer(GammaBlit_framebuffer);
opengl_set_object_label(GL_FRAMEBUFFER, GammaBlit_framebuffer, "Gamma blit framebuffer");
if (Scene_ldr_texture != 0 && Scene_texture_width == gr_screen.max_w && Scene_texture_height == gr_screen.max_h) {
// Scene_ldr_texture is dead scratch by flip time: its consumers (tonemap, AA, the final
// post pass) all write before reading within gr_opengl_post_process_end(), which completes
// before the flip. Reuse it as the gamma pass target instead of allocating another
// screen-sized texture. GammaBlit_texture stays 0 so shutdown won't delete it.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Scene_ldr_texture, 0);
} else {
// Scene textures are clamped to GL_max_renderbuffer_size and may not cover the screen;
// fall back to a dedicated texture in that case.
glGenTextures(1, &GammaBlit_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(GammaBlit_texture);
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, gr_screen.max_w, gr_screen.max_h, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, GammaBlit_texture, 0);
opengl_set_object_label(GL_TEXTURE, GammaBlit_texture, "Gamma blit texture");
}
GL_state.BindFrameBuffer(Back_framebuffer);
}
//Setup thruster distortion framebuffer
if (Gr_framebuffer_effects.any_set())
{
glGenFramebuffers(1, &Distortion_framebuffer);
GL_state.BindFrameBuffer(Distortion_framebuffer);
glGenTextures(2, Distortion_texture);
GL_state.Texture.SetActiveUnit(0);
GL_state.Texture.SetTarget(GL_TEXTURE_2D);
GL_state.Texture.Enable(Distortion_texture[0]);
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_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
GL_state.Texture.Enable(Distortion_texture[1]);
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_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Distortion_texture[0], 0);
}
if ( opengl_check_framebuffer() ) {
GL_state.BindFrameBuffer(0);
glDeleteFramebuffers(1, &Distortion_framebuffer);
Distortion_framebuffer = 0;
glDeleteTextures(2, Distortion_texture);
Distortion_texture[0] = 0;
Distortion_texture[1] = 0;
return;
}
if ( opengl_check_for_errors("post_init_framebuffer()") ) {
Scene_color_texture = 0;
Scene_depth_texture = 0;
Gr_post_processing_enabled = false;
Gr_enable_soft_particles = false;
return;
}
GL_state.BindFrameBuffer(0);
Scene_texture_initialized = 1;
Scene_framebuffer_in_frame = false;
}
void opengl_scene_texture_shutdown()
{
if ( !Scene_texture_initialized ) {
return;
}
// Everything opengl_setup_scene_textures() generated, in the same order. Note that
// GammaBlit_texture is 0 when the gamma pass is aliasing Scene_ldr_texture, so the shared
// texture is only released once.
opengl_delete_render_texture(Scene_color_texture);
opengl_delete_render_texture(Scene_ldr_texture);
opengl_delete_render_texture(Scene_position_texture);
opengl_delete_render_texture(Scene_normal_texture);
opengl_delete_render_texture(Scene_specular_texture);
opengl_delete_render_texture(Scene_emissive_texture);
opengl_delete_render_texture(Scene_composite_texture);
opengl_delete_render_texture(Scene_luminance_texture);
opengl_delete_render_texture(Cockpit_depth_texture);
opengl_delete_render_texture(Scene_depth_texture);
opengl_delete_render_framebuffer(Scene_framebuffer);
opengl_delete_render_texture(Scene_color_texture_ms);
opengl_delete_render_texture(Scene_position_texture_ms);
opengl_delete_render_texture(Scene_normal_texture_ms);
opengl_delete_render_texture(Scene_specular_texture_ms);
opengl_delete_render_texture(Scene_emissive_texture_ms);
opengl_delete_render_texture(Scene_depth_texture_ms);
opengl_delete_render_framebuffer(Scene_framebuffer_ms);
opengl_delete_render_texture(Back_texture);
opengl_delete_render_texture(Back_depth_texture);
opengl_delete_render_framebuffer(Back_framebuffer);
opengl_delete_render_texture(GammaBlit_texture);
opengl_delete_render_framebuffer(GammaBlit_framebuffer);
opengl_delete_render_texture(Distortion_texture[0]);
opengl_delete_render_texture(Distortion_texture[1]);
opengl_delete_render_framebuffer(Distortion_framebuffer);
Scene_texture_initialized = 0;
Scene_framebuffer_in_frame = false;
}
void gr_opengl_resize_render_targets()
{
// Nothing allocated yet (still inside gr_init()), or FBOs are unavailable entirely.
if ( !Scene_texture_initialized ) {
return;
}
// Grow only. Shrinking back would mean reallocating every G-buffer again the moment the window
// grew back, and the shrunk state is already handled correctly: Scene_texture_u_scale and
// _v_scale confine rendering to the sub-rectangle actually in use. The hardware limit is
// applied here rather than left to opengl_setup_scene_textures(), so that a viewport larger
// than anything the GPU can allocate compares equal below and stops asking.
const int new_width = MIN(MAX(gr_screen.max_w, Scene_texture_width), GL_max_renderbuffer_size);
const int new_height = MIN(MAX(gr_screen.max_h, Scene_texture_height), GL_max_renderbuffer_size);
// The overwhelmingly common case: qtFred calls gr_screen_resize() every frame and the game
// calls it on every SDL resize event, almost always at a size the current targets already
// cover -- or, past the hardware limit, at one they never will.
if ( new_width == Scene_texture_width && new_height == Scene_texture_height ) {
return;
}
// Tearing down the framebuffer we are currently rendering into would corrupt the frame rather
// than fail cleanly, so refuse rather than trying to recover. Callers resize between frames.
// Scene_framebuffer_in_frame covers the post-processing passes too: they only ever run inside
// gr_scene_texture_begin()/end(), so it is set for the whole of Post_in_frame as well.
if ( Scene_framebuffer_in_frame ) {
Assertion(false, "Tried to resize the render targets to %dx%d while a scene was being "
"rendered into them! The resize has been skipped; the frame will be stretched.",
new_width, new_height);
return;
}
mprintf(("Growing render targets from %dx%d to %dx%d to cover the new %dx%d viewport.\n",
Scene_texture_width, Scene_texture_height, new_width, new_height,
gr_screen.max_w, gr_screen.max_h));
// Leave the framebuffer cache pointing at a name that cannot be deleted out from under it.
GL_state.BindFrameBufferBoth(0, 0);
// Only the size-dependent resources are touched. The post-processing table, the compiled
// shaders and the SMAA lookup textures are all resolution-independent and stay alive, which is
// what keeps this cheap enough to run off a window drag. The post-processing targets are
// rebuilt after the scene textures because they are sized to match them.
opengl_scene_texture_shutdown();
opengl_setup_scene_textures(new_width, new_height);
// Reallocating larger is exactly when running out of video memory is most likely, and
// opengl_setup_scene_textures() reports that by leaving the scene uninitialized (having
// already turned post-processing and soft particles off). Rebuilding the post-processing
// targets on top of scene textures that don't exist would only make it worse, so stop here;
// the renderer keeps drawing without the offscreen pipeline.
if ( !Scene_texture_initialized ) {
mprintf(("Failed to allocate %dx%d render targets! The offscreen rendering pipeline has "
"been disabled for the rest of this session.\n", new_width, new_height));
return;
}
opengl_post_resize_render_targets();
}
size_t opengl_get_scene_render_target_bytes()
{
// The bytes for each pixel come from the internal formats that opengl_setup_scene_textures()
// gives to these textures: GL_RGBA16F holds 8 bytes, GL_RGBA8 and GL_DEPTH24_STENCIL8 hold 4.
// Keep the values below equal to the formats at the creation sites in that function.
constexpr size_t RGBA16F_BYTES = 8;
constexpr size_t RGBA8_BYTES = 4;
constexpr size_t DEPTH24_STENCIL8_BYTES = 4;
size_t bytes = 0;
auto add_texture = [&bytes](GLuint texture_id, size_t width, size_t height, size_t bytes_per_pixel,
size_t samples = 1) {
if (texture_id == 0) {
return;
}
bytes += width * height * bytes_per_pixel * samples;
};
const auto scene_w = static_cast<size_t>(Scene_texture_width);
const auto scene_h = static_cast<size_t>(Scene_texture_height);
add_texture(Scene_color_texture, scene_w, scene_h, RGBA16F_BYTES);
add_texture(Scene_ldr_texture, scene_w, scene_h, RGBA8_BYTES);
add_texture(Scene_position_texture, scene_w, scene_h, RGBA16F_BYTES);
add_texture(Scene_normal_texture, scene_w, scene_h, RGBA16F_BYTES);
add_texture(Scene_specular_texture, scene_w, scene_h, RGBA8_BYTES);
add_texture(Scene_emissive_texture, scene_w, scene_h, RGBA16F_BYTES);
add_texture(Scene_composite_texture, scene_w, scene_h, RGBA16F_BYTES);
add_texture(Scene_luminance_texture, scene_w, scene_h, RGBA16F_BYTES);
add_texture(Cockpit_depth_texture, scene_w, scene_h, DEPTH24_STENCIL8_BYTES);
add_texture(Scene_depth_texture, scene_w, scene_h, DEPTH24_STENCIL8_BYTES);
// The multisample textures hold one value for each sample, thus their bytes go up with the
// MSAA level. Cmdline_msaa_enabled holds the number of samples and is 0 when MSAA is off.
const auto samples = static_cast<size_t>(std::max(1, Cmdline_msaa_enabled));
add_texture(Scene_color_texture_ms, scene_w, scene_h, RGBA16F_BYTES, samples);
add_texture(Scene_position_texture_ms, scene_w, scene_h, RGBA16F_BYTES, samples);
add_texture(Scene_normal_texture_ms, scene_w, scene_h, RGBA16F_BYTES, samples);
add_texture(Scene_specular_texture_ms, scene_w, scene_h, RGBA8_BYTES, samples);
add_texture(Scene_emissive_texture_ms, scene_w, scene_h, RGBA16F_BYTES, samples);
add_texture(Scene_depth_texture_ms, scene_w, scene_h, DEPTH24_STENCIL8_BYTES, samples);
// The back buffer and gamma blit textures use the size of the screen, not the (clamped) size
// of the scene textures.
const auto screen_w = static_cast<size_t>(gr_screen.max_w);
const auto screen_h = static_cast<size_t>(gr_screen.max_h);
add_texture(Back_texture, screen_w, screen_h, RGBA16F_BYTES);
add_texture(Back_depth_texture, screen_w, screen_h, DEPTH24_STENCIL8_BYTES);
add_texture(GammaBlit_texture, screen_w, screen_h, RGBA8_BYTES);
// The two distortion textures always have the size 32x32.
add_texture(Distortion_texture[0], 32, 32, RGBA8_BYTES);
add_texture(Distortion_texture[1], 32, 32, RGBA8_BYTES);
return bytes;
}
void gr_opengl_scene_texture_begin()
{
if ( !Scene_texture_initialized ) {
return;
}
if ( Scene_framebuffer_in_frame ) {
return;
}
GR_DEBUG_SCOPE("Begin scene texture");
TRACE_SCOPE(tracing::SceneTextureBegin);
GL_state.PushFramebufferState();
GL_state.BindFrameBuffer(Scene_framebuffer);
// The cockpit depth mask is only written when the cockpit is actually rendered (see
// gr_opengl_post_process_save_zbuffer), and the views that skip it -- external, chase,
// topdown, warp chase -- would otherwise leave the lightshaft pass masking against the
// silhouette left over from the last cockpit-view frame. Clear it every frame; when a
// cockpit does render, save_zbuffer clears it again anyway.
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, Cockpit_depth_texture, 0);
GL_state.SetZbufferType(ZBUFFER_TYPE_FULL);
glClear(GL_DEPTH_BUFFER_BIT);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, Scene_depth_texture, 0);
if (GL_rendering_to_texture)
{
Scene_texture_u_scale = i2fl(gr_screen.max_w) / i2fl(Scene_texture_width);
Scene_texture_v_scale = i2fl(gr_screen.max_h) / i2fl(Scene_texture_height);
CLAMP(Scene_texture_u_scale, 0.0f, 1.0f);
CLAMP(Scene_texture_v_scale, 0.0f, 1.0f);
}
else
{
Scene_texture_u_scale = 1.0f;
Scene_texture_v_scale = 1.0f;
}
// A ratio above 1.0 means the viewport outgrew the allocation after the fact -- the scene
// texture can't be resized, so all we can do is render the part that fits and let the blit
// stretch it back out, which misaligns everything by a different amount on each axis. The
// floor above is meant to make this unreachable; if it does happen (a display hotplug or
// resolution change after gr_init(), or an allocation clamped by GL_max_renderbuffer_size on
// low-end hardware) say so once rather than every frame.
if (Scene_texture_u_scale > 1.0f || Scene_texture_v_scale > 1.0f) {
static bool reported_undersized_scene_texture = false;
if (!reported_undersized_scene_texture) {
reported_undersized_scene_texture = true;
nprintf(("OpenGL",
"Viewport (%dx%d) is larger than the scene texture backing it (%dx%d); "
"the post-processed image will be stretched to fit.\n",
gr_screen.max_w,
gr_screen.max_h,
Scene_texture_width,
Scene_texture_height));
}
}
CLAMP(Scene_texture_u_scale, 0.0f, 1.0f);
CLAMP(Scene_texture_v_scale, 0.0f, 1.0f);
if (!light_deferred_enabled()) {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
} else {
GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 };
glDrawBuffers(6, buffers);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
opengl_clear_deferred_buffers();
if (Cmdline_msaa_enabled > 0) {
GL_state.BindFrameBuffer(Scene_framebuffer_ms);
glDrawBuffers(6, buffers);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
opengl_clear_deferred_buffers();
GL_state.BindFrameBuffer(Scene_framebuffer);
}
glDrawBuffer(GL_COLOR_ATTACHMENT0);
}
Scene_framebuffer_in_frame = true;
if ( Gr_post_processing_enabled && !PostProcessing_override ) {
High_dynamic_range = true;
}
}
float time_buffer = 0.0f;
void gr_opengl_scene_texture_end()
{
if ( !Scene_framebuffer_in_frame ) {
return;
}
GR_DEBUG_SCOPE("End scene texture");
TRACE_SCOPE(tracing::SceneTextureEnd);
time_buffer+=flFrametime;
if(time_buffer>0.03f)
{
gr_opengl_update_distortion();
time_buffer = 0.0f;
}
if ( Gr_post_processing_enabled && !PostProcessing_override ) {
gr_opengl_post_process_end();
} else {
GR_DEBUG_SCOPE("Draw scene texture");
TRACE_SCOPE(tracing::DrawSceneTexture);
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.PopFramebufferState();
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_color_texture);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
opengl_shader_set_passthrough(true, High_dynamic_range);
GL_state.Array.BindArrayBuffer(0);
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);
}
// reset state
GL_state.DepthTest(depth);
GL_state.DepthMask(depth_mask);
GL_state.Blend(blend);
GL_state.CullFace(cull);
}
// Reset the UV scale values
Scene_texture_u_scale = 1.0f;
Scene_texture_v_scale = 1.0f;
Scene_framebuffer_in_frame = false;
High_dynamic_range = false;
}
void gr_opengl_copy_effect_texture()
{
GR_DEBUG_SCOPE("Copy effect texture");
if ( !Scene_framebuffer_in_frame ) {
return;
}
//Make sure we're reading from the up-to-date color texture
glReadBuffer(GL_COLOR_ATTACHMENT0);
glDrawBuffer(GL_COLOR_ATTACHMENT5);
glBlitFramebuffer(0, 0, gr_screen.max_w, gr_screen.max_h, 0, 0, gr_screen.max_w, gr_screen.max_h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
}
void gr_opengl_render_shield_impact(shield_material* material_info,
primitive_type prim_type,
vertex_layout* layout,
gr_buffer_handle buffer_handle,
int n_verts)
{
matrix4 impact_transform;
matrix4 impact_projection;
vec3d min;
vec3d max;
opengl_tnl_set_material(material_info, false);
float radius = material_info->get_impact_radius();
min.xyz.x = min.xyz.y = min.xyz.z = -radius;
max.xyz.x = max.xyz.y = max.xyz.z = radius;
vm_matrix4_set_orthographic(&impact_projection, &max, &min);
matrix impact_orient = material_info->get_impact_orient();
vec3d impact_pos = material_info->get_impact_pos();
vm_matrix4_set_inverse_transform(&impact_transform, &impact_orient, &impact_pos);
uint32_t array_index = 0;
if (material_info->get_texture_map(TM_BASE_TYPE) >= 0) {
float u_scale, v_scale;
if (!gr_opengl_tcache_set(material_info->get_texture_map(TM_BASE_TYPE), material_info->get_texture_type(),
&u_scale, &v_scale, &array_index)) {
mprintf(("WARNING: Error setting bitmap texture (%i)!\n", material_info->get_texture_map(TM_BASE_TYPE)));
}
}
opengl_set_generic_uniform_data<graphics::generic_data::shield_impact_data>(
[&](graphics::generic_data::shield_impact_data* data) {
data->hitNormal = impact_orient.vec.fvec;
data->shieldProjMatrix = impact_projection;
data->shieldModelViewMatrix = impact_transform;
data->shieldMapIndex = array_index;
data->srgb = High_dynamic_range ? 1 : 0;
data->color = material_info->get_color();
});
gr_matrix_set_uniforms();
opengl_render_primitives(prim_type, layout, n_verts, buffer_handle, 0, 0);
}
void gr_opengl_update_distortion()
{
if (Distortion_framebuffer == 0) {
// distortion is disabled
return;
}
GR_DEBUG_SCOPE("Update distortion");
TRACE_SCOPE(tracing::UpdateDistortion);
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);
opengl_shader_set_passthrough(true, High_dynamic_range);
GL_state.PushFramebufferState();
GL_state.BindFrameBuffer(Distortion_framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Distortion_texture[!Distortion_switch], 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glViewport(0,0,32,32);
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Distortion_texture[Distortion_switch]);
glClearColor(0.5f, 0.5f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
vertex vertices[4];
vertices[0].texture_position.u = 0.0f;
vertices[0].texture_position.v = 0.0f;
vertices[1].texture_position.u = 0.96875f;
vertices[1].texture_position.v = 0.0f;
vertices[2].texture_position.u = 0.0f;
vertices[2].texture_position.v = 1.0f;
vertices[3].texture_position.u = 0.96875f;
vertices[3].texture_position.v = 1.0f;
vertices[0].screen.xyw.x = 0.03f*(float)gr_screen.max_w;
vertices[0].screen.xyw.y = (float)gr_screen.max_h;
vertices[1].screen.xyw.x = (float)gr_screen.max_w;
vertices[1].screen.xyw.y = (float)gr_screen.max_h;
vertices[2].screen.xyw.x = 0.03f*(float)gr_screen.max_w;
vertices[2].screen.xyw.y = 0.0f;
vertices[3].screen.xyw.x = (float)gr_screen.max_w;
vertices[3].screen.xyw.y = 0.0f;
vertex_layout vert_def;
vert_def.add_vertex_component(vertex_format_data::POSITION2, sizeof(vertex), offsetof(vertex, screen));
vert_def.add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(vertex), offsetof(vertex, texture_position));
opengl_render_primitives_immediate(PRIM_TYPE_TRISTRIP, &vert_def, 4, vertices, sizeof(vertex) * 4);
opengl_shader_set_passthrough(false, High_dynamic_range);
vertex distortion_verts[33];
for(int i = 0; i < 33; i++)
{
distortion_verts[i].r = (ubyte)Random::next(256);
distortion_verts[i].g = (ubyte)Random::next(256);
distortion_verts[i].b = 255;
distortion_verts[i].a = 255;
distortion_verts[i].screen.xyw.x = 1.f;
distortion_verts[i].screen.xyw.y = (float)gr_screen.max_h*0.03125f*i;
}
vert_def = vertex_layout();
vert_def.add_vertex_component(vertex_format_data::POSITION2, sizeof(vertex), offsetof(vertex, screen));
vert_def.add_vertex_component(vertex_format_data::COLOR4, sizeof(vertex), offsetof(vertex, r));
opengl_render_primitives_immediate(PRIM_TYPE_POINTS, &vert_def, 33, distortion_verts, 33 * sizeof(vertex));
Distortion_switch = !Distortion_switch;
// reset state
GL_state.PopFramebufferState();
glViewport(0,0,gr_screen.max_w,gr_screen.max_h);
GL_state.DepthTest(depth);
GL_state.DepthMask(depth_mask);
GL_state.Blend(blend);
GL_state.CullFace(cull);
}
void opengl_render_primitives(primitive_type prim_type,
vertex_layout* layout,
int n_verts,
gr_buffer_handle buffer_handle,
size_t vert_offset,
size_t byte_offset)
{
GR_DEBUG_SCOPE("Render primitives");
Assertion(buffer_handle.isValid(),
"A valid buffer handle is required! Use the immediate buffer if data is not in GPU buffer yet.");
opengl_bind_vertex_layout(*layout, opengl_buffer_get_id(GL_ARRAY_BUFFER, buffer_handle), 0, byte_offset);
glDrawArrays(opengl_primitive_type(prim_type), (GLint)vert_offset, n_verts);
}
void opengl_render_primitives_immediate(primitive_type prim_type, vertex_layout* layout, int n_verts, void* data, int size)
{
auto offset = gr_add_to_immediate_buffer(size, data);
opengl_render_primitives(prim_type, layout, n_verts, gr_immediate_buffer_handle, 0, offset);
}
void gr_opengl_render_primitives(material* material_info,
primitive_type prim_type,
vertex_layout* layout,
int offset,
int n_verts,
gr_buffer_handle buffer_handle,
size_t buffer_offset)
{
GL_CHECK_FOR_ERRORS("start of gr_opengl_render_primitives()");
opengl_tnl_set_material(material_info, true);
opengl_render_primitives(prim_type, layout, n_verts, buffer_handle, offset, buffer_offset);
GL_CHECK_FOR_ERRORS("end of gr_opengl_render_primitives()");
}
void gr_opengl_render_primitives_particle(particle_material* material_info,
primitive_type prim_type,
vertex_layout* layout,
int offset,
int n_verts,
gr_buffer_handle buffer_handle)
{
GL_CHECK_FOR_ERRORS("start of gr_opengl_render_primitives_particle()");
opengl_tnl_set_material_particle(material_info);
opengl_render_primitives(prim_type, layout, n_verts, buffer_handle, offset, 0);
GL_CHECK_FOR_ERRORS("end of gr_opengl_render_primitives_particle()");
}
void gr_opengl_render_primitives_distortion(distortion_material* material_info,
primitive_type prim_type,
vertex_layout* layout,
int offset,
int n_verts,
gr_buffer_handle buffer_handle)
{
GL_CHECK_FOR_ERRORS("start of gr_opengl_render_primitives_distortion()");
opengl_tnl_set_material_distortion(material_info);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
opengl_render_primitives(prim_type, layout, n_verts, buffer_handle, offset, 0);
GLenum buffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
glDrawBuffers(2, buffers);
GL_CHECK_FOR_ERRORS("start of gr_opengl_render_primitives_distortion()");
}
void gr_opengl_render_movie(movie_material* material_info,
primitive_type prim_type,
vertex_layout* layout,
int n_verts,
gr_buffer_handle buffer,
size_t buffer_offset)
{
GR_DEBUG_SCOPE("Render movie frame");
gr_set_2d_matrix();
opengl_tnl_set_material_movie(material_info);
opengl_render_primitives(prim_type, layout, n_verts, buffer, 0, buffer_offset);
gr_end_2d_matrix();
}
void gr_opengl_render_nanovg(nanovg_material* material_info,
primitive_type prim_type,
vertex_layout* layout,
int offset,
int n_verts,
gr_buffer_handle buffer_handle)
{
GR_DEBUG_SCOPE("Render NanoVG primitives");
opengl_tnl_set_material_nanovg(material_info);
opengl_render_primitives(prim_type, layout, n_verts, buffer_handle, offset, 0);
}
void gr_opengl_render_primitives_batched(batched_bitmap_material* material_info,
primitive_type prim_type,
vertex_layout* layout,
int offset,
int n_verts,
gr_buffer_handle buffer_handle)
{
GR_DEBUG_SCOPE("Render batched primitives");
opengl_tnl_set_material_batched(material_info);
opengl_render_primitives(prim_type, layout, n_verts, buffer_handle, offset, 0);
}
void gr_opengl_render_rocket_primitives(interface_material* material_info,
primitive_type prim_type,
vertex_layout* layout,
int n_indices,
gr_buffer_handle vertex_buffer,
gr_buffer_handle index_buffer)
{
GR_DEBUG_SCOPE("Render rocket ui primitives");
gr_set_2d_matrix();
opengl_tnl_set_rocketui_material(material_info);
opengl_bind_vertex_layout(*layout,
opengl_buffer_get_id(GL_ARRAY_BUFFER, vertex_buffer),
opengl_buffer_get_id(GL_ELEMENT_ARRAY_BUFFER, index_buffer));
glDrawElements(opengl_primitive_type(prim_type), n_indices, GL_UNSIGNED_INT, nullptr);
gr_end_2d_matrix();
}
void opengl_draw_textured_quad(GLfloat x1,
GLfloat y1,
GLfloat u1,
GLfloat v1,
GLfloat x2,
GLfloat y2,
GLfloat u2,
GLfloat v2) {
GR_DEBUG_SCOPE("Draw textured quad");
GLfloat glVertices[4][4] = {
{ x1, y1, u1, v1 },
{ x1, y2, u1, v2 },
{ x2, y1, u2, v1 },
{ x2, y2, u2, v2 }
};
vertex_layout vert_def;
vert_def.add_vertex_component(vertex_format_data::POSITION2, sizeof(GLfloat) * 4, 0);
vert_def.add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(GLfloat) * 4, sizeof(GLfloat) * 2);
opengl_render_primitives_immediate(PRIM_TYPE_TRISTRIP, &vert_def, 4, glVertices, sizeof(glVertices));
}
void opengl_draw_full_screen_textured(GLfloat u1, GLfloat v1, GLfloat u2, GLfloat v2)
{
GR_DEBUG_SCOPE("Draw full screen triangle");
GLfloat glVertices[3][4] = {
{-1.f, -1.f, u1, v1},
{3.f, -1.f, u2 * 2.f, v1},
{-1.f, 3.f, u1, v2 * 2.f},
};
vertex_layout vert_def;
vert_def.add_vertex_component(vertex_format_data::POSITION2, sizeof(GLfloat) * 4, 0);
vert_def.add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(GLfloat) * 4, sizeof(GLfloat) * 2);
opengl_render_primitives_immediate(PRIM_TYPE_TRIS, &vert_def, 3, glVertices, sizeof(glVertices));
}
void opengl_draw_full_screen_scene_texture()
{
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_v_scale);
}
void gr_opengl_render_decals(decal_material* material_info,
primitive_type prim_type,
vertex_layout* layout,
int num_elements,
const indexed_vertex_source& binding,
const gr_buffer_handle& instance_buffer,
int num_instances) {
opengl_tnl_set_material_decal(material_info);
opengl_bind_vertex_layout_multiple(*layout,
SCP_vector<GLuint> { opengl_buffer_get_id(GL_ARRAY_BUFFER, binding.Vbuffer_handle), opengl_buffer_get_id(GL_ARRAY_BUFFER, instance_buffer) },
opengl_buffer_get_id(GL_ELEMENT_ARRAY_BUFFER, binding.Ibuffer_handle));
glDrawElementsInstanced(opengl_primitive_type(prim_type), num_elements, GL_UNSIGNED_INT, nullptr, num_instances);
}
void gr_opengl_start_decal_pass() {
// For now we only render into the diffuse channel of the framebuffer
#ifndef USE_OPENGL_ES
GLenum buffers[] = {
GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT4,
};
glDrawBuffers(3, buffers);
#else
GLenum buffers[] = {
GL_COLOR_ATTACHMENT0,
GL_NONE,
GL_COLOR_ATTACHMENT2,
GL_NONE,
GL_COLOR_ATTACHMENT4,
};
glDrawBuffers(5, buffers);
#endif
}
void gr_opengl_stop_decal_pass() {
GLenum buffers2[] = {
GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT1,
GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3,
GL_COLOR_ATTACHMENT4,
GL_COLOR_ATTACHMENT5,
};
glDrawBuffers(6, buffers2);
}
void gr_opengl_calculate_irrmap()
{
auto previous_target = gr_screen.rendering_to_texture;
int irr_shader = gr_opengl_maybe_create_shader(SDR_TYPE_IRRADIANCE_MAP_GEN, 0);
opengl_shader_set_current(irr_shader);
auto env_tex = bm_get_gr_info<tcache_slot_opengl>(ENVMAP);
glBindTexture(env_tex->texture_target, env_tex->texture_id);
GL_state.Texture.Enable(0, GL_TEXTURE_CUBE_MAP, env_tex->texture_id);
Current_shader->program->Uniforms.setTextureUniform("envmap", 0);
for (int i = 0; i < 6; i++) {
bm_set_render_target(gr_screen.irrmap_render_target, i);
gr_clear();
opengl_set_generic_uniform_data<graphics::generic_data::irrmap_data>(
[&](graphics::generic_data::irrmap_data* data) { data->face = i; });
opengl_draw_full_screen_textured(-1.0f, -1.0f, 1.0f, 1.0f);
}
bm_set_render_target(previous_target);
}