#[non_exhaustive]
#[repr(i32)]
pub enum ColorSpace {
Show 16 variants SrgbNonLinear = 0, DisplayP3NonLinear = 1_000_104_001, ExtendedSrgbLinear = 1_000_104_002, ExtendedSrgbNonLinear = 1_000_104_014, DisplayP3Linear = 1_000_104_003, DciP3NonLinear = 1_000_104_004, Bt709Linear = 1_000_104_005, Bt709NonLinear = 1_000_104_006, Bt2020Linear = 1_000_104_007, Hdr10St2084 = 1_000_104_008, DolbyVision = 1_000_104_009, Hdr10Hlg = 1_000_104_010, AdobeRgbLinear = 1_000_104_011, AdobeRgbNonLinear = 1_000_104_012, PassThrough = 1_000_104_013, DisplayNative = 1_000_213_000,
}
Expand description

How the presentation engine should interpret the data.

A quick lesson about color spaces

What is a color space?

Each pixel of a monitor is made of three components: one red, one green, and one blue. In the past, computers would simply send to the monitor the intensity of each of the three components.

This proved to be problematic, because depending on the brand of the monitor the colors would not exactly be the same. For example on some monitors, a value of [1.0, 0.0, 0.0] would be a bit more orange than on others.

In order to standardize this, there exist what are called color spaces: sRGB, AdobeRGB, DCI-P3, scRGB, etc. When you manipulate RGB values in a specific color space, these values have a precise absolute meaning in terms of color, that is the same across all systems and monitors.

Note: Color spaces are orthogonal to concept of RGB. RGB only indicates what is the representation of the data, but not how it is interpreted. You can think of this a bit like text encoding. An RGB value is a like a byte, in other words it is the medium by which values are communicated, and a color space is like a text encoding (eg. UTF-8), in other words it is the way the value should be interpreted.

The most commonly used color space today is sRGB. Most monitors today use this color space, and most images files are encoded in this color space.

Pixel formats and linear vs non-linear

In Vulkan all images have a specific format in which the data is stored. The data of an image consists of pixels in RGB but contains no information about the color space (or lack thereof) of these pixels. You are free to store them in whatever color space you want.

But one big practical problem with color spaces is that they are sometimes not linear, and in particular the popular sRGB color space is not linear. In a non-linear color space, a value of [0.6, 0.6, 0.6] for example is not twice as bright as a value of [0.3, 0.3, 0.3]. This is problematic, because operations such as taking the average of two colors or calculating the lighting of a texture with a dot product are mathematically incorrect and will produce incorrect colors.

Note: If the texture format has an alpha component, it is not affected by the color space and always behaves linearly.

In order to solve this Vulkan also provides image formats with the Srgb suffix, which are expected to contain RGB data in the sRGB color space. When you sample an image with such a format from a shader, the implementation will automatically turn the pixel values into a linear color space that is suitable for linear operations (such as additions or multiplications). When you write to a framebuffer attachment with such a format, the implementation will automatically perform the opposite conversion. These conversions are most of the time performed by the hardware and incur no additional cost.

Color space of the swapchain

The color space that you specify when you create a swapchain is how the implementation will interpret the raw data inside of the image.

Note: The implementation can choose to send the data in the swapchain image directly to the monitor, but it can also choose to write it in an intermediary buffer that is then read by the operating system or windowing system. Therefore the color space that the implementation supports is not necessarily the same as the one supported by the monitor.

It is your job to ensure that the data in the swapchain image is in the color space that is specified here, otherwise colors will be incorrect. The implementation will never perform any additional automatic conversion after the colors have been written to the swapchain image.

How do I handle this correctly?

The easiest way to handle color spaces in a cross-platform program is:

  • Always request the SrgbNonLinear color space when creating the swapchain.
  • Make sure that all your image files use the sRGB color space, and load them in images whose format has the Srgb suffix. Only use non-sRGB image formats for intermediary computations or to store non-color data.
  • Swapchain images should have a format with the Srgb suffix.

Note: Lots of developers are confused by color spaces. You can sometimes find articles talking about gamma correction and suggestion to put your colors to the power 2.2 for example. These are all hacks and you should use the sRGB pixel formats instead.

If you follow these three rules, then everything should render the same way on all platforms.

Additionally you can try detect whether the implementation supports any additional color space and perform a manual conversion to that color space from inside your shader.

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

SrgbNonLinear = 0

§

DisplayP3NonLinear = 1_000_104_001

§

ExtendedSrgbLinear = 1_000_104_002

§

ExtendedSrgbNonLinear = 1_000_104_014

§

DisplayP3Linear = 1_000_104_003

§

DciP3NonLinear = 1_000_104_004

§

Bt709Linear = 1_000_104_005

§

Bt709NonLinear = 1_000_104_006

§

Bt2020Linear = 1_000_104_007

§

Hdr10St2084 = 1_000_104_008

§

DolbyVision = 1_000_104_009

§

Hdr10Hlg = 1_000_104_010

§

AdobeRgbLinear = 1_000_104_011

§

AdobeRgbNonLinear = 1_000_104_012

§

PassThrough = 1_000_104_013

§

DisplayNative = 1_000_213_000

Trait Implementations§

source§

impl Clone for ColorSpace

source§

fn clone(&self) -> ColorSpace

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ColorSpace

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<ColorSpace> for ColorSpaceKHR

source§

fn from(val: ColorSpace) -> Self

Converts to this type from the input type.
source§

impl Hash for ColorSpace

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq for ColorSpace

source§

fn eq(&self, other: &ColorSpace) -> bool

This method tests for self and other values to be equal, and is used by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl TryFrom<ColorSpaceKHR> for ColorSpace

§

type Error = ()

The type returned in the event of a conversion error.
source§

fn try_from(val: ColorSpaceKHR) -> Result<Self, Self::Error>

Performs the conversion.
source§

impl Copy for ColorSpace

source§

impl Eq for ColorSpace

source§

impl StructuralEq for ColorSpace

source§

impl StructuralPartialEq for ColorSpace

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> ToOwned for Twhere T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.