Currently object bounds (`object.runtime.bb`) are lazily initialized
when accessed. This access happens from arbitrary threads, and
is unprotected by a mutex. This can cause access to stale data at
best, and crashes at worst. Eager calculation is meant to keep this
working, but it's fragile.
Since e8f4010611, geometry bounds are cached in the geometry
itself, which makes this object-level cache redundant. So, it's clearer
to build the `BoundBox` from those cached bounds and return it by
value, without interacting with the object's cached bounding box.
The code change is is mostly a move from `const BoundBox *` to
`std::optional<BoundBox>`. This is only one step of a larger change
described in #96968. Followup steps would include switching to
a simpler and smaller `Bounds` type, removing redundant object-
level access, and eventually removing `object.runtime.bb`.
Access of bounds from the object for mesh, curves, and point cloud
objects should now be thread-safe. Other object types still lazily
initialize the object `BoundBox` cache since they don't have
a data-level cache.
Pull Request: https://projects.blender.org/blender/blender/pulls/113465
208 lines
6.4 KiB
C++
208 lines
6.4 KiB
C++
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#pragma once
|
|
|
|
/** \file
|
|
* \ingroup draw
|
|
*
|
|
* Component / Object level resources like object attributes, matrices, visibility etc...
|
|
* Each of them are reference by resource index (#ResourceHandle).
|
|
*/
|
|
|
|
#include "BLI_math_matrix.hh"
|
|
|
|
#include "BKE_curve.h"
|
|
#include "BKE_duplilist.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_object.hh"
|
|
#include "BKE_volume.h"
|
|
#include "BLI_hash.h"
|
|
#include "DNA_curve_types.h"
|
|
#include "DNA_layer_types.h"
|
|
#include "DNA_meta_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "draw_handle.hh"
|
|
#include "draw_manager.hh"
|
|
#include "draw_shader_shared.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name ObjectMatrices
|
|
* \{ */
|
|
|
|
inline void ObjectMatrices::sync(const Object &object)
|
|
{
|
|
model.view() = blender::float4x4_view(object.object_to_world);
|
|
model_inverse.view() = blender::float4x4_view(object.world_to_object);
|
|
}
|
|
|
|
inline void ObjectMatrices::sync(const float4x4 &model_matrix)
|
|
{
|
|
model = model_matrix;
|
|
model_inverse = blender::math::invert(model_matrix);
|
|
}
|
|
|
|
inline std::ostream &operator<<(std::ostream &stream, const ObjectMatrices &matrices)
|
|
{
|
|
stream << "ObjectMatrices(" << std::endl;
|
|
stream << "model=" << matrices.model << ", " << std::endl;
|
|
stream << "model_inverse=" << matrices.model_inverse << ")" << std::endl;
|
|
return stream;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name ObjectInfos
|
|
* \{ */
|
|
|
|
ENUM_OPERATORS(eObjectInfoFlag, OBJECT_NEGATIVE_SCALE)
|
|
|
|
inline void ObjectInfos::sync()
|
|
{
|
|
object_attrs_len = 0;
|
|
object_attrs_offset = 0;
|
|
|
|
flag = eObjectInfoFlag::OBJECT_NO_INFO;
|
|
}
|
|
|
|
inline void ObjectInfos::sync(const blender::draw::ObjectRef ref, bool is_active_object)
|
|
{
|
|
object_attrs_len = 0;
|
|
object_attrs_offset = 0;
|
|
|
|
ob_color = ref.object->color;
|
|
index = ref.object->index;
|
|
SET_FLAG_FROM_TEST(flag, is_active_object, eObjectInfoFlag::OBJECT_ACTIVE);
|
|
SET_FLAG_FROM_TEST(
|
|
flag, ref.object->base_flag & BASE_SELECTED, eObjectInfoFlag::OBJECT_SELECTED);
|
|
SET_FLAG_FROM_TEST(
|
|
flag, ref.object->base_flag & BASE_FROM_DUPLI, eObjectInfoFlag::OBJECT_FROM_DUPLI);
|
|
SET_FLAG_FROM_TEST(
|
|
flag, ref.object->base_flag & BASE_FROM_SET, eObjectInfoFlag::OBJECT_FROM_SET);
|
|
SET_FLAG_FROM_TEST(
|
|
flag, ref.object->transflag & OB_NEG_SCALE, eObjectInfoFlag::OBJECT_NEGATIVE_SCALE);
|
|
|
|
if (ref.dupli_object == nullptr) {
|
|
/* TODO(fclem): this is rather costly to do at draw time. Maybe we can
|
|
* put it in ob->runtime and make depsgraph ensure it is up to date. */
|
|
random = BLI_hash_int_2d(BLI_hash_string(ref.object->id.name + 2), 0) *
|
|
(1.0f / (float)0xFFFFFFFF);
|
|
}
|
|
else {
|
|
random = ref.dupli_object->random_id * (1.0f / (float)0xFFFFFFFF);
|
|
}
|
|
|
|
if (ref.object->data == nullptr) {
|
|
orco_add = float3(0.0f);
|
|
orco_mul = float3(1.0f);
|
|
return;
|
|
}
|
|
|
|
switch (GS(reinterpret_cast<ID *>(ref.object->data)->name)) {
|
|
case ID_VO: {
|
|
BoundBox &bbox = *BKE_volume_boundbox_get(ref.object);
|
|
orco_add = (float3(bbox.vec[6]) + float3(bbox.vec[0])) * 0.5f; /* Center. */
|
|
orco_mul = (float3(bbox.vec[6]) - float3(bbox.vec[0])) * 0.5f; /* Half-Size. */
|
|
break;
|
|
}
|
|
case ID_ME: {
|
|
BKE_mesh_texspace_get(static_cast<Mesh *>(ref.object->data), orco_add, orco_mul);
|
|
break;
|
|
}
|
|
case ID_CU_LEGACY: {
|
|
Curve &cu = *static_cast<Curve *>(ref.object->data);
|
|
BKE_curve_texspace_ensure(&cu);
|
|
orco_add = cu.texspace_location;
|
|
orco_mul = cu.texspace_size;
|
|
break;
|
|
}
|
|
case ID_MB: {
|
|
MetaBall &mb = *static_cast<MetaBall *>(ref.object->data);
|
|
orco_add = mb.texspace_location;
|
|
orco_mul = mb.texspace_size;
|
|
break;
|
|
}
|
|
default:
|
|
orco_add = float3(0.0f);
|
|
orco_mul = float3(1.0f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline std::ostream &operator<<(std::ostream &stream, const ObjectInfos &infos)
|
|
{
|
|
stream << "ObjectInfos(";
|
|
if (infos.flag == eObjectInfoFlag::OBJECT_NO_INFO) {
|
|
stream << "skipped)" << std::endl;
|
|
return stream;
|
|
}
|
|
stream << "orco_add=" << infos.orco_add << ", ";
|
|
stream << "orco_mul=" << infos.orco_mul << ", ";
|
|
stream << "ob_color=" << infos.ob_color << ", ";
|
|
stream << "index=" << infos.index << ", ";
|
|
stream << "random=" << infos.random << ", ";
|
|
stream << "flag=" << infos.flag << ")" << std::endl;
|
|
return stream;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name ObjectBounds
|
|
* \{ */
|
|
|
|
inline void ObjectBounds::sync()
|
|
{
|
|
bounding_sphere.w = -1.0f; /* Disable test. */
|
|
}
|
|
|
|
inline void ObjectBounds::sync(Object &ob)
|
|
{
|
|
const std::optional<BoundBox> bbox = BKE_object_boundbox_get(&ob);
|
|
if (!bbox) {
|
|
bounding_sphere.w = -1.0f; /* Disable test. */
|
|
return;
|
|
}
|
|
*reinterpret_cast<float3 *>(&bounding_corners[0]) = bbox->vec[0];
|
|
*reinterpret_cast<float3 *>(&bounding_corners[1]) = bbox->vec[4];
|
|
*reinterpret_cast<float3 *>(&bounding_corners[2]) = bbox->vec[3];
|
|
*reinterpret_cast<float3 *>(&bounding_corners[3]) = bbox->vec[1];
|
|
bounding_sphere.w = 0.0f; /* Enable test. */
|
|
}
|
|
|
|
inline void ObjectBounds::sync(const float3 ¢er, const float3 &size)
|
|
{
|
|
*reinterpret_cast<float3 *>(&bounding_corners[0]) = center - size;
|
|
*reinterpret_cast<float3 *>(&bounding_corners[1]) = center + float3(+size.x, -size.y, -size.z);
|
|
*reinterpret_cast<float3 *>(&bounding_corners[2]) = center + float3(-size.x, +size.y, -size.z);
|
|
*reinterpret_cast<float3 *>(&bounding_corners[3]) = center + float3(-size.x, -size.y, +size.z);
|
|
bounding_sphere.w = 0.0; /* Enable test. */
|
|
}
|
|
|
|
inline std::ostream &operator<<(std::ostream &stream, const ObjectBounds &bounds)
|
|
{
|
|
stream << "ObjectBounds(";
|
|
if (bounds.bounding_sphere.w == -1.0f) {
|
|
stream << "skipped)" << std::endl;
|
|
return stream;
|
|
}
|
|
stream << std::endl;
|
|
stream << ".bounding_corners[0]"
|
|
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[0]) << std::endl;
|
|
stream << ".bounding_corners[1]"
|
|
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[1]) << std::endl;
|
|
stream << ".bounding_corners[2]"
|
|
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[2]) << std::endl;
|
|
stream << ".bounding_corners[3]"
|
|
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[3]) << std::endl;
|
|
stream << ".sphere=(pos=" << float3(bounds.bounding_sphere)
|
|
<< ", rad=" << bounds.bounding_sphere.w << std::endl;
|
|
stream << ")" << std::endl;
|
|
return stream;
|
|
}
|
|
|
|
/** \} */
|