raze/source/core/textures/hightile.cpp

449 lines
14 KiB
C++

/*
** hightile.cpp
** Handling hires replacement definitions
**
**---------------------------------------------------------------------------
** Copyright 2020 Christoph Oelckers
** 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.
**---------------------------------------------------------------------------
**
**
*/
#include "files.h"
#include "zstring.h"
#include "buildtiles.h"
#include "image.h"
#include "palette.h"
#include "m_crc32.h"
#include "build.h"
#include "gamecontrol.h"
#include "palettecontainer.h"
#include "texturemanager.h"
#include "c_dispatch.h"
#include "sc_man.h"
#include "gamestruct.h"
#include "hw_renderstate.h"
#include "skyboxtexture.h"
#include "gamefuncs.h"
enum ETexType
{
TT_INDEXED,
TT_TRUECOLOR,
};
CVARD(Bool, hw_shadeinterpolate, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "enable/disable shade interpolation")
struct HightileReplacement
{
FGameTexture* image;
FVector2 scale;
float alphacut, specpower, specfactor;
uint16_t palnum;
bool issky;
bool indexed;
};
static TMap<int, TArray<HightileReplacement>> tileReplacements;
static TMap<int, TArray<HightileReplacement>> textureReplacements;
static TMap<FGameTexture*, FGameTexture*> deferredChars;
FGameTexture* GetBaseForChar(FGameTexture* t)
{
auto c = deferredChars.CheckKey(t);
if (c) return *c;
return t;
}
void FontCharCreated(FGameTexture* base, FGameTexture* glyph)
{
if (base->GetName().IsNotEmpty())
deferredChars.Insert(glyph, base);
}
//===========================================================================
//
// Replacement textures
//
//===========================================================================
static void AddReplacement(int tilenum, const HightileReplacement& replace)
{
auto& Hightiles = tileReplacements[tilenum];
for (auto& ht : Hightiles)
{
if (replace.palnum == ht.palnum && replace.issky == ht.issky)
{
ht = replace;
return;
}
}
Hightiles.Push(replace);
}
//==========================================================================
//
// Remove a replacement
//
//==========================================================================
void tileRemoveReplacement(int tilenum)
{
tileReplacements.Remove(tilenum);
}
//===========================================================================
//
//
//
//===========================================================================
static HightileReplacement* FindReplacement(FTextureID texid, int palnum, bool skybox)
{
auto Hightiles = textureReplacements.CheckKey(texid.GetIndex());
if (!Hightiles) return nullptr;
for (;;)
{
for (auto& rep : *Hightiles)
{
if (rep.palnum == palnum && rep.issky == skybox) return &rep;
}
if (!palnum || palnum >= MAXPALOOKUPS - RESERVEDPALS) break;
palnum = 0;
}
return nullptr; // no replacement found
}
int checkTranslucentReplacement(FTextureID texid, int pal)
{
FGameTexture* tex = nullptr;
auto si = FindReplacement(texid, pal, 0);
if (si && hw_hightile) tex = si->image;
if (!tex || tex->GetTexelWidth() == 0 || tex->GetTexelHeight() == 0) return false;
return tex && tex->GetTranslucency();
}
FGameTexture* SkyboxReplacement(FTextureID texid, int palnum)
{
auto hr = FindReplacement(texid, palnum, true);
if (!hr) return nullptr;
return hr->image;
}
//==========================================================================
//
// Processes data from .def files into the textures
//
//==========================================================================
void highTileSetup()
{
for(int i=0;i<MAXTILES;i++)
{
auto tex = tileGetTexture(i);
if (!tex->isValid()) continue;
auto Hightile = tileReplacements.CheckKey(i);
if (!Hightile) continue;
textureReplacements.Insert(tex->GetID().GetIndex(), std::move(*Hightile));
}
tileReplacements.Clear();
decltype(deferredChars)::Iterator it(deferredChars);
decltype(deferredChars)::Pair* pair;
while (it.NextPair(pair))
{
auto rep = textureReplacements.CheckKey(pair->Value->GetID().GetIndex());
if (rep)
{
auto myrep = *rep; // don't create copies directly from the map we're inserting in!
auto chk = textureReplacements.CheckKey(pair->Key->GetID().GetIndex());
if (!chk) textureReplacements.Insert(pair->Key->GetID().GetIndex(), std::move(myrep));
}
}
deferredChars.Clear();
decltype(textureReplacements)::Iterator it2(textureReplacements);
decltype(textureReplacements)::Pair* pair2;
while (it2.NextPair(pair2))
{
auto tex = TexMan.GameByIndex(pair2->Key);
if (!tex->isValid()) continue;
auto Hightile = &pair2->Value;
if (!Hightile) continue;
FGameTexture* detailTex = nullptr, * glowTex = nullptr, * normalTex = nullptr, * specTex = nullptr;
float scalex = 1.f, scaley = 1.f;
for (auto& rep : *Hightile)
{
if (rep.palnum == GLOWPAL)
{
glowTex = rep.image;
}
if (rep.palnum == NORMALPAL)
{
normalTex = rep.image;
}
if (rep.palnum == SPECULARPAL)
{
specTex = rep.image;
}
if (rep.palnum == DETAILPAL)
{
detailTex = rep.image;
scalex = rep.scale.X;
scaley = rep.scale.Y;
}
}
if (detailTex || glowTex || normalTex || specTex)
{
for (auto& rep : *Hightile)
{
if (rep.issky) continue; // do not muck around with skyboxes (yet)
if (rep.palnum < NORMALPAL)
{
auto imgtex = rep.image;
// Make a copy so that multiple appearances of the same texture with different layers can be handled. They will all refer to the same internal texture anyway.
imgtex = MakeGameTexture(imgtex->GetTexture(), "", ETextureType::Any);
if (glowTex) imgtex->SetGlowmap(glowTex->GetTexture());
if (detailTex) imgtex->SetDetailmap(detailTex->GetTexture());
if (normalTex) imgtex->SetNormalmap(normalTex->GetTexture());
if (specTex) imgtex->SetSpecularmap(specTex->GetTexture());
imgtex->SetDetailScale(scalex, scaley);
rep.image = imgtex;
}
}
}
}
}
//==========================================================================
//
// Specifies a replacement texture for an ART tile.
//
//==========================================================================
int tileSetHightileReplacement(int tilenum, int palnum, FTextureID texid, float alphacut, float xscale, float yscale, float specpower, float specfactor, bool indexed)
{
// assumes the texture was already validated.
HightileReplacement replace = {};
replace.image = TexMan.GetGameTexture(texid);
replace.alphacut = min(alphacut,1.f);
replace.scale = { xscale, yscale };
replace.specpower = specpower; // currently unused
replace.specfactor = specfactor; // currently unused
replace.issky = 0;
replace.indexed = indexed;
replace.palnum = (uint16_t)palnum;
AddReplacement(tilenum, replace);
return 0;
}
//==========================================================================
//
// Define the faces of a skybox
//
//==========================================================================
int tileSetSkybox(int tilenum, int palnum, FString* facenames, bool indexed)
{
if ((uint32_t)tilenum >= (uint32_t)MAXTILES) return -1;
if ((uint32_t)palnum >= (uint32_t)MAXPALOOKUPS) return -1;
auto tex = tileGetTexture(tilenum);
if (tex->GetTexelWidth() <= 0 || tex->GetTexelHeight() <= 0)
{
Printf("Warning: defined skybox replacement for empty tile %d.", tilenum);
return -1; // cannot add replacements to empty tiles, must create one beforehand
}
HightileReplacement replace = {};
FGameTexture *faces[6];
for (int i = 0; i < 6; i++)
{
FTextureID texid = TexMan.CheckForTexture(facenames[i], ETextureType::Any, FTextureManager::TEXMAN_TryAny | FTextureManager::TEXMAN_ForceLookup);
if (!texid.isValid())
{
Printf("%s: Skybox image for tile %d does not exist or is invalid\n", facenames[i].GetChars(), tilenum);
return -1;
}
faces[i] = TexMan.GetGameTexture(texid);
}
FSkyBox* sbtex = new FSkyBox("");
memcpy(sbtex->faces, faces, sizeof(faces));
sbtex->previous = faces[0]; // won't ever be used, just to be safe.
sbtex->fliptop = true;
replace.image = MakeGameTexture(sbtex, "", ETextureType::Override);
TexMan.AddGameTexture(replace.image, false);
replace.issky = 1;
replace.indexed = indexed;
replace.palnum = (uint16_t)palnum;
AddReplacement(tilenum, replace);
return 0;
}
//===========================================================================
//
// Picks a texture for rendering for a given tilenum/palette combination
//
//===========================================================================
bool PickTexture(FGameTexture* tex, int paletteid, TexturePick& pick, bool wantindexed)
{
if (!tex->isValid() || tex->GetTexelWidth() <= 0 || tex->GetTexelHeight() <= 0) return false;
int usepalette = 0, useremap = 0;
if (!IsLuminosityTranslation(paletteid))
{
usepalette = paletteid == 0 ? 0 : GetTranslationType(paletteid) - Translation_Remap;
useremap = GetTranslationIndex(paletteid);
}
int TextureType = wantindexed? TT_INDEXED : TT_TRUECOLOR;
pick.translation = paletteid;
pick.basepalTint = 0xffffff;
auto& h = lookups.tables[useremap];
bool applytint = false;
// Canvas textures must be treated like hightile replacements in the following code.
int hipalswap = usepalette >= 0 ? useremap : 0;
auto rep = (hw_hightile && !(h.tintFlags & TINTF_ALWAYSUSEART)) ? FindReplacement(tex->GetID(), hipalswap, false) : nullptr;
if (rep || tex->GetTexture()->isHardwareCanvas())
{
if (rep)
{
tex = rep->image;
}
if (rep && rep->indexed && TextureType == TT_INDEXED)
{
pick.translation |= 0x80000000;
}
else if (!rep || !rep->indexed)
{
if (usepalette > 0)
{
// This is a global setting for the entire scene, so let's do it here, right at the start. (Fixme: Store this in a static table instead of reusing the same entry for all palettes.)
auto& hh = lookups.tables[MAXPALOOKUPS - 1];
// This sets a tinting color for global palettes, e.g. water or slime - only used for hires replacements (also an option for low-resource hardware where duplicating the textures may be problematic.)
pick.basepalTint = hh.tintColor;
}
if (!rep || rep->palnum != hipalswap || (h.tintFlags & TINTF_APPLYOVERALTPAL))
applytint = true;
if (!IsLuminosityTranslation(paletteid)) pick.translation = 0;
}
}
else
{
// Only look up the palette if we really want to use it (i.e. when creating a true color texture of an ART tile.)
if (TextureType == TT_TRUECOLOR)
{
if (h.tintFlags & (TINTF_ALWAYSUSEART | TINTF_USEONART))
{
applytint = true;
if (!(h.tintFlags & TINTF_APPLYOVERPALSWAP)) useremap = 0;
}
pick.translation = IsLuminosityTranslation(paletteid)? paletteid : paletteid == 0? 0 : TRANSLATION(usepalette + Translation_Remap, useremap);
}
else pick.translation |= 0x80000000;
}
if (applytint && h.tintFlags)
{
pick.tintFlags = h.tintFlags;
pick.tintColor = h.tintColor;
}
else
{
pick.tintFlags = -1;
pick.tintColor = 0xffffff;
}
pick.texture = tex;
return true;
}
bool PreBindTexture(FRenderState* state, FGameTexture*& tex, EUpscaleFlags& flags, int& scaleflags, int& clampmode, int& translation, int& overrideshader)
{
TexturePick pick;
auto t = tex;
if (tex->GetUseType() == ETextureType::Special) return true;
bool foggy = state && (state->GetFogColor() & 0xffffff);
if (PickTexture(tex, translation, pick, hw_int_useindexedcolortextures && !foggy))
{
int lookuppal = pick.translation & 0x7fffffff;
if (pick.translation & 0x80000000)
{
scaleflags |= CTF_Indexed;
if (state) state->EnableFog(0);
}
tex = pick.texture;
translation = lookuppal;
FVector4 addcol(0, 0, 0, 0);
FVector4 modcol(pick.basepalTint.r * (1.f / 255.f), pick.basepalTint.g * (1.f / 255.f), pick.basepalTint.b * (1.f / 255.f), 0);
FVector4 blendcol(0, 0, 0, 0);
int tmflags = 0;
if (pick.basepalTint != 0xffffff) tmflags |= TextureManipulation::ActiveBit;
if (pick.tintFlags != -1)
{
tmflags |= TextureManipulation::ActiveBit;
if (pick.tintFlags & TINTF_COLORIZE)
{
modcol.X *= pick.tintColor.r * (1.f / 64.f);
modcol.Y *= pick.tintColor.g * (1.f / 64.f);
modcol.Z *= pick.tintColor.b * (1.f / 64.f);
}
if (pick.tintFlags & TINTF_GRAYSCALE)
modcol.W = 1.f;
if (pick.tintFlags & TINTF_INVERT)
tmflags |= TextureManipulation::InvertBit;
if (pick.tintFlags & TINTF_BLENDMASK)
{
blendcol = modcol; // WTF???, but the tinting code really uses the same color for both!
tmflags |= (((pick.tintFlags & TINTF_BLENDMASK) >> 6) + 1) & TextureManipulation::BlendMask;
}
}
addcol.W = (float)tmflags;
if ((pick.translation & 0x80000000) && hw_shadeinterpolate) addcol.W += 16384; // hijack a free bit in here.
state->SetTextureColors(&modcol.X, &addcol.X, &blendcol.X);
}
return tex->GetTexelWidth() > t->GetTexelWidth() && tex->GetTexelHeight() > t->GetTexelHeight(); // returning 'true' means to disable programmatic upscaling.
}