Vulkan 学习笔记 05
本章的主要内容是关于资源管理,设计了一个基于句柄的资源管理器。为什么使用句柄呢,因为这样可以隔离实际资源的内存指针与用户接触的可能,尽量保证数据安全性。由于资源统一管理,既可以在这里接入统一的内存管理,避免四处修改代码,也可以做资源利用统计,加入一些缓存算法,提高效率。不过这个是第一次设计,肯定会有许多不足,后续应该会逐步优化。
为了方便使用,尽量设计了统一的接口。
#pragma once
#include "runtime/core/base/hash.h"
#include <atomic>
#include <cstdint>
#include <limits>
namespace ArchViz
{
using ResourceTypeId = uint16_t;
using MagicId = uint16_t;
using ResourceId = uint64_t;
constexpr MagicId k_magic_id = 0x1234;
constexpr ResourceTypeId k_invalid_resource_type_id = std::numeric_limits<uint16_t>::max();
constexpr ResourceId k_invalid_resource_id = std::numeric_limits<uint64_t>::max();
class ResourceTypeIdAllocator
{
public:
static ResourceTypeId alloc();
private:
static std::atomic<ResourceTypeId> m_next_id;
};
class ResourceIdAllocator
{
public:
static ResourceId alloc();
private:
static std::atomic<ResourceId> m_next_id;
};
struct ResHandle
{
MagicId magic {k_magic_id};
ResourceTypeId type;
ResourceId index;
};
static ResHandle k_invalid_res_handle {0, k_invalid_resource_type_id, k_invalid_index};
template<>
inline bool is_valid_handle(const ResHandle& handle)
{
return handle.index != k_invalid_index && handle.type != k_invalid_resource_type_id && handle.magic == k_magic_id;
}
constexpr uint32_t k_max_resource_count = 1024;
constexpr size_t k_max_resource_size = 1024 * 1024 * 40; // 40 Mb
} // namespace ArchViz
namespace std
{
template<>
struct hash<ArchViz::ResHandle>
{
size_t operator()(ArchViz::ResHandle const& handle) const
{
size_t handle_hash = 0;
ArchViz::hash_combine(handle_hash, handle.magic, handle.type, handle.index);
return handle_hash;
}
};
} // namespace std
#include "runtime/resource/resource_manager/resource_handle.h"
#include "runtime/core/base/macro.h"
namespace ArchViz
{
std::atomic<ResourceTypeId> ResourceTypeIdAllocator::m_next_id {0};
std::atomic<ResourceId> ResourceIdAllocator::m_next_id {0};
ResourceTypeId ResourceTypeIdAllocator::alloc()
{
std::atomic<ResourceTypeId> new_object_ret = m_next_id.load();
m_next_id++;
if (m_next_id >= k_invalid_resource_type_id)
{
LOG_FATAL("gobject id overflow");
}
return new_object_ret;
}
ResourceId ResourceIdAllocator::alloc()
{
std::atomic<ResourceId> new_object_ret = m_next_id.load();
m_next_id++;
if (m_next_id >= k_invalid_resource_id)
{
LOG_FATAL("gobject id overflow");
}
return new_object_ret;
}
} // namespace ArchViz
#pragma once
#include "runtime/core/base/macro.h"
#include "runtime/resource/resource_manager/resource_handle.h"
#include <array>
#include <atomic>
#include <cassert>
#include <memory>
#include <unordered_map>
namespace ArchViz
{
// The virtual inheritance of IComponentArray is unfortunate but, as far as I can tell, unavoidable. As seen later, we'll have a list of every ComponentArray (one per component type), and we need
// to notify all of them when an entity is destroyed so that it can remove the entity's data if it exists. The only way to keep a list of multiple templated types is to keep a list of their common
// interface so that we can call EntityDestroyed() on all of them.
class IResourceArray
{
public:
virtual ~IResourceArray() = default;
virtual void handleDestroyed(const ResourceId& res) = 0;
};
template<typename T>
class ResourceArray : public IResourceArray
{
public:
explicit ResourceArray(size_t count, size_t size);
virtual ~ResourceArray() = default;
std::weak_ptr<T> getData(const ResourceId& handle);
void insertData(const ResourceId& handle, std::shared_ptr<T> resource, size_t size);
void removeData(const ResourceId& handle);
void handleDestroyed(const ResourceId& handle) override;
private:
size_t m_max_count;
size_t m_max_size;
std::vector<std::shared_ptr<T>> m_resources {};
std::vector<size_t> m_resource_sizes {};
std::unordered_map<ResourceId, size_t> m_handle_to_index {};
std::unordered_map<size_t, ResourceId> m_index_to_handle {};
size_t m_array_size {0};
size_t m_content_size {0};
};
} // namespace ArchViz
#include "runtime/resource/resource_manager/resource_array.h"
namespace ArchViz
{
template<typename T>
ResourceArray<T>::ResourceArray(size_t count, size_t size) : m_max_count(count), m_max_size(size)
{
m_resources.resize(m_max_count);
m_resource_sizes.resize(m_max_count);
}
template<typename T>
void ResourceArray<T>::insertData(const ResourceId& handle, std::shared_ptr<T> resource, size_t size)
{
ASSERT(m_handle_to_index.count(handle) == 0 && "Component added to same entity more than once.");
if (m_content_size >= m_max_size)
{
LOG_WARN("Resource Array size exceed max content size: {}", m_max_size);
}
if (m_array_size >= m_max_size)
{
LOG_ERROR("Resource Array size exceed max array size: {}", m_array_size);
}
// Put new entry at end
size_t new_index = m_array_size;
m_handle_to_index[handle] = new_index;
m_index_to_handle[new_index] = handle;
m_resources[new_index] = resource;
m_resource_sizes[new_index] = size;
m_content_size += size;
m_array_size += 1;
}
template<typename T>
std::weak_ptr<T> ResourceArray<T>::getData(const ResourceId& handle)
{
// we can get non-existent resource with nullptr
// ASSERT(m_handle_to_index.count(handle) != 0 && "Retrieving non-existent resource.");
return m_resources[m_handle_to_index[handle]];
}
template<typename T>
void ResourceArray<T>::removeData(const ResourceId& handle)
{
ASSERT(m_handle_to_index.count(handle) != 0 && "Removing non-existent resource.");
// Copy element at end into deleted element's place to maintain density
// with smart pointer to avoid real memory copy
size_t index_of_removed_handle = m_handle_to_index[handle];
size_t index_of_last_element = m_array_size - 1;
m_resources[index_of_removed_handle] = m_resources[index_of_last_element];
m_resources[index_of_last_element] = nullptr;
// Update map to point to moved spot
ResHandle& handle_of_last_element = m_index_to_handle[index_of_last_element];
m_handle_to_index[index_of_last_element] = index_of_removed_handle;
m_index_to_handle[index_of_removed_handle] = handle_of_last_element;
// update size record
m_content_size -= m_resource_sizes[index_of_removed_handle];
m_resource_sizes[index_of_removed_handle] = m_resource_sizes[index_of_last_element];
m_resource_sizes[index_of_last_element] = 0;
// remove unused record
m_handle_to_index.erase(handle);
m_index_to_handle.erase(index_of_last_element);
--m_array_size;
}
template<typename T>
void ResourceArray<T>::handleDestroyed(const ResourceId& handle)
{
if (m_handle_to_index.count(handle) != 0)
{
removeData(handle);
}
}
} // namespace ArchViz
#pragma once
#include <memory>
#include <string>
#include <utility>
namespace ArchViz
{
class ILoader
{
public:
virtual ~ILoader() = default;
};
// create a resource from CI
template<typename T, typename CI>
class Loader : public ILoader
{
public:
virtual ~Loader() = default;
virtual std::pair<std::shared_ptr<T>, size_t> createResource(const CI& create_info) = 0;
virtual std::pair<std::shared_ptr<T>, size_t> createResource(const std::string& uri) = 0;
};
} // namespace ArchViz
// https://floooh.github.io/2018/06/17/handles-vs-pointers.html
// https://giordi91.github.io/post/resourcesystem/
#pragma once
#include "runtime/core/base/instanceof.h"
#include "runtime/core/base/macro.h"
#include "runtime/resource/resource_manager/loader/loader.h"
#include "runtime/resource/resource_manager/resource_array.h"
#include "runtime/resource/resource_manager/resource_handle.h"
#include <memory>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <unordered_map>
namespace ArchViz
{
struct ResourceManagerCreateInfo
{
std::unordered_map<const char*, size_t> resource_max_counts;
std::unordered_map<const char*, size_t> resource_max_sizes;
};
// a handle based simple resource manager
class ResourceManager
{
public:
void initialize();
void clear();
template<typename T>
void registerResourceType();
template<typename T>
ResourceTypeId getResourceType() const;
const char* getResourceTypeName(ResourceTypeId type) const;
template<typename T, typename L>
void registerResourceLoader();
template<typename T>
ResHandle createHandle();
template<typename T, typename CI>
std::weak_ptr<T> loadResource(const std::string& uri);
template<typename T, typename CI>
std::weak_ptr<T> loadResource(const std::string& uri, const CI& create_info);
template<typename T>
void addResource(const ResHandle& handle, std::shared_ptr<T> res);
template<typename T>
void removeResource(const ResHandle& handle);
template<typename T>
void removeResource(const std::string& uri);
template<typename T>
std::weak_ptr<T> getResource(const ResHandle& handle);
private:
template<typename T>
std::shared_ptr<ResourceArray<T>> getResourceArray()
{
const char* type_name = typeid(T).name();
ASSERT(m_resource_types.count(type_name) != 0 && "Component not registered before use.");
return std::static_pointer_cast<ResourceArray<T>>(m_resource_arrays[type_name]);
}
template<typename T>
std::shared_ptr<ResourceArray<T>> getResourceArray(ResourceTypeId type)
{
ASSERT(m_resource_id_arrays.count(type) != 0 && "Component not registered before use.");
return std::static_pointer_cast<ResourceArray<T>>(m_resource_id_arrays[type]);
}
private:
std::unordered_map<const char*, size_t> m_resource_max_counts; // software constraints
std::unordered_map<const char*, size_t> m_resource_max_sizes; // hardware constraints
std::unordered_map<const char*, ResourceTypeId> m_resource_types; // resource type map
std::unordered_map<ResourceTypeId, const char*> m_resource_types_inv; // resource type map
std::unordered_map<const char*, std::shared_ptr<IResourceArray>> m_resource_arrays; // resource storage
std::unordered_map<ResourceTypeId, std::shared_ptr<IResourceArray>> m_resource_id_arrays; // resource storage
std::unordered_map<const char*, std::shared_ptr<ILoader>> m_resource_loaders; // typeid -> loader
std::unordered_map<std::string, ResHandle> m_resource_handles; // uri -> handle
std::unordered_map<ResHandle, std::string> m_resource_handles_inv; // uri -> handle
};
} // namespace ArchViz
#include "runtime/resource/resource_manager/resource_manager.h"
#include "runtime/resource/resource_manager/loader/material_loader.h"
#include "runtime/resource/resource_manager/loader/obj_loader.h"
#include "runtime/resource/resource_manager/loader/texture_loader.h"
#include "runtime/resource/res_type/data/material_data.h"
#include "runtime/resource/res_type/data/mesh_data.h"
#include "runtime/core/base/hash.h"
#include "runtime/core/base/macro.h"
namespace ArchViz
{
void ResourceManager::initialize()
{
registerResourceType<MeshData>();
registerResourceType<TextureData>();
registerResourceType<MaterialData>();
registerResourceLoader<MeshData, ObjLoader>();
registerResourceLoader<TextureData, TextureLoader>();
registerResourceLoader<MaterialData, MaterialLoader>();
}
void ResourceManager::clear()
{
m_resource_max_counts.clear();
m_resource_max_sizes.clear();
m_resource_types.clear();
m_resource_types_inv.clear();
m_resource_arrays.clear();
m_resource_id_arrays.clear();
m_resource_loaders.clear();
m_resource_handles.clear();
}
template<typename T>
void ResourceManager::registerResourceType()
{
const std::type_info& type = typeid(T);
if (m_resource_types.count(type.name()) == 0)
{
// TODO : read from config file
ResourceTypeId id = ResourceTypeIdAllocator::alloc();
m_resource_types[type.name()] = id;
m_resource_types_inv[id] = type.name();
m_resource_arrays[type.name()] = std::make_shared<ResourceArray<T>>(k_max_resource_count, k_max_resource_size);
m_resource_id_arrays[id] = m_resource_arrays[type.name()];
// TODO : use this to trigger memory collect
m_resource_max_counts[type.name()] = k_max_resource_count;
m_resource_max_sizes[type.name()] = k_max_resource_size;
}
else
{
LOG_WARN("Registering resource type: {} more than once.", type.name());
}
}
template<typename T>
ResourceTypeId ResourceManager::getResourceType() const
{
const std::type_info& type = typeid(T);
if (m_resource_types.count(type.name()) != 0)
{
const std::type_info& type = typeid(T);
return m_resource_types[type.name()];
}
else
{
return k_invalid_resource_type_id;
}
}
const char* ResourceManager::getResourceTypeName(ResourceTypeId type) const
{
static const char* invalid_name = "invalid_type_id";
if (m_resource_types_inv.count(type) == 0)
{
LOG_ERROR("try to get invalid type id's name: {}", type);
return invalid_name;
}
else
{
// cannot use [] operator here
auto it = m_resource_types_inv.find(type);
return it->second;
}
}
template<typename T, typename L>
void ResourceManager::registerResourceLoader()
{
// one resource type can only have one loader now
const std::type_info& type = typeid(T);
if (m_resource_loaders.count(type.name()) == 0)
{
std::shared_ptr<L> loader = std::make_shared<L>();
// must be a ILoader class
bool is_loader = std::is_base_of<ILoader, L>::value;
ASSERT(is_loader && "not a valid loader class");
m_resource_loaders[type.name()] = loader;
}
}
template<typename T>
ResHandle ResourceManager::createHandle()
{
const auto& type = typeid(T);
auto type_id = m_resource_types[type.name()];
auto index = ResourceIdAllocator::alloc();
return {k_magic_id, type_id, index};
}
template<typename T>
void ResourceManager::addResource(const ResHandle& handle, std::shared_ptr<T> res)
{
// Add a component to the array for an handle
getResourceArray<T>(handle.type)->insertData(handle.index, res);
}
template<typename T>
void ResourceManager::removeResource(const ResHandle& handle)
{
if (m_resource_handles_inv.count(handle) == 0)
{
LOG_WARN("remove non-exist resource handle: {}", handle.index);
return;
}
// Remove a component from the array for an handle
getResourceArray<T>(handle.type)->removeData(handle.index);
std::string name = m_resource_handles_inv[handle];
m_resource_handles_inv.erase(handle);
m_resource_handles.erase(name);
}
template<typename T>
void ResourceManager::removeResource(const std::string& uri)
{
if (m_resource_handles.count(uri) == 0)
{
LOG_WARN("remove non-exist resource: {}", uri);
return;
}
auto handle = m_resource_handles[uri];
// Remove a component from the array for an handle
getResourceArray<T>(handle.type)->removeData(handle.index);
m_resource_handles_inv.erase(handle);
m_resource_handles.erase(uri);
}
template<typename T>
std::weak_ptr<T> ResourceManager::getResource(const ResHandle& handle)
{
// Get a reference to a component from the array for an handle
return getResourceArray<T>(handle.type)->getData(handle.index);
}
template<typename T, typename CI>
std::weak_ptr<T> ResourceManager::loadResource(const std::string& uri)
{
if (m_resource_handles.count(uri) != 0)
{
return getResource<T>(m_resource_handles[uri]);
}
const std::type_info& type = typeid(T);
if (m_resource_loaders.count(type.name()) == 0)
{
LOG_ERROR("cannot find a valid loadr for: {}", type.name());
return {};
}
std::shared_ptr<Loader<T, CI>> loader = std::static_pointer_cast<Loader<T, CI>>(m_resource_loaders[type.name()]);
std::shared_ptr<T> res = loader.createResource(uri);
if (res != nullptr)
{
auto handle = createHandle<T>();
m_resource_handles[uri] = res;
m_resource_handles_inv[handle] = res;
addResource<T>(handle, res);
return res;
}
return {};
}
template<typename T, typename CI>
std::weak_ptr<T> ResourceManager::loadResource(const std::string& uri, const CI& create_info)
{
if (m_resource_handles.count(uri) != 0)
{
return getResource<T>(m_resource_handles[uri]);
}
const std::type_info& type = typeid(T);
if (m_resource_loaders.count(type.name()) == 0)
{
LOG_ERROR("cannot find a valid loadr for: {}", type.name());
return {};
}
std::shared_ptr<Loader<T, CI>> loader = std::static_pointer_cast<Loader<T, CI>>(m_resource_loaders[type.name()]);
std::shared_ptr<T> res = loader.createResource(create_info);
if (res != nullptr)
{
auto handle = createHandle<T>();
m_resource_handles[uri] = res;
m_resource_handles_inv[handle] = res;
addResource<T>(handle, res);
return res;
}
return {};
}
} // namespace ArchViz