From fa0ea312ea3d50e9c663b6e1d17868a885342f09 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 30 Nov 2021 10:32:33 +0100 Subject: [PATCH] gobj: Move TextureReloadRequest to new Texture::async_ensure_ram_image() The task is implemented with just a simple lambda, much more compact than a whole TextureReloadRequest task. The latter is now deprecated. Since Loader can't be used within Texture, there's now a new task chain, configurable with texture-reload-num-threads and texture-reload-thread-priority. This does mean that it no longer happens on the same thread as model loads, but I think that's fine, and perhaps texture reloads should be higher priority than model loads anyway since a long texture reload delay with allow-incomplete-render directly and negatively affects user experience during gameplay. The new name also better communicates that it just calls get_ram_image(), it doesn't force a reload, but we could add an async_reload() for that if we want. --- panda/src/display/graphicsStateGuardian.cxx | 27 +------- panda/src/gobj/config_gobj.cxx | 17 +++++ panda/src/gobj/config_gobj.h | 3 + panda/src/gobj/texture.cxx | 70 +++++++++++++++++++++ panda/src/gobj/texture.h | 6 +- panda/src/gobj/textureReloadRequest.h | 2 + 6 files changed, 99 insertions(+), 26 deletions(-) diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 3e73649ad1..797dba0fcd 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -3581,31 +3581,8 @@ async_reload_texture(TextureContext *tc) { priority = _current_display_region->get_texture_reload_priority(); } - string task_name = string("reload:") + tc->get_texture()->get_name(); - PT(AsyncTaskManager) task_mgr = _loader->get_task_manager(); - - // See if we are already loading this task. - AsyncTaskCollection orig_tasks = task_mgr->find_tasks(task_name); - size_t num_tasks = orig_tasks.get_num_tasks(); - for (size_t ti = 0; ti < num_tasks; ++ti) { - AsyncTask *task = orig_tasks.get_task(ti); - if (task->is_exact_type(TextureReloadRequest::get_class_type()) && - ((TextureReloadRequest *)task)->get_texture() == tc->get_texture()) { - // This texture is already queued to be reloaded. Don't queue it again, - // just make sure the priority is updated, and return. - task->set_priority(std::max(task->get_priority(), priority)); - return (AsyncFuture *)task; - } - } - - // This texture has not yet been queued to be reloaded. Queue it up now. - PT(AsyncTask) request = - new TextureReloadRequest(task_name, - _prepared_objects, tc->get_texture(), - _supports_compressed_texture); - request->set_priority(priority); - _loader->load_async(request); - return (AsyncFuture *)request.p(); + Texture *tex = tc->get_texture(); + return tex->async_ensure_ram_image(_supports_compressed_texture, priority); } /** diff --git a/panda/src/gobj/config_gobj.cxx b/panda/src/gobj/config_gobj.cxx index b093925367..f3e8adbaf8 100644 --- a/panda/src/gobj/config_gobj.cxx +++ b/panda/src/gobj/config_gobj.cxx @@ -366,6 +366,23 @@ ConfigVariableDouble simple_image_threshold "simple images. Generally the value should be considerably " "less than 1.")); +ConfigVariableInt texture_reload_num_threads + ("texture-reload-num-threads", 1, + PRC_DESC("The number of threads that will be started by the Texture class " + "to reload textures asynchronously. These threads will only be " + "started if the asynchronous interface is used, and if threading " + "support is compiled into Panda. The default is one thread, " + "which allows textures to be loaded one at a time in a single " + "asychronous thread. You can set this higher, particularly if " + "you have many CPU's available, to allow loading multiple models " + "simultaneously.")); + +ConfigVariableEnum texture_reload_thread_priority + ("texture-reload-thread-priority", TP_normal, + PRC_DESC("The default thread priority to assign to the threads created for " + "asynchronous texture loading. The default is 'normal'; you may " + "also specify 'low', 'high', or 'urgent'.")); + ConfigVariableInt geom_cache_size ("geom-cache-size", 5000, PRC_DESC("Specifies the maximum number of entries in the cache " diff --git a/panda/src/gobj/config_gobj.h b/panda/src/gobj/config_gobj.h index f44df52a3c..1713a56751 100644 --- a/panda/src/gobj/config_gobj.h +++ b/panda/src/gobj/config_gobj.h @@ -24,6 +24,7 @@ #include "configVariableString.h" #include "configVariableList.h" #include "autoTextureScale.h" +#include "threadPriority.h" NotifyCategoryDecl(gobj, EXPCL_PANDA_GOBJ, EXPTP_PANDA_GOBJ); NotifyCategoryDecl(shader, EXPCL_PANDA_GOBJ, EXPTP_PANDA_GOBJ); @@ -62,6 +63,8 @@ extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_auto_power_2; extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_header_only; extern EXPCL_PANDA_GOBJ ConfigVariableInt simple_image_size; extern EXPCL_PANDA_GOBJ ConfigVariableDouble simple_image_threshold; +extern EXPCL_PANDA_GOBJ ConfigVariableInt texture_reload_num_threads; +extern EXPCL_PANDA_GOBJ ConfigVariableEnum texture_reload_thread_priority; extern EXPCL_PANDA_GOBJ ConfigVariableInt geom_cache_size; extern EXPCL_PANDA_GOBJ ConfigVariableInt geom_cache_min_frames; diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index bc6d218e38..9633379a82 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -43,6 +43,7 @@ #include "streamReader.h" #include "texturePeeker.h" #include "convert_srgb.h" +#include "asyncTaskManager.h" #ifdef HAVE_SQUISH #include @@ -1019,6 +1020,69 @@ load_related(const InternalName *suffix) const { return res; } +/** + * Schedules a background task that reloads the the Texture from its disk file + * if there is not currently a RAM image (or uncompressed RAM image, if + * allow_compression is false). + * + * A higher priority value indicates that this texture should be reloaded sooner + * than textures with a lower priority value. If the reload hasn't taken place + * yet, you can call this again to update the priority value. + * + * If someone else reloads the texture using an explicit call to reload() while + * an async reload request is pending, the async reload request is cancelled. + */ +PT(AsyncFuture) Texture:: +async_ensure_ram_image(bool allow_compression, int priority) { + CDLockedReader cdata(_cycler); + if (allow_compression ? do_has_ram_image(cdata) : do_has_uncompressed_ram_image(cdata)) { + // We already have a RAM image. + PT(AsyncFuture) fut = new AsyncFuture; + fut->set_result(nullptr); + return fut; + } + if (!do_can_reload(cdata)) { + // We don't have a filename to load from. This is an error. + return nullptr; + } + + AsyncTask *task = cdata->_reload_task; + if (task != nullptr) { + // This texture is already queued to be reloaded. Don't queue it again, + // just make sure the priority is updated, and return. + task->set_priority(std::max(task->get_priority(), priority)); + return (AsyncFuture *)task; + } + + CDWriter cdataw(_cycler, cdata, true); + + string task_name = string("reload:") + get_name(); + AsyncTaskManager *task_mgr = AsyncTaskManager::get_global_ptr(); + static PT(AsyncTaskChain) chain = task_mgr->make_task_chain("texture_reload"); + chain->set_num_threads(texture_reload_num_threads); + chain->set_thread_priority(texture_reload_thread_priority); + + double delay = async_load_delay; + + // This texture has not yet been queued to be reloaded. Queue it up now. + task = new FunctionAsyncTask(task_name, [=](AsyncTask *task) { + if (delay != 0.0) { + Thread::sleep(delay); + } + if (allow_compression) { + get_ram_image(); + } else { + get_uncompressed_ram_image(); + } + return AsyncTask::DS_done; + }); + task->set_task_chain("texture_reload"); + task->set_priority(priority); + task_mgr->add(task); + cdataw->_reload_task = task; + return (AsyncFuture *)task; +} + /** * Replaces the current system-RAM image with the new data, converting it * first if necessary from the indicated component-order format. See @@ -5611,6 +5675,12 @@ do_reload_ram_image(CData *cdata, bool allow_compression) { cache->store(record); } } + + // Remove any pending asynchronous reload operation. + if (cdata->_reload_task != nullptr) { + cdata->_reload_task->remove(); + cdata->_reload_task = nullptr; + } } /** diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index 0254db323a..3c78553c4c 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -45,7 +45,7 @@ #include "bamCacheRecord.h" #include "pnmImage.h" #include "pfmFile.h" -#include "asyncFuture.h" +#include "asyncTask.h" class TextureContext; class FactoryParams; @@ -448,6 +448,7 @@ PUBLISHED: MAKE_PROPERTY(expected_ram_image_size, get_expected_ram_image_size); MAKE_PROPERTY(expected_ram_page_size, get_expected_ram_page_size); + PT(AsyncFuture) async_ensure_ram_image(bool allow_compression = true, int priority = 0); INLINE CPTA_uchar get_ram_image(); INLINE CompressionMode get_ram_image_compression() const; INLINE CPTA_uchar get_uncompressed_ram_image(); @@ -784,6 +785,7 @@ protected: void do_set_pad_size(CData *cdata, int x, int y, int z); virtual bool do_can_reload(const CData *cdata) const; bool do_reload(CData *cdata); + AsyncFuture *do_async_ensure_ram_image(const CData *cdata, bool allow_compression, int priority); INLINE AutoTextureScale do_get_auto_texture_scale(const CData *cdata) const; @@ -1032,6 +1034,8 @@ protected: ModifiedPageRanges _modified_pages; + PT(AsyncTask) _reload_task; + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/gobj/textureReloadRequest.h b/panda/src/gobj/textureReloadRequest.h index 625ac3ce3e..1010cd7ec5 100644 --- a/panda/src/gobj/textureReloadRequest.h +++ b/panda/src/gobj/textureReloadRequest.h @@ -27,6 +27,8 @@ * force the texture's image to be re-read from disk. It is used by * GraphicsStateGuardian::async_reload_texture(), when get_incomplete_render() * is true. + * + * @deprecated Use Texture::async_ensure_ram_image() instead. */ class EXPCL_PANDA_GOBJ TextureReloadRequest : public AsyncTask { public: