Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions openxr-api-layer/shaders/overlay_text.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,13 @@ cbuffer TextConstants : register(b0)
// Overlay supersample factor (= renderer m_ss). The VS ignores it; the PS
// gates its edge-contrast + gamma corrections on supersample > 1 so the
// 1x (snapshot/golden) path stays byte-identical to the legacy shader.
float supersample; // reg1.x
float3 _pad; // reg1.yzw
float supersample; // reg1.x
// 1.0 when the runtime sRGB-decodes the overlay quad at composite (sRGB
// swapchain, e.g. SteamVR); 0.0 when it composites linearly (UNORM
// swapchain). Flips the PS coverage-gamma direction — see the DIRECTION
// CAVEAT in overlay_text_ps.hlsl.
float srgbComposite; // reg1.y
float2 _pad; // reg1.zw
};

Texture2D<float> atlasTexture : register(t0); // R8_UNORM glyph atlas
Expand Down
23 changes: 15 additions & 8 deletions openxr-api-layer/shaders/overlay_text_ps.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@
// was tuned for; it is the single tuning knob:
// * lower toward 1.0 → lighter stems (1.0 == the old no-correction path)
// * raise → heavier stems
// DIRECTION CAVEAT: this assumes the runtime composites the quad in linear
// space (true for the _UNORM swapchain on the tested runtimes). If a runtime
// instead treats it as sRGB, the correction inverts — use pow(c, TEXT_GAMMA).
// Worth one on-headset A/B pass to confirm weight + direction; it is purely
// cosmetic and trivially reverted via this constant.
// DIRECTION CAVEAT: the correction direction depends on whether the runtime
// composites the quad in linear space (UNORM swapchain — Pimax/WMR/Oculus) or
// sRGB-decodes it (sRGB swapchain — SteamVR). The renderer passes which case
// applies via the `srgbComposite` cbuffer flag: linear → pow(c, 1/TEXT_GAMMA)
// (lifts stems); sRGB → pow(c, TEXT_GAMMA) (the inverse). Both are still worth
// one on-headset A/B pass to confirm weight + direction; purely cosmetic and
// trivially tuned via TEXT_GAMMA (or by forcing the branch).
// =============================================================================

#include "overlay_text.hlsli"
Expand Down Expand Up @@ -98,11 +100,16 @@ float4 PSMain(TextVSOutput i) : SV_TARGET
// Edge contrast first: steepen the coverage ramp about 0.5 so glyph
// edges survive the compositor's bilinear resample crisper.
coverage = saturate((coverage - 0.5f) * EDGE_SHARPEN + 0.5f);
// Then gamma-correct the coverage for the linear-space alpha-over
// blend. Guard pow(0): pow(0, x) is spec-undefined in SM4.0 (a driver
// Then gamma-correct the coverage. The exponent direction depends on
// how the runtime composites the quad (see DIRECTION CAVEAT): linear
// (UNORM swapchain) lifts with 1/TEXT_GAMMA; sRGB (sRGB swapchain, the
// runtime sRGB-decodes the quad) needs the inverse, TEXT_GAMMA.
// Guard pow(0): pow(0, x) is spec-undefined in SM4.0 (a driver
// returning NaN would poison the blend) and 0 coverage must stay fully
// transparent, so branch it rather than lifting it with an epsilon.
coverage = (coverage > 0.0f) ? pow(coverage, 1.0f / TEXT_GAMMA)
float gammaExp = (srgbComposite > 0.5f) ? TEXT_GAMMA
: (1.0f / TEXT_GAMMA);
coverage = (coverage > 0.0f) ? pow(coverage, gammaExp)
: 0.0f;
}

Expand Down
7 changes: 5 additions & 2 deletions openxr-api-layer/utils/glyph_atlas_renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ namespace openxr_api_layer::utils::glyph_atlas {
UINT dstHeight,
const BuildResult& atlas,
UINT renderWidth,
UINT renderHeight) {
UINT renderHeight,
bool srgbComposite) {
if (!device || !ctx || dstWidth == 0 || dstHeight == 0) return false;
if (atlas.atlasWidth == 0 || atlas.atlasHeight == 0) return false;
if (atlas.bitmap.empty()) return false;
Expand All @@ -67,6 +68,7 @@ namespace openxr_api_layer::utils::glyph_atlas {
m_renderW = renderWidth ? renderWidth : dstWidth;
m_renderH = renderHeight ? renderHeight : dstHeight;
m_ss = static_cast<float>(m_renderW) / static_cast<float>(m_dstW);
m_srgbComposite = srgbComposite;
// Snapshot the atlas dimensions before createBuffers — it bakes
// them (with texSize) into an IMMUTABLE constant buffer, so they
// must be set first.
Expand Down Expand Up @@ -234,7 +236,8 @@ namespace openxr_api_layer::utils::glyph_atlas {
{ static_cast<float>(m_dstW), static_cast<float>(m_dstH) },
{ static_cast<float>(m_atlasW), static_cast<float>(m_atlasH) },
m_ss,
{ 0.0f, 0.0f, 0.0f }
m_srgbComposite ? 1.0f : 0.0f,
{ 0.0f, 0.0f }
};
D3D11_BUFFER_DESC bd{};
bd.ByteWidth = sizeof(TextConstants);
Expand Down
20 changes: 17 additions & 3 deletions openxr-api-layer/utils/glyph_atlas_renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,20 @@ namespace openxr_api_layer::utils::glyph_atlas {
//
// Returns false on any pipeline-creation failure. Caller logs +
// degrades to bypass — never crashes the host.
// `srgbComposite` tells the PS which way to apply its coverage gamma
// correction: false (default) = the runtime composites the quad in
// linear space (UNORM swapchain — Pimax/WMR/Oculus); true = the runtime
// sRGB-decodes the quad (sRGB swapchain — SteamVR), which inverts the
// correction. See overlay_text_ps.hlsl's DIRECTION CAVEAT. Defaulting
// to false keeps the snapshot/golden (supersample==1) path byte-stable.
bool init(Microsoft::WRL::ComPtr<ID3D11Device> device,
Microsoft::WRL::ComPtr<ID3D11DeviceContext> ctx,
UINT dstWidth,
UINT dstHeight,
const BuildResult& atlas,
UINT renderWidth = 0,
UINT renderHeight = 0);
UINT renderHeight = 0,
bool srgbComposite = false);

bool isReady() const noexcept { return m_ready; }

Expand Down Expand Up @@ -197,12 +204,15 @@ namespace openxr_api_layer::utils::glyph_atlas {
};

// Cbuffer mirror — two 16-byte registers. reg0 = texSize + atlasSize
// (2× float2); reg1 = supersample + pad. Must match overlay_text.hlsli.
// (2× float2); reg1 = supersample + srgbComposite + pad. Must match
// overlay_text.hlsli.
struct TextConstants {
float texSize[2]; // dest tex (kTexW, kTexH)
float atlasSize[2]; // atlas (atlasWidth, atlasHeight)
float supersample; // = m_ss; PS gates its corrections on > 1
float pad[3];
float srgbComposite; // 1.0 = runtime sRGB-decodes the quad; flips
// the PS coverage-gamma direction
float pad[2];
};
static_assert(sizeof(TextConstants) == 32,
"TextConstants must mirror overlay_text.hlsli's cbuffer "
Expand Down Expand Up @@ -240,6 +250,10 @@ namespace openxr_api_layer::utils::glyph_atlas {
UINT m_renderW = 0;
UINT m_renderH = 0;
float m_ss = 1.0f;
// True when the runtime sRGB-decodes the overlay quad at composite
// (sRGB swapchain); baked into the cbuffer to flip the PS coverage
// gamma. Default false = linear composite (the golden path).
bool m_srgbComposite = false;

Microsoft::WRL::ComPtr<ID3D11VertexShader> m_vs;
Microsoft::WRL::ComPtr<ID3D11PixelShader> m_ps;
Expand Down
Loading
Loading