/*****************************************************************************
* dxa9.c : DXVA2 GPU surface conversion module for vlc
*****************************************************************************
* Copyright (C) 2015 VLC authors, VideoLAN and VideoLabs
*
* Authors: 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.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_filter.h>
#include <vlc_picture.h>
#include "d3d9_filters.h"
#include "../../video_chroma/copy.h"
#include <windows.h>
#include <d3d9.h>
#include "../../video_chroma/d3d9_fmt.h"
typedef struct
{
/* GPU to CPU */
copy_cache_t cache;
/* CPU to GPU */
filter_t *filter;
picture_t *staging;
} filter_sys_t;
static bool GetLock(filter_t *p_filter, IDirect3DSurface9 *d3d,
D3DLOCKED_RECT *p_lock, D3DSURFACE_DESC *p_desc)
{
if (unlikely(FAILED( IDirect3DSurface9_GetDesc(d3d, p_desc))))
return false;
/* */
if (FAILED(IDirect3DSurface9_LockRect(d3d, p_lock, NULL, D3DLOCK_READONLY))) {
msg_Err(p_filter, "Failed to lock surface");
return false;
}
return true;
}
static void DXA9_YV12(filter_t *p_filter, picture_t *src, picture_t *dst)
{
copy_cache_t *p_copy_cache = p_filter->p_sys;
picture_sys_d3d9_t *p_sys = ActiveD3D9PictureSys(src);
D3DSURFACE_DESC desc;
D3DLOCKED_RECT lock;
if (!GetLock(p_filter, p_sys->surface, &lock, &desc))
return;
if (desc.Format == MAKEFOURCC('Y','V','1','2') ||
desc.Format == MAKEFOURCC('I','M','C','3')) {
if (dst->format.i_chroma == VLC_CODEC_I420)
picture_SwapUV( dst );
bool imc3 = desc.Format == MAKEFOURCC('I','M','C','3');
size_t chroma_pitch = imc3 ? lock.Pitch : (lock.Pitch / 2);
const size_t pitch[3] = {
lock.Pitch,
chroma_pitch,
chroma_pitch,
};
const uint8_t *plane[3] = {
(uint8_t*)lock.pBits,
(uint8_t*)lock.pBits + pitch[0] * desc.Height,
(uint8_t*)lock.pBits + pitch[0] * desc.Height
+ pitch[1] * desc.Height / 2,
};
if (imc3) {
const uint8_t *V = plane[1];
plane[1] = plane[2];
plane[2] = V;
}
Copy420_P_to_P(dst, plane, pitch, src->format.i_height, p_copy_cache);
if (dst->format.i_chroma == VLC_CODEC_I420)
picture_SwapUV( dst );
} else if (desc.Format == MAKEFOURCC('N','V','1','2')
|| desc.Format == MAKEFOURCC('P','0','1','0')) {
const uint8_t *plane[2] = {
lock.pBits,
(uint8_t*)lock.pBits + lock.Pitch * desc.Height
};
const size_t pitch[2] = {
lock.Pitch,
lock.Pitch,
};
if (desc.Format == MAKEFOURCC('N','V','1','2'))
Copy420_SP_to_P(dst, plane, pitch,
__MIN(desc.Height, src->format.i_y_offset + src->format.i_visible_height),
p_copy_cache);
else
Copy420_16_SP_to_P(dst, plane, pitch,
__MIN(desc.Height, src->format.i_y_offset + src->format.i_visible_height),
6, p_copy_cache);
if (dst->format.i_chroma == VLC_CODEC_YV12)
picture_SwapUV(dst);
} else {
msg_Err(p_filter, "Unsupported DXA9 conversion from 0x%08X to YV12", desc.Format);
}
/* */
IDirect3DSurface9_UnlockRect(p_sys->surface);
}
static void DXA9_NV12(filter_t *p_filter, picture_t *src, picture_t *dst)
{
copy_cache_t *p_copy_cache = p_filter->p_sys;
picture_sys_d3d9_t *p_sys = ActiveD3D9PictureSys(src);
D3DSURFACE_DESC desc;
D3DLOCKED_RECT lock;
if (!GetLock(p_filter, p_sys->surface, &lock, &desc))
return;
if (desc.Format == MAKEFOURCC('N','V','1','2')
|| desc.Format == MAKEFOURCC('P','0','1','0')) {
const uint8_t *plane[2] = {
lock.pBits,
(uint8_t*)lock.pBits + lock.Pitch * desc.Height
};
size_t pitch[2] = {
lock.Pitch,
lock.Pitch,
};
Copy420_SP_to_SP(dst, plane, pitch,
__MIN(desc.Height, src->format.i_y_offset + src->format.i_visible_height),
p_copy_cache);
} else {
msg_Err(p_filter, "Unsupported DXA9 conversion from 0x%08X to NV12", desc.Format);
}
/* */
IDirect3DSurface9_UnlockRect(p_sys->surface);
}
static void DeleteFilter( filter_t * p_filter )
{
vlc_filter_UnloadModule( p_filter );
es_format_Clean( &p_filter->fmt_in );
es_format_Clean( &p_filter->fmt_out );
vlc_object_delete(p_filter);
}
static picture_t *NewBuffer(filter_t *p_filter)
{
filter_t *p_parent = p_filter->owner.sys;
filter_sys_t *p_sys = p_parent->p_sys;
return p_sys->staging;
}
static vlc_decoder_device * HoldD3D9DecoderDevice(vlc_object_t *o, void *sys)
{
VLC_UNUSED(o);
filter_t *p_this = sys;
return filter_HoldDecoderDevice(p_this);
}
static filter_t *CreateFilter( filter_t *p_this, const es_format_t *p_fmt_in,
vlc_fourcc_t dst_chroma )
{
filter_t *p_filter;
p_filter = vlc_object_create( p_this, sizeof(filter_t) );
if (unlikely(p_filter == NULL))
return NULL;
static const struct filter_video_callbacks cbs = { NewBuffer, HoldD3D9DecoderDevice };
p_filter->b_allow_fmt_out_change = false;
p_filter->owner.video = &cbs;
p_filter->owner.sys = p_this;
es_format_InitFromVideo( &p_filter->fmt_in, &p_fmt_in->video );
es_format_InitFromVideo( &p_filter->fmt_out, &p_fmt_in->video );
p_filter->fmt_out.i_codec = p_filter->fmt_out.video.i_chroma = dst_chroma;
p_filter->p_module = vlc_filter_LoadModule (p_filter, "video converter", NULL, false );
if( !p_filter->p_module )
{
msg_Dbg( p_filter, "no video converter found" );
DeleteFilter( p_filter );
return NULL;
}
assert( p_filter->ops != NULL );
return p_filter;
}
static void YV12_D3D9(filter_t *p_filter, picture_t *src, picture_t *dst)
{
filter_sys_t *sys = p_filter->p_sys;
picture_sys_d3d9_t *p_dst_sys = ActiveD3D9PictureSys(dst);
if (sys->filter == NULL)
{
D3DLOCKED_RECT d3drect;
HRESULT hr = IDirect3DSurface9_LockRect(p_dst_sys->surface, &d3drect, NULL, 0);
if (FAILED(hr))
return;
picture_context_t *dst_pic_ctx = dst->context;
dst->context = NULL; // some CPU filters won't like the mix of CPU/GPU
picture_UpdatePlanes(dst, d3drect.pBits, d3drect.Pitch);
/* The dx/d3d buffer is always allocated as YV12 */
if (dst->format.i_chroma == VLC_CODEC_I420)
picture_SwapUV( dst );
picture_CopyPixels(dst, src);
dst->context = dst_pic_ctx;
IDirect3DSurface9_UnlockRect(p_dst_sys->surface);
}
else
{
picture_sys_d3d9_t *p_staging_sys = ActiveD3D9PictureSys(sys->staging);
D3DLOCKED_RECT d3drect;
HRESULT hr = IDirect3DSurface9_LockRect(p_staging_sys->surface, &d3drect, NULL, 0);
if (FAILED(hr))
return;
picture_UpdatePlanes(sys->staging, d3drect.pBits, d3drect.Pitch);
/* The dx/d3d buffer is always allocated as YV12 */
if (sys->staging->format.i_chroma == VLC_CODEC_I420)
picture_SwapUV( sys->staging );
picture_context_t *staging_pic_ctx = sys->staging->context;
sys->staging->context = NULL; // some CPU filters won't like the mix of CPU/GPU
picture_Hold( src );
sys->filter->ops->filter_video(sys->filter, src);
sys->staging->context = staging_pic_ctx;
IDirect3DSurface9_UnlockRect(p_staging_sys->surface);
d3d9_decoder_device_t *d3d9_decoder = GetD3D9OpaqueContext(p_filter->vctx_out);
RECT visibleSource = {
.right = dst->format.i_width, .bottom = dst->format.i_height,
};
IDirect3DDevice9_StretchRect( d3d9_decoder->d3ddev.dev,
p_staging_sys->surface, &visibleSource,
p_dst_sys->surface, &visibleSource,
D3DTEXF_NONE );
}
// stop pretending this is a CPU picture
dst->format.i_chroma = p_filter->fmt_out.video.i_chroma;
dst->i_planes = 0;
}
static picture_t *AllocateCPUtoGPUTexture(filter_t *p_filter)
{
IDirect3DSurface9 *texture = NULL;
video_format_t fmt_staging;
d3d9_decoder_device_t *devsys = GetD3D9OpaqueContext(p_filter->vctx_out);
static const D3DFORMAT outputFormats8[] = {
MAKEFOURCC('I','4','2','0'),
MAKEFOURCC('Y','V','1','2'),
MAKEFOURCC('N','V','1','2'),
D3DFMT_UNKNOWN
};
static const D3DFORMAT outputFormats10[] = {
MAKEFOURCC('P','0','1','0'),
MAKEFOURCC('I','4','2','0'),
MAKEFOURCC('Y','V','1','2'),
MAKEFOURCC('N','V','1','2'),
D3DFMT_UNKNOWN
};
D3DFORMAT format = D3DFMT_UNKNOWN;
const D3DFORMAT *list;
switch( p_filter->fmt_in.video.i_chroma ) {
case VLC_CODEC_I420:
case VLC_CODEC_YV12:
list = outputFormats8;
break;
case VLC_CODEC_I420_10L:
case VLC_CODEC_P010:
list = outputFormats10;
break;
default:
vlc_assert_unreachable();
}
while (*list != D3DFMT_UNKNOWN)
{
HRESULT hr = IDirect3DDevice9_CreateOffscreenPlainSurface(devsys->d3ddev.dev,
p_filter->fmt_out.video.i_width,
p_filter->fmt_out.video.i_height,
*list,
D3DPOOL_DEFAULT,
&texture,
NULL);
if (SUCCEEDED(hr)) {
format = *list;
msg_Dbg(p_filter, "using pixel format %4.4s", (char*)&format);
break;
}
list++;
}
if (format == D3DFMT_UNKNOWN)
{
msg_Err(p_filter, "Failed to find a usable pixel format");
goto done;
}
struct d3d9_pic_context *pic_ctx = calloc(1, sizeof(*pic_ctx));
if (unlikely(pic_ctx == NULL))
goto done;
pic_ctx->s = (picture_context_t) {
d3d9_pic_context_destroy, d3d9_pic_context_copy,
vlc_video_context_Hold(p_filter->vctx_out),
};
video_format_Copy(&fmt_staging, &p_filter->fmt_out.video);
fmt_staging.i_chroma = format;
picture_resource_t dummy_res = { .p_sys = NULL };
picture_t *p_dst = picture_NewFromResource(&fmt_staging, &dummy_res);
if (p_dst == NULL) {
msg_Err(p_filter, "Failed to map create the temporary picture.");
goto done;
}
pic_ctx->picsys.surface = texture;
p_dst->context = &pic_ctx->s;
return p_dst;
done:
return NULL;
}
static void D3D9CloseConverter( filter_t *p_filter )
{
filter_sys_t *p_sys = p_filter->p_sys;
CopyCleanCache( &p_sys->cache );
free( p_sys );
p_filter->p_sys = NULL;
}
VIDEO_FILTER_WRAPPER_CLOSE(DXA9_YV12, D3D9CloseConverter)
VIDEO_FILTER_WRAPPER_CLOSE(DXA9_NV12, D3D9CloseConverter)
static picture_t *YV12_D3D9_Filter( filter_t *p_filter, picture_t *p_pic )
{
picture_t *p_outpic = AllocateCPUtoGPUTexture( p_filter );
if( p_outpic )
{
YV12_D3D9( p_filter, p_pic, p_outpic );
picture_CopyProperties( p_outpic, p_pic );
}
picture_Release( p_pic );
return p_outpic;
}
int D3D9OpenConverter( filter_t *p_filter )
{
if ( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE &&
p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE_10B )
return VLC_EGENERIC;
if ( GetD3D9ContextPrivate(p_filter->vctx_in) == NULL )
return VLC_EGENERIC;
if ( p_filter->fmt_in.video.i_height != p_filter->fmt_out.video.i_height
|| p_filter->fmt_in.video.i_width != p_filter->fmt_out.video.i_width )
return VLC_EGENERIC;
uint8_t pixel_bytes = 1;
switch( p_filter->fmt_out.video.i_chroma ) {
case VLC_CODEC_I420:
case VLC_CODEC_YV12:
if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE )
return VLC_EGENERIC;
p_filter->ops = &DXA9_YV12_ops;
break;
case VLC_CODEC_I420_10L:
if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE_10B )
return VLC_EGENERIC;
p_filter->ops = &DXA9_YV12_ops;
pixel_bytes = 2;
break;
case VLC_CODEC_NV12:
if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE )
return VLC_EGENERIC;
p_filter->ops = &DXA9_NV12_ops;
break;
case VLC_CODEC_P010:
if( p_filter->fmt_in.video.i_chroma != VLC_CODEC_D3D9_OPAQUE_10B )
return VLC_EGENERIC;
p_filter->ops = &DXA9_NV12_ops;
pixel_bytes = 2;
break;
default:
return VLC_EGENERIC;
}
filter_sys_t *p_sys = calloc(1, sizeof(filter_sys_t));
if (!p_sys)
return VLC_ENOMEM;
if (CopyInitCache(&p_sys->cache, p_filter->fmt_in.video.i_width * pixel_bytes))
{
free(p_sys);
return VLC_ENOMEM;
}
p_filter->p_sys = p_sys;
return VLC_SUCCESS;
}
static void D3D9CloseCPUConverter( filter_t *p_filter )
{
filter_sys_t *p_sys = p_filter->p_sys;
if (p_sys->filter != NULL)
DeleteFilter(p_sys->filter);
if (p_sys->staging)
picture_Release(p_sys->staging);
vlc_video_context_Release(p_filter->vctx_out);
free( p_sys );
p_filter->p_sys = NULL;
}
static const struct vlc_filter_operations YV12_D3D9_ops = {
.filter_video = YV12_D3D9_Filter, .close = D3D9CloseCPUConverter,
};
int D3D9OpenCPUConverter( filter_t *p_filter )
{
int err = VLC_EGENERIC;
picture_t *p_dst = NULL;
if ( p_filter->fmt_out.video.i_chroma != VLC_CODEC_D3D9_OPAQUE
&& p_filter->fmt_out.video.i_chroma != VLC_CODEC_D3D9_OPAQUE_10B )
return VLC_EGENERIC;
if ( p_filter->fmt_in.video.i_height != p_filter->fmt_out.video.i_height
|| p_filter->fmt_in.video.i_width != p_filter->fmt_out.video.i_width )
return VLC_EGENERIC;
switch( p_filter->fmt_in.video.i_chroma ) {
case VLC_CODEC_I420:
case VLC_CODEC_YV12:
case VLC_CODEC_I420_10L:
case VLC_CODEC_P010:
p_filter->ops = &YV12_D3D9_ops;
break;
default:
return VLC_EGENERIC;
}
vlc_decoder_device *dec_device = filter_HoldDecoderDeviceType( p_filter, VLC_DECODER_DEVICE_DXVA2 );
if (dec_device == NULL)
{
msg_Err(p_filter, "Missing decoder device");
return VLC_EGENERIC;
}
d3d9_decoder_device_t *devsys = GetD3D9OpaqueDevice(dec_device);
if (devsys == NULL)
{
msg_Err(p_filter, "Incompatible decoder device %d", dec_device->type);
vlc_decoder_device_Release(dec_device);
return VLC_EGENERIC;
}
filter_sys_t *p_sys = calloc(1, sizeof(filter_sys_t));
if (!p_sys)
{
vlc_decoder_device_Release(dec_device);
return VLC_ENOMEM;
}
p_filter->vctx_out = vlc_video_context_Create( dec_device, VLC_VIDEO_CONTEXT_DXVA2, sizeof(d3d9_video_context_t),
&d3d9_vctx_ops );
vlc_decoder_device_Release(dec_device);
if ( p_filter->vctx_out == NULL )
{
msg_Err(p_filter, "Failed to create the video context");
goto done;
}
p_dst = AllocateCPUtoGPUTexture(p_filter);
if (p_dst == NULL)
goto done;
d3d9_video_context_t *vctx_sys = GetD3D9ContextPrivate( p_filter->vctx_out );
vctx_sys->format = p_dst->format.i_chroma;
if ( !video_format_IsSameChroma( &p_filter->fmt_in.video, &p_dst->format ) )
{
p_sys->filter = CreateFilter(p_filter, &p_filter->fmt_in, p_dst->format.i_chroma);
if (!p_sys->filter)
goto done;
p_sys->staging = p_dst;
}
else
{
picture_Release(p_dst);
}
p_filter->p_sys = p_sys;
err = VLC_SUCCESS;
done:
if (err != VLC_SUCCESS)
{
vlc_video_context_Release(p_filter->vctx_out);
free(p_sys);
}
return err;
}
¤ Dauer der Verarbeitung: 0.7 Sekunden
¤
*© Formatika GbR, Deutschland
|
|