vlc/modules/video_output/win32/direct3d9.c

1878 lines
65 KiB
C

/*****************************************************************************
* direct3d9.c: Windows Direct3D9 video output module
*****************************************************************************
* Copyright (C) 2006-2014 VLC authors and VideoLAN
*
* Authors: Martell Malone <martellmalone@gmail.com>,
* Damien Fouilleul <damienf@videolan.org>,
* Sasha Koruga <skoruga@gmail.com>,
* Felix Abecassis <felix.abecassis@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.
*****************************************************************************/
/*****************************************************************************
* Preamble:
*
* This plugin will use YUV surface if supported, using YUV will result in
* the best video quality (hardware filtering when rescaling the picture)
* and the fastest display as it requires less processing.
*
* If YUV overlay is not supported this plugin will use RGB offscreen video
* surfaces that will be blitted onto the primary surface (display) to
* effectively display the pictures.
*
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_vout_display.h>
#include <vlc_fs.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 <windows.h>
#include <d3d9.h>
#include <d3dx9effect.h>
#include "../../video_chroma/d3d9_fmt.h"
#include <dxvahd.h>
#include "common.h"
#include "builtin_shaders.h"
#include "../../video_chroma/copy.h"
#include <assert.h>
/*****************************************************************************
* Module descriptor
*****************************************************************************/
static int Open(vout_display_t *,
video_format_t *, vlc_video_context *);
static void Close(vout_display_t *);
#define DESKTOP_LONGTEXT N_(\
"The desktop mode allows you to display the video on the desktop.")
#define HW_BLENDING_TEXT N_("Use hardware blending support")
#define HW_BLENDING_LONGTEXT N_(\
"Try to use hardware acceleration for subtitle/OSD blending.")
#define HW_YUV_TEXT N_("Use hardware YUV to RGB conversions")
#define HW_YUV_LONGTEXT N_(\
"Try to use hardware acceleration for YUV to RGB conversions. " \
"This option doesn't have any effect when using overlays.")
#define PIXEL_SHADER_TEXT N_("Pixel Shader")
#define PIXEL_SHADER_LONGTEXT N_(\
"Choose a pixel shader to apply.")
#define PIXEL_SHADER_FILE_TEXT N_("Path to HLSL file")
#define PIXEL_SHADER_FILE_LONGTEXT N_("Path to an HLSL file containing a single pixel shader.")
/* The latest option in the selection list: used for loading a shader file. */
#define SELECTED_SHADER_FILE N_("HLSL File")
#define D3D9_HELP N_("Recommended video output for Windows Vista and later versions")
vlc_module_begin ()
set_shortname("Direct3D9")
set_description(N_("Direct3D9 video output"))
set_help(D3D9_HELP)
set_subcategory(SUBCAT_VIDEO_VOUT)
add_bool("direct3d9-hw-blending", true, HW_BLENDING_TEXT, HW_BLENDING_LONGTEXT)
add_bool("directx-hw-yuv", true, HW_YUV_TEXT, HW_YUV_LONGTEXT)
add_string("direct3d9-shader", "", PIXEL_SHADER_TEXT, PIXEL_SHADER_LONGTEXT)
add_loadfile("direct3d9-shader-file", NULL,
PIXEL_SHADER_FILE_TEXT, PIXEL_SHADER_FILE_LONGTEXT)
add_bool("direct3d9-dxvahd", true, DXVAHD_TEXT, NULL)
add_shortcut("direct3d9", "direct3d")
set_callback_display(Open, 280)
vlc_module_end ()
/*****************************************************************************
* Local prototypes.
*****************************************************************************/
static const vlc_fourcc_t d3d_subpicture_chromas[] = {
VLC_CODEC_RGBA,
0
};
typedef struct vout_display_sys_t
{
display_win32_area_t area;
bool allow_hw_yuv; /* Should we use hardware YUV->RGB conversions */
// core objects
d3d9_decoder_device_t *d3d9_device;
vlc_decoder_device *dec_device; // if d3d9_decoder comes from a decoder device
D3DFORMAT BufferFormat;
HINSTANCE hxdll; /* handle of the opened d3d9x dll */
IDirect3DPixelShader9* d3dx_shader;
UINT texture_width;
UINT texture_height;
// scene objects
IDirect3DTexture9 *sceneTexture;
IDirect3DVertexBuffer9 *sceneVertexBuffer;
D3DFORMAT d3dregion_format; /* Backbuffer output format */
size_t d3dregion_count; /* for subpictures */
struct d3d_region_t *d3dregion; /* for subpictures */
const d3d9_format_t *sw_texture_fmt; /* Rendering texture(s) format */
IDirect3DSurface9 *dx_render;
/* */
bool reset_device;
bool lost_not_ready;
bool clear_scene;
/* outside rendering */
void *outside_opaque;
libvlc_video_update_output_cb updateOutputCb;
libvlc_video_swap_cb swapCb;
libvlc_video_makeCurrent_cb startEndRenderingCb;
/* range converter */
struct {
HMODULE dll;
IDXVAHD_VideoProcessor *proc;
} processor;
} vout_display_sys_t;
/* */
typedef struct
{
FLOAT x,y,z; // vertex untransformed position
FLOAT rhw; // eye distance
D3DCOLOR diffuse; // diffuse color
FLOAT tu, tv; // texture relative coordinates
} CUSTOMVERTEX;
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)
typedef struct d3d_region_t {
D3DFORMAT format; // for subpictures
unsigned width; // for pixel shaders
unsigned height; // for pixel shaders
CUSTOMVERTEX vertex[4];
IDirect3DTexture9 *texture;
} d3d_region_t;
/* */
static HINSTANCE Direct3D9LoadShaderLibrary(void)
{
HINSTANCE instance = NULL;
for (int i = 43; i > 23; --i) {
WCHAR filename[16];
_snwprintf(filename, ARRAY_SIZE(filename), TEXT("D3dx9_%d.dll"), i);
instance = LoadLibrary(filename);
if (instance)
break;
}
return instance;
}
/**
* Compute the vertex ordering needed to rotate the video. Without
* rotation, the vertices of the rectangle are defined in a clockwise
* order. This function computes a remapping of the coordinates to
* implement the rotation, given fixed texture coordinates.
* The unrotated order is the following:
* 0--1
* | |
* 3--2
* For a 180 degrees rotation it should like this:
* 2--3
* | |
* 1--0
* Vertex 0 should be assigned coordinates at index 2 from the
* unrotated order and so on, thus yielding order: 2 3 0 1.
*/
static void orientationVertexOrder(video_orientation_t orientation, int vertex_order[static 4])
{
switch (orientation) {
case ORIENT_ROTATED_90: /* ORIENT_RIGHT_TOP */
vertex_order[0] = 1;
vertex_order[1] = 2;
vertex_order[2] = 3;
vertex_order[3] = 0;
break;
case ORIENT_ROTATED_270: /* ORIENT_LEFT_BOTTOM */
vertex_order[0] = 3;
vertex_order[1] = 0;
vertex_order[2] = 1;
vertex_order[3] = 2;
break;
case ORIENT_ROTATED_180: /* ORIENT_BOTTOM_RIGHT */
vertex_order[0] = 2;
vertex_order[1] = 3;
vertex_order[2] = 0;
vertex_order[3] = 1;
break;
case ORIENT_TRANSPOSED: /* ORIENT_LEFT_TOP */
vertex_order[0] = 0;
vertex_order[1] = 3;
vertex_order[2] = 2;
vertex_order[3] = 1;
break;
case ORIENT_HFLIPPED: /* ORIENT_TOP_RIGHT */
vertex_order[0] = 1;
vertex_order[1] = 0;
vertex_order[2] = 3;
vertex_order[3] = 2;
break;
case ORIENT_VFLIPPED: /* ORIENT_BOTTOM_LEFT */
vertex_order[0] = 3;
vertex_order[1] = 2;
vertex_order[2] = 1;
vertex_order[3] = 0;
break;
case ORIENT_ANTI_TRANSPOSED: /* ORIENT_RIGHT_BOTTOM */
vertex_order[0] = 2;
vertex_order[1] = 1;
vertex_order[2] = 0;
vertex_order[3] = 3;
break;
default:
vertex_order[0] = 0;
vertex_order[1] = 1;
vertex_order[2] = 2;
vertex_order[3] = 3;
break;
}
}
static void Direct3D9SetupVertices(CUSTOMVERTEX *vertices,
const RECT *full_texture, const RECT *visible_texture,
const RECT *rect_in_display,
int alpha,
video_orientation_t orientation)
{
/* Vertices of the dst rectangle in the unrotated (clockwise) order. */
const int vertices_coords[4][2] = {
{ rect_in_display->left, rect_in_display->top },
{ rect_in_display->right, rect_in_display->top },
{ rect_in_display->right, rect_in_display->bottom },
{ rect_in_display->left, rect_in_display->bottom },
};
/* Compute index remapping necessary to implement the rotation. */
int vertex_order[4];
orientationVertexOrder(orientation, vertex_order);
for (int i = 0; i < 4; ++i) {
vertices[i].x = vertices_coords[vertex_order[i]][0];
vertices[i].y = vertices_coords[vertex_order[i]][1];
}
float texture_right = (float)visible_texture->right / (float)full_texture->right;
float texture_left = (float)visible_texture->left / (float)full_texture->right;
float texture_top = (float)visible_texture->top / (float)full_texture->bottom;
float texture_bottom = (float)visible_texture->bottom / (float)full_texture->bottom;
vertices[0].tu = texture_left;
vertices[0].tv = texture_top;
vertices[1].tu = texture_right;
vertices[1].tv = texture_top;
vertices[2].tu = texture_right;
vertices[2].tv = texture_bottom;
vertices[3].tu = texture_left;
vertices[3].tv = texture_bottom;
for (int i = 0; i < 4; i++) {
/* -0.5f is a "feature" of DirectX and it seems to apply to Direct3d also */
/* http://www.sjbrown.co.uk/2003/05/01/fix-directx-rasterisation/ */
vertices[i].x -= 0.5;
vertices[i].y -= 0.5;
vertices[i].z = 0.0f;
vertices[i].rhw = 1.0f;
vertices[i].diffuse = D3DCOLOR_ARGB(alpha, 255, 255, 255);
}
}
/**
* It copies picture surface into a texture and setup the associated d3d_region_t.
*/
static int Direct3D9ImportPicture(vout_display_t *vd,
d3d_region_t *region,
IDirect3DSurface9 *source)
{
vout_display_sys_t *sys = vd->sys;
HRESULT hr;
if (!source) {
msg_Dbg(vd, "no surface to render?");
return VLC_EGENERIC;
}
/* retrieve texture top-level surface */
IDirect3DSurface9 *destination;
hr = IDirect3DTexture9_GetSurfaceLevel(sys->sceneTexture, 0, &destination);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed IDirect3DTexture9_GetSurfaceLevel: 0x%lX", hr);
return VLC_EGENERIC;
}
/* Copy picture surface into texture surface
* color space conversion happen here */
RECT source_visible_rect = {
.left = vd->source->i_x_offset,
.right = vd->source->i_x_offset + vd->source->i_visible_width,
.top = vd->source->i_y_offset,
.bottom = vd->source->i_y_offset + vd->source->i_visible_height,
};
RECT texture_visible_rect = {
.left = 0,
.right = sys->texture_width,
.top = 0,
.bottom = sys->texture_height,
};
// On nVidia & AMD, StretchRect will fail if the visible size isn't even.
// When copying the entire buffer, the margin end up being blended in the actual picture
// on nVidia (regardless of even/odd dimensions)
if (source_visible_rect.right & 1)
{
source_visible_rect.right++;
}
if (source_visible_rect.bottom & 1)
{
source_visible_rect.bottom++;
}
if (sys->processor.proc)
{
DXVAHD_STREAM_DATA inputStream = { 0 };
inputStream.Enable = TRUE;
inputStream.pInputSurface = source;
hr = IDXVAHD_VideoProcessor_VideoProcessBltHD( sys->processor.proc, destination, 0, 1, &inputStream );
if (FAILED(hr)) {
D3DSURFACE_DESC srcDesc, dstDesc;
IDirect3DSurface9_GetDesc(source, &srcDesc);
IDirect3DSurface9_GetDesc(destination, &dstDesc);
msg_Dbg(vd, "Failed VideoProcessBltHD src:%4.4s (%d) dst:%4.4s (%d) (hr=0x%lX)",
(const char*)&srcDesc.Format, srcDesc.Format,
(const char*)&dstDesc.Format, dstDesc.Format, hr);
}
}
else
{
hr = IDirect3DDevice9_StretchRect(sys->d3d9_device->d3ddev.dev, source, &source_visible_rect,
destination, &texture_visible_rect,
D3DTEXF_NONE);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed StretchRect: source 0x%p. (hr=0x%lX)",
(LPVOID)source, hr);
}
}
IDirect3DSurface9_Release(destination);
if (FAILED(hr))
return VLC_EGENERIC;
/* */
region->texture = sys->sceneTexture;
RECT rect_in_display = {
.left = sys->area.place.x,
.right = sys->area.place.x + sys->area.place.width,
.top = sys->area.place.y,
.bottom = sys->area.place.y + sys->area.place.height,
};
Direct3D9SetupVertices(region->vertex, &texture_visible_rect, &source_visible_rect,
&rect_in_display, 255, vd->source->orientation);
return VLC_SUCCESS;
}
static void Direct3D9DeleteRegions(size_t count, d3d_region_t *region)
{
for (size_t i = 0; i < count; i++) {
if (region[i].texture)
IDirect3DTexture9_Release(region[i].texture);
}
free(region);
}
/**
* It releases the scene resources.
*/
static void Direct3D9DestroyScene(vout_display_t *vd)
{
vout_display_sys_t *sys = vd->sys;
Direct3D9DeleteRegions(sys->d3dregion_count, sys->d3dregion);
sys->d3dregion_count = 0;
sys->d3dregion = NULL;
if (sys->sceneVertexBuffer)
{
IDirect3DVertexBuffer9_Release(sys->sceneVertexBuffer);
sys->sceneVertexBuffer = NULL;
}
if (sys->sceneTexture)
{
IDirect3DTexture9_Release(sys->sceneTexture);
sys->sceneTexture = NULL;
}
msg_Dbg(vd, "Direct3D9 scene released successfully");
}
static void Direct3D9DestroyShaders(vout_display_t *vd)
{
vout_display_sys_t *sys = vd->sys;
if (sys->d3dx_shader)
{
IDirect3DPixelShader9_Release(sys->d3dx_shader);
sys->d3dx_shader = NULL;
}
}
/**
* It destroys the picture and scene resources.
*/
static void Direct3D9DestroyResources(vout_display_t *vd)
{
vout_display_sys_t *sys = vd->sys;
Direct3D9DestroyScene(vd);
if (sys->dx_render)
{
IDirect3DSurface9_Release(sys->dx_render);
sys->dx_render = NULL;
}
Direct3D9DestroyShaders(vd);
}
static int UpdateOutput(vout_display_t *vd, const video_format_t *fmt,
libvlc_video_output_cfg_t *out)
{
vout_display_sys_t *sys = vd->sys;
libvlc_video_render_cfg_t cfg;
cfg.width = vd->cfg->display.width;
cfg.height = vd->cfg->display.height;
switch (fmt->i_chroma)
{
case VLC_CODEC_D3D9_OPAQUE:
cfg.bitdepth = 8;
break;
case VLC_CODEC_D3D9_OPAQUE_10B:
cfg.bitdepth = 10;
break;
default:
{
const vlc_chroma_description_t *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 = fmt->color_range == COLOR_RANGE_FULL;
cfg.primaries = (libvlc_video_color_primaries_t) fmt->primaries;
cfg.colorspace = (libvlc_video_color_space_t) fmt->space;
cfg.transfer = (libvlc_video_transfer_func_t) fmt->transfer;
cfg.device = sys->d3d9_device->d3ddev.dev;
libvlc_video_output_cfg_t local_out;
if (out == NULL)
out = &local_out;
if (!sys->updateOutputCb( sys->outside_opaque, &cfg, out ))
{
msg_Err(vd, "Failed to set the external render size");
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/**
* It allocates and initializes the resources needed to render the scene.
*/
static int Direct3D9CreateScene(vout_display_t *vd, const video_format_t *fmt)
{
vout_display_sys_t *sys = vd->sys;
IDirect3DDevice9 *d3ddev = sys->d3d9_device->d3ddev.dev;
HRESULT hr;
sys->texture_width = fmt->i_visible_width;
sys->texture_height = fmt->i_visible_height;
// On nVidia & AMD, StretchRect will fail if the visible size isn't even.
// When copying the entire buffer, the margin end up being blended in the actual picture
// on nVidia (regardless of even/odd dimensions)
if (sys->texture_height & 1) sys->texture_height++;
if (sys->texture_width & 1) sys->texture_width++;
/*
* Create a texture for use when rendering a scene
* for performance reason, texture format is identical to backbuffer
* which would usually be a RGB format
*/
hr = IDirect3DDevice9_CreateTexture(d3ddev,
sys->texture_width,
sys->texture_height,
1,
D3DUSAGE_RENDERTARGET,
sys->BufferFormat,
D3DPOOL_DEFAULT,
&sys->sceneTexture,
NULL);
if (FAILED(hr)) {
msg_Err(vd, "Failed to create texture. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
#ifndef NDEBUG
msg_Dbg(vd, "Direct3D created texture: %ix%i", sys->texture_width, sys->texture_height);
#endif
/*
** Create a vertex buffer for use when rendering scene
*/
hr = IDirect3DDevice9_CreateVertexBuffer(d3ddev,
sizeof(CUSTOMVERTEX)*4,
D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT,
&sys->sceneVertexBuffer,
NULL);
if (FAILED(hr)) {
msg_Err(vd, "Failed to create vertex buffer. (hr=0x%lX)", hr);
IDirect3DTexture9_Release(sys->sceneTexture);
sys->sceneTexture = NULL;
return VLC_EGENERIC;
}
// we use FVF instead of vertex shader
hr = IDirect3DDevice9_SetFVF(d3ddev, D3DFVF_CUSTOMVERTEX);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed SetFVF: 0x%lX", hr);
return -1;
}
/* */
sys->d3dregion_count = 0;
sys->d3dregion = NULL;
sys->clear_scene = true;
// Texture coordinates outside the range [0.0, 1.0] are set
// to the texture color at 0.0 or 1.0, respectively.
IDirect3DDevice9_SetSamplerState(d3ddev, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
IDirect3DDevice9_SetSamplerState(d3ddev, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
// Set linear filtering quality
if (sys->d3d9_device->d3ddev.caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) {
//msg_Dbg(vd, "Using D3DTEXF_LINEAR for minification");
IDirect3DDevice9_SetSamplerState(d3ddev, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
} else {
//msg_Dbg(vd, "Using D3DTEXF_POINT for minification");
IDirect3DDevice9_SetSamplerState(d3ddev, 0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
}
if (sys->d3d9_device->d3ddev.caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR) {
//msg_Dbg(vd, "Using D3DTEXF_LINEAR for magnification");
IDirect3DDevice9_SetSamplerState(d3ddev, 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
} else {
//msg_Dbg(vd, "Using D3DTEXF_POINT for magnification");
IDirect3DDevice9_SetSamplerState(d3ddev, 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
}
// set maximum ambient light
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_AMBIENT, D3DCOLOR_XRGB(255,255,255));
// Turn off culling
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_CULLMODE, D3DCULL_NONE);
// Turn off the zbuffer
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_ZENABLE, D3DZB_FALSE);
// Turn off lights
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_LIGHTING, FALSE);
// Enable dithering
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_DITHERENABLE, TRUE);
// disable stencil
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_STENCILENABLE, FALSE);
// manage blending
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_ALPHABLENDENABLE, FALSE);
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
if (sys->d3d9_device->d3ddev.caps.AlphaCmpCaps & D3DPCMPCAPS_GREATER) {
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_ALPHATESTENABLE,TRUE);
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_ALPHAREF, 0x00);
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_ALPHAFUNC,D3DCMP_GREATER);
}
// Set texture states
IDirect3DDevice9_SetTextureStageState(d3ddev, 0, D3DTSS_COLOROP,D3DTOP_SELECTARG1);
IDirect3DDevice9_SetTextureStageState(d3ddev, 0, D3DTSS_COLORARG1,D3DTA_TEXTURE);
IDirect3DDevice9_SetTextureStageState(d3ddev, 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
IDirect3DDevice9_SetTextureStageState(d3ddev, 0, D3DTSS_ALPHAARG1,D3DTA_TEXTURE);
IDirect3DDevice9_SetTextureStageState(d3ddev, 0, D3DTSS_ALPHAARG2,D3DTA_DIFFUSE);
msg_Dbg(vd, "Direct3D9 scene created successfully");
return VLC_SUCCESS;
}
static int Direct3D9CompileShader(vout_display_t *vd, const char *shader_source, size_t source_length)
{
vout_display_sys_t *sys = vd->sys;
HRESULT (WINAPI * OurD3DXCompileShader)(
LPCSTR pSrcData,
UINT srcDataLen,
const D3DXMACRO *pDefines,
LPD3DXINCLUDE pInclude,
LPCSTR pFunctionName,
LPCSTR pProfile,
DWORD Flags,
LPD3DXBUFFER *ppShader,
LPD3DXBUFFER *ppErrorMsgs,
LPD3DXCONSTANTTABLE *ppConstantTable);
OurD3DXCompileShader = (void*)GetProcAddress(sys->hxdll, "D3DXCompileShader");
if (!OurD3DXCompileShader) {
msg_Warn(vd, "Cannot locate reference to D3DXCompileShader; pixel shading will be disabled");
return VLC_EGENERIC;
}
LPD3DXBUFFER error_msgs = NULL;
LPD3DXBUFFER compiled_shader = NULL;
DWORD shader_flags = 0;
HRESULT hr = OurD3DXCompileShader(shader_source, source_length, NULL, NULL,
"main", "ps_3_0", shader_flags, &compiled_shader, &error_msgs, NULL);
if (FAILED(hr)) {
msg_Warn(vd, "D3DXCompileShader Error (hr=0x%lX)", hr);
if (error_msgs) {
msg_Warn(vd, "HLSL Compilation Error: %s", (char*)ID3DXBuffer_GetBufferPointer(error_msgs));
ID3DXBuffer_Release(error_msgs);
}
return VLC_EGENERIC;
}
hr = IDirect3DDevice9_CreatePixelShader(sys->d3d9_device->d3ddev.dev,
ID3DXBuffer_GetBufferPointer(compiled_shader),
&sys->d3dx_shader);
if (compiled_shader)
ID3DXBuffer_Release(compiled_shader);
if (error_msgs)
ID3DXBuffer_Release(error_msgs);
if (FAILED(hr)) {
msg_Warn(vd, "IDirect3DDevice9_CreatePixelShader error (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
#define MAX_SHADER_FILE_SIZE (1024*1024)
static int Direct3D9CreateShaders(vout_display_t *vd)
{
vout_display_sys_t *sys = vd->sys;
if (!sys->hxdll)
return VLC_EGENERIC;
/* Find which shader was selected in the list. */
char *selected_shader = var_InheritString(vd, "direct3d9-shader");
if (!selected_shader)
return VLC_SUCCESS; /* Nothing to do */
const char *shader_source_builtin = NULL;
char *shader_source_file = NULL;
FILE *fs = NULL;
for (size_t i = 0; i < BUILTIN_SHADERS_COUNT; ++i) {
if (!strcmp(selected_shader, builtin_shaders[i].name)) {
shader_source_builtin = builtin_shaders[i].code;
break;
}
}
if (shader_source_builtin) {
/* A builtin shader was selected. */
int err = Direct3D9CompileShader(vd, shader_source_builtin, strlen(shader_source_builtin));
if (err)
goto error;
} else {
if (strcmp(selected_shader, SELECTED_SHADER_FILE))
goto error; /* Unrecognized entry in the list. */
/* The source code of the shader needs to be read from a file. */
char *filepath = var_InheritString(vd, "direct3d9-shader-file");
if (!filepath || !*filepath)
{
free(filepath);
goto error;
}
/* Open file, find its size with fseek/ftell and read its content in a buffer. */
fs = vlc_fopen(filepath, "rb");
if (!fs)
goto error;
int ret = fseek(fs, 0, SEEK_END);
if (ret == -1)
goto error;
long length = ftell(fs);
if (length == -1 || length >= MAX_SHADER_FILE_SIZE)
goto error;
rewind(fs);
shader_source_file = vlc_alloc(length, sizeof(*shader_source_file));
if (!shader_source_file)
goto error;
ret = fread(shader_source_file, length, 1, fs);
if (ret != 1)
goto error;
ret = Direct3D9CompileShader(vd, shader_source_file, length);
if (ret)
goto error;
}
free(selected_shader);
free(shader_source_file);
fclose(fs);
return VLC_SUCCESS;
error:
Direct3D9DestroyShaders(vd);
free(selected_shader);
free(shader_source_file);
if (fs)
fclose(fs);
return VLC_EGENERIC;
}
/**
* It creates the picture and scene resources.
*/
static int Direct3D9CreateResources(vout_display_t *vd, const video_format_t *fmt)
{
vout_display_sys_t *sys = vd->sys;
if (Direct3D9CreateScene(vd, fmt)) {
msg_Err(vd, "Direct3D scene initialization failed !");
return VLC_EGENERIC;
}
if (Direct3D9CreateShaders(vd)) {
/* Failing to initialize shaders is not fatal. */
msg_Warn(vd, "Direct3D shaders initialization failed !");
}
sys->d3dregion_format = D3DFMT_UNKNOWN;
for (int i = 0; i < 2; i++) {
D3DFORMAT dfmt = i == 0 ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8;
if (SUCCEEDED(IDirect3D9_CheckDeviceFormat(sys->d3d9_device->hd3d.obj,
sys->d3d9_device->d3ddev.adapterId,
D3DDEVTYPE_HAL,
sys->BufferFormat,
D3DUSAGE_DYNAMIC,
D3DRTYPE_TEXTURE,
dfmt))) {
sys->d3dregion_format = dfmt;
break;
}
}
if( !is_d3d9_opaque( fmt->i_chroma ) )
{
HRESULT hr = IDirect3DDevice9_CreateOffscreenPlainSurface(sys->d3d9_device->d3ddev.dev,
fmt->i_width,
fmt->i_height,
sys->sw_texture_fmt->format,
D3DPOOL_DEFAULT,
&sys->dx_render,
NULL);
if (FAILED(hr)) {
msg_Err(vd, "Failed to allocate offscreen surface (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
}
return VLC_SUCCESS;
}
/**
* It reset the Direct3D9 device and its resources.
*/
static int Direct3D9Reset(vout_display_t *vd, const video_format_t *fmtp)
{
vout_display_sys_t *sys = vd->sys;
int res = D3D9_ResetDevice( VLC_OBJECT(vd), sys->d3d9_device );
if (res != VLC_SUCCESS)
return res;
/* release all D3D objects */
Direct3D9DestroyResources(vd);
if (UpdateOutput(vd, fmtp, NULL) != VLC_SUCCESS)
return VLC_EGENERIC;
/* re-create them */
if (Direct3D9CreateResources(vd, fmtp)) {
msg_Dbg(vd, "Direct3D9CreateResources failed !");
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
static void Direct3D9ImportSubpicture(vout_display_t *vd,
size_t *count_ptr, d3d_region_t **region,
const vlc_render_subpicture *subpicture)
{
vout_display_sys_t *sys = vd->sys;
size_t count = subpicture->regions.size;
const struct subpicture_region_rendered *r;
*count_ptr = count;
*region = calloc(count, sizeof(**region));
if (*region == NULL) {
*count_ptr = 0;
return;
}
int i = 0;
vlc_vector_foreach(r, &subpicture->regions) {
d3d_region_t *d3dr = &(*region)[i];
HRESULT hr;
d3dr->texture = NULL;
for (size_t j = 0; j < sys->d3dregion_count; j++) {
d3d_region_t *cache = &sys->d3dregion[j];
if (cache->texture &&
cache->format == sys->d3dregion_format &&
cache->width == r->p_picture->format.i_width &&
cache->height == r->p_picture->format.i_height) {
*d3dr = *cache;
memset(cache, 0, sizeof(*cache));
break;
}
}
if (!d3dr->texture) {
d3dr->format = sys->d3dregion_format;
d3dr->width = r->p_picture->format.i_width;
d3dr->height = r->p_picture->format.i_height;
hr = IDirect3DDevice9_CreateTexture(sys->d3d9_device->d3ddev.dev,
d3dr->width, d3dr->height,
1,
D3DUSAGE_DYNAMIC,
d3dr->format,
D3DPOOL_DEFAULT,
&d3dr->texture,
NULL);
if (FAILED(hr)) {
d3dr->texture = NULL;
msg_Err(vd, "Failed to create %dx%d texture for OSD (hr=0x%lX)",
d3dr->width, d3dr->height, hr);
i++;
continue;
}
#ifndef NDEBUG
msg_Dbg(vd, "Created %dx%d texture for OSD",
r->p_picture->format.i_width, r->p_picture->format.i_height);
#endif
}
D3DLOCKED_RECT lock;
hr = IDirect3DTexture9_LockRect(d3dr->texture, 0, &lock, NULL, 0);
if (SUCCEEDED(hr)) {
uint8_t *dst_data = lock.pBits;
int dst_pitch = lock.Pitch;
uint8_t *src_data = r->p_picture->p->p_pixels;
int src_pitch = r->p_picture->p->i_pitch;
if (d3dr->format == D3DFMT_A8B8G8R8 /*|| d3dr->format == D3DFMT_A8R8G8B8*/) {
if (dst_pitch == r->p_picture->p->i_pitch) {
memcpy(dst_data, src_data, r->p_picture->format.i_height * dst_pitch);
} else {
int copy_pitch = __MIN(dst_pitch, r->p_picture->p->i_pitch);
for (unsigned y = 0; y < r->p_picture->format.i_height; y++) {
memcpy(&dst_data[y * dst_pitch], &src_data[y * src_pitch], copy_pitch);
}
}
} else {
int copy_pitch = __MIN(dst_pitch, r->p_picture->p->i_pitch);
for (unsigned y = 0; y < r->p_picture->format.i_height; y++) {
for (int x = 0; x < copy_pitch; x += 4) {
dst_data[y * dst_pitch + x + 0] = src_data[y * src_pitch + x + 2];
dst_data[y * dst_pitch + x + 1] = src_data[y * src_pitch + x + 1];
dst_data[y * dst_pitch + x + 2] = src_data[y * src_pitch + x + 0];
dst_data[y * dst_pitch + x + 3] = src_data[y * src_pitch + x + 3];
}
}
}
hr = IDirect3DTexture9_UnlockRect(d3dr->texture, 0);
if (FAILED(hr))
msg_Err(vd, "Failed to unlock the texture");
} else {
msg_Err(vd, "Failed to lock the texture");
}
/* Map the subpicture to sys->sys.sys.place */
RECT rect_in_display;
rect_in_display.left = r->place.x;
rect_in_display.right = r->place.x + r->place.width;
rect_in_display.top = r->place.y;
rect_in_display.bottom = r->place.y + r->place.height;
rect_in_display.left += sys->area.place.x;
rect_in_display.right += sys->area.place.x;
rect_in_display.top += sys->area.place.y;
rect_in_display.bottom += sys->area.place.y;
RECT texture_rect;
texture_rect.left = 0;
texture_rect.right = r->p_picture->format.i_width;
texture_rect.top = 0;
texture_rect.bottom = r->p_picture->format.i_height;
RECT texture_visible_rect;
texture_visible_rect.left = r->p_picture->format.i_x_offset;
texture_visible_rect.right = r->p_picture->format.i_x_offset + r->p_picture->format.i_visible_width;
texture_visible_rect.top = r->p_picture->format.i_y_offset;
texture_visible_rect.bottom = r->p_picture->format.i_y_offset + r->p_picture->format.i_visible_height;
Direct3D9SetupVertices(d3dr->vertex, &texture_rect, &texture_visible_rect,
&rect_in_display, r->i_alpha, ORIENT_NORMAL);
i++;
}
}
static int Direct3D9RenderRegion(vout_display_t *vd,
const d3d_region_t *region,
bool use_pixel_shader)
{
vout_display_sys_t *sys = vd->sys;
IDirect3DDevice9 *d3ddev = sys->d3d9_device->d3ddev.dev;
HRESULT hr;
/* Import vertices */
void *vertex;
hr = IDirect3DVertexBuffer9_Lock(sys->sceneVertexBuffer, 0, 0, &vertex, D3DLOCK_DISCARD);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed IDirect3DVertexBuffer9_Lock: 0x%lX", hr);
return -1;
}
memcpy(vertex, region->vertex, sizeof(region->vertex));
hr = IDirect3DVertexBuffer9_Unlock(sys->sceneVertexBuffer);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed IDirect3DVertexBuffer9_Unlock: 0x%lX", hr);
return -1;
}
// Render the vertex buffer contents
hr = IDirect3DDevice9_SetStreamSource(d3ddev, 0, sys->sceneVertexBuffer, 0, sizeof(CUSTOMVERTEX));
if (FAILED(hr)) {
msg_Dbg(vd, "Failed SetStreamSource: 0x%lX", hr);
return -1;
}
// Setup our texture. Using textures introduces the texture stage states,
// which govern how textures get blended together (in the case of multiple
// textures) and lighting information. In this case, we are modulating
// (blending) our texture with the diffuse color of the vertices.
hr = IDirect3DDevice9_SetTexture(d3ddev, 0, (IDirect3DBaseTexture9*)region->texture);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed SetTexture: 0x%lX", hr);
return -1;
}
if (sys->d3dx_shader) {
if (use_pixel_shader)
{
hr = IDirect3DDevice9_SetPixelShader(d3ddev, sys->d3dx_shader);
float shader_data[4] = { region->width, region->height, 0, 0 };
hr = IDirect3DDevice9_SetPixelShaderConstantF(d3ddev, 0, shader_data, 1);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed SetPixelShaderConstantF: 0x%lX", hr);
return -1;
}
}
else /* Disable any existing pixel shader. */
hr = IDirect3DDevice9_SetPixelShader(d3ddev, NULL);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed SetPixelShader: 0x%lX", hr);
return -1;
}
}
// draw rectangle
hr = IDirect3DDevice9_DrawPrimitive(d3ddev, D3DPT_TRIANGLEFAN, 0, 2);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed DrawPrimitive: 0x%lX", hr);
return -1;
}
return 0;
}
/**
* It renders the scene.
*
* This function is intented for higher end 3D cards, with pixel shader support
* and at least 64 MiB of video RAM.
*/
static void Direct3D9RenderScene(vout_display_t *vd,
d3d_region_t *picture,
size_t subpicture_count,
d3d_region_t *subpicture)
{
vout_display_sys_t *sys = vd->sys;
IDirect3DDevice9 *d3ddev = sys->d3d9_device->d3ddev.dev;
HRESULT hr;
if (sys->startEndRenderingCb && !sys->startEndRenderingCb( sys->outside_opaque, true ))
return;
if (sys->clear_scene) {
/* Clear the backbuffer and the zbuffer */
hr = IDirect3DDevice9_Clear(d3ddev, 0, NULL, D3DCLEAR_TARGET,
D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed Clear: 0x%lX", hr);
return;
}
sys->clear_scene = false;
}
hr = IDirect3DDevice9_BeginScene(d3ddev);
if (FAILED(hr)) {
msg_Dbg(vd, "Failed BeginScene: 0x%lX", hr);
return;
}
Direct3D9RenderRegion(vd, picture, true);
if (subpicture_count)
{
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_ALPHABLENDENABLE, TRUE);
for (size_t i = 0; i < subpicture_count; i++) {
d3d_region_t *r = &subpicture[i];
if (r->texture)
Direct3D9RenderRegion(vd, r, false);
}
IDirect3DDevice9_SetRenderState(d3ddev, D3DRS_ALPHABLENDENABLE, FALSE);
}
hr = IDirect3DDevice9_EndScene(d3ddev);
if (FAILED(hr))
msg_Dbg(vd, "Failed EndScene: 0x%lX", hr);
if (sys->startEndRenderingCb)
sys->startEndRenderingCb( sys->outside_opaque, false );
}
static void Prepare(vout_display_t *vd, picture_t *picture,
const vlc_render_subpicture *subpicture, vlc_tick_t date)
{
VLC_UNUSED(date);
vout_display_sys_t *sys = vd->sys;
/* Position Change */
if (sys->area.place_changed) {
#if 0 /* need that when bicubic filter is available */
RECT rect;
UINT width, height;
GetClientRect(CommonVideoHWND(&sys->area), &rect);
width = RECTWidth(rect);
height = RECTHeight(rect);
if (width != p_sys->pp.BackBufferWidth || height != p_sys->pp.BackBufferHeight)
{
msg_Dbg(vd, "resizing device back buffers to (%lux%lu)", width, height);
// need to reset D3D device to resize back buffer
if (VLC_SUCCESS != Direct3D9ResetDevice(vd, width, height))
return VLC_EGENERIC;
}
#endif
UpdateOutput(vd, vd->fmt, NULL);
sys->clear_scene = true;
sys->area.place_changed = false;
}
d3d9_device_t *p_d3d9_dev = &sys->d3d9_device->d3ddev;
/* check if device is still available */
HRESULT hr = IDirect3DDevice9_TestCooperativeLevel(p_d3d9_dev->dev);
if (FAILED(hr)) {
if (hr == D3DERR_DEVICENOTRESET && !sys->reset_device) {
sys->reset_device = true;
/* FIXME what to do here in case of failure */
if (Direct3D9Reset(vd, vd->fmt)) {
msg_Err(vd, "Failed to reset device");
return;
}
sys->reset_device = false;
sys->lost_not_ready = false;
}
if (hr == D3DERR_DEVICELOST && !sys->lost_not_ready) {
/* Device is lost but not yet ready for reset. */
sys->lost_not_ready = true;
}
return;
}
/* FIXME it is a bit ugly, we need the surface to be unlocked for
* rendering.
* The clean way would be to release the picture (and ensure that
* the vout doesn't keep a reference). But because of the vout
* wrapper, we can't */
IDirect3DSurface9 *surface;
if ( !is_d3d9_opaque(picture->format.i_chroma) )
{
D3DLOCKED_RECT d3drect;
surface = sys->dx_render;
hr = IDirect3DSurface9_LockRect(surface, &d3drect, NULL, 0);
if (unlikely(FAILED(hr))) {
msg_Err(vd, "failed to lock surface");
return;
}
picture_t fake_pic = *picture;
picture_UpdatePlanes(&fake_pic, d3drect.pBits, d3drect.Pitch);
/* The dx/d3d buffer is always allocated as YV12 */
if (fake_pic.format.i_chroma == VLC_CODEC_I420)
picture_SwapUV( &fake_pic );
picture_CopyPixels(&fake_pic, picture);
IDirect3DSurface9_UnlockRect(surface);
}
else
{
const picture_sys_d3d9_t *picsys = ActiveD3D9PictureSys(picture);
surface = picsys->surface;
if (picsys->surface != surface)
{
D3DSURFACE_DESC srcDesc, dstDesc;
IDirect3DSurface9_GetDesc(picsys->surface, &srcDesc);
IDirect3DSurface9_GetDesc(surface, &dstDesc);
if ( srcDesc.Width == dstDesc.Width && srcDesc.Height == dstDesc.Height )
surface = picsys->surface;
else
{
RECT visibleSource;
visibleSource.left = 0;
visibleSource.top = 0;
visibleSource.right = picture->format.i_visible_width;
visibleSource.bottom = picture->format.i_visible_height;
hr = IDirect3DDevice9_StretchRect( p_d3d9_dev->dev, picsys->surface, &visibleSource, surface, &visibleSource, D3DTEXF_NONE);
if (FAILED(hr)) {
msg_Err(vd, "Failed to copy the hw surface to the decoder surface (hr=0x%lX)", hr );
}
}
}
}
d3d_region_t picture_region;
if (!Direct3D9ImportPicture(vd, &picture_region, surface)) {
picture_region.width = picture->format.i_visible_width;
picture_region.height = picture->format.i_visible_height;
size_t subpicture_region_count = 0;
d3d_region_t *subpicture_region = NULL;
if (subpicture)
Direct3D9ImportSubpicture(vd, &subpicture_region_count, &subpicture_region,
subpicture);
Direct3D9RenderScene(vd, &picture_region,
subpicture_region_count, subpicture_region);
Direct3D9DeleteRegions(sys->d3dregion_count, sys->d3dregion);
sys->d3dregion_count = subpicture_region_count;
sys->d3dregion = subpicture_region;
}
}
static void Swap(vout_display_t *vd)
{
vout_display_sys_t *sys = vd->sys;
const d3d9_device_t *p_d3d9_dev = &sys->d3d9_device->d3ddev;
// Present the back buffer contents to the display
// No stretching should happen here !
RECT src = {
.left = 0,
.right = vd->cfg->display.width,
.top = 0,
.bottom = vd->cfg->display.height
};
HRESULT hr;
if (sys->d3d9_device->hd3d.use_ex) {
hr = IDirect3DDevice9Ex_PresentEx(p_d3d9_dev->devex, &src, &src, CommonVideoHWND(&sys->area), NULL, 0);
} else {
hr = IDirect3DDevice9_Present(p_d3d9_dev->dev, &src, &src, CommonVideoHWND(&sys->area), NULL);
}
if (FAILED(hr)) {
msg_Dbg(vd, "Failed Present: 0x%lX", hr);
}
}
static void Display(vout_display_t *vd, picture_t *picture)
{
VLC_UNUSED(picture);
vout_display_sys_t *sys = vd->sys;
if (sys->lost_not_ready)
return;
sys->swapCb( sys->outside_opaque );
}
/**
* It releases an instance of Direct3D9
*/
static void Direct3D9Destroy(vout_display_sys_t *sys)
{
if (sys->processor.proc)
{
IDXVAHD_VideoProcessor_Release(sys->processor.proc);
FreeLibrary(sys->processor.dll);
}
if (sys->dec_device)
vlc_decoder_device_Release(sys->dec_device);
else if (sys->d3d9_device)
{
D3D9_ReleaseDevice(sys->d3d9_device);
}
if (sys->hxdll)
{
FreeLibrary(sys->hxdll);
sys->hxdll = NULL;
}
}
/**
* It tests if the conversion from src to dst is supported.
*/
static int Direct3D9CheckConversion(vout_display_t *vd, D3DFORMAT src)
{
vout_display_sys_t *sys = vd->sys;
IDirect3D9 *d3dobj = sys->d3d9_device->hd3d.obj;
D3DFORMAT dst = sys->BufferFormat;
HRESULT hr;
/* test whether device can create a surface of that format */
hr = IDirect3D9_CheckDeviceFormat(d3dobj, sys->d3d9_device->d3ddev.adapterId,
D3DDEVTYPE_HAL, dst, 0,
D3DRTYPE_SURFACE, src);
if (!SUCCEEDED(hr)) {
if (D3DERR_NOTAVAILABLE != hr)
msg_Err(vd, "Could not query adapter supported formats. (hr=0x%lX)", hr);
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
static const d3d9_format_t d3d_formats[] = {
/* YV12 is always used for planar 420, the planes are then swapped in Lock() */
{ "YV12", MAKEFOURCC('Y','V','1','2'), VLC_CODEC_YV12 },
{ "YV12", MAKEFOURCC('Y','V','1','2'), VLC_CODEC_I420 },
{ "NV12", MAKEFOURCC('N','V','1','2'), VLC_CODEC_NV12 },
{ "DXA9", MAKEFOURCC('N','V','1','2'), VLC_CODEC_D3D9_OPAQUE },
{ "DXA9_422", MAKEFOURCC('Y','U','Y','2'), VLC_CODEC_D3D9_OPAQUE },
{ "DXA9_444", MAKEFOURCC('A','Y','U','V'), VLC_CODEC_D3D9_OPAQUE },
{ "DXA9_10", MAKEFOURCC('P','0','1','0'), VLC_CODEC_D3D9_OPAQUE_10B },
{ "DXA9_10_422", MAKEFOURCC('Y','2','1','0'), VLC_CODEC_D3D9_OPAQUE_10B },
{ "DXA9_10_444", MAKEFOURCC('Y','4','1','0'), VLC_CODEC_D3D9_OPAQUE_10B },
{ "UYVY", D3DFMT_UYVY, VLC_CODEC_UYVY },
{ "YUY2", D3DFMT_YUY2, VLC_CODEC_YUYV },
{ "X8R8G8B8", D3DFMT_X8R8G8B8,VLC_CODEC_BGRX },
{ "A8R8G8B8", D3DFMT_A8R8G8B8,VLC_CODEC_BGRA },
{ "R8G8B8", D3DFMT_R8G8B8, VLC_CODEC_BGR24 },
{ "R5G6B5", D3DFMT_R5G6B5, VLC_CODEC_RGB565LE },
{ "X1R5G5B5", D3DFMT_X1R5G5B5,VLC_CODEC_RGB555LE },
{ NULL, 0, 0 }
};
static const d3d9_format_t *FindBufferFormat(D3DFORMAT fmt)
{
for (unsigned j = 0; d3d_formats[j].name; j++) {
const d3d9_format_t *format = &d3d_formats[j];
if (format->format != fmt)
continue;
return format;
}
return NULL;
}
/**
* It returns the format (closest to chroma) that can be converted to target */
static const d3d9_format_t *Direct3DFindFormat(vout_display_t *vd, const video_format_t *fmt, vlc_video_context *vctx)
{
vout_display_sys_t *sys = vd->sys;
bool hardware_scale_ok = !(fmt->i_visible_width & 1) && !(fmt->i_visible_height & 1);
if( !hardware_scale_ok )
msg_Warn( vd, "Disabling hardware chroma conversion due to odd dimensions" );
for (unsigned pass = 0; pass < 2; pass++) {
const vlc_fourcc_t *list;
const vlc_fourcc_t dxva_chroma[] = {fmt->i_chroma, 0};
D3DFORMAT decoder_format = D3DFMT_UNKNOWN;
if (pass == 0 && is_d3d9_opaque(fmt->i_chroma))
{
d3d9_video_context_t *vctx_sys = GetD3D9ContextPrivate(vctx);
assert(vctx_sys != NULL);
list = dxva_chroma;
decoder_format = vctx_sys->format;
msg_Dbg(vd, "favor decoder format: %4.4s (%d)", (const char*)&decoder_format, decoder_format);
}
else if (pass == 0 && hardware_scale_ok && sys->allow_hw_yuv && vlc_fourcc_IsYUV(fmt->i_chroma))
list = vlc_fourcc_GetYUVFallback(fmt->i_chroma);
else if (pass == 1)
list = vlc_fourcc_GetRGBFallback(fmt->i_chroma);
else
continue;
for (unsigned i = 0; list[i] != 0; i++) {
for (unsigned j = 0; d3d_formats[j].name; j++) {
const d3d9_format_t *format = &d3d_formats[j];
if (format->vlc_chroma != list[i])
continue;
if (decoder_format != D3DFMT_UNKNOWN && format->format != decoder_format)
continue; // not the Hardware format we prefer
msg_Dbg(vd, "trying surface pixel format: %s", format->name);
if (!Direct3D9CheckConversion(vd, format->format))
return format;
}
}
}
return NULL;
}
static void SetupProcessorInput(vout_display_t *vd, const video_format_t *fmt, const d3d9_format_t *d3dfmt)
{
vout_display_sys_t *sys = vd->sys;
DXVAHD_STREAM_STATE_D3DFORMAT_DATA d3dformat = { d3dfmt->format };
IDXVAHD_VideoProcessor_SetVideoProcessStreamState( sys->processor.proc, 0, DXVAHD_STREAM_STATE_D3DFORMAT, sizeof(d3dformat), &d3dformat );
DXVAHD_STREAM_STATE_FRAME_FORMAT_DATA frame_format = { DXVAHD_FRAME_FORMAT_PROGRESSIVE };
IDXVAHD_VideoProcessor_SetVideoProcessStreamState( sys->processor.proc, 0, DXVAHD_STREAM_STATE_FRAME_FORMAT, sizeof(frame_format), &frame_format );
DXVAHD_STREAM_STATE_INPUT_COLOR_SPACE_DATA colorspace = {
.Type = 0, // video, not graphics
.RGB_Range = fmt->color_range == COLOR_RANGE_FULL ? 0 : 1,
.YCbCr_xvYCC = fmt->color_range == COLOR_RANGE_FULL ? 1 : 0,
.YCbCr_Matrix = fmt->space == COLOR_SPACE_BT601 ? 0 : 1,
};
IDXVAHD_VideoProcessor_SetVideoProcessStreamState( sys->processor.proc, 0, DXVAHD_STREAM_STATE_INPUT_COLOR_SPACE, sizeof(colorspace), &colorspace );
DXVAHD_STREAM_STATE_SOURCE_RECT_DATA srcRect;
srcRect.Enable = TRUE;
srcRect.SourceRect = (RECT) {
.left = vd->source->i_x_offset,
.right = vd->source->i_x_offset + vd->source->i_visible_width,
.top = vd->source->i_y_offset,
.bottom = vd->source->i_y_offset + vd->source->i_visible_height,
};
IDXVAHD_VideoProcessor_SetVideoProcessStreamState( sys->processor.proc, 0, DXVAHD_STREAM_STATE_SOURCE_RECT, sizeof(srcRect), &srcRect );
DXVAHD_BLT_STATE_TARGET_RECT_DATA dstRect;
dstRect.Enable = TRUE;
dstRect.TargetRect = (RECT) {
.left = 0,
.right = vd->source->i_visible_width,
.top = 0,
.bottom = vd->source->i_visible_height,
};
IDXVAHD_VideoProcessor_SetVideoProcessBltState( sys->processor.proc, DXVAHD_BLT_STATE_TARGET_RECT, sizeof(dstRect), &dstRect);
}
static void GetFrameRate(DXVAHD_RATIONAL *r, const video_format_t *fmt)
{
if (fmt->i_frame_rate && fmt->i_frame_rate_base)
{
r->Numerator = fmt->i_frame_rate;
r->Denominator = fmt->i_frame_rate_base;
}
else
{
r->Numerator = 0;
r->Denominator = 0;
}
}
static int InitRangeProcessor(vout_display_t *vd, const d3d9_format_t *d3dfmt,
const libvlc_video_output_cfg_t *render_out)
{
vout_display_sys_t *sys = vd->sys;
HRESULT hr;
sys->processor.dll = LoadLibrary(TEXT("DXVA2.DLL"));
if (unlikely(!sys->processor.dll))
{
msg_Err(vd, "Failed to load DXVA2.DLL");
return VLC_EGENERIC;
}
D3DFORMAT *formatsList = NULL;
DXVAHD_VPCAPS *capsList = NULL;
IDXVAHD_Device *hd_device = NULL;
#ifdef __MINGW64_VERSION_MAJOR
typedef HRESULT (WINAPI* PDXVAHD_CreateDevice)(IDirect3DDevice9Ex *,const DXVAHD_CONTENT_DESC *,DXVAHD_DEVICE_USAGE,PDXVAHDSW_Plugin,IDXVAHD_Device **);
#endif
PDXVAHD_CreateDevice CreateDevice;
CreateDevice = (PDXVAHD_CreateDevice)GetProcAddress(sys->processor.dll, "DXVAHD_CreateDevice");
if (CreateDevice == NULL)
{
msg_Err(vd, "Can't create HD device (not Windows 7+)");
goto error;
}
DXVAHD_CONTENT_DESC desc;
desc.InputFrameFormat = DXVAHD_FRAME_FORMAT_PROGRESSIVE;
GetFrameRate( &desc.InputFrameRate, vd->source );
desc.InputWidth = vd->source->i_visible_width;
desc.InputHeight = vd->source->i_visible_height;
desc.OutputFrameRate = desc.InputFrameRate;
desc.OutputWidth = vd->source->i_visible_width;
desc.OutputHeight = vd->source->i_visible_height;
hr = CreateDevice(sys->d3d9_device->d3ddev.devex, &desc, DXVAHD_DEVICE_USAGE_PLAYBACK_NORMAL, NULL, &hd_device);
if (FAILED(hr))
{
msg_Dbg(vd, "Failed to create the device (error 0x%lX)", hr);
goto error;
}
DXVAHD_VPDEVCAPS devcaps = { 0 };
hr = IDXVAHD_Device_GetVideoProcessorDeviceCaps( hd_device, &devcaps );
if (unlikely(FAILED(hr)))
{
msg_Err(vd, "Failed to get the device capabilities (error 0x%lX)", hr);
goto error;
}
if (devcaps.VideoProcessorCount == 0)
{
msg_Warn(vd, "No good video processor found for range conversion");
goto error;
}
formatsList = malloc(devcaps.InputFormatCount * sizeof(*formatsList));
if (unlikely(formatsList == NULL))
{
msg_Dbg(vd, "Failed to allocate %u input formats", devcaps.InputFormatCount);
goto error;
}
hr = IDXVAHD_Device_GetVideoProcessorInputFormats( hd_device, devcaps.InputFormatCount, formatsList);
UINT i;
for (i=0; i<devcaps.InputFormatCount; i++)
{
if (formatsList[i] == d3dfmt->format)
break;
}
if (i == devcaps.InputFormatCount)
{
msg_Warn(vd, "Input format %s not supported for range conversion", d3dfmt->name);
goto error;
}
free(formatsList);
formatsList = malloc(devcaps.OutputFormatCount * sizeof(*formatsList));
if (unlikely(formatsList == NULL))
{
msg_Dbg(vd, "Failed to allocate %u output formats", devcaps.OutputFormatCount);
goto error;
}
hr = IDXVAHD_Device_GetVideoProcessorOutputFormats( hd_device, devcaps.OutputFormatCount, formatsList);
for (i=0; i<devcaps.OutputFormatCount; i++)
{
if (formatsList[i] == sys->BufferFormat)
break;
}
if (i == devcaps.OutputFormatCount)
{
msg_Warn(vd, "Output format %d not supported for range conversion", sys->BufferFormat);
goto error;
}
capsList = malloc(devcaps.VideoProcessorCount * sizeof(*capsList));
if (unlikely(capsList == NULL))
{
msg_Dbg(vd, "Failed to allocate %u video processors", devcaps.VideoProcessorCount);
goto error;
}
hr = IDXVAHD_Device_GetVideoProcessorCaps( hd_device, devcaps.VideoProcessorCount, capsList);
if (FAILED(hr))
{
msg_Dbg(vd, "Failed to get the processor caps (error 0x%lX)", hr);
goto error;
}
hr = IDXVAHD_Device_CreateVideoProcessor( hd_device, &capsList->VPGuid, &sys->processor.proc );
if (FAILED(hr))
{
msg_Dbg(vd, "Failed to create the processor (error 0x%lX)", hr);
goto error;
}
free(capsList);
free(formatsList);
IDXVAHD_Device_Release( hd_device );
SetupProcessorInput(vd, vd->source, d3dfmt);
DXVAHD_BLT_STATE_OUTPUT_COLOR_SPACE_DATA colorspace = {
.Usage = 0, // playback
.RGB_Range = render_out->full_range ? 0 : 1,
.YCbCr_xvYCC = render_out->full_range ? 1 : 0,
.YCbCr_Matrix = render_out->colorspace == libvlc_video_colorspace_BT601 ? 0 : 1,
};
hr = IDXVAHD_VideoProcessor_SetVideoProcessBltState( sys->processor.proc, DXVAHD_BLT_STATE_OUTPUT_COLOR_SPACE, sizeof(colorspace), &colorspace);
return VLC_SUCCESS;
error:
free(capsList);
free(formatsList);
if (hd_device)
IDXVAHD_Device_Release(hd_device);
FreeLibrary(sys->processor.dll);
return VLC_EGENERIC;
}
/**
* It creates a Direct3D9 device and the associated resources.
*/
static int Direct3D9Open(vout_display_t *vd, video_format_t *fmt, vlc_video_context *vctx)
{
vout_display_sys_t *sys = vd->sys;
libvlc_video_output_cfg_t render_out;
if (UpdateOutput(vd, vd->source, &render_out) != VLC_SUCCESS)
return VLC_EGENERIC;
sys->BufferFormat = render_out.d3d9_format;
const d3d9_format_t *dst_format = FindBufferFormat(sys->BufferFormat);
if (unlikely(!dst_format))
msg_Warn(vd, "Unknown back buffer format 0x%X", sys->BufferFormat);
/* Find the appropriate D3DFORMAT for the render chroma, the format will be the closest to
* the requested chroma which is usable by the hardware in an offscreen surface, as they
* typically support more formats than textures */
const d3d9_format_t *d3dfmt = Direct3DFindFormat(vd, vd->source, vctx);
if (!d3dfmt) {
msg_Err(vd, "unsupported source pixel format %4.4s", (const char*)&vd->source->i_chroma);
goto error;
}
msg_Dbg(vd, "found input surface format %s for source %4.4s", d3dfmt->name, (const char *)&vd->source->i_chroma);
bool force_dxva_hd = var_InheritBool(vd, "direct3d9-dxvahd");
if (force_dxva_hd || (dst_format && vd->source->color_range != COLOR_RANGE_FULL &&
sys->d3d9_device->d3ddev.identifier.VendorId == GPU_MANUFACTURER_NVIDIA &&
!vlc_fourcc_IsYUV(dst_format->vlc_chroma) && vlc_fourcc_IsYUV(d3dfmt->vlc_chroma)))
{
// NVIDIA bug, YUV to RGB internal conversion in StretchRect always converts from limited to limited range
msg_Dbg(vd, "init DXVA-HD processor from %s to %s", d3dfmt->name, dst_format?dst_format->name:"unknown");
int err = InitRangeProcessor( vd, d3dfmt, &render_out );
if (err != VLC_SUCCESS)
force_dxva_hd = false;
}
if (!force_dxva_hd)
{
// test whether device can perform color-conversion from that format to target format
HRESULT hr = IDirect3D9_CheckDeviceFormatConversion(sys->d3d9_device->hd3d.obj,
sys->d3d9_device->d3ddev.adapterId,
D3DDEVTYPE_HAL,
d3dfmt->format, sys->BufferFormat);
if (FAILED(hr))
{
msg_Dbg(vd, "Unsupported conversion from %s to %s", d3dfmt->name, dst_format?dst_format->name:"unknown" );
goto error;
}
msg_Dbg(vd, "using StrecthRect from %s to %s", d3dfmt->name, dst_format?dst_format->name:"unknown" );
}
/* */
*fmt = *vd->source;
fmt->i_chroma = d3dfmt->vlc_chroma;
sys->sw_texture_fmt = d3dfmt;
if (Direct3D9CreateResources(vd, fmt)) {
msg_Err(vd, "Failed to allocate resources");
goto error;
}
msg_Dbg(vd, "Direct3D9 display adapter successfully initialized");
return VLC_SUCCESS;
error:
return VLC_EGENERIC;
}
/**
* It releases the Direct3D9 device and its resources.
*/
static void Direct3D9Close(vout_display_t *vd)
{
Direct3D9DestroyResources(vd);
}
static int Control(vout_display_t *vd, int query)
{
vout_display_sys_t *sys = vd->sys;
CommonControl(vd, &sys->area, query);
return VLC_SUCCESS;
}
typedef struct
{
char **values;
char **descs;
size_t count;
} enum_context_t;
static void ListShaders(enum_context_t *ctx)
{
size_t num_shaders = BUILTIN_SHADERS_COUNT;
ctx->values = xrealloc(ctx->values, (ctx->count + num_shaders + 1) * sizeof(char *));
ctx->descs = xrealloc(ctx->descs, (ctx->count + num_shaders + 1) * sizeof(char *));
for (size_t i = 0; i < num_shaders; ++i) {
ctx->values[ctx->count] = strdup(builtin_shaders[i].name);
ctx->descs[ctx->count] = strdup(builtin_shaders[i].name);
ctx->count++;
}
ctx->values[ctx->count] = strdup(SELECTED_SHADER_FILE);
ctx->descs[ctx->count] = strdup(SELECTED_SHADER_FILE);
ctx->count++;
}
/* Populate the list of available shader techniques in the options */
static int FindShadersCallback(const char *name, char ***values, char ***descs)
{
VLC_UNUSED(name);
enum_context_t ctx = { NULL, NULL, 0 };
ListShaders(&ctx);
*values = ctx.values;
*descs = ctx.descs;
return ctx.count;
}
VLC_CONFIG_STRING_ENUM(FindShadersCallback)
static bool LocalSwapchainUpdateOutput( void *opaque, const libvlc_video_render_cfg_t *cfg, libvlc_video_output_cfg_t *out )
{
VLC_UNUSED(cfg);
vout_display_t *vd = opaque;
vout_display_sys_t *sys = vd->sys;
D3DDISPLAYMODE d3ddm;
HRESULT hr = IDirect3D9_GetAdapterDisplayMode(sys->d3d9_device->hd3d.obj, sys->d3d9_device->d3ddev.adapterId, &d3ddm);
if (unlikely(FAILED(hr)))
return false;
out->d3d9_format = d3ddm.Format;
out->full_range = true;
out->colorspace = libvlc_video_colorspace_BT709;
out->primaries = libvlc_video_primaries_BT709;
out->transfer = libvlc_video_transfer_func_SRGB;
out->orientation = libvlc_video_orient_top_left;
return true;
}
static void LocalSwapchainSwap( void *opaque )
{
vout_display_t *vd = opaque;
Swap( vd );
}
static const struct vlc_display_operations ops = {
.close = Close,
.prepare = Prepare,
.display = Display,
.control = Control,
};
/**
* It creates a Direct3D vout display.
*/
static int Open(vout_display_t *vd,
video_format_t *fmtp, vlc_video_context *context)
{
vout_display_sys_t *sys;
if ( !vd->obj.force && vd->source->projection_mode != PROJECTION_MODE_RECTANGULAR)
return VLC_EGENERIC; /* let a module who can handle it do it */
if ( !vd->obj.force && vd->source->mastering.max_luminance != 0)
return VLC_EGENERIC; /* let a module who can handle it do it */
/* Allocate structure */
vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
if (!sys)
return VLC_ENOMEM;
CommonInit(&sys->area, vd->source);
sys->outside_opaque = var_InheritAddress( vd, "vout-cb-opaque" );
sys->updateOutputCb = var_InheritAddress( vd, "vout-cb-update-output" );
sys->swapCb = var_InheritAddress( vd, "vout-cb-swap" );
sys->startEndRenderingCb = var_InheritAddress( vd, "vout-cb-make-current" );
if ( sys->swapCb == NULL || sys->startEndRenderingCb == NULL || sys->updateOutputCb == NULL )
{
/* use our own callbacks, since there isn't any external ones */
if (CommonWindowInit(vd, &sys->area, false))
goto error;
sys->outside_opaque = vd;
sys->updateOutputCb = LocalSwapchainUpdateOutput;
sys->swapCb = LocalSwapchainSwap;
sys->startEndRenderingCb = NULL;
}
CommonPlacePicture(vd, &sys->area);
sys->dec_device = context ? vlc_video_context_HoldDevice(context) : NULL;
sys->d3d9_device = GetD3D9OpaqueDevice(sys->dec_device);
if ( sys->d3d9_device == NULL )
{
if (sys->dec_device)
{
vlc_decoder_device_Release(sys->dec_device);
sys->dec_device = NULL;
}
sys->d3d9_device = D3D9_CreateDevice( vd );
if (sys->d3d9_device == NULL) {
msg_Err( vd, "D3D9 Creation failed!" );
goto error;
}
}
if ( vd->source->i_visible_width > sys->d3d9_device->d3ddev.caps.MaxTextureWidth ||
vd->source->i_visible_height > sys->d3d9_device->d3ddev.caps.MaxTextureHeight )
{
msg_Err(vd, "Textures too large %ux%u max possible: %lx%lx",
vd->source->i_visible_width, vd->source->i_visible_height,
sys->d3d9_device->d3ddev.caps.MaxTextureWidth,
sys->d3d9_device->d3ddev.caps.MaxTextureHeight);
goto error;
}
sys->hxdll = Direct3D9LoadShaderLibrary();
if (!sys->hxdll)
msg_Warn(vd, "cannot load Direct3D9 Shader Library; HLSL pixel shading will be disabled.");
sys->reset_device = false;
sys->lost_not_ready = false;
sys->allow_hw_yuv = var_CreateGetBool(vd, "directx-hw-yuv");
/* */
video_format_t fmt;
if (Direct3D9Open(vd, &fmt, context)) {
msg_Err(vd, "Direct3D9 could not be opened");
goto error;
}
/* Setup vout_display now that everything is fine */
if (var_InheritBool(vd, "direct3d9-hw-blending") &&
sys->d3dregion_format != D3DFMT_UNKNOWN &&
(sys->d3d9_device->d3ddev.caps.SrcBlendCaps & D3DPBLENDCAPS_SRCALPHA) &&
(sys->d3d9_device->d3ddev.caps.DestBlendCaps & D3DPBLENDCAPS_INVSRCALPHA) &&
(sys->d3d9_device->d3ddev.caps.TextureCaps & D3DPTEXTURECAPS_ALPHA) &&
(sys->d3d9_device->d3ddev.caps.TextureOpCaps & D3DTEXOPCAPS_SELECTARG1) &&
(sys->d3d9_device->d3ddev.caps.TextureOpCaps & D3DTEXOPCAPS_MODULATE))
vd->info.subpicture_chromas = d3d_subpicture_chromas;
else
vd->info.subpicture_chromas = NULL;
video_format_Clean(fmtp);
video_format_Copy(fmtp, &fmt);
vd->ops = &ops;
/* Change the window title bar text */
vlc_window_SetTitle(vd->cfg->window, VOUT_TITLE " (Direct3D9 output)");
return VLC_SUCCESS;
error:
Direct3D9Close(vd);
CommonWindowClean(&sys->area);
Direct3D9Destroy(sys);
free(vd->sys);
return VLC_EGENERIC;
}
/**
* It destroys a Direct3D vout display.
*/
static void Close(vout_display_t *vd)
{
vout_display_sys_t *sys = vd->sys;
Direct3D9Close(vd);
CommonWindowClean(&sys->area);
Direct3D9Destroy(sys);
free(sys);
}