/*****************************************************************************
* dxgi_swapchain.c: DXGI swapchain handled by the display module
*****************************************************************************
* Copyright (C) 2014-2021 VLC authors and VideoLAN
*
* Authors: Martell Malone <martellmalone@gmail.com>
* Steve Lhomme <robux4@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cassert>
#include <vlc_es.h>
#include <windows.h>
#include <dcomp.h>
#include <initguid.h>
#include "dxgi_swapchain.h"
#include <dxgi1_6.h>
#include "../../video_chroma/dxgi_fmt.h"
#include <new>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
typedef enum video_color_axis {
COLOR_AXIS_RGB,
COLOR_AXIS_YCBCR,
} video_color_axis;
typedef enum swapchain_surface_type {
SWAPCHAIN_SURFACE_HWND,
SWAPCHAIN_SURFACE_DCOMP,
} swapchain_surface_type;
typedef struct {
DXGI_COLOR_SPACE_TYPE dxgi;
const char *name;
video_color_axis axis;
video_color_primaries_t primaries;
video_transfer_func_t transfer;
video_color_space_t color;
bool b_full_range;
} dxgi_color_space;
struct dxgi_swapchain
{
vlc_object_t *obj = nullptr;
const d3d_format_t *pixelFormat = nullptr;
const dxgi_color_space *colorspace = nullptr;
swapchain_surface_type swapchainSurfaceType;
union {
HWND hwnd;
struct {
IDCompositionDevice *device;
IDCompositionVisual *visual;
} dcomp;
} swapchainSurface;
ComPtr<IDXGISwapChain1> dxgiswapChain; /* DXGI 1.2 swap chain */
ComPtr<IDXGISwapChain4> dxgiswapChain4; /* DXGI 1.5 for HDR metadata */
bool send_metadata;
DXGI_HDR_METADATA_HDR10 hdr10;
bool logged_capabilities = false;
};
#define DXGI_COLOR_RANGE_FULL 1 /* 0-255 */
#define DXGI_COLOR_RANGE_STUDIO 0 /* 16-235 */
#define TRANSFER_FUNC_10 TRANSFER_FUNC_LINEAR
#define TRANSFER_FUNC_22 TRANSFER_FUNC_SRGB
#define TRANSFER_FUNC_2084 TRANSFER_FUNC_SMPTE_ST2084
#define COLOR_PRIMARIES_BT601 COLOR_PRIMARIES_BT601_525
static const dxgi_color_space color_spaces[] = {
#define DXGIMAP(AXIS, RANGE, GAMMA, SITTING, PRIMARIES) \
{ DXGI_COLOR_SPACE_##AXIS##_##RANGE##_G##GAMMA##_##SITTING##_P##PRIMARIES, \
#AXIS " Rec." #PRIMARIES " gamma:" #GAMMA " range:" #RANGE, \
COLOR_AXIS_##AXIS, COLOR_PRIMARIES_BT##PRIMARIES, TRANSFER_FUNC_##GAMMA, \
COLOR_SPACE_BT##PRIMARIES, DXGI_COLOR_RANGE_##RANGE},
DXGIMAP(RGB, FULL, 22, NONE, 709)
DXGIMAP(YCBCR, STUDIO, 22, LEFT, 601)
DXGIMAP(YCBCR, FULL, 22, LEFT, 601)
DXGIMAP(RGB, FULL, 10, NONE, 709)
DXGIMAP(RGB, STUDIO, 22, NONE, 709)
DXGIMAP(YCBCR, STUDIO, 22, LEFT, 709)
DXGIMAP(YCBCR, FULL, 22, LEFT, 709)
DXGIMAP(RGB, STUDIO, 22, NONE, 2020)
DXGIMAP(YCBCR, STUDIO, 22, LEFT, 2020)
DXGIMAP(YCBCR, FULL, 22, LEFT, 2020)
DXGIMAP(YCBCR, STUDIO, 22, TOPLEFT, 2020)
DXGIMAP(RGB, FULL, 22, NONE, 2020)
DXGIMAP(RGB, FULL, 2084, NONE, 2020)
DXGIMAP(YCBCR, STUDIO, 2084, LEFT, 2020)
DXGIMAP(RGB, STUDIO, 2084, NONE, 2020)
DXGIMAP(YCBCR, STUDIO, 2084, TOPLEFT, 2020)
DXGIMAP(YCBCR, STUDIO, HLG, TOPLEFT, 2020)
DXGIMAP(YCBCR, FULL, HLG, TOPLEFT, 2020)
/*DXGIMAP(YCBCR, FULL, 22, NONE, 2020, 601)*/
{DXGI_COLOR_SPACE_RESERVED, NULL, COLOR_AXIS_RGB, COLOR_PRIMARIES_UNDEF, TRANSFER_FUNC_UNDEF, COLOR_SPACE_UNDEF, 0},
#undef DXGIMAP
};
static bool canHandleConversion(const dxgi_color_space *src, const dxgi_color_space *dst)
{
if (src == dst)
return true;
if (src->primaries == COLOR_PRIMARIES_BT2020)
return true; /* we can convert BT2020 to 2020 or 709 */
if (dst->transfer == TRANSFER_FUNC_BT709)
return true; /* we can handle anything to 709 */
return false; /* let Windows do the rest */
}
void DXGI_SelectSwapchainColorspace(dxgi_swapchain *display, const libvlc_video_render_cfg_t *cfg, bool match_display)
{
HRESULT hr;
int best = 0;
int score, best_score = 0;
UINT support;
ComPtr<IDXGISwapChain3> dxgiswapChain3;
ComPtr<IDXGIOutput> dxgiOutput;
hr = display->dxgiswapChain.As(&dxgiswapChain3);
if (FAILED(hr)) {
msg_Warn(display->obj, "could not get a IDXGISwapChain3");
goto done;
}
/* pick the best output based on color support and transfer */
/* TODO support YUV output later */
best = -1;
for (int i=0; color_spaces[i].name; ++i)
{
hr = dxgiswapChain3->CheckColorSpaceSupport(color_spaces[i].dxgi, &support);
if (SUCCEEDED(hr) && support) {
if (!display->logged_capabilities)
msg_Dbg(display->obj, "supports colorspace %s", color_spaces[i].name);
score = 0;
if (color_spaces[i].primaries == (video_color_primaries_t) cfg->primaries)
score++;
if (color_spaces[i].color == (video_color_space_t) cfg->colorspace)
score += 2; /* we don't want to translate color spaces */
if (color_spaces[i].transfer == (video_transfer_func_t) cfg->transfer ||
/* favor 2084 output for HLG source */
(color_spaces[i].transfer == TRANSFER_FUNC_SMPTE_ST2084 && cfg->transfer == libvlc_video_transfer_func_HLG))
score++;
if (color_spaces[i].b_full_range == cfg->full_range)
score++;
if (score > best_score || (score && best == -1)) {
best = i;
best_score = score;
}
}
}
display->logged_capabilities = true;
if (best == -1)
{
best = 0;
msg_Warn(display->obj, "no matching colorspace found force %s", color_spaces[best].name);
}
display->dxgiswapChain.As(&display->dxgiswapChain4);
if (match_display)
if (SUCCEEDED(display->dxgiswapChain->GetContainingOutput(&dxgiOutput)))
{
ComPtr<IDXGIOutput6> dxgiOutput6;
if (SUCCEEDED(dxgiOutput.As(&dxgiOutput6)))
{
DXGI_OUTPUT_DESC1 desc1;
if (SUCCEEDED(dxgiOutput6->GetDesc1(&desc1 )))
{
const dxgi_color_space *csp = NULL;
for (int i=0; color_spaces[i].name; ++i)
{
if (color_spaces[i].dxgi == desc1.ColorSpace)
{
if (!canHandleConversion(&color_spaces[best], &color_spaces[i]))
msg_Warn(display->obj, "Can't handle conversion to screen format %s", color_spaces[i].name);
else
{
best = i;
csp = &color_spaces[i];
}
break;
}
}
msg_Dbg(display->obj, "Output max luminance: %.1f, colorspace %s, bits per pixel %d", desc1.MaxFullFrameLuminance, csp?csp->name:"unknown", desc1.BitsPerColor);
//sys->display.luminance_peak = desc1.MaxFullFrameLuminance;
}
}
}
hr = dxgiswapChain3->SetColorSpace1(color_spaces[best].dxgi);
if (SUCCEEDED(hr))
msg_Dbg(display->obj, "using colorspace %s", color_spaces[best].name);
else
msg_Err(display->obj, "Failed to set colorspace %s. (hr=0x%lX)", color_spaces[best].name, hr);
done:
display->colorspace = &color_spaces[best];
display->send_metadata = color_spaces[best].transfer == (video_transfer_func_t) cfg->transfer &&
color_spaces[best].primaries == (video_color_primaries_t) cfg->primaries &&
color_spaces[best].color == (video_color_space_t) cfg->colorspace;
}
static void FillSwapChainDesc(dxgi_swapchain *display, UINT width, UINT height, DXGI_SWAP_CHAIN_DESC1 *out)
{
ZeroMemory(out, sizeof(*out));
out->BufferCount = DXGI_SWAP_FRAME_COUNT;
out->BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
out->SampleDesc.Count = 1;
out->SampleDesc.Quality = 0;
out->Width = width;
out->Height = height;
out->Format = display->pixelFormat->formatTexture;
//out->Flags = 512; // DXGI_SWAP_CHAIN_FLAG_YUV_VIDEO;
#if (_WIN32_WINNT < _WIN32_WINNT_WIN10)
bool isWin10OrGreater = false;
HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll"));
if (likely(hKernel32 != NULL))
isWin10OrGreater = GetProcAddress(hKernel32, "GetSystemCpuSetInformation") != NULL;
if (isWin10OrGreater)
#endif // !_WIN32_WINNT_WIN10
out->SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
#if (_WIN32_WINNT < _WIN32_WINNT_WIN10)
else
{
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
bool isWin8OrGreater = false;
if (likely(hKernel32 != NULL))
isWin8OrGreater = GetProcAddress(hKernel32, "GetCurrentThreadStackLimits") != NULL;
if (isWin8OrGreater)
#endif // !_WIN32_WINNT_WIN8
out->SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
else
{
out->SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
out->BufferCount = 1;
}
#endif // !_WIN32_WINNT_WIN8
}
#endif // !_WIN32_WINNT_WIN10
}
static void DXGI_CreateSwapchainHwnd(dxgi_swapchain *display,
IDXGIAdapter *dxgiadapter, IUnknown *pFactoryDevice,
UINT width, UINT height)
{
vlc_assert(display->swapchainSurfaceType == SWAPCHAIN_SURFACE_HWND);
if (display->swapchainSurface.hwnd == NULL)
{
msg_Err(display->obj, "missing a HWND to create the swapchain");
return;
}
DXGI_SWAP_CHAIN_DESC1 scd;
FillSwapChainDesc(display, width, height, &scd);
ComPtr<IDXGIFactory2> dxgifactory;
HRESULT hr = dxgiadapter->GetParent(IID_GRAPHICS_PPV_ARGS(&dxgifactory));
if (FAILED(hr)) {
msg_Err(display->obj, "Could not get the DXGI Factory. (hr=0x%lX)", hr);
return;
}
hr = dxgifactory->CreateSwapChainForHwnd(pFactoryDevice,
display->swapchainSurface.hwnd, &scd,
NULL, NULL, &display->dxgiswapChain);
if (hr == DXGI_ERROR_INVALID_CALL && scd.Format == DXGI_FORMAT_R10G10B10A2_UNORM)
{
msg_Warn(display->obj, "10 bits swapchain failed, try 8 bits");
scd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
hr = dxgifactory->CreateSwapChainForHwnd(pFactoryDevice,
display->swapchainSurface.hwnd, &scd,
NULL, NULL, &display->dxgiswapChain);
}
if (FAILED(hr)) {
msg_Err(display->obj, "Could not create the SwapChain. (hr=0x%lX)", hr);
}
}
static void DXGI_CreateSwapchainDComp(dxgi_swapchain *display,
IDXGIAdapter *dxgiadapter, IUnknown *pFactoryDevice,
UINT width, UINT height)
{
vlc_assert(display->swapchainSurfaceType == SWAPCHAIN_SURFACE_DCOMP);
if (!display->swapchainSurface.dcomp.device || !display->swapchainSurface.dcomp.visual)
{
msg_Err(display->obj, "missing a HWND to create the swapchain");
return;
}
DXGI_SWAP_CHAIN_DESC1 scd;
FillSwapChainDesc(display, width, height, &scd);
scd.Scaling = DXGI_SCALING_STRETCH;
scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
scd.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
ComPtr<IDXGIFactory2> dxgifactory;
HRESULT hr = dxgiadapter->GetParent(IID_GRAPHICS_PPV_ARGS(&dxgifactory));
if (FAILED(hr)) {
msg_Err(display->obj, "Could not get the DXGI Factory. (hr=0x%lX)", hr);
return;
}
hr = dxgifactory->CreateSwapChainForComposition(pFactoryDevice,
&scd, NULL, &display->dxgiswapChain);
if (hr == DXGI_ERROR_INVALID_CALL && scd.Format == DXGI_FORMAT_R10G10B10A2_UNORM)
{
msg_Warn(display->obj, "10 bits swapchain failed, try 8 bits");
scd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
hr = dxgifactory->CreateSwapChainForComposition(pFactoryDevice,
&scd, NULL, &display->dxgiswapChain);
}
if (SUCCEEDED(hr)) {
display->swapchainSurface.dcomp.visual->SetContent(display->dxgiswapChain.Get());
display->swapchainSurface.dcomp.device->Commit();
} else {
msg_Err(display->obj, "Could not create the SwapChain. (hr=0x%lX)", hr);
}
}
void DXGI_LocalSwapchainSwap( dxgi_swapchain *display )
{
DXGI_PRESENT_PARAMETERS presentParams = { };
HRESULT hr = display->dxgiswapChain->Present1(0, 0, &presentParams );
if ( hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET )
{
/* TODO device lost */
msg_Err( display->obj, "SwapChain Present failed. (hr=0x%lX)", hr );
}
}
void DXGI_LocalSwapchainSetMetadata( dxgi_swapchain *display, libvlc_video_metadata_type_t type, const void *metadata )
{
assert(type == libvlc_video_metadata_frame_hdr10);
if ( type == libvlc_video_metadata_frame_hdr10 && metadata &&
display->send_metadata && display->dxgiswapChain4.Get() )
{
const libvlc_video_frame_hdr10_metadata_t *p_hdr10 = static_cast<const libvlc_video_frame_hdr10_metadata_t *>(metadata);
DXGI_HDR_METADATA_HDR10 hdr10 = { };
hdr10.GreenPrimary[0] = p_hdr10->GreenPrimary[0];
hdr10.GreenPrimary[1] = p_hdr10->GreenPrimary[1];
hdr10.BluePrimary[0] = p_hdr10->BluePrimary[0];
hdr10.BluePrimary[1] = p_hdr10->BluePrimary[1];
hdr10.RedPrimary[0] = p_hdr10->RedPrimary[0];
hdr10.RedPrimary[1] = p_hdr10->RedPrimary[1];
hdr10.WhitePoint[0] = p_hdr10->WhitePoint[0];
hdr10.WhitePoint[1] = p_hdr10->WhitePoint[1];
hdr10.MaxMasteringLuminance = p_hdr10->MaxMasteringLuminance / 10000;
hdr10.MinMasteringLuminance = p_hdr10->MinMasteringLuminance;
hdr10.MaxContentLightLevel = p_hdr10->MaxContentLightLevel;
hdr10.MaxFrameAverageLightLevel = p_hdr10->MaxFrameAverageLightLevel;
if (memcmp(&display->hdr10, &hdr10, sizeof(hdr10)))
{
memcpy(&display->hdr10, &hdr10, sizeof(hdr10));
display->dxgiswapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10,
sizeof( &display->hdr10 ), &display->hdr10 );
}
}
}
dxgi_swapchain *DXGI_CreateLocalSwapchainHandleHwnd(vlc_object_t *o, HWND hwnd)
{
dxgi_swapchain *display = new (std::nothrow) dxgi_swapchain();
if (unlikely(display == NULL))
return NULL;
display->obj = o;
display->swapchainSurfaceType = SWAPCHAIN_SURFACE_HWND;
display->swapchainSurface.hwnd = hwnd;
return display;
}
dxgi_swapchain *DXGI_CreateLocalSwapchainHandleDComp(vlc_object_t *o, void* dcompDevice, void* dcompVisual)
{
dxgi_swapchain *display = new (std::nothrow) dxgi_swapchain();
if (unlikely(display == NULL))
return NULL;
display->obj = o;
display->swapchainSurfaceType = SWAPCHAIN_SURFACE_DCOMP;
display->swapchainSurface.dcomp.device = static_cast<IDCompositionDevice*>(dcompDevice);
display->swapchainSurface.dcomp.visual = static_cast<IDCompositionVisual*>(dcompVisual);
return display;
}
void DXGI_LocalSwapchainCleanupDevice( dxgi_swapchain *display )
{
delete display;
}
void DXGI_SwapchainUpdateOutput( dxgi_swapchain *display, libvlc_video_output_cfg_t *out )
{
out->u.dxgi_format = display->pixelFormat->formatTexture;
out->full_range = display->colorspace->b_full_range;
out->colorspace = (libvlc_video_color_space_t) display->colorspace->color;
out->primaries = (libvlc_video_color_primaries_t) display->colorspace->primaries;
out->transfer = (libvlc_video_transfer_func_t) display->colorspace->transfer;
out->orientation = libvlc_video_orient_top_left;
}
bool DXGI_UpdateSwapChain( dxgi_swapchain *display, IDXGIAdapter *dxgiadapter,
IUnknown *pFactoryDevice,
const d3d_format_t *newPixelFormat, const libvlc_video_render_cfg_t *cfg,
bool match_display )
{
// 0 dimensions are not allowed, a value of 8 is used otherwise
UINT width = cfg->width ? cfg->width : 8;
UINT height = cfg->height ? cfg->height : 8;
if (display->dxgiswapChain.Get() && display->pixelFormat != newPixelFormat)
{
// the pixel format changed, we need a new swapchain
display->dxgiswapChain.Reset();
display->logged_capabilities = false;
}
if ( !display->dxgiswapChain.Get() )
{
display->pixelFormat = newPixelFormat;
if (display->swapchainSurfaceType == SWAPCHAIN_SURFACE_DCOMP)
DXGI_CreateSwapchainDComp(display, dxgiadapter, pFactoryDevice,
width, height);
else // SWAPCHAIN_TARGET_HWND
DXGI_CreateSwapchainHwnd(display, dxgiadapter, pFactoryDevice,
width, height);
}
if ( !display->dxgiswapChain.Get() )
return false;
/* TODO detect is the size is the same as the output and switch to fullscreen mode */
HRESULT hr;
hr = display->dxgiswapChain->ResizeBuffers(0, width, height,
DXGI_FORMAT_UNKNOWN, 0 );
if ( FAILED( hr ) ) {
msg_Err( display->obj, "Failed to resize the backbuffer. (hr=0x%lX)", hr );
return false;
}
DXGI_SelectSwapchainColorspace(display, cfg, match_display);
return true;
}
Microsoft::WRL::ComPtr<IDXGISwapChain1> & DXGI_GetSwapChain1( dxgi_swapchain *display )
{
return display->dxgiswapChain;
}
Microsoft::WRL::ComPtr<IDXGISwapChain4> & DXGI_GetSwapChain4( dxgi_swapchain *display )
{
return display->dxgiswapChain4;
}
const d3d_format_t *DXGI_GetPixelFormat( dxgi_swapchain *display )
{
return display->pixelFormat;
}
¤ Dauer der Verarbeitung: 0.7 Sekunden
¤
*© Formatika GbR, Deutschland
|
|