This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/functions/FN_spans.hh
Jacques Lucke e5ee7e9a2d Geometry Nodes: don't delete existing attribute before new attribute is computed
This fixes the behavior of some nodes when the same attribute
name is used for input and output. If both attributes have a
different type, they can't exist at the same time. Therefore,
the input attribute has to be removed in order to create the
output attribute.

Previously, the input attribute was remove before it was used
in any computations. Now, the output is written to a temporary
buffer and only later saved in the geometry component. This
allows both attributes to coexist within the node.

The temporary attribute is only create when necessary. The
normal case without name collisions still works the same
as before.

Differential Revision: https://developer.blender.org/D10109

Ref T83793.
2021-01-14 15:52:08 +01:00

463 lines
11 KiB
C++

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup fn
*
* This file implements multiple variants of a span for different use cases. There are two
* requirements of the function system that require span implementations other from
* blender::Span<T>.
* 1. The function system works with a run-time type system (see `CPPType`). Therefore, it has to
* deal with types in a generic way. The type of a Span<T> has to be known at compile time.
* 2. Span<T> expects an underlying memory buffer that is as large as the span. However, sometimes
* we can save some memory and processing when we know that all elements are the same.
*
* The first requirement is solved with generic spans, which use the "G" prefix. Those
* store a CPPType instance to keep track of the type that is currently stored.
*
* The second requirement is solved with virtual spans. A virtual span behaves like a normal span,
* but it might not be backed up by an actual array. Elements in a virtual span are always
* immutable.
*
* Different use cases require different combinations of these properties and therefore use
* different data structures.
*/
#include "BLI_span.hh"
#include "FN_cpp_type.hh"
namespace blender::fn {
/**
* A generic span. It behaves just like a blender::Span<T>, but the type is only known at run-time.
*/
class GSpan {
private:
const CPPType *type_;
const void *data_;
int64_t size_;
public:
GSpan(const CPPType &type, const void *buffer, int64_t size)
: type_(&type), data_(buffer), size_(size)
{
BLI_assert(size >= 0);
BLI_assert(buffer != nullptr || size == 0);
BLI_assert(type.pointer_has_valid_alignment(buffer));
}
GSpan(const CPPType &type) : GSpan(type, nullptr, 0)
{
}
template<typename T>
GSpan(Span<T> array)
: GSpan(CPPType::get<T>(), static_cast<const void *>(array.data()), array.size())
{
}
const CPPType &type() const
{
return *type_;
}
bool is_empty() const
{
return size_ == 0;
}
int64_t size() const
{
return size_;
}
const void *data() const
{
return data_;
}
const void *operator[](int64_t index) const
{
BLI_assert(index < size_);
return POINTER_OFFSET(data_, type_->size() * index);
}
template<typename T> Span<T> typed() const
{
BLI_assert(type_->is<T>());
return Span<T>(static_cast<const T *>(data_), size_);
}
};
/**
* A generic mutable span. It behaves just like a blender::MutableSpan<T>, but the type is only
* known at run-time.
*/
class GMutableSpan {
private:
const CPPType *type_;
void *data_;
int64_t size_;
public:
GMutableSpan(const CPPType &type, void *buffer, int64_t size)
: type_(&type), data_(buffer), size_(size)
{
BLI_assert(size >= 0);
BLI_assert(buffer != nullptr || size == 0);
BLI_assert(type.pointer_has_valid_alignment(buffer));
}
GMutableSpan(const CPPType &type) : GMutableSpan(type, nullptr, 0)
{
}
template<typename T>
GMutableSpan(MutableSpan<T> array)
: GMutableSpan(CPPType::get<T>(), static_cast<void *>(array.begin()), array.size())
{
}
operator GSpan() const
{
return GSpan(*type_, data_, size_);
}
const CPPType &type() const
{
return *type_;
}
bool is_empty() const
{
return size_ == 0;
}
int64_t size() const
{
return size_;
}
void *data()
{
return data_;
}
void *operator[](int64_t index)
{
BLI_assert(index >= 0);
BLI_assert(index < size_);
return POINTER_OFFSET(data_, type_->size() * index);
}
void *operator[](int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < size_);
return POINTER_OFFSET(data_, type_->size() * index);
}
template<typename T> MutableSpan<T> typed()
{
BLI_assert(type_->is<T>());
return MutableSpan<T>(static_cast<T *>(data_), size_);
}
};
enum class VSpanCategory {
Single,
FullArray,
FullPointerArray,
};
template<typename T> struct VSpanBase {
protected:
int64_t virtual_size_;
VSpanCategory category_;
union {
struct {
const T *data;
} single;
struct {
const T *data;
} full_array;
struct {
const T *const *data;
} full_pointer_array;
} data_;
public:
bool is_single_element() const
{
switch (category_) {
case VSpanCategory::Single:
return true;
case VSpanCategory::FullArray:
return virtual_size_ == 1;
case VSpanCategory::FullPointerArray:
return virtual_size_ == 1;
}
BLI_assert(false);
return false;
}
bool is_full_array() const
{
switch (category_) {
case VSpanCategory::Single:
return virtual_size_ == 1;
case VSpanCategory::FullArray:
return true;
case VSpanCategory::FullPointerArray:
return virtual_size_ <= 1;
}
BLI_assert(false);
return false;
}
bool is_empty() const
{
return this->virtual_size_ == 0;
}
int64_t size() const
{
return this->virtual_size_;
}
};
BLI_STATIC_ASSERT((sizeof(VSpanBase<void>) == sizeof(VSpanBase<AlignedBuffer<64, 64>>)),
"should not depend on the size of the type");
/**
* A virtual span. It behaves like a blender::Span<T>, but might not be backed up by an actual
* array.
*/
template<typename T> class VSpan : public VSpanBase<T> {
friend class GVSpan;
VSpan(const VSpanBase<void> &values)
{
memcpy(this, &values, sizeof(VSpanBase<void>));
}
public:
VSpan()
{
this->virtual_size_ = 0;
this->category_ = VSpanCategory::FullArray;
this->data_.full_array.data = nullptr;
}
VSpan(Span<T> values)
{
this->virtual_size_ = values.size();
this->category_ = VSpanCategory::FullArray;
this->data_.full_array.data = values.begin();
}
VSpan(MutableSpan<T> values) : VSpan(Span<T>(values))
{
}
VSpan(Span<const T *> values)
{
this->virtual_size_ = values.size();
this->category_ = VSpanCategory::FullPointerArray;
this->data_.full_pointer_array.data = values.begin();
}
static VSpan FromSingle(const T *value, int64_t virtual_size)
{
VSpan ref;
ref.virtual_size_ = virtual_size;
ref.category_ = VSpanCategory::Single;
ref.data_.single.data = value;
return ref;
}
const T &operator[](int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < this->virtual_size_);
switch (this->category_) {
case VSpanCategory::Single:
return *this->data_.single.data;
case VSpanCategory::FullArray:
return this->data_.full_array.data[index];
case VSpanCategory::FullPointerArray:
return *this->data_.full_pointer_array.data[index];
}
BLI_assert(false);
return *this->data_.single.data;
}
const T &as_single_element() const
{
BLI_assert(this->is_single_element());
return (*this)[0];
}
Span<T> as_full_array() const
{
BLI_assert(this->is_full_array());
if (this->virtual_size_ == 0) {
return Span<T>();
}
const T *data = &(*this)[0];
return Span<T>(data, this->virtual_size_);
}
};
/**
* A generic virtual span. It behaves like a blender::Span<T>, but the type is only known at
* run-time and it might not be backed up by an actual array.
*/
class GVSpan : public VSpanBase<void> {
private:
const CPPType *type_;
GVSpan() = default;
public:
GVSpan(const CPPType &type)
{
this->type_ = &type;
this->virtual_size_ = 0;
this->category_ = VSpanCategory::FullArray;
this->data_.full_array.data = nullptr;
}
GVSpan(GSpan values)
{
this->type_ = &values.type();
this->virtual_size_ = values.size();
this->category_ = VSpanCategory::FullArray;
this->data_.full_array.data = values.data();
}
GVSpan(GMutableSpan values) : GVSpan(GSpan(values))
{
}
template<typename T> GVSpan(const VSpanBase<T> &values)
{
this->type_ = &CPPType::get<T>();
memcpy(this, &values, sizeof(VSpanBase<void>));
}
template<typename T> GVSpan(Span<T> values) : GVSpan(GSpan(values))
{
}
template<typename T> GVSpan(MutableSpan<T> values) : GVSpan(GSpan(values))
{
}
static GVSpan FromSingle(const CPPType &type, const void *value, int64_t virtual_size)
{
GVSpan ref;
ref.type_ = &type;
ref.virtual_size_ = virtual_size;
ref.category_ = VSpanCategory::Single;
ref.data_.single.data = value;
return ref;
}
static GVSpan FromSingleWithMaxSize(const CPPType &type, const void *value)
{
return GVSpan::FromSingle(type, value, INT64_MAX);
}
static GVSpan FromDefault(const CPPType &type)
{
return GVSpan::FromSingleWithMaxSize(type, type.default_value());
}
static GVSpan FromFullPointerArray(const CPPType &type, const void *const *values, int64_t size)
{
GVSpan ref;
ref.type_ = &type;
ref.virtual_size_ = size;
ref.category_ = VSpanCategory::FullPointerArray;
ref.data_.full_pointer_array.data = values;
return ref;
}
const CPPType &type() const
{
return *this->type_;
}
const void *operator[](int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < this->virtual_size_);
switch (this->category_) {
case VSpanCategory::Single:
return this->data_.single.data;
case VSpanCategory::FullArray:
return POINTER_OFFSET(this->data_.full_array.data, index * type_->size());
case VSpanCategory::FullPointerArray:
return this->data_.full_pointer_array.data[index];
}
BLI_assert(false);
return this->data_.single.data;
}
template<typename T> VSpan<T> typed() const
{
BLI_assert(type_->is<T>());
return VSpan<T>(*this);
}
const void *as_single_element() const
{
BLI_assert(this->is_single_element());
return (*this)[0];
}
GSpan as_full_array() const
{
BLI_assert(this->is_full_array());
if (this->virtual_size_ == 0) {
return GSpan(*this->type_);
}
const void *data = (*this)[0];
return GSpan(*this->type_, data, this->virtual_size_);
}
void materialize_to_uninitialized(void *dst) const
{
this->materialize_to_uninitialized(IndexRange(virtual_size_), dst);
}
void materialize_to_uninitialized(IndexMask mask, void *dst) const
{
BLI_assert(this->size() >= mask.min_array_size());
int64_t element_size = type_->size();
for (int64_t i : mask) {
type_->copy_to_uninitialized((*this)[i], POINTER_OFFSET(dst, element_size * i));
}
}
};
} // namespace blender::fn