Commit 4b482440 authored by Damien Fouilleul's avatar Damien Fouilleul

- backporting from 0.8.6

parent 3b44fa8c
......@@ -24,14 +24,13 @@
/*****************************************************************************
* Preamble:
*
* This plugin will use YUV overlay if supported, using overlay will result in
* This plugin will use YUV surface if supported, using YUV will result in
* the best video quality (hardware filering 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. This fallback method also enables us to
* display video in window mode.
* effectively display the pictures.
*
*****************************************************************************/
#include <errno.h> /* ENOMEM */
......@@ -73,7 +72,7 @@ static void Direct3DVoutReleasePictures ( vout_thread_t * );
static int Direct3DVoutLockSurface ( vout_thread_t *, picture_t * );
static int Direct3DVoutUnlockSurface( vout_thread_t *, picture_t * );
static void Direct3DVoutRenderDefault ( vout_thread_t *, picture_t * );
static void Direct3DVoutRenderSurface ( vout_thread_t *, picture_t * );
static int Direct3DVoutCreateScene ( vout_thread_t * );
static void Direct3DVoutReleaseScene ( vout_thread_t * );
......@@ -82,12 +81,30 @@ static void Direct3DVoutRenderScene ( vout_thread_t *, picture_t * );
/*****************************************************************************
* Module descriptor
*****************************************************************************/
static int get_capability_for_osversion()
{
OSVERSIONINFO winVer;
winVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if( GetVersionEx(&winVer) )
{
if( winVer.dwMajorVersion > 5 )
{
/* Windows Vista or above, make this module the default */
return 150;
}
}
/* Windows XP or lower, make sure this module isn't the default */
return 50;
}
vlc_module_begin();
set_shortname( "Direct3D" );
set_category( CAT_VIDEO );
set_subcategory( SUBCAT_VIDEO_VOUT );
set_description( _("DirectX 3D video output") );
set_capability( "video output", 150 );
set_capability( "video output", get_capability_for_osversion() );
add_shortcut( "direct3d" );
set_callbacks( OpenVideo, CloseVideo );
......@@ -120,7 +137,7 @@ typedef struct
/*****************************************************************************
* OpenVideo: allocate DirectX video thread output method
*****************************************************************************
* This function allocates and initialize the DirectX vout method.
* This function allocates and initialize the Direct3D vout method.
*****************************************************************************/
static int OpenVideo( vlc_object_t *p_this )
{
......@@ -157,9 +174,17 @@ static int OpenVideo( vlc_object_t *p_this )
SetRectEmpty( &p_vout->p_sys->rect_display );
SetRectEmpty( &p_vout->p_sys->rect_parent );
var_Create( p_vout, "directx-hw-yuv", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
var_Create( p_vout, "directx-device", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
var_Create( p_vout, "video-title", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
var_Create( p_vout, "disable-screensaver", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
p_vout->p_sys->b_cursor_hidden = 0;
p_vout->p_sys->i_lastmoved = mdate();
var_Create( p_vout, "video-title", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
var_Create( p_vout, "disable-screensaver", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
/* Set main window's size */
p_vout->p_sys->i_window_width = p_vout->i_window_width;
p_vout->p_sys->i_window_height = p_vout->i_window_height;
......@@ -287,6 +312,10 @@ static void CloseVideo( vlc_object_t *p_this )
static int Init( vout_thread_t *p_vout )
{
int i_ret;
vlc_value_t val;
var_Get( p_vout, "directx-hw-yuv", &val );
p_vout->p_sys->b_hw_yuv = val.b_bool;
/* Initialise Direct3D */
if( VLC_SUCCESS != Direct3DVoutOpen( p_vout ) )
......@@ -321,6 +350,9 @@ static int Init( vout_thread_t *p_vout )
return i_ret;
}
/* Change the window title bar text */
PostMessage( p_vout->p_sys->hwnd, WM_VLC_CHANGE_TEXT, 0, 0 );
p_vout->fmt_out.i_chroma = p_vout->output.i_chroma;
return VLC_SUCCESS;
}
......@@ -587,7 +619,8 @@ static int Manage( vout_thread_t *p_vout )
static void Display( vout_thread_t *p_vout, picture_t *p_pic )
{
LPDIRECT3DDEVICE9 p_d3ddev = p_vout->p_sys->p_d3ddev;
// Present the backbuffer contents to the display
// Present the back buffer contents to the display
// stretching and filtering happens here
HRESULT hr = IDirect3DDevice9_Present(p_d3ddev, NULL, NULL, NULL, NULL);
if( FAILED(hr) )
msg_Dbg( p_vout, "%s:%d (hr=0x%0lX)", __FUNCTION__, __LINE__, hr);
......@@ -605,8 +638,26 @@ static int Direct3DVoutCreate( vout_thread_t *p_vout )
LPDIRECT3D9 p_d3dobj;
D3DCAPS9 d3dCaps;
LPDIRECT3D9 (WINAPI *OurDirect3DCreate9)(UINT SDKVersion);
p_vout->p_sys->hd3d9_dll = LoadLibrary(TEXT("D3D9.DLL"));
if( NULL == p_vout->p_sys->hd3d9_dll )
{
msg_Warn( p_vout, "cannot load d3d9.dll, aborting" );
return VLC_EGENERIC;
}
OurDirect3DCreate9 =
(void *)GetProcAddress( p_vout->p_sys->hd3d9_dll,
TEXT("Direct3DCreate9") );
if( OurDirect3DCreate9 == NULL )
{
msg_Err( p_vout, "Cannot locate reference to Direct3DCreate9 ABI in DLL" );
return VLC_EGENERIC;
}
/* Create the D3D object. */
p_d3dobj = Direct3DCreate9( D3D_SDK_VERSION );
p_d3dobj = OurDirect3DCreate9( D3D_SDK_VERSION );
if( NULL == p_d3dobj )
{
msg_Err( p_vout, "Could not create Direct3D9 instance.");
......@@ -640,6 +691,11 @@ static void Direct3DVoutRelease( vout_thread_t *p_vout )
IDirect3D9_Release(p_vout->p_sys->p_d3dobj);
p_vout->p_sys->p_d3dobj = NULL;
}
if( NULL != p_vout->p_sys->hd3d9_dll )
{
FreeLibrary(p_vout->p_sys->hd3d9_dll);
p_vout->p_sys->hd3d9_dll = NULL;
}
}
/*****************************************************************************
......@@ -804,35 +860,41 @@ static D3DFORMAT Direct3DVoutSelectFormat( vout_thread_t *p_vout, D3DFORMAT targ
D3DFORMAT Direct3DVoutFindFormat(vout_thread_t *p_vout, int i_chroma, D3DFORMAT target)
{
switch( i_chroma )
if( p_vout->p_sys->b_hw_yuv )
{
case VLC_FOURCC('U','Y','V','Y'):
case VLC_FOURCC('U','Y','N','V'):
case VLC_FOURCC('Y','4','2','2'):
{
static const D3DFORMAT formats[] =
{ D3DFMT_UYVY, D3DFMT_YUY2, D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_R5G6B5, D3DFMT_X1R5G5B5 };
return Direct3DVoutSelectFormat(p_vout, target, formats, sizeof(formats)/sizeof(D3DFORMAT));
}
case VLC_FOURCC('I','4','2','0'):
case VLC_FOURCC('I','4','2','2'):
case VLC_FOURCC('Y','V','1','2'):
{
/* typically 3D textures don't support planar format
** fallback to packed version and use pixel
** shader or CPU for the conversion
*/
static const D3DFORMAT formats[] =
{ D3DFMT_YUY2, D3DFMT_UYVY, D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_R5G6B5, D3DFMT_X1R5G5B5 };
return Direct3DVoutSelectFormat(p_vout, target, formats, sizeof(formats)/sizeof(D3DFORMAT));
}
case VLC_FOURCC('Y','U','Y','2'):
case VLC_FOURCC('Y','U','N','V'):
switch( i_chroma )
{
static const D3DFORMAT formats[] =
{ D3DFMT_YUY2, D3DFMT_UYVY, D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_R5G6B5, D3DFMT_X1R5G5B5 };
return Direct3DVoutSelectFormat(p_vout, target, formats, sizeof(formats)/sizeof(D3DFORMAT));
case VLC_FOURCC('U','Y','V','Y'):
case VLC_FOURCC('U','Y','N','V'):
case VLC_FOURCC('Y','4','2','2'):
{
static const D3DFORMAT formats[] =
{ D3DFMT_UYVY, D3DFMT_YUY2, D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_R5G6B5, D3DFMT_X1R5G5B5 };
return Direct3DVoutSelectFormat(p_vout, target, formats, sizeof(formats)/sizeof(D3DFORMAT));
}
case VLC_FOURCC('I','4','2','0'):
case VLC_FOURCC('I','4','2','2'):
case VLC_FOURCC('Y','V','1','2'):
{
/* typically 3D textures don't support planar format
** fallback to packed version and use CPU for the conversion
*/
static const D3DFORMAT formats[] =
{ D3DFMT_YUY2, D3DFMT_UYVY, D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_R5G6B5, D3DFMT_X1R5G5B5 };
return Direct3DVoutSelectFormat(p_vout, target, formats, sizeof(formats)/sizeof(D3DFORMAT));
}
case VLC_FOURCC('Y','U','Y','2'):
case VLC_FOURCC('Y','U','N','V'):
{
static const D3DFORMAT formats[] =
{ D3DFMT_YUY2, D3DFMT_UYVY, D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_R5G6B5, D3DFMT_X1R5G5B5 };
return Direct3DVoutSelectFormat(p_vout, target, formats, sizeof(formats)/sizeof(D3DFORMAT));
}
}
}
switch( i_chroma )
{
case VLC_FOURCC('R', 'V', '1', '5'):
{
static const D3DFORMAT formats[] =
......@@ -858,7 +920,36 @@ D3DFORMAT Direct3DVoutFindFormat(vout_thread_t *p_vout, int i_chroma, D3DFORMAT
return Direct3DVoutSelectFormat(p_vout, target, formats, sizeof(formats)/sizeof(D3DFORMAT));
}
default:
;
{
/* use display default format */
LPDIRECT3D9 p_d3dobj = p_vout->p_sys->p_d3dobj;
D3DDISPLAYMODE d3ddm;
HRESULT hr = IDirect3D9_GetAdapterDisplayMode(p_d3dobj, D3DADAPTER_DEFAULT, &d3ddm );
if( SUCCEEDED(hr))
{
/*
** some professional cards could use some advanced pixel format as default,
** make sure we stick with chromas that we can handle internally
*/
switch( d3ddm.Format )
{
case D3DFMT_R8G8B8:
case D3DFMT_X8R8G8B8:
case D3DFMT_A8R8G8B8:
msg_Dbg( p_vout, "defaulting to adpater pixel format");
return Direct3DVoutSelectFormat(p_vout, target, &d3ddm.Format, 1);
default:
{
/* if we fall here, that probably means that we need to render some YUV format */
static const D3DFORMAT formats[] =
{ D3DFMT_R8G8B8, D3DFMT_X8R8G8B8, D3DFMT_A8R8G8B8 };
msg_Dbg( p_vout, "defaulting to built-in pixel format");
return Direct3DVoutSelectFormat(p_vout, target, formats, sizeof(formats)/sizeof(D3DFORMAT));
}
}
}
}
}
return D3DFMT_UNKNOWN;
}
......@@ -873,62 +964,75 @@ static int Direct3DVoutSetOutputFormat(vout_thread_t *p_vout, D3DFORMAT format)
case D3DFMT_UYVY:
p_vout->output.i_chroma = VLC_FOURCC('U', 'Y', 'V', 'Y');
break;
case D3DFMT_R8G8B8:
p_vout->output.i_chroma = VLC_FOURCC('R', 'V', '2', '4');
break;
case D3DFMT_X8R8G8B8:
case D3DFMT_A8R8G8B8:
/*
** FIXME: some custom masks are not handled properly in rgb_yuv converter,
** ARGB do NOT work !
*/
p_vout->output.i_chroma = VLC_FOURCC('R', 'V', '3', '2');
p_vout->output.i_rmask = 0x000000ff;
p_vout->output.i_rmask = 0x00ff0000;
p_vout->output.i_gmask = 0x0000ff00;
p_vout->output.i_bmask = 0x00ff0000;
p_vout->output.i_bmask = 0x000000ff;
# if defined( WORDS_BIGENDIAN )
p_vout->output.i_rrshift = 0;
p_vout->output.i_lrshift = 24;
p_vout->output.i_rgshift = 0;
p_vout->output.i_lgshift = 16;
p_vout->output.i_rbshift = 0;
p_vout->output.i_lbshift = 8;
# else
p_vout->output.i_rrshift = 0;
p_vout->output.i_lrshift = 8;
p_vout->output.i_rgshift = 0;
p_vout->output.i_lgshift = 16;
p_vout->output.i_rbshift = 0;
p_vout->output.i_lbshift = 24;
# endif
break;
case D3DFMT_R5G6B5:
p_vout->output.i_chroma = VLC_FOURCC('R', 'V', '1', '6');
# if defined( WORDS_BIGENDIAN )
p_vout->output.i_rmask = (0x1fL)<<11;
p_vout->output.i_gmask = (0x3fL)<<5;
p_vout->output.i_bmask = (0x1fL)<<0;
//p_vout->output.i_rshift = 11;
//p_vout->output.i_gshift = 5;
//p_vout->output.i_bshift = 0;
# if defined( WORDS_BIGENDIAN )
p_vout->output.i_rrshift = 0;
p_vout->output.i_lrshift = 11;
p_vout->output.i_rgshift = 0;
p_vout->output.i_lgshift = 5;
p_vout->output.i_rbshift = 0;
p_vout->output.i_lbshift = 0;
# else
/*
** FIXME: in little endian mode, following masking is not byte aligned,
** therefore green bits will not be sequentially merged !
*/
p_vout->output.i_rmask = (0x1fL)<<0;
p_vout->output.i_gmask = (0x3fL)<<5;
p_vout->output.i_bmask = (0x1fL)<<11;
//p_vout->output.i_rshift = 0;
//p_vout->output.i_gshift = 5;
//p_vout->output.i_bshift = 11;
/* FIXME: since components are not byte aligned,
there is not chance that this will work */
p_vout->output.i_rrshift = 0;
p_vout->output.i_lrshift = 0;
p_vout->output.i_rgshift = 0;
p_vout->output.i_lgshift = 5;
p_vout->output.i_rbshift = 0;
p_vout->output.i_lbshift = 11;
# endif
break;
case D3DFMT_X1R5G5B5:
p_vout->output.i_chroma = VLC_FOURCC('R', 'V', '1', '5');
# if defined( WORDS_BIGENDIAN )
p_vout->output.i_rmask = (0x1fL)<<10;
p_vout->output.i_gmask = (0x1fL)<<5;
p_vout->output.i_bmask = (0x1fL)<<0;
//p_vout->output.i_rshift = 10;
//p_vout->output.i_gshift = 5;
//p_vout->output.i_bshift = 0;
# if defined( WORDS_BIGENDIAN )
p_vout->output.i_rrshift = 0;
p_vout->output.i_lrshift = 10;
p_vout->output.i_rgshift = 0;
p_vout->output.i_lgshift = 5;
p_vout->output.i_rbshift = 0;
p_vout->output.i_lbshift = 0;
# else
/*
** FIXME: in little endian mode, following masking is not byte aligned,
** therefore green bits will not be sequentially merged !
*/
p_vout->output.i_rmask = (0x1fL)<<1;
p_vout->output.i_gmask = (0x1fL)<<6;
p_vout->output.i_bmask = (0x1fL)<<11;
//p_vout->output.i_rshift = 1;
//p_vout->output.i_gshift = 5;
//p_vout->output.i_bshift = 11;
/* FIXME: since components are not byte aligned,
there is not chance that this will work */
p_vout->output.i_rrshift = 0;
p_vout->output.i_lrshift = 1;
p_vout->output.i_rgshift = 0;
p_vout->output.i_lgshift = 5;
p_vout->output.i_rbshift = 0;
p_vout->output.i_lbshift = 11;
# endif
break;
default:
......@@ -986,7 +1090,7 @@ static int Direct3DVoutCreatePictures( vout_thread_t *p_vout, size_t i_num_pics
return VLC_EGENERIC;
}
/* fill surface with default color */
/* fill surface with black color */
IDirect3DDevice9_ColorFill(p_d3ddev, p_d3dsurf, NULL, D3DCOLOR_ARGB(0xFF, 0, 0, 0) );
/* assign surface to internal structure */
......@@ -1267,7 +1371,7 @@ static void Direct3DVoutReleaseScene( vout_thread_t *p_vout )
* This function is intented for lower end video cards, without pixel shader
* support or low video RAM
*****************************************************************************/
static void Direct3DVoutRenderDefault( vout_thread_t *p_vout, picture_t *p_pic )
static void Direct3DVoutRenderSurface( vout_thread_t *p_vout, picture_t *p_pic )
{
LPDIRECT3DDEVICE9 p_d3ddev = p_vout->p_sys->p_d3ddev;
LPDIRECT3DSURFACE9 p_d3dsrc, p_d3ddest;
......@@ -1312,7 +1416,7 @@ static void Direct3DVoutRenderDefault( vout_thread_t *p_vout, picture_t *p_pic )
continue;
}
/* Copy picture surface into texture surface, color space conversion happens here */
/* Copy picture surface into back buffer surface, color space conversion happens here */
hr = IDirect3DDevice9_StretchRect(p_d3ddev, p_d3dsrc, NULL, p_d3ddest, NULL, D3DTEXF_NONE);
IDirect3DSurface9_Release(p_d3ddest);
if( FAILED(hr) )
......
......@@ -77,6 +77,8 @@ struct picture_sys_t
* the linking stage.
*****************************************************************************/
#include <initguid.h>
#undef GUID_EXT
#define GUID_EXT
DEFINE_GUID( IID_IDirectDraw2, 0xB3A6F3E0,0x2B43,0x11CF,0xA2,0xDE,0x00,0xAA,0x00,0xB9,0x33,0x56 );
DEFINE_GUID( IID_IDirectDrawSurface2, 0x57805885,0x6eec,0x11cf,0x94,0x41,0xa8,0x23,0x03,0xc1,0x0e,0x27 );
......
......@@ -433,7 +433,7 @@ static int DirectXCreateWindow( vout_thread_t *p_vout )
* then fine, otherwise return with an error. */
if( !GetClassInfo( hInstance, _T("VLC DirectX"), &wndclass ) )
{
msg_Err( p_vout, "DirectXCreateWindow RegisterClass FAILED" );
msg_Err( p_vout, "DirectXCreateWindow RegisterClass FAILED (err=%lu)", GetLastError() );
return VLC_EGENERIC;
}
}
......@@ -448,7 +448,7 @@ static int DirectXCreateWindow( vout_thread_t *p_vout )
* then fine, otherwise return with an error. */
if( !GetClassInfo( hInstance, _T("VLC DirectX video"), &wndclass ) )
{
msg_Err( p_vout, "DirectXCreateWindow RegisterClass FAILED" );
msg_Err( p_vout, "DirectXCreateWindow RegisterClass FAILED (err=%lu)", GetLastError() );
return VLC_EGENERIC;
}
}
......@@ -505,7 +505,7 @@ static int DirectXCreateWindow( vout_thread_t *p_vout )
if( !p_vout->p_sys->hwnd )
{
msg_Warn( p_vout, "DirectXCreateWindow create window FAILED" );
msg_Warn( p_vout, "DirectXCreateWindow create window FAILED (err=%lu)", GetLastError() );
return VLC_EGENERIC;
}
......@@ -541,19 +541,19 @@ static int DirectXCreateWindow( vout_thread_t *p_vout )
* the size of the video, which allows us to use crazy overlay colorkeys
* without having them shown outside of the video area. */
p_vout->p_sys->hvideownd =
CreateWindow( _T("VLC DirectX video"), _T(""), /* window class */
WS_CHILD | WS_VISIBLE, /* window style */
0, 0,
p_vout->render.i_width, /* default width */
p_vout->render.i_height, /* default height */
p_vout->p_sys->hwnd, /* parent window */
NULL, hInstance,
(LPVOID)p_vout ); /* send p_vout to WM_CREATE */
CreateWindow( _T("VLC DirectX video"), _T(""), /* window class */
WS_CHILD | WS_VISIBLE, /* window style */
0, 0,
p_vout->render.i_width, /* default width */
p_vout->render.i_height, /* default height */
p_vout->p_sys->hwnd, /* parent window */
NULL, hInstance,
(LPVOID)p_vout ); /* send p_vout to WM_CREATE */
if( !p_vout->p_sys->hvideownd )
msg_Warn( p_vout, "can't create video sub-window" );
msg_Warn( p_vout, "can't create video sub-window" );
else
msg_Dbg( p_vout, "created video sub-window" );
msg_Dbg( p_vout, "created video sub-window" );
/* Now display the window */
ShowWindow( p_vout->p_sys->hwnd, SW_SHOW );
......@@ -768,10 +768,17 @@ static long FAR PASCAL DirectXEventProc( HWND hwnd, UINT message,
/* Store p_vout for future use */
p_vout = (vout_thread_t *)((CREATESTRUCT *)lParam)->lpCreateParams;
SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR)p_vout );
return TRUE;
}
else
{
p_vout = (vout_thread_t *)GetWindowLongPtr( hwnd, GWLP_USERDATA );
if( !p_vout )
{
/* Hmmm mozilla does manage somehow to save the pointer to our
* windowproc and still calls it after the vout has been closed. */
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
/* Catch the screensaver and the monitor turn-off */
......@@ -782,13 +789,6 @@ static long FAR PASCAL DirectXEventProc( HWND hwnd, UINT message,
return 0; /* this stops them from happening */
}
if( !p_vout )
{
/* Hmmm mozilla does manage somehow to save the pointer to our
* windowproc and still calls it after the vout has been closed. */
return DefWindowProc(hwnd, message, wParam, lParam);
}
if( hwnd == p_vout->p_sys->hvideownd )
return DefWindowProc(hwnd, message, wParam, lParam);
......@@ -845,7 +845,6 @@ static long FAR PASCAL DirectXEventProc( HWND hwnd, UINT message,
/* We do not want to relay these messages to the parent window
* because we rely on the background color for the overlay. */
return DefWindowProc(hwnd, message, wParam, lParam);
break;
default:
//msg_Dbg( p_vout, "WinProc WM Default %i", message );
......
......@@ -88,6 +88,8 @@ struct vout_sys_t
RECT rect_dest;
RECT rect_dest_clipped;
vlc_bool_t b_hw_yuv; /* Should we use hardware YUV->RGB conversions */
#ifdef MODULE_NAME_IS_vout_directx
/* Overlay alignment restrictions */
int i_align_src_boundary;
......@@ -97,7 +99,6 @@ struct vout_sys_t
vlc_bool_t b_using_overlay; /* Are we using an overlay surface */
vlc_bool_t b_use_sysmem; /* Should we use system memory for surfaces */
vlc_bool_t b_hw_yuv; /* Should we use hardware YUV->RGB conversions */
vlc_bool_t b_3buf_overlay; /* Should we use triple buffered overlays */
/* DDraw capabilities */
......@@ -123,6 +124,7 @@ struct vout_sys_t
#ifdef MODULE_NAME_IS_direct3d
// core objects
HINSTANCE hd3d9_dll; /* handle of the opened d3d9 dll */
LPDIRECT3D9 p_d3dobj;
LPDIRECT3DDEVICE9 p_d3ddev;
D3DPRESENT_PARAMETERS d3dpp;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment