Repository navigation
Conversation
…S, HWND/HDC/HGLRC)
…aceSize, SwapBuffers)
Add 'directx' to Backend enum and wire it into renderer selection. DirectX.zig implementation files will follow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete renderer interface implementation with stub methods: - DirectX.zig: main graphics API wrapper - Target.zig: render target (ID3D11RenderTargetView placeholder) - Frame.zig: frame context - RenderPass.zig: render pass with draw steps - Pipeline.zig: shader pipeline (HLSL compilation placeholder) - buffer.zig: GPU buffer abstraction (ID3D11Buffer placeholder) - Texture.zig: texture with replaceRegion (ID3D11Texture2D placeholder) - Sampler.zig: texture sampler (ID3D11SamplerState placeholder) - shaders.zig: pipeline definitions + Uniforms/CellText/Image/BgImage types Builds with: zig build -Drenderer=directx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete D3D11 implementation in C with flat API for Zig consumption: - Device creation with swap chain (D3D11CreateDeviceAndSwapChain) - Buffer create/update/bind (vertex, constant, structured) - Texture create/update/bind (2D textures with SRV) - Sampler create/bind - Pipeline create/bind (vertex + pixel shaders + input layout) - Render target create/bind - Blend state management - Draw and DrawInstanced - Swap chain resize and present Builds with: zig build -Drenderer=directx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Translated from GLSL 430 to HLSL Shader Model 5.0: - common.hlsl: shared cbuffer, unpacking, color math (linearize, contrast) - bg_color.hlsl: full-screen background color fill - cell_bg.hlsl: per-cell background from StructuredBuffer - cell_text.hlsl: text glyph rendering with font atlas sampling - image.hlsl: embedded image rendering (kitty protocol) - bg_image.hlsl: background image with fit/position/repeat/opacity Key GLSL → HLSL changes: - vec/ivec/uvec → float/int/uint (2/3/4) - layout(binding=N, std140) uniform → cbuffer : register(bN) - layout(std430) readonly buffer → StructuredBuffer<T> : register(tN) - texture() → Texture2D.Sample() - sampler2DRect → Texture2D + GetDimensions for coord normalization - mix() → lerp(), gl_VertexID → SV_VertexID - origin_upper_left → default in D3D11 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add extern declarations for all dx_* C functions - surfaceInit: placeholder (device created in threadEnter) - threadEnter: create D3D11 device + swap chain from HWND - drawFrameStart: bind backbuffer + clear - drawFrameEnd: present swap chain - surfaceSize: query backbuffer dimensions - presentLastTarget: re-present previous frame - Store DxDevice* handle in DirectX struct Builds with: zig build -Drenderer=directx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dx_compile_shader: compiles HLSL source to bytecode via D3DCompile dx_free_compiled_shader: frees compiled shader bytecode Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pipeline.createOnDevice: compiles HLSL VS + PS and creates D3D11 pipeline - Add dx_compile_shader/dx_free_compiled_shader extern declarations - Add dx_create_pipeline/dx_destroy_pipeline/dx_bind_pipeline externs - Make all dx struct extern fns public Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- shaders.zig: embed HLSL at comptime (common.hlsl + 5 shaders) - shaders.compileAll: compile all HLSL pipelines on D3D11 device - DirectX.initShaders: call compileAll with device handle - Remove #include from HLSL files (resolved at comptime by concatenation) - Pipeline.createOnDevice: compile VS + PS via dx_compile_shader Tested: D3D11 device + HLSL compilation + present working (black screen) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- RenderPass.step: bind pipeline, blend state, constant buffers, structured buffers (SRV), textures, samplers, and issue draw calls - RenderPass.begin: clear render targets if requested - Frame.renderPass: pass device handle from renderer API - Target: add rt_handle for D3D11 render target - Texture/Sampler: add dx_handle for D3D11 resource handles - Buffer: expose RawBuffer as nullable opaque pointer The draw pipeline is now connected end-to-end: device init → shader compile → bind resources → draw → present Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Buffer.sync: create D3D11 buffer on first sync, update on subsequent - Texture.init: create D3D11 texture with SRV - Sampler.init: create D3D11 sampler state - Use threadlocal current_device for resource creation - Change swap effect to DISCARD with BufferCount=1 - Set viewport in drawFrameStart - GhosttyBridge: skip WGL init when GHOSTTY_RENDERER=directx - Add D3D11 device creation logging Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hreadlocal - Move D3D11 device creation from threadEnter to surfaceInit (surfaceInit runs before initShaders, so shaders can compile) - Change current_device from threadlocal to global (surfaceInit thread != renderer thread, need cross-thread access) - Fix HLSL: rename 'linear' param to 'do_linearize' (reserved word) - All 5 HLSL shaders now compile successfully - GhosttyBridge: log GHOSTTY_RENDERER env var value Tested: black screen with D3D11 (device + shaders + present working) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Debugging DirectX render pipeline - shaders compile, device stable, draw calls issued but nothing visible yet. COM errors during rendering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…read ID3D11Device is thread-safe (CreateVertexShader etc), only ID3D11DeviceContext is single-threaded. So: - surfaceInit: create device (any thread OK) - initShaders: compile HLSL bytecode + create shader objects (device OK) - threadEnter: store device ref for context operations - drawFrameStart: red clear for debugging (shader not drawing yet) Pipeline split into compileBytecode (no device) + createDeviceObjects (needs device). Issue: bg_color shader not rendering (red clear visible = shaders not drawing). Need to debug pipeline handle creation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Direct draw in drawFrameStart successfully renders a green full-screen triangle via D3D11. The pipeline flow works: HLSL compile → CreateVertexShader/CreatePixelShader → Bind → Draw → Present Remaining issue: RenderPass.step draws don't appear (pipeline cache handles are valid but draws may be overwritten or render target unbound). Debug logging added: dx_draw, dx_create_pipeline, CreateShaders HRESULT Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: Pipeline objects were compiled on init thread but D3D11 context operations must happen on the renderer thread. Solution: - Pipeline.storeSource: save comptime HLSL source pointers (init thread) - Pipeline.getHandle: compile + create on first call (renderer thread) - Global handles[] array persists across Pipeline value copies - RenderPass.step: reset viewport + backbuffer before each draw - Restore HLSL bg_color to use cbuffer (remove test green) Tested: bg_image, cell_bg, cell_text pipelines compile and draw. Background appears dark (matching config #1e1e1e or uniforms not bound yet). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove all debug OutputDebugStringA/dbgLog calls - Remove test pipeline code from drawFrameStart/drawFrameEnd - Remove draw_log_count, sprintf debug messages from d3d11_impl.c - Pass topology parameter to dx_draw/dx_draw_instanced (triangle=4/TRIANGLELIST, triangle_strip=5/TRIANGLESTRIP) - Restore bg_color.hlsl to use cbuffer (remove test green) - Keep HLSL compile error reporting in d3d11_impl.c Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- d3d11_impl.c: dx_create_cell_text_pipeline with D3D11_INPUT_ELEMENT_DESC mapping CellText struct fields to HLSL semantics (per-instance data) - Pipeline: detect CellText vertex attributes, use cell_text_pipeline - RenderPass.step: bind first buffer as vertex buffer for instanced draws, remaining buffers as SRV Text still not visible - needs D3D11 Debug Layer to diagnose. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Debug Layer reports: 1. INPUTLAYOUT_NOT_SET: bg_image/image shaders need input layouts too 2. CREATESHADERRESOURCEVIEW_INVALIDFORMAT: structured buffer SRV needs proper format 3. SAMPLER_NOT_SET: sampler not bound at slot 0 4. VIEW_DIMENSION_MISMATCH: texture bound where shader expects buffer These are the exact issues preventing rendering. Fix in next commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add input layouts for bg_image (opacity+info) and image (grid_pos+offset+rect+size) - Pipeline detects layout type from vertex attributes (cell_text/bg_image/image) - Fix SRV NumElements field (was ElementWidth) - Clear all SRV bindings between render steps (prevent slot conflicts) - Correct vertex buffer stride per pipeline type Fixed: INPUTLAYOUT_NOT_SET and VIEW_DIMENSION_MISMATCH errors Remaining: SRV format issue for structured buffers, sampler not bound Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All 4 D3D11 Debug Layer errors resolved: 1. SRV: Use typed buffer (R32_UINT) instead of structured buffer 2. HLSL: StructuredBuffer<uint> → Buffer<uint> for typed buffers 3. Sampler: Create default linear sampler in drawFrameStart 4. Buffer binding: Use vertex buffer for pipelines with input layouts, SRV for shader_storage buffers without layouts 5. fg buffer: Use vertex buffer (D3D11_BIND_VERTEX_BUFFER) not SRV 6. Clear shader resources between render steps Only warnings remain (stride/size for bg_image). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full D3D11 rendering pipeline confirmed working: - Device creation, HLSL compilation, pipeline creation - Viewport, backbuffer, blend state setup per step - Constant buffer (uniforms) bound - Default sampler bound - Vertex buffer and SRV buffer binding - Draw/DrawInstanced with correct topology cell_bg PS forced to magenta = visible on screen. Remaining: restore normal shaders, fix bg_image stride warning, debug why normal cbuffer values produce transparent output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix rasterizer state (CullMode=NONE for full-screen triangles), use global current_device to survive value-copy semantics, implement syncFromArrayLists for cell text GPU upload, and clean up render pass with per-pass clear on first step. - Add rasterizer state with no backface culling in d3d11_impl.c - Use current_device global instead of self.device in drawFrameStart/End/surfaceSize - Implement Buffer.syncFromArrayLists to upload cell text data to GPU - Add per-pass clear in RenderPass.step (first step clears backbuffer) - Make presentLastTarget a no-op (DXGI_SWAP_EFFECT_DISCARD) - Restore cell_bg shader to normal (remove magenta test) - Remove debug logging from RenderPass and Pipeline Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add rasterizer state with CullMode=NONE (full-screen triangles are CCW) - Use global current_device instead of self.device (value-copy issue) - surfaceSize returns GetClientRect + auto dx_resize on size change - Cache backbuffer size in DxDevice (avoid COM query per frame) - dx_resize: add size guard, release all state, flush before resize - SRV buffer slots placed after textures (fix t1/t2 overlap) - beginFrame: clear only when needs_redraw=true - presentLastTarget: no-op (DISCARD preserves via DWM) - D3D11_CREATE_DEVICE_DEBUG conditional on NDEBUG - Remove dx_test_draw debug function - Add IS_CURSOR flag to cell_text PSInput - Remove unused frame_rendered field and debug logging Known issue: frames after initial render don't update (wakeup/event loop investigation needed for continuous redraw support). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The two comms sat at the top of main, far from the relay loop that reads them. Move them to the loop and say why sent_comm has to outlive an iteration.
DirectX: drop the scRGB color space probe
wsl: report the foreground name again after an exec
d3d11_impl.c requests the D3D11 debug layer and prints its trace lines under `#ifndef NDEBUG`, meant as "debug builds only". Zig only defines NDEBUG for ReleaseFast and ReleaseSmall, so the ReleaseSafe DLL the host ships has carried the debug layer since the guard was introduced (2026-04). The layer slows every D3D call, and device creation with it fails outright on a machine without the Graphics Tools optional feature; dx_create has no fallback for that case, so such a machine gets no renderer at all. Pass -DNDEBUG to the C source for every optimize mode but Debug.
Whether a build requests the debug layer was only visible by noticing the SDK layer DLLs in the loaded-module list, which is how the ReleaseSafe regression stayed unnoticed for five months. Ask the created device for its creation flags and put the answer in the unconditional "device created" trace, so any build's log states it directly.
DirectX: keep the debug layer out of release builds
d3d11_impl.c turned the D3D11 debug layer and its verbose traces on whenever NDEBUG was undefined, so whether a DLL carried the layer depended on what the toolchain happened to define for the optimize mode. That is how the layer shipped in every ReleaseSafe DLL for five months; #58 stopped that by defining NDEBUG, but the layer was still inferred rather than asked for. Add `-Ddirectx-debug-layer` (default off). The build passes GHOSTTY_D3D_DEBUG to the C source only for that option, and the source gates the layer and the traces on it. NDEBUG keeps its ordinary C meaning and no longer controls anything DirectX-specific.
…s are missing A device requested with D3D11_CREATE_DEVICE_DEBUG fails with DXGI_ERROR_SDK_COMPONENT_MISSING on a machine without the Graphics Tools optional feature, and both creation paths gave up on any failure. A debug-layer build therefore rendered nothing on such a machine, when all it had to lose was validation. Route both paths through one helper that retries without the flag on that error and says so in the trace.
DirectX: opt into the debug layer explicitly and survive missing SDK layers
Both Windows ptys create a named pipe to stand in for an anonymous one, because the termio write side needs overlapped I/O. Named pipes take SMB clients unless told otherwise, so the pipe that carries what the user types is reachable from another machine. Pass PIPE_REJECT_REMOTE_CLIENTS in WindowsPty and in the WSL bridge's Pty. Nothing is known to get through today: the name cannot be created ahead of us, there is one instance, and the process connects to its own pipe on the next line. This is depth, not a repair.
Reject remote clients on the Windows pty pipes
The helper is a Linux binary that runs in the distribution, but it could only be named by a Windows path: startWslBridge checks it with a Windows file access, and Launch hands it to wslpath. A host that ships it next to its executable then puts it where the user cannot write, and DrvFs gives such a file no execute bit, so the one place the path was allowed to point is a place the binary cannot run from. Treat a path that starts with a slash as being inside the distribution: skip the access check, which cannot see it from this side, and let the shell exec it as it stands instead of translating it. Installing the binary where it runs is then possible, and nothing changes for a Windows path.
Accept a helper path inside the distribution
The helper is the user's to install, since a copy the host shipped would sit where the user cannot write and could not be run from the distribution at all. Naming it by path then bought nothing but an existence check, and that check was the whole reason for carrying a path: an env var to set it, an executable directory to derive it from, wslpath to translate it, and a rule for telling a Linux path from a Windows one. Exec it by name and let the distribution's PATH find it, as it finds any other command. Nothing is checked beforehand, so a missing binary ends the session with the shell's own "ghostty-wsl-bridge: not found" instead of quietly becoming a ConPTY session that behaves differently; $0 is the name so that message says what is missing. To make that the right answer rather than a trap, wsl-bridge now defaults to false: a session that cannot start belongs to someone who asked for it. One setting is left where there were two, and GHOSTTY_WSL_BRIDGE is gone.
Start the WSL bridge helper by name, and off by default
A tab whose command is wsl starts wherever the Windows working directory translates to, under /mnt/c, and nothing in the config could say otherwise: working-directory is a Windows path, and --cd was read as the start of the in-distro command, so `wsl --cd ~` tried to run a program called --cd. A lone ~ fared no better. wsl.exe reads it as --cd ~, but the parse dropped it and emitted nothing, leaving `wsl ~` in /mnt/c as well while the comment beside it claimed the home directory. Parse --cd into the invocation, pass it to wsl.exe before --exec, and give a lone ~ the same meaning wsl.exe gives it. After `--` both stay part of the in-distro command, as they are in wsl.exe.
Take --cd, and let a lone ~ reach wsl.exe as one
wsl.exe takes --user to pick who the session runs as, and the invocation did not know it: the flag became the start of the in-distro command, so `wsl -u root` tried to run a program called -u. The bridge was the only way to open WSL where that line did not mean what wsl.exe means by it. Parse it into the invocation and pass it to wsl.exe before --exec, beside --cd. The helper is started as that user, so the shell it starts is too.
Take --user, so the session can run as someone else
SwapChain.init makes its 1x1 placeholder atlases with `data = undefined`, and the DirectX backend uploaded that data, so the length check saw whatever the stack held. On the first swap chain of a thread it was small and the upload was skipped; when a hidden window's swap chain was rebuilt it passed, and `@intCast(data.len)` panicked, ending the process from the renderer thread. The texture now starts empty and syncAtlasTexture fills it on the first frame, and the GPU is handed the planned byte count rather than the slice length.
DirectX: create the atlas texture empty, as Metal and OpenGL do
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary / 概要
Full Windows port of Ghostty with two renderer backends: OpenGL (via Mesa Zink/WGL) and native DirectX 11. Includes Win32 embedded apprt platform, DirectWrite font rendering, and all core terminal functionality.
日本語
GhosttyのフルWindowsポート。2つのレンダラーバックエンド搭載: OpenGL(Mesa Zink/WGL経由)とネイティブDirectX 11。Win32 embedded aprtプラットフォーム、DirectWriteフォントレンダリング、全コアターミナル機能を含む。
OpenGL Renderer (Mesa Zink) / OpenGLレンダラー
Changes / 変更
日本語
DirectX 11 Renderer / DirectX 11レンダラー
New Files / 新規ファイル
Key Technical Decisions / 主要な技術的決定
Native render loop: xev IOCP is incompatible with D3D11 device context (#7). Uses `while + Sleep(4)` at ~240fps with `timeBeginPeriod(1)`.
日本語
ネイティブレンダーループ: xev IOCPはD3D11デバイスコンテキストと非互換(#7)。`while + Sleep(4)`で約240fps、`timeBeginPeriod(1)`使用。
Global device handle: `current_device` global because DirectX struct is value-copied in GenericRenderer.
日本語
グローバルデバイスハンドル: DirectX構造体がGenericRendererで値コピーされるため`current_device`グローバルを使用。
Atomic window size: `GetClientRect` blocks from renderer thread. WM_SIZE → `dx_notify_resize` → atomic globals → `surfaceSize()` reads.
日本語
アトミックウィンドウサイズ: レンダラースレッドから`GetClientRect`がブロック。WM_SIZE → `dx_notify_resize` → atomicグローバル → `surfaceSize()`が読み取り。
Frame completion: `Frame.complete()` calls `frameCompleted(.healthy)` to release swap chain semaphore. Missing this caused deadlock after 2 frames (#7).
日本語
フレーム完了: `Frame.complete()`が`frameCompleted(.healthy)`を呼びスワップチェインセマフォを解放。これがないと2フレーム後にデッドロック(#7)。
Modified Shared Files / 変更された共有ファイル
Build / ビルド
DirectX 11 (default for Windows)
OpenGL (Mesa Zink fallback)
Known Issues / 既知の問題
PR