Mesh: Lines index buffer creation improvement #120720

Merged
Hans Goudey merged 66 commits from HooglyBoogly/blender:mesh-extract-lines-fix into main 2024-04-30 15:55:01 +02:00
5 changed files with 111 additions and 277 deletions
Showing only changes of commit eeb0a18140 - Show all commits

View File

@ -293,8 +293,6 @@ struct MeshBatchCache {
* Only valid after `DRW_mesh_batch_cache_create_requested` has been called. */
float tot_area, tot_uv_area;
bool no_loose_wire = false;
eV3DShadingColorType color_type;
bool pbvh_is_drawing;
};

View File

@ -665,7 +665,9 @@ void mesh_buffer_cache_create_requested(TaskGraph *task_graph,
#undef EXTRACT_ADD_REQUESTED
if (extractors.is_empty()) {
if (extractors.is_empty() && !DRW_ibo_requested(mbuflist->ibo.lines) &&
!DRW_ibo_requested(mbuflist->ibo.lines_loose))
{
return;
}
@ -709,7 +711,7 @@ void mesh_buffer_cache_create_requested(TaskGraph *task_graph,
task_graph,
[](void *__restrict task_data) {
const LooseEdgedata &data = *static_cast<LooseEdgedata *>(task_data);
extract_lines_mesh(data.mr, data.buffers.ibo.lines, data.buffers.ibo.lines_loose);
extract_lines(data.mr, data.buffers.ibo.lines, data.buffers.ibo.lines_loose);
},
new LooseEdgedata{*mr, *mbuflist},
[](void *task_data) { delete static_cast<LooseEdgedata *>(task_data); });
@ -825,9 +827,6 @@ void mesh_buffer_cache_create_requested_subdiv(MeshBatchCache &cache,
extractors.append(&extract_fdots_pos);
}
if (DRW_ibo_requested(mbuflist->ibo.lines_loose) || DRW_ibo_requested(mbuflist->ibo.lines)) {
extractors.append(&extract_lines);
}
EXTRACT_ADD_REQUESTED(ibo, edituv_points);
EXTRACT_ADD_REQUESTED(ibo, edituv_tris);
EXTRACT_ADD_REQUESTED(ibo, edituv_lines);
@ -850,7 +849,9 @@ void mesh_buffer_cache_create_requested_subdiv(MeshBatchCache &cache,
#undef EXTRACT_ADD_REQUESTED
if (extractors.is_empty()) {
if (extractors.is_empty() && !DRW_ibo_requested(mbuflist->ibo.lines) &&
!DRW_ibo_requested(mbuflist->ibo.lines_loose))
{
return;
}
@ -860,6 +861,8 @@ void mesh_buffer_cache_create_requested_subdiv(MeshBatchCache &cache,
mr, mbc, MR_ITER_LOOSE_EDGE | MR_ITER_LOOSE_VERT, MR_DATA_LOOSE_GEOM);
DRW_subdivide_loose_geom(&subdiv_cache, &mbc);
extract_lines_subdiv(subdiv_cache, mr, mbuflist->ibo.lines, mbuflist->ibo.lines_loose);
void *data_stack = MEM_mallocN(extractors.data_size_total(), __func__);
uint32_t data_offset = 0;
for (const ExtractorRunData &run_data : extractors) {

View File

@ -912,7 +912,7 @@ gpu::Batch *DRW_mesh_batch_cache_get_loose_edges(Mesh *mesh)
{
MeshBatchCache &cache = *mesh_batch_cache_get(mesh);
mesh_batch_cache_add_request(cache, MBC_LOOSE_EDGES);
if (cache.no_loose_wire) {
if (cache.final.loose_geom.edges.is_empty()) {
return nullptr;
}

View File

@ -337,12 +337,13 @@ void mesh_render_data_loop_edge_flag(const MeshRenderData &mr,
template<typename GPUType>
void extract_vert_normals(const MeshRenderData &mr, MutableSpan<GPUType> normals);
void extract_lines_mesh(const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose);
void extract_lines(const MeshRenderData &mr, gpu::IndexBuf *lines, gpu::IndexBuf *lines_loose);
void extract_lines_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose);
extern const MeshExtract extract_tris;
extern const MeshExtract extract_lines;
extern const MeshExtract extract_points;
extern const MeshExtract extract_fdots;
extern const MeshExtract extract_lines_paint_mask;

View File

@ -6,6 +6,8 @@
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "GPU_index_buffer.hh"
#include "extract_mesh.hh"
@ -15,15 +17,11 @@
namespace blender::draw {
/* ---------------------------------------------------------------------- */
/** \name Extract Edges Indices
* \{ */
static IndexMask calc_edge_visibility(const MeshRenderData &mr,
const IndexMask &start_mask,
IndexMaskMemory &memory)
static IndexMask calc_mesh_edge_visibility(const MeshRenderData &mr,
const IndexMask &mask,
IndexMaskMemory &memory)
{
IndexMask visible = start_mask;
IndexMask visible = mask;
if (!mr.mesh->runtime->subsurf_optimal_display_edges.is_empty()) {
const BoundedBitSpan visible_bits = mr.mesh->runtime->subsurf_optimal_display_edges;
visible = IndexMask::from_bits(visible, visible_bits, memory);
@ -40,15 +38,6 @@ static IndexMask calc_edge_visibility(const MeshRenderData &mr,
return visible;
}
static IndexMask calc_loose_edge_visibility(const MeshRenderData &mr, IndexMaskMemory &memory)
{
return calc_edge_visibility(mr, IndexMask::from_indices(mr.loose_edges, memory), memory);
}
static IndexMask calc_edge_visibility(const MeshRenderData &mr, IndexMaskMemory &memory)
{
return calc_edge_visibility(mr, IndexMask(mr.edges_num), memory);
}
/* In the GPU vertex buffers, the value for each vertex is duplicated to each of its vertex
* corners. So the edges on the GPU connect face corners rather than vertices. */
static uint2 edge_from_corners(const IndexRange face, const int corner)
@ -57,57 +46,53 @@ static uint2 edge_from_corners(const IndexRange face, const int corner)
return uint2(corner, corner_next);
}
static void fill_loose_lines_ibo(const int corners_num, MutableSpan<uint2> data)
static void fill_loose_lines_ibo(const uint corners_num, MutableSpan<uint2> data)
{
threading::memory_bandwidth_bound_task(data.size_in_bytes(), [&]() {
threading::parallel_for(data.index_range(), 4096, [&](const IndexRange range) {
for (const int loose_edge : range) {
const int index = corners_num + loose_edge * 2;
data[loose_edge] = uint2(index, index + 1);
}
});
array_utils::fill_index_range(data.cast<uint>(), corners_num);
});
}
void extract_lines_mesh(const MeshRenderData &mr, gpu::IndexBuf *lines, gpu::IndexBuf *lines_loose)
static void extract_lines_mesh(const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose)
{
IndexMaskMemory memory;
const IndexMask all_loose_edges = IndexMask::from_indices(mr.loose_edges, memory);
const IndexMask visible_loose_edges = calc_mesh_edge_visibility(mr, all_loose_edges, memory);
const int max_index = mr.corners_num + visible_loose_edges.size() * 2;
if (DRW_ibo_requested(lines_loose) && !DRW_ibo_requested(lines)) {
IndexMaskMemory memory;
const IndexMask visible = calc_loose_edge_visibility(mr, memory);
GPUIndexBufBuilder builder;
GPU_indexbuf_init(
&builder, GPU_PRIM_LINES, visible.size(), mr.corners_num + mr.loose_indices_num);
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, visible_loose_edges.size(), max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
BLI_assert(data.size() == visible.size());
fill_loose_lines_ibo(mr.corners_num, data);
GPU_indexbuf_build_in_place_ex(
&builder, 0, mr.corners_num + visible.size() * 2, false, lines_loose);
GPU_indexbuf_build_in_place_ex(&builder, 0, max_index, false, lines_loose);
return;
}
IndexMaskMemory memory;
const IndexMask visible = calc_edge_visibility(mr, memory);
// TODO: Also add loose edges here.
const IndexMask non_loose_edges = all_loose_edges.complement(mr.edges.index_range(), memory);
const IndexMask visible_non_loose_edges = calc_mesh_edge_visibility(mr, non_loose_edges, memory);
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, visible.size(), mr.corners_num);
GPU_indexbuf_init(&builder,
GPU_PRIM_LINES,
visible_non_loose_edges.size() + visible_loose_edges.size(),
max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
const OffsetIndices faces = mr.faces;
const Span<int> corner_edges = mr.corner_edges;
if (visible.size() == mr.edges_num) {
/* Use separate boolean array to avoid writing to the same indices again multiple times,
* possibly from different threads. This is slightly beneficial because booleans are 8 times
* smaller than the `uint2` for each edge. */
if (visible_non_loose_edges.size() == mr.edges_num) {
/* All edges in the mesh are visible. The edges in the GPU buffer will have the same order as
* the mesh's edges, so any remapping is unnecessary. Use a boolean array to avoid writing to
* the same indices again multiple times from different threads. This is slightly beneficial
* because booleans are 8 times smaller than the `uint2` for each edge. */
Array<bool> used(mr.edges_num, false);
threading::memory_bandwidth_bound_task(
used.as_span().size_in_bytes() + data.size_in_bytes() + corner_edges.size_in_bytes(),
[&]() {
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
for (const int corner : face) {
@ -127,8 +112,8 @@ void extract_lines_mesh(const MeshRenderData &mr, gpu::IndexBuf *lines, gpu::Ind
threading::memory_bandwidth_bound_task(
map.as_span().size_in_bytes() + data.size_in_bytes() + corner_edges.size_in_bytes(),
[&]() {
index_mask::build_reverse_map(visible, map.as_mutable_span());
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
index_mask::build_reverse_map(visible_non_loose_edges, map.as_mutable_span());
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
for (const int corner : face) {
@ -144,122 +129,70 @@ void extract_lines_mesh(const MeshRenderData &mr, gpu::IndexBuf *lines, gpu::Ind
});
}
GPU_indexbuf_build_in_place_ex(&builder, 0, mr.corners_num + visible.size() * 2, false, lines);
return;
fill_loose_lines_ibo(mr.corners_num, data.take_back(visible_loose_edges.size()));
GPU_indexbuf_build_in_place_ex(&builder, 0, max_index, false, lines);
if (DRW_ibo_requested(lines_loose)) {
GPU_indexbuf_create_subrange_in_place(
lines_loose, lines, visible_non_loose_edges.size() * 2, visible_loose_edges.size() * 2);
}
}
struct MeshExtract_LinesData {
GPUIndexBufBuilder elb;
BitSpan optimal_display_edges;
const int *e_origindex;
Span<bool> hide_edge;
bool test_visibility;
};
BLI_INLINE bool is_edge_visible(const MeshExtract_LinesData *data, const int edge)
static IndexMask calc_bm_edge_visibility(BMesh &bm, const IndexMask &mask, IndexMaskMemory &memory)
{
if (!data->hide_edge.is_empty() && data->hide_edge[edge]) {
return false;
}
if (data->e_origindex && data->e_origindex[edge] == ORIGINDEX_NONE) {
return false;
}
if (!data->optimal_display_edges.is_empty() && !data->optimal_display_edges[edge]) {
return false;
}
return true;
return IndexMask::from_predicate(mask, GrainSize(2048), memory, [&](const int i) {
return !BM_elem_flag_test_bool(BM_edge_at_index(&bm, i), BM_ELEM_HIDDEN);
});
}
static void extract_lines_init(const MeshRenderData &mr,
MeshBatchCache & /*cache*/,
void * /*buf*/,
void *tls_data)
static void extract_lines_bm(const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose)
{
MeshExtract_LinesData *data = static_cast<MeshExtract_LinesData *>(tls_data);
/* Put loose edges at the end. */
GPU_indexbuf_init(&data->elb,
BMesh &bm = *mr.bm;
IndexMaskMemory memory;
const IndexMask all_loose_edges = IndexMask::from_indices(mr.loose_edges, memory);
const IndexMask visible_loose_edges = calc_bm_edge_visibility(bm, all_loose_edges, memory);
const int max_index = mr.corners_num + visible_loose_edges.size() * 2;
const IndexMask non_loose_edges = all_loose_edges.complement(IndexRange(bm.totvert), memory);
const IndexMask visible_non_loose_edges = calc_bm_edge_visibility(bm, non_loose_edges, memory);
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder,
GPU_PRIM_LINES,
mr.edges_num + mr.loose_edges_num,
mr.corners_num + mr.loose_indices_num);
visible_non_loose_edges.size() + visible_loose_edges.size(),
max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
if (mr.extract_type == MR_EXTRACT_MESH) {
data->optimal_display_edges = mr.mesh->runtime->subsurf_optimal_display_edges;
data->e_origindex = mr.hide_unmapped_edges ? mr.e_origindex : nullptr;
data->hide_edge = mr.use_hide ? Span(mr.hide_edge) : Span<bool>();
visible_non_loose_edges.foreach_index(GrainSize(4096), [&](const int i, const int pos) {
const BMEdge &edge = *BM_edge_at_index(&bm, i);
data[pos] = uint2(BM_elem_index_get(edge.l), BM_elem_index_get(edge.l->next));
});
data->test_visibility = !data->optimal_display_edges.is_empty() || data->e_origindex ||
!data->hide_edge.is_empty();
fill_loose_lines_ibo(mr.corners_num, data.take_back(visible_loose_edges.size()));
GPU_indexbuf_build_in_place_ex(&builder, 0, max_index, false, lines);
if (DRW_ibo_requested(lines_loose)) {
GPU_indexbuf_create_subrange_in_place(
lines_loose, lines, visible_non_loose_edges.size() * 2, visible_loose_edges.size() * 2);
}
}
static void extract_lines_iter_face_bm(const MeshRenderData & /*mr*/,
const BMFace *f,
const int /*f_index*/,
void *tls_data)
void extract_lines(const MeshRenderData &mr, gpu::IndexBuf *lines, gpu::IndexBuf *lines_loose)
{
MeshExtract_LinesData *data = static_cast<MeshExtract_LinesData *>(tls_data);
GPUIndexBufBuilder *elb = &data->elb;
BMLoop *l_iter, *l_first;
/* Use #BMLoop.prev to match mesh order (to avoid minor differences in data extraction). */
l_iter = l_first = BM_FACE_FIRST_LOOP(f)->prev;
do {
if (!BM_elem_flag_test(l_iter->e, BM_ELEM_HIDDEN)) {
GPU_indexbuf_set_line_verts(elb,
BM_elem_index_get(l_iter->e),
BM_elem_index_get(l_iter),
BM_elem_index_get(l_iter->next));
}
else {
GPU_indexbuf_set_line_restart(elb, BM_elem_index_get(l_iter->e));
}
} while ((l_iter = l_iter->next) != l_first);
}
static void extract_lines_iter_loose_edge_bm(const MeshRenderData &mr,
const BMEdge *eed,
const int loose_edge_i,
void *tls_data)
{
MeshExtract_LinesData *data = static_cast<MeshExtract_LinesData *>(tls_data);
GPUIndexBufBuilder *elb = &data->elb;
const int l_index_offset = mr.edges_num + loose_edge_i;
if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) {
const int l_index = mr.corners_num + loose_edge_i * 2;
GPU_indexbuf_set_line_verts(elb, l_index_offset, l_index, l_index + 1);
if (mr.extract_type == MR_EXTRACT_MESH) {
extract_lines_mesh(mr, lines, lines_loose);
}
else {
GPU_indexbuf_set_line_restart(elb, l_index_offset);
extract_lines_bm(mr, lines, lines_loose);
}
/* Don't render the edge twice. */
GPU_indexbuf_set_line_restart(elb, BM_elem_index_get(eed));
}
static void extract_lines_task_reduce(void *_userdata_to, void *_userdata_from)
{
GPUIndexBufBuilder *elb_to = static_cast<GPUIndexBufBuilder *>(_userdata_to);
GPUIndexBufBuilder *elb_from = static_cast<GPUIndexBufBuilder *>(_userdata_from);
GPU_indexbuf_join(elb_to, elb_from);
}
static void extract_lines_finish(const MeshRenderData & /*mr*/,
MeshBatchCache & /*cache*/,
void *buf,
void *tls_data)
{
MeshExtract_LinesData *data = static_cast<MeshExtract_LinesData *>(tls_data);
GPUIndexBufBuilder *elb = &data->elb;
gpu::IndexBuf *ibo = static_cast<gpu::IndexBuf *>(buf);
GPU_indexbuf_build_in_place(elb, ibo);
}
static void extract_lines_init_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData & /*mr*/,
MeshBatchCache & /*cache*/,
void *buffer,
void * /*data*/)
static void generate_subdiv_lines(const DRWSubdivCache &subdiv_cache, gpu::IndexBuf *ibo)
{
const DRWSubdivLooseGeom &loose_geom = subdiv_cache.loose_geom;
gpu::IndexBuf *ibo = static_cast<gpu::IndexBuf *>(buffer);
GPU_indexbuf_init_build_on_device(ibo,
subdiv_cache.num_subdiv_loops * 2 + loose_geom.edge_len * 2);
@ -272,8 +205,7 @@ static void extract_lines_init_subdiv(const DRWSubdivCache &subdiv_cache,
static void extract_lines_loose_geom_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
void *buffer,
void * /*data*/)
gpu::IndexBuf *ibo)
{
const DRWSubdivLooseGeom &loose_geom = subdiv_cache.loose_geom;
if (loose_geom.edge_len == 0) {
@ -346,131 +278,31 @@ static void extract_lines_loose_geom_subdiv(const DRWSubdivCache &subdiv_cache,
}
}
gpu::IndexBuf *ibo = static_cast<gpu::IndexBuf *>(buffer);
draw_subdiv_build_lines_loose_buffer(subdiv_cache, ibo, flags, uint(loose_geom.edge_len));
GPU_vertbuf_discard(flags);
}
constexpr MeshExtract create_extractor_lines()
void extract_lines_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose)
{
MeshExtract extractor = {nullptr};
extractor.init = extract_lines_init;
extractor.iter_face_bm = extract_lines_iter_face_bm;
extractor.iter_loose_edge_bm = extract_lines_iter_loose_edge_bm;
extractor.init_subdiv = extract_lines_init_subdiv;
extractor.iter_loose_geom_subdiv = extract_lines_loose_geom_subdiv;
extractor.task_reduce = extract_lines_task_reduce;
extractor.finish = extract_lines_finish;
extractor.data_type = MR_DATA_NONE;
extractor.data_size = sizeof(MeshExtract_LinesData);
extractor.use_threading = true;
extractor.mesh_buffer_offset = offsetof(MeshBufferList, ibo.lines);
return extractor;
if (DRW_ibo_requested(lines_loose) && !DRW_ibo_requested(lines)) {
// TODO: Fix `edge_loose_offset`
extract_lines_loose_geom_subdiv(subdiv_cache, mr, lines_loose);
return;
}
generate_subdiv_lines(subdiv_cache, lines);
extract_lines_loose_geom_subdiv(subdiv_cache, mr, lines);
if (DRW_ibo_requested(lines_loose)) {
/* Multiply by 2 because these are edges indices. */
const int start = subdiv_cache.num_subdiv_loops * 2;
const int len = subdiv_cache.loose_geom.edge_len * 2;
GPU_indexbuf_create_subrange_in_place(lines_loose, lines, start, len);
}
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Extract Lines and Loose Edges Sub Buffer
* \{ */
static void extract_lines_loose_subbuffer(const MeshRenderData &mr, MeshBatchCache &cache)
{
BLI_assert(cache.final.buff.ibo.lines);
/* Multiply by 2 because these are edges indices. */
const int start = mr.edges_num * 2;
const int len = mr.loose_edges_num * 2;
GPU_indexbuf_create_subrange_in_place(
cache.final.buff.ibo.lines_loose, cache.final.buff.ibo.lines, start, len);
cache.no_loose_wire = (len == 0);
}
static void extract_lines_with_lines_loose_finish(const MeshRenderData &mr,
MeshBatchCache &cache,
void *buf,
void *tls_data)
{
MeshExtract_LinesData *data = static_cast<MeshExtract_LinesData *>(tls_data);
GPUIndexBufBuilder *elb = &data->elb;
gpu::IndexBuf *ibo = static_cast<gpu::IndexBuf *>(buf);
GPU_indexbuf_build_in_place(elb, ibo);
extract_lines_loose_subbuffer(mr, cache);
}
static void extract_lines_with_lines_loose_finish_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData & /*mr*/,
MeshBatchCache &cache,
void * /*buf*/,
void * /*_data*/)
{
/* Multiply by 2 because these are edges indices. */
const int start = subdiv_cache.num_subdiv_loops * 2;
const int len = subdiv_cache.loose_geom.edge_len * 2;
GPU_indexbuf_create_subrange_in_place(
cache.final.buff.ibo.lines_loose, cache.final.buff.ibo.lines, start, len);
cache.no_loose_wire = (len == 0);
}
constexpr MeshExtract create_extractor_lines_with_lines_loose()
{
MeshExtract extractor = {nullptr};
extractor.init = extract_lines_init;
extractor.iter_face_bm = extract_lines_iter_face_bm;
extractor.iter_loose_edge_bm = extract_lines_iter_loose_edge_bm;
extractor.task_reduce = extract_lines_task_reduce;
extractor.finish = extract_lines_with_lines_loose_finish;
extractor.init_subdiv = extract_lines_init_subdiv;
extractor.iter_loose_geom_subdiv = extract_lines_loose_geom_subdiv;
extractor.finish_subdiv = extract_lines_with_lines_loose_finish_subdiv;
extractor.data_type = MR_DATA_NONE;
extractor.data_size = sizeof(MeshExtract_LinesData);
extractor.use_threading = true;
extractor.mesh_buffer_offset = offsetof(MeshBufferList, ibo.lines);
return extractor;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Extract Loose Edges Sub Buffer
* \{ */
static void extract_lines_loose_only_init(const MeshRenderData &mr,
MeshBatchCache &cache,
void *buf,
void * /*tls_data*/)
{
BLI_assert(buf == cache.final.buff.ibo.lines_loose);
UNUSED_VARS_NDEBUG(buf);
extract_lines_loose_subbuffer(mr, cache);
}
static void extract_lines_loose_only_init_subdiv(const DRWSubdivCache & /*subdiv_cache*/,
const MeshRenderData &mr,
MeshBatchCache &cache,
void *buffer,
void * /*data*/)
{
BLI_assert(buffer == cache.final.buff.ibo.lines_loose);
UNUSED_VARS_NDEBUG(buffer);
extract_lines_loose_subbuffer(mr, cache);
}
constexpr MeshExtract create_extractor_lines_loose_only()
{
MeshExtract extractor = {nullptr};
extractor.init = extract_lines_loose_only_init;
extractor.init_subdiv = extract_lines_loose_only_init_subdiv;
extractor.data_type = MR_DATA_LOOSE_GEOM;
extractor.data_size = 0;
extractor.use_threading = false;
extractor.mesh_buffer_offset = offsetof(MeshBufferList, ibo.lines_loose);
return extractor;
}
/** \} */
const MeshExtract extract_lines = create_extractor_lines();
} // namespace blender::draw