''' 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 . ''' ''' 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()