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
47 changes: 47 additions & 0 deletions src/xrt/auxiliary/d3d/d3d_dxgi_formats.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,53 @@ d3d_dxgi_format_to_typeless_dxgi(DXGI_FORMAT format)
}
}

/*!
* Map an sRGB DXGI format to its plain UNORM sibling (identity for non-sRGB).
*
* Used to build the runtime's *internal* sampling view of an app color
* swapchain so the GPU does NOT auto-decode sRGB->linear when the compositor
* samples it. The display processor expects display-referred (sRGB-encoded)
* bytes, so the compositor must pass the app's bytes through unchanged rather
* than linearizing them (which would arrive ~2.2x too dark). Mirrors the GL
* GL_TEXTURE_SRGB_DECODE_EXT=GL_SKIP_DECODE_EXT fix.
*/
static inline DXGI_FORMAT
d3d_dxgi_format_srgb_to_unorm(DXGI_FORMAT format)
{
switch (format) {
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM;
default: return format;
}
}

/*!
* Pick the format for the runtime's INTERNAL sampling SRV of an app color
* swapchain so the GPU does NOT auto-decode sRGB->linear. Maps the 8-bit
* BGRA/RGBA family — whether the resource is TYPELESS, sRGB, or already UNORM —
* to its plain UNORM form. Identity for everything else. Used by the D3D12
* compositor where the swapchain resource is promoted to TYPELESS so the
* runtime SRV can reinterpret it as UNORM (a concrete sRGB resource can't be
* SRV-cast in D3D12). See d3d_dxgi_format_srgb_to_unorm for the comment on why
* passthrough (no decode) is correct.
*/
static inline DXGI_FORMAT
d3d_dxgi_format_to_unorm_sample(DXGI_FORMAT format)
{
switch (format) {
case DXGI_FORMAT_R8G8B8A8_TYPELESS:
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
case DXGI_FORMAT_R8G8B8A8_UNORM:
return DXGI_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_B8G8R8A8_TYPELESS:
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8A8_UNORM:
return DXGI_FORMAT_B8G8R8A8_UNORM;
default:
return format;
}
}

static inline int64_t
d3d_dxgi_format_to_vk(DXGI_FORMAT format)
{
Expand Down
11 changes: 10 additions & 1 deletion src/xrt/compositor/d3d11/comp_d3d11_swapchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,17 @@ comp_d3d11_swapchain_create(struct comp_d3d11_compositor *c,
DXGI_FORMAT typeless = d3d_dxgi_format_to_typeless_dxgi(dxgi_format);
if (typeless != dxgi_format) {
texture_format = typeless;
// srv_format and rtv_format remain as the original concrete format
// rtv_format remains the original concrete format so the app's own
// render path (incl. any sRGB encode it relies on) is unchanged.
}
// The runtime's internal SRV samples this image to build the composited
// atlas handed to the display processor. Use the UNORM sibling of an
// sRGB format so the sample does NOT auto-decode sRGB->linear: the DP
// expects display-referred (sRGB-encoded) bytes, so pass the app's
// bytes through unchanged. No-op for already-UNORM formats. Mirrors the
// GL GL_SKIP_DECODE_EXT fix. (Only the composited path samples this SRV;
// the single-layer zero-copy path hands the texture straight to the DP.)
srv_format = d3d_dxgi_format_srgb_to_unorm(srv_format);
}

// Create textures
Expand Down
11 changes: 9 additions & 2 deletions src/xrt/compositor/d3d12/comp_d3d12_renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "util/comp_layer_accum.h"
#include "util/u_logging.h"
#include "d3d/d3d_dxgi_formats.h"
#include "math/m_api.h"

#define WIN32_LEAN_AND_MEAN
Expand Down Expand Up @@ -402,7 +403,11 @@ render_window_space_layer(struct comp_d3d12_renderer *r,

D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
D3D12_RESOURCE_DESC res_desc = src_resource->GetDesc();
srv_desc.Format = res_desc.Format;
// Sample app color swapchains as UNORM (not their sRGB sibling) so the GPU
// does NOT auto-decode sRGB->linear; the DP wants display-referred bytes, so
// pass them through. Resource is TYPELESS for 8-bit color (see swapchain),
// which this maps to UNORM; identity for other/non-color formats.
srv_desc.Format = d3d_dxgi_format_to_unorm_sample(res_desc.Format);
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srv_desc.Texture2D.MipLevels = 1;
srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
Expand Down Expand Up @@ -1016,7 +1021,9 @@ comp_d3d12_renderer_draw_projection_pass(struct comp_d3d12_renderer *renderer,
}

D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
srv_desc.Format = src_desc.Format;
// UNORM sample (see the other SRV site): no sRGB auto-decode; the
// app's display-referred bytes pass through to the DP unchanged.
srv_desc.Format = d3d_dxgi_format_to_unorm_sample(src_desc.Format);
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srv_desc.Texture2D.MipLevels = 1;
srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
Expand Down
21 changes: 21 additions & 0 deletions src/xrt/compositor/d3d12/comp_d3d12_swapchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,27 @@ comp_d3d12_swapchain_create(struct comp_d3d12_compositor *c,
default:
break;
}
} else {
// For 8-bit color formats, create the resource TYPELESS so the runtime
// can build a UNORM sampling SRV that does NOT auto-decode sRGB->linear
// when compositing (the DP wants display-referred bytes; pass them
// through unchanged). The app still creates its own typed RTV from the
// format it requested. Bounded to the 8-bit BGRA/RGBA family where the
// TYPELESS->UNORM mapping is unambiguous; other formats stay concrete.
// (Unlike D3D11, D3D12 cannot SRV-cast a concrete sRGB resource, so the
// TYPELESS promotion is required here.) Mirrors the GL skip-decode fix.
switch (dxgi_format) {
case DXGI_FORMAT_R8G8B8A8_UNORM:
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
resource_format = DXGI_FORMAT_R8G8B8A8_TYPELESS;
break;
case DXGI_FORMAT_B8G8R8A8_UNORM:
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
resource_format = DXGI_FORMAT_B8G8R8A8_TYPELESS;
break;
default:
break;
}
}

// Create committed resources
Expand Down
43 changes: 42 additions & 1 deletion src/xrt/compositor/metal/comp_metal_compositor.m
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,20 @@
*
*/

// Map an sRGB Metal pixel format to its plain UNORM sibling (identity
// otherwise). Used to sample an app sRGB swapchain through a non-decoding view
// so the compositor passes the app's display-referred bytes through to the DP
// unchanged. Mirrors the GL/D3D/VK sRGB-passthrough fixes.
static MTLPixelFormat
metal_srgb_to_unorm(MTLPixelFormat format)
{
switch (format) {
case MTLPixelFormatRGBA8Unorm_sRGB: return MTLPixelFormatRGBA8Unorm;
case MTLPixelFormatBGRA8Unorm_sRGB: return MTLPixelFormatBGRA8Unorm;
default: return format;
}
}

static MTLPixelFormat
xrt_format_to_metal(int64_t format)
{
Expand Down Expand Up @@ -912,7 +926,12 @@ - (BOOL)wantsUpdateLayer
width:info->width
height:info->height
mipmapped:(info->mip_count > 1) ? YES : NO];
desc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
// PixelFormatView lets the compositor sample an sRGB swapchain through a
// UNORM view (sRGB passthrough — see the compose pass) without the GPU
// auto-decoding sRGB->linear. The app's own render target keeps the sRGB
// format, so its encoding (if any) is unchanged.
desc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead |
MTLTextureUsagePixelFormatView;
desc.storageMode = MTLStorageModeShared;
if (info->array_size > 1) {
desc.textureType = MTLTextureType2DArray;
Expand Down Expand Up @@ -1657,6 +1676,18 @@ - (BOOL)wantsUpdateLayer
if (src_tex == nil) {
continue;
}
// sRGB passthrough: sample an app sRGB swapchain through a UNORM
// view so the GPU does not auto-decode sRGB->linear; the DP wants
// display-referred bytes. No-op for UNORM. Mirrors GL/D3D/VK.
{
MTLPixelFormat unorm_fmt = metal_srgb_to_unorm(src_tex.pixelFormat);
if (unorm_fmt != src_tex.pixelFormat) {
id<MTLTexture> v = [src_tex newTextureViewWithPixelFormat:unorm_fmt];
if (v != nil) {
src_tex = v;
}
}
}

// Use sub-image norm_rect to sample correct region of source texture
struct xrt_normalized_rect nr = layer->data.proj.v[eye].sub.norm_rect;
Expand Down Expand Up @@ -1790,6 +1821,16 @@ - (BOOL)wantsUpdateLayer
if (src_tex == nil) {
continue;
}
// sRGB passthrough (see projection pass): sample through a UNORM view.
{
MTLPixelFormat unorm_fmt = metal_srgb_to_unorm(src_tex.pixelFormat);
if (unorm_fmt != src_tex.pixelFormat) {
id<MTLTexture> v = [src_tex newTextureViewWithPixelFormat:unorm_fmt];
if (v != nil) {
src_tex = v;
}
}
}

// Source UV sub-rect (default to full texture if not specified)
struct xrt_normalized_rect nr = ws->sub.norm_rect;
Expand Down
40 changes: 37 additions & 3 deletions src/xrt/compositor/vk_native/comp_vk_native_swapchain.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,43 @@ comp_vk_native_swapchain_create(struct comp_vk_native_compositor *c,
usage |= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}

// sRGB passthrough (mirrors the GL/D3D11/D3D12 fixes): the compose
// vkCmdBlitImage reads the source in the IMAGE's format, so an sRGB image
// would auto-decode sRGB->linear with no re-encode into the UNORM atlas,
// and the DP (which wants display-referred bytes) would get ~2.2x-too-dark
// content. There is no view to retag for a blit, so create the color image
// in the UNORM sibling — the blit then passes the app's stored bytes through
// unchanged. Expose the requested sRGB format via MUTABLE_FORMAT + a format
// list so the app can still create an sRGB view to render with encode.
VkFormat image_format = vk_format;
VkFormat srgb_view_format = VK_FORMAT_UNDEFINED;
if (!depth) {
switch (vk_format) {
case VK_FORMAT_R8G8B8A8_SRGB:
image_format = VK_FORMAT_R8G8B8A8_UNORM;
srgb_view_format = vk_format;
break;
case VK_FORMAT_B8G8R8A8_SRGB:
image_format = VK_FORMAT_B8G8R8A8_UNORM;
srgb_view_format = vk_format;
break;
default: break;
}
}
const bool mutable_srgb = (srgb_view_format != VK_FORMAT_UNDEFINED);
VkFormat view_format_list[2] = {image_format, srgb_view_format};
VkImageFormatListCreateInfo format_list_ci = {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
.viewFormatCount = 2,
.pViewFormats = view_format_list,
};

VkImageCreateInfo image_ci = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = mutable_srgb ? &format_list_ci : NULL,
.flags = mutable_srgb ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = vk_format,
.format = image_format,
.extent = {info->width, info->height, 1},
.mipLevels = info->mip_count > 0 ? info->mip_count : 1,
.arrayLayers = info->array_size > 0 ? info->array_size : 1,
Expand Down Expand Up @@ -321,12 +354,13 @@ comp_vk_native_swapchain_create(struct comp_vk_native_compositor *c,
return XRT_ERROR_VULKAN;
}

// Create image view
// Create image view (UNORM base for sRGB swapchains — passthrough, no
// sample-time decode; see the image-format note above).
VkImageViewCreateInfo view_ci = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = sc->images[i],
.viewType = view_type,
.format = vk_format,
.format = image_format,
.subresourceRange = {
.aspectMask = aspect,
.baseMipLevel = 0,
Expand Down
Loading