Framebuffer Gamma - OpenGL vs. Vulkan

Hello!

I’ve been able to integrate the Vulkan SDK in an application where I already have OpenGL working. I’ve managed to get all GLSL sources conformed for both platforms, so maintaining shaders for both platforms is really easy.

Renders are almost identical between both APIs, but I’m experiencing a significant gamma difference between OpenGL output and Vulkan output; running Intel’s latest Mesa driver on Arch Linux (MacBook Air Haswell).

This is a draw test using a shader outputting 20% grey directly:
[ATTACH]112[/ATTACH]

The expected color for comparison:
[ATTACH=CONFIG]113[/ATTACH]

OpenGL’s output is much closer to the expected without any special pipeline configuration.

The odd thing, if I run the Vulkan fragment output through a 2.2 gamma-curve, this gives the expected result.

return vec4(pow(color.rgb, vec3(2.2)), color.a)

I’d like to know if there’s a way to match OpenGL’s gamma output outside the shader. If this isn’t possible with Vulkan’s pipeline, I’ll integrate a gamma ramp-texture to perform the correction, sparing the shader the pow() expense.

It’s also possible i’m missing a detail in setting up my pipeline / framebuffer attachments.

Here are those attachments that somehow got unattached:

Bad:
[ATTACH]114[/ATTACH]

Expected:
[ATTACH=CONFIG]115[/ATTACH]

I’m beginning to understand the issue better now…

The ICD is giving me an sRGB swapchain surface (which is where the 2.2 gamma definition really comes from as I understand it).

I’m not drawing into the swapchain directly, rather a UNORM image, which gets blitted to the active swapchain buffer. Just need to make sure the conversion takes place during the blit. The standard seems to indicate this conversion should happen with vkCmdBlitImage, but I must be overlooking a detail myself, or the Mesa Haswell driver hasn’t implemented this detail yet.

Um, they can’t actually do that. That is, you are the one who provides the format when you create the swapchain.

However, that’s a good guess, since what I suspect is happening is similar.

When you call vkGetPhysicalDeviceSurfaceFormatsKHR to query the formats available for your swapchain, each format is paired with a colorspace bitfield. Now, if the format is sRGB, the colorspace will almost certainly also be sRGB.

However, it is possible that the format is non-sRGB, but the colorspace is VK_COLOR_SPACE_SRGB_NONLINEAR. What this means is that the display will display the data as if it is in the sRGB colorspace, but your rendering/copying operations will not convert the pixels to sRGB if the source is linear. In such a system, the display itself is sRGB, and it will assume any pixel data shoved at it is in that color space.

It sounds like you have encountered such a setup.

The best thing to do here is this. First, look through the available surface formats for one that doesn’t have the VK_COLOR_SPACE_SRGB_NONLINEAR field set. If those exist, pick an appropriate one from that list. If none exist (and thus the display will use sRGB, whether the format says so is or not), then look through the surface formats again. Now, you’re looking for a Vulkan image format which is in the sRGB colorspace. Pick that format for your swapchain.

That way, the images you acquire will have the appropriate colorspace.

One way to check this quickly with OpenGL is to query the internal format of the default framebuffer’s back-buffer. If it’s an sRGB format, then you’re probably on a system where everything is sRGB.

Keep this last thing in mind: none of this precludes the possibility of a driver bug. We are talking about a Mesa driver.

Ah!

So the device surface format is my discrepancy then. The weird thing with my Mesa install, is there are no sRGB-capable visual options according to glxinfo. Despite this, there is exactly one surface format returned by vkGetPhysicalDeviceSurfaceFormatsKHR

I did some digging into Mesa and found this telling piece of code:


static const VkSurfaceFormatKHR formats[] = {
   { .format = VK_FORMAT_B8G8R8A8_SRGB, },
};

static VkResult
x11_surface_get_formats(VkIcdSurfaceBase *surface,
                        struct anv_physical_device *device,
                        uint32_t *pSurfaceFormatCount,
                        VkSurfaceFormatKHR *pSurfaceFormats)
{
   if (pSurfaceFormats == NULL) {
      *pSurfaceFormatCount = ARRAY_SIZE(formats);
      return VK_SUCCESS;
   }

   assert(*pSurfaceFormatCount >= ARRAY_SIZE(formats));
   typed_memcpy(pSurfaceFormats, formats, *pSurfaceFormatCount);
   *pSurfaceFormatCount = ARRAY_SIZE(formats);

   return VK_SUCCESS;
}

No wonder I don’t have a non-sRGB option :doh:
I’ll probably end up adopting sRGB-conversion into my filter passes.

Otherwise thank you for helping me understand that non-assumed colorspace detail for blitting

@AR: I would note, VK_COLOR_SPACE_SRGB_NONLINEAR is the only option in VkColorSpaceKHR so there is no point looking for other.
Also it’s not a field (and even if it was that one enum value is =0).

I would note, VK_COLOR_SPACE_SRGB_NONLINEAR is the only option in VkColorSpaceKHR so there is no point looking for other.

I thought that was a flags field. So the display will always be sRGB? Devices can’t report anything else?

That’s the thing: you shouldn’t need one. If your implementation supports only one swapchain format, and that format is an sRGB image format, then the swapchain images should be using that format. As such, your blit should do the conversion for you. So either there’s a bug in the implementation of blit, or you were not passing the right format to vkCreateSwapchainKHR.

Ah cool! I got it working without shader hacks; just by setting vkCreateSwapchainKHR format like you said.

However it’s not the cleanest of solutions because the swapchain validation layer complains that my specified format is unsupported:

Code 14 : vkCreateSwapchainKHR() called with a non-supported pCreateInfo->imageFormat (i.e. 44).

Just by not loading that validation layer, my program works as expected.

Also, that table in the Mesa driver appears to be a surface format whitelist. I added VK_FORMAT_B8G8R8A8_UNORM to that table within Mesa and installed on my system. To my delight, my machine didn’t explode! Instead, it just worked (even with the validation layer in place), so I wonder if the Mesa devs plan to add more formats to that?

[QUOTE=jackoalan;40432]Ah cool! I got it working without shader hacks; just by setting vkCreateSwapchainKHR format like you said.

However it’s not the cleanest of solutions because the swapchain validation layer complains that my specified format is unsupported:[/quote]

If the format you passed was returned by vkGetPhysicalDeviceSurfaceFormatsKHR, then the validation layer has a bug in it. And if you’re not providing a format returned by that, then your code has a bug in it.

There’s no real reason to support other formats, since Vulkan apparently requires implementations to use sRGB displays. In any case, you should not assume that it “just worked” simply because nothing bad appeared to happen. After all, BGRA_UNORM is bit-for-bit identical to the equivalent sRGB version.

That’s the missing link! I’m now using a copy rather than a blit, even with all color attachments as BGRA_UNORM, and sure enough, it’s exactly what should be shown. I’ll be sure to verify on other platforms as the drivers become more compatible with older HW.