From 5f0088ab8b8885279407104093aed13e45cbfb0b Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Sun, 9 Oct 2016 12:50:57 +0200 Subject: [PATCH] Created new OpenGL framebuffer using the software renderer's hardware acceleration --- src/CMakeLists.txt | 1 + src/gl/system/gl_swframebuffer.cpp | 3357 ++++++++++++++++++++++++++++ src/gl/system/gl_swframebuffer.h | 470 ++++ src/textures/textures.h | 1 + 4 files changed, 3829 insertions(+) create mode 100644 src/gl/system/gl_swframebuffer.cpp create mode 100644 src/gl/system/gl_swframebuffer.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index de3316ced..dd241a3d7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1151,6 +1151,7 @@ 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_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 000000000..e0ff2be47 --- /dev/null +++ b/src/gl/system/gl_swframebuffer.cpp @@ -0,0 +1,3357 @@ +/* +** 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" + +CVAR(Bool, gl_antilag, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +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; + +IMPLEMENT_CLASS(OpenGLSWFrameBuffer) + +const char *const OpenGLSWFrameBuffer::ShaderNames[OpenGLSWFrameBuffer::NUM_SHADERS] = +{ + "NormalColor.fp", + "NormalColorPal.fp", + "NormalColorInv.fp", + "NormalColorPalInv.fp", + + "RedToAlpha.fp", + "RedToAlphaInv.fp", + + "VertexColor.fp", + + "SpecialColormap.fp", + "SpecialColorMapPal.fp", + + "InGameColormap.fp", + "InGameColormapDesat.fp", + "InGameColormapInv.fp", + "InGameColormapInvDesat.fp", + "InGameColormapPal.fp", + "InGameColormapPalDesat.fp", + "InGameColormapPalInv.fp", + "InGameColormapPalInvDesat.fp", + + "BurnWipe.fp", + "GammaCorrection.fp", +}; + +OpenGLSWFrameBuffer::OpenGLSWFrameBuffer(void *hMonitor, int width, int height, int bits, int refreshHz, bool fullscreen) : + Super(hMonitor, width, height, bits, refreshHz, fullscreen) +{ + // 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! + SetVSync(vid_vsync); + + VertexBuffer = nullptr; + IndexBuffer = nullptr; + FBTexture = nullptr; + TempRenderTexture = nullptr; + RenderTexture[0] = nullptr; + RenderTexture[1] = nullptr; + InitialWipeScreen = nullptr; + ScreenshotTexture = nullptr; + ScreenshotSurface = nullptr; + FinalWipeScreen = nullptr; + PaletteTexture = nullptr; + GammaTexture = nullptr; + FrontCopySurface = nullptr; + for (int i = 0; i < NUM_SHADERS; ++i) + { + Shaders[i] = nullptr; + } + GammaShader = nullptr; + BlockSurface[0] = nullptr; + BlockSurface[1] = 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]; + Atlases = nullptr; + PixelDoubling = 0; + SkipAt = -1; + CurrRenderTexture = 0; + RenderTextureToggle = 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 :: 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; + OldRenderTarget = nullptr; + + // 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); + + 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 (!CreateFBTexture() || + !CreatePaletteTexture()) + { + return false; + } + if (!CreateVertexes()) + { + return false; + } + CreateGammaTexture(); + CreateBlockSurfaces(); + 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() +{ + static const char models[][4] = { "30/", "20/", "14/" }; + FString shaderdir, shaderpath; + unsigned model, i; + int lump; + + // We determine the best available model simply by trying them all in + // order of decreasing preference. + for (model = 0; model < countof(models); ++model) + { + shaderdir = "shaders/gl/sm"; + shaderdir += models[model]; + for (i = 0; i < NUM_SHADERS; ++i) + { + shaderpath = shaderdir; + shaderpath += ShaderNames[i]; + lump = Wads.CheckNumForFullName(shaderpath); + if (lump >= 0) + { + FMemLump data = Wads.ReadLump(lump); + if (!CreatePixelShader((uint32_t *)data.GetMem(), &Shaders[i]) && i < SHADER_BurnWipe) + { + break; + } + } + } + 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(ScreenshotSurface); + SafeRelease(ScreenshotTexture); + SafeRelease(PaletteTexture); + for (int i = 0; i < NUM_SHADERS; ++i) + { + SafeRelease(Shaders[i]); + } + GammaShader = nullptr; + 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(RenderTexture[0]); + SafeRelease(RenderTexture[1]); + SafeRelease(InitialWipeScreen); + SafeRelease(VertexBuffer); + SafeRelease(IndexBuffer); + SafeRelease(BlockSurface[0]); + SafeRelease(BlockSurface[1]); + SafeRelease(FrontCopySurface); +} + +bool OpenGLSWFrameBuffer::Reset() +{ + ReleaseDefaultPoolItems(); + if (!CreateFBTexture() || !CreateVertexes()) + { + return false; + } + CreateBlockSurfaces(); + SetInitialState(); + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreateBlockSurfaces +// +// Create blocking surfaces for antilag. It's okay if these can't be +// created; antilag just won't work. +// +//========================================================================== + +void OpenGLSWFrameBuffer::CreateBlockSurfaces() +{ + BlockNum = 0; + if (CreateOffscreenPlainSurface(16, 16, GL_RGBA8, &BlockSurface[0])) + { + if (!CreateOffscreenPlainSurface(16, 16, GL_RGBA8, &BlockSurface[1])) + { + SafeRelease(BlockSurface[0]); + } + } +} + +//========================================================================== +// +// 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; + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreateFBTexture +// +// Creates the "Framebuffer" texture. With the advent of hardware-assisted +// 2D, this is something of a misnomer now. The FBTexture is only used for +// uploading the software 3D image to video memory so that it can be drawn +// to the real frame buffer. +// +// It also creates the TempRenderTexture, since this seemed like a +// convenient place to do so. +// +//========================================================================== + +bool OpenGLSWFrameBuffer::CreateFBTexture() +{ + if (!CreateTexture(Width, Height, 1, GL_R8, &FBTexture)) + { + int pow2width, pow2height, i; + + for (i = 1; i < Width; i <<= 1) {} pow2width = i; + for (i = 1; i < Height; i <<= 1) {} pow2height = i; + + if (!CreateTexture(pow2width, pow2height, 1, GL_R8, &FBTexture)) + { + return false; + } + else + { + FBWidth = pow2width; + FBHeight = pow2height; + } + } + else + { + FBWidth = Width; + FBHeight = Height; + } + RenderTextureToggle = 0; + RenderTexture[0] = nullptr; + RenderTexture[1] = nullptr; + if (!CreateTexture(FBWidth, FBHeight, 1, GL_RGBA8, &RenderTexture[0])) + { + return false; + } + if (Windowed || PixelDoubling) + { + // Windowed or pixel doubling: Create another render texture so we can flip between them. + RenderTextureToggle = 1; + if (!CreateTexture(FBWidth, FBHeight, 1, GL_RGBA8, &RenderTexture[1])) + { + return false; + } + } + else + { + // Fullscreen and not pixel doubling: Create a render target to have the back buffer copied to. + if (!CreateRenderTarget(Width, Height, GL_RGBA8, &FrontCopySurface)) + { + return false; + } + } + // Initialize the TempRenderTextures to black. + for (int i = 0; i <= RenderTextureToggle; ++i) + { + HWSurface *surf; + if (RenderTexture[i]->GetSurfaceLevel(0, &surf)) + { + ColorFill(surf, 0.0f, 0.0f, 0.0f); + delete surf; + } + } + TempRenderTexture = RenderTexture[0]; + CurrRenderTexture = 0; + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreatePaletteTexture +// +//========================================================================== + +bool OpenGLSWFrameBuffer::CreatePaletteTexture() +{ + if (!CreateTexture(256, 1, 1, GL_RGBA8, &PaletteTexture)) + { + return false; + } + return true; +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: CreateGammaTexture +// +//========================================================================== + +bool OpenGLSWFrameBuffer::CreateGammaTexture() +{ + return false; +} + +//========================================================================== +// +// 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 = 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); + } + else + { + if (igamma != 1) + { + UpdateGammaTexture(igamma); + GammaShader = Shaders[SHADER_GammaCorrection]; + } + else + { + GammaShader = nullptr; + } + } + psgamma[2] = psgamma[1] = psgamma[0] = igamma; + psgamma[3] = 0.5; // For SM14 version + SetPixelShaderConstantF(PSCONST_Gamma, psgamma, 1); + } + + if (NeedPalUpdate) + { + UploadPalette(); + } + + 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(); + DoWindowedGamma(); + + CopyNextFrontBuffer(); + + // Attempt to counter input lag. + if (gl_antilag && BlockSurface[0] != nullptr) + { + LockedRect lr; + volatile int dummy; + ColorFill(BlockSurface[BlockNum], 0.0f, 0x20/255.0f, 0x50/255.0f); + BlockNum ^= 1; + if (BlockSurface[BlockNum]->LockRect(&lr, nullptr, false)) + { + dummy = *(int *)lr.pBits; + BlockSurface[BlockNum]->UnlockRect(); + } + } + // Limiting the frame rate is as simple as waiting for the timer to signal this event. + if (FPSLimitEvent != nullptr) + { + WaitForSingleObject(FPSLimitEvent, 1000); + } + Present(); + InScene = false; + + if (RenderTextureToggle) + { + // Flip the TempRenderTexture to the other one now. + CurrRenderTexture ^= RenderTextureToggle; + TempRenderTexture = RenderTexture[CurrRenderTexture]; + } + + 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 :: CopyNextFrontBuffer +// +// Duplicates the contents of the back buffer that will become the front +// buffer upon Present into FrontCopySurface so that we can get the +// contents of the display without wasting time in GetFrontBufferData(). +// +//========================================================================== + +void OpenGLSWFrameBuffer::CopyNextFrontBuffer() +{ + HWSurface *backbuff; + + if (Windowed || PixelDoubling) + { + // Windowed mode or pixel doubling: TempRenderTexture has what we want + SafeRelease(FrontCopySurface); + if (TempRenderTexture->GetSurfaceLevel(0, &backbuff)) + { + FrontCopySurface = backbuff; + } + } + else + { + // Fullscreen, not pixel doubled: The back buffer has what we want, + // but it might be letter boxed. + if (GetBackBuffer(&backbuff)) + { + LTRBRect srcrect = { 0, LBOffsetI, Width, LBOffsetI + Height }; + StretchRect(backbuff, &srcrect, FrontCopySurface); + delete backbuff; + } + } +} + +//========================================================================== +// +// 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) + { + LTRBRect texrect = { 0, 0, Width, Height }; + LockedRect lockrect; + + if ((FBWidth == Width && FBHeight == Height && + FBTexture->LockRect(&lockrect, nullptr, true)) || + FBTexture->LockRect(&lockrect, &texrect, false)) + { + if (lockrect.Pitch == Pitch && Pitch == Width) + { + memcpy(lockrect.pBits, MemBuffer, Width * Height); + } + else + { + uint8_t *dest = (uint8_t *)lockrect.pBits; + uint8_t *src = MemBuffer; + for (int y = 0; y < Height; y++) + { + memcpy(dest, src, Width); + dest += lockrect.Pitch; + src += Pitch; + } + } + FBTexture->UnlockRect(); + } + } + InScene = true; + if (vid_hwaalines) + glEnable(GL_LINE_SMOOTH); + else + glDisable(GL_LINE_SMOOTH); + assert(OldRenderTarget == nullptr); + if (TempRenderTexture != nullptr && + ((Windowed && TempRenderTexture != FinalWipeScreen) || GatheringWipeScreen || PixelDoubling)) + { + HWSurface *targetsurf; + if (TempRenderTexture->GetSurfaceLevel(0, &targetsurf)) + { + if (GetRenderTarget(0, &OldRenderTarget)) + { + SetRenderTarget(0, targetsurf); + } + delete targetsurf; + } + } + + 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); + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: DoWindowedGamma +// +// Draws the render target texture to the real back buffer using a gamma- +// correcting pixel shader. +// +//========================================================================== + +void OpenGLSWFrameBuffer::DoWindowedGamma() +{ + if (OldRenderTarget != nullptr) + { + FBVERTEX verts[4]; + + CalcFullscreenCoords(verts, false, true, 0, 0xFFFFFFFF); + SetRenderTarget(0, OldRenderTarget); + SetTexture(0, TempRenderTexture); + SetPixelShader(Windowed && GammaShader ? GammaShader : Shaders[SHADER_NormalColor]); + SetAlphaBlend(0); + EnableAlphaTest(FALSE); + DrawTriangleFans(2, verts); + delete OldRenderTarget; + OldRenderTarget = nullptr; + } +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: UpdateGammaTexture +// +// Updates the gamma texture used by the PS14 shader. We only use the first +// half of the texture so that we needn't worry about imprecision causing +// it to grab from the border. +// +//========================================================================== + +void OpenGLSWFrameBuffer::UpdateGammaTexture(float igamma) +{ + LockedRect lockrect; + + if (GammaTexture != nullptr && GammaTexture->LockRect(&lockrect, nullptr, false)) + { + uint8_t *pix = (uint8_t *)lockrect.pBits; + for (int i = 0; i <= 128; ++i) + { + pix[i * 4 + 2] = pix[i * 4 + 1] = pix[i * 4] = uint8_t(255.f * powf(i / 128.f, igamma)); + pix[i * 4 + 3] = 255; + } + GammaTexture->UnlockRect(); + } +} + +void OpenGLSWFrameBuffer::UploadPalette() +{ + LockedRect lockrect; + + if (SkipAt < 0) + { + SkipAt = 256; + } + if (PaletteTexture->LockRect(&lockrect, nullptr, false)) + { + uint8_t *pix = (uint8_t *)lockrect.pBits; + int i; + + for (i = 0; i < SkipAt; ++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; + } + PaletteTexture->UnlockRect(); + BorderColor = ColorXRGB(SourcePalette[255].r, SourcePalette[255].g, SourcePalette[255].b); + } +} + +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(); + } +} + +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) +{ + 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(); + } + if (ScreenshotSurface != nullptr) + { + ScreenshotSurface->UnlockRect(); + delete ScreenshotSurface; + ScreenshotSurface = nullptr; + } + SafeRelease(ScreenshotTexture); +} + +//========================================================================== +// +// OpenGLSWFrameBuffer :: GetCurrentScreen +// +// Returns a texture containing the pixels currently visible on-screen. +// +//========================================================================== + +OpenGLSWFrameBuffer::HWTexture *OpenGLSWFrameBuffer::GetCurrentScreen() +{ + HWTexture *tex; + HWSurface *surf; + bool hr; + + if (FrontCopySurface == nullptr) + { + return nullptr; + } + + hr = CreateTexture(FBWidth, FBHeight, 1, GL_RGBA8, &tex); + + if (!hr) + { + return nullptr; + } + if (!tex->GetSurfaceLevel(0, &surf)) + { + delete tex; + return nullptr; + } + + // Video -> System memory : use GetRenderTargetData + GetRenderTargetData(FrontCopySurface, surf); + delete surf; + + if (!hr) + { + delete tex; + return nullptr; + } + return tex; +} + +/**************************************************************************/ +/* 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->Group1 = 0; + 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(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() +{ + LockedRect lrect; + 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->LockRect(&lrect, &rect, false)) + { + return false; + } + dest = (uint8_t *)lrect.pBits; + if (Box->Padded) + { + dest += lrect.Pitch + (format == GL_R8 ? 1 : 4); + } + GameTex->FillBuffer(dest, lrect.Pitch, GameTex->GetHeight(), ToTexFmt(format)); + if (Box->Padded) + { + // Clear top padding row. + dest = (uint8_t *)lrect.pBits; + int numbytes = GameTex->GetWidth() + 2; + if (format != GL_R8) + { + numbytes <<= 2; + } + memset(dest, 0, numbytes); + dest += lrect.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 += lrect.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 += lrect.Pitch; + } + } + // Clear bottom padding row. + memset(dest, 0, numbytes); + } + Box->Owner->Tex->UnlockRect(); + 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(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() +{ + LockedRect lrect; + uint32_t *buff; + const PalEntry *pal; + int skipat, i; + + assert(Tex != nullptr); + + if (!Tex->LockRect(&lrect, nullptr, 0)) + { + return false; + } + buff = (uint32_t *)lrect.pBits; + 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); + + Tex->UnlockRect(); + 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->Group1 = 0; + 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->Group1 = 0; + 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->Group1 = 0; + 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->Group1 != q2->Group1 || + 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->Handle); + 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 000000000..30e7715ab --- /dev/null +++ b/src/gl/system/gl_swframebuffer.h @@ -0,0 +1,470 @@ +#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; + }; + //#define D3DFVF_FBVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_TEX1) + + struct GammaRamp + { + uint16_t red[256], green[256], blue[256]; + }; + + struct LockedRect + { + int Pitch; + void *pBits; + }; + + struct LTRBRect + { + int left, top, right, bottom; + }; + + class HWSurface + { + public: + bool LockRect(LockedRect *outRect, LTRBRect *srcRect, bool discard) { outRect->Pitch = 0; outRect->pBits = nullptr; return false; } + void UnlockRect() { } + }; + + class HWTexture + { + public: + bool LockRect(LockedRect *outRect, LTRBRect *srcRect, bool discard) { outRect->Pitch = 0; outRect->pBits = nullptr; return false; } + void UnlockRect() { } + bool GetSurfaceLevel(int level, HWSurface **outSurface) { *outSurface = nullptr; return false; } + + int Handle = 0; + int WrapS = 0; + int WrapT = 0; + int Format = 0; + }; + + class HWVertexBuffer + { + public: + FBVERTEX *Lock() { return nullptr; } + void Unlock() { } + }; + + class HWIndexBuffer + { + public: + uint16_t *Lock() { return nullptr; } + void Unlock() { } + }; + + class HWPixelShader + { + public: + }; + + bool CreatePixelShader(const void *src, HWPixelShader **outShader) { *outShader = nullptr; return false; } + bool CreateVertexBuffer(int size, HWVertexBuffer **outVertexBuffer) { *outVertexBuffer = nullptr; return false; } + bool CreateIndexBuffer(int size, HWIndexBuffer **outIndexBuffer) { *outIndexBuffer = nullptr; return false; } + bool CreateOffscreenPlainSurface(int width, int height, int format, HWSurface **outSurface) { *outSurface = nullptr; return false; } + bool CreateTexture(int width, int height, int levels, int format, HWTexture **outTexture) { *outTexture = nullptr; return false; } + bool CreateRenderTarget(int width, int height, int format, HWSurface **outSurface) { *outSurface = nullptr; return false; } + bool GetBackBuffer(HWSurface **outSurface) { *outSurface = nullptr; return false; } + bool GetRenderTarget(int index, HWSurface **outSurface) { *outSurface = nullptr; return false; } + void GetRenderTargetData(HWSurface *a, HWSurface *b) { } + void ColorFill(HWSurface *surface, float red, float green, float blue) { } + void StretchRect(HWSurface *src, const LTRBRect *srcrect, HWSurface *dest) { } + bool SetRenderTarget(int index, HWSurface *surface) { return true; } + 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 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 a, uint32_t r, uint32_t g, uint32_t b) { 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 + { + union + { + struct + { + uint8_t Flags; + uint8_t ShaderNum : 4; + int BlendOp; + int SrcBlend, DestBlend; + }; + uint32_t Group1; + }; + 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. + }; + + enum + { + PSCONST_Desaturation = 1, + PSCONST_PaletteMod = 2, + PSCONST_Weights = 6, + PSCONST_Gamma = 7, + }; + 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 ShaderNames[NUM_SHADERS]; + + void Flip(); + void SetInitialState(); + bool CreateResources(); + void ReleaseResources(); + bool LoadShaders(); + void CreateBlockSurfaces(); + bool CreateFBTexture(); + bool CreatePaletteTexture(); + bool CreateGammaTexture(); + bool CreateVertexes(); + void UploadPalette(); + void UpdateGammaTexture(float igamma); + void CalcFullscreenCoords(FBVERTEX verts[4], bool viewarea_only, bool can_double, uint32_t color0, uint32_t color1) const; + bool Reset(); + HWTexture *GetCurrentScreen(); + 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 DoWindowedGamma(); + 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(); + void CopyNextFrontBuffer(); + + // 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; } } + + BOOL AlphaTestEnabled; + BOOL AlphaBlendEnabled; + int AlphaBlendOp; + int AlphaSrcBlend; + int AlphaDestBlend; + 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 SkipAt; + int LBOffsetI; + int RenderTextureToggle; + int CurrRenderTexture; + 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; + OpenGLTex *Textures; + Atlas *Atlases; + + HWTexture *FBTexture; + HWTexture *TempRenderTexture, *RenderTexture[2]; + HWTexture *PaletteTexture; + HWTexture *GammaTexture; + HWTexture *ScreenshotTexture; + HWSurface *ScreenshotSurface; + HWSurface *FrontCopySurface; + + HWVertexBuffer *VertexBuffer; + FBVERTEX *VertexData; + HWIndexBuffer *IndexBuffer; + uint16_t *IndexData; + BufferedTris *QuadExtra; + int VertexPos; + int IndexPos; + int QuadBatchPos; + enum { BATCH_None, BATCH_Quads, BATCH_Lines } BatchType; + + HWPixelShader *Shaders[NUM_SHADERS]; + HWPixelShader *GammaShader; + + HWSurface *BlockSurface[2]; + HWSurface *OldRenderTarget; + HWTexture *InitialWipeScreen, *FinalWipeScreen; + + 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/textures/textures.h b/src/textures/textures.h index 477c39ecc..af72dabc0 100644 --- a/src/textures/textures.h +++ b/src/textures/textures.h @@ -292,6 +292,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: