Skip to content

Shared texture mode: background thread crash when embedded in another D3D11 host process #95

@deblasis

Description

@deblasis

Summary

When ghostty runs in shared texture mode inside a host process that already has its own D3D11 devices and message loop (e.g. Unity Editor), a ghostty background thread crashes with a native access violation ~50ms after surface creation. The crash kills the host process.

Standalone processes (like the SharedTexture WinForms example in libghostty-dotnet) work fine with the same ghostty.dll and the same shared texture API surface.

Reproduction

  1. Build ghostty.dll from this repo (commit 7ac8cd1, branch windows)
  2. Load it into Unity 6 via P/Invoke
  3. Call the shared texture surface creation sequence:
    • ghostty_init(0, NULL) -- succeeds
    • ghostty_config_new/load/finalize -- succeeds
    • ghostty_app_new -- succeeds
    • ghostty_surface_new with hwnd=NULL, swap_chain_panel=NULL, shared_texture_out=<valid ptr> -- succeeds
    • ghostty_surface_get_d3d11_device/context/texture -- all return valid pointers
  4. Call ghostty_app_tick in a loop -- succeeds for 3 frames, D3D11 CopyResource from the texture works
  5. ~50ms after surface creation, a ghostty background thread crashes

Crash details

  • The crash is on a non-managed thread (Unity's Mono reports "domain required for stack walk")
  • Windows reports "Got a UNKNOWN while executing native code"
  • SetUnhandledExceptionFilter catches the exception but Unity's own crash handler still terminates
  • The crash is consistent and deterministic (~3 ticks, ~50ms)
  • Disabling SetOcclusion/SetFocus calls does not change the behavior
  • All ghostty native calls from the C# side are working correctly

Analysis

ghostty spawns two background threads on surface creation:

  • Renderer thread (renderer_thr) -- runs the D3D11 render loop via libxev
  • IO thread (io_thr) -- handles PTY I/O via libxev

Likely root causes (in order of probability):

1. D3D11 multithread protection not enabled

device.zig creates the D3D11 device without D3D11_CREATE_DEVICE_SINGLETHREADED, but the code never calls ID3D11Multithread::SetMultithreadProtected(TRUE) on the context. When the host process (Unity) has its own D3D11 devices and contexts on other threads, the shared GPU/driver state can conflict.

In embedded.zig there's a comment acknowledging this concern:

/// shared texture. Enable multithread protection on the context
/// when accessing from a non-render thread.

But the protection is never actually enabled.

2. libxev event loop conflict

Both background threads use libxev event loops. On Windows, libxev selects between IOCP and wepoll. Inside Unity's process (which has its own message pump and IOCP usage), the libxev initialization or first iteration may fail.

3. Timing matches lazy initialization

The ~50ms crash window (3 ticks at ~16ms each) matches the renderer thread completing its first few event loop iterations and hitting a deferred initialization failure -- not an immediate crash on thread start.

Why standalone works but embedding fails

The standalone SharedTexture example:

  • Owns the entire process, clean message loop
  • No competing D3D11 contexts
  • No competing IOCP/event loop usage

Unity embedding:

  • Unity already has D3D11 devices/contexts on multiple threads
  • Unity has its own event loop/message pump
  • ghostty's threads share GPU/driver state with Unity's threads

Environment

  • Windows 11 Pro 10.0.26200
  • Unity 6 LTS (6000.4.0f1)
  • ghostty.dll from commit 7ac8cd1
  • libghostty-dotnet branch feat/unity-integration

Suggested fix

At minimum, calling ID3D11Multithread::SetMultithreadProtected(TRUE) on the device context after creation in device.zig would address the D3D11 thread safety issue.

For full embedding support, the libxev event loop may need a compatibility mode or the threads may need to coordinate with the host process's threading model.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions