@@ -15,9 +15,13 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FLAX_REVERSE_Z
|
#if FLAX_REVERSE_Z
|
||||||
#define GPU_DEPTH_CLEAR_VALUE 0.0f
|
#define GPU_DEPTH_MIN_VALUE 1.0f
|
||||||
|
#define GPU_DEPTH_MAX_VALUE 0.0f
|
||||||
|
#define GPU_DEPTH_BOUNDS_SWAP(min, max) max, min
|
||||||
#else
|
#else
|
||||||
#define GPU_DEPTH_CLEAR_VALUE 1.0f
|
#define GPU_DEPTH_MIN_VALUE 0.0f
|
||||||
|
#define GPU_DEPTH_MAX_VALUE 1.0f
|
||||||
|
#define GPU_DEPTH_BOUNDS_SWAP(min, max) min, max
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class GPUConstantBuffer;
|
class GPUConstantBuffer;
|
||||||
@@ -214,7 +218,7 @@ public:
|
|||||||
/// <param name="depthBuffer">The depth buffer to clear.</param>
|
/// <param name="depthBuffer">The depth buffer to clear.</param>
|
||||||
/// <param name="depthValue">The clear depth value.</param>
|
/// <param name="depthValue">The clear depth value.</param>
|
||||||
/// <param name="stencilValue">The clear stencil value.</param>
|
/// <param name="stencilValue">The clear stencil value.</param>
|
||||||
API_FUNCTION() virtual void ClearDepth(GPUTextureView* depthBuffer, float depthValue = GPU_DEPTH_CLEAR_VALUE, uint8 stencilValue = 0) = 0;
|
API_FUNCTION() virtual void ClearDepth(GPUTextureView* depthBuffer, float depthValue = GPU_DEPTH_MAX_VALUE, uint8 stencilValue = 0) = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears an unordered access buffer with a float value.
|
/// Clears an unordered access buffer with a float value.
|
||||||
@@ -634,9 +638,10 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the minimum and maximum depth values for depth bounds test.
|
/// Sets the minimum and maximum depth values for depth bounds test.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>Both values must be between 0.0 and 1.0, MinDepth must be less than or equal to MaxDepth.</remarks>
|
||||||
/// <param name="minDepth">The minimum value for depth bound test.</param>
|
/// <param name="minDepth">The minimum value for depth bound test.</param>
|
||||||
/// <param name="maxDepth">The maximum value for depth bound test.</param>
|
/// <param name="maxDepth">The maximum value for depth bound test.</param>
|
||||||
API_FUNCTION() virtual void SetDepthBounds(float minDepth, float maxDepth) = 0;
|
API_FUNCTION() virtual void SetDepthBounds(float minDepth = 0.0f, float maxDepth = 1.0f) = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -684,6 +684,20 @@ float RenderTools::TemporalHalton(int32 index, int32 base)
|
|||||||
|
|
||||||
Float2 RenderTools::GetDepthBounds(const RenderView& view, const Float3& nearPoint, const Float3& farPoint)
|
Float2 RenderTools::GetDepthBounds(const RenderView& view, const Float3& nearPoint, const Float3& farPoint)
|
||||||
{
|
{
|
||||||
|
#if FLAX_REVERSE_Z
|
||||||
|
Float3 viewNearPoint = Float3::Transform(nearPoint, view.View);
|
||||||
|
Float3 viewFarPoint = Float3::Transform(farPoint, view.View);
|
||||||
|
|
||||||
|
viewNearPoint.Z = Math::Max(viewNearPoint.Z, view.Near);
|
||||||
|
viewFarPoint.Z = Math::Min(viewFarPoint.Z, view.Far);
|
||||||
|
|
||||||
|
Float4 clipNearPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, viewNearPoint.Z, 1));
|
||||||
|
Float4 clipFarPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, viewFarPoint.Z, 1));
|
||||||
|
clipNearPoint /= clipNearPoint.W;
|
||||||
|
clipFarPoint /= clipFarPoint.W;
|
||||||
|
|
||||||
|
return Float2(clipNearPoint.Z, clipFarPoint.Z);
|
||||||
|
#else
|
||||||
// Point closest the view
|
// Point closest the view
|
||||||
const Float4 nearPointClip = Matrix::TransformPosition(view.ViewProjection(), Float4(nearPoint, 1.0));
|
const Float4 nearPointClip = Matrix::TransformPosition(view.ViewProjection(), Float4(nearPoint, 1.0));
|
||||||
float nearDepth = nearPointClip.Z / nearPointClip.W;
|
float nearDepth = nearPointClip.Z / nearPointClip.W;
|
||||||
@@ -700,8 +714,8 @@ Float2 RenderTools::GetDepthBounds(const RenderView& view, const Float3& nearPoi
|
|||||||
nearDepth = Math::Clamp(nearDepth, 0.0f, 1.0f);
|
nearDepth = Math::Clamp(nearDepth, 0.0f, 1.0f);
|
||||||
farDepth = Math::Clamp(farDepth, nearDepth, 1.0f);
|
farDepth = Math::Clamp(farDepth, nearDepth, 1.0f);
|
||||||
|
|
||||||
// TODO: swap depths when using reversed depth buffer
|
|
||||||
return Float2(nearDepth, farDepth);
|
return Float2(nearDepth, farDepth);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Float2 RenderTools::GetDepthBounds(const RenderView& view, const BoundingSphere& bounds)
|
Float2 RenderTools::GetDepthBounds(const RenderView& view, const BoundingSphere& bounds)
|
||||||
@@ -713,6 +727,24 @@ Float2 RenderTools::GetDepthBounds(const RenderView& view, const BoundingSphere&
|
|||||||
|
|
||||||
Float2 RenderTools::GetDepthBounds(const RenderView& view, const Span<Float3>& points)
|
Float2 RenderTools::GetDepthBounds(const RenderView& view, const Span<Float3>& points)
|
||||||
{
|
{
|
||||||
|
#if FLAX_REVERSE_Z
|
||||||
|
// Find min and max view depth range for list of points
|
||||||
|
float nearDepth = view.Far, farDepth = view.Near;
|
||||||
|
for (int32 i = 0; i < points.Length(); i++)
|
||||||
|
{
|
||||||
|
const Float3 viewPoint = Float3::Transform(points[i], view.View);
|
||||||
|
nearDepth = Math::Min(nearDepth, viewPoint.Z);
|
||||||
|
farDepth = Math::Max(farDepth, viewPoint.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project end points to clip space
|
||||||
|
Float4 clipNearPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, nearDepth, 1));
|
||||||
|
Float4 clipFarPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, farDepth, 1));
|
||||||
|
clipNearPoint /= clipNearPoint.W;
|
||||||
|
clipFarPoint /= clipFarPoint.W;
|
||||||
|
|
||||||
|
return Float2(clipNearPoint.Z, clipFarPoint.Z);
|
||||||
|
#else
|
||||||
// Find min and max depth range for list of points
|
// Find min and max depth range for list of points
|
||||||
float nearDepth = 1.0f, farDepth = 0.0f;
|
float nearDepth = 1.0f, farDepth = 0.0f;
|
||||||
for (int32 i = 0; i < points.Length(); i++)
|
for (int32 i = 0; i < points.Length(); i++)
|
||||||
@@ -729,8 +761,8 @@ Float2 RenderTools::GetDepthBounds(const RenderView& view, const Span<Float3>& p
|
|||||||
nearDepth = Math::Clamp(nearDepth, 0.0f, 1.0f);
|
nearDepth = Math::Clamp(nearDepth, 0.0f, 1.0f);
|
||||||
farDepth = Math::Clamp(farDepth, nearDepth, 1.0f);
|
farDepth = Math::Clamp(farDepth, nearDepth, 1.0f);
|
||||||
|
|
||||||
// TODO: swap depths when using reversed depth buffer
|
|
||||||
return Float2(nearDepth, farDepth);
|
return Float2(nearDepth, farDepth);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Float2 RenderTools::GetDepthBounds(const RenderView& view, const BoundingBox& bounds)
|
Float2 RenderTools::GetDepthBounds(const RenderView& view, const BoundingBox& bounds)
|
||||||
@@ -749,11 +781,21 @@ Float2 RenderTools::GetDepthBounds(const RenderView& view, const OrientedBoundin
|
|||||||
|
|
||||||
float RenderTools::GetDepthBounds(const RenderView& view, const Float3& point, bool near)
|
float RenderTools::GetDepthBounds(const RenderView& view, const Float3& point, bool near)
|
||||||
{
|
{
|
||||||
|
#if FLAX_REVERSE_Z
|
||||||
|
Float3 viewPoint = Float3::Transform(point, view.View);
|
||||||
|
viewPoint.Z = Math::Clamp(viewPoint.Z, view.Near, view.Far);
|
||||||
|
|
||||||
|
Float4 clipPoint = Matrix::TransformPosition(view.Projection, Float4(0, 0, viewPoint.Z, 1));
|
||||||
|
clipPoint /= clipPoint.W;
|
||||||
|
|
||||||
|
return clipPoint.Z;
|
||||||
|
#else
|
||||||
const Float4 pointClip = Matrix::TransformPosition(view.ViewProjection(), Float4(point, 1.0));
|
const Float4 pointClip = Matrix::TransformPosition(view.ViewProjection(), Float4(point, 1.0));
|
||||||
float depth = pointClip.Z / pointClip.W;
|
float depth = pointClip.Z / pointClip.W;
|
||||||
if (depth >= 1.0f)
|
if (depth >= 1.0f)
|
||||||
depth = near ? 0.0f : 1.0f; // Point is behind the view
|
depth = near ? 0.0f : 1.0f; // Point is behind the view
|
||||||
return Math::Clamp(depth, 0.0f, 1.0f);
|
return Math::Clamp(depth, 0.0f, 1.0f);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
float RenderTools::GetDepthBounds(const RenderView& view, float viewDistance, bool near)
|
float RenderTools::GetDepthBounds(const RenderView& view, float viewDistance, bool near)
|
||||||
|
|||||||
@@ -173,7 +173,12 @@ public:
|
|||||||
static Float2 GetDepthBounds(const RenderView& view, const OrientedBoundingBox& bounds);
|
static Float2 GetDepthBounds(const RenderView& view, const OrientedBoundingBox& bounds);
|
||||||
static float GetDepthBounds(const RenderView& view, const Float3& point, bool near);
|
static float GetDepthBounds(const RenderView& view, const Float3& point, bool near);
|
||||||
static float GetDepthBounds(const RenderView& view, float viewDistance, bool near);
|
static float GetDepthBounds(const RenderView& view, float viewDistance, bool near);
|
||||||
static constexpr float DepthBoundMaxBackground = 1.0f - 0.0000001f; // Skip background/sky pixels from shading
|
// Skip background/sky pixels from shading
|
||||||
|
#if FLAX_REVERSE_Z
|
||||||
|
static constexpr float DepthBoundMaxBackground = 0.0000001f;
|
||||||
|
#else
|
||||||
|
static constexpr float DepthBoundMaxBackground = 1.0f - 0.0000001f;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Calculates error for a given render target format to reduce floating-point precision artifacts via QuantizeColor (from Noise.hlsl).
|
// Calculates error for a given render target format to reduce floating-point precision artifacts via QuantizeColor (from Noise.hlsl).
|
||||||
static Float3 GetColorQuantizationError(PixelFormat format);
|
static Float3 GetColorQuantizationError(PixelFormat format);
|
||||||
|
|||||||
@@ -796,9 +796,8 @@ void GPUContextVulkan::OnDrawCall()
|
|||||||
if (_psDirtyFlag && pipelineState && (_rtDepth || _rtCount))
|
if (_psDirtyFlag && pipelineState && (_rtDepth || _rtCount))
|
||||||
{
|
{
|
||||||
_psDirtyFlag = false;
|
_psDirtyFlag = false;
|
||||||
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer()->GetHandle();
|
|
||||||
const auto pipeline = pipelineState->GetState(_renderPass, _vertexLayout);
|
const auto pipeline = pipelineState->GetState(_renderPass, _vertexLayout);
|
||||||
vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
vkCmdBindPipeline(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
RENDER_STAT_PS_STATE_CHANGE();
|
RENDER_STAT_PS_STATE_CHANGE();
|
||||||
if (_depthBoundsEnable && (!_currentState || !_currentState->DepthBoundsEnable))
|
if (_depthBoundsEnable && (!_currentState || !_currentState->DepthBoundsEnable))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply
|
// Apply
|
||||||
context->SetDepthBounds(0.0f, RenderTools::GetDepthBounds(renderContext.View, aoSettings.FadeOutDistance, false));
|
context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(GPU_DEPTH_MIN_VALUE, RenderTools::GetDepthBounds(renderContext.View, aoSettings.FadeOutDistance, false)));
|
||||||
context->BindSR(SSAO_TEXTURE_SLOT0, m_finalResults->ViewArray());
|
context->BindSR(SSAO_TEXTURE_SLOT0, m_finalResults->ViewArray());
|
||||||
context->SetViewportAndScissors((float)m_sizeX, (float)m_sizeY);
|
context->SetViewportAndScissors((float)m_sizeX, (float)m_sizeY);
|
||||||
context->SetState(settings.SkipHalfPixels ? _psApplyHalf : _psApply);
|
context->SetState(settings.SkipHalfPixels ? _psApplyHalf : _psApply);
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV
|
|||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
{
|
{
|
||||||
Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius));
|
Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius));
|
||||||
context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y);
|
context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y));
|
||||||
}
|
}
|
||||||
context->UpdateCB(cb0, &perLight);
|
context->UpdateCB(cb0, &perLight);
|
||||||
context->BindCB(0, cb0);
|
context->BindCB(0, cb0);
|
||||||
@@ -331,7 +331,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV
|
|||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
{
|
{
|
||||||
Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius));
|
Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius));
|
||||||
context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y);
|
context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y));
|
||||||
}
|
}
|
||||||
context->UpdateCB(cb0, &perLight);
|
context->UpdateCB(cb0, &perLight);
|
||||||
context->BindCB(0, cb0);
|
context->BindCB(0, cb0);
|
||||||
@@ -365,7 +365,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV
|
|||||||
|
|
||||||
// Calculate lighting
|
// Calculate lighting
|
||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
context->SetDepthBounds(0.0f, RenderTools::DepthBoundMaxBackground);
|
context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(GPU_DEPTH_MIN_VALUE, RenderTools::DepthBoundMaxBackground));
|
||||||
context->UpdateCB(cb0, &perLight);
|
context->UpdateCB(cb0, &perLight);
|
||||||
context->BindCB(0, cb0);
|
context->BindCB(0, cb0);
|
||||||
context->BindCB(1, cb1);
|
context->BindCB(1, cb1);
|
||||||
@@ -398,7 +398,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV
|
|||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
{
|
{
|
||||||
Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius));
|
Float2 minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, light.Radius));
|
||||||
context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y);
|
context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y));
|
||||||
}
|
}
|
||||||
context->UpdateCB(cb0, &perLight);
|
context->UpdateCB(cb0, &perLight);
|
||||||
context->BindCB(0, cb0);
|
context->BindCB(0, cb0);
|
||||||
@@ -411,7 +411,7 @@ void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureV
|
|||||||
|
|
||||||
// Restore state
|
// Restore state
|
||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
context->SetDepthBounds(0, 1);
|
context->SetDepthBounds();
|
||||||
context->ResetRenderTarget();
|
context->ResetRenderTarget();
|
||||||
context->ResetSR();
|
context->ResetSR();
|
||||||
context->ResetCB();
|
context->ResetCB();
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
|
|||||||
|
|
||||||
// Setup depth bounds (if device supports it)
|
// Setup depth bounds (if device supports it)
|
||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y);
|
context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y));
|
||||||
|
|
||||||
// Pack probe properties buffer
|
// Pack probe properties buffer
|
||||||
probe.SetShaderData(data.PData);
|
probe.SetShaderData(data.PData);
|
||||||
@@ -368,7 +368,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
|
|||||||
context->UnBindSR(4);
|
context->UnBindSR(4);
|
||||||
context->ResetRenderTarget();
|
context->ResetRenderTarget();
|
||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
context->SetDepthBounds(0, 1);
|
context->SetDepthBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screen Space Reflections pass
|
// Screen Space Reflections pass
|
||||||
@@ -417,7 +417,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
|
|||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
{
|
{
|
||||||
context->SetRenderTarget(depthBufferRTV, lightBuffer);
|
context->SetRenderTarget(depthBufferRTV, lightBuffer);
|
||||||
context->SetDepthBounds(0, RenderTools::DepthBoundMaxBackground);
|
context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(GPU_DEPTH_MIN_VALUE, RenderTools::DepthBoundMaxBackground));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
context->SetRenderTarget(lightBuffer);
|
context->SetRenderTarget(lightBuffer);
|
||||||
@@ -430,7 +430,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
|
|||||||
context->SetState(_psCombinePass);
|
context->SetState(_psCombinePass);
|
||||||
context->DrawFullscreenTriangle();
|
context->DrawFullscreenTriangle();
|
||||||
if (_depthBounds)
|
if (_depthBounds)
|
||||||
context->SetDepthBounds(0, 1);
|
context->SetDepthBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderTargetPool::Release(ssrBuffer);
|
RenderTargetPool::Release(ssrBuffer);
|
||||||
|
|||||||
@@ -1147,11 +1147,7 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
|
|||||||
void ShadowsPass::ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData) const
|
void ShadowsPass::ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData) const
|
||||||
{
|
{
|
||||||
// Color.r is used by PS_DepthClear in Quad shader to clear depth
|
// Color.r is used by PS_DepthClear in Quad shader to clear depth
|
||||||
#if FLAX_REVERSE_Z
|
quadShaderData.Color = GPU_DEPTH_MAX_VALUE;
|
||||||
quadShaderData.Color = Float4::Zero;
|
|
||||||
#else
|
|
||||||
quadShaderData.Color = Float4::One;
|
|
||||||
#endif
|
|
||||||
context->UpdateCB(quadShaderCB, &quadShaderData);
|
context->UpdateCB(quadShaderCB, &quadShaderData);
|
||||||
context->BindCB(0, quadShaderCB);
|
context->BindCB(0, quadShaderCB);
|
||||||
|
|
||||||
@@ -1753,8 +1749,8 @@ void ShadowsPass::RenderShadowMask(RenderContextBatch& renderContextBatch, Rende
|
|||||||
if (light.IsPointLight || light.IsSpotLight)
|
if (light.IsPointLight || light.IsSpotLight)
|
||||||
minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, ((RenderLocalLightData&)light).Radius));
|
minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(light.Position, ((RenderLocalLightData&)light).Radius));
|
||||||
else //if (light.IsDirectionalLight)
|
else //if (light.IsDirectionalLight)
|
||||||
minMaxDepth = Float2(0.0f, RenderTools::DepthBoundMaxBackground);
|
minMaxDepth = Float2(GPU_DEPTH_MIN_VALUE, RenderTools::DepthBoundMaxBackground);
|
||||||
context->SetDepthBounds(minMaxDepth.X, minMaxDepth.Y);
|
context->SetDepthBounds(GPU_DEPTH_BOUNDS_SWAP(minMaxDepth.X, minMaxDepth.Y));
|
||||||
}
|
}
|
||||||
if (light.IsPointLight)
|
if (light.IsPointLight)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user