diff options
author | Jacques Lucke <jacques@blender.org> | 2021-12-27 19:26:09 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2021-12-27 19:26:09 +0300 |
commit | 1c9d8fcb477c5aea5555781cc209d60da126f48f (patch) | |
tree | 0dfd7e47181b1750d9f6521ef71150ae7178fce5 /source/blender/editors/render/render_preview.cc | |
parent | 644e6c7a3e99ae1d43edb25a7d4c3ed86727faba (diff) |
Render: move editor/render module to c++
Doing this in preparation for some work on asset preview generation.
Differential Revision: https://developer.blender.org/D13676
Diffstat (limited to 'source/blender/editors/render/render_preview.cc')
-rw-r--r-- | source/blender/editors/render/render_preview.cc | 1921 |
1 files changed, 1921 insertions, 0 deletions
diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc new file mode 100644 index 00000000000..779bcd1428c --- /dev/null +++ b/source/blender/editors/render/render_preview.cc @@ -0,0 +1,1921 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edrend + */ + +/* global includes */ + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#ifndef WIN32 +# include <unistd.h> +#else +# include <io.h> +#endif +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "PIL_time.h" + +#include "BLO_readfile.h" + +#include "DNA_brush_types.h" +#include "DNA_camera_types.h" +#include "DNA_collection_types.h" +#include "DNA_light_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_world_types.h" + +#include "BKE_animsys.h" +#include "BKE_appdir.h" +#include "BKE_armature.h" +#include "BKE_brush.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_icons.h" +#include "BKE_idprop.h" +#include "BKE_image.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_light.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_scene.h" +#include "BKE_screen.h" +#include "BKE_texture.h" +#include "BKE_world.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" +#include "IMB_thumbs.h" + +#include "BIF_glutil.h" + +#include "GPU_shader.h" + +#include "RE_engine.h" +#include "RE_pipeline.h" +#include "RE_texture.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_armature.h" +#include "ED_datafiles.h" +#include "ED_render.h" +#include "ED_screen.h" +#include "ED_view3d.h" +#include "ED_view3d_offscreen.h" + +#include "UI_interface_icons.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); + +/* -------------------------------------------------------------------- */ +/** \name Local Structs + * \{ */ + +typedef struct ShaderPreview { + /* from wmJob */ + void *owner; + short *stop, *do_update; + + Scene *scene; + ID *id, *id_copy; + ID *parent; + MTex *slot; + + /* datablocks with nodes need full copy during preview render, glsl uses it too */ + Material *matcopy; + Tex *texcopy; + Light *lampcopy; + World *worldcopy; + + /** Copy of the active objects #Object.color */ + float color[4]; + + int sizex, sizey; + uint *pr_rect; + ePreviewRenderMethod pr_method; + bool own_id_copy; + + Main *bmain; + Main *pr_main; +} ShaderPreview; + +typedef struct IconPreviewSize { + struct IconPreviewSize *next, *prev; + int sizex, sizey; + uint *rect; +} IconPreviewSize; + +typedef struct IconPreview { + Main *bmain; + Depsgraph *depsgraph; /* May be NULL (see #WM_OT_previews_ensure). */ + Scene *scene; + void *owner; + ID *id, *id_copy; /* May be NULL! (see ICON_TYPE_PREVIEW case in #ui_icon_ensure_deferred()) */ + ListBase sizes; + + /* May be NULL, is used for rendering IDs that require some other object for it to be applied on + * before the ID can be represented as an image, for example when rendering an Action. */ + struct Object *active_object; +} IconPreview; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Preview for Buttons + * \{ */ + +static Main *G_pr_main = NULL; +static Main *G_pr_main_grease_pencil = NULL; + +#ifndef WITH_HEADLESS +static Main *load_main_from_memory(const void *blend, int blend_size) +{ + const int fileflags = G.fileflags; + Main *bmain = NULL; + BlendFileData *bfd; + + G.fileflags |= G_FILE_NO_UI; + bfd = BLO_read_from_memory(blend, blend_size, BLO_READ_SKIP_NONE, NULL); + if (bfd) { + bmain = bfd->main; + + MEM_freeN(bfd); + } + G.fileflags = fileflags; + + return bmain; +} +#endif + +void ED_preview_ensure_dbase(void) +{ +#ifndef WITH_HEADLESS + static bool base_initialized = false; + BLI_assert(BLI_thread_is_main()); + if (!base_initialized) { + G_pr_main = load_main_from_memory(datatoc_preview_blend, datatoc_preview_blend_size); + G_pr_main_grease_pencil = load_main_from_memory(datatoc_preview_grease_pencil_blend, + datatoc_preview_grease_pencil_blend_size); + base_initialized = true; + } +#endif +} + +static bool check_engine_supports_preview(Scene *scene) +{ + RenderEngineType *type = RE_engines_find(scene->r.engine); + return (type->flag & RE_USE_PREVIEW) != 0; +} + +static bool preview_method_is_render(const ePreviewRenderMethod pr_method) +{ + return ELEM(pr_method, PR_ICON_RENDER, PR_BUTS_RENDER); +} + +void ED_preview_free_dbase(void) +{ + if (G_pr_main) { + BKE_main_free(G_pr_main); + } + + if (G_pr_main_grease_pencil) { + BKE_main_free(G_pr_main_grease_pencil); + } +} + +static Scene *preview_get_scene(Main *pr_main) +{ + if (pr_main == NULL) { + return NULL; + } + + return static_cast<Scene *>(pr_main->scenes.first); +} + +static const char *preview_collection_name(const ePreviewType pr_type) +{ + switch (pr_type) { + case MA_FLAT: + return "Flat"; + case MA_SPHERE: + return "Sphere"; + case MA_CUBE: + return "Cube"; + case MA_SHADERBALL: + return "Shader Ball"; + case MA_CLOTH: + return "Cloth"; + case MA_FLUID: + return "Fluid"; + case MA_SPHERE_A: + return "World Sphere"; + case MA_LAMP: + return "Lamp"; + case MA_SKY: + return "Sky"; + case MA_HAIR: + return "Hair"; + case MA_ATMOS: + return "Atmosphere"; + default: + BLI_assert_msg(0, "Unknown preview type"); + return ""; + } +} + +static bool render_engine_supports_ray_visibility(const Scene *sce) +{ + return !STREQ(sce->r.engine, RE_engine_id_BLENDER_EEVEE); +} + +static void switch_preview_collection_visibilty(ViewLayer *view_layer, const ePreviewType pr_type) +{ + /* Set appropriate layer as visible. */ + LayerCollection *lc = static_cast<LayerCollection *>(view_layer->layer_collections.first); + const char *collection_name = preview_collection_name(pr_type); + + for (lc = static_cast<LayerCollection *>(lc->layer_collections.first); lc; lc = lc->next) { + if (STREQ(lc->collection->id.name + 2, collection_name)) { + lc->collection->flag &= ~COLLECTION_HIDE_RENDER; + } + else { + lc->collection->flag |= COLLECTION_HIDE_RENDER; + } + } +} + +static const char *preview_floor_material_name(const Scene *scene, + const ePreviewRenderMethod pr_method) +{ + if (pr_method == PR_ICON_RENDER && render_engine_supports_ray_visibility(scene)) { + return "FloorHidden"; + } + return "Floor"; +} + +static void switch_preview_floor_material(Main *pr_main, + Mesh *me, + const Scene *scene, + const ePreviewRenderMethod pr_method) +{ + if (me->totcol == 0) { + return; + } + + const char *material_name = preview_floor_material_name(scene, pr_method); + Material *mat = static_cast<Material *>( + BLI_findstring(&pr_main->materials, material_name, offsetof(ID, name) + 2)); + if (mat) { + me->mat[0] = mat; + } +} + +static void switch_preview_floor_visibility(Main *pr_main, + const Scene *scene, + ViewLayer *view_layer, + const ePreviewRenderMethod pr_method) +{ + /* Hide floor for icon renders. */ + LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + if (STREQ(base->object->id.name + 2, "Floor")) { + base->object->visibility_flag &= ~OB_HIDE_RENDER; + if (pr_method == PR_ICON_RENDER) { + if (!render_engine_supports_ray_visibility(scene)) { + base->object->visibility_flag |= OB_HIDE_RENDER; + } + } + if (base->object->type == OB_MESH) { + switch_preview_floor_material( + pr_main, static_cast<Mesh *>(base->object->data), scene, pr_method); + } + } + } +} + +static void set_preview_visibility(Main *pr_main, + Scene *scene, + ViewLayer *view_layer, + const ePreviewType pr_type, + const ePreviewRenderMethod pr_method) +{ + switch_preview_collection_visibilty(view_layer, pr_type); + switch_preview_floor_visibility(pr_main, scene, view_layer, pr_method); + BKE_layer_collection_sync(scene, view_layer); +} + +static World *preview_get_localized_world(ShaderPreview *sp, World *world) +{ + if (world == NULL) { + return NULL; + } + if (sp->worldcopy != NULL) { + return sp->worldcopy; + } + + ID *id_copy = BKE_id_copy_ex(NULL, + &world->id, + NULL, + LIB_ID_CREATE_LOCAL | LIB_ID_COPY_LOCALIZE | + LIB_ID_COPY_NO_ANIMDATA); + sp->worldcopy = (World *)id_copy; + BLI_addtail(&sp->pr_main->worlds, sp->worldcopy); + return sp->worldcopy; +} + +static ID *duplicate_ids(ID *id, const bool allow_failure) +{ + if (id == NULL) { + /* Non-ID preview render. */ + return NULL; + } + + switch (GS(id->name)) { + case ID_OB: + case ID_MA: + case ID_TE: + case ID_LA: + case ID_WO: { + BLI_assert(BKE_previewimg_id_supports_jobs(id)); + ID *id_copy = BKE_id_copy_ex( + NULL, id, NULL, LIB_ID_CREATE_LOCAL | LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_NO_ANIMDATA); + return id_copy; + } + /* These support threading, but don't need duplicating. */ + case ID_IM: + case ID_BR: + BLI_assert(BKE_previewimg_id_supports_jobs(id)); + return NULL; + default: + if (!allow_failure) { + BLI_assert_msg(0, "ID type preview not supported."); + } + return NULL; + } +} + +static const char *preview_world_name(const Scene *sce, + const ID_Type id_type, + const ePreviewRenderMethod pr_method) +{ + /* When rendering material icons the floor will not be shown in the output. Cycles will use a + * material trick to show the floor in the reflections, but hide the floor for camera rays. For + * Eevee we use a transparent world that has a projected grid. + * + * In the future when Eevee supports vulkan raytracing we can re-evaluate and perhaps remove this + * approximation. + */ + if (id_type == ID_MA && pr_method == PR_ICON_RENDER && + !render_engine_supports_ray_visibility(sce)) { + return "WorldFloor"; + } + return "World"; +} + +static World *preview_get_world(Main *pr_main, + const Scene *sce, + const ID_Type id_type, + const ePreviewRenderMethod pr_method) +{ + World *result = NULL; + const char *world_name = preview_world_name(sce, id_type, pr_method); + result = static_cast<World *>( + BLI_findstring(&pr_main->worlds, world_name, offsetof(ID, name) + 2)); + + /* No world found return first world. */ + if (result == NULL) { + result = static_cast<World *>(pr_main->worlds.first); + } + + BLI_assert_msg(result, "Preview file has no world."); + return result; +} + +static void preview_sync_exposure(World *dst, const World *src) +{ + BLI_assert(dst); + BLI_assert(src); + dst->exp = src->exp; + dst->range = src->range; +} + +static World *preview_prepare_world(Main *pr_main, + const Scene *sce, + const World *world, + const ID_Type id_type, + const ePreviewRenderMethod pr_method) +{ + World *result = preview_get_world(pr_main, sce, id_type, pr_method); + if (world) { + preview_sync_exposure(result, world); + } + return result; +} + +/* call this with a pointer to initialize preview scene */ +/* call this with NULL to restore assigned ID pointers in preview scene */ +static Scene *preview_prepare_scene( + Main *bmain, Scene *scene, ID *id, int id_type, ShaderPreview *sp) +{ + Scene *sce; + Main *pr_main = sp->pr_main; + + memcpy(pr_main->filepath, BKE_main_blendfile_path(bmain), sizeof(pr_main->filepath)); + + sce = preview_get_scene(pr_main); + if (sce) { + ViewLayer *view_layer = static_cast<ViewLayer *>(sce->view_layers.first); + + /* Only enable the combined renderpass */ + view_layer->passflag = SCE_PASS_COMBINED; + view_layer->eevee.render_passes = 0; + + /* this flag tells render to not execute depsgraph or ipos etc */ + sce->r.scemode |= R_BUTS_PREVIEW; + BLI_strncpy(sce->r.engine, scene->r.engine, sizeof(sce->r.engine)); + + sce->r.color_mgt_flag = scene->r.color_mgt_flag; + BKE_color_managed_display_settings_copy(&sce->display_settings, &scene->display_settings); + + BKE_color_managed_view_settings_free(&sce->view_settings); + BKE_color_managed_view_settings_copy(&sce->view_settings, &scene->view_settings); + + if ((id && sp->pr_method == PR_ICON_RENDER) && id_type != ID_WO) { + sce->r.alphamode = R_ALPHAPREMUL; + } + else { + sce->r.alphamode = R_ADDSKY; + } + + sce->r.cfra = scene->r.cfra; + + /* Setup the world. */ + sce->world = preview_prepare_world( + pr_main, sce, scene->world, static_cast<ID_Type>(id_type), sp->pr_method); + + if (id_type == ID_TE) { + /* Texture is not actually rendered with engine, just set dummy value. */ + BLI_strncpy(sce->r.engine, RE_engine_id_BLENDER_EEVEE, sizeof(sce->r.engine)); + } + + if (id_type == ID_MA) { + Material *mat = NULL, *origmat = (Material *)id; + + if (origmat) { + /* work on a copy */ + BLI_assert(sp->id_copy != NULL); + mat = sp->matcopy = (Material *)sp->id_copy; + sp->id_copy = NULL; + BLI_addtail(&pr_main->materials, mat); + + /* Use current scene world for lighting. */ + if (mat->pr_flag == MA_PREVIEW_WORLD && sp->pr_method == PR_BUTS_RENDER) { + /* Use current scene world to light sphere. */ + sce->world = preview_get_localized_world(sp, scene->world); + } + else if (sce->world && sp->pr_method != PR_ICON_RENDER) { + /* Use a default world color. Using the current + * scene world can be slow if it has big textures. */ + sce->world->use_nodes = false; + sce->world->horr = 0.05f; + sce->world->horg = 0.05f; + sce->world->horb = 0.05f; + } + + /* For grease pencil, always use sphere for icon renders. */ + const ePreviewType preview_type = static_cast<ePreviewType>( + (sp->pr_method == PR_ICON_RENDER && sp->pr_main == G_pr_main_grease_pencil) ? + MA_SPHERE_A : + mat->pr_type); + set_preview_visibility(pr_main, sce, view_layer, preview_type, sp->pr_method); + } + else { + sce->display.render_aa = SCE_DISPLAY_AA_OFF; + } + + LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + if (base->object->id.name[2] == 'p') { + /* copy over object color, in case material uses it */ + copy_v4_v4(base->object->color, sp->color); + + if (OB_TYPE_SUPPORT_MATERIAL(base->object->type)) { + /* don't use BKE_object_material_assign, it changed mat->id.us, which shows in the UI + */ + Material ***matar = BKE_object_material_array_p(base->object); + int actcol = max_ii(base->object->actcol - 1, 0); + + if (matar && actcol < base->object->totcol) { + (*matar)[actcol] = mat; + } + } + else if (base->object->type == OB_LAMP) { + base->flag |= BASE_VISIBLE_DEPSGRAPH; + } + } + } + } + else if (id_type == ID_TE) { + Tex *tex = NULL, *origtex = (Tex *)id; + + if (origtex) { + BLI_assert(sp->id_copy != NULL); + tex = sp->texcopy = (Tex *)sp->id_copy; + sp->id_copy = NULL; + BLI_addtail(&pr_main->textures, tex); + } + } + else if (id_type == ID_LA) { + Light *la = NULL, *origla = (Light *)id; + + /* work on a copy */ + if (origla) { + BLI_assert(sp->id_copy != NULL); + la = sp->lampcopy = (Light *)sp->id_copy; + sp->id_copy = NULL; + BLI_addtail(&pr_main->lights, la); + } + + set_preview_visibility(pr_main, sce, view_layer, MA_LAMP, sp->pr_method); + + if (sce->world) { + /* Only use lighting from the light. */ + sce->world->use_nodes = false; + sce->world->horr = 0.0f; + sce->world->horg = 0.0f; + sce->world->horb = 0.0f; + } + + LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + if (base->object->id.name[2] == 'p') { + if (base->object->type == OB_LAMP) { + base->object->data = la; + } + } + } + } + else if (id_type == ID_WO) { + World *wrld = NULL, *origwrld = (World *)id; + + if (origwrld) { + BLI_assert(sp->id_copy != NULL); + wrld = sp->worldcopy = (World *)sp->id_copy; + sp->id_copy = NULL; + BLI_addtail(&pr_main->worlds, wrld); + } + + set_preview_visibility(pr_main, sce, view_layer, MA_SKY, sp->pr_method); + sce->world = wrld; + } + + return sce; + } + + return NULL; +} + +/* new UI convention: draw is in pixel space already. */ +/* uses UI_BTYPE_ROUNDBOX button in block to get the rect */ +static bool ed_preview_draw_rect(ScrArea *area, int split, int first, rcti *rect, rcti *newrect) +{ + Render *re; + RenderView *rv; + RenderResult rres; + char name[32]; + int offx = 0; + int newx = BLI_rcti_size_x(rect); + int newy = BLI_rcti_size_y(rect); + bool ok = false; + + if (!split || first) { + sprintf(name, "Preview %p", (void *)area); + } + else { + sprintf(name, "SecondPreview %p", (void *)area); + } + + if (split) { + if (first) { + offx = 0; + newx = newx / 2; + } + else { + offx = newx / 2; + newx = newx - newx / 2; + } + } + + /* test if something rendered ok */ + re = RE_GetRender(name); + + if (re == NULL) { + return false; + } + + RE_AcquireResultImageViews(re, &rres); + + if (!BLI_listbase_is_empty(&rres.views)) { + /* material preview only needs monoscopy (view 0) */ + rv = RE_RenderViewGetById(&rres, 0); + } + else { + /* possible the job clears the views but we're still drawing T45496 */ + rv = NULL; + } + + if (rv && rv->rectf) { + + if (abs(rres.rectx - newx) < 2 && abs(rres.recty - newy) < 2) { + + newrect->xmax = max_ii(newrect->xmax, rect->xmin + rres.rectx + offx); + newrect->ymax = max_ii(newrect->ymax, rect->ymin + rres.recty); + + if (rres.rectx && rres.recty) { + uchar *rect_byte = static_cast<uchar *>( + MEM_mallocN(rres.rectx * rres.recty * sizeof(int), "ed_preview_draw_rect")); + float fx = rect->xmin + offx; + float fy = rect->ymin; + + /* material preview only needs monoscopy (view 0) */ + RE_AcquiredResultGet32(re, &rres, (uint *)rect_byte, 0); + + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + immDrawPixelsTex( + &state, fx, fy, rres.rectx, rres.recty, GPU_RGBA8, false, rect_byte, 1.0f, 1.0f, NULL); + + MEM_freeN(rect_byte); + + ok = 1; + } + } + } + + RE_ReleaseResultImageViews(re, &rres); + + return ok; +} + +void ED_preview_draw(const bContext *C, void *idp, void *parentp, void *slotp, rcti *rect) +{ + if (idp) { + wmWindowManager *wm = CTX_wm_manager(C); + ScrArea *area = CTX_wm_area(C); + ID *id = (ID *)idp; + ID *parent = (ID *)parentp; + MTex *slot = (MTex *)slotp; + SpaceProperties *sbuts = CTX_wm_space_properties(C); + ShaderPreview *sp = static_cast<ShaderPreview *>(WM_jobs_customdata(wm, area)); + rcti newrect; + int ok; + int newx = BLI_rcti_size_x(rect); + int newy = BLI_rcti_size_y(rect); + + newrect.xmin = rect->xmin; + newrect.xmax = rect->xmin; + newrect.ymin = rect->ymin; + newrect.ymax = rect->ymin; + + if (parent) { + ok = ed_preview_draw_rect(area, 1, 1, rect, &newrect); + ok &= ed_preview_draw_rect(area, 1, 0, rect, &newrect); + } + else { + ok = ed_preview_draw_rect(area, 0, 0, rect, &newrect); + } + + if (ok) { + *rect = newrect; + } + + /* start a new preview render job if signaled through sbuts->preview, + * if no render result was found and no preview render job is running, + * or if the job is running and the size of preview changed */ + if ((sbuts != NULL && sbuts->preview) || + (!ok && !WM_jobs_test(wm, area, WM_JOB_TYPE_RENDER_PREVIEW)) || + (sp && (abs(sp->sizex - newx) >= 2 || abs(sp->sizey - newy) > 2))) { + if (sbuts != NULL) { + sbuts->preview = 0; + } + ED_preview_shader_job(C, area, id, parent, slot, newx, newy, PR_BUTS_RENDER); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name 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; + /* Current frame. */ + int cfra; + int sizex; + int sizey; +}; + +static bool object_preview_is_type_supported(const Object *ob) +{ + return OB_TYPE_IS_GEOMETRY(ob->type); +} + +static Object *object_preview_camera_create(Main *preview_main, + ViewLayer *view_layer, + Object *preview_object) +{ + 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 rotations to obliquely face the front. */ + float drotmat[3][3]; + const float eul[3] = {M_PI * 0.4f, 0.0f, M_PI * 0.1f}; + eul_to_mat3(drotmat, eul); + mul_m3_m3_post(rotmat, drotmat); + + camera->rotmode = ROT_MODE_QUAT; + mat3_to_quat(camera->quat, rotmat); + + /* Nice focal length for close portraiture. */ + ((Camera *)camera->data)->lens = 85; + + 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"); + /* Preview need to be in the current frame to get a thumbnail similar of what + * viewport displays. */ + CFRA = preview_data->cfra; + + ViewLayer *view_layer = static_cast<ViewLayer *>(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); + + 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 = {}; + preview_data.pr_main = preview_main; + /* Act on a copy. */ + preview_data.object = (Object *)preview->id_copy; + preview_data.cfra = preview->scene->r.cfra; + preview_data.sizex = preview_sized->sizex; + preview_data.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; + + View3DShading shading; + BKE_screen_view3d_shading_init(&shading); + /* Enable shadows, makes it a bit easier to see the shape. */ + shading.flag |= V3D_SHADING_SHADOW; + + ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple( + depsgraph, + DEG_get_evaluated_scene(depsgraph), + &shading, + OB_TEXTURE, + DEG_get_evaluated_object(depsgraph, scene->camera), + preview_sized->sizex, + preview_sized->sizey, + IB_rect, + V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS, + 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); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Action Preview + * \{ */ + +static struct PoseBackup *action_preview_render_prepare(IconPreview *preview) +{ + Object *object = preview->active_object; + if (object == NULL) { + WM_report(RPT_WARNING, "No active object, unable to apply the Action before rendering"); + return NULL; + } + if (object->pose == NULL) { + WM_reportf(RPT_WARNING, + "Object %s has no pose, unable to apply the Action before rendering", + object->id.name + 2); + return NULL; + } + + /* Create a backup of the current pose. */ + struct bAction *action = (struct bAction *)preview->id; + struct PoseBackup *pose_backup = ED_pose_backup_create_all_bones(object, action); + + /* Apply the Action as pose, so that it can be rendered. This assumes the Action represents a + * single pose, and that thus the evaluation time doesn't matter. */ + AnimationEvalContext anim_eval_context = {preview->depsgraph, 0.0f}; + BKE_pose_apply_action_all_bones(object, action, &anim_eval_context); + + /* Force evaluation of the new pose, before the preview is rendered. */ + DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); + DEG_evaluate_on_refresh(preview->depsgraph); + + return pose_backup; +} + +static void action_preview_render_cleanup(IconPreview *preview, struct PoseBackup *pose_backup) +{ + if (pose_backup == NULL) { + return; + } + ED_pose_backup_restore(pose_backup); + ED_pose_backup_free(pose_backup); + + DEG_id_tag_update(&preview->active_object->id, ID_RECALC_GEOMETRY); +} + +/* Render a pose from the scene camera. It is assumed that the scene camera is + * capturing the pose. The pose is applied temporarily to the current object + * before rendering. */ +static void action_preview_render(IconPreview *preview, IconPreviewSize *preview_sized) +{ + char err_out[256] = ""; + + Depsgraph *depsgraph = preview->depsgraph; + /* Not all code paths that lead to this function actually provide a depsgraph. + * The "Refresh Asset Preview" button (ED_OT_lib_id_generate_preview) does, + * but WM_OT_previews_ensure does not. */ + BLI_assert(depsgraph != NULL); + BLI_assert(preview->scene == DEG_get_input_scene(depsgraph)); + + /* Apply the pose before getting the evaluated scene, so that the new pose is evaluated. */ + struct PoseBackup *pose_backup = action_preview_render_prepare(preview); + + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); + Object *camera_eval = scene_eval->camera; + if (camera_eval == NULL) { + printf("Scene has no camera, unable to render preview of %s without it.\n", + preview->id->name + 2); + return; + } + + /* This renders with the Workbench engine settings stored on the Scene. */ + ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple(depsgraph, + scene_eval, + NULL, + OB_SOLID, + camera_eval, + preview_sized->sizex, + preview_sized->sizey, + IB_rect, + V3D_OFSDRAW_NONE, + R_ADDSKY, + NULL, + NULL, + err_out); + + action_preview_render_cleanup(preview, pose_backup); + + if (err_out[0] != '\0') { + printf("Error rendering Action %s preview: %s\n", preview->id->name + 2, err_out); + } + + if (ibuf) { + icon_copy_rect(ibuf, preview_sized->sizex, preview_sized->sizey, preview_sized->rect); + IMB_freeImBuf(ibuf); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name New Shader Preview System + * \{ */ + +/* inside thread, called by renderer, sets job update value */ +static void shader_preview_update(void *spv, + RenderResult *UNUSED(rr), + volatile struct rcti *UNUSED(rect)) +{ + ShaderPreview *sp = static_cast<ShaderPreview *>(spv); + + *(sp->do_update) = true; +} + +/* called by renderer, checks job value */ +static int shader_preview_break(void *spv) +{ + ShaderPreview *sp = static_cast<ShaderPreview *>(spv); + + return *(sp->stop); +} + +static void shader_preview_updatejob(void *UNUSED(spv)) +{ +} + +/* Renders texture directly to render buffer. */ +static void shader_preview_texture(ShaderPreview *sp, Tex *tex, Scene *sce, Render *re) +{ + /* Setup output buffer. */ + int width = sp->sizex; + int height = sp->sizey; + + /* This is needed otherwise no RenderResult is created. */ + sce->r.scemode &= ~R_BUTS_PREVIEW; + RE_InitState(re, NULL, &sce->r, &sce->view_layers, NULL, width, height, NULL); + RE_SetScene(re, sce); + + /* Create buffer in empty RenderView created in the init step. */ + RenderResult *rr = RE_AcquireResultWrite(re); + RenderView *rv = (RenderView *)rr->views.first; + rv->rectf = static_cast<float *>( + MEM_callocN(sizeof(float[4]) * width * height, "texture render result")); + RE_ReleaseResult(re); + + /* Get texture image pool (if any) */ + struct ImagePool *img_pool = BKE_image_pool_new(); + BKE_texture_fetch_images_for_pool(tex, img_pool); + + /* Fill in image buffer. */ + float *rect_float = rv->rectf; + float tex_coord[3] = {0.0f, 0.0f, 0.0f}; + bool color_manage = true; + + for (int y = 0; y < height; y++) { + /* Tex coords between -1.0f and 1.0f. */ + tex_coord[1] = ((float)y / (float)height) * 2.0f - 1.0f; + + for (int x = 0; x < width; x++) { + tex_coord[0] = ((float)x / (float)height) * 2.0f - 1.0f; + + /* Evaluate texture at tex_coord. */ + TexResult texres = {0}; + BKE_texture_get_value_ex(sce, tex, tex_coord, &texres, img_pool, color_manage); + + rect_float[0] = texres.tr; + rect_float[1] = texres.tg; + rect_float[2] = texres.tb; + rect_float[3] = texres.talpha ? texres.ta : 1.0f; + + rect_float += 4; + } + + /* Check if we should cancel texture preview. */ + if (shader_preview_break(sp)) { + break; + } + } + + BKE_image_pool_free(img_pool); +} + +static void shader_preview_render(ShaderPreview *sp, ID *id, int split, int first) +{ + Render *re; + Scene *sce; + float oldlens; + short idtype = GS(id->name); + char name[32]; + int sizex; + Main *pr_main = sp->pr_main; + + /* in case of split preview, use border render */ + if (split) { + if (first) { + sizex = sp->sizex / 2; + } + else { + sizex = sp->sizex - sp->sizex / 2; + } + } + else { + sizex = sp->sizex; + } + + /* we have to set preview variables first */ + sce = preview_get_scene(pr_main); + if (sce) { + sce->r.xsch = sizex; + sce->r.ysch = sp->sizey; + sce->r.size = 100; + } + + /* get the stuff from the builtin preview dbase */ + sce = preview_prepare_scene(sp->bmain, sp->scene, id, idtype, sp); + if (sce == NULL) { + return; + } + + if (!split || first) { + sprintf(name, "Preview %p", sp->owner); + } + else { + sprintf(name, "SecondPreview %p", sp->owner); + } + re = RE_GetRender(name); + + /* full refreshed render from first tile */ + if (re == NULL) { + re = RE_NewRender(name); + } + + /* sce->r gets copied in RE_InitState! */ + sce->r.scemode &= ~(R_MATNODE_PREVIEW | R_TEXNODE_PREVIEW); + sce->r.scemode &= ~R_NO_IMAGE_LOAD; + + if (sp->pr_method == PR_ICON_RENDER) { + sce->r.scemode |= R_NO_IMAGE_LOAD; + sce->display.render_aa = SCE_DISPLAY_AA_SAMPLES_8; + } + else { /* PR_BUTS_RENDER */ + sce->display.render_aa = SCE_DISPLAY_AA_SAMPLES_8; + } + + /* Callbacks are cleared on GetRender(). */ + if (sp->pr_method == PR_BUTS_RENDER) { + RE_display_update_cb(re, sp, shader_preview_update); + } + /* set this for all previews, default is react to G.is_break still */ + RE_test_break_cb(re, sp, shader_preview_break); + + /* lens adjust */ + oldlens = ((Camera *)sce->camera->data)->lens; + if (sizex > sp->sizey) { + ((Camera *)sce->camera->data)->lens *= (float)sp->sizey / (float)sizex; + } + + /* entire cycle for render engine */ + if (idtype == ID_TE) { + shader_preview_texture(sp, (Tex *)id, sce, re); + } + else { + /* Render preview scene */ + RE_PreviewRender(re, pr_main, sce); + } + + ((Camera *)sce->camera->data)->lens = oldlens; + + /* handle results */ + if (sp->pr_method == PR_ICON_RENDER) { + // char *rct = (char *)(sp->pr_rect + 32 * 16 + 16); + + if (sp->pr_rect) { + RE_ResultGet32(re, sp->pr_rect); + } + } + + /* unassign the pointers, reset vars */ + preview_prepare_scene(sp->bmain, sp->scene, NULL, GS(id->name), sp); + + /* XXX bad exception, end-exec is not being called in render, because it uses local main. */ +#if 0 + if (idtype == ID_TE) { + Tex *tex = (Tex *)id; + if (tex->use_nodes && tex->nodetree) + ntreeEndExecTree(tex->nodetree); + } +#endif +} + +/* runs inside thread for material and icons */ +static void shader_preview_startjob(void *customdata, short *stop, short *do_update) +{ + ShaderPreview *sp = static_cast<ShaderPreview *>(customdata); + + sp->stop = stop; + sp->do_update = do_update; + + if (sp->parent) { + shader_preview_render(sp, sp->id, 1, 1); + shader_preview_render(sp, sp->parent, 1, 0); + } + else { + shader_preview_render(sp, sp->id, 0, 0); + } + + *do_update = true; +} + +static void preview_id_copy_free(ID *id) +{ + struct IDProperty *properties; + /* get rid of copied ID */ + properties = IDP_GetProperties(id, false); + if (properties) { + IDP_FreePropertyContent_ex(properties, false); + MEM_freeN(properties); + } + BKE_libblock_free_datablock(id, 0); + MEM_freeN(id); +} + +static void shader_preview_free(void *customdata) +{ + ShaderPreview *sp = static_cast<ShaderPreview *>(customdata); + Main *pr_main = sp->pr_main; + ID *main_id_copy = NULL; + ID *sub_id_copy = NULL; + + if (sp->matcopy) { + main_id_copy = (ID *)sp->matcopy; + BLI_remlink(&pr_main->materials, sp->matcopy); + } + if (sp->texcopy) { + BLI_assert(main_id_copy == NULL); + main_id_copy = (ID *)sp->texcopy; + BLI_remlink(&pr_main->textures, sp->texcopy); + } + if (sp->worldcopy) { + /* worldcopy is also created for material with `Preview World` enabled */ + if (main_id_copy) { + sub_id_copy = (ID *)sp->worldcopy; + } + else { + main_id_copy = (ID *)sp->worldcopy; + } + BLI_remlink(&pr_main->worlds, sp->worldcopy); + } + if (sp->lampcopy) { + BLI_assert(main_id_copy == NULL); + main_id_copy = (ID *)sp->lampcopy; + BLI_remlink(&pr_main->lights, sp->lampcopy); + } + if (sp->own_id_copy) { + if (sp->id_copy) { + preview_id_copy_free(sp->id_copy); + } + if (main_id_copy) { + preview_id_copy_free(main_id_copy); + } + if (sub_id_copy) { + preview_id_copy_free(sub_id_copy); + } + } + + MEM_freeN(sp); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Icon Preview + * \{ */ + +static ImBuf *icon_preview_imbuf_from_brush(Brush *brush) +{ + static const int flags = IB_rect | IB_multilayer | IB_metadata; + + char path[FILE_MAX]; + const char *folder; + + if (!(brush->icon_imbuf)) { + if (brush->flag & BRUSH_CUSTOM_ICON) { + + if (brush->icon_filepath[0]) { + /* First use the path directly to try and load the file. */ + + BLI_strncpy(path, brush->icon_filepath, sizeof(brush->icon_filepath)); + BLI_path_abs(path, ID_BLEND_PATH_FROM_GLOBAL(&brush->id)); + + /* Use default color-spaces for brushes. */ + brush->icon_imbuf = IMB_loadiffname(path, flags, NULL); + + /* otherwise lets try to find it in other directories */ + if (!(brush->icon_imbuf)) { + folder = BKE_appdir_folder_id(BLENDER_DATAFILES, "brushicons"); + + BLI_make_file_string( + BKE_main_blendfile_path_from_global(), path, folder, brush->icon_filepath); + + if (path[0]) { + /* Use default color spaces. */ + brush->icon_imbuf = IMB_loadiffname(path, flags, NULL); + } + } + + if (brush->icon_imbuf) { + BKE_icon_changed(BKE_icon_id_ensure(&brush->id)); + } + } + } + } + + if (!(brush->icon_imbuf)) { + brush->id.icon_id = 0; + } + + return brush->icon_imbuf; +} + +static void icon_copy_rect(ImBuf *ibuf, uint w, uint h, uint *rect) +{ + struct ImBuf *ima; + uint *drect, *srect; + float scaledx, scaledy; + short ex, ey, dx, dy; + + /* paranoia test */ + if (ibuf == NULL || (ibuf->rect == NULL && ibuf->rect_float == NULL)) { + return; + } + + /* Waste of cpu cycles... but the imbuf API has no other way to scale fast (ton). */ + ima = IMB_dupImBuf(ibuf); + + if (!ima) { + return; + } + + if (ima->x > ima->y) { + scaledx = (float)w; + scaledy = ((float)ima->y / (float)ima->x) * (float)w; + } + else { + scaledx = ((float)ima->x / (float)ima->y) * (float)h; + scaledy = (float)h; + } + + /* Scaling down must never assign zero width/height, see: T89868. */ + ex = MAX2(1, (short)scaledx); + ey = MAX2(1, (short)scaledy); + + dx = (w - ex) / 2; + dy = (h - ey) / 2; + + IMB_scalefastImBuf(ima, ex, ey); + + /* if needed, convert to 32 bits */ + if (ima->rect == NULL) { + IMB_rect_from_float(ima); + } + + srect = ima->rect; + drect = rect; + + drect += dy * w + dx; + for (; ey > 0; ey--) { + memcpy(drect, srect, ex * sizeof(int)); + drect += w; + srect += ima->x; + } + + IMB_freeImBuf(ima); +} + +static void set_alpha(char *cp, int sizex, int sizey, char alpha) +{ + int a, size = sizex * sizey; + + for (a = 0; a < size; a++, cp += 4) { + cp[3] = alpha; + } +} + +static void icon_preview_startjob(void *customdata, short *stop, short *do_update) +{ + 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); + } + } + else { + ID *id = sp->id; + short idtype = GS(id->name); + + BLI_assert(id != NULL); + + if (idtype == ID_IM) { + Image *ima = (Image *)id; + ImBuf *ibuf = NULL; + ImageUser iuser; + BKE_imageuser_default(&iuser); + + if (ima == NULL) { + return; + } + + /* setup dummy image user */ + iuser.framenr = 1; + iuser.scene = sp->scene; + + /* 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, NULL); + if (ibuf == NULL || (ibuf->rect == NULL && ibuf->rect_float == NULL)) { + BKE_image_release_ibuf(ima, ibuf, NULL); + return; + } + + icon_copy_rect(ibuf, sp->sizex, sp->sizey, sp->pr_rect); + + *do_update = true; + + BKE_image_release_ibuf(ima, ibuf, NULL); + } + 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); + } + } + } +} + +/* use same function for icon & shader, so the job manager + * does not run two of them at the same time. */ + +static void common_preview_startjob(void *customdata, + short *stop, + short *do_update, + float *UNUSED(progress)) +{ + ShaderPreview *sp = static_cast<ShaderPreview *>(customdata); + + if (ELEM(sp->pr_method, PR_ICON_RENDER, PR_ICON_DEFERRED)) { + icon_preview_startjob(customdata, stop, do_update); + } + else { + shader_preview_startjob(customdata, stop, do_update); + } +} + +/** + * 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 ePreviewRenderMethod pr_method, + short *stop, + short *do_update, + float *progress) +{ + ShaderPreview *sp = MEM_cnew<ShaderPreview>("Icon ShaderPreview"); + + /* These types don't use the ShaderPreview mess, they have their own types and functions. */ + BLI_assert(!ip->id || !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 = pr_method; + 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 (sp->pr_method == PR_ICON_RENDER) { + BLI_assert(ip->id); + + /* grease pencil use its own preview file */ + if (GS(ip->id->name) == ID_MA) { + ma = (Material *)ip->id; + } + + if ((ma == NULL) || (ma->gp_style == NULL)) { + sp->pr_main = G_pr_main; + } + else { + sp->pr_main = G_pr_main_grease_pencil; + } + } + + common_preview_startjob(sp, stop, do_update, progress); + shader_preview_free(sp); +} + +/* exported functions */ + +/** + * Find the index to map \a icon_size to data in \a preview_image. + */ +static int icon_previewimg_size_index_get(const IconPreviewSize *icon_size, + const PreviewImage *preview_image) +{ + for (int i = 0; i < NUM_ICON_SIZES; i++) { + if ((preview_image->w[i] == icon_size->sizex) && (preview_image->h[i] == icon_size->sizey)) { + return i; + } + } + + BLI_assert_msg(0, "The searched icon size does not match any in the preview image"); + return -1; +} + +static void icon_preview_startjob_all_sizes(void *customdata, + short *stop, + short *do_update, + float *progress) +{ + IconPreview *ip = (IconPreview *)customdata; + IconPreviewSize *cur_size; + + for (cur_size = static_cast<IconPreviewSize *>(ip->sizes.first); cur_size; + cur_size = cur_size->next) { + PreviewImage *prv = static_cast<PreviewImage *>(ip->owner); + /* Is this a render job or a deferred loading job? */ + const ePreviewRenderMethod pr_method = (prv->tag & PRV_TAG_DEFFERED) ? PR_ICON_DEFERRED : + PR_ICON_RENDER; + + if (*stop) { + break; + } + + if (prv->tag & PRV_TAG_DEFFERED_DELETE) { + /* Non-thread-protected reading is not an issue here. */ + continue; + } + + /* check_engine_supports_preview() checks whether the engine supports "preview mode" (think: + * Material Preview). This check is only relevant when the render function called below is + * going to use such a mode. Object and Action render functions use Solid mode, though, so + * they can skip this test. */ + /* TODO: Decouple the ID-type-specific render functions from this function, so that it's not + * necessary to know here what happens inside lower-level functions. */ + const bool use_solid_render_mode = (ip->id != NULL) && ELEM(GS(ip->id->name), ID_OB, ID_AC); + if (!use_solid_render_mode && preview_method_is_render(pr_method) && + !check_engine_supports_preview(ip->scene)) { + continue; + } + +#ifndef NDEBUG + { + int size_index = icon_previewimg_size_index_get(cur_size, prv); + BLI_assert(!BKE_previewimg_is_finished(prv, size_index)); + } +#endif + + if (ip->id != NULL) { + switch (GS(ip->id->name)) { + case ID_OB: + if (object_preview_is_type_supported((Object *)ip->id)) { + /* Much simpler than the ShaderPreview mess used for other ID types. */ + object_preview_render(ip, cur_size); + continue; + } + break; + case ID_AC: + action_preview_render(ip, cur_size); + continue; + default: + /* Fall through to the same code as the `ip->id == NULL` case. */ + break; + } + } + other_id_types_preview_render(ip, cur_size, pr_method, stop, do_update, progress); + } +} + +static void icon_preview_add_size(IconPreview *ip, uint *rect, int sizex, int sizey) +{ + IconPreviewSize *cur_size = static_cast<IconPreviewSize *>(ip->sizes.first); + + 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; + } + + cur_size = cur_size->next; + } + + IconPreviewSize *new_size = MEM_cnew<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) +{ + IconPreview *ip = static_cast<IconPreview *>(customdata); + + if (ip->id) { + + if (GS(ip->id->name) == ID_BR) { + WM_main_add_notifier(NC_BRUSH | NA_EDITED, ip->id); + } +#if 0 + if (GS(ip->id->name) == ID_MA) { + Material *ma = (Material *)ip->id; + PreviewImage *prv_img = ma->preview; + int i; + + /* signal to gpu texture */ + for (i = 0; i < NUM_ICON_SIZES; i++) { + if (prv_img->gputexture[i]) { + GPU_texture_free(prv_img->gputexture[i]); + prv_img->gputexture[i] = NULL; + WM_main_add_notifier(NC_MATERIAL | ND_SHADING_DRAW, ip->id); + } + } + } +#endif + } + + if (ip->owner) { + PreviewImage *prv_img = static_cast<PreviewImage *>(ip->owner); + prv_img->tag &= ~PRV_TAG_DEFFERED_RENDERING; + + LISTBASE_FOREACH (IconPreviewSize *, icon_size, &ip->sizes) { + int size_index = icon_previewimg_size_index_get(icon_size, prv_img); + BKE_previewimg_finish(prv_img, size_index); + } + + if (prv_img->tag & PRV_TAG_DEFFERED_DELETE) { + BLI_assert(prv_img->tag & PRV_TAG_DEFFERED); + BKE_previewimg_deferred_release(prv_img); + } + } +} + +static void icon_preview_free(void *customdata) +{ + IconPreview *ip = (IconPreview *)customdata; + + if (ip->id_copy) { + preview_id_copy_free(ip->id_copy); + } + + BLI_freelistN(&ip->sizes); + MEM_freeN(ip); +} + +bool ED_preview_id_is_supported(const ID *id) +{ + if (id == NULL) { + return false; + } + + if (GS(id->name) == ID_OB) { + return object_preview_is_type_supported((const Object *)id); + } + return BKE_previewimg_id_get_p(id) != NULL; +} + +void ED_preview_icon_render( + const bContext *C, Scene *scene, ID *id, uint *rect, int sizex, int sizey) +{ + IconPreview ip = {NULL}; + short stop = false, update = false; + float progress = 0.0f; + + ED_preview_ensure_dbase(); + + ip.bmain = CTX_data_main(C); + ip.scene = scene; + ip.depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ip.owner = BKE_previewimg_id_ensure(id); + ip.id = 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); + ip.active_object = CTX_data_active_object(C); + + icon_preview_add_size(&ip, rect, sizex, sizey); + + icon_preview_startjob_all_sizes(&ip, &stop, &update, &progress); + + icon_preview_endjob(&ip); + + BLI_freelistN(&ip.sizes); + if (ip.id_copy != NULL) { + preview_id_copy_free(ip.id_copy); + } +} + +void ED_preview_icon_job( + const bContext *C, void *owner, ID *id, uint *rect, int sizex, int sizey, const bool delay) +{ + wmJob *wm_job; + 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); + + ip = MEM_cnew<IconPreview>("icon preview"); + + /* render all resolutions from suspended job too */ + old_ip = static_cast<IconPreview *>(WM_jobs_customdata_get(wm_job)); + if (old_ip) { + BLI_movelisttolist(&ip->sizes, &old_ip->sizes); + } + + /* customdata for preview thread */ + ip->bmain = CTX_data_main(C); + 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->id = id; + ip->id_copy = duplicate_ids(id, false); + + icon_preview_add_size(ip, rect, sizex, sizey); + + /* 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; + } + } + + /* setup job */ + WM_jobs_customdata_set(wm_job, ip, icon_preview_free); + WM_jobs_timer(wm_job, 0.1, NC_WINDOW, NC_WINDOW); + /* Wait 2s to start rendering icon previews, to not bog down user interaction. + * Particularly important for heavy scenes and Eevee using OpenGL that blocks + * the user interface drawing. */ + WM_jobs_delay_start(wm_job, (delay) ? 2.0 : 0.0); + WM_jobs_callbacks(wm_job, icon_preview_startjob_all_sizes, NULL, NULL, icon_preview_endjob); + + WM_jobs_start(CTX_wm_manager(C), wm_job); +} + +void ED_preview_shader_job(const bContext *C, + void *owner, + ID *id, + ID *parent, + MTex *slot, + int sizex, + int sizey, + ePreviewRenderMethod method) +{ + Object *ob = CTX_data_active_object(C); + wmJob *wm_job; + ShaderPreview *sp; + Scene *scene = CTX_data_scene(C); + const ID_Type id_type = GS(id->name); + + BLI_assert(BKE_previewimg_id_supports_jobs(id)); + + /* Use workspace render only for buttons Window, + * since the other previews are related to the datablock. */ + + if (preview_method_is_render(method) && !check_engine_supports_preview(scene)) { + return; + } + + ED_preview_ensure_dbase(); + + wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + owner, + "Shader Preview", + WM_JOB_EXCL_RENDER, + WM_JOB_TYPE_RENDER_PREVIEW); + sp = MEM_cnew<ShaderPreview>("shader preview"); + + /* customdata for preview thread */ + sp->scene = scene; + sp->owner = owner; + sp->sizex = sizex; + sp->sizey = sizey; + sp->pr_method = method; + sp->id = id; + sp->id_copy = duplicate_ids(id, false); + sp->own_id_copy = true; + sp->parent = parent; + sp->slot = slot; + sp->bmain = CTX_data_main(C); + Material *ma = NULL; + + /* hardcoded preview .blend for Eevee + Cycles, this should be solved + * once with custom preview .blend path for external engines */ + + /* grease pencil use its own preview file */ + if (id_type == ID_MA) { + ma = (Material *)id; + } + + if ((ma == NULL) || (ma->gp_style == NULL)) { + sp->pr_main = G_pr_main; + } + else { + sp->pr_main = G_pr_main_grease_pencil; + } + + if (ob && ob->totcol) { + copy_v4_v4(sp->color, ob->color); + } + else { + ARRAY_SET_ITEMS(sp->color, 0.0f, 0.0f, 0.0f, 1.0f); + } + + /* setup job */ + WM_jobs_customdata_set(wm_job, sp, shader_preview_free); + WM_jobs_timer(wm_job, 0.1, NC_MATERIAL, NC_MATERIAL); + WM_jobs_callbacks(wm_job, common_preview_startjob, NULL, shader_preview_updatejob, NULL); + + WM_jobs_start(CTX_wm_manager(C), wm_job); +} + +void ED_preview_kill_jobs(wmWindowManager *wm, Main *UNUSED(bmain)) +{ + if (wm) { + /* This is called to stop all preview jobs before scene data changes, to + * avoid invalid memory access. */ + WM_jobs_kill(wm, NULL, common_preview_startjob); + WM_jobs_kill(wm, NULL, icon_preview_startjob_all_sizes); + } +} + +typedef struct PreviewRestartQueueEntry { + struct PreviewRestartQueueEntry *next, *prev; + + enum eIconSizes size; + ID *id; +} PreviewRestartQueueEntry; + +static ListBase /* #PreviewRestartQueueEntry */ G_restart_previews_queue; + +void ED_preview_restart_queue_free(void) +{ + BLI_freelistN(&G_restart_previews_queue); +} + +void ED_preview_restart_queue_add(ID *id, enum eIconSizes size) +{ + PreviewRestartQueueEntry *queue_entry = MEM_new<PreviewRestartQueueEntry>(__func__); + queue_entry->size = size; + queue_entry->id = id; + BLI_addtail(&G_restart_previews_queue, queue_entry); +} + +void ED_preview_restart_queue_work(const bContext *C) +{ + LISTBASE_FOREACH_MUTABLE (PreviewRestartQueueEntry *, queue_entry, &G_restart_previews_queue) { + PreviewImage *preview = BKE_previewimg_id_get(queue_entry->id); + if (!preview) { + continue; + } + if (preview->flag[queue_entry->size] & PRV_USER_EDITED) { + /* Don't touch custom previews. */ + continue; + } + + BKE_previewimg_clear_single(preview, queue_entry->size); + UI_icon_render_id(C, NULL, queue_entry->id, queue_entry->size, true); + + BLI_freelinkN(&G_restart_previews_queue, queue_entry); + } +} + +/** \} */ |