Browse Source

Precalculate texture's mip levels during async loading, if applicable. Use a timer to avoid spending too much time per frame on finishing async loaded resources.

Lasse Öörni 11 years ago
parent
commit
cdd2dae4fb

+ 1 - 1
Source/Engine/Core/WorkQueue.cpp

@@ -32,7 +32,7 @@
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
-const unsigned MAX_NONTHREADED_WORK_USEC = 1000;
+const unsigned MAX_NONTHREADED_WORK_USEC = 5000;
 
 
 /// Worker thread managed by the work queue.
 /// Worker thread managed by the work queue.
 class WorkerThread : public Thread, public RefCounted
 class WorkerThread : public Thread, public RefCounted

+ 4 - 0
Source/Engine/Graphics/Direct3D9/D3D9Texture2D.cpp

@@ -74,6 +74,10 @@ bool Texture2D::BeginLoad(Deserializer& source)
         loadImage_.Reset();
         loadImage_.Reset();
         return false;
         return false;
     }
     }
+
+    // Precalculate mip levels if async loading
+    if (GetAsyncLoadState() == ASYNC_LOADING)
+        loadImage_->PrecalculateLevels();
     
     
     // Load the optional parameters file
     // Load the optional parameters file
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();

+ 6 - 0
Source/Engine/Graphics/Direct3D9/D3D9Texture3D.cpp

@@ -96,6 +96,9 @@ bool Texture3D::BeginLoad(Deserializer& source)
             name = texPath + name;
             name = texPath + name;
 
 
         loadImage_ = cache->GetTempResource<Image>(name);
         loadImage_ = cache->GetTempResource<Image>(name);
+        // Precalculate mip levels if async loading
+        if (loadImage_ && GetAsyncLoadState() == ASYNC_LOADING)
+            loadImage_->PrecalculateLevels();
         cache->StoreResourceDependency(this, name);
         cache->StoreResourceDependency(this, name);
     }
     }
     else if (colorlutElem)
     else if (colorlutElem)
@@ -116,6 +119,9 @@ bool Texture3D::BeginLoad(Deserializer& source)
             loadImage_.Reset();
             loadImage_.Reset();
             return false;
             return false;
         }
         }
+        // Precalculate mip levels if async loading
+        if (loadImage_ && GetAsyncLoadState() == ASYNC_LOADING)
+            loadImage_->PrecalculateLevels();
         cache->StoreResourceDependency(this, name);
         cache->StoreResourceDependency(this, name);
     }
     }
 
 

+ 11 - 1
Source/Engine/Graphics/Direct3D9/D3D9TextureCube.cpp

@@ -329,7 +329,17 @@ bool TextureCube::BeginLoad(Deserializer& source)
         
         
         faceElem = faceElem.GetNext("face");
         faceElem = faceElem.GetNext("face");
     }
     }
-    
+
+    // Precalculate mip levels if async loading
+    if (GetAsyncLoadState() == ASYNC_LOADING)
+    {
+        for (unsigned i = 0; i < loadImages_.Size(); ++i)
+        {
+            if (loadImages_[i])
+                loadImages_[i]->PrecalculateLevels();
+        }
+    }
+
     return true;
     return true;
 }
 }
 
 

+ 4 - 0
Source/Engine/Graphics/OpenGL/OGLTexture2D.cpp

@@ -76,6 +76,10 @@ bool Texture2D::BeginLoad(Deserializer& source)
         loadImage_.Reset();
         loadImage_.Reset();
         return false;
         return false;
     }
     }
+
+    // Precalculate mip levels if async loading
+    if (GetAsyncLoadState() == ASYNC_LOADING)
+        loadImage_->PrecalculateLevels();
     
     
     // Load the optional parameters file
     // Load the optional parameters file
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();

+ 7 - 0
Source/Engine/Graphics/OpenGL/OGLTexture3D.cpp

@@ -101,6 +101,9 @@ bool Texture3D::BeginLoad(Deserializer& source)
             name = texPath + name;
             name = texPath + name;
 
 
         loadImage_ = cache->GetTempResource<Image>(name);
         loadImage_ = cache->GetTempResource<Image>(name);
+        // Precalculate mip levels if async loading
+        if (loadImage_ && GetAsyncLoadState() == ASYNC_LOADING)
+            loadImage_->PrecalculateLevels();
         cache->StoreResourceDependency(this, name);
         cache->StoreResourceDependency(this, name);
     }
     }
     else if (colorlutElem)
     else if (colorlutElem)
@@ -121,12 +124,16 @@ bool Texture3D::BeginLoad(Deserializer& source)
             loadImage_.Reset();
             loadImage_.Reset();
             return false;
             return false;
         }
         }
+        // Precalculate mip levels if async loading
+        if (loadImage_ && GetAsyncLoadState() == ASYNC_LOADING)
+            loadImage_->PrecalculateLevels();
         cache->StoreResourceDependency(this, name);
         cache->StoreResourceDependency(this, name);
     }
     }
 
 
     return false;
     return false;
 }
 }
 
 
+
 bool Texture3D::EndLoad()
 bool Texture3D::EndLoad()
 {
 {
     // In headless mode, do not actually load the texture, just return success
     // In headless mode, do not actually load the texture, just return success

+ 13 - 3
Source/Engine/Graphics/OpenGL/OGLTextureCube.cpp

@@ -259,7 +259,7 @@ bool TextureCube::BeginLoad(Deserializer& source)
     }
     }
     
     
     cache->ResetDependencies(this);
     cache->ResetDependencies(this);
-    
+
     String texPath, texName, texExt;
     String texPath, texName, texExt;
     SplitPath(GetName(), texPath, texName, texExt);
     SplitPath(GetName(), texPath, texName, texExt);
     
     
@@ -271,7 +271,7 @@ bool TextureCube::BeginLoad(Deserializer& source)
     }
     }
     
     
     loadImages_.Clear();
     loadImages_.Clear();
-    
+
     XMLElement textureElem = loadParameters_->GetRoot();
     XMLElement textureElem = loadParameters_->GetRoot();
     XMLElement faceElem = textureElem.GetChild("face");
     XMLElement faceElem = textureElem.GetChild("face");
     while (faceElem)
     while (faceElem)
@@ -289,7 +289,17 @@ bool TextureCube::BeginLoad(Deserializer& source)
         
         
         faceElem = faceElem.GetNext("face");
         faceElem = faceElem.GetNext("face");
     }
     }
-    
+
+    // Precalculate mip levels if async loading
+    if (GetAsyncLoadState() == ASYNC_LOADING)
+    {
+        for (unsigned i = 0; i < loadImages_.Size(); ++i)
+        {
+            if (loadImages_[i])
+                loadImages_[i]->PrecalculateLevels();
+        }
+    }
+
     return true;
     return true;
 }
 }
 
 

+ 34 - 0
Source/Engine/Resource/Image.cpp

@@ -506,6 +506,7 @@ bool Image::SetSize(int width, int height, int depth, unsigned components)
     components_ = components;
     components_ = components;
     compressedFormat_ = CF_NONE;
     compressedFormat_ = CF_NONE;
     numCompressedLevels_ = 0;
     numCompressedLevels_ = 0;
+    nextLevel_.Reset();
 
 
     SetMemoryUse(width * height * depth * components);
     SetMemoryUse(width * height * depth * components);
     return true;
     return true;
@@ -556,7 +557,14 @@ void Image::SetData(const unsigned char* pixelData)
     if (!data_)
     if (!data_)
         return;
         return;
 
 
+    if (IsCompressed())
+    {
+        LOGERROR("Can not set new pixel data for a compressed image");
+        return;
+    }
+
     memcpy(data_.Get(), pixelData, width_ * height_ * depth_ * components_);
     memcpy(data_.Get(), pixelData, width_ * height_ * depth_ * components_);
+    nextLevel_.Reset();
 }
 }
 
 
 bool Image::LoadColorLUT(Deserializer& source)
 bool Image::LoadColorLUT(Deserializer& source)
@@ -937,6 +945,11 @@ SharedPtr<Image> Image::GetNextLevel() const
         return SharedPtr<Image>();
         return SharedPtr<Image>();
     }
     }
 
 
+    if (nextLevel_)
+        return nextLevel_;
+
+    PROFILE(CalculateImageMipLevel);
+
     int widthOut = width_ / 2;
     int widthOut = width_ / 2;
     int heightOut = height_ / 2;
     int heightOut = height_ / 2;
     int depthOut = depth_ / 2;
     int depthOut = depth_ / 2;
@@ -1374,6 +1387,27 @@ SDL_Surface* Image::GetSDLSurface(const IntRect& rect) const
     return surface;
     return surface;
 }
 }
 
 
+void Image::PrecalculateLevels()
+{
+    if (!data_ || IsCompressed())
+        return;
+
+    PROFILE(PrecalculateImageMipLevels);
+
+    nextLevel_.Reset();
+
+    if (width_ > 1 || height_ > 1)
+    {
+        SharedPtr<Image> current = GetNextLevel();
+        nextLevel_ = current;
+        while (current && (current->width_ > 1 || current->height_ > 1))
+        {
+            current->nextLevel_ = current->GetNextLevel();
+            current = current->nextLevel_;
+        }
+    }
+}
+
 unsigned char* Image::GetImageData(Deserializer& source, int& width, int& height, unsigned& components)
 unsigned char* Image::GetImageData(Deserializer& source, int& width, int& height, unsigned& components)
 {
 {
     unsigned dataSize = source.GetSize();
     unsigned dataSize = source.GetSize();

+ 4 - 0
Source/Engine/Resource/Image.h

@@ -170,6 +170,8 @@ public:
     Image* GetSubimage(const IntRect& rect) const;
     Image* GetSubimage(const IntRect& rect) const;
     /// Return an SDL surface from the image, or null if failed. Only RGB images are supported. Specify rect to only return partial image. You must free the surface yourself.
     /// Return an SDL surface from the image, or null if failed. Only RGB images are supported. Specify rect to only return partial image. You must free the surface yourself.
     SDL_Surface* GetSDLSurface(const IntRect& rect = IntRect::ZERO) const;
     SDL_Surface* GetSDLSurface(const IntRect& rect = IntRect::ZERO) const;
+    /// Precalculate the mip levels. Used by asynchronous texture loading.
+    void PrecalculateLevels();
 
 
 private:
 private:
     /// Decode an image using stb_image.
     /// Decode an image using stb_image.
@@ -191,6 +193,8 @@ private:
     CompressedFormat compressedFormat_;
     CompressedFormat compressedFormat_;
     /// Pixel data.
     /// Pixel data.
     SharedArrayPtr<unsigned char> data_;
     SharedArrayPtr<unsigned char> data_;
+    /// Precalculated mip level image.
+    SharedPtr<Image> nextLevel_;
 };
 };
 
 
 }
 }

+ 26 - 13
Source/Engine/Resource/ResourceCache.cpp

@@ -42,6 +42,8 @@
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
+const unsigned MAX_FINISH_RESOURCES_USEC = 5000;
+
 static const char* checkDirs[] = {
 static const char* checkDirs[] = {
     "Fonts",
     "Fonts",
     "Materials",
     "Materials",
@@ -1081,19 +1083,30 @@ void ResourceCache::HandleBeginFrame(StringHash eventType, VariantMap& eventData
         }
         }
     }
     }
     
     
-    // Check for background loaded resources that can be finalized
-    MutexLock lock(backgroundLoadMutex_);
-    
-    for (HashMap<Pair<StringHash, StringHash>, BackgroundLoadItem>::Iterator i = backgroundLoadQueue_.Begin();
-        i != backgroundLoadQueue_.End(); ++i)
+    // Check for background loaded resources that can be finished
+    if (IsStarted())
     {
     {
-        Resource* resource = i->second_.resource_;
-        unsigned numDeps = i->second_.dependencies_.Size();
-        AsyncLoadState state = resource->GetAsyncLoadState();
-        if (numDeps > 0 || state == ASYNC_QUEUED || state == ASYNC_LOADING)
-            continue;
-        else
-            FinishBackgroundLoading(i);
+        PROFILE(FinishBackgroundResources);
+
+        MutexLock lock(backgroundLoadMutex_);
+    
+        HiresTimer timer;
+
+        for (HashMap<Pair<StringHash, StringHash>, BackgroundLoadItem>::Iterator i = backgroundLoadQueue_.Begin();
+            i != backgroundLoadQueue_.End(); ++i)
+        {
+            Resource* resource = i->second_.resource_;
+            unsigned numDeps = i->second_.dependencies_.Size();
+            AsyncLoadState state = resource->GetAsyncLoadState();
+            if (numDeps > 0 || state == ASYNC_QUEUED || state == ASYNC_LOADING)
+                continue;
+            else
+                FinishBackgroundLoading(i);
+
+            // Break when certain time passed to avoid bogging down the framerate
+            if (timer.GetUSec(false) >= MAX_FINISH_RESOURCES_USEC)
+                break;
+        }
     }
     }
 }
 }
 
 
@@ -1140,7 +1153,7 @@ void ResourceCache::FinishBackgroundLoading(HashMap<Pair<StringHash, StringHash>
     if (success)
     if (success)
     {
     {
 #ifdef URHO3D_PROFILING
 #ifdef URHO3D_PROFILING
-        String profileBlockName("EndLoad" + resource->GetTypeName());
+        String profileBlockName("Finish" + resource->GetTypeName());
         
         
         Profiler* profiler = GetSubsystem<Profiler>();
         Profiler* profiler = GetSubsystem<Profiler>();
         if (profiler)
         if (profiler)