mpv/video/out/gpu/d3d11_helpers.c

1040 lines
37 KiB
C

/*
* This file is part of mpv.
*
* mpv 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.
*
* mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <windows.h>
#include <d3d11.h>
#include <dxgi1_6.h>
#include <versionhelpers.h>
#include "common/common.h"
#include "common/msg.h"
#include "misc/bstr.h"
#include "osdep/io.h"
#include "osdep/threads.h"
#include "osdep/windows_utils.h"
#include "d3d11_helpers.h"
// Windows 8 enum value, not present in mingw-w64 headers
#define DXGI_ADAPTER_FLAG_SOFTWARE (2)
typedef HRESULT(WINAPI *PFN_CREATE_DXGI_FACTORY)(REFIID riid, void **ppFactory);
typedef HRESULT(WINAPI *PFN_DXGI_GET_DEBUG_INTERFACE)(REFIID riid, void **ppDebug);
static mp_once d3d11_once = MP_STATIC_ONCE_INITIALIZER;
static PFN_D3D11_CREATE_DEVICE pD3D11CreateDevice = NULL;
static PFN_CREATE_DXGI_FACTORY pCreateDXGIFactory1 = NULL;
static PFN_DXGI_GET_DEBUG_INTERFACE pDXGIGetDebugInterface = NULL;
static void d3d11_load(void)
{
HMODULE d3d11 = LoadLibraryW(L"d3d11.dll");
HMODULE dxgilib = LoadLibraryW(L"dxgi.dll");
HMODULE dxgidebuglib = LoadLibraryW(L"dxgidebug.dll");
if (!d3d11 || !dxgilib)
return;
pD3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)
GetProcAddress(d3d11, "D3D11CreateDevice");
pCreateDXGIFactory1 = (PFN_CREATE_DXGI_FACTORY)
GetProcAddress(dxgilib, "CreateDXGIFactory1");
if (dxgidebuglib) {
pDXGIGetDebugInterface = (PFN_DXGI_GET_DEBUG_INTERFACE)
GetProcAddress(dxgidebuglib, "DXGIGetDebugInterface");
}
}
static bool load_d3d11_functions(struct mp_log *log)
{
mp_exec_once(&d3d11_once, d3d11_load);
if (!pD3D11CreateDevice || !pCreateDXGIFactory1) {
mp_fatal(log, "Failed to load base d3d11 functionality: "
"CreateDevice: %s, CreateDXGIFactory1: %s\n",
pD3D11CreateDevice ? "success" : "failure",
pCreateDXGIFactory1 ? "success": "failure");
return false;
}
return true;
}
#define D3D11_DXGI_ENUM(prefix, define) { case prefix ## define: return #define; }
static const char *d3d11_get_format_name(DXGI_FORMAT fmt)
{
switch (fmt) {
D3D11_DXGI_ENUM(DXGI_FORMAT_, UNKNOWN);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32A32_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32A32_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32A32_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32A32_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_SNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G8X24_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, D32_FLOAT_S8X24_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_FLOAT_X8X24_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, X32_TYPELESS_G8X24_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R10G10B10A2_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R10G10B10A2_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R10G10B10A2_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R11G11B10_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_UNORM_SRGB);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_SNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_SNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, D32_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R24G8_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, D24_UNORM_S8_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R24_UNORM_X8_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, X24_TYPELESS_G8_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_SNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_FLOAT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, D16_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_SNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_UINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_SNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_SINT);
D3D11_DXGI_ENUM(DXGI_FORMAT_, A8_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R1_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R9G9B9E5_SHAREDEXP);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_B8G8_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, G8R8_G8B8_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC1_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC1_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC1_UNORM_SRGB);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC2_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC2_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC2_UNORM_SRGB);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC3_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC3_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC3_UNORM_SRGB);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC4_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC4_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC4_SNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC5_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC5_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC5_SNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B5G6R5_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B5G5R5A1_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8A8_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8X8_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, R10G10B10_XR_BIAS_A2_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8A8_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8A8_UNORM_SRGB);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8X8_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8X8_UNORM_SRGB);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC6H_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC6H_UF16);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC6H_SF16);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC7_TYPELESS);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC7_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, BC7_UNORM_SRGB);
D3D11_DXGI_ENUM(DXGI_FORMAT_, AYUV);
D3D11_DXGI_ENUM(DXGI_FORMAT_, Y410);
D3D11_DXGI_ENUM(DXGI_FORMAT_, Y416);
D3D11_DXGI_ENUM(DXGI_FORMAT_, NV12);
D3D11_DXGI_ENUM(DXGI_FORMAT_, P010);
D3D11_DXGI_ENUM(DXGI_FORMAT_, P016);
D3D11_DXGI_ENUM(DXGI_FORMAT_, 420_OPAQUE);
D3D11_DXGI_ENUM(DXGI_FORMAT_, YUY2);
D3D11_DXGI_ENUM(DXGI_FORMAT_, Y210);
D3D11_DXGI_ENUM(DXGI_FORMAT_, Y216);
D3D11_DXGI_ENUM(DXGI_FORMAT_, NV11);
D3D11_DXGI_ENUM(DXGI_FORMAT_, AI44);
D3D11_DXGI_ENUM(DXGI_FORMAT_, IA44);
D3D11_DXGI_ENUM(DXGI_FORMAT_, P8);
D3D11_DXGI_ENUM(DXGI_FORMAT_, A8P8);
D3D11_DXGI_ENUM(DXGI_FORMAT_, B4G4R4A4_UNORM);
D3D11_DXGI_ENUM(DXGI_FORMAT_, P208);
D3D11_DXGI_ENUM(DXGI_FORMAT_, V208);
D3D11_DXGI_ENUM(DXGI_FORMAT_, V408);
D3D11_DXGI_ENUM(DXGI_FORMAT_, FORCE_UINT);
default:
return "<Unknown>";
}
}
static const char *d3d11_get_csp_name(DXGI_COLOR_SPACE_TYPE csp)
{
switch (csp) {
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_FULL_G22_NONE_P709);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_FULL_G10_NONE_P709);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G22_NONE_P709);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G22_NONE_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RESERVED);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_G22_NONE_P709_X601);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G22_LEFT_P601);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_G22_LEFT_P601);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G22_LEFT_P709);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_G22_LEFT_P709);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G22_LEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_G22_LEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_FULL_G2084_NONE_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G2084_LEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G2084_NONE_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G22_TOPLEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G2084_TOPLEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_FULL_G22_NONE_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_GHLG_TOPLEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_GHLG_TOPLEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G24_NONE_P709);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G24_NONE_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G24_LEFT_P709);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G24_LEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G24_TOPLEFT_P2020);
D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, CUSTOM);
default:
return "<Unknown>";
}
}
static bool d3d11_get_mp_csp(DXGI_COLOR_SPACE_TYPE csp,
struct pl_color_space *pl_color_system)
{
if (!pl_color_system)
return false;
// Colorspaces utilizing gamma 2.2 (G22) are set to
// AUTO as that keeps the current default flow regarding
// SDR transfer function handling.
// (no adjustment is done unless the user has a CMS LUT).
//
// Additionally, only set primary information with colorspaces
// utilizing non-709 primaries to keep the current behavior
// regarding not doing conversion from BT.601 to BT.709.
switch (csp) {
case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709:
*pl_color_system = (struct pl_color_space){
.transfer = PL_COLOR_TRC_UNKNOWN,
.primaries = PL_COLOR_PRIM_UNKNOWN,
};
break;
case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709:
*pl_color_system = (struct pl_color_space) {
.transfer = PL_COLOR_TRC_LINEAR,
.primaries = PL_COLOR_PRIM_UNKNOWN,
};
break;
case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020:
*pl_color_system = (struct pl_color_space) {
.transfer = PL_COLOR_TRC_PQ,
.primaries = PL_COLOR_PRIM_BT_2020,
};
break;
case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020:
*pl_color_system = (struct pl_color_space) {
.transfer = PL_COLOR_TRC_UNKNOWN,
.primaries = PL_COLOR_PRIM_BT_2020,
};
break;
default:
return false;
}
return true;
}
static bool query_output_format_and_colorspace(struct mp_log *log,
IDXGISwapChain *swapchain,
DXGI_FORMAT *out_fmt,
DXGI_COLOR_SPACE_TYPE *out_cspace)
{
IDXGIOutput *output = NULL;
IDXGIOutput6 *output6 = NULL;
DXGI_OUTPUT_DESC1 desc = { 0 };
char *monitor_name = NULL;
bool success = false;
if (!out_fmt || !out_cspace)
return false;
if (!mp_get_dxgi_output_desc(swapchain, &desc)) {
mp_err(log, "Failed to query swap chain's output information\n");
goto done;
}
monitor_name = mp_to_utf8(NULL, desc.DeviceName);
mp_verbose(log, "Queried output: %s, %ldx%ld @ %d bits, colorspace: %s (%d)\n",
monitor_name,
desc.DesktopCoordinates.right - desc.DesktopCoordinates.left,
desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top,
desc.BitsPerColor,
d3d11_get_csp_name(desc.ColorSpace),
desc.ColorSpace);
*out_cspace = desc.ColorSpace;
// limit ourselves to the 8bit and 10bit formats for now.
// while the 16bit float format would be preferable as something
// to default to, it seems to be hard-coded to linear transfer
// in windowed mode, and follows configured colorspace in full screen.
*out_fmt = desc.BitsPerColor > 8 ?
DXGI_FORMAT_R10G10B10A2_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM;
success = true;
done:
talloc_free(monitor_name);
SAFE_RELEASE(output6);
SAFE_RELEASE(output);
return success;
}
// Get a const array of D3D_FEATURE_LEVELs from max_fl to min_fl (inclusive)
static int get_feature_levels(int max_fl, int min_fl,
const D3D_FEATURE_LEVEL **out)
{
static const D3D_FEATURE_LEVEL levels[] = {
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};
static const int levels_len = MP_ARRAY_SIZE(levels);
int start = 0;
for (; start < levels_len; start++) {
if (levels[start] <= max_fl)
break;
}
int len = 0;
for (; start + len < levels_len; len++) {
if (levels[start + len] < min_fl)
break;
}
*out = &levels[start];
return len;
}
IDXGIAdapter1 *mp_get_dxgi_adapter(struct mp_log *log,
bstr requested_adapter_name,
bstr *listing)
{
HRESULT hr = S_OK;
IDXGIFactory1 *factory;
IDXGIAdapter1 *picked_adapter = NULL;
hr = pCreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&factory);
if (FAILED(hr)) {
mp_fatal(log, "Failed to create a DXGI factory: %s\n",
mp_HRESULT_to_str(hr));
return NULL;
}
for (unsigned int adapter_num = 0; hr != DXGI_ERROR_NOT_FOUND; adapter_num++)
{
IDXGIAdapter1 *adapter = NULL;
DXGI_ADAPTER_DESC1 desc = { 0 };
char *adapter_description = NULL;
hr = IDXGIFactory1_EnumAdapters1(factory, adapter_num, &adapter);
if (FAILED(hr)) {
if (hr != DXGI_ERROR_NOT_FOUND) {
mp_fatal(log, "Failed to enumerate at adapter %u\n",
adapter_num);
}
continue;
}
if (FAILED(IDXGIAdapter1_GetDesc1(adapter, &desc))) {
mp_fatal(log, "Failed to get adapter description when listing at adapter %u\n",
adapter_num);
continue;
}
adapter_description = mp_to_utf8(NULL, desc.Description);
if (listing) {
bstr_xappend_asprintf(NULL, listing,
"Adapter %u: vendor: %u, description: %s\n",
adapter_num, desc.VendorId,
adapter_description);
}
if (requested_adapter_name.len &&
bstr_case_startswith(bstr0(adapter_description),
requested_adapter_name))
{
picked_adapter = adapter;
}
talloc_free(adapter_description);
if (picked_adapter) {
break;
}
SAFE_RELEASE(adapter);
}
SAFE_RELEASE(factory);
return picked_adapter;
}
int mp_dxgi_validate_adapter(struct mp_log *log,
const struct m_option *opt,
struct bstr name, const char **value)
{
struct bstr param = bstr0(*value);
bool help = bstr_equals0(param, "help");
bool adapter_matched = false;
struct bstr listing = { 0 };
if (bstr_equals0(param, "")) {
return 0;
}
adapter_matched = mp_dxgi_list_or_verify_adapters(log,
help ? bstr0(NULL) : param,
help ? &listing : NULL);
if (help) {
mp_info(log, "Available DXGI adapters:\n%.*s",
BSTR_P(listing));
talloc_free(listing.start);
return M_OPT_EXIT;
}
if (!adapter_matched) {
mp_err(log, "No adapter matching '%.*s'!\n", BSTR_P(param));
}
return adapter_matched ? 0 : M_OPT_INVALID;
}
static HRESULT create_device(struct mp_log *log, IDXGIAdapter1 *adapter,
bool warp, bool debug, int max_fl, int min_fl,
ID3D11Device **dev)
{
const D3D_FEATURE_LEVEL *levels;
int levels_len = get_feature_levels(max_fl, min_fl, &levels);
if (!levels_len) {
mp_fatal(log, "No suitable Direct3D feature level found\n");
return E_FAIL;
}
D3D_DRIVER_TYPE type = warp ? D3D_DRIVER_TYPE_WARP
: D3D_DRIVER_TYPE_HARDWARE;
UINT flags = debug ? D3D11_CREATE_DEVICE_DEBUG : 0;
return pD3D11CreateDevice((IDXGIAdapter *)adapter, adapter ? D3D_DRIVER_TYPE_UNKNOWN : type,
NULL, flags, levels, levels_len, D3D11_SDK_VERSION, dev, NULL, NULL);
}
bool mp_dxgi_list_or_verify_adapters(struct mp_log *log,
bstr adapter_name,
bstr *listing)
{
IDXGIAdapter1 *picked_adapter = NULL;
if (!load_d3d11_functions(log)) {
return false;
}
if ((picked_adapter = mp_get_dxgi_adapter(log, adapter_name, listing))) {
SAFE_RELEASE(picked_adapter);
return true;
}
return false;
}
// Create a Direct3D 11 device for rendering and presentation. This is meant to
// reduce boilerplate in backends that D3D11, while also making sure they share
// the same device creation logic and log the same information.
bool mp_d3d11_create_present_device(struct mp_log *log,
struct d3d11_device_opts *opts,
ID3D11Device **dev_out)
{
bool debug = opts->debug;
bool warp = opts->force_warp;
int max_fl = opts->max_feature_level;
int min_fl = opts->min_feature_level;
// Normalize nullptr and an empty string to nullptr to simplify handling.
char *adapter_name = (opts->adapter_name && *(opts->adapter_name)) ?
opts->adapter_name : NULL;
ID3D11Device *dev = NULL;
IDXGIDevice1 *dxgi_dev = NULL;
IDXGIAdapter1 *adapter = NULL;
bool success = false;
HRESULT hr;
if (!load_d3d11_functions(log)) {
goto done;
}
adapter = mp_get_dxgi_adapter(log, bstr0(adapter_name), NULL);
if (adapter_name && !adapter) {
mp_warn(log, "Adapter matching '%s' was not found in the system! "
"Will fall back to the default adapter.\n",
adapter_name);
}
// Return here to retry creating the device
do {
// Use these default feature levels if they are not set
max_fl = max_fl ? max_fl : D3D_FEATURE_LEVEL_11_0;
min_fl = min_fl ? min_fl : D3D_FEATURE_LEVEL_9_1;
hr = create_device(log, adapter, warp, debug, max_fl, min_fl, &dev);
// Retry without debug, if SDK is not available
if (debug && hr == DXGI_ERROR_SDK_COMPONENT_MISSING) {
mp_warn(log, "gpu-debug disabled due to error: %s\n", mp_HRESULT_to_str(hr));
debug = false;
continue;
}
if (SUCCEEDED(hr))
break;
// Trying to create a D3D_FEATURE_LEVEL_12_0 device on Windows 8.1 or
// below will not succeed. Try an 11_1 device.
if (max_fl >= D3D_FEATURE_LEVEL_12_0 &&
min_fl <= D3D_FEATURE_LEVEL_11_1)
{
mp_dbg(log, "Failed to create 12_0+ device, trying 11_1\n");
max_fl = D3D_FEATURE_LEVEL_11_1;
continue;
}
// Trying to create a D3D_FEATURE_LEVEL_11_1 device on Windows 7
// without the platform update will not succeed. Try an 11_0 device.
if (max_fl >= D3D_FEATURE_LEVEL_11_1 &&
min_fl <= D3D_FEATURE_LEVEL_11_0)
{
mp_dbg(log, "Failed to create 11_1+ device, trying 11_0\n");
max_fl = D3D_FEATURE_LEVEL_11_0;
continue;
}
// Retry with WARP if allowed
if (!warp && opts->allow_warp) {
mp_dbg(log, "Failed to create hardware device, trying WARP\n");
warp = true;
max_fl = opts->max_feature_level;
min_fl = opts->min_feature_level;
continue;
}
mp_fatal(log, "Failed to create Direct3D 11 device: %s\n",
mp_HRESULT_to_str(hr));
goto done;
} while (true);
// if we picked an adapter, release it here - we're taking another
// from the device.
SAFE_RELEASE(adapter);
hr = ID3D11Device_QueryInterface(dev, &IID_IDXGIDevice1, (void**)&dxgi_dev);
if (FAILED(hr)) {
mp_fatal(log, "Failed to get DXGI device\n");
goto done;
}
hr = IDXGIDevice1_GetParent(dxgi_dev, &IID_IDXGIAdapter1, (void**)&adapter);
if (FAILED(hr)) {
mp_fatal(log, "Failed to get DXGI adapter\n");
goto done;
}
IDXGIDevice1_SetMaximumFrameLatency(dxgi_dev, opts->max_frame_latency);
DXGI_ADAPTER_DESC1 desc;
hr = IDXGIAdapter1_GetDesc1(adapter, &desc);
if (FAILED(hr)) {
mp_fatal(log, "Failed to get adapter description\n");
goto done;
}
D3D_FEATURE_LEVEL selected_level = ID3D11Device_GetFeatureLevel(dev);
mp_verbose(log, "Using Direct3D 11 feature level %u_%u\n",
((unsigned)selected_level) >> 12,
(((unsigned)selected_level) >> 8) & 0xf);
char *dev_name = mp_to_utf8(NULL, desc.Description);
mp_verbose(log, "Device Name: %s\n"
"Device ID: %04x:%04x (rev %02x)\n"
"Subsystem ID: %04x:%04x\n"
"LUID: %08lx%08lx\n",
dev_name,
desc.VendorId, desc.DeviceId, desc.Revision,
LOWORD(desc.SubSysId), HIWORD(desc.SubSysId),
desc.AdapterLuid.HighPart, desc.AdapterLuid.LowPart);
talloc_free(dev_name);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
warp = true;
// If the primary display adapter is a software adapter, the
// DXGI_ADAPTER_FLAG_SOFTWARE flag won't be set, but the device IDs should
// still match the Microsoft Basic Render Driver
if (desc.VendorId == 0x1414 && desc.DeviceId == 0x8c)
warp = true;
if (warp) {
mp_msg(log, opts->force_warp ? MSGL_V : MSGL_WARN,
"Using a software adapter\n");
}
*dev_out = dev;
dev = NULL;
success = true;
done:
SAFE_RELEASE(adapter);
SAFE_RELEASE(dxgi_dev);
SAFE_RELEASE(dev);
return success;
}
static HRESULT create_swapchain_1_2(ID3D11Device *dev, IDXGIFactory2 *factory,
struct mp_log *log,
struct d3d11_swapchain_opts *opts,
bool flip, DXGI_FORMAT format,
IDXGISwapChain **swapchain_out)
{
IDXGISwapChain *swapchain = NULL;
IDXGISwapChain1 *swapchain1 = NULL;
HRESULT hr;
DXGI_SWAP_CHAIN_DESC1 desc = {
.Width = opts->width ? opts->width : 1,
.Height = opts->height ? opts->height : 1,
.Format = format,
.SampleDesc = { .Count = 1 },
.BufferUsage = opts->usage,
};
if (flip) {
// UNORDERED_ACCESS with FLIP_SEQUENTIAL seems to be buggy with
// Windows 7 drivers
if ((desc.BufferUsage & DXGI_USAGE_UNORDERED_ACCESS) &&
!IsWindows8OrGreater())
{
mp_verbose(log, "Disabling UNORDERED_ACCESS for flip-model "
"swapchain backbuffers in Windows 7\n");
desc.BufferUsage &= ~DXGI_USAGE_UNORDERED_ACCESS;
}
if (IsWindows10OrGreater()) {
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
} else {
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
}
desc.BufferCount = opts->length;
} else {
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
desc.BufferCount = 1;
}
hr = IDXGIFactory2_CreateSwapChainForHwnd(factory, (IUnknown*)dev,
opts->window, &desc, NULL, NULL, &swapchain1);
if (FAILED(hr))
goto done;
hr = IDXGISwapChain1_QueryInterface(swapchain1, &IID_IDXGISwapChain,
(void**)&swapchain);
if (FAILED(hr))
goto done;
*swapchain_out = swapchain;
swapchain = NULL;
done:
SAFE_RELEASE(swapchain1);
SAFE_RELEASE(swapchain);
return hr;
}
static HRESULT create_swapchain_1_1(ID3D11Device *dev, IDXGIFactory1 *factory,
struct mp_log *log,
struct d3d11_swapchain_opts *opts,
DXGI_FORMAT format,
IDXGISwapChain **swapchain_out)
{
DXGI_SWAP_CHAIN_DESC desc = {
.BufferDesc = {
.Width = opts->width ? opts->width : 1,
.Height = opts->height ? opts->height : 1,
.Format = format,
},
.SampleDesc = { .Count = 1 },
.BufferUsage = opts->usage,
.BufferCount = 1,
.OutputWindow = opts->window,
.Windowed = TRUE,
.SwapEffect = DXGI_SWAP_EFFECT_DISCARD,
};
return IDXGIFactory1_CreateSwapChain(factory, (IUnknown*)dev, &desc,
swapchain_out);
}
static bool update_swapchain_format(struct mp_log *log,
IDXGISwapChain *swapchain,
DXGI_FORMAT format)
{
DXGI_SWAP_CHAIN_DESC desc;
HRESULT hr = IDXGISwapChain_GetDesc(swapchain, &desc);
if (FAILED(hr)) {
mp_fatal(log, "Failed to query swap chain's current state: %s\n",
mp_HRESULT_to_str(hr));
return false;
}
hr = IDXGISwapChain_ResizeBuffers(swapchain, 0, desc.BufferDesc.Width,
desc.BufferDesc.Height,
format, 0);
if (FAILED(hr)) {
mp_fatal(log, "Couldn't update swapchain format: %s\n",
mp_HRESULT_to_str(hr));
return false;
}
return true;
}
static bool update_swapchain_color_space(struct mp_log *log,
IDXGISwapChain *swapchain,
DXGI_COLOR_SPACE_TYPE color_space)
{
IDXGISwapChain4 *swapchain4 = NULL;
const char *csp_name = d3d11_get_csp_name(color_space);
bool success = false;
HRESULT hr = E_FAIL;
unsigned int csp_support_flags;
hr = IDXGISwapChain_QueryInterface(swapchain, &IID_IDXGISwapChain4,
(void *)&(swapchain4));
if (FAILED(hr)) {
mp_err(log, "Failed to create v4 swapchain for color space "
"configuration (%s)!\n",
mp_HRESULT_to_str(hr));
goto done;
}
hr = IDXGISwapChain4_CheckColorSpaceSupport(swapchain4,
color_space,
&csp_support_flags);
if (FAILED(hr)) {
mp_err(log, "Failed to check color space support for color space "
"%s (%d): %s!\n",
csp_name, color_space, mp_HRESULT_to_str(hr));
goto done;
}
mp_verbose(log,
"Swapchain capabilities for color space %s (%d): "
"normal: %s, overlay: %s\n",
csp_name, color_space,
(csp_support_flags & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) ?
"yes" : "no",
(csp_support_flags & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_OVERLAY_PRESENT) ?
"yes" : "no");
if (!(csp_support_flags & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) {
mp_err(log, "Color space %s (%d) is not supported by this swapchain!\n",
csp_name, color_space);
goto done;
}
hr = IDXGISwapChain4_SetColorSpace1(swapchain4, color_space);
if (FAILED(hr)) {
mp_err(log, "Failed to set color space %s (%d) for this swapchain "
"(%s)!\n",
csp_name, color_space, mp_HRESULT_to_str(hr));
goto done;
}
mp_verbose(log, "Swapchain successfully configured to color space %s (%d)!\n",
csp_name, color_space);
success = true;
done:
SAFE_RELEASE(swapchain4);
return success;
}
static bool configure_created_swapchain(struct mp_log *log,
IDXGISwapChain *swapchain,
DXGI_FORMAT requested_format,
DXGI_COLOR_SPACE_TYPE requested_csp,
struct pl_color_space *configured_csp)
{
DXGI_FORMAT probed_format = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT selected_format = DXGI_FORMAT_UNKNOWN;
DXGI_COLOR_SPACE_TYPE probed_colorspace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
DXGI_COLOR_SPACE_TYPE selected_colorspace;
const char *format_name = NULL;
const char *csp_name = NULL;
struct pl_color_space pl_color_system = { 0 };
bool mp_csp_mapped = false;
query_output_format_and_colorspace(log, swapchain,
&probed_format,
&probed_colorspace);
selected_format = requested_format != DXGI_FORMAT_UNKNOWN ?
requested_format :
(probed_format != DXGI_FORMAT_UNKNOWN ?
probed_format : DXGI_FORMAT_R8G8B8A8_UNORM);
selected_colorspace = requested_csp != -1 ?
requested_csp : probed_colorspace;
format_name = d3d11_get_format_name(selected_format);
csp_name = d3d11_get_csp_name(selected_colorspace);
mp_csp_mapped = d3d11_get_mp_csp(selected_colorspace, &pl_color_system);
mp_verbose(log, "Selected swapchain format %s (%d), attempting "
"to utilize it.\n",
format_name, selected_format);
if (!update_swapchain_format(log, swapchain, selected_format)) {
return false;
}
if (!IsWindows10OrGreater()) {
// On older than Windows 10, query_output_format_and_colorspace
// will not change probed_colorspace, and even if a user sets
// a colorspace it will not get applied. Thus warn user in case a
// value was specifically set and finish.
if (requested_csp != -1) {
mp_warn(log, "User selected a D3D11 color space %s (%d), "
"but configuration of color spaces is only supported"
"from Windows 10! The default configuration has been "
"left as-is.\n",
csp_name, selected_colorspace);
}
return true;
}
if (!mp_csp_mapped) {
mp_warn(log, "Color space %s (%d) does not have an mpv color space "
"mapping! Overriding to standard sRGB!\n",
csp_name, selected_colorspace);
selected_colorspace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
d3d11_get_mp_csp(selected_colorspace, &pl_color_system);
}
mp_verbose(log, "Selected swapchain color space %s (%d), attempting to "
"utilize it.\n",
csp_name, selected_colorspace);
if (!update_swapchain_color_space(log, swapchain, selected_colorspace)) {
return false;
}
if (configured_csp) {
*configured_csp = pl_color_system;
}
return true;
}
// Create a Direct3D 11 swapchain
bool mp_d3d11_create_swapchain(ID3D11Device *dev, struct mp_log *log,
struct d3d11_swapchain_opts *opts,
IDXGISwapChain **swapchain_out)
{
IDXGIDevice1 *dxgi_dev = NULL;
IDXGIAdapter1 *adapter = NULL;
IDXGIFactory1 *factory = NULL;
IDXGIFactory2 *factory2 = NULL;
IDXGISwapChain *swapchain = NULL;
bool success = false;
HRESULT hr;
hr = ID3D11Device_QueryInterface(dev, &IID_IDXGIDevice1, (void**)&dxgi_dev);
if (FAILED(hr)) {
mp_fatal(log, "Failed to get DXGI device\n");
goto done;
}
hr = IDXGIDevice1_GetParent(dxgi_dev, &IID_IDXGIAdapter1, (void**)&adapter);
if (FAILED(hr)) {
mp_fatal(log, "Failed to get DXGI adapter\n");
goto done;
}
hr = IDXGIAdapter1_GetParent(adapter, &IID_IDXGIFactory1, (void**)&factory);
if (FAILED(hr)) {
mp_fatal(log, "Failed to get DXGI factory\n");
goto done;
}
hr = IDXGIFactory1_QueryInterface(factory, &IID_IDXGIFactory2,
(void**)&factory2);
if (FAILED(hr))
factory2 = NULL;
bool flip = factory2 && opts->flip;
// Return here to retry creating the swapchain
do {
if (factory2) {
// Create a DXGI 1.2+ (Windows 8+) swap chain if possible
hr = create_swapchain_1_2(dev, factory2, log, opts, flip,
DXGI_FORMAT_R8G8B8A8_UNORM, &swapchain);
} else {
// Fall back to DXGI 1.1 (Windows 7)
hr = create_swapchain_1_1(dev, factory, log, opts,
DXGI_FORMAT_R8G8B8A8_UNORM, &swapchain);
}
if (SUCCEEDED(hr))
break;
if (flip) {
mp_dbg(log, "Failed to create flip-model swapchain, trying bitblt\n");
flip = false;
continue;
}
mp_fatal(log, "Failed to create swapchain: %s\n", mp_HRESULT_to_str(hr));
goto done;
} while (true);
// Prevent DXGI from making changes to the VO window, otherwise it will
// hook the Alt+Enter keystroke and make it trigger an ugly transition to
// exclusive fullscreen mode instead of running the user-set command.
IDXGIFactory_MakeWindowAssociation(factory, opts->window,
DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER |
DXGI_MWA_NO_PRINT_SCREEN);
if (factory2) {
mp_verbose(log, "Using DXGI 1.2+\n");
} else {
mp_verbose(log, "Using DXGI 1.1\n");
}
configure_created_swapchain(log, swapchain, opts->format,
opts->color_space,
opts->configured_csp);
DXGI_SWAP_CHAIN_DESC scd = {0};
IDXGISwapChain_GetDesc(swapchain, &scd);
if (scd.SwapEffect == DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL ||
scd.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD)
{
mp_verbose(log, "Using flip-model presentation\n");
} else {
mp_verbose(log, "Using bitblt-model presentation\n");
}
*swapchain_out = swapchain;
swapchain = NULL;
success = true;
done:
SAFE_RELEASE(swapchain);
SAFE_RELEASE(factory2);
SAFE_RELEASE(factory);
SAFE_RELEASE(adapter);
SAFE_RELEASE(dxgi_dev);
return success;
}
bool mp_get_dxgi_output_desc(IDXGISwapChain *swapchain, DXGI_OUTPUT_DESC1 *desc)
{
bool ret = false;
IDXGIOutput *output = NULL;
IDXGIOutput6 *output6 = NULL;
if (FAILED(IDXGISwapChain_GetContainingOutput(swapchain, &output)))
goto done;
if (FAILED(IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput6, (void**)&output6)))
goto done;
ret = SUCCEEDED(IDXGIOutput6_GetDesc1(output6, desc));
done:
SAFE_RELEASE(output);
SAFE_RELEASE(output6);
return ret;
}
void mp_d3d11_get_debug_interfaces(struct mp_log *log, IDXGIDebug **debug,
IDXGIInfoQueue **iqueue)
{
load_d3d11_functions(log);
*iqueue = NULL;
*debug = NULL;
if (!pDXGIGetDebugInterface)
return;
HRESULT hr;
hr = pDXGIGetDebugInterface(&IID_IDXGIInfoQueue, (void **) iqueue);
if (FAILED(hr)) {
mp_fatal(log, "Failed to get info queue: %s\n", mp_HRESULT_to_str(hr));
return;
}
// Store an unlimited amount of messages in the buffer. This is fine
// because we flush stored messages regularly (in debug_marker.)
IDXGIInfoQueue_SetMessageCountLimit(*iqueue, DXGI_DEBUG_D3D11, -1);
IDXGIInfoQueue_SetMessageCountLimit(*iqueue, DXGI_DEBUG_DXGI, -1);
// Push empty filter to get everything
DXGI_INFO_QUEUE_FILTER filter = {0};
IDXGIInfoQueue_PushStorageFilter(*iqueue, DXGI_DEBUG_ALL, &filter);
hr = pDXGIGetDebugInterface(&IID_IDXGIDebug, (void **) debug);
if (FAILED(hr)) {
mp_fatal(log, "Failed to get debug device: %s\n", mp_HRESULT_to_str(hr));
return;
}
}