Is it possible to render HDR in OpenGL 4.1 on macOS or iPadOS

I am trying to get my macOS app to render in HDR with an HDR texture. It is rendering but not in HDR. The app uses SDL3. I set {RED,GREEN,BLUE}_SIZE to 16 before creating the SDL window and the GL context. The app is rendering a textured quad using a GL_RGBA16F format texture. Is there something else I need to do? Is it even possible?

I’m going to interpret your question as “I want to render and display the result on a screen in HDR”, and the short answer is: yes you can do this.

You can do it with CAOpenGLLayer, with any OpenGL profile. The requirements are:

  1. you need to request a floating point pixel format. Via CGLPixelFormatAttribute, that means:
    kCGLPFAColorSize, 48, kCGLPFAColorFloat
    for 16-bit floats per channel, or request 96 for 32-bit floats per channel.
  2. you need to request the display machinery to present this drawable without clamping to SDR:
    self.wantsExtendedDynamicRangeContent = TRUE;

In more concrete terms, starting from sample code like CALayerEssentials, you need:

-(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
{
	self.wantsExtendedDynamicRangeContent = TRUE;
	
	CGLPixelFormatAttribute attr[] = {
		kCGLPFADisplayMask, mask,
		kCGLPFAAccelerated,
		kCGLPFANoRecovery,
		kCGLPFAColorSize, 48,
		kCGLPFAColorFloat,
		// depth buffer etc other attributes here
		0
	};

	CGLPixelFormatObj pix;
	GLint npix;
	CGLChoosePixelFormat(attr, &pix, &npix);
	return pix;
}

With SDL, you’ll have to figure out how to do those two things. (I don’t know how, but merely requesting 16 RED bits isn’t enough, because that should produce 16 fixed point bits, not float.)

After the drawable is set up, you can then draw floating point values outside of [0,1] to it and they won’t be clamped when composited by the window server. In a Core Profile context, you’d use shaders, but this works even in a legacy OpenGL 2.1 profile, via ARB_color_buffer_float. Here’s a minimal test pattern example:

glClearColor(0.5, 0.5, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT);

glClampColorARB(GL_CLAMP_VERTEX_COLOR_ARB, GL_TRUE);
glClampColorARB(GL_CLAMP_FRAGMENT_COLOR_ARB, GL_TRUE);
glBegin(GL_QUADS);
	glColor3f(-1, -1, -1); glVertex2f(-0.5, 1); glVertex2f( 0.0, 1);
	glColor3f( 3,  3,  3); glVertex2f( 0.0,-1); glVertex2f(-0.5,-1);
glEnd();

glClampColorARB(GL_CLAMP_VERTEX_COLOR_ARB, GL_FALSE);
glBegin(GL_QUADS);
	glColor3f(-1, -1, -1); glVertex2f( 0.0, 1); glVertex2f( 0.5, 1);
	glColor3f( 3,  3,  3); glVertex2f( 0.5,-1); glVertex2f( 0.0,-1);
glEnd();

glClampColorARB(GL_CLAMP_FRAGMENT_COLOR_ARB, GL_FALSE);
glBegin(GL_QUADS);
	glColor3f(-1, -1, -1); glVertex2f( 0.5, 1); glVertex2f( 1.0, 1);
	glColor3f( 3,  3,  3); glVertex2f( 1.0,-1); glVertex2f( 0.5,-1);
glEnd();

In Sonoma 14.8.7 on my M3 MacBook Pro (with the Liquid Retina XDR Display), the right side of this pattern shows a very bright unclamped HDR gradient (of course you need a machine with a display capable of showing EDR content.)

Note that “render” and “display” are two different things. Historical background: OpenGL on the Mac has supported HDR rendering for a very long time; floating point color buffers were on Nvidia and ATI GPUs as far back as ~2005, in Panther, Tiger and Leopard. However, the window server did not support directly displaying HDR, or tone-map content for you, so these were only useful for offscreen rendering (via pbuffers); an application would manually tone-map before display in a SDR window.

Much later, the window server gained this ability, but you need to inform the graphics (CA or MTL) layers that your drawable wantsExtendedDynamicRangeContent.

1 Like