mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-12-16 23:31:10 +00:00
3357 lines
81 KiB
C++
3357 lines
81 KiB
C++
/*
|
|
** 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<Win32Video *>(Video)->GoFullscreen(fullscreen));
|
|
|
|
TrueHeight = height;
|
|
/*if (fullscreen)
|
|
{
|
|
for (Win32Video::ModeInfo *mode = static_cast<Win32Video *>(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 <packnum> 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<OpenGLTex *>(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<OpenGLTex *>(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<OpenGLTex *>(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<OpenGLPal *>(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);
|
|
}
|