diff --git a/source/blender/editors/include/ED_render.h b/source/blender/editors/include/ED_render.h index e1b6a935d6d..ee40cc75e1e 100644 --- a/source/blender/editors/include/ED_render.h +++ b/source/blender/editors/include/ED_render.h @@ -24,6 +24,7 @@ struct Scene; struct ScrArea; struct bContext; struct bScreen; +struct PreviewImage; struct wmWindow; struct wmWindowManager; @@ -87,16 +88,13 @@ void ED_preview_shader_job(const struct bContext *C, ePreviewRenderMethod method); void ED_preview_icon_render(const struct bContext *C, struct Scene *scene, + struct PreviewImage *prv_img, struct ID *id, - unsigned int *rect, - int sizex, - int sizey); + enum eIconSizes icon_size); void ED_preview_icon_job(const struct bContext *C, - void *owner, + struct PreviewImage *prv_img, struct ID *id, - unsigned int *rect, - int sizex, - int sizey, + enum eIconSizes icon_size, bool delay); void ED_preview_restart_queue_free(void); diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index 630f83cf828..e99f6978f4c 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -1399,19 +1399,17 @@ static void icon_set_image(const bContext *C, const bool delay = prv_img->rect[size] != NULL; icon_create_rect(prv_img, size); - prv_img->flag[size] |= PRV_RENDERING; if (use_job && (!id || BKE_previewimg_id_supports_jobs(id))) { /* Job (background) version */ - ED_preview_icon_job( - C, prv_img, id, prv_img->rect[size], prv_img->w[size], prv_img->h[size], delay); + ED_preview_icon_job(C, prv_img, id, size, delay); } else { if (!scene) { scene = CTX_data_scene(C); } /* Immediate version */ - ED_preview_icon_render(C, scene, id, prv_img->rect[size], prv_img->w[size], prv_img->h[size]); + ED_preview_icon_render(C, scene, prv_img, id, size); } } 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 #include #include +#include #ifndef WIN32 # include @@ -1370,89 +1371,73 @@ static void icon_preview_startjob(void *customdata, short *stop, short *do_updat ShaderPreview *sp = static_cast(customdata); if (sp->pr_method == PR_ICON_DEFERRED) { - PreviewImage *prv = static_cast(sp->owner); - ImBuf *thumb; - char *deferred_data = static_cast(PRV_DEFERRED_DATA(prv)); - ThumbSource source = static_cast(deferred_data[0]); - char *path = &deferred_data[1]; + BLI_assert_unreachable(); + return; + } - // printf("generating deferred %d×%d preview for %s\n", sp->sizex, sp->sizey, path); + ID *id = sp->id; + short idtype = GS(id->name); - thumb = IMB_thumb_manage(path, THB_LARGE, source); + BLI_assert(id != nullptr); - if (thumb) { - /* PreviewImage assumes premultiplied alhpa... */ - IMB_premultiply_alpha(thumb); + if (idtype == ID_IM) { + Image *ima = (Image *)id; + ImBuf *ibuf = nullptr; + ImageUser iuser; + BKE_imageuser_default(&iuser); - icon_copy_rect(thumb, sp->sizex, sp->sizey, sp->pr_rect); - IMB_freeImBuf(thumb); + if (ima == nullptr) { + return; } + + /* 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; + } + + icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect); + + *do_update = true; + + BKE_image_release_ibuf(ima, ibuf, nullptr); + } + else if (idtype == ID_BR) { + Brush *br = (Brush *)id; + + br->icon_imbuf = icon_preview_imbuf_from_brush(br); + + memset(sp->pr_rect, 0x88, sp->sizex * sp->sizey * sizeof(uint)); + + if (!(br->icon_imbuf) || !(br->icon_imbuf->rect)) { + return; + } + + 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 { - ID *id = sp->id; - short idtype = GS(id->name); + /* re-use shader job */ + shader_preview_startjob(customdata, stop, do_update); - BLI_assert(id != nullptr); - - if (idtype == ID_IM) { - Image *ima = (Image *)id; - ImBuf *ibuf = nullptr; - ImageUser iuser; - BKE_imageuser_default(&iuser); - - if (ima == nullptr) { - return; - } - - /* 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; - } - - icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect); - - *do_update = true; - - BKE_image_release_ibuf(ima, ibuf, nullptr); - } - else if (idtype == ID_BR) { - Brush *br = (Brush *)id; - - br->icon_imbuf = icon_preview_imbuf_from_brush(br); - - memset(sp->pr_rect, 0x88, sp->sizex * sp->sizey * sizeof(uint)); - - if (!(br->icon_imbuf) || !(br->icon_imbuf->rect)) { - return; - } - - 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); - } + /* 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 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("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(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(customdata); + + IMB_thumb_locks_acquire(); + + while (RequestedPreview *request = reinterpret_cast( + BLI_thread_queue_pop_timeout(job_data->todo_queue_, 100))) { + if (*stop) { + break; + } + + PreviewImage *preview = request->preview; + + const char *deferred_data = static_cast(PRV_DEFERRED_DATA(preview)); + const ThumbSource source = static_cast(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(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(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(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("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(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); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 49fbf2c27e1..b00d441dd5d 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1247,6 +1247,8 @@ enum { WM_JOB_TYPE_COMPOSITE, WM_JOB_TYPE_RENDER, WM_JOB_TYPE_RENDER_PREVIEW, /* UI preview */ + /** Job for the UI to load previews from the file system (uses OS thumbnail cache). */ + WM_JOB_TYPE_LOAD_PREVIEW, /* UI preview */ WM_JOB_TYPE_OBJECT_SIM_OCEAN, WM_JOB_TYPE_OBJECT_SIM_FLUID, WM_JOB_TYPE_OBJECT_BAKE_TEXTURE,