Fix #132099: crash when using same geometry on objects with different material counts

The core issue was that the geometry batch cache (e.g. `MeshBatchCache` or
`PointCloudBatchCache`) was dependent on the object. This is problematic when
the the same geometry is used with multiple different objects because the cache
can't be consistent with all of them.

Fortunately, the only thing that was retrieved from the object was the number of
material slots, so if that can be avoided we should be fine. We can't just use
the number of material slots stored on the geometry because that may have no
material slots but still has material indices which are overridden on the object
level.

The solution is to take make the number of materials for a geometry only
dependent on the actual `material_index` attribute and not on the number of
available slots. More specifically, we find the maximal referenced material
index and handle that many materials. This number does not depend on how many
material slots there are on the object, but it still allows the object to
override materials slots that the mesh references.

A downside is that the maximum material index has to be computed which often
requires an iteration over the mesh. Fortunately, we can cache that quite easily
and the computation can be done in parallel. Also we are probably able to
eagerly update the material index in many cases when it's set instead of
computing it lazily. That is not implemented in this patch though.

The largest part of the patch is making the maximal material index easily
available on all the geometry types. Besides that, the material API is slightly
replaced and the drawing code now makes use of the updated API.

Pull Request: https://projects.blender.org/blender/blender/pulls/133498
This commit is contained in:
Jacques Lucke
2025-01-24 12:05:25 +01:00
parent 2feb435780
commit 2fda20e1db
30 changed files with 240 additions and 42 deletions

View File

@@ -118,6 +118,9 @@ class CurvesGeometryRuntime {
/** Normal direction vectors for each evaluated point. */
mutable SharedCache<Vector<float3>> evaluated_normal_cache;
/** The maximum of the "material_index" attribute. */
mutable SharedCache<std::optional<int>> max_material_index_cache;
/** Stores weak references to material data blocks. */
std::unique_ptr<bake::BakeMaterialsList> bake_materials;
@@ -305,6 +308,9 @@ class CurvesGeometry : public ::CurvesGeometry {
void count_memory(MemoryCounter &memory) const;
/** Get the largest material index used by the curves or nullopt if there are none. */
std::optional<int> material_index_max() const;
private:
/* --------------------------------------------------------------------
* Evaluation.
@@ -399,6 +405,8 @@ class CurvesGeometry : public ::CurvesGeometry {
* this in #finish() calls.
*/
void tag_radii_changed();
/** Call after changing the "material_index" attribute. */
void tag_material_index_changed();
void translate(const float3 &translation);
void transform(const float4x4 &matrix);

View File

@@ -9,6 +9,8 @@
* \brief General operations, lookup, etc. for materials.
*/
#include <optional>
struct ID;
struct Main;
struct Material;
@@ -155,11 +157,25 @@ Material *BKE_object_material_get_eval(Object *ob, short act);
* This is the maximum of the number of material slots on the object and geometry.
*/
int BKE_object_material_count_eval(const Object *ob);
/**
* Same as #BKE_object_material_count_eval, but returns at least one. This is commonly used in
* rendering code which has to use a fallback material if there is none.
* Returns the maximum material index used by the geometry. This returns zero if the geometry is
* empty or if all material indices are negative.
*/
int BKE_object_material_count_with_fallback_eval(const Object *ob);
std::optional<int> BKE_id_material_index_max_eval(const ID &id);
/**
* Gets the number of material slots used by the geometry. The corresponding material for each slot
* can be retrieved with #BKE_object_material_get_eval.
*
* These two functions give the same result when the mesh is provided itself, or an object that
* uses the mesh.
*
* NOTE: This may be higher or lower than the number of material slots on the object or
* object-data. However, it is always at least 1 (the fallback).
*/
int BKE_id_material_used_with_fallback_eval(const ID &id);
int BKE_object_material_used_with_fallback_eval(const Object &ob);
void BKE_id_material_eval_assign(ID *id, int slot, Material *material);
/**

View File

@@ -169,6 +169,8 @@ struct MeshRuntime {
SharedCache<std::unique_ptr<BVHTree, BVHTreeDeleter>> bvh_cache_loose_edges;
SharedCache<std::unique_ptr<BVHTree, BVHTreeDeleter>> bvh_cache_loose_edges_no_hidden;
SharedCache<std::optional<int>> max_material_index;
/** Needed in case we need to lazily initialize the mesh. */
CustomData_MeshMasks cd_mask_extra = {};

View File

@@ -5485,6 +5485,18 @@ void BKE_curve_correct_bezpart(const float v1[2], float v2[2], float v3[2], cons
}
}
std::optional<int> Curve::material_index_max() const
{
if (BLI_listbase_is_empty(&this->nurb)) {
return std::nullopt;
}
int max_index = 0;
LISTBASE_FOREACH (const Nurb *, nurb, &this->nurb) {
max_index = std::max<int>(max_index, nurb->mat_nr);
}
return max_index;
}
/* **** Depsgraph evaluation **** */
void BKE_curve_eval_geometry(Depsgraph *depsgraph, Curve *curve)

View File

@@ -46,6 +46,12 @@ static void tag_component_normals_changed(void *owner)
curves.tag_normals_changed();
}
static void tag_component_material_index_changed(void *owner)
{
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
curves.tag_material_index_changed();
}
/**
* This provider makes vertex groups available as float attributes.
*/
@@ -348,7 +354,7 @@ static GeometryAttributeProviders create_attribute_providers_for_curve()
CD_PROP_INT32,
BuiltinAttributeProvider::Deletable,
curve_access,
nullptr,
tag_component_material_index_changed,
AttributeValidator{&material_index_clamp});
static CurvesVertexGroupsAttributeProvider vertex_groups;

View File

@@ -117,6 +117,7 @@ CurvesGeometry::CurvesGeometry(const CurvesGeometry &other)
other.runtime->evaluated_length_cache,
other.runtime->evaluated_tangent_cache,
other.runtime->evaluated_normal_cache,
other.runtime->max_material_index_cache,
{},
true});
@@ -1082,6 +1083,7 @@ void CurvesGeometry::tag_topology_changed()
this->tag_positions_changed();
this->runtime->evaluated_offsets_cache.tag_dirty();
this->runtime->nurbs_basis_cache.tag_dirty();
this->runtime->max_material_index_cache.tag_dirty();
this->runtime->check_type_counts = true;
}
void CurvesGeometry::tag_normals_changed()
@@ -1089,6 +1091,10 @@ void CurvesGeometry::tag_normals_changed()
this->runtime->evaluated_normal_cache.tag_dirty();
}
void CurvesGeometry::tag_radii_changed() {}
void CurvesGeometry::tag_material_index_changed()
{
this->runtime->max_material_index_cache.tag_dirty();
}
static void translate_positions(MutableSpan<float3> positions, const float3 &translation)
{
@@ -1204,6 +1210,17 @@ std::optional<Bounds<float3>> CurvesGeometry::bounds_min_max() const
return this->runtime->bounds_cache.data();
}
std::optional<int> CurvesGeometry::material_index_max() const
{
this->runtime->max_material_index_cache.ensure([&](std::optional<int> &r_max_material_index) {
r_max_material_index = blender::bounds::max<int>(
this->attributes()
.lookup_or_default<int>("material_index", blender::bke::AttrDomain::Curve, 0)
.varray);
});
return this->runtime->max_material_index_cache.data();
}
void CurvesGeometry::count_memory(MemoryCounter &memory) const
{
memory.add_shared(this->runtime->curve_offsets_sharing_info, this->offsets().size_in_bytes());

View File

@@ -3222,6 +3222,28 @@ void GreasePencil::count_memory(blender::MemoryCounter &memory) const
}
}
std::optional<int> GreasePencil::material_index_max_eval() const
{
using namespace blender;
using namespace blender::bke;
std::optional<int> max_index;
for (const greasepencil::Layer *layer : this->layers()) {
if (const greasepencil::Drawing *drawing = this->get_eval_drawing(*layer)) {
const bke::CurvesGeometry &curves = drawing->strokes();
const std::optional<int> max_index_on_layer = curves.material_index_max();
if (max_index) {
if (max_index_on_layer) {
max_index = std::max(*max_index, *max_index_on_layer);
}
}
else {
max_index = max_index_on_layer;
}
}
}
return max_index;
}
blender::Span<const blender::bke::greasepencil::Layer *> GreasePencil::layers() const
{
BLI_assert(this->runtime != nullptr);

View File

@@ -49,6 +49,7 @@
#include "BKE_attribute.hh"
#include "BKE_brush.hh"
#include "BKE_curve.hh"
#include "BKE_curves.hh"
#include "BKE_displist.h"
#include "BKE_editmesh.hh"
#include "BKE_gpencil_legacy.h"
@@ -732,7 +733,7 @@ Material *BKE_object_material_get(Object *ob, short act)
return ma_p ? *ma_p : nullptr;
}
static const ID *get_evaluated_object_data_with_materials(Object *ob)
static const ID *get_evaluated_object_data_with_materials(const Object *ob)
{
const ID *data = static_cast<ID *>(ob->data);
/* Meshes in edit mode need special handling. */
@@ -799,10 +800,43 @@ int BKE_object_material_count_eval(const Object *ob)
return std::max(ob->totcol, len_p ? *len_p : 0);
}
int BKE_object_material_count_with_fallback_eval(const Object *ob)
std::optional<int> BKE_id_material_index_max_eval(const ID &id)
{
const int actual_count = BKE_object_material_count_eval(ob);
return std::max(1, actual_count);
switch (GS(id.name)) {
case ID_ME:
return reinterpret_cast<const Mesh &>(id).material_index_max();
case ID_CU_LEGACY:
return reinterpret_cast<const Curve &>(id).material_index_max();
case ID_CV:
return reinterpret_cast<const Curves &>(id).geometry.wrap().material_index_max();
case ID_PT:
return reinterpret_cast<const PointCloud &>(id).material_index_max();
case ID_GP:
return reinterpret_cast<const GreasePencil &>(id).material_index_max_eval();
case ID_VO:
case ID_MB:
/* Always use the first material. */
return 0;
case ID_GD_LEGACY:
/* Is not rendered anymore. */
BLI_assert_unreachable();
return 0;
default:
break;
}
return 0;
}
int BKE_id_material_used_with_fallback_eval(const ID &id)
{
const int max_material_index = std::max(0, BKE_id_material_index_max_eval(id).value_or(0));
return max_material_index + 1;
}
int BKE_object_material_used_with_fallback_eval(const Object &ob)
{
const ID *data = get_evaluated_object_data_with_materials(&ob);
return BKE_id_material_used_with_fallback_eval(*data);
}
void BKE_id_material_eval_assign(ID *id, int slot, Material *material)

View File

@@ -166,6 +166,7 @@ static void mesh_copy_data(Main *bmain,
mesh_dst->runtime->bvh_cache_loose_edges = mesh_src->runtime->bvh_cache_loose_edges;
mesh_dst->runtime->bvh_cache_loose_edges_no_hidden =
mesh_src->runtime->bvh_cache_loose_edges_no_hidden;
mesh_dst->runtime->max_material_index = mesh_src->runtime->max_material_index;
if (mesh_src->runtime->bake_materials) {
mesh_dst->runtime->bake_materials = std::make_unique<blender::bke::bake::BakeMaterialsList>(
*mesh_src->runtime->bake_materials);
@@ -1352,6 +1353,22 @@ void BKE_mesh_transform(Mesh *mesh, const float mat[4][4], bool do_keys)
mesh->tag_positions_changed();
}
std::optional<int> Mesh::material_index_max() const
{
this->runtime->max_material_index.ensure([&](std::optional<int> &value) {
if (this->faces_num == 0) {
value = std::nullopt;
}
else {
value = blender::bounds::max<int>(
this->attributes()
.lookup_or_default<int>("material_index", blender::bke::AttrDomain::Face, 0)
.varray);
}
});
return this->runtime->max_material_index.data();
}
static void translate_positions(MutableSpan<float3> positions, const float3 &translation)
{
using namespace blender;

View File

@@ -724,6 +724,13 @@ static void tag_component_sharpness_changed(void *owner)
}
}
static void tag_material_index_changed(void *owner)
{
if (Mesh *mesh = static_cast<Mesh *>(owner)) {
mesh->tag_material_index_changed();
}
}
/**
* This provider makes vertex groups available as float attributes.
*/
@@ -904,7 +911,7 @@ static GeometryAttributeProviders create_attribute_providers_for_mesh()
CD_PROP_INT32,
BuiltinAttributeProvider::Deletable,
face_access,
nullptr,
tag_material_index_changed,
AttributeValidator{&material_index_clamp});
static const auto int2_index_clamp = mf::build::SI1_SO<int2, int2>(

View File

@@ -332,6 +332,7 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
mesh->runtime->corner_tris_cache.data.tag_dirty();
mesh->runtime->corner_tri_faces_cache.tag_dirty();
mesh->runtime->shrinkwrap_boundary_cache.tag_dirty();
mesh->runtime->max_material_index.tag_dirty();
mesh->runtime->subsurf_face_dot_tags.clear_and_shrink();
mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink();
mesh->flag &= ~ME_NO_OVERLAPPING_TOPOLOGY;
@@ -421,6 +422,11 @@ void Mesh::tag_visibility_changed()
this->runtime->bvh_cache_loose_edges_no_hidden.tag_dirty();
}
void Mesh::tag_material_index_changed()
{
this->runtime->max_material_index.tag_dirty();
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -279,6 +279,17 @@ std::optional<blender::Bounds<blender::float3>> PointCloud::bounds_min_max() con
return this->runtime->bounds_cache.data();
}
std::optional<int> PointCloud::material_index_max() const
{
if (this->totpoint == 0) {
return std::nullopt;
}
return blender::bounds::max<int>(
this->attributes()
.lookup_or_default<int>("material_index", blender::bke::AttrDomain::Point, 0)
.varray);
}
void PointCloud::count_memory(blender::MemoryCounter &memory) const
{
CustomData_count_memory(this->pdata, this->totpoint, memory);

View File

@@ -16,6 +16,7 @@
#include "BLI_index_mask.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "BLI_virtual_array.hh"
namespace blender {
@@ -150,6 +151,31 @@ template<typename T>
return intersect(*a, *b);
}
/**
* Finds the maximum value for elements in the array.
*/
template<typename T> inline std::optional<T> max(const VArray<T> &values)
{
if (values.is_empty()) {
return std::nullopt;
}
if (const std::optional<T> value = values.get_if_single()) {
return value;
}
const VArraySpan<int> values_span = values;
return threading::parallel_reduce(
values_span.index_range(),
2048,
std::numeric_limits<T>::min(),
[&](const IndexRange range, int current_max) {
for (const int value : values_span.slice(range)) {
current_max = std::max(current_max, value);
}
return current_max;
},
[](const int a, const int b) { return std::max(a, b); });
}
} // namespace bounds
namespace detail {

View File

@@ -450,7 +450,7 @@ MaterialArray &MaterialModule::material_array_get(Object *ob, bool has_motion)
material_array_.materials.clear();
material_array_.gpu_materials.clear();
const int materials_len = DRW_cache_object_material_count_get(ob);
const int materials_len = BKE_object_material_used_with_fallback_eval(*ob);
for (auto i : IndexRange(materials_len)) {
::Material *blender_mat = material_from_slot(ob, i);

View File

@@ -52,7 +52,7 @@ GPENCIL_tObject *gpencil_object_cache_add(GPENCIL_PrivateData *pd,
/* Check if any material with holdout flag enabled. */
tgp_ob->do_mat_holdout = false;
const int tot_materials = BKE_object_material_count_eval(ob);
const int tot_materials = BKE_object_material_used_with_fallback_eval(*ob);
for (int i = 0; i < tot_materials; i++) {
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, i + 1);
if (((gp_style != nullptr) && (gp_style->flag & GP_MATERIAL_IS_STROKE_HOLDOUT)) ||

View File

@@ -165,7 +165,7 @@ GPENCIL_MaterialPool *gpencil_material_pool_create(GPENCIL_PrivateData *pd,
{
GPENCIL_MaterialPool *matpool = pd->last_material_pool;
int mat_len = max_ii(1, BKE_object_material_count_eval(ob));
int mat_len = BKE_object_material_used_with_fallback_eval(*ob);
bool reuse_matpool = matpool && ((matpool->used_count + mat_len) <= GPENCIL_MATERIAL_BUFFER_LEN);

View File

@@ -402,7 +402,7 @@ static void drw_shgroup_bone_custom_solid_mesh(const Armatures::DrawContext *ctx
using namespace blender::draw;
/* TODO(fclem): arg... less than ideal but we never iter on this object
* to assure batch cache is valid. */
DRW_mesh_batch_cache_validate(custom, mesh);
DRW_mesh_batch_cache_validate(mesh);
blender::gpu::Batch *surf = DRW_mesh_batch_cache_get_surface(mesh);
blender::gpu::Batch *edges = DRW_mesh_batch_cache_get_edge_detection(mesh, nullptr);
@@ -447,7 +447,7 @@ static void drw_shgroup_bone_custom_mesh_wire(const Armatures::DrawContext *ctx,
using namespace blender::draw;
/* TODO(fclem): arg... less than ideal but we never iter on this object
* to assure batch cache is valid. */
DRW_mesh_batch_cache_validate(custom, mesh);
DRW_mesh_batch_cache_validate(mesh);
blender::gpu::Batch *geom = DRW_mesh_batch_cache_get_all_edges(mesh);
if (geom) {

View File

@@ -758,7 +758,7 @@ bool Instance::object_is_rendered_transparent(const Object *object, const State
if (shading.color_type == V3D_SHADING_MATERIAL_COLOR) {
if (object->type == OB_MESH) {
const int materials_num = BKE_object_material_count_eval(object);
const int materials_num = BKE_object_material_used_with_fallback_eval(*object);
for (int i = 0; i < materials_num; i++) {
Material *mat = BKE_object_material_get_eval(const_cast<Object *>(object), i + 1);
if (mat && mat->a < 1.0f) {

View File

@@ -216,7 +216,7 @@ class Prepass : Overlay {
case OB_MESH:
if (use_material_slot_selection_) {
/* TODO(fclem): Improve the API. */
const int materials_len = DRW_cache_object_material_count_get(ob_ref.object);
const int materials_len = BKE_object_material_used_with_fallback_eval(*ob_ref.object);
Array<GPUMaterial *> materials(materials_len, nullptr);
geom_list = DRW_cache_mesh_surface_shaded_get(ob_ref.object, materials);
}

View File

@@ -259,7 +259,7 @@ class Instance {
bool has_transparent_material = false;
if (object_state.use_per_material_batches) {
const int material_count = DRW_cache_object_material_count_get(ob_ref.object);
const int material_count = BKE_object_material_used_with_fallback_eval(*ob_ref.object);
Span<gpu::Batch *> batches;
if (object_state.color_type == V3D_SHADING_TEXTURE_COLOR) {

View File

@@ -935,11 +935,6 @@ blender::gpu::VertBuf *DRW_cache_object_pos_vertbuf_get(Object *ob)
}
}
int DRW_cache_object_material_count_get(const Object *ob)
{
return BKE_object_material_count_with_fallback_eval(ob);
}
Span<blender::gpu::Batch *> DRW_cache_object_surface_material_get(
Object *ob, const Span<const GPUMaterial *> materials)
{
@@ -3293,7 +3288,7 @@ void drw_batch_cache_validate(Object *ob)
using namespace blender::draw;
switch (ob->type) {
case OB_MESH:
DRW_mesh_batch_cache_validate(*ob, *(Mesh *)ob->data);
DRW_mesh_batch_cache_validate(*(Mesh *)ob->data);
break;
case OB_CURVES_LEGACY:
case OB_FONT:
@@ -3307,7 +3302,7 @@ void drw_batch_cache_validate(Object *ob)
DRW_curves_batch_cache_validate((Curves *)ob->data);
break;
case OB_POINTCLOUD:
DRW_pointcloud_batch_cache_validate(*ob, (PointCloud *)ob->data);
DRW_pointcloud_batch_cache_validate((PointCloud *)ob->data);
break;
case OB_VOLUME:
DRW_volume_batch_cache_validate((Volume *)ob->data);

View File

@@ -72,7 +72,6 @@ blender::gpu::Batch *DRW_cache_object_loose_edges_get(Object *ob);
blender::Span<blender::gpu::Batch *> DRW_cache_object_surface_material_get(
Object *ob, blender::Span<const GPUMaterial *> materials);
blender::gpu::Batch *DRW_cache_object_face_wireframe_get(const Scene *scene, Object *ob);
int DRW_cache_object_material_count_get(const Object *ob);
/**
* Returns the vertbuf used by shaded surface batch.

View File

@@ -533,7 +533,7 @@ std::unique_ptr<MeshRenderData> mesh_render_data_create(Object &object,
{
std::unique_ptr<MeshRenderData> mr = std::make_unique<MeshRenderData>();
mr->toolsettings = ts;
mr->materials_num = BKE_object_material_count_with_fallback_eval(&object);
mr->materials_num = BKE_object_material_used_with_fallback_eval(object);
mr->object_to_world = object_to_world;

View File

@@ -46,7 +46,7 @@ void DRW_curve_batch_cache_validate(Curve *cu);
void DRW_curve_batch_cache_free(Curve *cu);
void DRW_mesh_batch_cache_dirty_tag(Mesh *mesh, eMeshBatchDirtyMode mode);
void DRW_mesh_batch_cache_validate(Object &object, Mesh &mesh);
void DRW_mesh_batch_cache_validate(Mesh &mesh);
void DRW_mesh_batch_cache_free(void *batch_cache);
void DRW_lattice_batch_cache_dirty_tag(Lattice *lt, int mode);
@@ -61,7 +61,7 @@ void DRW_curves_batch_cache_validate(Curves *curves);
void DRW_curves_batch_cache_free(Curves *curves);
void DRW_pointcloud_batch_cache_dirty_tag(PointCloud *pointcloud, int mode);
void DRW_pointcloud_batch_cache_validate(Object &object, PointCloud *pointcloud);
void DRW_pointcloud_batch_cache_validate(PointCloud *pointcloud);
void DRW_pointcloud_batch_cache_free(PointCloud *pointcloud);
void DRW_volume_batch_cache_dirty_tag(Volume *volume, int mode);

View File

@@ -530,7 +530,7 @@ BLI_INLINE void mesh_batch_cache_add_request(MeshBatchCache &cache, DRWBatchFlag
/* gpu::Batch cache management. */
static bool mesh_batch_cache_valid(Object &object, Mesh &mesh)
static bool mesh_batch_cache_valid(Mesh &mesh)
{
MeshBatchCache *cache = static_cast<MeshBatchCache *>(mesh.runtime->batch_cache);
@@ -548,14 +548,14 @@ static bool mesh_batch_cache_valid(Object &object, Mesh &mesh)
return false;
}
if (cache->mat_len != BKE_object_material_count_with_fallback_eval(&object)) {
if (cache->mat_len != BKE_id_material_used_with_fallback_eval(mesh.id)) {
return false;
}
return true;
}
static void mesh_batch_cache_init(Object &object, Mesh &mesh)
static void mesh_batch_cache_init(Mesh &mesh)
{
if (!mesh.runtime->batch_cache) {
mesh.runtime->batch_cache = MEM_new<MeshBatchCache>(__func__);
@@ -574,7 +574,7 @@ static void mesh_batch_cache_init(Object &object, Mesh &mesh)
// cache->vert_len = mesh_render_verts_len_get(mesh);
}
cache->mat_len = BKE_object_material_count_with_fallback_eval(&object);
cache->mat_len = BKE_id_material_used_with_fallback_eval(mesh.id);
cache->surface_per_mat = Array<gpu::Batch *>(cache->mat_len, nullptr);
cache->tris_per_mat = Array<gpu::IndexBuf *>(cache->mat_len, nullptr);
@@ -585,13 +585,13 @@ static void mesh_batch_cache_init(Object &object, Mesh &mesh)
drw_mesh_weight_state_clear(&cache->weight_state);
}
void DRW_mesh_batch_cache_validate(Object &object, Mesh &mesh)
void DRW_mesh_batch_cache_validate(Mesh &mesh)
{
if (!mesh_batch_cache_valid(object, mesh)) {
if (!mesh_batch_cache_valid(mesh)) {
if (mesh.runtime->batch_cache) {
mesh_batch_cache_clear(*static_cast<MeshBatchCache *>(mesh.runtime->batch_cache));
}
mesh_batch_cache_init(object, mesh);
mesh_batch_cache_init(mesh);
}
}

View File

@@ -92,20 +92,20 @@ static PointCloudBatchCache *pointcloud_batch_cache_get(PointCloud &pointcloud)
return static_cast<PointCloudBatchCache *>(pointcloud.batch_cache);
}
static bool pointcloud_batch_cache_valid(Object &object, PointCloud &pointcloud)
static bool pointcloud_batch_cache_valid(PointCloud &pointcloud)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud);
if (cache == nullptr) {
return false;
}
if (cache->eval_cache.mat_len != BKE_object_material_count_with_fallback_eval(&object)) {
if (cache->eval_cache.mat_len != BKE_id_material_used_with_fallback_eval(pointcloud.id)) {
return false;
}
return cache->is_dirty == false;
}
static void pointcloud_batch_cache_init(Object &object, PointCloud &pointcloud)
static void pointcloud_batch_cache_init(PointCloud &pointcloud)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud);
@@ -117,7 +117,7 @@ static void pointcloud_batch_cache_init(Object &object, PointCloud &pointcloud)
cache->eval_cache = {};
}
cache->eval_cache.mat_len = BKE_object_material_count_with_fallback_eval(&object);
cache->eval_cache.mat_len = BKE_id_material_used_with_fallback_eval(pointcloud.id);
cache->eval_cache.surface_per_mat = static_cast<gpu::Batch **>(
MEM_callocN(sizeof(gpu::Batch *) * cache->eval_cache.mat_len, __func__));
@@ -171,11 +171,11 @@ static void pointcloud_batch_cache_clear(PointCloud &pointcloud)
pointcloud_discard_attributes(*cache);
}
void DRW_pointcloud_batch_cache_validate(Object &object, PointCloud *pointcloud)
void DRW_pointcloud_batch_cache_validate(PointCloud *pointcloud)
{
if (!pointcloud_batch_cache_valid(object, *pointcloud)) {
if (!pointcloud_batch_cache_valid(*pointcloud)) {
pointcloud_batch_cache_clear(*pointcloud);
pointcloud_batch_cache_init(object, *pointcloud);
pointcloud_batch_cache_init(*pointcloud);
}
}

View File

@@ -13,6 +13,10 @@
#include "DNA_listBase.h"
#include "DNA_vec_types.h"
#ifdef __cplusplus
# include <optional>
#endif
/** Used in `readfile.cc` and `editfont.cc`. */
#define MAXTEXTBOX 256
@@ -315,6 +319,11 @@ typedef struct Curve {
char _pad3[7];
void *batch_cache;
#ifdef __cplusplus
/** Get the largest material index used by the curves or nullopt if there are none. */
std::optional<int> material_index_max() const;
#endif
} Curve;
#define CURVE_VFONT_ANY(cu) ((cu)->vfont), ((cu)->vfontb), ((cu)->vfonti), ((cu)->vfontbi)

View File

@@ -720,6 +720,9 @@ typedef struct GreasePencil {
blender::bke::AttributeAccessor attributes() const;
blender::bke::MutableAttributeAccessor attributes_for_write();
/** Get the largest material index used by the evaluated layers or nullopt if they are empty. */
std::optional<int> material_index_max_eval() const;
void count_memory(blender::MemoryCounter &memory) const;
/* For debugging purposes. */

View File

@@ -309,6 +309,9 @@ typedef struct Mesh {
/** Set cached mesh bounds to a known-correct value to avoid their lazy calculation later on. */
void bounds_set_eager(const blender::Bounds<blender::float3> &bounds);
/** Get the largest material index used by the mesh or nullopt if it has no faces. */
std::optional<int> material_index_max() const;
/**
* Cached map containing the index of the face using each face corner.
*/
@@ -429,6 +432,8 @@ typedef struct Mesh {
void tag_topology_changed();
/** Call when changing the ".hide_vert", ".hide_edge", or ".hide_poly" attributes. */
void tag_visibility_changed();
/** Call when changing the "material_index" attribute. */
void tag_material_index_changed();
#endif
} Mesh;

View File

@@ -66,6 +66,9 @@ typedef struct PointCloud {
std::optional<blender::Bounds<blender::float3>> bounds_min_max() const;
/** Get the largest material index used by the pointcloud or nullopt if it is empty. */
std::optional<int> material_index_max() const;
void count_memory(blender::MemoryCounter &memory) const;
#endif