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"
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"
38 CTerrainTextureManager::CTerrainTextureManager(Renderer::Backend::IDevice
* device
)
41 if (!VfsDirectoryExists(L
"art/terrains/"))
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
)
61 m_TextureEntries
.clear();
63 for (const std::pair
<const CStr
, CTerrainGroup
*>& tg
: m_TerrainGroups
)
65 m_TerrainGroups
.clear();
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
)
78 LOGWARNING("CTerrainTextureManager: Couldn't find terrain %s", tag
.c_str());
82 CTerrainTextureEntry
* CTerrainTextureManager::AddTexture(const CTerrainPropertiesPtr
& props
, const VfsPath
& path
)
84 CTerrainTextureEntry
* entry
= new CTerrainTextureEntry(props
, path
);
85 m_TextureEntries
.push_back(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
);
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());
111 data
.props
= CTerrainProperties::FromXML(data
.props
, path
);
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
);
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
);
132 CTerrainGroup
* CTerrainTextureManager::FindGroup(const CStr
& name
)
134 TerrainGroupMap::const_iterator it
= m_TerrainGroups
.find(name
);
135 if (it
!= m_TerrainGroups
.end())
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())
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
] =
169 L
"blendedgecorner.png",
170 L
"blendedgetwocorners.png",
171 L
"blendfourcorners.png",
172 L
"blendtwooppositecorners.png",
173 L
"blendlshapecorner.png",
174 L
"blendtwocorners.png",
176 L
"blendtwoedges.png",
177 L
"blendthreecorners.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.
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
;
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
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
)
229 u8
* src
= textures
[i
].get_data();
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
245 for (size_t k
= 0; k
< base
; ++k
)
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
)
268 // Enable the following to save a png of the generated texture
269 // in the public/ directory, for debugging.
272 ignore_result(t
.wrap(totalWidth
, totalHeight
, 8, TEX_GREY
, data
, 0));
274 const VfsPath
filename("blendtex.png");
277 RETURN_STATUS_IF_ERR(tex_encode(&t
, filename
.Extension(), &da
));
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
);
287 // ret = (Status)bytes_written;
290 ignore_result(da_free(&da
));
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
);
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
)
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
);