Added bicubic and lanczos downscale shaders, optimized existing bilinear downscale shaders
This commit is contained in:
parent
10b43836bf
commit
3cd334b61e
14
OBS.rc
14
OBS.rc
@ -139,12 +139,14 @@ BEGIN
|
|||||||
CONTROL "Settings.Video.Monitor",IDC_USEMONITOR,"Button",BS_AUTORADIOBUTTON,22,31,116,10
|
CONTROL "Settings.Video.Monitor",IDC_USEMONITOR,"Button",BS_AUTORADIOBUTTON,22,31,116,10
|
||||||
COMBOBOX IDC_MONITOR,144,30,36,126,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
|
COMBOBOX IDC_MONITOR,144,30,36,126,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
|
||||||
RTEXT "Settings.Video.Downscale",IDC_STATIC,4,61,138,8
|
RTEXT "Settings.Video.Downscale",IDC_STATIC,4,61,138,8
|
||||||
COMBOBOX IDC_DOWNSCALE,144,59,151,126,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
COMBOBOX IDC_DOWNSCALE,144,59,144,126,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||||
RTEXT "Settings.Video.FPS",IDC_STATIC,4,80,138,8
|
RTEXT "Settings.Video.Filter",IDC_STATIC,4,78,138,8
|
||||||
EDITTEXT IDC_FPS_EDIT,144,77,40,14,ES_AUTOHSCROLL | ES_NUMBER
|
COMBOBOX IDC_FILTER,144,76,234,126,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||||
CONTROL "",IDC_FPS,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,185,77,10,14
|
RTEXT "Settings.Video.FPS",IDC_STATIC,4,96,138,8
|
||||||
LTEXT "Settings.Info",IDC_INFO,7,117,418,37,NOT WS_VISIBLE
|
EDITTEXT IDC_FPS_EDIT,144,93,40,14,ES_AUTOHSCROLL | ES_NUMBER
|
||||||
CONTROL "Settings.Video.DisableAero",IDC_DISABLEAERO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,4,96,149,10,WS_EX_RIGHT
|
CONTROL "",IDC_FPS,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,185,93,10,14
|
||||||
|
LTEXT "Settings.Info",IDC_INFO,7,133,418,37,NOT WS_VISIBLE
|
||||||
|
CONTROL "Settings.Video.DisableAero",IDC_DISABLEAERO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,4,112,149,10,WS_EX_RIGHT
|
||||||
END
|
END
|
||||||
|
|
||||||
IDD_SETTINGS_GENERAL DIALOGEX 0, 0, 427, 336
|
IDD_SETTINGS_GENERAL DIALOGEX 0, 0, 427, 336
|
||||||
|
@ -536,6 +536,7 @@ private:
|
|||||||
UINT scaleCX, scaleCY;
|
UINT scaleCX, scaleCY;
|
||||||
UINT outputCX, outputCY;
|
UINT outputCX, outputCY;
|
||||||
float downscale;
|
float downscale;
|
||||||
|
int downscaleType;
|
||||||
UINT frameTime, fps;
|
UINT frameTime, fps;
|
||||||
bool bUsing444;
|
bool bUsing444;
|
||||||
|
|
||||||
|
@ -139,6 +139,7 @@ void OBS::Start()
|
|||||||
int defCX = screenRect.right - screenRect.left;
|
int defCX = screenRect.right - screenRect.left;
|
||||||
int defCY = screenRect.bottom - screenRect.top;
|
int defCY = screenRect.bottom - screenRect.top;
|
||||||
|
|
||||||
|
downscaleType = AppConfig->GetInt(TEXT("Video"), TEXT("Filter"), 0);
|
||||||
downscale = AppConfig->GetFloat(TEXT("Video"), TEXT("Downscale"), 1.0f);
|
downscale = AppConfig->GetFloat(TEXT("Video"), TEXT("Downscale"), 1.0f);
|
||||||
baseCX = AppConfig->GetInt(TEXT("Video"), TEXT("BaseWidth"), defCX);
|
baseCX = AppConfig->GetInt(TEXT("Video"), TEXT("BaseWidth"), defCX);
|
||||||
baseCY = AppConfig->GetInt(TEXT("Video"), TEXT("BaseHeight"), defCY);
|
baseCY = AppConfig->GetInt(TEXT("Video"), TEXT("BaseHeight"), defCY);
|
||||||
@ -179,17 +180,20 @@ void OBS::Start()
|
|||||||
|
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
CTSTR lpShader = NULL;
|
CTSTR lpShader;
|
||||||
if(CloseFloat(downscale, 1.0))
|
if(CloseFloat(downscale, 1.0))
|
||||||
lpShader = TEXT("shaders/DrawYUVTexture.pShader");
|
lpShader = TEXT("shaders/DrawYUVTexture.pShader");
|
||||||
else if(CloseFloat(downscale, 1.5))
|
else if(downscale < 2.01)
|
||||||
lpShader = TEXT("shaders/DownscaleYUV1.5.pShader");
|
{
|
||||||
else if(CloseFloat(downscale, 2.0))
|
switch(downscaleType)
|
||||||
lpShader = TEXT("shaders/DownscaleYUV2.pShader");
|
{
|
||||||
else if(CloseFloat(downscale, 2.25))
|
case 0: lpShader = TEXT("shaders/DownscaleBilinear1YUV.pShader"); break;
|
||||||
lpShader = TEXT("shaders/DownscaleYUV2.25.pShader");
|
case 1: lpShader = TEXT("shaders/DownscaleBicubicYUV.pShader"); break;
|
||||||
else if(CloseFloat(downscale, 3.0))
|
case 2: lpShader = TEXT("shaders/DownscaleLanczos6tapYUV.pShader"); break;
|
||||||
lpShader = TEXT("shaders/DownscaleYUV3.pShader");
|
}
|
||||||
|
}
|
||||||
|
else if(downscale < 3.01)
|
||||||
|
lpShader = TEXT("shaders/DownscaleBilinear9YUV.pShader");
|
||||||
else
|
else
|
||||||
CrashError(TEXT("Invalid downscale value (must be either 1.0, 1.5, 2.0, 2.25, or 3.0)"));
|
CrashError(TEXT("Invalid downscale value (must be either 1.0, 1.5, 2.0, 2.25, or 3.0)"));
|
||||||
|
|
||||||
|
@ -602,7 +602,7 @@ void OBS::MainCaptureLoop()
|
|||||||
if(renderFrameCtrlSize.x != oldRenderFrameCtrlWidth || renderFrameCtrlSize.y != oldRenderFrameCtrlHeight)
|
if(renderFrameCtrlSize.x != oldRenderFrameCtrlWidth || renderFrameCtrlSize.y != oldRenderFrameCtrlHeight)
|
||||||
{
|
{
|
||||||
// User is drag resizing the window. We don't recreate the swap chains so our coordinates are wrong
|
// User is drag resizing the window. We don't recreate the swap chains so our coordinates are wrong
|
||||||
SetViewport(0.0f, 0.0f, oldRenderFrameCtrlWidth, oldRenderFrameCtrlHeight);
|
SetViewport(0.0f, 0.0f, (float)oldRenderFrameCtrlWidth, (float)oldRenderFrameCtrlHeight);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
SetViewport(0.0f, 0.0f, renderFrameCtrlSize.x, renderFrameCtrlSize.y);
|
SetViewport(0.0f, 0.0f, renderFrameCtrlSize.x, renderFrameCtrlSize.y);
|
||||||
@ -653,7 +653,10 @@ void OBS::MainCaptureLoop()
|
|||||||
Texture *yuvRenderTexture = yuvRenderTextures[curRenderTarget];
|
Texture *yuvRenderTexture = yuvRenderTextures[curRenderTarget];
|
||||||
SetRenderTarget(yuvRenderTexture);
|
SetRenderTarget(yuvRenderTexture);
|
||||||
|
|
||||||
|
if(downscale < 2.01)
|
||||||
yuvScalePixelShader->SetVector2(hScaleVal, 1.0f/baseSize);
|
yuvScalePixelShader->SetVector2(hScaleVal, 1.0f/baseSize);
|
||||||
|
else if(downscale < 3.01)
|
||||||
|
yuvScalePixelShader->SetVector2(hScaleVal, 1.0f/(outputSize*3.0f));
|
||||||
|
|
||||||
Ortho(0.0f, outputSize.x, outputSize.y, 0.0f, -100.0f, 100.0f);
|
Ortho(0.0f, outputSize.x, outputSize.y, 0.0f, -100.0f, 100.0f);
|
||||||
SetViewport(0.0f, 0.0f, outputSize.x, outputSize.y);
|
SetViewport(0.0f, 0.0f, outputSize.x, outputSize.y);
|
||||||
@ -664,11 +667,11 @@ void OBS::MainCaptureLoop()
|
|||||||
if(bTransitioning)
|
if(bTransitioning)
|
||||||
{
|
{
|
||||||
BlendFunction(GS_BLEND_ONE, GS_BLEND_ZERO);
|
BlendFunction(GS_BLEND_ONE, GS_BLEND_ZERO);
|
||||||
DrawSpriteEx(transitionTexture, 0xFFFFFFFF, 0.0f, 0.0f, scaleSize.x, scaleSize.y, 0.0f, 0.0f, scaleSize.x, scaleSize.y);
|
DrawSpriteEx(transitionTexture, 0xFFFFFFFF, 0.0f, 0.0f, scaleSize.x, scaleSize.y, 0.0f, 0.0f, 1.0f, 1.0f);
|
||||||
BlendFunction(GS_BLEND_FACTOR, GS_BLEND_INVFACTOR, transitionAlpha);
|
BlendFunction(GS_BLEND_FACTOR, GS_BLEND_INVFACTOR, transitionAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawSpriteEx(mainRenderTextures[curRenderTarget], 0xFFFFFFFF, 0.0f, 0.0f, outputSize.x, outputSize.y, 0.0f, 0.0f, outputSize.x, outputSize.y);
|
DrawSpriteEx(mainRenderTextures[curRenderTarget], 0xFFFFFFFF, 0.0f, 0.0f, outputSize.x, outputSize.y, 0.0f, 0.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void RefreshDownscales(HWND hwnd, int cx, int cy);
|
void RefreshDownscales(HWND hwnd, int cx, int cy);
|
||||||
|
void RefreshFilters(HWND hwndParent, bool bGetConfig);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Interface
|
// Interface
|
||||||
|
@ -52,8 +52,8 @@ LRESULT WINAPI ResolutionEditSubclassProc(HWND hwnd, UINT message, WPARAM wParam
|
|||||||
return CallWindowProc((WNDPROC)editProc, hwnd, message, wParam, lParam);
|
return CallWindowProc((WNDPROC)editProc, hwnd, message, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int multiplierCount = 5;
|
const int multiplierCount = 9;
|
||||||
const float downscaleMultipliers[multiplierCount] = {1.0f, 1.5f, 2.0f, 2.25f, 3.0f};
|
const float downscaleMultipliers[multiplierCount] = {1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.25f, 2.5f, 2.75f, 3.0f};
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
// SettingsVideo class
|
// SettingsVideo class
|
||||||
@ -132,6 +132,41 @@ void SettingsVideo::RefreshDownscales(HWND hwnd, int cx, int cy)
|
|||||||
SendMessage(hwnd, CB_SETCURSEL, lastID, 0);
|
SendMessage(hwnd, CB_SETCURSEL, lastID, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsVideo::RefreshFilters(HWND hwndParent, bool bGetConfig)
|
||||||
|
{
|
||||||
|
HWND hwndFilter = GetDlgItem(hwndParent, IDC_FILTER);
|
||||||
|
HWND hwndDownscale = GetDlgItem(hwndParent, IDC_DOWNSCALE);
|
||||||
|
|
||||||
|
int curFilter;
|
||||||
|
if(bGetConfig)
|
||||||
|
curFilter = AppConfig->GetInt(TEXT("Video"), TEXT("Filter"), 0);
|
||||||
|
else
|
||||||
|
curFilter = (int)SendMessage(GetDlgItem(hwnd, IDC_FILTER), CB_GETCURSEL, 0, 0);
|
||||||
|
|
||||||
|
float downscale = 1.0f;
|
||||||
|
|
||||||
|
int curSel = (int)SendMessage(GetDlgItem(hwnd, IDC_DOWNSCALE), CB_GETCURSEL, 0, 0);
|
||||||
|
if(curSel != CB_ERR)
|
||||||
|
downscale = downscaleMultipliers[curSel];
|
||||||
|
|
||||||
|
SendMessage(hwndFilter, CB_RESETCONTENT, 0, 0);
|
||||||
|
if(downscale < 2.01)
|
||||||
|
{
|
||||||
|
SendMessage(hwndFilter, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Video.Filter.Bilinear"));
|
||||||
|
SendMessage(hwndFilter, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Video.Filter.Bicubic"));
|
||||||
|
SendMessage(hwndFilter, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Video.Filter.Lanczos"));
|
||||||
|
|
||||||
|
SendMessage(hwndFilter, CB_SETCURSEL, curFilter, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendMessage(hwndFilter, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Video.Filter.Bilinear"));
|
||||||
|
SendMessage(hwndFilter, CB_SETCURSEL, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableWindow(hwndFilter, (downscale > 1.01));
|
||||||
|
}
|
||||||
|
|
||||||
void SettingsVideo::ApplySettings()
|
void SettingsVideo::ApplySettings()
|
||||||
{
|
{
|
||||||
int curSel = (int)SendMessage(GetDlgItem(hwnd, IDC_MONITOR), CB_GETCURSEL, 0, 0);
|
int curSel = (int)SendMessage(GetDlgItem(hwnd, IDC_MONITOR), CB_GETCURSEL, 0, 0);
|
||||||
@ -157,6 +192,10 @@ void SettingsVideo::ApplySettings()
|
|||||||
if(curSel != CB_ERR)
|
if(curSel != CB_ERR)
|
||||||
AppConfig->SetFloat(TEXT("Video"), TEXT("Downscale"), downscaleMultipliers[curSel]);
|
AppConfig->SetFloat(TEXT("Video"), TEXT("Downscale"), downscaleMultipliers[curSel]);
|
||||||
|
|
||||||
|
curSel = (int)SendMessage(GetDlgItem(hwnd, IDC_FILTER), CB_GETCURSEL, 0, 0);
|
||||||
|
if(curSel == CB_ERR) curSel = 0;
|
||||||
|
AppConfig->SetInt(TEXT("Video"), TEXT("Filter"), curSel);
|
||||||
|
|
||||||
int gammaVal = (int)SendMessage(GetDlgItem(hwnd, IDC_GAMMA), TBM_GETPOS, 0, 0);
|
int gammaVal = (int)SendMessage(GetDlgItem(hwnd, IDC_GAMMA), TBM_GETPOS, 0, 0);
|
||||||
AppConfig->SetInt(TEXT("Video"), TEXT("Gamma"), gammaVal);
|
AppConfig->SetInt(TEXT("Video"), TEXT("Gamma"), gammaVal);
|
||||||
|
|
||||||
@ -297,6 +336,10 @@ INT_PTR SettingsVideo::ProcMessage(UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
|
|
||||||
//--------------------------------------------
|
//--------------------------------------------
|
||||||
|
|
||||||
|
RefreshFilters(hwnd, true);
|
||||||
|
|
||||||
|
//--------------------------------------------
|
||||||
|
|
||||||
ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_HIDE);
|
ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_HIDE);
|
||||||
SetChangedSettings(false);
|
SetChangedSettings(false);
|
||||||
|
|
||||||
@ -396,10 +439,18 @@ INT_PTR SettingsVideo::ProcMessage(UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
bDataChanged = true;
|
bDataChanged = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IDC_DOWNSCALE:
|
case IDC_FILTER:
|
||||||
if(HIWORD(wParam) == CBN_SELCHANGE)
|
if(HIWORD(wParam) == CBN_SELCHANGE)
|
||||||
bDataChanged = true;
|
bDataChanged = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case IDC_DOWNSCALE:
|
||||||
|
if(HIWORD(wParam) == CBN_SELCHANGE)
|
||||||
|
{
|
||||||
|
bDataChanged = true;
|
||||||
|
RefreshFilters(hwnd, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(bDataChanged)
|
if(bDataChanged)
|
||||||
|
@ -48,6 +48,8 @@
|
|||||||
#define IDC_BINDIP 1014
|
#define IDC_BINDIP 1014
|
||||||
#define IDC_MAXBITRATE 1015
|
#define IDC_MAXBITRATE 1015
|
||||||
#define IDC_COLORPICKER 1015
|
#define IDC_COLORPICKER 1015
|
||||||
|
#define IDC_DOWNSCALE2 1015
|
||||||
|
#define IDC_FILTER 1015
|
||||||
#define IDC_BUFFERSIZE 1016
|
#define IDC_BUFFERSIZE 1016
|
||||||
#define IDC_AUDIOCODEC 1017
|
#define IDC_AUDIOCODEC 1017
|
||||||
#define IDC_USECOLORKEY 1017
|
#define IDC_USECOLORKEY 1017
|
||||||
|
@ -196,10 +196,15 @@ Settings.Video.DisableAero "Disable Aero at startup:"
|
|||||||
Settings.Video.DisableAeroTooltip "Disabling Aero is recommended if using software monitor capture"
|
Settings.Video.DisableAeroTooltip "Disabling Aero is recommended if using software monitor capture"
|
||||||
Settings.Video.Downscale "Resolution Downscale:"
|
Settings.Video.Downscale "Resolution Downscale:"
|
||||||
Settings.Video.DownscaleTooltip "Downscale can improve video quality at the cost of resolution."
|
Settings.Video.DownscaleTooltip "Downscale can improve video quality at the cost of resolution."
|
||||||
|
Settings.Video.Filter "Filter:"
|
||||||
Settings.Video.FPS "FPS:"
|
Settings.Video.FPS "FPS:"
|
||||||
Settings.Video.Monitor "Monitor:"
|
Settings.Video.Monitor "Monitor:"
|
||||||
Settings.Video.Resolution "Base Resolution:"
|
Settings.Video.Resolution "Base Resolution:"
|
||||||
|
|
||||||
|
Settings.Video.Filter.Bilinear "Bilinear (fastest)"
|
||||||
|
Settings.Video.Filter.Bicubic "Bicubic sharper (good detail, 16 samples)"
|
||||||
|
Settings.Video.Filter.Lanczos "Lanczos (best detail, 36 samples)"
|
||||||
|
|
||||||
Sources.BitmapSource "Image"
|
Sources.BitmapSource "Image"
|
||||||
Sources.GameCaptureSource "Game Capture"
|
Sources.GameCaptureSource "Game Capture"
|
||||||
Sources.GlobalSource "Global Source"
|
Sources.GlobalSource "Global Source"
|
||||||
|
91
rundir/shaders/DownscaleBicubicYUV.pShader
Normal file
91
rundir/shaders/DownscaleBicubicYUV.pShader
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
uniform Texture2D diffuseTexture;
|
||||||
|
uniform float2 baseDimensionI;
|
||||||
|
|
||||||
|
SamplerState textureSampler
|
||||||
|
{
|
||||||
|
AddressU = Clamp;
|
||||||
|
AddressV = Clamp;
|
||||||
|
Filter = Linear;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertData
|
||||||
|
{
|
||||||
|
float4 pos : SV_Position;
|
||||||
|
float2 texCoord : TexCoord0;
|
||||||
|
};
|
||||||
|
|
||||||
|
float weight(float x)
|
||||||
|
{
|
||||||
|
float ax = abs(x);
|
||||||
|
// Sharper version.
|
||||||
|
// May look better in some cases.
|
||||||
|
const float B = 0.0;
|
||||||
|
const float C = 0.75;
|
||||||
|
|
||||||
|
if (ax < 1.0)
|
||||||
|
return (pow(x, 2.0) * ((12.0 - 9.0 * B - 6.0 * C) * ax + (-18.0 + 12.0 * B + 6.0 * C)) + (6.0 - 2.0 * B)) / 6.0;
|
||||||
|
else if ((ax >= 1.0) && (ax < 2.0))
|
||||||
|
return (pow(x, 2.0) * ((-B - 6.0 * C) * ax + (6.0 * B + 30.0 * C)) + (-12.0 * B - 48.0 * C) * ax + (8.0 * B + 24.0 * C)) / 6.0;
|
||||||
|
else
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 weight4(float x)
|
||||||
|
{
|
||||||
|
return float4(
|
||||||
|
weight(x - 2.0),
|
||||||
|
weight(x - 1.0),
|
||||||
|
weight(x),
|
||||||
|
weight(x + 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 pixel(float xpos, float ypos)
|
||||||
|
{
|
||||||
|
return diffuseTexture.Sample(textureSampler, float2(xpos, ypos)).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 get_line(float ypos, float4 xpos, float4 linetaps)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
pixel(xpos.r, ypos) * linetaps.r +
|
||||||
|
pixel(xpos.g, ypos) * linetaps.g +
|
||||||
|
pixel(xpos.b, ypos) * linetaps.b +
|
||||||
|
pixel(xpos.a, ypos) * linetaps.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 main(VertData input) : SV_Target
|
||||||
|
{
|
||||||
|
float2 stepxy = baseDimensionI;
|
||||||
|
float2 pos = input.texCoord + stepxy * 0.5;
|
||||||
|
float2 f = frac(pos / stepxy);
|
||||||
|
|
||||||
|
float4 linetaps = weight4(1.0 - f.x);
|
||||||
|
float4 columntaps = weight4(1.0 - f.y);
|
||||||
|
|
||||||
|
//make sure all taps added together is exactly 1.0, otherwise some (very small) distortion can occur
|
||||||
|
linetaps /= linetaps.r + linetaps.g + linetaps.b + linetaps.a;
|
||||||
|
columntaps /= columntaps.r + columntaps.g + columntaps.b + columntaps.a;
|
||||||
|
|
||||||
|
float2 xystart = (-1.5 - f) * stepxy + pos;
|
||||||
|
float4 xpos = float4(xystart.x, xystart.x + stepxy.x, xystart.x + stepxy.x * 2.0, xystart.x + stepxy.x * 3.0);
|
||||||
|
|
||||||
|
float4 rgba;
|
||||||
|
rgba.rgb =
|
||||||
|
get_line(xystart.y , xpos, linetaps) * columntaps.r +
|
||||||
|
get_line(xystart.y + stepxy.y , xpos, linetaps) * columntaps.g +
|
||||||
|
get_line(xystart.y + stepxy.y * 2.0, xpos, linetaps) * columntaps.b +
|
||||||
|
get_line(xystart.y + stepxy.y * 3.0, xpos, linetaps) * columntaps.a;
|
||||||
|
|
||||||
|
rgba.a = 1.0;
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
const float4x4 yuvMat = {0.257, -0.148, 0.439, 0.0,
|
||||||
|
0.504, -0.291, -0.368, 0.0,
|
||||||
|
0.098, 0.439, -0.071, 0.0,
|
||||||
|
0.0625, 0.50, 0.50, 1.0};
|
||||||
|
|
||||||
|
//a nice quick colorspace conversion
|
||||||
|
float4 yuvx = mul(float4(rgba.rgb, 1.0), yuvMat);
|
||||||
|
return float4(saturate(yuvx.zxy), rgba.a);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/********************************************************************************
|
/********************************************************************************
|
||||||
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
Copyright (C) 2013 Hugh Bailey <obs.jim@gmail.com>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -35,15 +35,11 @@ struct VertData
|
|||||||
|
|
||||||
float4 main(VertData input) : SV_Target
|
float4 main(VertData input) : SV_Target
|
||||||
{
|
{
|
||||||
float2 texAdjust = (input.texCoord-0.5)*2.0;
|
|
||||||
float2 floorVal = floor(texAdjust+0.001);
|
|
||||||
|
|
||||||
float4 rgba;
|
float4 rgba;
|
||||||
rgba = diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5f, 0.5f)) * baseDimensionI);
|
rgba.rgb = diffuseTexture.Sample(textureSampler, input.texCoord);
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5f, 0.5f)) * baseDimensionI);
|
rgba.a = 1.0;
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5f, 1.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5f, 1.5f)) * baseDimensionI);
|
//-------------------------------------------------------------
|
||||||
rgba *= 0.25;
|
|
||||||
|
|
||||||
const float4x4 yuvMat = {0.257, -0.148, 0.439, 0.0,
|
const float4x4 yuvMat = {0.257, -0.148, 0.439, 0.0,
|
||||||
0.504, -0.291, -0.368, 0.0,
|
0.504, -0.291, -0.368, 0.0,
|
@ -1,5 +1,5 @@
|
|||||||
/********************************************************************************
|
/********************************************************************************
|
||||||
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
Copyright (C) 2013 Hugh Bailey <obs.jim@gmail.com>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -17,8 +17,6 @@
|
|||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
//1.5 downscale requires kind of a lot of sampling, more sampling than there are pixels on the base texture
|
|
||||||
|
|
||||||
uniform Texture2D diffuseTexture;
|
uniform Texture2D diffuseTexture;
|
||||||
uniform float2 baseDimensionI;
|
uniform float2 baseDimensionI;
|
||||||
|
|
||||||
@ -37,28 +35,24 @@ struct VertData
|
|||||||
|
|
||||||
float4 main(VertData input) : SV_Target
|
float4 main(VertData input) : SV_Target
|
||||||
{
|
{
|
||||||
float2 texAdjust = (input.texCoord-0.5)*1.5;
|
float2 texCoord = input.texCoord;
|
||||||
float2 floorVal = floor(texAdjust+0.001);
|
|
||||||
float2 offset = (texAdjust-floorVal);
|
|
||||||
|
|
||||||
float2x2 pixelMat = {1.0, 1.0, 1.0, 1.0};
|
|
||||||
|
|
||||||
if(offset.x > 0.4)
|
|
||||||
pixelMat._11_21 = 0.5;
|
|
||||||
else
|
|
||||||
pixelMat._12_22 = 0.5;
|
|
||||||
|
|
||||||
if(offset.y > 0.4)
|
|
||||||
pixelMat._11_12 *= 0.5;
|
|
||||||
else
|
|
||||||
pixelMat._21_22 *= 0.5;
|
|
||||||
|
|
||||||
|
float2 adjust = baseDimensionI;
|
||||||
float4 rgba;
|
float4 rgba;
|
||||||
rgba = diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5, 0.5)) * baseDimensionI) * pixelMat._11;
|
rgba.rgb = diffuseTexture.Sample(textureSampler, texCoord).rgb;
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5, 0.5)) * baseDimensionI) * pixelMat._12;
|
rgba.rgb += diffuseTexture.Sample(textureSampler, texCoord + float2(-adjust.x, -adjust.y)).rgb;
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5, 1.5)) * baseDimensionI) * pixelMat._21;
|
rgba.rgb += diffuseTexture.Sample(textureSampler, texCoord + float2(-adjust.x, 0.0)).rgb;
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5, 1.5)) * baseDimensionI) * pixelMat._22;
|
rgba.rgb += diffuseTexture.Sample(textureSampler, texCoord + float2(-adjust.x, adjust.y)).rgb;
|
||||||
rgba /= 2.25;
|
rgba.rgb += diffuseTexture.Sample(textureSampler, texCoord + float2( 0.0, -adjust.y)).rgb;
|
||||||
|
rgba.rgb += diffuseTexture.Sample(textureSampler, texCoord + float2( 0.0, adjust.y)).rgb;
|
||||||
|
rgba.rgb += diffuseTexture.Sample(textureSampler, texCoord + float2( adjust.x, -adjust.y)).rgb;
|
||||||
|
rgba.rgb += diffuseTexture.Sample(textureSampler, texCoord + float2( adjust.x, 0.0)).rgb;
|
||||||
|
rgba.rgb += diffuseTexture.Sample(textureSampler, texCoord + float2( adjust.x, adjust.y)).rgb;
|
||||||
|
rgba.rgb /= 9.0;
|
||||||
|
|
||||||
|
rgba.a = 1.0;
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
const float4x4 yuvMat = {0.257, -0.148, 0.439, 0.0,
|
const float4x4 yuvMat = {0.257, -0.148, 0.439, 0.0,
|
||||||
0.504, -0.291, -0.368, 0.0,
|
0.504, -0.291, -0.368, 0.0,
|
102
rundir/shaders/DownscaleLanczos6tapYUV.pShader
Normal file
102
rundir/shaders/DownscaleLanczos6tapYUV.pShader
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
uniform Texture2D diffuseTexture;
|
||||||
|
uniform float2 baseDimensionI;
|
||||||
|
|
||||||
|
SamplerState textureSampler
|
||||||
|
{
|
||||||
|
AddressU = Clamp;
|
||||||
|
AddressV = Clamp;
|
||||||
|
Filter = Linear;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertData
|
||||||
|
{
|
||||||
|
float4 pos : SV_Position;
|
||||||
|
float2 texCoord : TexCoord0;
|
||||||
|
};
|
||||||
|
|
||||||
|
float sinc(float x)
|
||||||
|
{
|
||||||
|
const float PIval = 3.1415926535897932384626433832795;
|
||||||
|
return sin(x * PIval) / (x * PIval);
|
||||||
|
}
|
||||||
|
|
||||||
|
float weight(float x, float radius)
|
||||||
|
{
|
||||||
|
float ax = abs(x);
|
||||||
|
if (x == 0.0)
|
||||||
|
return 1.0;
|
||||||
|
else if (ax < radius)
|
||||||
|
return sinc(x) * sinc(x / radius);
|
||||||
|
else
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 weight3(float x)
|
||||||
|
{
|
||||||
|
return float3(
|
||||||
|
weight(x * 2.0 + 0.0 * 2.0 - 3.0, 3.0),
|
||||||
|
weight(x * 2.0 + 1.0 * 2.0 - 3.0, 3.0),
|
||||||
|
weight(x * 2.0 + 2.0 * 2.0 - 3.0, 3.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 pixel(float xpos, float ypos)
|
||||||
|
{
|
||||||
|
return diffuseTexture.Sample(textureSampler, float2(xpos, ypos)).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 get_line(float ypos, float3 xpos1, float3 xpos2, float3 linetaps1, float3 linetaps2)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
pixel(xpos1.r, ypos) * linetaps1.r +
|
||||||
|
pixel(xpos1.g, ypos) * linetaps2.r +
|
||||||
|
pixel(xpos1.b, ypos) * linetaps1.g +
|
||||||
|
pixel(xpos2.r, ypos) * linetaps2.g +
|
||||||
|
pixel(xpos2.g, ypos) * linetaps1.b +
|
||||||
|
pixel(xpos2.b, ypos) * linetaps2.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 main(VertData input) : SV_Target
|
||||||
|
{
|
||||||
|
float2 stepxy = baseDimensionI;
|
||||||
|
float2 pos = input.texCoord + stepxy * 0.5;
|
||||||
|
float2 f = frac(pos / stepxy);
|
||||||
|
|
||||||
|
float3 linetaps1 = weight3((1.0 - f.x) / 2.0);
|
||||||
|
float3 linetaps2 = weight3((1.0 - f.x) / 2.0 + 0.5);
|
||||||
|
float3 columntaps1 = weight3((1.0 - f.y) / 2.0);
|
||||||
|
float3 columntaps2 = weight3((1.0 - f.y) / 2.0 + 0.5);
|
||||||
|
|
||||||
|
//make sure all taps added together is exactly 1.0, otherwise some (very small) distortion can occur
|
||||||
|
float suml = linetaps1.r + linetaps1.g + linetaps1.b + linetaps2.r + linetaps2.g + linetaps2.b;
|
||||||
|
float sumc = columntaps1.r + columntaps1.g + columntaps1.b + columntaps2.r + columntaps2.g + columntaps2.b;
|
||||||
|
linetaps1 /= suml;
|
||||||
|
linetaps2 /= suml;
|
||||||
|
columntaps1 /= sumc;
|
||||||
|
columntaps2 /= sumc;
|
||||||
|
|
||||||
|
float2 xystart = (-2.5 - f) * stepxy + pos;
|
||||||
|
float3 xpos1 = float3(xystart.x, xystart.x + stepxy.x, xystart.x + stepxy.x * 2.0);
|
||||||
|
float3 xpos2 = float3(xystart.x + stepxy.x * 3.0, xystart.x + stepxy.x * 4.0, xystart.x + stepxy.x * 5.0);
|
||||||
|
|
||||||
|
float4 rgba;
|
||||||
|
rgba.rgb =
|
||||||
|
get_line(xystart.y , xpos1, xpos2, linetaps1, linetaps2) * columntaps1.r +
|
||||||
|
get_line(xystart.y + stepxy.y , xpos1, xpos2, linetaps1, linetaps2) * columntaps2.r +
|
||||||
|
get_line(xystart.y + stepxy.y * 2.0, xpos1, xpos2, linetaps1, linetaps2) * columntaps1.g +
|
||||||
|
get_line(xystart.y + stepxy.y * 3.0, xpos1, xpos2, linetaps1, linetaps2) * columntaps2.g +
|
||||||
|
get_line(xystart.y + stepxy.y * 4.0, xpos1, xpos2, linetaps1, linetaps2) * columntaps1.b +
|
||||||
|
get_line(xystart.y + stepxy.y * 5.0, xpos1, xpos2, linetaps1, linetaps2) * columntaps2.b;
|
||||||
|
|
||||||
|
rgba.a = 1.0;
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
const float4x4 yuvMat = {0.257, -0.148, 0.439, 0.0,
|
||||||
|
0.504, -0.291, -0.368, 0.0,
|
||||||
|
0.098, 0.439, -0.071, 0.0,
|
||||||
|
0.0625, 0.50, 0.50, 1.0};
|
||||||
|
|
||||||
|
//a nice quick colorspace conversion
|
||||||
|
float4 yuvx = mul(float4(rgba.rgb, 1.0), yuvMat);
|
||||||
|
return float4(saturate(yuvx.zxy), rgba.a);
|
||||||
|
}
|
@ -1,102 +0,0 @@
|
|||||||
/********************************************************************************
|
|
||||||
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
|
||||||
********************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
//2.25 is also pretty bad
|
|
||||||
|
|
||||||
uniform Texture2D diffuseTexture;
|
|
||||||
uniform float2 baseDimensionI;
|
|
||||||
|
|
||||||
SamplerState textureSampler
|
|
||||||
{
|
|
||||||
AddressU = Clamp;
|
|
||||||
AddressV = Clamp;
|
|
||||||
Filter = Linear;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertData
|
|
||||||
{
|
|
||||||
float4 pos : SV_Position;
|
|
||||||
float2 texCoord : TexCoord0;
|
|
||||||
};
|
|
||||||
|
|
||||||
float4 main(VertData input) : SV_Target
|
|
||||||
{
|
|
||||||
float2 texAdjust = (input.texCoord-0.5)*2.25;
|
|
||||||
float2 floorVal = floor(texAdjust+0.001);
|
|
||||||
float2 offset = (texAdjust-floorVal);
|
|
||||||
|
|
||||||
float3x3 pixelMat =
|
|
||||||
{
|
|
||||||
1.0, 1.0, 1.0,
|
|
||||||
1.0, 1.0, 1.0,
|
|
||||||
1.0, 1.0, 1.0
|
|
||||||
};
|
|
||||||
|
|
||||||
if(offset.x < 0.25)
|
|
||||||
pixelMat._13_23_33 = 0.25;
|
|
||||||
else if(offset.x < 0.5) //0.25
|
|
||||||
{
|
|
||||||
pixelMat._11_21_31 = 0.75;
|
|
||||||
pixelMat._13_23_33 = 0.5;
|
|
||||||
}
|
|
||||||
else if(offset.x < 0.75) //0.5
|
|
||||||
{
|
|
||||||
pixelMat._11_21_31 = 0.5;
|
|
||||||
pixelMat._13_23_33 = 0.75;
|
|
||||||
}
|
|
||||||
else if(offset.x < 1.0) //0.75
|
|
||||||
pixelMat._11_21_31 = 0.25;
|
|
||||||
|
|
||||||
|
|
||||||
if(offset.y < 0.25)
|
|
||||||
pixelMat._31_32_33 *= 0.25;
|
|
||||||
else if(offset.y < 0.5) //0.25
|
|
||||||
{
|
|
||||||
pixelMat._11_12_13 *= 0.75;
|
|
||||||
pixelMat._31_32_33 *= 0.5;
|
|
||||||
}
|
|
||||||
else if(offset.y < 0.75) //0.5
|
|
||||||
{
|
|
||||||
pixelMat._11_12_13 *= 0.5;
|
|
||||||
pixelMat._31_32_33 *= 0.75;
|
|
||||||
}
|
|
||||||
else if(offset.y < 1.0) //0.75
|
|
||||||
pixelMat._11_12_13 *= 0.25;
|
|
||||||
|
|
||||||
float4 rgba;
|
|
||||||
rgba = diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5, 0.5)) * baseDimensionI) * pixelMat._11;
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5, 0.5)) * baseDimensionI) * pixelMat._12;
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(2.5, 0.5)) * baseDimensionI) * pixelMat._13;
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5, 1.5)) * baseDimensionI) * pixelMat._21;
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5, 1.5)) * baseDimensionI) * pixelMat._22;
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(2.5, 1.5)) * baseDimensionI) * pixelMat._23;
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5, 2.5)) * baseDimensionI) * pixelMat._31;
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5, 2.5)) * baseDimensionI) * pixelMat._32;
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(2.5, 2.5)) * baseDimensionI) * pixelMat._33;
|
|
||||||
rgba /= 5.0625;
|
|
||||||
|
|
||||||
const float4x4 yuvMat = {0.257, -0.148, 0.439, 0.0,
|
|
||||||
0.504, -0.291, -0.368, 0.0,
|
|
||||||
0.098, 0.439, -0.071, 0.0,
|
|
||||||
0.0625, 0.50, 0.50, 1.0};
|
|
||||||
|
|
||||||
//a nice quick colorspace conversion
|
|
||||||
float4 yuvx = mul(float4(rgba.rgb, 1.0), yuvMat);
|
|
||||||
return float4(saturate(yuvx.zxy), rgba.a);
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
/********************************************************************************
|
|
||||||
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
|
||||||
********************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
uniform Texture2D diffuseTexture;
|
|
||||||
uniform float2 baseDimensionI;
|
|
||||||
|
|
||||||
SamplerState textureSampler
|
|
||||||
{
|
|
||||||
AddressU = Clamp;
|
|
||||||
AddressV = Clamp;
|
|
||||||
Filter = Linear;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VertData
|
|
||||||
{
|
|
||||||
float4 pos : SV_Position;
|
|
||||||
float2 texCoord : TexCoord0;
|
|
||||||
};
|
|
||||||
|
|
||||||
float4 main(VertData input) : SV_Target
|
|
||||||
{
|
|
||||||
float2 texAdjust = (input.texCoord-0.5)*3.0;
|
|
||||||
float2 floorVal = floor(texAdjust+0.001);
|
|
||||||
|
|
||||||
float4 rgba;
|
|
||||||
rgba = diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5f, 0.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5f, 0.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(2.5f, 0.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5f, 1.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5f, 1.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(2.5f, 1.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(0.5f, 2.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(1.5f, 2.5f)) * baseDimensionI);
|
|
||||||
rgba += diffuseTexture.Sample(textureSampler, (floorVal+float2(2.5f, 2.5f)) * baseDimensionI);
|
|
||||||
rgba /= 9.0;
|
|
||||||
|
|
||||||
const float4x4 yuvMat = {0.257, -0.148, 0.439, 0.0,
|
|
||||||
0.504, -0.291, -0.368, 0.0,
|
|
||||||
0.098, 0.439, -0.071, 0.0,
|
|
||||||
0.0625, 0.50, 0.50, 1.0};
|
|
||||||
|
|
||||||
//a nice quick colorspace conversion
|
|
||||||
float4 yuvx = mul(float4(rgba.rgb, 1.0), yuvMat);
|
|
||||||
return float4(saturate(yuvx.zxy), rgba.a);
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user