So for the purposes of this post I'm going to call pixel format with at least 10 bits per channel and at least Rec. 2020 color space HDR and pixel format with 8 bits per channel and less than Rec. 2020 color space LDR.
So.... pretty much nothing supports HDR. HDR data is extremely hard to work with as a result. But since even trash B-movies use HDR, only supporting LDR is shooting yourself in the foot.
From what I gathered so far, the only HDR usable raster images formats are:
- OpenEXR
- JPEG 2000 (optional)
Usable HDR vector image formats are:
- None
That's horrible. In my own quest I've tried to export frames of that movie to OpenEXR using ffmpeg but ffmpeg doesn't have OpenEXR export. And when trying to export PNG it used incorrect tone mapping which produced wrong colors. Ugh.
Lessons learned:
- LDR is dead. HDR is the only way. LDR is like ASCII, HDR is like Unicode.
- BMP is useless
- PNG is useless
- JPG is useless
- SVG is useless
So this topic is about how to handle colors correctly. Even before this quest I realized that the only correct way to represent colors in code is to use a floating point value for each channel:
- {l Code}: {l Select All Code}
struct Color
{
float red;
float green;
float blue;
float alpha;
};
That gives nice 128 bits per pixel and also HDR calculation for free because values can exceed 1.0 so we can keep a lot of data without rounding errors or truncation. Now, of course, we want HDR color space too and we still want to load legacy formats that use legacy color spaces. So I guess we need a different type for each color space. This can be solved in C++ with templates:
- {l Code}: {l Select All Code}
struct Rec2020 {};
struct sRGB {};
template <typename ColorSpace>
struct Color
{
...
};
Buuut, can we actually easily convert between color spaces? Is there an straightforward algorithm? What about non-RGB color models? What color space to use for a game engine? Do we need to hardcode it or let it be determined at runtime? So many questions...
Now for APIs. It looks like both OpenGL and Vulkan support these 32-bit floating point per channel buffers. However, what about displaying them? It looks like Vulkan got support of that as an extension just 3 months ago. Wait, extension? Really? I presume OpenGL is dead then I guess.
Ok, now - hardware. GPU-wise this is pretty simple. This article says you'll need at least Radeon RX 380 or Nvidia 900 series. Well, we don't care about Nvidia here because they destroyed Nouveau. AMD requires just proprietary firmware so it's not optimal but I'm more tolerant to proprietary firmware than proprietary drivers. The real kicker are HDR monitors. Since we're gamers here I only looked at gaming monitors and found prices to start from around 1800$. Yikes.
Software. I'm not sure if Mesa implements that Vulkan extension. Well, considering it was announced just 3 months ago, I guess not. I guess for now we need a tone mapping fallback in our graphics code. Speaking of tone mapping, I didn't even mention tone mapping. When I open that video file in VLC I get this in codec info:
Max. luminance: 4000 cd/m2
Min. luminance: 0050 cd/m2
Primary R: x=0.7080 y=0.2920
Primary G: x=0.1700 y=0.7970
Primary B: x=0.1310 y=0.0460
White point: x=0.3127 y=0.3290
MaxCLL: 725 cd/m2
MaxFALL: 162 cd/m2
These are runtime parameters so they must be stored somewhere. And I read HDR10+ can now alter luminance range on individual frames. So in order to convert pixel from one format to another you would need to take all of those parameters into account. Damn, it looks like working with color is very hard. That's why I've made this topic. I want to get colors right.