[Gameplay] Reduce loom cost
[0ad.git] / source / graphics / TerrainTextureManager.cpp
blobe38d6894ca43a16b1b207c230885313ed3fb519e
1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "TerrainTextureManager.h"
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/XML/Xeromyces.h"
32 #include "renderer/backend/IDevice.h"
33 #include "renderer/Renderer.h"
35 #include <algorithm>
36 #include <vector>
38 CTerrainTextureManager::CTerrainTextureManager(Renderer::Backend::IDevice* device)
39 : m_Device(device)
41 if (!VfsDirectoryExists(L"art/terrains/"))
42 return;
43 if (!CXeromyces::AddValidator(g_VFS, "terrain", "art/terrains/terrain.rng"))
44 LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain.rng'");
45 if (!CXeromyces::AddValidator(g_VFS, "terrain_texture", "art/terrains/terrain_texture.rng"))
46 LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain_texture.rng'");
49 CTerrainTextureManager::~CTerrainTextureManager()
51 UnloadTerrainTextures();
53 for (std::pair<const VfsPath, TerrainAlpha>& ta : m_TerrainAlphas)
54 ta.second.m_CompositeAlphaMap.reset();
57 void CTerrainTextureManager::UnloadTerrainTextures()
59 for (CTerrainTextureEntry* const& te : m_TextureEntries)
60 delete te;
61 m_TextureEntries.clear();
63 for (const std::pair<const CStr, CTerrainGroup*>& tg : m_TerrainGroups)
64 delete tg.second;
65 m_TerrainGroups.clear();
67 m_LastGroupIndex = 0;
70 CTerrainTextureEntry* CTerrainTextureManager::FindTexture(const CStr& tag_) const
72 CStr tag = tag_.BeforeLast("."); // Strip extension
74 for (CTerrainTextureEntry* const& te : m_TextureEntries)
75 if (te->GetTag() == tag)
76 return te;
78 LOGWARNING("CTerrainTextureManager: Couldn't find terrain %s", tag.c_str());
79 return 0;
82 CTerrainTextureEntry* CTerrainTextureManager::AddTexture(const CTerrainPropertiesPtr& props, const VfsPath& path)
84 CTerrainTextureEntry* entry = new CTerrainTextureEntry(props, path);
85 m_TextureEntries.push_back(entry);
86 return entry;
89 void CTerrainTextureManager::DeleteTexture(CTerrainTextureEntry* entry)
91 std::vector<CTerrainTextureEntry*>::iterator it = std::find(m_TextureEntries.begin(), m_TextureEntries.end(), entry);
92 if (it != m_TextureEntries.end())
93 m_TextureEntries.erase(it);
95 delete entry;
98 struct AddTextureCallbackData
100 CTerrainTextureManager* self;
101 CTerrainPropertiesPtr props;
104 static Status AddTextureDirCallback(const VfsPath& pathname, const uintptr_t cbData)
106 AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData;
107 VfsPath path = pathname / L"terrains.xml";
108 if (!VfsFileExists(path))
109 LOGMESSAGE("'%s' does not exist. Using previous properties.", path.string8());
110 else
111 data.props = CTerrainProperties::FromXML(data.props, path);
113 return INFO::OK;
116 static Status AddTextureCallback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
118 AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData;
119 if (pathname.Basename() != L"terrains")
120 data.self->AddTexture(data.props, pathname);
122 return INFO::OK;
125 int CTerrainTextureManager::LoadTerrainTextures()
127 AddTextureCallbackData data = {this, CTerrainPropertiesPtr(new CTerrainProperties(CTerrainPropertiesPtr()))};
128 vfs::ForEachFile(g_VFS, L"art/terrains/", AddTextureCallback, (uintptr_t)&data, L"*.xml", vfs::DIR_RECURSIVE, AddTextureDirCallback, (uintptr_t)&data);
129 return 0;
132 CTerrainGroup* CTerrainTextureManager::FindGroup(const CStr& name)
134 TerrainGroupMap::const_iterator it = m_TerrainGroups.find(name);
135 if (it != m_TerrainGroups.end())
136 return it->second;
137 else
138 return m_TerrainGroups[name] = new CTerrainGroup(name, ++m_LastGroupIndex);
141 // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and
142 // calculate the coordinate of each alphamap within this packed texture.
143 CTerrainTextureManager::TerrainAlphaMap::iterator
144 CTerrainTextureManager::LoadAlphaMap(const VfsPath& alphaMapType)
146 const std::wstring key = L"(alpha map composite" + alphaMapType.string() + L")";
148 CTerrainTextureManager::TerrainAlphaMap::iterator it = m_TerrainAlphas.find(alphaMapType);
150 if (it != g_TexMan.m_TerrainAlphas.end())
151 return it;
153 m_TerrainAlphas[alphaMapType] = TerrainAlpha();
154 it = m_TerrainAlphas.find(alphaMapType);
156 TerrainAlpha& result = it->second;
159 // load all textures and store Handle in array
161 Tex textures[NUM_ALPHA_MAPS] = {};
162 const VfsPath path = VfsPath("art/textures/terrain/alphamaps") / alphaMapType;
164 const wchar_t* fnames[NUM_ALPHA_MAPS] =
166 L"blendcircle.png",
167 L"blendlshape.png",
168 L"blendedge.png",
169 L"blendedgecorner.png",
170 L"blendedgetwocorners.png",
171 L"blendfourcorners.png",
172 L"blendtwooppositecorners.png",
173 L"blendlshapecorner.png",
174 L"blendtwocorners.png",
175 L"blendcorner.png",
176 L"blendtwoedges.png",
177 L"blendthreecorners.png",
178 L"blendushape.png",
179 L"blendbad.png"
181 size_t base = 0; // texture width/height (see below)
182 // For convenience, we require all alpha maps to be of the same BPP.
183 size_t bpp = 0;
184 for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
186 // note: these individual textures can be discarded afterwards;
187 // we cache the composite.
188 std::shared_ptr<u8> fileData;
189 size_t fileSize;
190 if (g_VFS->LoadFile(path / fnames[i], fileData, fileSize) != INFO::OK ||
191 textures[i].decode(fileData, fileSize) != INFO::OK)
193 m_TerrainAlphas.erase(it);
194 LOGERROR("Failed to load alphamap: %s", alphaMapType.string8());
196 const VfsPath standard("standard");
197 if (path != standard)
198 return LoadAlphaMap(standard);
199 return m_TerrainAlphas.end();
202 // Get its size and make sure they are all equal.
203 // (the packing algo assumes this).
204 if (textures[i].m_Width != textures[i].m_Height)
205 DEBUG_DISPLAY_ERROR(L"Alpha maps are not square");
206 // .. first iteration: establish size
207 if (i == 0)
209 base = textures[i].m_Width;
210 bpp = textures[i].m_Bpp;
212 // .. not first: make sure texture size matches
213 else if (base != textures[i].m_Width || bpp != textures[i].m_Bpp)
214 DEBUG_DISPLAY_ERROR(L"Alpha maps are not identically sized (including pixel depth)");
218 // copy each alpha map (tile) into one buffer, arrayed horizontally.
220 const size_t tileWidth = 2 + base + 2; // 2 pixel border (avoids bilinear filtering artifacts)
221 const size_t totalWidth = round_up_to_pow2(tileWidth * NUM_ALPHA_MAPS);
222 const size_t totalHeight = base; ENSURE(is_pow2(totalHeight));
223 std::shared_ptr<u8> data;
224 AllocateAligned(data, totalWidth * totalHeight, maxSectorSize);
225 // for each tile on row
226 for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
228 // get src of copy
229 u8* src = textures[i].get_data();
230 ENSURE(src);
232 const size_t srcStep = bpp / 8;
234 // get destination of copy
235 u8* dst = data.get() + (i * tileWidth);
237 // for each row of image
238 for (size_t j = 0; j < base; ++j)
240 // duplicate first pixel
241 *dst++ = *src;
242 *dst++ = *src;
244 // copy a row
245 for (size_t k = 0; k < base; ++k)
247 *dst++ = *src;
248 src += srcStep;
251 // duplicate last pixel
252 *dst++ = *(src - srcStep);
253 *dst++ = *(src - srcStep);
255 // advance write pointer for next row
256 dst += totalWidth - tileWidth;
259 result.m_AlphaMapCoords[i].u0 = static_cast<float>(i * tileWidth + 2) / totalWidth;
260 result.m_AlphaMapCoords[i].u1 = static_cast<float>((i + 1) * tileWidth - 2) / totalWidth;
261 result.m_AlphaMapCoords[i].v0 = 0.0f;
262 result.m_AlphaMapCoords[i].v1 = 1.0f;
265 for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
266 textures[i].free();
268 // Enable the following to save a png of the generated texture
269 // in the public/ directory, for debugging.
270 #if 0
271 Tex t;
272 ignore_result(t.wrap(totalWidth, totalHeight, 8, TEX_GREY, data, 0));
274 const VfsPath filename("blendtex.png");
276 DynArray da;
277 RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da));
279 // write to disk
280 //Status ret = INFO::OK;
282 std::shared_ptr<u8> file = DummySharedPtr(da.base);
283 const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
284 if (bytes_written > 0)
285 ENSURE(bytes_written == (ssize_t)da.pos);
286 //else
287 // ret = (Status)bytes_written;
290 ignore_result(da_free(&da));
291 #endif
293 result.m_CompositeAlphaMap = m_Device->CreateTexture2D("CompositeAlphaMap",
294 Renderer::Backend::ITexture::Usage::TRANSFER_DST |
295 Renderer::Backend::ITexture::Usage::SAMPLED,
296 Renderer::Backend::Format::A8_UNORM, totalWidth, totalHeight,
297 Renderer::Backend::Sampler::MakeDefaultSampler(
298 Renderer::Backend::Sampler::Filter::LINEAR,
299 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
301 result.m_CompositeDataToUpload = std::move(data);
303 m_AlphaMapsToUpload.emplace_back(it);
305 return it;
308 void CTerrainTextureManager::UploadResourcesIfNeeded(
309 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
311 for (const CTerrainTextureManager::TerrainAlphaMap::iterator& it : m_AlphaMapsToUpload)
313 TerrainAlpha& alphaMap = it->second;
314 if (!alphaMap.m_CompositeDataToUpload)
315 continue;
316 // Upload the composite texture.
317 Renderer::Backend::ITexture* texture = alphaMap.m_CompositeAlphaMap.get();
318 deviceCommandContext->UploadTexture(
319 texture, Renderer::Backend::Format::A8_UNORM, alphaMap.m_CompositeDataToUpload.get(),
320 texture->GetWidth() * texture->GetHeight());
321 alphaMap.m_CompositeDataToUpload.reset();
324 m_AlphaMapsToUpload.clear();
327 void CTerrainGroup::AddTerrain(CTerrainTextureEntry* pTerrain)
329 m_Terrains.push_back(pTerrain);
332 void CTerrainGroup::RemoveTerrain(CTerrainTextureEntry* pTerrain)
334 std::vector<CTerrainTextureEntry*>::iterator it = find(m_Terrains.begin(), m_Terrains.end(), pTerrain);
335 if (it != m_Terrains.end())
336 m_Terrains.erase(it);