Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Eisel <julian@blender.org>2022-02-18 20:11:16 +0300
committerJulian Eisel <julian@blender.org>2022-02-18 20:13:06 +0300
commit16ab6111f758ef77bd0e53ce2379ee8281629776 (patch)
tree4c56f1efb143102ea53827ad62ab339cee88c002 /source/blender/editors/render/render_preview.cc
parent1850a0b2ab1230d79cc56a74e877a9b371ae3773 (diff)
UI: Speedup preview icon loading from hard drive
Significantly improves loading speed of preview images from disk, e.g. custom previews loaded using `bpy.utils.previews.ImagePreviewCollection.load()`. See D14144 for details & comparison videos. Differential Revision: https://developer.blender.org/D14144 Reviewed by: Bastien Montagne
Diffstat (limited to 'source/blender/editors/render/render_preview.cc')
-rw-r--r--source/blender/editors/render/render_preview.cc367
1 files changed, 281 insertions, 86 deletions
diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc
index 6aaf551a88a..eca30a6ac25 100644
--- a/source/blender/editors/render/render_preview.cc
+++ b/source/blender/editors/render/render_preview.cc
@@ -10,6 +10,7 @@
#include <cmath>
#include <cstdlib>
#include <cstring>
+#include <list>
#ifndef WIN32
# include <unistd.h>
@@ -1370,89 +1371,73 @@ static void icon_preview_startjob(void *customdata, short *stop, short *do_updat
ShaderPreview *sp = static_cast<ShaderPreview *>(customdata);
if (sp->pr_method == PR_ICON_DEFERRED) {
- PreviewImage *prv = static_cast<PreviewImage *>(sp->owner);
- ImBuf *thumb;
- char *deferred_data = static_cast<char *>(PRV_DEFERRED_DATA(prv));
- ThumbSource source = static_cast<ThumbSource>(deferred_data[0]);
- char *path = &deferred_data[1];
-
- // printf("generating deferred %d×%d preview for %s\n", sp->sizex, sp->sizey, path);
-
- thumb = IMB_thumb_manage(path, THB_LARGE, source);
-
- if (thumb) {
- /* PreviewImage assumes premultiplied alhpa... */
- IMB_premultiply_alpha(thumb);
-
- icon_copy_rect(thumb, sp->sizex, sp->sizey, sp->pr_rect);
- IMB_freeImBuf(thumb);
- }
+ BLI_assert_unreachable();
+ return;
}
- else {
- ID *id = sp->id;
- short idtype = GS(id->name);
- BLI_assert(id != nullptr);
-
- if (idtype == ID_IM) {
- Image *ima = (Image *)id;
- ImBuf *ibuf = nullptr;
- ImageUser iuser;
- BKE_imageuser_default(&iuser);
+ ID *id = sp->id;
+ short idtype = GS(id->name);
- if (ima == nullptr) {
- return;
- }
+ BLI_assert(id != nullptr);
- /* setup dummy image user */
- iuser.framenr = 1;
- iuser.scene = sp->scene;
-
- /* NOTE(@elubie): this needs to be changed: here image is always loaded if not
- * already there. Very expensive for large images. Need to find a way to
- * only get existing `ibuf`. */
- ibuf = BKE_image_acquire_ibuf(ima, &iuser, nullptr);
- if (ibuf == nullptr || (ibuf->rect == nullptr && ibuf->rect_float == nullptr)) {
- BKE_image_release_ibuf(ima, ibuf, nullptr);
- return;
- }
+ if (idtype == ID_IM) {
+ Image *ima = (Image *)id;
+ ImBuf *ibuf = nullptr;
+ ImageUser iuser;
+ BKE_imageuser_default(&iuser);
- icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect);
+ if (ima == nullptr) {
+ return;
+ }
- *do_update = true;
+ /* setup dummy image user */
+ iuser.framenr = 1;
+ iuser.scene = sp->scene;
+ /* NOTE(@elubie): this needs to be changed: here image is always loaded if not
+ * already there. Very expensive for large images. Need to find a way to
+ * only get existing `ibuf`. */
+ ibuf = BKE_image_acquire_ibuf(ima, &iuser, nullptr);
+ if (ibuf == nullptr || (ibuf->rect == nullptr && ibuf->rect_float == nullptr)) {
BKE_image_release_ibuf(ima, ibuf, nullptr);
+ return;
}
- else if (idtype == ID_BR) {
- Brush *br = (Brush *)id;
- br->icon_imbuf = icon_preview_imbuf_from_brush(br);
+ icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect);
- memset(sp->pr_rect, 0x88, sp->sizex * sp->sizey * sizeof(uint));
+ *do_update = true;
- if (!(br->icon_imbuf) || !(br->icon_imbuf->rect)) {
- return;
- }
+ BKE_image_release_ibuf(ima, ibuf, nullptr);
+ }
+ else if (idtype == ID_BR) {
+ Brush *br = (Brush *)id;
- icon_copy_rect(br->icon_imbuf, sp->sizex, sp->sizey, sp->pr_rect);
+ br->icon_imbuf = icon_preview_imbuf_from_brush(br);
- *do_update = true;
- }
- else if (idtype == ID_SCR) {
- bScreen *screen = (bScreen *)id;
+ memset(sp->pr_rect, 0x88, sp->sizex * sp->sizey * sizeof(uint));
- ED_screen_preview_render(screen, sp->sizex, sp->sizey, sp->pr_rect);
- *do_update = true;
+ if (!(br->icon_imbuf) || !(br->icon_imbuf->rect)) {
+ return;
}
- else {
- /* re-use shader job */
- shader_preview_startjob(customdata, stop, do_update);
- /* world is rendered with alpha=0, so it wasn't displayed
- * this could be render option for sky to, for later */
- if (idtype == ID_WO) {
- set_alpha((char *)sp->pr_rect, sp->sizex, sp->sizey, 255);
- }
+ icon_copy_rect(br->icon_imbuf, sp->sizex, sp->sizey, sp->pr_rect);
+
+ *do_update = true;
+ }
+ else if (idtype == ID_SCR) {
+ bScreen *screen = (bScreen *)id;
+
+ ED_screen_preview_render(screen, sp->sizex, sp->sizey, sp->pr_rect);
+ *do_update = true;
+ }
+ else {
+ /* re-use shader job */
+ shader_preview_startjob(customdata, stop, do_update);
+
+ /* world is rendered with alpha=0, so it wasn't displayed
+ * this could be render option for sky to, for later */
+ if (idtype == ID_WO) {
+ set_alpha((char *)sp->pr_rect, sp->sizex, sp->sizey, 255);
}
}
}
@@ -1670,6 +1655,197 @@ static void icon_preview_endjob(void *customdata)
}
}
+/**
+ * Background job to manage requests for deferred loading of previews from the hard drive.
+ *
+ * Launches a single job to manage all incoming preview requests. The job is kept running until all
+ * preview requests are done loading (or it's otherwise aborted, e.g. by closing Blender).
+ *
+ * Note that this will use the OS thumbnail cache, i.e. load a preview from there or add it if not
+ * there yet. These two cases may lead to different performance.
+ */
+class PreviewLoadJob {
+ struct RequestedPreview {
+ PreviewImage *preview;
+ /** Requested size. */
+ eIconSizes icon_size;
+ };
+
+ /** The previews that are still to be loaded. */
+ ThreadQueue *todo_queue_; /* RequestedPreview * */
+ /** All unfinished preview requests, #update_fn() calls #finish_preview_request() on loaded
+ * previews and removes them from this list. Only access from the main thread! */
+ std::list<struct RequestedPreview> requested_previews_;
+
+ public:
+ PreviewLoadJob();
+ ~PreviewLoadJob();
+
+ static PreviewLoadJob &ensure_job(wmWindowManager *, wmWindow *);
+ static void load_jobless(PreviewImage *, eIconSizes);
+
+ void push_load_request(PreviewImage *, eIconSizes);
+
+ private:
+ static void run_fn(void *, short *, short *, float *);
+ static void update_fn(void *);
+ static void end_fn(void *);
+ static void free_fn(void *);
+
+ /** Mark a single requested preview as being done, remove the request. */
+ static void finish_request(RequestedPreview &);
+};
+
+PreviewLoadJob::PreviewLoadJob() : todo_queue_(BLI_thread_queue_init())
+{
+}
+
+PreviewLoadJob::~PreviewLoadJob()
+{
+ BLI_thread_queue_free(todo_queue_);
+}
+
+PreviewLoadJob &PreviewLoadJob::ensure_job(wmWindowManager *wm, wmWindow *win)
+{
+ wmJob *wm_job = WM_jobs_get(wm, win, nullptr, "Load Previews", 0, WM_JOB_TYPE_LOAD_PREVIEW);
+
+ if (!WM_jobs_is_running(wm_job)) {
+ PreviewLoadJob *job_data = MEM_new<PreviewLoadJob>("PreviewLoadJobData");
+
+ WM_jobs_customdata_set(wm_job, job_data, free_fn);
+ WM_jobs_timer(wm_job, 0.1, NC_WINDOW, NC_WINDOW);
+ WM_jobs_callbacks(wm_job, run_fn, nullptr, update_fn, end_fn);
+
+ WM_jobs_start(wm, wm_job);
+ }
+
+ return *reinterpret_cast<PreviewLoadJob *>(WM_jobs_customdata_get(wm_job));
+}
+
+void PreviewLoadJob::load_jobless(PreviewImage *preview, const eIconSizes icon_size)
+{
+ PreviewLoadJob job_data{};
+
+ job_data.push_load_request(preview, icon_size);
+
+ short stop = 0, do_update = 0;
+ float progress = 0;
+ run_fn(&job_data, &stop, &do_update, &progress);
+ update_fn(&job_data);
+ end_fn(&job_data);
+}
+
+void PreviewLoadJob::push_load_request(PreviewImage *preview, const eIconSizes icon_size)
+{
+ BLI_assert(preview->tag & PRV_TAG_DEFFERED);
+ RequestedPreview requested_preview{};
+ requested_preview.preview = preview;
+ requested_preview.icon_size = icon_size;
+
+ preview->flag[icon_size] |= PRV_RENDERING;
+ /* Warn main thread code that this preview is being rendered and cannot be freed. */
+ preview->tag |= PRV_TAG_DEFFERED_RENDERING;
+
+ requested_previews_.push_back(requested_preview);
+ BLI_thread_queue_push(todo_queue_, &requested_previews_.back());
+}
+
+void PreviewLoadJob::run_fn(void *customdata,
+ short *stop,
+ short *do_update,
+ float *UNUSED(progress))
+{
+ PreviewLoadJob *job_data = reinterpret_cast<PreviewLoadJob *>(customdata);
+
+ IMB_thumb_locks_acquire();
+
+ while (RequestedPreview *request = reinterpret_cast<RequestedPreview *>(
+ BLI_thread_queue_pop_timeout(job_data->todo_queue_, 100))) {
+ if (*stop) {
+ break;
+ }
+
+ PreviewImage *preview = request->preview;
+
+ const char *deferred_data = static_cast<char *>(PRV_DEFERRED_DATA(preview));
+ const ThumbSource source = static_cast<ThumbSource>(deferred_data[0]);
+ const char *path = &deferred_data[1];
+
+ // printf("loading deferred %d×%d preview for %s\n", request->sizex, request->sizey, path);
+
+ IMB_thumb_path_lock(path);
+ ImBuf *thumb = IMB_thumb_manage(path, THB_LARGE, source);
+ IMB_thumb_path_unlock(path);
+
+ if (thumb) {
+ /* PreviewImage assumes premultiplied alpha... */
+ IMB_premultiply_alpha(thumb);
+
+ icon_copy_rect(thumb,
+ preview->w[request->icon_size],
+ preview->h[request->icon_size],
+ preview->rect[request->icon_size]);
+ IMB_freeImBuf(thumb);
+ }
+
+ *do_update = true;
+ }
+
+ IMB_thumb_locks_release();
+}
+
+/* Only execute on the main thread! */
+void PreviewLoadJob::finish_request(RequestedPreview &request)
+{
+ PreviewImage *preview = request.preview;
+
+ preview->tag &= ~PRV_TAG_DEFFERED_RENDERING;
+ BKE_previewimg_finish(preview, request.icon_size);
+
+ BLI_assert_msg(BLI_thread_is_main(),
+ "Deferred releasing of preview images should only run on the main thread");
+ if (preview->tag & PRV_TAG_DEFFERED_DELETE) {
+ BLI_assert(preview->tag & PRV_TAG_DEFFERED);
+ BKE_previewimg_deferred_release(preview);
+ }
+}
+
+void PreviewLoadJob::update_fn(void *customdata)
+{
+ PreviewLoadJob *job_data = reinterpret_cast<PreviewLoadJob *>(customdata);
+
+ for (auto request_it = job_data->requested_previews_.begin();
+ request_it != job_data->requested_previews_.end();) {
+ RequestedPreview &requested = *request_it;
+ /* Skip items that are not done loading yet. */
+ if (requested.preview->tag & PRV_TAG_DEFFERED_RENDERING) {
+ ++request_it;
+ continue;
+ }
+ finish_request(requested);
+
+ /* Remove properly finished previews from the job data. */
+ auto next_it = job_data->requested_previews_.erase(request_it);
+ request_it = next_it;
+ }
+}
+
+void PreviewLoadJob::end_fn(void *customdata)
+{
+ PreviewLoadJob *job_data = reinterpret_cast<PreviewLoadJob *>(customdata);
+
+ /* Finish any possibly remaining queued previews. */
+ for (RequestedPreview &request : job_data->requested_previews_) {
+ finish_request(request);
+ }
+ job_data->requested_previews_.clear();
+}
+
+void PreviewLoadJob::free_fn(void *customdata)
+{
+ MEM_delete(reinterpret_cast<PreviewLoadJob *>(customdata));
+}
+
static void icon_preview_free(void *customdata)
{
IconPreview *ip = (IconPreview *)customdata;
@@ -1698,8 +1874,19 @@ bool ED_preview_id_is_supported(const ID *id)
}
void ED_preview_icon_render(
- const bContext *C, Scene *scene, ID *id, uint *rect, int sizex, int sizey)
+ const bContext *C, Scene *scene, PreviewImage *prv_img, ID *id, eIconSizes icon_size)
{
+ /* Deferred loading of previews from the file system. */
+ if (prv_img->tag & PRV_TAG_DEFFERED) {
+ if (prv_img->flag[icon_size] & PRV_RENDERING) {
+ /* Already in the queue, don't add it again. */
+ return;
+ }
+
+ PreviewLoadJob::load_jobless(prv_img, icon_size);
+ return;
+ }
+
IconPreview ip = {nullptr};
short stop = false, update = false;
float progress = 0.0f;
@@ -1716,7 +1903,10 @@ void ED_preview_icon_render(
ip.id_copy = duplicate_ids(id, true);
ip.active_object = CTX_data_active_object(C);
- icon_preview_add_size(&ip, rect, sizex, sizey);
+ prv_img->flag[icon_size] |= PRV_RENDERING;
+
+ icon_preview_add_size(
+ &ip, prv_img->rect[icon_size], prv_img->w[icon_size], prv_img->h[icon_size]);
icon_preview_startjob_all_sizes(&ip, &stop, &update, &progress);
@@ -1729,20 +1919,31 @@ void ED_preview_icon_render(
}
void ED_preview_icon_job(
- const bContext *C, void *owner, ID *id, uint *rect, int sizex, int sizey, const bool delay)
+ const bContext *C, PreviewImage *prv_img, ID *id, eIconSizes icon_size, const bool delay)
{
- wmJob *wm_job;
+ /* Deferred loading of previews from the file system. */
+ if (prv_img->tag & PRV_TAG_DEFFERED) {
+ if (prv_img->flag[icon_size] & PRV_RENDERING) {
+ /* Already in the queue, don't add it again. */
+ return;
+ }
+ PreviewLoadJob &load_job = PreviewLoadJob::ensure_job(CTX_wm_manager(C), CTX_wm_window(C));
+ load_job.push_load_request(prv_img, icon_size);
+
+ return;
+ }
+
IconPreview *ip, *old_ip;
ED_preview_ensure_dbase();
/* suspended start means it starts after 1 timer step, see WM_jobs_timer below */
- wm_job = WM_jobs_get(CTX_wm_manager(C),
- CTX_wm_window(C),
- owner,
- "Icon Preview",
- WM_JOB_EXCL_RENDER,
- WM_JOB_TYPE_RENDER_PREVIEW);
+ wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
+ CTX_wm_window(C),
+ prv_img,
+ "Icon Preview",
+ WM_JOB_EXCL_RENDER,
+ WM_JOB_TYPE_RENDER_PREVIEW);
ip = MEM_cnew<IconPreview>("icon preview");
@@ -1757,20 +1958,14 @@ void ED_preview_icon_job(
ip->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ip->scene = DEG_get_input_scene(ip->depsgraph);
ip->active_object = CTX_data_active_object(C);
- ip->owner = owner;
+ ip->owner = prv_img;
ip->id = id;
ip->id_copy = duplicate_ids(id, false);
- icon_preview_add_size(ip, rect, sizex, sizey);
+ prv_img->flag[icon_size] |= PRV_RENDERING;
- /* Special threading hack:
- * warn main code that this preview is being rendered and cannot be freed... */
- {
- PreviewImage *prv_img = static_cast<PreviewImage *>(owner);
- if (prv_img->tag & PRV_TAG_DEFFERED) {
- prv_img->tag |= PRV_TAG_DEFFERED_RENDERING;
- }
- }
+ icon_preview_add_size(
+ ip, prv_img->rect[icon_size], prv_img->w[icon_size], prv_img->h[icon_size]);
/* setup job */
WM_jobs_customdata_set(wm_job, ip, icon_preview_free);