Skip to content

Commit 68eb8d8

Browse files
authored
Fix that memory for struct data was released before wgpu-native consumed it (#765)
1 parent 2845a51 commit 68eb8d8

File tree

2 files changed

+45
-19
lines changed

2 files changed

+45
-19
lines changed

wgpu/backends/wgpu_native/_api.py

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,14 @@ def _get_limits(id: int, device: bool = False, adapter: bool = False):
334334
# not used: maxPushConstantSize
335335
# not used: maxNonSamplerBindings
336336
)
337+
338+
# Note that the object returned by ffi.cast() does not own the memory, so we must keep a ref to the uncast object, until wgpu-native has consumed it.
339+
c_limit_next_in_chain = ffi.addressof(c_limits_native, "chain")
340+
337341
# H: nextInChain: WGPUChainedStructOut *, maxTextureDimension1D: int, maxTextureDimension2D: int, maxTextureDimension3D: int, maxTextureArrayLayers: int, maxBindGroups: int, maxBindGroupsPlusVertexBuffers: int, maxBindingsPerBindGroup: int, maxDynamicUniformBuffersPerPipelineLayout: int, maxDynamicStorageBuffersPerPipelineLayout: int, maxSampledTexturesPerShaderStage: int, maxSamplersPerShaderStage: int, maxStorageBuffersPerShaderStage: int, maxStorageTexturesPerShaderStage: int, maxUniformBuffersPerShaderStage: int, maxUniformBufferBindingSize: int, maxStorageBufferBindingSize: int, minUniformBufferOffsetAlignment: int, minStorageBufferOffsetAlignment: int, maxVertexBuffers: int, maxBufferSize: int, maxVertexAttributes: int, maxVertexBufferArrayStride: int, maxInterStageShaderVariables: int, maxColorAttachments: int, maxColorAttachmentBytesPerSample: int, maxComputeWorkgroupStorageSize: int, maxComputeInvocationsPerWorkgroup: int, maxComputeWorkgroupSizeX: int, maxComputeWorkgroupSizeY: int, maxComputeWorkgroupSizeZ: int, maxComputeWorkgroupsPerDimension: int
338342
c_limits = new_struct_p(
339343
"WGPULimits *",
340-
nextInChain=ffi.addressof(c_limits_native, "chain"),
344+
nextInChain=c_limit_next_in_chain,
341345
# not used: maxTextureDimension1D
342346
# not used: maxTextureDimension2D
343347
# not used: maxTextureDimension3D
@@ -1214,12 +1218,15 @@ def canonicalize_limit_name(name):
12141218
c_trace_path = to_c_string_view(trace_path if trace_path else None)
12151219

12161220
# H: chain: WGPUChainedStruct, tracePath: WGPUStringView
1217-
extras = new_struct_p(
1221+
c_device_extras = new_struct_p(
12181222
"WGPUDeviceExtras *",
12191223
tracePath=c_trace_path,
12201224
# not used: chain
12211225
)
1222-
extras.chain.sType = lib.WGPUSType_DeviceExtras
1226+
c_device_extras.chain.sType = lib.WGPUSType_DeviceExtras
1227+
1228+
# Note that the object returned by ffi.cast() does not own the memory, so we must keep a ref to the uncast object, until wgpu-native has consumed it.
1229+
c_device_next_in_chain = ffi.cast("WGPUChainedStruct * ", c_device_extras)
12231230

12241231
# ----- Device lost
12251232

@@ -1277,7 +1284,7 @@ def uncaptured_error_callback(
12771284
# H: nextInChain: WGPUChainedStruct *, label: WGPUStringView, requiredFeatureCount: int, requiredFeatures: WGPUFeatureName *, requiredLimits: WGPULimits *, defaultQueue: WGPUQueueDescriptor, deviceLostCallbackInfo: WGPUDeviceLostCallbackInfo, uncapturedErrorCallbackInfo: WGPUUncapturedErrorCallbackInfo
12781285
struct = new_struct_p(
12791286
"WGPUDeviceDescriptor *",
1280-
nextInChain=ffi.cast("WGPUChainedStruct * ", extras),
1287+
nextInChain=c_device_next_in_chain,
12811288
label=to_c_string_view(label),
12821289
requiredFeatureCount=len(c_features),
12831290
requiredFeatures=new_array("WGPUFeatureName[]", c_features),
@@ -1708,7 +1715,7 @@ def _create_pipeline_layout(
17081715
bind_group_layouts_ids = [x._internal for x in bind_group_layouts]
17091716
c_layout_array = new_array("WGPUBindGroupLayout[]", bind_group_layouts_ids)
17101717

1711-
next_in_chain = ffi.NULL
1718+
c_pipeline_layout_next_in_chain = ffi.NULL
17121719
if push_constant_layouts:
17131720
count = len(push_constant_layouts)
17141721
c_push_constant_ranges = new_array("WGPUPushConstantRange[]", count)
@@ -1730,12 +1737,15 @@ def _create_pipeline_layout(
17301737
# not used: chain
17311738
)
17321739
c_pipeline_layout_extras.chain.sType = lib.WGPUSType_PipelineLayoutExtras
1733-
next_in_chain = ffi.cast("WGPUChainedStruct *", c_pipeline_layout_extras)
1740+
# Note that the object returned by ffi.cast() does not own the memory, so we must keep a ref to the uncast object, until wgpu-native has consumed it.
1741+
c_pipeline_layout_next_in_chain = ffi.cast(
1742+
"WGPUChainedStruct *", c_pipeline_layout_extras
1743+
)
17341744

17351745
# H: nextInChain: WGPUChainedStruct *, label: WGPUStringView, bindGroupLayoutCount: int, bindGroupLayouts: WGPUBindGroupLayout *
17361746
struct = new_struct_p(
17371747
"WGPUPipelineLayoutDescriptor *",
1738-
nextInChain=next_in_chain,
1748+
nextInChain=c_pipeline_layout_next_in_chain,
17391749
label=to_c_string_view(label),
17401750
bindGroupLayouts=c_layout_array,
17411751
bindGroupLayoutCount=len(bind_group_layouts),
@@ -1829,10 +1839,13 @@ def create_shader_module(
18291839
"Shader code must be str for WGSL or GLSL, or bytes for SpirV."
18301840
)
18311841

1842+
# Note that the object returned by ffi.cast() does not own the memory, so we must keep a ref to the uncast object, until wgpu-native has consumed it.
1843+
c_shader_module_next_in_chain = ffi.cast("WGPUChainedStruct *", source_struct)
1844+
18321845
# H: nextInChain: WGPUChainedStruct *, label: WGPUStringView
18331846
struct = new_struct_p(
18341847
"WGPUShaderModuleDescriptor *",
1835-
nextInChain=ffi.cast("WGPUChainedStruct *", source_struct),
1848+
nextInChain=c_shader_module_next_in_chain,
18361849
label=to_c_string_view(label),
18371850
)
18381851
# H: WGPUShaderModule f(WGPUDevice device, WGPUShaderModuleDescriptor const * descriptor)
@@ -1964,7 +1977,7 @@ def create_render_pipeline(
19641977
) -> GPURenderPipeline:
19651978
primitive = {} if primitive is None else primitive
19661979
multisample = {} if multisample is None else multisample
1967-
descriptor = self._create_render_pipeline_descriptor(
1980+
descriptor, _keep_alive = self._create_render_pipeline_descriptor(
19681981
label, layout, vertex, primitive, depth_stencil, multisample, fragment
19691982
)
19701983
# H: WGPURenderPipeline f(WGPUDevice device, WGPURenderPipelineDescriptor const * descriptor)
@@ -1985,7 +1998,7 @@ def create_render_pipeline_async(
19851998
primitive = {} if primitive is None else primitive
19861999
multisample = {} if multisample is None else multisample
19872000
# TODO: wgpuDeviceCreateRenderPipelineAsync is not yet implemented in wgpu-native
1988-
descriptor = self._create_render_pipeline_descriptor(
2001+
descriptor, _keep_alive = self._create_render_pipeline_descriptor(
19892002
label, layout, vertex, primitive, depth_stencil, multisample, fragment
19902003
)
19912004

@@ -2051,6 +2064,9 @@ def _create_render_pipeline_descriptor(
20512064
multisample: structs.MultisampleState,
20522065
fragment: structs.FragmentState,
20532066
):
2067+
# We need to keep some objects alive until the struct is consumed by wgpu-native
2068+
keep_alive = []
2069+
20542070
depth_stencil = depth_stencil or {}
20552071
multisample = multisample or {}
20562072
primitive = primitive or {}
@@ -2098,12 +2114,17 @@ def _create_render_pipeline_descriptor(
20982114
conservative=primitive_extras.get("conservative", False),
20992115
)
21002116
c_primitive_state_extras.chain.sType = lib.WGPUSType_PrimitiveStateExtras
2101-
next_in_chain = ffi.cast("WGPUChainedStruct *", c_primitive_state_extras)
2117+
2118+
# Note that the object returned by ffi.cast() does not own the memory, so we must keep a ref to the uncast object, until wgpu-native has consumed it.
2119+
c_primitive_state_next_in_chain = ffi.cast(
2120+
"WGPUChainedStruct *", c_primitive_state_extras
2121+
)
2122+
keep_alive.append(c_primitive_state_extras)
21022123

21032124
# H: nextInChain: WGPUChainedStruct *, topology: WGPUPrimitiveTopology, stripIndexFormat: WGPUIndexFormat, frontFace: WGPUFrontFace, cullMode: WGPUCullMode, unclippedDepth: WGPUBool/int
21042125
c_primitive_state = new_struct(
21052126
"WGPUPrimitiveState",
2106-
nextInChain=next_in_chain,
2127+
nextInChain=c_primitive_state_next_in_chain,
21072128
topology=primitive.get("topology", "triangle-list"),
21082129
stripIndexFormat=primitive.get("strip_index_format", 0),
21092130
frontFace=primitive.get("front_face", "ccw"),
@@ -2170,7 +2191,7 @@ def _create_render_pipeline_descriptor(
21702191
multisample=c_multisample_state,
21712192
fragment=c_fragment_state,
21722193
)
2173-
return struct
2194+
return struct, keep_alive
21742195

21752196
def _create_color_target_state(self, target):
21762197
if not target.get("blend", None):
@@ -2332,27 +2353,30 @@ def _create_statistics_query_set(self, label, count, statistics):
23322353
)
23332354

23342355
def _create_query_set(self, label, type, count, statistics):
2335-
next_in_chain = ffi.NULL
2356+
c_query_set_next_in_chain = ffi.NULL
23362357
if statistics:
23372358
c_statistics = new_array("WGPUPipelineStatisticName[]", statistics)
23382359
# H: chain: WGPUChainedStruct, pipelineStatistics: WGPUPipelineStatisticName *, pipelineStatisticCount: int
2339-
query_set_descriptor_extras = new_struct_p(
2360+
c_query_set_descriptor_extras = new_struct_p(
23402361
"WGPUQuerySetDescriptorExtras *",
23412362
pipelineStatisticCount=len(statistics),
23422363
pipelineStatistics=ffi.cast(
23432364
"WGPUPipelineStatisticName const *", c_statistics
23442365
),
23452366
# not used: chain
23462367
)
2347-
query_set_descriptor_extras.chain.sType = (
2368+
c_query_set_descriptor_extras.chain.sType = (
23482369
lib.WGPUSType_QuerySetDescriptorExtras
23492370
)
2350-
next_in_chain = ffi.cast("WGPUChainedStruct *", query_set_descriptor_extras)
2371+
# Note that the object returned by ffi.cast() does not own the memory, so we must keep a ref to the uncast object, until wgpu-native has consumed it.
2372+
c_query_set_next_in_chain = ffi.cast(
2373+
"WGPUChainedStruct *", c_query_set_descriptor_extras
2374+
)
23512375

23522376
# H: nextInChain: WGPUChainedStruct *, label: WGPUStringView, type: WGPUQueryType, count: int
23532377
query_set_descriptor = new_struct_p(
23542378
"WGPUQuerySetDescriptor *",
2355-
nextInChain=next_in_chain,
2379+
nextInChain=c_query_set_next_in_chain,
23562380
label=to_c_string_view(label),
23572381
type=type,
23582382
count=count,

wgpu/backends/wgpu_native/_helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,13 @@ def get_wgpu_instance(extras=None):
9595
raise RuntimeError(
9696
"Instance already exists. Please call `set_instance_extras` before the instance is created (calls to `request_adapter` or `enumerate_adapters`)"
9797
)
98+
9899
if _the_instance is None:
99100
# H: nextInChain: WGPUChainedStruct *
100101
struct = ffi.new("WGPUInstanceDescriptor *")
101102
if extras is not None:
102-
struct.nextInChain = ffi.cast("WGPUChainedStruct *", extras)
103+
c_instance_next_in_chain = ffi.cast("WGPUChainedStruct *", extras)
104+
struct.nextInChain = c_instance_next_in_chain
103105
_the_instance = lib.wgpuCreateInstance(struct)
104106
return _the_instance
105107

0 commit comments

Comments
 (0)