Wrecked the last commit. Async loading works now.

This commit is contained in:
William Moberg
2016-01-13 18:51:45 +01:00
parent a143ae7c85
commit 9f185a1e35
11 changed files with 194 additions and 199 deletions
+100 -45
View File
@@ -12,14 +12,33 @@
/** Base Resource class.
Implement this class for every resource to be handled by the resource manager.
Implement Create() to return a new object of that type.
If it should be possible to load the resource asyncronously (on a separate thread in the background),
using LoadAsync() then any necessary OpenGL calls (Eg. glGenBuffers(), glBindBuffer(), etc.) must be
made in GlCommands() method, and not inside the constructor (see Texture.cpp for an example).
*/
class Resource
{
friend class ResourceManager;
private:
bool m_FullyConstructed;
protected:
Resource() { }
Resource() : m_FullyConstructed(false) { }
//This method only needs to be overridden if:
// 1: a) It should be possible to load the resource with LoadAsync(), or
// b) This resource will be loaded inside the constructor of another resource that can be loaded with LoadAsync().
// c) Same as above but recursively.
// 2: a) The resource needs to make OpenGL calls, or
// b) The resource contains a resource that makes OGL calls, or
// c) The resource contains a resource that contains ... ... a resource that makes OGL calls, or
//
//If 2.a: any calls should be made in the GlCommands.
//If 2.b or 2.c: PostCtorGLCommands() should be called on the contained resource(s).
//If GlCommands needs to be overridden and GlCommands is also overridden by a baseclass then the baseclass
//implementation should be called in from the derived class implementation (see Model.cpp for an example).
virtual void GlCommands() { }
public:
// Pretend that this is a pure virtual function that you have to implement
@@ -28,6 +47,16 @@ public:
virtual void Reload() { }
virtual void OnChildReloaded(Resource* child) { }
//If this resource implements GlCommands and it is loaded in another resource,
//then the containing resource must also implement GlCommands and
//call this method in it (see RawModel.cpp for an example).
void PostCtorGLCommands()
{
if (!m_FullyConstructed) {
GlCommands();
}
m_FullyConstructed = true;
}
unsigned int TypeID;
unsigned int ResourceID;
@@ -40,30 +69,6 @@ private:
ResourceManager();
public:
//TODO: Check if this is ever used, and remove it if it isn't.
//Why would a resource load another resource async. in the ctor?
//Should be thrown in a Resource's constructor if it cannot complete because another resource is still loading.
//Eg. If a Model loads a Texture asyncronously in the constructor, and it is not done yet.
struct StillLoadingException : public std::exception
{
virtual const char* what() const throw()
{
return "Resource is still loading.";
}
};
//Should be thrown in a Resource's constructor if certain work needs to be handled by
//the main thread, and not a parallel worker thread. Eg. opengl commands.
struct WorkerCannotExecute : public std::exception
{
virtual const char* what() const throw()
{
return "Worker thread is unable to execute, main thread need to handle the code.";
}
};
static void AssertIsMainThread();
static bool IsMainThread();
/*static ResourceManager& Instance()
{
static ResourceManager s;
@@ -89,7 +94,6 @@ public:
*/
template <typename T>
static T* LoadAsync(std::string resourceName, Resource* parent = nullptr);
static Resource* LoadAsync(std::string resourceType, std::string resourceName, Resource* parent = nullptr);
/** Hot-loads a resource and caches it for future use.
Fairly safe to assume that return value is a valid pointer, will only return nullptr on error.
@@ -99,7 +103,6 @@ public:
*/
template <typename T>
static T* Load(std::string resourceName, Resource* parent = nullptr);
static Resource* Load(std::string resourceType, std::string resourceName, Resource* parent = nullptr);
/** Reloads an already loaded resource, keeping its resource ID intact.
@@ -113,25 +116,15 @@ public:
static void Update();
private:
//Represents pointer values to signify that a resource haven't failed, but is not fully loaded.
class SpecialResourcePointer
//This is a haxy way to make sure that IsMainThread is run when the program starts, so the master thread id is set.
struct MasterThreadChecker
{
public:
SpecialResourcePointer()
: m_Val(new Resource())
{}
~SpecialResourcePointer()
MasterThreadChecker()
{
delete m_Val;
ResourceManager::IsMainThread();
}
//Make this class implicitly convertible to the Resource*.
operator Resource* const() const { return m_Val; }
private:
Resource* const m_Val;
};
//TODO: Check if this is ever used, and remove it if it isn't.
static SpecialResourcePointer m_StillLoading;
static SpecialResourcePointer m_LoadWithMainThread;
static MasterThreadChecker m_Checker;
static std::unordered_map<std::string, std::string> m_CompilerTypenameToResourceType;
static std::unordered_map<std::string, std::function<Resource*(std::string)>> m_FactoryFunctions; // type -> factory function
@@ -156,6 +149,10 @@ private:
// Internal: Create a resource and cache it
static Resource* createResource(std::string resourceType, std::string resourceName, Resource* parent);
static bool IsMainThread();
template <bool Async>
static Resource* Load(std::string resourceType, std::string resourceName, Resource* parent = nullptr);
};
template <typename T>
@@ -168,7 +165,7 @@ T* ResourceManager::Load(std::string resourceName, Resource* parent /* = nullptr
return nullptr;
}
return static_cast<T*>(Load(it->second, resourceName, parent));
return static_cast<T*>(Load<false>(it->second, resourceName, parent));
}
template <typename T>
@@ -188,7 +185,65 @@ T* ResourceManager::LoadAsync(std::string resourceName, Resource* parent /* = nu
return nullptr;
}
return static_cast<T*>(LoadAsync(it->second, resourceName, parent));
return static_cast<T*>(Load<true>(it->second, resourceName, parent));
}
template <bool Async>
static Resource* ResourceManager::Load(std::string resourceType, std::string resourceName, Resource* parent /* = nullptr */)
{
auto cacheKey = std::make_pair(resourceType, resourceName);
decltype(m_ResourceCache)::iterator it;
//If a thread has already been launched to load this resource.
auto tIt = m_LoadingThreads.find(cacheKey);
if (tIt != m_LoadingThreads.end()) {
if (Async) {
//Return null if the thread is still working.
if (!tIt->second.try_join_for(boost::chrono::nanoseconds(1))) {
return nullptr;
}
//Else we know the thread has completed.
} else {
//Wait for the thread to finish loading.
tIt->second.join();
}
//When the thread is done, delete the thread.
m_LoadingThreads.erase(tIt);
//Find the resource that the thread loaded.
it = m_ResourceCache.find(cacheKey);
if (it != m_ResourceCache.end()) {
//At this point the resource may not be completely
//done since the worker threads cannot do opengl commands.
if (IsMainThread()) {
//Do the gl commands to complete the resource if we are not a worker thread.
it->second->PostCtorGLCommands();
}
return it->second;
} else {
//If resource is still null at cacheKey after thread finishes, it failed.
return nullptr;
}
}
//If resource has already been loaded and cached.
it = m_ResourceCache.find(cacheKey);
if (it != m_ResourceCache.end()) {
return it->second;
}
//If resource is not cached..
if (Async) {
//Create a thread that loads the resource into cache.
m_LoadingThreads[cacheKey] = boost::thread(createResource, resourceType, resourceName, parent);
return nullptr;
} else {
//load and return the resource.
Resource* res = createResource(resourceType, resourceName, parent);
if (IsMainThread() && res != nullptr) {
//Do the gl commands to complete the resource if we are not a worker thread.
res->PostCtorGLCommands();
}
return res;
}
}
#endif
+1
View File
@@ -10,6 +10,7 @@ class Model : public RawModel
private:
Model(std::string fileName);
virtual void GlCommands() override;
public:
~Model();
+1
View File
@@ -24,6 +24,7 @@ class RawModel : public Resource
protected:
RawModel(std::string fileName);
virtual void GlCommands() override;
public:
~RawModel();
+3
View File
@@ -11,7 +11,10 @@ class Texture : public BaseTexture
private:
Texture(std::string path);
virtual void GlCommands() override;
GLint m_Format;
Image* m_Image;
public:
~Texture();
+2 -2
View File
@@ -220,7 +220,7 @@ bool attachAABBComponentFromModel(World* world, EntityID id)
}
ComponentWrapper model = world->GetComponent(id, "Model");
ComponentWrapper collision = world->AttachComponent(id, "AABB");
Model* modelRes = ResourceManager::Load<Model>(model["Resource"]);
Model* modelRes = ResourceManager::LoadAsync<Model>(model["Resource"]);
if (modelRes == nullptr) {
return false;
}
@@ -247,7 +247,7 @@ bool GetEntityBox(World* world, ComponentWrapper& AABBComponent, AABB& outBox)
{
ComponentWrapper& cTrans = world->GetComponent(AABBComponent.EntityID, "Transform");
ComponentWrapper model = world->GetComponent(AABBComponent.EntityID, "Model");
Model* modelRes = ResourceManager::Load<Model>(model["Resource"]);
Model* modelRes = ResourceManager::LoadAsync<Model>(model["Resource"]);
outBox.CreateFromCenter(AABBComponent["BoxCenter"], AABBComponent["BoxSize"]);
glm::vec3 mini = outBox.MinCorner();
glm::vec3 maxi = outBox.MaxCorner();
+1 -92
View File
@@ -14,8 +14,6 @@ std::unordered_map<unsigned int, unsigned int> ResourceManager::m_ResourceCount;
FileWatcher ResourceManager::m_FileWatcher;
std::unordered_map<std::pair<std::string, std::string>, boost::thread> ResourceManager::m_LoadingThreads;
boost::recursive_mutex ResourceManager::m_Mutex;
ResourceManager::SpecialResourcePointer ResourceManager::m_StillLoading;
ResourceManager::SpecialResourcePointer ResourceManager::m_LoadWithMainThread;
unsigned int ResourceManager::GetTypeID(std::string resourceType)
{
@@ -80,86 +78,6 @@ void ResourceManager::Update()
m_FileWatcher.Check();
}
Resource* ResourceManager::LoadAsync(std::string resourceType, std::string resourceName, Resource* parent /*= nullptr*/)
{
auto cacheKey = std::make_pair(resourceType, resourceName);
decltype(m_ResourceCache)::iterator it;
//If a thread has already been launched to load this resource.
auto tIt = m_LoadingThreads.find(cacheKey);
if (tIt != m_LoadingThreads.end()) {
//If the thread is still working.
if (tIt->second.joinable()) {
return nullptr;
}
//Else, the thread is done.
m_LoadingThreads.erase(tIt);
it = m_ResourceCache.find(cacheKey);
if (it != m_ResourceCache.end()) {
//If the thread is done, but it cannot complete the rest, main thread must complete construction.
if (it->second == m_LoadWithMainThread) {
AssertIsMainThread();
return createResource(resourceType, resourceName, parent);
}
return it->second;
} else {
//If cache is still empty at cacheKey after thread finishes, it failed.
return nullptr;
}
}
//If resource has already been loaded and cached.
it = m_ResourceCache.find(cacheKey);
if (it != m_ResourceCache.end()) {
//if ConstructByMainThread, createResource from Main.
return it->second;
}
//Create a thread that loads the resource into cache.
m_LoadingThreads[cacheKey] = boost::thread(createResource, resourceType, resourceName, parent);
return nullptr;
}
Resource* ResourceManager::Load(std::string resourceType, std::string resourceName, Resource* parent /*= nullptr*/)
{
Resource* resource;
auto cacheKey = std::make_pair(resourceType, resourceName);
decltype(m_ResourceCache)::iterator it;
//If a thread has already been launched to load this resource.
auto tIt = m_LoadingThreads.find(cacheKey);
if (tIt != m_LoadingThreads.end()) {
//Wait for the thread to finish loading.
tIt->second.join();
//Then delete the thread.
m_LoadingThreads.erase(tIt);
it = m_ResourceCache.find(cacheKey);
if (it != m_ResourceCache.end()) {
//If the thread is done, but it cannot complete the rest, main thread must complete construction.
if (it->second == m_LoadWithMainThread) {
AssertIsMainThread();
return createResource(resourceType, resourceName, parent);
}
return it->second;
} else {
//If cache is still empty at cacheKey after thread finishes, it failed.
return nullptr;
}
}
//If resource has already been loaded and cached.
it = m_ResourceCache.find(cacheKey);
if (it != m_ResourceCache.end()) {
return it->second;
}
//If resource is not cached, load and return it.
resource = createResource(resourceType, resourceName, parent);
if (resource == m_LoadWithMainThread) {
//If we entered here then we know the caller is a worker thread.
//And we know the resource must be loaded from master thread.
throw(WorkerCannotExecute());
}
return resource;
}
Resource* ResourceManager::createResource(std::string resourceType, std::string resourceName, Resource* parent)
{
//Lock the mutex immediately, and unlock it when leaving the function.
@@ -174,12 +92,10 @@ Resource* ResourceManager::createResource(std::string resourceType, std::string
Resource* resource = nullptr;
try {
resource = facIt->second(resourceName);
} catch (const WorkerCannotExecute& e) {
resource = m_LoadWithMainThread;
} catch (const std::exception& e) {
LOG_ERROR("Failed to load resource \"%s\" of type \"%s\": %s", resourceName.c_str(), resourceType.c_str(), e.what());
}
if (resource != nullptr && resource != m_LoadWithMainThread) {
if (resource != nullptr) {
// Store IDs
resource->TypeID = GetTypeID(resourceType);
resource->ResourceID = GetNewResourceID(resource->TypeID);
@@ -204,10 +120,3 @@ bool ResourceManager::IsMainThread()
static boost::thread::id MainThreadId = boost::this_thread::get_id();
return boost::this_thread::get_id() == MainThreadId;
}
void ResourceManager::AssertIsMainThread()
{
if (!IsMainThread()) {
throw WorkerCannotExecute();
}
}
+54 -47
View File
@@ -3,58 +3,65 @@
Model::Model(std::string fileName)
: RawModel(fileName)
{
// Generate GL buffers
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, m_Vertices.size() * sizeof(Vertex), &m_Vertices[0], GL_STATIC_DRAW);
}
glGenBuffers(1, &ElementBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ElementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_Indices.size() * sizeof(unsigned int), &m_Indices[0], GL_STATIC_DRAW);
void Model::GlCommands()
{
//Call the base class method.
RawModel::GlCommands();
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
GLERROR("GLEW: BufferFail4");
// Generate GL buffers
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, m_Vertices.size() * sizeof(Vertex), &m_Vertices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
std::vector<int> structSizes = { 3, 3, 3, 3, 2, 4, 4, 4, 4, 4, 4 };
int stride = 0;
for (int size : structSizes) {
stride += size;
}
stride *= sizeof(GLfloat);
int offset = 0;
{
int element = 0;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * offset)); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
}
GLERROR("GLEW: BufferFail5");
glGenBuffers(1, &ElementBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ElementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_Indices.size() * sizeof(unsigned int), &m_Indices[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnableVertexAttribArray(6);
glEnableVertexAttribArray(7);
glEnableVertexAttribArray(8);
glEnableVertexAttribArray(9);
glEnableVertexAttribArray(10);
GLERROR("GLEW: BufferFail5");
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
GLERROR("GLEW: BufferFail4");
//CreateBuffers();
glBindBuffer(GL_ARRAY_BUFFER, buffer);
std::vector<int> structSizes = { 3, 3, 3, 3, 2, 4, 4, 4, 4, 4, 4 };
int stride = 0;
for (int size : structSizes) {
stride += size;
}
stride *= sizeof(GLfloat);
int offset = 0;
{
int element = 0;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * offset)); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
glVertexAttribPointer(element, structSizes[element], GL_FLOAT, GL_FALSE, stride, (GLvoid*)(sizeof(GLfloat) * (offset += structSizes[element - 1]))); element++;
}
GLERROR("GLEW: BufferFail5");
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnableVertexAttribArray(6);
glEnableVertexAttribArray(7);
glEnableVertexAttribArray(8);
glEnableVertexAttribArray(9);
glEnableVertexAttribArray(10);
GLERROR("GLEW: BufferFail5");
//CreateBuffers();
}
Model::~Model()
+16
View File
@@ -292,6 +292,22 @@ RawModel::RawModel(std::string fileName)
}
}
void RawModel::GlCommands()
{
//Since RawModel contains Textures that was loaded in the constructor, their GlCommands must be run.
for (auto& texGroup : TextureGroups) {
if (texGroup.Texture) {
texGroup.Texture->PostCtorGLCommands();
}
if (texGroup.NormalMap) {
texGroup.NormalMap->PostCtorGLCommands();
}
if (texGroup.SpecularMap) {
texGroup.SpecularMap->PostCtorGLCommands();
}
}
}
RawModel::~RawModel()
{
if (m_Skeleton) {
@@ -84,7 +84,6 @@ void RenderQueueFactory::FillModels(World* world, RenderQueue* renderQueue)
continue;
}
glm::vec4 color = modelC["Color"];
//Model* model = ResourceManager::Load<Model>(resource);
Model* model = ResourceManager::LoadAsync<Model>(resource);
if (model == nullptr) {
model = ResourceManager::Load<Model>("Models/Core/Error.obj");
+16 -11
View File
@@ -2,39 +2,44 @@
Texture::Texture(std::string path)
{
PNG image(path);
m_Image = new PNG(path);
if (image.Width == 0 && image.Height == 0 || image.Format == Image::ImageFormat::Unknown) {
image = PNG("Textures/Core/ErrorTexture.png");
if (image.Width == 0 && image.Height == 0 || image.Format == Image::ImageFormat::Unknown) {
if (m_Image->Width == 0 && m_Image->Height == 0 || m_Image->Format == Image::ImageFormat::Unknown) {
delete m_Image;
m_Image = new PNG("Textures/Core/ErrorTexture.png");
if (m_Image->Width == 0 && m_Image->Height == 0 || m_Image->Format == Image::ImageFormat::Unknown) {
LOG_ERROR("Couldn't even load the error texture. This is a dark day indeed.");
return;
}
}
this->Width = image.Width;
this->Height = image.Height;
this->Width = m_Image->Width;
this->Height = m_Image->Height;
GLint format;
switch (image.Format) {
switch (m_Image->Format) {
case Image::ImageFormat::RGB:
format = GL_RGB;
m_Format = GL_RGB;
break;
case Image::ImageFormat::RGBA:
format = GL_RGBA;
m_Format = GL_RGBA;
break;
}
}
void Texture::GlCommands()
{
// Construct the OpenGL texture
glGenTextures(1, &m_Texture);
glBindTexture(GL_TEXTURE_2D, m_Texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, format, image.Width, image.Height, 0, format, GL_UNSIGNED_BYTE, image.Data);
glTexImage2D(GL_TEXTURE_2D, 0, m_Format, Width, Height, 0, m_Format, GL_UNSIGNED_BYTE, m_Image->Data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GLERROR("Texture load");
delete m_Image;
m_Image = nullptr;
}
Texture::~Texture()
-1
View File
@@ -4,7 +4,6 @@
Game::Game(int argc, char* argv[])
{
ResourceManager::AssertIsMainThread();
ResourceManager::RegisterType<ConfigFile>("ConfigFile");
ResourceManager::RegisterType<Model>("Model");
ResourceManager::RegisterType<Texture>("Texture");