Part of https://projects.blender.org/blender/blender/pulls/141278 Blend files compatibility: If a World exists and "Use Nodes" is disabled, we add new nodes to the existing node tree (or create one if it doesn't) that emulates the behavior of a world without a node tree. This ensures backward and forward compatibility. Python API compatibility: - `world.use_nodes` was removed from Python API => **Breaking change** - `world.color` is still being used by Workbench, so it stays there, although it has no effect anymore when using Cycles or EEVEE. Python API changes: Creating a World using `bpy.data.worlds.new()` now creates a World with an empty (embedded) node tree. This was necessary to enable Python scripts to add nodes without having to create a node tree (which is currently not possible, because World node trees are embedded). Pull Request: https://projects.blender.org/blender/blender/pulls/142342
2302 lines
69 KiB
C++
2302 lines
69 KiB
C++
/* SPDX-FileCopyrightText: Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup edrend
|
|
*/
|
|
|
|
/* global includes */
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <list>
|
|
|
|
#ifndef WIN32
|
|
# include <unistd.h>
|
|
#else
|
|
# include <io.h>
|
|
#endif
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_math_matrix.h"
|
|
#include "BLI_math_rotation.h"
|
|
#include "BLI_path_utils.hh"
|
|
#include "BLI_rect.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_string_utf8.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "BLO_readfile.hh"
|
|
|
|
#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_armature.hh"
|
|
#include "BKE_brush.hh"
|
|
#include "BKE_colortools.hh"
|
|
#include "BKE_context.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_icons.h"
|
|
#include "BKE_idprop.hh"
|
|
#include "BKE_image.hh"
|
|
#include "BKE_layer.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_library.hh"
|
|
#include "BKE_light.h"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_material.hh"
|
|
#include "BKE_node.hh"
|
|
#include "BKE_object.hh"
|
|
#include "BKE_pose_backup.h"
|
|
#include "BKE_preview_image.hh"
|
|
#include "BKE_report.hh"
|
|
#include "BKE_scene.hh"
|
|
#include "BKE_screen.hh"
|
|
#include "BKE_texture.h"
|
|
#include "BKE_world.h"
|
|
|
|
#include "BLI_math_vector.h"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
#include "DEG_depsgraph_build.hh"
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "IMB_imbuf.hh"
|
|
#include "IMB_imbuf_types.hh"
|
|
#include "IMB_thumbs.hh"
|
|
|
|
#include "BIF_glutil.hh"
|
|
|
|
#include "RE_engine.h"
|
|
#include "RE_pipeline.h"
|
|
#include "RE_texture.h"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "ED_datafiles.h"
|
|
#include "ED_render.hh"
|
|
#include "ED_screen.hh"
|
|
#include "ED_view3d.hh"
|
|
#include "ED_view3d_offscreen.hh"
|
|
|
|
#include "UI_interface_icons.hh"
|
|
|
|
#include "ANIM_action.hh"
|
|
#include "ANIM_pose.hh"
|
|
|
|
#ifndef NDEBUG
|
|
/* Used for database init assert(). */
|
|
# include "BLI_threads.h"
|
|
#endif
|
|
|
|
static void icon_copy_rect(const ImBuf *ibuf, uint w, uint h, uint *rect);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Local Structs
|
|
* \{ */
|
|
|
|
struct ShaderPreview {
|
|
/* from wmJob */
|
|
void *owner;
|
|
bool *stop, *do_update;
|
|
|
|
Scene *scene;
|
|
ID *id, *id_copy;
|
|
ID *parent;
|
|
MTex *slot;
|
|
|
|
/* Data-blocks 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;
|
|
};
|
|
|
|
struct IconPreviewSize {
|
|
IconPreviewSize *next, *prev;
|
|
int sizex, sizey;
|
|
uint *rect;
|
|
};
|
|
|
|
struct IconPreview {
|
|
Main *bmain;
|
|
Depsgraph *depsgraph; /* May be nullptr (see #WM_OT_previews_ensure). */
|
|
Scene *scene;
|
|
void *owner;
|
|
/** May be nullptr! (see #ICON_TYPE_PREVIEW case in #ui_icon_ensure_deferred()). */
|
|
ID *id;
|
|
ID *id_copy;
|
|
ListBase sizes;
|
|
|
|
/* May be nullptr, 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. */
|
|
Object *active_object;
|
|
};
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Preview for Buttons
|
|
* \{ */
|
|
|
|
static Main *G_pr_main_grease_pencil = nullptr;
|
|
|
|
#ifndef WITH_HEADLESS
|
|
static Main *load_main_from_memory(const void *blend, int blend_size)
|
|
{
|
|
const int fileflags = G.fileflags;
|
|
Main *bmain = nullptr;
|
|
BlendFileData *bfd;
|
|
|
|
G.fileflags |= G_FILE_NO_UI;
|
|
bfd = BLO_read_from_memory(blend, blend_size, BLO_READ_SKIP_NONE, nullptr);
|
|
if (bfd) {
|
|
bmain = bfd->main;
|
|
MEM_delete(bfd);
|
|
}
|
|
G.fileflags = fileflags;
|
|
|
|
return bmain;
|
|
}
|
|
#endif
|
|
|
|
void ED_preview_ensure_dbase(const bool with_gpencil)
|
|
{
|
|
#ifndef WITH_HEADLESS
|
|
static bool base_initialized = false;
|
|
static bool base_initialized_gpencil = false;
|
|
BLI_assert(BLI_thread_is_main());
|
|
if (!base_initialized) {
|
|
G.pr_main = load_main_from_memory(datatoc_preview_blend, datatoc_preview_blend_size);
|
|
base_initialized = true;
|
|
}
|
|
if (!base_initialized_gpencil && with_gpencil) {
|
|
G_pr_main_grease_pencil = load_main_from_memory(datatoc_preview_grease_pencil_blend,
|
|
datatoc_preview_grease_pencil_blend_size);
|
|
base_initialized_gpencil = true;
|
|
}
|
|
#else
|
|
UNUSED_VARS(with_gpencil);
|
|
#endif
|
|
}
|
|
|
|
bool ED_check_engine_supports_preview(const 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()
|
|
{
|
|
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 == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
return static_cast<Scene *>(pr_main->scenes.first);
|
|
}
|
|
|
|
const char *ED_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_visibility(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 = ED_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 *mesh,
|
|
const Scene *scene,
|
|
const ePreviewRenderMethod pr_method)
|
|
{
|
|
if (mesh->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) {
|
|
mesh->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. */
|
|
BKE_view_layer_synced_ensure(scene, view_layer);
|
|
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ED_preview_set_visibility(Main *pr_main,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
const ePreviewType pr_type,
|
|
const ePreviewRenderMethod pr_method)
|
|
{
|
|
switch_preview_collection_visibility(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 == nullptr) {
|
|
return nullptr;
|
|
}
|
|
if (sp->worldcopy != nullptr) {
|
|
return sp->worldcopy;
|
|
}
|
|
|
|
ID *id_copy = BKE_id_copy_ex(nullptr,
|
|
&world->id,
|
|
nullptr,
|
|
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;
|
|
}
|
|
|
|
World *ED_preview_prepare_world_simple(Main *pr_main)
|
|
{
|
|
using namespace blender::bke;
|
|
|
|
World *world = BKE_world_add(pr_main, "SimpleWorld");
|
|
bNodeTree *ntree = world->nodetree;
|
|
ntree = blender::bke::node_tree_add_tree_embedded(
|
|
nullptr, &world->id, "World Nodetree", "ShaderNodeTree");
|
|
|
|
bNode *background = node_add_node(nullptr, *ntree, "ShaderNodeBackground");
|
|
bNode *output = node_add_node(nullptr, *ntree, "ShaderNodeOutputWorld");
|
|
node_add_link(*world->nodetree,
|
|
*background,
|
|
*node_find_socket(*background, SOCK_OUT, "Background"),
|
|
*output,
|
|
*node_find_socket(*output, SOCK_IN, "Surface"));
|
|
node_set_active(*ntree, *output);
|
|
|
|
world->nodetree = ntree;
|
|
return world;
|
|
}
|
|
|
|
void ED_preview_world_simple_set_rgb(World *world, const float color[4])
|
|
{
|
|
BLI_assert(world != nullptr);
|
|
|
|
bNode *background = blender::bke::node_find_node_by_name(*world->nodetree, "Background");
|
|
BLI_assert(background != nullptr);
|
|
|
|
auto color_socket = static_cast<bNodeSocketValueRGBA *>(
|
|
blender::bke::node_find_socket(*background, SOCK_IN, "Color")->default_value);
|
|
copy_v4_v4(color_socket->value, color);
|
|
}
|
|
|
|
static ID *duplicate_ids(ID *id, const bool allow_failure)
|
|
{
|
|
if (id == nullptr) {
|
|
/* Non-ID preview render. */
|
|
return nullptr;
|
|
}
|
|
|
|
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(nullptr,
|
|
id,
|
|
nullptr,
|
|
LIB_ID_CREATE_LOCAL | LIB_ID_COPY_LOCALIZE |
|
|
LIB_ID_COPY_NO_ANIMDATA);
|
|
return id_copy;
|
|
}
|
|
case ID_GR: {
|
|
/* Doesn't really duplicate the collection. Just creates a collection instance empty. */
|
|
BLI_assert(BKE_previewimg_id_supports_jobs(id));
|
|
Object *instance_empty = BKE_object_add_only_object(nullptr, OB_EMPTY, nullptr);
|
|
instance_empty->instance_collection = (Collection *)id;
|
|
instance_empty->transflag |= OB_DUPLICOLLECTION;
|
|
return &instance_empty->id;
|
|
}
|
|
/* These support threading, but don't need duplicating. */
|
|
case ID_IM:
|
|
BLI_assert(BKE_previewimg_id_supports_jobs(id));
|
|
return nullptr;
|
|
default:
|
|
if (!allow_failure) {
|
|
BLI_assert_msg(0, "ID type preview not supported.");
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
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 ray-tracing 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 = nullptr;
|
|
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 == nullptr) {
|
|
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;
|
|
}
|
|
|
|
World *ED_preview_prepare_world(Main *pr_main,
|
|
const Scene *scene,
|
|
const World *world,
|
|
const ID_Type id_type,
|
|
const ePreviewRenderMethod pr_method)
|
|
{
|
|
World *result = preview_get_world(pr_main, scene, 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 nullptr 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 render-pass. */
|
|
view_layer->passflag = SCE_PASS_COMBINED;
|
|
view_layer->eevee.render_passes = 0;
|
|
|
|
/* This flag tells render to not execute depsgraph or F-Curves etc. */
|
|
sce->r.scemode |= R_BUTS_PREVIEW;
|
|
STRNCPY_UTF8(sce->r.engine, scene->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 = ED_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. */
|
|
STRNCPY_UTF8(sce->r.engine, RE_engine_id_BLENDER_EEVEE);
|
|
}
|
|
|
|
if (id_type == ID_MA) {
|
|
Material *mat = nullptr, *origmat = (Material *)id;
|
|
|
|
if (origmat) {
|
|
/* work on a copy */
|
|
BLI_assert(sp->id_copy != nullptr);
|
|
mat = sp->matcopy = (Material *)sp->id_copy;
|
|
sp->id_copy = nullptr;
|
|
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 = ED_preview_prepare_world_simple(sp->bmain);
|
|
|
|
/* Use brighter world color for grease pencil. */
|
|
if (sp->pr_main == G_pr_main_grease_pencil) {
|
|
const float white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
ED_preview_world_simple_set_rgb(sce->world, white);
|
|
}
|
|
else {
|
|
const float dark[4] = {0.05f, 0.05f, 0.05f, 0.05f};
|
|
ED_preview_world_simple_set_rgb(sce->world, dark);
|
|
}
|
|
}
|
|
|
|
const ePreviewType preview_type = static_cast<ePreviewType>(mat->pr_type);
|
|
ED_preview_set_visibility(pr_main, sce, view_layer, preview_type, sp->pr_method);
|
|
}
|
|
else {
|
|
sce->display.render_aa = SCE_DISPLAY_AA_OFF;
|
|
}
|
|
BKE_view_layer_synced_ensure(sce, view_layer);
|
|
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
|
|
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_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (id_type == ID_TE) {
|
|
Tex *tex = nullptr, *origtex = (Tex *)id;
|
|
|
|
if (origtex) {
|
|
BLI_assert(sp->id_copy != nullptr);
|
|
tex = sp->texcopy = (Tex *)sp->id_copy;
|
|
sp->id_copy = nullptr;
|
|
BLI_addtail(&pr_main->textures, tex);
|
|
}
|
|
}
|
|
else if (id_type == ID_LA) {
|
|
Light *la = nullptr, *origla = (Light *)id;
|
|
|
|
/* work on a copy */
|
|
if (origla) {
|
|
BLI_assert(sp->id_copy != nullptr);
|
|
la = sp->lampcopy = (Light *)sp->id_copy;
|
|
sp->id_copy = nullptr;
|
|
BLI_addtail(&pr_main->lights, la);
|
|
}
|
|
|
|
ED_preview_set_visibility(pr_main, sce, view_layer, MA_LAMP, sp->pr_method);
|
|
|
|
if (sce->world) {
|
|
/* Only use lighting from the light. */
|
|
sce->world = ED_preview_prepare_world_simple(pr_main);
|
|
const float black[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
ED_preview_world_simple_set_rgb(sce->world, black);
|
|
}
|
|
|
|
BKE_view_layer_synced_ensure(sce, view_layer);
|
|
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
|
|
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 = nullptr, *origwrld = (World *)id;
|
|
|
|
if (origwrld) {
|
|
BLI_assert(sp->id_copy != nullptr);
|
|
wrld = sp->worldcopy = (World *)sp->id_copy;
|
|
sp->id_copy = nullptr;
|
|
BLI_addtail(&pr_main->worlds, wrld);
|
|
}
|
|
|
|
ED_preview_set_visibility(pr_main, sce, view_layer, MA_SKY, sp->pr_method);
|
|
sce->world = wrld;
|
|
}
|
|
|
|
return sce;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/* new UI convention: draw is in pixel space already. */
|
|
/* uses ButType::Roundbox button in block to get the rect */
|
|
static bool ed_preview_draw_rect(
|
|
Scene *scene, ScrArea *area, int split, int first, const 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) {
|
|
SNPRINTF_UTF8(name, "Preview %p", (void *)area);
|
|
}
|
|
else {
|
|
SNPRINTF_UTF8(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 == nullptr) {
|
|
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 #45496 */
|
|
rv = nullptr;
|
|
}
|
|
|
|
if (rv && rv->ibuf) {
|
|
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) {
|
|
float fx = rect->xmin + offx;
|
|
float fy = rect->ymin;
|
|
|
|
ED_draw_imbuf(
|
|
rv->ibuf, fx, fy, false, &scene->view_settings, &scene->display_settings, 1.0f, 1.0f);
|
|
|
|
ok = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
RE_ReleaseResultImageViews(re, &rres);
|
|
|
|
return ok;
|
|
}
|
|
|
|
void ED_preview_draw(
|
|
const bContext *C, void *idp, void *parentp, void *slotp, uiPreview *ui_preview, rcti *rect)
|
|
{
|
|
if (idp) {
|
|
Scene *scene = CTX_data_scene(C);
|
|
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_from_type(wm, area, WM_JOB_TYPE_RENDER_PREVIEW));
|
|
rcti newrect;
|
|
bool 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(scene, area, 1, 1, rect, &newrect);
|
|
ok &= ed_preview_draw_rect(scene, area, 1, 0, rect, &newrect);
|
|
}
|
|
else {
|
|
ok = ed_preview_draw_rect(scene, 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 != nullptr && sbuts->preview) || (ui_preview->tag & UI_PREVIEW_TAG_DIRTY) ||
|
|
(!ok && !WM_jobs_test(wm, area, WM_JOB_TYPE_RENDER_PREVIEW)) ||
|
|
(sp && (abs(sp->sizex - newx) >= 2 || abs(sp->sizey - newy) > 2)))
|
|
{
|
|
if (sbuts != nullptr) {
|
|
sbuts->preview = 0;
|
|
}
|
|
ED_preview_shader_job(C, area, id, parent, slot, newx, newy, PR_BUTS_RENDER);
|
|
ui_preview->tag &= ~UI_PREVIEW_TAG_DIRTY;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ED_previews_tag_dirty_by_id(const Main &bmain, const ID &id)
|
|
{
|
|
LISTBASE_FOREACH (const bScreen *, screen, &bmain.screens) {
|
|
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
|
|
LISTBASE_FOREACH (const ARegion *, region, &area->regionbase) {
|
|
LISTBASE_FOREACH (uiPreview *, preview, ®ion->ui_previews) {
|
|
if (preview->id_session_uid == id.session_uid) {
|
|
preview->tag |= UI_PREVIEW_TAG_DIRTY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \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 its 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,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
Object *preview_object)
|
|
{
|
|
Object *camera = BKE_object_add(preview_main, scene, view_layer, OB_CAMERA, "Preview Camera");
|
|
|
|
float rotmat[3][3];
|
|
float dummy_scale[3];
|
|
mat4_to_loc_rot_size(camera->loc, rotmat, dummy_scale, preview_object->object_to_world().ptr());
|
|
|
|
/* 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 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. */
|
|
scene->r.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 != nullptr);
|
|
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, scene, 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;
|
|
|
|
BKE_view_layer_synced_ensure(scene, view_layer);
|
|
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_with_set_clipping(
|
|
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();
|
|
char err_out[256] = "unknown";
|
|
|
|
BLI_assert(preview->id_copy && (preview->id_copy != preview->id));
|
|
|
|
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 = nullptr;
|
|
|
|
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(depsgraph, scene->camera),
|
|
preview_sized->sizex,
|
|
preview_sized->sizey,
|
|
IB_byte_data,
|
|
V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS,
|
|
R_ALPHAPREMUL,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
err_out);
|
|
/* TODO: color-management? */
|
|
|
|
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 Collection Preview
|
|
*
|
|
* For the most part this reuses the object preview code by creating an instance collection empty
|
|
* object and rendering that.
|
|
*
|
|
* \{ */
|
|
|
|
/**
|
|
* Check if the collection contains any geometry that can be rendered. Otherwise there's nothing to
|
|
* display in the preview, so don't generate one.
|
|
* Objects and sub-collections hidden in the render will be skipped.
|
|
*/
|
|
static bool collection_preview_contains_geometry_recursive(const Collection *collection)
|
|
{
|
|
LISTBASE_FOREACH (CollectionObject *, col_ob, &collection->gobject) {
|
|
if (col_ob->ob->visibility_flag & OB_HIDE_RENDER) {
|
|
continue;
|
|
}
|
|
if (OB_TYPE_IS_GEOMETRY(col_ob->ob->type)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
LISTBASE_FOREACH (CollectionChild *, child_col, &collection->children) {
|
|
if (child_col->collection->flag & COLLECTION_HIDE_RENDER) {
|
|
continue;
|
|
}
|
|
if (collection_preview_contains_geometry_recursive(child_col->collection)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Action Preview
|
|
* \{ */
|
|
|
|
static PoseBackup *action_preview_render_prepare(IconPreview *preview)
|
|
{
|
|
Object *object = preview->active_object;
|
|
if (object == nullptr) {
|
|
WM_global_report(RPT_WARNING, "No active object, unable to apply the Action before rendering");
|
|
return nullptr;
|
|
}
|
|
if (object->pose == nullptr) {
|
|
WM_global_reportf(RPT_WARNING,
|
|
"Object %s has no pose, unable to apply the Action before rendering",
|
|
object->id.name + 2);
|
|
return nullptr;
|
|
}
|
|
|
|
/* Create a backup of the current pose. */
|
|
blender::animrig::Action &pose_action = reinterpret_cast<bAction *>(preview->id)->wrap();
|
|
|
|
if (pose_action.slot_array_num == 0) {
|
|
WM_global_report(RPT_WARNING, "Action has no data, cannot render preview");
|
|
return nullptr;
|
|
}
|
|
|
|
blender::animrig::Slot &slot = blender::animrig::get_best_pose_slot_for_id(object->id,
|
|
pose_action);
|
|
PoseBackup *pose_backup = BKE_pose_backup_create_all_bones({object}, &pose_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};
|
|
blender::animrig::pose_apply_action_all_bones(
|
|
object, &pose_action, slot.handle, &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, PoseBackup *pose_backup)
|
|
{
|
|
if (pose_backup == nullptr) {
|
|
return;
|
|
}
|
|
BKE_pose_backup_restore(pose_backup);
|
|
BKE_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 != nullptr);
|
|
BLI_assert(preview->scene == DEG_get_input_scene(depsgraph));
|
|
|
|
/* Apply the pose before getting the evaluated scene, so that the new pose is evaluated. */
|
|
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 == nullptr) {
|
|
printf("Scene has no camera, unable to render preview of %s without it.\n",
|
|
preview->id->name + 2);
|
|
action_preview_render_cleanup(preview, pose_backup);
|
|
return;
|
|
}
|
|
|
|
/* This renders with the Workbench engine settings stored on the Scene. */
|
|
ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple(depsgraph,
|
|
scene_eval,
|
|
nullptr,
|
|
OB_SOLID,
|
|
camera_eval,
|
|
preview_sized->sizex,
|
|
preview_sized->sizey,
|
|
IB_byte_data,
|
|
V3D_OFSDRAW_NONE,
|
|
R_ADDSKY,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
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 Scene Preview
|
|
* \{ */
|
|
|
|
static bool scene_preview_is_supported(const Scene *scene)
|
|
{
|
|
return scene->camera != nullptr;
|
|
}
|
|
|
|
static void scene_preview_render(IconPreview *preview,
|
|
IconPreviewSize *preview_sized,
|
|
ReportList *reports)
|
|
{
|
|
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 != nullptr);
|
|
BLI_assert(preview->id != nullptr);
|
|
|
|
Scene *scene_eval = DEG_get_evaluated_scene(depsgraph);
|
|
Object *camera_eval = scene_eval->camera;
|
|
if (camera_eval == nullptr) {
|
|
BKE_reportf(reports,
|
|
RPT_ERROR,
|
|
"Scene has no camera, unable to render preview of %s without it.",
|
|
BKE_id_name(*preview->id));
|
|
return;
|
|
}
|
|
|
|
char err_out[256] = "";
|
|
/* This renders with the Workbench engine settings stored on the Scene. */
|
|
ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple(depsgraph,
|
|
scene_eval,
|
|
nullptr,
|
|
OB_SOLID,
|
|
camera_eval,
|
|
preview_sized->sizex,
|
|
preview_sized->sizey,
|
|
IB_byte_data,
|
|
V3D_OFSDRAW_NONE,
|
|
R_ADDSKY,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
err_out);
|
|
|
|
if (err_out[0] != '\0') {
|
|
BKE_reportf(reports,
|
|
RPT_ERROR,
|
|
"Error rendering Scene %s preview: %s.",
|
|
BKE_id_name(*preview->id),
|
|
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 * /*rr*/, rcti * /*rect*/)
|
|
{
|
|
ShaderPreview *sp = static_cast<ShaderPreview *>(spv);
|
|
|
|
*(sp->do_update) = true;
|
|
}
|
|
|
|
/* called by renderer, checks job value */
|
|
static bool shader_preview_break(void *spv)
|
|
{
|
|
ShaderPreview *sp = static_cast<ShaderPreview *>(spv);
|
|
|
|
return *(sp->stop);
|
|
}
|
|
|
|
static void shader_preview_updatejob(void * /*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, nullptr, &sce->r, &sce->view_layers, nullptr, width, height, nullptr);
|
|
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;
|
|
ImBuf *rv_ibuf = RE_RenderViewEnsureImBuf(rr, rv);
|
|
IMB_assign_float_buffer(rv_ibuf,
|
|
MEM_calloc_arrayN<float>(4 * width * height, "texture render result"),
|
|
IB_TAKE_OWNERSHIP);
|
|
RE_ReleaseResult(re);
|
|
|
|
/* Get texture image pool (if any) */
|
|
ImagePool *img_pool = BKE_image_pool_new();
|
|
BKE_texture_fetch_images_for_pool(tex, img_pool);
|
|
|
|
/* Fill in image buffer. */
|
|
float *rect_float = rv_ibuf->float_buffer.data;
|
|
float tex_coord[3] = {0.0f, 0.0f, 0.0f};
|
|
|
|
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(tex, tex_coord, &texres, img_pool, true);
|
|
copy_v4_fl4(rect_float,
|
|
texres.trgba[0],
|
|
texres.trgba[1],
|
|
texres.trgba[2],
|
|
texres.talpha ? texres.trgba[3] : 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 == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (!split || first) {
|
|
SNPRINTF_UTF8(name, "Preview %p", sp->owner);
|
|
}
|
|
else {
|
|
SNPRINTF_UTF8(name, "SecondPreview %p", sp->owner);
|
|
}
|
|
re = RE_GetRender(name);
|
|
|
|
/* full refreshed render from first tile */
|
|
if (re == nullptr) {
|
|
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, nullptr, 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, bool *stop, bool *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)
|
|
{
|
|
if (id->properties) {
|
|
IDP_FreePropertyContent_ex(id->properties, false);
|
|
MEM_freeN(id->properties);
|
|
id->properties = nullptr;
|
|
}
|
|
if (id->system_properties) {
|
|
IDP_FreePropertyContent_ex(id->system_properties, false);
|
|
MEM_freeN(id->system_properties);
|
|
id->system_properties = nullptr;
|
|
}
|
|
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 = nullptr;
|
|
ID *sub_id_copy = nullptr;
|
|
|
|
if (sp->matcopy) {
|
|
main_id_copy = (ID *)sp->matcopy;
|
|
BLI_remlink(&pr_main->materials, sp->matcopy);
|
|
}
|
|
if (sp->texcopy) {
|
|
BLI_assert(main_id_copy == nullptr);
|
|
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 == nullptr);
|
|
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 void icon_copy_rect(const ImBuf *ibuf, uint w, uint h, uint *rect)
|
|
{
|
|
if (ibuf == nullptr ||
|
|
(ibuf->byte_buffer.data == nullptr && ibuf->float_buffer.data == nullptr) || rect == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float scaledx, scaledy;
|
|
if (ibuf->x > ibuf->y) {
|
|
scaledx = float(w);
|
|
scaledy = (float(ibuf->y) / float(ibuf->x)) * float(w);
|
|
}
|
|
else {
|
|
scaledx = (float(ibuf->x) / float(ibuf->y)) * float(h);
|
|
scaledy = float(h);
|
|
}
|
|
|
|
/* Scaling down must never assign zero width/height, see: #89868. */
|
|
int ex = std::max<int>(1, scaledx);
|
|
int ey = std::max<int>(1, scaledy);
|
|
|
|
int dx = (w - ex) / 2;
|
|
int dy = (h - ey) / 2;
|
|
|
|
ImBuf *ima = IMB_scale_into_new(ibuf, ex, ey, IMBScaleFilter::Nearest, false);
|
|
if (ima == nullptr) {
|
|
return;
|
|
}
|
|
|
|
/* if needed, convert to 32 bits */
|
|
if (ima->byte_buffer.data == nullptr) {
|
|
IMB_byte_from_float(ima);
|
|
}
|
|
|
|
const uint *srect = reinterpret_cast<const uint *>(ima->byte_buffer.data);
|
|
uint *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, bool *stop, bool *do_update)
|
|
{
|
|
ShaderPreview *sp = static_cast<ShaderPreview *>(customdata);
|
|
|
|
if (sp->pr_method == PR_ICON_DEFERRED) {
|
|
BLI_assert_unreachable();
|
|
return;
|
|
}
|
|
|
|
ID *id = sp->id;
|
|
short idtype = GS(id->name);
|
|
|
|
BLI_assert(id != nullptr);
|
|
|
|
if (idtype == ID_IM) {
|
|
Image *ima = (Image *)id;
|
|
ImBuf *ibuf = nullptr;
|
|
ImageUser iuser;
|
|
BKE_imageuser_default(&iuser);
|
|
|
|
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->byte_buffer.data == nullptr && ibuf->float_buffer.data == 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 {
|
|
/* 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, wmJobWorkerStatus *worker_status)
|
|
{
|
|
ShaderPreview *sp = static_cast<ShaderPreview *>(customdata);
|
|
|
|
if (ELEM(sp->pr_method, PR_ICON_RENDER, PR_ICON_DEFERRED)) {
|
|
icon_preview_startjob(customdata, &worker_status->stop, &worker_status->do_update);
|
|
}
|
|
else {
|
|
shader_preview_startjob(customdata, &worker_status->stop, &worker_status->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,
|
|
wmJobWorkerStatus *worker_status)
|
|
{
|
|
ShaderPreview *sp = MEM_callocN<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 preview custom-data. */
|
|
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 = nullptr;
|
|
|
|
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 == nullptr) || (ma->gp_style == nullptr)) {
|
|
sp->pr_main = G.pr_main;
|
|
}
|
|
else {
|
|
sp->pr_main = G_pr_main_grease_pencil;
|
|
}
|
|
}
|
|
|
|
common_preview_startjob(sp, worker_status);
|
|
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, wmJobWorkerStatus *worker_status)
|
|
{
|
|
IconPreview *ip = (IconPreview *)customdata;
|
|
|
|
LISTBASE_FOREACH (IconPreviewSize *, cur_size, &ip->sizes) {
|
|
PreviewImage *prv = static_cast<PreviewImage *>(ip->owner);
|
|
/* Is this a render job or a deferred loading job? */
|
|
const ePreviewRenderMethod pr_method = (prv->runtime->deferred_loading_data) ?
|
|
PR_ICON_DEFERRED :
|
|
PR_ICON_RENDER;
|
|
|
|
if (worker_status->stop) {
|
|
break;
|
|
}
|
|
|
|
if (prv->runtime->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. Group, Object and Action render functions use Solid mode, though,
|
|
* so they can skip this test. Same is true for Images and Brushes, they can also skip this
|
|
* test since their preview is just pulled from ImBuf which is not dependent on the render
|
|
* engine. */
|
|
/* 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 != nullptr) &&
|
|
ELEM(GS(ip->id->name), ID_OB, ID_AC, ID_IM, ID_GR, ID_SCE);
|
|
if (!use_solid_render_mode && preview_method_is_render(pr_method) &&
|
|
!ED_check_engine_supports_preview(ip->scene))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* Workaround: Skip preview renders for linked IDs. Preview rendering can be slow and even
|
|
* freeze the UI (e.g. on Eevee shader compilation). And since the result will never be stored
|
|
* in a file, it's done every time the file is reloaded, so this becomes a frequent annoyance.
|
|
*/
|
|
if (!use_solid_render_mode && ip->id && !ID_IS_EDITABLE(ip->id)) {
|
|
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 != nullptr) {
|
|
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;
|
|
case ID_GR:
|
|
BLI_assert(collection_preview_contains_geometry_recursive((Collection *)ip->id));
|
|
/* A collection instance empty was created, so this can just reuse the object preview
|
|
* rendering. */
|
|
object_preview_render(ip, cur_size);
|
|
continue;
|
|
case ID_AC:
|
|
action_preview_render(ip, cur_size);
|
|
continue;
|
|
case ID_SCE:
|
|
scene_preview_render(ip, cur_size, worker_status->reports);
|
|
continue;
|
|
default:
|
|
/* Fall through to the same code as the `ip->id == nullptr` case. */
|
|
break;
|
|
}
|
|
}
|
|
other_id_types_preview_render(ip, cur_size, pr_method, worker_status);
|
|
}
|
|
}
|
|
|
|
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_callocN<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 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] = nullptr;
|
|
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->runtime->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->runtime->tag & PRV_TAG_DEFFERED_DELETE) {
|
|
BLI_assert(prv_img->runtime->deferred_loading_data);
|
|
BKE_previewimg_deferred_release(prv_img);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
/** Set to true by if the request was fully handled. */
|
|
std::atomic<bool> done = false;
|
|
/** Set to true if the request was handled but didn't result in a valid preview.
|
|
* #PRV_TAG_DEFFERED_INVALID will be set in response. */
|
|
std::atomic<bool> failure = false;
|
|
|
|
RequestedPreview(PreviewImage *preview, eIconSizes icon_size)
|
|
: preview(preview), icon_size(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<RequestedPreview> requested_previews_;
|
|
|
|
public:
|
|
PreviewLoadJob();
|
|
~PreviewLoadJob();
|
|
|
|
static PreviewLoadJob &ensure_job(wmWindowManager *wm, wmWindow *win);
|
|
static void load_jobless(PreviewImage *preview, eIconSizes icon_size);
|
|
|
|
void push_load_request(PreviewImage *preview, eIconSizes icon_size);
|
|
|
|
private:
|
|
static void run_fn(void *customdata, wmJobWorkerStatus *worker_status);
|
|
static void update_fn(void *customdata);
|
|
static void end_fn(void *customdata);
|
|
static void free_fn(void *customdata);
|
|
|
|
/** Mark a single requested preview as being done, remove the request. */
|
|
static void finish_request(RequestedPreview &request);
|
|
};
|
|
|
|
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", eWM_JobFlag(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 *static_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);
|
|
|
|
wmJobWorkerStatus worker_status = {};
|
|
run_fn(&job_data, &worker_status);
|
|
update_fn(&job_data);
|
|
end_fn(&job_data);
|
|
}
|
|
|
|
void PreviewLoadJob::push_load_request(PreviewImage *preview, const eIconSizes icon_size)
|
|
{
|
|
BLI_assert(preview->runtime->deferred_loading_data);
|
|
|
|
preview->flag[icon_size] |= PRV_RENDERING;
|
|
/* Warn main thread code that this preview is being rendered and cannot be freed. */
|
|
preview->runtime->tag |= PRV_TAG_DEFFERED_RENDERING;
|
|
|
|
requested_previews_.emplace_back(preview, icon_size);
|
|
BLI_thread_queue_push(
|
|
todo_queue_, &requested_previews_.back(), BLI_THREAD_QUEUE_WORK_PRIORITY_NORMAL);
|
|
}
|
|
|
|
void PreviewLoadJob::run_fn(void *customdata, wmJobWorkerStatus *worker_status)
|
|
{
|
|
PreviewLoadJob *job_data = static_cast<PreviewLoadJob *>(customdata);
|
|
|
|
IMB_thumb_locks_acquire();
|
|
|
|
while (RequestedPreview *request = static_cast<RequestedPreview *>(
|
|
BLI_thread_queue_pop_timeout(job_data->todo_queue_, 100)))
|
|
{
|
|
if (worker_status->stop) {
|
|
break;
|
|
}
|
|
|
|
PreviewImage *preview = request->preview;
|
|
|
|
const std::optional<int> source = BKE_previewimg_deferred_thumb_source_get(preview);
|
|
const char *filepath = BKE_previewimg_deferred_filepath_get(preview);
|
|
|
|
if (!source || !filepath) {
|
|
continue;
|
|
}
|
|
|
|
// printf("loading deferred %dx%d preview for %s\n", request->sizex, request->sizey, filepath);
|
|
|
|
IMB_thumb_path_lock(filepath);
|
|
ImBuf *thumb = IMB_thumb_manage(filepath, THB_LARGE, ThumbSource(*source));
|
|
IMB_thumb_path_unlock(filepath);
|
|
|
|
if (thumb) {
|
|
/* PreviewImage assumes premultiplied alpha. */
|
|
IMB_premultiply_alpha(thumb);
|
|
|
|
if (ED_preview_use_image_size(preview, request->icon_size)) {
|
|
preview->w[request->icon_size] = thumb->x;
|
|
preview->h[request->icon_size] = thumb->y;
|
|
BLI_assert(preview->rect[request->icon_size] == nullptr);
|
|
preview->rect[request->icon_size] = (uint *)MEM_dupallocN(thumb->byte_buffer.data);
|
|
}
|
|
else {
|
|
icon_copy_rect(thumb,
|
|
preview->w[request->icon_size],
|
|
preview->h[request->icon_size],
|
|
preview->rect[request->icon_size]);
|
|
}
|
|
IMB_freeImBuf(thumb);
|
|
}
|
|
else {
|
|
request->failure = true;
|
|
}
|
|
|
|
request->done = true;
|
|
worker_status->do_update = true;
|
|
}
|
|
|
|
IMB_thumb_locks_release();
|
|
}
|
|
|
|
/* Only execute on the main thread! */
|
|
void PreviewLoadJob::finish_request(RequestedPreview &request)
|
|
{
|
|
PreviewImage *preview = request.preview;
|
|
|
|
preview->runtime->tag &= ~PRV_TAG_DEFFERED_RENDERING;
|
|
if (request.failure) {
|
|
preview->runtime->tag |= PRV_TAG_DEFFERED_INVALID;
|
|
}
|
|
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->runtime->tag & PRV_TAG_DEFFERED_DELETE) {
|
|
BLI_assert(preview->runtime->deferred_loading_data);
|
|
BKE_previewimg_deferred_release(preview);
|
|
}
|
|
}
|
|
|
|
void PreviewLoadJob::update_fn(void *customdata)
|
|
{
|
|
PreviewLoadJob *job_data = static_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.done) {
|
|
++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 = static_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(static_cast<PreviewLoadJob *>(customdata));
|
|
}
|
|
|
|
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_use_image_size(const PreviewImage *preview, eIconSizes size)
|
|
{
|
|
return size == ICON_SIZE_PREVIEW && preview->runtime->deferred_loading_data;
|
|
}
|
|
|
|
bool ED_preview_id_is_supported(const ID *id, const char **r_disabled_hint)
|
|
{
|
|
if (id == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
/* Get both the result and the "potential" disabled hint. After that we can decide if the
|
|
* disabled hint needs to be returned to the caller. */
|
|
const auto [result, disabled_hint] = [id]() -> std::pair<bool, const char *> {
|
|
switch (GS(id->name)) {
|
|
case ID_NT:
|
|
return {false, RPT_("Node groups do not support automatic previews")};
|
|
case ID_OB:
|
|
return {object_preview_is_type_supported((const Object *)id),
|
|
RPT_("Object type does not support automatic previews")};
|
|
case ID_GR:
|
|
return {
|
|
collection_preview_contains_geometry_recursive((const Collection *)id),
|
|
RPT_("Collection does not contain object types that can be rendered for the automatic "
|
|
"preview")};
|
|
case ID_SCE:
|
|
return {scene_preview_is_supported((const Scene *)id),
|
|
RPT_("Scenes without a camera do not support previews")};
|
|
default:
|
|
return {BKE_previewimg_id_get_p(id) != nullptr,
|
|
RPT_("Data-block type does not support automatic previews")};
|
|
}
|
|
}();
|
|
|
|
if (result == false && disabled_hint && r_disabled_hint) {
|
|
*r_disabled_hint = disabled_hint;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void ED_preview_icon_render(
|
|
const bContext *C, Scene *scene, PreviewImage *prv_img, ID *id, eIconSizes icon_size)
|
|
{
|
|
/* Deferred loading of previews from the file system. */
|
|
if (prv_img->runtime->deferred_loading_data) {
|
|
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};
|
|
|
|
ED_preview_ensure_dbase(true);
|
|
|
|
ip.bmain = CTX_data_main(C);
|
|
if (GS(id->name) == ID_SCE) {
|
|
Scene *icon_scene = reinterpret_cast<Scene *>(id);
|
|
ip.scene = icon_scene;
|
|
ip.depsgraph = BKE_scene_ensure_depsgraph(
|
|
ip.bmain, ip.scene, BKE_view_layer_default_render(ip.scene));
|
|
ip.active_object = nullptr;
|
|
}
|
|
else {
|
|
ip.scene = scene;
|
|
ip.depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
/* 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);
|
|
}
|
|
ip.owner = BKE_previewimg_id_ensure(id);
|
|
ip.id = id;
|
|
|
|
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]);
|
|
|
|
wmJobWorkerStatus worker_status = {};
|
|
icon_preview_startjob_all_sizes(&ip, &worker_status);
|
|
|
|
icon_preview_endjob(&ip);
|
|
|
|
BLI_freelistN(&ip.sizes);
|
|
if (ip.id_copy != nullptr) {
|
|
preview_id_copy_free(ip.id_copy);
|
|
}
|
|
}
|
|
|
|
void ED_preview_icon_job(
|
|
const bContext *C, PreviewImage *prv_img, ID *id, eIconSizes icon_size, const bool delay)
|
|
{
|
|
/* Deferred loading of previews from the file system. */
|
|
if (prv_img->runtime->deferred_loading_data) {
|
|
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(true);
|
|
|
|
/* suspended start means it starts after 1 timer step, see WM_jobs_timer below */
|
|
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_callocN<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);
|
|
if (GS(id->name) == ID_SCE) {
|
|
Scene *icon_scene = reinterpret_cast<Scene *>(id);
|
|
ip->scene = icon_scene;
|
|
ip->depsgraph = BKE_scene_ensure_depsgraph(
|
|
ip->bmain, ip->scene, BKE_view_layer_default_render(ip->scene));
|
|
ip->active_object = nullptr;
|
|
}
|
|
else {
|
|
ip->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
ip->scene = DEG_get_input_scene(ip->depsgraph);
|
|
ip->id_copy = duplicate_ids(id, false);
|
|
ip->active_object = CTX_data_active_object(C);
|
|
}
|
|
ip->owner = prv_img;
|
|
ip->id = id;
|
|
|
|
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]);
|
|
|
|
/* 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, nullptr, nullptr, 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) && !ED_check_engine_supports_preview(scene)) {
|
|
return;
|
|
}
|
|
|
|
ED_preview_ensure_dbase(true);
|
|
|
|
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_callocN<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 = nullptr;
|
|
|
|
/* 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 == nullptr) || (ma->gp_style == nullptr)) {
|
|
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, nullptr, shader_preview_updatejob, nullptr);
|
|
|
|
WM_jobs_start(CTX_wm_manager(C), wm_job);
|
|
}
|
|
|
|
void ED_preview_kill_jobs(wmWindowManager *wm, Main * /*bmain*/)
|
|
{
|
|
if (wm) {
|
|
/* This is called to stop all preview jobs before scene data changes, to
|
|
* avoid invalid memory access. */
|
|
WM_jobs_kill_type(wm, nullptr, WM_JOB_TYPE_RENDER_PREVIEW);
|
|
}
|
|
}
|
|
|
|
void ED_preview_kill_jobs_for_id(wmWindowManager *wm, const ID *id)
|
|
{
|
|
const PreviewImage *preview = BKE_previewimg_id_get(id);
|
|
if (wm && preview) {
|
|
WM_jobs_kill_type(wm, preview, WM_JOB_TYPE_RENDER_PREVIEW);
|
|
}
|
|
}
|
|
|
|
struct PreviewRestartQueueEntry {
|
|
PreviewRestartQueueEntry *next, *prev;
|
|
|
|
enum eIconSizes size;
|
|
ID *id;
|
|
};
|
|
|
|
static ListBase /* #PreviewRestartQueueEntry */ G_restart_previews_queue;
|
|
|
|
void ED_preview_restart_queue_free()
|
|
{
|
|
BLI_freelistN(&G_restart_previews_queue);
|
|
}
|
|
|
|
void ED_preview_restart_queue_add(ID *id, enum eIconSizes size)
|
|
{
|
|
PreviewRestartQueueEntry *queue_entry = MEM_callocN<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, nullptr, queue_entry->id, queue_entry->size, true);
|
|
|
|
BLI_freelinkN(&G_restart_previews_queue, queue_entry);
|
|
}
|
|
}
|
|
|
|
/** \} */
|