What imageFormat should I use when creating a swapchain?

When creating my swapchain, using vkCreateSwapchainKHR (actually I am using the C++ interface, but it seems that all communication is done using the C API) I pass a pointer to a VkSwapchainCreateInfoKHR that has a member imageFormat described by the specs as “imageFormat is a VkFormat value specifying the format the swapchain image(s) will be created with.”

There are over 100 formats to choose from.

Looking at tutorials and examples.

  1. http://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Swap_chain#page_Surface-format

“Because of that we should also use an SRGB color format, of which one of the most common ones is VK_FORMAT_B8G8R8A8_SRGB.”

with code

for (const auto& availableFormat : availableFormats) {
    if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
        return availableFormat;
    }
}

In other words, they prefer VK_FORMAT_B8G8R8A8_SRGB.

  1. http://www.intel.com/content/www/us/en/developer/articles/training/practical-approach-to-vulkan-part-1.html

This page does not talk about swapchain formats, but their code can be found here:

with code:

vk::SurfaceFormatKHR VulkanCommon::GetSwapChainFormat( std::vector<vk::SurfaceFormatKHR> const & surface_formats ) const {                                                               
  // If the list contains only one entry with undefined format                                                                                                                           
  // it means that there are no preferred surface formats and any can be chosen                                                                                                          
  if( (surface_formats.size() == 1) &&                                                                                                                                                   
      (surface_formats[0].format == vk::Format::eUndefined) ) {                                                                                                                          
    return { vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear };                                                                                                            
  }                                                                                                                                                                                      
                                                                                                                                                                                         
  // Check if list contains most widely used R8 G8 B8 A8 format                                                                                                                          
  // with nonlinear color space                                                                                                                                                          
  for( auto & surface_format : surface_formats ) {                                                                                                                                       
    if( surface_format.format == vk::Format::eR8G8B8A8Unorm ) {                                                                                                                          
      return surface_format;                                                                                                                                                             
    }                                                                                                                                                                                    
  }                                                                                                                                                                                      
                                                                                                                                                                                         
  // Return the first format from the list                                                                                                                                               
  return surface_formats[0];                                                                                                                                                             
}                                                                                                                                                                                        

So clearly they prefer vk::Format::eR8G8B8A8Unorm, aka VK_FORMAT_R8G8B8A8_UNORM.

  1. vkguidedev which uses vkBootstrap (http://github.com/charles-lunarg/vk-bootstrap/blob/master/README.md)

Uses the code

    void SwapchainBuilder::add_desired_formats(std::vector<VkSurfaceFormatKHR>& formats) const {
            formats.push_back({ VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
            formats.push_back({ VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
    }

So, they prefer VK_FORMAT_R8G8B8A8_SRGB (assuming that doesn’t swap Red and Blue ;).

  1. http://github.com/khronosGroup/Vulkan-samples

Has a C++ example,

with the code:

// There is no preferred format, so pick a default one|
format        = formats[0];|
format.format = VK_FORMAT_B8G8R8A8_UNORM;|
[...]
for (auto &candidate : formats)
{
  switch (candidate.format)
  {
    case VK_FORMAT_R8G8B8A8_UNORM:
    case VK_FORMAT_B8G8R8A8_UNORM:
    case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
      format = candidate;
      break;
    default:
      break;

In other words, they prefer VK_FORMAT_R8G8B8A8_UNORM.

I know what the difference is between SRGB and UNORM, and I read

but the answer wasn’t very clear to me.

Even ignoring that there are close to 200 formats to choose from, apparently even tutorials and examples have a hard time to choose between srgb and unorm for the swapchain they create.

I am willing to assume that my images are encoded using srgb and that my surface (I am using XCB) uses srgb, but I don’t want a conversion, ever, between R8G8B8A8_SRGB and R8G8B8A8_UNORM for obvious reasons (that would throw away information and you’d get the worst of both worlds). Nevertheless, I am assuming that an image has to be converted to something linear at some point in order to do math, like blending.

I also discussed this on discord and it was said that the linear form in this case uses 32bit floats per channel; is this correct? That would be 128bits per texel, four times the size of an image as handled in the host as far as I can see. Are images processed at that size internally on hardware?

I am totally confused on what is ACTUALLY going on, how to get both speed and no loss of data; and why there is a difference in what examples/tutorials use even.

If I add to this the general case - what code should I write to pick the correct format for my swapchain from the 100+ existing ones? Of course, most will not even be supported by my hardware; but as soon as SOME are I need a strategy to make a good pick.

Hopefully someone can explain in detail what is going on and therefore how to pick the right format.

PS I spent 1.5 hours typing in this question, then hit Enter and get: “Sorry you can’t use links in your post.” I now removed all h…p / / prefixes. Hopefully at least it will let me post this.

PS Still not allowed… so I removed all links. THIS SUCKS.

What do you mean “a hard time”? They pick different answers. There isn’t some deep, moral right and wrong here. You pick the one that suits your needs, whatever those may be.

So… what are your needs?

Personally, I see no reason to use a non-sRGB image format if the colorSpace field is sRGB. But it’s ultimately up to you and what result you want and how much sRGB math you want to do in your fragment shader that’s generating this data.

You should also be aware that you shouldn’t use tutorials to form opinions on what is good programming practice. Use them to learn how a thing works, not how best to use it.

Blending happens by linearizing the pixel being read, then doing the math, then writing it. Technically, blending is allowed to happen in sRGB space, but I don’t think any hardware actually does that.

Also, note that linearizing the data does not require limiting the result to 8-bits-per-component. It’s not writing the linear value anywhere; it’s just part of how it reads the source pixel.

You’re using Vulkan. If the display engine says that it allows you to create a 128-bit-per-pixel swapchain image, it would only do so if it actually supports displaying such a thing. This would be used for HDR displays and the like.

So… what are your needs?

To do the Right Thing™ in my case.
The only conclusion that I can draw from different tutorials using different default formats is that either it really doesn’t matter, or half of them are making a mistake; since they all just draw a triangle.

What I want is:

  1. Seeing the correct colors.
  2. no loss of data due to conversions as a result of me choosing the wrong format.
  3. No unnecessary loss of speed (high FPS) because of the use of the wrong format.

There is nothing else I can tell you, that is why I am asking this question here.
If the correct answer (assuming the answer isn’t “it doesn’t matter” (aka, everything will be the same speed, look the same and will not result in loss of data) depends on something that I didn’t tell you then please ask me for this information.

Even better, and that is what I really asked I think; explain what is exactly going on, how things work etc, so I can draw my own conclusion based on extra information for my case, and others can make their choice based on their case.

Blending happens by linearizing the pixel being read, then doing the math, then writing it.

So the 32bit float (per color channel, so four times such a float per pixel) is used, but only for a single pixel at a time?

If the display engine says that it allows you to create a 128-bit-per-pixel swapchain image

No, that would be the internal format used after linearizing srgb data (as I was told on discord). If that is NOT for a single pixel at a time, but for the whole image when being internally processed then that internal image would use four times as much memory as the original that was read in.

That’s spam prevent for new user accounts (you’d be surprised at the number of spammer accounts that get killed per week on these forums). Keep using the forums and you’ll be able to post links soon. In the meantime, I fixed-up your links for you for the convenience of others viewing your post.

1 Like

Thank you! PS I love your name “Dark Photon”… pretty deep :).

How do you define “correct” colors?

That’s between your output colors and your colorspace and format choices.

As I said, this is Vulkan. You’re asking what formats the hardware allows for display. If a format wouldn’t be displayable by the hardware, it wouldn’t be on the list of allowed formats.

And “unnecessary” is a matter of perspective. If you ask for a 128-bit format, you’re probably going to get some performance loss compared to a 32-bit format. But you’re also going to be able to interface with an HDR display and use more of its colorspace.

Exactly how much bitdepth the blending system has is up to the GPU. But it has to be able to do blending with floating-point components.

“Internal format” of what? Either the swapchain image is 128-bits per pixel or it isn’t. There’s nothing “internal” about Vulkan; it’s all pretty explicit.