diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 09f3c7d2e..074fa8391 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -613,6 +613,11 @@ file( GLOB HEADER_FILES common/rendering/*.h common/rendering/gl_load/*.h common/rendering/gl/system/*.h + common/rendering/gl/renderer/*.h + common/rendering/gl/shaders/*.h + common/rendering/hwrenderer/data/*.h + common/rendering/hwrenderer/postprocessing/*.h + common/rendering/hwrenderer/utility/*.h build/src/*.h platform/win32/*.h @@ -660,7 +665,6 @@ set (PCH_SOURCES glbackend/glbackend.cpp glbackend/gl_palmanager.cpp glbackend/gl_texture.cpp - glbackend/gl_buffers.cpp glbackend/hw_draw2d.cpp thirdparty/src/base64.cpp @@ -811,13 +815,21 @@ set (PCH_SOURCES #common/input/i_input.cpp common/input/m_joy.cpp - common/rendering/v_video.cpp - common/rendering/v_framebuffer.cpp common/rendering/r_videoscale.cpp - common/rendering/gl_load/gl_interface.cpp + common/rendering/v_framebuffer.cpp + common/rendering/v_video.cpp + common/rendering/gl/renderer/gl_renderer.cpp + common/rendering/gl/renderer/gl_postprocess.cpp + common/rendering/gl/renderer/gl_postprocessstate.cpp + common/rendering/gl/renderer/gl_renderbuffers.cpp + common/rendering/gl/shaders/gl_shaderprogram.cpp + common/rendering/gl/system/gl_buffers.cpp common/rendering/gl/system/gl_debug.cpp common/rendering/gl/system/gl_framebuffer.cpp - + common/rendering/gl_load/gl_interface.cpp + common/rendering/hwrenderer/postprocessing/hw_postprocess.cpp + common/rendering/hwrenderer/postprocessing/hw_postprocess_cvars.cpp + common/rendering/hwrenderer/utility/hw_shaderpatcher.cpp ) if( MSVC ) @@ -882,9 +894,13 @@ include_directories( common/dobject common/menu common/input - common/rendering/gl/system common/rendering/gl_load - common/rendering/gl + common/rendering/gl/system + common/rendering/gl/renderer + common/rendering/gl/shaders + common/rendering/hwrenderer/data + common/rendering/hwrenderer/postprocessing + common/rendering/hwrenderer/utility common/rendering platform diff --git a/source/blood/CMakeLists.txt b/source/blood/CMakeLists.txt index 7c9968e24..dc90a3cc8 100644 --- a/source/blood/CMakeLists.txt +++ b/source/blood/CMakeLists.txt @@ -26,6 +26,14 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../common/music ${CMAKE_CURRENT_SOURCE_DIR}/../common/input ${CMAKE_CURRENT_SOURCE_DIR}/../platform + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl_load + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/system + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/renderer + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/shaders + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/data + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/postprocessing + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/utility + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering ) diff --git a/source/common/gamecvars.cpp b/source/common/gamecvars.cpp index 5fd247310..e6eecd6b5 100644 --- a/source/common/gamecvars.cpp +++ b/source/common/gamecvars.cpp @@ -503,6 +503,7 @@ CUSTOM_CVARD(Float, vid_saturation, 0.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "adju } CVAR(Int, gl_satformula, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); +CVAR(Int, gl_multisample, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); CCMD (bumpgamma) { diff --git a/source/common/gamecvars.h b/source/common/gamecvars.h index 9efc90ee1..a31d8b741 100644 --- a/source/common/gamecvars.h +++ b/source/common/gamecvars.h @@ -82,7 +82,7 @@ EXTERN_CVAR(Int, r_upscalefactor) EXTERN_CVAR(Float, vid_gamma) EXTERN_CVAR(Float, vid_contrast) EXTERN_CVAR(Float, vid_brightness) - +EXTERN_CVAR(Int, gl_multisample) EXTERN_CVAR(Bool, in_joystick) EXTERN_CVAR(Int, in_mouse) diff --git a/source/common/rendering/gl/renderer/gl_postprocess.cpp b/source/common/rendering/gl/renderer/gl_postprocess.cpp new file mode 100644 index 000000000..9d092756f --- /dev/null +++ b/source/common/rendering/gl/renderer/gl_postprocess.cpp @@ -0,0 +1,255 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2016 Magnus Norddahl +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_postprocess.cpp +** Post processing effects in the render pipeline +** +*/ + +#include "gl_load/gl_system.h" +#include "m_png.h" +#include "gl/system/gl_buffers.h" +#include "gl/system/gl_framebuffer.h" +#include "gamecvars.h" +#include "gl/system/gl_debug.h" +//#include "gl/renderer/gl_renderstate.h" +#include "gl/renderer/gl_renderbuffers.h" +#include "gl/renderer/gl_renderer.h" +#include "gl/renderer/gl_postprocessstate.h" +#include "gl/shaders/gl_shaderprogram.h" +#include "hwrenderer/postprocessing/hw_postprocess.h" +#include "hwrenderer/postprocessing/hw_postprocess_cvars.h" +//#include "hwrenderer/data/flatvertices.h" +#include "r_videoscale.h" + +extern bool vid_hdr_active; + +CVAR(Int, gl_dither_bpc, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) + +namespace OpenGLRenderer +{ + + +void FGLRenderer::RenderScreenQuad() +{ + // The buffer here needs to be enabled later again, of course. +#if 0 + auto buffer = static_cast(screen->mVertexData->GetBufferObjects().first); + buffer->Bind(nullptr); + glDrawArrays(GL_TRIANGLE_STRIP, FFlatVertexBuffer::PRESENT_INDEX, 4); +#else + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0, 0); + glVertex2f(-1, -1); + glTexCoord2f(0, 1); + glVertex2f(-1, 1); + glTexCoord2f(1, 0); + glVertex2f(1, -1); + glTexCoord2f(1, 1); + glVertex2f(1,1); + glEnd(); +#endif +} + +void FGLRenderer::PostProcessScene(int fixedcm, const std::function &afterBloomDrawEndScene2D) +{ + int sceneWidth = mBuffers->GetSceneWidth(); + int sceneHeight = mBuffers->GetSceneHeight(); + + GLPPRenderState renderstate(mBuffers); + + hw_postprocess.Pass1(&renderstate, fixedcm, sceneWidth, sceneHeight); + mBuffers->BindCurrentFB(); + afterBloomDrawEndScene2D(); + hw_postprocess.Pass2(&renderstate, fixedcm, sceneWidth, sceneHeight); +} + +//----------------------------------------------------------------------------- +// +// Adds ambient occlusion to the scene +// +//----------------------------------------------------------------------------- + +void FGLRenderer::AmbientOccludeScene(float m5) +{ + int sceneWidth = mBuffers->GetSceneWidth(); + int sceneHeight = mBuffers->GetSceneHeight(); + + GLPPRenderState renderstate(mBuffers); + hw_postprocess.ssao.Render(&renderstate, m5, sceneWidth, sceneHeight); +} + +void FGLRenderer::BlurScene(float gameinfobluramount) +{ + int sceneWidth = mBuffers->GetSceneWidth(); + int sceneHeight = mBuffers->GetSceneHeight(); + + GLPPRenderState renderstate(mBuffers); + + hw_postprocess.bloom.RenderBlur(&renderstate, sceneWidth, sceneHeight, gameinfobluramount); +} + +void FGLRenderer::ClearTonemapPalette() +{ + hw_postprocess.tonemap.ClearTonemapPalette(); +} + +//----------------------------------------------------------------------------- +// +// Copies the rendered screen to its final destination +// +//----------------------------------------------------------------------------- + +void FGLRenderer::Flush() +{ + CopyToBackbuffer(nullptr, true); +} + +//----------------------------------------------------------------------------- +// +// Gamma correct while copying to frame buffer +// +//----------------------------------------------------------------------------- + +void FGLRenderer::CopyToBackbuffer(const IntRect *bounds, bool applyGamma) +{ + //screen->Draw2D(); // draw all pending 2D stuff before copying the buffer + //screen->Clear2D(); + + GLPPRenderState renderstate(mBuffers); + + FGLDebug::PushGroup("CopyToBackbuffer"); + FGLPostProcessState savedState; + savedState.SaveTextureBindings(2); + mBuffers->BindOutputFB(); + + IntRect box; + if (bounds) + { + box = *bounds; + } + else + { + ClearBorders(); + box = screen->mOutputLetterbox; + } + + mBuffers->BindCurrentTexture(0); + DrawPresentTexture(box, applyGamma); + FGLDebug::PopGroup(); +} + +void FGLRenderer::DrawPresentTexture(const IntRect &box, bool applyGamma) +{ + glViewport(box.left, box.top, box.width, box.height); + + mBuffers->BindDitherTexture(1); + + glActiveTexture(GL_TEXTURE0); + if (ViewportLinearScale()) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + + mPresentShader->Bind(); + if (!applyGamma || framebuffer->IsHWGammaActive()) + { + mPresentShader->Uniforms->InvGamma = 1.0f; + mPresentShader->Uniforms->Contrast = 1.0f; + mPresentShader->Uniforms->Brightness = 0.0f; + mPresentShader->Uniforms->Saturation = 1.0f; + } + else + { + mPresentShader->Uniforms->InvGamma = 1.0f / clamp(vid_gamma, 0.1f, 4.f); + mPresentShader->Uniforms->Contrast = clamp(vid_contrast, 0.1f, 3.f); + mPresentShader->Uniforms->Brightness = clamp(vid_brightness, -0.8f, 0.8f); + mPresentShader->Uniforms->Saturation = clamp(vid_saturation, -15.0f, 15.f); + mPresentShader->Uniforms->GrayFormula = static_cast(gl_satformula); + } + if (vid_hdr_active && framebuffer->IsFullscreen()) + { + // Full screen exclusive mode treats a rgba16f frame buffer as linear. + // It probably will eventually in desktop mode too, but the DWM doesn't seem to support that. + mPresentShader->Uniforms->HdrMode = 1; + mPresentShader->Uniforms->ColorScale = (gl_dither_bpc == -1) ? 1023.0f : (float)((1 << gl_dither_bpc) - 1); + } + else + { + mPresentShader->Uniforms->HdrMode = 0; + mPresentShader->Uniforms->ColorScale = (gl_dither_bpc == -1) ? 255.0f : (float)((1 << gl_dither_bpc) - 1); + } + mPresentShader->Uniforms->Scale = { screen->mScreenViewport.width / (float)mBuffers->GetWidth(), screen->mScreenViewport.height / (float)mBuffers->GetHeight() }; + mPresentShader->Uniforms->Offset = { 0.0f, 0.0f }; + mPresentShader->Uniforms.SetData(); + static_cast(mPresentShader->Uniforms.GetBuffer())->BindBase(); + RenderScreenQuad(); +} + +//----------------------------------------------------------------------------- +// +// Fills the black bars around the screen letterbox +// +//----------------------------------------------------------------------------- + +void FGLRenderer::ClearBorders() +{ + const auto &box = screen->mOutputLetterbox; + + int clientWidth = framebuffer->GetClientWidth(); + int clientHeight = framebuffer->GetClientHeight(); + if (clientWidth == 0 || clientHeight == 0) + return; + + glViewport(0, 0, clientWidth, clientHeight); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glEnable(GL_SCISSOR_TEST); + if (box.top > 0) + { + glScissor(0, 0, clientWidth, box.top); + glClear(GL_COLOR_BUFFER_BIT); + } + if (clientHeight - box.top - box.height > 0) + { + glScissor(0, box.top + box.height, clientWidth, clientHeight - box.top - box.height); + glClear(GL_COLOR_BUFFER_BIT); + } + if (box.left > 0) + { + glScissor(0, box.top, box.left, box.height); + glClear(GL_COLOR_BUFFER_BIT); + } + if (clientWidth - box.left - box.width > 0) + { + glScissor(box.left + box.width, box.top, clientWidth - box.left - box.width, box.height); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); +} + +} \ No newline at end of file diff --git a/source/common/rendering/gl/renderer/gl_postprocessstate.cpp b/source/common/rendering/gl/renderer/gl_postprocessstate.cpp new file mode 100644 index 000000000..e74041f9e --- /dev/null +++ b/source/common/rendering/gl/renderer/gl_postprocessstate.cpp @@ -0,0 +1,140 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2016 Magnus Norddahl +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_postprocessstate.cpp +** Render state maintenance +** +**/ + +#include "templates.h" +#include "gl_load/gl_system.h" +#include "gl_load/gl_interface.h" +#include "gl/renderer/gl_postprocessstate.h" + +namespace OpenGLRenderer +{ + +//----------------------------------------------------------------------------- +// +// Saves state modified by post processing shaders +// +//----------------------------------------------------------------------------- + +FGLPostProcessState::FGLPostProcessState() +{ + glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTex); + glActiveTexture(GL_TEXTURE0); + SaveTextureBindings(1); + + glGetBooleanv(GL_BLEND, &blendEnabled); + glGetBooleanv(GL_SCISSOR_TEST, &scissorEnabled); + glGetBooleanv(GL_DEPTH_TEST, &depthEnabled); + glGetBooleanv(GL_MULTISAMPLE, &multisampleEnabled); + glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); + glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRgb); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha); + glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRgb); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha); + glGetIntegerv(GL_BLEND_DST_RGB, &blendDestRgb); + glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDestAlpha); + + glDisable(GL_MULTISAMPLE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); +} + +void FGLPostProcessState::SaveTextureBindings(unsigned int numUnits) +{ + while (textureBinding.Size() < numUnits) + { + unsigned int i = textureBinding.Size(); + + GLint texture; + glActiveTexture(GL_TEXTURE0 + i); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture); + glBindTexture(GL_TEXTURE_2D, 0); + textureBinding.Push(texture); + + GLint sampler; + glGetIntegerv(GL_SAMPLER_BINDING, &sampler); + glBindSampler(i, 0); + samplerBinding.Push(sampler); + } + glActiveTexture(GL_TEXTURE0); +} + +//----------------------------------------------------------------------------- +// +// Restores state at the end of post processing +// +//----------------------------------------------------------------------------- + +FGLPostProcessState::~FGLPostProcessState() +{ + if (blendEnabled) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + + if (scissorEnabled) + glEnable(GL_SCISSOR_TEST); + else + glDisable(GL_SCISSOR_TEST); + + if (depthEnabled) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + + if (multisampleEnabled) + glEnable(GL_MULTISAMPLE); + else + glDisable(GL_MULTISAMPLE); + + glBlendEquationSeparate(blendEquationRgb, blendEquationAlpha); + glBlendFuncSeparate(blendSrcRgb, blendDestRgb, blendSrcAlpha, blendDestAlpha); + + glUseProgram(currentProgram); + + // Fully unbind to avoid incomplete texture warnings from Nvidia's driver when gl_debug_level 4 is active + for (unsigned int i = 0; i < textureBinding.Size(); i++) + { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, 0); + } + + for (unsigned int i = 0; i < samplerBinding.Size(); i++) + { + glBindSampler(i, samplerBinding[i]); + } + + for (unsigned int i = 0; i < textureBinding.Size(); i++) + { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, textureBinding[i]); + } + + glActiveTexture(activeTex); +} + +} \ No newline at end of file diff --git a/source/common/rendering/gl/renderer/gl_postprocessstate.h b/source/common/rendering/gl/renderer/gl_postprocessstate.h new file mode 100644 index 000000000..70f911691 --- /dev/null +++ b/source/common/rendering/gl/renderer/gl_postprocessstate.h @@ -0,0 +1,41 @@ +#ifndef __GL_POSTPROCESSSTATE_H +#define __GL_POSTPROCESSSTATE_H + +#include +#include "gl_load/gl_interface.h" +#include "matrix.h" +#include "c_cvars.h" + +namespace OpenGLRenderer +{ + +class FGLPostProcessState +{ +public: + FGLPostProcessState(); + ~FGLPostProcessState(); + + void SaveTextureBindings(unsigned int numUnits); + +private: + FGLPostProcessState(const FGLPostProcessState &) = delete; + FGLPostProcessState &operator=(const FGLPostProcessState &) = delete; + + GLint activeTex; + TArray textureBinding; + TArray samplerBinding; + GLboolean blendEnabled; + GLboolean scissorEnabled; + GLboolean depthEnabled; + GLboolean multisampleEnabled; + GLint currentProgram; + GLint blendEquationRgb; + GLint blendEquationAlpha; + GLint blendSrcRgb; + GLint blendSrcAlpha; + GLint blendDestRgb; + GLint blendDestAlpha; +}; + +} +#endif diff --git a/source/common/rendering/gl/renderer/gl_renderbuffers.cpp b/source/common/rendering/gl/renderer/gl_renderbuffers.cpp new file mode 100644 index 000000000..8dd9f25b5 --- /dev/null +++ b/source/common/rendering/gl/renderer/gl_renderbuffers.cpp @@ -0,0 +1,933 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2016 Magnus Norddahl +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_renderbuffers.cpp +** Render buffers used during rendering +** +*/ + +#include "gl_load/gl_system.h" +#include "v_video.h" +#include "printf.h" +#include "gl_load/gl_interface.h" +#include "gamecvars.h" +#include "gl/system/gl_debug.h" +#include "gl/renderer/gl_renderer.h" +#include "gl/renderer/gl_renderbuffers.h" +#include "gl/renderer/gl_postprocessstate.h" +#include "gl/shaders/gl_shaderprogram.h" +#include "gl/system/gl_buffers.h" +#include + +EXTERN_CVAR(Int, gl_debug_level) + + +namespace OpenGLRenderer +{ + +//========================================================================== +// +// Initialize render buffers and textures used in rendering passes +// +//========================================================================== + +FGLRenderBuffers::FGLRenderBuffers() +{ + glGetIntegerv(GL_MAX_SAMPLES, &mMaxSamples); +} + +//========================================================================== +// +// Free render buffer resources +// +//========================================================================== + +FGLRenderBuffers::~FGLRenderBuffers() +{ + ClearScene(); + ClearPipeline(); + ClearEyeBuffers(); + DeleteTexture(mDitherTexture); +} + +void FGLRenderBuffers::ClearScene() +{ + DeleteFrameBuffer(mSceneFB); + DeleteFrameBuffer(mSceneDataFB); + DeleteRenderBuffer(mSceneMultisampleBuf); + DeleteRenderBuffer(mSceneFogBuf); + DeleteRenderBuffer(mSceneNormalBuf); + DeleteRenderBuffer(mSceneDepthStencilBuf); +} + +void FGLRenderBuffers::ClearPipeline() +{ + for (int i = 0; i < NumPipelineTextures; i++) + { + DeleteFrameBuffer(mPipelineFB[i]); + DeleteTexture(mPipelineTexture[i]); + } +} + +void FGLRenderBuffers::ClearEyeBuffers() +{ + for (auto handle : mEyeFBs) + DeleteFrameBuffer(handle); + + for (auto handle : mEyeTextures) + DeleteTexture(handle); + + mEyeTextures.Clear(); + mEyeFBs.Clear(); +} + +void FGLRenderBuffers::DeleteTexture(PPGLTexture &tex) +{ + if (tex.handle != 0) + glDeleteTextures(1, &tex.handle); + tex.handle = 0; +} + +void FGLRenderBuffers::DeleteRenderBuffer(PPGLRenderBuffer &buf) +{ + if (buf.handle != 0) + glDeleteRenderbuffers(1, &buf.handle); + buf.handle = 0; +} + +void FGLRenderBuffers::DeleteFrameBuffer(PPGLFrameBuffer &fb) +{ + if (fb.handle != 0) + glDeleteFramebuffers(1, &fb.handle); + fb.handle = 0; +} + +//========================================================================== +// +// Makes sure all render buffers have sizes suitable for rending at the +// specified resolution +// +//========================================================================== + +void FGLRenderBuffers::Setup(int width, int height, int sceneWidth, int sceneHeight) +{ + if (width <= 0 || height <= 0) + I_FatalError("Requested invalid render buffer sizes: screen = %dx%d", width, height); + + int samples = clamp((int)gl_multisample, 0, mMaxSamples); + + GLint activeTex; + GLint textureBinding; + glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTex); + glActiveTexture(GL_TEXTURE0); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding); + + if (width != mWidth || height != mHeight) + CreatePipeline(width, height); + + if (width != mWidth || height != mHeight || mSamples != samples) + CreateScene(width, height, samples, false); + + mWidth = width; + mHeight = height; + mSamples = samples; + mSceneWidth = sceneWidth; + mSceneHeight = sceneHeight; + + glBindTexture(GL_TEXTURE_2D, textureBinding); + glActiveTexture(activeTex); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (FailedCreate) + { + ClearScene(); + ClearPipeline(); + ClearEyeBuffers(); + mWidth = 0; + mHeight = 0; + mSamples = 0; + mSceneWidth = 0; + mSceneHeight = 0; + I_FatalError("Unable to create render buffers."); + } +} + +//========================================================================== +// +// Creates the scene buffers +// +//========================================================================== + +void FGLRenderBuffers::CreateScene(int width, int height, int samples, bool needsSceneTextures) +{ + ClearScene(); + + if (samples > 1) + { + if (needsSceneTextures) + { + mSceneMultisampleTex = Create2DMultisampleTexture("SceneMultisample", GL_RGBA16F, width, height, samples, false); + mSceneDepthStencilTex = Create2DMultisampleTexture("SceneDepthStencil", GL_DEPTH24_STENCIL8, width, height, samples, false); + mSceneFogTex = Create2DMultisampleTexture("SceneFog", GL_RGBA8, width, height, samples, false); + mSceneNormalTex = Create2DMultisampleTexture("SceneNormal", GL_RGB10_A2, width, height, samples, false); + mSceneFB = CreateFrameBuffer("SceneFB", mSceneMultisampleTex, {}, {}, mSceneDepthStencilTex, true); + mSceneDataFB = CreateFrameBuffer("SceneGBufferFB", mSceneMultisampleTex, mSceneFogTex, mSceneNormalTex, mSceneDepthStencilTex, true); + } + else + { + mSceneMultisampleBuf = CreateRenderBuffer("SceneMultisample", GL_RGBA16F, width, height, samples); + mSceneDepthStencilBuf = CreateRenderBuffer("SceneDepthStencil", GL_DEPTH24_STENCIL8, width, height, samples); + mSceneFB = CreateFrameBuffer("SceneFB", mSceneMultisampleBuf, mSceneDepthStencilBuf); + mSceneDataFB = CreateFrameBuffer("SceneGBufferFB", mSceneMultisampleBuf, mSceneDepthStencilBuf); + } + } + else + { + if (needsSceneTextures) + { + mSceneDepthStencilTex = Create2DTexture("SceneDepthStencil", GL_DEPTH24_STENCIL8, width, height); + mSceneFogTex = Create2DTexture("SceneFog", GL_RGBA8, width, height); + mSceneNormalTex = Create2DTexture("SceneNormal", GL_RGB10_A2, width, height); + mSceneFB = CreateFrameBuffer("SceneFB", mPipelineTexture[0], {}, {}, mSceneDepthStencilTex, false); + mSceneDataFB = CreateFrameBuffer("SceneGBufferFB", mPipelineTexture[0], mSceneFogTex, mSceneNormalTex, mSceneDepthStencilTex, false); + } + else + { + mSceneDepthStencilBuf = CreateRenderBuffer("SceneDepthStencil", GL_DEPTH24_STENCIL8, width, height); + mSceneFB = CreateFrameBuffer("SceneFB", mPipelineTexture[0], mSceneDepthStencilBuf); + mSceneDataFB = CreateFrameBuffer("SceneGBufferFB", mPipelineTexture[0], mSceneDepthStencilBuf); + } + } +} + +//========================================================================== +// +// Creates the buffers needed for post processing steps +// +//========================================================================== + +void FGLRenderBuffers::CreatePipeline(int width, int height) +{ + ClearPipeline(); + ClearEyeBuffers(); + + for (int i = 0; i < NumPipelineTextures; i++) + { + mPipelineTexture[i] = Create2DTexture("PipelineTexture", GL_RGBA16F, width, height); + mPipelineFB[i] = CreateFrameBuffer("PipelineFB", mPipelineTexture[i]); + } +} + +//========================================================================== +// +// Creates eye buffers if needed +// +//========================================================================== + +void FGLRenderBuffers::CreateEyeBuffers(int eye) +{ + if (mEyeFBs.Size() > unsigned(eye)) + return; + + GLint activeTex, textureBinding, frameBufferBinding; + glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTex); + glActiveTexture(GL_TEXTURE0); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &frameBufferBinding); + + while (mEyeFBs.Size() <= unsigned(eye)) + { + PPGLTexture texture = Create2DTexture("EyeTexture", GL_RGBA16F, mWidth, mHeight); + mEyeTextures.Push(texture); + mEyeFBs.Push(CreateFrameBuffer("EyeFB", texture)); + } + + glBindTexture(GL_TEXTURE_2D, textureBinding); + glActiveTexture(activeTex); + glBindFramebuffer(GL_FRAMEBUFFER, frameBufferBinding); +} + +//========================================================================== +// +// Creates a 2D texture defaulting to linear filtering and clamp to edge +// +//========================================================================== + +PPGLTexture FGLRenderBuffers::Create2DTexture(const char *name, GLuint format, int width, int height, const void *data) +{ + PPGLTexture tex; + tex.Width = width; + tex.Height = height; + glGenTextures(1, &tex.handle); + glBindTexture(GL_TEXTURE_2D, tex.handle); + FGLDebug::LabelObject(GL_TEXTURE, tex.handle, name); + + GLenum dataformat = 0, datatype = 0; + switch (format) + { + case GL_RGBA8: dataformat = GL_RGBA; datatype = GL_UNSIGNED_BYTE; break; + case GL_RGBA16: dataformat = GL_RGBA; datatype = GL_UNSIGNED_SHORT; break; + case GL_RGBA16F: dataformat = GL_RGBA; datatype = GL_FLOAT; break; + case GL_RGBA32F: dataformat = GL_RGBA; datatype = GL_FLOAT; break; + case GL_RGBA16_SNORM: dataformat = GL_RGBA; datatype = GL_SHORT; break; + case GL_R32F: dataformat = GL_RED; datatype = GL_FLOAT; break; + case GL_R16F: dataformat = GL_RED; datatype = GL_FLOAT; break; + case GL_RG32F: dataformat = GL_RG; datatype = GL_FLOAT; break; + case GL_RG16F: dataformat = GL_RG; datatype = GL_FLOAT; break; + case GL_RGB10_A2: dataformat = GL_RGBA; datatype = GL_UNSIGNED_INT_10_10_10_2; break; + case GL_DEPTH_COMPONENT24: dataformat = GL_DEPTH_COMPONENT; datatype = GL_FLOAT; break; + case GL_STENCIL_INDEX8: dataformat = GL_STENCIL_INDEX; datatype = GL_INT; break; + case GL_DEPTH24_STENCIL8: dataformat = GL_DEPTH_STENCIL; datatype = GL_UNSIGNED_INT_24_8; break; + default: I_FatalError("Unknown format passed to FGLRenderBuffers.Create2DTexture"); + } + + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, dataformat, datatype, data); + 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); + return tex; +} + +PPGLTexture FGLRenderBuffers::Create2DMultisampleTexture(const char *name, GLuint format, int width, int height, int samples, bool fixedSampleLocations) +{ + PPGLTexture tex; + tex.Width = width; + tex.Height = height; + glGenTextures(1, &tex.handle); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex.handle); + FGLDebug::LabelObject(GL_TEXTURE, tex.handle, name); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, format, width, height, fixedSampleLocations); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); + return tex; +} + +//========================================================================== +// +// Creates a render buffer +// +//========================================================================== + +PPGLRenderBuffer FGLRenderBuffers::CreateRenderBuffer(const char *name, GLuint format, int width, int height) +{ + PPGLRenderBuffer buf; + glGenRenderbuffers(1, &buf.handle); + glBindRenderbuffer(GL_RENDERBUFFER, buf.handle); + FGLDebug::LabelObject(GL_RENDERBUFFER, buf.handle, name); + glRenderbufferStorage(GL_RENDERBUFFER, format, width, height); + return buf; +} + +PPGLRenderBuffer FGLRenderBuffers::CreateRenderBuffer(const char *name, GLuint format, int width, int height, int samples) +{ + if (samples <= 1) + return CreateRenderBuffer(name, format, width, height); + + PPGLRenderBuffer buf; + glGenRenderbuffers(1, &buf.handle); + glBindRenderbuffer(GL_RENDERBUFFER, buf.handle); + FGLDebug::LabelObject(GL_RENDERBUFFER, buf.handle, name); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height); + return buf; +} + +//========================================================================== +// +// Creates a frame buffer +// +//========================================================================== + +PPGLFrameBuffer FGLRenderBuffers::CreateFrameBuffer(const char *name, PPGLTexture colorbuffer) +{ + PPGLFrameBuffer fb; + glGenFramebuffers(1, &fb.handle); + glBindFramebuffer(GL_FRAMEBUFFER, fb.handle); + FGLDebug::LabelObject(GL_FRAMEBUFFER, fb.handle, name); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer.handle, 0); + if (CheckFrameBufferCompleteness()) + ClearFrameBuffer(false, false); + return fb; +} + +PPGLFrameBuffer FGLRenderBuffers::CreateFrameBuffer(const char *name, PPGLTexture colorbuffer, PPGLRenderBuffer depthstencil) +{ + PPGLFrameBuffer fb; + glGenFramebuffers(1, &fb.handle); + glBindFramebuffer(GL_FRAMEBUFFER, fb.handle); + FGLDebug::LabelObject(GL_FRAMEBUFFER, fb.handle, name); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer.handle, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthstencil.handle); + if (CheckFrameBufferCompleteness()) + ClearFrameBuffer(true, true); + return fb; +} + +PPGLFrameBuffer FGLRenderBuffers::CreateFrameBuffer(const char *name, PPGLRenderBuffer colorbuffer, PPGLRenderBuffer depthstencil) +{ + PPGLFrameBuffer fb; + glGenFramebuffers(1, &fb.handle); + glBindFramebuffer(GL_FRAMEBUFFER, fb.handle); + FGLDebug::LabelObject(GL_FRAMEBUFFER, fb.handle, name); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer.handle); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthstencil.handle); + if (CheckFrameBufferCompleteness()) + ClearFrameBuffer(true, true); + return fb; +} + +PPGLFrameBuffer FGLRenderBuffers::CreateFrameBuffer(const char *name, PPGLTexture colorbuffer0, PPGLTexture colorbuffer1, PPGLTexture colorbuffer2, PPGLTexture depthstencil, bool multisample) +{ + PPGLFrameBuffer fb; + glGenFramebuffers(1, &fb.handle); + glBindFramebuffer(GL_FRAMEBUFFER, fb.handle); + FGLDebug::LabelObject(GL_FRAMEBUFFER, fb.handle, name); + if (multisample) + { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, colorbuffer0.handle, 0); + if (colorbuffer1.handle != 0) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D_MULTISAMPLE, colorbuffer1.handle, 0); + if (colorbuffer2.handle != 0) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D_MULTISAMPLE, colorbuffer2.handle, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, depthstencil.handle, 0); + } + else + { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer0.handle, 0); + if (colorbuffer1.handle != 0) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, colorbuffer1.handle, 0); + if (colorbuffer2.handle != 0) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, colorbuffer2.handle, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthstencil.handle, 0); + } + if (CheckFrameBufferCompleteness()) + ClearFrameBuffer(true, true); + return fb; +} + +//========================================================================== +// +// Verifies that the frame buffer setup is valid +// +//========================================================================== + +bool FGLRenderBuffers::CheckFrameBufferCompleteness() +{ + GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (result == GL_FRAMEBUFFER_COMPLETE) + return true; + + bool FailedCreate = true; + + if (gl_debug_level > 0) + { + 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; + } + Printf("%s\n", error.GetChars()); + } + + return false; +} + +//========================================================================== +// +// Clear frame buffer to make sure it never contains uninitialized data +// +//========================================================================== + +void FGLRenderBuffers::ClearFrameBuffer(bool stencil, bool depth) +{ + GLboolean scissorEnabled; + GLint stencilValue; + GLdouble depthValue; + glGetBooleanv(GL_SCISSOR_TEST, &scissorEnabled); + glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &stencilValue); + glGetDoublev(GL_DEPTH_CLEAR_VALUE, &depthValue); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClearDepth(0.0); + glClearStencil(0); + GLenum flags = GL_COLOR_BUFFER_BIT; + if (stencil) + flags |= GL_STENCIL_BUFFER_BIT; + if (depth) + flags |= GL_DEPTH_BUFFER_BIT; + glClear(flags); + glClearStencil(stencilValue); + glClearDepth(depthValue); + if (scissorEnabled) + glEnable(GL_SCISSOR_TEST); +} + +//========================================================================== +// +// Resolves the multisample frame buffer by copying it to the first pipeline texture +// +//========================================================================== + +void FGLRenderBuffers::BlitSceneToTexture() +{ + mCurrentPipelineTexture = 0; + + if (mSamples <= 1) + return; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, mSceneFB.handle); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mPipelineFB[mCurrentPipelineTexture].handle); + glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + if ((gl.flags & RFL_INVALIDATE_BUFFER) != 0) + { + GLenum attachments[2] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_STENCIL_ATTACHMENT }; + glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 2, attachments); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +//========================================================================== +// +// Eye textures and their frame buffers +// +//========================================================================== + +void FGLRenderBuffers::BlitToEyeTexture(int eye, bool allowInvalidate) +{ + CreateEyeBuffers(eye); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, mPipelineFB[mCurrentPipelineTexture].handle); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mEyeFBs[eye].handle); + glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + if ((gl.flags & RFL_INVALIDATE_BUFFER) != 0 && allowInvalidate) + { + GLenum attachments[2] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_STENCIL_ATTACHMENT }; + glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 2, attachments); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void FGLRenderBuffers::BlitFromEyeTexture(int eye) +{ + if (mEyeFBs.Size() <= unsigned(eye)) return; + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mPipelineFB[mCurrentPipelineTexture].handle); + glBindFramebuffer(GL_READ_FRAMEBUFFER, mEyeFBs[eye].handle); + glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + if ((gl.flags & RFL_INVALIDATE_BUFFER) != 0) + { + GLenum attachments[2] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_STENCIL_ATTACHMENT }; + glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 2, attachments); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void FGLRenderBuffers::BindEyeTexture(int eye, int texunit) +{ + CreateEyeBuffers(eye); + glActiveTexture(GL_TEXTURE0 + texunit); + glBindTexture(GL_TEXTURE_2D, mEyeTextures[eye].handle); +} + +void FGLRenderBuffers::BindDitherTexture(int texunit) +{ + if (!mDitherTexture) + { + static const float data[64] = + { + .0078125, .2578125, .1328125, .3828125, .0234375, .2734375, .1484375, .3984375, + .7578125, .5078125, .8828125, .6328125, .7734375, .5234375, .8984375, .6484375, + .0703125, .3203125, .1953125, .4453125, .0859375, .3359375, .2109375, .4609375, + .8203125, .5703125, .9453125, .6953125, .8359375, .5859375, .9609375, .7109375, + .0390625, .2890625, .1640625, .4140625, .0546875, .3046875, .1796875, .4296875, + .7890625, .5390625, .9140625, .6640625, .8046875, .5546875, .9296875, .6796875, + .1015625, .3515625, .2265625, .4765625, .1171875, .3671875, .2421875, .4921875, + .8515625, .6015625, .9765625, .7265625, .8671875, .6171875, .9921875, .7421875, + }; + + glActiveTexture(GL_TEXTURE0 + texunit); + mDitherTexture = Create2DTexture("DitherTexture", GL_R32F, 8, 8, data); + } + mDitherTexture.Bind(1, GL_NEAREST, GL_REPEAT); +} + +//========================================================================== +// +// Makes the scene frame buffer active (multisample, depth, stecil, etc.) +// +//========================================================================== + +void FGLRenderBuffers::BindSceneFB(bool sceneData) +{ + glBindFramebuffer(GL_FRAMEBUFFER, sceneData ? mSceneDataFB.handle : mSceneFB.handle); +} + +//========================================================================== +// +// Binds the scene color texture to the specified texture unit +// +//========================================================================== + +void FGLRenderBuffers::BindSceneColorTexture(int index) +{ + glActiveTexture(GL_TEXTURE0 + index); + if (mSamples > 1) + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mSceneMultisampleTex.handle); + else + glBindTexture(GL_TEXTURE_2D, mPipelineTexture[0].handle); +} + +//========================================================================== +// +// Binds the scene fog data texture to the specified texture unit +// +//========================================================================== + +void FGLRenderBuffers::BindSceneFogTexture(int index) +{ + glActiveTexture(GL_TEXTURE0 + index); + if (mSamples > 1) + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mSceneFogTex.handle); + else + glBindTexture(GL_TEXTURE_2D, mSceneFogTex.handle); +} + +//========================================================================== +// +// Binds the scene normal data texture to the specified texture unit +// +//========================================================================== + +void FGLRenderBuffers::BindSceneNormalTexture(int index) +{ + glActiveTexture(GL_TEXTURE0 + index); + if (mSamples > 1) + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mSceneNormalTex.handle); + else + glBindTexture(GL_TEXTURE_2D, mSceneNormalTex.handle); +} + +//========================================================================== +// +// Binds the depth texture to the specified texture unit +// +//========================================================================== + +void FGLRenderBuffers::BindSceneDepthTexture(int index) +{ + glActiveTexture(GL_TEXTURE0 + index); + if (mSamples > 1) + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mSceneDepthStencilTex.handle); + else + glBindTexture(GL_TEXTURE_2D, mSceneDepthStencilTex.handle); +} + +//========================================================================== +// +// Binds the current scene/effect/hud texture to the specified texture unit +// +//========================================================================== + +void FGLRenderBuffers::BindCurrentTexture(int index, int filter, int wrap) +{ + mPipelineTexture[mCurrentPipelineTexture].Bind(index, filter, wrap); +} + +//========================================================================== +// +// Makes the frame buffer for the current texture active +// +//========================================================================== + +void FGLRenderBuffers::BindCurrentFB() +{ + mPipelineFB[mCurrentPipelineTexture].Bind(); +} + +//========================================================================== +// +// Makes the frame buffer for the next texture active +// +//========================================================================== + +void FGLRenderBuffers::BindNextFB() +{ + int out = (mCurrentPipelineTexture + 1) % NumPipelineTextures; + mPipelineFB[out].Bind(); +} + +//========================================================================== +// +// Next pipeline texture now contains the output +// +//========================================================================== + +void FGLRenderBuffers::NextTexture() +{ + mCurrentPipelineTexture = (mCurrentPipelineTexture + 1) % NumPipelineTextures; +} + +//========================================================================== +// +// Makes the screen frame buffer active +// +//========================================================================== + +void FGLRenderBuffers::BindOutputFB() +{ + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +//========================================================================== +// +// Returns true if render buffers are supported and should be used +// +//========================================================================== + +bool FGLRenderBuffers::FailedCreate = false; + +//========================================================================== +// +// Creates or updates textures used by postprocess effects +// +//========================================================================== + +PPGLTextureBackend *GLPPRenderState::GetGLTexture(PPTexture *texture) +{ + if (!texture->Backend) + { + FGLPostProcessState savedState; + + auto backend = std::make_unique(); + + int glformat; + switch (texture->Format) + { + default: + case PixelFormat::Rgba8: glformat = GL_RGBA8; break; + case PixelFormat::Rgba16f: glformat = GL_RGBA16F; break; + case PixelFormat::R32f: glformat = GL_R32F; break; + case PixelFormat::Rg16f: glformat = GL_RG16F; break; + case PixelFormat::Rgba16_snorm: glformat = GL_RGBA16_SNORM; break; + } + + if (texture->Data) + backend->Tex = buffers->Create2DTexture("PPTexture", glformat, texture->Width, texture->Height, texture->Data.get()); + else + backend->Tex = buffers->Create2DTexture("PPTexture", glformat, texture->Width, texture->Height); + + texture->Backend = std::move(backend); + } + return static_cast(texture->Backend.get()); +} + +//========================================================================== +// +// Compile the shaders declared by post process effects +// +//========================================================================== + +FShaderProgram *GLPPRenderState::GetGLShader(PPShader *shader) +{ + if (!shader->Backend) + { + auto glshader = std::make_unique(); + + FString prolog; + if (!shader->Uniforms.empty()) + prolog = UniformBlockDecl::Create("Uniforms", shader->Uniforms, POSTPROCESS_BINDINGPOINT); + prolog += shader->Defines; + + glshader->Compile(FShaderProgram::Vertex, shader->VertexShader, "", shader->Version); + glshader->Compile(FShaderProgram::Fragment, shader->FragmentShader, prolog, shader->Version); + glshader->Link(shader->FragmentShader.GetChars()); + if (!shader->Uniforms.empty()) + glshader->SetUniformBufferLocation(POSTPROCESS_BINDINGPOINT, "Uniforms"); + + shader->Backend = std::move(glshader); + } + return static_cast(shader->Backend.get()); +} + +//========================================================================== +// +// Renders one post process effect +// +//========================================================================== + +void GLPPRenderState::Draw() +{ + FGLPostProcessState savedState; + + // Bind input textures + for (unsigned int index = 0; index < Textures.Size(); index++) + { + savedState.SaveTextureBindings(index + 1); + + const PPTextureInput &input = Textures[index]; + int filter = (input.Filter == PPFilterMode::Nearest) ? GL_NEAREST : GL_LINEAR; + int wrap = (input.Wrap == PPWrapMode::Clamp) ? GL_CLAMP_TO_EDGE : GL_REPEAT; + + switch (input.Type) + { + default: + case PPTextureType::CurrentPipelineTexture: + buffers->BindCurrentTexture(index, filter, wrap); + break; + + case PPTextureType::NextPipelineTexture: + I_FatalError("PPTextureType::NextPipelineTexture not allowed as input\n"); + break; + + case PPTextureType::PPTexture: + GetGLTexture(input.Texture)->Tex.Bind(index, filter, wrap); + break; + + case PPTextureType::SceneColor: + buffers->BindSceneColorTexture(index); + break; + + case PPTextureType::SceneFog: + buffers->BindSceneFogTexture(index); + break; + + case PPTextureType::SceneNormal: + buffers->BindSceneNormalTexture(index); + break; + + case PPTextureType::SceneDepth: + buffers->BindSceneDepthTexture(index); + break; + } + } + + // Set render target + switch (Output.Type) + { + default: + I_FatalError("Unsupported postprocess output type\n"); + break; + + case PPTextureType::CurrentPipelineTexture: + buffers->BindCurrentFB(); + break; + + case PPTextureType::NextPipelineTexture: + buffers->BindNextFB(); + break; + + case PPTextureType::PPTexture: + if (GetGLTexture(Output.Texture)->FB) + GetGLTexture(Output.Texture)->FB.Bind(); + else + GetGLTexture(Output.Texture)->FB = buffers->CreateFrameBuffer("PPTextureFB"/*Output.Texture.GetChars()*/, GetGLTexture(Output.Texture)->Tex); + break; + + case PPTextureType::SceneColor: + buffers->BindSceneFB(false); + break; + } + + // Set blend mode + if (BlendMode.BlendOp == STYLEOP_Add && BlendMode.SrcAlpha == STYLEALPHA_One && BlendMode.DestAlpha == STYLEALPHA_Zero && BlendMode.Flags == 0) + { + glDisable(GL_BLEND); + } + else + { + // To do: support all the modes + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + if (BlendMode.SrcAlpha == STYLEALPHA_One && BlendMode.DestAlpha == STYLEALPHA_One) + glBlendFunc(GL_ONE, GL_ONE); + else + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + // Setup viewport + glViewport(Viewport.left, Viewport.top, Viewport.width, Viewport.height); + + auto shader = GetGLShader(Shader); + + // Set uniforms + if (Uniforms.Data.Size() > 0) + { + if (!shader->Uniforms) + shader->Uniforms.reset(screen->CreateDataBuffer(POSTPROCESS_BINDINGPOINT, false, false)); + shader->Uniforms->SetData(Uniforms.Data.Size(), Uniforms.Data.Data()); + static_cast(shader->Uniforms.get())->BindBase(); + } + + // Set shader + shader->Bind(); + + // Draw the screen quad + GLRenderer->RenderScreenQuad(); + + // Advance to next PP texture if our output was sent there + if (Output.Type == PPTextureType::NextPipelineTexture) + buffers->NextTexture(); + + glViewport(screen->mScreenViewport.left, screen->mScreenViewport.top, screen->mScreenViewport.width, screen->mScreenViewport.height); +} + +void GLPPRenderState::PushGroup(const FString &name) +{ + FGLDebug::PushGroup(name.GetChars()); +} + +void GLPPRenderState::PopGroup() +{ + FGLDebug::PopGroup(); +} + + +// Store the current stereo 3D eye buffer, and Load the next one + +int FGLRenderBuffers::NextEye(int eyeCount) +{ + int nextEye = (mCurrentEye + 1) % eyeCount; + if (nextEye == mCurrentEye) return mCurrentEye; + BlitToEyeTexture(mCurrentEye); + mCurrentEye = nextEye; + BlitFromEyeTexture(mCurrentEye); + return mCurrentEye; +} + +} // namespace OpenGLRenderer \ No newline at end of file diff --git a/source/common/rendering/gl/renderer/gl_renderbuffers.h b/source/common/rendering/gl/renderer/gl_renderbuffers.h new file mode 100644 index 000000000..4f6ba3e5e --- /dev/null +++ b/source/common/rendering/gl/renderer/gl_renderbuffers.h @@ -0,0 +1,205 @@ + +#pragma once + +#include "hwrenderer/postprocessing/hw_postprocess.h" + +namespace OpenGLRenderer +{ + +class FGLRenderBuffers; + +class PPGLTexture +{ +public: + void Bind(int index, int filter = GL_NEAREST, int wrap = GL_CLAMP_TO_EDGE) + { + glActiveTexture(GL_TEXTURE0 + index); + glBindTexture(GL_TEXTURE_2D, handle); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap); + } + + int Width = -1; + int Height = -1; + + explicit operator bool() const { return handle != 0; } + +private: + GLuint handle = 0; + + friend class FGLRenderBuffers; + friend class PPGLTextureBackend; +}; + +class PPGLFrameBuffer +{ +public: + void Bind() + { + glBindFramebuffer(GL_FRAMEBUFFER, handle); + } + + explicit operator bool() const { return handle != 0; } + +private: + GLuint handle = 0; + + friend class FGLRenderBuffers; + friend class PPGLTextureBackend; +}; + +class PPGLRenderBuffer +{ +private: + GLuint handle = 0; + + explicit operator bool() const { return handle != 0; } + + friend class FGLRenderBuffers; +}; + +class PPGLTextureBackend : public PPTextureBackend +{ +public: + ~PPGLTextureBackend() + { + if (Tex.handle != 0) + { + glDeleteTextures(1, &Tex.handle); + Tex.handle = 0; + } + if (FB.handle != 0) + { + glDeleteFramebuffers(1, &FB.handle); + FB.handle = 0; + } + } + + PPGLTexture Tex; + PPGLFrameBuffer FB; +}; + +class FShaderProgram; + +class GLPPRenderState : public PPRenderState +{ +public: + GLPPRenderState(FGLRenderBuffers *buffers) : buffers(buffers) { } + + void PushGroup(const FString &name) override; + void PopGroup() override; + void Draw() override; + +private: + PPGLTextureBackend *GetGLTexture(PPTexture *texture); + FShaderProgram *GetGLShader(PPShader *shader); + + FGLRenderBuffers *buffers; +}; + +class FGLRenderBuffers +{ +public: + FGLRenderBuffers(); + ~FGLRenderBuffers(); + + void Setup(int width, int height, int sceneWidth, int sceneHeight); + + void BindSceneFB(bool sceneData); + void BindSceneColorTexture(int index); + void BindSceneFogTexture(int index); + void BindSceneNormalTexture(int index); + void BindSceneDepthTexture(int index); + void BlitSceneToTexture(); + + void BindCurrentTexture(int index, int filter = GL_NEAREST, int wrap = GL_CLAMP_TO_EDGE); + void BindCurrentFB(); + void BindNextFB(); + void NextTexture(); + + PPGLFrameBuffer GetCurrentFB() const { return mPipelineFB[mCurrentPipelineTexture]; } + + void BindOutputFB(); + + void BlitToEyeTexture(int eye, bool allowInvalidate=true); + void BlitFromEyeTexture(int eye); + void BindEyeTexture(int eye, int texunit); + int NextEye(int eyeCount); + int & CurrentEye() { return mCurrentEye; } + + void BindDitherTexture(int texunit); + + int GetWidth() const { return mWidth; } + int GetHeight() const { return mHeight; } + + int GetSceneWidth() const { return mSceneWidth; } + int GetSceneHeight() const { return mSceneHeight; } + +private: + void ClearScene(); + void ClearPipeline(); + void ClearEyeBuffers(); + void CreateScene(int width, int height, int samples, bool needsSceneTextures); + void CreatePipeline(int width, int height); + void CreateEyeBuffers(int eye); + + PPGLTexture Create2DTexture(const char *name, GLuint format, int width, int height, const void *data = nullptr); + PPGLTexture Create2DMultisampleTexture(const char *name, GLuint format, int width, int height, int samples, bool fixedSampleLocations); + PPGLRenderBuffer CreateRenderBuffer(const char *name, GLuint format, int width, int height); + PPGLRenderBuffer CreateRenderBuffer(const char *name, GLuint format, int width, int height, int samples); + PPGLFrameBuffer CreateFrameBuffer(const char *name, PPGLTexture colorbuffer); + PPGLFrameBuffer CreateFrameBuffer(const char *name, PPGLTexture colorbuffer, PPGLRenderBuffer depthstencil); + PPGLFrameBuffer CreateFrameBuffer(const char *name, PPGLRenderBuffer colorbuffer, PPGLRenderBuffer depthstencil); + PPGLFrameBuffer CreateFrameBuffer(const char *name, PPGLTexture colorbuffer0, PPGLTexture colorbuffer1, PPGLTexture colorbuffer2, PPGLTexture depthstencil, bool multisample); + bool CheckFrameBufferCompleteness(); + void ClearFrameBuffer(bool stencil, bool depth); + void DeleteTexture(PPGLTexture &handle); + void DeleteRenderBuffer(PPGLRenderBuffer &handle); + void DeleteFrameBuffer(PPGLFrameBuffer &handle); + + int mWidth = 0; + int mHeight = 0; + int mSamples = 0; + int mMaxSamples = 0; + int mSceneWidth = 0; + int mSceneHeight = 0; + + static const int NumPipelineTextures = 2; + int mCurrentPipelineTexture = 0; + + // Buffers for the scene + PPGLTexture mSceneMultisampleTex; + PPGLTexture mSceneDepthStencilTex; + PPGLTexture mSceneFogTex; + PPGLTexture mSceneNormalTex; + PPGLRenderBuffer mSceneMultisampleBuf; + PPGLRenderBuffer mSceneDepthStencilBuf; + PPGLRenderBuffer mSceneFogBuf; + PPGLRenderBuffer mSceneNormalBuf; + PPGLFrameBuffer mSceneFB; + PPGLFrameBuffer mSceneDataFB; + + // Effect/HUD buffers + PPGLTexture mPipelineTexture[NumPipelineTextures]; + PPGLFrameBuffer mPipelineFB[NumPipelineTextures]; + + // Eye buffers + TArray mEyeTextures; + TArray mEyeFBs; + int mCurrentEye = 0; + + // Shadow map texture + PPGLTexture mShadowMapTexture; + PPGLFrameBuffer mShadowMapFB; + int mCurrentShadowMapSize = 0; + + PPGLTexture mDitherTexture; + + static bool FailedCreate; + + friend class GLPPRenderState; +}; + +} \ No newline at end of file diff --git a/source/common/rendering/gl/renderer/gl_renderer.cpp b/source/common/rendering/gl/renderer/gl_renderer.cpp new file mode 100644 index 000000000..e672ebfcc --- /dev/null +++ b/source/common/rendering/gl/renderer/gl_renderer.cpp @@ -0,0 +1,243 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2005-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl1_renderer.cpp +** Renderer interface +** +*/ + +#include "gl_load/gl_system.h" +#include "files.h" +#include "v_video.h" +#include "m_png.h" +#include "filesystem.h" +#include "i_time.h" +#include "cmdlib.h" +//#include "swrenderer/r_swscene.h" +//#include "hwrenderer/utility/hw_clock.h" + +#include "gl_load/gl_interface.h" +#include "gl/system/gl_framebuffer.h" +#include "gamecvars.h" +#include "gl/system/gl_debug.h" +#include "gl/renderer/gl_renderer.h" +//#include "gl/renderer/gl_renderstate.h" +#include "gl/renderer/gl_renderbuffers.h" +#include "gl/shaders/gl_shaderprogram.h" +//#include "hwrenderer/data/flatvertices.h" +//#include "gl/textures/gl_samplers.h" +//#include "hwrenderer/dynlights/hw_lightbuffer.h" +//#include "hwrenderer/data/hw_viewpointbuffer.h" +#include "r_videoscale.h" +//#include "r_data/models/models.h" +#include "gl/renderer/gl_postprocessstate.h" +#include "gl/system/gl_buffers.h" + +EXTERN_CVAR(Int, screenblocks) +EXTERN_CVAR(Bool, cl_capfps) + +extern bool NoInterpolateView; + +void DoWriteSavePic(FileWriter *file, ESSType ssformat, uint8_t *scr, int width, int height, sector_t *viewsector, bool upsidedown); + +namespace OpenGLRenderer +{ + + FGLRenderer* GLRenderer; +//=========================================================================== +// +// Renderer interface +// +//=========================================================================== + +//----------------------------------------------------------------------------- +// +// Initialize +// +//----------------------------------------------------------------------------- + +FGLRenderer::FGLRenderer(OpenGLFrameBuffer *fb) +{ + framebuffer = fb; +} + +void FGLRenderer::Initialize(int width, int height) +{ + mScreenBuffers = new FGLRenderBuffers(); + mSaveBuffers = new FGLRenderBuffers(); + mBuffers = mScreenBuffers; + mPresentShader = new FPresentShader(); + mPresent3dCheckerShader = new FPresent3DCheckerShader(); + mPresent3dColumnShader = new FPresent3DColumnShader(); + mPresent3dRowShader = new FPresent3DRowShader(); + + //glGenQueries(1, &PortalQueryObject); + + // needed for the core profile, because someone decided it was a good idea to remove the default VAO. + glGenVertexArrays(1, &mVAOID); + glBindVertexArray(mVAOID); + FGLDebug::LabelObject(GL_VERTEX_ARRAY, mVAOID, "FGLRenderer.mVAOID"); + + mFBID = 0; + mOldFBID = 0; + + //mShaderManager = new FShaderManager; + //mSamplerManager = new FSamplerManager; +} + +FGLRenderer::~FGLRenderer() +{ + //FlushModels(); + //TexMan.FlushAll(); + //if (mShaderManager != nullptr) delete mShaderManager; + //if (mSamplerManager != nullptr) delete mSamplerManager; + if (mFBID != 0) glDeleteFramebuffers(1, &mFBID); + if (mVAOID != 0) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &mVAOID); + } + //if (PortalQueryObject != 0) glDeleteQueries(1, &PortalQueryObject); + + //if (swdrawer) delete swdrawer; + if (mBuffers) delete mBuffers; + if (mSaveBuffers) delete mSaveBuffers; + if (mPresentShader) delete mPresentShader; + if (mPresent3dCheckerShader) delete mPresent3dCheckerShader; + if (mPresent3dColumnShader) delete mPresent3dColumnShader; + if (mPresent3dRowShader) delete mPresent3dRowShader; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +bool FGLRenderer::StartOffscreen() +{ + bool firstBind = (mFBID == 0); + if (mFBID == 0) + glGenFramebuffers(1, &mFBID); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mOldFBID); + glBindFramebuffer(GL_FRAMEBUFFER, mFBID); + if (firstBind) + FGLDebug::LabelObject(GL_FRAMEBUFFER, mFBID, "OffscreenFB"); + return true; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FGLRenderer::EndOffscreen() +{ + glBindFramebuffer(GL_FRAMEBUFFER, mOldFBID); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FGLRenderer::BindToFrameBuffer(FMaterial *mat) +{ +#if 0 + auto BaseLayer = static_cast(mat->GetLayer(0, 0)); + + if (BaseLayer == nullptr) + { + // must create the hardware texture first + BaseLayer->BindOrCreate(mat->sourcetex, 0, 0, 0, 0); + FHardwareTexture::Unbind(0); + gl_RenderState.ClearLastMaterial(); + } + BaseLayer->BindToFrameBuffer(mat->GetWidth(), mat->GetHeight()); +#endif +} + +//=========================================================================== +// +// Render the view to a savegame picture +// +//=========================================================================== + +#if 0 +void FGLRenderer::WriteSavePic (player_t *player, FileWriter *file, int width, int height) +{ + IntRect bounds; + bounds.left = 0; + bounds.top = 0; + bounds.width = width; + bounds.height = height; + + // we must be sure the GPU finished reading from the buffer before we fill it with new data. + glFinish(); + + // Switch to render buffers dimensioned for the savepic + mBuffers = mSaveBuffers; + + hw_ClearFakeFlat(); + gl_RenderState.SetVertexBuffer(screen->mVertexData); + screen->mVertexData->Reset(); + screen->mLights->Clear(); + screen->mViewpoints->Clear(); + + // This shouldn't overwrite the global viewpoint even for a short time. + FRenderViewpoint savevp; + sector_t *viewsector = RenderViewpoint(savevp, players[consoleplayer].camera, &bounds, r_viewpoint.FieldOfView.Degrees, 1.6f, 1.6f, true, false); + glDisable(GL_STENCIL_TEST); + gl_RenderState.SetNoSoftLightLevel(); + CopyToBackbuffer(&bounds, false); + + // strictly speaking not needed as the glReadPixels should block until the scene is rendered, but this is to safeguard against shitty drivers + glFinish(); + + int numpixels = width * height; + uint8_t * scr = (uint8_t *)M_Malloc(numpixels * 3); + glReadPixels(0,0,width, height,GL_RGB,GL_UNSIGNED_BYTE,scr); + + DoWriteSavePic(file, SS_RGB, scr, width, height, viewsector, true); + M_Free(scr); + + // Switch back the screen render buffers + screen->SetViewportRects(nullptr); + mBuffers = mScreenBuffers; +} +#endif + +//=========================================================================== +// +// +// +//=========================================================================== + +void FGLRenderer::BeginFrame() +{ + mScreenBuffers->Setup(screen->mScreenViewport.width, screen->mScreenViewport.height, screen->mSceneViewport.width, screen->mSceneViewport.height); + //mSaveBuffers->Setup(SAVEPICWIDTH, SAVEPICHEIGHT, SAVEPICWIDTH, SAVEPICHEIGHT); +} + +} diff --git a/source/common/rendering/gl/renderer/gl_renderer.h b/source/common/rendering/gl/renderer/gl_renderer.h new file mode 100644 index 000000000..353ed3a44 --- /dev/null +++ b/source/common/rendering/gl/renderer/gl_renderer.h @@ -0,0 +1,121 @@ +#ifndef __GL_RENDERER_H +#define __GL_RENDERER_H + +#include "v_video.h" +#include "vectors.h" +#include "matrix.h" +#include "gl/renderer/gl_renderbuffers.h" +#include + +#ifdef _MSC_VER +#pragma warning(disable:4244) +#endif + +struct particle_t; +class FCanvasTexture; +class FFlatVertexBuffer; +class FSkyVertexBuffer; +class FShaderManager; +class HWPortal; +class FLightBuffer; +class DPSprite; +class FGLRenderBuffers; +class FGL2DDrawer; +class FHardwareTexture; +class SWSceneDrawer; +class HWViewpointBuffer; +struct FRenderViewpoint; + +namespace OpenGLRenderer +{ + class FSamplerManager; + class OpenGLFrameBuffer; + class FPresentShaderBase; + class FPresentShader; + class FPresent3DCheckerShader; + class FPresent3DColumnShader; + class FPresent3DRowShader; + +class FGLRenderer +{ +public: + + OpenGLFrameBuffer *framebuffer; + int mMirrorCount = 0; + int mPlaneMirrorCount = 0; + //FShaderManager *mShaderManager = nullptr; + //FSamplerManager *mSamplerManager = nullptr; + unsigned int mFBID; + unsigned int mVAOID; + //unsigned int PortalQueryObject; + unsigned int mStencilValue = 0; + + int mOldFBID; + + FGLRenderBuffers *mBuffers = nullptr; + FGLRenderBuffers *mScreenBuffers = nullptr; + FGLRenderBuffers *mSaveBuffers = nullptr; + FPresentShader *mPresentShader = nullptr; + FPresent3DCheckerShader *mPresent3dCheckerShader = nullptr; + FPresent3DColumnShader *mPresent3dColumnShader = nullptr; + FPresent3DRowShader *mPresent3dRowShader = nullptr; + + //FRotator mAngles; + + //SWSceneDrawer *swdrawer = nullptr; + + FGLRenderer(OpenGLFrameBuffer *fb); + ~FGLRenderer() ; + + void Initialize(int width, int height); + + void ClearBorders(); + + void PresentStereo(); + void RenderScreenQuad(); + void PostProcessScene(int fixedcm, const std::function &afterBloomDrawEndScene2D); + void AmbientOccludeScene(float m5); + void ClearTonemapPalette(); + void BlurScene(float gameinfobluramount); + void CopyToBackbuffer(const IntRect *bounds, bool applyGamma); + void DrawPresentTexture(const IntRect &box, bool applyGamma); + void Flush(); + //void Draw2D(F2DDrawer *data); +#if 0 + void WriteSavePic(player_t *player, FileWriter *file, int width, int height); +#endif + void BeginFrame(); + + + bool StartOffscreen(); + void EndOffscreen(); + + void BindToFrameBuffer(FMaterial *mat); + +private: + + void DrawScene(HWDrawInfo *di, int drawmode); + bool QuadStereoCheckInitialRenderContextState(); + void PresentAnaglyph(bool r, bool g, bool b); + void PresentSideBySide(); + void PresentTopBottom(); + void prepareInterleavedPresent(FPresentShaderBase& shader); + void PresentColumnInterleaved(); + void PresentRowInterleaved(); + void PresentCheckerInterleaved(); + void PresentQuadStereo(); + +}; + +struct TexFilter_s +{ + int minfilter; + int magfilter; + bool mipmapping; +} ; + + +extern FGLRenderer *GLRenderer; + +} +#endif diff --git a/source/common/rendering/gl/shaders/gl_shaderprogram.cpp b/source/common/rendering/gl/shaders/gl_shaderprogram.cpp new file mode 100644 index 000000000..8e80b2b1f --- /dev/null +++ b/source/common/rendering/gl/shaders/gl_shaderprogram.cpp @@ -0,0 +1,355 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2016 Magnus Norddahl +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_shaderprogram.cpp +** GLSL shader program compile and link +** +*/ + +#include "gl_load/gl_system.h" +#include "v_video.h" +#include "gl_load/gl_interface.h" +#include "gamecvars.h" +#include "gl/system/gl_debug.h" +#include "gl/shaders/gl_shaderprogram.h" +#include "hwrenderer/utility/hw_shaderpatcher.h" +#include "filesystem.h" +#include "printf.h" + +namespace OpenGLRenderer +{ + +bool IsShaderCacheActive(); +TArray LoadCachedProgramBinary(const FString &vertex, const FString &fragment, uint32_t &binaryFormat); +void SaveCachedProgramBinary(const FString &vertex, const FString &fragment, const TArray &binary, uint32_t binaryFormat); + +FShaderProgram::FShaderProgram() +{ + for (int i = 0; i < NumShaderTypes; i++) + mShaders[i] = 0; +} + +//========================================================================== +// +// Free shader program resources +// +//========================================================================== + +FShaderProgram::~FShaderProgram() +{ + if (mProgram != 0) + glDeleteProgram(mProgram); + + for (int i = 0; i < NumShaderTypes; i++) + { + if (mShaders[i] != 0) + glDeleteShader(mShaders[i]); + } +} + +//========================================================================== +// +// Creates an OpenGL shader object for the specified type of shader +// +//========================================================================== + +void FShaderProgram::CreateShader(ShaderType type) +{ + GLenum gltype = 0; + switch (type) + { + default: + case Vertex: gltype = GL_VERTEX_SHADER; break; + case Fragment: gltype = GL_FRAGMENT_SHADER; break; + } + mShaders[type] = glCreateShader(gltype); +} + +//========================================================================== +// +// Compiles a shader and attaches it the program object +// +//========================================================================== + +void FShaderProgram::Compile(ShaderType type, const char *lumpName, const char *defines, int maxGlslVersion) +{ + int lump = fileSystem.FindFile(lumpName); + if (lump == -1) I_FatalError("Unable to load '%s'", lumpName); + FString code = fileSystem.ReadFile(lump).GetString().GetChars(); + Compile(type, lumpName, code, defines, maxGlslVersion); +} + +void FShaderProgram::Compile(ShaderType type, const char *name, const FString &code, const char *defines, int maxGlslVersion) +{ + mShaderNames[type] = name; + mShaderSources[type] = PatchShader(type, code, defines, maxGlslVersion); +} + +void FShaderProgram::CompileShader(ShaderType type) +{ + CreateShader(type); + + const auto &handle = mShaders[type]; + + FGLDebug::LabelObject(GL_SHADER, handle, mShaderNames[type]); + + const FString &patchedCode = mShaderSources[type]; + int lengths[1] = { (int)patchedCode.Len() }; + const char *sources[1] = { patchedCode.GetChars() }; + glShaderSource(handle, 1, sources, lengths); + + glCompileShader(handle); + + GLint status = 0; + glGetShaderiv(handle, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) + { + I_FatalError("Compile Shader '%s':\n%s\n", mShaderNames[type].GetChars(), GetShaderInfoLog(handle).GetChars()); + } + else + { + if (mProgram == 0) + mProgram = glCreateProgram(); + glAttachShader(mProgram, handle); + } +} + +//========================================================================== +// +// Links a program with the compiled shaders +// +//========================================================================== + +void FShaderProgram::Link(const char *name) +{ + FGLDebug::LabelObject(GL_PROGRAM, mProgram, name); + + uint32_t binaryFormat = 0; + TArray binary; +#if 0 + if (IsShaderCacheActive()) + binary = LoadCachedProgramBinary(mShaderSources[Vertex], mShaderSources[Fragment], binaryFormat); +#endif + bool loadedFromBinary = false; + if (binary.Size() > 0 && glProgramBinary) + { + if (mProgram == 0) + mProgram = glCreateProgram(); + glProgramBinary(mProgram, binaryFormat, binary.Data(), binary.Size()); + GLint status = 0; + glGetProgramiv(mProgram, GL_LINK_STATUS, &status); + loadedFromBinary = (status == GL_TRUE); + } + + if (!loadedFromBinary) + { + CompileShader(Vertex); + CompileShader(Fragment); + + glLinkProgram(mProgram); + + GLint status = 0; + glGetProgramiv(mProgram, GL_LINK_STATUS, &status); + if (status == GL_FALSE) + { + I_FatalError("Link Shader '%s':\n%s\n", name, GetProgramInfoLog(mProgram).GetChars()); + } +#if 0 + else if (glProgramBinary && IsShaderCacheActive()) + { + int binaryLength = 0; + glGetProgramiv(mProgram, GL_PROGRAM_BINARY_LENGTH, &binaryLength); + binary.Resize(binaryLength); + glGetProgramBinary(mProgram, binary.Size(), &binaryLength, &binaryFormat, binary.Data()); + binary.Resize(binaryLength); + SaveCachedProgramBinary(mShaderSources[Vertex], mShaderSources[Fragment], binary, binaryFormat); + } +#endif + } + + // This is only for old OpenGL which didn't allow to set the binding from within the shader. + if (screen->glslversion < 4.20) + { + glUseProgram(mProgram); + for (auto &uni : samplerstobind) + { + auto index = glGetUniformLocation(mProgram, uni.first); + if (index >= 0) + { + glUniform1i(index, uni.second); + } + } + } + samplerstobind.Clear(); + samplerstobind.ShrinkToFit(); +} + +//========================================================================== +// +// Set uniform buffer location (only useful for GL 3.3) +// +//========================================================================== + +void FShaderProgram::SetUniformBufferLocation(int index, const char *name) +{ + if (screen->glslversion < 4.20) + { + GLuint uniformBlockIndex = glGetUniformBlockIndex(mProgram, name); + if (uniformBlockIndex != GL_INVALID_INDEX) + glUniformBlockBinding(mProgram, uniformBlockIndex, index); + } +} + +//========================================================================== +// +// Makes the shader the active program +// +//========================================================================== + +void FShaderProgram::Bind() +{ + glUseProgram(mProgram); +} + +//========================================================================== +// +// Returns the shader info log (warnings and compile errors) +// +//========================================================================== + +FString FShaderProgram::GetShaderInfoLog(GLuint handle) +{ + static char buffer[10000]; + GLsizei length = 0; + buffer[0] = 0; + glGetShaderInfoLog(handle, 10000, &length, buffer); + return FString(buffer); +} + +//========================================================================== +// +// Returns the program info log (warnings and compile errors) +// +//========================================================================== + +FString FShaderProgram::GetProgramInfoLog(GLuint handle) +{ + static char buffer[10000]; + GLsizei length = 0; + buffer[0] = 0; + glGetProgramInfoLog(handle, 10000, &length, buffer); + return FString(buffer); +} + +//========================================================================== +// +// Patches a shader to be compatible with the version of OpenGL in use +// +//========================================================================== + +FString FShaderProgram::PatchShader(ShaderType type, const FString &code, const char *defines, int maxGlslVersion) +{ + FString patchedCode; + + // If we have 4.2, always use it because it adds important new syntax. + if (maxGlslVersion < 420 && gl.glslversion >= 4.2f) maxGlslVersion = 420; + int shaderVersion = std::min((int)round(gl.glslversion * 10) * 10, maxGlslVersion); + patchedCode.AppendFormat("#version %d\n", shaderVersion); + + // TODO: Find some way to add extension requirements to the patching + // + // #extension GL_ARB_uniform_buffer_object : require + // #extension GL_ARB_shader_storage_buffer_object : require + + if (defines) + patchedCode << defines; + + // these settings are actually pointless but there seem to be some old ATI drivers that fail to compile the shader without setting the precision here. + patchedCode << "precision highp int;\n"; + patchedCode << "precision highp float;\n"; + + patchedCode << "#line 1\n"; + patchedCode << RemoveLayoutLocationDecl(code, type == Vertex ? "out" : "in"); + + if (maxGlslVersion < 420) + { + // Here we must strip out all layout(binding) declarations for sampler uniforms and store them in 'samplerstobind' which can then be processed by the link function. + patchedCode = RemoveSamplerBindings(patchedCode, samplerstobind); + } + + return patchedCode; +} + +///////////////////////////////////////////////////////////////////////////// + +void FPresentShaderBase::Init(const char * vtx_shader_name, const char * program_name) +{ + FString prolog = Uniforms.CreateDeclaration("Uniforms", PresentUniforms::Desc()); + + mShader.reset(new FShaderProgram()); + mShader->Compile(FShaderProgram::Vertex, "shaders/glsl/screenquad.vp", prolog, 330); + mShader->Compile(FShaderProgram::Fragment, vtx_shader_name, prolog, 330); + mShader->Link(program_name); + mShader->SetUniformBufferLocation(Uniforms.BindingPoint(), "Uniforms"); + Uniforms.Init(); +} + +void FPresentShader::Bind() +{ + if (!mShader) + { + Init("shaders/glsl/present.fp", "shaders/glsl/present"); + } + mShader->Bind(); +} + +///////////////////////////////////////////////////////////////////////////// + +void FPresent3DCheckerShader::Bind() +{ + if (!mShader) + { + Init("shaders/glsl/present_checker3d.fp", "shaders/glsl/presentChecker3d"); + } + mShader->Bind(); +} + +void FPresent3DColumnShader::Bind() +{ + if (!mShader) + { + Init("shaders/glsl/present_column3d.fp", "shaders/glsl/presentColumn3d"); + } + mShader->Bind(); +} + +void FPresent3DRowShader::Bind() +{ + if (!mShader) + { + Init("shaders/glsl/present_row3d.fp", "shaders/glsl/presentRow3d"); + } + mShader->Bind(); +} + + +} \ No newline at end of file diff --git a/source/common/rendering/gl/shaders/gl_shaderprogram.h b/source/common/rendering/gl/shaders/gl_shaderprogram.h new file mode 100644 index 000000000..b197a18ba --- /dev/null +++ b/source/common/rendering/gl/shaders/gl_shaderprogram.h @@ -0,0 +1,93 @@ + +#pragma once + +#include "gl_load/gl_system.h" +//#include "gl_shader.h" +#include "hwrenderer/postprocessing/hw_postprocess.h" + +namespace OpenGLRenderer +{ + +class FShaderProgram : public PPShaderBackend +{ +public: + FShaderProgram(); + ~FShaderProgram(); + + enum ShaderType + { + Vertex, + Fragment, + NumShaderTypes + }; + + void Compile(ShaderType type, const char *lumpName, const char *defines, int maxGlslVersion); + void Compile(ShaderType type, const char *name, const FString &code, const char *defines, int maxGlslVersion); + void Link(const char *name); + void SetUniformBufferLocation(int index, const char *name); + + void Bind(); + + GLuint Handle() { return mProgram; } + //explicit operator bool() const { return mProgram != 0; } + + std::unique_ptr Uniforms; + +private: + FShaderProgram(const FShaderProgram &) = delete; + FShaderProgram &operator=(const FShaderProgram &) = delete; + + void CompileShader(ShaderType type); + FString PatchShader(ShaderType type, const FString &code, const char *defines, int maxGlslVersion); + + void CreateShader(ShaderType type); + FString GetShaderInfoLog(GLuint handle); + FString GetProgramInfoLog(GLuint handle); + + GLuint mProgram = 0; + GLuint mShaders[NumShaderTypes]; + FString mShaderSources[NumShaderTypes]; + FString mShaderNames[NumShaderTypes]; + TArray> samplerstobind; +}; + +class FPresentShaderBase +{ +public: + virtual ~FPresentShaderBase() {} + virtual void Bind() = 0; + + ShaderUniforms Uniforms; + +protected: + virtual void Init(const char * vtx_shader_name, const char * program_name); + std::unique_ptr mShader; +}; + +class FPresentShader : public FPresentShaderBase +{ +public: + void Bind() override; + +}; + +class FPresent3DCheckerShader : public FPresentShaderBase +{ +public: + void Bind() override; +}; + +class FPresent3DColumnShader : public FPresentShaderBase +{ +public: + void Bind() override; +}; + +class FPresent3DRowShader : public FPresentShaderBase +{ +public: + void Bind() override; +}; + + +} \ No newline at end of file diff --git a/source/glbackend/gl_buffers.cpp b/source/common/rendering/gl/system/gl_buffers.cpp similarity index 99% rename from source/glbackend/gl_buffers.cpp rename to source/common/rendering/gl/system/gl_buffers.cpp index ce8ccb035..89a403a7c 100644 --- a/source/glbackend/gl_buffers.cpp +++ b/source/common/rendering/gl/system/gl_buffers.cpp @@ -26,7 +26,7 @@ #include #include "gl_load.h" -#include "glbackend.h" +//#include "glbackend.h" #include "gl_buffers.h" namespace OpenGLRenderer diff --git a/source/glbackend/gl_buffers.h b/source/common/rendering/gl/system/gl_buffers.h similarity index 100% rename from source/glbackend/gl_buffers.h rename to source/common/rendering/gl/system/gl_buffers.h diff --git a/source/common/rendering/gl/system/gl_debug.cpp b/source/common/rendering/gl/system/gl_debug.cpp index a64931c41..489f1f878 100644 --- a/source/common/rendering/gl/system/gl_debug.cpp +++ b/source/common/rendering/gl/system/gl_debug.cpp @@ -44,9 +44,9 @@ CUSTOM_CVAR(Int, gl_debug_level, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOIN CVAR(Bool, gl_debug_breakpoint, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); -bool gpuStatActive; -bool keepGpuStatActive; -FString gpuStatOutput; +extern bool gpuStatActive; +extern bool keepGpuStatActive; +extern FString gpuStatOutput; namespace OpenGLRenderer { diff --git a/source/common/rendering/gl_load/gl_interface.cpp b/source/common/rendering/gl_load/gl_interface.cpp index 634e6c49e..5240c19f8 100644 --- a/source/common/rendering/gl_load/gl_interface.cpp +++ b/source/common/rendering/gl_load/gl_interface.cpp @@ -34,7 +34,7 @@ #include "v_video.h" #include "printf.h" #include "gl_load/gl_interface.h" -//#include "hwrenderer/utility/hw_cvars.h" +#include "gamecvars.h" static TArray m_Extensions; RenderContext gl; diff --git a/source/glbackend/buffers.h b/source/common/rendering/hwrenderer/data/buffers.h similarity index 100% rename from source/glbackend/buffers.h rename to source/common/rendering/hwrenderer/data/buffers.h diff --git a/source/common/rendering/hwrenderer/data/renderqueue.h b/source/common/rendering/hwrenderer/data/renderqueue.h new file mode 100644 index 000000000..43b8aab1d --- /dev/null +++ b/source/common/rendering/hwrenderer/data/renderqueue.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include "tflags.h" + +// A render queue is what contains all render commands. +// On Vulkan there can be several of them so this interface is needed to allow for the needed parallelism. +// On OpenGL the render state is global so all this will do is to translate the system independent calls into OpenGL API calls. + +enum class ColormaskBits +{ + RED = 1, + GREEN = 2, + BLUE = 4, + ALPHA = 8 +}; + +typedef TFlags Colormask; + +class IRenderQueue +{ + Colormask mColorMask; + + + Colormask GetColorMask() const + { + return mColorMask; + } + + virtual void SetColorMask(Colormask mask) = 0; + + +}; diff --git a/source/common/rendering/hwrenderer/data/shaderuniforms.h b/source/common/rendering/hwrenderer/data/shaderuniforms.h new file mode 100644 index 000000000..2543b52b1 --- /dev/null +++ b/source/common/rendering/hwrenderer/data/shaderuniforms.h @@ -0,0 +1,154 @@ +#pragma once + +#include +#include "hwrenderer/data/buffers.h" +#include "v_video.h" + +enum +{ + LIGHTBUF_BINDINGPOINT = 1, + POSTPROCESS_BINDINGPOINT = 2, + VIEWPOINT_BINDINGPOINT = 3, + LIGHTNODES_BINDINGPOINT = 4, + LIGHTLINES_BINDINGPOINT = 5, + LIGHTLIST_BINDINGPOINT = 6 +}; + +enum class UniformType +{ + Int, + UInt, + Float, + Vec2, + Vec3, + Vec4, + IVec2, + IVec3, + IVec4, + UVec2, + UVec3, + UVec4, + Mat4 +}; + +class UniformFieldDesc +{ +public: + UniformFieldDesc() { } + UniformFieldDesc(const char *name, UniformType type, std::size_t offset) : Name(name), Type(type), Offset(offset) { } + + const char *Name; + UniformType Type; + std::size_t Offset; +}; + +class UniformBlockDecl +{ +public: + static FString Create(const char *name, const std::vector &fields, int bindingpoint) + { + FString decl; + FString layout; + if (bindingpoint == -1) + { + layout = "push_constant"; + } + else if (screen->glslversion < 4.20) + { + layout = "std140"; + } + else + { + layout.Format("std140, binding = %d", bindingpoint); + } + decl.Format("layout(%s) uniform %s\n{\n", layout.GetChars(), name); + for (size_t i = 0; i < fields.size(); i++) + { + decl.AppendFormat("\t%s %s;\n", GetTypeStr(fields[i].Type), fields[i].Name); + } + decl += "};\n"; + + return decl; + } + +private: + static const char *GetTypeStr(UniformType type) + { + switch (type) + { + default: + case UniformType::Int: return "int"; + case UniformType::UInt: return "uint"; + case UniformType::Float: return "float"; + case UniformType::Vec2: return "vec2"; + case UniformType::Vec3: return "vec3"; + case UniformType::Vec4: return "vec4"; + case UniformType::IVec2: return "ivec2"; + case UniformType::IVec3: return "ivec3"; + case UniformType::IVec4: return "ivec4"; + case UniformType::UVec2: return "uvec2"; + case UniformType::UVec3: return "uvec3"; + case UniformType::UVec4: return "uvec4"; + case UniformType::Mat4: return "mat4"; + } + } +}; + +template +class ShaderUniforms +{ +public: + ShaderUniforms() + { + memset(&Values, 0, sizeof(Values)); + } + + ~ShaderUniforms() + { + if (mBuffer != nullptr) + delete mBuffer; + } + + int BindingPoint() const + { + return bindingpoint; + } + + FString CreateDeclaration(const char *name, const std::vector &fields) + { + mFields = fields; + return UniformBlockDecl::Create(name, fields, bindingpoint); + } + + void Init() + { + if (mBuffer == nullptr) + mBuffer = screen->CreateDataBuffer(bindingpoint, false, false); + } + + void SetData() + { + if (mBuffer != nullptr) + mBuffer->SetData(sizeof(T), &Values); + } + + IDataBuffer* GetBuffer() const + { + // OpenGL needs to mess around with this in ways that should not be part of the interface. + return mBuffer; + } + + T *operator->() { return &Values; } + const T *operator->() const { return &Values; } + + T Values; + +private: + ShaderUniforms(const ShaderUniforms &) = delete; + ShaderUniforms &operator=(const ShaderUniforms &) = delete; + + IDataBuffer *mBuffer = nullptr; + std::vector mFields; +}; + + diff --git a/source/common/rendering/hwrenderer/postprocessing/hw_postprocess.cpp b/source/common/rendering/hwrenderer/postprocessing/hw_postprocess.cpp new file mode 100644 index 000000000..2497eb968 --- /dev/null +++ b/source/common/rendering/hwrenderer/postprocessing/hw_postprocess.cpp @@ -0,0 +1,826 @@ + +#include "v_video.h" +#include "hw_postprocess.h" +#include "gamecvars.h" +#include "stats.h" +#include "imagehelpers.h" +#include "hwrenderer/utility/hw_cvars.h" +#include "hwrenderer/postprocessing/hw_postprocess_cvars.h" +#include + +Postprocess hw_postprocess; + +PPResource *PPResource::First = nullptr; + +bool gpuStatActive = false; +bool keepGpuStatActive = false; +FString gpuStatOutput; + +ADD_STAT(gpu) +{ + keepGpuStatActive = true; + return gpuStatOutput; +} + +///////////////////////////////////////////////////////////////////////////// + +void PPBloom::UpdateTextures(int width, int height) +{ + if (width == lastWidth && height == lastHeight) + return; + + int bloomWidth = (width + 1) / 2; + int bloomHeight = (height + 1) / 2; + + for (int i = 0; i < NumBloomLevels; i++) + { + auto &blevel = levels[i]; + blevel.Viewport.left = 0; + blevel.Viewport.top = 0; + blevel.Viewport.width = (bloomWidth + 1) / 2; + blevel.Viewport.height = (bloomHeight + 1) / 2; + blevel.VTexture = { blevel.Viewport.width, blevel.Viewport.height, PixelFormat::Rgba16f }; + blevel.HTexture = { blevel.Viewport.width, blevel.Viewport.height, PixelFormat::Rgba16f }; + + bloomWidth = blevel.Viewport.width; + bloomHeight = blevel.Viewport.height; + } + + lastWidth = width; + lastHeight = height; +} + +void PPBloom::RenderBloom(PPRenderState *renderstate, int sceneWidth, int sceneHeight, int fixedcm) +{ + // Only bloom things if enabled and no special fixed light mode is active + if (!gl_bloom || sceneWidth <= 0 || sceneHeight <= 0) + { + return; + } + + renderstate->PushGroup("bloom"); + + UpdateTextures(sceneWidth, sceneHeight); + + ExtractUniforms extractUniforms; + extractUniforms.Scale = screen->SceneScale(); + extractUniforms.Offset = screen->SceneOffset(); + + auto &level0 = levels[0]; + + // Extract blooming pixels from scene texture: + renderstate->Clear(); + renderstate->Shader = &BloomExtract; + renderstate->Uniforms.Set(extractUniforms); + renderstate->Viewport = level0.Viewport; + renderstate->SetInputCurrent(0, PPFilterMode::Linear); + renderstate->SetInputTexture(1, &hw_postprocess.exposure.CameraTexture); + renderstate->SetOutputTexture(&level0.VTexture); + renderstate->SetNoBlend(); + renderstate->Draw(); + + const float blurAmount = gl_bloom_amount; + BlurUniforms blurUniforms; + ComputeBlurSamples(7, blurAmount, blurUniforms.SampleWeights); + + // Blur and downscale: + for (int i = 0; i < NumBloomLevels - 1; i++) + { + auto &blevel = levels[i]; + auto &next = levels[i + 1]; + + BlurStep(renderstate, blurUniforms, blevel.VTexture, blevel.HTexture, blevel.Viewport, false); + BlurStep(renderstate, blurUniforms, blevel.HTexture, blevel.VTexture, blevel.Viewport, true); + + // Linear downscale: + renderstate->Clear(); + renderstate->Shader = &BloomCombine; + renderstate->Uniforms.Clear(); + renderstate->Viewport = next.Viewport; + renderstate->SetInputTexture(0, &blevel.VTexture, PPFilterMode::Linear); + renderstate->SetOutputTexture(&next.VTexture); + renderstate->SetNoBlend(); + renderstate->Draw(); + } + + // Blur and upscale: + for (int i = NumBloomLevels - 1; i > 0; i--) + { + auto &blevel = levels[i]; + auto &next = levels[i - 1]; + + BlurStep(renderstate, blurUniforms, blevel.VTexture, blevel.HTexture, blevel.Viewport, false); + BlurStep(renderstate, blurUniforms, blevel.HTexture, blevel.VTexture, blevel.Viewport, true); + + // Linear upscale: + renderstate->Clear(); + renderstate->Shader = &BloomCombine; + renderstate->Uniforms.Clear(); + renderstate->Viewport = next.Viewport; + renderstate->SetInputTexture(0, &blevel.VTexture, PPFilterMode::Linear); + renderstate->SetOutputTexture(&next.VTexture); + renderstate->SetNoBlend(); + renderstate->Draw(); + } + + BlurStep(renderstate, blurUniforms, level0.VTexture, level0.HTexture, level0.Viewport, false); + BlurStep(renderstate, blurUniforms, level0.HTexture, level0.VTexture, level0.Viewport, true); + + // Add bloom back to scene texture: + renderstate->Clear(); + renderstate->Shader = &BloomCombine; + renderstate->Uniforms.Clear(); + renderstate->Viewport = screen->mSceneViewport; + renderstate->SetInputTexture(0, &level0.VTexture, PPFilterMode::Linear); + renderstate->SetOutputCurrent(); + renderstate->SetAdditiveBlend(); + renderstate->Draw(); + + renderstate->PopGroup(); +} + +void PPBloom::RenderBlur(PPRenderState *renderstate, int sceneWidth, int sceneHeight, float gameinfobluramount) +{ + // No scene, no blur! + if (sceneWidth <= 0 || sceneHeight <= 0) + return; + + UpdateTextures(sceneWidth, sceneHeight); + + // first, respect the CVar + float blurAmount = gl_menu_blur; + + // if CVar is negative, use the gameinfo entry + if (gl_menu_blur < 0) + blurAmount = gameinfobluramount; + + // if blurAmount == 0 or somehow still returns negative, exit to prevent a crash, clearly we don't want this + if (blurAmount <= 0.0) + { + return; + } + + renderstate->PushGroup("blur"); + + int numLevels = 3; + assert(numLevels <= NumBloomLevels); + + auto &level0 = levels[0]; + + // Grab the area we want to bloom: + renderstate->Clear(); + renderstate->Shader = &BloomCombine; + renderstate->Uniforms.Clear(); + renderstate->Viewport = level0.Viewport; + renderstate->SetInputCurrent(0, PPFilterMode::Linear); + renderstate->SetOutputTexture(&level0.VTexture); + renderstate->SetNoBlend(); + renderstate->Draw(); + + BlurUniforms blurUniforms; + ComputeBlurSamples(7, blurAmount, blurUniforms.SampleWeights); + + // Blur and downscale: + for (int i = 0; i < numLevels - 1; i++) + { + auto &blevel = levels[i]; + auto &next = levels[i + 1]; + + BlurStep(renderstate, blurUniforms, blevel.VTexture, blevel.HTexture, blevel.Viewport, false); + BlurStep(renderstate, blurUniforms, blevel.HTexture, blevel.VTexture, blevel.Viewport, true); + + // Linear downscale: + renderstate->Clear(); + renderstate->Shader = &BloomCombine; + renderstate->Uniforms.Clear(); + renderstate->Viewport = next.Viewport; + renderstate->SetInputTexture(0, &blevel.VTexture, PPFilterMode::Linear); + renderstate->SetOutputTexture(&next.VTexture); + renderstate->SetNoBlend(); + renderstate->Draw(); + } + + // Blur and upscale: + for (int i = numLevels - 1; i > 0; i--) + { + auto &blevel = levels[i]; + auto &next = levels[i - 1]; + + BlurStep(renderstate, blurUniforms, blevel.VTexture, blevel.HTexture, blevel.Viewport, false); + BlurStep(renderstate, blurUniforms, blevel.HTexture, blevel.VTexture, blevel.Viewport, true); + + // Linear upscale: + renderstate->Clear(); + renderstate->Shader = &BloomCombine; + renderstate->Uniforms.Clear(); + renderstate->Viewport = next.Viewport; + renderstate->SetInputTexture(0, &blevel.VTexture, PPFilterMode::Linear); + renderstate->SetOutputTexture(&next.VTexture); + renderstate->SetNoBlend(); + renderstate->Draw(); + } + + BlurStep(renderstate, blurUniforms, level0.VTexture, level0.HTexture, level0.Viewport, false); + BlurStep(renderstate, blurUniforms, level0.HTexture, level0.VTexture, level0.Viewport, true); + + // Copy blur back to scene texture: + renderstate->Clear(); + renderstate->Shader = &BloomCombine; + renderstate->Uniforms.Clear(); + renderstate->Viewport = screen->mScreenViewport; + renderstate->SetInputTexture(0, &level0.VTexture, PPFilterMode::Linear); + renderstate->SetOutputCurrent(); + renderstate->SetNoBlend(); + renderstate->Draw(); + + renderstate->PopGroup(); +} + +void PPBloom::BlurStep(PPRenderState *renderstate, const BlurUniforms &blurUniforms, PPTexture &input, PPTexture &output, PPViewport viewport, bool vertical) +{ + renderstate->Clear(); + renderstate->Shader = vertical ? &BlurVertical : &BlurHorizontal; + renderstate->Uniforms.Set(blurUniforms); + renderstate->Viewport = viewport; + renderstate->SetInputTexture(0, &input); + renderstate->SetOutputTexture(&output); + renderstate->SetNoBlend(); + renderstate->Draw(); +} + +float PPBloom::ComputeBlurGaussian(float n, float theta) // theta = Blur Amount +{ + return (float)((1.0f / sqrtf(2 * (float)M_PI * theta)) * expf(-(n * n) / (2.0f * theta * theta))); +} + +void PPBloom::ComputeBlurSamples(int sampleCount, float blurAmount, float *sampleWeights) +{ + sampleWeights[0] = ComputeBlurGaussian(0, blurAmount); + + float totalWeights = sampleWeights[0]; + + for (int i = 0; i < sampleCount / 2; i++) + { + float weight = ComputeBlurGaussian(i + 1.0f, blurAmount); + + sampleWeights[i * 2 + 1] = weight; + sampleWeights[i * 2 + 2] = weight; + + totalWeights += weight * 2; + } + + for (int i = 0; i < sampleCount; i++) + { + sampleWeights[i] /= totalWeights; + } +} + +///////////////////////////////////////////////////////////////////////////// + +void PPLensDistort::Render(PPRenderState *renderstate) +{ + if (gl_lens == 0) + { + return; + } + + float k[4] = + { + gl_lens_k, + gl_lens_k * gl_lens_chromatic, + gl_lens_k * gl_lens_chromatic * gl_lens_chromatic, + 0.0f + }; + float kcube[4] = + { + gl_lens_kcube, + gl_lens_kcube * gl_lens_chromatic, + gl_lens_kcube * gl_lens_chromatic * gl_lens_chromatic, + 0.0f + }; + + float aspect = screen->mSceneViewport.width / (float)screen->mSceneViewport.height; + + // Scale factor to keep sampling within the input texture + float r2 = aspect * aspect * 0.25f + 0.25f; + float sqrt_r2 = sqrt(r2); + float f0 = 1.0f + std::max(r2 * (k[0] + kcube[0] * sqrt_r2), 0.0f); + float f2 = 1.0f + std::max(r2 * (k[2] + kcube[2] * sqrt_r2), 0.0f); + float f = std::max(f0, f2); + float scale = 1.0f / f; + + LensUniforms uniforms; + uniforms.AspectRatio = aspect; + uniforms.Scale = scale; + uniforms.LensDistortionCoefficient = k; + uniforms.CubicDistortionValue = kcube; + + renderstate->PushGroup("lens"); + + renderstate->Clear(); + renderstate->Shader = &Lens; + renderstate->Uniforms.Set(uniforms); + renderstate->Viewport = screen->mScreenViewport; + renderstate->SetInputCurrent(0, PPFilterMode::Linear); + renderstate->SetOutputNext(); + renderstate->SetNoBlend(); + renderstate->Draw(); + + renderstate->PopGroup(); +} + +///////////////////////////////////////////////////////////////////////////// + +void PPFXAA::Render(PPRenderState *renderstate) +{ + if (0 == gl_fxaa) + { + return; + } + + CreateShaders(); + + FXAAUniforms uniforms; + uniforms.ReciprocalResolution = { 1.0f / screen->mScreenViewport.width, 1.0f / screen->mScreenViewport.height }; + + renderstate->PushGroup("fxaa"); + + renderstate->Clear(); + renderstate->Shader = &FXAALuma; + renderstate->Uniforms.Clear(); + renderstate->Viewport = screen->mScreenViewport; + renderstate->SetInputCurrent(0, PPFilterMode::Nearest); + renderstate->SetOutputNext(); + renderstate->SetNoBlend(); + renderstate->Draw(); + + renderstate->Shader = &FXAA; + renderstate->Uniforms.Set(uniforms); + renderstate->SetInputCurrent(0, PPFilterMode::Linear); + renderstate->Draw(); + + renderstate->PopGroup(); +} + +int PPFXAA::GetMaxVersion() +{ + return screen->glslversion >= 4.f ? 400 : 330; +} + +void PPFXAA::CreateShaders() +{ + if (LastQuality == gl_fxaa) + return; + + FXAALuma = { "shaders/glsl/fxaa.fp", "#define FXAA_LUMA_PASS\n", {} }; + FXAA = { "shaders/glsl/fxaa.fp", GetDefines(), FXAAUniforms::Desc(), GetMaxVersion() }; + LastQuality = gl_fxaa; +} + +FString PPFXAA::GetDefines() +{ + int quality; + + switch (gl_fxaa) + { + default: + case IFXAAShader::Low: quality = 10; break; + case IFXAAShader::Medium: quality = 12; break; + case IFXAAShader::High: quality = 29; break; + case IFXAAShader::Extreme: quality = 39; break; + } + + const int gatherAlpha = GetMaxVersion() >= 400 ? 1 : 0; + + // TODO: enable FXAA_GATHER4_ALPHA on OpenGL earlier than 4.0 + // when GL_ARB_gpu_shader5/GL_NV_gpu_shader5 extensions are supported + + FString result; + result.Format( + "#define FXAA_QUALITY__PRESET %i\n" + "#define FXAA_GATHER4_ALPHA %i\n", + quality, gatherAlpha); + + return result; +} + +///////////////////////////////////////////////////////////////////////////// + +void PPCameraExposure::Render(PPRenderState *renderstate, int sceneWidth, int sceneHeight) +{ + if (!gl_bloom) + { + return; + } + + renderstate->PushGroup("exposure"); + + UpdateTextures(sceneWidth, sceneHeight); + + ExposureExtractUniforms extractUniforms; + extractUniforms.Scale = screen->SceneScale(); + extractUniforms.Offset = screen->SceneOffset(); + + ExposureCombineUniforms combineUniforms; + combineUniforms.ExposureBase = gl_exposure_base; + combineUniforms.ExposureMin = gl_exposure_min; + combineUniforms.ExposureScale = gl_exposure_scale; + combineUniforms.ExposureSpeed = gl_exposure_speed; + + auto &level0 = ExposureLevels[0]; + + // Extract light blevel from scene texture: + renderstate->Clear(); + renderstate->Shader = &ExposureExtract; + renderstate->Uniforms.Set(extractUniforms); + renderstate->Viewport = level0.Viewport; + renderstate->SetInputCurrent(0, PPFilterMode::Linear); + renderstate->SetOutputTexture(&level0.Texture); + renderstate->SetNoBlend(); + renderstate->Draw(); + + // Find the average value: + for (size_t i = 0; i + 1 < ExposureLevels.size(); i++) + { + auto &blevel = ExposureLevels[i]; + auto &next = ExposureLevels[i + 1]; + + renderstate->Shader = &ExposureAverage; + renderstate->Uniforms.Clear(); + renderstate->Viewport = next.Viewport; + renderstate->SetInputTexture(0, &blevel.Texture, PPFilterMode::Linear); + renderstate->SetOutputTexture(&next.Texture); + renderstate->SetNoBlend(); + renderstate->Draw(); + } + + // Combine average value with current camera exposure: + renderstate->Shader = &ExposureCombine; + renderstate->Uniforms.Set(combineUniforms); + renderstate->Viewport.left = 0; + renderstate->Viewport.top = 0; + renderstate->Viewport.width = 1; + renderstate->Viewport.height = 1; + renderstate->SetInputTexture(0, &ExposureLevels.back().Texture, PPFilterMode::Linear); + renderstate->SetOutputTexture(&CameraTexture); + if (!FirstExposureFrame) + renderstate->SetAlphaBlend(); + else + renderstate->SetNoBlend(); + renderstate->Draw(); + + renderstate->PopGroup(); + + FirstExposureFrame = false; +} + +void PPCameraExposure::UpdateTextures(int width, int height) +{ + int firstwidth = std::max(width / 2, 1); + int firstheight = std::max(height / 2, 1); + + if (ExposureLevels.size() > 0 && ExposureLevels[0].Viewport.width == firstwidth && ExposureLevels[0].Viewport.height == firstheight) + { + return; + } + + ExposureLevels.clear(); + + int i = 0; + do + { + width = std::max(width / 2, 1); + height = std::max(height / 2, 1); + + PPExposureLevel blevel; + blevel.Viewport.left = 0; + blevel.Viewport.top = 0; + blevel.Viewport.width = width; + blevel.Viewport.height = height; + blevel.Texture = { blevel.Viewport.width, blevel.Viewport.height, PixelFormat::R32f }; + ExposureLevels.push_back(std::move(blevel)); + + i++; + + } while (width > 1 || height > 1); + + FirstExposureFrame = true; +} + +///////////////////////////////////////////////////////////////////////////// + +void PPTonemap::UpdateTextures() +{ + if (gl_tonemap == Palette && !PaletteTexture.Data) + { + std::shared_ptr data(new uint32_t[512 * 512], [](void *p) { delete[](uint32_t*)p; }); + + uint8_t *lut = (uint8_t *)data.get(); + for (int r = 0; r < 64; r++) + { + for (int g = 0; g < 64; g++) + { + for (int b = 0; b < 64; b++) + { + PalEntry color = ImageHelpers::BaseColors[(uint8_t)ImageHelpers::PTM_BestColor((r << 2) | (r >> 4), (g << 2) | (g >> 4), (b << 2) | (b >> 4), + gl_paltonemap_reverselookup, gl_paltonemap_powtable, 0, 256)]; + int index = ((r * 64 + g) * 64 + b) * 4; + lut[index] = color.r; + lut[index + 1] = color.g; + lut[index + 2] = color.b; + lut[index + 3] = 255; + } + } + } + + PaletteTexture = { 512, 512, PixelFormat::Rgba8, data }; + } +} + +void PPTonemap::Render(PPRenderState *renderstate) +{ + if (gl_tonemap == 0) + { + return; + } + + UpdateTextures(); + + PPShader *shader = nullptr; + switch (gl_tonemap) + { + default: + case Linear: shader = &LinearShader; break; + case Reinhard: shader = &ReinhardShader; break; + case HejlDawson: shader = &HejlDawsonShader; break; + case Uncharted2: shader = &Uncharted2Shader; break; + case Palette: shader = &PaletteShader; break; + } + + renderstate->PushGroup("tonemap"); + + renderstate->Clear(); + renderstate->Shader = shader; + renderstate->Viewport = screen->mScreenViewport; + renderstate->SetInputCurrent(0); + if (gl_tonemap == Palette) + renderstate->SetInputTexture(1, &PaletteTexture); + renderstate->SetOutputNext(); + renderstate->SetNoBlend(); + renderstate->Draw(); + + renderstate->PopGroup(); +} + +///////////////////////////////////////////////////////////////////////////// + +PPAmbientOcclusion::PPAmbientOcclusion() +{ + // Must match quality enum in PPAmbientOcclusion::DeclareShaders + double numDirections[NumAmbientRandomTextures] = { 2.0, 4.0, 8.0 }; + + std::mt19937 generator(1337); + std::uniform_real_distribution distribution(0.0, 1.0); + for (int quality = 0; quality < NumAmbientRandomTextures; quality++) + { + std::shared_ptr data(new int16_t[16 * 4], [](void *p) { delete[](int16_t*)p; }); + int16_t *randomValues = (int16_t *)data.get(); + + for (int i = 0; i < 16; i++) + { + double angle = 2.0 * M_PI * distribution(generator) / numDirections[quality]; + double x = cos(angle); + double y = sin(angle); + double z = distribution(generator); + double w = distribution(generator); + + randomValues[i * 4 + 0] = (int16_t)clamp(x * 32767.0, -32768.0, 32767.0); + randomValues[i * 4 + 1] = (int16_t)clamp(y * 32767.0, -32768.0, 32767.0); + randomValues[i * 4 + 2] = (int16_t)clamp(z * 32767.0, -32768.0, 32767.0); + randomValues[i * 4 + 3] = (int16_t)clamp(w * 32767.0, -32768.0, 32767.0); + } + + AmbientRandomTexture[quality] = { 4, 4, PixelFormat::Rgba16_snorm, data }; + } +} + +void PPAmbientOcclusion::CreateShaders() +{ + if (gl_ssao == LastQuality) + return; + + // Must match quality values in PPAmbientOcclusion::UpdateTextures + int numDirections, numSteps; + switch (gl_ssao) + { + default: + case LowQuality: numDirections = 2; numSteps = 4; break; + case MediumQuality: numDirections = 4; numSteps = 4; break; + case HighQuality: numDirections = 8; numSteps = 4; break; + } + + FString defines; + defines.Format(R"( + #define USE_RANDOM_TEXTURE + #define RANDOM_TEXTURE_WIDTH 4.0 + #define NUM_DIRECTIONS %d.0 + #define NUM_STEPS %d.0 + )", numDirections, numSteps); + + LinearDepth = { "shaders/glsl/lineardepth.fp", "", LinearDepthUniforms::Desc() }; + LinearDepthMS = { "shaders/glsl/lineardepth.fp", "#define MULTISAMPLE\n", LinearDepthUniforms::Desc() }; + AmbientOcclude = { "shaders/glsl/ssao.fp", defines, SSAOUniforms::Desc() }; + AmbientOccludeMS = { "shaders/glsl/ssao.fp", defines + "\n#define MULTISAMPLE\n", SSAOUniforms::Desc() }; + BlurVertical = { "shaders/glsl/depthblur.fp", "#define BLUR_VERTICAL\n", DepthBlurUniforms::Desc() }; + BlurHorizontal = { "shaders/glsl/depthblur.fp", "#define BLUR_HORIZONTAL\n", DepthBlurUniforms::Desc() }; + Combine = { "shaders/glsl/ssaocombine.fp", "", AmbientCombineUniforms::Desc() }; + CombineMS = { "shaders/glsl/ssaocombine.fp", "#define MULTISAMPLE\n", AmbientCombineUniforms::Desc() }; + + LastQuality = gl_ssao; +} + +void PPAmbientOcclusion::UpdateTextures(int width, int height) +{ + if ((width <= 0 || height <= 0) || (width == LastWidth && height == LastHeight)) + return; + + AmbientWidth = (width + 1) / 2; + AmbientHeight = (height + 1) / 2; + + LinearDepthTexture = { AmbientWidth, AmbientHeight, PixelFormat::R32f }; + Ambient0 = { AmbientWidth, AmbientHeight, PixelFormat::Rg16f }; + Ambient1 = { AmbientWidth, AmbientHeight, PixelFormat::Rg16f }; + + LastWidth = width; + LastHeight = height; +} + +void PPAmbientOcclusion::Render(PPRenderState *renderstate, float m5, int sceneWidth, int sceneHeight) +{ + if (gl_ssao == 0 || sceneWidth == 0 || sceneHeight == 0) + { + return; + } + + CreateShaders(); + UpdateTextures(sceneWidth, sceneHeight); + + float bias = gl_ssao_bias; + float aoRadius = gl_ssao_radius; + const float blurAmount = gl_ssao_blur; + float aoStrength = gl_ssao_strength; + + //float tanHalfFovy = tan(fovy * (M_PI / 360.0f)); + float tanHalfFovy = 1.0f / m5; + float invFocalLenX = tanHalfFovy * (sceneWidth / (float)sceneHeight); + float invFocalLenY = tanHalfFovy; + float nDotVBias = clamp(bias, 0.0f, 1.0f); + float r2 = aoRadius * aoRadius; + + float blurSharpness = 1.0f / blurAmount; + + auto sceneScale = screen->SceneScale(); + auto sceneOffset = screen->SceneOffset(); + + int randomTexture = clamp(gl_ssao - 1, 0, NumAmbientRandomTextures - 1); + + LinearDepthUniforms linearUniforms; + linearUniforms.SampleIndex = 0; + linearUniforms.LinearizeDepthA = 1.0f / screen->GetZFar() - 1.0f / screen->GetZNear(); + linearUniforms.LinearizeDepthB = std::max(1.0f / screen->GetZNear(), 1.e-8f); + linearUniforms.InverseDepthRangeA = 1.0f; + linearUniforms.InverseDepthRangeB = 0.0f; + linearUniforms.Scale = sceneScale; + linearUniforms.Offset = sceneOffset; + + SSAOUniforms ssaoUniforms; + ssaoUniforms.SampleIndex = 0; + ssaoUniforms.UVToViewA = { 2.0f * invFocalLenX, 2.0f * invFocalLenY }; + ssaoUniforms.UVToViewB = { -invFocalLenX, -invFocalLenY }; + ssaoUniforms.InvFullResolution = { 1.0f / AmbientWidth, 1.0f / AmbientHeight }; + ssaoUniforms.NDotVBias = nDotVBias; + ssaoUniforms.NegInvR2 = -1.0f / r2; + ssaoUniforms.RadiusToScreen = aoRadius * 0.5f / tanHalfFovy * AmbientHeight; + ssaoUniforms.AOMultiplier = 1.0f / (1.0f - nDotVBias); + ssaoUniforms.AOStrength = aoStrength; + ssaoUniforms.Scale = sceneScale; + ssaoUniforms.Offset = sceneOffset; + + DepthBlurUniforms blurUniforms; + blurUniforms.BlurSharpness = blurSharpness; + blurUniforms.PowExponent = gl_ssao_exponent; + + AmbientCombineUniforms combineUniforms; + combineUniforms.SampleCount = gl_multisample; + combineUniforms.Scale = screen->SceneScale(); + combineUniforms.Offset = screen->SceneOffset(); + combineUniforms.DebugMode = gl_ssao_debug; + + IntRect ambientViewport; + ambientViewport.left = 0; + ambientViewport.top = 0; + ambientViewport.width = AmbientWidth; + ambientViewport.height = AmbientHeight; + + renderstate->PushGroup("ssao"); + + // Calculate linear depth values + renderstate->Clear(); + renderstate->Shader = gl_multisample > 1 ? &LinearDepthMS : &LinearDepth; + renderstate->Uniforms.Set(linearUniforms); + renderstate->Viewport = ambientViewport; + renderstate->SetInputSceneDepth(0); + renderstate->SetInputSceneColor(1); + renderstate->SetOutputTexture(&LinearDepthTexture); + renderstate->SetNoBlend(); + renderstate->Draw(); + + // Apply ambient occlusion + renderstate->Clear(); + renderstate->Shader = gl_multisample > 1 ? &AmbientOccludeMS : &AmbientOcclude; + renderstate->Uniforms.Set(ssaoUniforms); + renderstate->Viewport = ambientViewport; + renderstate->SetInputTexture(0, &LinearDepthTexture); + renderstate->SetInputSceneNormal(1); + renderstate->SetInputTexture(2, &AmbientRandomTexture[randomTexture], PPFilterMode::Nearest, PPWrapMode::Repeat); + renderstate->SetOutputTexture(&Ambient0); + renderstate->SetNoBlend(); + renderstate->Draw(); + + // Blur SSAO texture + if (gl_ssao_debug < 2) + { + renderstate->Clear(); + renderstate->Shader = &BlurHorizontal; + renderstate->Uniforms.Set(blurUniforms); + renderstate->Viewport = ambientViewport; + renderstate->SetInputTexture(0, &Ambient0); + renderstate->SetOutputTexture(&Ambient1); + renderstate->SetNoBlend(); + renderstate->Draw(); + + renderstate->Clear(); + renderstate->Shader = &BlurVertical; + renderstate->Uniforms.Set(blurUniforms); + renderstate->Viewport = ambientViewport; + renderstate->SetInputTexture(0, &Ambient1); + renderstate->SetOutputTexture(&Ambient0); + renderstate->SetNoBlend(); + renderstate->Draw(); + } + + // Add SSAO back to scene texture: + renderstate->Clear(); + renderstate->Shader = gl_multisample > 1 ? &CombineMS : &Combine; + renderstate->Uniforms.Set(combineUniforms); + renderstate->Viewport = screen->mSceneViewport; + if (gl_ssao_debug < 4) + renderstate->SetInputTexture(0, &Ambient0, PPFilterMode::Linear); + else + renderstate->SetInputSceneNormal(0, PPFilterMode::Linear); + renderstate->SetInputSceneFog(1); + renderstate->SetOutputSceneColor(); + if (gl_ssao_debug != 0) + renderstate->SetNoBlend(); + else + renderstate->SetAlphaBlend(); + renderstate->Draw(); + + renderstate->PopGroup(); +} + +///////////////////////////////////////////////////////////////////////////// + +PPPresent::PPPresent() +{ + static const float data[64] = + { + .0078125, .2578125, .1328125, .3828125, .0234375, .2734375, .1484375, .3984375, + .7578125, .5078125, .8828125, .6328125, .7734375, .5234375, .8984375, .6484375, + .0703125, .3203125, .1953125, .4453125, .0859375, .3359375, .2109375, .4609375, + .8203125, .5703125, .9453125, .6953125, .8359375, .5859375, .9609375, .7109375, + .0390625, .2890625, .1640625, .4140625, .0546875, .3046875, .1796875, .4296875, + .7890625, .5390625, .9140625, .6640625, .8046875, .5546875, .9296875, .6796875, + .1015625, .3515625, .2265625, .4765625, .1171875, .3671875, .2421875, .4921875, + .8515625, .6015625, .9765625, .7265625, .8671875, .6171875, .9921875, .7421875, + }; + + std::shared_ptr pixels(new float[64], [](void *p) { delete[](float*)p; }); + memcpy(pixels.get(), data, 64 * sizeof(float)); + Dither = { 8, 8, PixelFormat::R32f, pixels }; +} + + + +void Postprocess::Pass1(PPRenderState* state, int fixedcm, int sceneWidth, int sceneHeight) +{ + exposure.Render(state, sceneWidth, sceneHeight); + bloom.RenderBloom(state, sceneWidth, sceneHeight, fixedcm); +} + +void Postprocess::Pass2(PPRenderState* state, int fixedcm, int sceneWidth, int sceneHeight) +{ + tonemap.Render(state); + lens.Render(state); + fxaa.Render(state); +} diff --git a/source/common/rendering/hwrenderer/postprocessing/hw_postprocess.h b/source/common/rendering/hwrenderer/postprocessing/hw_postprocess.h new file mode 100644 index 000000000..9b8033c9a --- /dev/null +++ b/source/common/rendering/hwrenderer/postprocessing/hw_postprocess.h @@ -0,0 +1,771 @@ +#pragma once + +#include "hwrenderer/data/shaderuniforms.h" +#include +#include + +struct PostProcessShader; + +typedef FRenderStyle PPBlendMode; +typedef IntRect PPViewport; + +class PPTexture; +class PPShader; + +enum class PPFilterMode { Nearest, Linear }; +enum class PPWrapMode { Clamp, Repeat }; +enum class PPTextureType { CurrentPipelineTexture, NextPipelineTexture, PPTexture, SceneColor, SceneFog, SceneNormal, SceneDepth, SwapChain, ShadowMap }; + +class PPTextureInput +{ +public: + PPFilterMode Filter = PPFilterMode::Nearest; + PPWrapMode Wrap = PPWrapMode::Clamp; + PPTextureType Type = PPTextureType::CurrentPipelineTexture; + PPTexture *Texture = nullptr; +}; + +class PPOutput +{ +public: + PPTextureType Type = PPTextureType::NextPipelineTexture; + PPTexture *Texture = nullptr; +}; + +class PPUniforms +{ +public: + PPUniforms() + { + } + + PPUniforms(const PPUniforms &src) + { + Data = src.Data; + } + + ~PPUniforms() + { + Clear(); + } + + PPUniforms &operator=(const PPUniforms &src) + { + Data = src.Data; + return *this; + } + + void Clear() + { + Data.Clear(); + } + + template + void Set(const T &v) + { + if (Data.Size() != (int)sizeof(T)) + { + Data.Resize(sizeof(T)); + memcpy(Data.Data(), &v, Data.Size()); + } + } + + TArray Data; +}; + +class PPRenderState +{ +public: + virtual ~PPRenderState() = default; + + virtual void PushGroup(const FString &name) = 0; + virtual void PopGroup() = 0; + + virtual void Draw() = 0; + + void Clear() + { + Shader = nullptr; + Textures = TArray(); + Uniforms = PPUniforms(); + Viewport = PPViewport(); + BlendMode = PPBlendMode(); + Output = PPOutput(); + ShadowMapBuffers = false; + } + + void SetInputTexture(int index, PPTexture *texture, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp) + { + if ((int)Textures.Size() < index + 1) + Textures.Resize(index + 1); + auto &tex = Textures[index]; + tex.Filter = filter; + tex.Wrap = wrap; + tex.Type = PPTextureType::PPTexture; + tex.Texture = texture; + } + + void SetInputCurrent(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp) + { + SetInputSpecialType(index, PPTextureType::CurrentPipelineTexture, filter, wrap); + } + + void SetInputSceneColor(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp) + { + SetInputSpecialType(index, PPTextureType::SceneColor, filter, wrap); + } + + void SetInputSceneFog(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp) + { + SetInputSpecialType(index, PPTextureType::SceneFog, filter, wrap); + } + + void SetInputSceneNormal(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp) + { + SetInputSpecialType(index, PPTextureType::SceneNormal, filter, wrap); + } + + void SetInputSceneDepth(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp) + { + SetInputSpecialType(index, PPTextureType::SceneDepth, filter, wrap); + } + + void SetInputSpecialType(int index, PPTextureType type, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp) + { + if ((int)Textures.Size() < index + 1) + Textures.Resize(index + 1); + auto &tex = Textures[index]; + tex.Filter = filter; + tex.Wrap = wrap; + tex.Type = type; + tex.Texture = nullptr; + } + + void SetShadowMapBuffers(bool enable) + { + ShadowMapBuffers = enable; + } + + void SetOutputTexture(PPTexture *texture) + { + Output.Type = PPTextureType::PPTexture; + Output.Texture = texture; + } + + void SetOutputCurrent() + { + Output.Type = PPTextureType::CurrentPipelineTexture; + Output.Texture = nullptr; + } + + void SetOutputNext() + { + Output.Type = PPTextureType::NextPipelineTexture; + Output.Texture = nullptr; + } + + void SetOutputSceneColor() + { + Output.Type = PPTextureType::SceneColor; + Output.Texture = nullptr; + } + + void SetOutputSwapChain() + { + Output.Type = PPTextureType::SwapChain; + Output.Texture = nullptr; + } + + void SetOutputShadowMap() + { + Output.Type = PPTextureType::ShadowMap; + Output.Texture = nullptr; + } + + void SetNoBlend() + { + BlendMode.BlendOp = STYLEOP_Add; + BlendMode.SrcAlpha = STYLEALPHA_One; + BlendMode.DestAlpha = STYLEALPHA_Zero; + BlendMode.Flags = 0; + } + + void SetAdditiveBlend() + { + BlendMode.BlendOp = STYLEOP_Add; + BlendMode.SrcAlpha = STYLEALPHA_One; + BlendMode.DestAlpha = STYLEALPHA_One; + BlendMode.Flags = 0; + } + + void SetAlphaBlend() + { + BlendMode.BlendOp = STYLEOP_Add; + BlendMode.SrcAlpha = STYLEALPHA_Src; + BlendMode.DestAlpha = STYLEALPHA_InvSrc; + BlendMode.Flags = 0; + } + + PPShader *Shader; + TArray Textures; + PPUniforms Uniforms; + PPViewport Viewport; + PPBlendMode BlendMode; + PPOutput Output; + bool ShadowMapBuffers = false; +}; + +enum class PixelFormat +{ + Rgba8, + Rgba16f, + R32f, + Rg16f, + Rgba16_snorm +}; + +class PPResource +{ +public: + PPResource() + { + Next = First; + First = this; + if (Next) Next->Prev = this; + } + + PPResource(const PPResource &) + { + Next = First; + First = this; + if (Next) Next->Prev = this; + } + + virtual ~PPResource() + { + if (Next) Next->Prev = Prev; + if (Prev) Prev->Next = Next; + else First = Next; + } + + PPResource &operator=(const PPResource &other) + { + return *this; + } + + static void ResetAll() + { + for (PPResource *cur = First; cur; cur = cur->Next) + cur->ResetBackend(); + } + + virtual void ResetBackend() = 0; + +private: + static PPResource *First; + PPResource *Prev = nullptr; + PPResource *Next = nullptr; +}; + +class PPTextureBackend +{ +public: + virtual ~PPTextureBackend() = default; +}; + +class PPTexture : public PPResource +{ +public: + PPTexture() = default; + PPTexture(int width, int height, PixelFormat format, std::shared_ptr data = {}) : Width(width), Height(height), Format(format), Data(data) { } + + void ResetBackend() override { Backend.reset(); } + + int Width; + int Height; + PixelFormat Format; + std::shared_ptr Data; + + std::unique_ptr Backend; +}; + +class PPShaderBackend +{ +public: + virtual ~PPShaderBackend() = default; +}; + +class PPShader : public PPResource +{ +public: + PPShader() = default; + PPShader(const FString &fragment, const FString &defines, const std::vector &uniforms, int version = 330) : FragmentShader(fragment), Defines(defines), Uniforms(uniforms), Version(version) { } + + void ResetBackend() override { Backend.reset(); } + + FString VertexShader = "shaders/glsl/screenquad.vp"; + FString FragmentShader; + FString Defines; + std::vector Uniforms; + int Version = 330; + + std::unique_ptr Backend; +}; + +///////////////////////////////////////////////////////////////////////////// + +struct ExtractUniforms +{ + FVector2 Scale; + FVector2 Offset; + + static std::vector Desc() + { + return + { + { "Scale", UniformType::Vec2, offsetof(ExtractUniforms, Scale) }, + { "Offset", UniformType::Vec2, offsetof(ExtractUniforms, Offset) } + }; + } +}; + +struct BlurUniforms +{ + float SampleWeights[8]; + + static std::vector Desc() + { + return + { + { "SampleWeights0", UniformType::Float, offsetof(BlurUniforms, SampleWeights[0]) }, + { "SampleWeights1", UniformType::Float, offsetof(BlurUniforms, SampleWeights[1]) }, + { "SampleWeights2", UniformType::Float, offsetof(BlurUniforms, SampleWeights[2]) }, + { "SampleWeights3", UniformType::Float, offsetof(BlurUniforms, SampleWeights[3]) }, + { "SampleWeights4", UniformType::Float, offsetof(BlurUniforms, SampleWeights[4]) }, + { "SampleWeights5", UniformType::Float, offsetof(BlurUniforms, SampleWeights[5]) }, + { "SampleWeights6", UniformType::Float, offsetof(BlurUniforms, SampleWeights[6]) }, + { "SampleWeights7", UniformType::Float, offsetof(BlurUniforms, SampleWeights[7]) }, + }; + } +}; + +enum { NumBloomLevels = 4 }; + +class PPBlurLevel +{ +public: + PPViewport Viewport; + PPTexture VTexture; + PPTexture HTexture; +}; + +class PPBloom +{ +public: + void RenderBloom(PPRenderState *renderstate, int sceneWidth, int sceneHeight, int fixedcm); + void RenderBlur(PPRenderState *renderstate, int sceneWidth, int sceneHeight, float gameinfobluramount); + +private: + void BlurStep(PPRenderState *renderstate, const BlurUniforms &blurUniforms, PPTexture &input, PPTexture &output, PPViewport viewport, bool vertical); + void UpdateTextures(int width, int height); + + static float ComputeBlurGaussian(float n, float theta); + static void ComputeBlurSamples(int sampleCount, float blurAmount, float *sampleWeights); + + PPBlurLevel levels[NumBloomLevels]; + int lastWidth = 0; + int lastHeight = 0; + + PPShader BloomCombine = { "shaders/glsl/bloomcombine.fp", "", {} }; + PPShader BloomExtract = { "shaders/glsl/bloomextract.fp", "", ExtractUniforms::Desc() }; + PPShader BlurVertical = { "shaders/glsl/blur.fp", "#define BLUR_VERTICAL\n", BlurUniforms::Desc() }; + PPShader BlurHorizontal = { "shaders/glsl/blur.fp", "#define BLUR_HORIZONTAL\n", BlurUniforms::Desc() }; +}; + +///////////////////////////////////////////////////////////////////////////// + +struct LensUniforms +{ + float AspectRatio; + float Scale; + float Padding0, Padding1; + FVector4 LensDistortionCoefficient; + FVector4 CubicDistortionValue; + + static std::vector Desc() + { + return + { + { "Aspect", UniformType::Float, offsetof(LensUniforms, AspectRatio) }, + { "Scale", UniformType::Float, offsetof(LensUniforms, Scale) }, + { "Padding0", UniformType::Float, offsetof(LensUniforms, Padding0) }, + { "Padding1", UniformType::Float, offsetof(LensUniforms, Padding1) }, + { "k", UniformType::Vec4, offsetof(LensUniforms, LensDistortionCoefficient) }, + { "kcube", UniformType::Vec4, offsetof(LensUniforms, CubicDistortionValue) } + }; + } +}; + +class PPLensDistort +{ +public: + void Render(PPRenderState *renderstate); + +private: + PPShader Lens = { "shaders/glsl/lensdistortion.fp", "", LensUniforms::Desc() }; +}; + +///////////////////////////////////////////////////////////////////////////// + +struct FXAAUniforms +{ + FVector2 ReciprocalResolution; + float Padding0, Padding1; + + static std::vector Desc() + { + return + { + { "ReciprocalResolution", UniformType::Vec2, offsetof(FXAAUniforms, ReciprocalResolution) }, + { "Padding0", UniformType::Float, offsetof(FXAAUniforms, Padding0) }, + { "Padding1", UniformType::Float, offsetof(FXAAUniforms, Padding1) } + }; + } +}; + +class PPFXAA +{ +public: + void Render(PPRenderState *renderstate); + +private: + void CreateShaders(); + int GetMaxVersion(); + FString GetDefines(); + + PPShader FXAALuma; + PPShader FXAA; + int LastQuality = -1; +}; + +///////////////////////////////////////////////////////////////////////////// + +struct ExposureExtractUniforms +{ + FVector2 Scale; + FVector2 Offset; + + static std::vector Desc() + { + return + { + { "Scale", UniformType::Vec2, offsetof(ExposureExtractUniforms, Scale) }, + { "Offset", UniformType::Vec2, offsetof(ExposureExtractUniforms, Offset) } + }; + } +}; + +struct ExposureCombineUniforms +{ + float ExposureBase; + float ExposureMin; + float ExposureScale; + float ExposureSpeed; + + static std::vector Desc() + { + return + { + { "ExposureBase", UniformType::Float, offsetof(ExposureCombineUniforms, ExposureBase) }, + { "ExposureMin", UniformType::Float, offsetof(ExposureCombineUniforms, ExposureMin) }, + { "ExposureScale", UniformType::Float, offsetof(ExposureCombineUniforms, ExposureScale) }, + { "ExposureSpeed", UniformType::Float, offsetof(ExposureCombineUniforms, ExposureSpeed) } + }; + } +}; + +class PPExposureLevel +{ +public: + PPViewport Viewport; + PPTexture Texture; +}; + +class PPCameraExposure +{ +public: + void Render(PPRenderState *renderstate, int sceneWidth, int sceneHeight); + + PPTexture CameraTexture = { 1, 1, PixelFormat::R32f }; + +private: + void UpdateTextures(int width, int height); + + std::vector ExposureLevels; + bool FirstExposureFrame = true; + + PPShader ExposureExtract = { "shaders/glsl/exposureextract.fp", "", ExposureExtractUniforms::Desc() }; + PPShader ExposureAverage = { "shaders/glsl/exposureaverage.fp", "", {}, 400 }; + PPShader ExposureCombine = { "shaders/glsl/exposurecombine.fp", "", ExposureCombineUniforms::Desc() }; +}; + +///////////////////////////////////////////////////////////////////////////// + +struct ColormapUniforms +{ + FVector4 MapStart; + FVector4 MapRange; + + static std::vector Desc() + { + return + { + { "uFixedColormapStart", UniformType::Vec4, offsetof(ColormapUniforms, MapStart) }, + { "uFixedColormapRange", UniformType::Vec4, offsetof(ColormapUniforms, MapRange) }, + }; + } +}; + +///////////////////////////////////////////////////////////////////////////// + +class PPTonemap +{ +public: + void Render(PPRenderState *renderstate); + void ClearTonemapPalette() { PaletteTexture = {}; } + +private: + void UpdateTextures(); + + PPTexture PaletteTexture; + + PPShader LinearShader = { "shaders/glsl/tonemap.fp", "#define LINEAR\n", {} }; + PPShader ReinhardShader = { "shaders/glsl/tonemap.fp", "#define REINHARD\n", {} }; + PPShader HejlDawsonShader = { "shaders/glsl/tonemap.fp", "#define HEJLDAWSON\n", {} }; + PPShader Uncharted2Shader = { "shaders/glsl/tonemap.fp", "#define UNCHARTED2\n", {} }; + PPShader PaletteShader = { "shaders/glsl/tonemap.fp", "#define PALETTE\n", {} }; + + enum TonemapMode + { + None, + Uncharted2, + HejlDawson, + Reinhard, + Linear, + Palette, + NumTonemapModes + }; +}; + +///////////////////////////////////////////////////////////////////////////// + +struct LinearDepthUniforms +{ + int SampleIndex; + float LinearizeDepthA; + float LinearizeDepthB; + float InverseDepthRangeA; + float InverseDepthRangeB; + float Padding0, Padding1, Padding2; + FVector2 Scale; + FVector2 Offset; + + static std::vector Desc() + { + return + { + { "SampleIndex", UniformType::Int, offsetof(LinearDepthUniforms, SampleIndex) }, + { "LinearizeDepthA", UniformType::Float, offsetof(LinearDepthUniforms, LinearizeDepthA) }, + { "LinearizeDepthB", UniformType::Float, offsetof(LinearDepthUniforms, LinearizeDepthB) }, + { "InverseDepthRangeA", UniformType::Float, offsetof(LinearDepthUniforms, InverseDepthRangeA) }, + { "InverseDepthRangeB", UniformType::Float, offsetof(LinearDepthUniforms, InverseDepthRangeB) }, + { "Padding0", UniformType::Float, offsetof(LinearDepthUniforms, Padding0) }, + { "Padding1", UniformType::Float, offsetof(LinearDepthUniforms, Padding1) }, + { "Padding2", UniformType::Float, offsetof(LinearDepthUniforms, Padding2) }, + { "Scale", UniformType::Vec2, offsetof(LinearDepthUniforms, Scale) }, + { "Offset", UniformType::Vec2, offsetof(LinearDepthUniforms, Offset) } + }; + } +}; + +struct SSAOUniforms +{ + FVector2 UVToViewA; + FVector2 UVToViewB; + FVector2 InvFullResolution; + float NDotVBias; + float NegInvR2; + float RadiusToScreen; + float AOMultiplier; + float AOStrength; + int SampleIndex; + float Padding0, Padding1; + FVector2 Scale; + FVector2 Offset; + + static std::vector Desc() + { + return + { + { "UVToViewA", UniformType::Vec2, offsetof(SSAOUniforms, UVToViewA) }, + { "UVToViewB", UniformType::Vec2, offsetof(SSAOUniforms, UVToViewB) }, + { "InvFullResolution", UniformType::Vec2, offsetof(SSAOUniforms, InvFullResolution) }, + { "NDotVBias", UniformType::Float, offsetof(SSAOUniforms, NDotVBias) }, + { "NegInvR2", UniformType::Float, offsetof(SSAOUniforms, NegInvR2) }, + { "RadiusToScreen", UniformType::Float, offsetof(SSAOUniforms, RadiusToScreen) }, + { "AOMultiplier", UniformType::Float, offsetof(SSAOUniforms, AOMultiplier) }, + { "AOStrength", UniformType::Float, offsetof(SSAOUniforms, AOStrength) }, + { "SampleIndex", UniformType::Int, offsetof(SSAOUniforms, SampleIndex) }, + { "Padding0", UniformType::Float, offsetof(SSAOUniforms, Padding0) }, + { "Padding1", UniformType::Float, offsetof(SSAOUniforms, Padding1) }, + { "Scale", UniformType::Vec2, offsetof(SSAOUniforms, Scale) }, + { "Offset", UniformType::Vec2, offsetof(SSAOUniforms, Offset) }, + }; + } +}; + +struct DepthBlurUniforms +{ + float BlurSharpness; + float PowExponent; + float Padding0, Padding1; + + static std::vector Desc() + { + return + { + { "BlurSharpness", UniformType::Float, offsetof(DepthBlurUniforms, BlurSharpness) }, + { "PowExponent", UniformType::Float, offsetof(DepthBlurUniforms, PowExponent) }, + { "Padding0", UniformType::Float, offsetof(DepthBlurUniforms, Padding0) }, + { "Padding1", UniformType::Float, offsetof(DepthBlurUniforms, Padding1) } + }; + } +}; + +struct AmbientCombineUniforms +{ + int SampleCount; + int DebugMode, Padding1, Padding2; + FVector2 Scale; + FVector2 Offset; + + static std::vector Desc() + { + return + { + { "SampleCount", UniformType::Int, offsetof(AmbientCombineUniforms, SampleCount) }, + { "DebugMode", UniformType::Int, offsetof(AmbientCombineUniforms, DebugMode) }, + { "Padding1", UniformType::Int, offsetof(AmbientCombineUniforms, Padding1) }, + { "Padding2", UniformType::Int, offsetof(AmbientCombineUniforms, Padding2) }, + { "Scale", UniformType::Vec2, offsetof(AmbientCombineUniforms, Scale) }, + { "Offset", UniformType::Vec2, offsetof(AmbientCombineUniforms, Offset) } + }; + } +}; + +class PPAmbientOcclusion +{ +public: + PPAmbientOcclusion(); + void Render(PPRenderState *renderstate, float m5, int sceneWidth, int sceneHeight); + +private: + void CreateShaders(); + void UpdateTextures(int width, int height); + + enum Quality + { + Off, + LowQuality, + MediumQuality, + HighQuality, + NumQualityModes + }; + + int AmbientWidth = 0; + int AmbientHeight = 0; + + int LastQuality = -1; + int LastWidth = 0; + int LastHeight = 0; + + PPShader LinearDepth; + PPShader LinearDepthMS; + PPShader AmbientOcclude; + PPShader AmbientOccludeMS; + PPShader BlurVertical; + PPShader BlurHorizontal; + PPShader Combine; + PPShader CombineMS; + + PPTexture LinearDepthTexture; + PPTexture Ambient0; + PPTexture Ambient1; + + enum { NumAmbientRandomTextures = 3 }; + PPTexture AmbientRandomTexture[NumAmbientRandomTextures]; +}; + +struct PresentUniforms +{ + float InvGamma; + float Contrast; + float Brightness; + float Saturation; + int GrayFormula; + int WindowPositionParity; // top-of-window might not be top-of-screen + FVector2 Scale; + FVector2 Offset; + float ColorScale; + int HdrMode; + + static std::vector Desc() + { + return + { + { "InvGamma", UniformType::Float, offsetof(PresentUniforms, InvGamma) }, + { "Contrast", UniformType::Float, offsetof(PresentUniforms, Contrast) }, + { "Brightness", UniformType::Float, offsetof(PresentUniforms, Brightness) }, + { "Saturation", UniformType::Float, offsetof(PresentUniforms, Saturation) }, + { "GrayFormula", UniformType::Int, offsetof(PresentUniforms, GrayFormula) }, + { "WindowPositionParity", UniformType::Int, offsetof(PresentUniforms, WindowPositionParity) }, + { "UVScale", UniformType::Vec2, offsetof(PresentUniforms, Scale) }, + { "UVOffset", UniformType::Vec2, offsetof(PresentUniforms, Offset) }, + { "ColorScale", UniformType::Float, offsetof(PresentUniforms, ColorScale) }, + { "HdrMode", UniformType::Int, offsetof(PresentUniforms, HdrMode) } + }; + } +}; + +class PPPresent +{ +public: + PPPresent(); + + PPTexture Dither; + + PPShader Present = { "shaders/glsl/present.fp", "", PresentUniforms::Desc() }; + PPShader Checker3D = { "shaders/glsl/present_checker3d.fp", "", PresentUniforms::Desc() }; + PPShader Column3D = { "shaders/glsl/present_column3d.fp", "", PresentUniforms::Desc() }; + PPShader Row3D = { "shaders/glsl/present_row3d.fp", "", PresentUniforms::Desc() }; +}; + +///////////////////////////////////////////////////////////////////////////// + +class Postprocess +{ +public: + PPBloom bloom; + PPLensDistort lens; + PPFXAA fxaa; + PPCameraExposure exposure; + PPTonemap tonemap; + PPAmbientOcclusion ssao; + PPPresent present; + + + void Pass1(PPRenderState *state, int fixedcm, int sceneWidth, int sceneHeight); + void Pass2(PPRenderState* state, int fixedcm, int sceneWidth, int sceneHeight); +}; + +extern Postprocess hw_postprocess; diff --git a/source/common/rendering/hwrenderer/postprocessing/hw_postprocess_cvars.cpp b/source/common/rendering/hwrenderer/postprocessing/hw_postprocess_cvars.cpp new file mode 100644 index 000000000..4f47593f4 --- /dev/null +++ b/source/common/rendering/hwrenderer/postprocessing/hw_postprocess_cvars.cpp @@ -0,0 +1,104 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2016 Magnus Norddahl +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_postprocess.cpp +** Post processing effects in the render pipeline +** +*/ + +#include "hw_postprocess_cvars.h" +#include "v_video.h" + +//========================================================================== +// +// CVARs +// +//========================================================================== +CVAR(Bool, gl_bloom, false, CVAR_ARCHIVE); +CUSTOM_CVAR(Float, gl_bloom_amount, 1.4f, CVAR_ARCHIVE) +{ + if (self < 0.1f) self = 0.1f; +} + +CVAR(Float, gl_exposure_scale, 1.3f, CVAR_ARCHIVE) +CVAR(Float, gl_exposure_min, 0.35f, CVAR_ARCHIVE) +CVAR(Float, gl_exposure_base, 0.35f, CVAR_ARCHIVE) +CVAR(Float, gl_exposure_speed, 0.05f, CVAR_ARCHIVE) + +CUSTOM_CVAR(Int, gl_tonemap, 0, CVAR_ARCHIVE) +{ + if (self < 0 || self > 5) + self = 0; +} + +CVAR(Bool, gl_lens, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CVAR(Float, gl_lens_k, -0.12f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Float, gl_lens_kcube, 0.1f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Float, gl_lens_chromatic, 1.12f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(Int, gl_fxaa, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 0 || self >= IFXAAShader::Count) + { + self = 0; + } +} + +CUSTOM_CVAR(Int, gl_ssao, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 0 || self > 3) + self = 0; +} + +CUSTOM_CVAR(Int, gl_ssao_portals, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; +} + +CVAR(Float, gl_ssao_strength, 0.7f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, gl_ssao_debug, 0, 0) +CVAR(Float, gl_ssao_bias, 0.2f, 0) +CVAR(Float, gl_ssao_radius, 80.0f, 0) +CUSTOM_CVAR(Float, gl_ssao_blur, 16.0f, 0) +{ + if (self < 0.1f) self = 0.1f; +} + +CUSTOM_CVAR(Float, gl_ssao_exponent, 1.8f, 0) +{ + if (self < 0.1f) self = 0.1f; +} + +CUSTOM_CVAR(Float, gl_paltonemap_powtable, 2.0f, CVAR_ARCHIVE | CVAR_NOINITCALL) +{ + screen->UpdatePalette(); +} + +CUSTOM_CVAR(Bool, gl_paltonemap_reverselookup, true, CVAR_ARCHIVE | CVAR_NOINITCALL) +{ + screen->UpdatePalette(); +} + +CVAR(Float, gl_menu_blur, -1.0f, CVAR_ARCHIVE) + diff --git a/source/common/rendering/hwrenderer/postprocessing/hw_postprocess_cvars.h b/source/common/rendering/hwrenderer/postprocessing/hw_postprocess_cvars.h new file mode 100644 index 000000000..adde1044b --- /dev/null +++ b/source/common/rendering/hwrenderer/postprocessing/hw_postprocess_cvars.h @@ -0,0 +1,54 @@ +#pragma once + +#include "c_cvars.h" + +class IFXAAShader +{ +public: + enum Quality + { + None, + Low, + Medium, + High, + Extreme, + Count + }; +}; + + + +//========================================================================== +// +// CVARs +// +//========================================================================== +EXTERN_CVAR(Bool, gl_bloom) +EXTERN_CVAR(Float, gl_bloom_amount) +EXTERN_CVAR(Float, gl_exposure_scale) +EXTERN_CVAR(Float, gl_exposure_min) +EXTERN_CVAR(Float, gl_exposure_base) +EXTERN_CVAR(Float, gl_exposure_speed) +EXTERN_CVAR(Int, gl_tonemap) +EXTERN_CVAR(Int, gl_bloom_kernel_size) +EXTERN_CVAR(Bool, gl_lens) +EXTERN_CVAR(Float, gl_lens_k) +EXTERN_CVAR(Float, gl_lens_kcube) +EXTERN_CVAR(Float, gl_lens_chromatic) +EXTERN_CVAR(Int, gl_fxaa) +EXTERN_CVAR(Int, gl_ssao) +EXTERN_CVAR(Int, gl_ssao_portals) +EXTERN_CVAR(Float, gl_ssao_strength) +EXTERN_CVAR(Int, gl_ssao_debug) +EXTERN_CVAR(Float, gl_ssao_bias) +EXTERN_CVAR(Float, gl_ssao_radius) +EXTERN_CVAR(Float, gl_ssao_blur) +EXTERN_CVAR(Float, gl_ssao_exponent) +EXTERN_CVAR(Float, gl_paltonemap_powtable) +EXTERN_CVAR(Bool, gl_paltonemap_reverselookup) +EXTERN_CVAR(Float, gl_menu_blur) +EXTERN_CVAR(Float, vid_brightness) +EXTERN_CVAR(Float, vid_contrast) +EXTERN_CVAR(Float, vid_saturation) +EXTERN_CVAR(Int, gl_satformula) + diff --git a/source/common/rendering/hwrenderer/utility/hw_cvars.cpp b/source/common/rendering/hwrenderer/utility/hw_cvars.cpp new file mode 100644 index 000000000..8f59ecf34 --- /dev/null +++ b/source/common/rendering/hwrenderer/utility/hw_cvars.cpp @@ -0,0 +1,86 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2005-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + + +#include "c_cvars.h" +#include "c_dispatch.h" +#include "v_video.h" +#include "hw_cvars.h" +#include "menu/menu.h" + + + +// OpenGL stuff moved here +// GL related CVARs + + +//========================================================================== +// +// Texture CVARs +// +//========================================================================== +#if 0 +CUSTOM_CVAR(Float,gl_texture_filter_anisotropic,8.0f,CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL) +{ + screen->TextureFilterChanged(); +} + +CCMD(gl_flush) +{ + //TexMan.FlushAll(); +} + +CUSTOM_CVAR(Int, gl_texture_filter, 4, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL) +{ + if (self < 0 || self > 6) self=4; + screen->TextureFilterChanged(); +} +CVAR(Bool, gl_precache, false, CVAR_ARCHIVE) +#endif + + +//========================================================================== +// +// Sprite CVARs +// +//========================================================================== +#if 0 +CVAR(Bool, gl_usecolorblending, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Bool, gl_sprite_blend, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); +CVAR(Int, gl_spriteclip, 1, CVAR_ARCHIVE) +CVAR(Float, gl_sclipthreshold, 10.0, CVAR_ARCHIVE) +CVAR(Float, gl_sclipfactor, 1.8f, CVAR_ARCHIVE) +CVAR(Int, gl_particles_style, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // 0 = square, 1 = round, 2 = smooth +CVAR(Int, gl_billboard_mode, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, gl_billboard_faces_camera, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, gl_billboard_particles, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, gl_enhanced_nv_stealth, 3, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Int, gl_fuzztype, 0, CVAR_ARCHIVE) +{ + if (self < 0 || self > 8) self = 0; +} + +CUSTOM_CVAR(Int, gl_shadowmap_filter, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 0 || self > 8) self = 1; +} +#endif diff --git a/source/common/rendering/hwrenderer/utility/hw_cvars.h b/source/common/rendering/hwrenderer/utility/hw_cvars.h new file mode 100644 index 000000000..724dbf147 --- /dev/null +++ b/source/common/rendering/hwrenderer/utility/hw_cvars.h @@ -0,0 +1,71 @@ +#pragma once + + +#include "c_cvars.h" + +EXTERN_CVAR(Bool,gl_enhanced_nightvision) +EXTERN_CVAR(Int, screenblocks); +EXTERN_CVAR(Bool, gl_texture) +EXTERN_CVAR(Int, gl_texture_filter) +EXTERN_CVAR(Float, gl_texture_filter_anisotropic) +EXTERN_CVAR(Int, gl_texture_format) +EXTERN_CVAR(Bool, gl_texture_usehires) +EXTERN_CVAR(Bool, gl_usefb) + +EXTERN_CVAR(Int, gl_weaponlight) + +EXTERN_CVAR (Bool, gl_light_sprites); +EXTERN_CVAR (Bool, gl_light_particles); +EXTERN_CVAR (Bool, gl_light_shadowmap); +EXTERN_CVAR (Int, gl_shadowmap_quality); + +EXTERN_CVAR(Int, gl_fogmode) +EXTERN_CVAR(Int, gl_lightmode) +EXTERN_CVAR(Bool,gl_mirror_envmap) + +EXTERN_CVAR(Bool,gl_mirrors) +EXTERN_CVAR(Bool,gl_mirror_envmap) + +EXTERN_CVAR(Float, gl_mask_threshold) +EXTERN_CVAR(Float, gl_mask_sprite_threshold) + +EXTERN_CVAR(Int, gl_multisample) + +EXTERN_CVAR(Bool, gl_bloom) +EXTERN_CVAR(Float, gl_bloom_amount) +EXTERN_CVAR(Int, gl_bloom_kernel_size) +EXTERN_CVAR(Int, gl_tonemap) +EXTERN_CVAR(Float, gl_exposure) +EXTERN_CVAR(Bool, gl_lens) +EXTERN_CVAR(Float, gl_lens_k) +EXTERN_CVAR(Float, gl_lens_kcube) +EXTERN_CVAR(Float, gl_lens_chromatic) +EXTERN_CVAR(Int, gl_ssao) +EXTERN_CVAR(Int, gl_ssao_portals) +EXTERN_CVAR(Float, gl_ssao_strength) +EXTERN_CVAR(Int, gl_ssao_debug) +EXTERN_CVAR(Float, gl_ssao_bias) +EXTERN_CVAR(Float, gl_ssao_radius) +EXTERN_CVAR(Float, gl_ssao_blur_amount) + +EXTERN_CVAR(Int, gl_debug_level) +EXTERN_CVAR(Bool, gl_debug_breakpoint) + + +EXTERN_CVAR(Bool, gl_usecolorblending) +EXTERN_CVAR(Bool, gl_sprite_blend) +EXTERN_CVAR(Int, gl_spriteclip) +EXTERN_CVAR(Float, gl_sclipthreshold) +EXTERN_CVAR(Float, gl_sclipfactor) +EXTERN_CVAR(Int, gl_particles_style) +EXTERN_CVAR(Int, gl_billboard_mode) +EXTERN_CVAR(Bool, gl_billboard_faces_camera) +EXTERN_CVAR(Bool, gl_billboard_particles) +EXTERN_CVAR(Int, gl_enhanced_nv_stealth) +EXTERN_CVAR(Int, gl_fuzztype) + +EXTERN_CVAR(Int, gl_shadowmap_filter) + +EXTERN_CVAR(Bool, gl_brightfog) +EXTERN_CVAR(Bool, gl_lightadditivesurfaces) +EXTERN_CVAR(Bool, gl_notexturefill) diff --git a/source/common/rendering/hwrenderer/utility/hw_shaderpatcher.cpp b/source/common/rendering/hwrenderer/utility/hw_shaderpatcher.cpp new file mode 100644 index 000000000..bdbaa50b4 --- /dev/null +++ b/source/common/rendering/hwrenderer/utility/hw_shaderpatcher.cpp @@ -0,0 +1,295 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2004-2018 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** hw_shaderpatcher.cpp +** +** Modifies shader source to account for different syntax versions or engine changes. +** +*/ + + +#include "hw_shaderpatcher.h" + + +static bool IsGlslWhitespace(char c) +{ + switch (c) + { + case ' ': + case '\r': + case '\n': + case '\t': + case '\f': + return true; + default: + return false; + } +} + +static FString NextGlslToken(const char *chars, long len, long &pos) +{ + // Eat whitespace + long tokenStart = pos; + while (tokenStart != len && IsGlslWhitespace(chars[tokenStart])) + tokenStart++; + + // Find token end + long tokenEnd = tokenStart; + while (tokenEnd != len && !IsGlslWhitespace(chars[tokenEnd]) && chars[tokenEnd] != ';') + tokenEnd++; + + pos = tokenEnd; + return FString(chars + tokenStart, tokenEnd - tokenStart); +} + +static bool isShaderType(const char *name) +{ + return !strcmp(name, "sampler1D") || !strcmp(name, "sampler2D") || !strcmp(name, "sampler3D") || !strcmp(name, "samplerCube") || !strcmp(name, "sampler2DMS"); +} + +FString RemoveLegacyUserUniforms(FString code) +{ + // User shaders must declare their uniforms via the GLDEFS file. + + code.Substitute("uniform sampler2D tex;", " "); + code.Substitute("uniform float timer;", " "); + + // The following code searches for legacy uniform declarations in the shader itself and replaces them with whitespace. + + long len = (long)code.Len(); + char *chars = code.LockBuffer(); + + long startIndex = 0; + while (true) + { + long matchIndex = code.IndexOf("uniform", startIndex); + if (matchIndex == -1) + break; + + bool isLegacyUniformName = false; + + bool isKeywordStart = matchIndex == 0 || IsGlslWhitespace(chars[matchIndex - 1]); + bool isKeywordEnd = matchIndex + 7 == len || IsGlslWhitespace(chars[matchIndex + 7]); + if (isKeywordStart && isKeywordEnd) + { + long pos = matchIndex + 7; + FString type = NextGlslToken(chars, len, pos); + FString identifier = NextGlslToken(chars, len, pos); + + isLegacyUniformName = type.Compare("float") == 0 && identifier.Compare("timer") == 0; + } + + if (isLegacyUniformName) + { + long statementEndIndex = code.IndexOf(';', matchIndex + 7); + if (statementEndIndex == -1) + statementEndIndex = len; + for (long i = matchIndex; i <= statementEndIndex; i++) + { + if (!IsGlslWhitespace(chars[i])) + chars[i] = ' '; + } + startIndex = statementEndIndex; + } + else + { + startIndex = matchIndex + 7; + } + } + + // Also remove all occurences of the token 'texture2d'. Some shaders may still use this deprecated function to access a sampler. + // Modern GLSL only allows use of 'texture'. + while (true) + { + long matchIndex = code.IndexOf("texture2d", startIndex); + if (matchIndex == -1) + break; + + // Check if this is a real token. + bool isKeywordStart = matchIndex == 0 || !isalnum(chars[matchIndex - 1] & 255); + bool isKeywordEnd = matchIndex + 9 == len || !isalnum(chars[matchIndex + 9] & 255); + if (isKeywordStart && isKeywordEnd) + { + chars[matchIndex + 7] = chars[matchIndex + 8] = ' '; + } + startIndex = matchIndex + 9; + } + + code.UnlockBuffer(); + + return code; +} + +FString RemoveSamplerBindings(FString code, TArray> &samplerstobind) +{ + long len = (long)code.Len(); + char *chars = code.LockBuffer(); + + long startIndex = 0; + long startpos, endpos; + while (true) + { + long matchIndex = code.IndexOf("layout(binding", startIndex); + if (matchIndex == -1) + break; + + bool isSamplerUniformName = false; + + bool isKeywordStart = matchIndex == 0 || IsGlslWhitespace(chars[matchIndex - 1]); + bool isKeywordEnd = matchIndex + 14 == len || IsGlslWhitespace(chars[matchIndex + 14]) || chars[matchIndex + 14] == '='; + if (isKeywordStart && isKeywordEnd) + { + long pos = matchIndex + 14; + startpos = matchIndex; + while (IsGlslWhitespace(chars[pos])) pos++; + if (chars[pos] == '=') + { + char *p; + pos++; + auto val = strtol(&chars[pos], &p, 0); + if (p != &chars[pos]) + { + pos = long(p - chars); + while (IsGlslWhitespace(chars[pos])) pos++; + if (chars[pos] == ')') + { + endpos = ++pos; + FString uniform = NextGlslToken(chars, len, pos); + FString type = NextGlslToken(chars, len, pos); + FString identifier = NextGlslToken(chars, len, pos); + + isSamplerUniformName = uniform.Compare("uniform") == 0 && isShaderType(type); + if (isSamplerUniformName) + { + samplerstobind.Push(std::make_pair(identifier, val)); + for (auto pos = startpos; pos < endpos; pos++) + { + if (!IsGlslWhitespace(chars[pos])) + chars[pos] = ' '; + } + } + } + } + } + } + + if (isSamplerUniformName) + { + startIndex = endpos; + } + else + { + startIndex = matchIndex + 7; + } + } + + code.UnlockBuffer(); + + return code; +} + +FString RemoveLayoutLocationDecl(FString code, const char *inoutkeyword) +{ + long len = (long)code.Len(); + char *chars = code.LockBuffer(); + + long startIndex = 0; + while (true) + { + long matchIndex = code.IndexOf("layout(location", startIndex); + if (matchIndex == -1) + break; + + long endIndex = matchIndex; + + // Find end of layout declaration + while (chars[endIndex] != ')' && chars[endIndex] != 0) + endIndex++; + + if (chars[endIndex] == ')') + endIndex++; + else if (chars[endIndex] == 0) + break; + + // Skip whitespace + while (IsGlslWhitespace(chars[endIndex])) + endIndex++; + + // keyword following the declaration? + bool keywordFound = true; + long i; + for (i = 0; inoutkeyword[i] != 0; i++) + { + if (chars[endIndex + i] != inoutkeyword[i]) + { + keywordFound = false; + break; + } + } + if (keywordFound && IsGlslWhitespace(chars[endIndex + i])) + { + // yes - replace declaration with spaces + for (long i = matchIndex; i < endIndex; i++) + chars[i] = ' '; + } + + startIndex = endIndex; + } + + code.UnlockBuffer(); + + return code; +} + +///////////////////////////////////////////////////////////////////////////// + +// Note: the MaterialShaderIndex enum in gl_shader.h needs to be updated whenever this array is modified. +const FDefaultShader defaultshaders[] = +{ + {"Default", "shaders/glsl/func_normal.fp", "shaders/glsl/material_normal.fp", ""}, + {"Warp 1", "shaders/glsl/func_warp1.fp", "shaders/glsl/material_normal.fp", ""}, + {"Warp 2", "shaders/glsl/func_warp2.fp", "shaders/glsl/material_normal.fp", ""}, + {"Brightmap","shaders/glsl/func_brightmap.fp", "shaders/glsl/material_normal.fp", "#define BRIGHTMAP\n"}, + {"Specular", "shaders/glsl/func_spec.fp", "shaders/glsl/material_specular.fp", "#define SPECULAR\n#define NORMALMAP\n"}, + {"SpecularBrightmap", "shaders/glsl/func_spec.fp", "shaders/glsl/material_specular.fp", "#define SPECULAR\n#define NORMALMAP\n#define BRIGHTMAP\n"}, + {"PBR","shaders/glsl/func_pbr.fp", "shaders/glsl/material_pbr.fp", "#define PBR\n#define NORMALMAP\n"}, + {"PBRBrightmap","shaders/glsl/func_pbr.fp", "shaders/glsl/material_pbr.fp", "#define PBR\n#define NORMALMAP\n#define BRIGHTMAP\n"}, + {"Paletted", "shaders/glsl/func_paletted.fp", "shaders/glsl/material_nolight.fp", ""}, + {"No Texture", "shaders/glsl/func_notexture.fp", "shaders/glsl/material_normal.fp", ""}, + {"Basic Fuzz", "shaders/glsl/fuzz_standard.fp", "shaders/glsl/material_normal.fp", ""}, + {"Smooth Fuzz", "shaders/glsl/fuzz_smooth.fp", "shaders/glsl/material_normal.fp", ""}, + {"Swirly Fuzz", "shaders/glsl/fuzz_swirly.fp", "shaders/glsl/material_normal.fp", ""}, + {"Translucent Fuzz", "shaders/glsl/fuzz_smoothtranslucent.fp", "shaders/glsl/material_normal.fp", ""}, + {"Jagged Fuzz", "shaders/glsl/fuzz_jagged.fp", "shaders/glsl/material_normal.fp", ""}, + {"Noise Fuzz", "shaders/glsl/fuzz_noise.fp", "shaders/glsl/material_normal.fp", ""}, + {"Smooth Noise Fuzz", "shaders/glsl/fuzz_smoothnoise.fp", "shaders/glsl/material_normal.fp", ""}, + {"Software Fuzz", "shaders/glsl/fuzz_software.fp", "shaders/glsl/material_normal.fp", ""}, + {nullptr,nullptr,nullptr,nullptr} +}; + +const FEffectShader effectshaders[] = +{ + { "fogboundary", "shaders/glsl/main.vp", "shaders/glsl/fogboundary.fp", nullptr, nullptr, "#define NO_ALPHATEST\n" }, + { "spheremap", "shaders/glsl/main.vp", "shaders/glsl/main.fp", "shaders/glsl/func_normal.fp", "shaders/glsl/material_normal.fp", "#define SPHEREMAP\n#define NO_ALPHATEST\n" }, + { "burn", "shaders/glsl/main.vp", "shaders/glsl/burn.fp", nullptr, nullptr, "#define SIMPLE\n#define NO_ALPHATEST\n" }, + { "stencil", "shaders/glsl/main.vp", "shaders/glsl/stencil.fp", nullptr, nullptr, "#define SIMPLE\n#define NO_ALPHATEST\n" }, +}; diff --git a/source/common/rendering/hwrenderer/utility/hw_shaderpatcher.h b/source/common/rendering/hwrenderer/utility/hw_shaderpatcher.h new file mode 100644 index 000000000..7133441a2 --- /dev/null +++ b/source/common/rendering/hwrenderer/utility/hw_shaderpatcher.h @@ -0,0 +1,30 @@ +#pragma once + +#include "tarray.h" +#include "zstring.h" +#include "utility" + +FString RemoveLegacyUserUniforms(FString code); +FString RemoveSamplerBindings(FString code, TArray> &samplerstobind); // For GL 3.3 compatibility which cannot declare sampler bindings in the sampler source. +FString RemoveLayoutLocationDecl(FString code, const char *inoutkeyword); + +struct FDefaultShader +{ + const char * ShaderName; + const char * gettexelfunc; + const char * lightfunc; + const char * Defines; +}; + +struct FEffectShader +{ + const char *ShaderName; + const char *vp; + const char *fp1; + const char *fp2; + const char *fp3; + const char *defines; +}; + +extern const FDefaultShader defaultshaders[]; +extern const FEffectShader effectshaders[]; diff --git a/source/common/rendering/v_framebuffer.cpp b/source/common/rendering/v_framebuffer.cpp index 7eb8f7c0d..72adbc3d7 100644 --- a/source/common/rendering/v_framebuffer.cpp +++ b/source/common/rendering/v_framebuffer.cpp @@ -292,6 +292,9 @@ void DFrameBuffer::SetViewportRects(IntRect *bounds) mSceneViewport.top = screenHeight - (height + viewwindowy - ((height - viewheight) / 2)); mSceneViewport.width = viewwidth; mSceneViewport.height = height; +#else + // For now use the full screen. This needs to be done better. + mSceneViewport = mScreenViewport; #endif // Scale viewports to fit letterbox @@ -302,12 +305,12 @@ void DFrameBuffer::SetViewportRects(IntRect *bounds) { mScreenViewport.width = mOutputLetterbox.width; mScreenViewport.height = mOutputLetterbox.height; -#if 0 + mSceneViewport.left = (int)round(mSceneViewport.left * scaleX); mSceneViewport.top = (int)round(mSceneViewport.top * scaleY); mSceneViewport.width = (int)round(mSceneViewport.width * scaleX); mSceneViewport.height = (int)round(mSceneViewport.height * scaleY); -#endif + } } diff --git a/source/common/rendering/v_video.h b/source/common/rendering/v_video.h index e835ad5a4..f4d676091 100644 --- a/source/common/rendering/v_video.h +++ b/source/common/rendering/v_video.h @@ -236,7 +236,7 @@ public: //IShadowMap mShadowMap; IntRect mScreenViewport; - //IntRect mSceneViewport; + IntRect mSceneViewport; IntRect mOutputLetterbox; float mSceneClearColor[4]; @@ -259,7 +259,7 @@ public: inline int GetWidth() const { return Width; } inline int GetHeight() const { return Height; } -#if 0 + // Currently there is no concept of a scene viewport in Build, but let's keep this stuff here to implement it later. FVector2 SceneScale() const { return { mSceneViewport.width / (float)mScreenViewport.width, mSceneViewport.height / (float)mScreenViewport.height }; @@ -269,7 +269,6 @@ public: { return { mSceneViewport.left / (float)mScreenViewport.width, mSceneViewport.top / (float)mScreenViewport.height }; } -#endif // Make the surface visible. virtual void Update (); @@ -395,8 +394,6 @@ extern DFrameBuffer *screen; #define SCREENHEIGHT (screen->GetHeight ()) #define SCREENPITCH (screen->GetPitch ()) -EXTERN_CVAR (Float, Gamma) - // Allocates buffer screens, call before R_Init. void V_InitScreenSize(); diff --git a/source/common/textures/imagehelpers.cpp b/source/common/textures/imagehelpers.cpp index 06217afe9..77bc4c4a0 100644 --- a/source/common/textures/imagehelpers.cpp +++ b/source/common/textures/imagehelpers.cpp @@ -71,6 +71,43 @@ namespace ImageHelpers return bestcolor; } + // [SP] Re-implemented BestColor for more precision rather than speed. This function is only ever called once until the game palette is changed. + + int PTM_BestColor(int r, int g, int b, bool reverselookup, float powtable_val, int first, int num) + { + const PalEntry* pal = BaseColors; + static double powtable[256]; + static bool firstTime = true; + static float trackpowtable = 0.; + + double fbestdist = DBL_MAX, fdist; + int bestcolor = 0; + + if (firstTime || trackpowtable != powtable_val) + { + auto pt = powtable_val; + trackpowtable = pt; + firstTime = false; + for (int x = 0; x < 256; x++) powtable[x] = pow((double)x / 255, (double)pt); + } + + for (int color = first; color < num; color++) + { + double x = powtable[abs(r - pal[color].r)]; + double y = powtable[abs(g - pal[color].g)]; + double z = powtable[abs(b - pal[color].b)]; + fdist = x + y + z; + if (color == first || (reverselookup ? (fdist <= fbestdist) : (fdist < fbestdist))) + { + if (fdist == 0 && !reverselookup) + return color; + + fbestdist = fdist; + bestcolor = color; + } + } + return bestcolor; + } void SetPalette(const PalEntry* colors) { diff --git a/source/common/textures/imagehelpers.h b/source/common/textures/imagehelpers.h index 04d47a894..34e64c7b2 100644 --- a/source/common/textures/imagehelpers.h +++ b/source/common/textures/imagehelpers.h @@ -59,6 +59,7 @@ namespace ImageHelpers // Todo: This should not pick fullbright colors. int BestColor(int r, int g, int b, int first = 0, int num = 255); + int PTM_BestColor(int r, int g, int b, bool reverselookup, float powtable_val, int first, int num); void SetPalette(const PalEntry* colors); diff --git a/source/duke3d/CMakeLists.txt b/source/duke3d/CMakeLists.txt index 4af68f588..9d5319995 100644 --- a/source/duke3d/CMakeLists.txt +++ b/source/duke3d/CMakeLists.txt @@ -26,6 +26,14 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../common/music ${CMAKE_CURRENT_SOURCE_DIR}/../common/input ${CMAKE_CURRENT_SOURCE_DIR}/../platform + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl_load + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/system + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/renderer + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/shaders + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/data + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/postprocessing + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/utility + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering ) diff --git a/source/exhumed/CMakeLists.txt b/source/exhumed/CMakeLists.txt index a20ec1157..d80b8d87f 100644 --- a/source/exhumed/CMakeLists.txt +++ b/source/exhumed/CMakeLists.txt @@ -24,7 +24,16 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../common/2d ${CMAKE_CURRENT_SOURCE_DIR}/../common/music ${CMAKE_CURRENT_SOURCE_DIR}/../common/input - ${CMAKE_CURRENT_SOURCE_DIR}/../platform ) + ${CMAKE_CURRENT_SOURCE_DIR}/../platform + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl_load + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/system + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/renderer + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/shaders + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/data + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/postprocessing + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/utility + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering + ) diff --git a/source/exhumed/src/sound.cpp b/source/exhumed/src/sound.cpp index 1044ea15b..a50e7a3eb 100644 --- a/source/exhumed/src/sound.cpp +++ b/source/exhumed/src/sound.cpp @@ -194,7 +194,7 @@ public: } else { - disttable[i] = 255 - eax >> 8; + disttable[i] = 255 - (eax >> 8); eax = (eax * eax) >> 8; } @@ -257,9 +257,8 @@ int LoadSound(const char* name) else if (!ISDEMOVER) // demo tries to load sound files it doesn't have { Printf("Unable to open sound '%s'!\n", filename.GetChars()); - return 0; } - + return -1; } //========================================================================== // diff --git a/source/rr/CMakeLists.txt b/source/rr/CMakeLists.txt index 346927d64..6d77db574 100644 --- a/source/rr/CMakeLists.txt +++ b/source/rr/CMakeLists.txt @@ -26,6 +26,14 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../common/music ${CMAKE_CURRENT_SOURCE_DIR}/../common/input ${CMAKE_CURRENT_SOURCE_DIR}/../platform + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl_load + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/system + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/renderer + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/shaders + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/data + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/postprocessing + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/utility + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering ) diff --git a/source/sw/CMakeLists.txt b/source/sw/CMakeLists.txt index dbee7430c..9e08df46b 100644 --- a/source/sw/CMakeLists.txt +++ b/source/sw/CMakeLists.txt @@ -26,6 +26,14 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../common/music ${CMAKE_CURRENT_SOURCE_DIR}/../common/input ${CMAKE_CURRENT_SOURCE_DIR}/../platform + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl_load + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/system + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/renderer + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/gl/shaders + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/data + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/postprocessing + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering/hwrenderer/utility + ${CMAKE_CURRENT_SOURCE_DIR}/../common/rendering )