diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d00a4be52..00a079011 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -771,6 +771,7 @@ set( FASTMATH_SOURCES gl/renderer/gl_renderstate.cpp gl/renderer/gl_renderbuffers.cpp gl/renderer/gl_lightdata.cpp + gl/renderer/gl_postprocess.cpp gl/hqnx/init.cpp gl/hqnx/hq2x.cpp gl/hqnx/hq3x.cpp @@ -815,6 +816,9 @@ set( FASTMATH_SOURCES gl/shaders/gl_texshader.cpp gl/shaders/gl_shaderprogram.cpp gl/shaders/gl_presentshader.cpp + gl/shaders/gl_bloomshader.cpp + gl/shaders/gl_blurshader.cpp + gl/shaders/gl_tonemapshader.cpp gl/system/gl_interface.cpp gl/system/gl_framebuffer.cpp gl/system/gl_menu.cpp diff --git a/src/actor.h b/src/actor.h index 01756ca02..dd247825a 100644 --- a/src/actor.h +++ b/src/actor.h @@ -381,6 +381,9 @@ enum ActorFlag7 MF7_LAXTELEFRAGDMG = 0x00100000, // [MC] Telefrag damage can be reduced. MF7_ICESHATTER = 0x00200000, // [MC] Shatters ice corpses regardless of damagetype. MF7_ALLOWTHRUFLAGS = 0x00400000, // [MC] Allow THRUACTORS and the likes on puffs to prevent mod breakage. + MF7_USEKILLSCRIPTS = 0x00800000, // [JM] Use "KILL" Script on death if not forced by GameInfo. + MF7_NOKILLSCRIPTS = 0x01000000, // [JM] No "KILL" Script on death whatsoever, even if forced by GameInfo. + MF7_SPRITEANGLE = 0x02000000, // [MC] Utilize the SpriteAngle property and lock the rotation to the degrees specified. }; // --- mobj.renderflags --- @@ -977,6 +980,8 @@ public: DVector3 __Pos; // double underscores so that it won't get used by accident. Access to this should be exclusively through the designated access functions. DVector3 OldRenderPos; + DAngle SpriteAngle; + DAngle SpriteRotation; DRotator Angles; DVector3 Vel; double Speed; diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index 26b27e202..0023cd271 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -378,6 +378,8 @@ enum WIF_MELEEWEAPON = 0x00008000, // melee weapon. Used by bots and monster AI. WIF_DEHAMMO = 0x00010000, // Uses Doom's original amount of ammo for the respective attack functions so that old DEHACKED patches work as intended. // AmmoUse1 will be set to the first attack's ammo use so that checking for empty weapons still works + WIF_NODEATHDESELECT = 0x00020000, // Don't jump to the Deselect state when the player dies + WIF_NODEATHINPUT = 0x00040000, // The weapon cannot be fired/reloaded/whatever when the player is dead WIF_CHEATNOTWEAPON = 0x08000000, // Give cheat considers this not a weapon (used by Sigil) // Flags used only by bot AI: diff --git a/src/g_shared/shared_hud.cpp b/src/g_shared/shared_hud.cpp index 46cf61d54..3a8dc388c 100644 --- a/src/g_shared/shared_hud.cpp +++ b/src/g_shared/shared_hud.cpp @@ -539,6 +539,38 @@ static void AddAmmoToList(AWeapon * weapdef) } } +static int GetDigitCount(int value) +{ + int digits = 0; + + do + { + value /= 10; + ++digits; + } + while (0 != value); + + return digits; +} + +static void GetAmmoTextLengths(player_t *CPlayer, int& ammocur, int& ammomax) +{ + for (auto type : orderedammos) + { + AAmmo * ammoitem = static_cast(CPlayer->mo->FindInventory(type)); + AAmmo * inv = nullptr == ammoitem + ? static_cast(GetDefaultByType(type)) + : ammoitem; + assert(nullptr != inv); + + ammocur = MAX(ammocur, nullptr == ammoitem ? 0 : ammoitem->Amount); + ammomax = MAX(ammomax, inv->MaxAmount); + } + + ammocur = GetDigitCount(ammocur); + ammomax = GetDigitCount(ammomax); +} + static int DrawAmmo(player_t *CPlayer, int x, int y) { @@ -586,7 +618,13 @@ static int DrawAmmo(player_t *CPlayer, int x, int y) // ok, we got all ammo types. Now draw the list back to front (bottom to top) - int def_width = ConFont->StringWidth("000/000"); + int ammocurlen = 0; + int ammomaxlen = 0; + GetAmmoTextLengths(CPlayer, ammocurlen, ammomaxlen); + + mysnprintf(buf, countof(buf), "%0*d/%0*d", ammocurlen, 0, ammomaxlen, 0); + + int def_width = ConFont->StringWidth(buf); int yadd = ConFont->GetHeight(); int xtext = x - def_width; @@ -618,7 +656,7 @@ static int DrawAmmo(player_t *CPlayer, int x, int y) int maxammo = inv->MaxAmount; int ammo = ammoitem? ammoitem->Amount : 0; - mysnprintf(buf, countof(buf), "%3d/%3d", ammo, maxammo); + mysnprintf(buf, countof(buf), "%*d/%*d", ammocurlen, ammo, ammomaxlen, maxammo); int tex_width= clamp(ConFont->StringWidth(buf)-def_width, 0, 1000); diff --git a/src/gi.cpp b/src/gi.cpp index 313df4f0b..9d45d2724 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -354,6 +354,7 @@ void FMapInfoParser::ParseGameInfo() GAMEINFOKEY_PATCH(mStatscreenFinishedFont, "statscreen_finishedpatch") GAMEINFOKEY_PATCH(mStatscreenEnteringFont, "statscreen_enteringpatch") GAMEINFOKEY_BOOL(norandomplayerclass, "norandomplayerclass") + GAMEINFOKEY_BOOL(forcekillscripts, "forcekillscripts") // [JM] Force kill scripts on thing death. (MF7_NOKILLSCRIPTS overrides.) else { diff --git a/src/gi.h b/src/gi.h index 2786b3425..c5b8f79cf 100644 --- a/src/gi.h +++ b/src/gi.h @@ -173,6 +173,7 @@ struct gameinfo_t FGIFont mStatscreenFinishedFont; FGIFont mStatscreenEnteringFont; bool norandomplayerclass; + bool forcekillscripts; const char *GetFinalePage(unsigned int num) const; }; diff --git a/src/gl/compatibility/gl_20.cpp b/src/gl/compatibility/gl_20.cpp index 27f08a344..fb4064d3b 100644 --- a/src/gl/compatibility/gl_20.cpp +++ b/src/gl/compatibility/gl_20.cpp @@ -705,28 +705,25 @@ bool GLWall::PrepareLight(ADynamicLight * light, int pass) return false; } - if (tcs != NULL) + Vector t1; + int outcnt[4] = { 0,0,0,0 }; + + for (int i = 0; i<4; i++) { - Vector t1; - int outcnt[4] = { 0,0,0,0 }; + t1.Set(&vtx[i * 3]); + Vector nearToVert = t1 - nearPt; + tcs[i].u = (nearToVert.Dot(right) * scale) + 0.5f; + tcs[i].v = (nearToVert.Dot(up) * scale) + 0.5f; - for (int i = 0; i<4; i++) - { - t1.Set(&vtx[i * 3]); - Vector nearToVert = t1 - nearPt; - tcs[i].u = (nearToVert.Dot(right) * scale) + 0.5f; - tcs[i].v = (nearToVert.Dot(up) * scale) + 0.5f; + // quick check whether the light touches this polygon + if (tcs[i].u<0) outcnt[0]++; + if (tcs[i].u>1) outcnt[1]++; + if (tcs[i].v<0) outcnt[2]++; + if (tcs[i].v>1) outcnt[3]++; - // quick check whether the light touches this polygon - if (tcs[i].u<0) outcnt[0]++; - if (tcs[i].u>1) outcnt[1]++; - if (tcs[i].v<0) outcnt[2]++; - if (tcs[i].v>1) outcnt[3]++; - - } - // The light doesn't touch this polygon - if (outcnt[0] == 4 || outcnt[1] == 4 || outcnt[2] == 4 || outcnt[3] == 4) return false; } + // The light doesn't touch this polygon + if (outcnt[0] == 4 || outcnt[1] == 4 || outcnt[2] == 4 || outcnt[3] == 4) return false; draw_dlight++; return true; diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp new file mode 100644 index 000000000..9dc0dd99e --- /dev/null +++ b/src/gl/renderer/gl_postprocess.cpp @@ -0,0 +1,386 @@ +/* +** gl_postprocess.cpp +** Post processing effects in the render pipeline +** +**--------------------------------------------------------------------------- +** Copyright 2016 Magnus Norddahl +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of GZDoom or a GZDoom derivative, this code will be +** covered by the terms of the GNU Lesser General Public License as published +** by the Free Software Foundation; either version 2.1 of the License, or (at +** your option) any later version. +** 5. Full disclosure of the entire project's source code, except for third +** party libraries is mandatory. (NOTE: This clause is non-negotiable!) +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "gl/system/gl_system.h" +#include "gi.h" +#include "m_png.h" +#include "m_random.h" +#include "st_stuff.h" +#include "dobject.h" +#include "doomstat.h" +#include "g_level.h" +#include "r_data/r_interpolate.h" +#include "r_utility.h" +#include "d_player.h" +#include "p_effect.h" +#include "sbar.h" +#include "po_man.h" +#include "r_utility.h" +#include "a_hexenglobal.h" +#include "p_local.h" +#include "gl/gl_functions.h" +#include "gl/system/gl_interface.h" +#include "gl/system/gl_framebuffer.h" +#include "gl/system/gl_cvars.h" +#include "gl/renderer/gl_lightdata.h" +#include "gl/renderer/gl_renderstate.h" +#include "gl/renderer/gl_renderbuffers.h" +#include "gl/renderer/gl_renderer.h" +#include "gl/data/gl_data.h" +#include "gl/data/gl_vertexbuffer.h" +#include "gl/shaders/gl_bloomshader.h" +#include "gl/shaders/gl_blurshader.h" +#include "gl/shaders/gl_tonemapshader.h" +#include "gl/shaders/gl_presentshader.h" + +//========================================================================== +// +// CVARs +// +//========================================================================== +CVAR(Bool, gl_bloom, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); +CVAR(Float, gl_bloom_amount, 1.4f, 0) +CVAR(Float, gl_exposure, 0.0f, 0) + +CUSTOM_CVAR(Int, gl_tonemap, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0 || self > 4) + self = 0; +} + +CUSTOM_CVAR(Int, gl_bloom_kernel_size, 7, 0) +{ + if (self < 3 || self > 15 || self % 2 == 0) + self = 7; +} + +EXTERN_CVAR(Float, vid_brightness) +EXTERN_CVAR(Float, vid_contrast) + +//----------------------------------------------------------------------------- +// +// Adds bloom contribution to scene texture +// +//----------------------------------------------------------------------------- + +void FGLRenderer::BloomScene() +{ + // Only bloom things if enabled and no special fixed light mode is active + if (!gl_bloom || !FGLRenderBuffers::IsEnabled() || gl_fixedcolormap != CM_DEFAULT) + return; + + const float blurAmount = gl_bloom_amount; + int sampleCount = gl_bloom_kernel_size; + + // TBD: Maybe need a better way to share state with other parts of the pipeline + GLint activeTex, textureBinding, samplerBinding; + glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTex); + glActiveTexture(GL_TEXTURE0); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding); + if (gl.flags & RFL_SAMPLER_OBJECTS) + { + glGetIntegerv(GL_SAMPLER_BINDING, &samplerBinding); + glBindSampler(0, 0); + } + GLboolean blendEnabled, scissorEnabled; + GLint currentProgram, blendEquationRgb, blendEquationAlpha, blendSrcRgb, blendSrcAlpha, blendDestRgb, blendDestAlpha; + glGetBooleanv(GL_BLEND, &blendEnabled); + glGetBooleanv(GL_SCISSOR_TEST, &scissorEnabled); + 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_BLEND); + glDisable(GL_SCISSOR_TEST); + + const auto &level0 = mBuffers->BloomLevels[0]; + + // Extract blooming pixels from scene texture: + glBindFramebuffer(GL_FRAMEBUFFER, level0.VFramebuffer); + glViewport(0, 0, level0.Width, level0.Height); + mBuffers->BindSceneTexture(0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + mBloomExtractShader->Bind(); + mBloomExtractShader->SceneTexture.Set(0); + mBloomExtractShader->Exposure.Set(mCameraExposure); + { + FFlatVertex *ptr = mVBO->GetBuffer(); + ptr->Set(-1.0f, -1.0f, 0, 0.0f, 0.0f); ptr++; + ptr->Set(-1.0f, 1.0f, 0, 0.0f, 1.0f); ptr++; + ptr->Set(1.0f, -1.0f, 0, 1.0f, 0.0f); ptr++; + ptr->Set(1.0f, 1.0f, 0, 1.0f, 1.0f); ptr++; + mVBO->RenderCurrent(ptr, GL_TRIANGLE_STRIP); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Blur and downscale: + for (int i = 0; i < FGLRenderBuffers::NumBloomLevels - 1; i++) + { + const auto &level = mBuffers->BloomLevels[i]; + const auto &next = mBuffers->BloomLevels[i + 1]; + mBlurShader->BlurHorizontal(mVBO, blurAmount, sampleCount, level.VTexture, level.HFramebuffer, level.Width, level.Height); + mBlurShader->BlurVertical(mVBO, blurAmount, sampleCount, level.HTexture, next.VFramebuffer, next.Width, next.Height); + } + + // Blur and upscale: + for (int i = FGLRenderBuffers::NumBloomLevels - 1; i > 0; i--) + { + const auto &level = mBuffers->BloomLevels[i]; + const auto &next = mBuffers->BloomLevels[i - 1]; + + mBlurShader->BlurHorizontal(mVBO, blurAmount, sampleCount, level.VTexture, level.HFramebuffer, level.Width, level.Height); + mBlurShader->BlurVertical(mVBO, blurAmount, sampleCount, level.HTexture, level.VFramebuffer, level.Width, level.Height); + + // Linear upscale: + glBindFramebuffer(GL_FRAMEBUFFER, next.VFramebuffer); + glViewport(0, 0, next.Width, next.Height); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, level.VTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + mBloomCombineShader->Bind(); + mBloomCombineShader->BloomTexture.Set(0); + { + FFlatVertex *ptr = mVBO->GetBuffer(); + ptr->Set(-1.0f, -1.0f, 0, 0.0f, 0.0f); ptr++; + ptr->Set(-1.0f, 1.0f, 0, 0.0f, 1.0f); ptr++; + ptr->Set(1.0f, -1.0f, 0, 1.0f, 0.0f); ptr++; + ptr->Set(1.0f, 1.0f, 0, 1.0f, 1.0f); ptr++; + mVBO->RenderCurrent(ptr, GL_TRIANGLE_STRIP); + } + } + + mBlurShader->BlurHorizontal(mVBO, blurAmount, sampleCount, level0.VTexture, level0.HFramebuffer, level0.Width, level0.Height); + mBlurShader->BlurVertical(mVBO, blurAmount, sampleCount, level0.HTexture, level0.VFramebuffer, level0.Width, level0.Height); + + // Add bloom back to scene texture: + mBuffers->BindSceneTextureFB(); + glViewport(0, 0, mOutputViewport.width, mOutputViewport.height); + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ONE); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, level0.VTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + mBloomCombineShader->Bind(); + mBloomCombineShader->BloomTexture.Set(0); + { + FFlatVertex *ptr = mVBO->GetBuffer(); + ptr->Set(-1.0f, -1.0f, 0, 0.0f, 0.0f); ptr++; + ptr->Set(-1.0f, 1.0f, 0, 0.0f, 1.0f); ptr++; + ptr->Set(1.0f, -1.0f, 0, 1.0f, 0.0f); ptr++; + ptr->Set(1.0f, 1.0f, 0, 1.0f, 1.0f); ptr++; + mVBO->RenderCurrent(ptr, GL_TRIANGLE_STRIP); + } + + if (blendEnabled) + glEnable(GL_BLEND); + if (scissorEnabled) + glEnable(GL_SCISSOR_TEST); + glBlendEquationSeparate(blendEquationRgb, blendEquationAlpha); + glBlendFuncSeparate(blendSrcRgb, blendDestRgb, blendSrcAlpha, blendDestAlpha); + glUseProgram(currentProgram); + glBindTexture(GL_TEXTURE_2D, textureBinding); + if (gl.flags & RFL_SAMPLER_OBJECTS) + glBindSampler(0, samplerBinding); + glActiveTexture(activeTex); +} + +//----------------------------------------------------------------------------- +// +// Tonemap scene texture and place the result in the HUD/2D texture +// +//----------------------------------------------------------------------------- + +void FGLRenderer::TonemapScene() +{ + if (gl_tonemap == 0) + return; + + GLint activeTex, textureBinding, samplerBinding; + glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTex); + glActiveTexture(GL_TEXTURE0); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding); + if (gl.flags & RFL_SAMPLER_OBJECTS) + { + glGetIntegerv(GL_SAMPLER_BINDING, &samplerBinding); + glBindSampler(0, 0); + } + + GLboolean blendEnabled, scissorEnabled; + glGetBooleanv(GL_BLEND, &blendEnabled); + glGetBooleanv(GL_SCISSOR_TEST, &scissorEnabled); + + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); + + mBuffers->BindHudFB(); + mBuffers->BindSceneTexture(0); + mTonemapShader->Bind(); + mTonemapShader->SceneTexture.Set(0); + mTonemapShader->Exposure.Set(mCameraExposure); + + FFlatVertex *ptr = mVBO->GetBuffer(); + ptr->Set(-1.0f, -1.0f, 0, 0.0f, 0.0f); ptr++; + ptr->Set(-1.0f, 1.0f, 0, 0.0f, 1.0f); ptr++; + ptr->Set(1.0f, -1.0f, 0, 1.0f, 0.0f); ptr++; + ptr->Set(1.0f, 1.0f, 0, 1.0f, 1.0f); ptr++; + mVBO->RenderCurrent(ptr, GL_TRIANGLE_STRIP); + + if (blendEnabled) + glEnable(GL_BLEND); + if (scissorEnabled) + glEnable(GL_SCISSOR_TEST); + glBindTexture(GL_TEXTURE_2D, textureBinding); + if (gl.flags & RFL_SAMPLER_OBJECTS) + glBindSampler(0, samplerBinding); + glActiveTexture(activeTex); +} + +//----------------------------------------------------------------------------- +// +// Gamma correct while copying to frame buffer +// +//----------------------------------------------------------------------------- + +void FGLRenderer::Flush() +{ + if (FGLRenderBuffers::IsEnabled()) + { + glDisable(GL_MULTISAMPLE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + + GLboolean blendEnabled; + GLint currentProgram; + GLint activeTex, textureBinding, samplerBinding; + glGetBooleanv(GL_BLEND, &blendEnabled); + glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); + glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTex); + glActiveTexture(GL_TEXTURE0); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding); + if (gl.flags & RFL_SAMPLER_OBJECTS) + { + glGetIntegerv(GL_SAMPLER_BINDING, &samplerBinding); + glBindSampler(0, 0); + } + + mBuffers->BindOutputFB(); + + // Calculate letterbox + int clientWidth = framebuffer->GetClientWidth(); + int clientHeight = framebuffer->GetClientHeight(); + float scaleX = clientWidth / (float)mOutputViewport.width; + float scaleY = clientHeight / (float)mOutputViewport.height; + float scale = MIN(scaleX, scaleY); + int width = (int)round(mOutputViewport.width * scale); + int height = (int)round(mOutputViewport.height * scale); + int x = (clientWidth - width) / 2; + int y = (clientHeight - height) / 2; + + // Black bars around the box: + glViewport(0, 0, clientWidth, clientHeight); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glEnable(GL_SCISSOR_TEST); + if (y > 0) + { + glScissor(0, 0, clientWidth, y); + glClear(GL_COLOR_BUFFER_BIT); + } + if (clientHeight - y - height > 0) + { + glScissor(0, y + height, clientWidth, clientHeight - y - height); + glClear(GL_COLOR_BUFFER_BIT); + } + if (x > 0) + { + glScissor(0, y, x, height); + glClear(GL_COLOR_BUFFER_BIT); + } + if (clientWidth - x - width > 0) + { + glScissor(x + width, y, clientWidth - x - width, height); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); + + // Present what was rendered: + glViewport(x, y, width, height); + glDisable(GL_BLEND); + + mPresentShader->Bind(); + mPresentShader->InputTexture.Set(0); + if (framebuffer->IsHWGammaActive()) + { + mPresentShader->Gamma.Set(1.0f); + mPresentShader->Contrast.Set(1.0f); + mPresentShader->Brightness.Set(0.0f); + } + else + { + mPresentShader->Gamma.Set(clamp(Gamma, 0.1f, 4.f)); + mPresentShader->Contrast.Set(clamp(vid_contrast, 0.1f, 3.f)); + mPresentShader->Brightness.Set(clamp(vid_brightness, -0.8f, 0.8f)); + } + mBuffers->BindHudTexture(0); + + FFlatVertex *ptr = GLRenderer->mVBO->GetBuffer(); + ptr->Set(-1.0f, -1.0f, 0, 0.0f, 0.0f); ptr++; + ptr->Set(-1.0f, 1.0f, 0, 0.0f, 1.0f); ptr++; + ptr->Set(1.0f, -1.0f, 0, 1.0f, 0.0f); ptr++; + ptr->Set(1.0f, 1.0f, 0, 1.0f, 1.0f); ptr++; + GLRenderer->mVBO->RenderCurrent(ptr, GL_TRIANGLE_STRIP); + + if (blendEnabled) + glEnable(GL_BLEND); + glUseProgram(currentProgram); + glBindTexture(GL_TEXTURE_2D, textureBinding); + if (gl.flags & RFL_SAMPLER_OBJECTS) + glBindSampler(0, samplerBinding); + glActiveTexture(activeTex); + } +} diff --git a/src/gl/renderer/gl_renderbuffers.cpp b/src/gl/renderer/gl_renderbuffers.cpp index 26377553f..a65626007 100644 --- a/src/gl/renderer/gl_renderbuffers.cpp +++ b/src/gl/renderer/gl_renderbuffers.cpp @@ -53,6 +53,9 @@ #include "i_system.h" #include "doomerrors.h" +CVAR(Int, gl_multisample, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); +CVAR(Bool, gl_renderbuffers, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); + //========================================================================== // // Initialize render buffers and textures used in rendering passes @@ -72,12 +75,60 @@ FGLRenderBuffers::FGLRenderBuffers() FGLRenderBuffers::~FGLRenderBuffers() { - if (mSceneFB != 0) - glDeleteFramebuffers(1, &mSceneFB); - if (mSceneTexture != 0) - glDeleteTextures(1, &mSceneTexture); - if (mSceneDepthStencil != 0) - glDeleteRenderbuffers(1, &mSceneDepthStencil); + ClearScene(); + ClearHud(); + ClearBloom(); +} + +void FGLRenderBuffers::ClearScene() +{ + DeleteFrameBuffer(mSceneFB); + DeleteFrameBuffer(mSceneTextureFB); + DeleteRenderBuffer(mSceneMultisample); + DeleteRenderBuffer(mSceneDepthStencil); + DeleteRenderBuffer(mSceneDepth); + DeleteRenderBuffer(mSceneStencil); + DeleteTexture(mSceneTexture); +} + +void FGLRenderBuffers::ClearHud() +{ + DeleteFrameBuffer(mHudFB); + DeleteTexture(mHudTexture); +} + +void FGLRenderBuffers::ClearBloom() +{ + for (int i = 0; i < NumBloomLevels; i++) + { + auto &level = BloomLevels[i]; + DeleteFrameBuffer(level.HFramebuffer); + DeleteFrameBuffer(level.VFramebuffer); + DeleteTexture(level.HTexture); + DeleteTexture(level.VTexture); + level = FGLBloomTextureLevel(); + } +} + +void FGLRenderBuffers::DeleteTexture(GLuint &handle) +{ + if (handle != 0) + glDeleteTextures(1, &handle); + handle = 0; +} + +void FGLRenderBuffers::DeleteRenderBuffer(GLuint &handle) +{ + if (handle != 0) + glDeleteRenderbuffers(1, &handle); + handle = 0; +} + +void FGLRenderBuffers::DeleteFrameBuffer(GLuint &handle) +{ + if (handle != 0) + glDeleteFramebuffers(1, &handle); + handle = 0; } //========================================================================== @@ -89,44 +140,271 @@ FGLRenderBuffers::~FGLRenderBuffers() void FGLRenderBuffers::Setup(int width, int height) { - if (width <= mWidth && height <= mHeight) - return; + int samples = GetCvarSamples(); - if (mSceneFB != 0) - glDeleteFramebuffers(1, &mSceneFB); - if (mSceneTexture != 0) - glDeleteTextures(1, &mSceneTexture); - if (mSceneDepthStencil != 0) - glDeleteRenderbuffers(1, &mSceneDepthStencil); + if (width == mWidth && height == mHeight && mSamples != samples) + { + CreateScene(mWidth, mHeight, samples); + mSamples = samples; + } + else if (width > mWidth || height > mHeight) + { + CreateScene(width, height, samples); + CreateHud(width, height); + CreateBloom(width, height); + mWidth = width; + mHeight = height; + mSamples = samples; + } - glGenFramebuffers(1, &mSceneFB); - glGenTextures(1, &mSceneTexture); - glGenRenderbuffers(1, &mSceneDepthStencil); - - glBindTexture(GL_TEXTURE_2D, mSceneTexture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr); - 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); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); - - glBindRenderbuffer(GL_RENDERBUFFER, mSceneDepthStencil); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); glBindRenderbuffer(GL_RENDERBUFFER, 0); - - glBindFramebuffer(GL_FRAMEBUFFER, mSceneFB); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mSceneTexture, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mSceneDepthStencil); - glBindFramebuffer(GL_FRAMEBUFFER, mOutputFB); - - mWidth = width; - mHeight = height; + glBindFramebuffer(GL_FRAMEBUFFER, 0); } //========================================================================== // -// Makes the scene frame buffer active +// Creates the scene buffers +// +//========================================================================== + +void FGLRenderBuffers::CreateScene(int width, int height, int samples) +{ + ClearScene(); + + mSceneTexture = Create2DTexture(GetHdrFormat(), width, height); + mSceneTextureFB = CreateFrameBuffer(mSceneTexture); + + if (samples > 1) + mSceneMultisample = CreateRenderBuffer(GetHdrFormat(), samples, width, height); + + if ((gl.flags & RFL_NO_DEPTHSTENCIL) != 0) + { + mSceneDepth = CreateRenderBuffer(GL_DEPTH_COMPONENT24, samples, width, height); + mSceneStencil = CreateRenderBuffer(GL_STENCIL_INDEX8, samples, width, height); + mSceneFB = CreateFrameBuffer(samples > 1 ? mSceneMultisample : mSceneTexture, mSceneDepth, mSceneStencil, samples > 1); + } + else + { + mSceneDepthStencil = CreateRenderBuffer(GL_DEPTH24_STENCIL8, samples, width, height); + mSceneFB = CreateFrameBuffer(samples > 1 ? mSceneMultisample : mSceneTexture, mSceneDepthStencil, samples > 1); + } +} + +//========================================================================== +// +// Creates the post-tonemapping-step buffers +// +//========================================================================== + +void FGLRenderBuffers::CreateHud(int width, int height) +{ + ClearHud(); + mHudTexture = Create2DTexture(GetHdrFormat(), width, height); + mHudFB = CreateFrameBuffer(mHudTexture); +} + +//========================================================================== +// +// Creates bloom pass working buffers +// +//========================================================================== + +void FGLRenderBuffers::CreateBloom(int width, int height) +{ + ClearBloom(); + + int bloomWidth = MAX(width / 2, 1); + int bloomHeight = MAX(height / 2, 1); + for (int i = 0; i < NumBloomLevels; i++) + { + auto &level = BloomLevels[i]; + level.Width = MAX(bloomWidth / 2, 1); + level.Height = MAX(bloomHeight / 2, 1); + + level.VTexture = Create2DTexture(GetHdrFormat(), level.Width, level.Height); + level.HTexture = Create2DTexture(GetHdrFormat(), level.Width, level.Height); + level.VFramebuffer = CreateFrameBuffer(level.VTexture); + level.HFramebuffer = CreateFrameBuffer(level.HTexture); + + bloomWidth = level.Width; + bloomHeight = level.Height; + } +} + +//========================================================================== +// +// Fallback support for older OpenGL where RGBA16F might not be available +// +//========================================================================== + +GLuint FGLRenderBuffers::GetHdrFormat() +{ + return ((gl.flags & RFL_NO_RGBA16F) != 0) ? GL_RGBA8 : GL_RGBA16F; +} + +//========================================================================== +// +// Converts the CVAR multisample value into a valid level for OpenGL +// +//========================================================================== + +int FGLRenderBuffers::GetCvarSamples() +{ + int maxSamples = 0; + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + + int samples = clamp((int)gl_multisample, 0, maxSamples); + + int count; + for (count = 0; samples > 0; count++) + samples >>= 1; + return count; +} + +//========================================================================== +// +// Creates a 2D texture defaulting to linear filtering and clamp to edge +// +//========================================================================== + +GLuint FGLRenderBuffers::Create2DTexture(GLuint format, int width, int height) +{ + GLuint type = (format == GL_RGBA16F) ? GL_FLOAT : GL_UNSIGNED_BYTE; + GLuint handle = 0; + glGenTextures(1, &handle); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, handle); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, type, nullptr); + 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 handle; +} + +//========================================================================== +// +// Creates a render buffer +// +//========================================================================== + +GLuint FGLRenderBuffers::CreateRenderBuffer(GLuint format, int width, int height) +{ + GLuint handle = 0; + glGenRenderbuffers(1, &handle); + glBindRenderbuffer(GL_RENDERBUFFER, handle); + glRenderbufferStorage(GL_RENDERBUFFER, format, width, height); + return handle; +} + +GLuint FGLRenderBuffers::CreateRenderBuffer(GLuint format, int samples, int width, int height) +{ + if (samples <= 1) + return CreateRenderBuffer(format, width, height); + + GLuint handle = 0; + glGenRenderbuffers(1, &handle); + glBindRenderbuffer(GL_RENDERBUFFER, handle); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height); + return handle; +} + +//========================================================================== +// +// Creates a frame buffer +// +//========================================================================== + +GLuint FGLRenderBuffers::CreateFrameBuffer(GLuint colorbuffer) +{ + GLuint handle = 0; + glGenFramebuffers(1, &handle); + glBindFramebuffer(GL_FRAMEBUFFER, handle); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0); + CheckFrameBufferCompleteness(); + return handle; +} + +GLuint FGLRenderBuffers::CreateFrameBuffer(GLuint colorbuffer, GLuint depthstencil, bool colorIsARenderBuffer) +{ + GLuint handle = 0; + glGenFramebuffers(1, &handle); + glBindFramebuffer(GL_FRAMEBUFFER, handle); + if (colorIsARenderBuffer) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer); + else + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthstencil); + CheckFrameBufferCompleteness(); + return handle; +} + +GLuint FGLRenderBuffers::CreateFrameBuffer(GLuint colorbuffer, GLuint depth, GLuint stencil, bool colorIsARenderBuffer) +{ + GLuint handle = 0; + glGenFramebuffers(1, &handle); + glBindFramebuffer(GL_FRAMEBUFFER, handle); + if (colorIsARenderBuffer) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer); + else + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencil); + CheckFrameBufferCompleteness(); + return handle; +} + +//========================================================================== +// +// Verifies that the frame buffer setup is valid +// +//========================================================================== + +void FGLRenderBuffers::CheckFrameBufferCompleteness() +{ + GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (result == GL_FRAMEBUFFER_COMPLETE) + return; + + FString error = "glCheckFramebufferStatus failed: "; + switch (result) + { + default: error.AppendFormat("error code %d", (int)result); break; + case GL_FRAMEBUFFER_UNDEFINED: error << "GL_FRAMEBUFFER_UNDEFINED"; break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: error << "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: error << "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: error << "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"; break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: error << "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"; break; + case GL_FRAMEBUFFER_UNSUPPORTED: error << "GL_FRAMEBUFFER_UNSUPPORTED"; break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: error << "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; break; + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: error << "GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS"; break; + } + I_FatalError(error); +} + +//========================================================================== +// +// Resolves the multisample frame buffer by copying it to the scene texture +// +//========================================================================== + +void FGLRenderBuffers::BlitSceneToTexture() +{ + if (mSamples <= 1) + return; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, mSceneFB); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mSceneTextureFB); + glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +//========================================================================== +// +// Makes the scene frame buffer active (multisample, depth, stecil, etc.) // //========================================================================== @@ -135,6 +413,31 @@ void FGLRenderBuffers::BindSceneFB() glBindFramebuffer(GL_FRAMEBUFFER, mSceneFB); } +//========================================================================== +// +// Makes the scene texture frame buffer active (final 2D texture only) +// +//========================================================================== + +void FGLRenderBuffers::BindSceneTextureFB() +{ + glBindFramebuffer(GL_FRAMEBUFFER, mSceneTextureFB); +} + +//========================================================================== +// +// Makes the 2D/HUD frame buffer active +// +//========================================================================== + +void FGLRenderBuffers::BindHudFB() +{ + if (gl_tonemap != 0) + glBindFramebuffer(GL_FRAMEBUFFER, mHudFB); + else + glBindFramebuffer(GL_FRAMEBUFFER, mSceneTextureFB); +} + //========================================================================== // // Makes the screen frame buffer active @@ -154,6 +457,32 @@ void FGLRenderBuffers::BindOutputFB() void FGLRenderBuffers::BindSceneTexture(int index) { - glActiveTexture(index); + glActiveTexture(GL_TEXTURE0 + index); glBindTexture(GL_TEXTURE_2D, mSceneTexture); } + +//========================================================================== +// +// Binds the 2D/HUD frame buffer texture to the specified texture unit +// +//========================================================================== + +void FGLRenderBuffers::BindHudTexture(int index) +{ + glActiveTexture(GL_TEXTURE0 + index); + if (gl_tonemap != 0) + glBindTexture(GL_TEXTURE_2D, mHudTexture); + else + glBindTexture(GL_TEXTURE_2D, mSceneTexture); +} + +//========================================================================== +// +// Returns true if render buffers are supported and should be used +// +//========================================================================== + +bool FGLRenderBuffers::IsEnabled() +{ + return gl_renderbuffers && gl.glslversion != 0; +} diff --git a/src/gl/renderer/gl_renderbuffers.h b/src/gl/renderer/gl_renderbuffers.h index bbb38b533..b4765b544 100644 --- a/src/gl/renderer/gl_renderbuffers.h +++ b/src/gl/renderer/gl_renderbuffers.h @@ -3,6 +3,17 @@ #include "gl/shaders/gl_shader.h" +class FGLBloomTextureLevel +{ +public: + GLuint VTexture = 0; + GLuint VFramebuffer = 0; + GLuint HTexture = 0; + GLuint HFramebuffer = 0; + GLuint Width = 0; + GLuint Height = 0; +}; + class FGLRenderBuffers { public: @@ -10,19 +21,53 @@ public: ~FGLRenderBuffers(); void Setup(int width, int height); + void BlitSceneToTexture(); void BindSceneFB(); + void BindSceneTextureFB(); + void BindHudFB(); void BindOutputFB(); void BindSceneTexture(int index); + void BindHudTexture(int index); - static bool IsSupported() { return gl.version >= 3.3f; } + enum { NumBloomLevels = 4 }; + FGLBloomTextureLevel BloomLevels[NumBloomLevels]; + + static bool IsEnabled(); private: + void ClearScene(); + void ClearHud(); + void ClearBloom(); + void CreateScene(int width, int height, int samples); + void CreateHud(int width, int height); + void CreateBloom(int width, int height); + GLuint Create2DTexture(GLuint format, int width, int height); + GLuint CreateRenderBuffer(GLuint format, int width, int height); + GLuint CreateRenderBuffer(GLuint format, int samples, int width, int height); + GLuint CreateFrameBuffer(GLuint colorbuffer); + GLuint CreateFrameBuffer(GLuint colorbuffer, GLuint depthstencil, bool colorIsARenderBuffer); + GLuint CreateFrameBuffer(GLuint colorbuffer, GLuint depth, GLuint stencil, bool colorIsARenderBuffer); + void CheckFrameBufferCompleteness(); + void DeleteTexture(GLuint &handle); + void DeleteRenderBuffer(GLuint &handle); + void DeleteFrameBuffer(GLuint &handle); + + int GetCvarSamples(); + GLuint GetHdrFormat(); + int mWidth = 0; int mHeight = 0; + int mSamples = 0; GLuint mSceneTexture = 0; + GLuint mSceneMultisample = 0; GLuint mSceneDepthStencil = 0; + GLuint mSceneDepth = 0; + GLuint mSceneStencil = 0; GLuint mSceneFB = 0; + GLuint mSceneTextureFB = 0; + GLuint mHudTexture = 0; + GLuint mHudFB = 0; GLuint mOutputFB = 0; }; diff --git a/src/gl/renderer/gl_renderer.cpp b/src/gl/renderer/gl_renderer.cpp index 6c1677b7b..a1d8bb2a7 100644 --- a/src/gl/renderer/gl_renderer.cpp +++ b/src/gl/renderer/gl_renderer.cpp @@ -62,6 +62,9 @@ #include "gl/data/gl_vertexbuffer.h" #include "gl/scene/gl_drawinfo.h" #include "gl/shaders/gl_shader.h" +#include "gl/shaders/gl_bloomshader.h" +#include "gl/shaders/gl_blurshader.h" +#include "gl/shaders/gl_tonemapshader.h" #include "gl/shaders/gl_presentshader.h" #include "gl/textures/gl_texture.h" #include "gl/textures/gl_translate.h" @@ -110,6 +113,10 @@ void gl_FlushModels(); void FGLRenderer::Initialize() { mBuffers = new FGLRenderBuffers(); + mBloomExtractShader = new FBloomExtractShader(); + mBloomCombineShader = new FBloomCombineShader(); + mBlurShader = new FBlurShader(); + mTonemapShader = new FTonemapShader(); mPresentShader = new FPresentShader(); // Only needed for the core profile, because someone decided it was a good idea to remove the default VAO. @@ -160,6 +167,10 @@ FGLRenderer::~FGLRenderer() } if (mBuffers) delete mBuffers; if (mPresentShader) delete mPresentShader; + if (mBloomExtractShader) delete mBloomExtractShader; + if (mBloomCombineShader) delete mBloomCombineShader; + if (mBlurShader) delete mBlurShader; + if (mTonemapShader) delete mTonemapShader; } //========================================================================== @@ -224,10 +235,13 @@ void FGLRenderer::SetupLevel() void FGLRenderer::Begin2D() { - if (FGLRenderBuffers::IsSupported()) + if (FGLRenderBuffers::IsEnabled()) { mBuffers->Setup(framebuffer->GetWidth(), framebuffer->GetHeight()); - mBuffers->BindSceneFB(); + if (mDrawingScene2D) + mBuffers->BindSceneFB(); + else + mBuffers->BindHudFB(); glViewport(0, 0, mOutputViewport.width, mOutputViewport.height); } else diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index 992076528..ad2046cad 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -19,6 +19,10 @@ class FLightBuffer; class FSamplerManager; class DPSprite; class FGLRenderBuffers; +class FBloomExtractShader; +class FBloomCombineShader; +class FBlurShader; +class FTonemapShader; class FPresentShader; inline float DEG2RAD(float deg) @@ -82,6 +86,10 @@ public: int mOldFBID; FGLRenderBuffers *mBuffers; + FBloomExtractShader *mBloomExtractShader; + FBloomCombineShader *mBloomCombineShader; + FBlurShader *mBlurShader; + FTonemapShader *mTonemapShader; FPresentShader *mPresentShader; FTexture *gllight; @@ -100,6 +108,8 @@ public: GL_IRECT mOutputViewportLB; GL_IRECT mOutputViewport; + bool mDrawingScene2D = false; + float mCameraExposure = 1.0f; FGLRenderer(OpenGLFrameBuffer *fb); ~FGLRenderer() ; @@ -107,7 +117,7 @@ public: angle_t FrustumAngle(); void SetViewArea(); void SetOutputViewport(GL_IRECT *bounds); - void Set3DViewport(); + void Set3DViewport(bool toscreen); void Reset3DViewport(); sector_t *RenderViewpoint (AActor * camera, GL_IRECT * bounds, float fov, float ratio, float fovratio, bool mainview, bool toscreen); void RenderView(player_t *player); @@ -147,6 +157,8 @@ public: void SetFixedColormap (player_t *player); void WriteSavePic (player_t *player, FILE *file, int width, int height); void EndDrawScene(sector_t * viewsector); + void BloomScene(); + void TonemapScene(); void Flush(); void SetProjection(float fov, float ratio, float fovratio); diff --git a/src/gl/renderer/gl_renderstate.h b/src/gl/renderer/gl_renderstate.h index 0172488eb..2ad611ab2 100644 --- a/src/gl/renderer/gl_renderstate.h +++ b/src/gl/renderer/gl_renderstate.h @@ -37,7 +37,6 @@ enum EEffect EFF_SPHEREMAP, EFF_BURN, EFF_STENCIL, - EFF_GAMMACORRECTION, MAX_EFFECTS }; diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 3f52ce2b1..b28e2c293 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -72,7 +72,6 @@ #include "gl/scene/gl_drawinfo.h" #include "gl/scene/gl_portal.h" #include "gl/shaders/gl_shader.h" -#include "gl/shaders/gl_presentshader.h" #include "gl/stereo3d/gl_stereo3d.h" #include "gl/stereo3d/scoped_view_shifter.h" #include "gl/textures/gl_translate.h" @@ -95,8 +94,6 @@ CVAR(Bool, gl_sort_textures, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) EXTERN_CVAR (Bool, cl_capfps) EXTERN_CVAR (Bool, r_deathcamera) -EXTERN_CVAR(Float, vid_brightness) -EXTERN_CVAR(Float, vid_contrast) extern int viewpitch; @@ -160,7 +157,7 @@ void FGLRenderer::SetViewArea() void FGLRenderer::Reset3DViewport() { - if (FGLRenderBuffers::IsSupported()) + if (FGLRenderBuffers::IsEnabled()) glViewport(0, 0, mOutputViewport.width, mOutputViewport.height); else glViewport(mOutputViewport.left, mOutputViewport.top, mOutputViewport.width, mOutputViewport.height); @@ -172,10 +169,10 @@ void FGLRenderer::Reset3DViewport() // //----------------------------------------------------------------------------- -void FGLRenderer::Set3DViewport() +void FGLRenderer::Set3DViewport(bool toscreen) { const auto &bounds = mOutputViewportLB; - if (FGLRenderBuffers::IsSupported()) + if (toscreen && FGLRenderBuffers::IsEnabled()) { mBuffers->Setup(mOutputViewport.width, mOutputViewport.height); mBuffers->BindSceneFB(); @@ -204,97 +201,6 @@ void FGLRenderer::Set3DViewport() glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE); } -//----------------------------------------------------------------------------- -// -// Run post processing steps and copy to frame buffer -// -//----------------------------------------------------------------------------- - -void FGLRenderer::Flush() -{ - if (FGLRenderBuffers::IsSupported()) - { - glDisable(GL_MULTISAMPLE); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - - mBuffers->BindOutputFB(); - - // Calculate letterbox - int clientWidth = framebuffer->GetClientWidth(); - int clientHeight = framebuffer->GetClientHeight(); - float scaleX = clientWidth / (float)mOutputViewport.width; - float scaleY = clientHeight / (float)mOutputViewport.height; - float scale = MIN(scaleX, scaleY); - int width = (int)round(mOutputViewport.width * scale); - int height = (int)round(mOutputViewport.height * scale); - int x = (clientWidth - width) / 2; - int y = (clientHeight - height) / 2; - - // Black bars around the box: - glViewport(0, 0, clientWidth, clientHeight); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glEnable(GL_SCISSOR_TEST); - if (y > 0) - { - glScissor(0, 0, clientWidth, y); - glClear(GL_COLOR_BUFFER_BIT); - } - if (clientHeight - y - height > 0) - { - glScissor(0, y + height, clientWidth, clientHeight - y - height); - glClear(GL_COLOR_BUFFER_BIT); - } - if (x > 0) - { - glScissor(0, y, x, height); - glClear(GL_COLOR_BUFFER_BIT); - } - if (clientWidth - x - width > 0) - { - glScissor(x + width, y, clientWidth - x - width, height); - glClear(GL_COLOR_BUFFER_BIT); - } - glDisable(GL_SCISSOR_TEST); - - // Present what was rendered: - glViewport(x, y, width, height); - - GLboolean blendEnabled; - GLint currentProgram; - glGetBooleanv(GL_BLEND, &blendEnabled); - glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); - glDisable(GL_BLEND); - - mPresentShader->Bind(); - mPresentShader->InputTexture.Set(0); - if (framebuffer->IsHWGammaActive()) - { - mPresentShader->Gamma.Set(1.0f); - mPresentShader->Contrast.Set(1.0f); - mPresentShader->Brightness.Set(0.0f); - } - else - { - mPresentShader->Gamma.Set(clamp(Gamma, 0.1f, 4.f)); - mPresentShader->Contrast.Set(clamp(vid_contrast, 0.1f, 3.f)); - mPresentShader->Brightness.Set(clamp(vid_brightness, -0.8f, 0.8f)); - } - mBuffers->BindSceneTexture(0); - - FFlatVertex *ptr = GLRenderer->mVBO->GetBuffer(); - ptr->Set(-1.0f, -1.0f, 0, 0.0f, 0.0f); ptr++; - ptr->Set(-1.0f, 1.0f, 0, 0.0f, 1.0f); ptr++; - ptr->Set(1.0f, -1.0f, 0, 1.0f, 0.0f); ptr++; - ptr->Set(1.0f, 1.0f, 0, 1.0f, 1.0f); ptr++; - GLRenderer->mVBO->RenderCurrent(ptr, GL_TRIANGLE_STRIP); - - if (blendEnabled) - glEnable(GL_BLEND); - glUseProgram(currentProgram); - } -} - //----------------------------------------------------------------------------- // // Setup the camera position @@ -914,6 +820,20 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo mViewActor=camera; } + if (toscreen) + { + if (gl_exposure == 0.0f) + { + float light = viewsector->lightlevel / 255.0f; + float exposure = MAX(1.0f + (1.0f - light * light) * 0.9f, 0.5f); + mCameraExposure = mCameraExposure * 0.995f + exposure * 0.005f; + } + else + { + mCameraExposure = gl_exposure; + } + } + // 'viewsector' will not survive the rendering so it cannot be used anymore below. retval = viewsector; @@ -927,7 +847,8 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo eye->SetUp(); // TODO: stereo specific viewport - needed when implementing side-by-side modes etc. SetOutputViewport(bounds); - Set3DViewport(); + Set3DViewport(toscreen); + mDrawingScene2D = true; mCurrentFoV = fov; // Stereo mode specific perspective projection SetProjection( eye->GetProjection(fov, ratio, fovratio) ); @@ -945,6 +866,13 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo ProcessScene(toscreen); if (mainview) EndDrawScene(retval); // do not call this for camera textures. + if (toscreen) + { + if (FGLRenderBuffers::IsEnabled()) mBuffers->BlitSceneToTexture(); + BloomScene(); + TonemapScene(); + } + mDrawingScene2D = false; eye->TearDown(); } stereo3dMode.TearDown(); diff --git a/src/gl/scene/gl_sprite.cpp b/src/gl/scene/gl_sprite.cpp index d144da8fd..98e0fb8d5 100644 --- a/src/gl/scene/gl_sprite.cpp +++ b/src/gl/scene/gl_sprite.cpp @@ -722,7 +722,11 @@ void GLSprite::Process(AActor* thing, sector_t * sector, int thruportal) { bool mirror; DAngle ang = (thingpos - ViewPos).Angle(); - FTextureID patch = gl_GetSpriteFrame(spritenum, thing->frame, -1, (ang - thing->Angles.Yaw).BAMs(), &mirror); + FTextureID patch; + if (thing->flags7 & MF7_SPRITEANGLE) + patch = gl_GetSpriteFrame(spritenum, thing->frame, -1, (thing->SpriteAngle).BAMs(), &mirror); + else + patch = gl_GetSpriteFrame(spritenum, thing->frame, -1, (ang - (thing->Angles.Yaw + thing->SpriteRotation)).BAMs(), &mirror); if (!patch.isValid()) return; int type = thing->renderflags & RF_SPRITETYPEMASK; gltexture = FMaterial::ValidateTexture(patch, (type == RF_FACESPRITE), false); diff --git a/src/gl/shaders/gl_bloomshader.cpp b/src/gl/shaders/gl_bloomshader.cpp new file mode 100644 index 000000000..c7191884f --- /dev/null +++ b/src/gl/shaders/gl_bloomshader.cpp @@ -0,0 +1,79 @@ +/* +** gl_bloomshader.cpp +** Shaders used to do bloom +** +**--------------------------------------------------------------------------- +** Copyright 2016 Magnus Norddahl +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of GZDoom or a GZDoom derivative, this code will be +** covered by the terms of the GNU Lesser General Public License as published +** by the Free Software Foundation; either version 2.1 of the License, or (at +** your option) any later version. +** 5. Full disclosure of the entire project's source code, except for third +** party libraries is mandatory. (NOTE: This clause is non-negotiable!) +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "gl/system/gl_system.h" +#include "files.h" +#include "m_swap.h" +#include "v_video.h" +#include "gl/gl_functions.h" +#include "vectors.h" +#include "gl/system/gl_interface.h" +#include "gl/system/gl_framebuffer.h" +#include "gl/system/gl_cvars.h" +#include "gl/shaders/gl_bloomshader.h" + +void FBloomExtractShader::Bind() +{ + if (!mShader) + { + mShader.Compile(FShaderProgram::Vertex, "shaders/glsl/bloomextract.vp", "", 330); + mShader.Compile(FShaderProgram::Fragment, "shaders/glsl/bloomextract.fp", "", 330); + mShader.SetFragDataLocation(0, "FragColor"); + mShader.Link("shaders/glsl/bloomextract"); + mShader.SetAttribLocation(0, "PositionInProjection"); + SceneTexture.Init(mShader, "SceneTexture"); + Exposure.Init(mShader, "ExposureAdjustment"); + } + mShader.Bind(); +} + +void FBloomCombineShader::Bind() +{ + if (!mShader) + { + mShader.Compile(FShaderProgram::Vertex, "shaders/glsl/bloomcombine.vp", "", 330); + mShader.Compile(FShaderProgram::Fragment, "shaders/glsl/bloomcombine.fp", "", 330); + mShader.SetFragDataLocation(0, "FragColor"); + mShader.Link("shaders/glsl/bloomcombine"); + mShader.SetAttribLocation(0, "PositionInProjection"); + BloomTexture.Init(mShader, "Bloom"); + } + mShader.Bind(); +} diff --git a/src/gl/shaders/gl_bloomshader.h b/src/gl/shaders/gl_bloomshader.h new file mode 100644 index 000000000..a8e93df83 --- /dev/null +++ b/src/gl/shaders/gl_bloomshader.h @@ -0,0 +1,29 @@ +#ifndef __GL_BLOOMSHADER_H +#define __GL_BLOOMSHADER_H + +#include "gl_shaderprogram.h" + +class FBloomExtractShader +{ +public: + void Bind(); + + FBufferedUniform1i SceneTexture; + FBufferedUniform1f Exposure; + +private: + FShaderProgram mShader; +}; + +class FBloomCombineShader +{ +public: + void Bind(); + + FBufferedUniform1i BloomTexture; + +private: + FShaderProgram mShader; +}; + +#endif \ No newline at end of file diff --git a/src/gl/shaders/gl_blurshader.cpp b/src/gl/shaders/gl_blurshader.cpp new file mode 100644 index 000000000..10168148b --- /dev/null +++ b/src/gl/shaders/gl_blurshader.cpp @@ -0,0 +1,287 @@ +/* +** gl_blurshader.cpp +** Gaussian blur shader +** +**--------------------------------------------------------------------------- +** Copyright 2016 Magnus Norddahl +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of GZDoom or a GZDoom derivative, this code will be +** covered by the terms of the GNU Lesser General Public License as published +** by the Free Software Foundation; either version 2.1 of the License, or (at +** your option) any later version. +** 5. Full disclosure of the entire project's source code, except for third +** party libraries is mandatory. (NOTE: This clause is non-negotiable!) +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "gl/system/gl_system.h" +#include "files.h" +#include "m_swap.h" +#include "v_video.h" +#include "gl/gl_functions.h" +#include "vectors.h" +#include "gl/system/gl_interface.h" +#include "gl/system/gl_framebuffer.h" +#include "gl/system/gl_cvars.h" +#include "gl/shaders/gl_blurshader.h" +#include "gl/data/gl_vertexbuffer.h" + +//========================================================================== +// +// Performs a vertical gaussian blur pass +// +//========================================================================== + +void FBlurShader::BlurVertical(FFlatVertexBuffer *vbo, float blurAmount, int sampleCount, GLuint inputTexture, GLuint outputFrameBuffer, int width, int height) +{ + Blur(vbo, blurAmount, sampleCount, inputTexture, outputFrameBuffer, width, height, true); +} + +//========================================================================== +// +// Performs a horizontal gaussian blur pass +// +//========================================================================== + +void FBlurShader::BlurHorizontal(FFlatVertexBuffer *vbo, float blurAmount, int sampleCount, GLuint inputTexture, GLuint outputFrameBuffer, int width, int height) +{ + Blur(vbo, blurAmount, sampleCount, inputTexture, outputFrameBuffer, width, height, false); +} + +//========================================================================== +// +// Helper for BlurVertical and BlurHorizontal. Executes the actual pass +// +//========================================================================== + +void FBlurShader::Blur(FFlatVertexBuffer *vbo, float blurAmount, int sampleCount, GLuint inputTexture, GLuint outputFrameBuffer, int width, int height, bool vertical) +{ + BlurSetup *setup = GetSetup(blurAmount, sampleCount); + if (vertical) + setup->VerticalShader->Bind(); + else + setup->HorizontalShader->Bind(); + + if (gl.glslversion < 1.3) + { + if (vertical) + { + setup->VerticalScaleX.Set(1.0f / width); + setup->VerticalScaleY.Set(1.0f / height); + } + else + { + setup->HorizontalScaleX.Set(1.0f / width); + setup->HorizontalScaleY.Set(1.0f / height); + } + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, inputTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindFramebuffer(GL_FRAMEBUFFER, outputFrameBuffer); + glViewport(0, 0, width, height); + glDisable(GL_BLEND); + + FFlatVertex *ptr = vbo->GetBuffer(); + ptr->Set(-1.0f, -1.0f, 0, 0.0f, 0.0f); ptr++; + ptr->Set(-1.0f, 1.0f, 0, 0.0f, 1.0f); ptr++; + ptr->Set(1.0f, -1.0f, 0, 1.0f, 0.0f); ptr++; + ptr->Set(1.0f, 1.0f, 0, 1.0f, 1.0f); ptr++; + vbo->RenderCurrent(ptr, GL_TRIANGLE_STRIP); +} + +//========================================================================== +// +// Compiles the blur shaders needed for the specified blur amount and +// kernel size +// +//========================================================================== + +FBlurShader::BlurSetup *FBlurShader::GetSetup(float blurAmount, int sampleCount) +{ + for (size_t mBlurSetupIndex = 0; mBlurSetupIndex < mBlurSetups.Size(); mBlurSetupIndex++) + { + if (mBlurSetups[mBlurSetupIndex].blurAmount == blurAmount && mBlurSetups[mBlurSetupIndex].sampleCount == sampleCount) + { + return &mBlurSetups[mBlurSetupIndex]; + } + } + + BlurSetup blurSetup(blurAmount, sampleCount); + + FString vertexCode = VertexShaderCode(); + FString horizontalCode = FragmentShaderCode(blurAmount, sampleCount, false); + FString verticalCode = FragmentShaderCode(blurAmount, sampleCount, true); + + blurSetup.VerticalShader = std::make_shared(); + blurSetup.VerticalShader->Compile(FShaderProgram::Vertex, "vertical blur vertex shader", vertexCode, "", 330); + blurSetup.VerticalShader->Compile(FShaderProgram::Fragment, "vertical blur fragment shader", verticalCode, "", 330); + blurSetup.VerticalShader->SetFragDataLocation(0, "FragColor"); + blurSetup.VerticalShader->SetAttribLocation(0, "PositionInProjection"); + blurSetup.VerticalShader->Link("vertical blur"); + blurSetup.VerticalShader->Bind(); + glUniform1i(glGetUniformLocation(*blurSetup.VerticalShader.get(), "SourceTexture"), 0); + + blurSetup.HorizontalShader = std::make_shared(); + blurSetup.HorizontalShader->Compile(FShaderProgram::Vertex, "horizontal blur vertex shader", vertexCode, "", 330); + blurSetup.HorizontalShader->Compile(FShaderProgram::Fragment, "horizontal blur fragment shader", horizontalCode, "", 330); + blurSetup.HorizontalShader->SetFragDataLocation(0, "FragColor"); + blurSetup.HorizontalShader->SetAttribLocation(0, "PositionInProjection"); + blurSetup.HorizontalShader->Link("horizontal blur"); + blurSetup.HorizontalShader->Bind(); + glUniform1i(glGetUniformLocation(*blurSetup.HorizontalShader.get(), "SourceTexture"), 0); + + if (gl.glslversion < 1.3) + { + blurSetup.VerticalScaleX.Init(*blurSetup.VerticalShader.get(), "ScaleX"); + blurSetup.VerticalScaleY.Init(*blurSetup.VerticalShader.get(), "ScaleY"); + blurSetup.HorizontalScaleX.Init(*blurSetup.HorizontalShader.get(), "ScaleX"); + blurSetup.HorizontalScaleY.Init(*blurSetup.HorizontalShader.get(), "ScaleY"); + } + + mBlurSetups.Push(blurSetup); + + return &mBlurSetups[mBlurSetups.Size() - 1]; +} + +//========================================================================== +// +// The vertex shader GLSL code +// +//========================================================================== + +FString FBlurShader::VertexShaderCode() +{ + return R"( + in vec4 PositionInProjection; + out vec2 TexCoord; + + void main() + { + gl_Position = PositionInProjection; + TexCoord = (gl_Position.xy + 1.0) * 0.5; + } + )"; +} + +//========================================================================== +// +// Generates the fragment shader GLSL code for a specific blur setup +// +//========================================================================== + +FString FBlurShader::FragmentShaderCode(float blurAmount, int sampleCount, bool vertical) +{ + TArray sampleWeights; + TArray sampleOffsets; + ComputeBlurSamples(sampleCount, blurAmount, sampleWeights, sampleOffsets); + + const char *fragmentShader = + R"( + in vec2 TexCoord; + uniform sampler2D SourceTexture; + out vec4 FragColor; + #if __VERSION__ < 130 + uniform float ScaleX; + uniform float ScaleY; + vec4 textureOffset(sampler2D s, vec2 texCoord, ivec2 offset) + { + return texture2D(s, texCoord + vec2(ScaleX * float(offset.x), ScaleY * float(offset.y))); + } + #endif + void main() + { + FragColor = %s; + } + )"; + + FString loopCode; + for (int i = 0; i < sampleCount; i++) + { + if (i > 0) + loopCode += " + "; + + if (vertical) + loopCode.AppendFormat("\r\n\t\t\ttextureOffset(SourceTexture, TexCoord, ivec2(0, %d)) * %f", sampleOffsets[i], (double)sampleWeights[i]); + else + loopCode.AppendFormat("\r\n\t\t\ttextureOffset(SourceTexture, TexCoord, ivec2(%d, 0)) * %f", sampleOffsets[i], (double)sampleWeights[i]); + } + + FString code; + code.Format(fragmentShader, loopCode.GetChars()); + return code; +} + +//========================================================================== +// +// Calculates the sample weight for a specific offset in the kernel +// +//========================================================================== + +float FBlurShader::ComputeGaussian(float n, float theta) // theta = Blur Amount +{ + return (float)((1.0f / sqrtf(2 * (float)M_PI * theta)) * expf(-(n * n) / (2.0f * theta * theta))); +} + +//========================================================================== +// +// Calculates the sample weights and offsets +// +//========================================================================== + +void FBlurShader::ComputeBlurSamples(int sampleCount, float blurAmount, TArray &sampleWeights, TArray &sampleOffsets) +{ + sampleWeights.Resize(sampleCount); + sampleOffsets.Resize(sampleCount); + + sampleWeights[0] = ComputeGaussian(0, blurAmount); + sampleOffsets[0] = 0; + + float totalWeights = sampleWeights[0]; + + for (int i = 0; i < sampleCount / 2; i++) + { + float weight = ComputeGaussian(i + 1.0f, blurAmount); + + sampleWeights[i * 2 + 1] = weight; + sampleWeights[i * 2 + 2] = weight; + sampleOffsets[i * 2 + 1] = i + 1; + sampleOffsets[i * 2 + 2] = -i - 1; + + totalWeights += weight * 2; + } + + for (int i = 0; i < sampleCount; i++) + { + sampleWeights[i] /= totalWeights; + } +} diff --git a/src/gl/shaders/gl_blurshader.h b/src/gl/shaders/gl_blurshader.h new file mode 100644 index 000000000..9af5a9101 --- /dev/null +++ b/src/gl/shaders/gl_blurshader.h @@ -0,0 +1,41 @@ +#ifndef __GL_BLURSHADER_H +#define __GL_BLURSHADER_H + +#include "gl_shaderprogram.h" +#include + +class FFlatVertexBuffer; + +class FBlurShader +{ +public: + void BlurVertical(FFlatVertexBuffer *vbo, float blurAmount, int sampleCount, GLuint inputTexture, GLuint outputFrameBuffer, int width, int height); + void BlurHorizontal(FFlatVertexBuffer *vbo, float blurAmount, int sampleCount, GLuint inputTexture, GLuint outputFrameBuffer, int width, int height); + +private: + void Blur(FFlatVertexBuffer *vbo, float blurAmount, int sampleCount, GLuint inputTexture, GLuint outputFrameBuffer, int width, int height, bool vertical); + + struct BlurSetup + { + BlurSetup(float blurAmount, int sampleCount) : blurAmount(blurAmount), sampleCount(sampleCount) { } + + float blurAmount; + int sampleCount; + std::shared_ptr VerticalShader; + std::shared_ptr HorizontalShader; + FBufferedUniform1f VerticalScaleX, VerticalScaleY; + FBufferedUniform1f HorizontalScaleX, HorizontalScaleY; + }; + + BlurSetup *GetSetup(float blurAmount, int sampleCount); + + FString VertexShaderCode(); + FString FragmentShaderCode(float blurAmount, int sampleCount, bool vertical); + + float ComputeGaussian(float n, float theta); + void ComputeBlurSamples(int sampleCount, float blurAmount, TArray &sample_weights, TArray &sample_offsets); + + TArray mBlurSetups; +}; + +#endif \ No newline at end of file diff --git a/src/gl/shaders/gl_presentshader.cpp b/src/gl/shaders/gl_presentshader.cpp index a5d514308..0ebfc9945 100644 --- a/src/gl/shaders/gl_presentshader.cpp +++ b/src/gl/shaders/gl_presentshader.cpp @@ -53,8 +53,8 @@ void FPresentShader::Bind() { if (!mShader) { - mShader.Compile(FShaderProgram::Vertex, "shaders/glsl/present.vp"); - mShader.Compile(FShaderProgram::Fragment, "shaders/glsl/present.fp"); + mShader.Compile(FShaderProgram::Vertex, "shaders/glsl/present.vp", "", 330); + mShader.Compile(FShaderProgram::Fragment, "shaders/glsl/present.fp", "", 330); mShader.SetFragDataLocation(0, "FragColor"); mShader.Link("shaders/glsl/present"); mShader.SetAttribLocation(0, "PositionInProjection"); diff --git a/src/gl/shaders/gl_shader.cpp b/src/gl/shaders/gl_shader.cpp index 551707398..f2b4c7ebd 100644 --- a/src/gl/shaders/gl_shader.cpp +++ b/src/gl/shaders/gl_shader.cpp @@ -56,48 +56,10 @@ #include "gl/renderer/gl_renderstate.h" #include "gl/system/gl_cvars.h" #include "gl/shaders/gl_shader.h" +#include "gl/shaders/gl_shaderprogram.h" #include "gl/textures/gl_material.h" #include "gl/dynlights/gl_lightbuffer.h" -//========================================================================== -// -// patch the shader source to work with -// GLSL 1.2 keywords and identifiers -// -//========================================================================== - -void PatchCommon(FString &code) -{ - code.Substitute("precision highp int;", ""); - code.Substitute("precision highp float;", ""); -} - -void PatchVertShader(FString &code) -{ - PatchCommon(code); - code.Substitute("in vec", "attribute vec"); - code.Substitute("out vec", "varying vec"); - code.Substitute("gl_ClipDistance", "//"); -} - -void PatchFragShader(FString &code) -{ - PatchCommon(code); - code.Substitute("out vec4 FragColor;", ""); - code.Substitute("FragColor", "gl_FragColor"); - code.Substitute("in vec", "varying vec"); - // this patches the switch statement to if's. - code.Substitute("break;", ""); - code.Substitute("switch (uFixedColormap)", "int i = uFixedColormap;"); - code.Substitute("case 0:", "if (i == 0)"); - code.Substitute("case 1:", "else if (i == 1)"); - code.Substitute("case 2:", "else if (i == 2)"); - code.Substitute("case 3:", "else if (i == 3)"); - code.Substitute("case 4:", "else if (i == 4)"); - code.Substitute("case 5:", "else if (i == 5)"); - code.Substitute("texture(", "texture2D("); -} - //========================================================================== // // @@ -204,8 +166,8 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * if (gl.glslversion < 1.3) { - PatchVertShader(vp_comb); - PatchFragShader(fp_comb); + FShaderProgram::PatchVertShader(vp_comb); + FShaderProgram::PatchFragShader(fp_comb); } hVertProg = glCreateShader(GL_VERTEX_SHADER); @@ -433,7 +395,6 @@ static const FEffectShader effectshaders[]= { "spheremap", "shaders/glsl/main.vp", "shaders/glsl/main.fp", "shaders/glsl/func_normal.fp", "#define SPHEREMAP\n#define NO_ALPHATEST\n" }, { "burn", "shaders/glsl/main.vp", "shaders/glsl/burn.fp", NULL, "#define SIMPLE\n#define NO_ALPHATEST\n" }, { "stencil", "shaders/glsl/main.vp", "shaders/glsl/stencil.fp", NULL, "#define SIMPLE\n#define NO_ALPHATEST\n" }, - { "gammacorrection", "shaders/glsl/main.vp", "shaders/glsl/gammacorrection.fp", NULL, "#define SIMPLE\n" }, }; diff --git a/src/gl/shaders/gl_shaderprogram.cpp b/src/gl/shaders/gl_shaderprogram.cpp index 35fd468ae..bed4a2e29 100644 --- a/src/gl/shaders/gl_shaderprogram.cpp +++ b/src/gl/shaders/gl_shaderprogram.cpp @@ -93,18 +93,23 @@ void FShaderProgram::CreateShader(ShaderType type) // //========================================================================== -void FShaderProgram::Compile(ShaderType type, const char *lumpName) +void FShaderProgram::Compile(ShaderType type, const char *lumpName, const char *defines, int maxGlslVersion) +{ + int lump = Wads.CheckNumForFullName(lumpName); + if (lump == -1) I_FatalError("Unable to load '%s'", lumpName); + FString code = Wads.ReadLump(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) { CreateShader(type); const auto &handle = mShaders[type]; - int lump = Wads.CheckNumForFullName(lumpName); - if (lump == -1) I_Error("Unable to load '%s'", lumpName); - FString code = Wads.ReadLump(lump).GetString().GetChars(); - - int lengths[1] = { (int)code.Len() }; - const char *sources[1] = { code.GetChars() }; + FString patchedCode = PatchShader(type, code, defines, maxGlslVersion); + int lengths[1] = { (int)patchedCode.Len() }; + const char *sources[1] = { patchedCode.GetChars() }; glShaderSource(handle, 1, sources, lengths); glCompileShader(handle); @@ -113,7 +118,7 @@ void FShaderProgram::Compile(ShaderType type, const char *lumpName) glGetShaderiv(handle, GL_COMPILE_STATUS, &status); if (status == GL_FALSE) { - I_Error("Compile Shader '%s':\n%s\n", lumpName, GetShaderInfoLog(handle).GetChars()); + I_FatalError("Compile Shader '%s':\n%s\n", name, GetShaderInfoLog(handle).GetChars()); } else { @@ -148,7 +153,7 @@ void FShaderProgram::Link(const char *name) glGetProgramiv(mProgram, GL_LINK_STATUS, &status); if (status == GL_FALSE) { - I_Error("Link Shader '%s':\n%s\n", name, GetProgramInfoLog(mProgram).GetChars()); + I_FatalError("Link Shader '%s':\n%s\n", name, GetProgramInfoLog(mProgram).GetChars()); } } @@ -203,3 +208,86 @@ FString FShaderProgram::GetProgramInfoLog(GLuint handle) 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; + + int shaderVersion = 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; + + if (gl.glslversion >= 1.3) + { + // 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 << code; + + if (gl.glslversion < 1.3) + { + if (type == Vertex) + PatchVertShader(patchedCode); + else if (type == Fragment) + PatchFragShader(patchedCode); + } + + return patchedCode; +} + +//========================================================================== +// +// patch the shader source to work with +// GLSL 1.2 keywords and identifiers +// +//========================================================================== + +void FShaderProgram::PatchCommon(FString &code) +{ + code.Substitute("precision highp int;", ""); + code.Substitute("precision highp float;", ""); +} + +void FShaderProgram::PatchVertShader(FString &code) +{ + PatchCommon(code); + code.Substitute("in vec", "attribute vec"); + code.Substitute("in float", "attribute float"); + code.Substitute("out vec", "varying vec"); + code.Substitute("out float", "varying float"); + code.Substitute("gl_ClipDistance", "//"); +} + +void FShaderProgram::PatchFragShader(FString &code) +{ + PatchCommon(code); + code.Substitute("out vec4 FragColor;", ""); + code.Substitute("FragColor", "gl_FragColor"); + code.Substitute("in vec", "varying vec"); + // this patches the switch statement to if's. + code.Substitute("break;", ""); + code.Substitute("switch (uFixedColormap)", "int i = uFixedColormap;"); + code.Substitute("case 0:", "if (i == 0)"); + code.Substitute("case 1:", "else if (i == 1)"); + code.Substitute("case 2:", "else if (i == 2)"); + code.Substitute("case 3:", "else if (i == 3)"); + code.Substitute("case 4:", "else if (i == 4)"); + code.Substitute("case 5:", "else if (i == 5)"); + code.Substitute("texture(", "texture2D("); +} diff --git a/src/gl/shaders/gl_shaderprogram.h b/src/gl/shaders/gl_shaderprogram.h index 9cc331525..16684ddff 100644 --- a/src/gl/shaders/gl_shaderprogram.h +++ b/src/gl/shaders/gl_shaderprogram.h @@ -15,7 +15,8 @@ public: NumShaderTypes }; - void Compile(ShaderType type, const char *lumpName); + 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 SetFragDataLocation(int index, const char *name); void Link(const char *name); void SetAttribLocation(int index, const char *name); @@ -24,7 +25,14 @@ public: operator GLuint() const { return mProgram; } explicit operator bool() const { return mProgram != 0; } + // Needed by FShader + static void PatchVertShader(FString &code); + static void PatchFragShader(FString &code); + private: + static FString PatchShader(ShaderType type, const FString &code, const char *defines, int maxGlslVersion); + static void PatchCommon(FString &code); + void CreateShader(ShaderType type); FString GetShaderInfoLog(GLuint handle); FString GetProgramInfoLog(GLuint handle); diff --git a/src/gl/shaders/gl_tonemapshader.cpp b/src/gl/shaders/gl_tonemapshader.cpp new file mode 100644 index 000000000..c26e8d1af --- /dev/null +++ b/src/gl/shaders/gl_tonemapshader.cpp @@ -0,0 +1,78 @@ +/* +** gl_tonemapshader.cpp +** Converts a HDR texture to 0-1 range by applying a tonemap operator +** +**--------------------------------------------------------------------------- +** Copyright 2016 Magnus Norddahl +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of GZDoom or a GZDoom derivative, this code will be +** covered by the terms of the GNU Lesser General Public License as published +** by the Free Software Foundation; either version 2.1 of the License, or (at +** your option) any later version. +** 5. Full disclosure of the entire project's source code, except for third +** party libraries is mandatory. (NOTE: This clause is non-negotiable!) +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "gl/system/gl_system.h" +#include "files.h" +#include "m_swap.h" +#include "v_video.h" +#include "gl/gl_functions.h" +#include "vectors.h" +#include "gl/system/gl_interface.h" +#include "gl/system/gl_framebuffer.h" +#include "gl/system/gl_cvars.h" +#include "gl/shaders/gl_tonemapshader.h" + +void FTonemapShader::Bind() +{ + auto &shader = mShader[gl_tonemap]; + if (!shader) + { + shader.Compile(FShaderProgram::Vertex, "shaders/glsl/tonemap.vp", "", 330); + shader.Compile(FShaderProgram::Fragment, "shaders/glsl/tonemap.fp", GetDefines(gl_tonemap), 330); + shader.SetFragDataLocation(0, "FragColor"); + shader.Link("shaders/glsl/tonemap"); + shader.SetAttribLocation(0, "PositionInProjection"); + SceneTexture.Init(shader, "InputTexture"); + Exposure.Init(shader, "ExposureAdjustment"); + } + shader.Bind(); +} + +const char *FTonemapShader::GetDefines(int mode) +{ + switch (mode) + { + default: + case Linear: return "#define LINEAR\n"; + case Reinhard: return "#define REINHARD\n"; + case HejlDawson: return "#define HEJLDAWSON\n"; + case Uncharted2: return "#define UNCHARTED2\n"; + } +} diff --git a/src/gl/shaders/gl_tonemapshader.h b/src/gl/shaders/gl_tonemapshader.h new file mode 100644 index 000000000..846fdf659 --- /dev/null +++ b/src/gl/shaders/gl_tonemapshader.h @@ -0,0 +1,30 @@ +#ifndef __GL_TONEMAPSHADER_H +#define __GL_TONEMAPSHADER_H + +#include "gl_shaderprogram.h" + +class FTonemapShader +{ +public: + void Bind(); + + FBufferedUniform1i SceneTexture; + FBufferedUniform1f Exposure; + +private: + enum TonemapMode + { + None, + Uncharted2, + HejlDawson, + Reinhard, + Linear, + NumTonemapModes + }; + + static const char *GetDefines(int mode); + + FShaderProgram mShader[NumTonemapModes]; +}; + +#endif \ No newline at end of file diff --git a/src/gl/system/gl_cvars.h b/src/gl/system/gl_cvars.h index cedeec06a..30f4cc4f3 100644 --- a/src/gl/system/gl_cvars.h +++ b/src/gl/system/gl_cvars.h @@ -41,4 +41,13 @@ EXTERN_CVAR(Bool, gl_seamless) EXTERN_CVAR(Float, gl_mask_threshold) EXTERN_CVAR(Float, gl_mask_sprite_threshold) +EXTERN_CVAR(Bool, gl_renderbuffers) +EXTERN_CVAR(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) + #endif // _GL_INTERN_H diff --git a/src/gl/system/gl_interface.cpp b/src/gl/system/gl_interface.cpp index 7d091ebb6..ced1b8b4a 100644 --- a/src/gl/system/gl_interface.cpp +++ b/src/gl/system/gl_interface.cpp @@ -192,6 +192,9 @@ void gl_LoadExtensions() { if (CheckExtension("GL_NV_GPU_shader4") || CheckExtension("GL_EXT_GPU_shader4")) gl.glslversion = 1.21f; // for pre-3.0 drivers that support capable hardware. Needed for Apple. else gl.glslversion = 0; + + if (!CheckExtension("GL_EXT_packed_float")) gl.flags |= RFL_NO_RGBA16F; + if (!CheckExtension("GL_EXT_packed_depth_stencil")) gl.flags |= RFL_NO_DEPTHSTENCIL; } else if (gl.version < 4.f) { diff --git a/src/gl/system/gl_interface.h b/src/gl/system/gl_interface.h index 433c2327c..1813e2c7f 100644 --- a/src/gl/system/gl_interface.h +++ b/src/gl/system/gl_interface.h @@ -20,6 +20,9 @@ enum RenderFlags RFL_SHADER_STORAGE_BUFFER = 4, RFL_BUFFER_STORAGE = 8, RFL_SAMPLER_OBJECTS = 16, + + RFL_NO_RGBA16F = 32, + RFL_NO_DEPTHSTENCIL = 64 }; enum TexMode diff --git a/src/gl/system/gl_wipe.cpp b/src/gl/system/gl_wipe.cpp index 6b15e0b6d..4c6922e4f 100644 --- a/src/gl/system/gl_wipe.cpp +++ b/src/gl/system/gl_wipe.cpp @@ -156,9 +156,9 @@ bool OpenGLFrameBuffer::WipeStartScreen(int type) glFinish(); wipestartscreen->Bind(0, false, false); - if (FGLRenderBuffers::IsSupported()) + if (FGLRenderBuffers::IsEnabled()) { - GLRenderer->mBuffers->BindSceneFB(); + GLRenderer->mBuffers->BindHudFB(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, Width, Height); } else @@ -192,8 +192,8 @@ void OpenGLFrameBuffer::WipeEndScreen() glFinish(); wipeendscreen->Bind(0, false, false); - if (FGLRenderBuffers::IsSupported()) - GLRenderer->mBuffers->BindSceneFB(); + if (FGLRenderBuffers::IsEnabled()) + GLRenderer->mBuffers->BindHudFB(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, Width, Height); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -230,9 +230,9 @@ bool OpenGLFrameBuffer::WipeDo(int ticks) glDisable(GL_DEPTH_TEST); glDepthMask(false); - if (FGLRenderBuffers::IsSupported()) + if (FGLRenderBuffers::IsEnabled()) { - GLRenderer->mBuffers->BindSceneFB(); + GLRenderer->mBuffers->BindHudFB(); glViewport(0, 0, GLRenderer->mOutputViewport.width, GLRenderer->mOutputViewport.height); } diff --git a/src/menu/menu.h b/src/menu/menu.h index aa4058f87..04ae7e3f7 100644 --- a/src/menu/menu.h +++ b/src/menu/menu.h @@ -418,7 +418,7 @@ public: class FPlayerNameBox : public FListMenuItemSelectable { - const char *mText; + FString mText; FFont *mFont; EColorRange mFontColor; int mFrameSize; @@ -447,7 +447,7 @@ public: class FValueTextItem : public FListMenuItemSelectable { TArray mSelections; - const char *mText; + FString mText; int mSelection; FFont *mFont; EColorRange mFontColor; @@ -472,7 +472,7 @@ public: class FSliderItem : public FListMenuItemSelectable { - const char *mText; + FString mText; FFont *mFont; EColorRange mFontColor; int mMinrange, mMaxrange; @@ -539,7 +539,7 @@ public: class FOptionMenuItem : public FListMenuItem { protected: - char *mLabel; + FString mLabel; bool mCentered; void drawLabel(int indent, int y, EColorRange color, bool grayed = false); @@ -548,7 +548,7 @@ public: FOptionMenuItem(const char *text, FName action = NAME_None, bool center = false) : FListMenuItem(0, 0, action) { - mLabel = copystring(text); + mLabel = text; mCentered = center; } diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp index d2db8e21e..2d136c32a 100644 --- a/src/menu/menudef.cpp +++ b/src/menu/menudef.cpp @@ -777,7 +777,15 @@ static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc) FString label = sc.String; sc.MustGetStringName(","); sc.MustGetString(); - FOptionMenuItem *it = new FOptionMenuItemSafeCommand(label, sc.String); + FString command = sc.String; + FString prompt; + // Check for optional custom prompt + if (sc.CheckString(",")) + { + sc.MustGetString(); + prompt = sc.String; + } + FOptionMenuItem *it = new FOptionMenuItemSafeCommand(label, command, prompt); desc->mItems.Push(it); } else if (sc.Compare("Control") || sc.Compare("MapControl")) diff --git a/src/menu/optionmenu.cpp b/src/menu/optionmenu.cpp index f6633c76a..efea7d910 100644 --- a/src/menu/optionmenu.cpp +++ b/src/menu/optionmenu.cpp @@ -479,7 +479,6 @@ void DOptionMenu::Drawer () FOptionMenuItem::~FOptionMenuItem() { - if (mLabel != NULL) delete [] mLabel; } int FOptionMenuItem::Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) @@ -507,14 +506,14 @@ int FOptionMenuItem::GetIndent() { return 0; } - const char *label = mLabel; + const char *label = mLabel.GetChars(); if (*label == '$') label = GStrings(label+1); return SmallFont->StringWidth(label); } void FOptionMenuItem::drawLabel(int indent, int y, EColorRange color, bool grayed) { - const char *label = mLabel; + const char *label = mLabel.GetChars(); if (*label == '$') label = GStrings(label+1); int overlay = grayed? MAKEARGB(96,48,0,0) : 0; diff --git a/src/menu/optionmenuitems.h b/src/menu/optionmenuitems.h index 3131e35f7..76718809e 100644 --- a/src/menu/optionmenuitems.h +++ b/src/menu/optionmenuitems.h @@ -103,9 +103,13 @@ public: class FOptionMenuItemSafeCommand : public FOptionMenuItemCommand { // action is a CCMD +protected: + FString mPrompt; + public: - FOptionMenuItemSafeCommand(const char *label, const char *menu) + FOptionMenuItemSafeCommand(const char *label, const char *menu, const char *prompt) : FOptionMenuItemCommand(label, menu) + , mPrompt(prompt) { } @@ -121,9 +125,13 @@ public: bool Activate() { - const char *msg = GStrings("SAFEMESSAGE"); + const char *msg = mPrompt.IsNotEmpty() ? mPrompt.GetChars() : "$SAFEMESSAGE"; + if (*msg == '$') + { + msg = GStrings(msg + 1); + } - const char *actionLabel = mLabel; + const char *actionLabel = mLabel.GetChars(); if (actionLabel != NULL) { if (*actionLabel == '$') @@ -528,7 +536,7 @@ public: int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) { - const char *txt = mCurrent? mAltText.GetChars() : mLabel; + const char *txt = mCurrent? mAltText.GetChars() : mLabel.GetChars(); if (*txt == '$') txt = GStrings(txt + 1); int w = SmallFont->StringWidth(txt) * CleanXfac_1; int x = (screen->GetWidth() - w) / 2; diff --git a/src/menu/playermenu.cpp b/src/menu/playermenu.cpp index cf63283ba..ce542c2f2 100644 --- a/src/menu/playermenu.cpp +++ b/src/menu/playermenu.cpp @@ -64,7 +64,7 @@ EXTERN_CVAR (Bool, cl_run) FPlayerNameBox::FPlayerNameBox(int x, int y, int height, int frameofs, const char *text, FFont *font, EColorRange color, FName action) : FListMenuItemSelectable(x, y, height, action) { - mText = copystring(text); + mText = text; mFont = font; mFontColor = color; mFrameSize = frameofs; @@ -74,7 +74,6 @@ FPlayerNameBox::FPlayerNameBox(int x, int y, int height, int frameofs, const cha FPlayerNameBox::~FPlayerNameBox() { - if (mText != NULL) delete [] mText; } //============================================================================= @@ -220,7 +219,7 @@ bool FPlayerNameBox::MenuEvent(int mkey, bool fromcontroller) FValueTextItem::FValueTextItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, EColorRange valuecolor, FName action, FName values) : FListMenuItemSelectable(x, y, height, action) { - mText = copystring(text); + mText = text; mFont = font; mFontColor = color; mFontColor2 = valuecolor; @@ -240,7 +239,6 @@ FValueTextItem::FValueTextItem(int x, int y, int height, const char *text, FFont FValueTextItem::~FValueTextItem() { - if (mText != NULL) delete [] mText; } //============================================================================= @@ -341,7 +339,7 @@ void FValueTextItem::Drawer(bool selected) FSliderItem::FSliderItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, FName action, int min, int max, int step) : FListMenuItemSelectable(x, y, height, action) { - mText = copystring(text); + mText = text; mFont = font; mFontColor = color; mSelection = 0; @@ -352,7 +350,6 @@ FSliderItem::FSliderItem(int x, int y, int height, const char *text, FFont *font FSliderItem::~FSliderItem() { - if (mText != NULL) delete [] mText; } //============================================================================= diff --git a/src/namedef.h b/src/namedef.h index 17fa9e291..cd0d5615b 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -214,6 +214,7 @@ xx(Brainexplode) // Weapon animator names. xx(Select) xx(Deselect) +xx(DeadLowered) xx(Ready) xx(Fire) xx(Hold) diff --git a/src/p_acs.h b/src/p_acs.h index 13dad4651..243e2ccf3 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -270,6 +270,8 @@ enum SCRIPT_Unloading = 13, SCRIPT_Disconnect = 14, SCRIPT_Return = 15, + SCRIPT_Event = 16, // [BB] + SCRIPT_Kill = 17, // [JM] }; // Script flags diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 5d2098233..48d79e4e0 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -380,6 +380,12 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) target = source; } + // [JM] Fire KILL type scripts for actor. Not needed for players, since they have the "DEATH" script type. + if (!player && !(flags7 & MF7_NOKILLSCRIPTS) && ((flags7 & MF7_USEKILLSCRIPTS) || gameinfo.forcekillscripts)) + { + FBehavior::StaticStartTypedScripts(SCRIPT_Kill, this, true, 0, true); + } + flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY); if (!(flags4 & MF4_DONTFALL)) flags&=~MF_NOGRAVITY; flags |= MF_DROPOFF; diff --git a/src/p_local.h b/src/p_local.h index 34b4cacd8..d02fedde8 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -162,7 +162,7 @@ PClassActor *P_GetSpawnableType(int spawnnum); void InitSpawnablesFromMapinfo(); int P_Thing_CheckInputNum(player_t *p, int inputnum); int P_Thing_Warp(AActor *caller, AActor *reference, double xofs, double yofs, double zofs, DAngle angle, int flags, double heightoffset, double radiusoffset, DAngle pitch); -bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, int count, int flags, int ptr); +int P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, int count, int flags, int ptr, bool counting = false); enum { diff --git a/src/p_map.cpp b/src/p_map.cpp index 031a25240..d2a669fda 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -1567,7 +1567,7 @@ bool P_CheckPosition(AActor *thing, const DVector2 &pos, FCheckPosition &tm, boo tm.floorpic = *rover->top.texture; tm.floorterrain = rover->model->GetTerrain(rover->top.isceiling); } - if (ff_bottom < tm.ceilingz && abs(delta1) >= abs(delta2)) + if (ff_bottom < tm.ceilingz && fabs(delta1) >= fabs(delta2)) { tm.ceilingz = ff_bottom; tm.ceilingpic = *rover->bottom.texture; diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 997112bbd..c5a2d400c 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -384,6 +384,11 @@ void AActor::Serialize(FArchive &arc) << RipLevelMin << RipLevelMax; arc << DefThreshold; + if (SaveVersion >= 4549) + { + arc << SpriteAngle; + arc << SpriteRotation; + } { FString tagstr; diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp index bad9c2d73..9f1233609 100644 --- a/src/p_pspr.cpp +++ b/src/p_pspr.cpp @@ -237,8 +237,7 @@ DPSprite *player_t::GetPSprite(PSPLayers layer) pspr->y = WEAPONTOP; } - pspr->oldx = pspr->x; - pspr->oldy = pspr->y; + pspr->firstTic = true; } return pspr; @@ -510,7 +509,7 @@ void P_DropWeapon (player_t *player) } // Since the weapon is dropping, stop blocking switching. player->WeaponState &= ~WF_DISABLESWITCH; - if (player->ReadyWeapon != nullptr) + if ((player->ReadyWeapon != nullptr) && (player->health > 0 || !(player->ReadyWeapon->WeaponFlags & WIF_NODEATHDESELECT))) { P_SetPsprite(player, PSP_WEAPON, player->ReadyWeapon->GetDownState()); } @@ -1087,7 +1086,7 @@ DEFINE_ACTION_FUNCTION(AInventory, A_Lower) { // Player is dead, so don't bring up a pending weapon // Player is dead, so keep the weapon off screen P_SetPsprite(player, PSP_FLASH, nullptr); - psp->SetState(nullptr); + psp->SetState(player->ReadyWeapon->FindState(NAME_DeadLowered)); return 0; } // [RH] Clear the flash state. Only needed for Strife. @@ -1396,20 +1395,23 @@ void player_t::TickPSprites() pspr = pspr->Next; } - if (ReadyWeapon == nullptr && (health > 0 || mo->DamageType != NAME_Fire)) + if ((health > 0) || (ReadyWeapon != nullptr && !(ReadyWeapon->WeaponFlags & WIF_NODEATHINPUT))) { - if (PendingWeapon != WP_NOCHANGE) - P_BringUpWeapon(this); - } - else - { - P_CheckWeaponSwitch(this); - if (WeaponState & (WF_WEAPONREADY | WF_WEAPONREADYALT)) + if (ReadyWeapon == nullptr) { - P_CheckWeaponFire(this); + if (PendingWeapon != WP_NOCHANGE) + P_BringUpWeapon(this); + } + else + { + P_CheckWeaponSwitch(this); + if (WeaponState & (WF_WEAPONREADY | WF_WEAPONREADYALT)) + { + P_CheckWeaponFire(this); + } + // Check custom buttons + P_CheckWeaponButtons(this); } - // Check custom buttons - P_CheckWeaponButtons(this); } } diff --git a/src/p_things.cpp b/src/p_things.cpp index 6f01715ad..d94ba6a01 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -696,16 +696,16 @@ int P_Thing_CheckInputNum(player_t *p, int inputnum) } return renum; } -bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, int count, int flags, int ptr) +int P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, int count, int flags, int ptr, bool counting) { AActor *ref = COPY_AAPTR(self, ptr); // We need these to check out. if (!ref || !classname || distance <= 0) - return false; + return 0; int counter = 0; - bool result = false; + int result = 0; double closer = distance, farther = 0, current = distance; const bool ptrWillChange = !!(flags & (CPXF_SETTARGET | CPXF_SETMASTER | CPXF_SETTRACER)); const bool ptrDistPref = !!(flags & (CPXF_CLOSEST | CPXF_FARTHEST)); @@ -740,7 +740,7 @@ bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, in if ((ref->Distance2D(mo) < distance && ((flags & CPXF_NOZ) || ((ref->Z() > mo->Z() && ref->Z() - mo->Top() < distance) || - (ref->Z() <= mo->Z() && mo->Z() - ref->Top() < distance))))) + (ref->Z() <= mo->Z() && mo->Z() - ref->Top() < distance))))) { if ((flags & CPXF_CHECKSIGHT) && !(P_CheckSight(mo, ref, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))) continue; @@ -766,19 +766,19 @@ bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, in { if (!(flags & (CPXF_COUNTDEAD | CPXF_DEADONLY))) continue; - counter++; } else { if (flags & CPXF_DEADONLY) continue; - counter++; } + counter++; // Abort if the number of matching classes nearby is greater, we have obviously succeeded in our goal. - if (counter > count) - { - result = (flags & (CPXF_LESSOREQUAL | CPXF_EXACT)) ? false : true; + // Don't abort if calling the counting version CheckProximity non-action function. + if (!counting && counter > count) + { + result = (flags & (CPXF_LESSOREQUAL | CPXF_EXACT)) ? 0 : 1; // However, if we have one SET* flag and either the closest or farthest flags, keep the function going. if (ptrWillChange && ptrDistPref) @@ -805,12 +805,14 @@ bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, in } } - if (counter == count) - result = true; - else if (counter < count) - result = !!((flags & CPXF_LESSOREQUAL) && !(flags & CPXF_EXACT)); - - return result; + if (!counting) + { + if (counter == count) + result = 1; + else if (counter < count) + result = !!((flags & CPXF_LESSOREQUAL) && !(flags & CPXF_EXACT)) ? 1 : 0; + } + return counting ? counter : result; } int P_Thing_Warp(AActor *caller, AActor *reference, double xofs, double yofs, double zofs, DAngle angle, int flags, double heightoffset, double radiusoffset, DAngle pitch) diff --git a/src/posix/cocoa/i_video.mm b/src/posix/cocoa/i_video.mm index 720b4cde0..2071de1dd 100644 --- a/src/posix/cocoa/i_video.mm +++ b/src/posix/cocoa/i_video.mm @@ -332,74 +332,6 @@ private: // --------------------------------------------------------------------------- -class NonCopyable -{ -protected: - NonCopyable() { } - ~NonCopyable() { } - -private: - NonCopyable(const NonCopyable&); - const NonCopyable& operator=(const NonCopyable&); -}; - - -// --------------------------------------------------------------------------- - - -class RenderTarget : private NonCopyable -{ -public: - RenderTarget(const GLsizei width, const GLsizei height); - ~RenderTarget(); - - void Bind(); - void Unbind(); - - FHardwareTexture& GetColorTexture() - { - return m_texture; - } - -private: - GLuint m_ID; - GLuint m_oldID; - - FHardwareTexture m_texture; - - static GLuint GetBoundID(); - -}; // class RenderTarget - - -// --------------------------------------------------------------------------- - - -class CocoaOpenGLFrameBuffer : public OpenGLFrameBuffer, private NonCopyable -{ - typedef OpenGLFrameBuffer Super; - -public: - CocoaOpenGLFrameBuffer(void* hMonitor, int width, int height, int bits, int refreshHz, bool fullscreen); - - virtual bool Lock(bool buffered); - virtual void Update(); - - virtual void GetScreenshotBuffer(const BYTE*& buffer, int& pitch, ESSType& color_type); - - virtual void SetSmoothPicture(bool smooth); - -private: - RenderTarget m_renderTarget; - - void DrawRenderTarget(); - -}; // class CocoaOpenGLFrameBuffer - - -// --------------------------------------------------------------------------- - - EXTERN_CVAR(Float, Gamma) CUSTOM_CVAR(Float, rgamma, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -660,7 +592,7 @@ DFrameBuffer* CocoaVideo::CreateFrameBuffer(const int width, const int height, c if (1 == s_currentRenderer) { - fb = new CocoaOpenGLFrameBuffer(NULL, width, height, 32, 60, fullscreen); + fb = new OpenGLFrameBuffer(NULL, width, height, 32, 60, fullscreen); } else { @@ -1153,6 +1085,20 @@ SDLGLFB::SDLGLFB(void*, const int width, const int height, int, int, const bool , m_lock(-1) , m_isUpdatePending(false) { + CGGammaValue gammaTable[GAMMA_TABLE_SIZE]; + uint32_t actualChannelSize; + + const CGError result = CGGetDisplayTransferByTable(kCGDirectMainDisplay, GAMMA_CHANNEL_SIZE, + gammaTable, &gammaTable[GAMMA_CHANNEL_SIZE], &gammaTable[GAMMA_CHANNEL_SIZE * 2], &actualChannelSize); + m_supportsGamma = kCGErrorSuccess == result && GAMMA_CHANNEL_SIZE == actualChannelSize; + + if (m_supportsGamma) + { + for (uint32_t i = 0; i < GAMMA_TABLE_SIZE; ++i) + { + m_originalGamma[i] = static_cast(gammaTable[i] * 65535.0f); + } + } } SDLGLFB::SDLGLFB() @@ -1236,288 +1182,36 @@ void SDLGLFB::SwapBuffers() void SDLGLFB::SetGammaTable(WORD* table) { + if (m_supportsGamma) + { + CGGammaValue gammaTable[GAMMA_TABLE_SIZE]; + + for (uint32_t i = 0; i < GAMMA_TABLE_SIZE; ++i) + { + gammaTable[i] = static_cast(table[i] / 65535.0f); + } + + CGSetDisplayTransferByTable(kCGDirectMainDisplay, GAMMA_CHANNEL_SIZE, + gammaTable, &gammaTable[GAMMA_CHANNEL_SIZE], &gammaTable[GAMMA_CHANNEL_SIZE * 2]); + } } void SDLGLFB::ResetGammaTable() { + if (m_supportsGamma) + { + SetGammaTable(m_originalGamma); + } } int SDLGLFB::GetClientWidth() { - return GetWidth(); + return static_cast(rbOpts.width + 2.0f * rbOpts.shiftX); } int SDLGLFB::GetClientHeight() { - return GetHeight(); -} - - -// --------------------------------------------------------------------------- - - -void BoundTextureSetFilter(const GLenum target, const GLint filter) -{ - glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter); - - glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -} - -EXTERN_CVAR(Float, vid_brightness) -EXTERN_CVAR(Float, vid_contrast) - -void BoundTextureDraw2D(const GLsizei width, const GLsizei height) -{ - gl_RenderState.SetEffect(EFF_GAMMACORRECTION); - gl_RenderState.SetTextureMode(TM_OPAQUE); - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - gl_RenderState.SetColor(Gamma, vid_contrast, vid_brightness); - gl_RenderState.Apply(); - - static const float x = 0.f; - static const float y = 0.f; - const float w = float(width); - const float h = float(height); - - static const float u1 = 0.f; - static const float v1 = 1.f; - static const float u2 = 1.f; - static const float v2 = 0.f; - - FFlatVertexBuffer* const vbo = GLRenderer->mVBO; - FFlatVertex* ptr = vbo->GetBuffer(); - - ptr->Set(x, y, 0, u1, v1); ++ptr; - ptr->Set(x, y + h, 0, u1, v2); ++ptr; - ptr->Set(x + w, y, 0, u2, v1); ++ptr; - ptr->Set(x + w, y + h, 0, u2, v2); ++ptr; - - vbo->RenderCurrent(ptr, GL_TRIANGLE_STRIP); - - gl_RenderState.SetEffect(EFF_NONE); - gl_RenderState.SetTextureMode(TM_MODULATE); -} - -bool BoundTextureSaveAsPNG(const GLenum target, const char* const path) -{ - if (NULL == path) - { - return false; - } - - GLint width = 0; - GLint height = 0; - - glGetTexLevelParameteriv(target, 0, GL_TEXTURE_WIDTH, &width ); - glGetTexLevelParameteriv(target, 0, GL_TEXTURE_HEIGHT, &height); - - if (0 == width || 0 == height) - { - Printf("BoundTextureSaveAsPNG: invalid texture size %ix%i\n", width, height); - - return false; - } - - static const int BYTES_PER_PIXEL = 4; - - const int imageSize = width * height * BYTES_PER_PIXEL; - unsigned char* imageBuffer = static_cast(malloc(imageSize)); - - if (NULL == imageBuffer) - { - Printf("BoundTextureSaveAsPNG: cannot allocate %i bytes\n", imageSize); - - return false; - } - - glGetTexImage(target, 0, GL_BGRA, GL_UNSIGNED_BYTE, imageBuffer); - - const int lineSize = width * BYTES_PER_PIXEL; - unsigned char lineBuffer[lineSize]; - - for (GLint line = 0; line < height / 2; ++line) - { - void* frontLinePtr = &imageBuffer[line * lineSize]; - void* backLinePtr = &imageBuffer[(height - line - 1) * lineSize]; - - memcpy( lineBuffer, frontLinePtr, lineSize); - memcpy(frontLinePtr, backLinePtr, lineSize); - memcpy( backLinePtr, lineBuffer, lineSize); - } - - FILE* file = fopen(path, "w"); - - if (NULL == file) - { - Printf("BoundTextureSaveAsPNG: cannot open file %s\n", path); - - free(imageBuffer); - - return false; - } - - const bool result = - M_CreatePNG(file, &imageBuffer[0], NULL, SS_BGRA, width, height, width * BYTES_PER_PIXEL) - && M_FinishPNG(file); - - fclose(file); - - free(imageBuffer); - - return result; -} - - -// --------------------------------------------------------------------------- - - -RenderTarget::RenderTarget(const GLsizei width, const GLsizei height) -: m_ID(0) -, m_oldID(0) -, m_texture(width, height, true) -{ - glGenFramebuffersEXT(1, &m_ID); - - Bind(); - m_texture.CreateTexture(NULL, width, height, 0, false, 0); - m_texture.BindToFrameBuffer(); - Unbind(); -} - -RenderTarget::~RenderTarget() -{ - glDeleteFramebuffersEXT(1, &m_ID); -} - - -void RenderTarget::Bind() -{ - const GLuint boundID = GetBoundID(); - - if (m_ID != boundID) - { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_ID); - m_oldID = boundID; - } -} - -void RenderTarget::Unbind() -{ - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_oldID); - m_oldID = 0; -} - - -GLuint RenderTarget::GetBoundID() -{ - GLint result; - glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &result); - - return static_cast(result); -} - - -// --------------------------------------------------------------------------- - - -CocoaOpenGLFrameBuffer::CocoaOpenGLFrameBuffer(void* hMonitor, int width, int height, int bits, int refreshHz, bool fullscreen) -: OpenGLFrameBuffer(hMonitor, width, height, bits, refreshHz, fullscreen) -, m_renderTarget(width, height) -{ - SetSmoothPicture(gl_smooth_rendered); - - // Fill render target with black color - - m_renderTarget.Bind(); - glClear(GL_COLOR_BUFFER_BIT); - m_renderTarget.Unbind(); -} - - -bool CocoaOpenGLFrameBuffer::Lock(bool buffered) -{ - if (0 == m_lock) - { - m_renderTarget.Bind(); - } - - return Super::Lock(buffered); -} - -void CocoaOpenGLFrameBuffer::Update() -{ - if (!CanUpdate()) - { - GLRenderer->Flush(); - return; - } - - Begin2D(false); - - DrawRateStuff(); - GLRenderer->Flush(); - - DrawRenderTarget(); - - Swap(); - Unlock(); - - CheckBench(); -} - - -void CocoaOpenGLFrameBuffer::GetScreenshotBuffer(const BYTE*& buffer, int& pitch, ESSType& color_type) -{ - m_renderTarget.Bind(); - - Super::GetScreenshotBuffer(buffer, pitch, color_type); - - m_renderTarget.Unbind(); -} - - -void CocoaOpenGLFrameBuffer::DrawRenderTarget() -{ - m_renderTarget.Unbind(); - m_renderTarget.GetColorTexture().Bind(0, 0, false); - - if (rbOpts.dirty) - { - // TODO: Figure out why the following glClear() call is needed - // to avoid drawing of garbage in fullscreen mode when - // in-game's aspect ratio is different from display one - glClear(GL_COLOR_BUFFER_BIT); - - rbOpts.dirty = false; - } - - glViewport(rbOpts.shiftX, rbOpts.shiftY, rbOpts.width, rbOpts.height); - - BoundTextureDraw2D(Width, Height); - - glViewport(0, 0, Width, Height); -} - - -void CocoaOpenGLFrameBuffer::SetSmoothPicture(const bool smooth) -{ - FHardwareTexture& texture = m_renderTarget.GetColorTexture(); - texture.Bind(0, 0, false); - BoundTextureSetFilter(GL_TEXTURE_2D, smooth ? GL_LINEAR : GL_NEAREST); -} - - -// --------------------------------------------------------------------------- - - -CUSTOM_CVAR(Bool, gl_smooth_rendered, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) -{ - if (NULL != screen) - { - screen->SetSmoothPicture(self); - } + return static_cast(rbOpts.height + 2.0f * rbOpts.shiftY); } diff --git a/src/posix/cocoa/sdlglvideo.h b/src/posix/cocoa/sdlglvideo.h index 4a63a9193..cab846a15 100644 --- a/src/posix/cocoa/sdlglvideo.h +++ b/src/posix/cocoa/sdlglvideo.h @@ -71,7 +71,12 @@ protected: int m_lock; bool m_isUpdatePending; - static const bool m_supportsGamma = false; + static const uint32_t GAMMA_CHANNEL_SIZE = 256; + static const uint32_t GAMMA_CHANNEL_COUNT = 3; + static const uint32_t GAMMA_TABLE_SIZE = GAMMA_CHANNEL_SIZE * GAMMA_CHANNEL_COUNT; + + bool m_supportsGamma; + WORD m_originalGamma[GAMMA_TABLE_SIZE]; SDLGLFB(); diff --git a/src/posix/cocoa/st_console.mm b/src/posix/cocoa/st_console.mm index e38a5cc85..b037fe6dd 100644 --- a/src/posix/cocoa/st_console.mm +++ b/src/posix/cocoa/st_console.mm @@ -248,9 +248,6 @@ void FConsoleWindow::AddText(const char* message) AddText(color, buffer); } -#define CHECK_BUFFER_SPACE \ - if (pos >= sizeof buffer - 3) { reset = true; continue; } - if (TEXTCOLOR_ESCAPE == *message) { const BYTE* colorID = reinterpret_cast(message) + 1; @@ -268,42 +265,20 @@ void FConsoleWindow::AddText(const char* message) message += 2; } - else if (0x1d == *message) // Opening bar character + else if (0x1d == *message || 0x1f == *message) // Opening and closing bar characters { - CHECK_BUFFER_SPACE; - - // Insert BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT - buffer[pos++] = '\xe2'; - buffer[pos++] = '\x95'; - buffer[pos++] = '\xbc'; + buffer[pos++] = '-'; ++message; } else if (0x1e == *message) // Middle bar character { - CHECK_BUFFER_SPACE; - - // Insert BOX DRAWINGS HEAVY HORIZONTAL - buffer[pos++] = '\xe2'; - buffer[pos++] = '\x94'; - buffer[pos++] = '\x81'; - ++message; - } - else if (0x1f == *message) // Closing bar character - { - CHECK_BUFFER_SPACE; - - // Insert BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT - buffer[pos++] = '\xe2'; - buffer[pos++] = '\x95'; - buffer[pos++] = '\xbe'; + buffer[pos++] = '='; ++message; } else { buffer[pos++] = *message++; } - -#undef CHECK_BUFFER_SPACE } if (0 != pos) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index ef41faea2..6904a05d4 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -395,6 +395,64 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetGibHealth) return 0; } +//========================================================================== +// +// GetSpriteAngle +// +// NON-ACTION function returns the sprite angle of a pointer. +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetSpriteAngle) +{ + if (numret > 0) + { + assert(ret != NULL); + PARAM_SELF_PROLOGUE(AActor); + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *target = COPY_AAPTR(self, ptr); + if (target == nullptr) + { + ret->SetFloat(0.0); + } + else + { + const double ang = target->SpriteAngle.Degrees; + ret->SetFloat(ang); + } + return 1; + } + return 0; +} + +//========================================================================== +// +// GetSpriteRotation +// +// NON-ACTION function returns the sprite rotation of a pointer. +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetSpriteRotation) +{ + if (numret > 0) + { + assert(ret != NULL); + PARAM_SELF_PROLOGUE(AActor); + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *target = COPY_AAPTR(self, ptr); + if (target == nullptr) + { + ret->SetFloat(0.0); + } + else + { + const double ang = target->SpriteRotation.Degrees; + ret->SetFloat(ang); + } + return 1; + } + return 0; +} + //========================================================================== // // GetZAt @@ -585,6 +643,38 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetPlayerInput) return 0; } +//========================================================================== +// +// CountProximity +// +// NON-ACTION function of A_CheckProximity that returns how much it counts. +// Takes a pointer as anyone may or may not be a player. +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, CountProximity) +{ + if (numret > 0) + { + PARAM_SELF_PROLOGUE(AActor); + PARAM_CLASS(classname, AActor); + PARAM_FLOAT(distance); + PARAM_INT_OPT(flags) { flags = 0; } + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *mobj = COPY_AAPTR(self, ptr); + if (mobj == nullptr) + { + ret->SetInt(0); + } + else + { + ret->SetInt(P_Thing_CheckProximity(self, classname, distance, 0, flags, ptr, true)); + } + return 1; + } + return 0; +} + //=========================================================================== // // __decorate_internal_state__ @@ -3013,12 +3103,19 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) // A_SelectWeapon // //=========================================================================== +enum SW_Flags +{ + SWF_SELECTPRIORITY = 1, +}; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) { PARAM_SELF_PROLOGUE(AActor); PARAM_CLASS(cls, AWeapon); + PARAM_INT_OPT(flags) { flags = 0; } - if (cls == NULL || self->player == NULL) + bool selectPriority = !!(flags & SWF_SELECTPRIORITY); + + if ((!selectPriority && cls == NULL) || self->player == NULL) { ACTION_RETURN_BOOL(false); } @@ -3033,6 +3130,14 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) } ACTION_RETURN_BOOL(true); } + else if (selectPriority) + { + // [XA] if the named weapon cannot be found (or is a dummy like 'None'), + // select the next highest priority weapon. This is basically + // the same as A_CheckReload minus the ammo check. Handy. + self->player->mo->PickNewWeapon(NULL); + ACTION_RETURN_BOOL(true); + } else { ACTION_RETURN_BOOL(false); @@ -7188,3 +7293,80 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FaceMovementDirection) ACTION_RETURN_BOOL(true); } +//========================================================================== +// +// A_CopySpriteFrame(from, to, flags) +// +// Copies the sprite and/or frame from one pointer to another. +//========================================================================== +enum CPSFFlags +{ + CPSF_NOSPRITE = 1, + CPSF_NOFRAME = 1 << 1, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopySpriteFrame) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT(from); + PARAM_INT(to); + PARAM_INT_OPT(flags) { flags = 0; } + + AActor *copyfrom = COPY_AAPTR(self, from); + AActor *copyto = COPY_AAPTR(self, to); + + if (copyfrom == copyto || copyfrom == nullptr || copyto == nullptr || ((flags & CPSF_NOSPRITE) && (flags & CPSF_NOFRAME))) + { + ACTION_RETURN_BOOL(false); + } + + if (!(flags & CPSF_NOSPRITE)) copyto->sprite = copyfrom->sprite; + if (!(flags & CPSF_NOFRAME)) copyto->frame = copyfrom->frame; + ACTION_RETURN_BOOL(true); +} + +//========================================================================== +// +// A_SetSpriteAngle(angle, ptr) +// +// Specifies which angle the actor must always draw its sprite from. +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpriteAngle) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FLOAT_OPT(angle) { angle = 0.; } + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (mobj == nullptr) + { + ACTION_RETURN_BOOL(false); + } + mobj->SpriteAngle = angle; + ACTION_RETURN_BOOL(true); +} + +//========================================================================== +// +// A_SetSpriteRotation(angle, ptr) +// +// Specifies how much to fake a sprite rotation. +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpriteRotation) +{ + PARAM_ACTION_PROLOGUE; + PARAM_ANGLE_OPT(angle) { angle = 0.; } + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (mobj == nullptr) + { + ACTION_RETURN_BOOL(false); + } + mobj->SpriteRotation = angle; + ACTION_RETURN_BOOL(true); +} diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index a86e98566..3b114fbee 100644 --- a/src/thingdef/thingdef_data.cpp +++ b/src/thingdef/thingdef_data.cpp @@ -257,6 +257,9 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(MF7, LAXTELEFRAGDMG, AActor, flags7), DEFINE_FLAG(MF7, ICESHATTER, AActor, flags7), DEFINE_FLAG(MF7, ALLOWTHRUFLAGS, AActor, flags7), + DEFINE_FLAG(MF7, USEKILLSCRIPTS, AActor, flags7), + DEFINE_FLAG(MF7, NOKILLSCRIPTS, AActor, flags7), + DEFINE_FLAG(MF7, SPRITEANGLE, AActor, flags7), // Effect flags DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects), @@ -362,6 +365,8 @@ static FFlagDef WeaponFlagDefs[] = DEFINE_FLAG(WIF, NO_AUTO_SWITCH, AWeapon, WeaponFlags), DEFINE_FLAG(WIF, AMMO_CHECKBOTH, AWeapon, WeaponFlags), DEFINE_FLAG(WIF, NOAUTOAIM, AWeapon, WeaponFlags), + DEFINE_FLAG(WIF, NODEATHDESELECT, AWeapon, WeaponFlags), + DEFINE_FLAG(WIF, NODEATHINPUT, AWeapon, WeaponFlags), DEFINE_DUMMY_FLAG(NOLMS), DEFINE_FLAG(WIF, ALT_USES_BOTH, AWeapon, WeaponFlags), diff --git a/src/thingdef/thingdef_exp.h b/src/thingdef/thingdef_exp.h index 58820ea15..b878e50f1 100644 --- a/src/thingdef/thingdef_exp.h +++ b/src/thingdef/thingdef_exp.h @@ -48,8 +48,10 @@ #define RESOLVE(p,c) if (p!=NULL) p = p->Resolve(c) #define ABORT(p) if (!(p)) { delete this; return NULL; } #define SAFE_RESOLVE(p,c) RESOLVE(p,c); ABORT(p) +#define SAFE_RESOLVE_OPT(p,c) if (p!=NULL) { SAFE_RESOLVE(p,c) } class VMFunctionBuilder; +class FxJumpStatement; //========================================================================== // @@ -59,21 +61,15 @@ class VMFunctionBuilder; struct FCompileContext { - PClassActor *cls; + TArray Jumps; + PClassActor *Class; - FCompileContext(PClassActor *_cls = NULL) - { - cls = _cls; - } + FCompileContext(PClassActor *cls = nullptr); - PSymbol *FindInClass(FName identifier) - { - return cls ? cls->Symbols.FindSymbol(identifier, true) : NULL; - } - PSymbol *FindGlobal(FName identifier) - { - return GlobalSymbols.FindSymbol(identifier, true); - } + PSymbol *FindInClass(FName identifier); + PSymbol *FindGlobal(FName identifier); + + void HandleJumps(int token, FxExpression *handler); }; //========================================================================== @@ -212,6 +208,8 @@ public: virtual ExpEmit Emit(VMFunctionBuilder *build); + TArray JumpAddresses; + FScriptPosition ScriptPosition; PType *ValueType; @@ -930,6 +928,80 @@ public: ExpEmit Emit(VMFunctionBuilder *build); }; +//========================================================================== +// +// FxWhileLoop +// +//========================================================================== + +class FxWhileLoop : public FxExpression +{ + FxExpression *Condition; + FxExpression *Code; + +public: + FxWhileLoop(FxExpression *condition, FxExpression *code, const FScriptPosition &pos); + ~FxWhileLoop(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// FxDoWhileLoop +// +//========================================================================== + +class FxDoWhileLoop : public FxExpression +{ + FxExpression *Condition; + FxExpression *Code; + +public: + FxDoWhileLoop(FxExpression *condition, FxExpression *code, const FScriptPosition &pos); + ~FxDoWhileLoop(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// FxForLoop +// +//========================================================================== + +class FxForLoop : public FxExpression +{ + FxExpression *Init; + FxExpression *Condition; + FxExpression *Iteration; + FxExpression *Code; + +public: + FxForLoop(FxExpression *init, FxExpression *condition, FxExpression *iteration, FxExpression *code, const FScriptPosition &pos); + ~FxForLoop(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// FxJumpStatement +// +//========================================================================== + +class FxJumpStatement : public FxExpression +{ +public: + FxJumpStatement(int token, const FScriptPosition &pos); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); + + int Token; + size_t Address; + FxExpression *AddressResolver; +}; + //========================================================================== // // FxReturnStatement diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index b4a0b3fda..acb26d05b 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -85,6 +85,45 @@ static const FLOP FxFlops[] = { NAME_TanH, FLOP_TANH, [](double v) { return g_tanh(v); } }, }; +//========================================================================== +// +// FCompileContext +// +//========================================================================== + +FCompileContext::FCompileContext(PClassActor *cls) : Class(cls) +{ +} + +PSymbol *FCompileContext::FindInClass(FName identifier) +{ + return Class ? Class->Symbols.FindSymbol(identifier, true) : nullptr; +} +PSymbol *FCompileContext::FindGlobal(FName identifier) +{ + return GlobalSymbols.FindSymbol(identifier, true); +} + +void FCompileContext::HandleJumps(int token, FxExpression *handler) +{ + for (unsigned int i = 0; i < Jumps.Size(); i++) + { + if (Jumps[i]->Token == token) + { + Jumps[i]->AddressResolver = handler; + handler->JumpAddresses.Push(Jumps[i]); + Jumps.Delete(i); + i--; + } + } +} + +//========================================================================== +// +// ExpEmit +// +//========================================================================== + ExpEmit::ExpEmit(VMFunctionBuilder *build, int type) : RegNum(build->Registers[type].Get(1)), RegType(type), Konst(false), Fixed(false) { @@ -2838,14 +2877,14 @@ FxSelf::FxSelf(const FScriptPosition &pos) FxExpression *FxSelf::Resolve(FCompileContext& ctx) { CHECKRESOLVED(); - if (!ctx.cls) + if (!ctx.Class) { // can't really happen with DECORATE's expression evaluator. ScriptPosition.Message(MSG_ERROR, "self used outside of a member function"); delete this; return NULL; } - ValueType = ctx.cls; + ValueType = ctx.Class; ValueType = NewPointer(RUNTIME_CLASS(DObject)); return this; } @@ -3818,6 +3857,369 @@ ExpEmit FxIfStatement::Emit(VMFunctionBuilder *build) return ExpEmit(); } +//========================================================================== +// +// FxWhileLoop +// +//========================================================================== + +FxWhileLoop::FxWhileLoop(FxExpression *condition, FxExpression *code, const FScriptPosition &pos) +: FxExpression(pos), Condition(condition), Code(code) +{ + ValueType = TypeVoid; +} + +FxWhileLoop::~FxWhileLoop() +{ + SAFE_DELETE(Condition); + SAFE_DELETE(Code); +} + +FxExpression *FxWhileLoop::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE(Condition, ctx); + SAFE_RESOLVE_OPT(Code, ctx); + + ctx.HandleJumps(TK_Break, this); + ctx.HandleJumps(TK_Continue, this); + + if (Condition->ValueType != TypeBool) + { + Condition = new FxBoolCast(Condition); + SAFE_RESOLVE(Condition, ctx); + } + + if (Condition->isConstant()) + { + if (static_cast(Condition)->GetValue().GetBool() == false) + { // Nothing happens + FxExpression *nop = new FxNop(ScriptPosition); + delete this; + return nop; + } + else if (Code == nullptr) + { // "while (true) { }" + // Someone could be using this for testing. + ScriptPosition.Message(MSG_WARNING, "Infinite empty loop"); + } + } + + return this; +} + +ExpEmit FxWhileLoop::Emit(VMFunctionBuilder *build) +{ + assert(Condition->ValueType == TypeBool); + + size_t loopstart, loopend; + size_t jumpspot; + + // Evaluate the condition and execute/break out of the loop. + loopstart = build->GetAddress(); + if (!Condition->isConstant()) + { + ExpEmit cond = Condition->Emit(build); + build->Emit(OP_TEST, cond.RegNum, 0); + jumpspot = build->Emit(OP_JMP, 0); + cond.Free(build); + } + else assert(static_cast(Condition)->GetValue().GetBool() == true); + + // Execute the loop's content. + if (Code != nullptr) + { + ExpEmit code = Code->Emit(build); + code.Free(build); + } + + // Loop back. + build->Backpatch(build->Emit(OP_JMP, 0), loopstart); + loopend = build->GetAddress(); + + if (!Condition->isConstant()) + { + build->Backpatch(jumpspot, loopend); + } + + // Give a proper address to any break/continue statement within this loop. + for (unsigned int i = 0; i < JumpAddresses.Size(); i++) + { + if (JumpAddresses[i]->Token == TK_Break) + { + build->Backpatch(JumpAddresses[i]->Address, loopend); + } + else + { // Continue statement. + build->Backpatch(JumpAddresses[i]->Address, loopstart); + } + } + + return ExpEmit(); +} + +//========================================================================== +// +// FxDoWhileLoop +// +//========================================================================== + +FxDoWhileLoop::FxDoWhileLoop(FxExpression *condition, FxExpression *code, const FScriptPosition &pos) +: FxExpression(pos), Condition(condition), Code(code) +{ + ValueType = TypeVoid; +} + +FxDoWhileLoop::~FxDoWhileLoop() +{ + SAFE_DELETE(Condition); + SAFE_DELETE(Code); +} + +FxExpression *FxDoWhileLoop::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE(Condition, ctx); + SAFE_RESOLVE_OPT(Code, ctx); + + ctx.HandleJumps(TK_Break, this); + ctx.HandleJumps(TK_Continue, this); + + if (Condition->ValueType != TypeBool) + { + Condition = new FxBoolCast(Condition); + SAFE_RESOLVE(Condition, ctx); + } + + if (Condition->isConstant()) + { + if (static_cast(Condition)->GetValue().GetBool() == false) + { // The code executes once, if any. + if (JumpAddresses.Size() == 0) + { // We would still have to handle the jumps however. + FxExpression *e = Code; + if (e == nullptr) e = new FxNop(ScriptPosition); + Code = nullptr; + delete this; + return e; + } + } + else if (Code == nullptr) + { // "do { } while (true);" + // Someone could be using this for testing. + ScriptPosition.Message(MSG_WARNING, "Infinite empty loop"); + } + } + + return this; +} + +ExpEmit FxDoWhileLoop::Emit(VMFunctionBuilder *build) +{ + assert(Condition->ValueType == TypeBool); + + size_t loopstart, loopend; + size_t codestart; + + // Execute the loop's content. + codestart = build->GetAddress(); + if (Code != nullptr) + { + ExpEmit code = Code->Emit(build); + code.Free(build); + } + + // Evaluate the condition and execute/break out of the loop. + loopstart = build->GetAddress(); + if (!Condition->isConstant()) + { + ExpEmit cond = Condition->Emit(build); + build->Emit(OP_TEST, cond.RegNum, 1); + cond.Free(build); + build->Backpatch(build->Emit(OP_JMP, 0), codestart); + } + else if (static_cast(Condition)->GetValue().GetBool() == true) + { // Always looping + build->Backpatch(build->Emit(OP_JMP, 0), codestart); + } + loopend = build->GetAddress(); + + // Give a proper address to any break/continue statement within this loop. + for (unsigned int i = 0; i < JumpAddresses.Size(); i++) + { + if (JumpAddresses[i]->Token == TK_Break) + { + build->Backpatch(JumpAddresses[i]->Address, loopend); + } + else + { // Continue statement. + build->Backpatch(JumpAddresses[i]->Address, loopstart); + } + } + + return ExpEmit(); +} + +//========================================================================== +// +// FxForLoop +// +//========================================================================== + +FxForLoop::FxForLoop(FxExpression *init, FxExpression *condition, FxExpression *iteration, FxExpression *code, const FScriptPosition &pos) +: FxExpression(pos), Init(init), Condition(condition), Iteration(iteration), Code(code) +{ + ValueType = TypeVoid; +} + +FxForLoop::~FxForLoop() +{ + SAFE_DELETE(Init); + SAFE_DELETE(Condition); + SAFE_DELETE(Iteration); + SAFE_DELETE(Code); +} + +FxExpression *FxForLoop::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE_OPT(Init, ctx); + SAFE_RESOLVE_OPT(Condition, ctx); + SAFE_RESOLVE_OPT(Iteration, ctx); + SAFE_RESOLVE_OPT(Code, ctx); + + ctx.HandleJumps(TK_Break, this); + ctx.HandleJumps(TK_Continue, this); + + if (Condition != nullptr) + { + if (Condition->ValueType != TypeBool) + { + Condition = new FxBoolCast(Condition); + SAFE_RESOLVE(Condition, ctx); + } + + if (Condition->isConstant()) + { + if (static_cast(Condition)->GetValue().GetBool() == false) + { // Nothing happens + FxExpression *nop = new FxNop(ScriptPosition); + delete this; + return nop; + } + else + { // "for (..; true; ..)" + delete Condition; + Condition = nullptr; + } + } + } + if (Condition == nullptr && Code == nullptr) + { // "for (..; ; ..) { }" + // Someone could be using this for testing. + ScriptPosition.Message(MSG_WARNING, "Infinite empty loop"); + } + + return this; +} + +ExpEmit FxForLoop::Emit(VMFunctionBuilder *build) +{ + assert((Condition && Condition->ValueType == TypeBool && !Condition->isConstant()) || Condition == nullptr); + + size_t loopstart, loopend; + size_t codestart; + size_t jumpspot; + + // Init statement. + if (Init != nullptr) + { + ExpEmit init = Init->Emit(build); + init.Free(build); + } + + // Evaluate the condition and execute/break out of the loop. + codestart = build->GetAddress(); + if (Condition != nullptr) + { + ExpEmit cond = Condition->Emit(build); + build->Emit(OP_TEST, cond.RegNum, 0); + cond.Free(build); + jumpspot = build->Emit(OP_JMP, 0); + } + + // Execute the loop's content. + if (Code != nullptr) + { + ExpEmit code = Code->Emit(build); + code.Free(build); + } + + // Iteration statement. + loopstart = build->GetAddress(); + if (Iteration != nullptr) + { + ExpEmit iter = Iteration->Emit(build); + iter.Free(build); + } + build->Backpatch(build->Emit(OP_JMP, 0), codestart); + + // End of loop. + loopend = build->GetAddress(); + if (Condition != nullptr) + { + build->Backpatch(jumpspot, loopend); + } + + // Give a proper address to any break/continue statement within this loop. + for (unsigned int i = 0; i < JumpAddresses.Size(); i++) + { + if (JumpAddresses[i]->Token == TK_Break) + { + build->Backpatch(JumpAddresses[i]->Address, loopend); + } + else + { // Continue statement. + build->Backpatch(JumpAddresses[i]->Address, loopstart); + } + } + + return ExpEmit(); +} + +//========================================================================== +// +// FxJumpStatement +// +//========================================================================== + +FxJumpStatement::FxJumpStatement(int token, const FScriptPosition &pos) +: FxExpression(pos), Token(token), AddressResolver(nullptr) +{ + ValueType = TypeVoid; +} + +FxExpression *FxJumpStatement::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + + ctx.Jumps.Push(this); + + return this; +} + +ExpEmit FxJumpStatement::Emit(VMFunctionBuilder *build) +{ + if (AddressResolver == nullptr) + { + ScriptPosition.Message(MSG_ERROR, "Jump statement %s has nowhere to go!", FScanner::TokenName(Token).GetChars()); + } + + Address = build->Emit(OP_JMP, 0); + + return ExpEmit(); +} + //========================================================================== // //========================================================================== @@ -4008,19 +4410,19 @@ ExpEmit FxClassTypeCast::Emit(VMFunctionBuilder *build) FxExpression *FxStateByIndex::Resolve(FCompileContext &ctx) { CHECKRESOLVED(); - if (ctx.cls->NumOwnedStates == 0) + if (ctx.Class->NumOwnedStates == 0) { // This can't really happen assert(false); } - if (ctx.cls->NumOwnedStates <= index) + if (ctx.Class->NumOwnedStates <= index) { ScriptPosition.Message(MSG_ERROR, "%s: Attempt to jump to non existing state index %d", - ctx.cls->TypeName.GetChars(), index); + ctx.Class->TypeName.GetChars(), index); delete this; return NULL; } - FxExpression *x = new FxConstant(ctx.cls->OwnedStates + index, ScriptPosition); + FxExpression *x = new FxConstant(ctx.Class->OwnedStates + index, ScriptPosition); delete this; return x; } @@ -4068,7 +4470,7 @@ FxExpression *FxMultiNameState::Resolve(FCompileContext &ctx) } else if (names[0] == NAME_Super) { - scope = dyn_cast(ctx.cls->ParentClass); + scope = dyn_cast(ctx.Class->ParentClass); } else { @@ -4079,9 +4481,9 @@ FxExpression *FxMultiNameState::Resolve(FCompileContext &ctx) delete this; return NULL; } - else if (!scope->IsDescendantOf(ctx.cls)) + else if (!scope->IsDescendantOf(ctx.Class)) { - ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(),ctx.cls->TypeName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(),ctx.Class->TypeName.GetChars()); delete this; return NULL; } diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index db6ef14a0..372f444cd 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -1345,6 +1345,24 @@ DEFINE_PROPERTY(gravity, F, Actor) defaults->Gravity = i; } +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(spriteangle, F, Actor) +{ + PROP_DOUBLE_PARM(i, 0); + defaults->SpriteAngle = i; +} + +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(spriterotation, F, Actor) +{ + PROP_DOUBLE_PARM(i, 0); + defaults->SpriteRotation = i; +} + //========================================================================== // //========================================================================== diff --git a/src/thingdef/thingdef_states.cpp b/src/thingdef/thingdef_states.cpp index ae1805119..ecc62bf29 100644 --- a/src/thingdef/thingdef_states.cpp +++ b/src/thingdef/thingdef_states.cpp @@ -480,6 +480,93 @@ static FxExpression *ParseIf(FScanner &sc, FState state, FString statestring, Ba return add; } +static FxExpression *ParseWhile(FScanner &sc, FState state, FString statestring, Baggage &bag, + PPrototype *&retproto, bool &lastwasret) +{ + FxExpression *cond, *code; + PPrototype *proto; + bool ret; + + sc.MustGetStringName("("); + cond = ParseExpression(sc, bag.Info); + sc.MustGetStringName(")"); + sc.MustGetStringName("{"); // Enforce braces like for if statements. + + code = ParseActions(sc, state, statestring, bag, proto, ret); + sc.MustGetString(); + + retproto = ReturnCheck(retproto, proto, sc); + lastwasret = false; // A while loop always jumps back. + + return new FxWhileLoop(cond, code, sc); +} + +static FxExpression *ParseDoWhile(FScanner &sc, FState state, FString statestring, Baggage &bag, + PPrototype *&retproto, bool &lastwasret) +{ + FxExpression *cond, *code; + PPrototype *proto; + bool ret; + + sc.MustGetStringName("{"); // Enforce braces like for if statements. + code = ParseActions(sc, state, statestring, bag, proto, ret); + + sc.MustGetStringName("while"); + sc.MustGetStringName("("); + cond = ParseExpression(sc, bag.Info); + sc.MustGetStringName(")"); + sc.MustGetStringName(";"); + sc.MustGetString(); + + retproto = ReturnCheck(retproto, proto, sc); + lastwasret = false; + + return new FxDoWhileLoop(cond, code, sc); +} + +static FxExpression *ParseFor(FScanner &sc, FState state, FString statestring, Baggage &bag, + PPrototype *&retproto, bool &lastwasret) +{ + FxExpression *init = nullptr; + FxExpression *cond = nullptr; + FxExpression *iter = nullptr; + FxExpression *code = nullptr; + PPrototype *proto; + bool ret; + + // Parse the statements. + sc.MustGetStringName("("); + sc.MustGetString(); + if (!sc.Compare(";")) + { + init = ParseAction(sc, state, statestring, bag); // That's all DECORATE can handle for now. + sc.MustGetStringName(";"); + } + sc.MustGetString(); + if (!sc.Compare(";")) + { + sc.UnGet(); + cond = ParseExpression(sc, bag.Info); + sc.MustGetStringName(";"); + } + sc.MustGetString(); + if (!sc.Compare(")")) + { + iter = ParseAction(sc, state, statestring, bag); + sc.MustGetStringName(")"); + } + + // Now parse the loop's content. + sc.MustGetStringName("{"); // Enforce braces like for if statements. + code = ParseActions(sc, state, statestring, bag, proto, ret); + sc.MustGetString(); + + retproto = ReturnCheck(retproto, proto, sc); + lastwasret = false; + + return new FxForLoop(init, cond, iter, code, sc); +} + FxExpression *ParseActions(FScanner &sc, FState state, FString statestring, Baggage &bag, PPrototype *&retproto, bool &endswithret) { @@ -505,9 +592,21 @@ FxExpression *ParseActions(FScanner &sc, FState state, FString statestring, Bagg FxExpression *add; lastwasret = false; if (sc.Compare("if")) - { // Hangle an if statement + { // Handle an if statement add = ParseIf(sc, state, statestring, bag, proto, lastwasret); } + else if (sc.Compare("while")) + { // Handle a while loop + add = ParseWhile(sc, state, statestring, bag, proto, lastwasret); + } + else if (sc.Compare("do")) + { // Handle a do-while loop + add = ParseDoWhile(sc, state, statestring, bag, proto, lastwasret); + } + else if (sc.Compare("for")) + { // Handle a for loop + add = ParseFor(sc, state, statestring, bag, proto, lastwasret); + } else if (sc.Compare("return")) { // Handle a return statement lastwasret = true; @@ -529,6 +628,18 @@ FxExpression *ParseActions(FScanner &sc, FState state, FString statestring, Bagg sc.MustGetString(); add = new FxReturnStatement(retexp, sc); } + else if (sc.Compare("break")) + { + add = new FxJumpStatement(TK_Break, sc); + sc.MustGetStringName(";"); + sc.MustGetString(); + } + else if (sc.Compare("continue")) + { + add = new FxJumpStatement(TK_Continue, sc); + sc.MustGetStringName(";"); + sc.MustGetString(); + } else { // Handle a regular action function call add = ParseAction(sc, state, statestring, bag); diff --git a/src/v_video.h b/src/v_video.h index 58dd2d9cb..fa1ce83df 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -415,8 +415,6 @@ public: virtual bool Is8BitMode() = 0; #endif - virtual void SetSmoothPicture(bool smooth) {} - protected: void DrawRateStuff (); void CopyFromBuff (BYTE *src, int srcPitch, int width, int height, BYTE *dest); diff --git a/src/version.h b/src/version.h index 8404ebeec..8859cbe7d 100644 --- a/src/version.h +++ b/src/version.h @@ -76,7 +76,7 @@ const char *GetVersionString(); // Use 4500 as the base git save version, since it's higher than the // SVN revision ever got. -#define SAVEVER 4548 +#define SAVEVER 4549 #define SAVEVERSTRINGIFY2(x) #x #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) diff --git a/src/zscript/vmbuilder.cpp b/src/zscript/vmbuilder.cpp index 2a4f6b1a8..eb49ffb14 100644 --- a/src/zscript/vmbuilder.cpp +++ b/src/zscript/vmbuilder.cpp @@ -436,6 +436,17 @@ bool VMFunctionBuilder::RegAvailability::Reuse(int reg) return true; } +//========================================================================== +// +// VMFunctionBuilder :: GetAddress +// +//========================================================================== + +size_t VMFunctionBuilder::GetAddress() +{ + return Code.Size(); +} + //========================================================================== // // VMFunctionBuilder :: Emit diff --git a/src/zscript/vmbuilder.h b/src/zscript/vmbuilder.h index 89b626c18..2d8721acc 100644 --- a/src/zscript/vmbuilder.h +++ b/src/zscript/vmbuilder.h @@ -34,6 +34,9 @@ public: int GetConstantAddress(void *ptr, VM_ATAG tag); int GetConstantString(FString str); + // Returns the address of the next instruction to be emitted. + size_t GetAddress(); + // Returns the address of the newly-emitted instruction. size_t Emit(int opcode, int opa, int opb, int opc); size_t Emit(int opcode, int opa, VM_SHALF opbc); diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index 8c35df21b..fa8932050 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -35,6 +35,8 @@ ACTOR Actor native //: Thinker BloodType "Blood", "BloodSplatter", "AxeBlood" ExplosionDamage 128 MissileHeight 32 + SpriteAngle 0 + SpriteRotation 0 // Functions native bool CheckClass(class checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false); @@ -48,6 +50,9 @@ ACTOR Actor native //: Thinker native float GetCrouchFactor(int ptr = AAPTR_PLAYER1); native float GetCVar(string cvar); native int GetPlayerInput(int inputnum, int ptr = AAPTR_DEFAULT); + native int CountProximity(class classname, float distance, int flags = 0, int ptr = AAPTR_DEFAULT); + native float GetSpriteAngle(int ptr = AAPTR_DEFAULT); + native float GetSpriteRotation(int ptr = AAPTR_DEFAULT); // Action functions // Meh, MBF redundant functions. Only for DeHackEd support. @@ -239,7 +244,7 @@ ACTOR Actor native //: Thinker native state A_JumpIfInTargetInventory(class itemtype, int amount, state label, int forward_ptr = AAPTR_DEFAULT); native bool A_GiveToTarget(class itemtype, int amount = 0, int forward_ptr = AAPTR_DEFAULT); native bool A_TakeFromTarget(class itemtype, int amount = 0, int flags = 0, int forward_ptr = AAPTR_DEFAULT); - native int A_RadiusGive(class itemtype, float distance, int flags, int amount = 0, class filter = "None", name species = "None", int mindist = 0, int limit = 0); + native int A_RadiusGive(class itemtype, float distance, int flags, int amount = 0, class filter = "None", name species = "None", float mindist = 0, int limit = 0); native state A_CheckSpecies(state jump, name species = "", int ptr = AAPTR_DEFAULT); native void A_CountdownArg(int argnum, state targstate = ""); action native A_CustomMeleeAttack(int damage = 0, sound meleesound = "", sound misssound = "", name damagetype = "none", bool bleed = true); @@ -260,7 +265,7 @@ ACTOR Actor native //: Thinker native state A_CheckLOF(state jump, int flags = 0, float range = 0, float minrange = 0, float angle = 0, float pitch = 0, float offsetheight = 0, float offsetwidth = 0, int ptr_target = AAPTR_DEFAULT, float offsetforward = 0); native state A_JumpIfTargetInLOS (state label, float/*angle*/ fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); native state A_JumpIfInTargetLOS (state label, float/*angle*/ fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); - native bool A_SelectWeapon(class whichweapon); + native bool A_SelectWeapon(class whichweapon, int flags = 0); action native A_Punch(); action native A_Feathers(); action native A_ClassBossHealth(); @@ -330,6 +335,9 @@ ACTOR Actor native //: Thinker native state A_CheckRange(float distance, state label, bool two_dimension = false); action native bool A_FaceMovementDirection(float offset = 0, float anglelimit = 0, float pitchlimit = 0, int flags = 0, int ptr = AAPTR_DEFAULT); action native int A_ClearOverlays(int sstart = 0, int sstop = 0, bool safety = true); + action native bool A_CopySpriteFrame(int from, int to, int flags = 0); + action native bool A_SetSpriteAngle(float angle = 0, int ptr = AAPTR_DEFAULT); + action native bool A_SetSpriteRotation(float angle = 0, int ptr = AAPTR_DEFAULT); native void A_RearrangePointers(int newtarget, int newmaster = AAPTR_DEFAULT, int newtracer = AAPTR_DEFAULT, int flags=0); native void A_TransferPointer(int ptr_source, int ptr_recepient, int sourcefield, int recepientfield=AAPTR_DEFAULT, int flags=0); diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index e11f3d0bb..30afd1064 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -150,6 +150,9 @@ const int WRF_ALLOWUSER2 = 256; const int WRF_ALLOWUSER3 = 512; const int WRF_ALLOWUSER4 = 1024; +// Flags for A_SelectWeapon +const int SWF_SELECTPRIORITY = 1; + // Morph constants const int MRF_ADDSTAMINA = 1; const int MRF_FULLHEALTH = 2; @@ -666,4 +669,11 @@ enum { GAF_RELATIVE = 1, GAF_SWITCH = 1 << 1, +}; + +//Flags for A_CopySpriteFrame +enum +{ + CPSF_NOSPRITE = 1, + CPSF_NOFRAME = 1 << 1, }; \ No newline at end of file diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 30b0f542f..21aab53a6 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2618,6 +2618,9 @@ GLPREFMNU_AMBLIGHT = "Ambient light level"; GLPREFMNU_RENDERQUALITY = "Rendering quality"; GLPREFMNU_VRMODE = "Stereo 3D VR"; GLPREFMNU_VRQUADSTEREO = "Enable Quad Stereo"; +GLPREFMNU_MULTISAMPLE = "Multisample"; +GLPREFMNU_TONEMAP = "Tonemap Mode"; +GLPREFMNU_BLOOM = "Bloom effect"; // Option Values OPTVAL_SMART = "Smart"; @@ -2642,6 +2645,7 @@ OPTVAL_2X = "2x"; OPTVAL_4X = "4x"; OPTVAL_8X = "8x"; OPTVAL_16X = "16x"; +OPTVAL_32X = "32x"; OPTVAL_USEASPALETTE = "Use as palette"; OPTVAL_BLEND = "Blend"; OPTVAL_STANDARD = "Standard"; @@ -2686,3 +2690,6 @@ OPTVAL_AMBERBLUE = "Amber/Blue"; OPTVAL_LEFTEYE = "Left Eye"; OPTVAL_RIGHTEYE = "Right Eye"; OPTVAL_QUADBUFFERED = "Quad-buffered"; +OPTVAL_UNCHARTED2 = "Uncharted 2"; +OPTVAL_HEJLDAWSON = "Hejl Dawson"; +OPTVAL_REINHARD = "Reinhard"; \ No newline at end of file diff --git a/wadsrc/static/menudef.z b/wadsrc/static/menudef.z index e96f6b55c..bcee4ca01 100644 --- a/wadsrc/static/menudef.z +++ b/wadsrc/static/menudef.z @@ -32,6 +32,15 @@ OptionValue "HWGammaModes" 2, "$OPTVAL_FULLSCREENONLY" } +OptionValue "TonemapModes" +{ + 0, "$OPTVAL_OFF" + 1, "$OPTVAL_UNCHARTED2" + 2, "$OPTVAL_HEJLDAWSON" + 3, "$OPTVAL_REINHARD" + 4, "$OPTVAL_LINEAR" +} + OptionValue "TextureFormats" { 0, "$OPTVAL_RGBA8" @@ -54,6 +63,16 @@ OptionValue "Anisotropy" 16, "$OPTVAL_16X" } +OptionValue "Multisample" +{ + 1, "$OPTVAL_OFF" + 2, "$OPTVAL_2X" + 4, "$OPTVAL_4X" + 8, "$OPTVAL_8X" + 16, "$OPTVAL_16X" + 32, "$OPTVAL_32X" +} + OptionValue "Colormaps" { 0, "$OPTVAL_USEASPALETTE" @@ -198,4 +217,7 @@ OptionMenu "GLPrefOptions" Option "$GLPREFMNU_RENDERQUALITY", gl_render_precise, "Precision" Option "$GLPREFMNU_VRMODE", vr_mode, "VRMode" Option "$GLPREFMNU_VRQUADSTEREO", vr_enable_quadbuffered, "OnOff" + Option "$GLPREFMNU_MULTISAMPLE", gl_multisample, "Multisample" + Option "$GLPREFMNU_TONEMAP", gl_tonemap, "TonemapModes" + Option "$GLPREFMNU_BLOOM", gl_bloom, "OnOff" } diff --git a/wadsrc/static/shaders/glsl/bloomcombine.fp b/wadsrc/static/shaders/glsl/bloomcombine.fp new file mode 100644 index 000000000..57496771c --- /dev/null +++ b/wadsrc/static/shaders/glsl/bloomcombine.fp @@ -0,0 +1,10 @@ + +in vec2 TexCoord; +out vec4 FragColor; + +uniform sampler2D Bloom; + +void main() +{ + FragColor = vec4(texture(Bloom, TexCoord).rgb, 0.0); +} diff --git a/wadsrc/static/shaders/glsl/bloomcombine.vp b/wadsrc/static/shaders/glsl/bloomcombine.vp new file mode 100644 index 000000000..5990669a5 --- /dev/null +++ b/wadsrc/static/shaders/glsl/bloomcombine.vp @@ -0,0 +1,9 @@ + +in vec4 PositionInProjection; +out vec2 TexCoord; + +void main() +{ + gl_Position = PositionInProjection; + TexCoord = PositionInProjection.xy * 0.5 + 0.5; +} diff --git a/wadsrc/static/shaders/glsl/bloomextract.fp b/wadsrc/static/shaders/glsl/bloomextract.fp new file mode 100644 index 000000000..dc753ce49 --- /dev/null +++ b/wadsrc/static/shaders/glsl/bloomextract.fp @@ -0,0 +1,12 @@ + +in vec2 TexCoord; +out vec4 FragColor; + +uniform sampler2D SceneTexture; +uniform float ExposureAdjustment; + +void main() +{ + vec4 color = texture(SceneTexture, TexCoord); + FragColor = max(vec4(color.rgb * ExposureAdjustment - 1, 1), vec4(0)); +} diff --git a/wadsrc/static/shaders/glsl/bloomextract.vp b/wadsrc/static/shaders/glsl/bloomextract.vp new file mode 100644 index 000000000..5990669a5 --- /dev/null +++ b/wadsrc/static/shaders/glsl/bloomextract.vp @@ -0,0 +1,9 @@ + +in vec4 PositionInProjection; +out vec2 TexCoord; + +void main() +{ + gl_Position = PositionInProjection; + TexCoord = PositionInProjection.xy * 0.5 + 0.5; +} diff --git a/wadsrc/static/shaders/glsl/gammacorrection.fp b/wadsrc/static/shaders/glsl/gammacorrection.fp deleted file mode 100644 index fe40ac680..000000000 --- a/wadsrc/static/shaders/glsl/gammacorrection.fp +++ /dev/null @@ -1,30 +0,0 @@ -uniform sampler2D tex; - -in vec4 vTexCoord; -in vec4 vColor; - -out vec4 FragColor; - -void main() -{ - vec3 color = texture(tex, vTexCoord.st).rgb; - -// /* DEBUG */ if (vTexCoord.x > 0.5) - { - // Apply contrast - float contrast = clamp(vColor.y, 0.1, 3.0); - color = color.rgb * contrast - (contrast - 1.0) * 0.5; - - // Apply gamma - float gamma = clamp(vColor.x, 0.1, 4.0); - color = sign(color) * pow(abs(color), vec3(1.0 / gamma)); - - // Apply brightness - float brightness = clamp(vColor.z, -0.8, 0.8); - color += brightness * 0.5; - - color = clamp(color, 0.0, 1.0); - } - - FragColor = vec4(color, 1.0); -} diff --git a/wadsrc/static/shaders/glsl/present.fp b/wadsrc/static/shaders/glsl/present.fp index 93534ba7f..4a3f41840 100644 --- a/wadsrc/static/shaders/glsl/present.fp +++ b/wadsrc/static/shaders/glsl/present.fp @@ -1,6 +1,4 @@ -#version 330 - in vec2 TexCoord; out vec4 FragColor; diff --git a/wadsrc/static/shaders/glsl/present.vp b/wadsrc/static/shaders/glsl/present.vp index b73fe7fc8..5990669a5 100644 --- a/wadsrc/static/shaders/glsl/present.vp +++ b/wadsrc/static/shaders/glsl/present.vp @@ -1,6 +1,4 @@ -#version 330 - in vec4 PositionInProjection; out vec2 TexCoord; diff --git a/wadsrc/static/shaders/glsl/tonemap.fp b/wadsrc/static/shaders/glsl/tonemap.fp new file mode 100644 index 000000000..35388b7b8 --- /dev/null +++ b/wadsrc/static/shaders/glsl/tonemap.fp @@ -0,0 +1,73 @@ + +in vec2 TexCoord; +out vec4 FragColor; + +uniform sampler2D InputTexture; +uniform float ExposureAdjustment; + +vec3 Linear(vec3 c) +{ + //return pow(c, 2.2); + return c * c; // cheaper, but assuming gamma of 2.0 instead of 2.2 +} + +vec3 sRGB(vec3 c) +{ + //return pow(c, vec3(1.0 / 2.2)); + return sqrt(c); // cheaper, but assuming gamma of 2.0 instead of 2.2 +} + +#if defined(LINEAR) + +vec3 Tonemap(vec3 color) +{ + return sRGB(color); +} + +#elif defined(REINHARD) + +vec3 Tonemap(vec3 color) +{ + color = color / (1 + color); + return sRGB(color); +} + +#elif defined(HEJLDAWSON) + +vec3 Tonemap(vec3 color) +{ + vec3 x = max(vec3(0), color - 0.004); + return (x * (6.2 * x + 0.5)) / (x * (6.2 * x + 1.7) + 0.06); // no sRGB needed +} + +#else + +vec3 Uncharted2Tonemap(vec3 x) +{ + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F; +} + +vec3 Tonemap(vec3 color) +{ + float W = 11.2; + float ExposureBias = 2.0; + vec3 curr = Uncharted2Tonemap(ExposureBias * color); + vec3 whiteScale = vec3(1) / Uncharted2Tonemap(vec3(W)); + return sRGB(curr * whiteScale); +} + +#endif + +void main() +{ + vec3 color = texture(InputTexture, TexCoord).rgb; + color = color * ExposureAdjustment; + color = Linear(color); // needed because gzdoom's scene texture is not linear at the moment + FragColor = vec4(Tonemap(color), 1.0); +} diff --git a/wadsrc/static/shaders/glsl/tonemap.vp b/wadsrc/static/shaders/glsl/tonemap.vp new file mode 100644 index 000000000..5990669a5 --- /dev/null +++ b/wadsrc/static/shaders/glsl/tonemap.vp @@ -0,0 +1,9 @@ + +in vec4 PositionInProjection; +out vec2 TexCoord; + +void main() +{ + gl_Position = PositionInProjection; + TexCoord = PositionInProjection.xy * 0.5 + 0.5; +}