BLI: refactor IndexMask for better performance and memory usage #104629
|
@ -213,6 +213,7 @@ class IndexMask {
|
|||
|
||||
template<typename T> void to_indices(MutableSpan<T> r_indices) const;
|
||||
void to_bits(MutableBitSpan r_bits, int64_t offset = 0) const;
|
||||
void to_bools(MutableSpan<bool> r_bools, int64_t offset = 0) const;
|
||||
std::optional<IndexRange> to_range() const;
|
||||
Vector<IndexRange> to_ranges() const;
|
||||
Vector<IndexRange> to_ranges_invert(IndexRange universe) const;
|
||||
|
|
|
@ -881,6 +881,12 @@ void IndexMask::to_bits(MutableBitSpan r_bits, int64_t offset) const
|
|||
index_mask_to_bits(*this, offset, r_bits);
|
||||
}
|
||||
|
||||
void IndexMask::to_bools(MutableSpan<bool> r_bools, int64_t offset = 0) const
|
||||
{
|
||||
r_bools.fill(false);
|
||||
this->foreach_index_optimized([&](const int64_t i) { r_bools[i - offset] = true; });
|
||||
}
|
||||
|
||||
std::optional<IndexRange> IndexMask::to_range() const
|
||||
{
|
||||
if (data_.indices_num == 0) {
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
|
||||
using blender::Array;
|
||||
using blender::IndexMask;
|
||||
using blender::IndexMaskMemory;
|
||||
using blender::Span;
|
||||
using blender::Vector;
|
||||
|
||||
|
@ -64,18 +65,14 @@ static Span<MDeformVert> get_vertex_group(const Mesh &mesh, const int defgrp_ind
|
|||
return {vertex_group, mesh.totvert};
|
||||
}
|
||||
|
||||
static Vector<int64_t> selected_indices_from_vertex_group(Span<MDeformVert> vertex_group,
|
||||
const int index,
|
||||
const bool invert)
|
||||
static IndexMask selected_indices_from_vertex_group(Span<MDeformVert> vertex_group,
|
||||
const int index,
|
||||
const bool invert,
|
||||
IndexMaskMemory &memory)
|
||||
{
|
||||
Vector<int64_t> selected_indices;
|
||||
for (const int i : vertex_group.index_range()) {
|
||||
const bool found = BKE_defvert_find_weight(&vertex_group[i], index) > 0.0f;
|
||||
if (found != invert) {
|
||||
selected_indices.append(i);
|
||||
}
|
||||
}
|
||||
return selected_indices;
|
||||
return IndexMask::from_predicate(vertex_group.index_range(), 512, memory, [&](const int i) {
|
||||
return (BKE_defvert_find_weight(&vertex_group[i], index) > 0.0f) != invert;
|
||||
});
|
||||
}
|
||||
|
||||
static Array<bool> selection_array_from_vertex_group(Span<MDeformVert> vertex_group,
|
||||
|
@ -98,8 +95,9 @@ static std::optional<Mesh *> calculate_weld(const Mesh &mesh, const WeldModifier
|
|||
|
||||
if (wmd.mode == MOD_WELD_MODE_ALL) {
|
||||
if (!vertex_group.is_empty()) {
|
||||
Vector<int64_t> selected_indices = selected_indices_from_vertex_group(
|
||||
vertex_group, defgrp_index, invert);
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask selected_indices = selected_indices_from_vertex_group(
|
||||
vertex_group, defgrp_index, invert, memory);
|
||||
return blender::geometry::mesh_merge_by_distance_all(
|
||||
mesh, IndexMask(selected_indices), wmd.merge_dist);
|
||||
}
|
||||
|
|
|
@ -282,13 +282,13 @@ class SampleCurveFunction : public mf::MultiFunction {
|
|||
|
||||
auto return_default = [&]() {
|
||||
if (!sampled_positions.is_empty()) {
|
||||
sampled_positions.fill_indices(mask, {0, 0, 0});
|
||||
index_mask::masked_fill(sampled_positions, {0, 0, 0}, mask);
|
||||
}
|
||||
if (!sampled_tangents.is_empty()) {
|
||||
sampled_tangents.fill_indices(mask, {0, 0, 0});
|
||||
index_mask::masked_fill(sampled_tangents, {0, 0, 0}, mask);
|
||||
}
|
||||
if (!sampled_normals.is_empty()) {
|
||||
sampled_normals.fill_indices(mask, {0, 0, 0});
|
||||
index_mask::masked_fill(sampled_normals, {0, 0, 0}, mask);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -325,18 +325,18 @@ class SampleCurveFunction : public mf::MultiFunction {
|
|||
|
||||
auto fill_invalid = [&](const IndexMask mask) {
|
||||
if (!sampled_positions.is_empty()) {
|
||||
sampled_positions.fill_indices(mask, float3(0));
|
||||
index_mask::masked_fill(sampled_positions, float3(0), mask);
|
||||
}
|
||||
if (!sampled_tangents.is_empty()) {
|
||||
sampled_tangents.fill_indices(mask, float3(0));
|
||||
index_mask::masked_fill(sampled_tangents, float3(0), mask);
|
||||
}
|
||||
if (!sampled_normals.is_empty()) {
|
||||
sampled_normals.fill_indices(mask, float3(0));
|
||||
index_mask::masked_fill(sampled_normals, float3(0), mask);
|
||||
}
|
||||
if (!sampled_values.is_empty()) {
|
||||
attribute_math::convert_to_static_type(source_data_->type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
sampled_values.typed<T>().fill_indices(mask, {});
|
||||
index_mask::masked_fill<T>(sampled_values.typed<T>(), {}, mask);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -367,16 +367,14 @@ class SampleCurveFunction : public mf::MultiFunction {
|
|||
if (!sampled_tangents.is_empty()) {
|
||||
length_parameterize::interpolate_to_masked<float3>(
|
||||
evaluated_tangents.slice(evaluated_points), indices, factors, mask, sampled_tangents);
|
||||
for (const int64_t i : mask) {
|
||||
sampled_tangents[i] = math::normalize(sampled_tangents[i]);
|
||||
}
|
||||
mask.foreach_index(
|
||||
[&](const int64_t i) { sampled_tangents[i] = math::normalize(sampled_tangents[i]); });
|
||||
}
|
||||
if (!sampled_normals.is_empty()) {
|
||||
length_parameterize::interpolate_to_masked<float3>(
|
||||
evaluated_normals.slice(evaluated_points), indices, factors, mask, sampled_normals);
|
||||
for (const int64_t i : mask) {
|
||||
sampled_normals[i] = math::normalize(sampled_normals[i]);
|
||||
}
|
||||
mask.foreach_index(
|
||||
[&](const int64_t i) { sampled_normals[i] = math::normalize(sampled_normals[i]); });
|
||||
}
|
||||
if (!sampled_values.is_empty()) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
|
@ -406,7 +404,7 @@ class SampleCurveFunction : public mf::MultiFunction {
|
|||
Vector<int64_t> invalid_indices;
|
||||
MultiValueMap<int, int64_t> indices_per_curve;
|
||||
devirtualize_varray(curve_indices, [&](const auto curve_indices) {
|
||||
for (const int64_t i : mask) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const int curve_i = curve_indices[i];
|
||||
if (curves.curves_range().contains(curve_i)) {
|
||||
indices_per_curve.add(curve_i, i);
|
||||
|
@ -414,13 +412,15 @@ class SampleCurveFunction : public mf::MultiFunction {
|
|||
else {
|
||||
invalid_indices.append(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
IndexMaskMemory memory;
|
||||
for (const int curve_i : indices_per_curve.keys()) {
|
||||
sample_curve(curve_i, IndexMask(indices_per_curve.lookup(curve_i)));
|
||||
sample_curve(curve_i,
|
||||
IndexMask::from_indices<int64_t>(indices_per_curve.lookup(curve_i), memory));
|
||||
}
|
||||
fill_invalid(IndexMask(invalid_indices));
|
||||
fill_invalid(IndexMask::from_indices<int64_t>(invalid_indices, memory));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,10 +63,12 @@ static void set_handle_type(bke::CurvesGeometry &curves,
|
|||
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
|
||||
|
||||
if (mode & GEO_NODE_CURVE_HANDLE_LEFT) {
|
||||
curves.handle_types_left_for_write().fill_indices(selection, new_handle_type);
|
||||
index_mask::masked_fill<int8_t>(
|
||||
curves.handle_types_left_for_write(), new_handle_type, selection);
|
||||
}
|
||||
if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) {
|
||||
curves.handle_types_right_for_write().fill_indices(selection, new_handle_type);
|
||||
index_mask::masked_fill<int8_t>(
|
||||
curves.handle_types_right_for_write(), new_handle_type, selection);
|
||||
}
|
||||
|
||||
/* Eagerly calculate automatically derived handle positions if necessary. */
|
||||
|
|
|
@ -74,16 +74,9 @@ static void save_selection_as_attribute(Mesh &mesh,
|
|||
MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
BLI_assert(!attributes.contains(id));
|
||||
|
||||
SpanAttributeWriter<bool> attribute = attributes.lookup_or_add_for_write_span<bool>(id, domain);
|
||||
/* Rely on the new attribute being zeroed by default. */
|
||||
BLI_assert(!attribute.span.as_span().contains(true));
|
||||
|
||||
if (selection.is_range()) {
|
||||
attribute.span.slice(selection.as_range()).fill(true);
|
||||
}
|
||||
else {
|
||||
attribute.span.fill_indices(selection, true);
|
||||
}
|
||||
SpanAttributeWriter<bool> attribute = attributes.lookup_or_add_for_write_only_span<bool>(id,
|
||||
domain);
|
||||
selection.to_bools(attribute.span);
|
||||
|
||||
attribute.finish();
|
||||
}
|
||||
|
@ -374,16 +367,19 @@ static void extrude_mesh_edges(Mesh &mesh,
|
|||
if (!edge_offsets.is_single()) {
|
||||
vert_offsets.reinitialize(orig_vert_size);
|
||||
attribute_math::DefaultPropagationMixer<float3> mixer(vert_offsets);
|
||||
for (const int i_edge : edge_selection) {
|
||||
edge_selection.foreach_index([&](const int i_edge) {
|
||||
const MEdge &edge = orig_edges[i_edge];
|
||||
const float3 offset = edge_offsets[i_edge];
|
||||
mixer.mix_in(edge.v1, offset);
|
||||
mixer.mix_in(edge.v2, offset);
|
||||
}
|
||||
});
|
||||
mixer.finalize();
|
||||
}
|
||||
|
||||
const VectorSet<int> new_vert_indices = vert_indices_from_edges(mesh, edge_selection.indices());
|
||||
Vector<int> edge_selection_indices(edge_selection.size());
|
||||
edge_selection.to_indices(edge_selection_indices.as_mutable_span());
|
||||
const VectorSet<int> new_vert_indices = vert_indices_from_edges<int>(mesh,
|
||||
edge_selection_indices);
|
||||
|
||||
const IndexRange new_vert_range{orig_vert_size, new_vert_indices.size()};
|
||||
/* The extruded edges connect the original and duplicate edges. */
|
||||
|
@ -642,10 +638,8 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
|||
return;
|
||||
}
|
||||
|
||||
Array<bool> poly_selection_array(orig_polys.size(), false);
|
||||
for (const int i_poly : poly_selection) {
|
||||
poly_selection_array[i_poly] = true;
|
||||
}
|
||||
Array<bool> poly_selection_array(orig_polys.size());
|
||||
poly_selection.to_bools(poly_selection_array);
|
||||
|
||||
/* Mix the offsets from the face domain to the vertex domain. Evaluate on the face domain above
|
||||
* in order to be consistent with the selection, and to use the face normals rather than vertex
|
||||
|
@ -654,13 +648,13 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
|||
if (!poly_offsets.is_single()) {
|
||||
vert_offsets.reinitialize(orig_vert_size);
|
||||
attribute_math::DefaultPropagationMixer<float3> mixer(vert_offsets);
|
||||
for (const int i_poly : poly_selection) {
|
||||
poly_selection.foreach_index([&](const int i_poly) {
|
||||
const MPoly &poly = orig_polys[i_poly];
|
||||
const float3 offset = poly_offsets[i_poly];
|
||||
for (const MLoop &loop : orig_loops.slice(poly.loopstart, poly.totloop)) {
|
||||
mixer.mix_in(loop.v, offset);
|
||||
}
|
||||
}
|
||||
});
|
||||
mixer.finalize();
|
||||
}
|
||||
|
||||
|
@ -672,12 +666,12 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
|||
* Start the size at one vert per poly to reduce unnecessary reallocation. */
|
||||
VectorSet<int> all_selected_verts;
|
||||
all_selected_verts.reserve(orig_polys.size());
|
||||
for (const int i_poly : poly_selection) {
|
||||
poly_selection.foreach_index([&](const int i_poly) {
|
||||
const MPoly &poly = orig_polys[i_poly];
|
||||
for (const MLoop &loop : orig_loops.slice(poly.loopstart, poly.totloop)) {
|
||||
all_selected_verts.add(loop.v);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Edges inside of an extruded region that are also attached to deselected edges. They must be
|
||||
* duplicated in order to leave the old edge attached to the unchanged deselected faces. */
|
||||
|
@ -806,7 +800,7 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
|||
}
|
||||
|
||||
/* Connect the selected faces to the extruded or duplicated edges and the new vertices. */
|
||||
for (const int i_poly : poly_selection) {
|
||||
poly_selection.foreach_index([&](const int i_poly) {
|
||||
const MPoly &poly = polys[i_poly];
|
||||
for (MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {
|
||||
const int i_new_vert = new_vert_indices.index_of_try(loop.v);
|
||||
|
@ -824,7 +818,7 @@ static void extrude_mesh_face_regions(Mesh &mesh,
|
|||
loop.e = new_inner_edge_range[i_new_inner_edge];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Create the faces on the sides of extruded regions. */
|
||||
for (const int i : boundary_edge_indices.index_range()) {
|
||||
|
|
|
@ -244,7 +244,7 @@ static Vector<ElementIsland> prepare_face_islands(const Mesh &mesh, const IndexM
|
|||
|
||||
/* Use the disjoint set data structure to determine which vertices have to be scaled together. */
|
||||
DisjointSet<int> disjoint_set(mesh.totvert);
|
||||
for (const int poly_index : face_selection) {
|
||||
face_selection.foreach_index([&](const int poly_index) {
|
||||
const MPoly &poly = polys[poly_index];
|
||||
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
|
||||
for (const int loop_index : IndexRange(poly.totloop - 1)) {
|
||||
|
@ -253,7 +253,7 @@ static Vector<ElementIsland> prepare_face_islands(const Mesh &mesh, const IndexM
|
|||
disjoint_set.join(v1, v2);
|
||||
}
|
||||
disjoint_set.join(poly_loops.first().v, poly_loops.last().v);
|
||||
}
|
||||
});
|
||||
|
||||
VectorSet<int> island_ids;
|
||||
Vector<ElementIsland> islands;
|
||||
|
@ -261,7 +261,7 @@ static Vector<ElementIsland> prepare_face_islands(const Mesh &mesh, const IndexM
|
|||
islands.reserve(face_selection.size());
|
||||
|
||||
/* Gather all of the face indices in each island into separate vectors. */
|
||||
for (const int poly_index : face_selection) {
|
||||
face_selection.foreach_index([&](const int poly_index) {
|
||||
const MPoly &poly = polys[poly_index];
|
||||
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
|
||||
const int island_id = disjoint_set.find_root(poly_loops[0].v);
|
||||
|
@ -271,7 +271,7 @@ static Vector<ElementIsland> prepare_face_islands(const Mesh &mesh, const IndexM
|
|||
}
|
||||
ElementIsland &island = islands[island_index];
|
||||
island.element_indices.append(poly_index);
|
||||
}
|
||||
});
|
||||
|
||||
return islands;
|
||||
}
|
||||
|
@ -340,10 +340,10 @@ static Vector<ElementIsland> prepare_edge_islands(const Mesh &mesh, const IndexM
|
|||
|
||||
/* Use the disjoint set data structure to determine which vertices have to be scaled together. */
|
||||
DisjointSet<int> disjoint_set(mesh.totvert);
|
||||
for (const int edge_index : edge_selection) {
|
||||
edge_selection.foreach_index([&](const int edge_index) {
|
||||
const MEdge &edge = edges[edge_index];
|
||||
disjoint_set.join(edge.v1, edge.v2);
|
||||
}
|
||||
});
|
||||
|
||||
VectorSet<int> island_ids;
|
||||
Vector<ElementIsland> islands;
|
||||
|
@ -351,7 +351,7 @@ static Vector<ElementIsland> prepare_edge_islands(const Mesh &mesh, const IndexM
|
|||
islands.reserve(edge_selection.size());
|
||||
|
||||
/* Gather all of the edge indices in each island into separate vectors. */
|
||||
for (const int edge_index : edge_selection) {
|
||||
edge_selection.foreach_index([&](const int edge_index) {
|
||||
const MEdge &edge = edges[edge_index];
|
||||
const int island_id = disjoint_set.find_root(edge.v1);
|
||||
const int island_index = island_ids.index_of_or_add(island_id);
|
||||
|
@ -360,7 +360,7 @@ static Vector<ElementIsland> prepare_edge_islands(const Mesh &mesh, const IndexM
|
|||
}
|
||||
ElementIsland &island = islands[island_index];
|
||||
island.element_indices.append(edge_index);
|
||||
}
|
||||
});
|
||||
|
||||
return islands;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ static void set_computed_position_and_offset(GeometryComponent &component,
|
|||
}
|
||||
}
|
||||
}
|
||||
const int grain_size = 10000;
|
||||
const GrainSize grain_size{10000};
|
||||
|
||||
switch (component.type()) {
|
||||
case GEO_COMPONENT_TYPE_CURVE: {
|
||||
|
@ -62,16 +62,13 @@ static void set_computed_position_and_offset(GeometryComponent &component,
|
|||
MutableVArraySpan<float3> out_positions_span = positions.varray;
|
||||
devirtualize_varray2(
|
||||
in_positions, in_offsets, [&](const auto in_positions, const auto in_offsets) {
|
||||
threading::parallel_for(
|
||||
selection.index_range(), grain_size, [&](const IndexRange range) {
|
||||
for (const int i : selection.slice(range)) {
|
||||
const float3 new_position = in_positions[i] + in_offsets[i];
|
||||
const float3 delta = new_position - out_positions_span[i];
|
||||
handle_right_attribute.span[i] += delta;
|
||||
handle_left_attribute.span[i] += delta;
|
||||
out_positions_span[i] = new_position;
|
||||
}
|
||||
});
|
||||
selection.foreach_index_optimized(grain_size, [&](const int i) {
|
||||
const float3 new_position = in_positions[i] + in_offsets[i];
|
||||
const float3 delta = new_position - out_positions_span[i];
|
||||
handle_right_attribute.span[i] += delta;
|
||||
handle_left_attribute.span[i] += delta;
|
||||
out_positions_span[i] = new_position;
|
||||
});
|
||||
});
|
||||
|
||||
out_positions_span.save();
|
||||
|
@ -90,23 +87,16 @@ static void set_computed_position_and_offset(GeometryComponent &component,
|
|||
MutableVArraySpan<float3> out_positions_span = positions.varray;
|
||||
if (positions_are_original) {
|
||||
devirtualize_varray(in_offsets, [&](const auto in_offsets) {
|
||||
threading::parallel_for(
|
||||
selection.index_range(), grain_size, [&](const IndexRange range) {
|
||||
for (const int i : selection.slice(range)) {
|
||||
out_positions_span[i] += in_offsets[i];
|
||||
}
|
||||
});
|
||||
selection.foreach_index_optimized(
|
||||
grain_size, [&](const int i) { out_positions_span[i] += in_offsets[i]; });
|
||||
});
|
||||
}
|
||||
else {
|
||||
devirtualize_varray2(
|
||||
in_positions, in_offsets, [&](const auto in_positions, const auto in_offsets) {
|
||||
threading::parallel_for(
|
||||
selection.index_range(), grain_size, [&](const IndexRange range) {
|
||||
for (const int i : selection.slice(range)) {
|
||||
out_positions_span[i] = in_positions[i] + in_offsets[i];
|
||||
}
|
||||
});
|
||||
selection.foreach_index_optimized(grain_size, [&](const int i) {
|
||||
out_positions_span[i] = in_positions[i] + in_offsets[i];
|
||||
});
|
||||
});
|
||||
}
|
||||
out_positions_span.save();
|
||||
|
|
|
@ -159,10 +159,10 @@ class ClampWrapperFunction : public mf::MultiFunction {
|
|||
/* This has actually been initialized in the call above. */
|
||||
MutableSpan<float> results = params.uninitialized_single_output<float>(output_param_index);
|
||||
|
||||
for (const int i : mask) {
|
||||
mask.foreach_index_optimized([&](const int i) {
|
||||
float &value = results[i];
|
||||
CLAMP(value, 0.0f, 1.0f);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -394,22 +394,20 @@ class MixColorFunction : public mf::MultiFunction {
|
|||
3, "Result");
|
||||
|
||||
if (clamp_factor_) {
|
||||
for (int64_t i : mask) {
|
||||
mask.foreach_index_optimized([&](const int64_t i) {
|
||||
results[i] = col1[i];
|
||||
ramp_blend(blend_type_, results[i], std::clamp(fac[i], 0.0f, 1.0f), col2[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
for (int64_t i : mask) {
|
||||
mask.foreach_index_optimized([&](const int64_t i) {
|
||||
results[i] = col1[i];
|
||||
ramp_blend(blend_type_, results[i], fac[i], col2[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (clamp_result_) {
|
||||
for (int64_t i : mask) {
|
||||
clamp_v3(results[i], 0.0f, 1.0f);
|
||||
}
|
||||
mask.foreach_index_optimized([&](const int64_t i) { clamp_v3(results[i], 0.0f, 1.0f); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -121,15 +121,13 @@ class MixRGBFunction : public mf::MultiFunction {
|
|||
MutableSpan<ColorGeometry4f> results = params.uninitialized_single_output<ColorGeometry4f>(
|
||||
3, "Color");
|
||||
|
||||
for (int64_t i : mask) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
results[i] = col1[i];
|
||||
ramp_blend(type_, results[i], clamp_f(fac[i], 0.0f, 1.0f), col2[i]);
|
||||
}
|
||||
});
|
||||
|
||||
if (clamp_) {
|
||||
for (int64_t i : mask) {
|
||||
clamp_v3(results[i], 0.0f, 1.0f);
|
||||
}
|
||||
mask.foreach_index([&](const int64_t i) { clamp_v3(results[i], 0.0f, 1.0f); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -51,12 +51,12 @@ class SeparateRGBFunction : public mf::MultiFunction {
|
|||
MutableSpan<float> gs = params.uninitialized_single_output<float>(2, "G");
|
||||
MutableSpan<float> bs = params.uninitialized_single_output<float>(3, "B");
|
||||
|
||||
for (int64_t i : mask) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
ColorGeometry4f color = colors[i];
|
||||
rs[i] = color.r;
|
||||
gs[i] = color.g;
|
||||
bs[i] = color.b;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue