diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9f2ee2e28c..79f40ae5c9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1194,6 +1194,8 @@ set( FASTMATH_SOURCES gl/shaders/gl_fxaashader.cpp gl/system/gl_interface.cpp gl/system/gl_framebuffer.cpp + gl/system/gl_swframebuffer.cpp + gl/system/gl_swwipe.cpp gl/system/gl_debug.cpp gl/system/gl_menu.cpp gl/system/gl_wipe.cpp diff --git a/src/gl/system/gl_swframebuffer.cpp b/src/gl/system/gl_swframebuffer.cpp new file mode 100644 index 0000000000..40d76aab17 --- /dev/null +++ b/src/gl/system/gl_swframebuffer.cpp @@ -0,0 +1,3613 @@ +/* +** gl_swframebuffer.cpp +** Code to let ZDoom use OpenGL as a simple framebuffer +** +**--------------------------------------------------------------------------- +** Copyright 1998-2011 Randy Heit +** 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. +** +** 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. +**--------------------------------------------------------------------------- +** +** This file does _not_ implement hardware-acclerated 3D rendering. It is +** just a means of getting the pixel data to the screen in a more reliable +** method on modern hardware by copying the entire frame to a texture, +** drawing that to the screen, and presenting. +** +** That said, it does implement hardware-accelerated 2D rendering. +*/ + +#include "gl/system/gl_system.h" +#include "files.h" +#include "m_swap.h" +#include "v_video.h" +#include "doomstat.h" +#include "m_png.h" +#include "m_crc32.h" +#include "vectors.h" +#include "v_palette.h" +#include "templates.h" + +#include "c_dispatch.h" +#include "templates.h" +#include "i_system.h" +#include "i_video.h" +#include "i_input.h" +#include "v_pfx.h" +#include "stats.h" +#include "doomerrors.h" +#include "r_main.h" +#include "r_data/r_translate.h" +#include "f_wipe.h" +#include "sbar.h" +#include "w_wad.h" +#include "r_data/colormaps.h" + +#include "gl/system/gl_interface.h" +#include "gl/system/gl_swframebuffer.h" +#include "gl/data/gl_data.h" +#include "gl/utility/gl_clock.h" +#include "gl/utility/gl_templates.h" +#include "gl/gl_functions.h" +#include "gl_debug.h" + +CVAR(Int, gl_showpacks, 0, 0) +#ifndef WIN32 // Defined in fb_d3d9 for Windows +CVAR(Bool, vid_hwaalines, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +#else +EXTERN_CVAR(Bool, vid_hwaalines) +#endif + +EXTERN_CVAR(Bool, vid_hw2d) +EXTERN_CVAR(Bool, fullscreen) +EXTERN_CVAR(Float, Gamma) +EXTERN_CVAR(Bool, vid_vsync) +EXTERN_CVAR(Float, transsouls) +EXTERN_CVAR(Int, vid_refreshrate) + +extern cycle_t BlitCycles; + +void gl_LoadExtensions(); + +IMPLEMENT_CLASS(OpenGLSWFrameBuffer) + +const char *const OpenGLSWFrameBuffer::ShaderDefines[OpenGLSWFrameBuffer::NUM_SHADERS] = +{ + "#define ENORMALCOLOR\n#define PALTEX 0\n#define DINVERT 0", // NormalColor + "#define ENORMALCOLOR\n#define PALTEX 1\n#define INVERT 0", // NormalColorPal + "#define ENORMALCOLOR\n#define PALTEX 0\n#define INVERT 1", // NormalColorInv + "#define ENORMALCOLOR\n#define PALTEX 1\n#define INVERT 1", // NormalColorPalInv + + "#define EREDTOALPHA\n#define INVERT 0", // RedToAlpha + "#define EREDTOALPHA\n#define INVERT 1", // RedToAlphaInv + + "#define EVERTEXCOLOR", // VertexColor + + "#define ESPECIALCOLORMAP\n#define PALTEX 0\n#define INVERT 0", // SpecialColormap + "#define ESPECIALCOLORMAP\n#define PALTEX 1\n#define INVERT 0", // SpecialColorMapPal + + "#define EINGAMECOLORMAP\n#define PALTEX 0\n#define INVERT 0\n#define DESAT 0", // InGameColormap + "#define EINGAMECOLORMAP\n#define PALTEX 0\n#define INVERT 0\n#define DESAT 1", // InGameColormapDesat + "#define EINGAMECOLORMAP\n#define PALTEX 0\n#define INVERT 1\n#define DESAT 0", // InGameColormapInv + "#define EINGAMECOLORMAP\n#define PALTEX 0\n#define INVERT 1\n#define DESAT 1", // InGameColormapInvDesat + "#define EINGAMECOLORMAP\n#define PALTEX 1\n#define INVERT 0\n#define DESAT 0", // InGameColormapPal + "#define EINGAMECOLORMAP\n#define PALTEX 1\n#define INVERT 0\n#define DESAT 1", // InGameColormapPalDesat + "#define EINGAMECOLORMAP\n#define PALTEX 1\n#define INVERT 1\n#define DESAT 0", // InGameColormapPalInv + "#define EINGAMECOLORMAP\n#define PALTEX 1\n#define INVERT 1\n#define DESAT 1", // InGameColormapPalInvDesat + + "#define EBURNWIPE", // BurnWipe + "#define EGAMMACORRECTION", // GammaCorrection +}; + +OpenGLSWFrameBuffer::OpenGLSWFrameBuffer(void *hMonitor, int width, int height, int bits, int refreshHz, bool fullscreen) : + Super(hMonitor, width, height, bits, refreshHz, fullscreen) +{ + // To do: this needs to cooperate with the same static in OpenGLFrameBuffer::InitializeState + static bool first = true; + if (first) + { + ogl_LoadFunctions(); + } + gl_LoadExtensions(); + InitializeState(); + + // SetVSync needs to be at the very top to workaround a bug in Nvidia's OpenGL driver. + // If wglSwapIntervalEXT is called after glBindFramebuffer in a frame the setting is not changed! + Super::SetVSync(vid_vsync); + + Debug = std::make_shared(); + Debug->Update(); + + VertexBuffer = nullptr; + IndexBuffer = nullptr; + FBTexture = nullptr; + InitialWipeScreen = nullptr; + ScreenshotTexture = nullptr; + FinalWipeScreen = nullptr; + PaletteTexture = nullptr; + for (int i = 0; i < NUM_SHADERS; ++i) + { + Shaders[i] = nullptr; + } + VSync = vid_vsync; + BlendingRect.left = 0; + BlendingRect.top = 0; + BlendingRect.right = FBWidth; + BlendingRect.bottom = FBHeight; + In2D = 0; + Palettes = nullptr; + Textures = nullptr; + Accel2D = true; + GatheringWipeScreen = false; + ScreenWipe = nullptr; + InScene = false; + QuadExtra = new BufferedTris[MAX_QUAD_BATCH]; + memset(QuadExtra, 0, sizeof(BufferedTris) * MAX_QUAD_BATCH); + Atlases = nullptr; + PixelDoubling = 0; + + Gamma = 1.0; + FlashColor0 = 0; + FlashColor1 = 0xFFFFFFFF; + FlashColor = 0; + FlashAmount = 0; + + NeedGammaUpdate = false; + NeedPalUpdate = false; + + if (MemBuffer == nullptr) + { + return; + } + + memcpy(SourcePalette, GPalette.BaseColors, sizeof(PalEntry) * 256); + + //Windowed = !(static_cast(Video)->GoFullscreen(fullscreen)); + + TrueHeight = height; + /*if (fullscreen) + { + for (Win32Video::ModeInfo *mode = static_cast(Video)->m_Modes; mode != nullptr; mode = mode->next) + { + if (mode->width == Width && mode->height == Height) + { + TrueHeight = mode->realheight; + PixelDoubling = mode->doubling; + break; + } + } + }*/ + // Offset from top of screen to top of letterboxed screen + LBOffsetI = (TrueHeight - Height) / 2; + LBOffset = float(LBOffsetI); + + CreateResources(); + SetInitialState(); +} + +OpenGLSWFrameBuffer::~OpenGLSWFrameBuffer() +{ + ReleaseResources(); + delete[] QuadExtra; +} + +OpenGLSWFrameBuffer::HWFrameBuffer::~HWFrameBuffer() +{ + if (Framebuffer != 0) glDeleteFramebuffers(1, (GLuint*)&Framebuffer); + delete Texture; +} + +OpenGLSWFrameBuffer::HWTexture::~HWTexture() +{ + if (Texture != 0) glDeleteTextures(1, (GLuint*)&Texture); + if (Buffers[0] != 0) glDeleteBuffers(2, (GLuint*)Buffers); +} + +OpenGLSWFrameBuffer::HWVertexBuffer::~HWVertexBuffer() +{ + if (VertexArray != 0) glDeleteVertexArrays(1, (GLuint*)&VertexArray); + if (Buffer != 0) glDeleteBuffers(1, (GLuint*)&Buffer); +} + +OpenGLSWFrameBuffer::FBVERTEX *OpenGLSWFrameBuffer::HWVertexBuffer::Lock() +{ + glBindBuffer(GL_ARRAY_BUFFER, Buffer); + return (FBVERTEX*)glMapBufferRange(GL_ARRAY_BUFFER, 0, Size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +} + +void OpenGLSWFrameBuffer::HWVertexBuffer::Unlock() +{ + glUnmapBuffer(GL_ARRAY_BUFFER); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +OpenGLSWFrameBuffer::HWIndexBuffer::~HWIndexBuffer() +{ + if (Buffer != 0) glDeleteBuffers(1, (GLuint*)&Buffer); +} + +uint16_t *OpenGLSWFrameBuffer::HWIndexBuffer::Lock() +{ + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &LockedOldBinding); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Buffer); + return (uint16_t*)glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, 0, Size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); +} + +void OpenGLSWFrameBuffer::HWIndexBuffer::Unlock() +{ + glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, LockedOldBinding); +} + +OpenGLSWFrameBuffer::HWPixelShader::~HWPixelShader() +{ + if (Program != 0) glDeleteProgram(Program); + if (VertexShader != 0) glDeleteShader(VertexShader); + if (FragmentShader != 0) glDeleteShader(FragmentShader); +} + +bool OpenGLSWFrameBuffer::CreateFrameBuffer(const FString &name, int width, int height, HWFrameBuffer **outFramebuffer) +{ + auto fb = std::make_unique(); + + if (!CreateTexture(name, width, height, 1, GL_RGBA16F, &fb->Texture)) + { + outFramebuffer = nullptr; + return false; + } + + glGenFramebuffers(1, (GLuint*)&fb->Framebuffer); + + GLint oldFramebufferBinding = 0, oldTextureBinding = 0; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFramebufferBinding); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTextureBinding); + + glBindFramebuffer(GL_FRAMEBUFFER, fb->Framebuffer); + FGLDebug::LabelObject(GL_FRAMEBUFFER, fb->Framebuffer, name); + + glBindTexture(GL_TEXTURE_2D, fb->Texture->Texture); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->Texture->Texture, 0); + + GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + glBindFramebuffer(GL_FRAMEBUFFER, oldFramebufferBinding); + glBindTexture(GL_TEXTURE_2D, oldTextureBinding); + + if (result != GL_FRAMEBUFFER_COMPLETE) + { + outFramebuffer = nullptr; + return false; + } + + *outFramebuffer = fb.release(); + return true; +} + +bool OpenGLSWFrameBuffer::CreatePixelShader(FString vertexsrc, FString fragmentsrc, const FString &defines, HWPixelShader **outShader) +{ + auto shader = std::make_unique(); + + shader->Program = glCreateProgram(); + shader->VertexShader = glCreateShader(GL_VERTEX_SHADER); + shader->FragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + vertexsrc = "#version 130\n" + defines + "\n#line 0\n" + vertexsrc; + fragmentsrc = "#version 130\n" + defines + "\n#line 0\n" + fragmentsrc; + + { + int lengths[1] = { (int)vertexsrc.Len() }; + const char *sources[1] = { vertexsrc.GetChars() }; + glShaderSource(shader->VertexShader, 1, sources, lengths); + glCompileShader(shader->VertexShader); + } + + { + int lengths[1] = { (int)fragmentsrc.Len() }; + const char *sources[1] = { fragmentsrc.GetChars() }; + glShaderSource(shader->FragmentShader, 1, sources, lengths); + glCompileShader(shader->FragmentShader); + } + + GLint status = 0; + int errorShader = shader->VertexShader; + glGetShaderiv(shader->VertexShader, GL_COMPILE_STATUS, &status); + if (status != GL_FALSE) { errorShader = shader->FragmentShader; glGetShaderiv(shader->FragmentShader, GL_COMPILE_STATUS, &status); } + if (status == GL_FALSE) + { + /*static char buffer[10000]; + GLsizei length = 0; + buffer[0] = 0; + glGetShaderInfoLog(errorShader, 10000, &length, buffer);*/ + + *outShader = nullptr; + return false; + } + + glAttachShader(shader->Program, shader->VertexShader); + glAttachShader(shader->Program, shader->FragmentShader); + glBindFragDataLocation(shader->Program, 0, "FragColor"); + glLinkProgram(shader->Program); + glGetProgramiv(shader->Program, GL_LINK_STATUS, &status); + if (status == GL_FALSE) + { + *outShader = nullptr; + return false; + } + glBindAttribLocation(shader->Program, 0, "AttrPosition"); + glBindAttribLocation(shader->Program, 1, "AttrColor0"); + glBindAttribLocation(shader->Program, 2, "AttrColor1"); + glBindAttribLocation(shader->Program, 3, "AttrTexCoord0"); + + shader->ConstantLocations[PSCONST_Desaturation] = glGetUniformLocation(shader->Program, "Desaturation"); + shader->ConstantLocations[PSCONST_PaletteMod] = glGetUniformLocation(shader->Program, "PaletteMod"); + shader->ConstantLocations[PSCONST_Weights] = glGetUniformLocation(shader->Program, "Weights"); + shader->ConstantLocations[PSCONST_Gamma] = glGetUniformLocation(shader->Program, "Gamma"); + shader->ConstantLocations[PSCONST_ScreenSize] = glGetUniformLocation(shader->Program, "ScreenSize"); + shader->ImageLocation = glGetUniformLocation(shader->Program, "Image"); + shader->PaletteLocation = glGetUniformLocation(shader->Program, "Palette"); + shader->NewScreenLocation = glGetUniformLocation(shader->Program, "NewScreen"); + shader->BurnLocation = glGetUniformLocation(shader->Program, "Burn"); + + *outShader = shader.release(); + return true; +} + +bool OpenGLSWFrameBuffer::CreateVertexBuffer(int size, HWVertexBuffer **outVertexBuffer) +{ + auto obj = std::make_unique(); + + obj->Size = size; + + GLint oldBinding = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &oldBinding); + + glGenVertexArrays(1, (GLuint*)&obj->VertexArray); + glGenBuffers(1, (GLuint*)&obj->Buffer); + glBindVertexArray(obj->VertexArray); + glBindBuffer(GL_ARRAY_BUFFER, obj->Buffer); + FGLDebug::LabelObject(GL_BUFFER, obj->Buffer, "VertexBuffer"); + glBufferData(GL_ARRAY_BUFFER, size, nullptr, GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, x)); + glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, color0)); + glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, color1)); + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, tu)); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(oldBinding); + + *outVertexBuffer = obj.release(); + return true; +} + +bool OpenGLSWFrameBuffer::CreateIndexBuffer(int size, HWIndexBuffer **outIndexBuffer) +{ + auto obj = std::make_unique(); + + obj->Size = size; + + GLint oldBinding = 0; + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &oldBinding); + + glGenBuffers(1, (GLuint*)&obj->Buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, obj->Buffer); + FGLDebug::LabelObject(GL_BUFFER, obj->Buffer, "IndexBuffer"); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, nullptr, GL_STREAM_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oldBinding); + + *outIndexBuffer = obj.release(); + return true; +} + +bool OpenGLSWFrameBuffer::CreateTexture(const FString &name, int width, int height, int levels, int format, HWTexture **outTexture) +{ + auto obj = std::make_unique(); + + obj->Format = format; + + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldBinding); + + glGenTextures(1, (GLuint*)&obj->Texture); + glBindTexture(GL_TEXTURE_2D, obj->Texture); + GLenum srcformat; + switch (format) + { + case GL_R8: srcformat = GL_RED; break; + case GL_RGBA8: srcformat = GL_BGRA; break; + case GL_RGBA16F: srcformat = GL_RGBA; break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: srcformat = GL_RGB; break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: srcformat = GL_RGBA; break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: srcformat = GL_RGBA; break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: srcformat = GL_RGBA; break; + default: + I_FatalError("Unknown format passed to CreateTexture"); + return false; + } + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, srcformat, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + FGLDebug::LabelObject(GL_TEXTURE, obj->Texture, name); + + glBindTexture(GL_TEXTURE_2D, oldBinding); + + *outTexture = obj.release(); + return true; +} + +OpenGLSWFrameBuffer::HWTexture *OpenGLSWFrameBuffer::CopyCurrentScreen() +{ + auto obj = std::make_unique(); + obj->Format = GL_RGBA16F; + + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldBinding); + + glGenTextures(1, (GLuint*)&obj->Texture); + glBindTexture(GL_TEXTURE_2D, obj->Texture); + + glCopyTexImage2D(GL_TEXTURE_2D, 0, obj->Format, 0, 0, Width, Height, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + FGLDebug::LabelObject(GL_TEXTURE, obj->Texture, "CopyCurrentScreen"); + + glBindTexture(GL_TEXTURE_2D, oldBinding); + + return obj.release(); +} + +void OpenGLSWFrameBuffer::SetGammaRamp(const GammaRamp *ramp) +{ +} + +void OpenGLSWFrameBuffer::SetPixelShaderConstantF(int uniformIndex, const float *data, int vec4fcount) +{ + assert(uniformIndex < NumPSCONST && vec4fcount == 1); // This emulation of d3d9 only works for very simple stuff + for (int i = 0; i < 4; i++) + ShaderConstants[uniformIndex * 4 + i] = data[i]; + if (CurrentShader && CurrentShader->ConstantLocations[uniformIndex] != -1) + glUniform4fv(CurrentShader->ConstantLocations[uniformIndex], vec4fcount, data); +} + +void OpenGLSWFrameBuffer::SetHWPixelShader(HWPixelShader *shader) +{ + if (shader != CurrentShader) + { + if (shader) + { + glUseProgram(shader->Program); + for (int i = 0; i < NumPSCONST; i++) + { + if (shader->ConstantLocations[i] != -1) + glUniform4fv(shader->ConstantLocations[i], 1, &ShaderConstants[i * 4]); + } + } + else + { + glUseProgram(0); + } + } + CurrentShader = shader; +} + +void OpenGLSWFrameBuffer::SetStreamSource(HWVertexBuffer *vertexBuffer) +{ + if (vertexBuffer) + glBindVertexArray(vertexBuffer->VertexArray); + else + glBindVertexArray(0); +} + +void OpenGLSWFrameBuffer::SetIndices(HWIndexBuffer *indexBuffer) +{ + if (indexBuffer) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer->Buffer); + else + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +void OpenGLSWFrameBuffer::DrawTriangleFans(int count, const FBVERTEX *vertices) +{ + count = 2 + count; + + GLint oldBinding = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &oldBinding); + + if (!StreamVertexBuffer) + { + StreamVertexBuffer = std::make_unique(); + glGenVertexArrays(1, (GLuint*)&StreamVertexBuffer->VertexArray); + glGenBuffers(1, (GLuint*)&StreamVertexBuffer->Buffer); + glBindVertexArray(StreamVertexBuffer->VertexArray); + glBindBuffer(GL_ARRAY_BUFFER, StreamVertexBuffer->Buffer); + glBufferData(GL_ARRAY_BUFFER, count * sizeof(FBVERTEX), vertices, GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, x)); + glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, color0)); + glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, color1)); + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, tu)); + } + else + { + glBindVertexArray(StreamVertexBuffer->VertexArray); + glBindBuffer(GL_ARRAY_BUFFER, StreamVertexBuffer->Buffer); + glBufferData(GL_ARRAY_BUFFER, count * sizeof(FBVERTEX), vertices, GL_STREAM_DRAW); + } + + glDrawArrays(GL_TRIANGLE_FAN, 0, count); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(oldBinding); +} + +void OpenGLSWFrameBuffer::DrawTriangleFans(int count, const BURNVERTEX *vertices) +{ + count = 2 + count; + + GLint oldBinding = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &oldBinding); + + if (!StreamVertexBufferBurn) + { + StreamVertexBufferBurn = std::make_unique(); + glGenVertexArrays(1, (GLuint*)&StreamVertexBufferBurn->VertexArray); + glGenBuffers(1, (GLuint*)&StreamVertexBufferBurn->Buffer); + glBindVertexArray(StreamVertexBufferBurn->VertexArray); + glBindBuffer(GL_ARRAY_BUFFER, StreamVertexBufferBurn->Buffer); + glBufferData(GL_ARRAY_BUFFER, count * sizeof(BURNVERTEX), vertices, GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(BURNVERTEX), (const GLvoid*)offsetof(BURNVERTEX, x)); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(BURNVERTEX), (const GLvoid*)offsetof(BURNVERTEX, tu0)); + } + else + { + glBindVertexArray(StreamVertexBufferBurn->VertexArray); + glBindBuffer(GL_ARRAY_BUFFER, StreamVertexBufferBurn->Buffer); + glBufferData(GL_ARRAY_BUFFER, count * sizeof(BURNVERTEX), vertices, GL_STREAM_DRAW); + } + + glDrawArrays(GL_TRIANGLE_FAN, 0, count); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(oldBinding); +} + +void OpenGLSWFrameBuffer::DrawPoints(int count, const FBVERTEX *vertices) +{ + GLint oldBinding = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &oldBinding); + + if (!StreamVertexBuffer) + { + StreamVertexBuffer = std::make_unique(); + glGenVertexArrays(1, (GLuint*)&StreamVertexBuffer->VertexArray); + glGenBuffers(1, (GLuint*)&StreamVertexBuffer->Buffer); + glBindVertexArray(StreamVertexBuffer->VertexArray); + glBindBuffer(GL_ARRAY_BUFFER, StreamVertexBuffer->Buffer); + glBufferData(GL_ARRAY_BUFFER, count * sizeof(FBVERTEX), vertices, GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, x)); + glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, color0)); + glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, color1)); + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(FBVERTEX), (const GLvoid*)offsetof(FBVERTEX, tu)); + } + else + { + glBindVertexArray(StreamVertexBuffer->VertexArray); + glBindBuffer(GL_ARRAY_BUFFER, StreamVertexBuffer->Buffer); + glBufferData(GL_ARRAY_BUFFER, count * sizeof(FBVERTEX), vertices, GL_STREAM_DRAW); + } + + glDrawArrays(GL_POINTS, 0, count); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(oldBinding); +} + +void OpenGLSWFrameBuffer::DrawLineList(int count) +{ + glDrawArrays(GL_LINES, 0, count * 2); +} + +void OpenGLSWFrameBuffer::DrawTriangleList(int minIndex, int numVertices, int startIndex, int primitiveCount) +{ + glDrawRangeElements(GL_TRIANGLES, minIndex, minIndex + numVertices - 1, primitiveCount * 3, GL_UNSIGNED_SHORT, (const void*)(startIndex * sizeof(uint16_t))); +} + +void OpenGLSWFrameBuffer::Present() +{ + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + FBVERTEX verts[4]; + + CalcFullscreenCoords(verts, false, true, 0, 0xFFFFFFFF); + //for (int i = 0; i < 4; i++) + // verts[i].tv = 1.0f - verts[i].tv; + SetTexture(0, OutputFB->Texture); + SetPixelShader(Shaders[SHADER_GammaCorrection]); + SetAlphaBlend(0); + EnableAlphaTest(false); + DrawTriangleFans(2, verts); + + SwapBuffers(); + Debug->Update(); + + glViewport(0, 0, GetClientWidth(), GetClientHeight()); + + float screensize[4] = { (float)GetClientWidth(), (float)GetClientHeight(), 1.0f, 1.0f }; + SetPixelShaderConstantF(PSCONST_ScreenSize, screensize, 1); + + glBindFramebuffer(GL_FRAMEBUFFER, OutputFB->Framebuffer); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: SetInitialState +// +// Called after initial device creation and reset, when everything is set +// to OpenGL's defaults. +// +//========================================================================== + +void OpenGLSWFrameBuffer::SetInitialState() +{ + AlphaBlendEnabled = false; + AlphaBlendOp = GL_FUNC_ADD; + AlphaSrcBlend = 0; + AlphaDestBlend = 0; + + CurPixelShader = nullptr; + memset(Constant, 0, sizeof(Constant)); + + for (unsigned i = 0; i < countof(Texture); ++i) + { + Texture[i] = nullptr; + SamplerWrapS[i] = GL_CLAMP_TO_EDGE; + SamplerWrapT[i] = GL_CLAMP_TO_EDGE; + } + + NeedGammaUpdate = true; + NeedPalUpdate = true; + + // This constant is used for grayscaling weights (.xyz) and color inversion (.w) + float weights[4] = { 77 / 256.f, 143 / 256.f, 37 / 256.f, 1 }; + SetPixelShaderConstantF(PSCONST_Weights, weights, 1); + + float screensize[4] = { (float)GetClientWidth(), (float)GetClientHeight(), 1.0f, 1.0f }; + SetPixelShaderConstantF(PSCONST_ScreenSize, screensize, 1); + + AlphaTestEnabled = false; + + CurBorderColor = 0; + + // Clear to black, just in case it wasn't done already. + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreateResources +// +//========================================================================== + +bool OpenGLSWFrameBuffer::CreateResources() +{ + Atlases = nullptr; + if (!LoadShaders()) + { + return false; + } + + if (!CreateFrameBuffer("OutputFB", Width, Height, &OutputFB)) + return false; + glBindFramebuffer(GL_FRAMEBUFFER, OutputFB->Framebuffer); + + if (!CreateFBTexture() || + !CreatePaletteTexture()) + { + return false; + } + if (!CreateVertexes()) + { + return false; + } + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: LoadShaders +// +// Returns true if all required shaders were loaded. (Gamma and burn wipe +// are the only ones not considered "required".) +// +//========================================================================== + +bool OpenGLSWFrameBuffer::LoadShaders() +{ + int lumpvert = Wads.CheckNumForFullName("shaders/glsl/swshader.vp"); + int lumpfrag = Wads.CheckNumForFullName("shaders/glsl/swshader.fp"); + if (lumpvert < 0 || lumpfrag < 0) + return false; + + FString vertsource = Wads.ReadLump(lumpvert).GetString(); + FString fragsource = Wads.ReadLump(lumpfrag).GetString(); + + FString shaderdir, shaderpath; + unsigned int i; + + // We determine the best available model simply by trying them all in + // order of decreasing preference. + for (i = 0; i < NUM_SHADERS; ++i) + { + shaderpath = shaderdir; + if (!CreatePixelShader(vertsource, fragsource, ShaderDefines[i], &Shaders[i]) && i < SHADER_BurnWipe) + { + break; + } + + glUseProgram(Shaders[i]->Program); + if (Shaders[i]->ImageLocation != -1) glUniform1i(Shaders[i]->ImageLocation, 0); + if (Shaders[i]->PaletteLocation != -1) glUniform1i(Shaders[i]->PaletteLocation, 1); + if (Shaders[i]->NewScreenLocation != -1) glUniform1i(Shaders[i]->NewScreenLocation, 0); + if (Shaders[i]->BurnLocation != -1) glUniform1i(Shaders[i]->BurnLocation, 1); + glUseProgram(0); + } + if (i == NUM_SHADERS) + { // Success! + return true; + } + // Failure. Release whatever managed to load (which is probably nothing.) + for (i = 0; i < NUM_SHADERS; ++i) + { + SafeRelease(Shaders[i]); + } + return false; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: ReleaseResources +// +//========================================================================== + +void OpenGLSWFrameBuffer::ReleaseResources() +{ + I_SaveWindowedPos(); + KillNativeTexs(); + KillNativePals(); + ReleaseDefaultPoolItems(); + SafeRelease(ScreenshotTexture); + SafeRelease(PaletteTexture); + for (int i = 0; i < NUM_SHADERS; ++i) + { + SafeRelease(Shaders[i]); + } + if (ScreenWipe != nullptr) + { + delete ScreenWipe; + ScreenWipe = nullptr; + } + Atlas *pack, *next; + for (pack = Atlases; pack != nullptr; pack = next) + { + next = pack->Next; + delete pack; + } + GatheringWipeScreen = false; +} + +void OpenGLSWFrameBuffer::ReleaseDefaultPoolItems() +{ + SafeRelease(FBTexture); + SafeRelease(FinalWipeScreen); + SafeRelease(InitialWipeScreen); + SafeRelease(VertexBuffer); + SafeRelease(IndexBuffer); + SafeRelease(OutputFB); +} + +bool OpenGLSWFrameBuffer::Reset() +{ + ReleaseDefaultPoolItems(); + + if (!CreateFrameBuffer("OutputFB", Width, Height, &OutputFB)) + return false; + glBindFramebuffer(GL_FRAMEBUFFER, OutputFB->Framebuffer); + + if (!CreateFBTexture() || !CreateVertexes()) + { + return false; + } + SetInitialState(); + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: KillNativePals +// +// Frees all native palettes. +// +//========================================================================== + +void OpenGLSWFrameBuffer::KillNativePals() +{ + while (Palettes != nullptr) + { + delete Palettes; + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: KillNativeTexs +// +// Frees all native textures. +// +//========================================================================== + +void OpenGLSWFrameBuffer::KillNativeTexs() +{ + while (Textures != nullptr) + { + delete Textures; + } +} + +bool OpenGLSWFrameBuffer::CreateFBTexture() +{ + CreateTexture("FBTexture", Width, Height, 1, GL_R8, &FBTexture); + FBWidth = Width; + FBHeight = Height; + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreatePaletteTexture +// +//========================================================================== + +bool OpenGLSWFrameBuffer::CreatePaletteTexture() +{ + if (!CreateTexture("PaletteTexture", 256, 1, 1, GL_RGBA8, &PaletteTexture)) + { + return false; + } + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreateVertexes +// +//========================================================================== + +bool OpenGLSWFrameBuffer::CreateVertexes() +{ + VertexPos = -1; + IndexPos = -1; + QuadBatchPos = -1; + BatchType = BATCH_None; + if (!CreateVertexBuffer(sizeof(FBVERTEX)*NUM_VERTS, &VertexBuffer)) + { + return false; + } + if (!CreateIndexBuffer(sizeof(uint16_t)*NUM_INDEXES, &IndexBuffer)) + { + return false; + } + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CalcFullscreenCoords +// +//========================================================================== + +void OpenGLSWFrameBuffer::CalcFullscreenCoords(FBVERTEX verts[4], bool viewarea_only, bool can_double, uint32_t color0, uint32_t color1) const +{ + float offset = LBOffset;//OldRenderTarget != nullptr ? 0 : LBOffset; + float top = offset - 0.5f; + float texright = float(Width) / float(FBWidth); + float texbot = float(Height) / float(FBHeight); + float mxl, mxr, myt, myb, tmxl, tmxr, tmyt, tmyb; + + if (viewarea_only) + { // Just calculate vertices for the viewarea/BlendingRect + mxl = float(BlendingRect.left) - 0.5f; + mxr = float(BlendingRect.right) - 0.5f; + myt = float(BlendingRect.top) + top; + myb = float(BlendingRect.bottom) + top; + tmxl = float(BlendingRect.left) / float(Width) * texright; + tmxr = float(BlendingRect.right) / float(Width) * texright; + tmyt = float(BlendingRect.top) / float(Height) * texbot; + tmyb = float(BlendingRect.bottom) / float(Height) * texbot; + } + else + { // Calculate vertices for the whole screen + mxl = -0.5f; + mxr = float(Width << (can_double ? PixelDoubling : 0)) - 0.5f; + myt = top; + myb = float(Height << (can_double ? PixelDoubling : 0)) + top; + tmxl = 0; + tmxr = texright; + tmyt = 0; + tmyb = texbot; + } + + //{ mxl, myt, 0, 1, 0, 0xFFFFFFFF, tmxl, tmyt }, + //{ mxr, myt, 0, 1, 0, 0xFFFFFFFF, tmxr, tmyt }, + //{ mxr, myb, 0, 1, 0, 0xFFFFFFFF, tmxr, tmyb }, + //{ mxl, myb, 0, 1, 0, 0xFFFFFFFF, tmxl, tmyb }, + + verts[0].x = mxl; + verts[0].y = myt; + verts[0].z = 0; + verts[0].rhw = 1; + verts[0].color0 = color0; + verts[0].color1 = color1; + verts[0].tu = tmxl; + verts[0].tv = tmyt; + + verts[1].x = mxr; + verts[1].y = myt; + verts[1].z = 0; + verts[1].rhw = 1; + verts[1].color0 = color0; + verts[1].color1 = color1; + verts[1].tu = tmxr; + verts[1].tv = tmyt; + + verts[2].x = mxr; + verts[2].y = myb; + verts[2].z = 0; + verts[2].rhw = 1; + verts[2].color0 = color0; + verts[2].color1 = color1; + verts[2].tu = tmxr; + verts[2].tv = tmyb; + + verts[3].x = mxl; + verts[3].y = myb; + verts[3].z = 0; + verts[3].rhw = 1; + verts[3].color0 = color0; + verts[3].color1 = color1; + verts[3].tu = tmxl; + verts[3].tv = tmyb; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: GetPageCount +// +//========================================================================== + +int OpenGLSWFrameBuffer::GetPageCount() +{ + return 1; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: PaletteChanged +// +//========================================================================== + +void OpenGLSWFrameBuffer::PaletteChanged() +{ +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: QueryNewPalette +// +//========================================================================== + +int OpenGLSWFrameBuffer::QueryNewPalette() +{ + return 0; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: IsValid +// +//========================================================================== + +bool OpenGLSWFrameBuffer::IsValid() +{ + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: IsFullscreen +// +//========================================================================== + +bool OpenGLSWFrameBuffer::IsFullscreen() +{ + return !Windowed; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Lock +// +//========================================================================== + +bool OpenGLSWFrameBuffer::Lock(bool buffered) +{ + if (LockCount++ > 0) + { + return false; + } + assert(!In2D); + Accel2D = vid_hw2d; + Buffer = MemBuffer; + return false; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Unlock +// +//========================================================================== + +void OpenGLSWFrameBuffer::Unlock() +{ + LOG1("Unlock <%d>\n", LockCount); + + if (LockCount == 0) + { + return; + } + + if (UpdatePending && LockCount == 1) + { + Update(); + } + else if (--LockCount == 0) + { + Buffer = nullptr; + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Update +// +// When In2D == 0: Copy buffer to screen and present +// When In2D == 1: Copy buffer to screen but do not present +// When In2D == 2: Set up for 2D drawing but do not draw anything +// When In2D == 3: Present and set In2D to 0 +// +//========================================================================== + +void OpenGLSWFrameBuffer::Update() +{ + if (In2D == 3) + { + if (InScene) + { + DrawRateStuff(); + DrawPackedTextures(gl_showpacks); + EndBatch(); // Make sure all batched primitives are drawn. + Flip(); + } + In2D = 0; + return; + } + + if (LockCount != 1) + { + I_FatalError("Framebuffer must have exactly 1 lock to be updated"); + if (LockCount > 0) + { + UpdatePending = true; + --LockCount; + } + return; + } + + if (In2D == 0) + { + DrawRateStuff(); + } + + if (NeedGammaUpdate) + { + float psgamma[4]; + float igamma; + + NeedGammaUpdate = false; + igamma = 1 / Gamma; + if (!Windowed) + { + GammaRamp ramp; + + for (int i = 0; i < 256; ++i) + { + ramp.blue[i] = ramp.green[i] = ramp.red[i] = uint16_t(65535.f * powf(i / 255.f, igamma)); + } + LOG("SetGammaRamp\n"); + SetGammaRamp(&ramp); + } + psgamma[2] = psgamma[1] = psgamma[0] = igamma; + psgamma[3] = 0.5; // For SM14 version + SetPixelShaderConstantF(PSCONST_Gamma, psgamma, 1); + } + + if (NeedPalUpdate) + { + UploadPalette(); + NeedPalUpdate = false; + } + + BlitCycles.Reset(); + BlitCycles.Clock(); + + LockCount = 0; + Draw3DPart(In2D <= 1); + if (In2D == 0) + { + Flip(); + } + + BlitCycles.Unclock(); + //LOG1 ("cycles = %d\n", BlitCycles); + + Buffer = nullptr; + UpdatePending = false; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Flip +// +//========================================================================== + +void OpenGLSWFrameBuffer::Flip() +{ + assert(InScene); + + DrawLetterbox(); + + Present(); + InScene = false; + + if (Windowed) + { + int clientWidth = GetClientWidth(); + int clientHeight = GetClientHeight(); + if (clientWidth > 0 && clientHeight > 0 && (Width != clientWidth || Height != clientHeight)) + { + Resize(clientWidth, clientHeight); + + TrueHeight = Height; + PixelDoubling = 0; + LBOffsetI = 0; + LBOffset = 0.0f; + Reset(); + + V_OutputResized(Width, Height); + } + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: PaintToWindow +// +//========================================================================== + +bool OpenGLSWFrameBuffer::PaintToWindow() +{ + if (LockCount != 0) + { + return false; + } + Draw3DPart(true); + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Draw3DPart +// +// The software 3D part, to be exact. +// +//========================================================================== + +void OpenGLSWFrameBuffer::Draw3DPart(bool copy3d) +{ + if (copy3d) + { + if (FBTexture->Buffers[0] == 0) + { + glGenBuffers(2, (GLuint*)FBTexture->Buffers); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, FBTexture->Buffers[0]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, Width * Height, nullptr, GL_STREAM_DRAW); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, FBTexture->Buffers[1]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, Width * Height, nullptr, GL_STREAM_DRAW); + } + else + { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, FBTexture->Buffers[FBTexture->CurrentBuffer]); + FBTexture->CurrentBuffer = (FBTexture->CurrentBuffer + 1) & 1; + } + + uint8_t *dest = (uint8_t*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, Width * Height, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + if (dest) + { + if (Pitch == Width) + { + memcpy(dest, MemBuffer, Width * Height); + } + else + { + uint8_t *src = MemBuffer; + for (int y = 0; y < Height; y++) + { + memcpy(dest, src, Width); + dest += Width; + src += Pitch; + } + } + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldBinding); + glBindTexture(GL_TEXTURE_2D, FBTexture->Texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, Width, Height, GL_RED, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, oldBinding); + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + InScene = true; + if (vid_hwaalines) + glEnable(GL_LINE_SMOOTH); + else + glDisable(GL_LINE_SMOOTH); + + SetTexture(0, FBTexture); + SetPaletteTexture(PaletteTexture, 256, BorderColor); + memset(Constant, 0, sizeof(Constant)); + SetAlphaBlend(0); + EnableAlphaTest(false); + SetPixelShader(Shaders[SHADER_NormalColorPal]); + if (copy3d) + { + FBVERTEX verts[4]; + uint32_t color0, color1; + if (Accel2D) + { + if (realfixedcolormap == nullptr) + { + color0 = 0; + color1 = 0xFFFFFFF; + } + else + { + color0 = ColorValue(realfixedcolormap->ColorizeStart[0] / 2, realfixedcolormap->ColorizeStart[1] / 2, realfixedcolormap->ColorizeStart[2] / 2, 0); + color1 = ColorValue(realfixedcolormap->ColorizeEnd[0] / 2, realfixedcolormap->ColorizeEnd[1] / 2, realfixedcolormap->ColorizeEnd[2] / 2, 1); + SetPixelShader(Shaders[SHADER_SpecialColormapPal]); + } + } + else + { + color0 = FlashColor0; + color1 = FlashColor1; + } + CalcFullscreenCoords(verts, Accel2D, false, color0, color1); + DrawTriangleFans(2, verts); + } + SetPixelShader(Shaders[SHADER_NormalColorPal]); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: DrawLetterbox +// +// Draws the black bars at the top and bottom of the screen for letterboxed +// modes. +// +//========================================================================== + +void OpenGLSWFrameBuffer::DrawLetterbox() +{ + if (LBOffsetI != 0) + { + glEnable(GL_SCISSOR_TEST); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glScissor(0, 0, Width, LBOffsetI); + glClear(GL_COLOR_BUFFER_BIT); + glScissor(0, Height + LBOffsetI, Width, TrueHeight - Height + LBOffsetI); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + } +} + +void OpenGLSWFrameBuffer::UploadPalette() +{ + if (PaletteTexture->Buffers[0] == 0) + { + glGenBuffers(2, (GLuint*)PaletteTexture->Buffers); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, PaletteTexture->Buffers[0]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 4, nullptr, GL_STREAM_DRAW); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, PaletteTexture->Buffers[1]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 4, nullptr, GL_STREAM_DRAW); + } + else + { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, PaletteTexture->Buffers[PaletteTexture->CurrentBuffer]); + PaletteTexture->CurrentBuffer = (PaletteTexture->CurrentBuffer + 1) & 1; + } + + uint8_t *pix = (uint8_t*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + if (pix) + { + int i; + + for (i = 0; i < 256; ++i, pix += 4) + { + pix[0] = SourcePalette[i].b; + pix[1] = SourcePalette[i].g; + pix[2] = SourcePalette[i].r; + pix[3] = (i == 0 ? 0 : 255); + // To let masked textures work, the first palette entry's alpha is 0. + } + pix += 4; + for (; i < 255; ++i, pix += 4) + { + pix[0] = SourcePalette[i].b; + pix[1] = SourcePalette[i].g; + pix[2] = SourcePalette[i].r; + pix[3] = 255; + } + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldBinding); + glBindTexture(GL_TEXTURE_2D, PaletteTexture->Texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 1, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, oldBinding); + BorderColor = ColorXRGB(SourcePalette[255].r, SourcePalette[255].g, SourcePalette[255].b); + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + +PalEntry *OpenGLSWFrameBuffer::GetPalette() +{ + return SourcePalette; +} + +void OpenGLSWFrameBuffer::UpdatePalette() +{ + NeedPalUpdate = true; +} + +bool OpenGLSWFrameBuffer::SetGamma(float gamma) +{ + LOG1("SetGamma %g\n", gamma); + Gamma = gamma; + NeedGammaUpdate = true; + return true; +} + +bool OpenGLSWFrameBuffer::SetFlash(PalEntry rgb, int amount) +{ + FlashColor = rgb; + FlashAmount = amount; + + // Fill in the constants for the pixel shader to do linear interpolation between the palette and the flash: + float r = rgb.r / 255.f, g = rgb.g / 255.f, b = rgb.b / 255.f, a = amount / 256.f; + FlashColor0 = ColorValue(r * a, g * a, b * a, 0); + a = 1 - a; + FlashColor1 = ColorValue(a, a, a, 1); + return true; +} + +void OpenGLSWFrameBuffer::GetFlash(PalEntry &rgb, int &amount) +{ + rgb = FlashColor; + amount = FlashAmount; +} + +void OpenGLSWFrameBuffer::GetFlashedPalette(PalEntry pal[256]) +{ + memcpy(pal, SourcePalette, 256 * sizeof(PalEntry)); + if (FlashAmount) + { + DoBlending(pal, pal, 256, FlashColor.r, FlashColor.g, FlashColor.b, FlashAmount); + } +} + +void OpenGLSWFrameBuffer::SetVSync(bool vsync) +{ + if (VSync != vsync) + { + VSync = vsync; + Reset(); + } + Super::SetVSync(vsync); +} + +void OpenGLSWFrameBuffer::NewRefreshRate() +{ + if (!Windowed) + { + Reset(); + } +} + +void OpenGLSWFrameBuffer::Blank() +{ +} + +void OpenGLSWFrameBuffer::SetBlendingRect(int x1, int y1, int x2, int y2) +{ + BlendingRect.left = x1; + BlendingRect.top = y1; + BlendingRect.right = x2; + BlendingRect.bottom = y2; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: GetScreenshotBuffer +// +// Returns a pointer into a surface holding the current screen data. +// +//========================================================================== + +void OpenGLSWFrameBuffer::GetScreenshotBuffer(const uint8_t *&buffer, int &pitch, ESSType &color_type) +{ + Super::GetScreenshotBuffer(buffer, pitch, color_type); + /* + LockedRect lrect; + + if (!Accel2D) + { + Super::GetScreenshotBuffer(buffer, pitch, color_type); + return; + } + buffer = nullptr; + if ((ScreenshotTexture = GetCurrentScreen()) != nullptr) + { + if (!ScreenshotTexture->GetSurfaceLevel(0, &ScreenshotSurface)) + { + delete ScreenshotTexture; + ScreenshotTexture = nullptr; + } + else if (!ScreenshotSurface->LockRect(&lrect, nullptr, false)) + { + delete ScreenshotSurface; + ScreenshotSurface = nullptr; + delete ScreenshotTexture; + ScreenshotTexture = nullptr; + } + else + { + buffer = (const uint8_t *)lrect.pBits; + pitch = lrect.Pitch; + color_type = SS_BGRA; + } + } + */ +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: ReleaseScreenshotBuffer +// +//========================================================================== + +void OpenGLSWFrameBuffer::ReleaseScreenshotBuffer() +{ + if (LockCount > 0) + { + Super::ReleaseScreenshotBuffer(); + } + SafeRelease(ScreenshotTexture); +} + +/**************************************************************************/ +/* 2D Stuff */ +/**************************************************************************/ + +//========================================================================== +// +// OpenGLSWFrameBuffer :: DrawPackedTextures +// +// DEBUG: Draws the texture atlases to the screen, starting with the +// 1-based packnum. Ignores atlases that are flagged for use by one +// texture only. +// +//========================================================================== + +void OpenGLSWFrameBuffer::DrawPackedTextures(int packnum) +{ + uint32_t empty_colors[8] = + { + 0x50FF0000, 0x5000FF00, 0x500000FF, 0x50FFFF00, + 0x50FF00FF, 0x5000FFFF, 0x50FF8000, 0x500080FF + }; + Atlas *pack; + int x = 8, y = 8; + + if (packnum <= 0) + { + return; + } + pack = Atlases; + // Find the first texture atlas that is an actual atlas. + while (pack != nullptr && pack->OneUse) + { // Skip textures that aren't used as atlases + pack = pack->Next; + } + // Skip however many atlases we would have otherwise drawn + // until we've skipped of them. + while (pack != nullptr && packnum != 1) + { + if (!pack->OneUse) + { // Skip textures that aren't used as atlases + packnum--; + } + pack = pack->Next; + } + // Draw atlases until we run out of room on the screen. + while (pack != nullptr) + { + if (pack->OneUse) + { // Skip textures that aren't used as atlases + pack = pack->Next; + continue; + } + + AddColorOnlyRect(x - 1, y - 1 - LBOffsetI, 258, 258, ColorXRGB(255, 255, 0)); + int back = 0; + for (PackedTexture *box = pack->UsedList; box != nullptr; box = box->Next) + { + AddColorOnlyQuad( + x + box->Area.left * 256 / pack->Width, + y + box->Area.top * 256 / pack->Height, + (box->Area.right - box->Area.left) * 256 / pack->Width, + (box->Area.bottom - box->Area.top) * 256 / pack->Height, empty_colors[back]); + back = (back + 1) & 7; + } + // AddColorOnlyQuad(x, y-LBOffsetI, 256, 256, ColorARGB(180,0,0,0)); + + CheckQuadBatch(); + + BufferedTris *quad = &QuadExtra[QuadBatchPos]; + FBVERTEX *vert = &VertexData[VertexPos]; + + quad->ClearSetup(); + if (pack->Format == GL_R8/* && !tex->IsGray*/) + { + quad->Flags = BQF_WrapUV | BQF_GamePalette/* | BQF_DisableAlphaTest*/; + quad->ShaderNum = BQS_PalTex; + } + else + { + quad->Flags = BQF_WrapUV/* | BQF_DisableAlphaTest*/; + quad->ShaderNum = BQS_Plain; + } + quad->Palette = nullptr; + quad->Texture = pack->Tex; + quad->NumVerts = 4; + quad->NumTris = 2; + + float x0 = float(x) - 0.5f; + float y0 = float(y) - 0.5f; + float x1 = x0 + 256.f; + float y1 = y0 + 256.f; + + vert[0].x = x0; + vert[0].y = y0; + vert[0].z = 0; + vert[0].rhw = 1; + vert[0].color0 = 0; + vert[0].color1 = 0xFFFFFFFF; + vert[0].tu = 0; + vert[0].tv = 0; + + vert[1].x = x1; + vert[1].y = y0; + vert[1].z = 0; + vert[1].rhw = 1; + vert[1].color0 = 0; + vert[1].color1 = 0xFFFFFFFF; + vert[1].tu = 1; + vert[1].tv = 0; + + vert[2].x = x1; + vert[2].y = y1; + vert[2].z = 0; + vert[2].rhw = 1; + vert[2].color0 = 0; + vert[2].color1 = 0xFFFFFFFF; + vert[2].tu = 1; + vert[2].tv = 1; + + vert[3].x = x0; + vert[3].y = y1; + vert[3].z = 0; + vert[3].rhw = 1; + vert[3].color0 = 0; + vert[3].color1 = 0xFFFFFFFF; + vert[3].tu = 0; + vert[3].tv = 1; + + IndexData[IndexPos] = VertexPos; + IndexData[IndexPos + 1] = VertexPos + 1; + IndexData[IndexPos + 2] = VertexPos + 2; + IndexData[IndexPos + 3] = VertexPos; + IndexData[IndexPos + 4] = VertexPos + 2; + IndexData[IndexPos + 5] = VertexPos + 3; + + QuadBatchPos++; + VertexPos += 4; + IndexPos += 6; + + x += 256 + 8; + if (x > Width - 256) + { + x = 8; + y += 256 + 8; + if (y > TrueHeight - 256) + { + return; + } + } + pack = pack->Next; + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: AllocPackedTexture +// +// Finds space to pack an image inside a texture atlas and returns it. +// Large images and those that need to wrap always get their own textures. +// +//========================================================================== + +OpenGLSWFrameBuffer::PackedTexture *OpenGLSWFrameBuffer::AllocPackedTexture(int w, int h, bool wrapping, int format) +{ + Atlas *pack; + Rect box; + bool padded; + + // The - 2 to account for padding + if (w > 256 - 2 || h > 256 - 2 || wrapping) + { // Create a new texture atlas. + pack = new Atlas(this, w, h, format); + pack->OneUse = true; + box = pack->Packer.Insert(w, h); + padded = false; + } + else + { // Try to find space in an existing texture atlas. + w += 2; // Add padding + h += 2; + for (pack = Atlases; pack != nullptr; pack = pack->Next) + { + // Use the first atlas it fits in. + if (pack->Format == format) + { + box = pack->Packer.Insert(w, h); + if (box.width != 0) + { + break; + } + } + } + if (pack == nullptr) + { // Create a new texture atlas. + pack = new Atlas(this, DEF_ATLAS_WIDTH, DEF_ATLAS_HEIGHT, format); + box = pack->Packer.Insert(w, h); + } + padded = true; + } + assert(box.width != 0 && box.height != 0); + return pack->AllocateImage(box, padded); +} + +//========================================================================== +// +// Atlas Constructor +// +//========================================================================== + +OpenGLSWFrameBuffer::Atlas::Atlas(OpenGLSWFrameBuffer *fb, int w, int h, int format) + : Packer(w, h, true) +{ + Tex = nullptr; + Format = format; + UsedList = nullptr; + OneUse = false; + Width = 0; + Height = 0; + Next = nullptr; + + // Attach to the end of the atlas list + Atlas **prev = &fb->Atlases; + while (*prev != nullptr) + { + prev = &((*prev)->Next); + } + *prev = this; + + fb->CreateTexture("Atlas", w, h, 1, format, &Tex); + Width = w; + Height = h; +} + +//========================================================================== +// +// Atlas Destructor +// +//========================================================================== + +OpenGLSWFrameBuffer::Atlas::~Atlas() +{ + PackedTexture *box, *next; + + SafeRelease(Tex); + for (box = UsedList; box != nullptr; box = next) + { + next = box->Next; + delete box; + } +} + +//========================================================================== +// +// Atlas :: AllocateImage +// +// Moves the box from the empty list to the used list, sizing it to the +// requested dimensions and adding additional boxes to the empty list if +// needed. +// +// The passed box *MUST* be in this texture atlas's empty list. +// +//========================================================================== + +OpenGLSWFrameBuffer::PackedTexture *OpenGLSWFrameBuffer::Atlas::AllocateImage(const Rect &rect, bool padded) +{ + PackedTexture *box = new PackedTexture; + + box->Owner = this; + box->Area.left = rect.x; + box->Area.top = rect.y; + box->Area.right = rect.x + rect.width; + box->Area.bottom = rect.y + rect.height; + + box->Left = float(box->Area.left + padded) / Width; + box->Right = float(box->Area.right - padded) / Width; + box->Top = float(box->Area.top + padded) / Height; + box->Bottom = float(box->Area.bottom - padded) / Height; + + box->Padded = padded; + + // Add it to the used list. + box->Next = UsedList; + if (box->Next != nullptr) + { + box->Next->Prev = &box->Next; + } + UsedList = box; + box->Prev = &UsedList; + + return box; +} + +//========================================================================== +// +// Atlas :: FreeBox +// +// Removes a box from the used list and deletes it. Space is returned to the +// waste list. Once all boxes for this atlas are freed, the entire bin +// packer is reinitialized for maximum efficiency. +// +//========================================================================== + +void OpenGLSWFrameBuffer::Atlas::FreeBox(OpenGLSWFrameBuffer::PackedTexture *box) +{ + *(box->Prev) = box->Next; + if (box->Next != nullptr) + { + box->Next->Prev = box->Prev; + } + Rect waste; + waste.x = box->Area.left; + waste.y = box->Area.top; + waste.width = box->Area.right - box->Area.left; + waste.height = box->Area.bottom - box->Area.top; + box->Owner->Packer.AddWaste(waste); + delete box; + if (UsedList == nullptr) + { + Packer.Init(Width, Height, true); + } +} + +//========================================================================== +// +// OpenGLTex Constructor +// +//========================================================================== + +OpenGLSWFrameBuffer::OpenGLTex::OpenGLTex(FTexture *tex, OpenGLSWFrameBuffer *fb, bool wrapping) +{ + // Attach to the texture list for the OpenGLSWFrameBuffer + Next = fb->Textures; + if (Next != nullptr) + { + Next->Prev = &Next; + } + Prev = &fb->Textures; + fb->Textures = this; + + GameTex = tex; + Box = nullptr; + IsGray = false; + + Create(fb, wrapping); +} + +//========================================================================== +// +// OpenGLTex Destructor +// +//========================================================================== + +OpenGLSWFrameBuffer::OpenGLTex::~OpenGLTex() +{ + if (Box != nullptr) + { + Box->Owner->FreeBox(Box); + Box = nullptr; + } + // Detach from the texture list + *Prev = Next; + if (Next != nullptr) + { + Next->Prev = Prev; + } + // Remove link from the game texture + if (GameTex != nullptr) + { + GameTex->Native = nullptr; + } +} + +//========================================================================== +// +// OpenGLTex :: CheckWrapping +// +// Returns true if the texture is compatible with the specified wrapping +// mode. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::OpenGLTex::CheckWrapping(bool wrapping) +{ + // If it doesn't need to wrap, then it works. + if (!wrapping) + { + return true; + } + // If it needs to wrap, then it can't be packed inside another texture. + return Box->Owner->OneUse; +} + +//========================================================================== +// +// OpenGLTex :: Create +// +// Creates an HWTexture for the texture and copies the image data +// to it. Note that unlike FTexture, this image is row-major. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::OpenGLTex::Create(OpenGLSWFrameBuffer *fb, bool wrapping) +{ + assert(Box == nullptr); + if (Box != nullptr) + { + Box->Owner->FreeBox(Box); + } + + Box = fb->AllocPackedTexture(GameTex->GetWidth(), GameTex->GetHeight(), wrapping, GetTexFormat()); + + if (Box == nullptr) + { + return false; + } + if (!Update()) + { + Box->Owner->FreeBox(Box); + Box = nullptr; + return false; + } + return true; +} + +//========================================================================== +// +// OpenGLTex :: Update +// +// Copies image data from the underlying FTexture to the OpenGL texture. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::OpenGLTex::Update() +{ + LTRBRect rect; + uint8_t *dest; + + assert(Box != nullptr); + assert(Box->Owner != nullptr); + assert(Box->Owner->Tex != nullptr); + assert(GameTex != nullptr); + + int format = Box->Owner->Tex->Format; + + rect = Box->Area; + + if (Box->Owner->Tex->Buffers[0] == 0) + glGenBuffers(2, (GLuint*)Box->Owner->Tex->Buffers); + + int bytesPerPixel = 4; + switch (format) + { + case GL_R8: bytesPerPixel = 1; break; + case GL_RGBA8: bytesPerPixel = 4; break; + default: return false; + } + + int buffersize = (rect.right - rect.left) * (rect.bottom - rect.top) * bytesPerPixel; + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, Box->Owner->Tex->Buffers[Box->Owner->Tex->CurrentBuffer]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, buffersize, nullptr, GL_STREAM_DRAW); + Box->Owner->Tex->CurrentBuffer = (Box->Owner->Tex->CurrentBuffer + 1) & 1; + + int pitch = (rect.right - rect.left) * bytesPerPixel; + uint8_t *bits = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, buffersize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + dest = bits; + if (!dest) + { + return false; + } + if (Box->Padded) + { + dest += pitch + (format == GL_R8 ? 1 : 4); + } + GameTex->FillBuffer(dest, pitch, GameTex->GetHeight(), ToTexFmt(format)); + if (Box->Padded) + { + // Clear top padding row. + dest = bits; + int numbytes = GameTex->GetWidth() + 2; + if (format != GL_R8) + { + numbytes <<= 2; + } + memset(dest, 0, numbytes); + dest += pitch; + // Clear left and right padding columns. + if (format == GL_R8) + { + for (int y = Box->Area.bottom - Box->Area.top - 2; y > 0; --y) + { + dest[0] = 0; + dest[numbytes - 1] = 0; + dest += pitch; + } + } + else + { + for (int y = Box->Area.bottom - Box->Area.top - 2; y > 0; --y) + { + *(uint32_t *)dest = 0; + *(uint32_t *)(dest + numbytes - 4) = 0; + dest += pitch; + } + } + // Clear bottom padding row. + memset(dest, 0, numbytes); + } + + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldBinding); + glBindTexture(GL_TEXTURE_2D, Box->Owner->Tex->Texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, format == GL_RGBA8 ? GL_BGRA : GL_RED, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, oldBinding); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + return true; +} + +//========================================================================== +// +// OpenGLTex :: GetTexFormat +// +// Returns the texture format that would best fit this texture. +// +//========================================================================== + +int OpenGLSWFrameBuffer::OpenGLTex::GetTexFormat() +{ + FTextureFormat fmt = GameTex->GetFormat(); + + IsGray = false; + + switch (fmt) + { + case TEX_Pal: return GL_R8; + case TEX_Gray: IsGray = true; return GL_R8; + case TEX_RGB: return GL_RGBA8; + case TEX_DXT1: return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + case TEX_DXT2: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + case TEX_DXT3: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + case TEX_DXT4: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; // Doesn't exist in OpenGL. Closest match is DXT5. + case TEX_DXT5: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + default: I_FatalError("GameTex->GetFormat() returned invalid format."); + } + return GL_R8; +} + +//========================================================================== +// +// OpenGLTex :: ToTexFmt +// +// Converts an OpenGL internal format constant to something the FTexture system +// understands. +// +//========================================================================== + +FTextureFormat OpenGLSWFrameBuffer::OpenGLTex::ToTexFmt(int fmt) +{ + switch (fmt) + { + case GL_R8: return IsGray ? TEX_Gray : TEX_Pal; + case GL_RGBA8: return TEX_RGB; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: return TEX_DXT1; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return TEX_DXT2; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return TEX_DXT3; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return TEX_DXT5; + default: + assert(0); // LOL WUT? + return TEX_Pal; + } +} + +//========================================================================== +// +// OpenGLPal Constructor +// +//========================================================================== + +OpenGLSWFrameBuffer::OpenGLPal::OpenGLPal(FRemapTable *remap, OpenGLSWFrameBuffer *fb) + : Tex(nullptr), Remap(remap) +{ + int count; + + // Attach to the palette list for the OpenGLSWFrameBuffer + Next = fb->Palettes; + if (Next != nullptr) + { + Next->Prev = &Next; + } + Prev = &fb->Palettes; + fb->Palettes = this; + + int pow2count; + + // Round up to the nearest power of 2. + for (pow2count = 1; pow2count < remap->NumEntries; pow2count <<= 1) + { + } + count = pow2count; + DoColorSkip = false; + + BorderColor = 0; + RoundedPaletteSize = count; + if (fb->CreateTexture("Pal", count, 1, 1, GL_RGBA8, &Tex)) + { + if (!Update()) + { + delete Tex; + Tex = nullptr; + } + } +} + +//========================================================================== +// +// OpenGLPal Destructor +// +//========================================================================== + +OpenGLSWFrameBuffer::OpenGLPal::~OpenGLPal() +{ + SafeRelease(Tex); + // Detach from the palette list + *Prev = Next; + if (Next != nullptr) + { + Next->Prev = Prev; + } + // Remove link from the remap table + if (Remap != nullptr) + { + Remap->Native = nullptr; + } +} + +//========================================================================== +// +// OpenGLPal :: Update +// +// Copies the palette to the texture. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::OpenGLPal::Update() +{ + uint32_t *buff; + const PalEntry *pal; + int skipat, i; + + assert(Tex != nullptr); + + if (Tex->Buffers[0] == 0) + { + glGenBuffers(2, (GLuint*)Tex->Buffers); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, Tex->Buffers[0]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, Remap->NumEntries * 4, nullptr, GL_STREAM_DRAW); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, Tex->Buffers[1]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, Remap->NumEntries * 4, nullptr, GL_STREAM_DRAW); + } + else + { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, Tex->Buffers[Tex->CurrentBuffer]); + Tex->CurrentBuffer = (Tex->CurrentBuffer + 1) & 1; + } + + buff = (uint32_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, Remap->NumEntries * 4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + if (buff == nullptr) + { + return false; + } + pal = Remap->Palette; + + // See explanation in UploadPalette() for skipat rationale. + skipat = MIN(Remap->NumEntries, DoColorSkip ? 256 - 8 : 256); + + for (i = 0; i < skipat; ++i) + { + buff[i] = ColorARGB(pal[i].a, pal[i].r, pal[i].g, pal[i].b); + } + for (++i; i < Remap->NumEntries; ++i) + { + buff[i] = ColorARGB(pal[i].a, pal[i - 1].r, pal[i - 1].g, pal[i - 1].b); + } + BorderColor = ColorARGB(pal[i].a, pal[i - 1].r, pal[i - 1].g, pal[i - 1].b); + + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldBinding); + glBindTexture(GL_TEXTURE_2D, Tex->Texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, Remap->NumEntries, 1, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, oldBinding); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Begin2D +// +// Begins 2D mode drawing operations. In particular, DrawTexture is +// rerouted to use Direct3D instead of the software renderer. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::Begin2D(bool copy3d) +{ + if (!Accel2D) + { + return false; + } + if (In2D) + { + return true; + } + In2D = 2 - copy3d; + Update(); + In2D = 3; + + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: DrawBlendingRect +// +// Call after Begin2D to blend the 3D view. +// +//========================================================================== + +void OpenGLSWFrameBuffer::DrawBlendingRect() +{ + if (!In2D || !Accel2D) + { + return; + } + Dim(FlashColor, FlashAmount / 256.f, viewwindowx, viewwindowy, viewwidth, viewheight); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreateTexture +// +// Returns a native texture that wraps a FTexture. +// +//========================================================================== + +FNativeTexture *OpenGLSWFrameBuffer::CreateTexture(FTexture *gametex, bool wrapping) +{ + OpenGLTex *tex = new OpenGLTex(gametex, this, wrapping); + if (tex->Box == nullptr) + { + delete tex; + return nullptr; + } + return tex; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreatePalette +// +// Returns a native texture that contains a palette. +// +//========================================================================== + +FNativePalette *OpenGLSWFrameBuffer::CreatePalette(FRemapTable *remap) +{ + OpenGLPal *tex = new OpenGLPal(remap, this); + if (tex->Tex == nullptr) + { + delete tex; + return nullptr; + } + return tex; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Clear +// +// Fills the specified region with a color. +// +//========================================================================== + +void OpenGLSWFrameBuffer::Clear(int left, int top, int right, int bottom, int palcolor, uint32 color) +{ + if (In2D < 2) + { + Super::Clear(left, top, right, bottom, palcolor, color); + return; + } + if (!InScene) + { + return; + } + if (palcolor >= 0 && color == 0) + { + color = GPalette.BaseColors[palcolor]; + } + else if (APART(color) < 255) + { + Dim(color, APART(color) / 255.f, left, top, right - left, bottom - top); + return; + } + AddColorOnlyQuad(left, top, right - left, bottom - top, color | 0xFF000000); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Dim +// +//========================================================================== + +void OpenGLSWFrameBuffer::Dim(PalEntry color, float amount, int x1, int y1, int w, int h) +{ + if (amount <= 0) + { + return; + } + if (In2D < 2) + { + Super::Dim(color, amount, x1, y1, w, h); + return; + } + if (!InScene) + { + return; + } + if (amount > 1) + { + amount = 1; + } + AddColorOnlyQuad(x1, y1, w, h, color | (int(amount * 255) << 24)); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: BeginLineBatch +// +//========================================================================== + +void OpenGLSWFrameBuffer::BeginLineBatch() +{ + if (In2D < 2 || !InScene || BatchType == BATCH_Lines) + { + return; + } + EndQuadBatch(); // Make sure all quads have been drawn first. + VertexData = VertexBuffer->Lock(); + VertexPos = 0; + BatchType = BATCH_Lines; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: EndLineBatch +// +//========================================================================== + +void OpenGLSWFrameBuffer::EndLineBatch() +{ + if (In2D < 2 || !InScene || BatchType != BATCH_Lines) + { + return; + } + VertexBuffer->Unlock(); + if (VertexPos > 0) + { + SetPixelShader(Shaders[SHADER_VertexColor]); + SetAlphaBlend(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + SetStreamSource(VertexBuffer); + DrawLineList(VertexPos / 2); + } + VertexPos = -1; + BatchType = BATCH_None; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: DrawLine +// +//========================================================================== + +void OpenGLSWFrameBuffer::DrawLine(int x0, int y0, int x1, int y1, int palcolor, uint32 color) +{ + if (In2D < 2) + { + Super::DrawLine(x0, y0, x1, y1, palcolor, color); + return; + } + if (!InScene) + { + return; + } + if (BatchType != BATCH_Lines) + { + BeginLineBatch(); + } + if (VertexPos == NUM_VERTS) + { // Flush the buffer and refill it. + EndLineBatch(); + BeginLineBatch(); + } + // Add the endpoints to the vertex buffer. + VertexData[VertexPos].x = float(x0); + VertexData[VertexPos].y = float(y0) + LBOffset; + VertexData[VertexPos].z = 0; + VertexData[VertexPos].rhw = 1; + VertexData[VertexPos].color0 = color; + VertexData[VertexPos].color1 = 0; + VertexData[VertexPos].tu = 0; + VertexData[VertexPos].tv = 0; + + VertexData[VertexPos + 1].x = float(x1); + VertexData[VertexPos + 1].y = float(y1) + LBOffset; + VertexData[VertexPos + 1].z = 0; + VertexData[VertexPos + 1].rhw = 1; + VertexData[VertexPos + 1].color0 = color; + VertexData[VertexPos + 1].color1 = 0; + VertexData[VertexPos + 1].tu = 0; + VertexData[VertexPos + 1].tv = 0; + + VertexPos += 2; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: DrawPixel +// +//========================================================================== + +void OpenGLSWFrameBuffer::DrawPixel(int x, int y, int palcolor, uint32 color) +{ + if (In2D < 2) + { + Super::DrawPixel(x, y, palcolor, color); + return; + } + if (!InScene) + { + return; + } + FBVERTEX pt = + { + float(x), float(y), 0, 1, color + }; + EndBatch(); // Draw out any batched operations. + SetPixelShader(Shaders[SHADER_VertexColor]); + SetAlphaBlend(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + DrawPoints(1, &pt); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: DrawTextureV +// +// If not in 2D mode, just call the normal software version. +// If in 2D mode, then use Direct3D calls to perform the drawing. +// +//========================================================================== + +void OpenGLSWFrameBuffer::DrawTextureParms(FTexture *img, DrawParms &parms) +{ + if (In2D < 2) + { + Super::DrawTextureParms(img, parms); + return; + } + if (!InScene) + { + return; + } + + OpenGLTex *tex = static_cast(img->GetNative(false)); + + if (tex == nullptr) + { + assert(tex != nullptr); + return; + } + + CheckQuadBatch(); + + double xscale = parms.destwidth / parms.texwidth; + double yscale = parms.destheight / parms.texheight; + double x0 = parms.x - parms.left * xscale; + double y0 = parms.y - parms.top * yscale; + double x1 = x0 + parms.destwidth; + double y1 = y0 + parms.destheight; + float u0 = tex->Box->Left; + float v0 = tex->Box->Top; + float u1 = tex->Box->Right; + float v1 = tex->Box->Bottom; + double uscale = 1.f / tex->Box->Owner->Width; + bool scissoring = false; + FBVERTEX *vert; + float yoffs; + + if (parms.flipX) + { + swapvalues(u0, u1); + } + if (parms.windowleft > 0 || parms.windowright < parms.texwidth) + { + double wi = MIN(parms.windowright, parms.texwidth); + x0 += parms.windowleft * xscale; + u0 = float(u0 + parms.windowleft * uscale); + x1 -= (parms.texwidth - wi) * xscale; + u1 = float(u1 - (parms.texwidth - wi) * uscale); + } + +#if 0 + float vscale = 1.f / tex->Box->Owner->Height / yscale; + if (y0 < parms.uclip) + { + v0 += (float(parms.uclip) - y0) * vscale; + y0 = float(parms.uclip); + } + if (y1 > parms.dclip) + { + v1 -= (y1 - float(parms.dclip)) * vscale; + y1 = float(parms.dclip); + } + if (x0 < parms.lclip) + { + u0 += float(parms.lclip - x0) * uscale / xscale * 2; + x0 = float(parms.lclip); + } + if (x1 > parms.rclip) + { + u1 -= (x1 - parms.rclip) * uscale / xscale * 2; + x1 = float(parms.rclip); + } +#else + // Use a scissor test because the math above introduces some jitter + // that is noticeable at low resolutions. Unfortunately, this means this + // quad has to be in a batch by itself. + if (y0 < parms.uclip || y1 > parms.dclip || x0 < parms.lclip || x1 > parms.rclip) + { + scissoring = true; + if (QuadBatchPos > 0) + { + EndQuadBatch(); + BeginQuadBatch(); + } + glEnable(GL_SCISSOR_TEST); + glScissor(parms.lclip, parms.uclip + LBOffsetI, parms.rclip - parms.lclip, parms.dclip - parms.uclip); + } +#endif + parms.bilinear = false; + + uint32_t color0, color1; + BufferedTris *quad = &QuadExtra[QuadBatchPos]; + + if (!SetStyle(tex, parms, color0, color1, *quad)) + { + goto done; + } + + quad->Texture = tex->Box->Owner->Tex; + if (parms.bilinear) + { + quad->Flags |= BQF_Bilinear; + } + quad->NumTris = 2; + quad->NumVerts = 4; + + yoffs = GatheringWipeScreen ? 0.5f : 0.5f - LBOffset; + +#if 0 + // Coordinates are truncated to integers, because that's effectively + // what the software renderer does. The hardware will instead round + // to nearest, it seems. + x0 = floorf(x0) - 0.5f; + y0 = floorf(y0) - yoffs; + x1 = floorf(x1) - 0.5f; + y1 = floorf(y1) - yoffs; +#else + x0 = x0 - 0.5f; + y0 = y0 - yoffs; + x1 = x1 - 0.5f; + y1 = y1 - yoffs; +#endif + + vert = &VertexData[VertexPos]; + + // Fill the vertex buffer. + vert[0].x = float(x0); + vert[0].y = float(y0); + vert[0].z = 0; + vert[0].rhw = 1; + vert[0].color0 = color0; + vert[0].color1 = color1; + vert[0].tu = u0; + vert[0].tv = v0; + + vert[1].x = float(x1); + vert[1].y = float(y0); + vert[1].z = 0; + vert[1].rhw = 1; + vert[1].color0 = color0; + vert[1].color1 = color1; + vert[1].tu = u1; + vert[1].tv = v0; + + vert[2].x = float(x1); + vert[2].y = float(y1); + vert[2].z = 0; + vert[2].rhw = 1; + vert[2].color0 = color0; + vert[2].color1 = color1; + vert[2].tu = u1; + vert[2].tv = v1; + + vert[3].x = float(x0); + vert[3].y = float(y1); + vert[3].z = 0; + vert[3].rhw = 1; + vert[3].color0 = color0; + vert[3].color1 = color1; + vert[3].tu = u0; + vert[3].tv = v1; + + // Fill the vertex index buffer. + IndexData[IndexPos] = VertexPos; + IndexData[IndexPos + 1] = VertexPos + 1; + IndexData[IndexPos + 2] = VertexPos + 2; + IndexData[IndexPos + 3] = VertexPos; + IndexData[IndexPos + 4] = VertexPos + 2; + IndexData[IndexPos + 5] = VertexPos + 3; + + // Batch the quad. + QuadBatchPos++; + VertexPos += 4; + IndexPos += 6; +done: + if (scissoring) + { + EndQuadBatch(); + glDisable(GL_SCISSOR_TEST); + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: FlatFill +// +// Fills an area with a repeating copy of the texture. +// +//========================================================================== + +void OpenGLSWFrameBuffer::FlatFill(int left, int top, int right, int bottom, FTexture *src, bool local_origin) +{ + if (In2D < 2) + { + Super::FlatFill(left, top, right, bottom, src, local_origin); + return; + } + if (!InScene) + { + return; + } + OpenGLTex *tex = static_cast(src->GetNative(true)); + if (tex == nullptr) + { + return; + } + float yoffs = GatheringWipeScreen ? 0.5f : 0.5f - LBOffset; + float x0 = float(left); + float y0 = float(top); + float x1 = float(right); + float y1 = float(bottom); + float itw = 1.f / float(src->GetWidth()); + float ith = 1.f / float(src->GetHeight()); + float xo = local_origin ? x0 : 0; + float yo = local_origin ? y0 : 0; + float u0 = (x0 - xo) * itw; + float v0 = (y0 - yo) * ith; + float u1 = (x1 - xo) * itw; + float v1 = (y1 - yo) * ith; + x0 -= 0.5f; + y0 -= yoffs; + x1 -= 0.5f; + y1 -= yoffs; + + CheckQuadBatch(); + + BufferedTris *quad = &QuadExtra[QuadBatchPos]; + FBVERTEX *vert = &VertexData[VertexPos]; + + quad->ClearSetup(); + if (tex->GetTexFormat() == GL_R8 && !tex->IsGray) + { + quad->Flags = BQF_WrapUV | BQF_GamePalette; // | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_PalTex; + } + else + { + quad->Flags = BQF_WrapUV; // | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_Plain; + } + quad->Palette = nullptr; + quad->Texture = tex->Box->Owner->Tex; + quad->NumVerts = 4; + quad->NumTris = 2; + + vert[0].x = x0; + vert[0].y = y0; + vert[0].z = 0; + vert[0].rhw = 1; + vert[0].color0 = 0; + vert[0].color1 = 0xFFFFFFFF; + vert[0].tu = u0; + vert[0].tv = v0; + + vert[1].x = x1; + vert[1].y = y0; + vert[1].z = 0; + vert[1].rhw = 1; + vert[1].color0 = 0; + vert[1].color1 = 0xFFFFFFFF; + vert[1].tu = u1; + vert[1].tv = v0; + + vert[2].x = x1; + vert[2].y = y1; + vert[2].z = 0; + vert[2].rhw = 1; + vert[2].color0 = 0; + vert[2].color1 = 0xFFFFFFFF; + vert[2].tu = u1; + vert[2].tv = v1; + + vert[3].x = x0; + vert[3].y = y1; + vert[3].z = 0; + vert[3].rhw = 1; + vert[3].color0 = 0; + vert[3].color1 = 0xFFFFFFFF; + vert[3].tu = u0; + vert[3].tv = v1; + + IndexData[IndexPos] = VertexPos; + IndexData[IndexPos + 1] = VertexPos + 1; + IndexData[IndexPos + 2] = VertexPos + 2; + IndexData[IndexPos + 3] = VertexPos; + IndexData[IndexPos + 4] = VertexPos + 2; + IndexData[IndexPos + 5] = VertexPos + 3; + + QuadBatchPos++; + VertexPos += 4; + IndexPos += 6; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: FillSimplePoly +// +// Here, "simple" means that a simple triangle fan can draw it. +// +//========================================================================== + +void OpenGLSWFrameBuffer::FillSimplePoly(FTexture *texture, FVector2 *points, int npoints, + double originx, double originy, double scalex, double scaley, + DAngle rotation, FDynamicColormap *colormap, int lightlevel) +{ + // Use an equation similar to player sprites to determine shade + double fadelevel = clamp((LIGHT2SHADE(lightlevel) / 65536. - 12) / NUMCOLORMAPS, 0.0, 1.0); + + BufferedTris *quad; + FBVERTEX *verts; + OpenGLTex *tex; + float yoffs, uscale, vscale; + int i, ipos; + uint32_t color0, color1; + float ox, oy; + float cosrot, sinrot; + bool dorotate = rotation != 0; + + if (npoints < 3) + { // This is no polygon. + return; + } + if (In2D < 2) + { + Super::FillSimplePoly(texture, points, npoints, originx, originy, scalex, scaley, rotation, colormap, lightlevel); + return; + } + if (!InScene) + { + return; + } + tex = static_cast(texture->GetNative(true)); + if (tex == nullptr) + { + return; + } + + cosrot = (float)cos(rotation.Radians()); + sinrot = (float)sin(rotation.Radians()); + + CheckQuadBatch(npoints - 2, npoints); + quad = &QuadExtra[QuadBatchPos]; + verts = &VertexData[VertexPos]; + + color0 = 0; + color1 = 0xFFFFFFFF; + + quad->ClearSetup(); + if (tex->GetTexFormat() == GL_R8 && !tex->IsGray) + { + quad->Flags = BQF_WrapUV | BQF_GamePalette | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_PalTex; + if (colormap != nullptr) + { + if (colormap->Desaturate != 0) + { + quad->Flags |= BQF_Desaturated; + } + quad->ShaderNum = BQS_InGameColormap; + quad->Desat = colormap->Desaturate; + color0 = ColorARGB(255, colormap->Color.r, colormap->Color.g, colormap->Color.b); + color1 = ColorARGB(uint32_t((1 - fadelevel) * 255), + uint32_t(colormap->Fade.r * fadelevel), + uint32_t(colormap->Fade.g * fadelevel), + uint32_t(colormap->Fade.b * fadelevel)); + } + } + else + { + quad->Flags = BQF_WrapUV | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_Plain; + } + quad->Palette = nullptr; + quad->Texture = tex->Box->Owner->Tex; + quad->NumVerts = npoints; + quad->NumTris = npoints - 2; + + yoffs = GatheringWipeScreen ? 0 : LBOffset; + uscale = float(1.f / (texture->GetScaledWidth() * scalex)); + vscale = float(1.f / (texture->GetScaledHeight() * scaley)); + ox = float(originx); + oy = float(originy); + + for (i = 0; i < npoints; ++i) + { + verts[i].x = points[i].X; + verts[i].y = points[i].Y + yoffs; + verts[i].z = 0; + verts[i].rhw = 1; + verts[i].color0 = color0; + verts[i].color1 = color1; + float u = points[i].X - 0.5f - ox; + float v = points[i].Y - 0.5f - oy; + if (dorotate) + { + float t = u; + u = t * cosrot - v * sinrot; + v = v * cosrot + t * sinrot; + } + verts[i].tu = u * uscale; + verts[i].tv = v * vscale; + } + for (ipos = IndexPos, i = 2; i < npoints; ++i, ipos += 3) + { + IndexData[ipos] = VertexPos; + IndexData[ipos + 1] = VertexPos + i - 1; + IndexData[ipos + 2] = VertexPos + i; + } + + QuadBatchPos++; + VertexPos += npoints; + IndexPos = ipos; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: AddColorOnlyQuad +// +// Adds a single-color, untextured quad to the batch. +// +//========================================================================== + +void OpenGLSWFrameBuffer::AddColorOnlyQuad(int left, int top, int width, int height, uint32_t color) +{ + BufferedTris *quad; + FBVERTEX *verts; + + CheckQuadBatch(); + quad = &QuadExtra[QuadBatchPos]; + verts = &VertexData[VertexPos]; + + float x = float(left) - 0.5f; + float y = float(top) - 0.5f + (GatheringWipeScreen ? 0 : LBOffset); + + quad->ClearSetup(); + quad->ShaderNum = BQS_ColorOnly; + if ((color & 0xFF000000) != 0xFF000000) + { + quad->BlendOp = GL_FUNC_ADD; + quad->SrcBlend = GL_SRC_ALPHA; + quad->DestBlend = GL_ONE_MINUS_SRC_ALPHA; + } + quad->Palette = nullptr; + quad->Texture = nullptr; + quad->NumVerts = 4; + quad->NumTris = 2; + + verts[0].x = x; + verts[0].y = y; + verts[0].z = 0; + verts[0].rhw = 1; + verts[0].color0 = color; + verts[0].color1 = 0; + verts[0].tu = 0; + verts[0].tv = 0; + + verts[1].x = x + width; + verts[1].y = y; + verts[1].z = 0; + verts[1].rhw = 1; + verts[1].color0 = color; + verts[1].color1 = 0; + verts[1].tu = 0; + verts[1].tv = 0; + + verts[2].x = x + width; + verts[2].y = y + height; + verts[2].z = 0; + verts[2].rhw = 1; + verts[2].color0 = color; + verts[2].color1 = 0; + verts[2].tu = 0; + verts[2].tv = 0; + + verts[3].x = x; + verts[3].y = y + height; + verts[3].z = 0; + verts[3].rhw = 1; + verts[3].color0 = color; + verts[3].color1 = 0; + verts[3].tu = 0; + verts[3].tv = 0; + + IndexData[IndexPos] = VertexPos; + IndexData[IndexPos + 1] = VertexPos + 1; + IndexData[IndexPos + 2] = VertexPos + 2; + IndexData[IndexPos + 3] = VertexPos; + IndexData[IndexPos + 4] = VertexPos + 2; + IndexData[IndexPos + 5] = VertexPos + 3; + + QuadBatchPos++; + VertexPos += 4; + IndexPos += 6; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: AddColorOnlyRect +// +// Like AddColorOnlyQuad, except it's hollow. +// +//========================================================================== + +void OpenGLSWFrameBuffer::AddColorOnlyRect(int left, int top, int width, int height, uint32_t color) +{ + AddColorOnlyQuad(left, top, width - 1, 1, color); // top + AddColorOnlyQuad(left + width - 1, top, 1, height - 1, color); // right + AddColorOnlyQuad(left + 1, top + height - 1, width - 1, 1, color); // bottom + AddColorOnlyQuad(left, top + 1, 1, height - 1, color); // left +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CheckQuadBatch +// +// Make sure there's enough room in the batch for one more set of triangles. +// +//========================================================================== + +void OpenGLSWFrameBuffer::CheckQuadBatch(int numtris, int numverts) +{ + if (BatchType == BATCH_Lines) + { + EndLineBatch(); + } + else if (QuadBatchPos == MAX_QUAD_BATCH || + VertexPos + numverts > NUM_VERTS || + IndexPos + numtris * 3 > NUM_INDEXES) + { + EndQuadBatch(); + } + if (QuadBatchPos < 0) + { + BeginQuadBatch(); + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: BeginQuadBatch +// +// Locks the vertex buffer for quads and sets the cursor to 0. +// +//========================================================================== + +void OpenGLSWFrameBuffer::BeginQuadBatch() +{ + if (In2D < 2 || !InScene || QuadBatchPos >= 0) + { + return; + } + EndLineBatch(); // Make sure all lines have been drawn first. + VertexData = VertexBuffer->Lock(); + IndexData = IndexBuffer->Lock(); + VertexPos = 0; + IndexPos = 0; + QuadBatchPos = 0; + BatchType = BATCH_Quads; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: EndQuadBatch +// +// Draws all the quads that have been batched up. +// +//========================================================================== + +void OpenGLSWFrameBuffer::EndQuadBatch() +{ + if (In2D < 2 || !InScene || BatchType != BATCH_Quads) + { + return; + } + BatchType = BATCH_None; + VertexBuffer->Unlock(); + IndexBuffer->Unlock(); + if (QuadBatchPos == 0) + { + QuadBatchPos = -1; + VertexPos = -1; + IndexPos = -1; + return; + } + SetStreamSource(VertexBuffer); + SetIndices(IndexBuffer); + bool uv_wrapped = false; + bool uv_should_wrap; + int indexpos, vertpos; + + indexpos = vertpos = 0; + for (int i = 0; i < QuadBatchPos; ) + { + const BufferedTris *quad = &QuadExtra[i]; + int j; + + int startindex = indexpos; + int startvertex = vertpos; + + indexpos += quad->NumTris * 3; + vertpos += quad->NumVerts; + + // Quads with matching parameters should be done with a single + // DrawPrimitive call. + for (j = i + 1; j < QuadBatchPos; ++j) + { + const BufferedTris *q2 = &QuadExtra[j]; + if (quad->Texture != q2->Texture || + !quad->IsSameSetup(*q2) || + quad->Palette != q2->Palette) + { + break; + } + if (quad->ShaderNum == BQS_InGameColormap && (quad->Flags & BQF_Desaturated) && quad->Desat != q2->Desat) + { + break; + } + indexpos += q2->NumTris * 3; + vertpos += q2->NumVerts; + } + + // Set the palette (if one) + if ((quad->Flags & BQF_Paletted) == BQF_GamePalette) + { + SetPaletteTexture(PaletteTexture, 256, BorderColor); + } + else if ((quad->Flags & BQF_Paletted) == BQF_CustomPalette) + { + assert(quad->Palette != nullptr); + SetPaletteTexture(quad->Palette->Tex, quad->Palette->RoundedPaletteSize, quad->Palette->BorderColor); + } + + // Set the alpha blending + SetAlphaBlend(quad->BlendOp, quad->SrcBlend, quad->DestBlend); + + // Set the alpha test + EnableAlphaTest(!(quad->Flags & BQF_DisableAlphaTest)); + + // Set the pixel shader + if (quad->ShaderNum == BQS_PalTex) + { + SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ? + SHADER_NormalColorPalInv : SHADER_NormalColorPal]); + } + else if (quad->ShaderNum == BQS_Plain) + { + SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ? + SHADER_NormalColorInv : SHADER_NormalColor]); + } + else if (quad->ShaderNum == BQS_RedToAlpha) + { + SetPixelShader(Shaders[(quad->Flags & BQF_InvertSource) ? + SHADER_RedToAlphaInv : SHADER_RedToAlpha]); + } + else if (quad->ShaderNum == BQS_ColorOnly) + { + SetPixelShader(Shaders[SHADER_VertexColor]); + } + else if (quad->ShaderNum == BQS_SpecialColormap) + { + int select; + + select = !!(quad->Flags & BQF_Paletted); + SetPixelShader(Shaders[SHADER_SpecialColormap + select]); + } + else if (quad->ShaderNum == BQS_InGameColormap) + { + int select; + + select = !!(quad->Flags & BQF_Desaturated); + select |= !!(quad->Flags & BQF_InvertSource) << 1; + select |= !!(quad->Flags & BQF_Paletted) << 2; + if (quad->Flags & BQF_Desaturated) + { + SetConstant(PSCONST_Desaturation, quad->Desat / 255.f, (255 - quad->Desat) / 255.f, 0, 0); + } + SetPixelShader(Shaders[SHADER_InGameColormap + select]); + } + + // Set the texture clamp addressing mode + uv_should_wrap = !!(quad->Flags & BQF_WrapUV); + if (uv_wrapped != uv_should_wrap) + { + uint32_t mode = uv_should_wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE; + uv_wrapped = uv_should_wrap; + SetSamplerWrapS(0, mode); + SetSamplerWrapT(0, mode); + } + + // Set the texture + if (quad->Texture != nullptr) + { + SetTexture(0, quad->Texture); + } + + // Draw the quad + DrawTriangleList( + startvertex, // MinIndex + vertpos - startvertex, // NumVertices + startindex, // StartIndex + (indexpos - startindex) / 3 // PrimitiveCount + /*4 * i, 4 * (j - i), 6 * i, 2 * (j - i)*/); + i = j; + } + if (uv_wrapped) + { + SetSamplerWrapS(0, GL_CLAMP_TO_EDGE); + SetSamplerWrapT(0, GL_CLAMP_TO_EDGE); + } + QuadBatchPos = -1; + VertexPos = -1; + IndexPos = -1; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: EndBatch +// +// Draws whichever type of primitive is currently being batched. +// +//========================================================================== + +void OpenGLSWFrameBuffer::EndBatch() +{ + if (BatchType == BATCH_Quads) + { + EndQuadBatch(); + } + else if (BatchType == BATCH_Lines) + { + EndLineBatch(); + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: SetStyle +// +// Patterned after R_SetPatchStyle. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::SetStyle(OpenGLTex *tex, DrawParms &parms, uint32_t &color0, uint32_t &color1, BufferedTris &quad) +{ + int fmt = tex->GetTexFormat(); + FRenderStyle style = parms.style; + float alpha; + bool stencilling; + + if (style.Flags & STYLEF_TransSoulsAlpha) + { + alpha = transsouls; + } + else if (style.Flags & STYLEF_Alpha1) + { + alpha = 1; + } + else + { + alpha = clamp(parms.Alpha, 0.f, 1.f); + } + + style.CheckFuzz(); + if (style.BlendOp == STYLEOP_Shadow) + { + style = LegacyRenderStyles[STYLE_TranslucentStencil]; + alpha = 0.3f; + parms.fillcolor = 0; + } + + // FIXME: Fuzz effect is not written + if (style.BlendOp == STYLEOP_FuzzOrAdd || style.BlendOp == STYLEOP_Fuzz) + { + style.BlendOp = STYLEOP_Add; + } + else if (style.BlendOp == STYLEOP_FuzzOrSub) + { + style.BlendOp = STYLEOP_Sub; + } + else if (style.BlendOp == STYLEOP_FuzzOrRevSub) + { + style.BlendOp = STYLEOP_RevSub; + } + + stencilling = false; + quad.Palette = nullptr; + quad.Flags = 0; + quad.Desat = 0; + + switch (style.BlendOp) + { + default: + case STYLEOP_Add: quad.BlendOp = GL_FUNC_ADD; break; + case STYLEOP_Sub: quad.BlendOp = GL_FUNC_SUBTRACT; break; + case STYLEOP_RevSub: quad.BlendOp = GL_FUNC_REVERSE_SUBTRACT; break; + case STYLEOP_None: return false; + } + quad.SrcBlend = GetStyleAlpha(style.SrcAlpha); + quad.DestBlend = GetStyleAlpha(style.DestAlpha); + + if (style.Flags & STYLEF_InvertOverlay) + { + // Only the overlay color is inverted, not the overlay alpha. + parms.colorOverlay = ColorARGB(APART(parms.colorOverlay), + 255 - RPART(parms.colorOverlay), 255 - GPART(parms.colorOverlay), + 255 - BPART(parms.colorOverlay)); + } + + SetColorOverlay(parms.colorOverlay, alpha, color0, color1); + + if (style.Flags & STYLEF_ColorIsFixed) + { + if (style.Flags & STYLEF_InvertSource) + { // Since the source color is a constant, we can invert it now + // without spending time doing it in the shader. + parms.fillcolor = ColorXRGB(255 - RPART(parms.fillcolor), + 255 - GPART(parms.fillcolor), 255 - BPART(parms.fillcolor)); + } + // Set up the color mod to replace the color from the image data. + color0 = (color0 & ColorRGBA(0, 0, 0, 255)) | (parms.fillcolor & ColorRGBA(255, 255, 255, 0)); + color1 &= ColorRGBA(0, 0, 0, 255); + + if (style.Flags & STYLEF_RedIsAlpha) + { + // Note that if the source texture is paletted, the palette is ignored. + quad.Flags = 0; + quad.ShaderNum = BQS_RedToAlpha; + } + else if (fmt == GL_R8) + { + quad.Flags = BQF_GamePalette; + quad.ShaderNum = BQS_PalTex; + } + else + { + quad.Flags = 0; + quad.ShaderNum = BQS_Plain; + } + } + else + { + if (style.Flags & STYLEF_RedIsAlpha) + { + quad.Flags = 0; + quad.ShaderNum = BQS_RedToAlpha; + } + else if (fmt == GL_R8) + { + if (parms.remap != nullptr) + { + quad.Flags = BQF_CustomPalette; + quad.Palette = reinterpret_cast(parms.remap->GetNative()); + quad.ShaderNum = BQS_PalTex; + } + else if (tex->IsGray) + { + quad.Flags = 0; + quad.ShaderNum = BQS_Plain; + } + else + { + quad.Flags = BQF_GamePalette; + quad.ShaderNum = BQS_PalTex; + } + } + else + { + quad.Flags = 0; + quad.ShaderNum = BQS_Plain; + } + if (style.Flags & STYLEF_InvertSource) + { + quad.Flags |= BQF_InvertSource; + } + + if (parms.specialcolormap != nullptr) + { // Emulate an invulnerability or similar colormap. + float *start, *end; + start = parms.specialcolormap->ColorizeStart; + end = parms.specialcolormap->ColorizeEnd; + if (quad.Flags & BQF_InvertSource) + { + quad.Flags &= ~BQF_InvertSource; + swapvalues(start, end); + } + quad.ShaderNum = BQS_SpecialColormap; + color0 = ColorRGBA(uint32_t(start[0] / 2 * 255), uint32_t(start[1] / 2 * 255), uint32_t(start[2] / 2 * 255), color0 >> 24); + color1 = ColorRGBA(uint32_t(end[0] / 2 * 255), uint32_t(end[1] / 2 * 255), uint32_t(end[2] / 2 * 255), color1 >> 24); + } + else if (parms.colormapstyle != nullptr) + { // Emulate the fading from an in-game colormap (colorized, faded, and desaturated) + if (parms.colormapstyle->Desaturate != 0) + { + quad.Flags |= BQF_Desaturated; + } + quad.ShaderNum = BQS_InGameColormap; + quad.Desat = parms.colormapstyle->Desaturate; + color0 = ColorARGB(color1 >> 24, + parms.colormapstyle->Color.r, + parms.colormapstyle->Color.g, + parms.colormapstyle->Color.b); + double fadelevel = parms.colormapstyle->FadeLevel; + color1 = ColorARGB(uint32_t((1 - fadelevel) * 255), + uint32_t(parms.colormapstyle->Fade.r * fadelevel), + uint32_t(parms.colormapstyle->Fade.g * fadelevel), + uint32_t(parms.colormapstyle->Fade.b * fadelevel)); + } + } + + // For unmasked images, force the alpha from the image data to be ignored. + if (!parms.masked && quad.ShaderNum != BQS_InGameColormap) + { + color0 = (color0 & ColorRGBA(255, 255, 255, 0)) | ColorValue(0, 0, 0, alpha); + color1 &= ColorRGBA(255, 255, 255, 0); + + // If our alpha is one and we are doing normal adding, then we can turn the blend off completely. + if (quad.BlendOp == GL_FUNC_ADD && + ((alpha == 1 && quad.SrcBlend == GL_SRC_ALPHA) || quad.SrcBlend == GL_ONE) && + ((alpha == 1 && quad.DestBlend == GL_ONE_MINUS_SRC_ALPHA) || quad.DestBlend == GL_ZERO)) + { + quad.BlendOp = 0; + } + quad.Flags |= BQF_DisableAlphaTest; + } + return true; +} + +int OpenGLSWFrameBuffer::GetStyleAlpha(int type) +{ + switch (type) + { + case STYLEALPHA_Zero: return GL_ZERO; + case STYLEALPHA_One: return GL_ONE; + case STYLEALPHA_Src: return GL_SRC_ALPHA; + case STYLEALPHA_InvSrc: return GL_ONE_MINUS_SRC_ALPHA; + default: return GL_ZERO; + } +} + + +void OpenGLSWFrameBuffer::SetColorOverlay(uint32_t color, float alpha, uint32_t &color0, uint32_t &color1) +{ + if (APART(color) != 0) + { + int a = APART(color) * 256 / 255; + color0 = ColorRGBA( + (RPART(color) * a) >> 8, + (GPART(color) * a) >> 8, + (BPART(color) * a) >> 8, + 0); + a = 256 - a; + color1 = ColorRGBA(a, a, a, int(alpha * 255)); + } + else + { + color0 = 0; + color1 = ColorValue(1, 1, 1, alpha); + } +} + +void OpenGLSWFrameBuffer::EnableAlphaTest(bool enabled) +{ + if (enabled != AlphaTestEnabled) + { + AlphaTestEnabled = enabled; + //glEnable(GL_ALPHA_TEST); // To do: move to shader as this is only in the compatibility profile + } +} + +void OpenGLSWFrameBuffer::SetAlphaBlend(int op, int srcblend, int destblend) +{ + if (op == 0) + { // Disable alpha blend + if (AlphaBlendEnabled) + { + AlphaBlendEnabled = false; + glDisable(GL_BLEND); + } + } + else + { // Enable alpha blend + assert(srcblend != 0); + assert(destblend != 0); + + if (!AlphaBlendEnabled) + { + AlphaBlendEnabled = true; + glEnable(GL_BLEND); + } + if (AlphaBlendOp != op) + { + AlphaBlendOp = op; + glBlendEquation(op); + } + if (AlphaSrcBlend != srcblend || AlphaDestBlend != destblend) + { + AlphaSrcBlend = srcblend; + AlphaDestBlend = destblend; + glBlendFunc(srcblend, destblend); + } + } +} + +void OpenGLSWFrameBuffer::SetConstant(int cnum, float r, float g, float b, float a) +{ + if (Constant[cnum][0] != r || + Constant[cnum][1] != g || + Constant[cnum][2] != b || + Constant[cnum][3] != a) + { + Constant[cnum][0] = r; + Constant[cnum][1] = g; + Constant[cnum][2] = b; + Constant[cnum][3] = a; + SetPixelShaderConstantF(cnum, Constant[cnum], 1); + } +} + +void OpenGLSWFrameBuffer::SetPixelShader(HWPixelShader *shader) +{ + if (CurPixelShader != shader) + { + CurPixelShader = shader; + SetHWPixelShader(shader); + } +} + +void OpenGLSWFrameBuffer::SetTexture(int tnum, HWTexture *texture) +{ + assert(unsigned(tnum) < countof(Texture)); + if (texture) + { + if (Texture[tnum] != texture || SamplerWrapS[tnum] != texture->WrapS || SamplerWrapT[tnum] != texture->WrapT) + { + Texture[tnum] = texture; + glActiveTexture(GL_TEXTURE0 + tnum); + glBindTexture(GL_TEXTURE_2D, texture->Texture); + if (Texture[tnum]->WrapS != SamplerWrapS[tnum]) + { + Texture[tnum]->WrapS = SamplerWrapS[tnum]; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, SamplerWrapS[tnum]); + } + if (Texture[tnum]->WrapT != SamplerWrapT[tnum]) + { + Texture[tnum]->WrapT = SamplerWrapT[tnum]; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, SamplerWrapT[tnum]); + } + } + } + else if (Texture[tnum] != texture) + { + Texture[tnum] = texture; + glActiveTexture(GL_TEXTURE0 + tnum); + glBindTexture(GL_TEXTURE_2D, 0); + } +} + +void OpenGLSWFrameBuffer::SetSamplerWrapS(int tnum, int mode) +{ + assert(unsigned(tnum) < countof(Texture)); + if (Texture[tnum] && SamplerWrapS[tnum] != mode) + { + SamplerWrapS[tnum] = mode; + Texture[tnum]->WrapS = mode; + glActiveTexture(GL_TEXTURE0 + tnum); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, SamplerWrapS[tnum]); + } +} + +void OpenGLSWFrameBuffer::SetSamplerWrapT(int tnum, int mode) +{ + assert(unsigned(tnum) < countof(Texture)); + if (Texture[tnum] && SamplerWrapT[tnum] != mode) + { + SamplerWrapT[tnum] = mode; + Texture[tnum]->WrapT = mode; + glActiveTexture(GL_TEXTURE0 + tnum); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, SamplerWrapT[tnum]); + } +} + +void OpenGLSWFrameBuffer::SetPaletteTexture(HWTexture *texture, int count, uint32_t border_color) +{ + // The pixel shader receives color indexes in the range [0.0,1.0]. + // The palette texture is also addressed in the range [0.0,1.0], + // HOWEVER the coordinate 1.0 is the right edge of the texture and + // not actually the texture itself. We need to scale and shift + // the palette indexes so they lie exactly in the center of each + // texel. For a normal palette with 256 entries, that means the + // range we use should be [0.5,255.5], adjusted so the coordinate + // is still within [0.0,1.0]. + // + // The constant register c2 is used to hold the multiplier in the + // x part and the adder in the y part. + float fcount = 1 / float(count); + SetConstant(PSCONST_PaletteMod, 255 * fcount, 0.5f * fcount, 0, 0); + SetTexture(1, texture); +} diff --git a/src/gl/system/gl_swframebuffer.h b/src/gl/system/gl_swframebuffer.h new file mode 100644 index 0000000000..1cf0288ce3 --- /dev/null +++ b/src/gl/system/gl_swframebuffer.h @@ -0,0 +1,498 @@ +#ifndef __GL_SWFRAMEBUFFER +#define __GL_SWFRAMEBUFFER + +#ifdef _WIN32 +#include "win32iface.h" +#include "win32gliface.h" +#endif + +#include "SkylineBinPack.h" + +#include + +class FGLDebug; + +#ifdef _WIN32 +class OpenGLSWFrameBuffer : public Win32GLFrameBuffer +{ + typedef Win32GLFrameBuffer Super; + DECLARE_CLASS(OpenGLSWFrameBuffer, Win32GLFrameBuffer) +#else +#include "sdlglvideo.h" +class OpenGLFrameBuffer : public SDLGLFB +{ +// typedef SDLGLFB Super; //[C]commented, DECLARE_CLASS defines this in linux + DECLARE_CLASS(OpenGLSWFrameBuffer, SDLGLFB) +#endif + + +public: + + explicit OpenGLSWFrameBuffer() {} + OpenGLSWFrameBuffer(void *hMonitor, int width, int height, int bits, int refreshHz, bool fullscreen); + ~OpenGLSWFrameBuffer(); + + bool IsValid() override; + bool Lock(bool buffered) override; + void Unlock() override; + void Update() override; + PalEntry *GetPalette() override; + void GetFlashedPalette(PalEntry palette[256]) override; + void UpdatePalette() override; + bool SetGamma(float gamma) override; + bool SetFlash(PalEntry rgb, int amount) override; + void GetFlash(PalEntry &rgb, int &amount) override; + int GetPageCount() override; + bool IsFullscreen() override; + void PaletteChanged() override; + int QueryNewPalette() override; + void Blank() override; + bool PaintToWindow() override; + void SetVSync(bool vsync) override; + void NewRefreshRate() override; + void GetScreenshotBuffer(const uint8_t *&buffer, int &pitch, ESSType &color_type) override; + void ReleaseScreenshotBuffer() override; + void SetBlendingRect(int x1, int y1, int x2, int y2) override; + bool Begin2D(bool copy3d) override; + void DrawBlendingRect() override; + FNativeTexture *CreateTexture(FTexture *gametex, bool wrapping) override; + FNativePalette *CreatePalette(FRemapTable *remap) override; + void DrawTextureParms(FTexture *img, DrawParms &parms) override; + void Clear(int left, int top, int right, int bottom, int palcolor, uint32 color) override; + void Dim(PalEntry color, float amount, int x1, int y1, int w, int h) override; + void FlatFill(int left, int top, int right, int bottom, FTexture *src, bool local_origin) override; + void DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32 realcolor) override; + void DrawPixel(int x, int y, int palcolor, uint32 rgbcolor) override; + void FillSimplePoly(FTexture *tex, FVector2 *points, int npoints, double originx, double originy, double scalex, double scaley, DAngle rotation, FDynamicColormap *colormap, int lightlevel) override; + bool WipeStartScreen(int type) override; + void WipeEndScreen() override; + bool WipeDo(int ticks) override; + void WipeCleanup() override; + bool Is8BitMode() override { return false; } + int GetTrueHeight() override { return TrueHeight; } + +private: + struct FBVERTEX + { + float x, y, z, rhw; + uint32_t color0, color1; + float tu, tv; + }; + + struct BURNVERTEX + { + float x, y, z, rhw; + float tu0, tv0; + float tu1, tv1; + }; + + enum + { + PSCONST_Desaturation, + PSCONST_PaletteMod, + PSCONST_Weights, + PSCONST_Gamma, + PSCONST_ScreenSize, + NumPSCONST + }; + + struct GammaRamp + { + uint16_t red[256], green[256], blue[256]; + }; + + struct LTRBRect + { + int left, top, right, bottom; + }; + + class HWTexture + { + public: + HWTexture() { Buffers[0] = 0; Buffers[1] = 0; } + ~HWTexture(); + + int Texture = 0; + int Buffers[2]; + int CurrentBuffer = 0; + int WrapS = 0; + int WrapT = 0; + int Format = 0; + }; + + class HWFrameBuffer + { + public: + ~HWFrameBuffer(); + + int Framebuffer = 0; + HWTexture *Texture = nullptr; + }; + + + class HWVertexBuffer + { + public: + ~HWVertexBuffer(); + + FBVERTEX *Lock(); + void Unlock(); + + int VertexArray = 0; + int Buffer = 0; + int Size = 0; + }; + + class HWIndexBuffer + { + public: + ~HWIndexBuffer(); + + uint16_t *Lock(); + void Unlock(); + + int Buffer = 0; + int Size = 0; + + private: + int LockedOldBinding = 0; + }; + + class HWPixelShader + { + public: + ~HWPixelShader(); + + int Program = 0; + int VertexShader = 0; + int FragmentShader = 0; + + int ConstantLocations[NumPSCONST]; + int ImageLocation = -1; + int PaletteLocation = -1; + int NewScreenLocation = -1; + int BurnLocation = -1; + }; + + bool CreateFrameBuffer(const FString &name, int width, int height, HWFrameBuffer **outFramebuffer); + bool CreatePixelShader(FString vertexsrc, FString fragmentsrc, const FString &defines, HWPixelShader **outShader); + bool CreateVertexBuffer(int size, HWVertexBuffer **outVertexBuffer); + bool CreateIndexBuffer(int size, HWIndexBuffer **outIndexBuffer); + bool CreateTexture(const FString &name, int width, int height, int levels, int format, HWTexture **outTexture); + void SetGammaRamp(const GammaRamp *ramp); + void SetPixelShaderConstantF(int uniformIndex, const float *data, int vec4fcount); + void SetHWPixelShader(HWPixelShader *shader); + void SetStreamSource(HWVertexBuffer *vertexBuffer); + void SetIndices(HWIndexBuffer *indexBuffer); + void DrawTriangleFans(int count, const FBVERTEX *vertices); + void DrawTriangleFans(int count, const BURNVERTEX *vertices); + void DrawPoints(int count, const FBVERTEX *vertices); + void DrawLineList(int count); + void DrawTriangleList(int minIndex, int numVertices, int startIndex, int primitiveCount); + void Present(); + + static uint32_t ColorARGB(uint32_t a, uint32_t r, uint32_t g, uint32_t b) { return ((a & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | ((b) & 0xff); } + static uint32_t ColorRGBA(uint32_t r, uint32_t g, uint32_t b, uint32_t a) { return ColorARGB(a, r, g, b); } + static uint32_t ColorXRGB(uint32_t r, uint32_t g, uint32_t b) { return ColorARGB(0xff, r, g, b); } + static uint32_t ColorValue(float r, float g, float b, float a) { return ColorRGBA((uint32_t)(r * 255.0f), (uint32_t)(g * 255.0f), (uint32_t)(b * 255.0f), (uint32_t)(a * 255.0f)); } + + // The number of points for the vertex buffer. + enum { NUM_VERTS = 10240 }; + + // The number of indices for the index buffer. + enum { NUM_INDEXES = ((NUM_VERTS * 6) / 4) }; + + // The number of quads we can batch together. + enum { MAX_QUAD_BATCH = (NUM_INDEXES / 6) }; + + // The default size for a texture atlas. + enum { DEF_ATLAS_WIDTH = 512 }; + enum { DEF_ATLAS_HEIGHT = 512 }; + + // TYPES ------------------------------------------------------------------- + + struct Atlas; + + struct PackedTexture + { + Atlas *Owner; + + PackedTexture **Prev, *Next; + + // Pixels this image covers + LTRBRect Area; + + // Texture coordinates for this image + float Left, Top, Right, Bottom; + + // Texture has extra space on the border? + bool Padded; + }; + + struct Atlas + { + Atlas(OpenGLSWFrameBuffer *fb, int width, int height, int format); + ~Atlas(); + + PackedTexture *AllocateImage(const Rect &rect, bool padded); + void FreeBox(PackedTexture *box); + + SkylineBinPack Packer; + Atlas *Next; + HWTexture *Tex; + int Format; + PackedTexture *UsedList; // Boxes that contain images + int Width, Height; + bool OneUse; + }; + + class OpenGLTex : public FNativeTexture + { + public: + OpenGLTex(FTexture *tex, OpenGLSWFrameBuffer *fb, bool wrapping); + ~OpenGLTex(); + + FTexture *GameTex; + PackedTexture *Box; + + OpenGLTex **Prev; + OpenGLTex *Next; + + bool IsGray; + + bool Create(OpenGLSWFrameBuffer *fb, bool wrapping); + bool Update(); + bool CheckWrapping(bool wrapping); + int GetTexFormat(); + FTextureFormat ToTexFmt(int fmt); + }; + + class OpenGLPal : public FNativePalette + { + public: + OpenGLPal(FRemapTable *remap, OpenGLSWFrameBuffer *fb); + ~OpenGLPal(); + + OpenGLPal **Prev; + OpenGLPal *Next; + + HWTexture *Tex; + uint32_t BorderColor; + bool DoColorSkip; + + bool Update(); + + FRemapTable *Remap; + int RoundedPaletteSize; + }; + + // Flags for a buffered quad + enum + { + BQF_GamePalette = 1, + BQF_CustomPalette = 7, + BQF_Paletted = 7, + BQF_Bilinear = 8, + BQF_WrapUV = 16, + BQF_InvertSource = 32, + BQF_DisableAlphaTest = 64, + BQF_Desaturated = 128, + }; + + // Shaders for a buffered quad + enum + { + BQS_PalTex, + BQS_Plain, + BQS_RedToAlpha, + BQS_ColorOnly, + BQS_SpecialColormap, + BQS_InGameColormap, + }; + + struct PackedTexture; + struct Atlas; + + struct BufferedTris + { + uint8_t Flags; + uint8_t ShaderNum; + int BlendOp; + int SrcBlend; + int DestBlend; + + uint8_t Desat; + OpenGLPal *Palette; + HWTexture *Texture; + uint16_t NumVerts; // Number of _unique_ vertices used by this set. + uint16_t NumTris; // Number of triangles used by this set. + + void ClearSetup() + { + Flags = 0; + ShaderNum = 0; + BlendOp = 0; + SrcBlend = 0; + DestBlend = 0; + } + + bool IsSameSetup(const BufferedTris &other) const + { + return Flags == other.Flags && ShaderNum == other.ShaderNum && BlendOp == other.BlendOp && SrcBlend == other.SrcBlend && DestBlend == other.DestBlend; + } + }; + + enum + { + SHADER_NormalColor, + SHADER_NormalColorPal, + SHADER_NormalColorInv, + SHADER_NormalColorPalInv, + + SHADER_RedToAlpha, + SHADER_RedToAlphaInv, + + SHADER_VertexColor, + + SHADER_SpecialColormap, + SHADER_SpecialColormapPal, + + SHADER_InGameColormap, + SHADER_InGameColormapDesat, + SHADER_InGameColormapInv, + SHADER_InGameColormapInvDesat, + SHADER_InGameColormapPal, + SHADER_InGameColormapPalDesat, + SHADER_InGameColormapPalInv, + SHADER_InGameColormapPalInvDesat, + + SHADER_BurnWipe, + SHADER_GammaCorrection, + + NUM_SHADERS + }; + static const char *const ShaderDefines[NUM_SHADERS]; + + void Flip(); + void SetInitialState(); + bool CreateResources(); + void ReleaseResources(); + bool LoadShaders(); + bool CreateFBTexture(); + bool CreatePaletteTexture(); + bool CreateVertexes(); + void UploadPalette(); + void CalcFullscreenCoords(FBVERTEX verts[4], bool viewarea_only, bool can_double, uint32_t color0, uint32_t color1) const; + bool Reset(); + HWTexture *CopyCurrentScreen(); + void ReleaseDefaultPoolItems(); + void KillNativePals(); + void KillNativeTexs(); + PackedTexture *AllocPackedTexture(int width, int height, bool wrapping, int format); + void DrawPackedTextures(int packnum); + void DrawLetterbox(); + void Draw3DPart(bool copy3d); + bool SetStyle(OpenGLTex *tex, DrawParms &parms, uint32_t &color0, uint32_t &color1, BufferedTris &quad); + static int GetStyleAlpha(int type); + static void SetColorOverlay(uint32_t color, float alpha, uint32_t &color0, uint32_t &color1); + void AddColorOnlyQuad(int left, int top, int width, int height, uint32_t color); + void AddColorOnlyRect(int left, int top, int width, int height, uint32_t color); + void CheckQuadBatch(int numtris = 2, int numverts = 4); + void BeginQuadBatch(); + void EndQuadBatch(); + void BeginLineBatch(); + void EndLineBatch(); + void EndBatch(); + + // State + void EnableAlphaTest(bool enabled); + void SetAlphaBlend(int op, int srcblend = 0, int destblend = 0); + void SetConstant(int cnum, float r, float g, float b, float a); + void SetPixelShader(HWPixelShader *shader); + void SetTexture(int tnum, HWTexture *texture); + void SetSamplerWrapS(int tnum, int mode); + void SetSamplerWrapT(int tnum, int mode); + void SetPaletteTexture(HWTexture *texture, int count, uint32_t border_color); + + template static void SafeRelease(T &x) { if (x != nullptr) { delete x; x = nullptr; } } + + std::shared_ptr Debug; + + std::unique_ptr StreamVertexBuffer, StreamVertexBufferBurn; + float ShaderConstants[NumPSCONST * 4]; + HWPixelShader *CurrentShader = nullptr; + + HWFrameBuffer *OutputFB = nullptr; + + bool AlphaTestEnabled = false; + bool AlphaBlendEnabled = false; + int AlphaBlendOp = 0; + int AlphaSrcBlend = 0; + int AlphaDestBlend = 0; + float Constant[3][4]; + uint32_t CurBorderColor; + HWPixelShader *CurPixelShader; + HWTexture *Texture[5]; + int SamplerWrapS[5], SamplerWrapT[5]; + + PalEntry SourcePalette[256]; + uint32_t BorderColor; + uint32_t FlashColor0, FlashColor1; + PalEntry FlashColor; + int FlashAmount; + int TrueHeight; + int PixelDoubling; + int LBOffsetI; + float LBOffset; + float Gamma; + bool UpdatePending; + bool NeedPalUpdate; + bool NeedGammaUpdate; + int FBWidth, FBHeight; + bool VSync; + LTRBRect BlendingRect; + int In2D; + bool InScene; + bool GatheringWipeScreen; + bool AALines; + uint8_t BlockNum; + OpenGLPal *Palettes = nullptr; + OpenGLTex *Textures = nullptr; + Atlas *Atlases = nullptr; + + HWTexture *FBTexture = nullptr; + HWTexture *PaletteTexture = nullptr; + HWTexture *ScreenshotTexture = nullptr; + + HWVertexBuffer *VertexBuffer = nullptr; + FBVERTEX *VertexData = nullptr; + HWIndexBuffer *IndexBuffer = nullptr; + uint16_t *IndexData = nullptr; + BufferedTris *QuadExtra = nullptr; + int VertexPos; + int IndexPos; + int QuadBatchPos; + enum { BATCH_None, BATCH_Quads, BATCH_Lines } BatchType; + + HWPixelShader *Shaders[NUM_SHADERS]; + + HWTexture *InitialWipeScreen = nullptr, *FinalWipeScreen = nullptr; + + class Wiper + { + public: + virtual ~Wiper(); + virtual bool Run(int ticks, OpenGLSWFrameBuffer *fb) = 0; + + void DrawScreen(OpenGLSWFrameBuffer *fb, HWTexture *tex, int blendop = 0, uint32_t color0 = 0, uint32_t color1 = 0xFFFFFFF); + }; + + class Wiper_Melt; friend class Wiper_Melt; + class Wiper_Burn; friend class Wiper_Burn; + class Wiper_Crossfade; friend class Wiper_Crossfade; + + Wiper *ScreenWipe; +}; + + +#endif //__GL_SWFRAMEBUFFER diff --git a/src/gl/system/gl_swwipe.cpp b/src/gl/system/gl_swwipe.cpp new file mode 100644 index 0000000000..2f36272b6b --- /dev/null +++ b/src/gl/system/gl_swwipe.cpp @@ -0,0 +1,592 @@ +/* +** gl_swwipe.cpp +** Implements the different screen wipes using OpenGL calls. +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** 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. +** +** 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. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include "gl/system/gl_system.h" +#include "files.h" +#include "m_swap.h" +#include "v_video.h" +#include "doomstat.h" +#include "m_png.h" +#include "m_crc32.h" +#include "vectors.h" +#include "v_palette.h" +#include "templates.h" + +#include "c_dispatch.h" +#include "templates.h" +#include "i_system.h" +#include "i_video.h" +#include "i_input.h" +#include "v_pfx.h" +#include "stats.h" +#include "doomerrors.h" +#include "r_main.h" +#include "r_data/r_translate.h" +#include "f_wipe.h" +#include "sbar.h" +#include "w_wad.h" +#include "r_data/colormaps.h" + +#include "gl/system/gl_interface.h" +#include "gl/system/gl_swframebuffer.h" +#include "gl/data/gl_data.h" +#include "gl/utility/gl_clock.h" +#include "gl/utility/gl_templates.h" +#include "gl/gl_functions.h" +#include "gl_debug.h" +#include "m_random.h" + +class OpenGLSWFrameBuffer::Wiper_Crossfade : public OpenGLSWFrameBuffer::Wiper +{ +public: + Wiper_Crossfade(); + bool Run(int ticks, OpenGLSWFrameBuffer *fb); + +private: + int Clock; +}; + +class OpenGLSWFrameBuffer::Wiper_Melt : public OpenGLSWFrameBuffer::Wiper +{ +public: + Wiper_Melt(); + bool Run(int ticks, OpenGLSWFrameBuffer *fb); + +private: + // Match the strip sizes that oldschool Doom used. + static const int WIDTH = 160, HEIGHT = 200; + int y[WIDTH]; +}; + +class OpenGLSWFrameBuffer::Wiper_Burn : public OpenGLSWFrameBuffer::Wiper +{ +public: + Wiper_Burn(OpenGLSWFrameBuffer *fb); + ~Wiper_Burn(); + bool Run(int ticks, OpenGLSWFrameBuffer *fb); + +private: + static const int WIDTH = 64, HEIGHT = 64; + uint8_t BurnArray[WIDTH * (HEIGHT + 5)]; + HWTexture *BurnTexture; + int Density; + int BurnTime; +}; + +//========================================================================== +// +// OpenGLSWFrameBuffer :: WipeStartScreen +// +// Called before the current screen has started rendering. This needs to +// save what was drawn the previous frame so that it can be animated into +// what gets drawn this frame. +// +// In fullscreen mode, we use GetFrontBufferData() to grab the data that +// is visible on screen right now. +// +// In windowed mode, we can't do that because we'll get the whole desktop. +// Instead, we can conveniently use the TempRenderTexture, which is normally +// used for gamma-correcting copying the image to the back buffer. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::WipeStartScreen(int type) +{ + if (!Accel2D) + { + return Super::WipeStartScreen(type); + } + + switch (type) + { + case wipe_Melt: + ScreenWipe = new Wiper_Melt; + break; + + case wipe_Burn: + ScreenWipe = new Wiper_Burn(this); + break; + + case wipe_Fade: + ScreenWipe = new Wiper_Crossfade; + break; + + default: + return false; + } + + InitialWipeScreen = CopyCurrentScreen(); + + // Make even fullscreen model render to the TempRenderTexture, so + // we can have a copy of the new screen readily available. + GatheringWipeScreen = true; + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: WipeEndScreen +// +// The screen we want to animate to has just been drawn. This function is +// called in place of Update(), so it has not been Presented yet. +// +//========================================================================== + +void OpenGLSWFrameBuffer::WipeEndScreen() +{ + if (!Accel2D) + { + Super::WipeEndScreen(); + return; + } + + // Don't do anything if there is no starting point. + if (InitialWipeScreen == NULL) + { + return; + } + + // If the whole screen was drawn without 2D accel, get it in to + // video memory now. + if (!In2D) + { + Begin2D(true); + } + + EndBatch(); // Make sure all batched primitives have been drawn. + + FinalWipeScreen = CopyCurrentScreen(); + + // At this point, InitialWipeScreen holds the screen we are wiping from. + // FinalWipeScreen holds the screen we are wiping to, which may be the + // same texture as TempRenderTexture. +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: WipeDo +// +// Perform the actual wipe animation. The number of tics since the last +// time this function was called is passed in. Returns true when the wipe +// is over. The first time this function has been called, the screen is +// still locked from before and EndScene() still has not been called. +// Successive times need to call BeginScene(). +// +//========================================================================== + +bool OpenGLSWFrameBuffer::WipeDo(int ticks) +{ + if (!Accel2D) + { + return Super::WipeDo(ticks); + } + + // Sanity checks. + if (InitialWipeScreen == NULL || FinalWipeScreen == NULL) + { + return true; + } + if (GatheringWipeScreen) + { // This is the first time we've been called for this wipe. + GatheringWipeScreen = false; + } + else + { // This is the second or later time we've been called for this wipe. + InScene = true; + } + + In2D = 3; + + EnableAlphaTest(false); + bool done = ScreenWipe->Run(ticks, this); + DrawLetterbox(); + return done; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: WipeCleanup +// +// Release any resources that were specifically created for the wipe. +// +//========================================================================== + +void OpenGLSWFrameBuffer::WipeCleanup() +{ + if (ScreenWipe != NULL) + { + delete ScreenWipe; + ScreenWipe = NULL; + } + SafeRelease( InitialWipeScreen ); + SafeRelease( FinalWipeScreen ); + GatheringWipeScreen = false; + if (!Accel2D) + { + Super::WipeCleanup(); + return; + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper Constructor +// +//========================================================================== + +OpenGLSWFrameBuffer::Wiper::~Wiper() +{ +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper :: DrawScreen +// +// Draw either the initial or target screen completely to the screen. +// +//========================================================================== + +void OpenGLSWFrameBuffer::Wiper::DrawScreen(OpenGLSWFrameBuffer *fb, HWTexture *tex, + int blendop, uint32_t color0, uint32_t color1) +{ + FBVERTEX verts[4]; + + fb->CalcFullscreenCoords(verts, false, false, color0, color1); + fb->SetTexture(0, tex); + fb->SetAlphaBlend(blendop, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + fb->SetPixelShader(fb->Shaders[SHADER_NormalColor]); + fb->DrawTriangleFans(2, verts); +} + +// WIPE: CROSSFADE --------------------------------------------------------- + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper_Crossfade Constructor +// +//========================================================================== + +OpenGLSWFrameBuffer::Wiper_Crossfade::Wiper_Crossfade() +: Clock(0) +{ +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper_Crossfade :: Run +// +// Fades the old screen into the new one over 32 ticks. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::Wiper_Crossfade::Run(int ticks, OpenGLSWFrameBuffer *fb) +{ + Clock += ticks; + + // Put the initial screen back to the buffer. + DrawScreen(fb, fb->InitialWipeScreen); + + // Draw the new screen on top of it. + DrawScreen(fb, fb->FinalWipeScreen, GL_FUNC_ADD, ColorValue(0,0,0,Clock / 32.f), ColorRGBA(255,255,255,0)); + + return Clock >= 32; +} + +// WIPE: MELT -------------------------------------------------------------- + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper_Melt Constructor +// +//========================================================================== + +OpenGLSWFrameBuffer::Wiper_Melt::Wiper_Melt() +{ + int i, r; + + // setup initial column positions + // (y<0 => not ready to scroll yet) + y[0] = -(M_Random() & 15); + for (i = 1; i < WIDTH; ++i) + { + r = (M_Random()%3) - 1; + y[i] = clamp(y[i-1] + r, -15, 0); + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper_Melt :: Run +// +// Fades the old screen into the new one over 32 ticks. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::Wiper_Melt::Run(int ticks, OpenGLSWFrameBuffer *fb) +{ + // Draw the new screen on the bottom. + DrawScreen(fb, fb->FinalWipeScreen); + + int i, dy; + int fbwidth = fb->Width; + int fbheight = fb->Height; + bool done = true; + + // Copy the old screen in vertical strips on top of the new one. + while (ticks--) + { + done = true; + for (i = 0; i < WIDTH; i++) + { + if (y[i] < 0) + { + y[i]++; + done = false; + } + else if (y[i] < HEIGHT) + { + dy = (y[i] < 16) ? y[i]+1 : 8; + y[i] = MIN(y[i] + dy, HEIGHT); + done = false; + } + if (ticks == 0) + { // Only draw for the final tick. + RECT rect; + POINT dpt; + + dpt.x = i * fbwidth / WIDTH; + dpt.y = MAX(0, y[i] * fbheight / HEIGHT); + rect.left = dpt.x; + rect.top = 0; + rect.right = (i + 1) * fbwidth / WIDTH; + rect.bottom = fbheight - dpt.y; + if (rect.bottom > rect.top) + { + fb->CheckQuadBatch(); + + BufferedTris *quad = &fb->QuadExtra[fb->QuadBatchPos]; + FBVERTEX *vert = &fb->VertexData[fb->VertexPos]; + WORD *index = &fb->IndexData[fb->IndexPos]; + + quad->ClearSetup(); + quad->Flags = BQF_DisableAlphaTest; + quad->ShaderNum = BQS_Plain; + quad->Palette = NULL; + quad->Texture = fb->InitialWipeScreen; + quad->NumVerts = 4; + quad->NumTris = 2; + + // Fill the vertex buffer. + float u0 = rect.left / float(fb->FBWidth); + float v0 = 0; + float u1 = rect.right / float(fb->FBWidth); + float v1 = (rect.bottom - rect.top) / float(fb->FBHeight); + + float x0 = float(rect.left) - 0.5f; + float x1 = float(rect.right) - 0.5f; + float y0 = float(dpt.y + fb->LBOffsetI) - 0.5f; + float y1 = float(fbheight + fb->LBOffsetI) - 0.5f; + + vert[0].x = x0; + vert[0].y = y0; + vert[0].z = 0; + vert[0].rhw = 1; + vert[0].color0 = 0; + vert[0].color1 = 0xFFFFFFF; + vert[0].tu = u0; + vert[0].tv = v0; + + vert[1].x = x1; + vert[1].y = y0; + vert[1].z = 0; + vert[1].rhw = 1; + vert[1].color0 = 0; + vert[1].color1 = 0xFFFFFFF; + vert[1].tu = u1; + vert[1].tv = v0; + + vert[2].x = x1; + vert[2].y = y1; + vert[2].z = 0; + vert[2].rhw = 1; + vert[2].color0 = 0; + vert[2].color1 = 0xFFFFFFF; + vert[2].tu = u1; + vert[2].tv = v1; + + vert[3].x = x0; + vert[3].y = y1; + vert[3].z = 0; + vert[3].rhw = 1; + vert[3].color0 = 0; + vert[3].color1 = 0xFFFFFFF; + vert[3].tu = u0; + vert[3].tv = v1; + + // Fill the vertex index buffer. + index[0] = fb->VertexPos; + index[1] = fb->VertexPos + 1; + index[2] = fb->VertexPos + 2; + index[3] = fb->VertexPos; + index[4] = fb->VertexPos + 2; + index[5] = fb->VertexPos + 3; + + // Batch the quad. + fb->QuadBatchPos++; + fb->VertexPos += 4; + fb->IndexPos += 6; + } + } + } + } + fb->EndQuadBatch(); + return done; +} + +// WIPE: BURN -------------------------------------------------------------- + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper_Burn Constructor +// +//========================================================================== + +OpenGLSWFrameBuffer::Wiper_Burn::Wiper_Burn(OpenGLSWFrameBuffer *fb) +{ + Density = 4; + BurnTime = 0; + memset(BurnArray, 0, sizeof(BurnArray)); + if (fb->Shaders[SHADER_BurnWipe] == NULL || !fb->CreateTexture("BurnWipe", WIDTH, HEIGHT, 1, GL_R8, &BurnTexture)) + { + BurnTexture = NULL; + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper_Burn Destructor +// +//========================================================================== + +OpenGLSWFrameBuffer::Wiper_Burn::~Wiper_Burn() +{ + SafeRelease( BurnTexture ); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: Wiper_Burn :: Run +// +//========================================================================== + +bool OpenGLSWFrameBuffer::Wiper_Burn::Run(int ticks, OpenGLSWFrameBuffer *fb) +{ + bool done; + + BurnTime += ticks; + ticks *= 2; + + // Make the fire burn + done = false; + while (!done && ticks--) + { + Density = wipe_CalcBurn(BurnArray, WIDTH, HEIGHT, Density); + done = (Density < 0); + } + + // Update the burn texture with the new burn data + + if (BurnTexture->Buffers[0] == 0) + { + glGenBuffers(2, (GLuint*)BurnTexture->Buffers); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, BurnTexture->Buffers[0]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, WIDTH * HEIGHT, nullptr, GL_STREAM_DRAW); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, BurnTexture->Buffers[1]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, WIDTH * HEIGHT, nullptr, GL_STREAM_DRAW); + } + else + { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, BurnTexture->Buffers[BurnTexture->CurrentBuffer]); + BurnTexture->CurrentBuffer = (BurnTexture->CurrentBuffer + 1) & 1; + } + + uint8_t *dest = (uint8_t*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, WIDTH * HEIGHT, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + if (dest) + { + memcpy(dest, BurnArray, WIDTH * HEIGHT); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldBinding); + glBindTexture(GL_TEXTURE_2D, BurnTexture->Texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, GL_RED, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, oldBinding); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + // Put the initial screen back to the buffer. + DrawScreen(fb, fb->InitialWipeScreen); + + // Burn the new screen on top of it. + float top = fb->LBOffset - 0.5f; + float right = float(fb->Width) - 0.5f; + float bot = float(fb->Height) + top; + float texright = float(fb->Width) / float(fb->FBWidth); + float texbot = float(fb->Height) / float(fb->FBHeight); + + BURNVERTEX verts[4] = + { + { -0.5f, top, 0.5f, 1.f, 0.f, 0.f, 0, 0 }, + { right, top, 0.5f, 1.f, texright, 0.f, 1, 0 }, + { right, bot, 0.5f, 1.f, texright, texbot, 1, 1 }, + { -0.5f, bot, 0.5f, 1.f, 0.f, texbot, 0, 1 } + }; + + fb->SetTexture(0, fb->FinalWipeScreen); + fb->SetTexture(1, BurnTexture); + fb->SetAlphaBlend(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + fb->SetPixelShader(fb->Shaders[SHADER_BurnWipe]); + glActiveTexture(GL_TEXTURE1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + fb->DrawTriangleFans(2, verts); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glActiveTexture(GL_TEXTURE0); + + // The fire may not always stabilize, so the wipe is forced to end + // after an arbitrary maximum time. + return done || (BurnTime > 40); +} diff --git a/src/textures/textures.h b/src/textures/textures.h index 58792fc416..26b73c6ec8 100644 --- a/src/textures/textures.h +++ b/src/textures/textures.h @@ -312,6 +312,7 @@ public: static void FlipNonSquareBlockRemap (BYTE *blockto, const BYTE *blockfrom, int x, int y, int srcpitch, const BYTE *remap); friend class D3DTex; + friend class OpenGLSWFrameBuffer; public: diff --git a/src/win32/hardware.cpp b/src/win32/hardware.cpp index 3cf941307f..cf39975fd4 100644 --- a/src/win32/hardware.cpp +++ b/src/win32/hardware.cpp @@ -137,9 +137,13 @@ void I_InitGraphics () val.Bool = !!Args->CheckParm ("-devparm"); ticker.SetGenericRepDefault (val, CVAR_Bool); - //currentrenderer = vid_renderer; - if (currentrenderer==1) Video = gl_CreateVideo(); - else Video = new Win32Video (0); +//#define USE_D3D9_VIDEO +#ifdef USE_D3D9_VIDEO + if (currentrenderer == 1) Video = gl_CreateVideo(); + else Video = new Win32Video(0); +#else + Video = gl_CreateVideo(); +#endif if (Video == NULL) I_FatalError ("Failed to initialize display"); diff --git a/src/win32/win32gliface.cpp b/src/win32/win32gliface.cpp index d78986cb5d..d46f45a02f 100644 --- a/src/win32/win32gliface.cpp +++ b/src/win32/win32gliface.cpp @@ -25,6 +25,7 @@ #include "gl/renderer/gl_renderer.h" #include "gl/system/gl_framebuffer.h" +#include "gl/system/gl_swframebuffer.h" extern "C" { _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; @@ -387,7 +388,10 @@ DFrameBuffer *Win32GLVideo::CreateFrameBuffer(int width, int height, bool bgra, //old->GetFlash(flashColor, flashAmount); delete old; } - fb = new OpenGLFrameBuffer(m_hMonitor, m_DisplayWidth, m_DisplayHeight, m_DisplayBits, m_DisplayHz, fs); + if (vid_renderer == 1) + fb = new OpenGLFrameBuffer(m_hMonitor, m_DisplayWidth, m_DisplayHeight, m_DisplayBits, m_DisplayHz, fs); + else + fb = new OpenGLSWFrameBuffer(m_hMonitor, m_DisplayWidth, m_DisplayHeight, m_DisplayBits, m_DisplayHz, fs); return fb; } diff --git a/wadsrc/static/shaders/glsl/swshader.fp b/wadsrc/static/shaders/glsl/swshader.fp new file mode 100644 index 0000000000..639ea92e5e --- /dev/null +++ b/wadsrc/static/shaders/glsl/swshader.fp @@ -0,0 +1,146 @@ + +in vec4 PixelColor0; +in vec4 PixelColor1; +in vec4 PixelTexCoord0; + +out vec4 FragColor; + +uniform sampler2D Image; +uniform sampler2D Palette; +uniform sampler2D NewScreen; +uniform sampler2D Burn; + +uniform vec4 Desaturation; // { Desat, 1 - Desat } +uniform vec4 PaletteMod; +uniform vec4 Weights; // RGB->Gray weighting { 77/256.0, 143/256.0, 37/256.0, 1 } +uniform vec4 Gamma; + +vec4 TextureLookup(vec2 tex_coord) +{ +#if PALTEX + float index = texture(Image, tex_coord).x; + index = index * PaletteMod.x + PaletteMod.y; + return texture(Palette, vec2(index, 0.5)); +#else + return texture(Image, tex_coord); +#endif +} + +vec4 Invert(vec4 rgb) +{ +#if INVERT + rgb.rgb = Weights.www - rgb.xyz; +#endif + return rgb; +} + +float Grayscale(vec4 rgb) +{ + return dot(rgb.rgb, Weights.rgb); +} + +vec4 SampleTexture(vec2 tex_coord) +{ + return Invert(TextureLookup(tex_coord)); +} + +// Normal color calculation for most drawing modes. + +vec4 NormalColor(vec2 tex_coord, vec4 Flash, vec4 InvFlash) +{ + return Flash + SampleTexture(tex_coord) * InvFlash; +} + +// Copy the red channel to the alpha channel. Pays no attention to palettes. + +vec4 RedToAlpha(vec2 tex_coord, vec4 Flash, vec4 InvFlash) +{ + vec4 color = Invert(texture(Image, tex_coord)); + color.a = color.r; + return Flash + color * InvFlash; +} + +// Just return the value of c0. + +vec4 VertexColor(vec4 color) +{ + return color; +} + +// Emulate one of the special colormaps. (Invulnerability, gold, etc.) + +vec4 SpecialColormap(vec2 tex_coord, vec4 start, vec4 end) +{ + vec4 color = SampleTexture(tex_coord); + vec4 range = end - start; + // We can't store values greater than 1.0 in a color register, so we multiply + // the final result by 2 and expect the caller to divide the start and end by 2. + color.rgb = 2 * (start + Grayscale(color) * range).rgb; + // Duplicate alpha semantics of NormalColor. + color.a = start.a + color.a * end.a; + return color; +} + +// In-game colormap effect: fade to a particular color and multiply by another, with +// optional desaturation of the original color. Desaturation is stored in c1. +// Fade level is packed int fade.a. Fade.rgb has been premultiplied by alpha. +// Overall alpha is in color.a. +vec4 InGameColormap(vec2 tex_coord, vec4 color, vec4 fade) +{ + vec4 rgb = SampleTexture(tex_coord); + + // Desaturate +#if DESAT + vec3 intensity; + intensity.rgb = vec3(Grayscale(rgb) * Desaturation.x); + rgb.rgb = intensity.rgb + rgb.rgb * Desaturation.y; +#endif + + // Fade + rgb.rgb = rgb.rgb * fade.aaa + fade.rgb; + + // Shade and Alpha + rgb = rgb * color; + + return rgb; +} + +// Windowed gamma correction. + +vec4 GammaCorrection(vec2 tex_coord) +{ + vec4 color = texture(Image, tex_coord); + color.rgb = pow(color.rgb, Gamma.rgb); + return color; +} + +// The burn wipe effect. + +vec4 BurnWipe(vec4 coord) +{ + vec4 color = texture(NewScreen, coord.xy); + vec4 alpha = texture(Burn, coord.zw); + color.a = alpha.r * 2; + return color; +} + +void main() +{ +#if defined(ENORMALCOLOR) + FragColor = NormalColor(PixelTexCoord0.xy, PixelColor0, PixelColor1); +#elif defined(EREDTOALPHA) + FragColor = RedToAlpha(PixelTexCoord0.xy, PixelColor0, PixelColor1); +#elif defined(EVERTEXCOLOR) + FragColor = VertexColor(PixelColor0); +#elif defined(ESPECIALCOLORMAP) + FragColor = SpecialColormap(PixelTexCoord0.xy, PixelColor0, PixelColor1); +#elif defined(EINGAMECOLORMAP) + FragColor = InGameColormap(PixelTexCoord0.xy, PixelColor0, PixelColor1); +#elif defined(EBURNWIPE) + FragColor = BurnWipe(PixelTexCoord0); +#elif defined(EGAMMACORRECTION) + FragColor = GammaCorrection(PixelTexCoord0.xy); +#else + #error Entry point define is missing +#endif +} diff --git a/wadsrc/static/shaders/glsl/swshader.vp b/wadsrc/static/shaders/glsl/swshader.vp new file mode 100644 index 0000000000..a317025aaf --- /dev/null +++ b/wadsrc/static/shaders/glsl/swshader.vp @@ -0,0 +1,22 @@ + +in vec4 AttrPosition; +in vec4 AttrColor0; +in vec4 AttrColor1; +in vec4 AttrTexCoord0; + +out vec4 PixelColor0; +out vec4 PixelColor1; +out vec4 PixelTexCoord0; + +uniform vec4 ScreenSize; + +void main() +{ + gl_Position = vec4(AttrPosition.xy / ScreenSize.xy * 2.0 - 1.0, 1.0, 1.0); +#if defined(EGAMMACORRECTION) + gl_Position.y = -gl_Position.y; +#endif + PixelColor0 = AttrColor0.bgra; + PixelColor1 = AttrColor1.bgra; + PixelTexCoord0 = AttrTexCoord0; +}