/* * 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 #include #include #include "gpu.h" #include "swapchain.h" #include "utils.h" struct d3d11_csp_mapping { DXGI_COLOR_SPACE_TYPE d3d11_csp; DXGI_FORMAT d3d11_fmt; struct pl_color_space out_csp; }; static struct d3d11_csp_mapping map_pl_csp_to_d3d11(const struct pl_color_space *hint, bool use_8bit_sdr) { if (pl_color_space_is_hdr(hint) && hint->transfer != PL_COLOR_TRC_LINEAR) { struct pl_color_space pl_csp = pl_color_space_hdr10; pl_csp.hdr = (struct pl_hdr_metadata) { // Whitelist only values that we support signalling metadata for .prim = hint->hdr.prim, .min_luma = hint->hdr.min_luma, .max_luma = hint->hdr.max_luma, .max_cll = hint->hdr.max_cll, .max_fall = hint->hdr.max_fall, }; return (struct d3d11_csp_mapping){ .d3d11_csp = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, .d3d11_fmt = DXGI_FORMAT_R10G10B10A2_UNORM, .out_csp = pl_csp, }; } else if (pl_color_primaries_is_wide_gamut(hint->primaries) || hint->transfer == PL_COLOR_TRC_LINEAR) { // scRGB a la VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, // so could be utilized for HDR/wide gamut content as well // with content that goes beyond 0.0-1.0. return (struct d3d11_csp_mapping){ .d3d11_csp = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709, .d3d11_fmt = DXGI_FORMAT_R16G16B16A16_FLOAT, .out_csp = { .primaries = PL_COLOR_PRIM_BT_709, .transfer = PL_COLOR_TRC_LINEAR, } }; } return (struct d3d11_csp_mapping){ .d3d11_csp = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709, .d3d11_fmt = use_8bit_sdr ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R10G10B10A2_UNORM, .out_csp = pl_color_space_monitor, }; } struct priv { struct pl_sw_fns impl; struct d3d11_ctx *ctx; IDXGISwapChain *swapchain; pl_tex backbuffer; // Currently requested or applied swap chain configuration. // Affected by received colorspace hints. struct d3d11_csp_mapping csp_map; // Whether a swapchain backbuffer format reconfiguration has been // requested by means of an additional resize action. bool update_swapchain_format; // Whether 10-bit backbuffer format is disabled for SDR content. bool disable_10bit_sdr; // Fallback to 8-bit RGB was triggered due to lack of compatiblity bool fallback_8bit_rgb; }; static void d3d11_sw_destroy(pl_swapchain sw) { struct priv *p = PL_PRIV(sw); pl_tex_destroy(sw->gpu, &p->backbuffer); SAFE_RELEASE(p->swapchain); pl_free((void *) sw); } static int d3d11_sw_latency(pl_swapchain sw) { struct priv *p = PL_PRIV(sw); struct d3d11_ctx *ctx = p->ctx; UINT max_latency; IDXGIDevice1_GetMaximumFrameLatency(ctx->dxgi_dev, &max_latency); return max_latency; } static pl_tex get_backbuffer(pl_swapchain sw) { struct priv *p = PL_PRIV(sw); struct d3d11_ctx *ctx = p->ctx; ID3D11Texture2D *backbuffer = NULL; pl_tex tex = NULL; D3D(IDXGISwapChain_GetBuffer(p->swapchain, 0, &IID_ID3D11Texture2D, (void **) &backbuffer)); tex = pl_d3d11_wrap(sw->gpu, pl_d3d11_wrap_params( .tex = (ID3D11Resource *) backbuffer, )); error: SAFE_RELEASE(backbuffer); return tex; } static bool d3d11_sw_resize(pl_swapchain sw, int *width, int *height) { struct priv *p = PL_PRIV(sw); struct d3d11_ctx *ctx = p->ctx; DXGI_SWAP_CHAIN_DESC desc = {0}; IDXGISwapChain_GetDesc(p->swapchain, &desc); int w = PL_DEF(*width, desc.BufferDesc.Width); int h = PL_DEF(*height, desc.BufferDesc.Height); bool format_changed = p->csp_map.d3d11_fmt != desc.BufferDesc.Format; if (format_changed) { PL_INFO(ctx, "Attempting to reconfigure swap chain format: %s -> %s", pl_get_dxgi_format_name(desc.BufferDesc.Format), pl_get_dxgi_format_name(p->csp_map.d3d11_fmt)); } if (w != desc.BufferDesc.Width || h != desc.BufferDesc.Height || format_changed) { if (p->backbuffer) { PL_ERR(sw, "Tried resizing the swapchain while a frame was in " "progress! Please submit the current frame first."); return false; } HRESULT hr = IDXGISwapChain_ResizeBuffers(p->swapchain, 0, w, h, p->csp_map.d3d11_fmt, desc.Flags); if (hr == E_INVALIDARG && p->csp_map.d3d11_fmt != DXGI_FORMAT_R8G8B8A8_UNORM) { PL_WARN(sw, "Reconfiguring the swapchain failed, re-trying with R8G8B8A8_UNORM fallback."); D3D(IDXGISwapChain_ResizeBuffers(p->swapchain, 0, w, h, DXGI_FORMAT_R8G8B8A8_UNORM, desc.Flags)); // re-configure the colorspace to 8-bit RGB SDR fallback p->csp_map = map_pl_csp_to_d3d11(&pl_color_space_unknown, true); p->fallback_8bit_rgb = true; } else if (FAILED(hr)) { PL_ERR(sw, "Reconfiguring the swapchain failed with error: %s", pl_hresult_to_str(hr)); return false; } } *width = w; *height = h; p->update_swapchain_format = false; return true; error: return false; } static bool d3d11_sw_start_frame(pl_swapchain sw, struct pl_swapchain_frame *out_frame) { struct priv *p = PL_PRIV(sw); struct d3d11_ctx *ctx = p->ctx; if (ctx->is_failed) return false; if (p->backbuffer) { PL_ERR(sw, "Attempted calling `pl_swapchain_start_frame` while a frame " "was already in progress! Call `pl_swapchain_submit_frame` first."); return false; } if (p->update_swapchain_format) { int w = 0, h = 0; if (!d3d11_sw_resize(sw, &w, &h)) return false; } p->backbuffer = get_backbuffer(sw); if (!p->backbuffer) return false; int bits = 0; pl_fmt fmt = p->backbuffer->params.format; for (int i = 0; i < fmt->num_components; i++) bits = PL_MAX(bits, fmt->component_depth[i]); *out_frame = (struct pl_swapchain_frame) { .fbo = p->backbuffer, .flipped = false, .color_repr = { .sys = PL_COLOR_SYSTEM_RGB, .levels = PL_COLOR_LEVELS_FULL, .alpha = PL_ALPHA_UNKNOWN, .bits = { .sample_depth = bits, .color_depth = bits, }, }, .color_space = p->csp_map.out_csp, }; return true; } static bool d3d11_sw_submit_frame(pl_swapchain sw) { struct priv *p = PL_PRIV(sw); struct d3d11_ctx *ctx = p->ctx; // Release the backbuffer. We shouldn't hold onto it unnecessarily, because // it prevents external code from resizing the swapchain, which we'd // otherwise support just fine. pl_tex_destroy(sw->gpu, &p->backbuffer); return !ctx->is_failed; } static void d3d11_sw_swap_buffers(pl_swapchain sw) { struct priv *p = PL_PRIV(sw); struct d3d11_ctx *ctx = p->ctx; // Present can fail with a device removed error D3D(IDXGISwapChain_Present(p->swapchain, 1, 0)); error: return; } static DXGI_HDR_METADATA_HDR10 set_hdr10_metadata(const struct pl_hdr_metadata *hdr) { return (DXGI_HDR_METADATA_HDR10) { .RedPrimary = { roundf(hdr->prim.red.x * 50000), roundf(hdr->prim.red.y * 50000) }, .GreenPrimary = { roundf(hdr->prim.green.x * 50000), roundf(hdr->prim.green.y * 50000) }, .BluePrimary = { roundf(hdr->prim.blue.x * 50000), roundf(hdr->prim.blue.y * 50000) }, .WhitePoint = { roundf(hdr->prim.white.x * 50000), roundf(hdr->prim.white.y * 50000) }, .MaxMasteringLuminance = roundf(hdr->max_luma), .MinMasteringLuminance = roundf(hdr->min_luma * 10000), .MaxContentLightLevel = roundf(hdr->max_cll), .MaxFrameAverageLightLevel = roundf(hdr->max_fall), }; } static bool set_swapchain_metadata(struct d3d11_ctx *ctx, IDXGISwapChain3 *swapchain3, struct d3d11_csp_mapping *csp_map) { IDXGISwapChain4 *swapchain4 = NULL; bool ret = false; bool is_hdr = pl_color_space_is_hdr(&csp_map->out_csp); DXGI_HDR_METADATA_HDR10 hdr10 = is_hdr ? set_hdr10_metadata(&csp_map->out_csp.hdr) : (DXGI_HDR_METADATA_HDR10){ 0 }; D3D(IDXGISwapChain3_SetColorSpace1(swapchain3, csp_map->d3d11_csp)); // if we succeeded to set the color space, it's good enough, // since older versions of Windows 10 will not have swapchain v4 available. ret = true; if (FAILED(IDXGISwapChain3_QueryInterface(swapchain3, &IID_IDXGISwapChain4, (void **)&swapchain4))) { PL_TRACE(ctx, "v4 swap chain interface is not available, skipping HDR10 " "metadata configuration."); goto error; } D3D(IDXGISwapChain4_SetHDRMetaData(swapchain4, is_hdr ? DXGI_HDR_METADATA_TYPE_HDR10 : DXGI_HDR_METADATA_TYPE_NONE, is_hdr ? sizeof(hdr10) : 0, is_hdr ? &hdr10 : NULL)); goto success; error: csp_map->out_csp.hdr = (struct pl_hdr_metadata) { 0 }; success: SAFE_RELEASE(swapchain4); return ret; } static bool d3d11_format_supported(struct d3d11_ctx *ctx, DXGI_FORMAT fmt) { UINT sup = 0; UINT wanted_sup = D3D11_FORMAT_SUPPORT_TEXTURE2D | D3D11_FORMAT_SUPPORT_DISPLAY | D3D11_FORMAT_SUPPORT_SHADER_SAMPLE | D3D11_FORMAT_SUPPORT_RENDER_TARGET | D3D11_FORMAT_SUPPORT_BLENDABLE; D3D(ID3D11Device_CheckFormatSupport(ctx->dev, fmt, &sup)); return (sup & wanted_sup) == wanted_sup; error: return false; } static bool d3d11_csp_supported(struct d3d11_ctx *ctx, IDXGISwapChain3 *swapchain3, DXGI_COLOR_SPACE_TYPE color_space) { UINT csp_support_flags = 0; D3D(IDXGISwapChain3_CheckColorSpaceSupport(swapchain3, color_space, &csp_support_flags)); return (csp_support_flags & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT); error: return false; } static void update_swapchain_color_config(pl_swapchain sw, const struct pl_color_space *csp, bool is_internal) { struct priv *p = PL_PRIV(sw); struct d3d11_ctx *ctx = p->ctx; IDXGISwapChain3 *swapchain3 = NULL; struct d3d11_csp_mapping old_map = p->csp_map; // ignore config changes in fallback mode if (p->fallback_8bit_rgb) goto cleanup; HRESULT hr = IDXGISwapChain_QueryInterface(p->swapchain, &IID_IDXGISwapChain3, (void **)&swapchain3); if (FAILED(hr)) { PL_TRACE(ctx, "v3 swap chain interface is not available, skipping " "color space configuration."); swapchain3 = NULL; } // Lack of swap chain v3 means we cannot control swap chain color space; // Only effective formats are the 8 and 10 bit RGB ones. struct d3d11_csp_mapping csp_map = map_pl_csp_to_d3d11(swapchain3 ? csp : &pl_color_space_unknown, p->disable_10bit_sdr); if (p->csp_map.d3d11_fmt == csp_map.d3d11_fmt && p->csp_map.d3d11_csp == csp_map.d3d11_csp && pl_color_space_equal(&p->csp_map.out_csp, &csp_map.out_csp)) goto cleanup; PL_INFO(ctx, "%s swap chain configuration%s: format: %s, color space: %s.", is_internal ? "Initial" : "New", is_internal ? "" : " received from hint", pl_get_dxgi_format_name(csp_map.d3d11_fmt), pl_get_dxgi_csp_name(csp_map.d3d11_csp)); bool fmt_supported = d3d11_format_supported(ctx, csp_map.d3d11_fmt); bool csp_supported = swapchain3 ? d3d11_csp_supported(ctx, swapchain3, csp_map.d3d11_csp) : true; if (!fmt_supported || !csp_supported) { PL_ERR(ctx, "New swap chain configuration was deemed not supported: " "format: %s, color space: %s. Failling back to 8bit RGB.", fmt_supported ? "supported" : "unsupported", csp_supported ? "supported" : "unsupported"); // fall back to 8bit sRGB if requested configuration is not supported csp_map = map_pl_csp_to_d3d11(&pl_color_space_unknown, true); } p->csp_map = csp_map; p->update_swapchain_format = true; if (!swapchain3) goto cleanup; if (!set_swapchain_metadata(ctx, swapchain3, &p->csp_map)) { // format succeeded, but color space configuration failed p->csp_map = old_map; p->csp_map.d3d11_fmt = csp_map.d3d11_fmt; } pl_d3d11_flush_message_queue(ctx, "After colorspace hint"); cleanup: SAFE_RELEASE(swapchain3); } static void d3d11_sw_colorspace_hint(pl_swapchain sw, const struct pl_color_space *csp) { update_swapchain_color_config(sw, csp, false); } IDXGISwapChain *pl_d3d11_swapchain_unwrap(pl_swapchain sw) { struct priv *p = PL_PRIV(sw); IDXGISwapChain_AddRef(p->swapchain); return p->swapchain; } static const struct pl_sw_fns d3d11_swapchain = { .destroy = d3d11_sw_destroy, .latency = d3d11_sw_latency, .resize = d3d11_sw_resize, .colorspace_hint = d3d11_sw_colorspace_hint, .start_frame = d3d11_sw_start_frame, .submit_frame = d3d11_sw_submit_frame, .swap_buffers = d3d11_sw_swap_buffers, }; static HRESULT create_swapchain_1_2(struct d3d11_ctx *ctx, IDXGIFactory2 *factory, const struct pl_d3d11_swapchain_params *params, bool flip, UINT width, UINT height, DXGI_FORMAT format, IDXGISwapChain **swapchain_out) { IDXGISwapChain *swapchain = NULL; IDXGISwapChain1 *swapchain1 = NULL; HRESULT hr; DXGI_SWAP_CHAIN_DESC1 desc = { .Width = width, .Height = height, .Format = format, .SampleDesc.Count = 1, .BufferUsage = DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_RENDER_TARGET_OUTPUT, .Flags = params->flags, }; if (ID3D11Device_GetFeatureLevel(ctx->dev) >= D3D_FEATURE_LEVEL_11_0) desc.BufferUsage |= DXGI_USAGE_UNORDERED_ACCESS; if (flip) { UINT max_latency; IDXGIDevice1_GetMaximumFrameLatency(ctx->dxgi_dev, &max_latency); // Make sure we have at least enough buffers to allow `max_latency` // frames in-flight at once, plus one frame for the frontbuffer desc.BufferCount = max_latency + 1; if (IsWindows10OrGreater()) { desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; } else { desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; } desc.BufferCount = PL_MIN(desc.BufferCount, DXGI_MAX_SWAP_CHAIN_BUFFERS); } else { desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; desc.BufferCount = 1; } if (params->window) { hr = IDXGIFactory2_CreateSwapChainForHwnd(factory, (IUnknown *) ctx->dev, params->window, &desc, NULL, NULL, &swapchain1); } else if (params->core_window) { hr = IDXGIFactory2_CreateSwapChainForCoreWindow(factory, (IUnknown *) ctx->dev, params->core_window, &desc, NULL, &swapchain1); } else { hr = IDXGIFactory2_CreateSwapChainForComposition(factory, (IUnknown *) ctx->dev, &desc, 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(struct d3d11_ctx *ctx, IDXGIFactory1 *factory, const struct pl_d3d11_swapchain_params *params, UINT width, UINT height, DXGI_FORMAT format, IDXGISwapChain **swapchain_out) { DXGI_SWAP_CHAIN_DESC desc = { .BufferDesc = { .Width = width, .Height = height, .Format = format, }, .SampleDesc.Count = 1, .BufferUsage = DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_RENDER_TARGET_OUTPUT, .BufferCount = 1, .OutputWindow = params->window, .Windowed = TRUE, .SwapEffect = DXGI_SWAP_EFFECT_DISCARD, .Flags = params->flags, }; return IDXGIFactory1_CreateSwapChain(factory, (IUnknown *) ctx->dev, &desc, swapchain_out); } static IDXGISwapChain *create_swapchain(struct d3d11_ctx *ctx, const struct pl_d3d11_swapchain_params *params, DXGI_FORMAT format) { IDXGIDevice1 *dxgi_dev = NULL; IDXGIAdapter1 *adapter = NULL; IDXGIFactory1 *factory = NULL; IDXGIFactory2 *factory2 = NULL; IDXGISwapChain *swapchain = NULL; bool success = false; HRESULT hr; D3D(ID3D11Device_QueryInterface(ctx->dev, &IID_IDXGIDevice1, (void **) &dxgi_dev)); D3D(IDXGIDevice1_GetParent(dxgi_dev, &IID_IDXGIAdapter1, (void **) &adapter)); D3D(IDXGIAdapter1_GetParent(adapter, &IID_IDXGIFactory1, (void **) &factory)); hr = IDXGIFactory1_QueryInterface(factory, &IID_IDXGIFactory2, (void **) &factory2); if (FAILED(hr)) factory2 = NULL; bool flip = factory2 && !params->blit; UINT width = PL_DEF(params->width, 1); UINT height = PL_DEF(params->height, 1); // If both width and height are unset, the default size is the window size if (params->window && params->width == 0 && params->height == 0) { RECT rc; if (GetClientRect(params->window, &rc)) { width = PL_DEF(rc.right - rc.left, 1); height = PL_DEF(rc.bottom - rc.top, 1); } } // 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(ctx, factory2, params, flip, width, height, format, &swapchain); } else { // Fall back to DXGI 1.1 (Windows 7) hr = create_swapchain_1_1(ctx, factory, params, width, height, format, &swapchain); } if (SUCCEEDED(hr)) break; pl_d3d11_after_error(ctx, hr); if (flip) { PL_DEBUG(ctx, "Failed to create flip-model swapchain, trying bitblt"); flip = false; continue; } PL_FATAL(ctx, "Failed to create swapchain: %s", pl_hresult_to_str(hr)); goto error; } while (true); // Prevent DXGI from making changes to the window, otherwise it will hook // the Alt+Enter keystroke and make it trigger an ugly transition to // legacy exclusive fullscreen mode. IDXGIFactory_MakeWindowAssociation(factory, params->window, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_PRINT_SCREEN); success = true; error: if (!success) SAFE_RELEASE(swapchain); SAFE_RELEASE(factory2); SAFE_RELEASE(factory); SAFE_RELEASE(adapter); SAFE_RELEASE(dxgi_dev); return swapchain; } pl_swapchain pl_d3d11_create_swapchain(pl_d3d11 d3d11, const struct pl_d3d11_swapchain_params *params) { struct d3d11_ctx *ctx = PL_PRIV(d3d11); pl_gpu gpu = d3d11->gpu; bool success = false; struct pl_swapchain_t *sw = pl_zalloc_obj(NULL, sw, struct priv); struct priv *p = PL_PRIV(sw); *sw = (struct pl_swapchain_t) { .log = gpu->log, .gpu = gpu, }; *p = (struct priv) { .impl = d3d11_swapchain, .ctx = ctx, // default to standard 8 or 10 bit RGB, unset pl_color_space .csp_map = { .d3d11_fmt = params->disable_10bit_sdr ? DXGI_FORMAT_R8G8B8A8_UNORM : (d3d11_format_supported(ctx, DXGI_FORMAT_R10G10B10A2_UNORM) ? DXGI_FORMAT_R10G10B10A2_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM), }, .disable_10bit_sdr = params->disable_10bit_sdr, }; if (params->swapchain) { p->swapchain = params->swapchain; IDXGISwapChain_AddRef(params->swapchain); } else { p->swapchain = create_swapchain(ctx, params, p->csp_map.d3d11_fmt); if (!p->swapchain) goto error; } DXGI_SWAP_CHAIN_DESC scd = {0}; IDXGISwapChain_GetDesc(p->swapchain, &scd); if (scd.SwapEffect == DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL || scd.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD) { PL_INFO(gpu, "Using flip-model presentation"); } else { PL_INFO(gpu, "Using bitblt-model presentation"); } p->csp_map.d3d11_fmt = scd.BufferDesc.Format; update_swapchain_color_config(sw, &pl_color_space_unknown, true); success = true; error: if (!success) { PL_FATAL(gpu, "Failed to create Direct3D 11 swapchain"); d3d11_sw_destroy(sw); sw = NULL; } return sw; }