mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 14:23:53 +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:
|
else:
|
||||||
result.append(point)
|
result.append(point)
|
||||||
return result
|
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
|
from mathutils import Vector
|
||||||
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.module.model.decorator import PolylineDecorator
|
from bonsai.bim.module.model.decorator import GpuSnapDecorator, PolylineDecorator
|
||||||
|
|
||||||
|
|
||||||
class PolylineOperator:
|
class PolylineOperator:
|
||||||
@@ -424,6 +424,7 @@ class PolylineOperator:
|
|||||||
def cleanup(self, context: bpy.Types.Context):
|
def cleanup(self, context: bpy.Types.Context):
|
||||||
context.workspace.status_text_set(text=None)
|
context.workspace.status_text_set(text=None)
|
||||||
PolylineDecorator.uninstall()
|
PolylineDecorator.uninstall()
|
||||||
|
GpuSnapDecorator.uninstall()
|
||||||
tool.Polyline.clear_polyline()
|
tool.Polyline.clear_polyline()
|
||||||
tool.Raycast.clear_cache()
|
tool.Raycast.clear_cache()
|
||||||
tool.Blender.update_viewport()
|
tool.Blender.update_viewport()
|
||||||
@@ -460,6 +461,7 @@ class PolylineOperator:
|
|||||||
|
|
||||||
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> None:
|
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> None:
|
||||||
PolylineDecorator.install(context)
|
PolylineDecorator.install(context)
|
||||||
|
GpuSnapDecorator.install(context, event, detection=tool.Raycast.detect_gpu_snaps)
|
||||||
tool.Snap.clear_snapping_point()
|
tool.Snap.clear_snapping_point()
|
||||||
|
|
||||||
self.tool_state.use_default_container = False
|
self.tool_state.use_default_container = False
|
||||||
|
|||||||
@@ -1108,6 +1108,13 @@ class Raycast(bonsai.core.tool.Raycast):
|
|||||||
if _offscreen is None:
|
if _offscreen is None:
|
||||||
_offscreen = GPUOffScreen(max(w, 1), max(h, 1), format="RGBA8")
|
_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()
|
_encoding_shader.bind()
|
||||||
|
|
||||||
if xray_mode:
|
if xray_mode:
|
||||||
@@ -1137,27 +1144,30 @@ class Raycast(bonsai.core.tool.Raycast):
|
|||||||
|
|
||||||
buffers_list = []
|
buffers_list = []
|
||||||
last_buf = None
|
last_buf = None
|
||||||
with _offscreen.bind():
|
try:
|
||||||
fb = gpu.state.active_framebuffer_get()
|
with _offscreen.bind():
|
||||||
fb.clear(color=(0.0, 0.0, 0.0, 0.0), depth=1.0)
|
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:
|
for batch, world_mat, slot_base in render_ops:
|
||||||
mvp = rv3d.perspective_matrix @ world_mat
|
mvp = rv3d.perspective_matrix @ world_mat
|
||||||
_encoding_shader.uniform_float("MVP", mvp)
|
_encoding_shader.uniform_float("MVP", mvp)
|
||||||
_encoding_shader.uniform_float("slot_base", float(slot_base))
|
_encoding_shader.uniform_float("slot_base", float(slot_base))
|
||||||
with gpu.matrix.push_pop():
|
with gpu.matrix.push_pop():
|
||||||
gpu.matrix.load_matrix(Matrix.Identity(4))
|
gpu.matrix.load_matrix(Matrix.Identity(4))
|
||||||
batch.draw(_encoding_shader)
|
batch.draw(_encoding_shader)
|
||||||
|
|
||||||
if read_per_object: # gets all buffers
|
if read_per_object: # gets all buffers
|
||||||
buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
|
buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
|
||||||
buffers_list.append(buf)
|
buffers_list.append(buf)
|
||||||
if not read_per_object:
|
if not read_per_object:
|
||||||
last_buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
|
last_buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
|
||||||
|
finally:
|
||||||
# Restore state
|
gpu.state.depth_mask_set(prev_depth_mask)
|
||||||
gpu.state.depth_mask_set(True)
|
gpu.state.depth_test_set(prev_depth_test)
|
||||||
gpu.state.depth_test_set("LESS")
|
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)
|
mouse_read_rect = (w, h, mx, my, read_x, read_y)
|
||||||
if tris:
|
if tris:
|
||||||
@@ -1173,6 +1183,20 @@ class Raycast(bonsai.core.tool.Raycast):
|
|||||||
def get_gpu_wireframe_snaps(cls, context, event, objs_to_raycast):
|
def get_gpu_wireframe_snaps(cls, context, event, objs_to_raycast):
|
||||||
return cls.get_gpu_detection_snaps(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
|
@classmethod
|
||||||
def clear_cache(cls):
|
def clear_cache(cls):
|
||||||
global _wireframe_batch_cache, _wireframe_vert_fmt, _triangle_batch_cache, _triangle_vert_fmt, _encoding_shader, _offscreen, _obj_list
|
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
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.module.drawing.data import DecoratorData
|
from bonsai.bim.module.drawing.data import DecoratorData
|
||||||
from bonsai.bim.module.drawing.decoration import CutDecorator
|
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:
|
if TYPE_CHECKING:
|
||||||
from bonsai.bim.prop import BIMSnapGroups, BIMSnapProperties
|
from bonsai.bim.prop import BIMSnapGroups, BIMSnapProperties
|
||||||
|
|
||||||
|
|
||||||
def _get_gpu_object_snaps(
|
def _process_gpu_object_snaps(
|
||||||
context: bpy.types.Context,
|
context: bpy.types.Context,
|
||||||
event: bpy.types.Event,
|
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,
|
xray_mode: bool,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
detected_snaps: 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:
|
if not xray_mode:
|
||||||
for snap in snap_faces:
|
for snap in snap_faces:
|
||||||
@@ -85,6 +85,17 @@ def _get_gpu_object_snaps(
|
|||||||
return detected_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(
|
def _get_cpu_object_snaps(
|
||||||
context: bpy.types.Context,
|
context: bpy.types.Context,
|
||||||
event: bpy.types.Event,
|
event: bpy.types.Event,
|
||||||
@@ -551,7 +562,15 @@ class Snap(bonsai.core.tool.Snap):
|
|||||||
props = cls.get_snap_props()
|
props = cls.get_snap_props()
|
||||||
|
|
||||||
if props.use_gpu_snapping:
|
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:
|
else:
|
||||||
detected_snaps.extend(_get_cpu_object_snaps(context, event, objs_to_raycast, xray_mode))
|
detected_snaps.extend(_get_cpu_object_snaps(context, event, objs_to_raycast, xray_mode))
|
||||||
cut_snaps = _get_cut_object_snaps(context, event)
|
cut_snaps = _get_cut_object_snaps(context, event)
|
||||||
|
|||||||
Reference in New Issue
Block a user