mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 12:13:43 +00:00
snap: fix possible crash
- use draw handler to move the expensive synchronous GPU readback out of the mouse-event loop. - restore GPU state (depth, blend, face culling) around the offscreen
This commit is contained in:
committed by
Bruno Perdigão
parent
0f629d0d54
commit
a6d59bf61c
@@ -2874,3 +2874,54 @@ class WallSystemPathDecorator(_ConnectedNetworkPathDecorator):
|
||||
else:
|
||||
result.append(point)
|
||||
return result
|
||||
|
||||
|
||||
class GpuSnapDecorator(tool.Blender.ViewportDecorator):
|
||||
"""Draw-handler that performs GPU snap detection once per viewport draw.
|
||||
|
||||
The detection work is injected via ``install(..., detection=...)`` so this
|
||||
decorator has no knowledge of the raycast or snap subsystems. The modal
|
||||
snapping code sets the current request via :meth:`set_request` and reads the
|
||||
decoded result from :meth:`get_cache`.
|
||||
"""
|
||||
|
||||
draw_method = "draw"
|
||||
|
||||
request: dict[str, Any] = {"mouse_x": 0, "mouse_y": 0, "objs_to_raycast": []}
|
||||
cache: tuple[list[dict], bpy.types.Object | None, list[dict]] | None = None
|
||||
detection: Any = None
|
||||
event: Any = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context: bpy.types.Context, event=None, detection=None) -> None:
|
||||
if detection is not None:
|
||||
cls.detection = detection
|
||||
cls.event = event
|
||||
cls.request = {"mouse_x": 0, "mouse_y": 0, "objs_to_raycast": []}
|
||||
cls.cache = None
|
||||
super().install(context)
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls) -> None:
|
||||
cls.cache = None
|
||||
cls.event = None
|
||||
super().uninstall()
|
||||
|
||||
@classmethod
|
||||
def set_request(cls, event, objs_to_raycast: list[bpy.types.Object]) -> None:
|
||||
cls.event = event
|
||||
cls.request["mouse_x"] = int(event.mouse_region_x)
|
||||
cls.request["mouse_y"] = int(event.mouse_region_y)
|
||||
cls.request["objs_to_raycast"] = objs_to_raycast
|
||||
|
||||
@classmethod
|
||||
def get_cache(cls) -> tuple[list[dict], bpy.types.Object | None, list[dict]] | None:
|
||||
return cls.cache
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
cls = type(self)
|
||||
if cls.detection is None or cls.event is None or not cls.request["objs_to_raycast"]:
|
||||
cls.cache = None
|
||||
return
|
||||
|
||||
cls.cache = cls.detection(context, cls.event, cls.request)
|
||||
|
||||
@@ -26,7 +26,7 @@ import ifcopenshell.util.unit
|
||||
from mathutils import Vector
|
||||
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.model.decorator import PolylineDecorator
|
||||
from bonsai.bim.module.model.decorator import GpuSnapDecorator, PolylineDecorator
|
||||
|
||||
|
||||
class PolylineOperator:
|
||||
@@ -424,6 +424,7 @@ class PolylineOperator:
|
||||
def cleanup(self, context: bpy.Types.Context):
|
||||
context.workspace.status_text_set(text=None)
|
||||
PolylineDecorator.uninstall()
|
||||
GpuSnapDecorator.uninstall()
|
||||
tool.Polyline.clear_polyline()
|
||||
tool.Raycast.clear_cache()
|
||||
tool.Blender.update_viewport()
|
||||
@@ -460,6 +461,7 @@ class PolylineOperator:
|
||||
|
||||
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> None:
|
||||
PolylineDecorator.install(context)
|
||||
GpuSnapDecorator.install(context, event, detection=tool.Raycast.detect_gpu_snaps)
|
||||
tool.Snap.clear_snapping_point()
|
||||
|
||||
self.tool_state.use_default_container = False
|
||||
|
||||
@@ -1108,6 +1108,13 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
if _offscreen is None:
|
||||
_offscreen = GPUOffScreen(max(w, 1), max(h, 1), format="RGBA8")
|
||||
|
||||
# Save GPU state so it can be restored even if readback fails.
|
||||
prev_depth_mask = gpu.state.depth_mask_get()
|
||||
prev_depth_test = gpu.state.depth_test_get()
|
||||
prev_blend = gpu.state.blend_get()
|
||||
face_culling_get = getattr(gpu.state, "face_culling_get", None)
|
||||
prev_face_culling = face_culling_get() if face_culling_get is not None else None
|
||||
|
||||
_encoding_shader.bind()
|
||||
|
||||
if xray_mode:
|
||||
@@ -1137,27 +1144,30 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
|
||||
buffers_list = []
|
||||
last_buf = None
|
||||
with _offscreen.bind():
|
||||
fb = gpu.state.active_framebuffer_get()
|
||||
fb.clear(color=(0.0, 0.0, 0.0, 0.0), depth=1.0)
|
||||
try:
|
||||
with _offscreen.bind():
|
||||
fb = gpu.state.active_framebuffer_get()
|
||||
fb.clear(color=(0.0, 0.0, 0.0, 0.0), depth=1.0)
|
||||
|
||||
for batch, world_mat, slot_base in render_ops:
|
||||
mvp = rv3d.perspective_matrix @ world_mat
|
||||
_encoding_shader.uniform_float("MVP", mvp)
|
||||
_encoding_shader.uniform_float("slot_base", float(slot_base))
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.load_matrix(Matrix.Identity(4))
|
||||
batch.draw(_encoding_shader)
|
||||
for batch, world_mat, slot_base in render_ops:
|
||||
mvp = rv3d.perspective_matrix @ world_mat
|
||||
_encoding_shader.uniform_float("MVP", mvp)
|
||||
_encoding_shader.uniform_float("slot_base", float(slot_base))
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.load_matrix(Matrix.Identity(4))
|
||||
batch.draw(_encoding_shader)
|
||||
|
||||
if read_per_object: # gets all buffers
|
||||
buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
|
||||
buffers_list.append(buf)
|
||||
if not read_per_object:
|
||||
last_buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
|
||||
|
||||
# Restore state
|
||||
gpu.state.depth_mask_set(True)
|
||||
gpu.state.depth_test_set("LESS")
|
||||
if read_per_object: # gets all buffers
|
||||
buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
|
||||
buffers_list.append(buf)
|
||||
if not read_per_object:
|
||||
last_buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
|
||||
finally:
|
||||
gpu.state.depth_mask_set(prev_depth_mask)
|
||||
gpu.state.depth_test_set(prev_depth_test)
|
||||
gpu.state.blend_set(prev_blend)
|
||||
if prev_face_culling is not None:
|
||||
gpu.state.face_culling_set(prev_face_culling)
|
||||
|
||||
mouse_read_rect = (w, h, mx, my, read_x, read_y)
|
||||
if tris:
|
||||
@@ -1173,6 +1183,20 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
def get_gpu_wireframe_snaps(cls, context, event, objs_to_raycast):
|
||||
return cls.get_gpu_detection_snaps(context, event, objs_to_raycast)
|
||||
|
||||
@classmethod
|
||||
def detect_gpu_snaps(cls, context, event, request):
|
||||
"""GPU snap detection callback for ``GpuSnapDecorator``."""
|
||||
if bpy.app.background:
|
||||
return None
|
||||
|
||||
objs_to_raycast = request["objs_to_raycast"]
|
||||
if not objs_to_raycast:
|
||||
return None
|
||||
|
||||
solid_snaps, closest_obj = cls.get_gpu_solid_snaps(context, event, objs_to_raycast)
|
||||
wireframe_snaps, _ = cls.get_gpu_wireframe_snaps(context, event, objs_to_raycast)
|
||||
return solid_snaps, closest_obj, wireframe_snaps
|
||||
|
||||
@classmethod
|
||||
def clear_cache(cls):
|
||||
global _wireframe_batch_cache, _wireframe_vert_fmt, _triangle_batch_cache, _triangle_vert_fmt, _encoding_shader, _offscreen, _obj_list
|
||||
|
||||
@@ -30,21 +30,21 @@ import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.drawing.data import DecoratorData
|
||||
from bonsai.bim.module.drawing.decoration import CutDecorator
|
||||
from bonsai.bim.module.model.decorator import PolylineDecorator
|
||||
from bonsai.bim.module.model.decorator import GpuSnapDecorator, PolylineDecorator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.prop import BIMSnapGroups, BIMSnapProperties
|
||||
|
||||
|
||||
def _get_gpu_object_snaps(
|
||||
def _process_gpu_object_snaps(
|
||||
context: bpy.types.Context,
|
||||
event: bpy.types.Event,
|
||||
objs_to_raycast: list[bpy.types.Object],
|
||||
snap_faces: list[dict[str, Any]],
|
||||
closest_obj: bpy.types.Object | None,
|
||||
wireframe_snaps: list[dict[str, Any]],
|
||||
xray_mode: bool,
|
||||
) -> list[dict[str, Any]]:
|
||||
detected_snaps: list[dict[str, Any]] = []
|
||||
snap_faces, closest_obj = tool.Raycast.get_gpu_solid_snaps(context, event, objs_to_raycast)
|
||||
wireframe_snaps, _ = tool.Raycast.get_gpu_wireframe_snaps(context, event, objs_to_raycast)
|
||||
|
||||
if not xray_mode:
|
||||
for snap in snap_faces:
|
||||
@@ -85,6 +85,17 @@ def _get_gpu_object_snaps(
|
||||
return detected_snaps
|
||||
|
||||
|
||||
def _get_gpu_object_snaps(
|
||||
context: bpy.types.Context,
|
||||
event: bpy.types.Event,
|
||||
objs_to_raycast: list[bpy.types.Object],
|
||||
xray_mode: bool,
|
||||
) -> list[dict[str, Any]]:
|
||||
snap_faces, closest_obj = tool.Raycast.get_gpu_solid_snaps(context, event, objs_to_raycast)
|
||||
wireframe_snaps, _ = tool.Raycast.get_gpu_wireframe_snaps(context, event, objs_to_raycast)
|
||||
return _process_gpu_object_snaps(context, event, snap_faces, closest_obj, wireframe_snaps, xray_mode)
|
||||
|
||||
|
||||
def _get_cpu_object_snaps(
|
||||
context: bpy.types.Context,
|
||||
event: bpy.types.Event,
|
||||
@@ -551,7 +562,15 @@ class Snap(bonsai.core.tool.Snap):
|
||||
props = cls.get_snap_props()
|
||||
|
||||
if props.use_gpu_snapping:
|
||||
detected_snaps.extend(_get_gpu_object_snaps(context, event, objs_to_raycast, xray_mode))
|
||||
GpuSnapDecorator.set_request(event, objs_to_raycast)
|
||||
cached = GpuSnapDecorator.get_cache()
|
||||
if cached is None:
|
||||
detected_snaps.extend(_get_gpu_object_snaps(context, event, objs_to_raycast, xray_mode))
|
||||
else:
|
||||
snap_faces, closest_obj, wireframe_snaps = cached
|
||||
detected_snaps.extend(
|
||||
_process_gpu_object_snaps(context, event, snap_faces, closest_obj, wireframe_snaps, xray_mode)
|
||||
)
|
||||
else:
|
||||
detected_snaps.extend(_get_cpu_object_snaps(context, event, objs_to_raycast, xray_mode))
|
||||
cut_snaps = _get_cut_object_snaps(context, event)
|
||||
|
||||
Reference in New Issue
Block a user