mirror of
https://github.com/ZuneDev/MicrosoftIris.git
synced 2026-07-27 13:13:29 -07:00
147 lines
10 KiB
Markdown
147 lines
10 KiB
Markdown
# UIXrender.dll — rendering & message pump log
|
|
|
|
Append-only. Do not edit previous entries. Newest first.
|
|
|
|
---
|
|
|
|
## 2026-07-22 — Landed: real StbTrueType text rendering + backend-agnostic message pump
|
|
|
|
Built, AOT-published, and tested. Summary of what's now real vs. before.
|
|
|
|
**Text rendering (was: ratio-approximated measurement + `E_NOTIMPL` rasterization):**
|
|
- `Subsystems/Text/LoadedFont.cs` — a TrueType face over StbTrueTypeSharp (pure managed,
|
|
zero GPU). Pins the font bytes (stb stores the data pointer) and exposes scaled
|
|
advance/kerning/ascent/line-height and glyph coverage rasterization.
|
|
- `FontStore.cs` — resolves a face name to a font: runtime-registered fonts
|
|
(`SpLoadFontResource`, now real — reads the embedded resource and registers it) →
|
|
platform system-font discovery (Windows/Linux/macOS dirs) → first-available fallback.
|
|
Cached, thread-safe. Returns null when nothing resolves, so callers degrade to the ratio
|
|
metrics rather than faking.
|
|
- `TextLayout.cs` — real measurement over actual glyph advances + kerning, with the old
|
|
ratio `TextMetrics` kept strictly as the no-font fallback. Word wrap is real either way.
|
|
- `GlyphRun.cs` — the object `hGlyphRunInfo` points at; `Rasterize(color)` composites stb
|
|
coverage bitmaps into a straight-alpha ARGB32 buffer (BGRA-in-memory, matching
|
|
`SurfaceFormat.ARGB32`). `TextBitmap` owns that buffer; `SpFreeDib` (repointed to
|
|
`HandleTable.Free`) releases it.
|
|
- Wired: `SpSimpleTextMeasure` now produces a real glyph run + real geometry;
|
|
`SpRichTextRasterize` (was `E_NOTIMPL`) composites it to a bitmap;
|
|
`SpRichTextMeasure`/`SpRichTextGetNaturalBounds`/`SpSimpleTextMeasurePossible` use the
|
|
real font. Outline/shadow passes and rich-text multi-run `rrcb` remain TODO.
|
|
|
|
**Message pump (was: `PeekMessage`→0, `WaitMessage`→`Thread.Sleep`, `Invoke(null)`→no-op):**
|
|
- `Subsystems/Os/MessagePump.cs` — a `ConcurrentQueue<Action>` + `ManualResetEventSlim`,
|
|
with a lost-wakeup guard (reset only when empty, re-check after). No windowing/GPU
|
|
dependency. `IWindowMessageSource` is the pluggable seam for a host/optional backend to
|
|
inject OS messages during Peek.
|
|
- Wired through `EngineService`: `PeekMessage` → drain+run queued work, report
|
|
ProcessedMessage(1)/None(0); `WaitMessage` → real bounded wait that returns early on a
|
|
post; `Invoke(null ptr)` → `PostWake` (**the InterThreadWake fix** — a blocked
|
|
`WaitMessage` now actually wakes); async `Invoke(real ptr)` → deferred onto the pump
|
|
thread; `SetWindowMessageSource` exposes the seam. `SpPostDeferredImeMessage` now posts
|
|
its dispatch onto the pump (genuinely deferred). `SpCreateNotifyWindow` now returns
|
|
`S_OK` + a token (was `E_NOTIMPL`, which threw inside `UIForm.Initialize`'s `IFC`).
|
|
|
|
**Verified:** `dotnet build UIXrender -f net8.0` clean; whole solution builds except the
|
|
pre-existing `SimpleDebugClient`/`SimpleIrisApp` errors; NativeAOT `linux-x64` publish
|
|
succeeds with **zero trim/AOT warnings** (StbTrueTypeSharp is pure-managed, AOT-safe) and
|
|
exports **exactly 193/193** (surface unchanged). `Tests/UIXrender.Engine.Tests` extended
|
|
to **66 checks, all passing**, exercising real behaviour: proportional metrics ('WWW' >
|
|
'iii', which the ratio path could never produce), word-wrap growth, rasterized ink
|
|
(non-zero alpha pixels — glyphs actually drawn), deferred-work execution on Peek,
|
|
`InterThreadWake` unblocking a blocked `WaitMessage` in ~50ms (not the 5s timeout),
|
|
`WaitMessage` honouring its timeout, and `PeekMessage` reporting the right WorkResult.
|
|
|
|
**Still open / not done (flagged, not hidden):**
|
|
- **No on-screen compositing / real window.** By design (backend-agnostic): text
|
|
measurement + CPU rasterization is complete, but presenting pixels to a screen needs a
|
|
window+GPU backend, which stays out of core. `IWindowMessageSource` is the seam for a
|
|
later optional backend package. `SpPeekMessage` therefore never returns
|
|
`NewUserMessage(2)` (no HWND to Win32-dispatch).
|
|
- Text: DPI scaling (fontHeightPts treated as pixels for now), complex-script shaping
|
|
(stb is Latin-oriented), and outline/shadow/underline rendering passes.
|
|
- Font family matching is filename-heuristic, not the TTF `name` table / fontconfig — a
|
|
bold/italic style request currently resolves the family's plain face.
|
|
|
|
## 2026-07-22 — Course-correction: no GLFW/SDL; backend-agnostic pump + pluggable window seam
|
|
|
|
**What happened:** I asked the user (AskUserQuestion) how to back the message pump and
|
|
they picked "Silk.NET.Windowing real window". While setting that up the user pushed back:
|
|
*"why are you adding a dependency on GLFW? That's not backend-agnostic."* Correct.
|
|
|
|
**The constraint, stated plainly:** `Silk.NET.Windowing` is an abstraction over `IWindow`,
|
|
but there is **no backend-agnostic way to create a real OS window** — `Window.Create()`
|
|
needs a concrete platform provider (GLFW *or* SDL) registered via a backend package at
|
|
runtime, or it throws "no platform registered". So "real window" unavoidably pulls in
|
|
GLFW or SDL, which violates the backend-agnostic requirement that has governed this whole
|
|
effort (and matches FullSurface.md decision 2: UIXrender is loaded into a host that
|
|
already owns its window). Nothing windowing-related had been added to the real project
|
|
yet — only a throwaway scratchpad probe — so there was nothing to revert.
|
|
|
|
**Revised design (what's actually being built):**
|
|
- **Message pump = a backend-agnostic message queue** owned by the render-session thread.
|
|
This is exactly and only what the render engine's loop uses the pump for, verified from
|
|
the call sites (`UIX.RenderApi/.../Internal/RenderEngine.cs`):
|
|
`IRenderEngine.WaitForWork` → `SpWaitMessage(timeout)` (block until work or timeout);
|
|
`IRenderEngine.ProcessNativeEvents` → `SpPeekMessage(drain)` (drain + report a
|
|
WorkResult); `IRenderEngine.InterThreadWake` → `SpInvoke(ctx, NULL, NULL, false)` (wake
|
|
a blocked wait from another thread). A `ConcurrentQueue` + a wait handle implements all
|
|
three correctly with no windowing/GPU dependency at all.
|
|
- **`ProcessNativeEvents`'s `NewUserMessage` (2) path is deliberately never taken.** That
|
|
branch forwards a Win32 `MSG` to `RenderWindow.ForwardWindowMessage` then calls
|
|
`Win32Api.TranslateMessage`/`DispatchMessage` — genuine Win32/HWND dispatch that only
|
|
exists on Windows with a real window. The backend-agnostic pump has no HWND, so
|
|
`SpPeekMessage` returns `ProcessedMessage` (1) when it ran queued work, else `0` (none),
|
|
and leaves `out msg` default — which routes `ProcessNativeEvents` down its non-Win32
|
|
branch (`return TestFlag(nResult, 1)`), exactly right for a windowless pump.
|
|
- **Pluggable window seam, no dependency:** a small `IWindowMessageSource` interface plus
|
|
`EngineService.SetWindowMessageSource(...)` lets a host that owns a window (or an
|
|
*optional, separate* Silk.NET.Windowing backend package that never ships in UIXrender
|
|
core) inject OS window/input messages into the pump later. The pump harvests from the
|
|
source during Peek if one is registered. This preserves the "windowing can plug in"
|
|
intent of the user's original menu choice while keeping UIXrender core free of GLFW/SDL.
|
|
|
|
**Deferred-invoke semantics fixed at the same time:** `SpInvoke` with a **null** function
|
|
pointer is `InterThreadWake` → post a wake to the pump (this is *the* fix the user's "must
|
|
implement a pump" requirement was about: a blocked `SpWaitMessage` now actually wakes). A
|
|
**non-null** async `SpInvoke` enqueues the callback to run *on the pump thread* during the
|
|
next Peek (real "deferred invoke to the render thread" semantics), not on a random
|
|
threadpool thread as the interim wiring did; a non-null sync `SpInvoke` runs inline.
|
|
|
|
---
|
|
|
|
## 2026-07-22 — Plan: text rendering (StbTrueTypeSharp) + message pump
|
|
|
|
**Task (user):** implement rendering, specifically text rendering, and — mandatory — a
|
|
message pump so `SpPeekMessage`/`SpWaitMessage` work correctly. Backend-agnostic; pause
|
|
and ask if no suitable abstraction exists (which is why the font backend was an
|
|
AskUserQuestion — Silk.NET has no font/text abstraction, and the community option
|
|
SilkyNvg was rejected for being OpenGL-only).
|
|
|
|
**Font backend chosen (user):** StbTrueTypeSharp — pure-managed stb_truetype port,
|
|
zero-GPU, NativeAOT-clean, pairs with the StbImageSharp decoder already referenced. Gives
|
|
glyph metrics (real measurement) and CPU glyph rasterization (real `SpRichTextRasterize`).
|
|
No complex-script shaping — line/word layout is built here (the ratio-based `TextMetrics`
|
|
already did the layout; only the per-glyph *width* becomes real).
|
|
|
|
**Text subsystem shape (`Subsystems/Rendering/Text/`):**
|
|
- `FontStore` — resolves a TextStyle font-face name to a loaded font: registered fonts
|
|
(via `SpLoadFontResource`), then platform system-font discovery (Windows `Fonts`, Linux
|
|
`/usr/share/fonts` & friends, macOS), then a first-available fallback. Cached,
|
|
thread-safe. When nothing resolves, callers fall back to the existing ratio metrics and
|
|
rasterization reports `E_NOTIMPL` — honest degradation, not a fake.
|
|
- `LoadedFont` — wraps `stbtt_fontinfo` + v-metrics; scaled advance/kerning/ascent/line
|
|
height, and glyph coverage-bitmap rasterization.
|
|
- `TextLayout` — real measurement (word wrap over real advances) and a measured run.
|
|
- `GlyphRun` — a measured run stored behind a `HandleTable` handle (this becomes the
|
|
`hGlyphRunInfo` the managed side round-trips from measure to rasterize).
|
|
|
|
**Rasterize lifecycle (read off the managed consumers, not guessed):**
|
|
`SpRichTextRasterize` returns `phTextBitmap` + `ppvBits` + size, which
|
|
`Microsoft.Iris.Drawing.RichText.Rasterize` wraps in a `Dib`
|
|
(`UIX/.../RenderAPI/Drawing/Dib.cs`) that frees `phTextBitmap` via **`SpFreeDib`**. So the
|
|
rasterizer allocates one unmanaged ARGB buffer, returns a `HandleTable` handle as
|
|
`phTextBitmap` and the buffer pointer as `ppvBits`, and `SpFreeDib` is repointed to
|
|
`HandleTable.Free` (its previous body referenced a since-removed GDI helper and only ever
|
|
mattered on Windows; the only producer of "DIBs" in this reimplementation is this
|
|
rasterizer). `SpRichTextDestroyGlyphRunInfo` already frees the glyph-run handle.
|