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 : }
|