diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp index c68cd6531f..3c3dc4ee12 100644 --- a/src/gl/renderer/gl_postprocess.cpp +++ b/src/gl/renderer/gl_postprocess.cpp @@ -188,7 +188,7 @@ void FGLRenderer::BloomScene() mBlurShader->BlurVertical(mVBO, blurAmount, sampleCount, level0.HTexture, level0.VFramebuffer, level0.Width, level0.Height); // Add bloom back to scene texture: - mBuffers->BindSceneFB(); + mBuffers->BindSceneTextureFB(); glViewport(0, 0, mOutputViewport.width, mOutputViewport.height); glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); diff --git a/src/gl/renderer/gl_renderbuffers.cpp b/src/gl/renderer/gl_renderbuffers.cpp index 8be9e6b075..4863e1ec2c 100644 --- a/src/gl/renderer/gl_renderbuffers.cpp +++ b/src/gl/renderer/gl_renderbuffers.cpp @@ -53,6 +53,7 @@ #include "i_system.h" #include "doomerrors.h" +CVAR(Int, gl_multisample, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); CVAR(Bool, gl_renderbuffers, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); //========================================================================== @@ -74,10 +75,29 @@ FGLRenderBuffers::FGLRenderBuffers() FGLRenderBuffers::~FGLRenderBuffers() { - Clear(); + ClearScene(); + ClearHud(); + ClearBloom(); } -void FGLRenderBuffers::Clear() +void FGLRenderBuffers::ClearScene() +{ + DeleteFrameBuffer(mSceneFB); + DeleteFrameBuffer(mSceneTextureFB); + DeleteRenderBuffer(mSceneMultisample); + DeleteRenderBuffer(mSceneDepthStencil); + DeleteRenderBuffer(mSceneDepth); + DeleteRenderBuffer(mSceneStencil); + DeleteTexture(mSceneTexture); +} + +void FGLRenderBuffers::ClearHud() +{ + DeleteFrameBuffer(mHudFB); + DeleteTexture(mHudTexture); +} + +void FGLRenderBuffers::ClearBloom() { for (int i = 0; i < NumBloomLevels; i++) { @@ -88,16 +108,6 @@ void FGLRenderBuffers::Clear() DeleteTexture(level.VTexture); level = FGLBloomTextureLevel(); } - - DeleteFrameBuffer(mSceneFB); - DeleteTexture(mSceneTexture); - DeleteRenderBuffer(mSceneDepthStencil); - DeleteRenderBuffer(mSceneDepth); - DeleteRenderBuffer(mSceneStencil); - DeleteFrameBuffer(mHudFB); - DeleteTexture(mHudTexture); - mWidth = 0; - mHeight = 0; } void FGLRenderBuffers::DeleteTexture(GLuint &handle) @@ -130,28 +140,80 @@ void FGLRenderBuffers::DeleteFrameBuffer(GLuint &handle) void FGLRenderBuffers::Setup(int width, int height) { - if (width <= mWidth && height <= mHeight) - return; + int samples = GetCvarSamples(); - Clear(); + if (width == mWidth && height == mHeight && mSamples != samples) + { + CreateScene(mWidth, mHeight, samples); + mSamples = samples; + } + else if (width > mWidth || height > mHeight) + { + CreateScene(width, height, samples); + CreateHud(width, height); + CreateBloom(width, height); + mWidth = width; + mHeight = height; + mSamples = samples; + } - GLuint hdrFormat = ((gl.flags & RFL_NO_RGBA16F) != 0) ? GL_RGBA8 : GL_RGBA16F; + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +//========================================================================== +// +// Creates the scene buffers +// +//========================================================================== + +void FGLRenderBuffers::CreateScene(int width, int height, int samples) +{ + ClearScene(); + + mSceneTexture = Create2DTexture(GetHdrFormat(), width, height); + mSceneTextureFB = CreateFrameBuffer(mSceneTexture); + + if (samples > 1) + mSceneMultisample = CreateRenderBuffer(GetHdrFormat(), samples, width, height); - mSceneTexture = Create2DTexture(hdrFormat, width, height); if ((gl.flags & RFL_NO_DEPTHSTENCIL) != 0) { - mSceneDepth = CreateRenderBuffer(GL_DEPTH_COMPONENT24, width, height); - mSceneStencil = CreateRenderBuffer(GL_STENCIL_INDEX8, width, height); - mSceneFB = CreateFrameBuffer(mSceneTexture, mSceneDepth, mSceneStencil); + mSceneDepth = CreateRenderBuffer(GL_DEPTH_COMPONENT24, samples, width, height); + mSceneStencil = CreateRenderBuffer(GL_STENCIL_INDEX8, samples, width, height); + mSceneFB = CreateFrameBuffer(samples > 1 ? mSceneMultisample : mSceneTexture, mSceneDepth, mSceneStencil); } else { - mSceneDepthStencil = CreateRenderBuffer(GL_DEPTH24_STENCIL8, width, height); - mSceneFB = CreateFrameBuffer(mSceneTexture, mSceneDepthStencil); + mSceneDepthStencil = CreateRenderBuffer(GL_DEPTH24_STENCIL8, samples, width, height); + mSceneFB = CreateFrameBuffer(samples > 1 ? mSceneMultisample : mSceneTexture, mSceneDepthStencil); } +} - mHudTexture = Create2DTexture(hdrFormat, width, height); +//========================================================================== +// +// Creates the post-tonemapping-step buffers +// +//========================================================================== + +void FGLRenderBuffers::CreateHud(int width, int height) +{ + ClearHud(); + mHudTexture = Create2DTexture(GetHdrFormat(), width, height); mHudFB = CreateFrameBuffer(mHudTexture); +} + +//========================================================================== +// +// Creates bloom pass working buffers +// +//========================================================================== + +void FGLRenderBuffers::CreateBloom(int width, int height) +{ + ClearBloom(); int bloomWidth = MAX(width / 2, 1); int bloomHeight = MAX(height / 2, 1); @@ -161,22 +223,44 @@ void FGLRenderBuffers::Setup(int width, int height) level.Width = MAX(bloomWidth / 2, 1); level.Height = MAX(bloomHeight / 2, 1); - level.VTexture = Create2DTexture(hdrFormat, level.Width, level.Height); - level.HTexture = Create2DTexture(hdrFormat, level.Width, level.Height); + level.VTexture = Create2DTexture(GetHdrFormat(), level.Width, level.Height); + level.HTexture = Create2DTexture(GetHdrFormat(), level.Width, level.Height); level.VFramebuffer = CreateFrameBuffer(level.VTexture); level.HFramebuffer = CreateFrameBuffer(level.HTexture); bloomWidth = level.Width; bloomHeight = level.Height; } +} - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, 0); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glBindFramebuffer(GL_FRAMEBUFFER, mOutputFB); +//========================================================================== +// +// Fallback support for older OpenGL where RGBA16F might not be available +// +//========================================================================== - mWidth = width; - mHeight = height; +GLuint FGLRenderBuffers::GetHdrFormat() +{ + return ((gl.flags & RFL_NO_RGBA16F) != 0) ? GL_RGBA8 : GL_RGBA16F; +} + +//========================================================================== +// +// Converts the CVAR multisample value into a valid level for OpenGL +// +//========================================================================== + +int FGLRenderBuffers::GetCvarSamples() +{ + int maxSamples = 0; + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + + int samples = clamp((int)gl_multisample, 0, maxSamples); + + int count; + for (count = 0; samples > 0; count++) + samples >>= 1; + return count; } //========================================================================== @@ -215,6 +299,18 @@ GLuint FGLRenderBuffers::CreateRenderBuffer(GLuint format, int width, int height return handle; } +GLuint FGLRenderBuffers::CreateRenderBuffer(GLuint format, int samples, int width, int height) +{ + if (samples <= 1) + return CreateRenderBuffer(format, width, height); + + GLuint handle = 0; + glGenRenderbuffers(1, &handle); + glBindRenderbuffer(GL_RENDERBUFFER, handle); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height); + return handle; +} + //========================================================================== // // Creates a frame buffer @@ -227,6 +323,7 @@ GLuint FGLRenderBuffers::CreateFrameBuffer(GLuint colorbuffer) glGenFramebuffers(1, &handle); glBindFramebuffer(GL_FRAMEBUFFER, handle); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0); + CheckFrameBufferCompleteness(); return handle; } @@ -237,6 +334,7 @@ GLuint FGLRenderBuffers::CreateFrameBuffer(GLuint colorbuffer, GLuint depthstenc glBindFramebuffer(GL_FRAMEBUFFER, handle); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthstencil); + CheckFrameBufferCompleteness(); return handle; } @@ -248,12 +346,59 @@ GLuint FGLRenderBuffers::CreateFrameBuffer(GLuint colorbuffer, GLuint depth, GLu glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencil); + CheckFrameBufferCompleteness(); return handle; } //========================================================================== // -// Makes the scene frame buffer active +// Verifies that the frame buffer setup is valid +// +//========================================================================== + +void FGLRenderBuffers::CheckFrameBufferCompleteness() +{ + GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (result == GL_FRAMEBUFFER_COMPLETE) + return; + + FString error = "glCheckFramebufferStatus failed: "; + switch (result) + { + default: error.AppendFormat("error code %d", (int)result); break; + case GL_FRAMEBUFFER_UNDEFINED: error << "GL_FRAMEBUFFER_UNDEFINED"; break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: error << "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: error << "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: error << "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"; break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: error << "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"; break; + case GL_FRAMEBUFFER_UNSUPPORTED: error << "GL_FRAMEBUFFER_UNSUPPORTED"; break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: error << "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; break; + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: error << "GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS"; break; + } + I_FatalError(error); +} + +//========================================================================== +// +// Resolves the multisample frame buffer by copying it to the scene texture +// +//========================================================================== + +void FGLRenderBuffers::BlitSceneToTexture() +{ + if (mSamples <= 1) + return; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, mSceneFB); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mSceneTextureFB); + glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +//========================================================================== +// +// Makes the scene frame buffer active (multisample, depth, stecil, etc.) // //========================================================================== @@ -262,6 +407,17 @@ void FGLRenderBuffers::BindSceneFB() glBindFramebuffer(GL_FRAMEBUFFER, mSceneFB); } +//========================================================================== +// +// Makes the scene texture frame buffer active (final 2D texture only) +// +//========================================================================== + +void FGLRenderBuffers::BindSceneTextureFB() +{ + glBindFramebuffer(GL_FRAMEBUFFER, mSceneTextureFB); +} + //========================================================================== // // Makes the 2D/HUD frame buffer active @@ -273,7 +429,7 @@ void FGLRenderBuffers::BindHudFB() if (gl_tonemap != 0) glBindFramebuffer(GL_FRAMEBUFFER, mHudFB); else - glBindFramebuffer(GL_FRAMEBUFFER, mSceneFB); + glBindFramebuffer(GL_FRAMEBUFFER, mSceneTextureFB); } //========================================================================== diff --git a/src/gl/renderer/gl_renderbuffers.h b/src/gl/renderer/gl_renderbuffers.h index ba5e94ab33..9c221a42a7 100644 --- a/src/gl/renderer/gl_renderbuffers.h +++ b/src/gl/renderer/gl_renderbuffers.h @@ -21,7 +21,9 @@ public: ~FGLRenderBuffers(); void Setup(int width, int height); + void BlitSceneToTexture(); void BindSceneFB(); + void BindSceneTextureFB(); void BindHudFB(); void BindOutputFB(); void BindSceneTexture(int index); @@ -33,24 +35,37 @@ public: static bool IsEnabled(); private: - void Clear(); + void ClearScene(); + void ClearHud(); + void ClearBloom(); + void CreateScene(int width, int height, int samples); + void CreateHud(int width, int height); + void CreateBloom(int width, int height); GLuint Create2DTexture(GLuint format, int width, int height); GLuint CreateRenderBuffer(GLuint format, int width, int height); + GLuint CreateRenderBuffer(GLuint format, int samples, int width, int height); GLuint CreateFrameBuffer(GLuint colorbuffer); GLuint CreateFrameBuffer(GLuint colorbuffer, GLuint depthstencil); GLuint CreateFrameBuffer(GLuint colorbuffer, GLuint depth, GLuint stencil); + void CheckFrameBufferCompleteness(); void DeleteTexture(GLuint &handle); void DeleteRenderBuffer(GLuint &handle); void DeleteFrameBuffer(GLuint &handle); + int GetCvarSamples(); + GLuint GetHdrFormat(); + int mWidth = 0; int mHeight = 0; + int mSamples = 0; GLuint mSceneTexture = 0; + GLuint mSceneMultisample = 0; GLuint mSceneDepthStencil = 0; GLuint mSceneDepth = 0; GLuint mSceneStencil = 0; GLuint mSceneFB = 0; + GLuint mSceneTextureFB = 0; GLuint mHudTexture = 0; GLuint mHudFB = 0; GLuint mOutputFB = 0; diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index aea69d9416..b28e2c2930 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -868,6 +868,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo if (mainview) EndDrawScene(retval); // do not call this for camera textures. if (toscreen) { + if (FGLRenderBuffers::IsEnabled()) mBuffers->BlitSceneToTexture(); BloomScene(); TonemapScene(); } diff --git a/src/gl/system/gl_cvars.h b/src/gl/system/gl_cvars.h index 27b588e115..30f4cc4f30 100644 --- a/src/gl/system/gl_cvars.h +++ b/src/gl/system/gl_cvars.h @@ -41,9 +41,10 @@ EXTERN_CVAR(Bool, gl_seamless) EXTERN_CVAR(Float, gl_mask_threshold) EXTERN_CVAR(Float, gl_mask_sprite_threshold) -EXTERN_CVAR(Bool, gl_renderbuffers); +EXTERN_CVAR(Bool, gl_renderbuffers) +EXTERN_CVAR(Int, gl_multisample) -EXTERN_CVAR(Bool, gl_bloom); +EXTERN_CVAR(Bool, gl_bloom) EXTERN_CVAR(Float, gl_bloom_amount) EXTERN_CVAR(Int, gl_bloom_kernel_size) EXTERN_CVAR(Int, gl_tonemap) diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index df47661a4c..21aab53a60 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2618,6 +2618,7 @@ GLPREFMNU_AMBLIGHT = "Ambient light level"; GLPREFMNU_RENDERQUALITY = "Rendering quality"; GLPREFMNU_VRMODE = "Stereo 3D VR"; GLPREFMNU_VRQUADSTEREO = "Enable Quad Stereo"; +GLPREFMNU_MULTISAMPLE = "Multisample"; GLPREFMNU_TONEMAP = "Tonemap Mode"; GLPREFMNU_BLOOM = "Bloom effect"; @@ -2644,6 +2645,7 @@ OPTVAL_2X = "2x"; OPTVAL_4X = "4x"; OPTVAL_8X = "8x"; OPTVAL_16X = "16x"; +OPTVAL_32X = "32x"; OPTVAL_USEASPALETTE = "Use as palette"; OPTVAL_BLEND = "Blend"; OPTVAL_STANDARD = "Standard"; diff --git a/wadsrc/static/menudef.z b/wadsrc/static/menudef.z index d49197dad2..bcee4ca01a 100644 --- a/wadsrc/static/menudef.z +++ b/wadsrc/static/menudef.z @@ -63,6 +63,16 @@ OptionValue "Anisotropy" 16, "$OPTVAL_16X" } +OptionValue "Multisample" +{ + 1, "$OPTVAL_OFF" + 2, "$OPTVAL_2X" + 4, "$OPTVAL_4X" + 8, "$OPTVAL_8X" + 16, "$OPTVAL_16X" + 32, "$OPTVAL_32X" +} + OptionValue "Colormaps" { 0, "$OPTVAL_USEASPALETTE" @@ -207,6 +217,7 @@ OptionMenu "GLPrefOptions" Option "$GLPREFMNU_RENDERQUALITY", gl_render_precise, "Precision" Option "$GLPREFMNU_VRMODE", vr_mode, "VRMode" Option "$GLPREFMNU_VRQUADSTEREO", vr_enable_quadbuffered, "OnOff" + Option "$GLPREFMNU_MULTISAMPLE", gl_multisample, "Multisample" Option "$GLPREFMNU_TONEMAP", gl_tonemap, "TonemapModes" Option "$GLPREFMNU_BLOOM", gl_bloom, "OnOff" }