Mesh: Lines index buffer creation improvement

Implements another phase of #116901, this time for the `lines` and
`lines_loose` index buffers that store indices for wireframe drawing.
The key improvement is removing loose edge's dependency on the main
edge index buffer. That means for the majority of meshes with no loose
edges, edge index extraction can be completely skipped. Even when there
are loose edges, only the loose edges need to be extracted with
wireframe turned off.

Besides that improvement, there are more changes to use data-oriented
code with visible hot loops instead of the virtual function call design
used for the existing mesh extractor system. For this step I completely
replaced the `extract_lines` object, which is in line with the general
plan for this area.

Additionally, hidden edge filtering is done ahead of time using several
`IndexMask` operations. This means only indices for visible edges need
to be uploaded to the GPU, and no restart index stripping needs to be
performed on macOS.

On my usual test file with 1.9 million vertices, I observed an
improvement from 26 to 33 FPS with wireframe off, and from 9.15 to 9.5
FPS with wireframe on.

Pull Request: https://projects.blender.org/blender/blender/pulls/120720
This commit is contained in:
Hans Goudey
2024-04-30 15:54:50 +02:00
parent 462e7aeedd
commit 1b7e67a851
7 changed files with 274 additions and 343 deletions

View File

@@ -113,7 +113,7 @@ struct MeshBufferList {
gpu::IndexBuf *tris;
/* Loose edges last. */
gpu::IndexBuf *lines;
/* Sub buffer of `lines` only containing the loose edges. */
/* Potentially a sub buffer of `lines` only containing the loose edges. */
gpu::IndexBuf *lines_loose;
gpu::IndexBuf *points;
gpu::IndexBuf *fdots;

View File

@@ -654,27 +654,6 @@ void mesh_buffer_cache_create_requested(TaskGraph *task_graph,
EXTRACT_ADD_REQUESTED(vbo, vnor);
EXTRACT_ADD_REQUESTED(ibo, tris);
if (DRW_ibo_requested(mbuflist->ibo.lines_loose)) {
/* `ibo.lines_loose` require the `ibo.lines` buffer. */
if (mbuflist->ibo.lines == nullptr) {
DRW_ibo_request(nullptr, &mbuflist->ibo.lines);
}
const MeshExtract *extractor = DRW_ibo_requested(mbuflist->ibo.lines) ?
&extract_lines_with_lines_loose :
&extract_lines_loose_only;
extractors.append(extractor);
}
else if (DRW_ibo_requested(mbuflist->ibo.lines)) {
const MeshExtract *extractor;
if (mbuflist->ibo.lines_loose != nullptr) {
/* Update `ibo.lines_loose` as it depends on `ibo.lines`. */
extractor = &extract_lines_with_lines_loose;
}
else {
extractor = &extract_lines;
}
extractors.append(extractor);
}
EXTRACT_ADD_REQUESTED(ibo, points);
EXTRACT_ADD_REQUESTED(ibo, fdots);
EXTRACT_ADD_REQUESTED(ibo, lines_paint_mask);
@@ -686,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;
}
@@ -721,6 +702,26 @@ void mesh_buffer_cache_create_requested(TaskGraph *task_graph,
/* Simple heuristic. */
const bool use_thread = (mr->corners_num + mr->loose_indices_num) > MIN_RANGE_LEN;
if (DRW_ibo_requested(mbuflist->ibo.lines) || DRW_ibo_requested(mbuflist->ibo.lines_loose)) {
struct LooseEdgedata {
MeshRenderData &mr;
MeshBufferList &buffers;
MeshBatchCache &cache;
};
TaskNode *task_node = BLI_task_graph_node_create(
task_graph,
[](void *__restrict task_data) {
const LooseEdgedata &data = *static_cast<LooseEdgedata *>(task_data);
extract_lines(data.mr,
data.buffers.ibo.lines,
data.buffers.ibo.lines_loose,
data.cache.no_loose_wire);
},
new LooseEdgedata{*mr, *mbuflist, cache},
[](void *task_data) { delete static_cast<LooseEdgedata *>(task_data); });
BLI_task_graph_edge_create(task_node_mesh_render_data, task_node);
}
if (use_thread) {
/* First run the requested extractors that do not support asynchronous ranges. */
for (const ExtractorRunData &run_data : extractors) {
@@ -830,27 +831,6 @@ void mesh_buffer_cache_create_requested_subdiv(MeshBatchCache &cache,
extractors.append(&extract_fdots_pos);
}
if (DRW_ibo_requested(mbuflist->ibo.lines_loose)) {
/* `ibo.lines_loose` require the `ibo.lines` buffer. */
if (mbuflist->ibo.lines == nullptr) {
DRW_ibo_request(nullptr, &mbuflist->ibo.lines);
}
const MeshExtract *extractor = DRW_ibo_requested(mbuflist->ibo.lines) ?
&extract_lines_with_lines_loose :
&extract_lines_loose_only;
extractors.append(extractor);
}
else if (DRW_ibo_requested(mbuflist->ibo.lines)) {
const MeshExtract *extractor;
if (mbuflist->ibo.lines_loose != nullptr) {
/* Update `ibo.lines_loose` as it depends on `ibo.lines`. */
extractor = &extract_lines_with_lines_loose;
}
else {
extractor = &extract_lines;
}
extractors.append(extractor);
}
EXTRACT_ADD_REQUESTED(ibo, edituv_points);
EXTRACT_ADD_REQUESTED(ibo, edituv_tris);
EXTRACT_ADD_REQUESTED(ibo, edituv_lines);
@@ -873,7 +853,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;
}
@@ -883,6 +865,9 @@ 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, cache.no_loose_wire);
void *data_stack = MEM_mallocN(extractors.data_size_total(), __func__);
uint32_t data_offset = 0;
for (const ExtractorRunData &run_data : extractors) {

View File

@@ -1576,7 +1576,6 @@ void DRW_mesh_batch_cache_create_requested(TaskGraph *task_graph,
}
assert_deps_valid(MBC_LOOSE_EDGES, {BUFFER_INDEX(ibo.lines_loose), BUFFER_INDEX(vbo.pos)});
if (DRW_batch_requested(cache.batch.loose_edges, GPU_PRIM_LINES)) {
DRW_ibo_request(nullptr, &mbuflist->ibo.lines);
DRW_ibo_request(cache.batch.loose_edges, &mbuflist->ibo.lines_loose);
DRW_vbo_request(cache.batch.loose_edges, &mbuflist->vbo.pos);
}

View File

@@ -1332,7 +1332,8 @@ static void draw_subdiv_init_ubo_storage(const DRWSubdivCache &cache,
const int src_offset,
const int dst_offset,
const uint total_dispatch_size,
const bool has_sculpt_mask)
const bool has_sculpt_mask,
const uint edge_loose_offset)
{
ubo->src_offset = src_offset;
ubo->dst_offset = dst_offset;
@@ -1342,7 +1343,7 @@ static void draw_subdiv_init_ubo_storage(const DRWSubdivCache &cache,
ubo->patches_are_triangular = cache.gpu_patch_map.patches_are_triangular;
ubo->coarse_face_count = cache.num_coarse_faces;
ubo->num_subdiv_loops = cache.num_subdiv_loops;
ubo->edge_loose_offset = cache.num_subdiv_loops * 2;
ubo->edge_loose_offset = edge_loose_offset;
ubo->has_sculpt_mask = has_sculpt_mask;
ubo->coarse_face_smooth_mask = SUBDIV_COARSE_FACE_FLAG_SMOOTH_MASK;
ubo->coarse_face_select_mask = SUBDIV_COARSE_FACE_FLAG_SELECT_MASK;
@@ -1359,11 +1360,17 @@ static void draw_subdiv_ubo_update_and_bind(const DRWSubdivCache &cache,
const int src_offset,
const int dst_offset,
const uint total_dispatch_size,
const bool has_sculpt_mask = false)
const bool has_sculpt_mask = false,
const uint edge_loose_offset = 0)
{
DRWSubdivUboStorage storage;
draw_subdiv_init_ubo_storage(
cache, &storage, src_offset, dst_offset, total_dispatch_size, has_sculpt_mask);
draw_subdiv_init_ubo_storage(cache,
&storage,
src_offset,
dst_offset,
total_dispatch_size,
has_sculpt_mask,
edge_loose_offset);
if (!cache.ubo) {
const_cast<DRWSubdivCache *>(&cache)->ubo = GPU_uniformbuf_create_ex(
@@ -1396,7 +1403,8 @@ static void drw_subdiv_compute_dispatch(const DRWSubdivCache &cache,
const int src_offset,
const int dst_offset,
uint total_dispatch_size,
const bool has_sculpt_mask = false)
const bool has_sculpt_mask = false,
const uint edge_loose_offset = 0)
{
const uint max_res_x = uint(GPU_max_work_group_count(0));
@@ -1423,8 +1431,13 @@ static void drw_subdiv_compute_dispatch(const DRWSubdivCache &cache,
* we presume it all fits. */
BLI_assert(dispatch_ry < uint(GPU_max_work_group_count(1)));
draw_subdiv_ubo_update_and_bind(
cache, shader, src_offset, dst_offset, total_dispatch_size, has_sculpt_mask);
draw_subdiv_ubo_update_and_bind(cache,
shader,
src_offset,
dst_offset,
total_dispatch_size,
has_sculpt_mask,
edge_loose_offset);
GPU_compute_dispatch(shader, dispatch_rx, dispatch_ry, 1);
}
@@ -1858,6 +1871,7 @@ void draw_subdiv_build_lines_buffer(const DRWSubdivCache &cache, gpu::IndexBuf *
void draw_subdiv_build_lines_loose_buffer(const DRWSubdivCache &cache,
gpu::IndexBuf *lines_indices,
gpu::VertBuf *lines_flags,
uint edge_loose_offset,
uint num_loose_edges)
{
GPUShader *shader = get_subdiv_shader(SHADER_BUFFER_LINES_LOOSE);
@@ -1866,7 +1880,7 @@ void draw_subdiv_build_lines_loose_buffer(const DRWSubdivCache &cache,
GPU_indexbuf_bind_as_ssbo(lines_indices, 3);
GPU_vertbuf_bind_as_ssbo(lines_flags, 4);
drw_subdiv_compute_dispatch(cache, shader, 0, 0, num_loose_edges);
drw_subdiv_compute_dispatch(cache, shader, 0, 0, num_loose_edges, false, edge_loose_offset);
/* This generates an index buffer, so we need to put a barrier on the element array. */
GPU_memory_barrier(GPU_BARRIER_ELEMENT_ARRAY);

View File

@@ -277,6 +277,7 @@ void draw_subdiv_build_lines_buffer(const DRWSubdivCache &cache, gpu::IndexBuf *
void draw_subdiv_build_lines_loose_buffer(const DRWSubdivCache &cache,
gpu::IndexBuf *lines_indices,
gpu::VertBuf *lines_flags,
uint edge_loose_offset,
uint num_loose_edges);
void draw_subdiv_build_fdots_buffers(const DRWSubdivCache &cache,

View File

@@ -337,10 +337,17 @@ 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(const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose,
bool &no_loose_wire);
void extract_lines_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose,
bool &no_loose_wire);
extern const MeshExtract extract_tris;
extern const MeshExtract extract_lines;
extern const MeshExtract extract_lines_with_lines_loose;
extern const MeshExtract extract_lines_loose_only;
extern const MeshExtract extract_points;
extern const MeshExtract extract_fdots;
extern const MeshExtract extract_lines_paint_mask;

View File

@@ -6,195 +6,216 @@
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "GPU_index_buffer.hh"
#include "extract_mesh.hh"
#include "draw_cache_inline.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
/* ---------------------------------------------------------------------- */
/** \name Extract Edges Indices
* \{ */
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_mesh_edge_visibility(const MeshRenderData &mr,
const IndexMask &mask,
IndexMaskMemory &memory)
{
if (!data->hide_edge.is_empty() && data->hide_edge[edge]) {
return false;
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);
}
if (data->e_origindex && data->e_origindex[edge] == ORIGINDEX_NONE) {
return false;
if (!mr.hide_edge.is_empty()) {
/* Like #IndexMask::from_bools but reversed. */
const Span<bool> hide_edge = mr.hide_edge;
visible = IndexMask::from_predicate(
visible, GrainSize(4096), memory, [&](const int64_t i) { return !hide_edge[i]; });
}
if (!data->optimal_display_edges.is_empty() && !data->optimal_display_edges[edge]) {
return false;
if (mr.hide_unmapped_edges && mr.e_origindex != nullptr) {
const int *orig_index = mr.e_origindex;
visible = IndexMask::from_predicate(visible, GrainSize(4096), memory, [&](const int64_t i) {
return orig_index[i] != ORIGINDEX_NONE;
});
}
return true;
return visible;
}
static void extract_lines_init(const MeshRenderData &mr,
MeshBatchCache & /*cache*/,
void * /*buf*/,
void *tls_data)
/* 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)
{
MeshExtract_LinesData *data = static_cast<MeshExtract_LinesData *>(tls_data);
/* Put loose edges at the end. */
GPU_indexbuf_init(&data->elb,
GPU_PRIM_LINES,
mr.edges_num + mr.loose_edges_num,
mr.corners_num + mr.loose_indices_num);
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>();
data->test_visibility = !data->optimal_display_edges.is_empty() || data->e_origindex ||
!data->hide_edge.is_empty();
}
const int corner_next = bke::mesh::face_corner_next(face, corner);
return uint2(corner, corner_next);
}
static void extract_lines_iter_face_bm(const MeshRenderData & /*mr*/,
const BMFace *f,
const int /*f_index*/,
void *tls_data)
static void fill_loose_lines_ibo(const uint corners_num, MutableSpan<uint2> data)
{
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);
/* Vertices for loose edges are not shared in the GPU vertex buffers, so the indices are simply
* an increasing contiguous range. Ideally this would be generated on the GPU itself, or just
* unnecessary, but a large number of loose edges isn't expected to be a common performance
* bottleneck either. */
threading::memory_bandwidth_bound_task(data.size_in_bytes(), [&]() {
array_utils::fill_index_range(data.cast<uint>(), corners_num);
});
}
static void extract_lines_iter_face_mesh(const MeshRenderData &mr,
const int face_index,
void *tls_data)
static void extract_lines_mesh(const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose,
bool &no_loose_wire)
{
MeshExtract_LinesData *data = static_cast<MeshExtract_LinesData *>(tls_data);
GPUIndexBufBuilder *elb = &data->elb;
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;
const IndexRange face = mr.faces[face_index];
no_loose_wire = visible_loose_edges.is_empty();
/* Using face & loop iterator would complicate accessing the adjacent loop. */
if (data->test_visibility) {
for (const int corner : face) {
const int edge = mr.corner_edges[corner];
const int corner_next = bke::mesh::face_corner_next(face, corner);
if (is_edge_visible(data, edge)) {
GPU_indexbuf_set_line_verts(elb, edge, corner, corner_next);
}
else {
GPU_indexbuf_set_line_restart(elb, edge);
}
}
}
else {
for (const int corner : face) {
const int edge = mr.corner_edges[corner];
const int corner_next = bke::mesh::face_corner_next(face, corner);
GPU_indexbuf_set_line_verts(elb, edge, corner, corner_next);
}
}
}
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);
}
else {
GPU_indexbuf_set_line_restart(elb, l_index_offset);
}
/* Don't render the edge twice. */
GPU_indexbuf_set_line_restart(elb, BM_elem_index_get(eed));
}
static void extract_lines_iter_loose_edge_mesh(const MeshRenderData &mr,
const int2 /*edge*/,
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;
const int e_index = mr.loose_edges[loose_edge_i];
if (is_edge_visible(data, e_index)) {
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);
}
else {
GPU_indexbuf_set_line_restart(elb, l_index_offset);
}
/* Don't render the edge twice. */
GPU_indexbuf_set_line_restart(elb, e_index);
}
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*/)
{
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);
if (subdiv_cache.num_subdiv_loops == 0) {
if (DRW_ibo_requested(lines_loose) && !DRW_ibo_requested(lines)) {
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, visible_loose_edges.size(), max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
fill_loose_lines_ibo(mr.corners_num, data);
GPU_indexbuf_build_in_place_ex(&builder, 0, max_index, false, lines_loose);
return;
}
draw_subdiv_build_lines_buffer(subdiv_cache, ibo);
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_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_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(), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
for (const int corner : face) {
const int edge = corner_edges[corner];
/* Access to `used` don't need to be atomic because any of the possible face corner
* indices from `edge_from_corners` are correct, since they all correspond to the
* same #Mesh vertex. `used` only exists here as a performance optimization to
* avoid writing to the VBO unnecessarily. */
if (used[edge]) {
continue;
}
data[edge] = edge_from_corners(face, corner);
used[edge] = true;
}
}
});
});
}
else {
Array<int> map(mr.corners_num, -1);
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_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) {
const int edge = corner_edges[corner];
if (map[edge] == -1) {
continue;
}
data[map[edge]] = edge_from_corners(face, corner);
map[edge] = -1;
}
}
});
});
}
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 IndexMask calc_bm_edge_visibility(BMesh &bm, const IndexMask &mask, IndexMaskMemory &memory)
{
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_bm(const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose,
bool &no_loose_wire)
{
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;
no_loose_wire = visible_loose_edges.is_empty();
const IndexMask non_loose_edges = all_loose_edges.complement(IndexRange(bm.totedge), 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,
visible_non_loose_edges.size() + visible_loose_edges.size(),
max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
/* Make use of BMesh's edge to loop topology knowledge to iterate over edges instead of
* iterating over faces and defining edges implicitly as done in the #Mesh extraction. */
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));
});
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);
}
}
void extract_lines(const MeshRenderData &mr,
gpu::IndexBuf *lines,
gpu::IndexBuf *lines_loose,
bool &no_loose_wire)
{
if (mr.extract_type == MR_EXTRACT_MESH) {
extract_lines_mesh(mr, lines, lines_loose, no_loose_wire);
}
else {
extract_lines_bm(mr, lines, lines_loose, no_loose_wire);
}
}
static void extract_lines_loose_geom_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
void *buffer,
void * /*data*/)
const int edge_loose_offset,
gpu::IndexBuf *ibo)
{
const DRWSubdivLooseGeom &loose_geom = subdiv_cache.loose_geom;
if (loose_geom.edge_len == 0) {
@@ -267,137 +288,41 @@ 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));
draw_subdiv_build_lines_loose_buffer(
subdiv_cache, ibo, flags, uint(edge_loose_offset), 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,
bool &no_loose_wire)
{
MeshExtract extractor = {nullptr};
extractor.init = extract_lines_init;
extractor.iter_face_bm = extract_lines_iter_face_bm;
extractor.iter_face_mesh = extract_lines_iter_face_mesh;
extractor.iter_loose_edge_bm = extract_lines_iter_loose_edge_bm;
extractor.iter_loose_edge_mesh = extract_lines_iter_loose_edge_mesh;
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;
const DRWSubdivLooseGeom &loose_geom = subdiv_cache.loose_geom;
const int loose_num = loose_geom.edge_len * 2;
no_loose_wire = loose_num == 0;
if (subdiv_cache.num_subdiv_loops == 0) {
return;
}
if (DRW_ibo_requested(lines_loose) && !DRW_ibo_requested(lines)) {
GPU_indexbuf_init_build_on_device(lines_loose, loose_num);
extract_lines_loose_geom_subdiv(subdiv_cache, mr, 0, lines_loose);
return;
}
const int non_loose_num = subdiv_cache.num_subdiv_loops * 2;
GPU_indexbuf_init_build_on_device(lines, non_loose_num + loose_num);
draw_subdiv_build_lines_buffer(subdiv_cache, lines);
extract_lines_loose_geom_subdiv(subdiv_cache, mr, non_loose_num, lines);
if (DRW_ibo_requested(lines_loose)) {
/* Multiply by 2 because these are edges indices. */
GPU_indexbuf_create_subrange_in_place(lines_loose, lines, non_loose_num, loose_num);
}
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \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_face_mesh = extract_lines_iter_face_mesh;
extractor.iter_loose_edge_bm = extract_lines_iter_loose_edge_bm;
extractor.iter_loose_edge_mesh = extract_lines_iter_loose_edge_mesh;
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();
const MeshExtract extract_lines_with_lines_loose = create_extractor_lines_with_lines_loose();
const MeshExtract extract_lines_loose_only = create_extractor_lines_loose_only();
} // namespace blender::draw