Blender V5.0
overlay_wireframe.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#pragma once
10
11#include "BKE_paint.hh"
12#include "DNA_volume_types.h"
13
14#include "DRW_render.hh"
15#include "draw_common.hh"
16#include "draw_sculpt.hh"
17
18#include "overlay_base.hh"
19#include "overlay_mesh.hh"
20
21namespace blender::draw::overlay {
22
33 private:
34 PassMain wireframe_ps_ = {"Wireframe"};
35 struct ColoringPass {
36 PassMain::Sub *curves_ps_ = nullptr;
37 PassMain::Sub *mesh_ps_ = nullptr;
38 PassMain::Sub *pointcloud_ps_ = nullptr;
39 /* Variant for meshes that force drawing all edges. */
40 PassMain::Sub *mesh_all_edges_ps_ = nullptr;
41 } colored, non_colored;
42
43 /* Copy of the depth buffer to be able to read it during wireframe rendering. */
44 TextureFromPool tmp_depth_tx_ = {"tmp_depth_tx"};
45 bool do_depth_copy_workaround_ = false;
46
47 /* Force display of wireframe on surface objects, regardless of the object display settings. */
48 bool show_wire_ = false;
49
50 public:
51 void begin_sync(Resources &res, const State &state) final
52 {
53 enabled_ = state.is_space_v3d() && (state.is_wireframe_mode || !state.hide_overlays);
54 if (!enabled_) {
55 return;
56 }
57
58 show_wire_ = state.is_wireframe_mode || state.show_wireframes();
59
60 const bool is_selection = res.is_selection();
61 const bool do_smooth_lines = (U.gpu_flag & USER_GPU_FLAG_OVERLAY_SMOOTH_WIRE) != 0;
62 const bool is_transform = (G.moving & G_TRANSFORM_OBJ) != 0;
63 const float wire_threshold = wire_discard_threshold_get(state.overlay.wireframe_threshold);
64
65 gpu::Texture **depth_tex = (state.xray_enabled) ? &res.depth_tx : &tmp_depth_tx_;
66 if (is_selection) {
67 depth_tex = &res.dummy_depth_tx;
68 }
69
70 /* Note: Depth buffer has different format when doing selection. Avoid copy in this case. */
71 do_depth_copy_workaround_ = !is_selection && (depth_tex == &tmp_depth_tx_);
72
73 {
74 auto &pass = wireframe_ps_;
75 pass.init();
76 pass.bind_ubo(OVERLAY_GLOBALS_SLOT, &res.globals_buf);
77 pass.bind_ubo(DRW_CLIPPING_UBO_SLOT, &res.clip_planes_buf);
80 state.clipping_plane_count);
81 res.select_bind(pass);
82
83 auto shader_pass =
84 [&](gpu::Shader *shader, const char *name, bool use_coloring, float wire_threshold) {
85 auto &sub = pass.sub(name);
86 if (res.shaders->wireframe_mesh.get() == shader) {
87 sub.specialize_constant(shader, "use_custom_depth_bias", do_smooth_lines);
88 }
89 sub.shader_set(shader);
90 sub.bind_texture("depth_tx", depth_tex);
91 sub.push_constant("wire_opacity", state.overlay.wireframe_opacity);
92 sub.push_constant("is_transform", is_transform);
93 sub.push_constant("color_type", state.v3d->shading.wire_color_type);
94 sub.push_constant("use_coloring", use_coloring);
95 sub.push_constant("wire_step_param", wire_threshold);
96 sub.push_constant("ndc_offset_factor", &state.ndc_offset_factor);
97 sub.push_constant("is_hair", false);
98 return ⊂
99 };
100
101 auto coloring_pass = [&](ColoringPass &ps, bool use_color) {
102 overlay::ShaderModule &sh = *res.shaders;
103 ps.mesh_ps_ = shader_pass(sh.wireframe_mesh.get(), "Mesh", use_color, wire_threshold);
104 ps.mesh_all_edges_ps_ = shader_pass(sh.wireframe_mesh.get(), "Wire", use_color, 1.0f);
105 ps.pointcloud_ps_ = shader_pass(sh.wireframe_points.get(), "PtCloud", use_color, 1.0f);
106 ps.curves_ps_ = shader_pass(sh.wireframe_curve.get(), "Curve", use_color, 1.0f);
107 };
108
109 coloring_pass(non_colored, false);
110 coloring_pass(colored, true);
111 }
112 }
113
114 void object_sync_ex(Manager &manager,
115 const ObjectRef &ob_ref,
116 Resources &res,
117 const State &state,
118 const bool in_edit_paint_mode,
119 const bool in_edit_mode)
120 {
121 if (!enabled_) {
122 return;
123 }
124
125 if (ob_ref.object->dt < OB_WIRE) {
126 return;
127 }
128
129 const bool all_edges = (ob_ref.object->dtx & OB_DRAW_ALL_EDGES) != 0;
130 const bool show_surface_wire = show_wire_ || (ob_ref.object->dtx & OB_DRAWWIRE) ||
131 (ob_ref.object->dt == OB_WIRE);
132
133 ColoringPass &coloring = in_edit_paint_mode ? non_colored : colored;
134 switch (ob_ref.object->type) {
135 case OB_CURVES_LEGACY: {
136 gpu::Batch *geom = DRW_cache_curve_edge_wire_get(ob_ref.object);
137 coloring.curves_ps_->draw(
138 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
139 break;
140 }
141 case OB_FONT: {
142 gpu::Batch *geom = DRW_cache_text_edge_wire_get(ob_ref.object);
143 coloring.curves_ps_->draw(
144 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
145 break;
146 }
147 case OB_SURF: {
148 gpu::Batch *geom = DRW_cache_surf_edge_wire_get(ob_ref.object);
149 coloring.curves_ps_->draw(
150 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
151 break;
152 }
153 case OB_CURVES:
154 /* TODO(fclem): Not yet implemented. */
155 break;
156 case OB_GREASE_PENCIL: {
157 if (show_surface_wire) {
158 gpu::Batch *geom = DRW_cache_grease_pencil_face_wireframe_get(state.scene,
159 ob_ref.object);
160 coloring.curves_ps_->draw(
161 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
162 }
163 break;
164 }
165 case OB_MESH: {
166 /* Force display in edit mode when overlay is off in wireframe mode (see #78484). */
167 const bool wireframe_no_overlay = state.hide_overlays && state.is_wireframe_mode;
168
169 /* In some cases the edit mode wireframe overlay is already drawn for the same edges.
170 * We want to avoid this redundant work and avoid Z-fighting, but detecting this case is
171 * relatively complicated. Whether edit mode draws edges on the evaluated mesh depends on
172 * whether there is a separate cage and whether there is a valid mapping between the
173 * evaluated and original edit mesh. */
174 const bool edit_wires_overlap_all = mesh_edit_wires_overlap(ob_ref, in_edit_mode);
175
176 const bool bypass_mode_check = wireframe_no_overlay || !edit_wires_overlap_all;
177
178 if (show_surface_wire) {
179 if (BKE_sculptsession_use_pbvh_draw(ob_ref.object, state.rv3d)) {
180 ResourceHandleRange handle = manager.unique_handle(ob_ref);
181
183 coloring.mesh_all_edges_ps_->draw(batch.batch, handle);
184 }
185 }
186 else if (!in_edit_mode || bypass_mode_check) {
187 /* Only draw the wireframe in edit mode if object has edit cage.
188 * Otherwise the wireframe will conflict with the edit cage drawing and produce
189 * unpleasant aliasing. */
190 gpu::Batch *geom = DRW_cache_mesh_face_wireframe_get(ob_ref.object);
191 (all_edges ? coloring.mesh_all_edges_ps_ : coloring.mesh_ps_)
192 ->draw(geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
193 }
194 }
195
196 /* Draw loose geometry. */
197 if (!in_edit_paint_mode || bypass_mode_check) {
199 gpu::Batch *geom;
200 if ((mesh.edges_num == 0) && (mesh.verts_num > 0)) {
202 coloring.pointcloud_ps_->draw(
203 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
204 }
205 else if ((geom = DRW_cache_mesh_loose_edges_get(ob_ref.object))) {
206 coloring.mesh_all_edges_ps_->draw(
207 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
208 }
209 }
210 break;
211 }
212 case OB_POINTCLOUD: {
213 if (show_surface_wire) {
214 gpu::Batch *geom = DRW_pointcloud_batch_cache_get_dots(ob_ref.object);
215 coloring.pointcloud_ps_->draw(
216 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
217 }
218 break;
219 }
220 case OB_VOLUME: {
221 if (show_surface_wire) {
222 gpu::Batch *geom = DRW_cache_volume_face_wireframe_get(ob_ref.object);
223 if (geom == nullptr) {
224 break;
225 }
226 if (DRW_object_get_data_for_drawing<Volume>(*ob_ref.object).display.wireframe_type ==
228 {
229 coloring.pointcloud_ps_->draw(
230 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
231 }
232 else {
233 coloring.mesh_ps_->draw(
234 geom, manager.unique_handle(ob_ref), res.select_id(ob_ref).get());
235 }
236 }
237 break;
238 }
239 default:
240 /* Would be good to have. */
241 // BLI_assert_unreachable();
242 break;
243 }
244 }
245
246 void pre_draw(Manager &manager, View &view) final
247 {
248 if (!enabled_) {
249 return;
250 }
251
252 manager.generate_commands(wireframe_ps_, view);
253 }
254
255 void copy_depth(TextureRef &depth_tx)
256 {
257 if (!enabled_ || !do_depth_copy_workaround_) {
258 return;
259 }
260
262 int2 render_size = int2(depth_tx.size());
263 tmp_depth_tx_.acquire(render_size, gpu::TextureFormat::SFLOAT_32_DEPTH_UINT_8, usage);
264
265 /* WORKAROUND: Nasty framebuffer copy.
266 * We should find a way to have nice wireframe without this. */
267 GPU_texture_copy(tmp_depth_tx_, depth_tx);
268 }
269
270 void draw_line(Framebuffer &framebuffer, Manager &manager, View &view) final
271 {
272 if (!enabled_) {
273 return;
274 }
275
276 GPU_framebuffer_bind(framebuffer);
277 manager.submit_only(wireframe_ps_, view);
278
279 tmp_depth_tx_.release();
280 }
281
282 private:
283 float wire_discard_threshold_get(float threshold)
284 {
285 /* Use `sqrt` since the value stored in the edge is a variation of the cosine, so its square
286 * becomes more proportional with a variation of angle. */
287 threshold = sqrt(abs(threshold));
288 /* The maximum value (255 in the VBO) is used to force hide the edge. */
289 return math::interpolate(0.0f, 1.0f - (1.0f / 255.0f), threshold);
290 }
291
292 static bool mesh_edit_wires_overlap(const ObjectRef &ob_ref, const bool in_edit_mode)
293 {
294 if (!in_edit_mode) {
295 return false;
296 }
298 const Mesh *orig_edit_mesh = BKE_object_get_pre_modified_mesh(ob_ref.object);
299 const bool edit_mapping_valid = BKE_editmesh_eval_orig_map_available(mesh, orig_edit_mesh);
300 if (!edit_mapping_valid) {
301 /* The mesh edit mode overlay doesn't include wireframe for the evaluated mesh when it
302 * doesn't correspond with the original edit mesh. So the main wireframe overlay should draw
303 * wires for the evaluated mesh instead. */
304 return false;
305 }
307 /* If a cage exists, the edit overlay might not display every edge. */
308 return false;
309 }
310 /* The edit mode overlay displays all of the edges of the evaluated mesh; drawing the edges
311 * again would be redundant. */
312 return true;
313 }
314};
315
316} // namespace blender::draw::overlay
bool BKE_editmesh_eval_orig_map_available(const Mesh &mesh_eval, const Mesh *mesh_orig)
Definition editmesh.cc:67
@ G_TRANSFORM_OBJ
const Mesh * BKE_object_get_pre_modified_mesh(const Object *object)
bool BKE_sculptsession_use_pbvh_draw(const Object *ob, const RegionView3D *rv3d)
Definition paint.cc:3068
@ OB_WIRE
@ OB_DRAW_ALL_EDGES
@ OB_DRAWWIRE
@ OB_SURF
@ OB_FONT
@ OB_GREASE_PENCIL
@ OB_MESH
@ OB_POINTCLOUD
@ OB_VOLUME
@ OB_CURVES_LEGACY
@ OB_CURVES
@ USER_GPU_FLAG_OVERLAY_SMOOTH_WIRE
@ VOLUME_WIREFRAME_POINTS
T & DRW_object_get_data_for_drawing(const Object &object)
static AppView * view
void GPU_framebuffer_bind(blender::gpu::FrameBuffer *fb)
void GPU_texture_copy(blender::gpu::Texture *dst, blender::gpu::Texture *src)
eGPUTextureUsage
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_TEXTURE_USAGE_ATTACHMENT
#define U
ResourceHandleRange unique_handle(const ObjectRef &ref)
int3 size(int miplvl=0) const
void draw(gpu::Batch *batch, uint instance_len=-1, uint vertex_len=-1, uint vertex_first=-1, ResourceIndexRange res_index={}, uint custom_id=0)
Definition draw_pass.hh:893
detail::PassBase< command::DrawMultiBuf > Sub
Definition draw_pass.hh:499
static bool mesh_has_edit_cage(const Object *ob)
void begin_sync(Resources &res, const State &state) final
void draw_line(Framebuffer &framebuffer, Manager &manager, View &view) final
void pre_draw(Manager &manager, View &view) final
void object_sync_ex(Manager &manager, const ObjectRef &ob_ref, Resources &res, const State &state, const bool in_edit_paint_mode, const bool in_edit_mode)
void copy_depth(TextureRef &depth_tx)
blender::gpu::Shader * get()
#define DRW_CLIPPING_UBO_SLOT
#define OVERLAY_GLOBALS_SLOT
@ DRW_STATE_WRITE_DEPTH
Definition draw_state.hh:29
@ DRW_STATE_WRITE_COLOR
Definition draw_state.hh:30
@ DRW_STATE_DEPTH_LESS_EQUAL
Definition draw_state.hh:38
@ DRW_STATE_FIRST_VERTEX_CONVENTION
Definition draw_state.hh:72
struct @021025263243242147216143265077100330027142264337::@225245033123204053237120173316075113304004012000 batch
#define abs
#define sqrt
static ulong state[N]
#define G(x, y, z)
gpu::Batch * DRW_cache_text_edge_wire_get(Object *ob)
gpu::Batch * DRW_cache_mesh_all_verts_get(Object *ob)
gpu::Batch * DRW_cache_grease_pencil_face_wireframe_get(const Scene *scene, Object *ob)
detail::Pass< command::DrawMultiBuf > PassMain
gpu::Batch * DRW_cache_mesh_loose_edges_get(Object *ob)
gpu::Batch * DRW_cache_mesh_face_wireframe_get(Object *ob)
Vector< SculptBatch > sculpt_batches_get(const Object *ob, SculptBatchFeature features)
blender::gpu::Batch * DRW_pointcloud_batch_cache_get_dots(Object *ob)
gpu::Batch * DRW_cache_curve_edge_wire_get(Object *ob)
gpu::Batch * DRW_cache_surf_edge_wire_get(Object *ob)
gpu::Batch * DRW_cache_volume_face_wireframe_get(Object *ob)
T interpolate(const T &a, const T &b, const FactorT &t)
VecBase< int32_t, 2 > int2
const char * name
int edges_num
int verts_num
virtual void draw(Framebuffer &, Manager &, View &)
const ID select_id(const ObjectRef &ob_ref, uint sub_object_id=0)