mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-27 20:20:40 +00:00
- stripped down the palette manager in the GL backend and let it use the data that's managed elsewhere.
This class is only needed to manage the palette textures used by the indexed render mode, all the rest is available globally.
This commit is contained in:
parent
4178d48fb6
commit
758e4ad7cb
9 changed files with 53 additions and 333 deletions
|
@ -23,7 +23,6 @@ extern void Polymost_prepare_loadboard(void);
|
|||
void polymost_outputGLDebugMessage(uint8_t severity, const char* format, ...);
|
||||
|
||||
//void phex(char v, char *s);
|
||||
void uploadbasepalette(int32_t basepalnum);
|
||||
void polymost_drawsprite(int32_t snum);
|
||||
void polymost_drawmaskwall(int32_t damaskwallcnt);
|
||||
void polymost_dorotatespritemodel(int32_t sx, int32_t sy, int32_t z, int16_t a, int16_t picnum, int8_t dashade, uint8_t dapalnum, int32_t dastat, uint8_t daalpha, uint8_t dablend, int32_t uniqid);
|
||||
|
|
|
@ -65,11 +65,6 @@ void paletteSetColorTable(int32_t id, uint8_t const* table, bool notransparency,
|
|||
}
|
||||
remap.Inactive = twodonly; // use Inactive as a marker for the postprocessing so that for pure 2D palettes the creation of shade tables can be skipped.
|
||||
GPalette.UpdateTranslation(TRANSLATION(Translation_BasePalettes, id), &remap);
|
||||
|
||||
// Todo: remove this once the texture code can use GPalette directly
|
||||
#ifdef USE_OPENGL
|
||||
uploadbasepalette(id);
|
||||
#endif
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
@ -225,20 +225,6 @@ void polymost_glreset()
|
|||
|
||||
FileReader GetBaseResource(const char* fn);
|
||||
|
||||
// one-time initialization of OpenGL for polymost
|
||||
static void polymost_glinit()
|
||||
{
|
||||
for (int basepalnum = 0; basepalnum < MAXBASEPALS; ++basepalnum)
|
||||
{
|
||||
uploadbasepalette(basepalnum);
|
||||
}
|
||||
for (int palookupnum = 0; palookupnum < MAXPALOOKUPS; ++palookupnum)
|
||||
{
|
||||
GLInterface.SetPalswapData(palookupnum, paletteGetLookupTable(palookupnum), numshades+1, palookupfog[palookupnum]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void resizeglcheck(void)
|
||||
{
|
||||
//FUK
|
||||
|
@ -277,26 +263,6 @@ static void resizeglcheck(void)
|
|||
GLInterface.SetIdentityMatrix(Matrix_Model);
|
||||
}
|
||||
|
||||
void uploadbasepalette(int32_t basepalnum)
|
||||
{
|
||||
auto remap = GPalette.GetTranslation(Translation_BasePalettes, basepalnum);
|
||||
if (!remap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t basepalWFullBrightInfo[4*256];
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
basepalWFullBrightInfo[i*4+0] = remap->Palette[i].b;
|
||||
basepalWFullBrightInfo[i*4+1] = remap->Palette[i].g;
|
||||
basepalWFullBrightInfo[i*4+2] = remap->Palette[i].r;
|
||||
basepalWFullBrightInfo[i * 4 + 3] = remap->Palette[i].a;
|
||||
}
|
||||
|
||||
GLInterface.SetPaletteData(basepalnum, basepalWFullBrightInfo);
|
||||
}
|
||||
|
||||
//(dpx,dpy) specifies an n-sided polygon. The polygon must be a convex clockwise loop.
|
||||
// n must be <= 8 (assume clipping can double number of vertices)
|
||||
//method: 0:solid, 1:masked(255 is transparent), 2:transluscent #1, 3:transluscent #2
|
||||
|
@ -4659,6 +4625,5 @@ static void PolymostProcessVoxels(void)
|
|||
|
||||
void Polymost_Startup()
|
||||
{
|
||||
polymost_glinit();
|
||||
PolymostProcessVoxels();
|
||||
}
|
||||
|
|
|
@ -1136,14 +1136,14 @@ int32_t polymost_voxdraw(voxmodel_t *m, tspriteptr_t const tspr)
|
|||
int prevClamp = GLInterface.GetClamp();
|
||||
GLInterface.SetClamp(0);
|
||||
#if 1
|
||||
int palId = GLInterface.LookupPalette(curbasepal, globalpal, false);
|
||||
auto palette = GLInterface.GetPaletteData(palId);
|
||||
int palId = TRANSLATION(Translation_Remap + curbasepal, globalpal);
|
||||
auto palette = GPalette.TranslationToTable(palId);
|
||||
if (!m->texIds) m->texIds = new TMap<int, FHardwareTexture*>;
|
||||
auto pTex = m->texIds->CheckKey(palId);
|
||||
FHardwareTexture* htex;
|
||||
if (!pTex)
|
||||
{
|
||||
htex = gloadtex(m->mytex, m->mytexx, m->mytexy, m->is8bit, palette);
|
||||
htex = gloadtex(m->mytex, m->mytexx, m->mytexy, m->is8bit, palette->Palette);
|
||||
m->texIds->Insert(palId, htex);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -45,8 +45,14 @@ private:
|
|||
|
||||
TranslatedTexture * GetTexID(int translation, bool expanded)
|
||||
{
|
||||
auto remap = GPalette.TranslationToTable(translation);
|
||||
translation = remap == nullptr ? 0 : remap->Index;
|
||||
// Allow negative indices to pass through unchanged.
|
||||
// This is needed for allowing the client to allocate slots that aren't matched to a palette, e.g. Build's indexed variants.
|
||||
if (translation >= 0)
|
||||
{
|
||||
auto remap = GPalette.TranslationToTable(translation);
|
||||
translation = remap == nullptr ? 0 : remap->Index;
|
||||
}
|
||||
else translation &= ~0x7fffffff;
|
||||
|
||||
if (translation == 0)
|
||||
{
|
||||
|
|
|
@ -42,12 +42,11 @@
|
|||
#include "imagehelpers.h"
|
||||
#include "v_font.h"
|
||||
#include "palette.h"
|
||||
#include "build.h"
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// The palette manager will contain all palettes being used for texture
|
||||
// creation. It is also responsible for creating palette textures for indexed
|
||||
// rendering.
|
||||
// This class manages the hardware data for the indexed render mode.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
|
@ -62,149 +61,20 @@ PaletteManager::~PaletteManager()
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
void PaletteManager::DeleteAllTextures()
|
||||
{
|
||||
for (auto& pal : palettes)
|
||||
{
|
||||
if (pal.paltexture) delete pal.paltexture;
|
||||
pal.paltexture = nullptr;
|
||||
}
|
||||
for (auto& pal : palswaps)
|
||||
{
|
||||
if (pal.swaptexture) delete pal.swaptexture;
|
||||
pal.swaptexture = nullptr;
|
||||
}
|
||||
if (palswapTexture) delete palswapTexture;
|
||||
palswapTexture = nullptr;
|
||||
}
|
||||
|
||||
void PaletteManager::DeleteAll()
|
||||
{
|
||||
DeleteAllTextures();
|
||||
palettes.Reset();
|
||||
palswaps.Reset();
|
||||
for (auto& pal : palettetextures)
|
||||
{
|
||||
if (pal) delete pal;
|
||||
pal = nullptr;
|
||||
}
|
||||
for (auto& pal : palswaptextures)
|
||||
{
|
||||
if (pal) delete pal;
|
||||
pal = nullptr;
|
||||
}
|
||||
lastindex = ~0u;
|
||||
lastsindex = ~0u;
|
||||
memset(palettemap, 0, sizeof(palettemap));
|
||||
memset(palswapmap, 0, sizeof(palswapmap));
|
||||
numshades = 1;
|
||||
|
||||
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// Adds a new palette while looking for duplicates.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
unsigned PaletteManager::FindPalette(const uint8_t *paldata)
|
||||
{
|
||||
auto crc32 = CalcCRC32(paldata, 1024);
|
||||
for (unsigned int i = 0; i< palettes.Size(); i++)
|
||||
{
|
||||
if (crc32 == palettes[i].crc32)
|
||||
{
|
||||
if (!memcmp(paldata, palettes[i].colors, 1024))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
PaletteData pd;
|
||||
memcpy(pd.colors, paldata, 1024);
|
||||
pd.crc32 = crc32;
|
||||
pd.paltexture = nullptr;
|
||||
pd.shadesdone = false;
|
||||
return palettes.Push(pd);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// Adds a new palette while looking for duplicates.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
unsigned PaletteManager::FindPalswap(const uint8_t* paldata, palette_t &fadecolor)
|
||||
{
|
||||
if (paldata == nullptr) return 0;
|
||||
auto crc32 = CalcCRC32(paldata, 256 * numshades);
|
||||
for (unsigned int i = 0; i < palswaps.Size(); i++)
|
||||
{
|
||||
if (crc32 == palswaps[i].crc32)
|
||||
{
|
||||
if (!memcmp(paldata, palswaps[i].lookup, 256 * numshades))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
PalswapData pd;
|
||||
pd.lookup = paldata;
|
||||
pd.crc32 = crc32;
|
||||
pd.swaptexture = nullptr;
|
||||
memset(pd.brightcolors, 0, 256);
|
||||
pd.isbright = false;
|
||||
|
||||
for (int i = 0; i < 255; i++)
|
||||
{
|
||||
int firstmap = paldata[i];
|
||||
int lastmap = paldata[i + 256 * (numshades - 2)];
|
||||
|
||||
PalEntry color1 = palettes[palettemap[0]].colors[firstmap];
|
||||
PalEntry color2 = palettes[palettemap[0]].colors[lastmap];
|
||||
int lum1 = color1.Amplitude();
|
||||
int lum2 = color2.Amplitude();
|
||||
if (lum1 > 40 && lum2 * 10 >= lum1 * 9)
|
||||
{
|
||||
pd.brightcolors[i] = 255;
|
||||
pd.isbright = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (fadecolor.f == 0)
|
||||
{
|
||||
// Find what index maps to black (or the darkest available color)
|
||||
int found = -1;
|
||||
PalEntry foundColor = 0xffffffff;
|
||||
for (int i = 0; i < 255; i++)
|
||||
{
|
||||
int map = paldata[i];
|
||||
PalEntry color = palettes[palettemap[0]].colors[map];
|
||||
if (color.Luminance() < foundColor.Luminance())
|
||||
{
|
||||
foundColor = color;
|
||||
found = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the fade color. We pick what black, or the darkest color, maps to in the lowest shade level.
|
||||
int map = paldata[(numshades - 2) * 256 + found]; // do not look in the latest shade level because it doesn't always contain useful data for this.
|
||||
pd.fadeColor = palettes[palettemap[0]].colors[map];
|
||||
if (pd.fadeColor.Luminance() < 10) pd.fadeColor = 0; // Account for the inability to check the last fade level by using a higher threshold for determining black fog.
|
||||
}
|
||||
else
|
||||
{
|
||||
pd.fadeColor = PalEntry(fadecolor.r, fadecolor.g, fadecolor.b);
|
||||
}
|
||||
|
||||
return palswaps.Push(pd);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void PaletteManager::SetPalette(int index, const uint8_t* data)
|
||||
{
|
||||
// New palettes may only be added if declared transient or on startup.
|
||||
// Otherwise this would require a renderer reset to flush out the textures affected by the change.
|
||||
|
||||
if (index < 0 || index > 255) return; // invalid index - ignore.
|
||||
palettemap[index] = FindPalette(data);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
@ -215,21 +85,28 @@ void PaletteManager::SetPalette(int index, const uint8_t* data)
|
|||
|
||||
void PaletteManager::BindPalette(int index)
|
||||
{
|
||||
if (palettemap[index] < palettes.Size())
|
||||
auto palettedata = GPalette.GetTranslation(Translation_BasePalettes, index);
|
||||
if (palettedata == nullptr)
|
||||
{
|
||||
auto uindex = palettemap[index];
|
||||
if (uindex != lastindex)
|
||||
index = 0;
|
||||
palettedata = GPalette.GetTranslation(Translation_BasePalettes, index);
|
||||
};
|
||||
|
||||
if (palettedata)
|
||||
{
|
||||
if (index != lastindex)
|
||||
{
|
||||
lastindex = uindex;
|
||||
if (palettes[uindex].paltexture == nullptr)
|
||||
lastindex = index;
|
||||
|
||||
if (palettetextures[index] == nullptr)
|
||||
{
|
||||
auto p = GLInterface.NewTexture();
|
||||
p->CreateTexture(256, 1, FHardwareTexture::TrueColor, false);
|
||||
p->LoadTexture((uint8_t*)palettes[uindex].colors);
|
||||
p->LoadTexture((uint8_t*)palettedata->Palette);
|
||||
p->SetSampler(SamplerNoFilterClampXY);
|
||||
palettes[uindex].paltexture = p;
|
||||
palettetextures[index] = p;
|
||||
}
|
||||
inst->BindTexture(2, palettes[uindex].paltexture);
|
||||
inst->BindTexture(2, palettetextures[index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,92 +118,27 @@ void PaletteManager::BindPalette(int index)
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
void PaletteManager::SetPalswapData(int index, const uint8_t* data, int numshades_, palette_t &fadecolor)
|
||||
{
|
||||
if (index < 0 || index > 255) return; // invalid index - ignore.
|
||||
numshades = numshades_;
|
||||
palswapmap[index] = FindPalswap(data, fadecolor);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void PaletteManager::BindPalswap(int index)
|
||||
{
|
||||
if (palswapmap[index] < palswaps.Size())
|
||||
if (LookupTables[index].Len() == 0) index = 0;
|
||||
if (LookupTables[index].Len() > 0)
|
||||
{
|
||||
auto uindex = palswapmap[index];
|
||||
if (uindex != lastsindex)
|
||||
if (index != lastsindex)
|
||||
{
|
||||
lastsindex = uindex;
|
||||
auto& ps = palswaps[uindex];
|
||||
if (ps.swaptexture == nullptr)
|
||||
lastsindex = index;
|
||||
if (palswaptextures[index] == nullptr)
|
||||
{
|
||||
auto p = GLInterface.NewTexture();
|
||||
p->CreateTexture(256, numshades, FHardwareTexture::Indexed, false);
|
||||
p->LoadTexture((uint8_t*)ps.lookup);
|
||||
p->LoadTexture((uint8_t*)LookupTables[index].GetChars());
|
||||
p->SetSampler(SamplerNoFilterClampXY);
|
||||
ps.swaptexture = p;
|
||||
palswaptextures[index] = p;
|
||||
}
|
||||
inst->BindTexture(1, ps.swaptexture);
|
||||
inst->SetFadeColor(ps.fadeColor);
|
||||
inst->BindTexture(1, palswaptextures[index]);
|
||||
inst->SetFadeColor(PalEntry(palookupfog[index].r, palookupfog[index].g, palookupfog[index].b));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
int PaletteManager::LookupPalette(int palette, int palswap, bool brightmap, bool nontransparent255)
|
||||
{
|
||||
int realpal = palettemap[palette];
|
||||
int realswap = palswapmap[palswap];
|
||||
int combined = (nontransparent255? 0x2000000 : 0) + (brightmap? 0x1000000 : 0) + realpal * 0x10000 + realswap;
|
||||
int* combinedindex = swappedpalmap.CheckKey(combined);
|
||||
if (combinedindex) return *combinedindex;
|
||||
|
||||
PaletteData* paldata = &palettes[realpal];
|
||||
PalswapData* swapdata = &palswaps[realswap];
|
||||
PalEntry swappedpalette[256];
|
||||
int end = paldata->colors[255].a == 255 ? 256 : 255;
|
||||
if (!brightmap)
|
||||
{
|
||||
for (int i = 0; i < end; i++)
|
||||
{
|
||||
int swapi = swapdata->lookup[i];
|
||||
swappedpalette[i] = paldata->colors[swapi];
|
||||
swappedpalette[i].a = 255;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!swapdata->isbright)
|
||||
{
|
||||
swappedpalmap.Insert(combined, -1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
memset(swappedpalette, 0, sizeof(swappedpalette));
|
||||
for (int i = 0; i < 255; i++)
|
||||
{
|
||||
int swapi = swapdata->brightcolors[i];
|
||||
if (swapi)
|
||||
{
|
||||
found = true;
|
||||
swappedpalette[i] = 0xffffffff;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
swappedpalmap.Insert(combined, -1);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (end == 255) swappedpalette[255] = 0;
|
||||
int palid = FindPalette((uint8_t*)swappedpalette);
|
||||
swappedpalmap.Insert(combined, palid);
|
||||
return palid;
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ void GLInstance::Deinit()
|
|||
if (surfaceShader) delete surfaceShader;
|
||||
surfaceShader = nullptr;
|
||||
activeShader = nullptr;
|
||||
palmanager.DeleteAllTextures();
|
||||
palmanager.DeleteAll();
|
||||
lastPalswapIndex = -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,25 +20,6 @@ class F2DDrawer;
|
|||
struct palette_t;
|
||||
extern int xdim, ydim;
|
||||
|
||||
struct PaletteData
|
||||
{
|
||||
int32_t crc32;
|
||||
PalEntry colors[256];
|
||||
bool shadesdone;
|
||||
int whiteindex, blackindex;
|
||||
FHardwareTexture* paltexture;
|
||||
};
|
||||
|
||||
struct PalswapData
|
||||
{
|
||||
int32_t crc32;
|
||||
bool isbright;
|
||||
const uint8_t *lookup; // points to the original data. This is static so no need to copy
|
||||
FHardwareTexture* swaptexture;
|
||||
PalEntry fadeColor;
|
||||
uint8_t brightcolors[255];
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PALSWAP_TEXTURE_SIZE = 2048
|
||||
|
@ -46,18 +27,12 @@ enum
|
|||
|
||||
class PaletteManager
|
||||
{
|
||||
// The current engine limit is 256 palettes and 256 palswaps.
|
||||
uint32_t palettemap[256] = {};
|
||||
uint32_t palswapmap[256] = {};
|
||||
FHardwareTexture* palettetextures[256] = {};
|
||||
FHardwareTexture* palswaptextures[256] = {};
|
||||
|
||||
uint32_t lastindex = ~0u;
|
||||
uint32_t lastsindex = ~0u;
|
||||
int numshades = 1;
|
||||
|
||||
// All data is being stored in contiguous blocks that can be used as uniform buffers as-is.
|
||||
TArray<PaletteData> palettes;
|
||||
TArray<PalswapData> palswaps;
|
||||
TMap<int, int> swappedpalmap;
|
||||
FHardwareTexture* palswapTexture = nullptr;
|
||||
GLInstance* const inst;
|
||||
|
||||
//OpenGLRenderer::GLDataBuffer* palswapBuffer = nullptr;
|
||||
|
@ -69,17 +44,8 @@ public:
|
|||
{}
|
||||
~PaletteManager();
|
||||
void DeleteAll();
|
||||
void DeleteAllTextures();
|
||||
void SetPalette(int index, const uint8_t *data);
|
||||
void SetPalswapData(int index, const uint8_t* data, int numshades, palette_t &fadecolor);
|
||||
|
||||
void BindPalette(int index);
|
||||
void BindPalswap(int index);
|
||||
int ActivePalswap() const { return lastsindex; }
|
||||
int LookupPalette(int palette, int palswap, bool brightmap, bool nontransparent255 = false);
|
||||
const PalEntry *GetPaletteData(int palid) const { return palettes[palid].colors; }
|
||||
unsigned FindPalette(const uint8_t* paldata);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -230,16 +196,6 @@ public:
|
|||
renderState.mBias.mChanged = true;
|
||||
}
|
||||
|
||||
void SetPaletteData(int index, const uint8_t* data)
|
||||
{
|
||||
palmanager.SetPalette(index, data);
|
||||
}
|
||||
|
||||
void SetPalswapData(int index, const uint8_t* data, int numshades, palette_t& fadecolor)
|
||||
{
|
||||
palmanager.SetPalswapData(index, data, numshades, fadecolor);
|
||||
}
|
||||
|
||||
void SetPalswap(int index);
|
||||
|
||||
int GetClamp()
|
||||
|
@ -508,11 +464,6 @@ public:
|
|||
renderState.fullscreenTint = color;
|
||||
}
|
||||
|
||||
int GetPaletteIndex(PalEntry* palette)
|
||||
{
|
||||
return palmanager.FindPalette((uint8_t*)palette);
|
||||
}
|
||||
|
||||
void EnableAlphaTest(bool on)
|
||||
{
|
||||
renderState.AlphaTest = on;
|
||||
|
@ -523,13 +474,6 @@ public:
|
|||
renderState.AlphaThreshold = al;
|
||||
}
|
||||
|
||||
int LookupPalette(int palette, int palswap, bool brightmap, bool nontransparent255 = false)
|
||||
{
|
||||
return palmanager.LookupPalette(palette, palswap, brightmap, nontransparent255);
|
||||
}
|
||||
const PalEntry* GetPaletteData(int palid) const { return palmanager.GetPaletteData(palid); }
|
||||
|
||||
|
||||
FHardwareTexture* CreateIndexedTexture(FTexture* tex);
|
||||
FHardwareTexture* CreateTrueColorTexture(FTexture* tex, int palid, bool checkfulltransparency = false, bool rgb8bit = false);
|
||||
FHardwareTexture *LoadTexture(FTexture* tex, int texturetype, int palid);
|
||||
|
|
|
@ -198,11 +198,10 @@ void main()
|
|||
palettedColor.rgb = mix(palettedColor.rgb, palettedColorNext.rgb, shadeFrac);
|
||||
}
|
||||
|
||||
fullbright = palettedColor.a; // This only gets set for paletted rendering.
|
||||
palettedColor.a = c_one-floor(color.r);
|
||||
palettedColor.a = color.r == 0.0? 0.0 : 1.0;// c_one-floor(color.r);
|
||||
color = palettedColor;
|
||||
color.rgb *= detailColor.rgb; // with all this palettizing, this can only be applied afterward, even though it is wrong to do it this way.
|
||||
if (fullbright == 0.0) color.rgb *= v_color.rgb; // Well, this is dead wrong but unavoidable. For colored fog it applies the light to the fog as well...
|
||||
color.rgb *= v_color.rgb; // Well, this is dead wrong but unavoidable. For colored fog it applies the light to the fog as well...
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue