gdsdiff 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gdsdiff/__init__.py +148 -0
- gdsdiff/__main__.py +7 -0
- gdsdiff/_environment.py +12 -0
- gdsdiff/_version.py +34 -0
- gdsdiff/acceptance_evidence.py +530 -0
- gdsdiff/api.py +2955 -0
- gdsdiff/backends/__init__.py +26 -0
- gdsdiff/backends/base.py +77 -0
- gdsdiff/backends/boundary_loop_ctypes.py +191 -0
- gdsdiff/backends/cpu.py +20 -0
- gdsdiff/backends/cpu_ctypes.py +255 -0
- gdsdiff/backends/cuda.py +40 -0
- gdsdiff/backends/cuda_ctypes.py +1297 -0
- gdsdiff/backends/exact.py +424 -0
- gdsdiff/backends/geometry_ctypes.py +252 -0
- gdsdiff/backends/identity.py +53 -0
- gdsdiff/backends/metal.py +198 -0
- gdsdiff/backends/metal_ctypes.py +866 -0
- gdsdiff/backends/polygon_extract_ctypes.py +233 -0
- gdsdiff/backends/structural_ctypes.py +130 -0
- gdsdiff/boundary_loops.py +616 -0
- gdsdiff/cache.py +544 -0
- gdsdiff/canonicalize.py +176 -0
- gdsdiff/cli.py +352 -0
- gdsdiff/config.py +257 -0
- gdsdiff/cuda_setup.py +174 -0
- gdsdiff/design_history.py +65 -0
- gdsdiff/diagnostics.py +42 -0
- gdsdiff/diff_gds.py +66 -0
- gdsdiff/exact_diff_markdown.py +979 -0
- gdsdiff/exact_oracle.py +649 -0
- gdsdiff/extract.py +376 -0
- gdsdiff/gds_metadata.py +43 -0
- gdsdiff/geometry.py +112 -0
- gdsdiff/grid.py +143 -0
- gdsdiff/hierarchy.py +215 -0
- gdsdiff/hierarchy_extract.py +263 -0
- gdsdiff/inventory.py +153 -0
- gdsdiff/multiprocessing_support.py +39 -0
- gdsdiff/native.py +135 -0
- gdsdiff/native_cache.py +265 -0
- gdsdiff/native_src/cpu_prefilter.cpp +349 -0
- gdsdiff/native_src/gds_boundary_loops.cpp +505 -0
- gdsdiff/native_src/gds_geometry_scan.cpp +601 -0
- gdsdiff/native_src/gds_polygon_extract.cpp +594 -0
- gdsdiff/native_src/gds_structural_scan.cpp +335 -0
- gdsdiff/native_src/gdsdiff/CMakeLists.txt +74 -0
- gdsdiff/native_src/gdsdiff/core.cpp +191 -0
- gdsdiff/native_src/gdsdiff/core.hpp +96 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cu +304 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cuh +33 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cu +133 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cuh +17 -0
- gdsdiff/native_src/gdsdiff/cuda_ctypes.cu +4528 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cu +194 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cuh +34 -0
- gdsdiff/native_src/gdsdiff/metal_ctypes.mm +2791 -0
- gdsdiff/native_src/gdsdiff/python_bindings.cpp +21 -0
- gdsdiff/native_src/gdsdiff/test_core.cpp +93 -0
- gdsdiff/native_src/gdsdiff/test_cuda_assignment.cu +109 -0
- gdsdiff/native_src/gdsdiff/test_cuda_candidates.cu +94 -0
- gdsdiff/native_src/gdsdiff/test_cuda_memory.cu +97 -0
- gdsdiff/policy.py +305 -0
- gdsdiff/polygon_components.py +860 -0
- gdsdiff/profiles.py +152 -0
- gdsdiff/py.typed +1 -0
- gdsdiff/rect_coverage.py +384 -0
- gdsdiff/report.py +354 -0
- gdsdiff/schemas/gdsdiff_report-v1.schema.json +92 -0
- gdsdiff/semantics.py +75 -0
- gdsdiff/source_edge_diff.py +1120 -0
- gdsdiff/spatial.py +71 -0
- gdsdiff/structural_guard.py +161 -0
- gdsdiff/surplus_coverage.py +255 -0
- gdsdiff/synthetic_suite.py +1643 -0
- gdsdiff/templates.py +709 -0
- gdsdiff/tiling.py +153 -0
- gdsdiff/windowed_exact.py +270 -0
- gdsdiff/windows.py +184 -0
- gdsdiff-0.1.0.dist-info/METADATA +135 -0
- gdsdiff-0.1.0.dist-info/RECORD +85 -0
- gdsdiff-0.1.0.dist-info/WHEEL +5 -0
- gdsdiff-0.1.0.dist-info/entry_points.txt +4 -0
- gdsdiff-0.1.0.dist-info/licenses/LICENSE +674 -0
- gdsdiff-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import platform
|
|
5
|
+
import subprocess
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from ..config import LayerSpec
|
|
13
|
+
from ..exact_oracle import DiffFragment
|
|
14
|
+
from ..geometry import PolygonBuffer
|
|
15
|
+
from ..native_cache import ensure_cached_native_library
|
|
16
|
+
from ..tiling import CpuTilePrefilterResult, OversizedPolygon, TileConfig, TileKey
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MetalCtypesUnavailable(RuntimeError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _CPrefilterResult(ctypes.Structure):
|
|
24
|
+
_fields_ = [
|
|
25
|
+
("candidate_tiles", ctypes.POINTER(ctypes.c_int64)),
|
|
26
|
+
("candidate_count", ctypes.c_uint64),
|
|
27
|
+
("oversized", ctypes.POINTER(ctypes.c_int64)),
|
|
28
|
+
("oversized_count", ctypes.c_uint64),
|
|
29
|
+
("assignment_count", ctypes.c_uint64),
|
|
30
|
+
("collapsed_pair_count", ctypes.c_uint64),
|
|
31
|
+
("oversized_matched_count", ctypes.c_uint64),
|
|
32
|
+
("metal_kernel_count", ctypes.c_uint64),
|
|
33
|
+
("metal_event_elapsed_ms", ctypes.c_float),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _CPolygonRecordDeltaResult(ctypes.Structure):
|
|
38
|
+
_fields_ = [
|
|
39
|
+
("record_count", ctypes.c_uint64),
|
|
40
|
+
("layers", ctypes.POINTER(ctypes.c_int32)),
|
|
41
|
+
("datatypes", ctypes.POINTER(ctypes.c_int32)),
|
|
42
|
+
("directions", ctypes.POINTER(ctypes.c_uint8)),
|
|
43
|
+
("counts", ctypes.POINTER(ctypes.c_uint64)),
|
|
44
|
+
("bboxes", ctypes.POINTER(ctypes.c_int64)),
|
|
45
|
+
("vertex_counts", ctypes.POINTER(ctypes.c_uint64)),
|
|
46
|
+
("flags", ctypes.POINTER(ctypes.c_uint32)),
|
|
47
|
+
("hashes", ctypes.POINTER(ctypes.c_uint64)),
|
|
48
|
+
("representative_indices", ctypes.POINTER(ctypes.c_uint64)),
|
|
49
|
+
("old_input_count", ctypes.c_uint64),
|
|
50
|
+
("new_input_count", ctypes.c_uint64),
|
|
51
|
+
("matched_record_count", ctypes.c_uint64),
|
|
52
|
+
("reduced_key_count", ctypes.c_uint64),
|
|
53
|
+
("metal_kernel_count", ctypes.c_uint64),
|
|
54
|
+
("metal_event_elapsed_ms", ctypes.c_float),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _CByteCompareResult(ctypes.Structure):
|
|
59
|
+
_fields_ = [
|
|
60
|
+
("old_size", ctypes.c_uint64),
|
|
61
|
+
("new_size", ctypes.c_uint64),
|
|
62
|
+
("same", ctypes.c_uint8),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class _CBoundaryLoopResult(ctypes.Structure):
|
|
67
|
+
_fields_ = [
|
|
68
|
+
("fragment_count", ctypes.c_uint64),
|
|
69
|
+
("vertex_count", ctypes.c_uint64),
|
|
70
|
+
("vertices_xy", ctypes.POINTER(ctypes.c_int64)),
|
|
71
|
+
("fragment_offsets", ctypes.POINTER(ctypes.c_uint64)),
|
|
72
|
+
("bboxes", ctypes.POINTER(ctypes.c_int64)),
|
|
73
|
+
("twice_areas", ctypes.POINTER(ctypes.c_int64)),
|
|
74
|
+
("dropped_zero_area_loop_count", ctypes.c_uint64),
|
|
75
|
+
("metal_kernel_count", ctypes.c_uint64),
|
|
76
|
+
("metal_event_elapsed_ms", ctypes.c_float),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _CContextOverlapPairsResult(ctypes.Structure):
|
|
81
|
+
_fields_ = [
|
|
82
|
+
("pair_count", ctypes.c_uint64),
|
|
83
|
+
("component_indices", ctypes.POINTER(ctypes.c_uint64)),
|
|
84
|
+
("polygon_indices", ctypes.POINTER(ctypes.c_uint64)),
|
|
85
|
+
("metal_kernel_count", ctypes.c_uint64),
|
|
86
|
+
("metal_event_elapsed_ms", ctypes.c_float),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class _PackedBuffer:
|
|
92
|
+
bboxes: np.ndarray
|
|
93
|
+
layers: np.ndarray
|
|
94
|
+
datatypes: np.ndarray
|
|
95
|
+
canonical_ids: np.ndarray
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True)
|
|
99
|
+
class MetalByteCompare:
|
|
100
|
+
old_size: int
|
|
101
|
+
new_size: int
|
|
102
|
+
same: bool
|
|
103
|
+
method: str = "metal-byte-compare"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class MetalPolygonRecordDelta:
|
|
108
|
+
layers: np.ndarray
|
|
109
|
+
datatypes: np.ndarray
|
|
110
|
+
directions: np.ndarray
|
|
111
|
+
counts: np.ndarray
|
|
112
|
+
bboxes: np.ndarray
|
|
113
|
+
vertex_counts: np.ndarray
|
|
114
|
+
flags: np.ndarray
|
|
115
|
+
hashes: np.ndarray
|
|
116
|
+
representative_indices: np.ndarray
|
|
117
|
+
old_input_count: int
|
|
118
|
+
new_input_count: int
|
|
119
|
+
matched_record_count: int
|
|
120
|
+
reduced_key_count: int
|
|
121
|
+
metal_kernel_count: int
|
|
122
|
+
metal_event_elapsed_s: float
|
|
123
|
+
transferred_bytes: int
|
|
124
|
+
accelerator: str = "metal"
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def record_count(self) -> int:
|
|
128
|
+
return int(len(self.directions))
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def cuda_kernel_count(self) -> int:
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def cuda_event_elapsed_s(self) -> float:
|
|
136
|
+
return 0.0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class MetalBoundaryLoopResult:
|
|
141
|
+
fragments: tuple[DiffFragment, ...]
|
|
142
|
+
dropped_zero_area_loop_count: int
|
|
143
|
+
metal_kernel_count: int
|
|
144
|
+
metal_event_elapsed_s: float
|
|
145
|
+
transferred_bytes: int
|
|
146
|
+
accelerator: str = "metal"
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def cuda_kernel_count(self) -> int:
|
|
150
|
+
return 0
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def cuda_event_elapsed_s(self) -> float:
|
|
154
|
+
return 0.0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(frozen=True)
|
|
158
|
+
class MetalContextOverlapPairs:
|
|
159
|
+
component_indices: np.ndarray
|
|
160
|
+
polygon_indices: np.ndarray
|
|
161
|
+
metal_kernel_count: int
|
|
162
|
+
metal_event_elapsed_s: float
|
|
163
|
+
transferred_bytes: int
|
|
164
|
+
accelerator: str = "metal"
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def pair_count(self) -> int:
|
|
168
|
+
return int(len(self.component_indices))
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def cuda_kernel_count(self) -> int:
|
|
172
|
+
return 0
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def cuda_event_elapsed_s(self) -> float:
|
|
176
|
+
return 0.0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def metal_ctypes_available() -> bool:
|
|
180
|
+
try:
|
|
181
|
+
library = _load_library()
|
|
182
|
+
return bool(library.gds_metal_available())
|
|
183
|
+
except MetalCtypesUnavailable:
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def release_metal_device_memory() -> None:
|
|
188
|
+
library = _load_library()
|
|
189
|
+
status = library.gds_metal_release_device_memory()
|
|
190
|
+
if status != 0:
|
|
191
|
+
message = library.gds_metal_last_error()
|
|
192
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal device memory release failed")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def compare_files_metal(old_path: str | Path, new_path: str | Path) -> MetalByteCompare:
|
|
196
|
+
old_size = Path(old_path).stat().st_size
|
|
197
|
+
new_size = Path(new_path).stat().st_size
|
|
198
|
+
if old_size != new_size:
|
|
199
|
+
return MetalByteCompare(old_size=old_size, new_size=new_size, same=False)
|
|
200
|
+
|
|
201
|
+
library = _load_library()
|
|
202
|
+
result = _CByteCompareResult()
|
|
203
|
+
status = library.gds_metal_compare_files(
|
|
204
|
+
str(old_path).encode("utf-8"),
|
|
205
|
+
str(new_path).encode("utf-8"),
|
|
206
|
+
ctypes.byref(result),
|
|
207
|
+
)
|
|
208
|
+
if status != 0:
|
|
209
|
+
message = library.gds_metal_last_error()
|
|
210
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal byte compare failed")
|
|
211
|
+
return MetalByteCompare(old_size=int(result.old_size), new_size=int(result.new_size), same=bool(result.same))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_metal_ctypes_tile_prefilter(
|
|
215
|
+
old_buffer: PolygonBuffer,
|
|
216
|
+
old_canonical_ids,
|
|
217
|
+
new_buffer: PolygonBuffer,
|
|
218
|
+
new_canonical_ids,
|
|
219
|
+
config: TileConfig,
|
|
220
|
+
) -> CpuTilePrefilterResult:
|
|
221
|
+
old_packed = _pack_buffer(old_buffer, old_canonical_ids)
|
|
222
|
+
new_packed = _pack_buffer(new_buffer, new_canonical_ids)
|
|
223
|
+
library = _load_library()
|
|
224
|
+
result = _CPrefilterResult()
|
|
225
|
+
status = library.gds_metal_prefilter(
|
|
226
|
+
_ptr_i64(old_packed.bboxes),
|
|
227
|
+
_ptr_i32(old_packed.layers),
|
|
228
|
+
_ptr_i32(old_packed.datatypes),
|
|
229
|
+
_ptr_u64(old_packed.canonical_ids),
|
|
230
|
+
ctypes.c_uint64(len(old_packed.canonical_ids)),
|
|
231
|
+
_ptr_i64(new_packed.bboxes),
|
|
232
|
+
_ptr_i32(new_packed.layers),
|
|
233
|
+
_ptr_i32(new_packed.datatypes),
|
|
234
|
+
_ptr_u64(new_packed.canonical_ids),
|
|
235
|
+
ctypes.c_uint64(len(new_packed.canonical_ids)),
|
|
236
|
+
ctypes.c_int64(config.tile_size),
|
|
237
|
+
ctypes.c_uint64(config.max_tiles_per_polygon),
|
|
238
|
+
ctypes.byref(result),
|
|
239
|
+
)
|
|
240
|
+
if status != 0:
|
|
241
|
+
message = library.gds_metal_last_error()
|
|
242
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal tile prefilter failed")
|
|
243
|
+
try:
|
|
244
|
+
return CpuTilePrefilterResult(
|
|
245
|
+
candidate_tiles=_candidate_tiles_from_result(result),
|
|
246
|
+
oversized_candidates=_oversized_from_result(result),
|
|
247
|
+
assignment_count=int(result.assignment_count),
|
|
248
|
+
collapsed_pair_count=int(result.collapsed_pair_count),
|
|
249
|
+
oversized_matched_count=int(result.oversized_matched_count),
|
|
250
|
+
)
|
|
251
|
+
finally:
|
|
252
|
+
library.gds_metal_free_prefilter_result(ctypes.byref(result))
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def run_metal_rect_set_exact_fragments(
|
|
256
|
+
old_rects: np.ndarray,
|
|
257
|
+
new_rects: np.ndarray,
|
|
258
|
+
old_counts: np.ndarray,
|
|
259
|
+
new_counts: np.ndarray,
|
|
260
|
+
*,
|
|
261
|
+
max_fragments: int = 4096,
|
|
262
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
263
|
+
library = _load_library()
|
|
264
|
+
old_rects = np.ascontiguousarray(old_rects, dtype=np.int64)
|
|
265
|
+
new_rects = np.ascontiguousarray(new_rects, dtype=np.int64)
|
|
266
|
+
old_counts = np.ascontiguousarray(old_counts, dtype=np.uint8)
|
|
267
|
+
new_counts = np.ascontiguousarray(new_counts, dtype=np.uint8)
|
|
268
|
+
if old_rects.ndim != 3 or old_rects.shape[2] != 4:
|
|
269
|
+
raise ValueError("old_rects must have shape (N, max_rects, 4)")
|
|
270
|
+
if new_rects.shape != old_rects.shape:
|
|
271
|
+
raise ValueError("new_rects must match old_rects shape")
|
|
272
|
+
task_count, max_rects, _ = old_rects.shape
|
|
273
|
+
if old_counts.shape != (task_count,) or new_counts.shape != (task_count,):
|
|
274
|
+
raise ValueError("count arrays must have shape (N,)")
|
|
275
|
+
if max_rects <= 0 or max_rects > 16:
|
|
276
|
+
raise ValueError("max_rects must be between 1 and 16")
|
|
277
|
+
if max_fragments <= 0 or max_fragments > 4096:
|
|
278
|
+
raise ValueError("max_fragments must be between 1 and 4096")
|
|
279
|
+
rects = np.zeros((task_count, 2, max_fragments, 4), dtype=np.int64)
|
|
280
|
+
counts = np.zeros((task_count, 2), dtype=np.uint16)
|
|
281
|
+
areas = np.zeros((task_count, 3), dtype=np.int64)
|
|
282
|
+
status = library.gds_metal_rect_set_exact_fragments(
|
|
283
|
+
_ptr_i64(old_rects),
|
|
284
|
+
_ptr_i64(new_rects),
|
|
285
|
+
old_counts.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)),
|
|
286
|
+
new_counts.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)),
|
|
287
|
+
ctypes.c_uint64(task_count),
|
|
288
|
+
ctypes.c_uint16(max_rects),
|
|
289
|
+
ctypes.c_uint16(max_fragments),
|
|
290
|
+
_ptr_i64(rects),
|
|
291
|
+
counts.ctypes.data_as(ctypes.POINTER(ctypes.c_uint16)),
|
|
292
|
+
_ptr_i64(areas),
|
|
293
|
+
)
|
|
294
|
+
if status != 0:
|
|
295
|
+
message = library.gds_metal_last_error()
|
|
296
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal rectangle set exact failed")
|
|
297
|
+
return rects, counts, areas
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def run_metal_polygon_record_delta_from_hashes(old_buffer: PolygonBuffer, new_buffer: PolygonBuffer) -> MetalPolygonRecordDelta:
|
|
301
|
+
if old_buffer.polygon_hashes is None or new_buffer.polygon_hashes is None:
|
|
302
|
+
raise MetalCtypesUnavailable("native polygon hashes are unavailable")
|
|
303
|
+
total_count = old_buffer.polygon_count + new_buffer.polygon_count
|
|
304
|
+
max_records = _record_delta_max_polygons()
|
|
305
|
+
if total_count > max_records:
|
|
306
|
+
raise MetalCtypesUnavailable(
|
|
307
|
+
f"Metal polygon record delta is limited to {max_records} total polygons; got {total_count}"
|
|
308
|
+
)
|
|
309
|
+
library = _load_library()
|
|
310
|
+
old_packed = _pack_polygon_hash_record_buffer(old_buffer)
|
|
311
|
+
new_packed = _pack_polygon_hash_record_buffer(new_buffer)
|
|
312
|
+
result = _CPolygonRecordDeltaResult()
|
|
313
|
+
status = library.gds_metal_polygon_record_delta_from_hashes(
|
|
314
|
+
_ptr_u64(old_packed["offsets"]),
|
|
315
|
+
_ptr_i32(old_packed["layers"]),
|
|
316
|
+
_ptr_i32(old_packed["datatypes"]),
|
|
317
|
+
_ptr_i64(old_packed["bboxes"]),
|
|
318
|
+
_ptr_u32(old_packed["flags"]),
|
|
319
|
+
_ptr_u64(old_packed["hashes"]),
|
|
320
|
+
ctypes.c_uint64(old_buffer.polygon_count),
|
|
321
|
+
_ptr_u64(new_packed["offsets"]),
|
|
322
|
+
_ptr_i32(new_packed["layers"]),
|
|
323
|
+
_ptr_i32(new_packed["datatypes"]),
|
|
324
|
+
_ptr_i64(new_packed["bboxes"]),
|
|
325
|
+
_ptr_u32(new_packed["flags"]),
|
|
326
|
+
_ptr_u64(new_packed["hashes"]),
|
|
327
|
+
ctypes.c_uint64(new_buffer.polygon_count),
|
|
328
|
+
ctypes.byref(result),
|
|
329
|
+
)
|
|
330
|
+
if status != 0:
|
|
331
|
+
message = library.gds_metal_last_error()
|
|
332
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal polygon record delta failed")
|
|
333
|
+
try:
|
|
334
|
+
count = int(result.record_count)
|
|
335
|
+
return MetalPolygonRecordDelta(
|
|
336
|
+
layers=_copy_result_array(result.layers, count, np.int32),
|
|
337
|
+
datatypes=_copy_result_array(result.datatypes, count, np.int32),
|
|
338
|
+
directions=_copy_result_array(result.directions, count, np.uint8),
|
|
339
|
+
counts=_copy_result_array(result.counts, count, np.uint64),
|
|
340
|
+
bboxes=_copy_result_array(result.bboxes, count * 4, np.int64).reshape((count, 4)),
|
|
341
|
+
vertex_counts=_copy_result_array(result.vertex_counts, count, np.uint64),
|
|
342
|
+
flags=_copy_result_array(result.flags, count, np.uint32),
|
|
343
|
+
hashes=_copy_result_array(result.hashes, count * 2, np.uint64).reshape((count, 2)),
|
|
344
|
+
representative_indices=_copy_result_array(result.representative_indices, count, np.uint64),
|
|
345
|
+
old_input_count=int(result.old_input_count),
|
|
346
|
+
new_input_count=int(result.new_input_count),
|
|
347
|
+
matched_record_count=int(result.matched_record_count),
|
|
348
|
+
reduced_key_count=int(result.reduced_key_count),
|
|
349
|
+
metal_kernel_count=int(result.metal_kernel_count),
|
|
350
|
+
metal_event_elapsed_s=float(result.metal_event_elapsed_ms) / 1000.0,
|
|
351
|
+
transferred_bytes=_polygon_record_transfer_bytes(old_packed, new_packed),
|
|
352
|
+
)
|
|
353
|
+
finally:
|
|
354
|
+
library.gds_metal_free_polygon_record_delta_result(ctypes.byref(result))
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def run_metal_context_overlap_pairs(
|
|
358
|
+
component_bboxes: np.ndarray,
|
|
359
|
+
component_layers: np.ndarray,
|
|
360
|
+
component_datatypes: np.ndarray,
|
|
361
|
+
polygon_bboxes: np.ndarray,
|
|
362
|
+
polygon_layers: np.ndarray,
|
|
363
|
+
polygon_datatypes: np.ndarray,
|
|
364
|
+
) -> MetalContextOverlapPairs:
|
|
365
|
+
component_bboxes = np.ascontiguousarray(component_bboxes, dtype=np.int64)
|
|
366
|
+
component_layers = np.ascontiguousarray(component_layers, dtype=np.int32)
|
|
367
|
+
component_datatypes = np.ascontiguousarray(component_datatypes, dtype=np.int32)
|
|
368
|
+
polygon_bboxes = np.ascontiguousarray(polygon_bboxes, dtype=np.int64)
|
|
369
|
+
polygon_layers = np.ascontiguousarray(polygon_layers, dtype=np.int32)
|
|
370
|
+
polygon_datatypes = np.ascontiguousarray(polygon_datatypes, dtype=np.int32)
|
|
371
|
+
if component_bboxes.ndim != 2 or component_bboxes.shape[1] != 4:
|
|
372
|
+
raise ValueError("component_bboxes must have shape (N, 4)")
|
|
373
|
+
if polygon_bboxes.ndim != 2 or polygon_bboxes.shape[1] != 4:
|
|
374
|
+
raise ValueError("polygon_bboxes must have shape (M, 4)")
|
|
375
|
+
component_count = component_bboxes.shape[0]
|
|
376
|
+
polygon_count = polygon_bboxes.shape[0]
|
|
377
|
+
if component_layers.shape != (component_count,) or component_datatypes.shape != (component_count,):
|
|
378
|
+
raise ValueError("component layer/datatype arrays must have shape (N,)")
|
|
379
|
+
if polygon_layers.shape != (polygon_count,) or polygon_datatypes.shape != (polygon_count,):
|
|
380
|
+
raise ValueError("polygon layer/datatype arrays must have shape (M,)")
|
|
381
|
+
total_pairs = component_count * polygon_count
|
|
382
|
+
max_pairs = _context_overlap_max_pairs()
|
|
383
|
+
if total_pairs > max_pairs:
|
|
384
|
+
raise MetalCtypesUnavailable(
|
|
385
|
+
f"Metal context overlap prototype is limited to {max_pairs} component/polygon pairs; got {total_pairs}"
|
|
386
|
+
)
|
|
387
|
+
library = _load_library()
|
|
388
|
+
result = _CContextOverlapPairsResult()
|
|
389
|
+
status = library.gds_metal_context_overlap_pairs(
|
|
390
|
+
_ptr_i64(component_bboxes.reshape((-1,))),
|
|
391
|
+
_ptr_i32(component_layers),
|
|
392
|
+
_ptr_i32(component_datatypes),
|
|
393
|
+
ctypes.c_uint64(component_count),
|
|
394
|
+
_ptr_i64(polygon_bboxes.reshape((-1,))),
|
|
395
|
+
_ptr_i32(polygon_layers),
|
|
396
|
+
_ptr_i32(polygon_datatypes),
|
|
397
|
+
ctypes.c_uint64(polygon_count),
|
|
398
|
+
ctypes.byref(result),
|
|
399
|
+
)
|
|
400
|
+
if status != 0:
|
|
401
|
+
message = library.gds_metal_last_error()
|
|
402
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal context overlap pairs failed")
|
|
403
|
+
try:
|
|
404
|
+
count = int(result.pair_count)
|
|
405
|
+
return MetalContextOverlapPairs(
|
|
406
|
+
component_indices=_copy_result_array(result.component_indices, count, np.uint64),
|
|
407
|
+
polygon_indices=_copy_result_array(result.polygon_indices, count, np.uint64),
|
|
408
|
+
metal_kernel_count=int(result.metal_kernel_count),
|
|
409
|
+
metal_event_elapsed_s=float(result.metal_event_elapsed_ms) / 1000.0,
|
|
410
|
+
transferred_bytes=int(
|
|
411
|
+
component_bboxes.nbytes
|
|
412
|
+
+ component_layers.nbytes
|
|
413
|
+
+ component_datatypes.nbytes
|
|
414
|
+
+ polygon_bboxes.nbytes
|
|
415
|
+
+ polygon_layers.nbytes
|
|
416
|
+
+ polygon_datatypes.nbytes
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
finally:
|
|
420
|
+
library.gds_metal_free_context_overlap_pairs_result(ctypes.byref(result))
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def run_metal_boundary_loops_from_edges(edges: np.ndarray) -> MetalBoundaryLoopResult:
|
|
424
|
+
edges = np.ascontiguousarray(edges, dtype=np.int64)
|
|
425
|
+
if edges.ndim != 2 or edges.shape[1] != 4:
|
|
426
|
+
raise ValueError("edges must have shape (N, 4)")
|
|
427
|
+
library = _load_library()
|
|
428
|
+
result = _CBoundaryLoopResult()
|
|
429
|
+
status = library.gds_metal_boundary_loops_from_edges(
|
|
430
|
+
_ptr_i64(edges.reshape((-1,))),
|
|
431
|
+
ctypes.c_uint64(len(edges)),
|
|
432
|
+
ctypes.byref(result),
|
|
433
|
+
)
|
|
434
|
+
if status != 0:
|
|
435
|
+
message = library.gds_metal_last_error()
|
|
436
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal boundary loop tracing failed")
|
|
437
|
+
try:
|
|
438
|
+
count = int(result.fragment_count)
|
|
439
|
+
vertex_count = int(result.vertex_count)
|
|
440
|
+
vertices = _copy_result_array(result.vertices_xy, vertex_count * 2, np.int64).reshape((vertex_count, 2))
|
|
441
|
+
offsets = _copy_result_array(result.fragment_offsets, count + 1, np.uint64)
|
|
442
|
+
bboxes = _copy_result_array(result.bboxes, count * 4, np.int64).reshape((count, 4))
|
|
443
|
+
twice_areas = _copy_result_array(result.twice_areas, count, np.int64)
|
|
444
|
+
fragments = []
|
|
445
|
+
for index in range(count):
|
|
446
|
+
start = int(offsets[index])
|
|
447
|
+
stop = int(offsets[index + 1])
|
|
448
|
+
points = tuple((int(x), int(y)) for x, y in vertices[start:stop])
|
|
449
|
+
bbox = tuple(int(value) for value in bboxes[index])
|
|
450
|
+
fragments.append(DiffFragment(points=points, bbox=bbox, twice_area=int(twice_areas[index])))
|
|
451
|
+
return MetalBoundaryLoopResult(
|
|
452
|
+
fragments=tuple(sorted(fragments, key=lambda fragment: (fragment.bbox, fragment.twice_area, fragment.points))),
|
|
453
|
+
dropped_zero_area_loop_count=int(result.dropped_zero_area_loop_count),
|
|
454
|
+
metal_kernel_count=int(result.metal_kernel_count),
|
|
455
|
+
metal_event_elapsed_s=float(result.metal_event_elapsed_ms) / 1000.0,
|
|
456
|
+
transferred_bytes=int(edges.nbytes),
|
|
457
|
+
)
|
|
458
|
+
finally:
|
|
459
|
+
library.gds_metal_free_boundary_loop_result(ctypes.byref(result))
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def run_metal_one_sided_boundary_edges(
|
|
463
|
+
vertices_xy: np.ndarray,
|
|
464
|
+
polygon_offsets: np.ndarray,
|
|
465
|
+
bboxes: np.ndarray,
|
|
466
|
+
signed_twice_areas: np.ndarray,
|
|
467
|
+
segment_points: np.ndarray,
|
|
468
|
+
segment_polygon_indices: np.ndarray,
|
|
469
|
+
) -> np.ndarray:
|
|
470
|
+
vertices_xy = np.ascontiguousarray(vertices_xy, dtype=np.int64)
|
|
471
|
+
polygon_offsets = np.ascontiguousarray(polygon_offsets, dtype=np.uint64)
|
|
472
|
+
bboxes = np.ascontiguousarray(bboxes, dtype=np.int64)
|
|
473
|
+
signed_twice_areas = np.ascontiguousarray(signed_twice_areas, dtype=np.int64)
|
|
474
|
+
segment_points = np.ascontiguousarray(segment_points, dtype=np.float64)
|
|
475
|
+
segment_polygon_indices = np.ascontiguousarray(segment_polygon_indices, dtype=np.uint32)
|
|
476
|
+
if vertices_xy.ndim != 2 or vertices_xy.shape[1] != 2:
|
|
477
|
+
raise ValueError("vertices_xy must have shape (N, 2)")
|
|
478
|
+
if polygon_offsets.ndim != 1 or len(polygon_offsets) < 1:
|
|
479
|
+
raise ValueError("polygon_offsets must be a non-empty vector")
|
|
480
|
+
polygon_count = len(polygon_offsets) - 1
|
|
481
|
+
if bboxes.shape != (polygon_count, 4):
|
|
482
|
+
raise ValueError("bboxes must have shape (polygon_count, 4)")
|
|
483
|
+
if signed_twice_areas.shape != (polygon_count,):
|
|
484
|
+
raise ValueError("signed_twice_areas must have shape (polygon_count,)")
|
|
485
|
+
if segment_points.ndim != 2 or segment_points.shape[1] != 4:
|
|
486
|
+
raise ValueError("segment_points must have shape (segment_count, 4)")
|
|
487
|
+
segment_count = len(segment_points)
|
|
488
|
+
if segment_polygon_indices.shape != (segment_count,):
|
|
489
|
+
raise ValueError("segment_polygon_indices must have shape (segment_count,)")
|
|
490
|
+
out_edges = np.zeros((segment_count, 4), dtype=np.float64)
|
|
491
|
+
out_keep = np.zeros((segment_count,), dtype=np.uint8)
|
|
492
|
+
library = _load_library()
|
|
493
|
+
status = library.gds_metal_one_sided_boundary_edges(
|
|
494
|
+
_ptr_i64(vertices_xy.reshape((-1,))),
|
|
495
|
+
ctypes.c_uint64(len(vertices_xy)),
|
|
496
|
+
_ptr_u64(polygon_offsets),
|
|
497
|
+
_ptr_i64(bboxes.reshape((-1,))),
|
|
498
|
+
_ptr_i64(signed_twice_areas),
|
|
499
|
+
ctypes.c_uint64(polygon_count),
|
|
500
|
+
_ptr_f64(segment_points.reshape((-1,))),
|
|
501
|
+
segment_polygon_indices.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)),
|
|
502
|
+
ctypes.c_uint64(segment_count),
|
|
503
|
+
_ptr_f64(out_edges.reshape((-1,))),
|
|
504
|
+
out_keep.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)),
|
|
505
|
+
)
|
|
506
|
+
if status != 0:
|
|
507
|
+
message = library.gds_metal_last_error()
|
|
508
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal one-sided boundary edges failed")
|
|
509
|
+
return out_edges[out_keep.astype(bool)].reshape((-1, 2, 2))
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def run_metal_mixed_boundary_edges(
|
|
513
|
+
vertices_xy: np.ndarray,
|
|
514
|
+
polygon_offsets: np.ndarray,
|
|
515
|
+
bboxes: np.ndarray,
|
|
516
|
+
polygon_sides: np.ndarray,
|
|
517
|
+
segment_points: np.ndarray,
|
|
518
|
+
*,
|
|
519
|
+
direction: str,
|
|
520
|
+
) -> np.ndarray:
|
|
521
|
+
vertices_xy = np.ascontiguousarray(vertices_xy, dtype=np.int64)
|
|
522
|
+
polygon_offsets = np.ascontiguousarray(polygon_offsets, dtype=np.uint64)
|
|
523
|
+
bboxes = np.ascontiguousarray(bboxes, dtype=np.int64)
|
|
524
|
+
polygon_sides = np.ascontiguousarray(polygon_sides, dtype=np.uint8)
|
|
525
|
+
segment_points = np.ascontiguousarray(segment_points, dtype=np.float64)
|
|
526
|
+
if direction not in {"old", "new"}:
|
|
527
|
+
raise ValueError("direction must be 'old' or 'new'")
|
|
528
|
+
if vertices_xy.ndim != 2 or vertices_xy.shape[1] != 2:
|
|
529
|
+
raise ValueError("vertices_xy must have shape (N, 2)")
|
|
530
|
+
if polygon_offsets.ndim != 1 or len(polygon_offsets) < 1:
|
|
531
|
+
raise ValueError("polygon_offsets must be a non-empty vector")
|
|
532
|
+
polygon_count = len(polygon_offsets) - 1
|
|
533
|
+
if bboxes.shape != (polygon_count, 4):
|
|
534
|
+
raise ValueError("bboxes must have shape (polygon_count, 4)")
|
|
535
|
+
if polygon_sides.shape != (polygon_count,):
|
|
536
|
+
raise ValueError("polygon_sides must have shape (polygon_count,)")
|
|
537
|
+
if segment_points.ndim != 2 or segment_points.shape[1] != 4:
|
|
538
|
+
raise ValueError("segment_points must have shape (segment_count, 4)")
|
|
539
|
+
segment_count = len(segment_points)
|
|
540
|
+
out_edges = np.zeros((segment_count, 4), dtype=np.float64)
|
|
541
|
+
out_keep = np.zeros((segment_count,), dtype=np.uint8)
|
|
542
|
+
library = _load_library()
|
|
543
|
+
status = library.gds_metal_mixed_boundary_edges(
|
|
544
|
+
_ptr_i64(vertices_xy.reshape((-1,))),
|
|
545
|
+
ctypes.c_uint64(len(vertices_xy)),
|
|
546
|
+
_ptr_u64(polygon_offsets),
|
|
547
|
+
_ptr_i64(bboxes.reshape((-1,))),
|
|
548
|
+
polygon_sides.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)),
|
|
549
|
+
ctypes.c_uint64(polygon_count),
|
|
550
|
+
_ptr_f64(segment_points.reshape((-1,))),
|
|
551
|
+
ctypes.c_uint64(segment_count),
|
|
552
|
+
ctypes.c_uint8(1 if direction == "old" else 2),
|
|
553
|
+
_ptr_f64(out_edges.reshape((-1,))),
|
|
554
|
+
out_keep.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)),
|
|
555
|
+
)
|
|
556
|
+
if status != 0:
|
|
557
|
+
message = library.gds_metal_last_error()
|
|
558
|
+
raise MetalCtypesUnavailable(message.decode("utf-8") if message else "Metal mixed boundary edges failed")
|
|
559
|
+
return out_edges[out_keep.astype(bool)].reshape((-1, 2, 2))
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _ptr_i64(array: np.ndarray):
|
|
563
|
+
return array.ctypes.data_as(ctypes.POINTER(ctypes.c_int64))
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _ptr_i32(array: np.ndarray):
|
|
567
|
+
return array.ctypes.data_as(ctypes.POINTER(ctypes.c_int32))
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _ptr_u64(array: np.ndarray):
|
|
571
|
+
return array.ctypes.data_as(ctypes.POINTER(ctypes.c_uint64))
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _ptr_u32(array: np.ndarray):
|
|
575
|
+
return array.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32))
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _ptr_f64(array: np.ndarray):
|
|
579
|
+
return array.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _copy_result_array(pointer, count: int, dtype) -> np.ndarray:
|
|
583
|
+
if count == 0:
|
|
584
|
+
return np.empty((0,), dtype=dtype)
|
|
585
|
+
if not pointer:
|
|
586
|
+
raise MetalCtypesUnavailable("Metal result returned null output array")
|
|
587
|
+
ctype = np.ctypeslib.as_ctypes_type(dtype)
|
|
588
|
+
array = ctypes.cast(pointer, ctypes.POINTER(ctype * count)).contents
|
|
589
|
+
return np.frombuffer(array, dtype=dtype).copy()
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def _candidate_tiles_from_result(result: _CPrefilterResult) -> tuple[TileKey, ...]:
|
|
593
|
+
if result.candidate_count == 0:
|
|
594
|
+
return ()
|
|
595
|
+
array = np.ctypeslib.as_array(result.candidate_tiles, shape=(int(result.candidate_count) * 4,))
|
|
596
|
+
return tuple(
|
|
597
|
+
TileKey(LayerSpec(int(array[index]), int(array[index + 1])), int(array[index + 2]), int(array[index + 3]))
|
|
598
|
+
for index in range(0, len(array), 4)
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _oversized_from_result(result: _CPrefilterResult) -> tuple[OversizedPolygon, ...]:
|
|
603
|
+
if result.oversized_count == 0:
|
|
604
|
+
return ()
|
|
605
|
+
array = np.ctypeslib.as_array(result.oversized, shape=(int(result.oversized_count) * 8,))
|
|
606
|
+
return tuple(
|
|
607
|
+
OversizedPolygon(
|
|
608
|
+
layer=LayerSpec(int(array[index]), int(array[index + 1])),
|
|
609
|
+
canonical_id=int(array[index + 2]),
|
|
610
|
+
side_mask=int(array[index + 3]),
|
|
611
|
+
tile_bbox=(int(array[index + 4]), int(array[index + 5]), int(array[index + 6]), int(array[index + 7])),
|
|
612
|
+
)
|
|
613
|
+
for index in range(0, len(array), 8)
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _pack_buffer(buffer: PolygonBuffer, canonical_ids) -> _PackedBuffer:
|
|
618
|
+
ids = np.ascontiguousarray(canonical_ids, dtype=np.uint64)
|
|
619
|
+
if len(ids) != buffer.polygon_count:
|
|
620
|
+
raise ValueError("canonical id vector length must match polygon count")
|
|
621
|
+
layer_values = np.asarray([layer.layer for layer in buffer.layer_table.layers], dtype=np.int32)
|
|
622
|
+
datatype_values = np.asarray([layer.datatype for layer in buffer.layer_table.layers], dtype=np.int32)
|
|
623
|
+
return _PackedBuffer(
|
|
624
|
+
bboxes=np.ascontiguousarray(buffer.bboxes, dtype=np.int64),
|
|
625
|
+
layers=np.ascontiguousarray(layer_values[buffer.layer_ids], dtype=np.int32),
|
|
626
|
+
datatypes=np.ascontiguousarray(datatype_values[buffer.layer_ids], dtype=np.int32),
|
|
627
|
+
canonical_ids=ids,
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def _pack_polygon_hash_record_buffer(buffer: PolygonBuffer) -> dict[str, np.ndarray]:
|
|
632
|
+
if buffer.polygon_hashes is None:
|
|
633
|
+
raise MetalCtypesUnavailable("native polygon hashes are unavailable")
|
|
634
|
+
layer_values = np.asarray([layer.layer for layer in buffer.layer_table.layers], dtype=np.int32)
|
|
635
|
+
datatype_values = np.asarray([layer.datatype for layer in buffer.layer_table.layers], dtype=np.int32)
|
|
636
|
+
layers = np.ascontiguousarray(layer_values[buffer.layer_ids], dtype=np.int32)
|
|
637
|
+
datatypes = np.ascontiguousarray(datatype_values[buffer.layer_ids], dtype=np.int32)
|
|
638
|
+
return {
|
|
639
|
+
"offsets": np.ascontiguousarray(buffer.polygon_offsets, dtype=np.uint64),
|
|
640
|
+
"layers": layers,
|
|
641
|
+
"datatypes": datatypes,
|
|
642
|
+
"bboxes": np.ascontiguousarray(buffer.bboxes.reshape((-1,)), dtype=np.int64),
|
|
643
|
+
"flags": np.ascontiguousarray(buffer.flags, dtype=np.uint32),
|
|
644
|
+
"hashes": np.ascontiguousarray(buffer.polygon_hashes.reshape((-1,)), dtype=np.uint64),
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _polygon_record_transfer_bytes(*packed_buffers: dict[str, np.ndarray]) -> int:
|
|
649
|
+
return sum(int(array.nbytes) for packed in packed_buffers for array in packed.values())
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _record_delta_max_polygons() -> int:
|
|
653
|
+
import os
|
|
654
|
+
|
|
655
|
+
try:
|
|
656
|
+
value = int(os.environ.get("GDS_METAL_RECORD_DELTA_MAX_POLYGONS", ""))
|
|
657
|
+
except ValueError:
|
|
658
|
+
return 10_000_000
|
|
659
|
+
return value if value > 0 else 10_000_000
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _context_overlap_max_pairs() -> int:
|
|
663
|
+
import os
|
|
664
|
+
|
|
665
|
+
try:
|
|
666
|
+
value = int(os.environ.get("GDS_METAL_CONTEXT_OVERLAP_MAX_PAIRS", ""))
|
|
667
|
+
except ValueError:
|
|
668
|
+
return 64 * 1024 * 1024
|
|
669
|
+
return value if value > 0 else 64 * 1024 * 1024
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
@lru_cache(maxsize=1)
|
|
673
|
+
def _load_library() -> ctypes.CDLL:
|
|
674
|
+
if platform.system() != "Darwin" or platform.machine() not in {"arm64", "aarch64"}:
|
|
675
|
+
raise MetalCtypesUnavailable("Metal ctypes backend requires macOS on Apple Silicon")
|
|
676
|
+
path = _library_path()
|
|
677
|
+
library = ctypes.CDLL(str(path))
|
|
678
|
+
library.gds_metal_last_error.argtypes = []
|
|
679
|
+
library.gds_metal_last_error.restype = ctypes.c_char_p
|
|
680
|
+
library.gds_metal_available.argtypes = []
|
|
681
|
+
library.gds_metal_available.restype = ctypes.c_int
|
|
682
|
+
library.gds_metal_release_device_memory.argtypes = []
|
|
683
|
+
library.gds_metal_release_device_memory.restype = ctypes.c_int
|
|
684
|
+
library.gds_metal_compare_files.argtypes = [
|
|
685
|
+
ctypes.c_char_p,
|
|
686
|
+
ctypes.c_char_p,
|
|
687
|
+
ctypes.POINTER(_CByteCompareResult),
|
|
688
|
+
]
|
|
689
|
+
library.gds_metal_compare_files.restype = ctypes.c_int
|
|
690
|
+
library.gds_metal_prefilter.argtypes = [
|
|
691
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
692
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
693
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
694
|
+
ctypes.POINTER(ctypes.c_uint64),
|
|
695
|
+
ctypes.c_uint64,
|
|
696
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
697
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
698
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
699
|
+
ctypes.POINTER(ctypes.c_uint64),
|
|
700
|
+
ctypes.c_uint64,
|
|
701
|
+
ctypes.c_int64,
|
|
702
|
+
ctypes.c_uint64,
|
|
703
|
+
ctypes.POINTER(_CPrefilterResult),
|
|
704
|
+
]
|
|
705
|
+
library.gds_metal_prefilter.restype = ctypes.c_int
|
|
706
|
+
library.gds_metal_free_prefilter_result.argtypes = [ctypes.POINTER(_CPrefilterResult)]
|
|
707
|
+
library.gds_metal_free_prefilter_result.restype = None
|
|
708
|
+
library.gds_metal_rect_set_exact_fragments.argtypes = [
|
|
709
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
710
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
711
|
+
ctypes.POINTER(ctypes.c_uint8),
|
|
712
|
+
ctypes.POINTER(ctypes.c_uint8),
|
|
713
|
+
ctypes.c_uint64,
|
|
714
|
+
ctypes.c_uint16,
|
|
715
|
+
ctypes.c_uint16,
|
|
716
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
717
|
+
ctypes.POINTER(ctypes.c_uint16),
|
|
718
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
719
|
+
]
|
|
720
|
+
library.gds_metal_rect_set_exact_fragments.restype = ctypes.c_int
|
|
721
|
+
library.gds_metal_polygon_record_delta_from_hashes.argtypes = [
|
|
722
|
+
ctypes.POINTER(ctypes.c_uint64),
|
|
723
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
724
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
725
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
726
|
+
ctypes.POINTER(ctypes.c_uint32),
|
|
727
|
+
ctypes.POINTER(ctypes.c_uint64),
|
|
728
|
+
ctypes.c_uint64,
|
|
729
|
+
ctypes.POINTER(ctypes.c_uint64),
|
|
730
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
731
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
732
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
733
|
+
ctypes.POINTER(ctypes.c_uint32),
|
|
734
|
+
ctypes.POINTER(ctypes.c_uint64),
|
|
735
|
+
ctypes.c_uint64,
|
|
736
|
+
ctypes.POINTER(_CPolygonRecordDeltaResult),
|
|
737
|
+
]
|
|
738
|
+
library.gds_metal_polygon_record_delta_from_hashes.restype = ctypes.c_int
|
|
739
|
+
library.gds_metal_free_polygon_record_delta_result.argtypes = [ctypes.POINTER(_CPolygonRecordDeltaResult)]
|
|
740
|
+
library.gds_metal_free_polygon_record_delta_result.restype = None
|
|
741
|
+
library.gds_metal_context_overlap_pairs.argtypes = [
|
|
742
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
743
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
744
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
745
|
+
ctypes.c_uint64,
|
|
746
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
747
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
748
|
+
ctypes.POINTER(ctypes.c_int32),
|
|
749
|
+
ctypes.c_uint64,
|
|
750
|
+
ctypes.POINTER(_CContextOverlapPairsResult),
|
|
751
|
+
]
|
|
752
|
+
library.gds_metal_context_overlap_pairs.restype = ctypes.c_int
|
|
753
|
+
library.gds_metal_free_context_overlap_pairs_result.argtypes = [ctypes.POINTER(_CContextOverlapPairsResult)]
|
|
754
|
+
library.gds_metal_free_context_overlap_pairs_result.restype = None
|
|
755
|
+
library.gds_metal_boundary_loops_from_edges.argtypes = [
|
|
756
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
757
|
+
ctypes.c_uint64,
|
|
758
|
+
ctypes.POINTER(_CBoundaryLoopResult),
|
|
759
|
+
]
|
|
760
|
+
library.gds_metal_boundary_loops_from_edges.restype = ctypes.c_int
|
|
761
|
+
library.gds_metal_free_boundary_loop_result.argtypes = [ctypes.POINTER(_CBoundaryLoopResult)]
|
|
762
|
+
library.gds_metal_free_boundary_loop_result.restype = None
|
|
763
|
+
library.gds_metal_one_sided_boundary_edges.argtypes = [
|
|
764
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
765
|
+
ctypes.c_uint64,
|
|
766
|
+
ctypes.POINTER(ctypes.c_uint64),
|
|
767
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
768
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
769
|
+
ctypes.c_uint64,
|
|
770
|
+
ctypes.POINTER(ctypes.c_double),
|
|
771
|
+
ctypes.POINTER(ctypes.c_uint32),
|
|
772
|
+
ctypes.c_uint64,
|
|
773
|
+
ctypes.POINTER(ctypes.c_double),
|
|
774
|
+
ctypes.POINTER(ctypes.c_uint8),
|
|
775
|
+
]
|
|
776
|
+
library.gds_metal_one_sided_boundary_edges.restype = ctypes.c_int
|
|
777
|
+
library.gds_metal_mixed_boundary_edges.argtypes = [
|
|
778
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
779
|
+
ctypes.c_uint64,
|
|
780
|
+
ctypes.POINTER(ctypes.c_uint64),
|
|
781
|
+
ctypes.POINTER(ctypes.c_int64),
|
|
782
|
+
ctypes.POINTER(ctypes.c_uint8),
|
|
783
|
+
ctypes.c_uint64,
|
|
784
|
+
ctypes.POINTER(ctypes.c_double),
|
|
785
|
+
ctypes.c_uint64,
|
|
786
|
+
ctypes.c_uint8,
|
|
787
|
+
ctypes.POINTER(ctypes.c_double),
|
|
788
|
+
ctypes.POINTER(ctypes.c_uint8),
|
|
789
|
+
]
|
|
790
|
+
library.gds_metal_mixed_boundary_edges.restype = ctypes.c_int
|
|
791
|
+
return library
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def _build_library(path: Path) -> None:
|
|
795
|
+
source = _source_path()
|
|
796
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
797
|
+
compiler = _clangxx_path()
|
|
798
|
+
command = [
|
|
799
|
+
compiler,
|
|
800
|
+
"-std=c++17",
|
|
801
|
+
"-O3",
|
|
802
|
+
"-dynamiclib",
|
|
803
|
+
"-fPIC",
|
|
804
|
+
"-fobjc-arc",
|
|
805
|
+
"-isysroot",
|
|
806
|
+
_sdk_path(),
|
|
807
|
+
str(source),
|
|
808
|
+
"-framework",
|
|
809
|
+
"Foundation",
|
|
810
|
+
"-framework",
|
|
811
|
+
"Metal",
|
|
812
|
+
"-o",
|
|
813
|
+
str(path),
|
|
814
|
+
]
|
|
815
|
+
try:
|
|
816
|
+
completed = subprocess.run(command, check=False, text=True, capture_output=True)
|
|
817
|
+
except FileNotFoundError as exc:
|
|
818
|
+
raise MetalCtypesUnavailable("clang++ is not available") from exc
|
|
819
|
+
if completed.returncode != 0:
|
|
820
|
+
raise MetalCtypesUnavailable((completed.stderr or completed.stdout).strip() or "clang++ failed")
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _clangxx_path() -> str:
|
|
824
|
+
try:
|
|
825
|
+
completed = subprocess.run(["xcrun", "--find", "clang++"], check=False, text=True, capture_output=True)
|
|
826
|
+
except FileNotFoundError:
|
|
827
|
+
return "clang++"
|
|
828
|
+
candidate = completed.stdout.strip()
|
|
829
|
+
return candidate if completed.returncode == 0 and candidate else "clang++"
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def _sdk_path() -> str:
|
|
833
|
+
try:
|
|
834
|
+
completed = subprocess.run(["xcrun", "--show-sdk-path"], check=False, text=True, capture_output=True)
|
|
835
|
+
except FileNotFoundError:
|
|
836
|
+
return "/"
|
|
837
|
+
candidate = completed.stdout.strip()
|
|
838
|
+
return candidate if completed.returncode == 0 and candidate else "/"
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def _library_path() -> Path:
|
|
842
|
+
compiler = _clangxx_path()
|
|
843
|
+
sdk = _sdk_path()
|
|
844
|
+
return ensure_cached_native_library(
|
|
845
|
+
"_gds_compare_metal_ctypes.so",
|
|
846
|
+
sources=(_source_path(),),
|
|
847
|
+
build_key=(
|
|
848
|
+
compiler,
|
|
849
|
+
"-std=c++17",
|
|
850
|
+
"-O3",
|
|
851
|
+
"-dynamiclib",
|
|
852
|
+
"-fPIC",
|
|
853
|
+
"-fobjc-arc",
|
|
854
|
+
"-isysroot",
|
|
855
|
+
sdk,
|
|
856
|
+
"-framework",
|
|
857
|
+
"Foundation",
|
|
858
|
+
"-framework",
|
|
859
|
+
"Metal",
|
|
860
|
+
),
|
|
861
|
+
builder=_build_library,
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def _source_path() -> Path:
|
|
866
|
+
return Path(__file__).resolve().parents[1] / "native_src" / "gdsdiff" / "metal_ctypes.mm"
|