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).
This commit is contained in:
Andrej730
2023-05-04 18:02:25 +05:00
parent 73a3cc2cc5
commit ca286f8d19
3 changed files with 798 additions and 1274 deletions
File diff suppressed because it is too large Load Diff
@@ -23,7 +23,7 @@ from bpy import types
from mathutils import Vector from mathutils import Vector
from mathutils import geometry from mathutils import geometry
from bpy_extras import view3d_utils from bpy_extras import view3d_utils
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader
from ifcopenshell.util.unit import si_conversions from ifcopenshell.util.unit import si_conversions
@@ -251,11 +251,12 @@ X3DISC = (
class CustomGizmo: class CustomGizmo:
# FIXME: highliting/selection doesnt work # FIXME: highliting/selection doesnt work
def draw_very_custom_shape(self, ctx, custom_shape, select_id=None): def draw_very_custom_shape(self, ctx, custom_shape, select_id=None):
# similar to draw_custom_shape # create shader and batch
shape, batch, shader = custom_shape shader_wrapper, batch = custom_shape
shader = shader_wrapper.get_shader()
# setup params
shader.bind() shader.bind()
if select_id is not None: if select_id is not None:
gpu.select.load_id(select_id) gpu.select.load_id(select_id)
else: else:
@@ -264,13 +265,17 @@ class CustomGizmo:
else: else:
color = (*self.color, self.alpha) color = (*self.color, self.alpha)
shader.uniform_float("color", color) shader.uniform_float("color", color)
shape.glenable() shader_wrapper.glenable()
shader_wrapper.uniform_region(ctx)
shape.uniform_region(ctx) # using `with` block to make sure matrix multiplication
# shader.uniform_float('modelMatrix', self.matrix_world) # won't affect other shaders
with gpu.matrix.push_pop(): with gpu.matrix.push_pop():
gpu.matrix.multiply_matrix(self.matrix_world) # using matrix_world seems to be unaffected by matrix_offset
batch.draw() # therefore we use basis @ offset
matrix = self.matrix_basis @ self.matrix_offset
gpu.matrix.multiply_matrix(matrix)
batch.draw(shader)
gpu.state.blend_set("NONE") gpu.state.blend_set("NONE")
@@ -360,6 +365,7 @@ class UglyDotGizmo(OffsetHandle, types.Gizmo):
self.draw_custom_shape(self.custom_shape, select_id=select_id) self.draw_custom_shape(self.custom_shape, select_id=select_id)
# TODO: dead code?
class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo): class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
"""Single dot viewport-aligned""" """Single dot viewport-aligned"""
@@ -389,10 +395,6 @@ class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
self.refresh() self.refresh()
self.draw_very_custom_shape(ctx, self.custom_shape, select_id=select_id) self.draw_very_custom_shape(ctx, self.custom_shape, select_id=select_id)
# doesn't get called
# def test_select(self, ctx, location):
# pass
class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo): class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
"""Extrusion guides """Extrusion guides
@@ -407,19 +409,25 @@ class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
__slots__ = ("scale_value", "custom_shape") __slots__ = ("scale_value", "custom_shape")
def setup(self): def setup(self):
shader = ExtrusionGuidesShader() """setup `custom_shape`"""
self.custom_shape = shader, shader.batch(pos=((0, 0, 0), (0, 0, 1))), shader.prog shader_wrapper = ExtrusionGuidesShader()
self.use_draw_scale = False verts = [Vector((0, 0, 0)), Vector((0, 0, 1))]
verts, edges = shader_wrapper.process_geometry(verts)
def refresh(self): self.custom_shape = shader_wrapper, shader_wrapper.batch(
depth = self.target_get_value("depth") / self.scale_value pos=verts,
self.matrix_offset.col[2][2] = depth # z-scaled indices=edges,
)
def draw(self, ctx): def draw(self, ctx):
self.refresh() self.refresh()
self.draw_very_custom_shape(ctx, self.custom_shape) self.draw_very_custom_shape(ctx, self.custom_shape)
def refresh(self):
depth = self.target_get_value("depth") / self.scale_value
self.matrix_offset.col[2][2] = depth # z-scaled
# TODO: dead code?
class DimensionLabelGizmo(types.Gizmo): class DimensionLabelGizmo(types.Gizmo):
"""Text label for a dimension""" """Text label for a dimension"""
@@ -486,6 +494,7 @@ class ExtrusionWidget(types.GizmoGroup):
theme = ctx.preferences.themes[0].user_interface theme = ctx.preferences.themes[0].user_interface
scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit) scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit)
# setup handle
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d") gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
gz.matrix_basis = basis gz.matrix_basis = basis
gz.scale_basis = 0.1 gz.scale_basis = 0.1
@@ -496,6 +505,7 @@ class ExtrusionWidget(types.GizmoGroup):
gz.target_set_prop("offset", prop, "value") gz.target_set_prop("offset", prop, "value")
gz.scale_value = scale_value gz.scale_value = scale_value
# setup guides
gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides") gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides")
gz.matrix_basis = basis gz.matrix_basis = basis
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary) gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
@@ -16,9 +16,23 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from gpu.types import GPUShader
from gpu_extras.batch import batch_for_shader from gpu_extras.batch import batch_for_shader
import gpu 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 = """ BASE_DEF_GLSL = """
@@ -29,12 +43,6 @@ BASE_DEF_GLSL = """
#define lineSmooth true #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 = """ BASE_LIB_GLSL = """
// TODO: redefine as macor instead // TODO: redefine as macor instead
uniform vec2 winsize; uniform vec2 winsize;
@@ -160,12 +168,11 @@ void do_vertex_util(vec4 pos, vec2 ofs)
// geometry utils // geometry utils
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) { 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; // TODO: radius is unnecessary?
vec4 ear = dir * width;
head[0] = side * -radius; head[0] = side * -radius;
head[1] = nose * -.5; head[1] = side * length * -.5;
head[2] = vec4(0) + ear; head[2] = dir * width;
head[3] = nose * .5; head[3] = side * length * .5;
head[4] = side * radius; 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: class BaseShader:
"""Wrapper for GPUShader """Wrapper for GPUShader
To use for viewport decorations with geometry generated on GPU side. To use for viewport decorations with geometry generated on GPU side.
@@ -265,48 +288,49 @@ class BaseShader:
""" """
def __init__(self): def __init__(self):
# NB: libcode arg doesn't work # 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
# TODO: rename to .shader self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
self.prog = GPUShader( self.base_shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
vertexcode=self.VERT_GLSL,
fragcode=self.FRAG_GLSL, def get_shader(self):
geocode=self.LIB_GLSL + self.GEOM_GLSL, """Returns shader for this type"""
defines=self.DEF_GLSL, return self.line_shader if self.TYPE == "LINES" else self.base_shader
)
def batch(self, indices=None, **data): def batch(self, indices=None, **data):
"""Returns automatic GPUBatch filled with provided parameters""" """Returns automatic GPUBatch filled with provided parameters"""
batch = batch_for_shader(self.prog, self.TYPE, data, indices=indices) shader = self.get_shader()
batch.program_set(self.prog) batch = batch_for_shader(shader, self.TYPE, data, indices=indices)
return batch return batch
def bind(self): 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): def glenable(self):
gpu.state.blend_set("ALPHA") gpu.state.blend_set("ALPHA")
gpu.state.depth_test_set("LESS_EQUAL") gpu.state.depth_test_set("LESS_EQUAL")
def uniform_region(self, ctx): def uniform_region(self, ctx):
shader = self.bind()
region = ctx.region region = ctx.region
region3d = ctx.region_data region3d = ctx.region_data
uniform_floats = { uniform_floats = {
"viewMatrix": region3d.perspective_matrix, "ModelViewProjectionMatrix": region3d.perspective_matrix,
"winsize": (region.width, region.height), # POLYLINE_UNIFORM_COLOR specific uniforms
"viewportSize": (region.width, region.height),
"lineWidth": 2.5, "lineWidth": 2.5,
} }
for name, value in uniform_floats.items(): for name, value in uniform_floats.items():
try: shader.uniform_float(name, value)
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
# 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): class BaseLinesShader(BaseShader):
"""Draws line segments with gaps around vertices at endpoints""" """Draws line segments with gaps around vertices at endpoints"""
@@ -429,37 +453,24 @@ class ExtrusionGuidesShader(GizmoShader):
TYPE = "LINES" TYPE = "LINES"
DEF_GLSL = ( def process_geometry(self, verts):
BaseShader.DEF_GLSL CROSS_SIZE = 0.5
+ """
#define CROSS_SIZE .5
"""
)
GEOM_GLSL = """ p0, p1 = verts
uniform mat4 ModelViewProjectionMatrix; bx = Vector((1, 0, 0)) * CROSS_SIZE
by = Vector((0, 1, 0)) * CROSS_SIZE
layout(lines) in; output_verts = []
layout(triangle_strip, max_vertices=MAX_POINTS) out; output_edges = []
out_kwargs = {
"output_verts": output_verts,
"output_edges": output_edges,
}
void main() { start_i = 0
// default setup for macro to work start_i = add_verts_sequence(add_offsets(p0, [-bx, bx]), start_i, **out_kwargs)
vec2 EDGE_DIR; 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; return output_verts, output_edges
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();
}
"""