""" Centralized backend resolution for trellis2-apple. Resolves differentiable rasterization (dr), mesh processing (MeshBackend, BVH), remeshing, and grid_sample once at import time. All other modules import from here. Priority: Metal (mtldiffrast) > CUDA (nvdiffrast) > CPU fallback. Mesh: cumesh (auto-selects Metal/CUDA internally). """ import platform import os from functools import lru_cache import torch # --------------------------------------------------------------------------- # Detect platform # --------------------------------------------------------------------------- HAS_MPS = hasattr(torch.backends, 'mps') and torch.backends.mps.is_available() HAS_CUDA = torch.cuda.is_available() METAL_DISABLED = os.environ.get('TRELLIS_DISABLE_METAL', '0') == '1' BACKEND_ERRORS = {} # --------------------------------------------------------------------------- # Differentiable rasterizer (dr) # mtldiffrast (Metal) and nvdiffrast (CUDA) are separate packages # --------------------------------------------------------------------------- dr = None _dr_backend = None if not METAL_DISABLED: try: import mtldiffrast.torch as _mtldr dr = _mtldr _dr_backend = 'metal' except (ImportError, RuntimeError, OSError) as exc: BACKEND_ERRORS['mtldiffrast'] = str(exc) if dr is None: try: import nvdiffrast.torch as _nvdr dr = _nvdr _dr_backend = 'cuda' except (ImportError, RuntimeError, OSError) as exc: BACKEND_ERRORS['nvdiffrast'] = str(exc) HAS_DR = dr is not None def RasterizeContext(device=None): """Create the appropriate rasterization context for the active backend.""" if _dr_backend == 'metal': return dr.MtlRasterizeContext(device=device) elif _dr_backend == 'cuda': return dr.RasterizeCudaContext(device=device) raise RuntimeError("No differentiable rasterization backend available") # --------------------------------------------------------------------------- # Mesh processing (MeshBackend, BVH, remesh) # cumesh auto-selects Metal or CUDA backend internally # --------------------------------------------------------------------------- MeshBackend = None BVH = None remesh_narrow_band_dc = None _mesh_backend = None if not METAL_DISABLED or platform.system() != 'Darwin': try: import cumesh MeshBackend = cumesh.CuMesh BVH = cumesh.cuBVH remesh_narrow_band_dc = cumesh.remeshing.remesh_narrow_band_dc _mesh_backend = 'metal' if platform.system() == 'Darwin' else 'cuda' except (ImportError, RuntimeError, OSError) as exc: BACKEND_ERRORS['cumesh'] = str(exc) HAS_MESH = MeshBackend is not None HAS_REMESH = remesh_narrow_band_dc is not None # --------------------------------------------------------------------------- # CPU fallback mesh libraries # --------------------------------------------------------------------------- try: import trimesh HAS_TRIMESH = True except ImportError: trimesh = None HAS_TRIMESH = False try: import fast_simplification HAS_FAST_SIMPLIFICATION = True except ImportError: fast_simplification = None HAS_FAST_SIMPLIFICATION = False # --------------------------------------------------------------------------- # Grid sample (flex_gemm on Metal/CUDA, F.grid_sample fallback) # --------------------------------------------------------------------------- _flex_grid_sample_3d = None HAS_FLEX_GEMM = False if not METAL_DISABLED or platform.system() != 'Darwin': try: from flex_gemm.ops.grid_sample import grid_sample_3d as _fgs3d _flex_grid_sample_3d = _fgs3d HAS_FLEX_GEMM = True except (ImportError, RuntimeError, OSError) as exc: BACKEND_ERRORS['flex_gemm'] = str(exc) # --------------------------------------------------------------------------- # Overall backend name # --------------------------------------------------------------------------- BACKEND = _dr_backend or _mesh_backend or ('cpu' if HAS_TRIMESH else None) HAS_GPU = HAS_MPS or HAS_CUDA def _probe_result(ok, error=None, **details): return {'ok': bool(ok), 'error': error, **details} @lru_cache(maxsize=1) def probe_metal_rasterizer(): """Run a tiny UV raster instead of treating a successful import as proof.""" if METAL_DISABLED or _dr_backend != 'metal': return _probe_result(False, 'Metal rasterizer is disabled or unavailable') try: positions = torch.tensor( [ [-1.0, -1.0, 0.0, 1.0], [1.0, -1.0, 0.0, 1.0], [1.0, 1.0, 0.0, 1.0], [-1.0, 1.0, 0.0, 1.0], ], dtype=torch.float32, ) triangles = torch.tensor([[0, 1, 2], [0, 2, 3]], dtype=torch.int32) raster, _ = dr.rasterize( RasterizeContext(), positions, triangles, resolution=[8, 8], ) covered = int((raster[..., 3] != 0).sum().item()) ok = tuple(raster.shape) == (1, 8, 8, 4) and covered == 64 return _probe_result(ok, None if ok else 'unexpected raster output', covered_pixels=covered) except Exception as exc: return _probe_result(False, f'{type(exc).__name__}: {exc}') @lru_cache(maxsize=1) def probe_metal_mesh_bvh(): """Exercise both MtlMesh and an actual nearest-point MtlBVH query.""" if METAL_DISABLED or _mesh_backend != 'metal' or not HAS_MPS: return _probe_result(False, 'Metal mesh/BVH backend is disabled or unavailable') try: vertices = torch.tensor( [ [-1.0, -1.0, -1.0], [1.0, -1.0, -1.0], [1.0, 1.0, -1.0], [-1.0, 1.0, -1.0], [-1.0, -1.0, 1.0], [1.0, -1.0, 1.0], [1.0, 1.0, 1.0], [-1.0, 1.0, 1.0], ], dtype=torch.float32, device='mps', ) faces = torch.tensor( [ [0, 1, 2], [0, 2, 3], [4, 6, 5], [4, 7, 6], [0, 4, 5], [0, 5, 1], [1, 5, 6], [1, 6, 2], [2, 6, 7], [2, 7, 3], [3, 7, 4], [3, 4, 0], ], dtype=torch.int32, device='mps', ) mesh = MeshBackend() mesh.init(vertices, faces) mesh_vertices, mesh_faces = mesh.read() bvh = BVH(vertices, faces) query = torch.tensor([[0.0, 0.0, 1.2]], dtype=torch.float32, device='mps') distance, face_id, uvw = bvh.unsigned_distance(query, return_uvw=True) torch.mps.synchronize() ok = ( tuple(mesh_vertices.shape) == (8, 3) and tuple(mesh_faces.shape) == (12, 3) and bool(torch.isfinite(distance).all().item()) and bool(torch.isfinite(uvw).all().item()) and int(face_id.item()) >= 0 ) return _probe_result( ok, None if ok else 'unexpected mesh/BVH output', distance=float(distance.cpu().item()), face_id=int(face_id.cpu().item()), ) except Exception as exc: return _probe_result(False, f'{type(exc).__name__}: {exc}') def probe_metal_backends(): return { 'rasterizer': probe_metal_rasterizer(), 'mesh_bvh': probe_metal_mesh_bvh(), } def backend_report(run_probes=True): """Return a JSON-serializable snapshot used by setup and run metadata.""" report = { 'platform': platform.system(), 'mps_available': HAS_MPS, 'cuda_available': HAS_CUDA, 'metal_disabled': METAL_DISABLED, 'rasterizer': _dr_backend, 'mesh': _mesh_backend, 'flex_gemm': HAS_FLEX_GEMM, 'trimesh': HAS_TRIMESH, 'fast_simplification': HAS_FAST_SIMPLIFICATION, 'errors': dict(BACKEND_ERRORS), } if run_probes and platform.system() == 'Darwin': report['capability_probes'] = probe_metal_backends() return report