Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement 3DLUT support #37

Closed
haasn opened this issue Jun 28, 2018 · 6 comments
Closed

Implement 3DLUT support #37

haasn opened this issue Jun 28, 2018 · 6 comments

Comments

@haasn
Copy link
Owner

haasn commented Jun 28, 2018

VLC wants this. The main question is: Which 3DLUT formats do we support? My considerations:

  1. I don't want to do string->float parsing in libplacebo, so that rules out the "plaintext" formats
  2. I would prefer a format that has as much metadata as possible describing the input and output space of the 3DLUT, otherwise the user would have to specify this
  3. Preferably, the API in libplacebo itself should just take a pointer to the LUT + metadata in a struct, but we would provide a utils/ file to parse this information from a file

I've seen some samples of 3DLUT formats but none of them really matched my requirements. In particular, what I ideally want is something where the 3DLUT data itself can just be a raw pointer into the file itself. The closest so far is madVR's 3DLT format, but it has metadata as plaintext/string comments, which goes against my "no float parsing" rule.

We could NIH our own format + provide a dedicated command line tool to convert from format X to ours though (e.g. using setlocale + scanf).

@haasn
Copy link
Owner Author

haasn commented Jun 28, 2018

Also, considerations for our own format:

  • What encoding do we use for the input/output space?
  • Do we apply any numerical non-linearity to the signal?
  • How do we handle HDR outputs and, more importantly, inputs?
  • Do we use a consistent reference color space or do we make the primaries variable?

In theory we could use something like a 16-bit scRGB encoding for both input and output. This would simplify the definition and implementation while also supporting wide gamut and HDR signals, although this would have the downside of requiring more data points for the same precision in the in-gamut region.

Either way, if we want to support HDR inputs we'd need to come up with some extension of the [0,1]³ cube concept to extend it to out-of-range values. HDR outputs on the other hand are trivial if we just use floating point values.

@haasn
Copy link
Owner Author

haasn commented Jun 28, 2018

Rather than scRGB we could also use some simple log encoding (perhaps a nonlinearity like HLG OETF) to support out-of-range values, although this way we would still need a variable gamut. Alternatively we could define our own log encoding that has both positive and negative extensions and use this to represent out-of-gamut values as negative inputs.

We could also make the nonlinearity optional, so users could choose to use whether to support HDR or not. I guess based on this I would probably just end up mapping the enum pl_color_transfer and enum pl_color_primaries 1:1 to the header, this way we get good support for pretty much anything we want - and the ability to extend it later; rather than trying to tie users to a single fixed gamut/transfer.

@madshi
Copy link

madshi commented Jun 30, 2018

One argument for using madVR's format is that some calibration applications out there already (kind of) support it. Well, some only support it indirectly, by producing eeColor 3DLUT 65^3 text files (which madVR then can import). But e.g. ArgyllCMS and DisplayCAL support it directly. JFWIW...

I'm handling out-of-range values in that way that I actually use video levels 3DLUTs, so BTB/WTW can be "miused" for slightly out-of-range values. Unfortunately in my experience some calibration applications out there have various kind of problems producing useful 3DLUT data for out-of-range values. It varies from simple clipping to outright data corruption. So I'm not sure how useful this really is.

Not too happy about the lack of non-text metadata myself. But it's too late to change it now. Don't want to break compatability, obviously.

FYI, when the madVR 3DLUT was defined many years ago, we did some testing and found gamma RGB to work best (compared to linear light or YCbCr). Also, we've compared various output formats and decided on int16 as the best bang-for-the-buck format.

@haasn
Copy link
Owner Author

haasn commented Jul 1, 2018

For the record, and so I never have to dig through the internet to find it again, this is what the madVR spec looks like:

   enum {PAGE_SIZE = 16384};
   struct H3DLUT
   {
      char signature[4];         // file signature; must be: '3DLT'
      long fileVersion;          // file format version number (currently "1")
      char programName[32];      // name of the program that created the file
      long long programVersion;  // version number of the program that created the file
      long inputBitDepth[3];     // input bit depth per component (Y,Cb,Cr or R,G,B)
      long inputColorEncoding;   // input color encoding standard
      long outputBitDepth;       // output bit depth for all components (valid values are 8, 16 and 32)
      long outputColorEncoding;  // output color encoding standard
      long parametersFileOffset; // number of bytes between the beginning of the file and array parametersData
      long parametersSize;       // size in bytes of the array parametersData
      long lutFileOffset;        // number of bytes between the beginning of the file and array lutData
      long lutCompressionMethod; // type of compression used if any (0 = none, ...)
      long lutCompressedSize;    // size in bytes of the array lutData inside the file, whether compressed or not
      long lutUncompressedSize;  // true size in bytes of the array lutData when in memory for usage (outside the file)
      // This header is followed by the char array 'parametersData', of length 'parametersSize',
      // and by the array 'lutDataxx', of length 'lutCompressedSize'.
   };
   char  parametersData[1];
   union LUTDATA
   {
      unsigned char  lutData8[1];
      unsigned short lutData16[1];
      float          lutData32[1];
   };
   // The array 'parametersData' starts 'parametersFileOffset' bytes after the beginning of the file.
   // The array 'lutDataxx' starts 'lutFileOffset' bytes after the beginning of the file.
   // When creating a 3DLUT file, 'lutDataxx' should be positioned on a 16384 byte boundary.
   //
   // parametersData - char array with size parametersSize that contains an exact copy of the 
   //                  input file with the commands and settings used for creating the 3DLUT file
   // lutDataxx      - array with size lutSizeUncompressed that contains the 3D LUTs output values.
   //                  The type used depends on the 'outputBitDepth' field:
   //                     - unsigned char,  if outputBitDepth =  8
   //                     - unsigned short, if outputBitDepth = 16
   //                     - float,          if outputBitDepth = 32
   //   The offset inside the array is calculated as:
   //     offset = (cr<<(inputBitDepth[1]+inputBitDepth[0])+cb<<(inputBitDepth[0])+y)*3   // YCbCr input
   //     offset = ( r<<(inputBitDepth[1]+inputBitDepth[0])+ g<<(inputBitDepth[0])+b)*3   // RGB input
   //   The output order inside the array is:
   //     Y = lutDataxx(offset); Cb = lutDataxx(offset+1); Cr = lutDataxx(offset+2)       // YCbCr output
   //     B = lutDataxx(offset); G  = lutDataxx(offset+1); R  = lutDataxx(offset+2)       // RGB output
   //   The 'lutUncompressedSize' of the array is calculated as:
   //     lutDim = 3*(2^inputBitDepth[0]*2^inputBitDepth[1]*2^inputBitDepth[2])
   //     lutUncompressedSize = lutDim*outputBitDepth/8
   // This specification assumes: char = 1 byte; short = 2 byte; float = 4 byte; long = 4 byte; long long = 8 byte

@haasn
Copy link
Owner Author

haasn commented Jul 1, 2018

It doesn't seem like that specification includes the definition of what inputColorEncoding and outputColorEncoding mean, nor the interpretation of the magic comment in parametersData. Is there anything more up-to-date about this format?

I'm handling out-of-range values in that way that I actually use video levels 3DLUTs, so BTB/WTW can be "miused" for slightly out-of-range values.

Good idea, but I don't think it's enough to handle BT.2020 without a change of primaries, and it's not enough to handle BT.2100 at all. So being able to specify which encoding/primaries would be crucial for wide gamut / HDR support, and I'm again reluctant to implement my own float parsing methods for this purpose.

FYI, when the madVR 3DLUT was defined many years ago, we did some testing and found gamma RGB to work best (compared to linear light or YCbCr). Also, we've compared various output formats and decided on int16 as the best bang-for-the-buck format.

What gamma function do you apply in your format? In e.g. mpv we just use gamma 2.4 more or less arbitrarily (the ideal CRT response), but I definitely want to extend this to allow using *-Log, HLG or PQ for libplacebo.

Anyway, here's my plan:

  1. I will define my own struct that includes all metadata I want, which will be used for the API of the actual rendering commands and internally.
  2. I will define my own 3DLUT format that is basically this struct but in a file.
  3. I will define a helper function in utils/3dlut.h to parse a madVR format 3DLUT into this struct format, which will probably check to see if the locale works and error out if it doesn't. (Or long-term, be replaced by custom float parsing code). I can also add some hacks to e.g. replace . by , in locales where this is required, since I don't think the madVR spec uses , for anything meaningful.
  4. I will possibly also add a helper function to parse the plaintext 3DLUT generated by Light Illusion etc.
  5. As part of the library I will expose a command line utility to convert between the 3DLUT formats that libplacebo supports.

Then it's up to users to decide what they want to support and how they want to do so; with the preferred / fastest format being the libplacebo internal format and the others only being provided for compatibility.

@madshi
Copy link

madshi commented Jul 1, 2018

Input/OutputColorEncoding is explained here:

https://gist.github.com/kkkrackpot/292062e13111f7506135351821a575cc
https://forum.doom9.org/showthread.php?p=1525672#post1525672

If anything is missing, let me know and I'll check if I can find it somewhere.

madVR currently supports 3DLUTs with BT.601, BT.709, EBU/PAL, DCI-P3 and BT.2020 "input" primaries. I don't recall right now if I'm parsing floating point text for the metadata or if I'm using the 3DLUT header's "inputColorSpace" double fields. Would have to check my source code to confirm.

In theory the 3DLUT format could support different transfer functions, but madVR only supports either SDR in as a pure power 2.2 gamma curve (I'm feeding in SDR content "untouched", though, just converted to RGB with video levels), or HDR in as PQ. I've not looked into HLG yet. Also, madVR requires 3DLUTs to be always video levels. Other formats are in theory supported by the 3DLUT format itself, but madVR can't handle that properly.

Creating your own format with some converter tools sounds fine to me. If I may suggest, an important format to support would be eeColor text files. It's the most simple 3DLUT format possible, unfortunately without any metadata. All the (Windows) calibration applications can create those.

I'm always using hard coded "." as a floating point separator, to avoid string parsing problems. In Delphi I can tell the string / floating point routines that I want to use a specific separator instead of the locale. If I leave this at default config (which uses the locale) all hell breaks lose. If the floating routines in your programming language don't support that, I suppose you could cheat by using "SetThreadLocale()" to switch to USA locale temporarily (on Windows, at least). That way you wouldn't have to manually replace "." to "," or stuff like that.

@haasn haasn closed this as completed Dec 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants