realitix / vulkan

The ultimate Python binding for Vulkan API
Apache License 2.0
505 stars 46 forks source link

Aggressive GC? #94

Open gibki opened 1 month ago

gibki commented 1 month ago

My program started exploding randomly after I refactored this:

vertex_stage = vk.VkPipelineShaderStageCreateInfo(
    stage=vk.VK_SHADER_STAGE_VERTEX_BIT,
    module=vertex_shader,
    pName='main',
)
pipeline = vk.vkCreateGraphicsPipelines(
    pStages=[vertex_stage]
)

Into this:

pipeline = vk.vkCreateGraphicsPipelines(
    pStages=[
        vk.VkPipelineShaderStageCreateInfo(
            stage=vk.VK_SHADER_STAGE_VERTEX_BIT,
            module=vertex_shader,
            pName='main',
        )
    ]
)

The validation layer is (sometimes) reporting VUID-VkPipelineShaderStageCreateInfo-pName-00707(ERROR / SPEC): msgNum: -1282697375 - Validation Error: [ VUID-VkPipelineShaderStageCreateInfo-pName-00707 ] | MessageID = 0xb38b9761 | vkCreateGraphicsPipelines(): pCreateInfos[0].pStages[0].pName `` entrypoint not found for stage VK_SHADER_STAGE_VERTEX_BIT. The Vulkan spec states: pName must be the name of an OpEntryPoint in module with an execution model that matches stage (https://vulkan.lunarg.com/doc/view/1.3.296.0/windows/1.3-extensions/vkspec.html#VUID-VkPipelineShaderStageCreateInfo-pName-00707)

I think the VkPipelineShaderStageCreateInfo object and the underlying string get garbage collected before the wrapping call to vkCreateGraphicsPipelines happens.

I don't know if this is a bug, maybe I misunderstand object lifetimes, but it's definitely perplexing.

gibki commented 1 month ago

You can reproduce this with this snippet:

import vulkan as vk

for i in range(1000):
    pipeline_info = vk.VkGraphicsPipelineCreateInfo(
        pStages=[vk.VkPipelineShaderStageCreateInfo(
            pName='main'
        )]
    )
    assert vk.ffi.string(pipeline_info.pStages[0].pName) == b'main'
    vk.ffi.new('VkPipeline[%d]' % 1)  # trigger GC
    assert vk.ffi.string(pipeline_info.pStages[0].pName) == b'main' # will fail eventually

The issue is that the cdata string object is only held up by a weak reference, keyed on the result of vk.VkPipelineShaderStageCreateInfo. When vk.VkGraphicsPipelineCreateInfo converts the list of stages in creates a new array and copies over the data, but the object holding up the string goes out of scope.

This is quick and dirty modification to _cast_ptr2 to rekey the reference:

if isinstance(x, list):
    iterable_references = []
    for item in x:
        item_references = _weakkey_dict.get(item)
        if item_references:
            iterable_references.append(item_references)
    if iterable_references:
        _weakkey_dict[ret] = iterable_references
realitix commented 1 month ago

Hello @gibki, your assomption is good. Behind the scene, cffi is used and clean the ressources. Could you do a patch for that ?

gibki commented 1 month ago

https://github.com/realitix/vulkan/pull/95

I only started checking out Vulkan so I don't have a big application on hand, but it seems to work for the demos.