Files
blender-portable-repo/scripts/addons/RetopoFlow/addon_common/common/bmesh_render.py
T
2026-03-17 14:30:01 -06:00

327 lines
14 KiB
Python

'''
Copyright (C) 2023 CG Cookie
http://cgcookie.com
hello@cgcookie.com
Created by Jonathan Denning, Jonathan Williamson, and Patrick Moore
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
'''
notes: something is really wrong here to have such poor performance
Below are some related, interesting links
- https://machinesdontcare.wordpress.com/2008/02/02/glsl-discard-z-fighting-supersampling/
- https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/BestPracticesforShaders/BestPracticesforShaders.html
- https://stackoverflow.com/questions/16415037/opengl-core-profile-incredible-slowdown-on-os-x
'''
import os
import re
import math
import ctypes
import random
import traceback
import gpu
import bpy
from bpy_extras.view3d_utils import region_2d_to_origin_3d
from mathutils import Vector, Matrix, Quaternion
from mathutils.bvhtree import BVHTree
from . import gpustate
from .debug import dprint
from .decorators import blender_version_wrapper, add_cache, only_in_blender_version
from .drawing import Drawing
from .maths import (Point, Direction, Frame, XForm, invert_matrix, matrix_normal)
from .profiler import profiler
from .utils import shorten_floats
def glSetDefaultOptions():
gpustate.blend('ALPHA')
gpustate.depth_test('LESS_EQUAL')
def glSetMirror(symmetry=None, view=None, effect=0.0, frame: Frame=None):
mirroring = (0, 0, 0)
if symmetry and frame:
mx = 1.0 if 'x' in symmetry else 0.0
my = 1.0 if 'y' in symmetry else 0.0
mz = 1.0 if 'z' in symmetry else 0.0
mirroring = (mx, my, mz)
bmeshShader.assign('mirror_o', frame.o)
bmeshShader.assign('mirror_x', frame.x)
bmeshShader.assign('mirror_y', frame.y)
bmeshShader.assign('mirror_z', frame.z)
bmeshShader.assign('mirror_view', {'Edge': 1, 'Face': 2}.get(view, 0))
bmeshShader.assign('mirror_effect', effect)
bmeshShader.assign('mirroring', mirroring)
def triangulateFace(verts):
l = len(verts)
if l < 3: return
if l == 3:
yield verts
return
if l == 4:
v0,v1,v2,v3 = verts
yield (v0,v1,v2)
yield (v0,v2,v3)
return
iv = iter(verts)
v0, v2 = next(iv), next(iv)
for v3 in iv:
v1, v2 = v2, v3
yield (v0, v1, v2)
#############################################################################################################
#############################################################################################################
#############################################################################################################
import gpu
from gpu_extras.batch import batch_for_shader
if not bpy.app.background:
Drawing.glCheckError(f'Pre-compile check: bmesh render shader')
verts_vs, verts_fs = gpustate.shader_parse_file('bmesh_render_verts.glsl', includeVersion=False)
verts_shader, verts_ubos = gpustate.gpu_shader('bmesh render: verts', verts_vs, verts_fs)
edges_vs, edges_fs = gpustate.shader_parse_file('bmesh_render_edges.glsl', includeVersion=False)
edges_shader, edges_ubos = gpustate.gpu_shader('bmesh render: edges', edges_vs, edges_fs)
faces_vs, faces_fs = gpustate.shader_parse_file('bmesh_render_faces.glsl', includeVersion=False)
faces_shader, faces_ubos = gpustate.gpu_shader('bmesh render: faces', faces_vs, faces_fs)
Drawing.glCheckError(f'Compiled bmesh render shader')
class BufferedRender_Batch:
_quarantine = {}
POINTS = 1
LINES = 2
TRIANGLES = 3
def __init__(self, drawtype):
global faces_shader, edges_shader, verts_shader
self.count = 0
self.drawtype = drawtype
self.shader, self.shader_ubos, self.shader_type, self.drawtype_name, self.gl_count, self.options_prefix = {
self.POINTS: (verts_shader, verts_ubos, 'POINTS', 'points', 1, 'point'),
self.LINES: (edges_shader, edges_ubos, 'LINES', 'lines', 2, 'line'),
self.TRIANGLES: (faces_shader, faces_ubos, 'TRIS', 'triangles', 3, 'poly'),
}[self.drawtype]
self.batch = None
self._quarantine.setdefault(self.shader, set())
def buffer(self, pos, norm, sel, warn, pin, seam):
if self.shader == None: return
if self.shader_type == 'POINTS':
data = {
# repeat each value 6 times
'vert_pos': [p for p in pos for __ in range(6)],
'vert_norm': [n for n in norm for __ in range(6)],
'selected': [s for s in sel for __ in range(6)],
'warning': [w for w in warn for __ in range(6)],
'pinned': [p for p in pin for __ in range(6)],
'seam': [p for p in seam for __ in range(6)],
'vert_offset': [o for _ in pos for o in [(0,0), (1,0), (0,1), (0,1), (1,0), (1,1)]],
}
elif self.shader_type == 'LINES':
data = {
# repeat each value 6 times
'vert_pos0': [p0 for p0 in pos [0::2] for __ in range(6)],
'vert_pos1': [p1 for p1 in pos [1::2] for __ in range(6)],
'vert_norm': [n for n in norm[0::2] for __ in range(6)],
'selected': [s for s in sel [0::2] for __ in range(6)],
'warning': [w for w in warn[0::2] for __ in range(6)],
'pinned': [p for p in pin [0::2] for __ in range(6)],
'seam': [s for s in seam[0::2] for __ in range(6)],
'vert_offset': [o for _ in pos[0::2] for o in [(0,0), (0,1), (1,1), (0,0), (1,1), (1,0)]],
}
elif self.shader_type == 'TRIS':
data = {
'vert_pos': pos,
'vert_norm': norm,
'selected': sel,
'pinned': pin,
# 'seam': seam,
}
else: assert False, f'BufferedRender_Batch.buffer: Unhandled type: {self.shader_type}'
self.batch = batch_for_shader(self.shader, 'TRIS', data)
self.count = len(pos)
def set_options(self, prefix, opts):
if not opts: return
prefix = f'{prefix} ' if prefix else ''
def set_if_set(opt, cb):
opt = f'{prefix}{opt}'
if opt not in opts: return
cb(opts[opt])
Drawing.glCheckError(f'setting {opt} to {opts[opt]}')
Drawing.glCheckError('BufferedRender_Batch.set_options: start')
dpi_mult = opts.get('dpi mult', 1.0)
set_if_set('color', lambda v: self.set_shader_option('color_normal', v))
set_if_set('color selected', lambda v: self.set_shader_option('color_selected', v))
set_if_set('color warning', lambda v: self.set_shader_option('color_warning', v))
set_if_set('color pinned', lambda v: self.set_shader_option('color_pinned', v))
set_if_set('color seam', lambda v: self.set_shader_option('color_seam', v))
set_if_set('hidden', lambda v: self.set_shader_option('hidden', (v, 0, 0, 0)))
set_if_set('offset', lambda v: self.set_shader_option('offset', (v, 0, 0, 0)))
set_if_set('dotoffset', lambda v: self.set_shader_option('dotoffset', (v, 0, 0, 0)))
if self.shader_type == 'POINTS':
set_if_set('size', lambda v: self.set_shader_option('radius', (v*dpi_mult, 0, 0, 0)))
elif self.shader_type == 'LINES':
set_if_set('width', lambda v: self.set_shader_option('radius', (v*dpi_mult, 2*dpi_mult, 0, 0)))
def _draw(self, sx, sy, sz):
self.set_shader_option('vert_scale', (sx, sy, sz, 0))
self.shader_ubos.update_shader()
self.batch.draw(self.shader)
def is_quarantined(self, k):
return k in self._quarantine[self.shader]
def quarantine(self, k):
# dprint(f'BufferedRender_Batch: quarantining {k} for {self.shader}')
pass
self._quarantine[self.shader].add(k)
def set_shader_option(self, k, v):
if self.is_quarantined(k): return
try: self.shader_ubos.options.assign(k, v)
except Exception as e: self.quarantine(k)
def draw(self, opts):
if self.shader == None or self.count == 0: return
if self.drawtype == self.LINES and opts.get('line width', 1.0) <= 0: return
if self.drawtype == self.POINTS and opts.get('point size', 1.0) <= 0: return
ctx = bpy.context
area, spc, r3d = ctx.area, ctx.space_data, ctx.space_data.region_3d
rgn = ctx.region
if 'blend' in opts: gpustate.blend(opts['blend'])
if 'depth test' in opts: gpustate.depth_test(opts['depth test'])
if 'depth mask' in opts: gpustate.depth_mask(opts['depth mask'])
self.shader.bind()
# set defaults
self.set_shader_option('color_normal', (1.0, 1.0, 1.0, 0.5))
self.set_shader_option('color_selected', (0.5, 1.0, 0.5, 0.5))
self.set_shader_option('color_warning', (1.0, 0.5, 0.0, 0.5))
self.set_shader_option('color_pinned', (1.0, 0.0, 0.5, 0.5))
self.set_shader_option('color_seam', (1.0, 0.0, 0.5, 0.5))
self.set_shader_option('hidden', (0.9, 0, 0, 0))
self.set_shader_option('offset', (0.0, 0, 0, 0))
self.set_shader_option('dotoffset', (0.0, 0, 0, 0))
self.set_shader_option('vert_scale', (1.0, 1.0, 1.0))
self.set_shader_option('radius', (1.0, 0, 0, 0))
use0 = [
1.0 if (not opts.get('no selection', False)) else 0.0,
1.0 if (not opts.get('no warning', False)) else 0.0,
1.0 if (not opts.get('no pinned', False)) else 0.0,
1.0 if (not opts.get('no seam', False)) else 0.0,
]
use1 = [
1.0 if (self.drawtype == self.POINTS) else 0.0,
0.0,
0.0,
0.0,
]
self.set_shader_option('use_settings0', use0)
self.set_shader_option('use_settings1', use1)
self.set_shader_option('matrix_m', opts['matrix model'])
self.set_shader_option('matrix_mn', opts['matrix normal'])
self.set_shader_option('matrix_t', opts['matrix target'])
self.set_shader_option('matrix_ti', opts['matrix target inverse'])
self.set_shader_option('matrix_v', opts['matrix view'])
self.set_shader_option('matrix_vn', opts['matrix view normal'])
self.set_shader_option('matrix_p', opts['matrix projection'])
mx, my, mz = opts.get('mirror x', False), opts.get('mirror y', False), opts.get('mirror z', False)
symmetry = opts.get('symmetry', None)
symmetry_frame = opts.get('symmetry frame', None)
symmetry_view = opts.get('symmetry view', None)
symmetry_effect = opts.get('symmetry effect', 0.0)
mirroring = (0, 0, 0, 0)
if symmetry and symmetry_frame:
mirroring = (
1 if 'x' in symmetry else 0,
1 if 'y' in symmetry else 0,
1 if 'z' in symmetry else 0,
)
self.set_shader_option('mirror_o', symmetry_frame.o)
self.set_shader_option('mirror_x', symmetry_frame.x)
self.set_shader_option('mirror_y', symmetry_frame.y)
self.set_shader_option('mirror_z', symmetry_frame.z)
mirror_settings = [
{'Edge': 1.0, 'Face': 2.0}.get(symmetry_view, 0.0),
symmetry_effect,
0.0,
0.0,
]
self.set_shader_option('mirror_settings', mirror_settings)
self.set_shader_option('mirroring', mirroring)
view_settings0 = [
r3d.view_distance,
0.0 if (r3d.view_perspective == 'ORTHO') else 1.0,
opts.get('focus mult', 1.0),
opts.get('alpha backface', 0.5),
]
view_settings1 = [
1.0 if opts.get('cull backfaces', False) else 0.0,
opts['unit scaling factor'],
opts.get('normal offset', 0.0) if symmetry_view is None else 0.05,
1.0 if opts.get('constrain offset', True) else 0.0,
]
view_settings2 = [
0.99 if symmetry_view is None else 1.0,
0.0,
0.0,
0.0,
]
self.set_shader_option('view_settings0', view_settings0)
self.set_shader_option('view_settings1', view_settings1)
self.set_shader_option('view_settings2', view_settings2)
self.set_shader_option('view_position', region_2d_to_origin_3d(rgn, r3d, (area.width/2, area.height/2)))
self.set_shader_option('clip', (spc.clip_start, spc.clip_end, 0.0, 0.0))
self.set_shader_option('screen_size', (area.width, area.height, 0.0, 0.0))
self.set_options(self.options_prefix, opts)
self._draw(1, 1, 1)
if opts['draw mirrored'] and (mx or my or mz):
self.set_options(f'{self.options_prefix} mirror', opts)
if mx: self._draw(-1, 1, 1)
if my: self._draw( 1, -1, 1)
if mz: self._draw( 1, 1, -1)
if mx and my: self._draw(-1, -1, 1)
if mx and mz: self._draw(-1, 1, -1)
if my and mz: self._draw( 1, -1, -1)
if mx and my and mz: self._draw(-1, -1, -1)
gpu.shader.unbind()