Centralize colorspace and color range handling

This commit is contained in:
Cameron Gutman
2022-09-24 12:28:23 -05:00
parent 78b522ec1d
commit 99885f5b4b
10 changed files with 93 additions and 121 deletions
@@ -80,8 +80,8 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
m_SwapChain(nullptr), m_SwapChain(nullptr),
m_DeviceContext(nullptr), m_DeviceContext(nullptr),
m_RenderTargetView(nullptr), m_RenderTargetView(nullptr),
m_LastColorSpace(AVCOL_SPC_UNSPECIFIED), m_LastColorSpace(-1),
m_LastColorRange(AVCOL_RANGE_UNSPECIFIED), m_LastFullRange(false),
m_AllowTearing(false), m_AllowTearing(false),
m_VideoGenericPixelShader(nullptr), m_VideoGenericPixelShader(nullptr),
m_VideoBt601LimPixelShader(nullptr), m_VideoBt601LimPixelShader(nullptr),
@@ -673,11 +673,14 @@ void D3D11VARenderer::renderOverlay(Overlay::OverlayType type)
void D3D11VARenderer::bindColorConversion(AVFrame* frame) void D3D11VARenderer::bindColorConversion(AVFrame* frame)
{ {
bool fullRange = isFrameFullRange(frame);
int colorspace = getFrameColorspace(frame);
// We have purpose-built shaders for the common Rec 601 (SDR) and Rec 2020 (HDR) cases // We have purpose-built shaders for the common Rec 601 (SDR) and Rec 2020 (HDR) cases
if (frame->color_range == AVCOL_RANGE_MPEG && frame->colorspace == AVCOL_SPC_SMPTE170M) { if (!fullRange && colorspace == COLORSPACE_REC_601) {
m_DeviceContext->PSSetShader(m_VideoBt601LimPixelShader, nullptr, 0); m_DeviceContext->PSSetShader(m_VideoBt601LimPixelShader, nullptr, 0);
} }
else if (frame->color_range == AVCOL_RANGE_MPEG && frame->colorspace == AVCOL_SPC_BT2020_NCL) { else if (!fullRange && colorspace == COLORSPACE_REC_2020) {
m_DeviceContext->PSSetShader(m_VideoBt2020LimPixelShader, nullptr, 0); m_DeviceContext->PSSetShader(m_VideoBt2020LimPixelShader, nullptr, 0);
} }
else { else {
@@ -685,14 +688,14 @@ void D3D11VARenderer::bindColorConversion(AVFrame* frame)
m_DeviceContext->PSSetShader(m_VideoGenericPixelShader, nullptr, 0); m_DeviceContext->PSSetShader(m_VideoGenericPixelShader, nullptr, 0);
// If nothing has changed since last frame, we're done // If nothing has changed since last frame, we're done
if (frame->colorspace == m_LastColorSpace && frame->color_range == m_LastColorRange) { if (colorspace == m_LastColorSpace && fullRange == m_LastFullRange) {
return; return;
} }
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Falling back to generic video pixel shader for %d:%d", "Falling back to generic video pixel shader for %d (%s range)",
frame->colorspace, colorspace,
frame->color_range); fullRange ? "full" : "limited");
D3D11_BUFFER_DESC constDesc = {}; D3D11_BUFFER_DESC constDesc = {};
constDesc.ByteWidth = sizeof(CSC_CONST_BUF); constDesc.ByteWidth = sizeof(CSC_CONST_BUF);
@@ -701,41 +704,21 @@ void D3D11VARenderer::bindColorConversion(AVFrame* frame)
constDesc.CPUAccessFlags = 0; constDesc.CPUAccessFlags = 0;
constDesc.MiscFlags = 0; constDesc.MiscFlags = 0;
// This handles the case where the color range is unknown,
// so that we use Limited color range which is the default
// behavior for Moonlight.
CSC_CONST_BUF constBuf = {}; CSC_CONST_BUF constBuf = {};
bool fullRange = (frame->color_range == AVCOL_RANGE_JPEG);
const float* rawCscMatrix; const float* rawCscMatrix;
switch (frame->colorspace) { switch (colorspace) {
case AVCOL_SPC_SMPTE170M: case COLORSPACE_REC_601:
case AVCOL_SPC_BT470BG:
rawCscMatrix = fullRange ? k_CscMatrix_Bt601Full : k_CscMatrix_Bt601Lim; rawCscMatrix = fullRange ? k_CscMatrix_Bt601Full : k_CscMatrix_Bt601Lim;
break; break;
case AVCOL_SPC_BT709: case COLORSPACE_REC_709:
rawCscMatrix = fullRange ? k_CscMatrix_Bt709Full : k_CscMatrix_Bt709Lim; rawCscMatrix = fullRange ? k_CscMatrix_Bt709Full : k_CscMatrix_Bt709Lim;
break; break;
case AVCOL_SPC_BT2020_NCL: case COLORSPACE_REC_2020:
case AVCOL_SPC_BT2020_CL:
rawCscMatrix = fullRange ? k_CscMatrix_Bt2020Full : k_CscMatrix_Bt2020Lim; rawCscMatrix = fullRange ? k_CscMatrix_Bt2020Full : k_CscMatrix_Bt2020Lim;
break; break;
default: default:
// Sunshine doesn't always populate this data correctly, SDL_assert(false);
// so fallback to assuming they're sending what we asked for. return;
switch (getDecoderColorspace()) {
case COLORSPACE_REC_601:
rawCscMatrix = fullRange ? k_CscMatrix_Bt601Full : k_CscMatrix_Bt601Lim;
break;
case COLORSPACE_REC_709:
rawCscMatrix = fullRange ? k_CscMatrix_Bt709Full : k_CscMatrix_Bt709Lim;
break;
case COLORSPACE_REC_2020:
rawCscMatrix = fullRange ? k_CscMatrix_Bt2020Full : k_CscMatrix_Bt2020Lim;
break;
default:
SDL_assert(false);
return;
}
} }
// We need to adjust our raw CSC matrix to be column-major and with float3 vectors // We need to adjust our raw CSC matrix to be column-major and with float3 vectors
@@ -769,8 +752,8 @@ void D3D11VARenderer::bindColorConversion(AVFrame* frame)
} }
} }
m_LastColorSpace = frame->colorspace; m_LastColorSpace = colorspace;
m_LastColorRange = frame->color_range; m_LastFullRange = fullRange;
} }
void D3D11VARenderer::renderVideo(AVFrame* frame) void D3D11VARenderer::renderVideo(AVFrame* frame)
@@ -49,8 +49,8 @@ private:
int m_TextureAlignment; int m_TextureAlignment;
int m_DisplayWidth; int m_DisplayWidth;
int m_DisplayHeight; int m_DisplayHeight;
AVColorSpace m_LastColorSpace; int m_LastColorSpace;
AVColorRange m_LastColorRange; bool m_LastFullRange;
bool m_AllowTearing; bool m_AllowTearing;
+16 -18
View File
@@ -50,8 +50,8 @@ DrmRenderer::DrmRenderer(IFFmpegRenderer *backendRenderer)
m_CrtcId(0), m_CrtcId(0),
m_PlaneId(0), m_PlaneId(0),
m_CurrentFbId(0), m_CurrentFbId(0),
m_LastColorRange(AVCOL_RANGE_UNSPECIFIED), m_LastFullRange(false),
m_LastColorSpace(AVCOL_SPC_UNSPECIFIED), m_LastColorSpace(-1),
m_ColorEncodingProp(nullptr), m_ColorEncodingProp(nullptr),
m_ColorRangeProp(nullptr), m_ColorRangeProp(nullptr),
m_HdrOutputMetadataProp(nullptr), m_HdrOutputMetadataProp(nullptr),
@@ -608,7 +608,12 @@ void DrmRenderer::renderFrame(AVFrame* frame)
return; return;
} }
if (frame->color_range != m_LastColorRange) { int colorspace = getFrameColorspace(frame);
bool fullRange = isFrameFullRange(frame);
// We also update the color range when the colorspace changes in order to handle initialization
// where the last color range value may not actual be applied to the plane.
if (fullRange != m_LastFullRange || colorspace != m_LastColorSpace) {
const char* desiredValue = getDrmColorRangeValue(frame); const char* desiredValue = getDrmColorRangeValue(frame);
if (m_ColorRangeProp != nullptr && desiredValue != nullptr) { if (m_ColorRangeProp != nullptr && desiredValue != nullptr) {
@@ -647,10 +652,10 @@ void DrmRenderer::renderFrame(AVFrame* frame)
"COLOR_RANGE property does not exist on output plane. Colors may be inaccurate!"); "COLOR_RANGE property does not exist on output plane. Colors may be inaccurate!");
} }
m_LastColorRange = frame->color_range; m_LastFullRange = fullRange;
} }
if (frame->colorspace != m_LastColorSpace) { if (colorspace != m_LastColorSpace) {
const char* desiredValue = getDrmColorEncodingValue(frame); const char* desiredValue = getDrmColorEncodingValue(frame);
if (m_ColorEncodingProp != nullptr && desiredValue != nullptr) { if (m_ColorEncodingProp != nullptr && desiredValue != nullptr) {
@@ -689,7 +694,7 @@ void DrmRenderer::renderFrame(AVFrame* frame)
"COLOR_ENCODING property does not exist on output plane. Colors may be inaccurate!"); "COLOR_ENCODING property does not exist on output plane. Colors may be inaccurate!");
} }
m_LastColorSpace = frame->colorspace; m_LastColorSpace = colorspace;
} }
// Update the overlay // Update the overlay
@@ -745,12 +750,12 @@ bool DrmRenderer::isDirectRenderingSupported()
const char* DrmRenderer::getDrmColorEncodingValue(AVFrame* frame) const char* DrmRenderer::getDrmColorEncodingValue(AVFrame* frame)
{ {
switch (frame->colorspace) { switch (getFrameColorspace(frame)) {
case AVCOL_SPC_SMPTE170M: case COLORSPACE_REC_601:
return "ITU-R BT.601 YCbCr"; return "ITU-R BT.601 YCbCr";
case AVCOL_SPC_BT709: case COLORSPACE_REC_709:
return "ITU-R BT.709 YCbCr"; return "ITU-R BT.709 YCbCr";
case AVCOL_SPC_BT2020_NCL: case COLORSPACE_REC_2020:
return "ITU-R BT.2020 YCbCr"; return "ITU-R BT.2020 YCbCr";
default: default:
return NULL; return NULL;
@@ -759,14 +764,7 @@ const char* DrmRenderer::getDrmColorEncodingValue(AVFrame* frame)
const char* DrmRenderer::getDrmColorRangeValue(AVFrame* frame) const char* DrmRenderer::getDrmColorRangeValue(AVFrame* frame)
{ {
switch (frame->color_range) { return isFrameFullRange(frame) ? "YCbCr full range" : "YCbCr limited range";
case AVCOL_RANGE_MPEG:
return "YCbCr limited range";
case AVCOL_RANGE_JPEG:
return "YCbCr full range";
default:
return NULL;
}
} }
#ifdef HAVE_EGL #ifdef HAVE_EGL
+2 -2
View File
@@ -78,8 +78,8 @@ private:
uint32_t m_CrtcId; uint32_t m_CrtcId;
uint32_t m_PlaneId; uint32_t m_PlaneId;
uint32_t m_CurrentFbId; uint32_t m_CurrentFbId;
AVColorRange m_LastColorRange; bool m_LastFullRange;
AVColorSpace m_LastColorSpace; int m_LastColorSpace;
drmModePropertyPtr m_ColorEncodingProp; drmModePropertyPtr m_ColorEncodingProp;
drmModePropertyPtr m_ColorRangeProp; drmModePropertyPtr m_ColorRangeProp;
drmModePropertyPtr m_HdrOutputMetadataProp; drmModePropertyPtr m_HdrOutputMetadataProp;
+4 -18
View File
@@ -1009,17 +1009,7 @@ void DXVA2Renderer::renderFrame(AVFrame *frame)
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]); IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
HRESULT hr; HRESULT hr;
switch (frame->color_range) { m_Desc.SampleFormat.NominalRange = isFrameFullRange(frame) ? DXVA2_NominalRange_0_255 : DXVA2_NominalRange_16_235;
case AVCOL_RANGE_JPEG:
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_0_255;
break;
case AVCOL_RANGE_MPEG:
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_16_235;
break;
default:
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_Unknown;
break;
}
switch (frame->color_primaries) { switch (frame->color_primaries) {
case AVCOL_PRI_BT709: case AVCOL_PRI_BT709:
@@ -1067,17 +1057,13 @@ void DXVA2Renderer::renderFrame(AVFrame *frame)
break; break;
} }
switch (frame->colorspace) { switch (getFrameColorspace(frame)) {
case AVCOL_SPC_BT709: case COLORSPACE_REC_709:
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709; m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
break; break;
case AVCOL_SPC_BT470BG: case COLORSPACE_REC_601:
case AVCOL_SPC_SMPTE170M:
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT601; m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT601;
break; break;
case AVCOL_SPC_SMPTE240M:
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_SMPTE240M;
break;
default: default:
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_Unknown; m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_Unknown;
break; break;
@@ -739,10 +739,7 @@ const float *EGLRenderer::getColorOffsets(const AVFrame* frame) {
static const float limitedOffsets[] = { 16.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f }; static const float limitedOffsets[] = { 16.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f };
static const float fullOffsets[] = { 0.0f, 128.0f / 255.0f, 128.0f / 255.0f }; static const float fullOffsets[] = { 0.0f, 128.0f / 255.0f, 128.0f / 255.0f };
// This handles the case where the color range is unknown, return isFrameFullRange(frame) ? fullOffsets : limitedOffsets;
// so that we use Limited color range which is the default
// behavior for Moonlight.
return (frame->color_range == AVCOL_RANGE_JPEG) ? fullOffsets : limitedOffsets;
} }
const float *EGLRenderer::getColorMatrix(const AVFrame* frame) { const float *EGLRenderer::getColorMatrix(const AVFrame* frame) {
@@ -780,33 +777,17 @@ const float *EGLRenderer::getColorMatrix(const AVFrame* frame) {
1.4746f, -0.5714f, 0.0f 1.4746f, -0.5714f, 0.0f
}; };
// This handles the case where the color range is unknown, bool fullRange = isFrameFullRange(frame);
// so that we use Limited color range which is the default switch (getFrameColorspace(frame)) {
// behavior for Moonlight. case COLORSPACE_REC_601:
bool fullRange = (frame->color_range == AVCOL_RANGE_JPEG);
switch (frame->colorspace) {
case AVCOL_SPC_SMPTE170M:
case AVCOL_SPC_BT470BG:
return fullRange ? bt601Full : bt601Lim; return fullRange ? bt601Full : bt601Lim;
case AVCOL_SPC_BT709: case COLORSPACE_REC_709:
return fullRange ? bt709Full : bt709Lim; return fullRange ? bt709Full : bt709Lim;
case AVCOL_SPC_BT2020_NCL: case COLORSPACE_REC_2020:
case AVCOL_SPC_BT2020_CL:
return fullRange ? bt2020Full : bt2020Lim; return fullRange ? bt2020Full : bt2020Lim;
default: default:
// Some backends don't populate this, so we'll assume SDL_assert(false);
// the host gave us what we asked for by default. }
switch (getDecoderColorspace()) {
case COLORSPACE_REC_601:
return fullRange ? bt601Full : bt601Lim;
case COLORSPACE_REC_709:
return fullRange ? bt709Full : bt709Lim;
case COLORSPACE_REC_2020:
return fullRange ? bt2020Full : bt2020Lim;
default:
SDL_assert(false);
}
};
return bt601Lim; return bt601Lim;
} }
@@ -158,6 +158,31 @@ public:
return COLORSPACE_REC_601; return COLORSPACE_REC_601;
} }
virtual int getFrameColorspace(AVFrame* frame) {
// Prefer the colorspace field on the AVFrame itself
switch (frame->colorspace) {
case AVCOL_SPC_SMPTE170M:
case AVCOL_SPC_BT470BG:
return COLORSPACE_REC_601;
case AVCOL_SPC_BT709:
return COLORSPACE_REC_709;
case AVCOL_SPC_BT2020_NCL:
case AVCOL_SPC_BT2020_CL:
return COLORSPACE_REC_2020;
default:
// If the colorspace is not populated, assume the encoder
// is sending the colorspace that we requested.
return getDecoderColorspace();
}
}
virtual bool isFrameFullRange(AVFrame* frame) {
// This handles the case where the color range is unknown,
// so that we use Limited color range which is the default
// behavior for Moonlight.
return frame->color_range == AVCOL_RANGE_JPEG;
}
virtual bool isRenderThreadSupported() { virtual bool isRenderThreadSupported() {
// Render thread is supported by default // Render thread is supported by default
return true; return true;
@@ -10,7 +10,7 @@ SdlRenderer::SdlRenderer()
m_Renderer(nullptr), m_Renderer(nullptr),
m_Texture(nullptr), m_Texture(nullptr),
m_SwPixelFormat(AV_PIX_FMT_NONE), m_SwPixelFormat(AV_PIX_FMT_NONE),
m_ColorSpace(AVCOL_SPC_UNSPECIFIED), m_ColorSpace(-1),
m_MapFrame(false) m_MapFrame(false)
{ {
SDL_zero(m_OverlayTextures); SDL_zero(m_OverlayTextures);
@@ -373,7 +373,8 @@ ReadbackRetry:
// Because the specific YUV color conversion shader is established at // Because the specific YUV color conversion shader is established at
// texture creation for most SDL render backends, we need to recreate // texture creation for most SDL render backends, we need to recreate
// the texture when the colorspace changes. // the texture when the colorspace changes.
if (frame->colorspace != m_ColorSpace) { int colorspace = getFrameColorspace(frame);
if (colorspace != m_ColorSpace) {
#ifdef HAVE_CUDA #ifdef HAVE_CUDA
if (m_CudaGLHelper != nullptr) { if (m_CudaGLHelper != nullptr) {
delete m_CudaGLHelper; delete m_CudaGLHelper;
@@ -386,7 +387,7 @@ ReadbackRetry:
m_Texture = nullptr; m_Texture = nullptr;
} }
m_ColorSpace = frame->colorspace; m_ColorSpace = colorspace;
} }
if (m_Texture == nullptr) { if (m_Texture == nullptr) {
@@ -410,13 +411,12 @@ ReadbackRetry:
goto Exit; goto Exit;
} }
switch (frame->colorspace) switch (colorspace)
{ {
case AVCOL_SPC_BT709: case COLORSPACE_REC_709:
SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT709); SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT709);
break; break;
case AVCOL_SPC_BT470BG: case COLORSPACE_REC_601:
case AVCOL_SPC_SMPTE170M:
default: default:
SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT601); SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT601);
break; break;
@@ -26,7 +26,7 @@ private:
SDL_Renderer* m_Renderer; SDL_Renderer* m_Renderer;
SDL_Texture* m_Texture; SDL_Texture* m_Texture;
enum AVPixelFormat m_SwPixelFormat; enum AVPixelFormat m_SwPixelFormat;
enum AVColorSpace m_ColorSpace; int m_ColorSpace;
bool m_MapFrame; bool m_MapFrame;
SDL_Texture* m_OverlayTextures[Overlay::OverlayMax]; SDL_Texture* m_OverlayTextures[Overlay::OverlayMax];
SDL_Rect m_OverlayRects[Overlay::OverlayMax]; SDL_Rect m_OverlayRects[Overlay::OverlayMax];
+9 -10
View File
@@ -40,7 +40,7 @@ public:
m_FormatDesc(nullptr), m_FormatDesc(nullptr),
m_StreamView(nullptr), m_StreamView(nullptr),
m_DisplayLink(nullptr), m_DisplayLink(nullptr),
m_LastAvColorSpace(AVCOL_SPC_UNSPECIFIED), m_LastColorSpace(-1),
m_ColorSpace(nullptr), m_ColorSpace(nullptr),
m_VsyncMutex(nullptr), m_VsyncMutex(nullptr),
m_VsyncPassed(nullptr) m_VsyncPassed(nullptr)
@@ -200,27 +200,26 @@ public:
// Reset m_ColorSpace if the colorspace changes. This can happen when // Reset m_ColorSpace if the colorspace changes. This can happen when
// a game enters HDR mode (Rec 601 -> Rec 2020). // a game enters HDR mode (Rec 601 -> Rec 2020).
if (frame->colorspace != m_LastAvColorSpace) { int colorspace = getFrameColorspace(frame);
if (colorspace != m_LastColorSpace) {
if (m_ColorSpace != nullptr) { if (m_ColorSpace != nullptr) {
CGColorSpaceRelease(m_ColorSpace); CGColorSpaceRelease(m_ColorSpace);
m_ColorSpace = nullptr; m_ColorSpace = nullptr;
} }
switch (frame->colorspace) { switch (colorspace) {
case AVCOL_SPC_BT709: case COLORSPACE_REC_709:
m_ColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_709); m_ColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_709);
break; break;
case AVCOL_SPC_BT2020_NCL: case COLORSPACE_REC_2020:
m_ColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020); m_ColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020);
break; break;
case AVCOL_SPC_SMPTE170M: case COLORSPACE_REC_601:
m_ColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); m_ColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
break; break;
default:
break;
} }
m_LastAvColorSpace = frame->colorspace; m_LastColorSpace = colorspace;
} }
if (m_ColorSpace != nullptr) { if (m_ColorSpace != nullptr) {
@@ -522,7 +521,7 @@ private:
NSView* m_StreamView; NSView* m_StreamView;
NSTextField* m_OverlayTextFields[Overlay::OverlayMax]; NSTextField* m_OverlayTextFields[Overlay::OverlayMax];
CVDisplayLinkRef m_DisplayLink; CVDisplayLinkRef m_DisplayLink;
AVColorSpace m_LastAvColorSpace; int m_LastColorSpace;
CGColorSpaceRef m_ColorSpace; CGColorSpaceRef m_ColorSpace;
SDL_mutex* m_VsyncMutex; SDL_mutex* m_VsyncMutex;
SDL_cond* m_VsyncPassed; SDL_cond* m_VsyncPassed;