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