From 4b0396695c622d1ac8669600fa820e80b1f0979f Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Mon, 14 Dec 2020 12:48:16 +0100 Subject: UI/Assets: Support generating object preview images Object previews are really helpful for visual data-block selection, like asset browsing. Having them be generative should also be quite handy and should work well enough in many, if not most cases. What this does is simple: * Place the object (actually a deep copy of it, for thread safety) in a virtual .blend into an empty scene/view-layer. * Add a camera, point it towards the front of the object, assuming that means pointing towards its +Y axis. * Use "Camera Fit Frame to Selected" logic to put the object into frame. * Create a threaded off-screen render. Of course, such an automatic preview will not work in all situations. E.g. it currently does a bad job capturing a single plane. We could add options for more advanced automatic previews, but probably custom previews is more important, which I committed already (812ea9184221). Part of the first Asset Browser milestone. Check the #asset_browser_milestone_1 project milestone on developer.blender.org. Reviewed as part of https://developer.blender.org/D9719. Reviewed by: Bastien Montagne, Brecht Van Lommel --- source/blender/editors/render/render_preview.c | 260 ++++++++++++++++++++----- 1 file changed, 209 insertions(+), 51 deletions(-) (limited to 'source/blender/editors/render/render_preview.c') diff --git a/source/blender/editors/render/render_preview.c b/source/blender/editors/render/render_preview.c index d67113083a3..4466d9f434d 100644 --- a/source/blender/editors/render/render_preview.c +++ b/source/blender/editors/render/render_preview.c @@ -68,6 +68,7 @@ #include "BKE_main.h" #include "BKE_material.h" #include "BKE_node.h" +#include "BKE_object.h" #include "BKE_scene.h" #include "BKE_texture.h" #include "BKE_world.h" @@ -94,12 +95,16 @@ #include "ED_datafiles.h" #include "ED_render.h" #include "ED_screen.h" +#include "ED_view3d.h" +#include "ED_view3d_offscreen.h" #ifndef NDEBUG /* Used for database init assert(). */ # include "BLI_threads.h" #endif +static void icon_copy_rect(ImBuf *ibuf, uint w, uint h, uint *rect); + ImBuf *get_brush_icon(Brush *brush) { static const int flags = IB_rect | IB_multilayer | IB_metadata; @@ -336,7 +341,7 @@ static World *preview_get_localized_world(ShaderPreview *sp, World *world) return sp->worldcopy; } -static ID *duplicate_ids(ID *id) +static ID *duplicate_ids(ID *id, const bool allow_failure) { if (id == NULL) { /* Non-ID preview render. */ @@ -344,6 +349,7 @@ static ID *duplicate_ids(ID *id) } switch (GS(id->name)) { + case ID_OB: case ID_MA: case ID_TE: case ID_LA: @@ -357,7 +363,9 @@ static ID *duplicate_ids(ID *id) case ID_SCR: return NULL; default: - BLI_assert(!"ID type preview not supported."); + if (!allow_failure) { + BLI_assert(!"ID type preview not supported."); + } return NULL; } } @@ -698,6 +706,132 @@ void ED_preview_draw(const bContext *C, void *idp, void *parentp, void *slotp, r } } +/* **************************** Object preview ****************** */ + +struct ObjectPreviewData { + /* The main for the preview, not of the current file. */ + Main *pr_main; + /* Copy of the object to create the preview for. The copy is for thread safety (and to insert it + * into an own main). */ + Object *object; + int sizex; + int sizey; +}; + +static Object *object_preview_camera_create( + Main *preview_main, ViewLayer *view_layer, Object *preview_object, int sizex, int sizey) +{ + Object *camera = BKE_object_add(preview_main, view_layer, OB_CAMERA, "Preview Camera"); + + float rotmat[3][3]; + float dummyscale[3]; + mat4_to_loc_rot_size(camera->loc, rotmat, dummyscale, preview_object->obmat); + + /* Camera is Y up, so needs additional 90deg rotation around X to match object's Z up. */ + float drotmat[3][3]; + axis_angle_to_mat3_single(drotmat, 'X', M_PI_2); + mul_m3_m3_post(rotmat, drotmat); + + camera->rotmode = ROT_MODE_QUAT; + mat3_to_quat(camera->quat, rotmat); + + /* shader_preview_render() does this too. */ + if (sizex > sizey) { + ((Camera *)camera->data)->lens *= (float)sizey / (float)sizex; + } + + return camera; +} + +static Scene *object_preview_scene_create(const struct ObjectPreviewData *preview_data, + Depsgraph **r_depsgraph) +{ + Scene *scene = BKE_scene_add(preview_data->pr_main, "Object preview scene"); + ViewLayer *view_layer = scene->view_layers.first; + Depsgraph *depsgraph = DEG_graph_new( + preview_data->pr_main, scene, view_layer, DAG_EVAL_VIEWPORT); + + BLI_assert(preview_data->object != NULL); + BLI_addtail(&preview_data->pr_main->objects, preview_data->object); + + BKE_collection_object_add(preview_data->pr_main, scene->master_collection, preview_data->object); + + Object *camera_object = object_preview_camera_create(preview_data->pr_main, + view_layer, + preview_data->object, + preview_data->sizex, + preview_data->sizey); + + scene->camera = camera_object; + scene->r.xsch = preview_data->sizex; + scene->r.ysch = preview_data->sizey; + scene->r.size = 100; + + Base *preview_base = BKE_view_layer_base_find(view_layer, preview_data->object); + /* For 'view selected' below. */ + preview_base->flag |= BASE_SELECTED; + + DEG_graph_build_from_view_layer(depsgraph); + DEG_evaluate_on_refresh(depsgraph); + + ED_view3d_camera_to_view_selected(preview_data->pr_main, depsgraph, scene, camera_object); + + BKE_scene_graph_update_tagged(depsgraph, preview_data->pr_main); + + *r_depsgraph = depsgraph; + return scene; +} + +static void object_preview_render(IconPreview *preview, IconPreviewSize *preview_sized) +{ + Main *preview_main = BKE_main_new(); + const float pixelsize_old = U.pixelsize; + char err_out[256] = "unknown"; + + BLI_assert(preview->id_copy && (preview->id_copy != preview->id)); + + struct ObjectPreviewData preview_data = { + .pr_main = preview_main, + /* Act on a copy. */ + .object = (Object *)preview->id_copy, + .sizex = preview_sized->sizex, + .sizey = preview_sized->sizey, + }; + Depsgraph *depsgraph; + Scene *scene = object_preview_scene_create(&preview_data, &depsgraph); + + /* Ownership is now ours. */ + preview->id_copy = NULL; + + U.pixelsize = 2.0f; + + ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple( + depsgraph, + DEG_get_evaluated_scene(depsgraph), + NULL, + OB_SOLID, + DEG_get_evaluated_object(depsgraph, scene->camera), + preview_sized->sizex, + preview_sized->sizey, + IB_rect, + V3D_OFSDRAW_NONE, + R_ALPHAPREMUL, + NULL, + NULL, + err_out); + /* TODO color-management? */ + + U.pixelsize = pixelsize_old; + + if (ibuf) { + icon_copy_rect(ibuf, preview_sized->sizex, preview_sized->sizey, preview_sized->rect); + IMB_freeImBuf(ibuf); + } + + DEG_graph_free(depsgraph); + BKE_main_free(preview_main); +} + /* **************************** new shader preview system ****************** */ /* inside thread, called by renderer, sets job update value */ @@ -1188,27 +1322,54 @@ static void common_preview_startjob(void *customdata, } } -/* exported functions */ - -static void icon_preview_add_size(IconPreview *ip, uint *rect, int sizex, int sizey) +/** + * Some ID types already have their own, more focused rendering (only objects right now). This is + * for the other ones, which all share #ShaderPreview and some functions. + */ +static void other_id_types_preview_render(IconPreview *ip, + IconPreviewSize *cur_size, + const bool is_deferred, + short *stop, + short *do_update, + float *progress) { - IconPreviewSize *cur_size = ip->sizes.first, *new_size; + ShaderPreview *sp = MEM_callocN(sizeof(ShaderPreview), "Icon ShaderPreview"); + const bool is_render = !is_deferred; + + /* These types don't use the ShaderPreview mess, they have their own types and functions. */ + BLI_assert(!ELEM(GS(ip->id->name), ID_OB)); + + /* construct shader preview from image size and previewcustomdata */ + sp->scene = ip->scene; + sp->owner = ip->owner; + sp->sizex = cur_size->sizex; + sp->sizey = cur_size->sizey; + sp->pr_method = is_render ? PR_ICON_RENDER : PR_ICON_DEFERRED; + sp->pr_rect = cur_size->rect; + sp->id = ip->id; + sp->id_copy = ip->id_copy; + sp->bmain = ip->bmain; + sp->own_id_copy = false; + Material *ma = NULL; - while (cur_size) { - if (cur_size->sizex == sizex && cur_size->sizey == sizey) { - /* requested size is already in list, no need to add it again */ - return; + if (is_render) { + BLI_assert(ip->id); + + /* grease pencil use its own preview file */ + if (GS(ip->id->name) == ID_MA) { + ma = (Material *)ip->id; } - cur_size = cur_size->next; + if ((ma == NULL) || (ma->gp_style == NULL)) { + sp->pr_main = G_pr_main; + } + else { + sp->pr_main = G_pr_main_grease_pencil; + } } - new_size = MEM_callocN(sizeof(IconPreviewSize), "IconPreviewSize"); - new_size->sizex = sizex; - new_size->sizey = sizey; - new_size->rect = rect; - - BLI_addtail(&ip->sizes, new_size); + common_preview_startjob(sp, stop, do_update, progress); + shader_preview_free(sp); } static void icon_preview_startjob_all_sizes(void *customdata, @@ -1235,41 +1396,36 @@ static void icon_preview_startjob_all_sizes(void *customdata, continue; } - ShaderPreview *sp = MEM_callocN(sizeof(ShaderPreview), "Icon ShaderPreview"); - const bool is_render = !(prv->tag & PRV_TAG_DEFFERED); - - /* construct shader preview from image size and previewcustomdata */ - sp->scene = ip->scene; - sp->owner = ip->owner; - sp->sizex = cur_size->sizex; - sp->sizey = cur_size->sizey; - sp->pr_method = is_render ? PR_ICON_RENDER : PR_ICON_DEFERRED; - sp->pr_rect = cur_size->rect; - sp->id = ip->id; - sp->id_copy = ip->id_copy; - sp->bmain = ip->bmain; - sp->own_id_copy = false; - Material *ma = NULL; - - if (is_render) { - BLI_assert(ip->id); - - /* grease pencil use its own preview file */ - if (GS(ip->id->name) == ID_MA) { - ma = (Material *)ip->id; - } + if (ELEM(GS(ip->id->name), ID_OB)) { + /* Much simpler than the ShaderPreview mess used for other ID types. */ + object_preview_render(ip, cur_size); + } + else { + other_id_types_preview_render( + ip, cur_size, (prv->tag & PRV_TAG_DEFFERED), stop, do_update, progress); + } + } +} - if ((ma == NULL) || (ma->gp_style == NULL)) { - sp->pr_main = G_pr_main; - } - else { - sp->pr_main = G_pr_main_grease_pencil; - } +static void icon_preview_add_size(IconPreview *ip, uint *rect, int sizex, int sizey) +{ + IconPreviewSize *cur_size = ip->sizes.first, *new_size; + + while (cur_size) { + if (cur_size->sizex == sizex && cur_size->sizey == sizey) { + /* requested size is already in list, no need to add it again */ + return; } - common_preview_startjob(sp, stop, do_update, progress); - shader_preview_free(sp); + cur_size = cur_size->next; } + + new_size = MEM_callocN(sizeof(IconPreviewSize), "IconPreviewSize"); + new_size->sizex = sizex; + new_size->sizey = sizey; + new_size->rect = rect; + + BLI_addtail(&ip->sizes, new_size); } static void icon_preview_endjob(void *customdata) @@ -1333,7 +1489,9 @@ void ED_preview_icon_render(Main *bmain, Scene *scene, ID *id, uint *rect, int s ip.scene = scene; ip.owner = BKE_previewimg_id_ensure(id); ip.id = id; - ip.id_copy = duplicate_ids(id); + /* Control isn't given back to the caller until the preview is done. So we don't need to copy + * the ID to avoid thread races. */ + ip.id_copy = duplicate_ids(id, true); icon_preview_add_size(&ip, rect, sizex, sizey); @@ -1376,7 +1534,7 @@ void ED_preview_icon_job( ip->scene = CTX_data_scene(C); ip->owner = owner; ip->id = id; - ip->id_copy = duplicate_ids(id); + ip->id_copy = duplicate_ids(id, false); icon_preview_add_size(ip, rect, sizex, sizey); @@ -1445,7 +1603,7 @@ void ED_preview_shader_job(const bContext *C, sp->sizey = sizey; sp->pr_method = method; sp->id = id; - sp->id_copy = duplicate_ids(id); + sp->id_copy = duplicate_ids(id, false); sp->own_id_copy = true; sp->parent = parent; sp->slot = slot; -- cgit v1.2.3