MDLAsset loads texture in usdz file loaded with wrong colorspace

I have a very basic usdz file from this repo

I call loadTextures() after loading the usdz via MDLAsset. Inspecting the MDLTexture object I can tell it is assigning a colorspace of linear rgb instead of srgb although the image file in the usdz is srgb.

This causes the textures to ultimately render as over saturated.

In the code I later convert the MDLTexture to MTLTexture via MTKTextureLoader but if I set the srgb option it seems to ignore it.

This significantly impacts the usefulness of Model I/O if it can't load a simple usdz texture correctly. Am I missing something?

Thanks!

Answered by DTS Engineer in 896092022

Thanks for the follow-ups, and for confirming it persists on the macOS 27 beta.

I reproduced this on shipping macOS 26.5 using your lowpoly-house.usdz, so it is not specific to the 27 beta. Loading the base-color texture through MDLAsset.loadTextures() and then MTKTextureLoader.newTexture(texture:options:) produces a texture with a linear pixel format (rgba8Unorm). The shader then receives the sRGB-encoded bytes without the sRGB-to-linear decode, which is the oversaturation you are seeing. I confirmed it by reading a texel back through the GPU: a stored byte of 152 arrives as 0.596 (raw/255) instead of the decoded 0.314.

MTKTextureLoader.Option.SRGB has no effect on this path. Passing true, false, or omitting it all leave the result linear. The same option is honored when you load the image from a URL or data, where .SRGB = true produces an rgba8Unorm_srgb texture. So the option is respected for URL and data sources and ignored when the source is an MDLTexture.

Your usdz is authored correctly: the PNG is tagged sRGB, and the UsdUVTexture does not set sourceColorSpace, which defaults to auto and resolves to sRGB for this image. That matches why Reality Composer Pro, the Xcode viewer, and Preview all render it correctly, so this is not a content issue on your side.

Two workarounds, both of which I verified produce the correct decoded color on 26.5:

  1. Reinterpret the loaded texture with an sRGB pixel format. Load it with pixel-format-view usage, then make an sRGB view:
let usage: MTLTextureUsage = [.shaderRead, .pixelFormatView]
let texture = try loader.newTexture(
    texture: mdlTexture,
    options: [.textureUsage: NSNumber(value: usage.rawValue)])
let srgbTexture = texture.makeTextureView(pixelFormat: .rgba8Unorm_srgb)

Use srgbTexture for rendering. Reading a texel back through this view returns the correct sRGB-to-linear value.

  1. Bypass MDLTexture and load the image bytes directly, where the SRGB option is honored:
let texture = try loader.newTexture(URL: imageURL, options: [.SRGB: true])

The workaround operates on the loaded MTLTexture, so it applies regardless of whether the source image is PNG or JPEG.

Since the SRGB option is ignored specifically on the MDLTexture path, that looks like a defect rather than intended behavior, and it is worth a Feedback report. For the report, attach this usdz and note that the resulting MTLTexture is rgba8Unorm regardless of the SRGB option, while the URL path honors it. That gives the team a precise, self-contained repro.

Please let me know if there are any questions.

Facing the same issue. MTKTextureLoader.Option.SRGB option does not work and the loaded texture object always has _isLinearTexture=false (in my case the textures are in jpg format).

Hi,

FWIW I can't reproduce the issue with Reality Composer Pro or the Xcode viewer.

It's unclear what's going with the transfer function (gamma vs linear) and/or jpeg support without more details.

Since this may be a defect in ModelIO or Metal, please file a bug report with the Feedback Assistant. To help expedite attach a focused sample project that demonstrates this issue.

Also of note, jpeg format doesn't support an alpha channel.

Thanks for looking into it. I will file a feedback as you requested.

Regarding the xcode viewer and reality compose they display the texture just fine. Even macOS preview does.

It‘s just in a custom app using model IO (about as basic as possible) the texture colors are saturated.

Thanks again.

Just to update, this is still an issue in macOS 27 Beta. To be clear checking in reality compose or macOS preview doesn't help. You need an Xcode project that specifically calls MTKTextureLoader.newTexure() and the texture is loaded as if it is linear no matter what.

Thanks for the follow-ups, and for confirming it persists on the macOS 27 beta.

I reproduced this on shipping macOS 26.5 using your lowpoly-house.usdz, so it is not specific to the 27 beta. Loading the base-color texture through MDLAsset.loadTextures() and then MTKTextureLoader.newTexture(texture:options:) produces a texture with a linear pixel format (rgba8Unorm). The shader then receives the sRGB-encoded bytes without the sRGB-to-linear decode, which is the oversaturation you are seeing. I confirmed it by reading a texel back through the GPU: a stored byte of 152 arrives as 0.596 (raw/255) instead of the decoded 0.314.

MTKTextureLoader.Option.SRGB has no effect on this path. Passing true, false, or omitting it all leave the result linear. The same option is honored when you load the image from a URL or data, where .SRGB = true produces an rgba8Unorm_srgb texture. So the option is respected for URL and data sources and ignored when the source is an MDLTexture.

Your usdz is authored correctly: the PNG is tagged sRGB, and the UsdUVTexture does not set sourceColorSpace, which defaults to auto and resolves to sRGB for this image. That matches why Reality Composer Pro, the Xcode viewer, and Preview all render it correctly, so this is not a content issue on your side.

Two workarounds, both of which I verified produce the correct decoded color on 26.5:

  1. Reinterpret the loaded texture with an sRGB pixel format. Load it with pixel-format-view usage, then make an sRGB view:
let usage: MTLTextureUsage = [.shaderRead, .pixelFormatView]
let texture = try loader.newTexture(
    texture: mdlTexture,
    options: [.textureUsage: NSNumber(value: usage.rawValue)])
let srgbTexture = texture.makeTextureView(pixelFormat: .rgba8Unorm_srgb)

Use srgbTexture for rendering. Reading a texel back through this view returns the correct sRGB-to-linear value.

  1. Bypass MDLTexture and load the image bytes directly, where the SRGB option is honored:
let texture = try loader.newTexture(URL: imageURL, options: [.SRGB: true])

The workaround operates on the loaded MTLTexture, so it applies regardless of whether the source image is PNG or JPEG.

Since the SRGB option is ignored specifically on the MDLTexture path, that looks like a defect rather than intended behavior, and it is worth a Feedback report. For the report, attach this usdz and note that the resulting MTLTexture is rgba8Unorm regardless of the SRGB option, while the URL path honors it. That gives the team a precise, self-contained repro.

Please let me know if there are any questions.

Thanks for taking the time to confirm. I have filed a new feedback.

MDLAsset loads texture in usdz file loaded with wrong colorspace
 
 
Q