Skip to content

Safe Texture lifetime handling for readback-free encoding#104

Draft
ruccho wants to merge 2 commits intomainfrom
feature/safe_texture_lifetime
Draft

Safe Texture lifetime handling for readback-free encoding#104
ruccho wants to merge 2 commits intomainfrom
feature/safe_texture_lifetime

Conversation

@ruccho
Copy link
Copy Markdown
Collaborator

@ruccho ruccho commented Apr 10, 2026

Closes #89

Problem

On macOS, resizing the window during recording may cause a crash (EXC_BAD_ACCESS in objc_retainMetalTexture::try_from_unity_native_texture_ptr). The root cause is that GetNativeTexturePtr() was called at frame capture time, and the resulting pointer was stored in the pipeline. By the time the native encoder consumed it, the RenderTexture could have been released and recreated (due to resize), leaving a dangling pointer.

On Vulkan (Android), the same class of bug exists but is harder to trigger: VkImage has no reference-counting mechanism, so even a successful retain-equivalent at push time would not protect the handle through to the deferred graphics event.

Solution

Defer GetNativeTexturePtr() to the latest safe moment — immediately before CommandBuffer.IssuePluginEventAndData on the main thread.

Before:

Capture → GetNativeTexturePtr() → store nint → FFI (objc_retain / VkImage copy) → ... → Graphics Event → blit

After:

Capture → store TextureHandle (weak GCHandle) → FFI (token only, no retain) → ... → Graphics Event callback → resolve token → GetNativeTexturePtr() → validity check → write to repr(C) context → IssuePluginEventAndData → blit (retain + use)

Key properties:

  • GetNativeTexturePtr() is called on the main thread, right before ExecuteCommandBuffer, so the pointer is always fresh.
  • If the texture was destroyed or recreated, the graphics event is skipped and the native context is freed — no crash, just a dropped frame.

API changes

  • Introduced TextureHandle (opaque struct in UniEnc.Unity) wrapping a weak GCHandle. Provides Alloc(Texture) / Free().
  • VideoEncoderExtensions.UnsafePushUnityFrameAsync now takes TextureHandle instead of nint.
  • Old nint-based overloads are marked [Obsolete("...", true)].

Rust-side changes

  • VideoFrame::BlitSource no longer holds a platform texture (MetalTexture / VulkanTexture). It stores a texture_token: usize and PhantomData<BlitSourceType>.
  • GraphicsEventIssuer::issue_graphics_event callback signature changed to FnOnce(*mut c_void) — receives the native pointer as an argument at execution time.
  • Platform encoders (Apple VideoToolbox / Android MediaCodec) call TryFromUnityNativeTexturePointer inside the graphics-event closure, not at push time.
  • unienc_video_encoder_push_blit_source no longer calls try_from_unity_native_texture_ptr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS: Crash when resizing window during recording

1 participant