Index: ps/trunk/source/graphics/TextureManager.cpp
===================================================================
--- ps/trunk/source/graphics/TextureManager.cpp (revision 26799)
+++ ps/trunk/source/graphics/TextureManager.cpp (revision 26800)
@@ -1,1051 +1,1118 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "TextureManager.h"
#include "graphics/Color.h"
#include "graphics/TextureConverter.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/bits.h"
#include "lib/file/vfs/vfs_tree.h"
#include "lib/hash.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#include "maths/MD5.h"
#include "ps/CacheLoader.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
#include
#include
#include
#include
#include
#include
#include
namespace
{
Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(Tex& textureData, const bool hasS3TC)
{
const bool alpha = (textureData.m_Flags & TEX_ALPHA) != 0;
const bool grey = (textureData.m_Flags & TEX_GREY) != 0;
const size_t dxt = textureData.m_Flags & TEX_DXT;
// Some backends don't support BGR as an internal format (like GLES).
// TODO: add a check that the format is internally supported.
if ((textureData.m_Flags & TEX_BGR) != 0)
{
LOGWARNING("Using slow path to convert BGR texture.");
textureData.transform_to(textureData.m_Flags & ~TEX_BGR);
}
if (dxt)
{
if (hasS3TC)
{
switch (dxt)
{
case DXT1A:
return Renderer::Backend::Format::BC1_RGBA_UNORM;
case 1:
return Renderer::Backend::Format::BC1_RGB_UNORM;
case 3:
return Renderer::Backend::Format::BC2_UNORM;
case 5:
return Renderer::Backend::Format::BC3_UNORM;
default:
LOGERROR("Unknown DXT compression.");
return Renderer::Backend::Format::UNDEFINED;
}
}
else
textureData.transform_to(textureData.m_Flags & ~TEX_DXT);
}
switch (textureData.m_Bpp)
{
case 8:
ENSURE(grey);
return Renderer::Backend::Format::L8_UNORM;
case 24:
ENSURE(!alpha);
return Renderer::Backend::Format::R8G8B8_UNORM;
case 32:
ENSURE(alpha);
return Renderer::Backend::Format::R8G8B8A8_UNORM;
default:
LOGERROR("Unsupported BPP: %zu", textureData.m_Bpp);
}
return Renderer::Backend::Format::UNDEFINED;
}
} // anonymous namespace
class CPredefinedTexture
{
public:
const CTexturePtr& GetTexture()
{
return m_Texture;
}
void CreateTexture(
std::unique_ptr backendTexture,
CTextureManagerImpl* textureManager)
{
Renderer::Backend::GL::CTexture* fallback = backendTexture.get();
CTextureProperties props(VfsPath{});
m_Texture = CTexturePtr(new CTexture(
std::move(backendTexture), fallback, props, textureManager));
m_Texture->m_State = CTexture::UPLOADED;
m_Texture->m_Self = m_Texture;
}
private:
CTexturePtr m_Texture;
};
class CSingleColorTexture final : public CPredefinedTexture
{
public:
CSingleColorTexture(const CColor& color, const bool disableGL,
CTextureManagerImpl* textureManager)
: m_Color(color)
{
if (disableGL)
return;
std::stringstream textureName;
textureName << "SingleColorTexture (";
textureName << "R:" << m_Color.r << ", ";
textureName << "G:" << m_Color.g << ", ";
textureName << "B:" << m_Color.b << ", ";
textureName << "A:" << m_Color.a << ")";
std::unique_ptr backendTexture =
g_VideoMode.GetBackendDevice()->CreateTexture2D(
textureName.str().c_str(),
Renderer::Backend::Format::R8G8B8A8_UNORM,
1, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
CreateTexture(std::move(backendTexture), textureManager);
}
void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!GetTexture() || !GetTexture()->GetBackendTexture())
return;
const SColor4ub color32 = m_Color.AsSColor4ub();
// Construct 1x1 32-bit texture
const u8 data[4] =
{
color32.R,
color32.G,
color32.B,
color32.A
};
deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(),
Renderer::Backend::Format::R8G8B8A8_UNORM, data, std::size(data));
}
private:
CColor m_Color;
};
+class CSingleColorTextureCube final : public CPredefinedTexture
+{
+public:
+ CSingleColorTextureCube(const CColor& color, const bool disableGL,
+ CTextureManagerImpl* textureManager)
+ : m_Color(color)
+ {
+ if (disableGL)
+ return;
+
+ std::stringstream textureName;
+ textureName << "SingleColorTextureCube (";
+ textureName << "R:" << m_Color.r << ", ";
+ textureName << "G:" << m_Color.g << ", ";
+ textureName << "B:" << m_Color.b << ", ";
+ textureName << "A:" << m_Color.a << ")";
+
+ std::unique_ptr backendTexture =
+ g_VideoMode.GetBackendDevice()->CreateTexture(
+ textureName.str().c_str(), Renderer::Backend::GL::CTexture::Type::TEXTURE_CUBE,
+ Renderer::Backend::Format::R8G8B8A8_UNORM,
+ 1, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
+ Renderer::Backend::Sampler::Filter::LINEAR,
+ Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, 1);
+ CreateTexture(std::move(backendTexture), textureManager);
+ }
+
+ void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
+ {
+ if (!GetTexture() || !GetTexture()->GetBackendTexture())
+ return;
+
+ const SColor4ub color32 = m_Color.AsSColor4ub();
+ // Construct 1x1 32-bit texture
+ const u8 data[4] =
+ {
+ color32.R,
+ color32.G,
+ color32.B,
+ color32.A
+ };
+
+ for (size_t face = 0; face < 6; ++face)
+ {
+ deviceCommandContext->UploadTexture(
+ GetTexture()->GetBackendTexture(), Renderer::Backend::Format::R8G8B8A8_UNORM,
+ data, std::size(data), 0, face);
+ }
+ }
+
+private:
+ CColor m_Color;
+};
+
class CGradientTexture final : public CPredefinedTexture
{
public:
static const uint32_t WIDTH = 256;
static const uint32_t NUMBER_OF_LEVELS = 9;
CGradientTexture(const CColor& colorFrom, const CColor& colorTo,
const bool disableGL, CTextureManagerImpl* textureManager)
: m_ColorFrom(colorFrom), m_ColorTo(colorTo)
{
if (disableGL)
return;
std::stringstream textureName;
textureName << "GradientTexture";
textureName << " From (";
textureName << "R:" << m_ColorFrom.r << ", ";
textureName << "G:" << m_ColorFrom.g << ", ";
textureName << "B:" << m_ColorFrom.b << ", ";
textureName << "A:" << m_ColorFrom.a << ")";
textureName << " To (";
textureName << "R:" << m_ColorTo.r << ",";
textureName << "G:" << m_ColorTo.g << ",";
textureName << "B:" << m_ColorTo.b << ",";
textureName << "A:" << m_ColorTo.a << ")";
std::unique_ptr backendTexture =
g_VideoMode.GetBackendDevice()->CreateTexture2D(
textureName.str().c_str(),
Renderer::Backend::Format::R8G8B8A8_UNORM,
WIDTH, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE),
NUMBER_OF_LEVELS);
CreateTexture(std::move(backendTexture), textureManager);
}
void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!GetTexture() || !GetTexture()->GetBackendTexture())
return;
std::array, WIDTH> data;
for (uint32_t x = 0; x < WIDTH; ++x)
{
const float t = static_cast(x) / (WIDTH - 1);
const CColor color(
Interpolate(m_ColorFrom.r, m_ColorTo.r, t),
Interpolate(m_ColorFrom.g, m_ColorTo.g, t),
Interpolate(m_ColorFrom.b, m_ColorTo.b, t),
Interpolate(m_ColorFrom.a, m_ColorTo.a, t));
const SColor4ub color32 = color.AsSColor4ub();
data[x][0] = color32.R;
data[x][1] = color32.G;
data[x][2] = color32.B;
data[x][3] = color32.A;
}
for (uint32_t level = 0; level < NUMBER_OF_LEVELS; ++level)
{
deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(),
Renderer::Backend::Format::R8G8B8A8_UNORM, data.data(), (WIDTH >> level) * data[0].size(), level);
// Prepare data for the next level.
const uint32_t nextLevelWidth = (WIDTH >> (level + 1));
if (nextLevelWidth > 0)
{
for (uint32_t x = 0; x < nextLevelWidth; ++x)
data[x] = data[(x << 1)];
// Border values should be the same.
data[nextLevelWidth - 1] = data[(WIDTH >> level) - 1];
}
}
}
private:
CColor m_ColorFrom, m_ColorTo;
};
struct TPhash
{
std::size_t operator()(const CTextureProperties& textureProperties) const
{
std::size_t seed = 0;
hash_combine(seed, m_PathHash(textureProperties.m_Path));
hash_combine(seed, textureProperties.m_AddressModeU);
hash_combine(seed, textureProperties.m_AddressModeV);
hash_combine(seed, textureProperties.m_AnisotropicFilterEnabled);
hash_combine(seed, textureProperties.m_FormatOverride);
hash_combine(seed, textureProperties.m_IgnoreQuality);
return seed;
}
std::size_t operator()(const CTexturePtr& texture) const
{
return this->operator()(texture->m_Properties);
}
private:
std::hash m_PathHash;
};
struct TPequal_to
{
bool operator()(const CTextureProperties& lhs, const CTextureProperties& rhs) const
{
return
lhs.m_Path == rhs.m_Path &&
lhs.m_AddressModeU == rhs.m_AddressModeU &&
lhs.m_AddressModeV == rhs.m_AddressModeV &&
lhs.m_AnisotropicFilterEnabled == rhs.m_AnisotropicFilterEnabled &&
lhs.m_FormatOverride == rhs.m_FormatOverride &&
lhs.m_IgnoreQuality == rhs.m_IgnoreQuality;
}
bool operator()(const CTexturePtr& lhs, const CTexturePtr& rhs) const
{
return this->operator()(lhs->m_Properties, rhs->m_Properties);
}
};
class CTextureManagerImpl
{
friend class CTexture;
public:
CTextureManagerImpl(PIVFS vfs, bool highQuality, bool disableGL) :
m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_DisableGL(disableGL),
m_TextureConverter(vfs, highQuality),
m_DefaultTexture(CColor(0.25f, 0.25f, 0.25f, 1.0f), disableGL, this),
m_ErrorTexture(CColor(1.0f, 0.0f, 1.0f, 1.0f), disableGL, this),
m_WhiteTexture(CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this),
m_TransparentTexture(CColor(0.0f, 0.0f, 0.0f, 0.0f), disableGL, this),
m_AlphaGradientTexture(
- CColor(1.0f, 1.0f, 1.0f, 0.0f), CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this)
+ CColor(1.0f, 1.0f, 1.0f, 0.0f), CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this),
+ m_BlackTextureCube(CColor(0.0f, 0.0f, 0.0f, 1.0f), disableGL, this)
{
// Allow hotloading of textures
RegisterFileReloadFunc(ReloadChangedFileCB, this);
if (disableGL)
return;
Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice();
m_HasS3TC =
backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGB_UNORM) &&
backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGBA_UNORM) &&
backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC2_UNORM) &&
backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC3_UNORM);
}
~CTextureManagerImpl()
{
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
}
const CTexturePtr& GetErrorTexture()
{
return m_ErrorTexture.GetTexture();
}
const CTexturePtr& GetWhiteTexture()
{
return m_WhiteTexture.GetTexture();
}
const CTexturePtr& GetTransparentTexture()
{
return m_TransparentTexture.GetTexture();
}
const CTexturePtr& GetAlphaGradientTexture()
{
return m_AlphaGradientTexture.GetTexture();
}
+ const CTexturePtr& GetBlackTextureCube()
+ {
+ return m_BlackTextureCube.GetTexture();
+ }
+
/**
* See CTextureManager::CreateTexture
*/
CTexturePtr CreateTexture(const CTextureProperties& props)
{
// Construct a new default texture with the given properties to use as the search key
CTexturePtr texture(new CTexture(
nullptr, m_DisableGL ? nullptr : m_DefaultTexture.GetTexture()->GetBackendTexture(),
props, this));
// Try to find an existing texture with the given properties
TextureCache::iterator it = m_TextureCache.find(texture);
if (it != m_TextureCache.end())
return *it;
// Can't find an existing texture - finish setting up this new texture
texture->m_Self = texture;
m_TextureCache.insert(texture);
m_HotloadFiles[props.m_Path].insert(texture);
return texture;
}
CTexturePtr WrapBackendTexture(
std::unique_ptr backendTexture)
{
ENSURE(backendTexture);
Renderer::Backend::GL::CTexture* fallback = backendTexture.get();
CTextureProperties props(VfsPath{});
CTexturePtr texture(new CTexture(
std::move(backendTexture), fallback, props, this));
texture->m_State = CTexture::UPLOADED;
texture->m_Self = texture;
return texture;
}
/**
* Load the given file into the texture object and upload it to OpenGL.
* Assumes the file already exists.
*/
void LoadTexture(const CTexturePtr& texture, const VfsPath& path)
{
if (m_DisableGL)
return;
PROFILE2("load texture");
PROFILE2_ATTR("name: %ls", path.string().c_str());
std::shared_ptr fileData;
size_t fileSize;
const Status loadStatus = g_VFS->LoadFile(path, fileData, fileSize);
if (loadStatus != INFO::OK)
{
LOGERROR("Texture failed to load; \"%s\" %s",
texture->m_Properties.m_Path.string8(), GetStatusAsString(loadStatus).c_str());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
return;
}
texture->m_TextureData = std::make_unique();
Tex& textureData = *texture->m_TextureData;
const Status decodeStatus = textureData.decode(fileData, fileSize);
if (decodeStatus != INFO::OK)
{
LOGERROR("Texture failed to decode; \"%s\" %s",
texture->m_Properties.m_Path.string8(), GetStatusAsString(decodeStatus).c_str());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
return;
}
if (!is_pow2(textureData.m_Width) || !is_pow2(textureData.m_Height))
{
LOGERROR("Texture should have width and height be power of two; \"%s\" %zux%zu",
texture->m_Properties.m_Path.string8(), textureData.m_Width, textureData.m_Height);
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
return;
}
// Initialise base color from the texture
texture->m_BaseColor = textureData.get_average_color();
Renderer::Backend::Format format = Renderer::Backend::Format::UNDEFINED;
if (texture->m_Properties.m_FormatOverride != Renderer::Backend::Format::UNDEFINED)
{
format = texture->m_Properties.m_FormatOverride;
// TODO: it'd be good to remove the override hack and provide information
// via XML.
ENSURE((textureData.m_Flags & TEX_DXT) == 0);
if (format == Renderer::Backend::Format::A8_UNORM)
{
ENSURE(textureData.m_Bpp == 8 && (textureData.m_Flags & TEX_GREY));
}
else if (format == Renderer::Backend::Format::R8G8B8A8_UNORM)
{
ENSURE(textureData.m_Bpp == 32 && (textureData.m_Flags & TEX_ALPHA));
}
else
debug_warn("Unsupported format override.");
}
else
{
format = ChooseFormatAndTransformTextureDataIfNeeded(textureData, m_HasS3TC);
}
if (format == Renderer::Backend::Format::UNDEFINED)
{
LOGERROR("Texture failed to choose format; \"%s\"", texture->m_Properties.m_Path.string8());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
return;
}
const uint32_t width = texture->m_TextureData->m_Width;
const uint32_t height = texture->m_TextureData->m_Height ;
const uint32_t MIPLevelCount = texture->m_TextureData->GetMIPLevels().size();
texture->m_BaseLevelOffset = 0;
Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT);
defaultSamplerDesc.addressModeU = texture->m_Properties.m_AddressModeU;
defaultSamplerDesc.addressModeV = texture->m_Properties.m_AddressModeV;
if (texture->m_Properties.m_AnisotropicFilterEnabled)
{
int maxAnisotropy = 1;
CFG_GET_VAL("textures.maxanisotropy", maxAnisotropy);
const int allowedValues[] = {2, 4, 8, 16};
if (std::find(std::begin(allowedValues), std::end(allowedValues), maxAnisotropy) != std::end(allowedValues))
{
defaultSamplerDesc.anisotropyEnabled = true;
defaultSamplerDesc.maxAnisotropy = maxAnisotropy;
}
}
if (!texture->m_Properties.m_IgnoreQuality)
{
int quality = 2;
CFG_GET_VAL("textures.quality", quality);
if (quality == 1)
{
if (MIPLevelCount > 1 && std::min(width, height) > 8)
texture->m_BaseLevelOffset += 1;
}
else if (quality == 0)
{
if (MIPLevelCount > 2 && std::min(width, height) > 16)
texture->m_BaseLevelOffset += 2;
while (std::min(width >> texture->m_BaseLevelOffset, height >> texture->m_BaseLevelOffset) > 256 &&
MIPLevelCount > texture->m_BaseLevelOffset + 1)
{
texture->m_BaseLevelOffset += 1;
}
defaultSamplerDesc.mipFilter = Renderer::Backend::Sampler::Filter::NEAREST;
defaultSamplerDesc.anisotropyEnabled = false;
}
}
texture->m_BackendTexture = g_VideoMode.GetBackendDevice()->CreateTexture2D(
texture->m_Properties.m_Path.string8().c_str(),
format, (width >> texture->m_BaseLevelOffset), (height >> texture->m_BaseLevelOffset),
defaultSamplerDesc, MIPLevelCount - texture->m_BaseLevelOffset);
}
/**
* Set up some parameters for the loose cache filename code.
*/
void PrepareCacheKey(const CTexturePtr& texture, MD5& hash, u32& version)
{
// Hash the settings, so we won't use an old loose cache file if the
// settings have changed
CTextureConverter::Settings settings = GetConverterSettings(texture);
settings.Hash(hash);
// Arbitrary version number - change this if we update the code and
// need to invalidate old users' caches
version = 1;
}
/**
* Attempts to load a cached version of a texture.
* If the texture is loaded (or there was an error), returns true.
* Otherwise, returns false to indicate the caller should generate the cached version.
*/
bool TryLoadingCached(const CTexturePtr& texture)
{
MD5 hash;
u32 version;
PrepareCacheKey(texture, hash, version);
VfsPath loadPath;
Status ret = m_CacheLoader.TryLoadingCached(texture->m_Properties.m_Path, hash, version, loadPath);
if (ret == INFO::OK)
{
// Found a cached texture - load it
LoadTexture(texture, loadPath);
return true;
}
else if (ret == INFO::SKIPPED)
{
// No cached version was found - we'll need to create it
return false;
}
else
{
ENSURE(ret < 0);
// No source file or archive cache was found, so we can't load the
// real texture at all - return the error texture instead
LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", texture->m_Properties.m_Path.string8());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
return true;
}
}
/**
* Initiates an asynchronous conversion process, from the texture's
* source file to the corresponding loose cache file.
*/
void ConvertTexture(const CTexturePtr& texture)
{
const VfsPath sourcePath = texture->m_Properties.m_Path;
PROFILE2("convert texture");
PROFILE2_ATTR("name: %ls", sourcePath.string().c_str());
MD5 hash;
u32 version;
PrepareCacheKey(texture, hash, version);
const VfsPath looseCachePath = m_CacheLoader.LooseCachePath(sourcePath, hash, version);
CTextureConverter::Settings settings = GetConverterSettings(texture);
m_TextureConverter.ConvertTexture(texture, sourcePath, looseCachePath, settings);
}
bool TextureExists(const VfsPath& path) const
{
return m_VFS->GetFileInfo(m_CacheLoader.ArchiveCachePath(path), 0) == INFO::OK ||
m_VFS->GetFileInfo(path, 0) == INFO::OK;
}
bool GenerateCachedTexture(const VfsPath& sourcePath, VfsPath& archiveCachePath)
{
archiveCachePath = m_CacheLoader.ArchiveCachePath(sourcePath);
CTextureProperties textureProps(sourcePath);
CTexturePtr texture = CreateTexture(textureProps);
CTextureConverter::Settings settings = GetConverterSettings(texture);
if (!m_TextureConverter.ConvertTexture(texture, sourcePath, VfsPath("cache") / archiveCachePath, settings))
return false;
while (true)
{
CTexturePtr textureOut;
VfsPath dest;
bool ok;
if (m_TextureConverter.Poll(textureOut, dest, ok))
return ok;
std::this_thread::sleep_for(std::chrono::microseconds(1));
}
}
bool MakeProgress()
{
// Process any completed conversion tasks
{
CTexturePtr texture;
VfsPath dest;
bool ok;
if (m_TextureConverter.Poll(texture, dest, ok))
{
if (ok)
{
LoadTexture(texture, dest);
}
else
{
LOGERROR("Texture failed to convert: \"%s\"", texture->m_Properties.m_Path.string8());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
}
texture->m_State = CTexture::LOADED;
return true;
}
}
// We'll only push new conversion requests if it's not already busy
bool converterBusy = m_TextureConverter.IsBusy();
if (!converterBusy)
{
// Look for all high-priority textures needing conversion.
// (Iterating over all textures isn't optimally efficient, but it
// doesn't seem to be a problem yet and it's simpler than maintaining
// multiple queues.)
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::HIGH_NEEDS_CONVERTING)
{
// Start converting this texture
(*it)->m_State = CTexture::HIGH_IS_CONVERTING;
ConvertTexture(*it);
return true;
}
}
}
// Try loading prefetched textures from their cache
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::PREFETCH_NEEDS_LOADING)
{
if (TryLoadingCached(*it))
{
(*it)->m_State = CTexture::LOADED;
}
else
{
(*it)->m_State = CTexture::PREFETCH_NEEDS_CONVERTING;
}
return true;
}
}
// If we've got nothing better to do, then start converting prefetched textures.
if (!converterBusy)
{
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::PREFETCH_NEEDS_CONVERTING)
{
(*it)->m_State = CTexture::PREFETCH_IS_CONVERTING;
ConvertTexture(*it);
return true;
}
}
}
return false;
}
bool MakeUploadProgress(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!m_PredefinedTexturesUploaded)
{
m_DefaultTexture.Upload(deviceCommandContext);
m_ErrorTexture.Upload(deviceCommandContext);
m_WhiteTexture.Upload(deviceCommandContext);
m_TransparentTexture.Upload(deviceCommandContext);
m_AlphaGradientTexture.Upload(deviceCommandContext);
+ m_BlackTextureCube.Upload(deviceCommandContext);
m_PredefinedTexturesUploaded = true;
return true;
}
return false;
}
/**
* Compute the conversion settings that apply to a given texture, by combining
* the textures.xml files from its directory and all parent directories
* (up to the VFS root).
*/
CTextureConverter::Settings GetConverterSettings(const CTexturePtr& texture)
{
fs::wpath srcPath = texture->m_Properties.m_Path.string();
std::vector files;
VfsPath p;
for (fs::wpath::iterator it = srcPath.begin(); it != srcPath.end(); ++it)
{
VfsPath settingsPath = p / "textures.xml";
m_HotloadFiles[settingsPath].insert(texture);
CTextureConverter::SettingsFile* f = GetSettingsFile(settingsPath);
if (f)
files.push_back(f);
p = p / GetWstringFromWpath(*it);
}
return m_TextureConverter.ComputeSettings(GetWstringFromWpath(srcPath.leaf()), files);
}
/**
* Return the (cached) settings file with the given filename,
* or NULL if it doesn't exist.
*/
CTextureConverter::SettingsFile* GetSettingsFile(const VfsPath& path)
{
SettingsFilesMap::iterator it = m_SettingsFiles.find(path);
if (it != m_SettingsFiles.end())
return it->second.get();
if (m_VFS->GetFileInfo(path, NULL) >= 0)
{
std::shared_ptr settings(m_TextureConverter.LoadSettings(path));
m_SettingsFiles.insert(std::make_pair(path, settings));
return settings.get();
}
else
{
m_SettingsFiles.insert(std::make_pair(path, std::shared_ptr()));
return NULL;
}
}
static Status ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast(param)->ReloadChangedFile(path);
}
Status ReloadChangedFile(const VfsPath& path)
{
// Uncache settings file, if this is one
m_SettingsFiles.erase(path);
// Find all textures using this file
HotloadFilesMap::iterator files = m_HotloadFiles.find(path);
if (files != m_HotloadFiles.end())
{
// Flag all textures using this file as needing reloading
for (std::set>::iterator it = files->second.begin(); it != files->second.end(); ++it)
{
if (std::shared_ptr texture = it->lock())
{
texture->m_State = CTexture::UNLOADED;
texture->ResetBackendTexture(
nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
}
}
}
return INFO::OK;
}
void ReloadAllTextures()
{
for (const CTexturePtr& texture : m_TextureCache)
{
texture->m_State = CTexture::UNLOADED;
texture->ResetBackendTexture(
nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
}
}
size_t GetBytesUploaded() const
{
size_t size = 0;
for (TextureCache::const_iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
size += (*it)->GetUploadedSize();
return size;
}
void OnQualityChanged()
{
ReloadAllTextures();
}
private:
PIVFS m_VFS;
CCacheLoader m_CacheLoader;
bool m_DisableGL;
CTextureConverter m_TextureConverter;
CSingleColorTexture m_DefaultTexture;
CSingleColorTexture m_ErrorTexture;
CSingleColorTexture m_WhiteTexture;
CSingleColorTexture m_TransparentTexture;
CGradientTexture m_AlphaGradientTexture;
+ CSingleColorTextureCube m_BlackTextureCube;
bool m_PredefinedTexturesUploaded = false;
// Cache of all loaded textures
using TextureCache =
std::unordered_set;
TextureCache m_TextureCache;
// TODO: we ought to expire unused textures from the cache eventually
// Store the set of textures that need to be reloaded when the given file
// (a source file or settings.xml) is modified
using HotloadFilesMap =
std::unordered_map, std::owner_less>>>;
HotloadFilesMap m_HotloadFiles;
// Cache for the conversion settings files
using SettingsFilesMap =
std::unordered_map>;
SettingsFilesMap m_SettingsFiles;
bool m_HasS3TC = false;
};
CTexture::CTexture(
std::unique_ptr texture,
Renderer::Backend::GL::CTexture* fallback,
const CTextureProperties& props, CTextureManagerImpl* textureManager) :
m_BackendTexture(std::move(texture)), m_FallbackBackendTexture(fallback),
m_BaseColor(0), m_State(UNLOADED), m_Properties(props),
m_TextureManager(textureManager)
{
}
CTexture::~CTexture() = default;
void CTexture::UploadBackendTextureIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (IsUploaded())
return;
if (!IsLoaded())
TryLoad();
if (!IsLoaded())
return;
else if (!m_TextureData || !m_BackendTexture)
{
ResetBackendTexture(nullptr, m_TextureManager->GetErrorTexture()->GetBackendTexture());
m_State = UPLOADED;
return;
}
m_UploadedSize = 0;
for (uint32_t textureDataLevel = m_BaseLevelOffset, level = 0; textureDataLevel < m_TextureData->GetMIPLevels().size(); ++textureDataLevel)
{
const Tex::MIPLevel& levelData = m_TextureData->GetMIPLevels()[textureDataLevel];
deviceCommandContext->UploadTexture(m_BackendTexture.get(), m_BackendTexture->GetFormat(),
levelData.data, levelData.dataSize, level++);
m_UploadedSize += levelData.dataSize;
}
m_TextureData.reset();
m_State = UPLOADED;
}
Renderer::Backend::GL::CTexture* CTexture::GetBackendTexture()
{
return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture;
}
const Renderer::Backend::GL::CTexture* CTexture::GetBackendTexture() const
{
return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture;
}
bool CTexture::TryLoad()
{
// If we haven't started loading, then try loading, and if that fails then request conversion.
// If we have already tried prefetch loading, and it failed, bump the conversion request to HIGH priority.
if (m_State == UNLOADED || m_State == PREFETCH_NEEDS_LOADING || m_State == PREFETCH_NEEDS_CONVERTING)
{
if (std::shared_ptr self = m_Self.lock())
{
if (m_State != PREFETCH_NEEDS_CONVERTING && m_TextureManager->TryLoadingCached(self))
m_State = LOADED;
else
m_State = HIGH_NEEDS_CONVERTING;
}
}
return IsLoaded() || IsUploaded();
}
void CTexture::Prefetch()
{
if (m_State == UNLOADED)
{
if (std::shared_ptr self = m_Self.lock())
{
m_State = PREFETCH_NEEDS_LOADING;
}
}
}
void CTexture::ResetBackendTexture(
std::unique_ptr backendTexture,
Renderer::Backend::GL::CTexture* fallbackBackendTexture)
{
m_BackendTexture = std::move(backendTexture);
m_FallbackBackendTexture = fallbackBackendTexture;
}
size_t CTexture::GetWidth() const
{
return GetBackendTexture()->GetWidth();
}
size_t CTexture::GetHeight() const
{
return GetBackendTexture()->GetHeight();
}
bool CTexture::HasAlpha() const
{
const Renderer::Backend::Format format = GetBackendTexture()->GetFormat();
return
format == Renderer::Backend::Format::A8_UNORM ||
format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
format == Renderer::Backend::Format::BC1_RGBA_UNORM ||
format == Renderer::Backend::Format::BC2_UNORM ||
format == Renderer::Backend::Format::BC3_UNORM;
}
u32 CTexture::GetBaseColor() const
{
return m_BaseColor;
}
size_t CTexture::GetUploadedSize() const
{
return m_UploadedSize;
}
// CTextureManager: forward all calls to impl:
CTextureManager::CTextureManager(PIVFS vfs, bool highQuality, bool disableGL) :
m(new CTextureManagerImpl(vfs, highQuality, disableGL))
{
}
CTextureManager::~CTextureManager()
{
delete m;
}
CTexturePtr CTextureManager::CreateTexture(const CTextureProperties& props)
{
return m->CreateTexture(props);
}
CTexturePtr CTextureManager::WrapBackendTexture(
std::unique_ptr backendTexture)
{
return m->WrapBackendTexture(std::move(backendTexture));
}
bool CTextureManager::TextureExists(const VfsPath& path) const
{
return m->TextureExists(path);
}
const CTexturePtr& CTextureManager::GetErrorTexture()
{
return m->GetErrorTexture();
}
const CTexturePtr& CTextureManager::GetWhiteTexture()
{
return m->GetWhiteTexture();
}
const CTexturePtr& CTextureManager::GetTransparentTexture()
{
return m->GetTransparentTexture();
}
const CTexturePtr& CTextureManager::GetAlphaGradientTexture()
{
return m->GetAlphaGradientTexture();
}
+const CTexturePtr& CTextureManager::GetBlackTextureCube()
+{
+ return m->GetBlackTextureCube();
+}
+
bool CTextureManager::MakeProgress()
{
return m->MakeProgress();
}
bool CTextureManager::MakeUploadProgress(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
return m->MakeUploadProgress(deviceCommandContext);
}
bool CTextureManager::GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath)
{
return m->GenerateCachedTexture(path, outputPath);
}
size_t CTextureManager::GetBytesUploaded() const
{
return m->GetBytesUploaded();
}
void CTextureManager::OnQualityChanged()
{
m->OnQualityChanged();
}
Index: ps/trunk/source/graphics/TextureManager.h
===================================================================
--- ps/trunk/source/graphics/TextureManager.h (revision 26799)
+++ ps/trunk/source/graphics/TextureManager.h (revision 26800)
@@ -1,371 +1,376 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_TEXTUREMANAGER
#define INCLUDED_TEXTUREMANAGER
#include "graphics/Texture.h"
#include "lib/file/vfs/vfs.h"
#include "lib/tex/tex.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/backend/gl/Texture.h"
#include
class CTextureProperties;
class CTextureManagerImpl;
/**
* Texture manager with asynchronous loading and automatic DDS conversion/compression.
*
* Input textures can be any format. They will be converted to DDS using settings defined
* in files named "texture.xml", in the same directory as the texture and in its parent
* directories. See CTextureConverter for the XML syntax. The DDS file will be cached
* for faster loading in the future.
*
* Typically the graphics code will initialise many textures at the start of the game,
* mostly for off-screen objects, by calling CreateTexture().
* Loading texture data may be very slow (especially if it needs to be converted
* to DDS), and we don't want the game to become unresponsive.
* CreateTexture therefore returns an object immediately, without loading the
* texture. If the object is never used then the data will never be loaded.
*
* Typically, the renderer will call CTexture::Bind() when it wants to use the
* texture. This will trigger the loading of the texture data. If it can be loaded
* quickly (i.e. there is already a cached DDS version), then it will be loaded before
* the function returns, and the texture can be rendered as normal.
*
* If loading will take a long time, then Bind() binds a default placeholder texture
* and starts loading the texture in the background. It will use the correct texture
* when the renderer next calls Bind() after the load has finished.
*
* It is also possible to prefetch textures which are not being rendered yet, but
* are expected to be rendered soon (e.g. for off-screen terrain tiles).
* These will be loaded in the background, when there are no higher-priority textures
* to load.
*
* The same texture file can be safely loaded multiple times with different GL parameters
* (but this should be avoided whenever possible, as it wastes VRAM).
*
* For release packages, DDS files can be precached by appending ".dds" to their name,
* which will be used instead of doing runtime conversion. This means most players should
* never experience the slow asynchronous conversion behaviour.
* These cache files will typically be packed into an archive for faster loading;
* if no archive cache is available then the source file will be converted and stored
* as a loose cache file on the user's disk.
*/
class CTextureManager
{
NONCOPYABLE(CTextureManager);
public:
/**
* Construct texture manager. vfs must be the VFS instance used for all textures
* loaded from this object.
* highQuality is slower and intended for batch-conversion modes.
* disableGL is intended for tests, and will disable all GL uploads.
*/
CTextureManager(PIVFS vfs, bool highQuality, bool disableGL);
~CTextureManager();
/**
* Create a texture with the given properties.
* The texture data will not be loaded immediately.
*/
CTexturePtr CreateTexture(const CTextureProperties& props);
/**
* Wraps a backend texture.
*/
CTexturePtr WrapBackendTexture(
std::unique_ptr backendTexture);
/**
* Returns a magenta texture. Use this for highlighting errors
* (e.g. missing terrain textures).
*/
const CTexturePtr& GetErrorTexture();
/**
* Returns a single color RGBA texture with CColor(1.0f, 1.0f, 1.0f, 1.0f).
*/
const CTexturePtr& GetWhiteTexture();
/**
* Returns a single color RGBA texture with CColor(0.0f, 0.0f, 0.0f, 0.0f).
*/
const CTexturePtr& GetTransparentTexture();
/**
* Returns a white RGBA texture with alpha gradient.
*/
const CTexturePtr& GetAlphaGradientTexture();
/**
+ * Returns a single color RGBA texture cube with CColor(0.0f, 0.0f, 0.0f, 1.0f).
+ */
+ const CTexturePtr& GetBlackTextureCube();
+
+ /**
* Work on asynchronous texture loading operations, if any.
* Returns true if it did any work.
* The caller should typically loop this per frame until it returns
* false or exceeds the allocated time for this frame.
*/
bool MakeProgress();
/**
* Work on asynchronous texture uploading operations, if any.
* Returns true if it did any work. Mostly the same as MakeProgress.
*/
bool MakeUploadProgress(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/**
* Synchronously converts and compresses and saves the texture,
* and returns the output path (minus a "cache/" prefix). This
* is intended for pre-caching textures in release archives.
* @return true on success
*/
bool GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath);
/**
* Returns true if the given texture exists.
* This tests both for the original and converted filename.
*/
bool TextureExists(const VfsPath& path) const;
/**
* Returns total number of bytes uploaded for all current texture.
*/
size_t GetBytesUploaded() const;
/**
* Should be called on any quality or anisotropic change.
*/
void OnQualityChanged();
private:
CTextureManagerImpl* m;
};
/**
* Represents the filename and GL parameters of a texture,
* for passing to CTextureManager::CreateTexture.
*/
class CTextureProperties
{
friend class CTextureManagerImpl;
friend struct TextureCacheCmp;
friend struct TPequal_to;
friend struct TPhash;
public:
/**
* Use the given texture name, and default GL parameters.
*/
explicit CTextureProperties(const VfsPath& path)
: m_Path(path)
{
}
CTextureProperties(
const VfsPath& path, const Renderer::Backend::Format formatOverride)
: m_Path(path), m_FormatOverride(formatOverride)
{
}
/**
* Set sampler address mode.
*/
void SetAddressMode(const Renderer::Backend::Sampler::AddressMode addressMode)
{
m_AddressModeU = m_AddressModeV = addressMode;
}
/**
* Set sampler address mode separately for different coordinates.
*/
void SetAddressMode(
const Renderer::Backend::Sampler::AddressMode addressModeU,
const Renderer::Backend::Sampler::AddressMode addressModeV)
{
m_AddressModeU = addressModeU;
m_AddressModeV = addressModeV;
}
/**
* The value of max anisotropy is set by options. Though it might make sense
* to add an override.
*/
void SetAnisotropicFilter(const bool enabled) { m_AnisotropicFilterEnabled = enabled; }
// TODO: rather than this static definition of texture properties
// (especially anisotropy), maybe we want something that can be more
// easily tweaked in an Options menu? e.g. the caller just specifies
// "terrain texture mode" and we combine it with the user's options.
// That'd let us dynamically change texture properties easily.
//
// enum EQualityMode
// {
// NONE,
// TERRAIN,
// MODEL,
// GUI
// }
// void SetQuality(EQualityMode mode, float anisotropy, float lodbias, int reducemipmaps, ...);
//
// or something a bit like that.
void SetIgnoreQuality(bool ignore) { m_IgnoreQuality = ignore; }
private:
// Must update TPhash, TPequal_to when changing these fields
VfsPath m_Path;
Renderer::Backend::Sampler::AddressMode m_AddressModeU =
Renderer::Backend::Sampler::AddressMode::REPEAT;
Renderer::Backend::Sampler::AddressMode m_AddressModeV =
Renderer::Backend::Sampler::AddressMode::REPEAT;
bool m_AnisotropicFilterEnabled = false;
Renderer::Backend::Format m_FormatOverride =
Renderer::Backend::Format::UNDEFINED;
bool m_IgnoreQuality = false;
};
/**
* Represents a texture object.
* The texture data may or may not have been loaded yet.
* Before it has been loaded, all operations will act on a default
* 1x1-pixel grey texture instead.
*/
class CTexture
{
NONCOPYABLE(CTexture);
public:
~CTexture();
/**
* Returns the width (in pixels) of the current texture.
*/
size_t GetWidth() const;
/**
* Returns the height (in pixels) of the current texture.
*/
size_t GetHeight() const;
/**
* Returns whether the current texture has an alpha channel.
*/
bool HasAlpha() const;
/**
* Returns the ARGB value of the lowest mipmap level (i.e. the
* average of the whole texture).
* Returns 0 if the texture has no mipmaps.
*/
u32 GetBaseColor() const;
/**
* Returns total number of bytes uploaded for this texture.
*/
size_t GetUploadedSize() const;
/**
* Uploads a texture data to a backend texture if successfully loaded.
* If the texture data hasn't been loaded yet, this may wait a short while to
* load it. If loading takes too long then it will return sooner and the data will
* be loaded in a background thread, so this does not guarantee the texture really
* will be uploaded.
*/
void UploadBackendTextureIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/**
* Returns a backend texture if successfully uploaded, else fallback.
*/
Renderer::Backend::GL::CTexture* GetBackendTexture();
const Renderer::Backend::GL::CTexture* GetBackendTexture() const;
/**
* Attempt to load the texture data quickly, as with
* GetUploadedBackendTextureIfNeeded(). Returns whether the texture data is
* currently loaded (but not uploaded).
*/
bool TryLoad();
/**
* Returns whether the texture data is currently loaded.
*/
bool IsLoaded() const { return m_State == LOADED; }
/**
* Returns whether the texture data is currently uploaded.
*/
bool IsUploaded() const { return m_State == UPLOADED; }
/**
* Activate the prefetching optimisation for this texture.
* Use this if it is likely the texture will be needed in the near future.
* It will be loaded in the background so that it is likely to be ready when
* it is used by Bind().
*/
void Prefetch();
private:
friend class CTextureManagerImpl;
friend class CPredefinedTexture;
friend struct TextureCacheCmp;
friend struct TPequal_to;
friend struct TPhash;
// Only the texture manager can create these
explicit CTexture(
std::unique_ptr texture,
Renderer::Backend::GL::CTexture* fallback,
const CTextureProperties& props, CTextureManagerImpl* textureManager);
void ResetBackendTexture(
std::unique_ptr backendTexture,
Renderer::Backend::GL::CTexture* fallbackBackendTexture);
const CTextureProperties m_Properties;
std::unique_ptr m_BackendTexture;
// It's possible to m_FallbackBackendTexture references m_BackendTexture.
Renderer::Backend::GL::CTexture* m_FallbackBackendTexture = nullptr;
u32 m_BaseColor;
std::unique_ptr m_TextureData;
size_t m_UploadedSize = 0;
uint32_t m_BaseLevelOffset = 0;
enum
{
UNLOADED, // loading has not started
PREFETCH_NEEDS_LOADING, // was prefetched; currently waiting to try loading from cache
PREFETCH_NEEDS_CONVERTING, // was prefetched; currently waiting to be sent to the texture converter
PREFETCH_IS_CONVERTING, // was prefetched; currently being processed by the texture converter
HIGH_NEEDS_CONVERTING, // high-priority; currently waiting to be sent to the texture converter
HIGH_IS_CONVERTING, // high-priority; currently being processed by the texture converter
LOADED, // loading texture data has completed (successfully or not)
UPLOADED // uploading to backend has completed (successfully or not)
} m_State;
CTextureManagerImpl* m_TextureManager;
// Self-reference to let us recover the CTexturePtr for this object.
// (weak pointer to avoid cycles)
std::weak_ptr m_Self;
};
#endif // INCLUDED_TEXTUREMANAGER
Index: ps/trunk/source/renderer/SkyManager.cpp
===================================================================
--- ps/trunk/source/renderer/SkyManager.cpp (revision 26799)
+++ ps/trunk/source/renderer/SkyManager.cpp (revision 26800)
@@ -1,330 +1,340 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "renderer/SkyManager.h"
#include "graphics/LightEnv.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "lib/bits.h"
#include "lib/tex/tex.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStr.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/VideoMode.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/RenderingOptions.h"
#include
SkyManager::SkyManager()
: m_VertexArray(Renderer::Backend::GL::CBuffer::Type::VERTEX, false)
{
CFG_GET_VAL("showsky", m_SkyVisible);
}
void SkyManager::LoadAndUploadSkyTexturesIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
- if (m_SkyCubeMap)
+ if (m_SkyTextureCube)
return;
+ m_SkyTextureCube = g_Renderer.GetTextureManager().GetBlackTextureCube();
+
GPU_SCOPED_LABEL(deviceCommandContext, "Load Sky Textures");
static const CStrW images[NUMBER_OF_TEXTURES + 1] = {
L"front",
L"back",
L"top",
L"top",
L"right",
L"left"
};
/*for (size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); ++i)
{
VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(s_imageNames[i])+L".dds");
CTextureProperties textureProps(path);
textureProps.SetWrap(GL_CLAMP_TO_EDGE);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_SkyTexture[i] = texture;
}*/
///////////////////////////////////////////////////////////////////////////
// HACK: THE HORRIBLENESS HERE IS OVER 9000. The following code is a HUGE hack and will be removed completely
// as soon as all the hardcoded GL_TEXTURE_2D references are corrected in the TextureManager/OGL/tex libs.
Tex textures[NUMBER_OF_TEXTURES + 1];
for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i)
{
VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i]) + L".dds");
std::shared_ptr file;
size_t fileSize;
if (g_VFS->LoadFile(path, file, fileSize) != INFO::OK)
{
path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i]) + L".dds.cached.dds");
if (g_VFS->LoadFile(path, file, fileSize) != INFO::OK)
{
LOGERROR("Error creating sky cubemap '%s', can't load file: '%s'.", m_SkySet.ToUTF8().c_str(), path.string8().c_str());
return;
}
}
if (textures[i].decode(file, fileSize) != INFO::OK ||
textures[i].transform_to((textures[i].m_Flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)) != INFO::OK)
{
LOGERROR("Error creating sky cubemap '%s', can't decode file: '%s'.", m_SkySet.ToUTF8().c_str(), path.string8().c_str());
return;
}
if (!is_pow2(textures[i].m_Width) || !is_pow2(textures[i].m_Height))
{
LOGERROR("Error creating sky cubemap '%s', cube textures should have power of 2 sizes.", m_SkySet.ToUTF8().c_str());
return;
}
if (textures[i].m_Width != textures[0].m_Width || textures[i].m_Height != textures[0].m_Height)
{
LOGERROR("Error creating sky cubemap '%s', cube textures have different sizes.", m_SkySet.ToUTF8().c_str());
return;
}
}
- m_SkyCubeMap = g_VideoMode.GetBackendDevice()->CreateTexture("SkyCubeMap",
- Renderer::Backend::GL::CTexture::Type::TEXTURE_CUBE,
- Renderer::Backend::Format::R8G8B8A8_UNORM, textures[0].m_Width, textures[0].m_Height,
- Renderer::Backend::Sampler::MakeDefaultSampler(
- Renderer::Backend::Sampler::Filter::LINEAR,
- Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, 1);
+ std::unique_ptr skyCubeMap =
+ g_VideoMode.GetBackendDevice()->CreateTexture("SkyCubeMap",
+ Renderer::Backend::GL::CTexture::Type::TEXTURE_CUBE,
+ Renderer::Backend::Format::R8G8B8A8_UNORM, textures[0].m_Width, textures[0].m_Height,
+ Renderer::Backend::Sampler::MakeDefaultSampler(
+ Renderer::Backend::Sampler::Filter::LINEAR,
+ Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, 1);
std::vector rotated;
for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i)
{
u8* data = textures[i].get_data();
// We need to rotate the side if it's looking up or down.
// TODO: maybe it should be done during texture conversion.
if (i == 2 || i == 3)
{
rotated.resize(textures[i].m_DataSize);
for (size_t y = 0; y < textures[i].m_Height; ++y)
{
for (size_t x = 0; x < textures[i].m_Width; ++x)
{
const size_t invX = y;
const size_t invY = textures[i].m_Width - x - 1;
rotated[(y * textures[i].m_Width + x) * 4 + 0] = data[(invY * textures[i].m_Width + invX) * 4 + 0];
rotated[(y * textures[i].m_Width + x) * 4 + 1] = data[(invY * textures[i].m_Width + invX) * 4 + 1];
rotated[(y * textures[i].m_Width + x) * 4 + 2] = data[(invY * textures[i].m_Width + invX) * 4 + 2];
rotated[(y * textures[i].m_Width + x) * 4 + 3] = data[(invY * textures[i].m_Width + invX) * 4 + 3];
}
}
deviceCommandContext->UploadTexture(
- m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
+ skyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
&rotated[0], textures[i].m_DataSize, 0, i);
}
else
{
deviceCommandContext->UploadTexture(
- m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
+ skyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
data, textures[i].m_DataSize, 0, i);
}
}
+
+ m_SkyTextureCube = g_Renderer.GetTextureManager().WrapBackendTexture(std::move(skyCubeMap));
///////////////////////////////////////////////////////////////////////////
}
+Renderer::Backend::GL::CTexture* SkyManager::GetSkyCube()
+{
+ return m_SkyTextureCube->GetBackendTexture();
+}
+
void SkyManager::SetSkySet(const CStrW& newSet)
{
if (newSet == m_SkySet)
return;
- m_SkyCubeMap.reset();
+ m_SkyTextureCube.reset();
m_SkySet = newSet;
}
std::vector SkyManager::GetSkySets() const
{
std::vector skies;
// Find all subdirectories in art/textures/skies
const VfsPath path(L"art/textures/skies/");
DirectoryNames subdirectories;
if (g_VFS->GetDirectoryEntries(path, 0, &subdirectories) != INFO::OK)
{
LOGERROR("Error opening directory '%s'", path.string8());
return std::vector(1, GetSkySet()); // just return what we currently have
}
for(size_t i = 0; i < subdirectories.size(); i++)
skies.push_back(subdirectories[i].string());
sort(skies.begin(), skies.end());
return skies;
}
void SkyManager::RenderSky(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
GPU_SCOPED_LABEL(deviceCommandContext, "Render sky");
if (!m_SkyVisible)
return;
// Do nothing unless SetSkySet was called
- if (m_SkySet.empty() || !m_SkyCubeMap)
+ if (m_SkySet.empty() || !m_SkyTextureCube)
return;
if (m_VertexArray.GetNumberOfVertices() == 0)
CreateSkyCube();
const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera();
CShaderTechniquePtr skytech =
g_Renderer.GetShaderManager().LoadEffect(str_sky_simple);
deviceCommandContext->SetGraphicsPipelineState(
skytech->GetGraphicsPipelineStateDesc());
deviceCommandContext->BeginPass();
Renderer::Backend::GL::CShaderProgram* shader = skytech->GetShader();
- shader->BindTexture(str_baseTex, m_SkyCubeMap.get());
+ shader->BindTexture(str_baseTex, m_SkyTextureCube->GetBackendTexture());
// Translate so the sky center is at the camera space origin.
CMatrix3D translate;
translate.SetTranslation(camera.GetOrientation().GetTranslation());
// Currently we have a hardcoded near plane in the projection matrix.
CMatrix3D scale;
scale.SetScaling(10.0f, 10.0f, 10.0f);
// Rotate so that the "left" face, which contains the brightest part of
// each skymap, is in the direction of the sun from our light
// environment.
CMatrix3D rotate;
rotate.SetYRotation(M_PI + g_Renderer.GetSceneRenderer().GetLightEnv().GetRotation());
shader->Uniform(
str_transform,
camera.GetViewProjection() * translate * rotate * scale);
m_VertexArray.PrepareForRendering();
u8* base = m_VertexArray.Bind(deviceCommandContext);
const GLsizei stride = static_cast(m_VertexArray.GetStride());
shader->VertexPointer(
m_AttributePosition.format, stride, base + m_AttributePosition.offset);
shader->TexCoordPointer(
GL_TEXTURE0, m_AttributeUV.format, stride, base + m_AttributeUV.offset);
deviceCommandContext->Draw(0, m_VertexArray.GetNumberOfVertices());
deviceCommandContext->EndPass();
}
void SkyManager::CreateSkyCube()
{
m_AttributePosition.format = Renderer::Backend::Format::R32G32B32_SFLOAT;
m_VertexArray.AddAttribute(&m_AttributePosition);
m_AttributeUV.format = Renderer::Backend::Format::R32G32B32_SFLOAT;
m_VertexArray.AddAttribute(&m_AttributeUV);
// 6 sides of cube with 6 vertices.
m_VertexArray.SetNumberOfVertices(6 * 6);
m_VertexArray.Layout();
VertexArrayIterator attrPosition = m_AttributePosition.GetIterator();
VertexArrayIterator attrUV = m_AttributeUV.GetIterator();
#define ADD_VERTEX(U, V, W, VX, VY, VZ) \
STMT( \
attrPosition->X = VX; \
attrPosition->Y = VY; \
attrPosition->Z = VZ; \
++attrPosition; \
attrUV->X = U; \
attrUV->Y = V; \
attrUV->Z = W; \
++attrUV;)
// Axis -X
ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
// Axis +X
ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
// Axis -Y
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f);
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f);
// Axis +Y
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f);
// Axis -Z
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f);
// Axis +Z
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f);
#undef ADD_VERTEX
m_VertexArray.Upload();
m_VertexArray.FreeBackingStore();
}
Index: ps/trunk/source/renderer/SkyManager.h
===================================================================
--- ps/trunk/source/renderer/SkyManager.h (revision 26799)
+++ ps/trunk/source/renderer/SkyManager.h (revision 26800)
@@ -1,117 +1,113 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* Sky settings and texture management
*/
#ifndef INCLUDED_SKYMANAGER
#define INCLUDED_SKYMANAGER
#include "graphics/Texture.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/backend/gl/Texture.h"
#include "renderer/VertexArray.h"
#include
#include
/**
* Class SkyManager: Maintain sky settings and textures, and render the sky.
*/
class SkyManager
{
public:
SkyManager();
/**
* Render the sky.
*/
void RenderSky(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/**
* Return the currently selected sky set name.
*/
inline const CStrW& GetSkySet() const
{
return m_SkySet;
}
- Renderer::Backend::GL::CTexture* GetSkyCube()
- {
- return m_SkyCubeMap.get();
- }
+ Renderer::Backend::GL::CTexture* GetSkyCube();
/**
* Set the sky set name.
*/
void SetSkySet(const CStrW& name);
/**
* Return a sorted list of available sky sets, in a form suitable
* for passing to SetSkySet.
*/
std::vector GetSkySets() const;
bool IsSkyVisible() const
{
return m_SkyVisible;
}
void SetSkyVisible(bool value)
{
m_SkyVisible = value;
}
/**
* Load all sky textures from files and upload to GPU.
*/
void LoadAndUploadSkyTexturesIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
private:
void CreateSkyCube();
bool m_SkyVisible = true;
/// Name of current skyset (a directory within art/textures/skies)
CStrW m_SkySet;
// Indices into m_SkyTexture
enum
{
FRONT,
BACK,
RIGHT,
LEFT,
TOP,
NUMBER_OF_TEXTURES
};
// Sky textures
CTexturePtr m_SkyTexture[NUMBER_OF_TEXTURES];
-
- std::unique_ptr m_SkyCubeMap;
+ CTexturePtr m_SkyTextureCube;
VertexArray m_VertexArray;
VertexArray::Attribute m_AttributePosition;
VertexArray::Attribute m_AttributeUV;
};
#endif // INCLUDED_SKYMANAGER