warp-lang 1.8.0__py3-none-win_amd64.whl → 1.9.0__py3-none-win_amd64.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.

Potentially problematic release.


This version of warp-lang might be problematic. Click here for more details.

Files changed (153) hide show
  1. warp/__init__.py +282 -103
  2. warp/__init__.pyi +482 -110
  3. warp/bin/warp-clang.dll +0 -0
  4. warp/bin/warp.dll +0 -0
  5. warp/build.py +93 -30
  6. warp/build_dll.py +48 -63
  7. warp/builtins.py +955 -137
  8. warp/codegen.py +327 -209
  9. warp/config.py +1 -1
  10. warp/context.py +1363 -800
  11. warp/examples/core/example_marching_cubes.py +1 -0
  12. warp/examples/core/example_render_opengl.py +100 -3
  13. warp/examples/fem/example_apic_fluid.py +98 -52
  14. warp/examples/fem/example_convection_diffusion_dg.py +25 -4
  15. warp/examples/fem/example_diffusion_mgpu.py +8 -3
  16. warp/examples/fem/utils.py +68 -22
  17. warp/examples/interop/example_jax_callable.py +34 -4
  18. warp/examples/interop/example_jax_kernel.py +27 -1
  19. warp/fabric.py +1 -1
  20. warp/fem/cache.py +27 -19
  21. warp/fem/domain.py +2 -2
  22. warp/fem/field/nodal_field.py +2 -2
  23. warp/fem/field/virtual.py +266 -166
  24. warp/fem/geometry/geometry.py +5 -5
  25. warp/fem/integrate.py +200 -91
  26. warp/fem/space/restriction.py +4 -0
  27. warp/fem/space/shape/tet_shape_function.py +3 -10
  28. warp/jax_experimental/custom_call.py +1 -1
  29. warp/jax_experimental/ffi.py +203 -54
  30. warp/marching_cubes.py +708 -0
  31. warp/native/array.h +103 -8
  32. warp/native/builtin.h +90 -9
  33. warp/native/bvh.cpp +64 -28
  34. warp/native/bvh.cu +58 -58
  35. warp/native/bvh.h +2 -2
  36. warp/native/clang/clang.cpp +7 -7
  37. warp/native/coloring.cpp +13 -3
  38. warp/native/crt.cpp +2 -2
  39. warp/native/crt.h +3 -5
  40. warp/native/cuda_util.cpp +42 -11
  41. warp/native/cuda_util.h +10 -4
  42. warp/native/exports.h +1842 -1908
  43. warp/native/fabric.h +2 -1
  44. warp/native/hashgrid.cpp +37 -37
  45. warp/native/hashgrid.cu +2 -2
  46. warp/native/initializer_array.h +1 -1
  47. warp/native/intersect.h +4 -4
  48. warp/native/mat.h +1913 -119
  49. warp/native/mathdx.cpp +43 -43
  50. warp/native/mesh.cpp +24 -24
  51. warp/native/mesh.cu +26 -26
  52. warp/native/mesh.h +5 -3
  53. warp/native/nanovdb/GridHandle.h +179 -12
  54. warp/native/nanovdb/HostBuffer.h +8 -7
  55. warp/native/nanovdb/NanoVDB.h +517 -895
  56. warp/native/nanovdb/NodeManager.h +323 -0
  57. warp/native/nanovdb/PNanoVDB.h +2 -2
  58. warp/native/quat.h +337 -16
  59. warp/native/rand.h +7 -7
  60. warp/native/range.h +7 -1
  61. warp/native/reduce.cpp +10 -10
  62. warp/native/reduce.cu +13 -14
  63. warp/native/runlength_encode.cpp +2 -2
  64. warp/native/runlength_encode.cu +5 -5
  65. warp/native/scan.cpp +3 -3
  66. warp/native/scan.cu +4 -4
  67. warp/native/sort.cpp +10 -10
  68. warp/native/sort.cu +22 -22
  69. warp/native/sparse.cpp +8 -8
  70. warp/native/sparse.cu +14 -14
  71. warp/native/spatial.h +366 -17
  72. warp/native/svd.h +23 -8
  73. warp/native/temp_buffer.h +2 -2
  74. warp/native/tile.h +303 -70
  75. warp/native/tile_radix_sort.h +5 -1
  76. warp/native/tile_reduce.h +16 -25
  77. warp/native/tuple.h +2 -2
  78. warp/native/vec.h +385 -18
  79. warp/native/volume.cpp +54 -54
  80. warp/native/volume.cu +1 -1
  81. warp/native/volume.h +2 -1
  82. warp/native/volume_builder.cu +30 -37
  83. warp/native/warp.cpp +150 -149
  84. warp/native/warp.cu +337 -193
  85. warp/native/warp.h +227 -226
  86. warp/optim/linear.py +736 -271
  87. warp/render/imgui_manager.py +289 -0
  88. warp/render/render_opengl.py +137 -57
  89. warp/render/render_usd.py +0 -1
  90. warp/sim/collide.py +1 -2
  91. warp/sim/graph_coloring.py +2 -2
  92. warp/sim/integrator_vbd.py +10 -2
  93. warp/sparse.py +559 -176
  94. warp/tape.py +2 -0
  95. warp/tests/aux_test_module_aot.py +7 -0
  96. warp/tests/cuda/test_async.py +3 -3
  97. warp/tests/cuda/test_conditional_captures.py +101 -0
  98. warp/tests/geometry/test_marching_cubes.py +233 -12
  99. warp/tests/sim/test_cloth.py +89 -6
  100. warp/tests/sim/test_coloring.py +82 -7
  101. warp/tests/test_array.py +56 -5
  102. warp/tests/test_assert.py +53 -0
  103. warp/tests/test_atomic_cas.py +127 -114
  104. warp/tests/test_codegen.py +3 -2
  105. warp/tests/test_context.py +8 -15
  106. warp/tests/test_enum.py +136 -0
  107. warp/tests/test_examples.py +2 -2
  108. warp/tests/test_fem.py +45 -2
  109. warp/tests/test_fixedarray.py +229 -0
  110. warp/tests/test_func.py +18 -15
  111. warp/tests/test_future_annotations.py +7 -5
  112. warp/tests/test_linear_solvers.py +30 -0
  113. warp/tests/test_map.py +1 -1
  114. warp/tests/test_mat.py +1540 -378
  115. warp/tests/test_mat_assign_copy.py +178 -0
  116. warp/tests/test_mat_constructors.py +574 -0
  117. warp/tests/test_module_aot.py +287 -0
  118. warp/tests/test_print.py +69 -0
  119. warp/tests/test_quat.py +162 -34
  120. warp/tests/test_quat_assign_copy.py +145 -0
  121. warp/tests/test_reload.py +2 -1
  122. warp/tests/test_sparse.py +103 -0
  123. warp/tests/test_spatial.py +140 -34
  124. warp/tests/test_spatial_assign_copy.py +160 -0
  125. warp/tests/test_static.py +48 -0
  126. warp/tests/test_struct.py +43 -3
  127. warp/tests/test_tape.py +38 -0
  128. warp/tests/test_types.py +0 -20
  129. warp/tests/test_vec.py +216 -441
  130. warp/tests/test_vec_assign_copy.py +143 -0
  131. warp/tests/test_vec_constructors.py +325 -0
  132. warp/tests/tile/test_tile.py +206 -152
  133. warp/tests/tile/test_tile_cholesky.py +605 -0
  134. warp/tests/tile/test_tile_load.py +169 -0
  135. warp/tests/tile/test_tile_mathdx.py +2 -558
  136. warp/tests/tile/test_tile_matmul.py +179 -0
  137. warp/tests/tile/test_tile_mlp.py +1 -1
  138. warp/tests/tile/test_tile_reduce.py +100 -11
  139. warp/tests/tile/test_tile_shared_memory.py +16 -16
  140. warp/tests/tile/test_tile_sort.py +59 -55
  141. warp/tests/unittest_suites.py +16 -0
  142. warp/tests/walkthrough_debug.py +1 -1
  143. warp/thirdparty/unittest_parallel.py +108 -9
  144. warp/types.py +554 -264
  145. warp/utils.py +68 -86
  146. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/METADATA +28 -65
  147. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/RECORD +150 -138
  148. warp/native/marching.cpp +0 -19
  149. warp/native/marching.cu +0 -514
  150. warp/native/marching.h +0 -19
  151. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/WHEEL +0 -0
  152. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/licenses/LICENSE.md +0 -0
  153. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,289 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ import warp as wp
19
+
20
+
21
+ class ImGuiManager:
22
+ """Base class for managing an ImGui UI."""
23
+
24
+ def __init__(self, renderer):
25
+ try:
26
+ import imgui
27
+ from imgui.integrations.pyglet import PygletProgrammablePipelineRenderer
28
+
29
+ self.imgui = imgui
30
+ self.is_available = True
31
+ except ImportError:
32
+ self.is_available = False
33
+ print('Warning: imgui not found. To use the UI, please install it with: pip install "imgui[pyglet]"')
34
+ return
35
+
36
+ self.imgui.create_context()
37
+ self.renderer = renderer
38
+ self.impl = PygletProgrammablePipelineRenderer(self.renderer.window)
39
+
40
+ def render_frame(self):
41
+ """Renders a single frame of the UI. This should be called from the main render loop."""
42
+ if not self.is_available:
43
+ return
44
+
45
+ io = self.imgui.get_io()
46
+ self.renderer.enable_mouse_interaction = not io.want_capture_mouse
47
+ self.renderer.enable_keyboard_interaction = not io.want_capture_keyboard
48
+ io.display_size = self.renderer.screen_width, self.renderer.screen_height
49
+
50
+ self.imgui.new_frame()
51
+
52
+ self.draw_ui()
53
+
54
+ self.imgui.render()
55
+ self.imgui.end_frame()
56
+ self.impl.render(self.imgui.get_draw_data())
57
+
58
+ def draw_ui(self):
59
+ """Draws the UI. To be implemented by subclasses."""
60
+ pass
61
+
62
+ def open_save_file_dialog(
63
+ self,
64
+ title: str = "Save File",
65
+ defaultextension: str = "",
66
+ filetypes: list[tuple[str, str]] | None = None,
67
+ ) -> str | None:
68
+ """Opens a file dialog for saving a file and returns the selected path."""
69
+ try:
70
+ import tkinter as tk
71
+ from tkinter import filedialog
72
+ except ImportError:
73
+ print("Warning: tkinter not found. To use the file dialog, please install it.")
74
+ return None
75
+
76
+ try:
77
+ root = tk.Tk()
78
+ except tk.TclError:
79
+ print("Warning: no display found - cannot open file dialog.")
80
+ return None
81
+
82
+ root.withdraw() # Hide the main window
83
+ file_path = filedialog.asksaveasfilename(
84
+ defaultextension=defaultextension,
85
+ filetypes=filetypes or [("All Files", "*.*")],
86
+ title=title,
87
+ )
88
+ root.destroy()
89
+ return file_path
90
+
91
+ def open_load_file_dialog(
92
+ self, title: str = "Open File", filetypes: list[tuple[str, str]] | None = None
93
+ ) -> str | None:
94
+ """Opens a file dialog for loading a file and returns the selected path."""
95
+ try:
96
+ import tkinter as tk
97
+ from tkinter import filedialog
98
+ except ImportError:
99
+ print("Warning: tkinter not found. To use the file dialog, please install it.")
100
+ return None
101
+
102
+ try:
103
+ root = tk.Tk()
104
+ except tk.TclError:
105
+ print("Warning: no display found - cannot open file dialog.")
106
+ return None
107
+
108
+ root.withdraw() # Hide the main window
109
+ file_path = filedialog.askopenfilename(
110
+ filetypes=filetypes or [("All Files", "*.*")],
111
+ title=title,
112
+ )
113
+ root.destroy()
114
+ return file_path
115
+
116
+ def drag_vec3(self, label, vec, speed=0.1, min_val=0.0, max_val=100.0):
117
+ """Helper method to create a drag widget for a wp.vec3"""
118
+ changed, *values = self.imgui.drag_float3(label, *vec, speed, min_val, max_val)
119
+ if changed:
120
+ vec = wp.vec3(*values)
121
+ return changed, vec
122
+
123
+ def drag_vec3_list(
124
+ self,
125
+ label_prefix: str,
126
+ vec_list: list[wp.vec3] | wp.array,
127
+ speed: float = 0.1,
128
+ min_val: float = 0.0,
129
+ max_val: float = 100.0,
130
+ max_num_elements: int = 10,
131
+ ) -> tuple[bool, list[wp.vec3] | wp.array]:
132
+ """Helper method to create drag widgets for a list of wp.vec3.
133
+
134
+ Note: The `label_prefix` is used to create unique IDs for each widget.
135
+ If you are displaying multiple lists, ensure that `label_prefix` is unique
136
+ for each list to prevent UI elements from interfering with each other.
137
+ """
138
+ self.imgui.text(f"List: {label_prefix} with {len(vec_list)} elements")
139
+ changed = False
140
+
141
+ # Convert to numpy array if it's a warp array
142
+ is_warp_array = isinstance(vec_list, wp.array)
143
+ working_array = vec_list.numpy() if is_warp_array else vec_list
144
+
145
+ for i, vec in enumerate(working_array[:max_num_elements]):
146
+ vec_changed, new_vec = self.drag_vec3(f"{label_prefix} {i}", vec, speed, min_val, max_val)
147
+ if vec_changed:
148
+ working_array[i] = new_vec
149
+ changed = True
150
+
151
+ if is_warp_array:
152
+ if changed:
153
+ return changed, wp.array(working_array, dtype=wp.vec3)
154
+ return changed, vec_list
155
+ return changed, working_array
156
+
157
+ def drag_vec2(
158
+ self, label: str, vec: wp.vec2, speed: float = 0.1, min_val: float = 0.0, max_val: float = 100.0
159
+ ) -> tuple[bool, wp.vec2]:
160
+ """Helper method to create a drag widget for a wp.vec2"""
161
+ changed, *values = self.imgui.drag_float2(label, *vec, speed, min_val, max_val)
162
+ if changed:
163
+ vec = wp.vec2(*values)
164
+ return changed, vec
165
+
166
+ def drag_vec2_list(
167
+ self,
168
+ label_prefix: str,
169
+ vec_list: list[wp.vec2] | wp.array,
170
+ speed: float = 0.1,
171
+ min_val: float = 0.0,
172
+ max_val: float = 100.0,
173
+ max_num_elements: int = 10,
174
+ ) -> tuple[bool, list[wp.vec2] | wp.array]:
175
+ """Helper method to create drag widgets for a list of wp.vec2.
176
+
177
+ Note: The `label_prefix` is used to create unique IDs for each widget.
178
+ If you are displaying multiple lists, ensure that `label_prefix` is unique
179
+ for each list to prevent UI elements from interfering with each other.
180
+ """
181
+ self.imgui.text(f"List: {label_prefix} with {len(vec_list)} elements")
182
+ changed = False
183
+
184
+ # Convert to numpy array if it's a warp array
185
+ is_warp_array = isinstance(vec_list, wp.array)
186
+ working_array = vec_list.numpy() if is_warp_array else vec_list
187
+
188
+ for i, vec in enumerate(working_array[:max_num_elements]):
189
+ vec_changed, new_vec = self.drag_vec2(f"{label_prefix} {i}", vec, speed, min_val, max_val)
190
+ if vec_changed:
191
+ working_array[i] = new_vec
192
+ changed = True
193
+
194
+ if is_warp_array:
195
+ if changed:
196
+ return changed, wp.array(working_array, dtype=wp.vec2)
197
+ return changed, vec_list
198
+ return changed, working_array
199
+
200
+ def drag_vec4(
201
+ self, label: str, vec: wp.vec4, speed: float = 0.1, min_val: float = 0.0, max_val: float = 100.0
202
+ ) -> tuple[bool, wp.vec4]:
203
+ """Helper method to create a drag widget for a wp.vec4"""
204
+ changed, *values = self.imgui.drag_float4(label, *vec, speed, min_val, max_val)
205
+ if changed:
206
+ vec = wp.vec4(*values)
207
+ return changed, vec
208
+
209
+ def drag_vec4_list(
210
+ self,
211
+ label_prefix: str,
212
+ vec_list: list[wp.vec4] | wp.array,
213
+ speed: float = 0.1,
214
+ min_val: float = 0.0,
215
+ max_val: float = 100.0,
216
+ max_num_elements: int = 10,
217
+ ) -> tuple[bool, list[wp.vec4] | wp.array]:
218
+ """Helper method to create drag widgets for a list of wp.vec4.
219
+
220
+ Note: The `label_prefix` is used to create unique IDs for each widget.
221
+ If you are displaying multiple lists, ensure that `label_prefix` is unique
222
+ for each list to prevent UI elements from interfering with each other.
223
+ """
224
+ self.imgui.text(f"List: {label_prefix} with {len(vec_list)} elements")
225
+ changed = False
226
+
227
+ # Convert to numpy array if it's a warp array
228
+ is_warp_array = isinstance(vec_list, wp.array)
229
+ working_array = vec_list.numpy() if is_warp_array else vec_list
230
+
231
+ for i, vec in enumerate(working_array[:max_num_elements]):
232
+ vec_changed, new_vec = self.drag_vec4(f"{label_prefix} {i}", vec, speed, min_val, max_val)
233
+ if vec_changed:
234
+ working_array[i] = new_vec
235
+ changed = True
236
+
237
+ if is_warp_array:
238
+ if changed:
239
+ return changed, wp.array(working_array, dtype=wp.vec4)
240
+ return changed, vec_list
241
+ return changed, working_array
242
+
243
+ def drag_float(
244
+ self, label: str, val: float, speed: float = 0.1, min_val: float = 0.0, max_val: float = 100.0
245
+ ) -> tuple[bool, float]:
246
+ """Helper method to create a drag widget for a float"""
247
+ changed, value = self.imgui.drag_float(label, val, speed, min_val, max_val)
248
+ if changed:
249
+ val = value
250
+ return changed, val
251
+
252
+ def drag_float_list(
253
+ self,
254
+ label_prefix: str,
255
+ float_list: list[float] | wp.array,
256
+ speed: float = 0.1,
257
+ min_val: float = 0.0,
258
+ max_val: float = 100.0,
259
+ max_num_elements: int = 10,
260
+ ) -> tuple[bool, list[float] | wp.array]:
261
+ """Helper method to create drag widgets for a list of floats.
262
+
263
+ Note: The `label_prefix` is used to create unique IDs for each widget.
264
+ If you are displaying multiple lists, ensure that `label_prefix` is unique
265
+ for each list to prevent UI elements from interfering with each other.
266
+ """
267
+ self.imgui.text(f"List: {label_prefix} with {len(float_list)} elements")
268
+ changed = False
269
+
270
+ # Convert to numpy array if it's a warp array
271
+ is_warp_array = isinstance(float_list, wp.array)
272
+ working_array = float_list.numpy() if is_warp_array else float_list
273
+
274
+ for i, val in enumerate(working_array[:max_num_elements]):
275
+ val_changed, new_val = self.drag_float(f"{label_prefix} {i}", val, speed, min_val, max_val)
276
+ if val_changed:
277
+ working_array[i] = new_val
278
+ changed = True
279
+
280
+ if is_warp_array:
281
+ if changed:
282
+ return changed, wp.array(working_array, dtype=float_list.dtype)
283
+ return changed, float_list
284
+ return changed, working_array
285
+
286
+ def shutdown(self):
287
+ if not self.is_available:
288
+ return
289
+ self.impl.shutdown()
@@ -320,15 +320,14 @@ def update_vbo_transforms(
320
320
  @wp.kernel
321
321
  def update_vbo_vertices(
322
322
  points: wp.array(dtype=wp.vec3),
323
- scale: wp.vec3,
324
323
  # outputs
325
324
  vbo_vertices: wp.array(dtype=float, ndim=2),
326
325
  ):
327
326
  tid = wp.tid()
328
327
  p = points[tid]
329
- vbo_vertices[tid, 0] = p[0] * scale[0]
330
- vbo_vertices[tid, 1] = p[1] * scale[1]
331
- vbo_vertices[tid, 2] = p[2] * scale[2]
328
+ vbo_vertices[tid, 0] = p[0]
329
+ vbo_vertices[tid, 1] = p[1]
330
+ vbo_vertices[tid, 2] = p[2]
332
331
 
333
332
 
334
333
  @wp.kernel
@@ -422,7 +421,6 @@ def compute_gfx_vertices(
422
421
  def compute_average_normals(
423
422
  indices: wp.array(dtype=int, ndim=2),
424
423
  vertices: wp.array(dtype=wp.vec3),
425
- scale: wp.vec3,
426
424
  # outputs
427
425
  normals: wp.array(dtype=wp.vec3),
428
426
  faces_per_vertex: wp.array(dtype=int),
@@ -431,9 +429,9 @@ def compute_average_normals(
431
429
  i = indices[tid, 0]
432
430
  j = indices[tid, 1]
433
431
  k = indices[tid, 2]
434
- v0 = vertices[i] * scale[0]
435
- v1 = vertices[j] * scale[1]
436
- v2 = vertices[k] * scale[2]
432
+ v0 = vertices[i]
433
+ v1 = vertices[j]
434
+ v2 = vertices[k]
437
435
  n = wp.normalize(wp.cross(v1 - v0, v2 - v0))
438
436
  wp.atomic_add(normals, i, n)
439
437
  wp.atomic_add(faces_per_vertex, i, 1)
@@ -448,16 +446,15 @@ def assemble_gfx_vertices(
448
446
  vertices: wp.array(dtype=wp.vec3, ndim=1),
449
447
  normals: wp.array(dtype=wp.vec3),
450
448
  faces_per_vertex: wp.array(dtype=int),
451
- scale: wp.vec3,
452
449
  # outputs
453
450
  gfx_vertices: wp.array(dtype=float, ndim=2),
454
451
  ):
455
452
  tid = wp.tid()
456
453
  v = vertices[tid]
457
454
  n = normals[tid] / float(faces_per_vertex[tid])
458
- gfx_vertices[tid, 0] = v[0] * scale[0]
459
- gfx_vertices[tid, 1] = v[1] * scale[1]
460
- gfx_vertices[tid, 2] = v[2] * scale[2]
455
+ gfx_vertices[tid, 0] = v[0]
456
+ gfx_vertices[tid, 1] = v[1]
457
+ gfx_vertices[tid, 2] = v[2]
461
458
  gfx_vertices[tid, 3] = n[0]
462
459
  gfx_vertices[tid, 4] = n[1]
463
460
  gfx_vertices[tid, 5] = n[2]
@@ -1001,6 +998,7 @@ class OpenGLRenderer:
1001
998
  enable_mouse_interaction=True,
1002
999
  enable_keyboard_interaction=True,
1003
1000
  device=None,
1001
+ use_legacy_opengl: bool | None = None,
1004
1002
  ):
1005
1003
  """
1006
1004
  Args:
@@ -1031,6 +1029,7 @@ class OpenGLRenderer:
1031
1029
  enable_mouse_interaction (bool): Whether to enable mouse interaction.
1032
1030
  enable_keyboard_interaction (bool): Whether to enable keyboard interaction.
1033
1031
  device (Devicelike): Where to store the internal data.
1032
+ use_legacy_opengl (bool | None): Whether to use a legacy OpenGL implementation that is more compatible with macOS. If ``None``, it will be automatically detected based on the operating system.
1034
1033
 
1035
1034
  Note:
1036
1035
 
@@ -1076,6 +1075,11 @@ class OpenGLRenderer:
1076
1075
  self.render_depth = render_depth
1077
1076
  self.enable_backface_culling = enable_backface_culling
1078
1077
 
1078
+ if use_legacy_opengl is None:
1079
+ self.use_legacy_opengl = sys.platform == "darwin"
1080
+ else:
1081
+ self.use_legacy_opengl = use_legacy_opengl
1082
+
1079
1083
  if device is None:
1080
1084
  self._device = wp.get_preferred_device()
1081
1085
  else:
@@ -1165,6 +1169,8 @@ class OpenGLRenderer:
1165
1169
  self._wp_instance_bodies = None
1166
1170
  self._update_shape_instances = False
1167
1171
  self._add_shape_instances = False
1172
+ self.instance_matrix_size = 0
1173
+ self.instance_color_size = 0
1168
1174
 
1169
1175
  # additional shape instancer used for points and line rendering
1170
1176
  self._shape_instancers = {}
@@ -2054,9 +2060,43 @@ Instances: {len(self._instances)}"""
2054
2060
  num_instances = len(self._shape_instances[shape])
2055
2061
 
2056
2062
  gl.glBindVertexArray(vao)
2057
- gl.glDrawElementsInstancedBaseInstance(
2058
- gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, num_instances, start_instance_idx
2059
- )
2063
+ if self.use_legacy_opengl:
2064
+ if self.instance_matrix_size > 0 and self.instance_color_size:
2065
+ # update attribute pointers
2066
+ matrix_size = self.instance_matrix_size
2067
+ color_size = self.instance_color_size
2068
+
2069
+ # update transforms
2070
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_transform_gl_buffer)
2071
+ for i in range(4):
2072
+ gl.glVertexAttribPointer(
2073
+ 3 + i,
2074
+ 4,
2075
+ gl.GL_FLOAT,
2076
+ gl.GL_FALSE,
2077
+ matrix_size,
2078
+ ctypes.c_void_p(start_instance_idx * matrix_size + i * matrix_size // 4),
2079
+ )
2080
+ gl.glVertexAttribDivisor(3 + i, 1)
2081
+
2082
+ # update colors
2083
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color1_buffer)
2084
+ gl.glVertexAttribPointer(
2085
+ 7, 3, gl.GL_FLOAT, gl.GL_FALSE, color_size, ctypes.c_void_p(start_instance_idx * color_size)
2086
+ )
2087
+ gl.glVertexAttribDivisor(7, 1)
2088
+
2089
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color2_buffer)
2090
+ gl.glVertexAttribPointer(
2091
+ 8, 3, gl.GL_FLOAT, gl.GL_FALSE, color_size, ctypes.c_void_p(start_instance_idx * color_size)
2092
+ )
2093
+ gl.glVertexAttribDivisor(8, 1)
2094
+
2095
+ gl.glDrawElementsInstanced(gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, num_instances)
2096
+ else:
2097
+ gl.glDrawElementsInstancedBaseInstance(
2098
+ gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, num_instances, start_instance_idx
2099
+ )
2060
2100
 
2061
2101
  start_instance_idx += num_instances
2062
2102
 
@@ -2106,9 +2146,43 @@ Instances: {len(self._instances)}"""
2106
2146
  start_instance_idx = self._inverse_instance_ids[instance]
2107
2147
 
2108
2148
  gl.glBindVertexArray(vao)
2109
- gl.glDrawElementsInstancedBaseInstance(
2110
- gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, 1, start_instance_idx
2111
- )
2149
+ if self.use_legacy_opengl:
2150
+ if self.instance_matrix_size > 0 and self.instance_color_size:
2151
+ # update attribute pointers
2152
+ matrix_size = self.instance_matrix_size
2153
+ color_size = self.instance_color_size
2154
+
2155
+ # update transforms
2156
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_transform_gl_buffer)
2157
+ for j in range(4):
2158
+ gl.glVertexAttribPointer(
2159
+ 3 + j,
2160
+ 4,
2161
+ gl.GL_FLOAT,
2162
+ gl.GL_FALSE,
2163
+ matrix_size,
2164
+ ctypes.c_void_p(start_instance_idx * matrix_size + j * matrix_size // 4),
2165
+ )
2166
+ gl.glVertexAttribDivisor(3 + j, 1)
2167
+
2168
+ # update colors
2169
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color1_buffer)
2170
+ gl.glVertexAttribPointer(
2171
+ 7, 3, gl.GL_FLOAT, gl.GL_FALSE, color_size, ctypes.c_void_p(start_instance_idx * color_size)
2172
+ )
2173
+ gl.glVertexAttribDivisor(7, 1)
2174
+
2175
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color2_buffer)
2176
+ gl.glVertexAttribPointer(
2177
+ 8, 3, gl.GL_FLOAT, gl.GL_FALSE, color_size, ctypes.c_void_p(start_instance_idx * color_size)
2178
+ )
2179
+ gl.glVertexAttribDivisor(8, 1)
2180
+
2181
+ gl.glDrawElementsInstanced(gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, 1)
2182
+ else:
2183
+ gl.glDrawElementsInstancedBaseInstance(
2184
+ gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, 1, start_instance_idx
2185
+ )
2112
2186
 
2113
2187
  if self.draw_axis:
2114
2188
  self._axis_instancer.render()
@@ -2396,6 +2470,7 @@ Instances: {len(self._instances)}"""
2396
2470
 
2397
2471
  # set up instance attribute pointers
2398
2472
  matrix_size = transforms[0].nbytes
2473
+ self.instance_matrix_size = matrix_size
2399
2474
 
2400
2475
  instance_ids = []
2401
2476
  instance_custom_ids = []
@@ -2404,6 +2479,7 @@ Instances: {len(self._instances)}"""
2404
2479
  inverse_instance_ids = {}
2405
2480
  instance_count = 0
2406
2481
  colors_size = np.zeros(3, dtype=np.float32).nbytes
2482
+ self.instance_color_size = colors_size
2407
2483
  for shape, (vao, _vbo, _ebo, _tri_count, _vertex_cuda_buffer) in self._shape_gl_buffers.items():
2408
2484
  gl.glBindVertexArray(vao)
2409
2485
 
@@ -2445,7 +2521,7 @@ Instances: {len(self._instances)}"""
2445
2521
 
2446
2522
  gl.glBindVertexArray(0)
2447
2523
 
2448
- def update_shape_instance(self, name, pos=None, rot=None, color1=None, color2=None, visible=None):
2524
+ def update_shape_instance(self, name, pos=None, rot=None, color1=None, color2=None, scale=None, visible=None):
2449
2525
  """Update the instance properties of the shape
2450
2526
 
2451
2527
  Args:
@@ -2461,7 +2537,7 @@ Instances: {len(self._instances)}"""
2461
2537
  self._switch_context()
2462
2538
 
2463
2539
  if name in self._instances:
2464
- i, body, shape, tf, scale, old_color1, old_color2, v = self._instances[name]
2540
+ i, body, shape, tf, old_scale, old_color1, old_color2, v = self._instances[name]
2465
2541
  if visible is None:
2466
2542
  visible = v
2467
2543
  new_tf = np.copy(tf)
@@ -2474,7 +2550,7 @@ Instances: {len(self._instances)}"""
2474
2550
  body,
2475
2551
  shape,
2476
2552
  new_tf,
2477
- scale,
2553
+ old_scale if scale is None else scale,
2478
2554
  old_color1 if color1 is None else color1,
2479
2555
  old_color2 if color2 is None else color2,
2480
2556
  visible,
@@ -2968,7 +3044,7 @@ Instances: {len(self._instances)}"""
2968
3044
  geo_hash = hash(("box", tuple(extents)))
2969
3045
  if geo_hash in self._shape_geo_hash:
2970
3046
  shape = self._shape_geo_hash[geo_hash]
2971
- if self.update_shape_instance(name, pos, rot):
3047
+ if self.update_shape_instance(name, pos, rot, color1=color, color2=color):
2972
3048
  return shape
2973
3049
  else:
2974
3050
  vertices, indices = self._create_box_mesh(extents)
@@ -3031,50 +3107,54 @@ Instances: {len(self._instances)}"""
3031
3107
  if not update_topology:
3032
3108
  if name in self._instances:
3033
3109
  # Update the instance's transform.
3034
- self.update_shape_instance(name, pos, rot, color1=colors)
3110
+ self.update_shape_instance(name, pos, rot, color1=colors, color2=colors, scale=scale, visible=visible)
3035
3111
 
3036
3112
  if shape is not None:
3037
3113
  # Update the shape's point positions.
3038
- self.update_shape_vertices(shape, points, scale)
3114
+ self.update_shape_vertices(shape, points)
3039
3115
 
3040
3116
  if not is_template and name not in self._instances:
3041
3117
  # Create a new instance.
3042
3118
  body = self._resolve_body_id(parent_body)
3043
- self.add_shape_instance(name, shape, body, pos, rot, color1=colors)
3119
+ self.add_shape_instance(name, shape, body, pos, rot, color1=colors, scale=scale)
3044
3120
 
3045
3121
  return shape
3046
3122
 
3047
3123
  # No existing shape for the given mesh was found, or its topology may have changed,
3048
3124
  # so we need to define a new one either way.
3049
- if smooth_shading:
3050
- normals = wp.zeros(point_count, dtype=wp.vec3)
3051
- vertices = wp.array(points, dtype=wp.vec3)
3052
- faces_per_vertex = wp.zeros(point_count, dtype=int)
3053
- wp.launch(
3054
- compute_average_normals,
3055
- dim=idx_count,
3056
- inputs=[wp.array(indices, dtype=int), vertices, scale],
3057
- outputs=[normals, faces_per_vertex],
3058
- )
3059
- gfx_vertices = wp.zeros((point_count, 8), dtype=float)
3060
- wp.launch(
3061
- assemble_gfx_vertices,
3062
- dim=point_count,
3063
- inputs=[vertices, normals, faces_per_vertex, scale],
3064
- outputs=[gfx_vertices],
3065
- )
3066
- gfx_vertices = gfx_vertices.numpy()
3067
- gfx_indices = indices.flatten()
3068
- else:
3069
- gfx_vertices = wp.zeros((idx_count * 3, 8), dtype=float)
3070
- wp.launch(
3071
- compute_gfx_vertices,
3072
- dim=idx_count,
3073
- inputs=[wp.array(indices, dtype=int), wp.array(points, dtype=wp.vec3), scale],
3074
- outputs=[gfx_vertices],
3075
- )
3076
- gfx_vertices = gfx_vertices.numpy()
3077
- gfx_indices = np.arange(idx_count * 3)
3125
+ with wp.ScopedDevice(self._device):
3126
+ if smooth_shading:
3127
+ normals = wp.zeros(point_count, dtype=wp.vec3)
3128
+ vertices = wp.array(points, dtype=wp.vec3)
3129
+ faces_per_vertex = wp.zeros(point_count, dtype=int)
3130
+ wp.launch(
3131
+ compute_average_normals,
3132
+ dim=idx_count,
3133
+ inputs=[wp.array(indices, dtype=int), vertices],
3134
+ outputs=[normals, faces_per_vertex],
3135
+ record_tape=False,
3136
+ )
3137
+ gfx_vertices = wp.zeros((point_count, 8), dtype=float)
3138
+ wp.launch(
3139
+ assemble_gfx_vertices,
3140
+ dim=point_count,
3141
+ inputs=[vertices, normals, faces_per_vertex],
3142
+ outputs=[gfx_vertices],
3143
+ record_tape=False,
3144
+ )
3145
+ gfx_vertices = gfx_vertices.numpy()
3146
+ gfx_indices = indices.flatten()
3147
+ else:
3148
+ gfx_vertices = wp.zeros((idx_count * 3, 8), dtype=float)
3149
+ wp.launch(
3150
+ compute_gfx_vertices,
3151
+ dim=idx_count,
3152
+ inputs=[wp.array(indices, dtype=int), wp.array(points, dtype=wp.vec3)],
3153
+ outputs=[gfx_vertices],
3154
+ record_tape=False,
3155
+ )
3156
+ gfx_vertices = gfx_vertices.numpy()
3157
+ gfx_indices = np.arange(idx_count * 3)
3078
3158
 
3079
3159
  # If there was a shape for the given mesh, clean it up.
3080
3160
  if shape is not None:
@@ -3090,7 +3170,7 @@ Instances: {len(self._instances)}"""
3090
3170
  if not is_template:
3091
3171
  # Create a new instance if necessary.
3092
3172
  body = self._resolve_body_id(parent_body)
3093
- self.add_shape_instance(name, shape, body, pos, rot, color1=colors)
3173
+ self.add_shape_instance(name, shape, body, pos, rot, color1=colors, scale=scale)
3094
3174
 
3095
3175
  return shape
3096
3176
 
@@ -3278,7 +3358,7 @@ Instances: {len(self._instances)}"""
3278
3358
  lines = np.array(lines)
3279
3359
  self._render_lines(name, lines, color, radius)
3280
3360
 
3281
- def update_shape_vertices(self, shape, points, scale):
3361
+ def update_shape_vertices(self, shape, points):
3282
3362
  if isinstance(points, wp.array):
3283
3363
  wp_points = points.to(self._device)
3284
3364
  else:
@@ -3291,7 +3371,7 @@ Instances: {len(self._instances)}"""
3291
3371
  wp.launch(
3292
3372
  update_vbo_vertices,
3293
3373
  dim=vertices_shape[0],
3294
- inputs=[wp_points, scale],
3374
+ inputs=[wp_points],
3295
3375
  outputs=[vbo_vertices],
3296
3376
  device=self._device,
3297
3377
  )
warp/render/render_usd.py CHANGED
@@ -647,7 +647,6 @@ class UsdRenderer:
647
647
  mesh.GetDisplayColorAttr().Set(colors, self.time)
648
648
 
649
649
  self._shape_constructors[name] = UsdGeom.Mesh
650
- self._shape_custom_scale[name] = scale
651
650
 
652
651
  if not is_template:
653
652
  _usd_set_xform(mesh, pos, rot, scale, self.time)
warp/sim/collide.py CHANGED
@@ -1236,8 +1236,7 @@ def handle_contact_pairs(
1236
1236
  p_b_body = closest_point_box(geo_scale_b, query_b)
1237
1237
  p_b_world = wp.transform_point(X_ws_b, p_b_body)
1238
1238
  diff = p_a_world - p_b_world
1239
- # use center of box A to query normal to make sure we are not inside B
1240
- query_b = wp.transform_point(X_sw_b, wp.transform_get_translation(X_ws_a))
1239
+
1241
1240
  normal = wp.transform_vector(X_ws_b, box_sdf_grad(geo_scale_b, query_b))
1242
1241
  distance = wp.dot(diff, normal)
1243
1242
 
@@ -234,7 +234,7 @@ def color_graph(
234
234
  f"graph_edge_indices must be a 2 dimensional array! The provided one is {graph_edge_indices.ndim} dimensional."
235
235
  )
236
236
 
237
- num_colors = wp.context.runtime.core.graph_coloring(
237
+ num_colors = wp.context.runtime.core.wp_graph_coloring(
238
238
  num_nodes,
239
239
  graph_edge_indices.__ctype__(),
240
240
  algorithm.value,
@@ -242,7 +242,7 @@ def color_graph(
242
242
  )
243
243
 
244
244
  if balance_colors:
245
- max_min_ratio = wp.context.runtime.core.balance_coloring(
245
+ max_min_ratio = wp.context.runtime.core.wp_balance_coloring(
246
246
  num_nodes,
247
247
  graph_edge_indices.__ctype__(),
248
248
  num_colors,