Divide Framework 0.1
A free and open-source 3D Framework under heavy development
Loading...
Searching...
No Matches
ResourceCache.inl
Go to the documentation of this file.
1
2
3/*
4 Copyright (c) 2018 DIVIDE-Studio
5 Copyright (c) 2009 Ionut Cava
6
7 This file is part of DIVIDE Framework.
8
9 Permission is hereby granted, free of charge, to any person obtaining a copy
10 of this software
11 and associated documentation files (the "Software"), to deal in the Software
12 without restriction,
13 including without limitation the rights to use, copy, modify, merge, publish,
14 distribute, sublicense,
15 and/or sell copies of the Software, and to permit persons to whom the
16 Software is furnished to do so,
17 subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in
20 all copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED,
24 INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
25 PARTICULAR PURPOSE AND NONINFRINGEMENT.
26 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
27 DAMAGES OR OTHER LIABILITY,
28 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
29 IN CONNECTION WITH THE SOFTWARE
30 OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
32 */
33
34#pragma once
35#ifndef DVD_RESOURCE_CACHE_INL_
36#define DVD_RESOURCE_CACHE_INL_
37
44
45namespace Divide
46{
48 {
49 explicit ResourcePoolBase( RenderAPI api );
50
51 virtual ~ResourcePoolBase();
52 virtual void printResources( bool error ) = 0;
53 virtual void processDeletionQueue() = 0;
54
55 protected:
57 };
58
59 template<typename T>
60 struct ResourcePool final : public ResourcePoolBase
61 {
62 struct Entry
63 {
64 ResourcePtr<T> _ptr{ nullptr };
65 size_t _descriptorHash{ 0u };
67 };
68
69 constexpr static size_t ResourcePoolSize = 512u;
70
71 explicit ResourcePool( RenderAPI api );
72
73 void resize( size_t size);
74
75 void queueDeletion(Handle<T>& handle);
76 void processDeletionQueue() override;
77
78 [[nodiscard]] ResourcePtr<T> get( Handle<T> handle );
79
80 Handle<T> retrieveHandleLocked( const size_t descriptorHash );
81
82 void deallocate( Handle<T>& handle );
83
84 [[nodiscard]] Handle<T> allocate( size_t descriptorHash );
85
86 void commitLocked(Handle<T> handle, ResourcePtr<T> ptr);
87
88 void printResources( bool error ) final;
89
91 eastl::fixed_vector<std::pair<bool, U8>, ResourcePoolSize, true> _freeList;
92 eastl::fixed_vector<Entry, ResourcePoolSize, true> _resPool;
93
95 [[nodiscard]] Handle<T> allocateLocked( size_t descriptorHash );
96
97 private:
98 moodycamel::ConcurrentQueue<Handle<T>> _deletionQueue;
99 };
100
101 template <typename T> requires std::is_base_of_v<CachedResource, T>
102 using MemPool = MemoryPool<T, prevPOW2( sizeof( T ) ) * 1u << 5u>;
103
104 template <typename T> requires std::is_base_of_v<CachedResource, T>
106 {
107 static MemPool<T> s_memPool;
108 return s_memPool;
109 }
110
111 template <typename T> requires std::is_base_of_v<CachedResource, T>
113 {
114 static ResourcePool<T> s_pool(api);
115
116 return s_pool;
117 }
118
119 template<typename T>
121 {
122 if ( handle != INVALID_HANDLE<T> )
123 {
124 _deletionQueue.enqueue(handle);
125 handle = INVALID_HANDLE<T>;
126 }
127 }
128
129 template<typename T>
131 {
132 Handle<T> handle{};
133 while (_deletionQueue.try_dequeue( handle ))
134 {
135 deallocate( handle );
136 }
137 }
138
139 template<typename T>
140 void ResourcePool<T>::printResources( const bool error )
141 {
142 SharedLock<SharedMutex> r_lock( _lock );
143
144 bool first = true;
145 const size_t poolSize = _freeList.size();
146 for ( size_t i = 0u; i < poolSize; ++i)
147 {
148 if (!_freeList[i].first)
149 {
150 DIVIDE_ASSERT(_resPool[i]._ptr != nullptr);
151
152 if ( first )
153 {
154 if ( error )
155 {
156 Console::errorfn( LOCALE_STR( "RESOURCE_CACHE_POOL_TYPE" ), _resPool[i]._ptr->typeName() );
157 }
158 else
159 {
160 Console::printfn( LOCALE_STR( "RESOURCE_CACHE_POOL_TYPE" ), _resPool[i]._ptr->typeName() );
161 }
162 first = false;
163 }
164
165 if (error)
166 {
167 Console::errorfn( LOCALE_STR( "RESOURCE_CACHE_GET_RES_INC" ), _resPool[i]._ptr->resourceName(), _resPool[i]._refCount );
168 }
169 else
170 {
171 Console::printfn( LOCALE_STR("RESOURCE_CACHE_GET_RES_INC"), _resPool[i]._ptr->resourceName(), _resPool[i]._refCount );
172 }
173 }
174 }
175 }
176
177 template<typename T>
179 {
180 DIVIDE_ASSERT(handle != INVALID_HANDLE<T>);
181
182 SharedLock<SharedMutex> r_lock( _lock );
183 DIVIDE_ASSERT( _freeList[handle._index].second == handle._generation );
184
185 return _resPool[handle._index]._ptr;
186 }
187
188 template<typename T>
190 : ResourcePoolBase(api)
191 {
192
194 }
195
196 template<typename T>
197 void ResourcePool<T>::resize( const size_t size )
198 {
199 _freeList.resize( size, std::make_pair( true, 0u ) );
200 _resPool.resize( size, {} );
201 }
202
203 template<typename T>
204 Handle<T> ResourcePool<T>::allocateLocked( const size_t descriptorHash )
205 {
206 Handle<T> handleOut = {};
207 for ( auto& it : _freeList )
208 {
209 if ( it.first )
210 {
211 it.first = false;
212 handleOut._generation = it.second;
213 Entry& entry = _resPool[handleOut._index];
214 entry._descriptorHash = descriptorHash;
215 entry._refCount = 1u;
216
217 return handleOut;
218 }
219
220 ++handleOut._index;
221 }
222
223 resize( _freeList.size() + ResourcePoolSize );
224
225 return allocateLocked(descriptorHash);
226 }
227
228 template<typename T>
229 Handle<T> ResourcePool<T>::allocate( const size_t descriptorHash )
230 {
231 LockGuard<SharedMutex> lock( _lock );
232 return allocateLocked( descriptorHash );
233 }
234
235 template <typename T>
237 {
238 GetMemPool<T>().deleteElement( ptr );
239 }
240
241 template<>
243 {
244 switch ( _api )
245 {
246 case RenderAPI::None: GetMemPool<noTexture>().deleteElement( static_cast<ResourcePtr<noTexture>>( ptr ) ); break;
247 case RenderAPI::OpenGL: GetMemPool<glTexture>().deleteElement( static_cast<ResourcePtr<glTexture>>( ptr ) ); break;
248 case RenderAPI::Vulkan: GetMemPool<vkTexture>().deleteElement( static_cast<ResourcePtr<vkTexture>>( ptr ) ); break;
250 }
251 }
252
253 template<>
255 {
256 switch ( _api )
257 {
258 case RenderAPI::None: GetMemPool<noShaderProgram>().deleteElement( static_cast<ResourcePtr<noShaderProgram>>( ptr) ); break;
259 case RenderAPI::OpenGL: GetMemPool<glShaderProgram>().deleteElement( static_cast<ResourcePtr<glShaderProgram>>( ptr) ); break;
260 case RenderAPI::Vulkan: GetMemPool<vkShaderProgram>().deleteElement( static_cast<ResourcePtr<vkShaderProgram>>( ptr) ); break;
262 }
263 }
264
265 template <typename T>
267 {
268 if ( handle == INVALID_HANDLE<T> )
269 {
270 Console::errorfn( LOCALE_STR( "ERROR_RESOURCE_CACHE_UNKNOWN_RESOURCE" ) );
271 return;
272 }
273
274 ResourcePtr<T> ptr = nullptr;
275 size_t descriptorHash = 0u;
276
277 {
278 LockGuard<SharedMutex> w_lock( _lock );
279 if ( _freeList[handle._index].second != handle._generation )
280 {
281 // Already free
282 return;
283 }
284
285 Entry& entry = _resPool[handle._index];
286 if ( --entry._refCount == 0u)
287 {
288 ptr = entry._ptr;
289 descriptorHash = entry._descriptorHash;
290
291 entry = {};
292 ++_freeList[handle._index].second;
293 _freeList[handle._index].first = true;
294 }
295 else
296 {
297 Console::printfn( LOCALE_STR( "RESOURCE_CACHE_REM_RES_DEC" ), entry._ptr->resourceName().c_str(), entry._refCount );
298 }
299 }
300 handle = INVALID_HANDLE<T>;
301
302 if ( ptr != nullptr )
303 {
304 Console::printfn( LOCALE_STR( "RESOURCE_CACHE_REM_RES" ), ptr->resourceName().c_str(), descriptorHash );
305
306 if ( ptr->getState() == ResourceState::RES_LOADED)
307 {
308 ptr->setState(ResourceState::RES_UNLOADING);
309 if (!ptr->unload())
310 {
311 Console::errorfn( LOCALE_STR( "ERROR_RESOURCE_REM" ), ptr->resourceName().c_str(), ptr->getGUID() );
312 ptr->setState(ResourceState::RES_UNKNOWN);
313 }
314 else
315 {
316 ptr->setState(ResourceState::RES_CREATED);
317 }
318 }
319
321 deallocateInternal( ptr );
322 }
323 }
324
325 template<typename T>
327 {
328 Handle<T> ret{};
329 for ( const auto&[free, generation] : _freeList )
330 {
331 if ( !free )
332 {
333 Entry& entry = _resPool[ret._index];
334
335 if ( entry._descriptorHash == descriptorHash )
336 {
337 ret._generation = generation;
338 ++entry._refCount;
339
340 Console::printfn( LOCALE_STR( "RESOURCE_CACHE_GET_RES_INC" ), entry._ptr->resourceName(), entry._refCount );
341 return ret;
342 }
343 }
344 ++ret._index;
345 }
346
347 return INVALID_HANDLE<T>;
348 }
349
350 template <typename T>
352 {
353 _resPool[handle._index]._ptr = ptr;
354 }
355
356 template <typename T> requires std::is_base_of_v<CachedResource, T>
358 {
359 if ( handle != INVALID_HANDLE<T>)
360 {
361 ResourcePool<T>& pool = GetPool<T>( s_renderAPI );
362 SharedLock<SharedMutex> r_lock( pool._lock );
363 if ( pool._freeList[handle._index].second == handle._generation )
364 {
365 auto& entry = pool._resPool[handle._index];
366 ++entry._refCount;
367 Console::printfn( LOCALE_STR( "RESOURCE_CACHE_GET_RES_INC" ), entry._ptr->resourceName(), entry._refCount );
368 }
369 }
370
371 return handle;
372 }
373
374 template<typename T> requires std::is_base_of_v<CachedResource, T>
375 Handle<T> ResourceCache::RetrieveOrAllocateHandle( const size_t descriptorHash, bool& wasInCache )
376 {
377 ResourcePool<T>& pool = GetPool<T>(s_renderAPI);
378 {
379 SharedLock<SharedMutex> r_lock(pool._lock);
380 const Handle<T> ret = pool.retrieveHandleLocked(descriptorHash);
381 if ( ret != INVALID_HANDLE<T> )
382 {
383 wasInCache = true;
384 return ret;
385 }
386 }
387
388 UniqueLock<SharedMutex> r_lock( pool._lock );
389 // Check again
390 const Handle<T> ret = pool.retrieveHandleLocked( descriptorHash );
391 if ( ret != INVALID_HANDLE<T> )
392 {
393 wasInCache = true;
394 return ret;
395 }
396
397 // Cache miss. Allocate new resource
398 return pool.allocateLocked(descriptorHash);
399 }
400
401
402 template <typename T> requires std::is_base_of_v<CachedResource, T>
403 T* ResourceCache::Get( const Handle<T> handle )
404 {
405 if ( handle != INVALID_HANDLE<T> ) [[likely]]
406 {
407 ResourcePool<T>& pool = GetPool<T>( s_renderAPI );
408 SharedLock<SharedMutex> r_lock( pool._lock );
409 if ( pool._freeList[handle._index].second == handle._generation )
410 {
411 return pool._resPool[handle._index]._ptr;
412 }
413 }
414
415 return nullptr;
416 }
417
418 template <typename T> requires std::is_base_of_v<CachedResource, T>
419 void ResourceCache::Destroy( Handle<T>& handle, const bool immediate )
420 {
421 if ( handle == INVALID_HANDLE<T> ) [[unlikely]]
422 {
423 //Perfectly valid operation (e.g. material texture slots). Easier to check here instead of every single DestroyResourceCall.
424 NOP();
425 return;
426 }
427
428 if ( immediate || !s_enabled ) [[unlikely]]
429 {
430 GetPool<T>( s_renderAPI ).deallocate( handle );
431 }
432 else
433 {
434 GetPool<T>( s_renderAPI ).queueDeletion( handle );
435 }
436
437 }
438
439 template<typename T> requires std::is_base_of_v<Resource, T>
441 {
442 return GetMemPool<T>().newElement( descriptor );
443 }
444
445 template<>
446 inline ResourcePtr<ShaderProgram> ResourceCache::AllocateInternal<ShaderProgram>( const ResourceDescriptor<ShaderProgram>& descriptor )
447 {
448 switch ( s_renderAPI )
449 {
450 case RenderAPI::None: return GetMemPool<noShaderProgram>().newElement( *s_context, descriptor );
451 case RenderAPI::OpenGL: return GetMemPool<glShaderProgram>().newElement( *s_context, descriptor );
452 case RenderAPI::Vulkan: return GetMemPool<vkShaderProgram>().newElement( *s_context, descriptor );
453
455 }
456
457 return nullptr;
458 }
459
460 template<>
461 inline ResourcePtr<Texture> ResourceCache::AllocateInternal<Texture>( const ResourceDescriptor<Texture>& descriptor )
462 {
463 switch ( s_renderAPI )
464 {
465 case RenderAPI::None: return GetMemPool<noTexture>().newElement( *s_context, descriptor );
466 case RenderAPI::OpenGL: return GetMemPool<glTexture>().newElement( *s_context, descriptor );
467 case RenderAPI::Vulkan: return GetMemPool<vkTexture>().newElement( *s_context, descriptor );
469 }
470
471 return nullptr;
472 }
473
474 template<typename T> requires std::is_base_of_v<Resource, T>
476 {
477 ResourcePool<T>& pool = GetPool<T>( s_renderAPI );
478
479 LockGuard<SharedMutex> lock( pool._lock );
480 ResourcePtr<T> ptr = AllocateInternal<T>( descriptor );
481 if ( ptr != nullptr )
482 {
483 pool.commitLocked( handle, ptr );
484 }
485
486 return ptr;
487 }
488
489 template<typename T> requires std::is_base_of_v<Resource, T>
491 {
492 Time::ProfileTimer loadTimer{};
493
494 loadTimer.start();
495 if ( ptr != nullptr )
496 {
497 ptr->setState( ResourceState::RES_LOADING );
498
499 if ( ptr->load( *s_context ) ) [[likely]]
500 {
501 ptr->setState( ResourceState::RES_THREAD_LOADED );
502 }
503 else
504 {
505 ptr->setState( ResourceState::RES_UNKNOWN );
506 }
507 }
508 loadTimer.stop();
509 const F32 durationMS = Time::MicrosecondsToMilliseconds<F32>( loadTimer.get() );
510
511 if ( ptr != nullptr && ptr->getState() == ResourceState::RES_THREAD_LOADED ) [[likely]]
512 {
513 Console::printfn( LOCALE_STR( "RESOURCE_CACHE_BUILD" ),
514 descriptor.resourceName(),
515 ptr->typeName(),
516 ptr->getGUID(),
517 descriptor.getHash(),
518 durationMS );
519 }
520 else
521 {
522 Console::errorfn( LOCALE_STR( "RESOURCE_CACHE_BUILD_FAILED" ),
523 descriptor.resourceName(),
524 durationMS );
525 }
526 }
527
528 template<typename T> requires std::is_base_of_v<Resource, T>
529 ResourcePtr<T> ResourceCache::Allocate( const Handle<T> handle, const ResourceDescriptor<T>& descriptor, const size_t descriptorHash )
530 {
531 Time::ProfileTimer loadTimer{};
532 loadTimer.start();
533 ResourcePtr<T> ptr = AllocateAndCommit<T>( handle, descriptor );
534 loadTimer.stop();
535 const F32 durationMS = Time::MicrosecondsToMilliseconds<F32>( loadTimer.get() );
536
537 if ( ptr != nullptr ) [[likely]]
538 {
539 Console::printfn( LOCALE_STR( "RESOURCE_CACHE_ALLOCATE" ),
540 descriptor.resourceName(),
541 ptr->typeName(),
542 ptr->getGUID(),
543 descriptorHash,
544 durationMS );
545 }
546 else
547 {
548 Console::errorfn( LOCALE_STR("RESOURCE_CACHE_ALLOCATE_FAILED"),
549 descriptor.resourceName(),
550 durationMS );
551 }
552
553 return ptr;
554 }
555
556 template <typename T> requires std::is_base_of_v<CachedResource, T>
557 Handle<T> ResourceCache::LoadResource( const ResourceDescriptor<T>& descriptor, bool& wasInCache, std::atomic_uint& taskCounter )
558 {
560
561 taskCounter.fetch_add( 1u );
562
563 // The loading process may change the resource descriptor so always use the user-specified descriptor hash for lookup!
564 const size_t loadingHash = descriptor.getHash();
565
566 // If two threads are trying to load the same resource at the same time, by the time one of them adds the resource to the cache, it's too late
567 // So check if the hash is currently in the "processing" list, and if it is, just busy-spin until done
568 // Once done, lock the hash for ourselves
569 ResourceLoadLock res_lock( loadingHash, *s_context );
571
572 Handle<T> ret = RetrieveOrAllocateHandle<T>( loadingHash, wasInCache );
573 if ( wasInCache )
574 {
575 taskCounter.fetch_sub( 1u );
576 return ret;
577 }
578
579 Console::printfn( LOCALE_STR( "RESOURCE_CACHE_GET_RES" ), descriptor.resourceName().c_str(), loadingHash );
580
581 ResourcePtr<T> ptr = ResourceCache::Allocate<T>(ret, descriptor, loadingHash);
582
583 if ( ptr != nullptr )
584 {
585 Start( *CreateTask( [ptr, descriptor]( const Task& )
586 {
587 ResourceCache::Build<T>( ptr, descriptor );
588 }),
590 descriptor.waitForReady() ? TaskPriority::REALTIME : TaskPriority::DONT_CARE,
591 [ptr, ret, &taskCounter, resName = descriptor.resourceName()]()
592 {
593 DIVIDE_ASSERT(ret != INVALID_HANDLE<T>);
594
595 if ( ptr->getState() == ResourceState::RES_THREAD_LOADED && ptr->postLoad()) [[likely]]
596 {
597 ptr->setState( ResourceState::RES_LOADED );
598 }
599 else
600 {
601 Console::printfn( LOCALE_STR( "ERROR_RESOURCE_CACHE_LOAD_RES_NAME" ), resName.c_str() );
602 Handle<T> retCpy = ret;
603 GetPool<T>(s_renderAPI).deallocate( retCpy );
604 }
605
606 taskCounter.fetch_sub( 1u );
607 }
608 );
609 }
610
611 return ret;
612 }
613
614} //namespace Divide
615
616#endif //DVD_RESOURCE_CACHE_INL_
#define LOCALE_STR(X)
Definition: Localization.h:91
#define DIVIDE_ASSERT(...)
#define DIVIDE_UNEXPECTED_CALL()
#define NOP()
TaskPool & taskPool(const TaskPoolType type) noexcept
static ResourcePtr< T > AllocateInternal(const ResourceDescriptor< T > &descriptor)
static Handle< T > RetrieveFromCache(Handle< T > handle)
static void Destroy(Handle< T > &handle, const bool immediate)
static Handle< T > RetrieveOrAllocateHandle(size_t descriptorHash, bool &wasInCache)
static T * Get(Handle< T > handle)
static PlatformContext * s_context
static Handle< T > LoadResource(const ResourceDescriptor< T > &descriptor, bool &wasInCache, std::atomic_uint &taskCounter)
static ResourcePtr< T > Allocate(Handle< T > handle, const ResourceDescriptor< T > &descriptor, size_t descriptorHash)
static RenderAPI s_renderAPI
static void Build(ResourcePtr< T > ptr, const ResourceDescriptor< T > &descriptor)
static ResourcePtr< T > AllocateAndCommit(Handle< T > handle, const ResourceDescriptor< T > &descriptor)
Handle console commands that start with a forward slash.
Definition: AIProcessor.cpp:7
std::lock_guard< mutex > LockGuard
Definition: SharedMutex.h:55
bool SafeToDelete(Resource *res)
Definition: Resource.cpp:35
ResourcePool< T > & GetPool(const RenderAPI api)
T * ResourcePtr
Definition: Resource.h:112
MemPool< T > & GetMemPool()
@ RES_UNLOADING
The resource is unloading, deleting data, etc.
@ RES_LOADED
The resource is available for usage.
@ RES_THREAD_LOADED
The resource is loaded but not yet available.
@ RES_CREATED
The pointer has been created and instantiated, but no data has been loaded.
@ RES_LOADING
The resource is loading, creating data, parsing scripts, etc.
@ RES_UNKNOWN
The resource exists, but it's state is undefined.
Task * CreateTask(Predicate &&threadedFunction, bool allowedInIdle=true)
Definition: TaskPool.inl:45
std::shared_mutex SharedMutex
Definition: SharedMutex.h:43
std::unique_lock< mutex > UniqueLock
Definition: SharedMutex.h:52
constexpr U32 prevPOW2(U32 n) noexcept
Definition: MathHelper.inl:219
std::shared_lock< mutex > SharedLock
Definition: SharedMutex.h:49
MemoryPool< T, prevPOW2(sizeof(T)) *1u<< 5u > MemPool
@ REALTIME
not threaded
@ Vulkan
not supported yet
@ None
No rendering. Used for testing or server code.
void Start(Task &task, TaskPool &pool, TaskPriority priority=TaskPriority::DONT_CARE, const DELEGATE< void > &onCompletionFunction={})
Definition: Task.cpp:9
uint32_t U32
Project const SceneEntry & entry
Definition: DefaultScene.h:41
static NO_INLINE void errorfn(const char *format, T &&... args)
static NO_INLINE void printfn(const char *format, T &&... args)
size_t getHash() const final
Definition: Resource.inl:71
U32 _refCount
size_t _descriptorHash
ResourcePtr< T > _ptr
virtual void printResources(bool error)=0
virtual void processDeletionQueue()=0
void deallocate(Handle< T > &handle)
eastl::fixed_vector< Entry, ResourcePoolSize, true > _resPool
void processDeletionQueue() override
void deallocateInternal(ResourcePtr< T > ptr)
static constexpr size_t ResourcePoolSize
Handle< T > retrieveHandleLocked(const size_t descriptorHash)
ResourcePtr< T > get(Handle< T > handle)
eastl::fixed_vector< std::pair< bool, U8 >, ResourcePoolSize, true > _freeList
moodycamel::ConcurrentQueue< Handle< T > > _deletionQueue
void queueDeletion(Handle< T > &handle)
void commitLocked(Handle< T > handle, ResourcePtr< T > ptr)
void resize(size_t size)
ResourcePool(RenderAPI api)
Handle< T > allocateLocked(size_t descriptorHash)
Handle< T > allocate(size_t descriptorHash)
void printResources(bool error) final