Ticket #1167: colladacache+archivebuild_fixes-02172012.patch
File colladacache+archivebuild_fixes-02172012.patch, 19.1 KB (added by , 12 years ago) |
---|
-
source/graphics/ColladaManager.cpp
1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 21 21 22 22 #include "graphics/ModelDef.h" 23 23 #include "lib/fnv_hash.h" 24 #include "maths/MD5.h" 25 #include "ps/CacheLoader.h" 24 26 #include "ps/CLogger.h" 25 27 #include "ps/CStr.h" 26 28 #include "ps/DllLoader.h" … … 62 64 int (*convert_dae_to_psa)(const char* dae, Collada::OutputFn psa_writer, void* cb_data); 63 65 64 66 public: 65 CColladaManagerImpl( )66 : dll("Collada") 67 CColladaManagerImpl(const PIVFS& vfs) 68 : dll("Collada"), m_VFS(vfs) 67 69 { 68 70 } 69 71 … … 109 111 set_logger(ColladaLog, static_cast<void*>(&skeletonPath)); 110 112 111 113 CVFSFile skeletonFile; 112 if (skeletonFile.Load( g_VFS, skeletonPath) != PSRETURN_OK)114 if (skeletonFile.Load(m_VFS, skeletonPath) != PSRETURN_OK) 113 115 { 114 116 LOGERROR(L"Failed to read skeleton definitions"); 115 117 dll.Unload(); … … 123 125 dll.Unload(); 124 126 return false; 125 127 } 126 127 // TODO: the cached PMD/PSA files should probably be invalidated when128 // the skeleton definition file is changed, else people will get confused129 // as to why it's not picking up their changes130 128 } 131 129 132 130 // Set the filename for the logger to report … … 137 135 CStr daeData; 138 136 { 139 137 CVFSFile daeFile; 140 if (daeFile.Load( g_VFS, daeFilename) != PSRETURN_OK)138 if (daeFile.Load(m_VFS, daeFilename) != PSRETURN_OK) 141 139 return false; 142 140 daeData = daeFile.GetAsString(); 143 141 } 144 142 145 143 // Do the conversion into a memory buffer 144 // We need to check the result, as archive builder needs to know if the source dae 145 // was sucessfully converted to .pmd/psa 146 int result = -1; 146 147 WriteBuffer writeBuffer; 147 148 switch (type) 148 149 { 149 case CColladaManager::PMD: convert_dae_to_pmd(daeData.c_str(), ColladaOutput, &writeBuffer); break; 150 case CColladaManager::PSA: convert_dae_to_psa(daeData.c_str(), ColladaOutput, &writeBuffer); break; 150 case CColladaManager::PMD: 151 result = convert_dae_to_pmd(daeData.c_str(), ColladaOutput, &writeBuffer); 152 break; 153 case CColladaManager::PSA: 154 result = convert_dae_to_psa(daeData.c_str(), ColladaOutput, &writeBuffer); 155 break; 151 156 } 152 157 153 158 // don't create zero-length files (as happens in test_invalid_dae when … … 155 160 // logic warns when asked to load such. 156 161 if (writeBuffer.Size()) 157 162 { 158 Status ret = g_VFS->CreateFile(pmdFilename, writeBuffer.Data(), writeBuffer.Size());163 Status ret = m_VFS->CreateFile(pmdFilename, writeBuffer.Data(), writeBuffer.Size()); 159 164 ENSURE(ret == INFO::OK); 160 165 } 161 166 162 return true;167 return (result == 0); 163 168 } 169 170 private: 171 PIVFS m_VFS; 164 172 }; 165 173 166 CColladaManager::CColladaManager( )167 : m(new CColladaManagerImpl( ))174 CColladaManager::CColladaManager(const PIVFS& vfs) 175 : m(new CColladaManagerImpl(vfs)), m_VFS(vfs) 168 176 { 169 177 } 170 178 … … 173 181 delete m; 174 182 } 175 183 176 VfsPath CColladaManager::GetLoadableFilename(const VfsPath& pathnameNoExtension, FileType type)184 void CColladaManager::PrepareCacheKey(MD5& hash, u32& version) 177 185 { 186 // Include skeletons.xml file info in the hash 187 VfsPath skeletonPath("art/skeletons/skeletons.xml"); 188 FileInfo fileInfo; 189 if (m_VFS->GetFileInfo(skeletonPath, &fileInfo) != INFO::OK) 190 { 191 // This shouldn't occur for any sensible reasons 192 LOGERROR(L"Failed to stat '%ls' for DAE caching", skeletonPath.string().c_str()); 193 // We can continue, something else will break if we try loading a skeletal model 194 } 195 else 196 { 197 u64 skeletonsModifyTime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it 198 u64 skeletonsSize = (u64)fileInfo.Size(); 199 hash.Update((const u8*)&skeletonsModifyTime, sizeof(skeletonsModifyTime)); 200 hash.Update((const u8*)&skeletonsSize, sizeof(skeletonsSize)); 201 } 202 203 // Add converter version to the hash 204 int converterVersion = COLLADA_CONVERTER_VERSION; 205 hash.Update((const u8*)&converterVersion, sizeof(converterVersion)); 206 207 // Arbitrary version number - change this if we update the code and 208 // need to invalidate old users' caches 209 version = 1; 210 } 211 212 VfsPath CColladaManager::GetLoadablePath(const VfsPath& pathnameNoExtension, FileType type) 213 { 178 214 std::wstring extn; 179 215 switch (type) 180 216 { … … 186 222 /* 187 223 188 224 If there is a .dae file: 189 * Calculate a hash to identify it. 190 * Look for a cached .pmd file matching that hash. 191 * If it exists, load it. Else, convert the .dae into .pmd and load it. 192 Otherwise, if there is a (non-cache) .pmd file: 193 * Load it. 194 Else, fail. 225 * Calculate hash of skeletons.xml and converter version. 226 * Use CCacheLoader to check for archived or loose cached .pmd/psa. 227 * If it exists: 228 * Return pathname of .pmd/psa. 229 * Else: 230 * Convert the .dae into .pmd/psa. 231 * Return pathname of .pmd/psa. 232 Else, if there is a (non-cached) .psa/pmd file: 233 * Return pathname of .pmd/psa. 234 Else: 235 * Fail (return empty string). 195 236 196 The hash calculation ought to be fast, since normally (during development) 197 the .dae file will exist but won't have changed recently and so the cache 198 would be used. Hence, just hash the file's size, mtime, and the converter 199 version number (so updates of the converter can cause regeneration of .pmds) 200 instead of the file's actual contents. 237 Since we use CCacheLoader which automatically hashes file size and mtime, 238 and handles archived files and loose cache, when preparing the cache key 239 we add converter version number (so updates of the converter cause 240 regeneration of the .pmd/psa) and the global skeletons.xml file size and 241 mtime, as modelers frequently change the contents of skeletons.xml and get 242 perplexed if the in-game models haven't updated as expected (we don't know 243 which models were affected by the skeletons.xml change, if any, so we just 244 regenerate all of them) 201 245 202 TODO (maybe): The .dae -> .pmd conversion may fail (e.g. if the .dae is246 TODO (maybe): The .dae -> .pmd/psa conversion may fail (e.g. if the .dae is 203 247 invalid or unsupported), but it may take a long time to start the conversion 204 248 then realise it's not going to work. That will delay the loading of the game 205 249 every time, which is annoying, so maybe it should cache the error message … … 208 252 209 253 */ 210 254 211 // (TODO: the comments and variable names say "pmd" but actually they can 212 // be "psa" too.) 213 214 VfsPath dae(pathnameNoExtension.ChangeExtension(L".dae")); 215 if (! VfsFileExists(dae)) 255 // Check if the .dae source exists 256 VfsPath sourcePath = pathnameNoExtension.ChangeExtension(L".dae"); 257 if (m_VFS->GetFileInfo(sourcePath, 0) != INFO::OK) 216 258 { 217 // No .dae - got to use the .pmd, assuming there is one 259 // No .dae source file - have to use the uncached .pmd/psa if it exists 260 // (cached .pmd/psa are only created by converting DAEs) 218 261 return pathnameNoExtension.ChangeExtension(extn); 219 262 } 220 263 221 // There is a .dae - see if there's an up-to-date cached copy 264 // Now we're looking for cached files 265 CCacheLoader cacheLoader(m_VFS, extn); 266 MD5 hash; 267 u32 version; 268 PrepareCacheKey(hash, version); 222 269 223 FileInfo fileInfo; 224 if (g_VFS->GetFileInfo(dae, &fileInfo) < 0) 270 VfsPath loadPath; 271 Status ret = cacheLoader.TryLoadingCached(sourcePath, hash, version, loadPath); 272 if (ret == INFO::OK) 225 273 { 226 // This shouldn't occur for any sensible reasons 227 LOGERROR(L"Failed to stat DAE file '%ls'", dae.string().c_str()); 228 return VfsPath(); 274 // Found a cached version 275 return loadPath; 229 276 } 277 else if (ret == INFO::SKIPPED) 278 { 279 // No cached version was found - we'll need to create it 280 } 281 else 282 { 283 ENSURE(ret < 0); 284 // This shouldn't happen, we already checked that the .dae exists 285 return L""; 286 } 230 287 231 // Build a struct of all the data we want to hash. 232 // (Use ints and not time_t/off_t because we don't care about overflow 233 // but do care about the fields not being 64-bit aligned) 234 // (Remove the lowest bit of mtime because some things round it to a 235 // resolution of 2 seconds) 236 #pragma pack(push, 1) 237 struct { int version; int mtime; int size; } hashSource 238 = { COLLADA_CONVERTER_VERSION, (int)fileInfo.MTime() & ~1, (int)fileInfo.Size() }; 239 cassert(sizeof(hashSource) == sizeof(int) * 3); // no padding, because that would be bad 240 #pragma pack(pop) 288 // Cached version isn't up to date with DAE or skeletons.xml, so regenerate it 289 if (! m->Convert(sourcePath, loadPath, type)) 290 return L""; 241 291 242 // Calculate the hash, convert to hex 243 u32 hash = fnv_hash(static_cast<void*>(&hashSource), sizeof(hashSource)); 244 wchar_t hashString[9]; 245 swprintf_s(hashString, ARRAY_SIZE(hashString), L"%08x", hash); 246 std::wstring extension(L"_"); 247 extension += hashString; 248 extension += extn; 292 return loadPath; 293 } 249 294 250 // realDaePath_ is "[..]/mods/whatever/art/meshes/whatever.dae" 251 OsPath realDaePath_; 252 Status ret = g_VFS->GetRealPath(dae, realDaePath_); 253 ENSURE(ret == INFO::OK); 254 wchar_t realDaeBuf[PATH_MAX]; 255 wcscpy_s(realDaeBuf, ARRAY_SIZE(realDaeBuf), realDaePath_.string().c_str()); 256 std::replace(realDaeBuf, realDaeBuf+ARRAY_SIZE(realDaeBuf), '\\', '/'); 257 const wchar_t* realDaePath = wcsstr(realDaeBuf, L"mods/"); 258 259 // cachedPmdVfsPath is "cache/mods/whatever/art/meshes/whatever_{hash}.pmd" 260 VfsPath cachedPmdVfsPath = VfsPath("cache") / realDaePath; 261 cachedPmdVfsPath = cachedPmdVfsPath.ChangeExtension(extension); 262 263 // If it's not in the cache, we'll have to create it first 264 if (! VfsFileExists(cachedPmdVfsPath)) 295 bool CColladaManager::GenerateCachedFile(const VfsPath& sourcePath, FileType type, VfsPath& archiveCachePath) 296 { 297 std::wstring extn; 298 switch (type) 265 299 { 266 if (! m->Convert(dae, cachedPmdVfsPath, type)) 267 return L""; // failed to convert 300 case PMD: extn = L".pmd"; break; 301 case PSA: extn = L".psa"; break; 302 // no other alternatives 268 303 } 269 304 270 return cachedPmdVfsPath; 305 CCacheLoader cacheLoader(m_VFS, extn); 306 MD5 hash; 307 u32 version; 308 PrepareCacheKey(hash, version); 309 310 archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath); 311 312 return m->Convert(sourcePath, VfsPath("cache") / archiveCachePath, type); 271 313 } -
source/graphics/ColladaManager.h
1 /* Copyright (C) 20 09Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 18 18 #ifndef INCLUDED_COLLADAMANAGER 19 19 #define INCLUDED_COLLADAMANAGER 20 20 21 #include "lib/file/vfs/vfs.h" 21 22 #include "lib/file/vfs/vfs_path.h" 22 23 23 24 class CStr8; 24 25 class CColladaManagerImpl; 26 class MD5; 25 27 26 28 class CColladaManager 27 29 { 28 30 public: 29 31 enum FileType { PMD, PSA }; 30 32 31 CColladaManager( );33 CColladaManager(const PIVFS& vfs); 32 34 ~CColladaManager(); 33 35 34 36 /** … … 42 44 * @return full VFS path (including extension) of file to load; or empty 43 45 * string if there was a problem and it could not be loaded. 44 46 */ 45 VfsPath GetLoadable Filename(const VfsPath& pathnameNoExtension, FileType type);47 VfsPath GetLoadablePath(const VfsPath& pathnameNoExtension, FileType type); 46 48 49 /** 50 * Converts DAE to archive cached .pmd/psa and outputs the resulting path 51 * (used by archive builder) 52 * 53 * @param sourcePath[in] path of the .dae to load 54 * @param type[in] FileType, .pmd or .psa 55 * @param archiveCachePath[out] output path of the cached file 56 * 57 * @return true if COLLADA converter completed successfully; or false if it failed 58 */ 59 bool GenerateCachedFile(const VfsPath& sourcePath, FileType type, VfsPath& archiveCachePath); 60 47 61 private: 62 /** 63 * Creates MD5 hash key from skeletons.xml info and COLLADA converter version, 64 * used to invalidate cached .pmd/psas 65 * 66 * @param hash[out] resulting MD5 hash 67 * @param version[out] version passed to CCacheLoader, used if code change should force 68 * cache invalidation 69 */ 70 void PrepareCacheKey(MD5& hash, u32& version); 71 48 72 CColladaManagerImpl* m; 73 PIVFS m_VFS; 49 74 }; 50 75 51 76 #endif // INCLUDED_COLLADAMANAGER -
source/graphics/GameView.cpp
41 41 #include "maths/Matrix3D.h" 42 42 #include "maths/Quaternion.h" 43 43 #include "ps/ConfigDB.h" 44 #include "ps/Filesystem.h" 44 45 #include "ps/Game.h" 45 46 #include "ps/Globals.h" 46 47 #include "ps/Hotkey.h" … … 152 153 public: 153 154 CGameViewImpl(CGame* game) 154 155 : Game(game), 155 ColladaManager( ), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager),156 ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), 156 157 ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()), 157 158 LOSTexture(*game->GetSimulation2()), 158 159 TerritoryTexture(*game->GetSimulation2()), -
source/graphics/MeshManager.cpp
49 49 50 50 PROFILE("load mesh"); 51 51 52 VfsPath pmdFilename = m_ColladaManager.GetLoadable Filename(name, CColladaManager::PMD);52 VfsPath pmdFilename = m_ColladaManager.GetLoadablePath(name, CColladaManager::PMD); 53 53 54 54 if (pmdFilename.empty()) 55 55 { -
source/graphics/SkeletonAnimManager.cpp
61 61 CSkeletonAnimDef* def = NULL; 62 62 63 63 // Find the file to load 64 VfsPath psaFilename = m_ColladaManager.GetLoadable Filename(name, CColladaManager::PSA);64 VfsPath psaFilename = m_ColladaManager.GetLoadablePath(name, CColladaManager::PSA); 65 65 66 66 if (psaFilename.empty()) 67 67 { -
source/graphics/tests/test_MeshManager.h
1 /* Copyright (C) 20 09Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 102 102 void setUp() 103 103 { 104 104 initVfs(); 105 colladaManager = new CColladaManager( );105 colladaManager = new CColladaManager(g_VFS); 106 106 meshManager = new CMeshManager(*colladaManager); 107 107 } 108 108 … … 166 166 copyFile(srcDAE, testDAE); 167 167 copyFile(srcSkeletonDefs, testSkeletonDefs); 168 168 169 VfsPath daeName1 = colladaManager->GetLoadable Filename(testBase, CColladaManager::PMD);170 VfsPath daeName2 = colladaManager->GetLoadable Filename(testBase, CColladaManager::PMD);169 VfsPath daeName1 = colladaManager->GetLoadablePath(testBase, CColladaManager::PMD); 170 VfsPath daeName2 = colladaManager->GetLoadablePath(testBase, CColladaManager::PMD); 171 171 TS_ASSERT(!daeName1.empty()); 172 172 TS_ASSERT_PATH_EQUALS(daeName1, daeName2); 173 173 // TODO: it'd be nice to test that it really isn't doing the DAE->PMD -
source/ps/ArchiveBuilder.cpp
20 20 #include "ArchiveBuilder.h" 21 21 22 22 #include "graphics/TextureManager.h" 23 #include "graphics/ColladaManager.h" 23 24 #include "lib/tex/tex_codec.h" 24 25 #include "lib/file/archive/archive_zip.h" 25 26 #include "lib/file/vfs/vfs_util.h" … … 41 42 42 43 DeleteDirectory(m_TempDir/"_archivecache"); // clean up in case the last run failed 43 44 44 m_VFS->Mount(L"cache/", m_TempDir/"_archivecache /");45 m_VFS->Mount(L"cache/", m_TempDir/"_archivecache"/""); 45 46 46 47 m_VFS->Mount(L"", mod/"", VFS_MOUNT_MUST_EXIST | VFS_MOUNT_KEEP_DELETED); 47 48 … … 76 77 77 78 // Use CTextureManager instead of CTextureConverter directly, 78 79 // so it can deal with all the loading of settings.xml files 79 CTextureManager tex man(m_VFS, true, true);80 CTextureManager textureManager(m_VFS, true, true); 80 81 82 CColladaManager colladaManager(m_VFS); 83 81 84 CXeromyces xero; 82 85 83 86 for (size_t i = 0; i < m_Files.size(); ++i) … … 99 102 { 100 103 VfsPath cachedPath; 101 104 debug_printf(L"Converting texture %ls\n", realPath.string().c_str()); 102 bool ok = tex man.GenerateCachedTexture(path, cachedPath);105 bool ok = textureManager.GenerateCachedTexture(path, cachedPath); 103 106 ENSURE(ok); 104 107 105 108 OsPath cachedRealPath; … … 113 116 continue; 114 117 } 115 118 116 // TODO: should cache DAE->PMD and DAE->PSA conversions too 119 // Cache PMD conversions of DAE models 120 if (boost::algorithm::starts_with(path.string(), L"art/meshes/") && 121 path.Extension() == L".dae" 122 ) 123 { 124 VfsPath cachedPath; 125 debug_printf(L"Converting model %ls\n", realPath.string().c_str()); 126 bool ok = colladaManager.GenerateCachedFile(path, CColladaManager::PMD, cachedPath); 127 128 // The DAE might fail to convert for whatever reason, and in that case 129 // it can't be used in the game, so we just exclude it 130 // (alternatively we could throw release blocking errors on useless files) 131 if (ok) 132 { 133 OsPath cachedRealPath; 134 ret = m_VFS->GetRealPath(VfsPath("cache")/cachedPath, cachedRealPath); 135 ENSURE(ret == INFO::OK); 117 136 137 writer->AddFile(cachedRealPath, cachedPath); 138 } 139 140 // We don't want to store the original file too (since it's a 141 // large waste of space), so skip to the next file 142 continue; 143 } 144 145 // Cache PSA conversions of DAE animations 146 if (boost::algorithm::starts_with(path.string(), L"art/animation/") && 147 path.Extension() == L".dae" 148 ) 149 { 150 VfsPath cachedPath; 151 debug_printf(L"Converting animation %ls\n", realPath.string().c_str()); 152 bool ok = colladaManager.GenerateCachedFile(path, CColladaManager::PSA, cachedPath); 153 154 // The DAE might fail to convert for whatever reason, and in that case 155 // it can't be used in the game, so we just exclude it 156 // (alternatively we could throw release blocking errors on useless files) 157 if (ok) 158 { 159 OsPath cachedRealPath; 160 ret = m_VFS->GetRealPath(VfsPath("cache")/cachedPath, cachedRealPath); 161 ENSURE(ret == INFO::OK); 162 163 writer->AddFile(cachedRealPath, cachedPath); 164 } 165 166 // We don't want to store the original file too (since it's a 167 // large waste of space), so skip to the next file 168 continue; 169 } 170 118 171 debug_printf(L"Adding %ls\n", realPath.string().c_str()); 119 172 writer->AddFile(realPath, path); 120 173 -
source/tools/atlas/GameInterface/ActorViewer.cpp
37 37 #include "graphics/UnitManager.h" 38 38 #include "graphics/Overlay.h" 39 39 #include "maths/MathUtil.h" 40 #include "ps/Filesystem.h" 40 41 #include "ps/Font.h" 41 42 #include "ps/CLogger.h" 42 43 #include "ps/GameSetup/Config.h" … … 61 62 ActorViewerImpl() : 62 63 Entity(INVALID_ENTITY), 63 64 Terrain(), 64 ColladaManager( ),65 ColladaManager(g_VFS), 65 66 MeshManager(ColladaManager), 66 67 SkeletonAnimManager(ColladaManager), 67 68 UnitManager(),