forked from bartvdbraak/blender
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
This commit is contained in:
parent
1850a0b2ab
commit
16ab6111f7
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
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<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);
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user