Added the Chrome HDR tonemap operator

Also added support for the SDL_PIXELFORMAT_XBGR2101010 pixel format to the D3D12, D3D11, and Metal renderers.
This commit is contained in:
Sam Lantinga
2024-02-21 09:03:03 -08:00
parent 4ba6aeee9d
commit 54c2ba6afd
50 changed files with 15111 additions and 21123 deletions

View File

@@ -91,9 +91,7 @@ typedef enum SDL_MetalFragmentFunction
SDL_METAL_FRAGMENT_SOLID = 0,
SDL_METAL_FRAGMENT_COPY,
SDL_METAL_FRAGMENT_YUV,
SDL_METAL_FRAGMENT_NV12,
SDL_METAL_FRAGMENT_NV21,
SDL_METAL_FRAGMENT_HDR10,
SDL_METAL_FRAGMENT_ADVANCED,
SDL_METAL_FRAGMENT_COUNT,
} SDL_MetalFragmentFunction;
@@ -247,12 +245,8 @@ static NSString *GetFragmentFunctionName(SDL_MetalFragmentFunction function)
return @"SDL_Copy_fragment";
case SDL_METAL_FRAGMENT_YUV:
return @"SDL_YUV_fragment";
case SDL_METAL_FRAGMENT_NV12:
return @"SDL_NV12_fragment";
case SDL_METAL_FRAGMENT_NV21:
return @"SDL_NV21_fragment";
case SDL_METAL_FRAGMENT_HDR10:
return @"SDL_HDR10_fragment";
case SDL_METAL_FRAGMENT_ADVANCED:
return @"SDL_Advanced_fragment";
default:
return nil;
}
@@ -390,9 +384,7 @@ void MakeShaderPipelines(METAL_RenderData *data, METAL_ShaderPipelines *pipeline
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_SOLID], "SDL primitives pipeline", rtformat, SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID);
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_COPY], "SDL copy pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY);
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_YUV], "SDL YUV pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_YUV);
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV12], "SDL NV12 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV12);
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV21], "SDL NV21 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV21);
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_HDR10], "SDL HDR10 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_HDR10);
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_ADVANCED], "SDL advanced pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_ADVANCED);
}
static METAL_ShaderPipelines *ChooseShaderPipelines(METAL_RenderData *data, MTLPixelFormat rtformat)
@@ -637,7 +629,8 @@ static int METAL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
MTLPixelFormat pixfmt;
MTLTextureDescriptor *mtltexdesc;
id<MTLTexture> mtltexture, mtltextureUv;
BOOL yuv, nv12;
BOOL yuv = FALSE;
BOOL nv12 = FALSE;
METAL_TextureData *texturedata;
CVPixelBufferRef pixelbuffer = nil;
IOSurfaceRef surface = nil;
@@ -665,6 +658,9 @@ static int METAL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
pixfmt = MTLPixelFormatBGRA8Unorm;
}
break;
case SDL_PIXELFORMAT_XBGR2101010:
pixfmt = MTLPixelFormatRGB10A2Unorm;
break;
case SDL_PIXELFORMAT_IYUV:
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_NV12:
@@ -745,30 +741,17 @@ static int METAL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
} else {
texturedata.mtlsampler = data.mtlsamplerlinear;
}
if (SDL_COLORSPACETRANSFER(texture->colorspace) == SDL_TRANSFER_CHARACTERISTICS_SRGB) {
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
} else if (yuv) {
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
} else {
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_ADVANCED;
}
texturedata.mtltexture = mtltexture;
texturedata.mtltextureUv = mtltextureUv;
#if SDL_HAVE_YUV
texturedata.yuv = yuv;
texturedata.nv12 = nv12;
if (yuv) {
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
} else if (texture->format == SDL_PIXELFORMAT_NV12) {
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
} else if (texture->format == SDL_PIXELFORMAT_NV21) {
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
} else if (texture->format == SDL_PIXELFORMAT_P010) {
if(SDL_COLORSPACEPRIMARIES(texture->colorspace) == SDL_COLOR_PRIMARIES_BT2020 &&
SDL_COLORSPACETRANSFER(texture->colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) {
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_HDR10;
} else {
return SDL_SetError("Unsupported YUV colorspace");
}
} else
#endif
{
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
}
#if SDL_HAVE_YUV
if (yuv || nv12) {
size_t offset = GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8);
@@ -1167,20 +1150,6 @@ static int METAL_QueueNoOp(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
return 0; /* nothing to do in this backend. */
}
static int METAL_QueueSetColorScale(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
{
const size_t vertlen = (2 * sizeof(float));
float *verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.color.first);
if (!verts) {
return -1;
}
*verts++ = (float)SDL_RenderingLinearSpace(renderer);
*verts++ = cmd->data.color.color_scale;
return 0;
}
static int METAL_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
{
SDL_FColor color = cmd->data.draw.color;
@@ -1320,6 +1289,35 @@ static int METAL_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, S
return 0;
}
/* These should mirror the definitions in SDL_shaders_metal.metal */
//static const float TONEMAP_NONE = 0;
//static const float TONEMAP_LINEAR = 1;
static const float TONEMAP_CHROME = 2;
//static const float TEXTURETYPE_NONE = 0;
static const float TEXTURETYPE_RGB = 1;
static const float TEXTURETYPE_NV12 = 2;
static const float TEXTURETYPE_NV21 = 3;
static const float TEXTURETYPE_YUV = 4;
static const float INPUTTYPE_UNSPECIFIED = 0;
static const float INPUTTYPE_SRGB = 1;
static const float INPUTTYPE_SCRGB = 2;
static const float INPUTTYPE_HDR10 = 3;
typedef struct
{
float scRGB_output;
float texture_type;
float input_type;
float color_scale;
float tonemap_method;
float tonemap_factor1;
float tonemap_factor2;
float sdr_white_point;
} PixelShaderConstants;
typedef struct
{
__unsafe_unretained id<MTLRenderPipelineState> pipeline;
@@ -1332,17 +1330,74 @@ typedef struct
SDL_bool viewport_dirty;
SDL_Rect viewport;
size_t projection_offset;
SDL_bool color_scale_dirty;
size_t color_scale_offset;
SDL_bool shader_constants_dirty;
PixelShaderConstants shader_constants;
} METAL_DrawStateCache;
static SDL_bool SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader,
const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
static void SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_Texture *texture, PixelShaderConstants *constants)
{
float output_headroom;
SDL_zerop(constants);
constants->scRGB_output = (float)SDL_RenderingLinearSpace(renderer);
constants->color_scale = cmd->data.draw.color_scale;
if (texture) {
switch (texture->format) {
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_IYUV:
constants->texture_type = TEXTURETYPE_YUV;
constants->input_type = INPUTTYPE_SRGB;
break;
case SDL_PIXELFORMAT_NV12:
constants->texture_type = TEXTURETYPE_NV12;
constants->input_type = INPUTTYPE_SRGB;
break;
case SDL_PIXELFORMAT_NV21:
constants->texture_type = TEXTURETYPE_NV21;
constants->input_type = INPUTTYPE_SRGB;
break;
case SDL_PIXELFORMAT_P010:
constants->texture_type = TEXTURETYPE_NV12;
constants->input_type = INPUTTYPE_HDR10;
break;
default:
constants->texture_type = TEXTURETYPE_RGB;
if (texture->colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
constants->input_type = INPUTTYPE_SCRGB;
} else if (SDL_COLORSPACEPRIMARIES(texture->colorspace) == SDL_COLOR_PRIMARIES_BT2020 &&
SDL_COLORSPACETRANSFER(texture->colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) {
constants->input_type = INPUTTYPE_HDR10;
} else {
constants->input_type = INPUTTYPE_UNSPECIFIED;
}
break;
}
constants->sdr_white_point = texture->SDR_white_point;
if (renderer->target) {
output_headroom = renderer->target->HDR_headroom;
} else {
output_headroom = renderer->HDR_headroom;
}
if (texture->HDR_headroom > output_headroom) {
constants->tonemap_method = TONEMAP_CHROME;
constants->tonemap_factor1 = (output_headroom / (texture->HDR_headroom * texture->HDR_headroom));
constants->tonemap_factor2 = (1.0f / output_headroom);
}
}
}
static SDL_bool SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader, PixelShaderConstants *shader_constants, const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
{
METAL_RenderData *data = (__bridge METAL_RenderData *)renderer->driverdata;
const SDL_BlendMode blend = cmd->data.draw.blend;
size_t first = cmd->data.draw.first;
id<MTLRenderPipelineState> newpipeline;
PixelShaderConstants solid_constants;
if (!METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, statecache->vertex_buffer)) {
return SDL_FALSE;
@@ -1394,17 +1449,28 @@ static SDL_bool SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cm
statecache->cliprect_dirty = SDL_FALSE;
}
if (statecache->color_scale_dirty) {
[data.mtlcmdencoder setFragmentBufferOffset:statecache->color_scale_offset atIndex:0];
statecache->color_scale_dirty = SDL_FALSE;
}
newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend);
if (newpipeline != statecache->pipeline) {
[data.mtlcmdencoder setRenderPipelineState:newpipeline];
statecache->pipeline = newpipeline;
}
if (!shader_constants) {
SetupShaderConstants(renderer, cmd, NULL, &solid_constants);
shader_constants = &solid_constants;
}
if (statecache->shader_constants_dirty ||
SDL_memcmp(shader_constants, &statecache->shader_constants, sizeof(*shader_constants)) != 0) {
id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:sizeof(*shader_constants) options:MTLResourceStorageModeShared];
mtlbufconstants.label = @"SDL shader constants data";
SDL_memcpy([mtlbufconstants contents], shader_constants, sizeof(*shader_constants));
[data.mtlcmdencoder setFragmentBuffer:mtlbufconstants offset:0 atIndex:0];
SDL_memcpy(&statecache->shader_constants, shader_constants, sizeof(*shader_constants));
statecache->shader_constants_dirty = SDL_FALSE;
}
if (constants_offset != statecache->constants_offset) {
if (constants_offset != CONSTANTS_OFFSET_INVALID) {
[data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3];
@@ -1422,8 +1488,11 @@ static SDL_bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cm
METAL_RenderData *data = (__bridge METAL_RenderData *)renderer->driverdata;
SDL_Texture *texture = cmd->data.draw.texture;
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
PixelShaderConstants constants;
if (!SetDrawState(renderer, cmd, texturedata.fragmentFunction, constants_offset, mtlbufvertex, statecache)) {
SetupShaderConstants(renderer, cmd, texture, &constants);
if (!SetDrawState(renderer, cmd, texturedata.fragmentFunction, &constants, constants_offset, mtlbufvertex, statecache)) {
return SDL_FALSE;
}
@@ -1465,11 +1534,10 @@ static int METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
statecache.vertex_buffer = nil;
statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
statecache.texture = NULL;
statecache.color_scale_dirty = SDL_TRUE;
statecache.shader_constants_dirty = SDL_TRUE;
statecache.cliprect_dirty = SDL_TRUE;
statecache.viewport_dirty = SDL_TRUE;
statecache.projection_offset = 0;
statecache.color_scale_offset = 0;
// !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation?
if (vertsize > 0) {
@@ -1478,8 +1546,7 @@ static int METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
* data from the GPU than to read managed/private data, but we avoid the
* cost of copying the data and the code's simpler. Apple's best
* practices guide recommends this approach for streamed vertex data.
* TODO: this buffer is also used for constants. Is performance still
* good for those, or should we have a managed buffer for them? */
*/
mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared];
mtlbufvertex.label = @"SDL vertex data";
SDL_memcpy([mtlbufvertex contents], vertices, vertsize);
@@ -1519,8 +1586,6 @@ static int METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
case SDL_RENDERCMD_SETCOLORSCALE:
{
statecache.color_scale_offset = cmd->data.color.first;
statecache.color_scale_dirty = SDL_TRUE;
break;
}
@@ -1542,7 +1607,7 @@ static int METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
statecache.pipeline = nil;
statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
statecache.texture = NULL;
statecache.color_scale_dirty = SDL_TRUE;
statecache.shader_constants_dirty = SDL_TRUE;
statecache.cliprect_dirty = SDL_TRUE;
statecache.viewport_dirty = SDL_TRUE;
@@ -1569,7 +1634,7 @@ static int METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
{
const size_t count = cmd->data.draw.count;
const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip;
if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) {
if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) {
[data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
}
break;
@@ -1594,7 +1659,7 @@ static int METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd,
[data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count];
}
} else {
if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) {
if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) {
[data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count];
}
}
@@ -1658,6 +1723,9 @@ static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rec
case MTLPixelFormatRGBA8Unorm_sRGB:
format = SDL_PIXELFORMAT_ABGR8888;
break;
case MTLPixelFormatRGB10A2Unorm:
format = SDL_PIXELFORMAT_XBGR2101010;
break;
case MTLPixelFormatRGBA16Float:
format = SDL_PIXELFORMAT_RGBA64_FLOAT;
break;
@@ -2080,7 +2148,7 @@ static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, SDL_PropertiesID c
renderer->SetRenderTarget = METAL_SetRenderTarget;
renderer->QueueSetViewport = METAL_QueueSetViewport;
renderer->QueueSetDrawColor = METAL_QueueNoOp;
renderer->QueueSetColorScale = METAL_QueueSetColorScale;
renderer->QueueSetColorScale = METAL_QueueNoOp;
renderer->QueueDrawPoints = METAL_QueueDrawPoints;
renderer->QueueDrawLines = METAL_QueueDrawLines;
renderer->QueueGeometry = METAL_QueueGeometry;
@@ -2158,6 +2226,7 @@ SDL_RenderDriver METAL_RenderDriver = {
9,
{ SDL_PIXELFORMAT_ARGB8888,
SDL_PIXELFORMAT_ABGR8888,
SDL_PIXELFORMAT_XBGR2101010,
SDL_PIXELFORMAT_RGBA64_FLOAT,
SDL_PIXELFORMAT_RGBA128_FLOAT,
SDL_PIXELFORMAT_YV12,