Some HDR Questions and Problems

For about the last 3 years, I’ve processed most photos for HDR display in darktable, and would love to do so in vkdt. On top of the speed and colour improvements, vkdt can actually display in HDR on macOS! Whereas with darktable I edited using the distorted SDR preview and regular exports. However I haven’t seriously used vkdt for HDR yet, for a few reasons. And sorry, on a technical level I am rather out of my depth. Hopefully this remains comprehensible but I wouldn’t say I really understand HDR or much about colour spaces.

While vkdt displays in HDR, there are some complexities. The clipping of the image vkdt displays varies with display brightness. But even in the (fixed brightness) P3-ST 2084 reference mode, a linear exported EXR viewed in macOS Preview has different overall brightness and clips different parts of the image than the vkdt preview. I assume these are due to differences in… ‘peak white’? or other HDR metadata? Though also maybe related to linear? In darktable I used HLG, and the clipping in the final image matched the waveform and clipping indicators (at least after setting the softproof and display profiles), and didn’t change with display brightness.

My understanding is vkdt always renders and (once past the colour module at least?) processes in linear, D65, BT.2020? But I’m not sure how colour spaces are managed through the node graph. For instance tone-mapping modules seem to only map to an SDR transfer curve. And something like colenc seems only appropriate for output? In darktable I still typically use a tone-mapper for HDR, usually filmic rgb, which seems to adapt to the output colour profile.

Also with clipping indicators and waveforms, while the check module allows you to specify a colour gamut its exposure checking seems limited to SDR. And the hist waveform also seems to be limited to SDR.

And of practical importance, output formats. I don’t think any support HLG or PQ. You can ‘use’ them with EXR (or say, PFM I suppose), but I don’t think EXR really supports transfer curves and is meant to always assume linear. And besides EXR files are always massive. I can convert EXR to something else, but this seems to make colour spaces and clipping even more difficult, as well as just being inconvenient. So consider this another use case for JPEG-XL or AVIF output (JXL being my personal favourite).

Sorry if that was messy, but I’ve been struggling to write something on the topic for a while now. I would love to know of any solutions or explanations, hopefully it described some issues well enough. And of course, thank you for vkdt!

I wanted to include an HDR photo at the end here, but perhaps going with the general theme the forums encountered an error on upload :wink:

1 Like

heya,

my hdr monitor just broke so i can’t look at hdr images right now :frowning:
but:

there’s some information on the display colour management howto website.

key info:

try to run vkdt from the console like:

$ vkdt -d qvk
...
[qvk] using A2R10G10B10_UNORM_PACK32 and colour space HDR10 ST2084
...

and look for a line like the above^. that means your display is capable of 10 bit rendering and the system supports rendering to HDR10 frame buffers. vkdt will use this, unless explicitly forbidden.

now unfortunately since windows makes a mess of this by default, vkdt by default disables hdr mode in the config, so you’ll need to enable it. make sure ~/.config/vkdt/config.rc contains this line:

intgui/allowhdr:1

to control the amount of hdr headroom, there is this setting:

fltgui/hdr_avg_nits:64

increase the value to leave more hdr headroom (i.e. render more values > 1.0 without clipping). note that this will dim the ui as compared to the luminance peaks in the image.

you can disable the filmcurv module to simply let the highlights blow out of 1.0 (i think it’s awesome to see this btw… with enough dynamic range you don’t need tonemapping). the OpenDRT module might give you more leeway towards hdr mastering. filmsim always renders to SDR.

it’s thinkable to use tonemapping and then an exposure module after it to blow up the range again.

colenc you don’t need. this is just to convert for output/when you export to a particular file. this is mostly useful for video. and yes, in exr vkdt stores whatever you want and includes tags about trc, too. unfortunately you’re right about the linear assumption, the trc tag is not conforming to standard.

Can you output to PNG? That can have a CICP block in the header that flags it as HLG or PQ, full range or narrow range.

1 Like

NB that PNG needs to be 16b for HDR, and uses lossless (deflate) compression, so probably not much smaller/different to EXR w/ PIZ (hopefully 16b half float is used instead of 32b?)…

maybe i should comment on that too:

the input is whatever it is, and it tags the image as such (primaries, trc). mpeg or jpeg range is corrected on input, numbers are always full range from there.

the colour module takes pretty much anything and does the input device transform. it always outputs bt2020/linear (if correctly configured, you can of course force wrong parameters).

colenc does the reverse, prepares linear bt2020 for output encoding of choice (including limited range).

display colour management is separate from that, i’d like to say wayland does it and i just pass the abovementioned hdr10 buffer. i tried on macintosh once and that seemed to “just work” out of the box too.

all that was about displaying hdr signals on your hdr monitor. fair point about actually encoding this into something useful on disk such as jxl. support for this output format should be a useful addition.

4 Likes

Oh no! My condolences for your HDR monitor :frowning:

I had indeed already included intgui/allowhdr:1, and vkdt does display in HDR for me. I’d also read the colour management docs, though I think a lot went over my head. I am on macOS though, where I get the impression Quartz Compositor works similarly to Wayland. I’d tried using fltgui/hdr_avg_nits: but I am still unsure of what exactly it represents. Is it the peak luminance? Or nits above SDR white? Or something else? A 1000 nit default works well enough for me (the MacBook Pro screens can hit 1600 nits, but only 1000 nits sustained full-screen). For reference, the output of vkdt -d qvk:

% /Applications/vkdt.app/Contents/MacOS/vkdt -d qvk
[gui] vkdt 1.0.99-508-g1e430ae5 (c) 2020--2026 johannes hanika
[gui] glfwGetVersionString() : 3.5.1 Cocoa NSGL Null EGL OSMesa monotonic dynamic
objc[82117]: Class MVKBlockObserver is implemented in both /Applications/vkdt.app/Contents/Frameworks/libMoltenVK.dylib (0x113f1dfd8) and /opt/homebrew/Cellar/molten-vk/1.4.2/lib/libMoltenVK.dylib (0x114949fd8). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
[gui] vk extension required by GLFW:
[gui]   VK_KHR_surface
[gui]   VK_EXT_metal_surface
[qvk] dev 0: vendorid 0x106b
[qvk] dev 0: Apple M3 Pro
[qvk] max number of allocations 1073741824
[qvk] max image allocation size 16384 x 16384
[qvk] max uniform buffer range 4294967295
[qvk] dev 1: vendorid 0x106b
[qvk] dev 1: Apple M3 Pro
[qvk] max number of allocations 1073741824
[qvk] max image allocation size 16384 x 16384
[qvk] max uniform buffer range 4294967295
[qvk] num queue families: 4
[qvk] picked device 0 without ray tracing and without float atomics and without coopmat support
[qvk] queue 0 is idx 0 family 0
[qvk] using A2R10G10B10_UNORM_PACK32 and colour space HDR10 ST2084
[qvk] vulkan present mode: FIFO (vsync)
[gui] no gamepad found
[gui] [menu] loaded 2 entries for 'lighttable'
[gui] [menu] loaded 70 entries for 'darkroom'
[gui] [menu] loaded 2 entries for 'nodes'
[gui] [menu] loaded 0 entries for 'files'
[qvk] using A2R10G10B10_UNORM_PACK32 and colour space HDR10 ST2084
[qvk] vulkan present mode: FIFO (vsync)

And the Metal HUD showing the pixel format:


To illustrate the differences I was referencing with the vkdt preview vs an exported EXR, here are some images (sorry for all the moiré) —


This is in the display’s P3-ST 2084 reference mode (with fixed brightness, SDR: 100nits, HDR: 1000nits). They’re very close, though in the top-right, on the log in the background, the EXR clips before the vkdt preview.

I have also discovered that macOS does use the ENABLE_HDR_WSI=1 metadata. Here is the same scene, without ENABLE_HDR_WSI=1, in the default display profile (variable brightness, SDR: 600nits, HDR: 1600nits, Brightness Control (?) : 2–600nits) at 100% and 37.5% —


and here with ENABLE_HDR_WSI=1


Which, uhhh, is curious. Though it doesn’t seem to affect the reference mode —

Which still shows the difference in clipping. Given that metadata is being used to tune the display of HDR, it might be relevant that the EXR doesn’t have a whiteLuminance attribute

% exrinfo -a -v DSC00827.exr
File 'DSC00827.exr': ver 2 flags longnames
 parts: 1
 part 1: <single>
  channels: chlist 3 channels
   'B': half samp 1 1
   'G': half samp 1 1
   'R': half samp 1 1
  compression: compression 'none' (0x00)
  dataWindow: box2i [ 0, 0 - 5297 7945 ] 5298 x 7946
  displayWindow: box2i [ 0, 0 - 5297 7945 ] 5298 x 7946
  lineOrder: lineOrder 0 (increasing)
  pixelAspectRatio: float 1
  screenWindowCenter: v2f [ 0, 0 ]
  screenWindowWidth: float 1
  chromaticities: chromaticities r[0.708, 0.292] g[0.17, 0.797] b[0.131, 0.046] w[0.3127, 0.329]
  trc: char (size 8 unp size 0 hdlrs 0x0 0x0 0x0)

:person_shrugging:


For the above I did indeed disable filmcurv, and the increased dynamic range does reduce the need somewhat. In darktable I can on a fair few occasions not use a tone-mapper with HDR and the resulting tonality is often very pleasing and more subtle. But I’m still usually in situations when tone-mapping is necessary or useful. And also it’s helpful creatively if you want a different tonality. I had poked around OpenDRT, but don’t think I managed to get HDR output, and regardless I couldn’t really figure it out. But I’ll have another look!

Would it be unwise or unfeasible for the tone-mappers to treat the HDR peak luminance as 1.0? I assume that has problems I don’t understand, but from the outside it seems sort of what darktable is currently doing. And analogous to adding another exposure module afterward (which I hadn’t thought of doing, thank you!)


On using PNG I don’t think vkdt currently support it as an output format. Or at least, it’s not there for me? It would be interesting to see if its presentation matches the EXR though. cjxl accepts EXR files, though in the case of the vkdt exports at least, I always get some manner of weird clipping.


I think I might have used the wrong term with ‘management’ as I was more referring to the internal vkdt pipeline instead of display colour management. I do still feel some lingering uncertainty, but I’m struggling to articulate it right now and this post is long enough! Thanks for the explanation :slight_smile:

…just a quick question: the exr you exported from vkdt and it appears clipped at 1.0? if you load it back into vkdt is hat clipped too? or is this other viewer maybe clipping?

the avg nits value is essentially the multiplier that is applied to the data when encoding it in the pq frame buffer. depending on the compositor the exact meaning of it seems to vary a bit.

Ahh in vkdt, the exr does appear the same as the darkroom preview. So presumably there isn’t an encoding error, say. I guess I’m trying to figure out where the difference arises, if it’s due to different metadata, or incorrect communication with the compositor. The app I was viewing the exr in is ‘Preview’, the default macOS image and document viewer. Using a different program ‘tev’ I get yet another presentation, this time brighter and more clipped again. In general I’m quite trusting of the presentation provided by Preview. But I don’t know if there even is a standard way to present linear data? (Or even PQ or HLG data?) Or if certain metadata is needed for equal presentation.

I don’t think the clipping is at literal 1.0 values in the file if that’s what you mean. The photos I posted are quite underexposed but the display was very bright, and tev shows values for all subpixels in the bright parts of the exr > 10.0. It even says the maximum value in the image is 27.453.

Is the average nits value a multiplier of… sdr white? If the default is 1000nits, what average nits value gives that?

possible. if you use a normal 8-bit framebuffer you can’t pass more than 1.0.

i forget where i got the pq/average nits formula from. so maybe in fact average is not a good word. anyways, pq is essentially a glorified gamma curve, mapping [0,1] to itself. there is a scale factor involved, that is 10,000 in the spec. vkdt applies this scale factor only when rendering for display, i.e. the scene referred linear value is divided by the scale factor (10,000 or config average nits), and then the pq oetf (inverse) is applied to go from linear to quantised. that’s then stored in the 10-bit per channel frame buffer.

there is a vulkan extension that allows us to annotate the hdr data with some extra metadata about intended peak luminance etc, how the data was mastered. possible that macos would respect that, i haven’t seen compositors make use of this (i’m not setting it in case the extension is not supported). fwiw vkdt attempts to use vkSetHdrMetadataEXT to set max luminance to 1000, min luminance to 0, maxContentLightLevel to 1000 and maxFrameAverageLightLevel = 128 (called in gui.c:599).

it was my understanding that this metadata is supplied so the compositor can do “tonemapping” of image data that has been mastered for a different set of parameters than is present as output device.

in any case the compositor usually undoes the non-linear pq part just fine, and then displays the resulting linear value on the screen with what seems to be some extra scale factor that i’m not sure where it’s coming from. that’s why we have a config option.

1 Like

thinking about this again, i believe this attribute is exactly the same as the average nits vkdt config option (maps linear 1.0 to absolute value in nits). would it make sense to:

  • call the option in vkdt the same, just to have at least two places with the same naming…
  • wire the option somewhere more prominently in the ui settings
  • on exr export, write the current ui settings to the exr file

?

as i said… unfortunately now i can’t test these things for real, but i want that stuff to work, and some of it also affects rendering on SDR screens when using bt2020/pq frame buffers.

It’s a bit more complicated than that - most other TRC curves work like this (relative [0, 1.0] to other relative [0, 1.0] range), but PQ is special in that it as an absolute curve, it always maps to [0, 10000nits], so you have to take care w/ extra scaling what you actually map into that range, i.e. first decide what your max highlight value means in nits (e.g. relative to mid-tones/18% gray one wants to place at some X nits, or SDR diffuse white one targets at 100-200nits typically; incidentally libjxl assumes 255nits for diffuse white as a convenient max value of 8-bit SDR data). This is also why one needs to pass around the extra metadata in the form of maxCLL and/or maxFALL, etc.

right. absolute with arbitrary scale factor :expressionless:

Well, not quite arbitrary, it needs to be chosen (“mastered”) by the content creator. :wink:

fair :slight_smile: let’s do it. not sure storing the global ui choice in the exr is a good idea. maybe the mastering should be a per-project/image file choice.

Either that or have a fixed scale factor but say it’s creator’s responsibility to place/choose their SDR diffuse white at 1.0 (or lower, and then go from 1.0 = 203nits).

fwiw playing around with the exr whiteLuminance tag, the viewers I’m using don’t seem to do anything with it. The presentation remains the same.

An article kmilos linked to elsewhere about OpenEXR has this diagram which I think gets at exr being meant only for scene-referred linear data. Which I guess means it’s not really meant to achieve a consistent final appearance on displays anyway?

Which I guess means it’s not really meant to achieve a consistent final appearance on displays anyway?

Absolutely not - EXR is meant as an interchange (or archival) format for film and CGI production. Support for various metadata in EXR is very ad-hoc (i.e. app or domain specific) and not widespread at all, so it can’t just be used “as-is”.

I don’t think any format today is 100% bulletproof yet when it comes to HDR displays, but best bets are probably HEIC/AVIF (true 10/12b or gain map), UltraHDR JPEGs (gain map), and then perhaps JXL (true 10/12b or gain map) and lastly PNGs. See also Create and edit true HDR (High Dynamic Range) images - Greg Benz Photography

1 Like

Also just to note, I haven’t been able to get the fltgui/hdr_avg_nits: setting to… do anything. Both the image appearance and UI remain the same for me. (Other config settings still work).

Also also, the libjxl people made some useful little tools, including one to make a pq png from an exr. Just in case anyone finds them useful.

1 Like

It would probably be ideal as a per project or file choice. Not sure where the most natural place would be, maybe as a parameter on display modules and the export interface? (That would mean that updating one wouldn’t necessarily update the other though… or if export modules are added after display modules in the node graph, could they pass along that data? That could also be a nice way of say, choosing a gamut to view in display and then passing that info to the export module. Though maybe that overly scopes the display to also be a colenc module). While I can’t find viewers that use the whiteLuminance tag, the aforementioned exr_to_pq uses it in seemingly much the same way vkdt uses hdr_avg_nits. So it would seem sensible to include it in files, after all it has an impact on how the image appears to the editor, and how to reproduce that appearance externally.

Also, ‘hdr_avg_nits’ doesn’t seem a correct name to represent the brightness of (1, 1, 1). Especially as it’s not the same as the Frame Average Light Level, which I think refers to the average luminance of all pixels. Maybe SDR White? Though I think that might also be slightly incorrect. JXL’s ‘Intensity target’ seems to be the same idea too.

More generally than EXR, I guess what I would hope for is that the vkdt darkroom would exactly match a correctly exported image of a suitable format. Or at least are decoded into equivalent signals sent to the compositor by a conformant program. I don’t know if such a format, or suitably comprehensive HDR decoding standard exists though. But I would note that in macOS Preview, the same HLG image exported from darktable appears identical when exported as AVIF or JXL, suggesting some agreement in how formats appear.

p.s.
Reading that explanation of JXL’s intensity target I think has me understanding why the darktable HDR images have been so unproblematic for me. They’re HLG images, and thus have relative brightness, with an intensity target set to 1000 nits, which is treated as their max display luminance. So they are conceptually the same as SDR images (as the values are scaled such that the max is set to the intensity target), just with a more efficient(?) transfer curve and a request that the system gives it a larger brightness range?