Skip to content

feat: replace glslang+spirv-cross with glslpp for GLSL→HLSL shadertoy compilation#370

Draft
deblasis wants to merge 286 commits into
windowsfrom
feat/glslpp-integration
Draft

feat: replace glslang+spirv-cross with glslpp for GLSL→HLSL shadertoy compilation#370
deblasis wants to merge 286 commits into
windowsfrom
feat/glslpp-integration

Conversation

@deblasis
Copy link
Copy Markdown
Owner

Summary

Replace the glslang + SPIRV-Cross pipeline (C++ via DLL) with glslpp (pure Zig) for the shadertoy GLSL→HLSL compilation path on Windows/DX12.

What changed

  • src/renderer/shadertoy.zig — HLSL target now calls glslpp.compileGlslToHlsl() instead of the glslang+spirv-cross DLL pipeline. No more 8MB stack thread spawn for HLSL.
  • build.zig.zon / src/build/SharedDeps.zig — glslpp added as a Zig dependency.
  • pkg/glslang/main.zig — Removed wrapper import.
  • pkg/glslang/shader_wrapper.cpp — Deleted (290 lines of MSVC-compiled C++ wrapper DLL).
  • pkg/glslang/wrapper.zig — Deleted (DLL loader).
  • src/renderer/directx12/Pipeline.zig — Updated comment to reference glslpp binding_shift.
  • justfile — Added ci recipe, removed shader_wrapper deployment references.
  • .zig-version / mise.toml — Pin Zig 0.15.2.

What stayed the same

  • MSL / GLSL paths still use glslang + spirv-cross (unchanged).
  • Built-in DX12 shaders (shaders.hlsl, cell.hlsl) are pre-written HLSL compiled by DXC at build time — not affected.
  • pkg/glslang/build_msvc.bat and the glslang static library — still needed for MSL/GLSL.

Correctness verification

Both CRT and focus shadertoy shaders produce HLSL that:

  • Compiles with DXC (0 errors, 0 warnings)
  • Uses register(b0) for the cbuffer (matches DX12 root signature)
  • Uses register(t0)/register(s0) for texture/sampler
  • Has correct SV_Position input and SV_Target output semantics
  • mainImage(out float4, float2) signature preserved correctly

Side-by-side comparison with spirv-cross reference output shows:

  • Identical cbuffer member layout (std140 offsets match)
  • Same number of texture samples (glslpp has 6 vs spirv-cross 7 — glslpp correctly DCEs the unused oricol variable)
  • Byte-identical DXIL output from DXC for equivalent float literals

Performance

Pipeline Avg Min
glslang + spirv-cross (CLI) ~335ms ~289ms
glslpp (in-process) ~4.6ms ~3.1ms

~73x faster average, eliminates process spawn, C++ runtime, and DLL isolation hacks.

glslpp repo

The corresponding glslpp commits (already on main):

  • 033ff25 — Fix binding_shift=-1 for register(b0), fix for-loop test, fix leak
  • f56c80d — CI-equivalent justfile
  • 2bba2e6 — Regenerate focus HLSL output

How to test

# Full CI pipeline (glslpp)
cd ../glslpp && just ci

# Build wintty
just build-dll

# Run existing shadertoy tests
just test

Remaining work

  • Manual runtime smoke test with custom shadertoy shader
  • Visual verification of CRT/focus shader rendering

deblasis added 30 commits May 5, 2026 18:29
…osition) (#92)

* renderer/dx11: add IDXGIResource interface with GetSharedHandle

* renderer/dx11: add D3D11_RESOURCE_MISC_SHARED constant and Flush binding

* renderer/dx11: add shared texture ownership to Target

* renderer/dx11: extend Device for shared texture mode (no swap chain)

* renderer/dx11: wire shared texture mode through renderer backend

Add shared_target field to DirectX11 to own the texture and RTV in
shared texture mode. Update init() to detect the new surface variant
and create the Target immediately. Update deinit(), initTarget(), and
beginFrame() to route through shared_target when present, keeping swap
chain mode unchanged.

* renderer/dx11: simplify shared texture log statement

* ghostty.h: add shared texture fields to Windows platform struct

* apprt/embedded: add shared texture fields to Windows platform

* docs: update surface support from dual to triple (shared texture)

* renderer/dx11: add shared texture lifecycle test

Tests init, resize, and deinit of the shared texture path on a real
D3D11 device. Skips on non-Windows platforms.

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>

* renderer/dx11: fix gpu_test for optional swap_chain field

* renderer/dx11: fix shared texture ownership and add tests

Fix initTarget copying owned COM pointers into borrowed Target (potential
double-release). Skip DXGI factory/adapter queries in shared texture mode
since no swap chain is needed. Merge InitError/ResizeError into single
SharedTextureError. Strengthen resize test to assert handle changes.

Add two new tests: borrowed target deinit safety and Device init in
shared texture mode (swap chain null, present flushes, resize updates
dimensions).

* ghostty: expose D3D11 device for shared texture consumers

Add ghostty_surface_get_d3d11_device() so shared texture consumers can
open the DXGI handle on ghostty's own device, avoiding cross-device
synchronization issues that cause blank reads.

* ghostty: expose D3D11 context and texture for shared texture consumers

Add ghostty_surface_get_d3d11_context() and
ghostty_surface_get_d3d11_texture() so same-process consumers can
CopyResource directly from ghostty's render texture without going
through OpenSharedResource. Avoids cross-device sync issues entirely.

* renderer/dx11: clean up shared texture review items

Use idiomatic if-capture instead of force-unwrap (.?) in beginFrame.
Set self.* = undefined in Target.deinit for double-deinit safety.
Remove redundant shared_texture_mode bool from Device (swap_chain == null
is the canonical check). Extract createTestDevice helper in Target tests.

---------

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
* renderer/dx11: add DirectComposition COM bindings

* renderer/dx11: wire DirectComposition into HWND surface init

Replace CreateSwapChainForHwnd with a DirectComposition visual tree
plus CreateSwapChainForComposition for the HWND surface path.

Creates IDCompositionDevice/Target/Visual, binds the swap chain as
the visual content, roots the visual on the HWND target, and commits
before returning so DWM sees the first frame immediately. Cleanup
releases dcomp objects in reverse creation order in deinit().

The swap_chain_panel path is unchanged.

* renderer/dx11: add dcomp Commit to present and cleanup to deinit

Call IDCompositionDevice::Commit() after each Present() so DWM picks
up the new frame. The call is a no-op for non-HWND surfaces since
dcomp_device is null there.

* renderer/dx11: update HWND test for DirectComposition, add negative test
MVWT breakdown:
- Build infra: 95% (15% weight)
- DX11 infra: 95% (15% weight) - DirectComposition + shared texture done
- App scaffold: 70% (10% weight) - 6 .NET examples
- Cell rendering: 90% (20% weight) - full pipeline wired end-to-end
- DirectWrite: 0% (15% weight)
- ConPTY: 25% (10% weight)
- Keyboard/mouse/clipboard: 30% (10% weight)
- DPI/theming: 20% (5% weight)

MVT: 0/30 checkboxes in #26
Shared textures created for cross-process interop only had
D3D11_BIND_RENDER_TARGET, preventing consumers from creating
a Shader Resource View directly on the shared handle. This
forced every consumer to create an intermediate copy texture
and copy each frame, adding unnecessary GPU overhead.

Adding D3D11_BIND_SHADER_RESOURCE allows consumers to create
an SRV directly on the shared texture, enabling zero-copy
texture sharing.
* refactor: extract shared COM primitives to src/os/windows_com.zig

Move GUID, HRESULT, IUnknown and helpers out of the DX11 renderer
into a shared location so the upcoming DirectWrite font backend can
reuse them. The renderer's com.zig re-exports everything so all
existing imports are unchanged.

* font: add directwrite_freetype backend variant

New backend for Windows that will use DirectWrite for font discovery
with FreeType rendering and HarfBuzz shaping. Currently maps to the
same behavior as plain freetype -- discovery implementation comes next.

Made the default on Windows, replacing plain freetype.

* font: add DirectWrite COM interface bindings

Define the DirectWrite COM interfaces needed for font discovery:
IDWriteFactory3, IDWriteFontCollection/1, IDWriteFontFamily,
IDWriteFont (through Font2 for IsColorFont), IDWriteFontFace,
IDWriteFontFile, IDWriteLocalFontFileLoader, IDWriteFontFallback,
IDWriteLocalizedStrings, IDWriteTextAnalysisSource.

Includes runtime dwrite.dll loading and a UTF-16 to UTF-8 helper
for reading localized font name strings.

* font: implement DirectWrite font discovery

Add DirectWrite discovery struct with discover() for family name
matching and discoverFallback() using IDWriteFontFallback::MapCharacters
for glyph coverage. Includes a minimal IDWriteTextAnalysisSource
implementation and scored sorting matching the CoreText pattern.

Also adds IDWriteFont::AddRef wrapper needed by DiscoverIterator.

* font: wire DirectWrite through DeferredFace

Add dw field with IDWriteFont handle for deferred loading.
Implement familyName, name (via GetInformationalStrings),
hasCodepoint (via HasCharacter + IsColorFont for presentation),
and loadDirectWrite (extract file path from IDWriteLocalFontFileLoader,
hand to FreeType via Face.initFile).

* font: add DirectWrite integration tests

Test discovery by family name (Consolas), codepoint matching,
bold style filtering, emoji fallback via MapCharacters, and
full DeferredFace load through to FreeType.

Fix CreateNumberSubstitution to use NONE method (was using
FROM_CULTURE which requires a non-null locale).

* font: use std.unicode for UTF-16/UTF-8 conversions in DirectWrite

Replace manual UTF-16-to-UTF-8 encoder in loadDirectWrite with
std.unicode.utf16LeToUtf8 which handles surrogate pairs correctly.
Replace ASCII-only utf8ToUtf16Le helper with std.unicode.utf8ToUtf16Le
so non-ASCII font family names work. Extract magic number 2 to
DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE.
* refactor: extract shared COM primitives to src/os/windows_com.zig

Move GUID, HRESULT, IUnknown and helpers out of the DX11 renderer
into a shared location so the upcoming DirectWrite font backend can
reuse them. The renderer's com.zig re-exports everything so all
existing imports are unchanged.

* font: add directwrite_freetype backend variant

New backend for Windows that will use DirectWrite for font discovery
with FreeType rendering and HarfBuzz shaping. Currently maps to the
same behavior as plain freetype -- discovery implementation comes next.

Made the default on Windows, replacing plain freetype.

* font: add DirectWrite COM interface bindings

Define the DirectWrite COM interfaces needed for font discovery:
IDWriteFactory3, IDWriteFontCollection/1, IDWriteFontFamily,
IDWriteFont (through Font2 for IsColorFont), IDWriteFontFace,
IDWriteFontFile, IDWriteLocalFontFileLoader, IDWriteFontFallback,
IDWriteLocalizedStrings, IDWriteTextAnalysisSource.

Includes runtime dwrite.dll loading and a UTF-16 to UTF-8 helper
for reading localized font name strings.

* font: implement DirectWrite font discovery

Add DirectWrite discovery struct with discover() for family name
matching and discoverFallback() using IDWriteFontFallback::MapCharacters
for glyph coverage. Includes a minimal IDWriteTextAnalysisSource
implementation and scored sorting matching the CoreText pattern.

Also adds IDWriteFont::AddRef wrapper needed by DiscoverIterator.

* font: wire DirectWrite through DeferredFace

Add dw field with IDWriteFont handle for deferred loading.
Implement familyName, name (via GetInformationalStrings),
hasCodepoint (via HasCharacter + IsColorFont for presentation),
and loadDirectWrite (extract file path from IDWriteLocalFontFileLoader,
hand to FreeType via Face.initFile).

* font: add DirectWrite integration tests

Test discovery by family name (Consolas), codepoint matching,
bold style filtering, emoji fallback via MapCharacters, and
full DeferredFace load through to FreeType.

Fix CreateNumberSubstitution to use NONE method (was using
FROM_CULTURE which requires a non-null locale).

* font: use std.unicode for UTF-16/UTF-8 conversions in DirectWrite

Replace manual UTF-16-to-UTF-8 encoder in loadDirectWrite with
std.unicode.utf16LeToUtf8 which handles surrogate pairs correctly.
Replace ASCII-only utf8ToUtf16Le helper with std.unicode.utf8ToUtf16Le
so non-ASCII font family names work. Extract magic number 2 to
DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE.

* font: add Segoe UI Emoji as Windows fallback font

Mirror the macOS Apple Color Emoji pattern for Windows: discover
"Segoe UI Emoji" via DirectWrite and add it as the preferred emoji
fallback. Skip embedded Noto Color Emoji on Windows when discovery
is available, same as macOS.

* font: improve emoji fallback comment and guard readability

Match the macOS comment style for the Windows emoji block. Simplify
the embedded emoji guard using positive-sense grouping so it reads
as "not (macOS or Windows), or no discovery" -- easier to extend.

* font: document light hinting as intentional default on Windows

Light hinting (TARGET_LIGHT) preserves glyph shapes rather than
snapping to the pixel grid, matching the CoreText approach on macOS.
Native ClearType rendering would require a DirectWrite rasterization
backend (Phase 2). Add test confirming default load flags and update
freetype-load-flags doc to mention Windows.

* font: trim comment verbosity per review feedback
* refactor: extract shared COM primitives to src/os/windows_com.zig

Move GUID, HRESULT, IUnknown and helpers out of the DX11 renderer
into a shared location so the upcoming DirectWrite font backend can
reuse them. The renderer's com.zig re-exports everything so all
existing imports are unchanged.

* font: add directwrite_freetype backend variant

New backend for Windows that will use DirectWrite for font discovery
with FreeType rendering and HarfBuzz shaping. Currently maps to the
same behavior as plain freetype -- discovery implementation comes next.

Made the default on Windows, replacing plain freetype.

* font: add DirectWrite COM interface bindings

Define the DirectWrite COM interfaces needed for font discovery:
IDWriteFactory3, IDWriteFontCollection/1, IDWriteFontFamily,
IDWriteFont (through Font2 for IsColorFont), IDWriteFontFace,
IDWriteFontFile, IDWriteLocalFontFileLoader, IDWriteFontFallback,
IDWriteLocalizedStrings, IDWriteTextAnalysisSource.

Includes runtime dwrite.dll loading and a UTF-16 to UTF-8 helper
for reading localized font name strings.

* font: implement DirectWrite font discovery

Add DirectWrite discovery struct with discover() for family name
matching and discoverFallback() using IDWriteFontFallback::MapCharacters
for glyph coverage. Includes a minimal IDWriteTextAnalysisSource
implementation and scored sorting matching the CoreText pattern.

Also adds IDWriteFont::AddRef wrapper needed by DiscoverIterator.

* font: wire DirectWrite through DeferredFace

Add dw field with IDWriteFont handle for deferred loading.
Implement familyName, name (via GetInformationalStrings),
hasCodepoint (via HasCharacter + IsColorFont for presentation),
and loadDirectWrite (extract file path from IDWriteLocalFontFileLoader,
hand to FreeType via Face.initFile).

* font: add DirectWrite integration tests

Test discovery by family name (Consolas), codepoint matching,
bold style filtering, emoji fallback via MapCharacters, and
full DeferredFace load through to FreeType.

Fix CreateNumberSubstitution to use NONE method (was using
FROM_CULTURE which requires a non-null locale).

* font: use std.unicode for UTF-16/UTF-8 conversions in DirectWrite

Replace manual UTF-16-to-UTF-8 encoder in loadDirectWrite with
std.unicode.utf16LeToUtf8 which handles surrogate pairs correctly.
Replace ASCII-only utf8ToUtf16Le helper with std.unicode.utf8ToUtf16Le
so non-ASCII font family names work. Extract magic number 2 to
DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE.

* font: add Segoe UI Emoji as Windows fallback font

Mirror the macOS Apple Color Emoji pattern for Windows: discover
"Segoe UI Emoji" via DirectWrite and add it as the preferred emoji
fallback. Skip embedded Noto Color Emoji on Windows when discovery
is available, same as macOS.

* font: improve emoji fallback comment and guard readability

Match the macOS comment style for the Windows emoji block. Simplify
the embedded emoji guard using positive-sense grouping so it reads
as "not (macOS or Windows), or no discovery" -- easier to extend.
* font: verify variable font variations through DirectWrite discovery

Add test confirming that font-variation axes flow through DirectWrite
discovery into DeferredFace for application at load time. Document
that DirectWrite does not need variation-based scoring because
GetWeight/GetStyle already return instance-level values.

* font: simplify variation test assertions per review
* font: fix review findings in DirectWrite backend

- Replace hand-rolled UTF-16-to-UTF-8 encoder in getLocalizedString
  with std.unicode.utf16LeToUtf8 (handles surrogate pairs correctly)
- Fix stale "ASCII fast path" comment in discovery.zig
- Document why undefined Collection is safe in fallback test

* font: remove stale BMP/surrogate doc comment from getLocalizedString
* renderer: rename directx11/ to directx12/, delete DX11-specific files

* renderer: extract surface.zig and gpu_data.zig for DX12

* chore: nits (#115)

Remove redundant CellText test block (comptime assertions cover it),
add doc comment explaining cross-backend drift gap, document
handle_out out-parameter in SharedTextureConfig.

* renderer: fix broken gpu_test.zig import, update stale DX11 comment
Update backend.zig enum to directx12, swap d3d11->d3d12 in
GhosttyLib.zig, and create a stub DirectX12.zig that satisfies the
full GenericRenderer contract. All functions are no-ops returning
defaults.

Delete DX11-specific files (d3d11.zig, device.zig, cell_grid.zig,
cell_pipeline.zig, gpu_test.zig) and replace with DX12 stubs for
Target, Frame, RenderPass, Pipeline, Sampler, Texture, Buffer, and
Shaders. Carry forward com.zig, dxgi.zig, dcomp.zig unchanged.
Extract surface.zig and gpu_data.zig as new DX12-specific modules.

Guard DX11 C API exports in embedded.zig with @Hasfield checks so
they compile cleanly against the DX12 backend.
* renderer: rename directx11/ to directx12/, delete DX11-specific files

* renderer: update backend enum and imports for directx12

Switch the renderer backend from directx11 to directx12:
- backend.zig: rename enum variant and Windows default
- renderer.zig: import DirectX12 instead of DirectX11
- GhosttyLib.zig: link d3d12 instead of d3d11
- embedded.zig: use @Hasfield guards for DX11-specific C API
  exports so they compile (returning null) with any backend
- com.zig: remove gpu_test.zig reference (deleted in rename PR)
- com_test.zig: remove D3D11-specific struct size tests

* build: switch HLSL compilation from fxc/SM5.0 to dxc/SM6.0

HlslStep.zig now uses dxc.exe instead of fxc.exe. Targets Shader
Model 6.0, outputs DXIL bytecode. dxc.exe found via Windows SDK
first, then PATH fallback (for Vulkan SDK installs).

SharedDeps.zig profiles updated from vs_5_0/ps_5_0 to vs_6_0/ps_6_0.
No HLSL source changes needed -- existing shaders are SM 6.0 compatible.

* docs: fix stale cell_pipeline.zig reference in HLSL README
* renderer: update backend enum and imports for directx12

Switch the renderer backend from directx11 to directx12:
- backend.zig: rename enum variant and Windows default
- renderer.zig: import DirectX12 instead of DirectX11
- GhosttyLib.zig: link d3d12 instead of d3d11
- embedded.zig: use @Hasfield guards for DX11-specific C API
  exports so they compile (returning null) with any backend
- com.zig: remove gpu_test.zig reference (deleted in rename PR)
- com_test.zig: remove D3D11-specific struct size tests

* renderer: add D3D12 COM interface bindings

* fix: correct D3D12_RESOURCE_STATES values, move GUIDs into structs

- DEPTH_WRITE was 0x8 (collided with UNORDERED_ACCESS), fixed to 0x10
- Add missing INDIRECT_ARGUMENT (0x200) enum variant
- Move all IID constants from module-level into interface structs as
  pub const IID, matching the dxgi.zig pattern
- Type D3D12_DEPTH_STENCIL_DESC fields with proper enums
  (D3D12_STENCIL_OP, D3D12_DEPTH_WRITE_MASK, D3D12_COMPARISON_FUNC)
- Improve tests: actual expected GUID values, more struct size checks,
  COM vtable pointer layout verification

* fix: correct D3D12 struct size tests, fix PRESENT enum duplicate

- D3D12_HEAP_PROPERTIES: 32 -> 20 (5 x u32, not padded)
- D3D12_RANGE: hardcoded 8 -> 2 * @sizeof(usize) (SIZE_T is pointer-sized)
- D3D12_RESOURCE_STATES.PRESENT: move to pub const to avoid duplicate enum tag
- Add size tests for D3D12_RESOURCE_DESC, D3D12_RESOURCE_BARRIER,
  D3D12_ROOT_PARAMETER, D3D12_GRAPHICS_PIPELINE_STATE_DESC
- "DX11 renderer" -> "DirectX renderer"
- "fxc/dxc" -> "dxc" (fxc is no longer used)
- directx11/shaders.zig -> directx12/gpu_data.zig
- Remove DX11-specific qualifiers from blend state comments
* renderer: DX12 device creation with command queue and fence

Add device.zig with ID3D12Device, DIRECT command queue, and fence for
CPU/GPU synchronization. Supports all three surface modes (HWND with
DirectComposition, SwapChainPanel, SharedTexture). Triple-buffered
swap chain, debug layer enabled in debug builds.

Also fix D3D12_RESOURCE_STATES: convert from enum to u32 constants
because several D3D12 state values alias (DEPTH_WRITE and
UNORDERED_ACCESS both 0x8, COMMON and PRESENT both 0).

Add CreateDXGIFactory2 extern to dxgi.zig.

* fix: remove unused Allocator import in device.zig

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>

* fix: use inline IID constants for D3D12 COM interfaces

device.zig referenced standalone IID_ID3D12* constants that don't exist
in d3d12.zig. The codebase defines GUIDs as pub const IID on each struct
(e.g. ID3D12Device.IID), matching the pattern already used for DXGI and
DComp in the same file. Zig's lazy evaluation hid this because init()
isn't called yet, but it would fail at runtime.

---------

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
* build: switch HLSL compilation from fxc/SM5.0 to dxc/SM6.0

HlslStep.zig now uses dxc.exe instead of fxc.exe. Targets Shader
Model 6.0, outputs DXIL bytecode. dxc.exe found via Windows SDK
first, then PATH fallback (for Vulkan SDK installs).

SharedDeps.zig profiles updated from vs_5_0/ps_5_0 to vs_6_0/ps_6_0.
No HLSL source changes needed -- existing shaders are SM 6.0 compatible.

* renderer: DX12 descriptor heap management

Linear allocator for CBV/SRV/UAV, sampler, and RTV descriptor heaps.
Tracks CPU and GPU handles with per-descriptor increment size.

* fix: add reset(), fix doc wording, add RTV and reset tests

- Add reset() method so the linear allocator can be reused per-frame
- Fix doc comment: "Callers typically create" instead of stating as fact
- Add test for gpuHandle returning zero on non-shader-visible (RTV) heaps
- Add test verifying reset() allows slot reuse after exhaustion

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>

* fix: use inline IID, guard gpuHandle for non-shader-visible heaps

- Use ID3D12DescriptorHeap.IID instead of removed standalone constant
- Return zeroed GPU handle when gpu_start is 0 (RTV heaps)
- Add debug bounds assertions in cpuHandle/gpuHandle

---------

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
* build: switch HLSL compilation from fxc/SM5.0 to dxc/SM6.0

HlslStep.zig now uses dxc.exe instead of fxc.exe. Targets Shader
Model 6.0, outputs DXIL bytecode. dxc.exe found via Windows SDK
first, then PATH fallback (for Vulkan SDK installs).

SharedDeps.zig profiles updated from vs_5_0/ps_5_0 to vs_6_0/ps_6_0.
No HLSL source changes needed -- existing shaders are SM 6.0 compatible.

* renderer: DX12 Frame with command allocator and command list

Each in-flight frame owns a command allocator and graphics command list.
Reset per frame, closes on complete, reports health to GenericRenderer.

* fix(renderer/dx12): use optional pointers in Frame, remove UB on stub path

Command allocator and command list fields are now optional (?*T) so that
beginFrame() can safely return a stub frame with null D3D objects instead
of undefined pointers. complete() and reset() guard against null, avoiding
undefined behavior when the device isn't wired yet.

Also: remove scoped blocks around HR checks, add hrFmt helper to dedupe
HRESULT formatting, close command list before release in deinit(), and
replace trivial tests with error-set field and type assertions.

* fix: use inline IIDs and Zig 0.15 @typeinfo in Frame.zig

- Replace standalone d3d12.IID_ID3D12CommandAllocator/GraphicsCommandList
  with inline d3d12.ID3D12CommandAllocator.IID to match codebase convention
- Fix error set tests to use Zig 0.15 @typeinfo API (.@"fn" and
  .error_union.error_set) instead of removed .Fn.return_error_set
…ing) (#117)

* renderer: DX12 Target (RTV + barriers) and RenderPass (command recording)

Replace stubs with real implementations. Target wraps a swap chain back
buffer resource with its RTV descriptor handle. RenderPass records into
an ID3D12GraphicsCommandList following the same begin/step/complete
pattern as Metal and OpenGL -- transitions barriers, sets viewport and
scissor, clears, and will bind pipelines once those types are real.
Wires Frame.renderPass() to create a RenderPass with the frame's
command list.

* fix: collect RTV handles for single OMSetRenderTargets call

Per-attachment OMSetRenderTargets calls silently overwrote each other,
leaving only the last target bound. Now all RTV handles are collected
into a stack array and bound with a single call. Viewport and scissor
are also set once from the first valid target instead of per-attachment.

* fix: correct compile errors in Target/RenderPass/Frame

- Fix invalid D3D12_RESOURCE_STATE_* references to use
  D3D12_RESOURCE_STATES enum members (PRESENT, RENDER_TARGET)
- Fix optional-to-non-optional type mismatch in Frame.renderPass
  by unwrapping command_list with stub fallback
- Make Target.transitionBarrier a method on self instead of a
  free function taking a bare resource pointer
- Null resource in Target.deinit to prevent use-after-free
- Make RenderPass.command_list optional to support stub path
- Name the magic 8 as D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT
* renderer: DX12 clear-to-color through real device + command list

Wire DirectX12.zig stubs to real GPU init. Device creation, swap
chain (via DirectComposition), command queue, fence sync, triple-
buffered command recording, clear render target, and present.

Add IDXGISwapChain3 binding for GetCurrentBackBufferIndex. Make
Device and DescriptorHeap types pub for use from DirectX12.zig.

* fix: address PR review feedback for DX12 clear-to-color

- Remove @constcast escape in beginFrame, use renderer.api for mutable
  access instead (matches DX11 pattern)
- Replace @Panic with error.NoWindowsSurface at surface boundary
- Use specific error names (NoSwapChain, NoDevice, FrameSyncFailed,
  FrameNotReady) instead of overloaded error.PresentFailed
- Add comments on @ptrCast(@aligncast) pointer coercion chains
- Document single-threaded assumption on fence_value mutation
- Improve surfaceSize TODO about per-frame GetDesc1 cost
- Document unused alloc parameter (COM-based allocation)

* fix: correct compile error and review findings in DX12 clear-to-color

- Fix IID_ID3D12Resource -> ID3D12Resource.IID (would not compile on Windows)
- Write back Frame to gpu_frames after mutation to keep stored copy current
- Remove unused apprt import
- Add log.warn on GetDesc1 failure in surfaceSize
- Document SharedTexture path, presentLastTarget fence safety, TDR TODO
* renderer: DX12 Buffer with upload heap and persistent mapping

Single ID3D12Resource per Buffer(T) in D3D12_HEAP_TYPE_UPLOAD,
mapped once at creation. Regrows at 2x when sync data exceeds
capacity. Per-frame isolation handled by GenericRenderer's
FrameState, so no ring buffer segmentation needed.

* fix: address code review findings in DX12 buffer implementation

- RawBuffer.size: u32 -> u64 to prevent silent truncation on large buffers
- copy(): void -> !void, returns error.BufferMapFailed instead of silently
  swallowing null mapped pointer, consistent with Metal's error.MetalFailed
- syncFromArrayLists: replace ad-hoc error.BufferNotMapped with same
  error.BufferMapFailed + log.warn pattern for consistency
- Add errdefer self.release() in init/initFill to prevent resource leaks
  on partial failure (matches Metal/OpenGL backend patterns)
- Add comment explaining ignored heap_props fields for non-CUSTOM types
- Remove unsafe @intcast(byte_size) since size is now u64

* fix: deinit must fully reset buffer state, not just Release()

deinit() was only calling Release() on the resource but left mapped,
len, and buffer fields with stale/dangling values. Delegate to
release() for complete cleanup, matching the pattern used by sync()
and the errdefer in init/initFill.

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>

* fix: correct compile errors in DX12 buffer -- enum/IID references, unused import

---------

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
…or) (#126)

* renderer: DX12 Texture (DEFAULT heap + staging) and Sampler (descriptor)

Texture uses a committed resource in DEFAULT heap with SRV descriptor.
Uploads go through a staging buffer copied via CopyTextureRegion.
Sampler is a descriptor in the sampler heap, not a standalone object.

* fix: review cleanup for DX12 Texture/Sampler implementation

- Replace magic numbers with D3D12_TEXTURE_COPY_TYPE enum
- Remove unused com import from Sampler.zig
- Add log.err for staging buffer creation and Map failures
- Add log.warn for unknown pixel formats in bppForFormat
- Deduplicate transition call in Texture.init

* fix: correct build-breaking bugs and improve safety in DX12 Texture

- Fix IID_ID3D12Resource (nonexistent) to ID3D12Resource.IID (matching Buffer.zig)
- Fix D3D12_RESOURCE_STATE_* C-style names to D3D12_RESOURCE_STATES.* enum variants
- Fix staging buffer comment: DX12 does NOT extend resource lifetimes for
  recorded commands (unlike D3D11), document the actual safety precondition
- Add log.err on null early returns in uploadRegion for debuggability
- Document replaceRegion silent failure as intentional (Metal API parity)
- Note descriptor slot leak on failed init (linear allocator, not fixable)
- Replace break :switch with return in bppForFormat, upgrade warn to err
* renderer: DX12 Pipeline with root signature and PSO

Shared root signature with inline CBV (b0), SRV descriptor table
(t0..t2), and sampler descriptor table (s0). Per-pipeline PSO
creation from DXIL bytecode with configurable blend mode and
input layout.

* fix: correct build-breaking bugs and improve safety in DX12 Pipeline

Use nested IID constants (ID3D12RootSignature.IID, ID3D12PipelineState.IID)
instead of non-existent top-level aliases, matching the convention used
throughout the codebase (device.zig, Frame.zig, buffer.zig, etc.).

Add root_signature ownership to Shaders so the shared root signature
gets released on deinit, preventing a COM leak.
* renderer: DX12 shader loading from embedded DXIL bytecode

Replace the shaders.zig stub with DXIL bytecode loading via @embedfile
and input element descriptions for all 5 pipelines. Shaders.init()
creates a shared root signature and PSOs for bg_color, cell_bg,
cell_text, image, and bg_image.

* fix: review cleanup for DX12 shader loading

- Remove unused dxgi import
- Move root signature ownership to explicit Shaders.root_signature
  field instead of burying it in bg_color pipeline
- Add errdefer cleanup in init() to prevent COM resource leaks
  on error (root signature + already-created PSOs)
- Remove redundant byte-offset runtime tests that duplicate the
  comptime assertions already validating struct layouts
- Use inline for in deinit() instead of listing each pipeline

* fix: root_signature ownership tests and pre-existing compile errors

Add tests verifying Pipeline.deinit does not touch root_signature
(owned by Shaders, released separately in Shaders.deinit). A bogus
sentinel pointer catches accidental double-free if the contract
changes.

Also fix two pre-existing compile errors:
- Pipeline.zig: DepthStencilState enum fields expected enums not ints
- Frame.zig: @typeof on struct field is not valid Zig syntax

* fix: review cleanup for DX12 shader loading

- Fix deinit order: release PSOs before root signature to match
  init order and DX12 convention
- Replace duplicated struct size assertions with cross-referencing
  input layout byte offsets against gpu_data struct offsets
- Add TODO(#127) for root signature v1.1 upgrade
- Fix misleading test name in Frame.zig
* renderer: DX12 wire all 5 render pipelines end-to-end

RenderPass.step() now binds PSO, root signature, uniforms (inline
CBV), textures (SRV descriptor table), samplers, and instance vertex
buffers, then issues DrawInstanced calls.

Shader-visible CBV/SRV/UAV and sampler descriptor heaps created at
init. initShaders() creates the 5 PSOs via Shaders.init(). Texture
and sampler options wired with device and heap pointers so atlas
textures get real SRV descriptors.

RawBuffer gains a stride field so the vertex buffer view knows the
per-instance element size without losing type info.

* fix: code review fixes for DX12 pipeline wiring

- Remove dead srv_heap/sampler_heap fields from RenderPass struct
  (stored but never read via self.*, only used in begin() via opts)
- Extract magic heap size numbers (64/16) into named constants
  (srv_heap_capacity, sampler_heap_capacity) with doc comments
- Add @constcast safety rationale comments at all call sites
- Clarify DX12 descriptor table vs Metal per-texture binding in
  texture/sampler comments (table-based, not individual indices)
- Add TODO for multi-buffer binding (buffers[1..] as storage)
- Update tests to remove assertions on deleted fields

* fix: replace TODOs with tracked issues, document RawBuffer u32 choice

Replace inline TODOs with GitHub issue references:
- #128: multi-buffer binding for cell_text pipeline
- #129: custom shader support (.hlsl shadertoy target)
- #130: TDR recovery (DXGI_ERROR_DEVICE_REMOVED)
- #131: cache surfaceSize dimensions
- #132: frame pool fence wait wiring

Add why-comment on RawBuffer.size/stride u32 types matching
D3D12_VERTEX_BUFFER_VIEW fields.

* docs: update Win32 example comments from DX11 to DX12

The renderer backend defaulted to DX12 since the pivot (backend.zig:22).
The example code is renderer-agnostic but the comments still said DX11.
* renderer: DX12 GPU integration tests

Tests device creation, command queue, fence signaling, descriptor heaps,
buffers, textures, samplers, PSO creation, frame lifecycle, and swap
chain modes (HWND with DirectComposition, SharedTexture) against a real
D3D12 device.

* fix: execute GPU commands in texture integration tests

Texture tests recorded CopyTextureRegion and ResourceBarrier commands
on the command list but never submitted them. This left staging buffers
in limbo and meant assertions only checked CPU-side state tracking.

- Add executeAndWait() + reset() after texture creation with initial data
- Add executeAndWait() + reset() after replaceRegion
- Change silent catch {} to catch return in replaceRegion test

* fix: errdefer cleanup, consistent error handling in GPU tests

- Rewrite createTestDevice() to return !TestDevice with errdefer for
  each COM object, matching device.zig convention. Removes ~30 lines
  of manual cascading Release() calls.
- Standardize all callers from 'orelse return' to 'catch return'.
- Add comment noting Frame.init's undefined renderer/target fields.
- Rename 'all 5 PSOs' test to avoid coupling to pipeline count.
- Use expectEqual for exact 2x growth factor assertion.

* fix: code review fixes for DX12 GPU integration tests

- Check WaitForSingleObject return value instead of discarding
- Propagate executeAndWait errors in texture tests instead of swallowing
- Use real DefWindowProcW for HWND test window procedure
- Remove unused dcomp import, use idiomatic null checks
- Add self.* = undefined in TestDevice.deinit
- Simplify comment that referenced removed DX11 code
* renderer: DX12 fix surface creation crash (#133)

* renderer: DX12 use @max for swap chain dimension clamp, add HWND 0x0 test

Use @max(width, 1) to match codebase style (see embedded.zig:811-812).
Add HWND-mode test that exercises the createCompositionSwapChain clamp
path with 0x0 dimensions, verifying the swap chain is created at 1x1.
* renderer: DX12 cache surfaceSize dimensions (#131)

* renderer: DX12 add why-comments to surfaceSize cache

Explain why the cache is the primary source of truth: DX12 uses
composition swap chains for all surface types, so there is no
GetClientRect equivalent. Document the GetDesc1 fallback limits
and the setTargetSize contract.
* renderer: DX12 upgrade root signature to v1.1 (#127)

* renderer: DX12 root signature v1.1 review fixes

- Replace magic Version=2 with typed D3D_ROOT_SIGNATURE_VERSION enum
- Remove dead D3D12SerializeRootSignature v1.0 extern
- Add size tests for D3D12_ROOT_SIGNATURE_DESC1, D3D12_VERSIONED_ROOT_SIGNATURE_DESC
- Consolidate v1.1 size tests into main struct sizes test block
deblasis added 29 commits May 5, 2026 18:30
Adds IsInternalKey alongside IsProfileSubkey in WindowsOnlyKeys, and wires it into ConfigService's diagnostic loop so app-private knobs (currently internal.update-simulator) stop leaking into the settings notice list.

Surfaced during PR # 349 work after the user noticed "unknown field: internal.update-simulator" in the warnings InfoBar.
Reframes the 4 WinUI smoke stubs in Ghostty.Tests.Windows as manual-smoke specs and converts TitleBarPassthroughTests from a failing Assert.Fail to [Fact(Skip=...)]. Headers document what to check by hand; Skip messages explain why XAML+dispatcher hosting is out of scope here.

Counts: 11 pass / 4 skip / 0 fail (was 11 / 3 / 1).
Closes # 344. Vertical-strip users now get the same profile-aware new-tab control as horizontal mode (Click / Alt+Click / Shift+Click + dropdown). New FlyoutPlacement DP on NewTabSplitButton; vertical sets it to Right. Collapsed strip widens 40 -> 56 so the dropdown chevron is visible in collapsed mode (LayoutCoordinator.VerticalStripCollapsedWidth is the single source of truth).
Replaces SplitButton inside NewTabSplitButton with a StackPanel + paired Button + chevron Button + shared MenuFlyout. New Orientation DP picks stack direction (Horizontal default; Vertical for VerticalTabStrip). Reverts # 354's 40 -> 56 strip-width bump; the chevron is now visible-stacked instead of visible-wider. Click semantics unchanged.

Follow-up to # 354.
)

Visual polish on # 355. Wraps the paired Buttons in a rounded outer Border with a 1px separator and transparent inner Buttons; theme key overrides at UserControl.Resources scope (per-button scope crashed Window.SystemBackdrop at startup). Chevron glyph derives from the existing Orientation DP: ChevronDown horizontal, ChevronRight vertical.

Closes # 343.
Folds the two reviewer items deferred from # 356: SymbolThemeFontFamily + BodyTextBlockFontSize on the FontIcons (chevron stays at FontSize=10 with a why comment since no theme ramp entry matches), and orientation switching moves from the imperative OnOrientationChanged callback to a VisualStateManager group with HorizontalState + VerticalState. Behavior unchanged.
Fixes pwsh under default config on non-UTF-8 OEM Windows (e.g. Italian
CP-850): encoding lands at 65001, Nerd Font glyphs render, full
PSReadLine line-editing works.

Two key changes:

* `termio: inject utf-8 preamble on bypass transport ( # 341)` drops
  the `mode != .conpty` early-return so PR # 308's preamble fires on
  `.bypass` for pwsh under the user's actual repro path.
* `termio: force conpty for pwsh + suppress csi response under
  bypass+pwsh` matches WezTerm / Alacritty / libghostty-vt-dotnet:
  pwsh's PSReadLine needs a console handle for VT input. Other
  VT-aware shells (bash, wsl, ssh, nu, fish, zsh, elvish, xonsh)
  keep using bypass.

Plus the supporting infrastructure: `utf8-console = auto | always |
never` config knob with CJK-aware default, `isCjkAnsiCodePage` and
`resolveUtf8Console` helpers, removal of the obsolete
`ensureUtf8Console` runtime hack (replaced by
`<activeCodePage>UTF-8</activeCodePage>` in the Zig GUI manifest),
and a `WriteKind` tag on termio messages for transport-aware
response suppression.

Manual probe on Italian CP-850 Windows: chcp reports 65001,
[Console]::OutputEncoding reports CodePage 65001,
[Console]::IsInputRedirected reports False, Oh-My-Posh Nerd Font
glyphs render, backspace + Enter + arrow-up history + tab completion
all work. `utf8-console = never` kill switch verified.

Refs deblasis/wintty # 341
Upstream's freetype_windows landed `Discover.init(lib)` across all
backends. Our directwrite_freetype was still init() with no args, so
list_fonts and SharedGridSet failed to compile after the rebase.
Make DirectWrite.init accept (and ignore) a Library handle. Tests
pass undefined since the parameter is unused.
Promote WarnIfNoLibghostty to ErrorIfNoLibghostty in
windows/Ghostty/Ghostty.csproj.

The Condition="Exists(...)" on the <None Include> for ghostty.dll
above is evaluated at MSBuild project-load time, not target-execution
time. A missing dll silently drops the copy item and the produced
binary throws DllNotFoundException at startup -- the process stays
alive in Task Manager but no window paints because async-void
OnLaunched exception plumbing reaches Application.UnhandledException
with Handled=false without reliably tearing the process down.

Not gated on \$(PublishAot) because that property is set unconditionally
in this csproj's main PropertyGroup: it only enables AOT analyzers,
not AOT compilation. dotnet build always produces a managed assembly
that loads ghostty.dll via runtime DllImport. A clean
zig build -Dapp-runtime=none produces both ghostty.dll and
ghostty-static.lib together, so requiring the dll covers every
consumer including AOT publish (which resolves ghostty.dll when
launched from bin\ rather than publish\).

Error text uses [System.IO.Path]::GetFullPath to surface the absolute
path of the missing artifact, and instructs the user to run zig in
the directory containing build.zig (unambiguous in both the source
tree and the materialised wintty-release tier tree).

Layer B follow-up to deblasis/wintty-release # 82 / # 83. Closes
deblasis/wintty-release # 84.
)

The <Content Include=\"\$(GeneratedBrandingDir)AppIcon.scale-*.png\"> uses
a wildcard, which expands at MSBuild evaluation time. The
GenerateBrandingAssets target above runs in BeforeBuild, after
evaluation, so on a clean build the wildcard matches zero files, no
Content items are added, and bin\Assets\ never appears. The
AppIconBadge then resolves ms-appx:///Assets/AppIcon.png to nothing and
renders the broken-image placeholder where the Ghostty ghost should be.

Replacing the wildcard with explicit scale names (100/150/200/400 to
match PngWriter.ScaleTargets) lets MSBuild add the items at evaluation
regardless of whether the files exist yet; the Copy task at build time
succeeds because GenerateBrandingAssets has produced them by then. The
WinUI 3 _GenerateProjectPriFileCore target runs after BeforeBuild, so
the PRI resolves ms-appx://Assets/AppIcon.png to the scale-qualified
file at runtime as before.

Second instance of the same evaluation-time-vs-target-execution-time
anti-pattern in this csproj. The first was the ghostty.dll <None Include>
Condition=\"Exists(...)\" trap, fixed in # 360.
* nit: toning down comments verbosity and slop

* nit: round 2 — Zig renderer + COM vtable slot counts + CI + IconGen
* feat(libghostty): add ghostty_build_info FFI export

Exposes version, commit, channel, Zig version, and build mode as a single
struct readable without ghostty_init. Used by the Windows host for
+version output and the Version command-palette dialog.

* feat(libghostty): declare ghostty_build_info in ghostty.h

Header companion to the FFI export landed in the previous commit.

* fixup(libghostty): match Zig type-naming pattern in ghostty_build_info

- Rename Zig type ghostty_build_info_s -> BuildInfo, matching the existing
  Info / String pattern in main_c.zig where the C-side typedef name lives
  in a doc comment above the PascalCase Zig alias.
- Replace emdash with semicolon in the doc comment per ASCII-only rule.
- Document the b ++ "" idiom so future readers understand the comptime
  sentinel coercion.

* style(libghostty): match comptimePrint idiom and trim doc comments

- Use comptimePrint for the commit-hash literal, matching the sibling
  version-string block. The b ++ "" trick worked but isn't used elsewhere
  in the codebase.
- Trim the over-explanatory comment blocks above both file-scope
  constants. comptimePrint makes the comptime requirement self-evident.
- Move the lifetime / encoding / commit-empty paragraph from the type
  doc onto the function doc, leaving the type doc as just the C name —
  matches the Info / String pattern in the same file.
* feat(core): add Edition enum for version output

Single-value Oss in the public repo. Overlay extends with Sponsor and Pro.

* feat(core): add EditionLabel.Format

Pure formatter for Edition. Public path returns oss. Single-arg
signature; overlay extends to (Edition, Channel) when adding
sponsor/pro cases.

* feat(core): add LibGhosttyBuildInfo record + P/Invoke bridge

Reads libghostty's ghostty_build_info FFI. Blittable struct,
NativeAOT-compatible. Strings marshalled to managed copies; native
lifetimes are static so no free is required.

* feat(core): add VersionInfo record

* feat(core): add VersionRenderer with plain and ANSI rendering

Single renderer used by the +version CLI and the Version palette dialog.
ANSI variant wraps the header in OSC 8; plain variant is used for the
dialog and for redirected stdout. xUnit fixtures cover both paths plus
the unknown-commit fallback.

* feat(core): add MSBuild target to generate BuildInfo.g.cs

Emits Wintty version, commit (with -dirty suffix), MSBuild config, and
Edition.Oss as compile-time constants. WriteOnlyWhenDifferent avoids
spurious rebuilds. Git unavailable falls back to commit unknown.

* feat(core): add VersionRenderer.Build() integration

Combines BuildInfo (MSBuild), libghostty FFI, and runtime probes into a
VersionInfo. Used by the +version CLI and the palette dialog.

* fixup(core): minor cleanups from review pass

- Document why <Compile Include> stays inside GenerateBuildInfo: the
  reviewer suggested moving it to top-level for IDE IntelliSense, but
  $(IntermediateOutputPath) is set by Microsoft.NET.Sdk.targets which
  imports after csproj XML evaluation, so a top-level Include resolves
  to a wrong path and creates a duplicate Compile item. Verified by
  trying it.
- Drop redundant System. prefix on ArgumentOutOfRangeException; the
  csproj has ImplicitUsings enabled.
- Pull the field-padding column out of Field() into a class-level
  FieldValueColumn const so it stops looking like a magic number.

The Channel-propagation flag from review is not a bug for this codebase:
local Release builds correctly stay on the "tip" channel string. CI
Stable releases pass -p:Channel=Stable as a global property, which does
propagate to Ghostty.Core via ProjectReference.
* feat(host): add CommandCategory.About

Bucket for the Version palette item (and future About-flavored entries
like Open Logs Folder, Report a Bug).

* feat(host): add CliActions.PrintVersion

Renders VersionInfo to stdout. Picks ANSI vs plain via
Console.IsOutputRedirected. No AttachConsole - Wintty.exe is a
console-subsystem app and inherits the parent terminal natively.

* feat(host): intercept +version / --version / -v before libghostty dispatch

Routes to CliActions.PrintVersion which uses the C#-side VersionRenderer
shared with the Version command-palette dialog. Skips ghostty_init since
ghostty_build_info is callable without it.

Also extends RegisterNativeResolver to cover Ghostty.Core.dll so that
LibGhosttyBuildInfoBridge (which lives in Core) can resolve ghostty.dll
via the same native/ path as the host assembly.

* feat(host): add Version palette command and dialog

VersionCommandSource exposes a single Version palette item under the
About category. VersionDialog renders the same plain text the CLI
emits and offers a Copy button (no dismiss; brief Copied confirm) for
bug-report use.

Uses DialogTracker so in-flight dialog is drained before window
teardown, consistent with RenameTabDialog and close-confirmation flows.
Uses WinClipboard alias to resolve the Clipboard static against
Windows.ApplicationModel.DataTransfer rather than Ghostty.Clipboard.

* feat(host): register VersionCommandSource in palette

* fixup(host): clipboard hardening and icon escape on Version dialog

VersionDialog.xaml.cs:
- Wrap WinClipboard.SetContent in try/catch (COMException) to match
  the existing CO_E_NOTINITIALIZED / CLIPBRD_E_CANT_OPEN handling in
  WinUiClipboardBackend. An unhandled COMException on an async void
  event handler tears the process down.
- Capture the original primary-button label once at construction and
  add a _copyInProgress guard so a rapid double-click on Copy doesn't
  end up reading "Copied" as the rest state and leave the button stuck
  on "Copied" indefinitely.

VersionCommandSource.cs:
- Replace the embedded U+E946 PUA glyph with the  escape sequence
  to keep the source ASCII per the keyboard-chars-only rule, matching
  the existing BuiltInCommandSource convention.
…366)

* feat(core): add RenderPlainBody and CommitUrl helpers

VersionRenderer now exposes:
- RenderPlainBody(VersionInfo): the Version + Build Config blocks
  without the leading "Wintty <version>" header line. Lets the dialog
  show just the body, since its title bar already carries the version.
- CommitUrl(VersionInfo): the github.com commit URL or null when no
  commit is known. Used by the dialog's clickable hyperlink and by
  ANSI rendering's OSC 8 wrap.

RenderPlain stays unchanged so the CLI output and the clipboard payload
keep the full text (header + URL line + body), which is what bug-report
pastes need.

* feat(host): polish Version dialog with app icon and inline hyperlink

- Title row now has the 40x40 app icon (via AppIconSource.Current) next
  to "Wintty <version>". Drops the redundant "Wintty <version>" line
  the body used to repeat from the title bar.
- The commit URL is rendered as an inline Hyperlink inside a TextBlock
  rather than a HyperlinkButton, so it reads as colored monospace text
  in the same flow as the body, with no button border or focus chrome.
  Clicking opens the commit page in the default browser.
- Dialog body uses VersionRenderer.RenderPlainBody (no header / URL
  line). Clipboard payload still uses RenderPlain so paste keeps every
  field for bug-report use.
* termio: document canonical CSI 6 n reply shape

The cursor-position DSR handler has been emitting the canonical
ESC [ <row> ; <col> R form, but a downstream wintty-bench probe
reported an 18-byte payload before the terminating R. The libghostty
emit path is unambiguously canonical (matching tests already exist
in stream_terminal.zig), so add an inline note pointing future
investigators at the more likely sources: PTY transport interleaving
and out-of-band replies such as the mode 2048 size report.

No behavioral change.

* docs(termio): tighten CSI 6 n reviewer notes

Address review feedback on PR #368: trim the speculative
transport-interleaving parenthetical and reference size_report.zig by
filename so the mode 2048 disambiguation is anchored to a real symbol.

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>

---------

Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
The AttentionValueInfoBadgeStyle resource key isn't defined anywhere in
this codebase, so the previous raw cast would throw a KeyNotFoundException
the first time the sidebar tried to render an info badge for a page with
a nonzero search-hit count. Mirror the TryGetValue + is pattern from
TabColorPalettePicker.GetBrushResource and fall back to the InfoBadge
default style when the key is missing.
- Add glslpp as Zig dependency (pure-Zig, no C++ runtime)
- Replace shader_wrapper.dll path with glslpp.compileGlslToHlsl()
- Skip 8MB stack thread spawn for HLSL target (no C++ ABI issues)
- Keep glslang+spirv-cross for MSL/GLSL paths (separate thread still needed)
- CRT shader validates through DXC with 0 errors
- Remove shader_wrapper.cpp (MSVC-compiled C++ DLL for GLSL→HLSL)
- Remove wrapper.zig (DLL loader)
- Remove wrapper import from pkg/glslang/main.zig
- Update Pipeline.zig comment: 'shader_wrapper' -> 'glslpp binding_shift'
- glslpp (pure Zig) now handles GLSL→HLSL for the shadertoy path
- Keep glslang+spirv-cross for MSL/GLSL cross-compilation paths
- Add .zig-version and mise.toml for Zig 0.15.2 pinning
… recipe

- Add 'ci' recipe: check-zig + test + build-release (local CI equivalent)
- Add 'check-zig' recipe to verify Zig 0.15.2
- Add 'build-release' recipe (ReleaseSafe, what CI uses)
- Rename deploy-shader-wrapper -> deploy (no more shader_wrapper.dll)
- Mark build-shader-wrapper as -legacy (kept for reference)
- Fix typo: depploy -> deploy
The production shader compilation path now uses glslpp exclusively for
all three targets. This eliminates the need for:
- 8MB stack thread for glslang+spirv-cross on Windows
- C++ runtime dependencies (glslang DLL, spirv-cross)
- Thread isolation for MSL/GLSL compilation

glslang and spirv-cross imports are kept for existing tests only.
The next step is to remove them entirely once tests are migrated.
- shadertoy.zig: Remove glslang/spirv-cross imports, legacy functions
  (spirvFromGlsl, mslFromSpv, hlslFromSpv, glslFromSpv, spvCross, SpirvLog)
- shadertoy.zig: Rewrite tests to use glslpp convenience APIs
- global.zig: Remove glslang initialization
- SharedDeps.zig: Remove glslang/spirv-cross build setup
- Config.zig: Remove glslang/spirv-cross system integration options
- build.zig.zon: Remove glslang/spirv-cross dependency entries
- generic.zig, main_c.zig: Update comments

All shader compilation now goes through glslpp (pure-Zig).
Both wintty shaders render pixel-perfect vs spirv-cross reference.

Pre-existing test failures in Exec.zig and message.zig are unrelated.
These C++ dependencies are no longer needed — glslpp handles all
shader compilation (GLSL → SPIR-V → HLSL/GLSL/MSL) natively in Zig.
Saves ~60MB from the repository.
- Exec.zig: Add missing 4th argument (utf8_console=.never) to execCommand
  calls in Windows tests. The parameter was added to execCommand but the
  tests weren't updated.
- message.zig: Make Message size test platform-aware. On 64-bit Windows
  with MSVC ABI, alignment padding causes the struct to be larger than 41
  bytes. Test now uses a reasonable upper bound on 64-bit platforms.
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.

1 participant