Repository: Genesis-Embodied-AI/Genesis
File: tests/test_sensor_camera.py (Python)

Code before:
def test_camera_destroy_cleans_shared_metadata():
    import gc
    import weakref

    scene = gs.Scene(show_viewer=False)
    scene.add_entity(morph=gs.morphs.Plane())

    camera = scene.add_sensor(gs.sensors.RasterizerCameraOptions(res=(64, 64)))

    scene.build()
    camera.read()

    shared_metadata = camera._shared_metadata
    renderer = shared_metadata.renderer
    offscreen_renderer = renderer._renderer
    offscreen_renderer_ref = weakref.ref(offscreen_renderer)

    scene.destroy()

    assert shared_metadata.renderer is None
    assert shared_metadata.context is None
    assert shared_metadata.sensors is None
    assert renderer._renderer is None

    del offscreen_renderer
    gc.collect()
    assert offscreen_renderer_ref() is None


@pytest.mark.required
def test_multiple_cameras_destroy_cleans_shared_renderer():
    import gc
    import weakref

    scene = gs.Scene(show_viewer=False)
    scene.add_entity(morph=gs.morphs.Plane())

    cam1 = scene.add_sensor(gs.sensors.RasterizerCameraOptions(res=(64, 64)))
    cam2 = scene.add_sensor(gs.sensors.RasterizerCameraOptions(res=(32, 32)))

    scene.build()
    cam1.read()
    cam2.read()

    shared_metadata = cam1._shared_metadata
    offscreen_renderer = shared_metadata.renderer._renderer
    offscreen_renderer_ref = weakref.ref(offscreen_renderer)

    scene.destroy()

    assert shared_metadata.renderer is None

Review comment (bug):
In my view it is a requirement to release memory right away without waiting for garbage collection. If someone creates another scene right away, it may cause OpenGL related race condition or similar.

Code after:


@pytest.mark.required
@pytest.mark.parametrize("backend", [gs.cuda])
@pytest.mark.skipif(not ENABLE_MADRONA, reason="BatchRenderer is not supported because 'gs_madrona' is not available.")
def test_batch_renderer_destroy():
    scene = gs.Scene(show_viewer=False)
    # FIXME: This test fails without any entities in the scene.
    scene.add_entity(morph=gs.morphs.Plane())
    cam1 = scene.add_sensor(gs.sensors.BatchRendererCameraOptions(res=(64, 64), use_rasterizer=True))
    cam2 = scene.add_sensor(gs.sensors.BatchRendererCameraOptions(res=(64, 64), use_rasterizer=True))

    scene.build()
    cam1.read()
    cam2.read()

    shared_metadata = cam1._shared_metadata
    assert cam1._shared_metadata is cam2._shared_metadata
    assert len(shared_metadata.sensors) == 2
    assert shared_metadata.renderer is not None

    scene.destroy()

    assert shared_metadata.sensors is None
    assert shared_metadata.renderer is None


@pytest.mark.required
@pytest.mark.parametrize("backend", [gs.cuda])
@pytest.mark.skipif(not ENABLE_RAYTRACER, reason="RayTracer is not supported because 'LuisaRenderPy' is not available.")
def test_raytracer_destroy():
    scene = gs.Scene(
        renderer=gs.renderers.RayTracer(
            env_surface=gs.surfaces.Emission(
                emissive_texture=gs.textures.ColorTexture(color=(0.2, 0.3, 0.5)),
            ),
            env_radius=20.0,
        ),
        show_viewer=False,
    )

    cam1 = scene.add_sensor(gs.sensors.RaytracerCameraOptions(res=(64, 64)))
    cam2 = scene.add_sensor(gs.sensors.RaytracerCameraOptions(res=(64, 64)))

    scene.build()
    cam1.read()
    cam2.read()

    shared_metadata = cam1._shared_metadata
    assert cam1._shared_metadata is cam2._shared_metadata
    assert len(shared_metadata.sensors) == 2