Using EDR/HDR on macOS with OpenGL and GLFW

2021-12-21

/uploads/blog/2021/1640095641158-image.png.converted.jpg macOS introduced EDR/ HDR support for OpenGL and Metal few years back. This years' new MacBook Pro came with HDR screens by default. This is a quick note about how to enable EDR/ HDR for GLFW.

The idea about HDR is that, typically the color rendered to the screen are clamped to [0, 1]. However when HDR is enabled, the screen could display color higher than 1.0. By default this is disabled so all colors are still clamped to 1.0.

To use HDR, it only needs 2 things to be set correctly:

  1. The HDR is enabled for the OpenGL/ Metal surface so OS is not doing clamping
  2. The framebuffer is using FP16 (RGBA16F) format so OpenGL is not doing clamping

To implement in GLFW, in src/nsgl_context.m, for example, do the following:

    GLFWbool useHDR;

    if (fbconfig->redBits != GLFW_DONT_CARE &&
        fbconfig->greenBits != GLFW_DONT_CARE &&
        fbconfig->blueBits != GLFW_DONT_CARE)
    {
        int colorBits;

        if (fbconfig->redBits == 16 &&
            fbconfig->greenBits == 16 &&
            fbconfig->blueBits == 16)
        {
            colorBits = 64;
            addAttrib(NSOpenGLPFAColorFloat);
            useHDR = true;
        }
        else {
            colorBits = fbconfig->redBits +
                    fbconfig->greenBits +
                    fbconfig->blueBits;

            // macOS needs non-zero color size, so set reasonable values
            if (colorBits == 0)
                colorBits = 24;
            else if (colorBits < 15)
                colorBits = 15;
            useHDR = false; 
        }

        setAttrib(NSOpenGLPFAColorSize, colorBits);
    }

This piece of code sets the framebuffer to use RGBA16F if both R/G/B channel depth is set to 16 bit. You could also modify the GLFW code to use additional hint to enable HDR.

Later when the view has been initialized, enable the HDR accordingly:

    [window->ns.view setWantsExtendedDynamicRangeOpenGLSurface: useHDR];

Then the GLFW is ready to use HDR. You could start implement HDR code in any language that has GLFW binding, for example C, Obj C, or Java + lwjgl.

First make sure it hints the GLFW to use HDR (correspond to the previously shown modifications to GLFW):

    glfwWindowHint(GLFW_RED_BITS, 16);
    glfwWindowHint(GLFW_GREEN_BITS, 16);
    glfwWindowHint(GLFW_BLUE_BITS, 16);

Note that in macOS, the maximum color value you could use in HDR mode is not a constant value. This should be queried using the maximumExtendedDynamicRangeColorComponentValue:

static void queryEDR()
{
    NSArray *screenArray = [NSScreen screens];
    unsigned screenCount = [screenArray count];
    for (unsigned i = 0; i < screenCount; i++) {
        NSScreen *screen = [screenArray objectAtIndex: i];
        int width = [screen frame].size.width;
        int height = [screen frame].size.height;
        float maxPot = (float)screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
        float maxRef = (float)screen.maximumReferenceExtendedDynamicRangeColorComponentValue;
        float maxVal = (float)screen.maximumExtendedDynamicRangeColorComponentValue;
        printf("Screen %d: width: %d, height: %d, EDR: %.2f, ref: %.2f, pot: %.2f\n",
                i, width, height, maxVal, maxRef, maxPot);
    }
}

Done.