LCOV - code coverage report
Current view: top level - source/graphics - TerrainTextureManager.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 16 142 11.3 %
Date: 2023-01-19 00:18:29 Functions: 5 17 29.4 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "TerrainTextureManager.h"
      21             : 
      22             : #include "graphics/TerrainTextureEntry.h"
      23             : #include "graphics/TerrainProperties.h"
      24             : #include "graphics/TextureManager.h"
      25             : #include "lib/allocators/shared_ptr.h"
      26             : #include "lib/bits.h"
      27             : #include "lib/tex/tex.h"
      28             : #include "lib/timer.h"
      29             : #include "ps/CLogger.h"
      30             : #include "ps/Filesystem.h"
      31             : #include "ps/VideoMode.h"
      32             : #include "ps/XML/Xeromyces.h"
      33             : #include "renderer/backend/IDevice.h"
      34             : #include "renderer/Renderer.h"
      35             : 
      36             : #include <algorithm>
      37             : #include <boost/algorithm/string.hpp>
      38             : #include <vector>
      39             : 
      40           6 : CTerrainTextureManager::CTerrainTextureManager()
      41           6 :     : m_LastGroupIndex(0)
      42             : {
      43           6 :     if (!VfsDirectoryExists(L"art/terrains/"))
      44           6 :         return;
      45           0 :     if (!CXeromyces::AddValidator(g_VFS, "terrain", "art/terrains/terrain.rng"))
      46           0 :         LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain.rng'");
      47           0 :     if (!CXeromyces::AddValidator(g_VFS, "terrain_texture", "art/terrains/terrain_texture.rng"))
      48           0 :         LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain_texture.rng'");
      49             : }
      50             : 
      51          12 : CTerrainTextureManager::~CTerrainTextureManager()
      52             : {
      53           6 :     UnloadTerrainTextures();
      54             : 
      55           6 :     for (std::pair<const VfsPath, TerrainAlpha>& ta : m_TerrainAlphas)
      56           0 :         ta.second.m_CompositeAlphaMap.reset();
      57           6 : }
      58             : 
      59           6 : void CTerrainTextureManager::UnloadTerrainTextures()
      60             : {
      61           6 :     for (CTerrainTextureEntry* const& te : m_TextureEntries)
      62           0 :         delete te;
      63           6 :     m_TextureEntries.clear();
      64             : 
      65           6 :     for (const std::pair<const CStr, CTerrainGroup*>& tg : m_TerrainGroups)
      66           0 :         delete tg.second;
      67           6 :     m_TerrainGroups.clear();
      68             : 
      69           6 :     m_LastGroupIndex = 0;
      70           6 : }
      71             : 
      72           0 : CTerrainTextureEntry* CTerrainTextureManager::FindTexture(const CStr& tag_) const
      73             : {
      74           0 :     CStr tag = tag_.BeforeLast("."); // Strip extension
      75             : 
      76           0 :     for (CTerrainTextureEntry* const& te : m_TextureEntries)
      77           0 :         if (te->GetTag() == tag)
      78           0 :             return te;
      79             : 
      80           0 :     LOGWARNING("CTerrainTextureManager: Couldn't find terrain %s", tag.c_str());
      81           0 :     return 0;
      82             : }
      83             : 
      84           0 : CTerrainTextureEntry* CTerrainTextureManager::AddTexture(const CTerrainPropertiesPtr& props, const VfsPath& path)
      85             : {
      86           0 :     CTerrainTextureEntry* entry = new CTerrainTextureEntry(props, path);
      87           0 :     m_TextureEntries.push_back(entry);
      88           0 :     return entry;
      89             : }
      90             : 
      91           0 : void CTerrainTextureManager::DeleteTexture(CTerrainTextureEntry* entry)
      92             : {
      93           0 :     std::vector<CTerrainTextureEntry*>::iterator it = std::find(m_TextureEntries.begin(), m_TextureEntries.end(), entry);
      94           0 :     if (it != m_TextureEntries.end())
      95           0 :         m_TextureEntries.erase(it);
      96             : 
      97           0 :     delete entry;
      98           0 : }
      99             : 
     100           0 : struct AddTextureCallbackData
     101             : {
     102             :     CTerrainTextureManager* self;
     103             :     CTerrainPropertiesPtr props;
     104             : };
     105             : 
     106           0 : static Status AddTextureDirCallback(const VfsPath& pathname, const uintptr_t cbData)
     107             : {
     108           0 :     AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData;
     109           0 :     VfsPath path = pathname / L"terrains.xml";
     110           0 :     if (!VfsFileExists(path))
     111           0 :         LOGMESSAGE("'%s' does not exist. Using previous properties.", path.string8());
     112             :     else
     113           0 :         data.props = CTerrainProperties::FromXML(data.props, path);
     114             : 
     115           0 :     return INFO::OK;
     116             : }
     117             : 
     118           0 : static Status AddTextureCallback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
     119             : {
     120           0 :     AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData;
     121           0 :     if (pathname.Basename() != L"terrains")
     122           0 :         data.self->AddTexture(data.props, pathname);
     123             : 
     124           0 :     return INFO::OK;
     125             : }
     126             : 
     127           0 : int CTerrainTextureManager::LoadTerrainTextures()
     128             : {
     129           0 :     AddTextureCallbackData data = {this, CTerrainPropertiesPtr(new CTerrainProperties(CTerrainPropertiesPtr()))};
     130           0 :     vfs::ForEachFile(g_VFS, L"art/terrains/", AddTextureCallback, (uintptr_t)&data, L"*.xml", vfs::DIR_RECURSIVE, AddTextureDirCallback, (uintptr_t)&data);
     131           0 :     return 0;
     132             : }
     133             : 
     134           0 : CTerrainGroup* CTerrainTextureManager::FindGroup(const CStr& name)
     135             : {
     136           0 :     TerrainGroupMap::const_iterator it = m_TerrainGroups.find(name);
     137           0 :     if (it != m_TerrainGroups.end())
     138           0 :         return it->second;
     139             :     else
     140           0 :         return m_TerrainGroups[name] = new CTerrainGroup(name, ++m_LastGroupIndex);
     141             : }
     142             : 
     143             : // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and
     144             : // calculate the coordinate of each alphamap within this packed texture.
     145             : CTerrainTextureManager::TerrainAlphaMap::iterator
     146           0 : CTerrainTextureManager::LoadAlphaMap(const VfsPath& alphaMapType)
     147             : {
     148           0 :     const std::wstring key = L"(alpha map composite" + alphaMapType.string() + L")";
     149             : 
     150           0 :     CTerrainTextureManager::TerrainAlphaMap::iterator it = m_TerrainAlphas.find(alphaMapType);
     151             : 
     152           0 :     if (it != g_TexMan.m_TerrainAlphas.end())
     153           0 :         return it;
     154             : 
     155           0 :     m_TerrainAlphas[alphaMapType] = TerrainAlpha();
     156           0 :     it = m_TerrainAlphas.find(alphaMapType);
     157             : 
     158           0 :     TerrainAlpha& result = it->second;
     159             : 
     160             :     //
     161             :     // load all textures and store Handle in array
     162             :     //
     163           0 :     Tex textures[NUM_ALPHA_MAPS] = {};
     164           0 :     const VfsPath path = VfsPath("art/textures/terrain/alphamaps") / alphaMapType;
     165             : 
     166           0 :     const wchar_t* fnames[NUM_ALPHA_MAPS] =
     167             :     {
     168             :         L"blendcircle.png",
     169             :         L"blendlshape.png",
     170             :         L"blendedge.png",
     171             :         L"blendedgecorner.png",
     172             :         L"blendedgetwocorners.png",
     173             :         L"blendfourcorners.png",
     174             :         L"blendtwooppositecorners.png",
     175             :         L"blendlshapecorner.png",
     176             :         L"blendtwocorners.png",
     177             :         L"blendcorner.png",
     178             :         L"blendtwoedges.png",
     179             :         L"blendthreecorners.png",
     180             :         L"blendushape.png",
     181             :         L"blendbad.png"
     182             :     };
     183           0 :     size_t base = 0;    // texture width/height (see below)
     184             :     // For convenience, we require all alpha maps to be of the same BPP.
     185           0 :     size_t bpp = 0;
     186           0 :     for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
     187             :     {
     188             :         // note: these individual textures can be discarded afterwards;
     189             :         // we cache the composite.
     190           0 :         std::shared_ptr<u8> fileData;
     191             :         size_t fileSize;
     192           0 :         if (g_VFS->LoadFile(path / fnames[i], fileData, fileSize) != INFO::OK ||
     193           0 :             textures[i].decode(fileData, fileSize) != INFO::OK)
     194             :         {
     195           0 :             m_TerrainAlphas.erase(it);
     196           0 :             LOGERROR("Failed to load alphamap: %s", alphaMapType.string8());
     197             : 
     198           0 :             const VfsPath standard("standard");
     199           0 :             if (path != standard)
     200           0 :                 return LoadAlphaMap(standard);
     201           0 :             return m_TerrainAlphas.end();
     202             :         }
     203             : 
     204             :         // Get its size and make sure they are all equal.
     205             :         // (the packing algo assumes this).
     206           0 :         if (textures[i].m_Width != textures[i].m_Height)
     207           0 :             DEBUG_DISPLAY_ERROR(L"Alpha maps are not square");
     208             :         // .. first iteration: establish size
     209           0 :         if (i == 0)
     210             :         {
     211           0 :             base = textures[i].m_Width;
     212           0 :             bpp = textures[i].m_Bpp;
     213             :         }
     214             :         // .. not first: make sure texture size matches
     215           0 :         else if (base != textures[i].m_Width || bpp != textures[i].m_Bpp)
     216           0 :             DEBUG_DISPLAY_ERROR(L"Alpha maps are not identically sized (including pixel depth)");
     217             :     }
     218             : 
     219             :     //
     220             :     // copy each alpha map (tile) into one buffer, arrayed horizontally.
     221             :     //
     222           0 :     const size_t tileWidth = 2 + base + 2;  // 2 pixel border (avoids bilinear filtering artifacts)
     223           0 :     const size_t totalWidth = round_up_to_pow2(tileWidth * NUM_ALPHA_MAPS);
     224           0 :     const size_t totalHeight = base; ENSURE(is_pow2(totalHeight));
     225           0 :     std::shared_ptr<u8> data;
     226           0 :     AllocateAligned(data, totalWidth * totalHeight, maxSectorSize);
     227             :     // for each tile on row
     228           0 :     for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
     229             :     {
     230             :         // get src of copy
     231           0 :         u8* src = textures[i].get_data();
     232           0 :         ENSURE(src);
     233             : 
     234           0 :         const size_t srcStep = bpp / 8;
     235             : 
     236             :         // get destination of copy
     237           0 :         u8* dst = data.get() + (i * tileWidth);
     238             : 
     239             :         // for each row of image
     240           0 :         for (size_t j = 0; j < base; ++j)
     241             :         {
     242             :             // duplicate first pixel
     243           0 :             *dst++ = *src;
     244           0 :             *dst++ = *src;
     245             : 
     246             :             // copy a row
     247           0 :             for (size_t k = 0; k < base; ++k)
     248             :             {
     249           0 :                 *dst++ = *src;
     250           0 :                 src += srcStep;
     251             :             }
     252             : 
     253             :             // duplicate last pixel
     254           0 :             *dst++ = *(src - srcStep);
     255           0 :             *dst++ = *(src - srcStep);
     256             : 
     257             :             // advance write pointer for next row
     258           0 :             dst += totalWidth - tileWidth;
     259             :         }
     260             : 
     261           0 :         result.m_AlphaMapCoords[i].u0 = static_cast<float>(i * tileWidth + 2) / totalWidth;
     262           0 :         result.m_AlphaMapCoords[i].u1 = static_cast<float>((i + 1) * tileWidth - 2) / totalWidth;
     263           0 :         result.m_AlphaMapCoords[i].v0 = 0.0f;
     264           0 :         result.m_AlphaMapCoords[i].v1 = 1.0f;
     265             :     }
     266             : 
     267           0 :     for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
     268           0 :         textures[i].free();
     269             : 
     270             :     // Enable the following to save a png of the generated texture
     271             :     // in the public/ directory, for debugging.
     272             : #if 0
     273             :     Tex t;
     274             :     ignore_result(t.wrap(totalWidth, totalHeight, 8, TEX_GREY, data, 0));
     275             : 
     276             :     const VfsPath filename("blendtex.png");
     277             : 
     278             :     DynArray da;
     279             :     RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da));
     280             : 
     281             :     // write to disk
     282             :     //Status ret = INFO::OK;
     283             :     {
     284             :         std::shared_ptr<u8> file = DummySharedPtr(da.base);
     285             :         const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
     286             :         if (bytes_written > 0)
     287             :             ENSURE(bytes_written == (ssize_t)da.pos);
     288             :         //else
     289             :         //  ret = (Status)bytes_written;
     290             :     }
     291             : 
     292             :     ignore_result(da_free(&da));
     293             : #endif
     294             : 
     295           0 :     result.m_CompositeAlphaMap = g_VideoMode.GetBackendDevice()->CreateTexture2D("CompositeAlphaMap",
     296             :         Renderer::Backend::ITexture::Usage::TRANSFER_DST |
     297             :             Renderer::Backend::ITexture::Usage::SAMPLED,
     298             :         Renderer::Backend::Format::A8_UNORM, totalWidth, totalHeight,
     299           0 :         Renderer::Backend::Sampler::MakeDefaultSampler(
     300             :             Renderer::Backend::Sampler::Filter::LINEAR,
     301           0 :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
     302             : 
     303           0 :     result.m_CompositeDataToUpload = std::move(data);
     304             : 
     305           0 :     m_AlphaMapsToUpload.emplace_back(it);
     306             : 
     307           0 :     return it;
     308             : }
     309             : 
     310           0 : void CTerrainTextureManager::UploadResourcesIfNeeded(
     311             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
     312             : {
     313           0 :     for (const CTerrainTextureManager::TerrainAlphaMap::iterator& it : m_AlphaMapsToUpload)
     314             :     {
     315           0 :         TerrainAlpha& alphaMap = it->second;
     316           0 :         if (!alphaMap.m_CompositeDataToUpload)
     317           0 :             continue;
     318             :         // Upload the composite texture.
     319           0 :         Renderer::Backend::ITexture* texture = alphaMap.m_CompositeAlphaMap.get();
     320           0 :         deviceCommandContext->UploadTexture(
     321           0 :             texture, Renderer::Backend::Format::A8_UNORM, alphaMap.m_CompositeDataToUpload.get(),
     322           0 :             texture->GetWidth() * texture->GetHeight());
     323           0 :         alphaMap.m_CompositeDataToUpload.reset();
     324             :     }
     325             : 
     326           0 :     m_AlphaMapsToUpload.clear();
     327           0 : }
     328             : 
     329           0 : void CTerrainGroup::AddTerrain(CTerrainTextureEntry* pTerrain)
     330             : {
     331           0 :     m_Terrains.push_back(pTerrain);
     332           0 : }
     333             : 
     334           0 : void CTerrainGroup::RemoveTerrain(CTerrainTextureEntry* pTerrain)
     335             : {
     336           0 :     std::vector<CTerrainTextureEntry*>::iterator it = find(m_Terrains.begin(), m_Terrains.end(), pTerrain);
     337           0 :     if (it != m_Terrains.end())
     338           0 :         m_Terrains.erase(it);
     339           3 : }

Generated by: LCOV version 1.13