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
- Build ghostty.dll from this repo (commit 7ac8cd1, branch
windows)
- Load it into Unity 6 via P/Invoke
- 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
- Call
ghostty_app_tick in a loop -- succeeds for 3 frames, D3D11 CopyResource from the texture works
- ~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.
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
windows)ghostty_init(0, NULL)-- succeedsghostty_config_new/load/finalize-- succeedsghostty_app_new-- succeedsghostty_surface_newwithhwnd=NULL, swap_chain_panel=NULL, shared_texture_out=<valid ptr>-- succeedsghostty_surface_get_d3d11_device/context/texture-- all return valid pointersghostty_app_tickin a loop -- succeeds for 3 frames, D3D11 CopyResource from the texture worksCrash details
Analysis
ghostty spawns two background threads on surface creation:
renderer_thr) -- runs the D3D11 render loop via libxevio_thr) -- handles PTY I/O via libxevLikely root causes (in order of probability):
1. D3D11 multithread protection not enabled
device.zigcreates the D3D11 device withoutD3D11_CREATE_DEVICE_SINGLETHREADED, but the code never callsID3D11Multithread::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.zigthere's a comment acknowledging this concern: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:
Unity embedding:
Environment
feat/unity-integrationSuggested fix
At minimum, calling
ID3D11Multithread::SetMultithreadProtected(TRUE)on the device context after creation indevice.zigwould 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.