vlc/modules/video_output/win32/direct3d11.cpp

1667 lines
62 KiB
C++

/*****************************************************************************
* direct3d11.cpp: Windows Direct3D11 video output 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 <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_vout_display.h>
#include <vlc/libvlc.h>
#include <vlc/libvlc_picture.h>
#include <vlc/libvlc_media.h>
#include <vlc/libvlc_renderer_discoverer.h>
#include <vlc/libvlc_media_player.h>
#include <cassert>
#include <math.h>
#include <new>
#include "../../video_chroma/d3d11_fmt.h"
#include "../../hw/nvdec/nvdec_fmt.h"
#include "d3d11_quad.h"
#include "d3d11_shaders.h"
#include "d3d11_scaler.h"
#include "d3d11_tonemap.h"
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
#include "d3d11_swapchain.h"
#endif
#include "common.h"
#include "../../video_chroma/copy.h"
#ifdef HAVE_DXGI1_6_H
# include <dxgi1_6.h>
#endif
using Microsoft::WRL::ComPtr;
static int Open(vout_display_t *,
video_format_t *, vlc_video_context *);
static void Close(vout_display_t *);
#define D3D11_HELP N_("Recommended video output for Windows 8 and later versions")
#define HW_BLENDING_TEXT N_("Use hardware blending support")
#define HW_BLENDING_LONGTEXT N_(\
"Try to use hardware acceleration for subtitle/OSD blending.")
#define UPSCALE_MODE_TEXT N_("Video Upscaling Mode")
#define UPSCALE_MODE_LONGTEXT N_("Select the upscaling mode for video.")
static const char *const ppsz_upscale_mode[] = {
"linear", "point", "processor", "super" };
static const char *const ppsz_upscale_mode_text[] = {
N_("Linear Sampler"), N_("Point Sampler"), N_("Video Processor"), N_("Super Resolution") };
#define HDR_MODE_TEXT N_("HDR Output Mode")
#define HDR_MODE_LONGTEXT N_("Use HDR output even if the source is SDR.")
static const char *const ppsz_hdr_mode[] = {
"auto", "never", "always", "generate" };
static const char *const ppsz_hdr_mode_text[] = {
N_("Auto"), N_("Never out HDR"), N_("Always output HDR"), N_("Generate HDR from SDR") };
vlc_module_begin ()
set_shortname("Direct3D11")
set_description(N_("Direct3D11 video output"))
set_help(D3D11_HELP)
set_subcategory(SUBCAT_VIDEO_VOUT)
add_bool("direct3d11-hw-blending", true, HW_BLENDING_TEXT, HW_BLENDING_LONGTEXT)
add_string("d3d11-upscale-mode", "linear", UPSCALE_MODE_TEXT, UPSCALE_MODE_LONGTEXT)
change_string_list(ppsz_upscale_mode, ppsz_upscale_mode_text)
add_string("d3d11-hdr-mode", "auto", HDR_MODE_TEXT, HDR_MODE_LONGTEXT)
change_string_list(ppsz_hdr_mode, ppsz_hdr_mode_text)
add_shortcut("direct3d11")
set_callback_display(Open, 300)
vlc_module_end ()
enum d3d11_upscale
{
upscale_LinearSampler,
upscale_PointSampler,
upscale_VideoProcessor,
upscale_SuperResolution,
};
enum d3d11_hdr
{
hdr_Auto,
hdr_Never,
hdr_Always,
hdr_Fake,
};
typedef struct vout_display_sys_t
{
display_win32_area_t area = {};
int log_level = 1;
display_info_t display = {};
d3d11_device_t *d3d_dev = NULL;
d3d11_decoder_device_t *local_d3d_dev = NULL; // when opened without a video context
d3d_shader_compiler_t *shaders = nullptr;
d3d11_quad_t picQuad = {};
#ifdef HAVE_D3D11_4_H
d3d11_gpu_fence fence = {};
#endif
picture_sys_d3d11_t stagingSys = {};
plane_t stagingPlanes[PICTURE_PLANE_MAX];
d3d11_vertex_shader_t projectionVShader = {};
d3d11_vertex_shader_t flatVShader = {};
/* copy from the decoder pool into picSquad before display
* Uses a Texture2D with slices rather than a Texture2DArray for the decoder */
bool legacy_shader = false;
// SPU
vlc_fourcc_t pSubpictureChromas[2] = {};
d3d11_quad_t regionQuad = {};
int d3dregion_count = 0;
picture_t **d3dregions = nullptr;
/* outside rendering */
void *outside_opaque = nullptr;
libvlc_video_update_output_cb updateOutputCb = nullptr;
libvlc_video_swap_cb swapCb = nullptr;
libvlc_video_makeCurrent_cb startEndRenderingCb = nullptr;
libvlc_video_frameMetadata_cb sendMetadataCb = nullptr;
libvlc_video_output_select_plane_cb selectPlaneCb = nullptr;
// upscaling
enum d3d11_upscale upscaleMode = upscale_LinearSampler;
d3d11_scaler *scaleProc = nullptr;
// HDR mode
enum d3d11_hdr hdrMode = hdr_Auto;
d3d11_tonemapper *tonemapProc = nullptr;
} vout_display_sys_t;
static void Prepare(vout_display_t *, picture_t *, const vlc_render_subpicture *, vlc_tick_t);
static void Display(vout_display_t *, picture_t *);
static int Direct3D11Open (vout_display_t *, video_format_t *, vlc_video_context *);
static void Direct3D11Close(vout_display_t *);
static int SetupOutputFormat(vout_display_t *, video_format_t *, vlc_video_context *, video_format_t *quad);
static int Direct3D11CreateFormatResources (vout_display_t *, const video_format_t *);
static int Direct3D11CreateGenericResources(vout_display_t *);
static void Direct3D11DestroyResources(vout_display_t *);
static void Direct3D11DeleteRegions(int, picture_t **);
static int Direct3D11MapSubpicture(vout_display_t *, int *, picture_t ***, const vlc_render_subpicture *);
static int Control(vout_display_t *, int);
static int UpdateDisplayFormat(vout_display_t *vd, const video_format_t *fmt)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
libvlc_video_render_cfg_t cfg;
cfg.width = vd->cfg->display.width;
cfg.height = vd->cfg->display.height;
if (sys->hdrMode == hdr_Always || sys->hdrMode == hdr_Fake)
{
// force a fake HDR source
// corresponds to DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020
cfg.bitdepth = 10;
cfg.full_range = true;
cfg.primaries = libvlc_video_primaries_BT2020;
cfg.colorspace = libvlc_video_colorspace_BT2020;
cfg.transfer = libvlc_video_transfer_func_PQ;
}
else if (sys->hdrMode == hdr_Never)
{
// corresponds to DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
cfg.bitdepth = 8;
cfg.full_range = true;
cfg.primaries = libvlc_video_primaries_BT709;
cfg.colorspace = libvlc_video_colorspace_BT709;
cfg.transfer = libvlc_video_transfer_func_BT709;
}
else
{
switch (fmt->i_chroma)
{
case VLC_CODEC_D3D11_OPAQUE:
case VLC_CODEC_D3D11_OPAQUE_ALPHA:
cfg.bitdepth = 8;
break;
case VLC_CODEC_D3D11_OPAQUE_RGBA:
case VLC_CODEC_D3D11_OPAQUE_BGRA:
cfg.bitdepth = 8;
break;
case VLC_CODEC_RGBA10LE:
case VLC_CODEC_D3D11_OPAQUE_10B:
cfg.bitdepth = 10;
break;
default:
{
const auto *p_format = vlc_fourcc_GetChromaDescription(fmt->i_chroma);
if (p_format == NULL)
{
cfg.bitdepth = 8;
}
else
{
cfg.bitdepth = p_format->pixel_bits == 0 ? 8 : p_format->pixel_bits /
(p_format->plane_count==1 ? p_format->pixel_size : 1);
}
}
break;
}
cfg.full_range = sys->picQuad.quad_fmt.color_range == COLOR_RANGE_FULL ||
/* the YUV->RGB conversion already output full range */
is_d3d11_opaque(sys->picQuad.quad_fmt.i_chroma) ||
vlc_fourcc_IsYUV(sys->picQuad.quad_fmt.i_chroma);
cfg.primaries = (libvlc_video_color_primaries_t) sys->picQuad.quad_fmt.primaries;
cfg.colorspace = (libvlc_video_color_space_t) sys->picQuad.quad_fmt.space;
cfg.transfer = (libvlc_video_transfer_func_t) sys->picQuad.quad_fmt.transfer;
}
libvlc_video_output_cfg_t out;
if (!sys->updateOutputCb( sys->outside_opaque, &cfg, &out ))
{
msg_Err(vd, "Failed to set format %dx%d %d bits on output", cfg.width, cfg.height, cfg.bitdepth);
return VLC_EGENERIC;
}
if (sys->upscaleMode == upscale_VideoProcessor || sys->upscaleMode == upscale_SuperResolution)
{
D3D11_UpscalerUpdate(VLC_OBJECT(vd), sys->scaleProc, sys->d3d_dev,
vd->source, &sys->picQuad.quad_fmt, &vd->cfg->display);
if (D3D11_UpscalerUsed(sys->scaleProc))
{
D3D11_UpscalerGetSize(sys->scaleProc, &sys->picQuad.quad_fmt.i_width, &sys->picQuad.quad_fmt.i_height);
sys->picQuad.quad_fmt.i_x_offset = 0;
sys->picQuad.quad_fmt.i_y_offset = 0;
sys->picQuad.quad_fmt.i_visible_width = sys->picQuad.quad_fmt.i_width;
sys->picQuad.quad_fmt.i_visible_height = sys->picQuad.quad_fmt.i_height;
sys->picQuad.generic.i_width = sys->picQuad.quad_fmt.i_width;
sys->picQuad.generic.i_height = sys->picQuad.quad_fmt.i_height;
CommonPlacePicture(vd, &sys->area);
}
}
display_info_t new_display = { };
new_display.pixelFormat = D3D11_RenderFormat((DXGI_FORMAT)out.dxgi_format, DXGI_FORMAT_UNKNOWN, false);
if (unlikely(new_display.pixelFormat == NULL))
{
msg_Err(vd, "Could not find the output format.");
return VLC_EGENERIC;
}
new_display.color = (video_color_space_t) out.colorspace;
new_display.transfer = (video_transfer_func_t) out.transfer;
new_display.primaries = (video_color_primaries_t) out.primaries;
new_display.b_full_range = out.full_range;
new_display.orientation = (video_orientation_t) out.orientation;
/* guestimate the display peak luminance */
switch (new_display.transfer)
{
case TRANSFER_FUNC_LINEAR:
case TRANSFER_FUNC_SRGB:
new_display.luminance_peak = DEFAULT_SRGB_BRIGHTNESS;
break;
case TRANSFER_FUNC_SMPTE_ST2084:
new_display.luminance_peak = MAX_PQ_BRIGHTNESS;
break;
default:
new_display.luminance_peak = DEFAULT_SRGB_BRIGHTNESS;
break;
}
if ( sys->display.pixelFormat == NULL ||
( sys->display.pixelFormat != new_display.pixelFormat ||
sys->display.luminance_peak != new_display.luminance_peak ||
sys->display.color != new_display.color ||
sys->display.transfer != new_display.transfer ||
sys->display.primaries != new_display.primaries ||
sys->display.b_full_range != new_display.b_full_range ||
sys->display.orientation != new_display.orientation ))
{
sys->display = new_display;
/* TODO release the pixel shaders if the format changed */
if (Direct3D11CreateFormatResources(vd, fmt)) {
msg_Err(vd, "Failed to allocate format resources");
return VLC_EGENERIC;
}
}
return VLC_SUCCESS;
}
static void UpdateSize(vout_display_t *vd)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
msg_Dbg(vd, "Detected size change %dx%d", sys->area.place.width,
sys->area.place.height);
UpdateDisplayFormat(vd, vd->fmt);
RECT rect_dst;
rect_dst.left = sys->area.place.x;
rect_dst.right = sys->area.place.x + sys->area.place.width;
rect_dst.top = sys->area.place.y;
rect_dst.bottom = sys->area.place.y + sys->area.place.height;
sys->picQuad.UpdateViewport( &rect_dst, sys->display.pixelFormat );
RECT source_rect;
source_rect.left = sys->picQuad.quad_fmt.i_x_offset;
source_rect.right = sys->picQuad.quad_fmt.i_x_offset + sys->picQuad.quad_fmt.i_visible_width;
source_rect.top = sys->picQuad.quad_fmt.i_y_offset;
source_rect.bottom = sys->picQuad.quad_fmt.i_y_offset + sys->picQuad.quad_fmt.i_visible_height;
d3d11_device_lock( sys->d3d_dev );
D3D11_UpdateQuadPosition(vd, sys->d3d_dev, &sys->picQuad, &source_rect,
video_format_GetTransform(vd->source->orientation, sys->display.orientation));
D3D11_UpdateViewpoint( vd, sys->d3d_dev, &sys->picQuad, &vd->cfg->viewpoint,
(float) vd->cfg->display.width / vd->cfg->display.height );
d3d11_device_unlock( sys->d3d_dev );
#ifndef NDEBUG
msg_Dbg( vd, "picQuad position (%.02f,%.02f) %.02fx%.02f",
sys->picQuad.cropViewport[0].TopLeftX, sys->picQuad.cropViewport[0].TopLeftY,
sys->picQuad.cropViewport[0].Width, sys->picQuad.cropViewport[0].Height );
#endif
}
static int SetViewpoint(vout_display_t *vd, const vlc_viewpoint_t *viewpoint)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
if ( sys->picQuad.viewpointShaderConstant.Get() )
{
d3d11_device_lock( sys->d3d_dev );
D3D11_UpdateViewpoint( vd, sys->d3d_dev, &sys->picQuad, viewpoint,
(float) vd->cfg->display.width / vd->cfg->display.height );
d3d11_device_unlock( sys->d3d_dev );
}
return VLC_SUCCESS;
}
static int UpdateStaging(vout_display_t *vd, const video_format_t *fmt)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
if (sys->legacy_shader)
{
/* we need a staging texture */
ID3D11Texture2D *textures[DXGI_MAX_SHADER_VIEW] = {0};
video_format_t texture_fmt = *vd->source;
texture_fmt.i_width = sys->picQuad.generic.i_width;
texture_fmt.i_height = sys->picQuad.generic.i_height;
if (!is_d3d11_opaque(fmt->i_chroma))
{
texture_fmt.i_chroma = sys->picQuad.generic.textureFormat->fourcc;
}
if (AllocateTextures(vd, sys->d3d_dev, sys->picQuad.generic.textureFormat, &texture_fmt,
false, textures, sys->stagingPlanes))
{
msg_Err(vd, "Failed to allocate the staging texture");
return VLC_EGENERIC;
}
if (D3D11_AllocateResourceView(vlc_object_logger(vd), sys->d3d_dev->d3ddevice, sys->picQuad.generic.textureFormat,
textures, 0, sys->stagingSys.renderSrc))
{
msg_Err(vd, "Failed to allocate the staging shader view");
return VLC_EGENERIC;
}
for (unsigned plane = 0; plane < DXGI_MAX_SHADER_VIEW; plane++)
sys->stagingSys.texture[plane] = textures[plane];
}
return VLC_SUCCESS;
}
static enum d3d11_hdr HdrModeFromString(vlc_logger *logger, const char *psz_hdr)
{
if (strcmp("auto", psz_hdr) == 0)
return hdr_Auto;
if (strcmp("never", psz_hdr) == 0)
return hdr_Never;
if (strcmp("always", psz_hdr) == 0)
return hdr_Always;
if (strcmp("generate", psz_hdr) == 0)
return hdr_Fake;
vlc_warning(logger, "unknown HDR mode %s, using auto mode", psz_hdr);
return hdr_Auto;
}
static void InitTonemapProcessor(vout_display_t *vd, const video_format_t *fmt_in)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
if (sys->hdrMode != hdr_Fake)
return;
#ifdef HAVE_DXGI1_6_H
{ // check the main display is in HDR mode
HRESULT hr;
ComPtr<IDXGIAdapter> adapter;
ComPtr<IDXGIFactory2> factory;
ComPtr<IDXGIOutput> dxgiOutput;
ComPtr<IDXGIOutput6> dxgiOutput6;
DXGI_OUTPUT_DESC1 desc1;
hr = CreateDXGIFactory1(IID_GRAPHICS_PPV_ARGS(factory.GetAddressOf()));
if (FAILED(hr))
goto error;
UINT adapter_index = 0;
hr = factory->EnumAdapters(adapter_index, adapter.GetAddressOf());
if (FAILED(hr))
goto error;
UINT output_index = 0;
hr = adapter->EnumOutputs(output_index, dxgiOutput.GetAddressOf());
if (FAILED(hr))
goto error;
hr = dxgiOutput.As(&dxgiOutput6);
if (FAILED(hr))
goto error;
hr = dxgiOutput6->GetDesc1(&desc1);
if (FAILED(hr))
goto error;
if (desc1.ColorSpace != DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
{
msg_Warn(vd, "not an HDR display");
goto error;
}
}
#endif
sys->tonemapProc = D3D11_TonemapperCreate(VLC_OBJECT(vd), sys->d3d_dev, fmt_in);
if (sys->tonemapProc != NULL)
{
msg_Dbg(vd, "Using tonemapper");
return;
}
error:
sys->hdrMode = hdr_Auto;
msg_Dbg(vd, "failed to create the tone mapper, using default HDR mode");
}
static const auto ops = []{
struct vlc_display_operations ops {};
ops.close = Close;
ops.prepare = Prepare;
ops.display = Display;
ops.control = Control;
ops.set_viewpoint = SetViewpoint;
return ops;
}();
static int Open(vout_display_t *vd,
video_format_t *fmtp, vlc_video_context *context)
{
vout_display_sys_t *sys = new (std::nothrow) vout_display_sys_t();
if (!sys)
return VLC_ENOMEM;
vd->sys = sys;
char *psz_hdr = var_InheritString(vd, "d3d11-hdr-mode");
sys->hdrMode = HdrModeFromString(vlc_object_logger(vd), psz_hdr);
free(psz_hdr);
d3d11_decoder_device_t *dev_sys = NULL;
int ret = D3D_CreateShaderCompiler(VLC_OBJECT(vd), &sys->shaders);
if (ret != VLC_SUCCESS)
goto error;
CommonInit(&sys->area, &sys->picQuad.quad_fmt);
sys->outside_opaque = var_InheritAddress( vd, "vout-cb-opaque" );
sys->updateOutputCb = (libvlc_video_update_output_cb)var_InheritAddress( vd, "vout-cb-update-output" );
sys->swapCb = (libvlc_video_swap_cb)var_InheritAddress( vd, "vout-cb-swap" );
sys->startEndRenderingCb = (libvlc_video_makeCurrent_cb)var_InheritAddress( vd, "vout-cb-make-current" );
sys->sendMetadataCb = (libvlc_video_frameMetadata_cb)var_InheritAddress( vd, "vout-cb-metadata" );
sys->selectPlaneCb = (libvlc_video_output_select_plane_cb)var_InheritAddress( vd, "vout-cb-select-plane" );
dev_sys = GetD3D11OpaqueContext( context );
if ( dev_sys == NULL )
{
// No d3d11 device, we create one
sys->local_d3d_dev = D3D11_CreateDevice(vd, NULL, false, vd->obj.force);
if (sys->local_d3d_dev == NULL) {
msg_Err(vd, "Could not Create the D3D11 device.");
goto error;
}
dev_sys = sys->local_d3d_dev;
}
sys->d3d_dev = &dev_sys->d3d_dev;
InitTonemapProcessor(vd, vd->source);
if ( sys->swapCb == NULL || sys->startEndRenderingCb == NULL || sys->updateOutputCb == NULL )
{
#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
msg_Err(vd, "UWP apps needs to set an external rendering target");
goto error;
#else // WINAPI_PARTITION_DESKTOP
if (vd->cfg->window->type == VLC_WINDOW_TYPE_HWND)
{
if (CommonWindowInit(vd, &sys->area,
vd->source->projection_mode != PROJECTION_MODE_RECTANGULAR))
goto error;
}
/* use our internal swapchain callbacks */
dxgi_swapchain *swap = nullptr;
#if defined(HAVE_DCOMP_H)
if (vd->cfg->window->type == VLC_WINDOW_TYPE_DCOMP)
swap = DXGI_CreateLocalSwapchainHandleDComp(VLC_OBJECT(vd),
vd->cfg->window->display.dcomp_device,
vd->cfg->window->handle.dcomp_visual);
else
#endif //HAVE_DCOMP_H
swap = DXGI_CreateLocalSwapchainHandleHwnd(VLC_OBJECT(vd), CommonVideoHWND(&sys->area));
if (unlikely(swap == NULL))
goto error;
sys->outside_opaque = D3D11_CreateLocalSwapchain(VLC_OBJECT(vd), sys->d3d_dev, swap,
sys->hdrMode != hdr_Never && sys->hdrMode != hdr_Always);
if (unlikely(sys->outside_opaque == NULL))
{
DXGI_LocalSwapchainCleanupDevice(swap);
goto error;
}
sys->updateOutputCb = D3D11_LocalSwapchainUpdateOutput;
sys->swapCb = D3D11_LocalSwapchainSwap;
sys->startEndRenderingCb = D3D11_LocalSwapchainStartEndRendering;
sys->sendMetadataCb = D3D11_LocalSwapchainSetMetadata;
sys->selectPlaneCb = D3D11_LocalSwapchainSelectPlane;
#endif // WINAPI_PARTITION_DESKTOP
}
if (Direct3D11Open(vd, fmtp, context)) {
msg_Err(vd, "Direct3D11 could not be opened");
goto error;
}
vlc_window_SetTitle(vd->cfg->window, VOUT_TITLE " (Direct3D11 output)");
msg_Dbg(vd, "Direct3D11 display adapter successfully initialized");
if (var_InheritBool(vd, "direct3d11-hw-blending") &&
sys->regionQuad.generic.textureFormat != NULL)
{
sys->pSubpictureChromas[0] = sys->regionQuad.generic.textureFormat->fourcc;
sys->pSubpictureChromas[1] = 0;
vd->info.subpicture_chromas = sys->pSubpictureChromas;
}
else
vd->info.subpicture_chromas = NULL;
vd->ops = &ops;
msg_Dbg(vd, "Direct3D11 Open Succeeded");
return VLC_SUCCESS;
error:
Close(vd);
return VLC_EGENERIC;
}
static void Close(vout_display_t *vd)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
D3D_ReleaseShaderCompiler(sys->shaders);
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
CommonWindowClean(&sys->area);
#endif
Direct3D11Close(vd);
delete sys;
}
static int Control(vout_display_t *vd, int query)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
if (sys->upscaleMode == upscale_VideoProcessor || sys->upscaleMode == upscale_SuperResolution)
{
D3D11_UpscalerUpdate(VLC_OBJECT(vd), sys->scaleProc, sys->d3d_dev,
vd->source, &sys->picQuad.quad_fmt, &vd->cfg->display);
if (sys->scaleProc && D3D11_UpscalerUsed(sys->scaleProc))
{
D3D11_UpscalerGetSize(sys->scaleProc, &sys->picQuad.quad_fmt.i_width, &sys->picQuad.quad_fmt.i_height);
sys->picQuad.quad_fmt.i_x_offset = 0;
sys->picQuad.quad_fmt.i_y_offset = 0;
sys->picQuad.quad_fmt.i_visible_width = sys->picQuad.quad_fmt.i_width;
sys->picQuad.quad_fmt.i_visible_height = sys->picQuad.quad_fmt.i_height;
sys->picQuad.generic.i_width = sys->picQuad.quad_fmt.i_width;
sys->picQuad.generic.i_height = sys->picQuad.quad_fmt.i_height;
}
}
CommonControl( vd, &sys->area, query );
if ( sys->area.place_changed )
{
UpdateSize(vd);
sys->area.place_changed =false;
}
return VLC_SUCCESS;
}
static bool SelectRenderPlane(void *opaque, size_t plane, ID3D11RenderTargetView **targetView)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(opaque);
if (!sys->selectPlaneCb)
{
*targetView = NULL;
return plane == 0; // we only support one packed RGBA plane by default
}
return sys->selectPlaneCb(sys->outside_opaque, plane, (void*)targetView);
}
static void PreparePicture(vout_display_t *vd, picture_t *picture,
const vlc_render_subpicture *subpicture,
vlc_tick_t date)
{
VLC_UNUSED(date);
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
if (sys->picQuad.generic.textureFormat->formatTexture == DXGI_FORMAT_UNKNOWN)
{
D3D11_MAPPED_SUBRESOURCE mappedResource;
int i;
HRESULT hr;
bool b_mapped = true;
for (i = 0; i < picture->i_planes; i++) {
hr = sys->d3d_dev->d3dcontext->Map(sys->stagingSys.resource[i],
0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if( unlikely(FAILED(hr)) )
{
while (i-- > 0)
sys->d3d_dev->d3dcontext->Unmap(sys->stagingSys.resource[i], 0);
b_mapped = false;
break;
}
sys->stagingPlanes[i].i_pitch = mappedResource.RowPitch;
sys->stagingPlanes[i].p_pixels = static_cast<uint8_t*>(mappedResource.pData);
}
if (b_mapped)
{
for (i = 0; i < picture->i_planes; i++)
plane_CopyPixels(&sys->stagingPlanes[i], &picture->p[i]);
for (i = 0; i < picture->i_planes; i++)
sys->d3d_dev->d3dcontext->Unmap(sys->stagingSys.resource[i], 0);
}
}
else if (!is_d3d11_opaque(picture->format.i_chroma))
{
D3D11_MAPPED_SUBRESOURCE mappedResource;
HRESULT hr;
hr = sys->d3d_dev->d3dcontext->Map(sys->stagingSys.resource[0],
0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if( unlikely(FAILED(hr)) )
msg_Err(vd, "Failed to map the %4.4s staging picture. (hr=0x%lX)", (const char*)&picture->format.i_chroma, hr);
else
{
uint8_t *buf = static_cast<uint8_t*>(mappedResource.pData);
for (int i = 0; i < picture->i_planes; i++)
{
sys->stagingPlanes[i].i_pitch = mappedResource.RowPitch;
sys->stagingPlanes[i].p_pixels = buf;
plane_CopyPixels(&sys->stagingPlanes[i], &picture->p[i]);
buf += sys->stagingPlanes[i].i_pitch * sys->stagingPlanes[i].i_lines;
}
sys->d3d_dev->d3dcontext->Unmap(sys->stagingSys.resource[0], 0);
}
}
else
{
picture_sys_d3d11_t *p_sys = ActiveD3D11PictureSys(picture);
D3D11_TEXTURE2D_DESC srcDesc;
p_sys->texture[KNOWN_DXGI_INDEX]->GetDesc(&srcDesc);
if (sys->tonemapProc)
{
if (FAILED(D3D11_TonemapperProcess(VLC_OBJECT(vd), sys->tonemapProc, p_sys)))
return;
}
else if (sys->scaleProc && D3D11_UpscalerUsed(sys->scaleProc))
{
if (D3D11_UpscalerScale(VLC_OBJECT(vd), sys->scaleProc, p_sys) != VLC_SUCCESS)
return;
uint32_t witdh, height;
D3D11_UpscalerGetSize(sys->scaleProc, &witdh, &height);
srcDesc.Width = witdh;
srcDesc.Height = height;
}
else if (sys->legacy_shader)
{
D3D11_TEXTURE2D_DESC texDesc;
sys->stagingSys.texture[0]->GetDesc(&texDesc);
D3D11_BOX box;
box.top = 0;
box.bottom = __MIN(srcDesc.Height, texDesc.Height);
box.left = 0;
box.right = __MIN(srcDesc.Width, texDesc.Width);
box.back = 1;
sys->d3d_dev->d3dcontext->CopySubresourceRegion(
sys->stagingSys.resource[KNOWN_DXGI_INDEX],
0, 0, 0, 0,
p_sys->resource[KNOWN_DXGI_INDEX],
p_sys->slice_index, &box);
}
else
{
if (srcDesc.BindFlags & D3D11_BIND_SHADER_RESOURCE)
{
/* for performance reason we don't want to allocate this during
* display, do it preferably when creating the texture */
assert(p_sys->renderSrc[0]!=NULL);
}
if ( sys->picQuad.generic.i_height != srcDesc.Height ||
sys->picQuad.generic.i_width != srcDesc.Width )
{
/* the decoder produced different sizes than the vout, we need to
* adjust the vertex */
sys->picQuad.quad_fmt.i_width = srcDesc.Width;
sys->picQuad.quad_fmt.i_height = srcDesc.Height;
sys->picQuad.generic.i_width = sys->picQuad.quad_fmt.i_width;
sys->picQuad.generic.i_height = sys->picQuad.quad_fmt.i_height;
CommonPlacePicture(vd, &sys->area);
UpdateSize(vd);
}
}
}
if (subpicture) {
int subpicture_region_count = 0;
picture_t **subpicture_regions = NULL;
Direct3D11MapSubpicture(vd, &subpicture_region_count, &subpicture_regions, subpicture);
Direct3D11DeleteRegions(sys->d3dregion_count, sys->d3dregions);
sys->d3dregion_count = subpicture_region_count;
sys->d3dregions = subpicture_regions;
}
if (picture->format.mastering.max_luminance)
{
D3D11_UpdateQuadLuminanceScale(vd, sys->d3d_dev, &sys->picQuad, (float)sys->display.luminance_peak / D3D_GetFormatLuminance(VLC_OBJECT(vd), &picture->format));
}
/* Render the quad */
ID3D11ShaderResourceView **renderSrc;
ID3D11ShaderResourceView *SRV[DXGI_MAX_SHADER_VIEW];
if (sys->scaleProc && D3D11_UpscalerUsed(sys->scaleProc))
{
D3D11_UpscalerGetSRV(sys->scaleProc, SRV);
renderSrc = SRV;
}
else if (sys->legacy_shader)
renderSrc = sys->stagingSys.renderSrc;
else {
picture_sys_d3d11_t *p_sys = ActiveD3D11PictureSys(picture);
if (sys->tonemapProc)
p_sys = D3D11_TonemapperGetOutput(sys->tonemapProc);
renderSrc = p_sys->renderSrc;
}
D3D11_RenderQuad(sys->d3d_dev, &sys->picQuad,
vd->source->projection_mode == PROJECTION_MODE_RECTANGULAR ? &sys->flatVShader : &sys->projectionVShader,
renderSrc, SelectRenderPlane, sys);
if (subpicture) {
// draw the additional vertices
for (int i = 0; i < sys->d3dregion_count; ++i) {
if (sys->d3dregions[i])
{
d3d11_quad_t *quad = (d3d11_quad_t *) sys->d3dregions[i]->p_sys;
D3D11_RenderQuad(sys->d3d_dev, quad, &sys->flatVShader,
quad->picSys.renderSrc, SelectRenderPlane, sys);
}
}
}
#ifdef HAVE_D3D11_4_H
if (sys->log_level >= 4)
{
vlc_tick_t render_start = vlc_tick_now();
D3D11_WaitFence(sys->fence);
msg_Dbg(vd, "waited %" PRId64 " ms for the render fence", MS_FROM_VLC_TICK(vlc_tick_now() - render_start));
}
else
{
D3D11_WaitFence(sys->fence);
}
#endif
}
static void Prepare(vout_display_t *vd, picture_t *picture,
const vlc_render_subpicture *subpicture, vlc_tick_t date)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
d3d11_device_lock( sys->d3d_dev );
if ( sys->startEndRenderingCb( sys->outside_opaque, true ))
{
if ( sys->sendMetadataCb && picture->format.mastering.max_luminance )
{
libvlc_video_frame_hdr10_metadata_t hdr10;
hdr10.GreenPrimary[0] = picture->format.mastering.primaries[0];
hdr10.GreenPrimary[1] = picture->format.mastering.primaries[1];
hdr10.BluePrimary[0] = picture->format.mastering.primaries[2];
hdr10.BluePrimary[1] = picture->format.mastering.primaries[3];
hdr10.RedPrimary[0] = picture->format.mastering.primaries[4];
hdr10.RedPrimary[1] = picture->format.mastering.primaries[5];
hdr10.WhitePoint[0] = picture->format.mastering.white_point[0];
hdr10.WhitePoint[1] = picture->format.mastering.white_point[1];
hdr10.MinMasteringLuminance = picture->format.mastering.min_luminance;
hdr10.MaxMasteringLuminance = picture->format.mastering.max_luminance;
hdr10.MaxContentLightLevel = picture->format.lighting.MaxCLL;
hdr10.MaxFrameAverageLightLevel = picture->format.lighting.MaxFALL;
sys->sendMetadataCb( sys->outside_opaque, libvlc_video_metadata_frame_hdr10, &hdr10 );
}
PreparePicture(vd, picture, subpicture, date);
sys->startEndRenderingCb( sys->outside_opaque, false );
}
d3d11_device_unlock( sys->d3d_dev );
}
static void Display(vout_display_t *vd, picture_t *picture)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
VLC_UNUSED(picture);
d3d11_device_lock( sys->d3d_dev );
sys->swapCb(sys->outside_opaque);
d3d11_device_unlock( sys->d3d_dev );
}
static const d3d_format_t *GetDirectRenderingFormat(vout_display_t *vd, vlc_fourcc_t i_src_chroma)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
UINT supportFlags = D3D11_FORMAT_SUPPORT_SHADER_LOAD;
if (is_d3d11_opaque(i_src_chroma))
supportFlags |= D3D11_FORMAT_SUPPORT_DECODER_OUTPUT;
return FindD3D11Format( vd, sys->d3d_dev, i_src_chroma, DXGI_RGB_FORMAT|DXGI_YUV_FORMAT, 0, 0, 0, 0,
is_d3d11_opaque(i_src_chroma) ? DXGI_CHROMA_GPU : DXGI_CHROMA_CPU, supportFlags );
}
static const d3d_format_t *GetDirectDecoderFormat(vout_display_t *vd, vlc_fourcc_t i_src_chroma)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
UINT supportFlags = D3D11_FORMAT_SUPPORT_DECODER_OUTPUT;
return FindD3D11Format( vd, sys->d3d_dev, i_src_chroma, DXGI_RGB_FORMAT|DXGI_YUV_FORMAT, 0, 0, 0, 0,
DXGI_CHROMA_GPU, supportFlags );
}
static const d3d_format_t *GetDisplayFormatByDepth(vout_display_t *vd, uint8_t bit_depth,
uint8_t widthDenominator,
uint8_t heightDenominator,
uint8_t alpha_bits,
bool from_processor,
int rgb_yuv)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
UINT supportFlags = D3D11_FORMAT_SUPPORT_SHADER_LOAD;
if (from_processor)
supportFlags |= D3D11_FORMAT_SUPPORT_VIDEO_PROCESSOR_OUTPUT;
return FindD3D11Format( vd, sys->d3d_dev, 0, rgb_yuv,
bit_depth, widthDenominator+1, heightDenominator+1, alpha_bits,
DXGI_CHROMA_CPU, supportFlags );
}
static const d3d_format_t *GetBlendableFormat(vout_display_t *vd, vlc_fourcc_t i_src_chroma)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
UINT supportFlags = D3D11_FORMAT_SUPPORT_SHADER_LOAD | D3D11_FORMAT_SUPPORT_BLENDABLE;
return FindD3D11Format( vd, sys->d3d_dev, i_src_chroma, DXGI_RGB_FORMAT|DXGI_YUV_FORMAT, 0, 0, 0, 8, DXGI_CHROMA_CPU, supportFlags );
}
static void InitScaleProcessor(vout_display_t *vd)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
if (sys->upscaleMode != upscale_VideoProcessor && sys->upscaleMode != upscale_SuperResolution)
return;
sys->scaleProc = D3D11_UpscalerCreate(VLC_OBJECT(vd), sys->d3d_dev, sys->picQuad.quad_fmt.i_chroma,
sys->upscaleMode == upscale_SuperResolution, &sys->picQuad.generic.textureFormat);
if (sys->scaleProc == NULL)
{
msg_Dbg(vd, "forcing linear sampler");
sys->upscaleMode = upscale_LinearSampler;
}
msg_Dbg(vd, "Using %s scaler", ppsz_upscale_mode_text[sys->upscaleMode]);
}
static int Direct3D11Open(vout_display_t *vd, video_format_t *fmtp, vlc_video_context *vctx)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
video_format_t fmt;
video_format_Copy(&fmt, vd->source);
video_format_Copy(&sys->picQuad.quad_fmt, &fmt);
int err = SetupOutputFormat(vd, &fmt, vctx, &sys->picQuad.quad_fmt);
if (err != VLC_SUCCESS)
{
if (!is_d3d11_opaque(vd->source->i_chroma)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
&& vd->obj.force
#endif
)
{
const vlc_fourcc_t *list = vlc_fourcc_GetFallback(vd->source->i_chroma);
for (unsigned i = 0; list[i] != 0; i++) {
if (list[i] == vd->source->i_chroma)
continue;
fmt.i_chroma = list[i];
err = SetupOutputFormat(vd, &fmt, nullptr, &sys->picQuad.quad_fmt);
if (err == VLC_SUCCESS)
break;
}
}
if (err != VLC_SUCCESS)
{
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
if ( sys->swapCb == D3D11_LocalSwapchainSwap )
D3D11_LocalSwapchainCleanupDevice( sys->outside_opaque );
#endif // WINAPI_PARTITION_DESKTOP
return err;
}
}
if (!is_d3d11_opaque(fmt.i_chroma))
{
sys->picQuad.quad_fmt.i_chroma = sys->picQuad.generic.textureFormat->fourcc;
}
/* adjust the decoder sizes to have proper padding */
if ( sys->picQuad.generic.textureFormat->heightDenominator != 1 ||
sys->picQuad.generic.textureFormat->widthDenominator != 1 )
{
sys->picQuad.quad_fmt.i_width = (sys->picQuad.quad_fmt.i_width + 0x01) & ~0x01;
sys->picQuad.quad_fmt.i_height = (sys->picQuad.quad_fmt.i_height + 0x01) & ~0x01;
}
sys->picQuad.generic.i_width = sys->picQuad.quad_fmt.i_width;
sys->picQuad.generic.i_height = sys->picQuad.quad_fmt.i_height;
char *psz_upscale = var_InheritString(vd, "d3d11-upscale-mode");
if (strcmp("linear", psz_upscale) == 0)
sys->upscaleMode = upscale_LinearSampler;
else if (strcmp("point", psz_upscale) == 0)
sys->upscaleMode = upscale_PointSampler;
else if (strcmp("processor", psz_upscale) == 0)
sys->upscaleMode = upscale_VideoProcessor;
else if (strcmp("super", psz_upscale) == 0)
sys->upscaleMode = upscale_SuperResolution;
else
{
msg_Warn(vd, "unknown upscale mode %s, using linear sampler", psz_upscale);
sys->upscaleMode = upscale_LinearSampler;
}
free(psz_upscale);
InitScaleProcessor(vd);
CommonPlacePicture(vd, &sys->area);
err = UpdateDisplayFormat(vd, &sys->picQuad.quad_fmt);
if (err != VLC_SUCCESS) {
msg_Err(vd, "Could not update the backbuffer");
return err;
}
if (Direct3D11CreateGenericResources(vd)) {
msg_Err(vd, "Failed to allocate resources");
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
if ( sys->swapCb == D3D11_LocalSwapchainSwap )
D3D11_LocalSwapchainCleanupDevice( sys->outside_opaque );
#endif // WINAPI_PARTITION_DESKTOP
return VLC_EGENERIC;
}
video_format_Clean(fmtp);
*fmtp = fmt;
sys->log_level = var_InheritInteger(vd, "verbose");
return VLC_SUCCESS;
}
static const d3d_format_t *SelectOutputFormat(vout_display_t *vd, const video_format_t *fmt, vlc_video_context *vctx,
const d3d_format_t * & decoder_format)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
const d3d_format_t *res = nullptr;
d3d11_video_context_t *vtcx_sys = GetD3D11ContextPrivate(vctx);
if (vtcx_sys != NULL &&
D3D11_DeviceSupportsFormat( sys->d3d_dev, vtcx_sys->format, D3D11_FORMAT_SUPPORT_SHADER_LOAD ))
{
res = D3D11_RenderFormat(vtcx_sys->format, vtcx_sys->secondary ,true);
if (res != nullptr)
return res;
}
// look for the requested pixel format first
res = GetDirectRenderingFormat(vd, fmt->i_chroma);
if (res != nullptr)
return res;
// look for any pixel format that we can handle with enough pixels per channel
uint8_t bits_per_channel;
uint8_t widthDenominator, heightDenominator;
uint8_t alpha_bits = 0;
vlc_fourcc_t cpu_chroma;
if (is_d3d11_opaque(fmt->i_chroma))
cpu_chroma = DxgiFormatFourcc(vtcx_sys->format);
else if (is_nvdec_opaque(fmt->i_chroma))
cpu_chroma = NVDECToVlcChroma(fmt->i_chroma);
else
cpu_chroma = fmt->i_chroma;
const auto *p_format = vlc_fourcc_GetChromaDescription(cpu_chroma);
if (unlikely(p_format == NULL || p_format->plane_count == 0))
{
bits_per_channel = 8;
widthDenominator = heightDenominator = 2;
}
else
{
bits_per_channel = p_format->pixel_bits /
(p_format->plane_count==1 ? p_format->pixel_size : 1);
widthDenominator = heightDenominator = 1;
for (size_t i=0; i<p_format->plane_count; i++)
{
if (widthDenominator < p_format->p[i].w.den)
widthDenominator = p_format->p[i].w.den;
if (heightDenominator < p_format->p[i].h.den)
heightDenominator = p_format->p[1].h.den;
}
switch (cpu_chroma) // FIXME get this info from the core
{
case VLC_CODEC_YUVA:
case VLC_CODEC_YUV422A:
case VLC_CODEC_YUV420A:
case VLC_CODEC_VUYA:
case VLC_CODEC_RGBA:
case VLC_CODEC_ARGB:
case VLC_CODEC_BGRA:
case VLC_CODEC_ABGR:
case VLC_CODEC_D3D11_OPAQUE_RGBA:
case VLC_CODEC_D3D11_OPAQUE_BGRA:
case VLC_CODEC_D3D11_OPAQUE_ALPHA:
alpha_bits = 8;
break;
case VLC_CODEC_YUVA_444_10L:
case VLC_CODEC_YUVA_444_10B:
alpha_bits = 10;
break;
case VLC_CODEC_RGBA10LE:
bits_per_channel = 10;
alpha_bits = 2;
break;
case VLC_CODEC_YUVA_444_12L:
case VLC_CODEC_YUVA_444_12B:
alpha_bits = 12;
break;
case VLC_CODEC_RGBA64:
alpha_bits = 16;
break;
}
}
/* look for a decoder format that can be decoded but not used in shaders */
if ( is_d3d11_opaque(fmt->i_chroma) )
decoder_format = GetDirectDecoderFormat(vd, fmt->i_chroma);
bool is_rgb = !vlc_fourcc_IsYUV(fmt->i_chroma);
res = GetDisplayFormatByDepth(vd, bits_per_channel,
widthDenominator, heightDenominator, alpha_bits,
decoder_format!=nullptr,
is_rgb ? DXGI_RGB_FORMAT : DXGI_YUV_FORMAT);
if (res != nullptr)
return res;
res = GetDisplayFormatByDepth(vd, bits_per_channel,
widthDenominator, heightDenominator, alpha_bits,
decoder_format!=nullptr,
is_rgb ? DXGI_YUV_FORMAT : DXGI_RGB_FORMAT);
if (res != nullptr)
return res;
// look for any pixel format that we can handle
return GetDisplayFormatByDepth(vd, 0, 0, 0, 0, true, DXGI_YUV_FORMAT|DXGI_RGB_FORMAT);
}
static int SetupOutputFormat(vout_display_t *vd, video_format_t *fmt, vlc_video_context *vctx, video_format_t *quad_fmt)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
const d3d_format_t *decoder_format = nullptr;
if (sys->hdrMode == hdr_Fake)
{
// force a fake HDR source
// corresponds to DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020
vctx = nullptr; // TODO create an internal one from the tonemapper
quad_fmt->i_chroma = VLC_CODEC_RGBA10LE;
quad_fmt->primaries = COLOR_PRIMARIES_BT2020;
quad_fmt->transfer = TRANSFER_FUNC_SMPTE_ST2084;
quad_fmt->space = COLOR_SPACE_BT2020;
quad_fmt->color_range = COLOR_RANGE_FULL;
// request an input format that can be input of a VideoProcessor
UINT supportFlags = D3D11_FORMAT_SUPPORT_VIDEO_PROCESSOR_INPUT;
decoder_format = FindD3D11Format( vd, sys->d3d_dev, fmt->i_chroma, DXGI_RGB_FORMAT|DXGI_YUV_FORMAT, 0, 0, 0, 0,
is_d3d11_opaque(fmt->i_chroma) ? DXGI_CHROMA_GPU : DXGI_CHROMA_CPU, supportFlags );
}
sys->picQuad.generic.textureFormat = SelectOutputFormat(vd, quad_fmt, vctx, decoder_format);
if ( !sys->picQuad.generic.textureFormat )
{
msg_Err(vd, "Could not get a suitable texture pixel format");
return VLC_EGENERIC;
}
msg_Dbg( vd, "Using pixel format %s for chroma %4.4s", sys->picQuad.generic.textureFormat->name,
(char *)&fmt->i_chroma );
fmt->i_chroma = decoder_format ? decoder_format->fourcc : sys->picQuad.generic.textureFormat->fourcc;
/* select the subpicture region pixel format */
sys->regionQuad.generic.textureFormat = GetBlendableFormat(vd, VLC_CODEC_RGBA);
if (!sys->regionQuad.generic.textureFormat)
sys->regionQuad.generic.textureFormat = GetBlendableFormat(vd, VLC_CODEC_BGRA);
return VLC_SUCCESS;
}
static void Direct3D11Close(vout_display_t *vd)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
Direct3D11DestroyResources(vd);
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
if ( sys->swapCb == D3D11_LocalSwapchainSwap )
D3D11_LocalSwapchainCleanupDevice( sys->outside_opaque );
#endif // WINAPI_PARTITION_DESKTOP
if (sys->d3d_dev && sys->d3d_dev == &sys->local_d3d_dev->d3d_dev)
D3D11_ReleaseDevice( sys->local_d3d_dev );
msg_Dbg(vd, "Direct3D11 display adapter closed");
}
static bool CanUseTextureArray(vout_display_t *vd)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
// 15.200.1062.1004 is wrong - 2015/08/03 - 15.7.1 WHQL
// 21.19.144.1281 is wrong -
// 22.19.165.3 is good - 2017/05/04 - ReLive Edition 17.5.1
const auto WDDM_os = []{
struct wddm_version wddm = {};
wddm.wddm = 21; // starting with drivers designed for W10 Anniversary Update
return wddm;
}();
if (D3D11CheckDriverVersion(sys->d3d_dev, GPU_MANUFACTURER_AMD, &WDDM_os) != VLC_SUCCESS)
{
msg_Dbg(vd, "AMD driver too old, fallback to legacy shader mode");
return false;
}
// xx.xx.1000.xxx drivers can't happen here for WDDM > 2.0
const auto WDDM_build = []{
struct wddm_version wddm = {};
wddm.revision = 162;
return wddm;
}();
if (D3D11CheckDriverVersion(sys->d3d_dev, GPU_MANUFACTURER_AMD, &WDDM_build) != VLC_SUCCESS)
{
msg_Dbg(vd, "Bogus AMD driver detected, fallback to legacy shader mode");
return false;
}
return true;
}
static bool BogusZeroCopy(const vout_display_t *vd)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
if (sys->d3d_dev->adapterDesc.VendorId != GPU_MANUFACTURER_AMD)
return false;
switch (sys->d3d_dev->adapterDesc.DeviceId)
{
case 0x687F: // RX Vega 56/64
case 0x6863: // RX Vega Frontier Edition
case 0x15DD: // RX Vega 8/11 (Ryzen iGPU)
{
const auto WDDM = []{
struct wddm_version wddm = {};
wddm.revision = 14011; // 18.10.2 - 2018/06/11
return wddm;
}();
return D3D11CheckDriverVersion(sys->d3d_dev, GPU_MANUFACTURER_AMD, &WDDM) != VLC_SUCCESS;
}
default:
return false;
}
}
/* TODO : handle errors better
TODO : separate out into smaller functions like createshaders */
static int Direct3D11CreateFormatResources(vout_display_t *vd, const video_format_t *fmt)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
HRESULT hr;
sys->legacy_shader = sys->d3d_dev->feature_level < D3D_FEATURE_LEVEL_10_0 ||
(sys->scaleProc == nullptr && !CanUseTextureArray(vd)) ||
BogusZeroCopy(vd) || (sys->tonemapProc == NULL && !is_d3d11_opaque(fmt->i_chroma));
d3d_shader_blob pPSBlob[DXGI_MAX_RENDER_TARGET] = { };
hr = D3D11_CompilePixelShaderBlob(vd, sys->shaders, sys->d3d_dev,
&sys->display, fmt->transfer,
fmt->color_range == COLOR_RANGE_FULL,
&sys->picQuad, pPSBlob);
if (FAILED(hr))
{
msg_Err(vd, "Failed to compile the pixel shader. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
hr = D3D11_SetQuadPixelShader(VLC_OBJECT(vd), sys->d3d_dev, sys->upscaleMode != upscale_LinearSampler,
&sys->picQuad, pPSBlob);
if (FAILED(hr))
{
msg_Err(vd, "Failed to set the pixel shader. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
if (D3D11_AllocateQuad(vd, sys->d3d_dev, sys->picQuad.quad_fmt.projection_mode, &sys->picQuad) != VLC_SUCCESS)
{
msg_Err(vd, "Could not allocate quad buffers.");
return VLC_EGENERIC;
}
if (D3D11_SetupQuad( vd, sys->d3d_dev, &sys->picQuad.quad_fmt, &sys->picQuad, &sys->display) != VLC_SUCCESS)
{
msg_Err(vd, "Could not Create the main quad picture.");
return VLC_EGENERIC;
}
RECT source_rect;
source_rect.left = sys->picQuad.quad_fmt.i_x_offset;
source_rect.right = sys->picQuad.quad_fmt.i_x_offset + sys->picQuad.quad_fmt.i_visible_width;
source_rect.top = sys->picQuad.quad_fmt.i_y_offset;
source_rect.bottom = sys->picQuad.quad_fmt.i_y_offset + sys->picQuad.quad_fmt.i_visible_height;
if (!D3D11_UpdateQuadPosition(vd, sys->d3d_dev, &sys->picQuad, &source_rect,
video_format_GetTransform(sys->picQuad.quad_fmt.orientation, sys->display.orientation)))
{
msg_Err(vd, "Could not set quad picture position.");
return VLC_EGENERIC;
}
if ( vd->source->projection_mode == PROJECTION_MODE_EQUIRECTANGULAR ||
vd->source->projection_mode == PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD )
D3D11_UpdateViewpoint( vd, sys->d3d_dev, &sys->picQuad, &vd->cfg->viewpoint,
(float) vd->cfg->display.width / vd->cfg->display.height );
if (is_d3d11_opaque(fmt->i_chroma)) {
ComPtr<ID3D10Multithread> pMultithread;
hr = sys->d3d_dev->d3ddevice->QueryInterface(IID_GRAPHICS_PPV_ARGS(&pMultithread));
if (SUCCEEDED(hr))
pMultithread->SetMultithreadProtected(TRUE);
}
return UpdateStaging(vd, fmt);
}
static int Direct3D11CreateGenericResources(vout_display_t *vd)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
HRESULT hr;
#ifdef HAVE_D3D11_4_H
hr = D3D11_InitFence(*sys->d3d_dev, sys->fence);
if (SUCCEEDED(hr))
{
msg_Dbg(vd, "using GPU render fence");
}
#endif
ComPtr<ID3D11BlendState> pSpuBlendState;
D3D11_BLEND_DESC spuBlendDesc = { };
spuBlendDesc.RenderTarget[0].BlendEnable = TRUE;
spuBlendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
/* output colors */
spuBlendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
spuBlendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; /* keep source intact */
spuBlendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; /* RGB colors + inverse alpha (255 is full opaque) */
/* output alpha */
spuBlendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
spuBlendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; /* keep source intact */
spuBlendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; /* discard */
hr = sys->d3d_dev->d3ddevice->CreateBlendState(&spuBlendDesc, &pSpuBlendState);
if (FAILED(hr)) {
msg_Err(vd, "Could not create SPU blend state. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
sys->d3d_dev->d3dcontext->OMSetBlendState(pSpuBlendState.Get(), NULL, 0xFFFFFFFF);
/* disable depth testing as we're only doing 2D
* see https://msdn.microsoft.com/en-us/library/windows/desktop/bb205074%28v=vs.85%29.aspx
* see http://rastertek.com/dx11tut11.html
*/
D3D11_DEPTH_STENCIL_DESC stencilDesc = { };
ComPtr<ID3D11DepthStencilState> pDepthStencilState;
hr = sys->d3d_dev->d3ddevice->CreateDepthStencilState(&stencilDesc, &pDepthStencilState );
if (SUCCEEDED(hr))
sys->d3d_dev->d3dcontext->OMSetDepthStencilState(pDepthStencilState.Get(), 0);
if (sys->regionQuad.generic.textureFormat != NULL)
{
d3d_shader_blob pPSBlob[DXGI_MAX_RENDER_TARGET] = { };
hr = D3D11_CompilePixelShaderBlob(vd, sys->shaders, sys->d3d_dev,
&sys->display, TRANSFER_FUNC_SRGB, true,
&sys->regionQuad, pPSBlob);
if (FAILED(hr))
{
msg_Err(vd, "Failed to create the SPU pixel shader. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
hr = D3D11_SetQuadPixelShader(VLC_OBJECT(vd), sys->d3d_dev, true,
&sys->regionQuad, pPSBlob);
if (FAILED(hr))
{
msg_Err(vd, "Failed to create the SPU pixel shader. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
}
d3d_shader_blob VSBlob = { };
hr = D3D11_CompileVertexShaderBlob(VLC_OBJECT(vd), sys->shaders, sys->d3d_dev, true, &VSBlob);
if(FAILED(hr)) {
msg_Err(vd, "Failed to compile the flat vertex shader. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
hr = D3D11_CreateVertexShader(vd, &VSBlob, sys->d3d_dev, &sys->flatVShader);
if(FAILED(hr)) {
msg_Err(vd, "Failed to create the vertex input layout. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
hr = D3D11_CompileVertexShaderBlob(VLC_OBJECT(vd), sys->shaders, sys->d3d_dev, false, &VSBlob);
if(FAILED(hr)) {
msg_Err(vd, "Failed to compile the 360 vertex shader. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
hr = D3D11_CreateVertexShader(vd, &VSBlob, sys->d3d_dev, &sys->projectionVShader);
if(FAILED(hr)) {
msg_Err(vd, "Failed to create the projection vertex shader. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
RECT rect_dst;
rect_dst.left = sys->area.place.x;
rect_dst.right = sys->area.place.x + sys->area.place.width;
rect_dst.top = sys->area.place.y;
rect_dst.bottom = sys->area.place.y + sys->area.place.height;
sys->picQuad.UpdateViewport( &rect_dst, sys->display.pixelFormat );
#ifndef NDEBUG
msg_Dbg( vd, "picQuad position (%.02f,%.02f) %.02fx%.02f",
sys->picQuad.cropViewport[0].TopLeftX, sys->picQuad.cropViewport[0].TopLeftY,
sys->picQuad.cropViewport[0].Width, sys->picQuad.cropViewport[0].Height );
#endif
D3D11_UpdateViewpoint( vd, sys->d3d_dev, &sys->picQuad, &vd->cfg->viewpoint,
(float) vd->cfg->display.width / vd->cfg->display.height );
msg_Dbg(vd, "Direct3D11 resources created");
return VLC_SUCCESS;
}
static void Direct3D11DestroyResources(vout_display_t *vd)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
if (sys->tonemapProc != NULL)
{
D3D11_TonemapperDestroy(sys->tonemapProc);
sys->tonemapProc = NULL;
}
if (sys->scaleProc != nullptr)
{
D3D11_UpscalerDestroy(sys->scaleProc);
sys->scaleProc = nullptr;
}
sys->picQuad.Reset();
Direct3D11DeleteRegions(sys->d3dregion_count, sys->d3dregions);
sys->d3dregion_count = 0;
sys->regionQuad.Reset();
ReleaseD3D11PictureSys(&sys->stagingSys);
D3D11_ReleaseVertexShader(&sys->flatVShader);
D3D11_ReleaseVertexShader(&sys->projectionVShader);
#ifdef HAVE_D3D11_4_H
D3D11_ReleaseFence(sys->fence);
#endif
msg_Dbg(vd, "Direct3D11 resources destroyed");
}
static void Direct3D11DeleteRegions(int count, picture_t **region)
{
for (int i = 0; i < count; ++i) {
if (region[i]) {
picture_Release(region[i]);
}
}
free(region);
}
static void DestroyPictureQuad(picture_t *p_picture)
{
d3d11_quad_t *quad = static_cast<d3d11_quad_t *>(p_picture->p_sys);
delete quad;
}
static int Direct3D11MapSubpicture(vout_display_t *vd, int *subpicture_region_count,
picture_t ***region, const vlc_render_subpicture *subpicture)
{
vout_display_sys_t *sys = static_cast<vout_display_sys_t *>(vd->sys);
D3D11_MAPPED_SUBRESOURCE mappedResource;
D3D11_TEXTURE2D_DESC texDesc;
HRESULT hr;
int err;
if (sys->regionQuad.generic.textureFormat == NULL)
return VLC_EGENERIC;
size_t count = subpicture->regions.size;
const struct subpicture_region_rendered *r;
*region = static_cast<picture_t**>(calloc(count, sizeof(picture_t *)));
if (unlikely(*region==NULL))
return VLC_ENOMEM;
*subpicture_region_count = count;
int i = 0;
vlc_vector_foreach(r, &subpicture->regions) {
if (!r->place.width || !r->place.height)
{
i++;
continue; // won't render anything, keep the cache for later
}
for (int j = 0; j < sys->d3dregion_count; j++) {
picture_t *cache = sys->d3dregions[j];
d3d11_quad_t *cache_quad = cache ? static_cast<d3d11_quad_t *>(cache->p_sys) : nullptr;
if (cache_quad != nullptr && cache_quad->picSys.texture[KNOWN_DXGI_INDEX]) {
cache_quad->picSys.texture[KNOWN_DXGI_INDEX]->GetDesc(&texDesc );
if (texDesc.Format == sys->regionQuad.generic.textureFormat->formatTexture &&
texDesc.Width == r->p_picture->format.i_width &&
texDesc.Height == r->p_picture->format.i_height) {
(*region)[i] = cache;
memset(&sys->d3dregions[j], 0, sizeof(cache)); // do not reuse this cached value a second time
break;
}
}
}
picture_t *quad_picture = (*region)[i];
d3d11_quad_t *quad;
if (quad_picture != NULL)
quad = static_cast<d3d11_quad_t*>(quad_picture->p_sys);
else
{
d3d11_quad_t *d3dquad = new (std::nothrow) d3d11_quad_t;
if (unlikely(d3dquad==NULL)) {
i++;
continue;
}
quad = d3dquad;
if (AllocateTextures(vd, sys->d3d_dev, sys->regionQuad.generic.textureFormat, &r->p_picture->format,
false, d3dquad->picSys.texture, NULL)) {
msg_Err(vd, "Failed to allocate %dx%d texture for OSD",
r->p_picture->format.i_width, r->p_picture->format.i_height);
for (int j=0; j<DXGI_MAX_SHADER_VIEW; j++)
if (d3dquad->picSys.texture[j])
d3dquad->picSys.texture[j]->Release();
delete d3dquad;
i++;
continue;
}
if (D3D11_AllocateResourceView(vlc_object_logger(vd), sys->d3d_dev->d3ddevice, sys->regionQuad.generic.textureFormat,
d3dquad->picSys.texture, 0,
d3dquad->picSys.renderSrc)) {
msg_Err(vd, "Failed to create %dx%d shader view for OSD",
r->p_picture->format.i_width, r->p_picture->format.i_height);
delete d3dquad;
i++;
continue;
}
d3dquad->generic.i_width = r->p_picture->format.i_width;
d3dquad->generic.i_height = r->p_picture->format.i_height;
d3dquad->generic.textureFormat = sys->regionQuad.generic.textureFormat;
err = D3D11_AllocateQuad(vd, sys->d3d_dev, PROJECTION_MODE_RECTANGULAR, d3dquad);
if (err != VLC_SUCCESS)
{
msg_Err(vd, "Failed to allocate %dx%d quad for OSD",
r->p_picture->format.i_width, r->p_picture->format.i_height);
delete d3dquad;
i++;
continue;
}
err = D3D11_SetupQuad( vd, sys->d3d_dev, &r->p_picture->format, d3dquad, &sys->display );
if (err != VLC_SUCCESS) {
msg_Err(vd, "Failed to setup %dx%d quad for OSD",
r->p_picture->format.i_width, r->p_picture->format.i_height);
delete d3dquad;
i++;
continue;
}
const auto picres = [](picture_sys_d3d11_t * p_sys){
picture_resource_t res = {};
res.p_sys = p_sys;
res.pf_destroy = DestroyPictureQuad;
return res;
}((picture_sys_d3d11_t *) d3dquad);
video_format_t no_crop = r->p_picture->format;
no_crop.i_x_offset = no_crop.i_y_offset = 0;
no_crop.i_visible_width = no_crop.i_width;
no_crop.i_visible_height = no_crop.i_height;
(*region)[i] = picture_NewFromResource(&no_crop, &picres);
if ((*region)[i] == NULL) {
msg_Err(vd, "Failed to create %dx%d picture for OSD",
r->p_picture->format.i_width, r->p_picture->format.i_height);
d3dquad->Reset();
i++;
continue;
}
quad_picture = (*region)[i];
for (size_t j=0; j<ARRAY_SIZE(sys->regionQuad.d3dpixelShader); j++)
{
/* TODO use something more accurate if we have different formats */
if (sys->regionQuad.d3dpixelShader[j])
{
d3dquad->d3dpixelShader[j] = sys->regionQuad.d3dpixelShader[j];
}
}
}
hr = sys->d3d_dev->d3dcontext->Map(quad->picSys.resource[KNOWN_DXGI_INDEX], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if( SUCCEEDED(hr) ) {
picture_UpdatePlanes(quad_picture, static_cast<uint8_t*>(mappedResource.pData), mappedResource.RowPitch);
picture_CopyPixels(quad_picture, r->p_picture);
sys->d3d_dev->d3dcontext->Unmap(quad->picSys.resource[KNOWN_DXGI_INDEX], 0);
} else {
msg_Err(vd, "Failed to map the SPU texture (hr=0x%lX)", hr );
picture_Release(quad_picture);
if ((*region)[i] == quad_picture)
(*region)[i] = NULL;
i++;
continue;
}
RECT output;
output.left = r->p_picture->format.i_x_offset;
output.right = r->p_picture->format.i_x_offset + r->p_picture->format.i_visible_width;
output.top = r->p_picture->format.i_y_offset;
output.bottom = r->p_picture->format.i_y_offset + r->p_picture->format.i_visible_height;
D3D11_UpdateQuadPosition(vd, sys->d3d_dev, quad, &output,
video_format_GetTransform(ORIENT_NORMAL, sys->display.orientation));
RECT spuViewport;
spuViewport.left = r->place.x;
spuViewport.top = r->place.y;
spuViewport.right = r->place.x + r->place.width;
spuViewport.bottom = r->place.y + r->place.height;
/* move the SPU inside the video area */
spuViewport.left += sys->area.place.x;
spuViewport.right += sys->area.place.x;
spuViewport.top += sys->area.place.y;
spuViewport.bottom += sys->area.place.y;
quad->UpdateViewport( &spuViewport, sys->display.pixelFormat );
D3D11_UpdateQuadOpacity(vd, sys->d3d_dev, quad, r->i_alpha / 255.0f );
i++;
}
return VLC_SUCCESS;
}