/* * This file is part of libplacebo. * * libplacebo 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. * * libplacebo 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 libplacebo. If not, see . */ #include "gpu.h" // Windows 8 enum value, not present in mingw-w64 v7 #define DXGI_ADAPTER_FLAG_SOFTWARE (2) const struct pl_d3d11_params pl_d3d11_default_params = { PL_D3D11_DEFAULTS }; static INIT_ONCE d3d11_once = INIT_ONCE_STATIC_INIT; static PFN_D3D11_CREATE_DEVICE pD3D11CreateDevice = NULL; static __typeof__(&CreateDXGIFactory1) pCreateDXGIFactory1 = NULL; #ifdef PL_HAVE_DXGI_DEBUG static __typeof__(&DXGIGetDebugInterface) pDXGIGetDebugInterface = NULL; #endif static void d3d11_load(void) { BOOL bPending = FALSE; InitOnceBeginInitialize(&d3d11_once, 0, &bPending, NULL); if (bPending) { HMODULE d3d11 = LoadLibraryW(L"d3d11.dll"); if (d3d11) { pD3D11CreateDevice = (void *) GetProcAddress(d3d11, "D3D11CreateDevice"); } HMODULE dxgi = LoadLibraryW(L"dxgi.dll"); if (dxgi) { pCreateDXGIFactory1 = (void *) GetProcAddress(dxgi, "CreateDXGIFactory1"); } #ifdef PL_HAVE_DXGI_DEBUG HMODULE dxgi_debug = LoadLibraryW(L"dxgidebug.dll"); if (dxgi_debug) { pDXGIGetDebugInterface = (void *) GetProcAddress(dxgi_debug, "DXGIGetDebugInterface"); } #endif } InitOnceComplete(&d3d11_once, 0, NULL); } // 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 = PL_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; } static bool is_null_luid(LUID luid) { return luid.LowPart == 0 && luid.HighPart == 0; } static IDXGIAdapter *get_adapter(pl_d3d11 d3d11, LUID adapter_luid) { struct d3d11_ctx *ctx = PL_PRIV(d3d11); IDXGIFactory1 *factory = NULL; IDXGIAdapter1 *adapter1 = NULL; IDXGIAdapter *adapter = NULL; HRESULT hr; if (!pCreateDXGIFactory1) { PL_FATAL(ctx, "Failed to load dxgi.dll"); goto error; } pCreateDXGIFactory1(&IID_IDXGIFactory1, (void **) &factory); for (int i = 0;; i++) { hr = IDXGIFactory1_EnumAdapters1(factory, i, &adapter1); if (hr == DXGI_ERROR_NOT_FOUND) break; if (FAILED(hr)) { PL_FATAL(ctx, "Failed to enumerate adapters"); goto error; } DXGI_ADAPTER_DESC1 desc; D3D(IDXGIAdapter1_GetDesc1(adapter1, &desc)); if (desc.AdapterLuid.LowPart == adapter_luid.LowPart && desc.AdapterLuid.HighPart == adapter_luid.HighPart) { break; } SAFE_RELEASE(adapter1); } if (!adapter1) { PL_FATAL(ctx, "Adapter with LUID %08lx%08lx not found", adapter_luid.HighPart, adapter_luid.LowPart); goto error; } D3D(IDXGIAdapter1_QueryInterface(adapter1, &IID_IDXGIAdapter, (void **) &adapter)); error: SAFE_RELEASE(factory); SAFE_RELEASE(adapter1); return adapter; } static bool has_sdk_layers(void) { // This will fail if the SDK layers aren't installed return SUCCEEDED(pD3D11CreateDevice(NULL, D3D_DRIVER_TYPE_NULL, NULL, D3D11_CREATE_DEVICE_DEBUG, NULL, 0, D3D11_SDK_VERSION, NULL, NULL, NULL)); } static ID3D11Device *create_device(struct pl_d3d11_t *d3d11, const struct pl_d3d11_params *params) { struct d3d11_ctx *ctx = PL_PRIV(d3d11); bool debug = params->debug; bool warp = params->force_software; int max_fl = params->max_feature_level; int min_fl = params->min_feature_level; ID3D11Device *dev = NULL; IDXGIDevice1 *dxgi_dev = NULL; IDXGIAdapter *adapter = NULL; bool release_adapter = false; HRESULT hr; d3d11_load(); if (!pD3D11CreateDevice) { PL_FATAL(ctx, "Failed to load d3d11.dll"); goto error; } if (params->adapter) { adapter = params->adapter; } else if (!is_null_luid(params->adapter_luid)) { adapter = get_adapter(d3d11, params->adapter_luid); release_adapter = true; } if (debug && !has_sdk_layers()) { PL_INFO(ctx, "Debug layer not available, removing debug flag"); debug = false; } // Return here to retry creating the device do { // Use these default feature levels if they are not set max_fl = PL_DEF(max_fl, D3D_FEATURE_LEVEL_12_1); min_fl = PL_DEF(min_fl, D3D_FEATURE_LEVEL_9_1); // Get a list of feature levels from min_fl to max_fl const D3D_FEATURE_LEVEL *levels; int levels_len = get_feature_levels(max_fl, min_fl, &levels); if (!levels_len) { PL_FATAL(ctx, "No suitable Direct3D feature level found"); goto error; } D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_UNKNOWN; if (!adapter) { if (warp) { type = D3D_DRIVER_TYPE_WARP; } else { type = D3D_DRIVER_TYPE_HARDWARE; } } UINT flags = params->flags; if (debug) flags |= D3D11_CREATE_DEVICE_DEBUG; hr = pD3D11CreateDevice(adapter, type, NULL, flags, levels, levels_len, D3D11_SDK_VERSION, &dev, NULL, NULL); if (SUCCEEDED(hr)) break; pl_d3d11_after_error(ctx, hr); // 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 (hr == E_INVALIDARG && max_fl >= D3D_FEATURE_LEVEL_12_0 && min_fl <= D3D_FEATURE_LEVEL_11_1) { PL_DEBUG(ctx, "Failed to create 12_0+ device, trying 11_1"); 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 (hr == E_INVALIDARG && max_fl >= D3D_FEATURE_LEVEL_11_1 && min_fl <= D3D_FEATURE_LEVEL_11_0) { PL_DEBUG(ctx, "Failed to create 11_1+ device, trying 11_0"); max_fl = D3D_FEATURE_LEVEL_11_0; continue; } // Retry with WARP if allowed if (!adapter && !warp && params->allow_software) { PL_DEBUG(ctx, "Failed to create hardware device, trying WARP: %s", pl_hresult_to_str(hr)); warp = true; max_fl = params->max_feature_level; min_fl = params->min_feature_level; continue; } PL_FATAL(ctx, "Failed to create Direct3D 11 device: %s", pl_hresult_to_str(hr)); goto error; } while (true); if (params->max_frame_latency) { D3D(ID3D11Device_QueryInterface(dev, &IID_IDXGIDevice1, (void **) &dxgi_dev)); IDXGIDevice1_SetMaximumFrameLatency(dxgi_dev, params->max_frame_latency); } d3d11->software = warp; error: if (release_adapter) SAFE_RELEASE(adapter); SAFE_RELEASE(dxgi_dev); return dev; } static void init_debug_layer(struct d3d11_ctx *ctx, bool leak_check) { #ifdef PL_HAVE_DXGI_DEBUG if (!pDXGIGetDebugInterface) d3d11_load(); if (!pDXGIGetDebugInterface) goto error; D3D(pDXGIGetDebugInterface(&IID_IDXGIInfoQueue, (void **) &ctx->iqueue)); // Push empty filter to get everything IDXGIInfoQueue_PushStorageFilter(ctx->iqueue, DXGI_DEBUG_ALL, &(DXGI_INFO_QUEUE_FILTER){0}); // Filter some annoying D3D11 messages DXGI_INFO_QUEUE_MESSAGE_ID deny_ids[] = { // This false-positive error occurs every time we Draw() with a shader // that samples from a texture format that only supports point sampling. // Since we already use CheckFormatSupport to know which formats can be // linearly sampled from, we shouldn't ever bind a non-point sampler to // a format that doesn't support it. D3D11_MESSAGE_ID_DEVICE_DRAW_RESOURCE_FORMAT_SAMPLE_UNSUPPORTED, }; DXGI_INFO_QUEUE_FILTER filter = { .DenyList = { .NumIDs = PL_ARRAY_SIZE(deny_ids), .pIDList = deny_ids, }, }; IDXGIInfoQueue_PushStorageFilter(ctx->iqueue, DXGI_DEBUG_D3D11, &filter); IDXGIInfoQueue_SetMessageCountLimit(ctx->iqueue, DXGI_DEBUG_D3D11, -1); IDXGIInfoQueue_SetMessageCountLimit(ctx->iqueue, DXGI_DEBUG_DXGI, -1); if (leak_check) D3D(pDXGIGetDebugInterface(&IID_IDXGIDebug, (void **) &ctx->debug)); error: return; #endif } void pl_d3d11_destroy(pl_d3d11 *ptr) { pl_d3d11 d3d11 = *ptr; if (!d3d11) return; struct d3d11_ctx *ctx = PL_PRIV(d3d11); pl_gpu_destroy(d3d11->gpu); SAFE_RELEASE(ctx->dev); SAFE_RELEASE(ctx->dxgi_dev); #ifdef PL_HAVE_DXGI_DEBUG if (ctx->debug) { // Report any leaked objects pl_d3d11_flush_message_queue(ctx, "After destroy"); IDXGIDebug_ReportLiveObjects(ctx->debug, DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_DETAIL); pl_d3d11_flush_message_queue(ctx, "After leak check"); IDXGIDebug_ReportLiveObjects(ctx->debug, DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_SUMMARY); pl_d3d11_flush_message_queue(ctx, "After leak summary"); } SAFE_RELEASE(ctx->debug); SAFE_RELEASE(ctx->iqueue); #endif pl_free_ptr((void **) ptr); } pl_d3d11 pl_d3d11_create(pl_log log, const struct pl_d3d11_params *params) { params = PL_DEF(params, &pl_d3d11_default_params); IDXGIAdapter1 *adapter = NULL; IDXGIAdapter2 *adapter2 = NULL; bool success = false; HRESULT hr; struct pl_d3d11_t *d3d11 = pl_zalloc_obj(NULL, d3d11, struct d3d11_ctx); struct d3d11_ctx *ctx = PL_PRIV(d3d11); ctx->log = log; ctx->d3d11 = d3d11; if (params->device) { d3d11->device = params->device; ID3D11Device_AddRef(d3d11->device); } else { d3d11->device = create_device(d3d11, params); if (!d3d11->device) goto error; } ctx->dev = d3d11->device; if (params->debug || ID3D11Device_GetCreationFlags(d3d11->device) & D3D11_CREATE_DEVICE_DEBUG) { // Do not report live object on pl_d3d11_destroy if device was created // externally, it makes no sense as there will be a lot of things alive. init_debug_layer(ctx, !params->device); } D3D(ID3D11Device_QueryInterface(d3d11->device, &IID_IDXGIDevice1, (void **) &ctx->dxgi_dev)); D3D(IDXGIDevice1_GetParent(ctx->dxgi_dev, &IID_IDXGIAdapter1, (void **) &adapter)); hr = IDXGIAdapter1_QueryInterface(adapter, &IID_IDXGIAdapter2, (void **) &adapter2); if (FAILED(hr)) adapter2 = NULL; if (adapter2) { PL_INFO(ctx, "Using DXGI 1.2+"); } else { PL_INFO(ctx, "Using DXGI 1.1"); } D3D_FEATURE_LEVEL fl = ID3D11Device_GetFeatureLevel(d3d11->device); PL_INFO(ctx, "Using Direct3D 11 feature level %u_%u", ((unsigned) fl) >> 12, (((unsigned) fl) >> 8) & 0xf); char *dev_name = NULL; UINT vendor_id, device_id, revision, subsys_id; LUID adapter_luid; UINT flags; if (adapter2) { // DXGI 1.2 IDXGIAdapter2::GetDesc2 is preferred over the DXGI 1.1 // version because it reports the real adapter information when using // feature level 9 hardware DXGI_ADAPTER_DESC2 desc; D3D(IDXGIAdapter2_GetDesc2(adapter2, &desc)); dev_name = pl_to_utf8(NULL, desc.Description); vendor_id = desc.VendorId; device_id = desc.DeviceId; revision = desc.Revision; subsys_id = desc.SubSysId; adapter_luid = desc.AdapterLuid; flags = desc.Flags; } else { DXGI_ADAPTER_DESC1 desc; D3D(IDXGIAdapter1_GetDesc1(adapter, &desc)); dev_name = pl_to_utf8(NULL, desc.Description); vendor_id = desc.VendorId; device_id = desc.DeviceId; revision = desc.Revision; subsys_id = desc.SubSysId; adapter_luid = desc.AdapterLuid; flags = desc.Flags; } PL_INFO(ctx, "Direct3D 11 device properties:"); PL_INFO(ctx, " Device Name: %s", dev_name); PL_INFO(ctx, " Device ID: %04x:%04x (rev %02x)", vendor_id, device_id, revision); PL_INFO(ctx, " Subsystem ID: %04x:%04x", LOWORD(subsys_id), HIWORD(subsys_id)); PL_INFO(ctx, " LUID: %08lx%08lx", adapter_luid.HighPart, adapter_luid.LowPart); pl_free(dev_name); LARGE_INTEGER version; hr = IDXGIAdapter1_CheckInterfaceSupport(adapter, &IID_IDXGIDevice, &version); if (SUCCEEDED(hr)) { PL_INFO(ctx, " Driver version: %u.%u.%u.%u", HIWORD(version.HighPart), LOWORD(version.HighPart), HIWORD(version.LowPart), LOWORD(version.LowPart)); } // Note: DXGI_ADAPTER_FLAG_SOFTWARE doesn't exist before Windows 8, but we // also set d3d11->software in create_device if we pick WARP ourselves if (flags & DXGI_ADAPTER_FLAG_SOFTWARE) d3d11->software = 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 (vendor_id == 0x1414 && device_id == 0x8c) d3d11->software = true; if (d3d11->software) { bool external_adapter = params->device || params->adapter || !is_null_luid(params->adapter_luid); // The allow_software flag only applies if the API user didn't manually // specify an adapter or a device if (!params->allow_software && !external_adapter) { // If we got this far with allow_software set, the primary adapter // must be a software adapter PL_ERR(ctx, "Primary adapter is a software adapter"); goto error; } // If a software adapter was manually specified, don't show a warning enum pl_log_level level = PL_LOG_WARN; if (external_adapter || params->force_software) level = PL_LOG_INFO; PL_MSG(ctx, level, "Using a software adapter"); } d3d11->gpu = pl_gpu_create_d3d11(ctx); if (!d3d11->gpu) goto error; success = true; error: if (!success) { PL_FATAL(ctx, "Failed initializing Direct3D 11 device"); pl_d3d11_destroy((pl_d3d11 *) &d3d11); } SAFE_RELEASE(adapter); SAFE_RELEASE(adapter2); return d3d11; }