mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 22:56:30 -10:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76286c5332 | ||
|
|
e4546de24e | ||
|
|
336f198594 | ||
|
|
5c7059b451 | ||
|
|
4c0ff8414d | ||
|
|
019c4bacea | ||
|
|
e7d58d85cb | ||
|
|
84fa0a622f |
No files matched your search
@@ -11,6 +11,7 @@ void main() {
|
||||
}
|
||||
#else
|
||||
in vec4 vertPosition;
|
||||
in vec4 vertTexCoord;
|
||||
|
||||
out vec2 v_rcpFrame;
|
||||
#ifdef GL_ES
|
||||
@@ -27,6 +28,8 @@ layout (std140) uniform genericData {
|
||||
void main() {
|
||||
gl_Position = vertPosition;
|
||||
v_rcpFrame = vec2(1.0/rt_w, 1.0/rt_h);
|
||||
v_pos = vertPosition.xy*0.5 + 0.5;
|
||||
// Use the real texcoord rather than deriving it from vertPosition: the draw call may ask for a
|
||||
// sub-rectangle of the source texture, which the clip-space formula ignored. Matches post-v.sdr.
|
||||
v_pos = vertTexCoord.xy;
|
||||
}
|
||||
#endif
|
||||
@@ -927,6 +927,29 @@ bool gr_is_smaa_mode(AntiAliasMode mode) {
|
||||
return mode == AntiAliasMode::SMAA_Low || mode == AntiAliasMode::SMAA_Medium || mode == AntiAliasMode::SMAA_High || mode == AntiAliasMode::SMAA_Ultra;
|
||||
}
|
||||
|
||||
SCP_vector<float> gr_get_supported_anisotropy_levels()
|
||||
{
|
||||
float max;
|
||||
if (!gr_get_property(gr_property::MAX_ANISOTROPY, &max)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (max <= 2.0f) {
|
||||
return {};
|
||||
}
|
||||
|
||||
SCP_vector<float> out;
|
||||
|
||||
// We assume here that the anisotropy levels are powers of two...
|
||||
float current = 1.0f;
|
||||
while (current <= max) {
|
||||
out.push_back(current);
|
||||
current *= 2.0f;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void parse_post_processing_func()
|
||||
{
|
||||
bool value;
|
||||
@@ -1710,6 +1733,13 @@ void gr_screen_resize(int width, int height)
|
||||
gr_screen.save_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;
|
||||
|
||||
gr_setup_viewport();
|
||||
|
||||
// The offscreen targets that back the scene/post-processing pipeline were sized for the old
|
||||
// gr_screen; give the backend a chance to grow them before anything renders at the new size.
|
||||
// Backends that already handle this elsewhere (Vulkan, via recreateSwapChain()) leave it unset.
|
||||
if (gr_screen.gf_resize_render_targets) {
|
||||
gr_screen.gf_resize_render_targets();
|
||||
}
|
||||
}
|
||||
|
||||
void gr_window_to_render_pos(float& x, float& y)
|
||||
|
||||
@@ -917,6 +917,12 @@ typedef struct screen {
|
||||
std::function<void()> gf_scene_texture_end;
|
||||
std::function<void()> gf_copy_effect_texture;
|
||||
|
||||
// Grow the offscreen render targets that back the scene/post-processing pipeline to cover the
|
||||
// current gr_screen, if they don't already. Called from gr_screen_resize(). Optional: the
|
||||
// Vulkan backend leaves this unset because VulkanRenderer::recreateSwapChain() already owns
|
||||
// resizing its extent-sized targets.
|
||||
std::function<void()> gf_resize_render_targets;
|
||||
|
||||
std::function<void(int zbias)> gf_zbias;
|
||||
|
||||
std::function<void(int)> gf_set_fill_mode;
|
||||
@@ -1502,6 +1508,12 @@ inline bool gr_get_property(gr_property property, void* destination)
|
||||
return gr_screen.gf_get_property(property, destination);
|
||||
}
|
||||
|
||||
// Anisotropic filtering levels the current hardware supports: 1.0 (off), then powers of two up to
|
||||
// the reported maximum. Empty if anisotropy is unavailable or the hardware caps out below 4x, in
|
||||
// which case there is nothing meaningful to offer. Backs both the in-game option's enumerator and
|
||||
// qtFRED's Preferences combo, so the two can't drift.
|
||||
SCP_vector<float> gr_get_supported_anisotropy_levels();
|
||||
|
||||
inline void gr_push_debug_group(const char* name)
|
||||
{
|
||||
gr_screen.gf_push_debug_group(name);
|
||||
|
||||
@@ -1126,6 +1126,7 @@ void gr_opengl_init_function_pointers()
|
||||
gr_screen.gf_scene_texture_begin = gr_opengl_scene_texture_begin;
|
||||
gr_screen.gf_scene_texture_end = gr_opengl_scene_texture_end;
|
||||
gr_screen.gf_copy_effect_texture = gr_opengl_copy_effect_texture;
|
||||
gr_screen.gf_resize_render_targets = gr_opengl_resize_render_targets;
|
||||
|
||||
gr_screen.gf_deferred_lighting_begin = gr_opengl_deferred_lighting_begin;
|
||||
gr_screen.gf_deferred_lighting_msaa = gr_opengl_deferred_lighting_msaa;
|
||||
@@ -1516,7 +1517,7 @@ bool gr_opengl_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps)
|
||||
opengl_shader_init();
|
||||
|
||||
// post processing effects, after shaders are initialized
|
||||
opengl_setup_scene_textures();
|
||||
opengl_setup_scene_textures(gr_screen.max_w, gr_screen.max_h);
|
||||
opengl_post_process_init();
|
||||
|
||||
// must be called after extensions are setup
|
||||
|
||||
@@ -86,7 +86,7 @@ void gr_opengl_deferred_lighting_begin(bool clearNonColorBufs)
|
||||
Current_shader->program->Uniforms.setTextureUniform("tex", 0);
|
||||
GL_state.SetAlphaBlendMode(gr_alpha_blend::ALPHA_BLEND_NONE);
|
||||
GL_state.SetZbufferType(ZBUFFER_TYPE_NONE);
|
||||
opengl_draw_full_screen_textured(0, 0, 1, 1);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
} else {
|
||||
// Copy the existing color data into the emissive part of the G-buffer since everything that already existed is
|
||||
// treated as emissive
|
||||
@@ -159,7 +159,9 @@ void gr_opengl_deferred_lighting_msaa()
|
||||
});
|
||||
GL_state.SetAlphaBlendMode(gr_alpha_blend::ALPHA_BLEND_NONE);
|
||||
GL_state.SetZbufferType(ZBUFFER_TYPE_WRITE);
|
||||
opengl_draw_full_screen_textured(0, 0, 1, 1);
|
||||
// msaa-f.sdr resolves via ivec2(textureSize(texColor) * fragTexCoord), so the texcoords have to
|
||||
// stay inside the rendered sub-rectangle of the multisampled G-buffer.
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
void gr_opengl_deferred_lighting_end()
|
||||
@@ -328,8 +330,12 @@ void gr_opengl_deferred_lighting_finish()
|
||||
shadow_cascade_params_bind_deferred();
|
||||
}
|
||||
|
||||
header->invScreenWidth = 1.0f / gr_screen.max_w;
|
||||
header->invScreenHeight = 1.0f / gr_screen.max_h;
|
||||
// deferred-f.sdr turns gl_FragCoord into a G-buffer texture coordinate with these, so they
|
||||
// have to normalize against the G-buffer's own dimensions. Those only equal gr_screen while
|
||||
// the viewport exactly fills the scene textures -- not after a shrink, and not when the
|
||||
// allocation was clamped by GL_max_renderbuffer_size.
|
||||
header->invScreenWidth = 1.0f / Scene_texture_width;
|
||||
header->invScreenHeight = 1.0f / Scene_texture_height;
|
||||
header->nearPlane = gr_near_plane;
|
||||
|
||||
{
|
||||
@@ -564,7 +570,8 @@ void gr_opengl_deferred_lighting_finish()
|
||||
data->clip_dist = Neb2_fog_clip_distance;
|
||||
});
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
// fog-f.sdr samples the composite and depth targets straight off fragTexCoord.
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
if (bDrawNebVolumetrics) {
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
@@ -660,6 +667,12 @@ void gr_opengl_deferred_lighting_finish()
|
||||
|
||||
{
|
||||
GR_DEBUG_SCOPE("Volumetric Nebulae Draw");
|
||||
// Deliberately unscaled. volumetric-f.sdr uses fragTexCoord for two incompatible
|
||||
// things: reconstructing an eye-space ray direction, which needs the full 0..1 range
|
||||
// across the viewport, and sampling composite/depth/emissive, which needs the
|
||||
// rendered sub-rectangle. Scaling here would fix the sampling and skew every ray.
|
||||
// Separating the two needs a second varying (or a scale uniform) in the shader; until
|
||||
// then volumetrics are only correct while the targets exactly match the viewport.
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
}
|
||||
GL_state.Texture.Enable(Scene_emissive_texture);
|
||||
|
||||
@@ -70,6 +70,33 @@ 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 ) {
|
||||
@@ -98,7 +125,7 @@ void gr_opengl_sphere(material* material_def, float /*rad*/)
|
||||
}
|
||||
|
||||
extern int opengl_check_framebuffer();
|
||||
void opengl_setup_scene_textures()
|
||||
void opengl_setup_scene_textures(int width, int height)
|
||||
{
|
||||
Scene_texture_initialized = 0;
|
||||
|
||||
@@ -113,10 +140,10 @@ void opengl_setup_scene_textures()
|
||||
return;
|
||||
}
|
||||
|
||||
// clamp size, if needed
|
||||
Scene_texture_width = gr_screen.max_w;
|
||||
Scene_texture_height = gr_screen.max_h;
|
||||
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;
|
||||
}
|
||||
@@ -125,6 +152,13 @@ void opengl_setup_scene_textures()
|
||||
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);
|
||||
@@ -333,32 +367,15 @@ void opengl_setup_scene_textures()
|
||||
|
||||
if ( opengl_check_framebuffer() ) {
|
||||
GL_state.BindFrameBuffer(0);
|
||||
glDeleteFramebuffers(1, &Scene_framebuffer);
|
||||
Scene_framebuffer = 0;
|
||||
opengl_delete_render_framebuffer(Scene_framebuffer);
|
||||
|
||||
glDeleteTextures(1, &Scene_color_texture);
|
||||
Scene_color_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_position_texture);
|
||||
Scene_position_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_normal_texture);
|
||||
Scene_normal_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_specular_texture);
|
||||
Scene_specular_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_emissive_texture);
|
||||
Scene_emissive_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_depth_texture);
|
||||
Scene_depth_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_luminance_texture);
|
||||
Scene_luminance_texture = 0;
|
||||
|
||||
//glDeleteTextures(1, &Scene_fxaa_output_texture);
|
||||
//Scene_fxaa_output_texture = 0;
|
||||
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;
|
||||
@@ -687,79 +704,105 @@ void opengl_scene_texture_shutdown()
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Scene_color_texture ) {
|
||||
glDeleteTextures(1, &Scene_color_texture);
|
||||
Scene_color_texture = 0;
|
||||
}
|
||||
// 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);
|
||||
|
||||
if ( Scene_position_texture ) {
|
||||
glDeleteTextures(1, &Scene_position_texture);
|
||||
Scene_position_texture = 0;
|
||||
}
|
||||
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);
|
||||
|
||||
if ( Scene_normal_texture ) {
|
||||
glDeleteTextures(1, &Scene_normal_texture);
|
||||
Scene_normal_texture = 0;
|
||||
}
|
||||
opengl_delete_render_texture(Back_texture);
|
||||
opengl_delete_render_texture(Back_depth_texture);
|
||||
opengl_delete_render_framebuffer(Back_framebuffer);
|
||||
|
||||
if ( Scene_specular_texture ) {
|
||||
glDeleteTextures(1, &Scene_specular_texture);
|
||||
Scene_specular_texture = 0;
|
||||
}
|
||||
opengl_delete_render_texture(GammaBlit_texture);
|
||||
opengl_delete_render_framebuffer(GammaBlit_framebuffer);
|
||||
|
||||
if (Scene_emissive_texture) {
|
||||
glDeleteTextures(1, &Scene_emissive_texture);
|
||||
Scene_emissive_texture = 0;
|
||||
}
|
||||
|
||||
if ( Scene_depth_texture ) {
|
||||
glDeleteTextures(1, &Scene_depth_texture);
|
||||
Scene_depth_texture = 0;
|
||||
}
|
||||
|
||||
if ( Scene_framebuffer ) {
|
||||
glDeleteFramebuffers(1, &Scene_framebuffer);
|
||||
Scene_framebuffer = 0;
|
||||
}
|
||||
|
||||
if (Back_texture) {
|
||||
glDeleteTextures(1, &Back_texture);
|
||||
Back_texture = 0;
|
||||
}
|
||||
|
||||
if (Back_depth_texture) {
|
||||
glDeleteTextures(1, &Back_depth_texture);
|
||||
Back_depth_texture = 0;
|
||||
}
|
||||
|
||||
if (Back_framebuffer) {
|
||||
glDeleteFramebuffers(1, &Back_framebuffer);
|
||||
Back_framebuffer = 0;
|
||||
}
|
||||
|
||||
if (GammaBlit_texture) {
|
||||
glDeleteTextures(1, &GammaBlit_texture);
|
||||
GammaBlit_texture = 0;
|
||||
}
|
||||
|
||||
if (GammaBlit_framebuffer) {
|
||||
glDeleteFramebuffers(1, &GammaBlit_framebuffer);
|
||||
GammaBlit_framebuffer = 0;
|
||||
}
|
||||
|
||||
glDeleteTextures(2, Distortion_texture);
|
||||
Distortion_texture[0] = 0;
|
||||
Distortion_texture[1] = 0;
|
||||
|
||||
if ( Distortion_framebuffer ) {
|
||||
glDeleteFramebuffers(1, &Distortion_framebuffer);
|
||||
Distortion_framebuffer = 0;
|
||||
}
|
||||
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()
|
||||
@@ -860,6 +903,30 @@ void gr_opengl_scene_texture_begin()
|
||||
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);
|
||||
@@ -1306,6 +1373,11 @@ void opengl_draw_full_screen_textured(GLfloat u1, GLfloat v1, GLfloat u2, GLfloa
|
||||
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,
|
||||
|
||||
@@ -55,8 +55,14 @@ void gr_opengl_render_shield_impact(shield_material* material_info,
|
||||
gr_buffer_handle buffer_handle,
|
||||
int n_verts);
|
||||
|
||||
void opengl_setup_scene_textures();
|
||||
void opengl_setup_scene_textures(int width, int height);
|
||||
void opengl_scene_texture_shutdown();
|
||||
void gr_opengl_resize_render_targets();
|
||||
|
||||
// Release a render target, keeping the GL state cache in sync. See the definitions for why the
|
||||
// cache matters now that these are rebuilt mid-session.
|
||||
void opengl_delete_render_texture(GLuint& tex);
|
||||
void opengl_delete_render_framebuffer(GLuint& fbo);
|
||||
|
||||
/**
|
||||
* @brief Gives the bytes that the scene, backbuffer, and distortion textures hold
|
||||
@@ -156,6 +162,12 @@ void opengl_draw_textured_quad(GLfloat x1,
|
||||
*/
|
||||
void opengl_draw_full_screen_textured(GLfloat u1, GLfloat v1, GLfloat u2, GLfloat v2);
|
||||
|
||||
// Fullscreen pass over a source that is one of the scene/post-processing textures. Those are only
|
||||
// filled out to Scene_texture_u_scale/v_scale of their allocation, so sampling them over the full
|
||||
// [0,1] range would pull in whatever is beyond the rendered region. Prefer this over passing
|
||||
// literal 1.0f extents whenever the bound texture came from that pipeline.
|
||||
void opengl_draw_full_screen_scene_texture();
|
||||
|
||||
inline GLenum opengl_primitive_type(primitive_type prim_type);
|
||||
|
||||
void gr_opengl_start_decal_pass();
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
#include "es_compatibility.h"
|
||||
#endif
|
||||
|
||||
static void opengl_post_setup_render_targets();
|
||||
static void opengl_post_shutdown_render_targets();
|
||||
|
||||
extern bool PostProcessing_override;
|
||||
extern int opengl_check_framebuffer();
|
||||
// Needed to track where the FXAA shaders are
|
||||
@@ -99,7 +102,7 @@ void opengl_post_pass_tonemap()
|
||||
|
||||
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);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
void opengl_post_pass_bloom()
|
||||
@@ -134,7 +137,10 @@ void opengl_post_pass_bloom()
|
||||
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_color_texture);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
// Reads the scene texture directly rather than an already-cropped intermediate, so it is
|
||||
// the scaled variant. The blur/composite passes below read Bloom_textures, which this pass
|
||||
// fills edge to edge, so those stay unscaled.
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
// ------ end bright pass ------
|
||||
|
||||
@@ -293,7 +299,7 @@ void opengl_post_pass_fxaa()
|
||||
|
||||
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);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
// set and configure post shader ..
|
||||
opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_FXAA, 0));
|
||||
@@ -310,7 +316,7 @@ void opengl_post_pass_fxaa()
|
||||
|
||||
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_draw_full_screen_scene_texture();
|
||||
|
||||
opengl_shader_set_current();
|
||||
}
|
||||
@@ -333,7 +339,7 @@ static void smaa_detect_edges()
|
||||
|
||||
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);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
static void smaa_calculate_blending_weights()
|
||||
@@ -358,7 +364,7 @@ static void smaa_calculate_blending_weights()
|
||||
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);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
static void smaa_neighborhood_blending()
|
||||
@@ -381,7 +387,7 @@ static void smaa_neighborhood_blending()
|
||||
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);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
void smaa_resolve()
|
||||
@@ -491,7 +497,7 @@ void opengl_post_lightshafts()
|
||||
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);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
GL_state.Blend(GL_FALSE);
|
||||
break;
|
||||
@@ -625,7 +631,7 @@ void gr_opengl_post_process_end()
|
||||
// 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);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
//Shadow Map debug window
|
||||
//#define SHADOW_DEBUG
|
||||
@@ -1020,77 +1026,93 @@ static GLuint load_smaa_texture(GLsizei width, GLsizei height, GLenum format, co
|
||||
return tex;
|
||||
}
|
||||
|
||||
static void setup_smaa_resources()
|
||||
// The SMAA area and search textures are fixed-size lookup tables baked into the binary, so unlike
|
||||
// everything else here they survive a resolution change untouched.
|
||||
static void setup_smaa_lookup_textures()
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
static void setup_smaa_render_targets()
|
||||
{
|
||||
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()
|
||||
static void shutdown_smaa_render_targets()
|
||||
{
|
||||
bool rval = false;
|
||||
opengl_delete_render_texture(Smaa_edges_tex);
|
||||
opengl_delete_render_framebuffer(Smaa_edge_detection_fb);
|
||||
|
||||
// clamp size, if needed
|
||||
Post_texture_width = gr_screen.max_w;
|
||||
Post_texture_height = gr_screen.max_h;
|
||||
opengl_delete_render_texture(Smaa_blend_tex);
|
||||
opengl_delete_render_framebuffer(Smaa_blending_weight_fb);
|
||||
|
||||
if (Post_texture_width > GL_max_renderbuffer_size) {
|
||||
Post_texture_width = GL_max_renderbuffer_size;
|
||||
}
|
||||
opengl_delete_render_texture(Smaa_output_tex);
|
||||
opengl_delete_render_framebuffer(Smaa_neighborhood_blending_fb);
|
||||
}
|
||||
|
||||
if (Post_texture_height > GL_max_renderbuffer_size) {
|
||||
Post_texture_height = GL_max_renderbuffer_size;
|
||||
}
|
||||
// Allocate every post-processing resource whose size follows the scene textures. Split out from
|
||||
// opengl_post_process_init() so gr_opengl_resize_render_targets() can rebuild just these without
|
||||
// re-parsing post_processing.tbl or recompiling shaders.
|
||||
static void opengl_post_setup_render_targets()
|
||||
{
|
||||
// These consume the scene textures pass by pass, so they have to match them exactly rather
|
||||
// than being sized from gr_screen independently -- see gr_opengl_scene_texture_begin() for
|
||||
// what the two sizes diverging would mean.
|
||||
Post_texture_width = Scene_texture_width;
|
||||
Post_texture_height = Scene_texture_height;
|
||||
|
||||
GL_state.PushFramebufferState();
|
||||
|
||||
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();
|
||||
//}
|
||||
setup_smaa_render_targets();
|
||||
|
||||
GL_state.PopFramebufferState();
|
||||
|
||||
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;
|
||||
opengl_delete_render_texture(Bloom_textures[0]);
|
||||
opengl_delete_render_texture(Bloom_textures[1]);
|
||||
opengl_delete_render_framebuffer(Bloom_framebuffer);
|
||||
}
|
||||
|
||||
static void opengl_post_shutdown_render_targets()
|
||||
{
|
||||
opengl_post_process_shutdown_bloom();
|
||||
shutdown_smaa_render_targets();
|
||||
}
|
||||
|
||||
void opengl_post_resize_render_targets()
|
||||
{
|
||||
// Post-processing may have been disabled outright (no FBOs, missing shaders, or turned off in
|
||||
// the table), in which case none of these resources exist and none should start existing now.
|
||||
if ( !Post_initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Bloom_textures[1] ) {
|
||||
glDeleteTextures(1, &Bloom_textures[1]);
|
||||
Bloom_textures[1] = 0;
|
||||
}
|
||||
opengl_post_shutdown_render_targets();
|
||||
opengl_post_setup_render_targets();
|
||||
}
|
||||
|
||||
if ( Bloom_framebuffer > 0 ) {
|
||||
glDeleteFramebuffers(1, &Bloom_framebuffer);
|
||||
Bloom_framebuffer = 0;
|
||||
}
|
||||
// generate and test the framebuffer and textures that we are going to use
|
||||
static bool opengl_post_init_framebuffer()
|
||||
{
|
||||
setup_smaa_lookup_textures();
|
||||
|
||||
opengl_post_setup_render_targets();
|
||||
|
||||
return !opengl_check_for_errors("post_init_framebuffer()");
|
||||
}
|
||||
|
||||
size_t opengl_get_postprocessing_render_target_bytes()
|
||||
@@ -1188,20 +1210,16 @@ void opengl_post_process_shutdown()
|
||||
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;
|
||||
}
|
||||
}
|
||||
opengl_delete_render_framebuffer(Post_framebuffer_id[0]);
|
||||
opengl_delete_render_framebuffer(Post_framebuffer_id[1]);
|
||||
|
||||
graphics::Post_processing_manager->clear();
|
||||
graphics::Post_processing_manager = nullptr;
|
||||
|
||||
opengl_post_process_shutdown_bloom();
|
||||
opengl_post_shutdown_render_targets();
|
||||
|
||||
opengl_delete_render_texture(Smaa_area_tex);
|
||||
opengl_delete_render_texture(Smaa_search_tex);
|
||||
|
||||
Post_in_frame = false;
|
||||
Post_active_shader_index = 0;
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
void opengl_post_process_init();
|
||||
void opengl_post_process_shutdown();
|
||||
|
||||
// Rebuild the resolution-dependent subset of the above for the current scene texture size, without
|
||||
// re-parsing post_processing.tbl or recompiling shaders. No-op if post-processing isn't active.
|
||||
void opengl_post_resize_render_targets();
|
||||
|
||||
/**
|
||||
* @brief Gives the bytes that the bloom and SMAA render textures hold
|
||||
*
|
||||
|
||||
@@ -92,28 +92,6 @@ static auto TextureFilteringOption __UNUSED = options::OptionBuilder<int>("Graph
|
||||
.parser(parse_texture_filtering_func)
|
||||
.finish();
|
||||
|
||||
static SCP_vector<float> anisotropic_value_enumerator()
|
||||
{
|
||||
float max;
|
||||
if (!gr_get_property(gr_property::MAX_ANISOTROPY, &max)) {
|
||||
return SCP_vector<float>();
|
||||
}
|
||||
|
||||
if (max <= 2.0f) {
|
||||
return SCP_vector<float>();
|
||||
}
|
||||
|
||||
SCP_vector<float> out;
|
||||
|
||||
// We assume here that the anisotropy levels are powers of two...
|
||||
float current = 1.0f;
|
||||
while (current <= max) {
|
||||
out.push_back(current);
|
||||
current *= 2.0f;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
static SCP_string anisotropic_display(float val)
|
||||
{
|
||||
if (val < 2.0f) {
|
||||
@@ -137,7 +115,7 @@ static float anisotropic_default()
|
||||
static auto AnisotropyOption = options::OptionBuilder<float>("Graphics.Anisotropy",
|
||||
std::pair<const char*, int>{"Anistropic filtering", 1736},
|
||||
std::pair<const char*, int>{"Controls the amount of anistropic filtering of the textures", 1737})
|
||||
.enumerator(anisotropic_value_enumerator)
|
||||
.enumerator(gr_get_supported_anisotropy_levels)
|
||||
.category(std::make_pair("Graphics", 1825))
|
||||
.display(anisotropic_display)
|
||||
.default_func(anisotropic_default)
|
||||
@@ -213,8 +191,15 @@ void opengl_tcache_init()
|
||||
// check what mipmap filter we should be using
|
||||
// 0 == Bilinear
|
||||
// 1 == Trilinear
|
||||
// Seed from the legacy config key first: TextureFilteringOption's default_func returns
|
||||
// GL_mipmap_filter, so this read is what supplies that default. Only then let the option
|
||||
// override it, the same order the anisotropy setting below uses.
|
||||
GL_mipmap_filter = os_config_read_uint(NULL, "TextureFilter", 1);
|
||||
|
||||
if (Using_in_game_options) {
|
||||
GL_mipmap_filter = TextureFilteringOption->getValue();
|
||||
}
|
||||
|
||||
if (GL_mipmap_filter > 1) {
|
||||
GL_mipmap_filter = 1;
|
||||
}
|
||||
|
||||
@@ -507,8 +507,12 @@ void VulkanDeferredLighting::render(vk::CommandBuffer cmd)
|
||||
{
|
||||
auto* header = reinterpret_cast<graphics::deferred_global_data*>(uboMapped + regionBase);
|
||||
memset(header, 0, sizeof(graphics::deferred_global_data));
|
||||
header->invScreenWidth = 1.0f / gr_screen.max_w;
|
||||
header->invScreenHeight = 1.0f / gr_screen.max_h;
|
||||
// Same as the OpenGL backend: deferred-f.sdr normalizes gl_FragCoord against these to
|
||||
// sample the G-buffer, so they must describe the G-buffer, not gr_screen. resize() keeps
|
||||
// sceneExtent equal to gr_screen today, which is why deriving it from the extent is a
|
||||
// no-op here -- but it states the actual requirement instead of relying on that.
|
||||
header->invScreenWidth = 1.0f / static_cast<float>(m_ctx->sceneExtent.width);
|
||||
header->invScreenHeight = 1.0f / static_cast<float>(m_ctx->sceneExtent.height);
|
||||
header->nearPlane = gr_near_plane;
|
||||
|
||||
if (m_shadow->isInitialized() && Shadow_quality != ShadowQuality::Disabled) {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Module: qtfred
|
||||
|
||||
Entry-point guide for this module lives at:
|
||||
**`documentation/modules/qtfred.md`** (from repo root).
|
||||
|
||||
It covers the editor's purpose, build requirements, layout, and how it relates to
|
||||
the shared engine code in `code/`.
|
||||
|
||||
For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`.
|
||||
For build/test/style conventions see the root `AGENTS.md`.
|
||||
@@ -10,7 +10,9 @@
|
||||
<h1>Preferences</h1>
|
||||
<p>Opens via <strong>File › Preferences</strong>.</p>
|
||||
<p>Controls QtFRED editor settings. Changes take effect immediately; there is no
|
||||
Apply button.</p>
|
||||
Apply button. The exception is a handful of graphics settings that the renderer
|
||||
can only read while it is starting up - those are marked below, and QtFRED
|
||||
applies them the next time it is launched.</p>
|
||||
|
||||
<h2>General</h2>
|
||||
<p>Miscellaneous editor options. Hover over each setting for a tooltip
|
||||
@@ -37,6 +39,50 @@ and <strong>Misc</strong>, which holds the object and subsystem commands and the
|
||||
remaining tools. The menu items and their shortcuts are the same in both layouts,
|
||||
and the change takes effect immediately.</p>
|
||||
|
||||
<h2>Graphics</h2>
|
||||
<p>Controls how the 3D viewport is rendered. These settings affect the editor
|
||||
only - they are stored separately from the game's own graphics options and
|
||||
never change them.</p>
|
||||
|
||||
<h3>Post-processing</h3>
|
||||
<p><strong>Enable post-processing in the viewport</strong> routes the viewport
|
||||
through the same HDR rendering pipeline the game uses, adding bloom,
|
||||
tonemapping, and lightshafts. It is off by default, so missions look the way
|
||||
they always have until you opt in. The same switch is available as
|
||||
<strong>View › Enable Post Processing</strong>.</p>
|
||||
<p>Every other setting in this section only changes what you see while
|
||||
post-processing is on, because they are all consumed by that pipeline.</p>
|
||||
|
||||
<h3>Shadows & anti-aliasing</h3>
|
||||
<ul>
|
||||
<li><strong>Shadow quality</strong> - detail level of the shadows cast by the
|
||||
mission's sun. Higher settings cost more performance. Shadows are off
|
||||
until you raise this above <em>Disabled</em>, so enabling post-processing
|
||||
alone will not produce any. <em>Requires a restart</em> - including the
|
||||
first time you turn it on.</li>
|
||||
<li><strong>Anti-aliasing</strong> - post-process anti-aliasing mode (FXAA or
|
||||
SMAA, in increasing quality). Applies immediately.</li>
|
||||
<li><strong>MSAA</strong> - multisample anti-aliasing for the 3D scene, off
|
||||
or 4x/8x. More expensive than the post-process modes above but higher
|
||||
quality. <em>Requires a restart.</em></li>
|
||||
</ul>
|
||||
|
||||
<h3>Textures</h3>
|
||||
<ul>
|
||||
<li><strong>Texture filtering</strong> - bilinear or trilinear mipmap
|
||||
filtering. Left alone, QtFRED uses the same default the game does.
|
||||
<em>Requires a restart.</em></li>
|
||||
<li><strong>Anisotropic filtering</strong> - sharpens textures viewed at a
|
||||
steep angle. The list is limited to the levels your GPU reports, and the
|
||||
control is greyed out if it reports nothing worth offering. Left alone,
|
||||
QtFRED uses your hardware's maximum. <em>Requires a restart.</em></li>
|
||||
</ul>
|
||||
|
||||
<h3>Gamma</h3>
|
||||
<p>Brightness of the viewport, from 0.10 to 5.00. QtFRED defaults this to 3.00,
|
||||
higher than the game, because the viewport is not tonemapped unless
|
||||
post-processing is enabled. Applies immediately.</p>
|
||||
|
||||
<h2>Grid</h2>
|
||||
<p>Configures the reference grid shown in the main viewport. You can set which
|
||||
world-space plane the grid lies on (XZ, XY, or YZ) and adjust the grid's center
|
||||
|
||||
@@ -28,6 +28,8 @@ add_file_folder("Source/Mission"
|
||||
src/mission/EditorViewport.h
|
||||
src/mission/FredRenderer.cpp
|
||||
src/mission/FredRenderer.h
|
||||
src/mission/GraphicsSettings.cpp
|
||||
src/mission/GraphicsSettings.h
|
||||
src/mission/IDialogProvider.h
|
||||
src/mission/management.cpp
|
||||
src/mission/management.h
|
||||
|
||||
@@ -123,6 +123,7 @@ EditorViewport::EditorViewport(Editor* in_editor, std::unique_ptr<FredRenderer>&
|
||||
syncMissionLayerNames();
|
||||
|
||||
loadSettings();
|
||||
view.Graphics.applyLive();
|
||||
|
||||
fredApp->runAfterInit([this]() { initialSetup(); });
|
||||
}
|
||||
@@ -185,6 +186,8 @@ void EditorViewport::loadSettings() {
|
||||
camera.setInvertOrbitY(settings.value("camera_invert_orbit_y", camera.getInvertOrbitY()).toBool());
|
||||
camera.setOrbitSelectionMidpoint(settings.value("camera_orbit_selection_midpoint", camera.getOrbitSelectionMidpoint()).toBool());
|
||||
settings.endGroup();
|
||||
|
||||
view.Graphics = GraphicsSettings::load();
|
||||
}
|
||||
|
||||
void EditorViewport::saveSettings() const {
|
||||
@@ -236,7 +239,10 @@ void EditorViewport::saveSettings() const {
|
||||
settings.setValue("camera_invert_orbit_y", camera.getInvertOrbitY());
|
||||
settings.setValue("camera_orbit_selection_midpoint", camera.getOrbitSelectionMidpoint());
|
||||
settings.endGroup();
|
||||
|
||||
view.Graphics.save();
|
||||
}
|
||||
|
||||
void EditorViewport::needsUpdate() {
|
||||
_renderer->scheduleUpdate();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "CameraController.h"
|
||||
#include "FredRenderer.h"
|
||||
#include "Editor.h"
|
||||
#include "GraphicsSettings.h"
|
||||
#include "IDialogProvider.h"
|
||||
#include "ui/ThemeMode.h"
|
||||
|
||||
@@ -64,6 +65,9 @@ struct ViewSettings {
|
||||
int Outline_lod = 1;
|
||||
float Label_font_scale = 1.0f; // multiplier applied to viewport text labels
|
||||
|
||||
//! Preferences > Graphics. Owns its own persistence and apply rules; see GraphicsSettings.
|
||||
GraphicsSettings Graphics;
|
||||
|
||||
ViewSettings();
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <graphics/font.h>
|
||||
#include <graphics/matrix.h>
|
||||
#include <graphics/light.h>
|
||||
#include <graphics/shadows.h>
|
||||
#include <lighting/lighting.h>
|
||||
#include <starfield/starfield.h>
|
||||
#include <ship/ship.h>
|
||||
@@ -28,6 +29,8 @@
|
||||
#include <mod_table/mod_table.h>
|
||||
#include <cfile/cfile.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "mission/object.h"
|
||||
#include "prop/prop.h"
|
||||
#include "weapon/weapon.h"
|
||||
@@ -56,6 +59,38 @@ void disable_htl() {
|
||||
gr_end_view_matrix();
|
||||
}
|
||||
|
||||
//! Scoped gr_scene_texture_begin()/end(), so the two can't drift apart as render_frame() grows.
|
||||
struct ScenePostProcessing {
|
||||
ScenePostProcessing() { gr_scene_texture_begin(); }
|
||||
~ScenePostProcessing() { gr_scene_texture_end(); }
|
||||
|
||||
ScenePostProcessing(const ScenePostProcessing&) = delete;
|
||||
ScenePostProcessing& operator=(const ScenePostProcessing&) = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Render the shadow pass for the current camera.
|
||||
*
|
||||
* Only meaningful inside a ScenePostProcessing scope: the shadow pass writes into deferred
|
||||
* G-buffer surfaces that only exist while the scene texture is bound. Mirrors game_render_frame(),
|
||||
* which calls this right after its own gr_scene_texture_begin() + stars_draw().
|
||||
*
|
||||
* shadows_render_all() works on the HTL proj/view matrix stack -- the same one enable_htl() and
|
||||
* disable_htl() push and pop for the starfield -- and expects it to be open: it ends whatever is
|
||||
* active (gr_end_view_matrix() asserts on modelview_matrix_depth) and restores it when done. The
|
||||
* starfield's disable_htl() just popped that stack, hence the push here, and the matching pop
|
||||
* afterwards so the next frame's enable_htl() doesn't assert on a stack left open.
|
||||
*/
|
||||
void render_shadows() {
|
||||
gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance);
|
||||
gr_set_view_matrix(&Eye_position, &Eye_matrix);
|
||||
|
||||
shadows_render_all(Proj_fov, &Eye_matrix, &Eye_position, nullptr, nullptr, nullptr);
|
||||
|
||||
gr_end_proj_matrix();
|
||||
gr_end_view_matrix();
|
||||
}
|
||||
|
||||
bool fred_colors_inited = false;
|
||||
color colour_white;
|
||||
color colour_green;
|
||||
@@ -995,6 +1030,15 @@ void FredRenderer::render_frame(int cur_object_index,
|
||||
|
||||
g3_set_view_matrix(&_viewport->camera.eye_pos, &_viewport->camera.eye_orient, 0.5f);
|
||||
|
||||
// Optionally run the 3D world through the game's HDR post-processing pipeline (bloom,
|
||||
// tonemapping, lightshafts, shadows) instead of drawing straight to the default framebuffer.
|
||||
// Brackets only the 3D content, the same way game_render_frame() does; the 2D overlays further
|
||||
// down (distances, ship info, tooltips) stay outside it.
|
||||
std::optional<ScenePostProcessing> postProcessing;
|
||||
if (view().Graphics.enablePostProcessing) {
|
||||
postProcessing.emplace();
|
||||
}
|
||||
|
||||
// Force max star detail so the editor always shows the full Num_stars count
|
||||
// regardless of the player's graphics quality setting (Detail.num_stars can be 0).
|
||||
int saved_detail_stars = Detail.num_stars;
|
||||
@@ -1004,6 +1048,10 @@ void FredRenderer::render_frame(int cur_object_index,
|
||||
disable_htl();
|
||||
Detail.num_stars = saved_detail_stars;
|
||||
|
||||
if (postProcessing) {
|
||||
render_shadows();
|
||||
}
|
||||
|
||||
if (view().Show_horizon) {
|
||||
gr_set_color(128, 128, 64);
|
||||
g3_draw_horizon_line();
|
||||
@@ -1020,6 +1068,8 @@ void FredRenderer::render_frame(int cur_object_index,
|
||||
render_models(cur_object_index);
|
||||
render_volumetric_overlay();
|
||||
|
||||
postProcessing.reset();
|
||||
|
||||
if (view().Show_distances) {
|
||||
display_distances();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "mission/GraphicsSettings.h"
|
||||
|
||||
#include <cmdline/cmdline.h>
|
||||
#include <options/OptionsManager.h>
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
namespace fso::fred {
|
||||
|
||||
namespace {
|
||||
|
||||
const char* SETTINGS_GROUP = "Preferences";
|
||||
|
||||
const char* KEY_POST_PROCESSING = "view_enable_post_processing";
|
||||
const char* KEY_SHADOW_QUALITY = "view_graphics_shadow_quality";
|
||||
const char* KEY_AA_MODE = "view_graphics_aa_mode";
|
||||
const char* KEY_MSAA_SAMPLES = "view_graphics_msaa_samples";
|
||||
const char* KEY_TEXTURE_FILTER = "view_graphics_texture_filter";
|
||||
const char* KEY_ANISOTROPY = "view_graphics_anisotropy";
|
||||
const char* KEY_GAMMA = "view_graphics_gamma";
|
||||
|
||||
// A settings file can be hand-edited or left over from a build with a different set of values, so
|
||||
// anything that ends up in an enum or a fixed value list gets checked rather than cast blindly.
|
||||
template <typename T>
|
||||
T readEnum(const QSettings& settings, const char* key, T fallback, T highest)
|
||||
{
|
||||
const int raw = settings.value(key, static_cast<int>(fallback)).toInt();
|
||||
|
||||
if (raw < 0 || raw > static_cast<int>(highest)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return static_cast<T>(raw);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool GraphicsSettings::operator==(const GraphicsSettings& rhs) const
|
||||
{
|
||||
return enablePostProcessing == rhs.enablePostProcessing && shadowQuality == rhs.shadowQuality &&
|
||||
aaMode == rhs.aaMode && msaaSamples == rhs.msaaSamples && gamma == rhs.gamma &&
|
||||
textureFilter == rhs.textureFilter && anisotropy == rhs.anisotropy;
|
||||
}
|
||||
|
||||
SCP_vector<int> GraphicsSettings::validMsaaSampleCounts()
|
||||
{
|
||||
return {0, 4, 8};
|
||||
}
|
||||
|
||||
GraphicsSettings GraphicsSettings::load()
|
||||
{
|
||||
GraphicsSettings out;
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup(SETTINGS_GROUP);
|
||||
|
||||
out.enablePostProcessing = settings.value(KEY_POST_PROCESSING, out.enablePostProcessing).toBool();
|
||||
out.shadowQuality = readEnum(settings, KEY_SHADOW_QUALITY, out.shadowQuality, ShadowQuality::Ultra);
|
||||
out.aaMode = readEnum(settings, KEY_AA_MODE, out.aaMode, AntiAliasMode::SMAA_Ultra);
|
||||
out.gamma = settings.value(KEY_GAMMA, out.gamma).toFloat();
|
||||
|
||||
if (settings.contains(KEY_TEXTURE_FILTER)) {
|
||||
out.textureFilter = settings.value(KEY_TEXTURE_FILTER).toInt() != 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
if (settings.contains(KEY_ANISOTROPY)) {
|
||||
out.anisotropy = settings.value(KEY_ANISOTROPY).toFloat();
|
||||
}
|
||||
|
||||
const int samples = settings.value(KEY_MSAA_SAMPLES, out.msaaSamples).toInt();
|
||||
const auto validSamples = validMsaaSampleCounts();
|
||||
if (std::find(validSamples.begin(), validSamples.end(), samples) != validSamples.end()) {
|
||||
out.msaaSamples = samples;
|
||||
}
|
||||
|
||||
settings.endGroup();
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void GraphicsSettings::save() const
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup(SETTINGS_GROUP);
|
||||
|
||||
settings.setValue(KEY_POST_PROCESSING, enablePostProcessing);
|
||||
settings.setValue(KEY_SHADOW_QUALITY, static_cast<int>(shadowQuality));
|
||||
settings.setValue(KEY_AA_MODE, static_cast<int>(aaMode));
|
||||
settings.setValue(KEY_MSAA_SAMPLES, msaaSamples);
|
||||
settings.setValue(KEY_GAMMA, gamma);
|
||||
|
||||
// Don't write the sentinels back out -- an absent key is what keeps the engine default in play.
|
||||
if (textureFilter != NO_TEXTURE_FILTER_CHOICE) {
|
||||
settings.setValue(KEY_TEXTURE_FILTER, textureFilter);
|
||||
}
|
||||
|
||||
if (anisotropy != NO_ANISOTROPY_CHOICE) {
|
||||
settings.setValue(KEY_ANISOTROPY, anisotropy);
|
||||
}
|
||||
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void GraphicsSettings::applyLive() const
|
||||
{
|
||||
Gr_aa_mode = aaMode;
|
||||
gr_set_gamma(gamma);
|
||||
}
|
||||
|
||||
GraphicsSettings GraphicsSettings::applyBeforeGrInit()
|
||||
{
|
||||
const GraphicsSettings settings = load();
|
||||
|
||||
Cmdline_msaa_enabled = settings.msaaSamples;
|
||||
Shadow_quality = settings.shadowQuality;
|
||||
|
||||
auto* options = options::OptionsManager::instance();
|
||||
|
||||
// Overrides are in-memory only, so they steer this session's gr_init() without touching the
|
||||
// config file the game reads its own graphics settings from.
|
||||
if (settings.textureFilter != NO_TEXTURE_FILTER_CHOICE) {
|
||||
options->setOverride("Graphics.TextureFilter", std::to_string(settings.textureFilter), "qtFRED Preferences");
|
||||
}
|
||||
|
||||
if (settings.anisotropy != NO_ANISOTROPY_CHOICE) {
|
||||
options->setOverride("Graphics.Anisotropy", std::to_string(settings.anisotropy), "qtFRED Preferences");
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
} // namespace fso::fred
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <graphics/2d.h>
|
||||
#include <graphics/shadows.h>
|
||||
|
||||
namespace fso::fred {
|
||||
|
||||
/**
|
||||
* @brief qtFRED's Preferences > Graphics settings.
|
||||
*
|
||||
* Single owner of these values: the QSettings keys, their defaults, and the rules for when each
|
||||
* one can be applied all live here. Two very different callers need them -- management.cpp before
|
||||
* gr_init(), and EditorViewport once the editor is up -- and previously each read QSettings with
|
||||
* its own copy of the key strings and default values.
|
||||
*
|
||||
* The settings split by *when* they can take effect, which is what the two apply functions below
|
||||
* encode:
|
||||
*
|
||||
* - applyLive() settings can be changed at any time and are re-applied whenever the user hits
|
||||
* Apply in Preferences.
|
||||
* - applyBeforeGrInit() settings are baked into GPU resources during gr_init() and cannot be
|
||||
* changed afterwards, so they are read straight out of QSettings before the editor exists.
|
||||
* Shadow quality is the sharpest example: shadow_cascade_params_init() only runs during
|
||||
* gr_init(), and only if Shadow_quality is already non-Disabled, so setting it later leaves the
|
||||
* cascade buffers unsized and crashes the next frame that renders shadows.
|
||||
*/
|
||||
struct GraphicsSettings {
|
||||
// Texture filtering and anisotropy reach the engine through the options system, whose defaults
|
||||
// are better informed than anything qtFRED could hardcode -- the user's existing config for
|
||||
// one, the hardware maximum for the other. Both therefore carry a sentinel meaning "the user
|
||||
// has not chosen one", and applyBeforeGrInit() overrides only once there is a real choice.
|
||||
static constexpr int NO_TEXTURE_FILTER_CHOICE = -1;
|
||||
static constexpr float NO_ANISOTROPY_CHOICE = 0.0f;
|
||||
|
||||
/**
|
||||
* @brief Run the viewport through the game's HDR scene-texture + post-processing pipeline.
|
||||
*
|
||||
* Off by default so missions keep looking exactly as they do today unless a user opts in.
|
||||
* Everything else on this struct is only visible in the viewport while this is on.
|
||||
*/
|
||||
bool enablePostProcessing = false;
|
||||
|
||||
ShadowQuality shadowQuality = ShadowQuality::Disabled;
|
||||
AntiAliasMode aaMode = AntiAliasMode::None;
|
||||
|
||||
int msaaSamples = 0; //!< 0 (off), 4, or 8; see validMsaaSampleCounts()
|
||||
float gamma = 3.0f; //!< matches the brightness qtFRED has always launched with
|
||||
|
||||
int textureFilter = NO_TEXTURE_FILTER_CHOICE; //!< 0 = bilinear, 1 = trilinear
|
||||
float anisotropy = NO_ANISOTROPY_CHOICE; //!< 1.0 = off, otherwise a power of two
|
||||
|
||||
bool operator==(const GraphicsSettings& rhs) const;
|
||||
bool operator!=(const GraphicsSettings& rhs) const { return !(*this == rhs); }
|
||||
|
||||
/**
|
||||
* @brief The MSAA sample counts the Preferences combo offers, in combo order.
|
||||
*/
|
||||
static SCP_vector<int> validMsaaSampleCounts();
|
||||
|
||||
static GraphicsSettings load();
|
||||
void save() const;
|
||||
|
||||
/**
|
||||
* @brief Push the settings that can be changed without a restart into the engine.
|
||||
*/
|
||||
void applyLive() const;
|
||||
|
||||
/**
|
||||
* @brief Load and apply the settings that must be in place before gr_init() runs.
|
||||
*
|
||||
* The engine reads texture filtering and anisotropy through the options system, so those are
|
||||
* pushed as in-memory-only overrides rather than written out -- qtFRED must never rewrite the
|
||||
* config file the game reads its own graphics settings from.
|
||||
*
|
||||
* @return the settings that were loaded, so the caller can applyLive() them once gr_init() has
|
||||
* returned and there is a renderer to apply them to.
|
||||
*/
|
||||
static GraphicsSettings applyBeforeGrInit();
|
||||
};
|
||||
|
||||
} // namespace fso::fred
|
||||
@@ -28,6 +28,7 @@ PreferencesDialogModel::PreferencesDialogModel(QObject* parent, EditorViewport*
|
||||
, _toolbarIconSize(viewport->toolbar_icon_size)
|
||||
, _outlineLod(viewport->view.Outline_lod)
|
||||
, _labelFontScale(viewport->view.Label_font_scale)
|
||||
, _graphics(viewport->view.Graphics)
|
||||
, _invertOrbitX(viewport->camera.getInvertOrbitX())
|
||||
, _invertOrbitY(viewport->camera.getInvertOrbitY())
|
||||
, _orbitSelectionMidpoint(viewport->camera.getOrbitSelectionMidpoint())
|
||||
@@ -73,6 +74,9 @@ bool PreferencesDialogModel::apply() {
|
||||
_viewport->toolbar_icon_size = _toolbarIconSize;
|
||||
_viewport->view.Outline_lod = _outlineLod;
|
||||
_viewport->view.Label_font_scale = _labelFontScale;
|
||||
_viewport->view.Graphics = _graphics;
|
||||
// Only some of these can take effect now; the rest need a restart (see GraphicsSettings).
|
||||
_viewport->view.Graphics.applyLive();
|
||||
_viewport->camera.setInvertOrbitX(_invertOrbitX);
|
||||
_viewport->camera.setInvertOrbitY(_invertOrbitY);
|
||||
_viewport->camera.setOrbitSelectionMidpoint(_orbitSelectionMidpoint);
|
||||
@@ -192,6 +196,9 @@ void PreferencesDialogModel::setOutlineLod(int value) { modify(_outlineLod, valu
|
||||
double PreferencesDialogModel::getLabelFontScale() const { return _labelFontScale; }
|
||||
void PreferencesDialogModel::setLabelFontScale(double value) { modify(_labelFontScale, static_cast<float>(value)); }
|
||||
|
||||
const GraphicsSettings& PreferencesDialogModel::getGraphics() const { return _graphics; }
|
||||
void PreferencesDialogModel::setGraphics(const GraphicsSettings& value) { modify(_graphics, value); }
|
||||
|
||||
QKeySequence PreferencesDialogModel::getControlKey(ControlAction action) const {
|
||||
auto it = _controlKeys.find(action);
|
||||
Assertion(it != _controlKeys.end(), "Unknown control action!");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "mission/GraphicsSettings.h"
|
||||
#include "mission/dialogs/AbstractDialogModel.h"
|
||||
#include "ui/ControlBindings.h"
|
||||
#include "ui/ThemeMode.h"
|
||||
@@ -73,6 +74,11 @@ public:
|
||||
double getLabelFontScale() const;
|
||||
void setLabelFontScale(double value);
|
||||
|
||||
// Graphics. The dialog edits a whole GraphicsSettings rather than a field at a time; the
|
||||
// setter exists so edits still route through modify() and mark the model dirty.
|
||||
const GraphicsSettings& getGraphics() const;
|
||||
void setGraphics(const GraphicsSettings& value);
|
||||
|
||||
// Controls
|
||||
QKeySequence getControlKey(ControlAction action) const;
|
||||
void setControlKey(ControlAction action, const QKeySequence& sequence);
|
||||
@@ -119,6 +125,8 @@ private:
|
||||
int _outlineLod;
|
||||
float _labelFontScale;
|
||||
|
||||
GraphicsSettings _graphics;
|
||||
|
||||
// Controls
|
||||
std::map<ControlAction, QKeySequence> _controlKeys;
|
||||
bool _invertOrbitX;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "object.h"
|
||||
|
||||
#include "mission/GraphicsSettings.h"
|
||||
|
||||
#include "cmdline/cmdline.h"
|
||||
|
||||
#include <asteroid/asteroid.h>
|
||||
@@ -126,9 +128,16 @@ initialize(const std::string& cfilepath, int argc, char* argv[], Editor* editor,
|
||||
// Cmdline_noglow = 1;
|
||||
Cmdline_window = 1;
|
||||
|
||||
// These have to be in place before gr_init() bakes them into GPU resources, which is well
|
||||
// before EditorViewport (and its copy of the settings) exists.
|
||||
const GraphicsSettings graphicsSettings = GraphicsSettings::applyBeforeGrInit();
|
||||
|
||||
std::unique_ptr<QtGraphicsOperations> graphicsOps(new QtGraphicsOperations(editor));
|
||||
gr_init(std::move(graphicsOps));
|
||||
gr_set_gamma(3.0f);
|
||||
|
||||
// The rest needs a live renderer. EditorViewport re-applies these once it exists, but the
|
||||
// startup screens render before that.
|
||||
graphicsSettings.applyLive();
|
||||
|
||||
io::mouse::CursorManager::get()->showCursor(false);
|
||||
|
||||
|
||||
@@ -959,6 +959,7 @@ void FredView::syncViewOptions() {
|
||||
|
||||
connectActionToViewSetting(ui->actionLighting_from_Suns, &_viewport->view.Lighting_on);
|
||||
connectActionToViewSetting(ui->actionRender_Full_Detail, &_viewport->view.FullDetail);
|
||||
connectActionToViewSetting(ui->actionEnable_Post_Processing, &_viewport->view.Graphics.enablePostProcessing);
|
||||
|
||||
connectActionToViewSetting(ui->actionShowDistances, &_viewport->view.Show_distances);
|
||||
|
||||
|
||||
@@ -8,9 +8,54 @@
|
||||
#include "ui/util/SignalBlockers.h"
|
||||
#include "ui/widgets/sexp_tree_view.h"
|
||||
|
||||
#include <options/Option.h>
|
||||
#include <options/OptionsManager.h>
|
||||
|
||||
namespace fso::fred::dialogs {
|
||||
namespace {
|
||||
|
||||
// The engine already describes each of these settings once, with its own value list and display
|
||||
// names (Graphics.Shadows in shadows.cpp, Graphics.AAMode and Graphics.TextureFilter in 2d.cpp and
|
||||
// gropengltexture.cpp). Reading the labels back out of those definitions keeps this dialog from
|
||||
// carrying a second, silently-diverging copy of them.
|
||||
//
|
||||
// The values are enumerated in declaration order, which for these three is enum order, so a combo
|
||||
// index is the enum value. Returns empty if the option isn't registered, in which case the caller
|
||||
// leaves the combo alone rather than showing a half-populated list.
|
||||
SCP_vector<options::ValueDescription> engineOptionValues(const char* configKey)
|
||||
{
|
||||
for (const auto& option : options::OptionsManager::instance()->getOptions()) {
|
||||
if (option->getConfigKey() == configKey && option->getType() == options::OptionType::Selection) {
|
||||
return option->getValidValues();
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void populateFromEngineOption(QComboBox* combo, const char* configKey)
|
||||
{
|
||||
const auto values = engineOptionValues(configKey);
|
||||
|
||||
for (const auto& value : values) {
|
||||
combo->addItem(QString::fromStdString(value.display));
|
||||
}
|
||||
|
||||
// Nothing to choose from means the option isn't registered in this build (a renderer compiled
|
||||
// out, say). Grey the control out rather than leaving an empty combo that looks broken.
|
||||
combo->setEnabled(!values.empty());
|
||||
}
|
||||
|
||||
// The model stores graphics settings as one struct, so an edit is read-modify-write. Doing it this
|
||||
// way keeps every control's slot to the one line that actually differs.
|
||||
template <typename F>
|
||||
void editGraphics(PreferencesDialogModel* model, F&& mutate)
|
||||
{
|
||||
GraphicsSettings graphics = model->getGraphics();
|
||||
mutate(graphics);
|
||||
model->setGraphics(graphics);
|
||||
}
|
||||
|
||||
int themeModeToIndex(ThemeMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
@@ -95,6 +140,27 @@ void PreferencesDialog::applyChanges() {
|
||||
}
|
||||
|
||||
void PreferencesDialog::initializeUi() {
|
||||
// setupUi() has already run connectSlotsByName(), so filling a combo here would fire its
|
||||
// currentIndexChanged slot and mark the model modified before the user has touched anything.
|
||||
// (Items declared in the .ui file were added before the connections existed and so were safe.)
|
||||
util::SignalBlockers blockers(this);
|
||||
|
||||
populateFromEngineOption(ui->shadowQualityCombo, "Graphics.Shadows");
|
||||
populateFromEngineOption(ui->aaModeCombo, "Graphics.AAMode");
|
||||
populateFromEngineOption(ui->textureFilterCombo, "Graphics.TextureFilter");
|
||||
|
||||
// Anisotropy levels are hardware-dependent, so ask the engine for the same list its own
|
||||
// options screen offers. Empty means the hardware can't do it; leave the combo disabled.
|
||||
_anisotropyLevels = gr_get_supported_anisotropy_levels();
|
||||
for (float level : _anisotropyLevels) {
|
||||
ui->anisotropyCombo->addItem(level <= 1.0f ? tr("Off") : tr("%1x").arg(level, 0, 'g', 0));
|
||||
}
|
||||
ui->anisotropyCombo->setEnabled(!_anisotropyLevels.empty());
|
||||
|
||||
for (int samples : GraphicsSettings::validMsaaSampleCounts()) {
|
||||
ui->msaaCombo->addItem(samples == 0 ? tr("Off") : tr("%1x").arg(samples));
|
||||
}
|
||||
|
||||
// Build the controls key-binding form dynamically from the registered bindings
|
||||
auto* form = new QFormLayout(ui->controlsFormWidget);
|
||||
auto& bindings = ControlBindings::instance();
|
||||
@@ -128,6 +194,35 @@ void PreferencesDialog::updateUi() {
|
||||
ui->toolbarIconSizeCombo->setCurrentIndex(iconSize <= 16 ? 0 : iconSize >= 32 ? 2 : 1);
|
||||
ui->outlineLodCombo->setCurrentIndex(_model->getOutlineLod());
|
||||
ui->labelFontScaleSpin->setValue(_model->getLabelFontScale());
|
||||
|
||||
// Graphics
|
||||
const GraphicsSettings& graphics = _model->getGraphics();
|
||||
|
||||
ui->enablePostProcessing->setChecked(graphics.enablePostProcessing);
|
||||
ui->shadowQualityCombo->setCurrentIndex(static_cast<int>(graphics.shadowQuality));
|
||||
ui->aaModeCombo->setCurrentIndex(static_cast<int>(graphics.aaMode));
|
||||
ui->gammaSpin->setValue(graphics.gamma);
|
||||
|
||||
const auto msaaCounts = GraphicsSettings::validMsaaSampleCounts();
|
||||
ui->msaaCombo->setCurrentIndex(
|
||||
static_cast<int>(std::find(msaaCounts.begin(), msaaCounts.end(), graphics.msaaSamples) - msaaCounts.begin()));
|
||||
|
||||
// Both of these can be "not chosen", in which case the engine picks: trilinear, and the
|
||||
// hardware's maximum anisotropy. Show what will actually be used rather than a blank.
|
||||
ui->textureFilterCombo->setCurrentIndex(
|
||||
graphics.textureFilter == GraphicsSettings::NO_TEXTURE_FILTER_CHOICE ? 1 : graphics.textureFilter);
|
||||
|
||||
if (!_anisotropyLevels.empty()) {
|
||||
int index = static_cast<int>(_anisotropyLevels.size()) - 1;
|
||||
for (size_t i = 0; i < _anisotropyLevels.size(); ++i) {
|
||||
if (_anisotropyLevels[i] == graphics.anisotropy) {
|
||||
index = static_cast<int>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ui->anisotropyCombo->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
ui->showSexpHelpMissionEvents->setChecked(_model->getShowSexpHelpMissionEvents());
|
||||
ui->showSexpHelpMissionGoals->setChecked(_model->getShowSexpHelpMissionGoals());
|
||||
ui->showSexpHelpMissionCutscenes->setChecked(_model->getShowSexpHelpMissionCutscenes());
|
||||
@@ -210,6 +305,43 @@ void PreferencesDialog::on_themeCombo_currentIndexChanged(int index) {
|
||||
_model->setThemeMode(themeModeFromIndex(index));
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_enablePostProcessing_toggled(bool checked) {
|
||||
editGraphics(_model.get(), [=](GraphicsSettings& g) { g.enablePostProcessing = checked; });
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_shadowQualityCombo_currentIndexChanged(int index) {
|
||||
editGraphics(_model.get(), [=](GraphicsSettings& g) { g.shadowQuality = static_cast<ShadowQuality>(index); });
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_aaModeCombo_currentIndexChanged(int index) {
|
||||
editGraphics(_model.get(), [=](GraphicsSettings& g) { g.aaMode = static_cast<AntiAliasMode>(index); });
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_msaaCombo_currentIndexChanged(int index) {
|
||||
const auto counts = GraphicsSettings::validMsaaSampleCounts();
|
||||
if (index < 0 || static_cast<size_t>(index) >= counts.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
editGraphics(_model.get(), [=](GraphicsSettings& g) { g.msaaSamples = counts[index]; });
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_textureFilterCombo_currentIndexChanged(int index) {
|
||||
editGraphics(_model.get(), [=](GraphicsSettings& g) { g.textureFilter = index; });
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_anisotropyCombo_currentIndexChanged(int index) {
|
||||
if (index < 0 || static_cast<size_t>(index) >= _anisotropyLevels.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
editGraphics(_model.get(), [=](GraphicsSettings& g) { g.anisotropy = _anisotropyLevels[index]; });
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_gammaSpin_valueChanged(double value) {
|
||||
editGraphics(_model.get(), [=](GraphicsSettings& g) { g.gamma = static_cast<float>(value); });
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_dataMenuStyleCombo_currentIndexChanged(int index) {
|
||||
_model->setDataMenuStyle(static_cast<DataMenuStyle>(index));
|
||||
}
|
||||
|
||||
@@ -35,6 +35,14 @@ private slots:
|
||||
void on_outlineLodCombo_currentIndexChanged(int index);
|
||||
void on_labelFontScaleSpin_valueChanged(double value);
|
||||
void on_themeCombo_currentIndexChanged(int index);
|
||||
// Graphics
|
||||
void on_enablePostProcessing_toggled(bool checked);
|
||||
void on_shadowQualityCombo_currentIndexChanged(int index);
|
||||
void on_aaModeCombo_currentIndexChanged(int index);
|
||||
void on_msaaCombo_currentIndexChanged(int index);
|
||||
void on_textureFilterCombo_currentIndexChanged(int index);
|
||||
void on_anisotropyCombo_currentIndexChanged(int index);
|
||||
void on_gammaSpin_valueChanged(double value);
|
||||
void on_dataMenuStyleCombo_currentIndexChanged(int index);
|
||||
void on_classicMenuLayout_toggled(bool checked);
|
||||
void on_showSexpHelpMissionEvents_toggled(bool checked);
|
||||
@@ -66,6 +74,8 @@ private: // NOLINT(readability-redundant-access-specifiers)
|
||||
|
||||
std::unique_ptr<Ui::PreferencesDialog> ui;
|
||||
std::unique_ptr<PreferencesDialogModel> _model;
|
||||
//! Anisotropy levels backing the combo, in combo order. Queried once; hardware-dependent.
|
||||
SCP_vector<float> _anisotropyLevels;
|
||||
std::map<ControlAction, QKeySequenceEdit*> _controlEditors;
|
||||
FredView* _fredView = nullptr;
|
||||
EditorViewport* _viewport = nullptr;
|
||||
|
||||
@@ -120,6 +120,8 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionLighting_from_Suns"/>
|
||||
<addaction name="actionRender_Full_Detail"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionEnable_Post_Processing"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuSpeed">
|
||||
<property name="title">
|
||||
@@ -917,6 +919,17 @@
|
||||
<string>Render Full Detail</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEnable_Post_Processing">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable Post Processing</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Render the 3D viewport through the game's HDR post-processing pipeline (bloom, tonemapping, lightshafts)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCamera">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -399,6 +399,159 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="graphicsTab">
|
||||
<attribute name="title">
|
||||
<string>Graphics</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="graphicsLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enablePostProcessing">
|
||||
<property name="toolTip">
|
||||
<string>Route the 3D viewport through the game's HDR post-processing pipeline (bloom, tonemapping, lightshafts) instead of drawing straight to the screen. The other settings on this tab only have a visible effect while this is on.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable post-processing in the viewport</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="shadowsAAGroup">
|
||||
<property name="title">
|
||||
<string>Shadows && Anti-Aliasing</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="shadowsAALayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="shadowQualityLabel">
|
||||
<property name="text">
|
||||
<string>Shadow quality:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="shadowQualityCombo">
|
||||
<property name="toolTip">
|
||||
<string>Quality of the shadows cast by the mission's sun in the viewport. Requires post-processing to be enabled. Restarting qtFRED is required for a change to take effect.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="aaModeLabel">
|
||||
<property name="text">
|
||||
<string>Anti-aliasing:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="aaModeCombo">
|
||||
<property name="toolTip">
|
||||
<string>Post-process anti-aliasing mode used in the viewport. Requires post-processing to be enabled.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="msaaLabel">
|
||||
<property name="text">
|
||||
<string>MSAA:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="msaaCombo">
|
||||
<property name="toolTip">
|
||||
<string>Multisample anti-aliasing used for the viewport's 3D scene. Requires post-processing to be enabled. Restarting qtFRED is required for a change to take effect.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="texturesGroup">
|
||||
<property name="title">
|
||||
<string>Textures</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="texturesLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="textureFilterLabel">
|
||||
<property name="text">
|
||||
<string>Texture filtering:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="textureFilterCombo">
|
||||
<property name="toolTip">
|
||||
<string>Mipmap filtering used for textures. Restarting qtFRED is required for a change to take effect.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="anisotropyLabel">
|
||||
<property name="text">
|
||||
<string>Anisotropic filtering:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="anisotropyCombo">
|
||||
<property name="toolTip">
|
||||
<string>Anisotropic texture filtering level. Restarting qtFRED is required for a change to take effect.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="brightnessGroup">
|
||||
<property name="title">
|
||||
<string>Brightness</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="brightnessLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="gammaLabel">
|
||||
<property name="text">
|
||||
<string>Gamma:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="gammaSpin">
|
||||
<property name="toolTip">
|
||||
<string>Brightness of the viewport. qtFRED defaults this higher than the game since the viewport isn't normally tonemapped.</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.10</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>5.00</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.05</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="graphicsSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="gridTab">
|
||||
<attribute name="title">
|
||||
<string>Grid</string>
|
||||
|
||||
Reference in new issue
Block a user