From ca286f8d1937bacc871e7da9cfce42d448d68e2d Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 4 May 2023 18:02:25 +0500 Subject: [PATCH] Migrating shaders to builtins to support M1 #2897 Migrated all shaders from geometry shaders to builtins (all geometry data now calculated in python before passing to shader) to make them more reliable and support Metal backend on Mac M1 (tested that it works - both annotations and gizmo). The current downside is that there is no more custom frag shaders too - meaning we do not support dashed lines in the annotations (currently Hidden and Grid just use a bit less bright annotation color). --- .../bim/module/drawing/decoration.py | 1887 ++++++----------- .../blenderbim/bim/module/drawing/gizmos.py | 50 +- .../blenderbim/bim/module/drawing/shaders.py | 135 +- 3 files changed, 798 insertions(+), 1274 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index a9609906ff..ab12fd9c8b 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -27,14 +27,13 @@ import ifcopenshell import ifcopenshell.util.element import blenderbim.tool as tool import blenderbim.bim.module.drawing.helper as helper -from math import pi, sin, cos, tan, acos, atan, degrees +from math import pi, sin, cos, tan, acos, atan, degrees, radians, ceil from bpy.types import SpaceView3D from mathutils import Vector, Matrix from bpy_extras.view3d_utils import location_3d_to_region_2d -from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat from gpu_extras.batch import batch_for_shader from blenderbim.bim.module.drawing.data import DecoratorData -from blenderbim.bim.module.drawing.shaders import BASE_LIB_GLSL, BASE_DEF_GLSL +from blenderbim.bim.module.drawing.shaders import BASE_LIB_GLSL, BASE_DEF_GLSL, add_verts_sequence, add_offsets def ccw(A, B, C): @@ -42,75 +41,104 @@ def ccw(A, B, C): return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x) +def worldspace_to_winspace(verts, context): + """Convert world space verts to window space""" + region = context.region + region3d = context.region_data + clipspace_verts = [region3d.perspective_matrix @ Vector(v) for v in verts] + winspace_vector = Vector([region.width / 2, region.height / 2, 1]) + winspace_vector_offset = Vector([region.width / 2, region.height / 2, 0]) + winspace_verts = [v * winspace_vector + winspace_vector_offset for v in clipspace_verts] + return winspace_verts + + +def winspace_to_worldspace(verts, context): + """Convert winspace verts to world space""" + region = context.region + region3d = context.region_data + # clipspace_vector = 1 / winspace_vector + clipspace_vector = Vector([1 / (region.width / 2), 1 / (region.height / 2), 1]) + winspace_vector_offset = Vector([region.width / 2, region.height / 2, 0]) + clipspace_verts = [(v - winspace_vector_offset) * clipspace_vector for v in verts] + worldspace_verts = [region3d.perspective_matrix.inverted() @ v for v in clipspace_verts] + return worldspace_verts + + +def get_arrow_head(edge_dir, size, rot_matrix_cw, rot_matrix_ccw): + head = [] + head.append(edge_dir * size) + head.append((rot_matrix_cw @ head[0].xy).to_3d()) + head.append((rot_matrix_ccw @ head[0].xy).to_3d()) + return head + + +def get_triangle_head(edge_dir, side, length, width): + head = [ + edge_dir * length * (-0.5), + side * width, + edge_dir * length * (0.5), + ] + return head + + +def get_callout_head(edge_dir, edge_side, callout_size, callout_gap): + head = [ + # callout handle + edge_dir * -callout_size + edge_side * callout_gap, + # callout triangle + edge_dir * callout_gap * 2 + edge_side * callout_gap, + edge_dir * callout_gap, + edge_side * callout_gap, + ] + return head + + +def get_circle_head(size, segments=12): + angle_d = 2 * pi / segments + head = [] + for i in range(segments): + angle = angle_d * i + head.append(Vector([cos(angle), sin(angle), 0]) * size) + return head + + +def get_circle_head_asterisk(size, segments=6): + circle_head = get_circle_head(size, segments) + middle = segments // 2 + return zip(circle_head[:middle], circle_head[middle:]) + + +def get_angle_circle(circle_start, circle_angle, counterclockwise, segments=12): + angle_d = 2 * pi / segments + angle_segs = max(1, ceil(circle_angle / angle_d)) + angle_d = circle_angle / angle_segs + head = [] + circle_start = circle_start.xy + + for i in range(angle_segs + 1): + angle = angle_d * i + if counterclockwise: + rot_matrix_ccw = Matrix.Rotation(angle, 2) + head.append((rot_matrix_ccw @ circle_start).to_3d()) + else: + rot_matrix_cw = Matrix.Rotation(-angle, 2) + head.append((rot_matrix_cw @ circle_start).to_3d()) + return head + + class BaseDecorator: # base name of objects to decorate objecttype = "NOTDEFINED" - DEF_GLSL = BASE_DEF_GLSL - LIB_GLSL = BASE_LIB_GLSL - - VERT_GLSL = """ - uniform mat4 viewMatrix; - in vec3 pos; - in uint topo; - in vec3 next_vert; - out uint type; - out vec4 v_next_vert; - - void main() { - gl_Position = viewMatrix * vec4(pos, 1.0); - type = topo; - v_next_vert = viewMatrix * vec4(next_vert, 1.0); - } - """ - - GEOM_GLSL = """ - uniform float viewportDrawingScale; - - layout(lines) in; - layout(triangle_strip, max_vertices = 4) out; - - void main() { - // default setup for macro to work - vec4 clip2win = matCLIP2WIN(); - vec4 win2clip = matWIN2CLIP(); - vec2 EDGE_DIR; - - vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; - do_edge_verts(p0, p1); - EndPrimitive(); - } - """ - - FRAG_GLSL = """ - uniform vec4 color; - uniform float lineWidth; - - in float smoothline; - out vec4 fragColor; - void main() { - vec2 co = gl_FragCoord.xy; - - fragColor = color; - if (lineSmooth) { - fragColor.a *= clamp((lineWidth + SMOOTH_WIDTH) * 0.5 - abs(smoothline), 0.0, 1.0); //test - } - } - """ - def __init__(self): - # NB: libcode param doesn't work - self.shader = GPUShader( - vertexcode=self.VERT_GLSL, - fragcode=self.FRAG_GLSL, - geocode=self.LIB_GLSL + self.GEOM_GLSL, - defines=self.DEF_GLSL, - ) - self.font_id = blf.load( os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf") ) + # 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated + self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR") + self.base_shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR") + def get_camera_width_mm(self): # Horrific prototype code to ensure bgl draws at drawing scales # https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view @@ -188,6 +216,20 @@ class BaseDecorator: results.append(obj) return results + def get_splines(self, obj): + """Iterates through splines + Args: + obj: Blender object with Curve data + + Yields: + verts: points of each spline, world coords + """ + for spline in obj.data.splines: + spline_points = spline.bezier_points if spline.bezier_points else spline.points + if len(spline_points) < 2: + continue + yield [obj.matrix_world @ p.co for p in spline_points] + def get_path_geom(self, obj, topo=True): """Parses path geometry into line segments @@ -224,7 +266,7 @@ class BaseDecorator: return vertices, indices, topology - def get_mesh_geom(self, obj): + def get_mesh_geom(self, obj, check_mode=True): """Parses mesh geometry into line segments Args: @@ -234,6 +276,9 @@ class BaseDecorator: vertices: 3-tuples of coords indices: 2-tuples of each segment verices' indices """ + if check_mode and obj.data.is_editmode: + return self.get_editmesh_geom(obj) + vertices = [obj.matrix_world @ v.co for v in obj.data.vertices] indices = [e.vertices for e in obj.data.edges] return vertices, indices @@ -249,69 +294,76 @@ class BaseDecorator: """perform actual drawing stuff""" raise NotImplementedError() - def draw_lines( - self, - context, - obj, - vertices, - indices, - topology=None, - is_scale_dependant=True, - fill_next_vertices=False, - extra_float_kwargs={}, - smoothing=True, - ): - """use `is_scale_dependant` = `False` if shader is not using uniform viewportDrawingScale - otherwise uniform will be discarded during the optimization process - and you will get `ValueError: GPUShader.uniform_float: uniform viewportDrawingScale not found` - """ + def draw_arrow(self, context, obj): + # gather geometry data and convert to winspace + verts, edges_original, _ = self.get_path_geom(obj, topo=False) + if not edges_original: + return + viewportDrawingScale = self.get_viewport_drawing_scale(context) + winspace_verts = worldspace_to_winspace(verts, context) + # setup geometry parameters + arrow_size = viewportDrawingScale * 16 + angle = radians(15) + rot_matrix_cw = Matrix.Rotation(-angle, 2) + rot_matrix_ccw = Matrix.Rotation(angle, 2) + + output_verts = [] + output_edges = [] + out_kwargs = { + "output_verts": output_verts, + "output_edges": output_edges, + } + last_vert = len(winspace_verts) - 1 + + # process edges + for edge in edges_original: + v0, v1 = winspace_verts[edge[0]], winspace_verts[edge[1]] + start_i = len(output_verts) + + # arrow head on last vert + if edge[1] == last_vert: + edge_dir = (v1 - v0).normalized() + arrow_head = get_arrow_head(edge_dir, arrow_size, rot_matrix_cw, rot_matrix_ccw) + add_verts_sequence([v1, v1 - arrow_head[1], v1 - arrow_head[2]], start_i, **out_kwargs, closed=True) + start_i += 3 + gap = edge_dir * arrow_size + # stem with gaps for arrows + add_verts_sequence([v0, v1 - gap], start_i, **out_kwargs) + else: + # stem with gaps for arrows + add_verts_sequence([v0, v1], start_i, **out_kwargs) + + self.draw_lines(context, obj, output_verts, output_edges) + + def draw_batch(self, shader_type, content_pos, color, indices=None): + shader = self.line_shader if shader_type == "LINES" else self.base_shader + batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices) + shader.uniform_float("color", color) + batch.draw(shader) + + def draw_lines(self, context, obj, vertices, indices, color=None): + """`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center""" region = context.region - region3d = context.region_data - color = context.preferences.addons["blenderbim"].preferences.decorations_colour - - fmt = GPUVertFormat() - fmt.attr_add(id="pos", comp_type="F32", len=3, fetch_mode="FLOAT") - if topology: - fmt.attr_add(id="topo", comp_type="U8", len=1, fetch_mode="INT") - if fill_next_vertices: - fmt.attr_add(id="next_vert", comp_type="F32", len=3, fetch_mode="FLOAT") - - vbo = GPUVertBuf(len=len(vertices), format=fmt) - vbo.attr_fill(id="pos", data=vertices) - if topology: - vbo.attr_fill(id="topo", data=topology) - - if fill_next_vertices: - shifted_vertices = vertices[1:] + [vertices[0]] - vbo.attr_fill(id="next_vert", data=shifted_vertices) - - ibo = GPUIndexBuf(type="LINES", seq=indices) - - batch = GPUBatch(type="LINES", buf=vbo, elem=ibo) - - self.shader.bind() - self.shader.uniform_float("viewMatrix", region3d.perspective_matrix) - self.shader.uniform_float("winsize", (region.width, region.height)) - self.shader.uniform_float("color", color) - if smoothing: - self.shader.uniform_float("lineWidth", 1.0) - - if is_scale_dependant: - # Horrific prototype code - factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom) - camera_width_px = factor * context.region.width - mm_to_px = camera_width_px / self.get_camera_width_mm() - # 0.00025 is a magic constant number I visually discovered to get the right number. - # It probably should be dynamically calculated using system.dpi or something. - viewport_drawing_scale = 0.00025 * mm_to_px - self.shader.uniform_float("viewportDrawingScale", viewport_drawing_scale) - - for kwarg, value in extra_float_kwargs.items(): - self.shader.uniform_float(kwarg, value) + if not color: + color = context.preferences.addons["blenderbim"].preferences.decorations_colour + self.line_shader.bind() + # POLYLINE_UNIFORM_COLOR specific uniforms + self.line_shader.uniform_float("viewportSize", (region.width, region.height)) + self.line_shader.uniform_float("lineWidth", 1.0) gpu.state.blend_set("ALPHA") - batch.draw(self.shader) + self.draw_batch("LINES", vertices, color, indices) + + def get_viewport_drawing_scale(self, context): + # Horrific prototype code + factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom) + camera_width_px = factor * context.region.width + mm_to_px = camera_width_px / self.get_camera_width_mm() + # 0.00025 is a magic constant number I visually discovered to get the right number. + # It probably should be dynamically calculated using system.dpi or something. + viewport_drawing_scale = 0.00025 * mm_to_px + return viewport_drawing_scale def draw_label( self, @@ -385,16 +437,16 @@ class BaseDecorator: pos -= rotation_matrix.transposed() @ box_alignment_offset else: + # horizontal centering if center: - # horizontal centering pos -= Vector((cos, sin)) * w * 0.5 + # vertical centering if vcenter: - # vertical centering pos -= Vector((-sin, cos)) * h * 0.5 + # side-shifting if gap: - # side-shifting pos += Vector((-sin, cos)) * gap blf.enable(font_id, blf.ROTATION) @@ -417,6 +469,29 @@ class BaseDecorator: split_unit=context.scene.unit_settings.system == "IMPERIAL", ) + def draw_asterisk(self, context, obj): + # gather geometry data and convert to winspace + verts = [obj.location] + viewportDrawingScale = self.get_viewport_drawing_scale(context) + winspace_verts = worldspace_to_winspace(verts, context) + + # setup geometry parameters + circle_size = viewportDrawingScale * 4 + + output_verts = [] + output_edges = [] + out_kwargs = { + "output_verts": output_verts, + "output_edges": output_edges, + } + + v0 = winspace_verts[0] + asterisk_head = get_circle_head_asterisk(circle_size) + start_i = 0 + for segment in asterisk_head: + start_i = add_verts_sequence(add_offsets(v0, segment), start_i, **out_kwargs) + self.draw_lines(context, obj, output_verts, output_edges) + def draw_text(self, context, obj, text_world_position=None): """if `text_world_position` is not provided, the object's location will be used""" if not text_world_position: @@ -442,9 +517,7 @@ class BaseDecorator: # draw asterisk symbol to indicate that there is some symbol that's not shown in viewport if symbol: - verts = [text_world_position] - idxs = [(0, 0)] - self.draw_lines(context, obj, verts, idxs) + self.draw_asterisk(context, obj) # NOTE: for now we assume that scale is uniform text_scale = obj.scale.x @@ -476,102 +549,70 @@ class DimensionDecorator(BaseDecorator): objecttype = "DIMENSION" - DEF_GLSL = ( - BaseDecorator.DEF_GLSL - + """ - #define OBLIQUE_SYMBOL_ANGLE PI / 4.0 - #define OLBIQUE_SYMBOL_SIZE 10.0 - #define OBLIQUE_HEAD_VERTS 7 - - #define ARROW_ANGLE PI / 12.0 - #define ARROW_SIZE 16.0 - """ - ) - - GEOM_GLSL = """ - uniform float viewportDrawingScale; - uniform float use_oblique_style; - - layout(lines) in; - layout(triangle_strip, max_vertices=MAX_POINTS) out; - - void oblique_dimension_head(in vec4 dir, in float size, - in float angle, out vec4 head[OBLIQUE_HEAD_VERTS]) { - float c = cos(angle), s = sin(angle); - vec4 ortho = vec4(-dir.y, dir.x, 0, 0); - head[0] = -dir * size*0.66; - head[1] = vec4(0); - head[2] = ortho * size; - head[3] = -ortho * size; - head[4] = vec4(0); - head[5] = vec4((mat2(c, +s, -s, c) * dir.xy) * size * 0.47, 0, 0); - head[6] = -head[5]; - } - - void main() { - vec4 clip2win = matCLIP2WIN(); - vec4 win2clip = matWIN2CLIP(); - - vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; - - vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1); - vec4 edge = p1w - p0w, dir = normalize(edge); - - vec4 p, p_next; - vec2 EDGE_DIR; - - if (use_oblique_style > 0) { - vec4 head[OBLIQUE_HEAD_VERTS]; - oblique_dimension_head(dir, viewportDrawingScale * OLBIQUE_SYMBOL_SIZE, OBLIQUE_SYMBOL_ANGLE, head); - - // start edge arrow - p_next = WIN2CLIP( (p0w + head[0]) ); - for (int i = 0; i < OBLIQUE_HEAD_VERTS-1; i++) { - do_edge_verts_win( (p0w + head[i]), (p0w + head[i+1]) ); - } - EndPrimitive(); - - // end edge arrow - p_next = WIN2CLIP( (p1w + head[0]) ); - for (int i = 0; i < OBLIQUE_HEAD_VERTS-1; i++) { - do_edge_verts_win( (p1w + head[i]), (p1w + head[i+1]) ); - } - EndPrimitive(); - - // stem - do_edge_verts(p0, p1); - EndPrimitive(); - - } else { - vec4 head[3]; - arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head); - - // start edge arrow - do_edge_verts( p0, WIN2CLIP( (p0w + head[1]) ) ); - do_edge_verts_win( p0w + head[1], p0w + head[2] ); - do_edge_verts( WIN2CLIP( p0w + head[2] ), p0 ); - EndPrimitive(); - - // end edge arrow - do_edge_verts( p1, WIN2CLIP( p1w - head[1] ) ); - do_edge_verts_win( p1w - head[1], p1w - head[2] ); - do_edge_verts( WIN2CLIP( p1w - head[2] ), p1 ); - EndPrimitive(); - - // stem, with gaps for arrows - do_edge_verts_win( p0w + head[0], p1w - head[0] ); - EndPrimitive(); - } - } - """ - def decorate(self, context, obj): - verts, idxs, _ = self.get_path_geom(obj, topo=False) + # gather geometry data and convert to winspace + verts_original, edges_original, _ = self.get_path_geom(obj, topo=False) + if not edges_original: + return + winspace_verts = worldspace_to_winspace(verts_original, context) + viewportDrawingScale = self.get_viewport_drawing_scale(context) + + # setup geometry parameters dimension_style = DecoratorData.get_dimension_data(obj)["dimension_style"] - self.draw_lines( - context, obj, verts, idxs, extra_float_kwargs={"use_oblique_style": float(dimension_style == "oblique")} - ) - self.draw_labels(context, obj, verts, idxs) + if dimension_style == "oblique": + size = viewportDrawingScale * 10 # OLBIQUE_SYMBOL_SIZE + angle = radians(45) + else: + size = viewportDrawingScale * 16 # ARROW_SIZE + angle = radians(15) + rot_matrix_cw = Matrix.Rotation(-angle, 2) + rot_matrix_ccw = Matrix.Rotation(angle, 2) + + output_verts = [] + output_edges = [] + out_kwargs = { + "output_verts": output_verts, + "output_edges": output_edges, + } + + # process edges + for edge in edges_original: + v0, v1 = winspace_verts[edge[0]], winspace_verts[edge[1]] + edge_dir = (v1 - v0).normalized() + start_i = len(output_verts) + + if dimension_style == "oblique": + ortho = Vector((-edge_dir.y, edge_dir.x)).to_3d() + # oblique dimension head + head = [] + head.append(-edge_dir * size * 0.66) + head.append(Vector([0] * 3)) + head.append(ortho * size) + head.append(-head[-1]) + head.append(Vector([0] * 3)) + head.append(((rot_matrix_ccw @ edge_dir.xy) * size * 0.47).to_3d()) + head.append(-head[-1]) + + n_segments = len(head) - 1 + # start edge arrow + add_verts_sequence([v0 + v for v in head], start_i, **out_kwargs) + # end edge arrow + add_verts_sequence([v1 + v for v in head], start_i + n_segments + 1, **out_kwargs) + # stem + add_verts_sequence([v0, v1], start_i + n_segments * 2 + 2, **out_kwargs) + + else: + # arrow dimension head + head = get_arrow_head(edge_dir, size, rot_matrix_cw, rot_matrix_ccw) + # start edge arrow + add_verts_sequence([v0, v0 + head[1], v0 + head[2]], start_i, **out_kwargs, closed=True) + # end edge arrow + add_verts_sequence([v1, v1 - head[1], v1 - head[2]], start_i + 3, **out_kwargs, closed=True) + # stem with gaps for arrows + add_verts_sequence([v0 + head[0], v1 - head[0]], start_i + 6, **out_kwargs) + + self.draw_lines(context, obj, output_verts, output_edges) + self.draw_labels(context, obj, verts_original, edges_original) def draw_labels(self, context, obj, vertices, indices): region = context.region @@ -614,82 +655,58 @@ class AngleDecorator(BaseDecorator): objecttype = "ANGLE" - DEF_GLSL = ( - BaseDecorator.DEF_GLSL - + """ - #define ARROW_ANGLE PI / 12.0 - #define ARROW_SIZE 8.0 - #define CIRCLE_SIZE 6.0 - """ - ) - - GEOM_GLSL = """ - uniform float viewportDrawingScale; - - layout(lines) in; - layout(triangle_strip, max_vertices=MAX_POINTS) out; - in uint type[]; - in vec4 v_next_vert[]; - - // per edge shader - void main() { - // default setup for macro to work - vec4 clip2win = matCLIP2WIN(); - vec4 win2clip = matWIN2CLIP(); - vec2 EDGE_DIR; - - vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; - vec4 p2 = v_next_vert[1]; - uint t0 = type[0], t1 = type[1]; - - vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1); - vec4 p2w = CLIP2WIN(p2); - vec4 edge0 = p1w - p0w, dir = normalize(edge0); - vec4 p; - - // draw a segment line - do_edge_verts(p0, p1); - EndPrimitive(); - - // end edge with angle circle for the non-last segment - if (t1 == 0u) { // draws only on internal vertex - edge0 = p0w - p1w; - vec4 dir0 = normalize(edge0); - vec4 edge1 = p2w - p1w; - vec4 dir1 = normalize(edge1); - - float angle_circle_size = min( length(edge0), length(edge1) ); - vec4 circle_start = dir0 * angle_circle_size; - vec4 circle_end = dir1 * angle_circle_size; - - float cos_a = dot( edge0, edge1 ) / ( length(edge0) * length(edge1) ); - float circle_angle = acos(cos_a); - - vec4 circle_head_data[CIRCLE_SEGS+1]; - float angle_segs; - bool counterclockwise = check_counterclockwise(p2w, p1w, p0w); - angle_circle_head( - circle_start, - circle_angle, - counterclockwise, - circle_head_data, - angle_segs); - - for(int i=0; i 0) { - do_circle_head(p0w, head); - EndPrimitive(); - } - - if (display_start_symbol > 0) { - // start edge triangle - do_triangle_head(p0w, head5); - EndPrimitive(); - } - - // end edge circle - if (display_end_circle > 0) { - do_circle_head(p1w, head); - EndPrimitive(); - } - - // end edge triangle - if (display_end_symbol > 0) { - do_triangle_head(p1w, head5); - EndPrimitive(); - } - - vec4 divider_line_size[2]; - divider_line_size[0] = side * viewportDrawingScale * CIRCLE_SIZE; - divider_line_size[1] = side * viewportDrawingScale * CIRCLE_SIZE; - if (connect_markers == 0) { - divider_line_size[0] = divider_line_size[0] * 3; - } - - // NOTE: basically we consider first vertex to be - // start and the second to be the end marker - // which is a bit inconsistent with svg - // but not that anyone will use multiple edge section annotation - - vec4 gap[2]; - - // start reference divider - if (display_start_symbol > 0) { - do_edge_verts_win( p0w + divider_line_size[0], p0w - divider_line_size[1] ); - EndPrimitive(); - } - - // end reference divider - if (display_end_symbol > 0) { - do_edge_verts_win( p1w + divider_line_size[1], p1w - divider_line_size[0] ); - EndPrimitive(); - } - - // stem - if (connect_markers > 0) { - gap[0] = (display_start_symbol > 0 ? (side * viewportDrawingScale * CIRCLE_SIZE) : vec4(0)); - gap[1] = (display_end_symbol > 0 ? (side * viewportDrawingScale * CIRCLE_SIZE) : vec4(0)); - do_edge_verts_win( p0w + gap[0], p1w - gap[1] ); - EndPrimitive(); - } - } - """ - def decorate(self, context, obj): + # gather geometry data and convert to winspace if obj.data.is_editmode: - verts, idxs = self.get_editmesh_geom(obj) + verts, edges_original = self.get_editmesh_geom(obj) else: - verts, idxs = self.get_mesh_geom(obj) + verts, edges_original = self.get_mesh_geom(obj) + if not edges_original: + return + viewportDrawingScale = self.get_viewport_drawing_scale(context) + winspace_verts = worldspace_to_winspace(verts, context) + + # setup geometry parameters + triangle_length = viewportDrawingScale * 22.63 + triangle_width = viewportDrawingScale * 11.31 + circle_size = viewportDrawingScale * 8 + + output_verts = [] + output_edges = [] + out_kwargs = { + "output_verts": output_verts, + "output_edges": output_edges, + } + + display_data = DecoratorData.get_section_markers_display_data(obj) + connect_markers = display_data["connect_markers"] + display_start_symbol = display_data["start"]["add_symbol"] + display_end_symbol = display_data["end"]["add_symbol"] + display_start_circle = display_data["start"]["add_circle"] + display_end_circle = display_data["end"]["add_circle"] + + # process edges + for edge in edges_original: + v0, v1 = winspace_verts[edge[0]], winspace_verts[edge[1]] + start_i = len(output_verts) + + if display_start_circle or display_end_circle: + circle_head = get_circle_head(circle_size) + + if display_start_symbol or display_end_symbol or connect_markers: + edge_dir = (v1 - v0).normalized() + side = (edge_dir.yx * Vector((1, -1))).to_3d() + edge_dir_circle = edge_dir * circle_size + + if display_start_symbol or display_end_symbol: + triangle_head = get_triangle_head(edge_dir, -side, triangle_length, triangle_width) + divider_offset = [] + divider_offset.append(edge_dir_circle if connect_markers else edge_dir_circle * 3) + divider_offset.append(edge_dir_circle) + + if display_start_circle: + start_i = add_verts_sequence([v + v0 for v in circle_head], start_i, **out_kwargs, closed=True) + # circle middle divider + if not display_start_symbol: + start_i = add_verts_sequence( + [v0 + divider_offset[0], v0 - divider_offset[1]], start_i, **out_kwargs + ) + + if display_start_symbol: + start_i = add_verts_sequence([v + v0 for v in triangle_head], start_i, **out_kwargs, closed=True) + + if display_end_circle: + start_i = add_verts_sequence([v + v1 for v in circle_head], start_i, **out_kwargs, closed=True) + # circle middle divider + if not display_end_symbol: + start_i = add_verts_sequence( + [v1 + divider_offset[1], v1 - divider_offset[0]], start_i, **out_kwargs + ) + + if display_end_symbol: + start_i = add_verts_sequence([v + v1 for v in triangle_head], start_i, **out_kwargs, closed=True) + + if connect_markers: + gap = [] + gap.append(edge_dir_circle if display_start_symbol else Vector((0, 0, 0))) + gap.append(edge_dir_circle if display_end_symbol else Vector((0, 0, 0))) + add_verts_sequence([v0 + gap[0], v1 - gap[1]], start_i, **out_kwargs) # TODO: add dashed line to shader with frag shader - display_data = DecoratorData.get_section_markers_display_data(obj) - self.draw_lines( - context, - obj, - verts, - idxs, - extra_float_kwargs={ - "connect_markers": float(display_data["connect_markers"]), - "display_start_symbol": float(display_data["start"]["add_symbol"]), - "display_end_symbol": float(display_data["end"]["add_symbol"]), - "display_start_circle": float(display_data["start"]["add_circle"]), - "display_end_circle": float(display_data["end"]["add_circle"]), - }, - ) + self.draw_lines(context, obj, output_verts, output_edges) class TextDecorator(BaseDecorator): @@ -1848,48 +1391,6 @@ class TextDecorator(BaseDecorator): objecttype = "TEXT" - DEF_GLSL = ( - BaseDecorator.DEF_GLSL - + """ - #define CIRCLE_SIZE 4.0 - #define CIRCLE_SEGS_ASTERISK 6 - """ - ) - - GEOM_GLSL = """ - uniform float viewportDrawingScale; - - layout(lines) in; - layout(triangle_strip, max_vertices=MAX_POINTS) out; - - void circle_head_asterisk(in float size, out vec4 head[CIRCLE_SEGS_ASTERISK]) { - float angle_d = PI * 2 / CIRCLE_SEGS_ASTERISK; - for(int i = 0; i. -from gpu.types import GPUShader from gpu_extras.batch import batch_for_shader import gpu +from mathutils import Vector + + +# NOTES: +# Since Metal doesn't support geometry shaders we stick to builtin shaders +# and generate all geometry data in python before passing it to the shader. +# This way was considered to be the most reliable atm. +# More: https://blender.stackexchange.com/questions/291674/migrating-geometry-shaders-to-metal +# +# BGL deprecation: +# since `bgl` is deprecated, creating smoothing lines became tricky +# Notes for creating shaders with smoothed lines: +# in geom shader - use triangle_strip, DEFAULT_SETUP, do_edge_verts or do_vertex to emit vertices +# in frag shader - use lineWidth uniform, smoothline flaot in, smoothing shader code from base shader +# mind the vertex limit since emitting vertices for smoothed lines produces twice as much vertices BASE_DEF_GLSL = """ @@ -29,12 +43,6 @@ BASE_DEF_GLSL = """ #define lineSmooth true """ -# since `bgl` is deprecated, creating smoothing lines became tricky -# Notes for creating shaders with smoothed lines: -# in geom shader - use triangle_strip, DEFAULT_SETUP, do_edge_verts or do_vertex to emit vertices -# in frag shader - use lineWidth uniform, smoothline flaot in, smoothing shader code from base shader -# mind the vertex limit since emitting vertices for smoothed lines produces twice as much vertices - BASE_LIB_GLSL = """ // TODO: redefine as macor instead uniform vec2 winsize; @@ -160,12 +168,11 @@ void do_vertex_util(vec4 pos, vec2 ofs) // geometry utils void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) { - vec4 nose = side * length; - vec4 ear = dir * width; + // TODO: radius is unnecessary? head[0] = side * -radius; - head[1] = nose * -.5; - head[2] = vec4(0) + ear; - head[3] = nose * .5; + head[1] = side * length * -.5; + head[2] = dir * width; + head[3] = side * length * .5; head[4] = side * radius; } @@ -189,6 +196,22 @@ void do_circle_head(vec4 pos_w, vec4 head[CIRCLE_SEGS]) { """ +def add_verts_sequence(verts, start_i, output_verts, output_edges, closed=False): + """Add sequence of verts to output lists, returns next vertex index""" + for i, v in enumerate(verts[:-1], start_i): + output_verts.append(v) + output_edges.append((i, i + 1)) + output_verts.append(verts[-1]) + if closed: + output_edges.append((i + 1, start_i)) + return i + 2 + + +def add_offsets(v, offsets): + """returns list of verts with offsets added""" + return [v + offset for offset in offsets] + + class BaseShader: """Wrapper for GPUShader To use for viewport decorations with geometry generated on GPU side. @@ -265,48 +288,49 @@ class BaseShader: """ def __init__(self): - # NB: libcode arg doesn't work - # TODO: rename to .shader - self.prog = GPUShader( - vertexcode=self.VERT_GLSL, - fragcode=self.FRAG_GLSL, - geocode=self.LIB_GLSL + self.GEOM_GLSL, - defines=self.DEF_GLSL, - ) + # 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated + self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR") + self.base_shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR") + + def get_shader(self): + """Returns shader for this type""" + return self.line_shader if self.TYPE == "LINES" else self.base_shader def batch(self, indices=None, **data): """Returns automatic GPUBatch filled with provided parameters""" - batch = batch_for_shader(self.prog, self.TYPE, data, indices=indices) - batch.program_set(self.prog) + shader = self.get_shader() + batch = batch_for_shader(shader, self.TYPE, data, indices=indices) return batch def bind(self): - self.prog.bind() + """need to bind shader before changing it's uniforms""" + shader = self.get_shader() + shader.bind() + return shader def glenable(self): gpu.state.blend_set("ALPHA") gpu.state.depth_test_set("LESS_EQUAL") def uniform_region(self, ctx): + shader = self.bind() + region = ctx.region region3d = ctx.region_data uniform_floats = { - "viewMatrix": region3d.perspective_matrix, - "winsize": (region.width, region.height), + "ModelViewProjectionMatrix": region3d.perspective_matrix, + # POLYLINE_UNIFORM_COLOR specific uniforms + "viewportSize": (region.width, region.height), "lineWidth": 2.5, } for name, value in uniform_floats.items(): - try: - self.prog.uniform_float(name, value) - # TODO: shouldn't just try'n'catch them - # because they may indicate errors in code - except ValueError: # unused uniform - pass + shader.uniform_float(name, value) -# TODO: add smoothing if this shades is going to be used +# TODO: add smoothing if this shader is going to be used +# TODO: dead code? class BaseLinesShader(BaseShader): """Draws line segments with gaps around vertices at endpoints""" @@ -429,37 +453,24 @@ class ExtrusionGuidesShader(GizmoShader): TYPE = "LINES" - DEF_GLSL = ( - BaseShader.DEF_GLSL - + """ - #define CROSS_SIZE .5 - """ - ) + def process_geometry(self, verts): + CROSS_SIZE = 0.5 - GEOM_GLSL = """ - uniform mat4 ModelViewProjectionMatrix; + p0, p1 = verts + bx = Vector((1, 0, 0)) * CROSS_SIZE + by = Vector((0, 1, 0)) * CROSS_SIZE - layout(lines) in; - layout(triangle_strip, max_vertices=MAX_POINTS) out; + output_verts = [] + output_edges = [] + out_kwargs = { + "output_verts": output_verts, + "output_edges": output_edges, + } - void main() { - // default setup for macro to work - vec2 EDGE_DIR; + start_i = 0 + start_i = add_verts_sequence(add_offsets(p0, [-bx, bx]), start_i, **out_kwargs) + start_i = add_verts_sequence(add_offsets(p0, [-by, by]), start_i, **out_kwargs) + start_i = add_verts_sequence(add_offsets(p1, [-bx, bx]), start_i, **out_kwargs) + start_i = add_verts_sequence(add_offsets(p1, [-by, by]), start_i, **out_kwargs) - vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; - do_edge_verts(p0, p1); - EndPrimitive(); - - vec4 bx = ModelViewProjectionMatrix[0] * CROSS_SIZE; - vec4 by = ModelViewProjectionMatrix[1] * CROSS_SIZE; - - do_edge_verts(p0 - bx, p0 + bx); - EndPrimitive(); - do_edge_verts(p0 - by, p0 + by); - EndPrimitive(); - do_edge_verts(p1 - bx, p1 + bx); - EndPrimitive(); - do_edge_verts(p1 - by, p1 + by); - EndPrimitive(); - } - """ + return output_verts, output_edges