mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-10-25 10:20:52 +00:00
D3D1x/WebGPU: Survive device loss in the readback paths. (#4001)
This commit is contained in:
+71
-31
@@ -1909,8 +1909,17 @@ namespace bgfx { namespace d3d11
|
||||
|
||||
m_deviceCtx->CopySubresourceRegion(texture.m_staging, subresource, 0, 0, 0, texture.m_ptr, subresource, NULL);
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
DX_CHECK(m_deviceCtx->Map(texture.m_staging, subresource, D3D11_MAP_READ, 0, &mapped) );
|
||||
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
||||
const HRESULT hr = m_deviceCtx->Map(texture.m_staging, subresource, D3D11_MAP_READ, 0, &mapped);
|
||||
|
||||
if (FAILED(hr) )
|
||||
{
|
||||
BX_TRACE("readTexture: Map failed 0x%08x, device removed reason 0x%08x."
|
||||
, uint32_t(hr)
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t srcWidth = bx::max(1, texture.m_width >>_mip);
|
||||
uint32_t srcHeight = bx::max(1, texture.m_height>>_mip);
|
||||
@@ -1979,10 +1988,21 @@ namespace bgfx { namespace d3d11
|
||||
box.back = 1;
|
||||
m_deviceCtx->CopySubresourceRegion(staging, 0, _offset, 0, 0, buffer.m_ptr, 0, &box);
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
DX_CHECK(m_deviceCtx->Map(staging, 0, D3D11_MAP_READ, 0, &mapped) );
|
||||
bx::memCopy(_data, (const uint8_t*)mapped.pData + _offset, _size);
|
||||
m_deviceCtx->Unmap(staging, 0);
|
||||
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
||||
const HRESULT hr = m_deviceCtx->Map(staging, 0, D3D11_MAP_READ, 0, &mapped);
|
||||
|
||||
if (FAILED(hr) )
|
||||
{
|
||||
BX_TRACE("readBuffer: Map failed 0x%08x, device removed reason 0x%08x."
|
||||
, uint32_t(hr)
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
bx::memCopy(_data, (const uint8_t*)mapped.pData + _offset, _size);
|
||||
m_deviceCtx->Unmap(staging, 0);
|
||||
}
|
||||
|
||||
DX_RELEASE(staging, 0);
|
||||
}
|
||||
@@ -2159,21 +2179,31 @@ namespace bgfx { namespace d3d11
|
||||
}
|
||||
}
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
DX_CHECK(m_deviceCtx->Map(texture, 0, D3D11_MAP_READ, 0, &mapped) );
|
||||
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
||||
hr = m_deviceCtx->Map(texture, 0, D3D11_MAP_READ, 0, &mapped);
|
||||
|
||||
g_callback->screenShot(
|
||||
_filePath
|
||||
, backBufferDesc.Width
|
||||
, backBufferDesc.Height
|
||||
, mapped.RowPitch
|
||||
, colorFormat
|
||||
, mapped.pData
|
||||
, backBufferDesc.Height*mapped.RowPitch
|
||||
, false
|
||||
);
|
||||
if (FAILED(hr) )
|
||||
{
|
||||
BX_TRACE("requestScreenShot: Map failed 0x%08x, device removed reason 0x%08x."
|
||||
, uint32_t(hr)
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_callback->screenShot(
|
||||
_filePath
|
||||
, backBufferDesc.Width
|
||||
, backBufferDesc.Height
|
||||
, mapped.RowPitch
|
||||
, colorFormat
|
||||
, mapped.pData
|
||||
, backBufferDesc.Height*mapped.RowPitch
|
||||
, false
|
||||
);
|
||||
|
||||
m_deviceCtx->Unmap(texture, 0);
|
||||
m_deviceCtx->Unmap(texture, 0);
|
||||
}
|
||||
|
||||
DX_RELEASE(texture, 0);
|
||||
}
|
||||
@@ -3622,21 +3652,31 @@ namespace bgfx { namespace d3d11
|
||||
m_deviceCtx->CopyResource(m_captureTexture, m_captureResolve);
|
||||
}
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
DX_CHECK(m_deviceCtx->Map(m_captureTexture, 0, D3D11_MAP_READ, 0, &mapped) );
|
||||
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
||||
const HRESULT hr = m_deviceCtx->Map(m_captureTexture, 0, D3D11_MAP_READ, 0, &mapped);
|
||||
|
||||
bimg::imageSwizzleBgra8(
|
||||
mapped.pData
|
||||
, mapped.RowPitch
|
||||
, m_scd.width
|
||||
, m_scd.height
|
||||
, mapped.pData
|
||||
, mapped.RowPitch
|
||||
);
|
||||
if (FAILED(hr) )
|
||||
{
|
||||
BX_TRACE("capture: Map failed 0x%08x, device removed reason 0x%08x."
|
||||
, uint32_t(hr)
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
bimg::imageSwizzleBgra8(
|
||||
mapped.pData
|
||||
, mapped.RowPitch
|
||||
, m_scd.width
|
||||
, m_scd.height
|
||||
, mapped.pData
|
||||
, mapped.RowPitch
|
||||
);
|
||||
|
||||
g_callback->captureFrame(mapped.pData, m_scd.height*mapped.RowPitch);
|
||||
g_callback->captureFrame(mapped.pData, m_scd.height*mapped.RowPitch);
|
||||
|
||||
m_deviceCtx->Unmap(m_captureTexture, 0);
|
||||
m_deviceCtx->Unmap(m_captureTexture, 0);
|
||||
}
|
||||
|
||||
DX_RELEASE(backBuffer, 0);
|
||||
}
|
||||
|
||||
+148
-52
@@ -693,11 +693,13 @@ namespace bgfx { namespace d3d12
|
||||
#endif // BX_PLATFORM_WINDOWS
|
||||
}
|
||||
|
||||
static bool reportDeviceLost(HRESULT _hr);
|
||||
|
||||
ID3D12Resource* createCommittedResource(ID3D12Device* _device, HeapProperty::Enum _heapProperty, const D3D12_RESOURCE_DESC* _resourceDesc, const D3D12_CLEAR_VALUE* _clearValue, bool _memSet = false, D3D12_HEAP_FLAGS _heapFlags = D3D12_HEAP_FLAG_NONE)
|
||||
{
|
||||
const HeapProperty& heapProperty = s_heapProperties[_heapProperty];
|
||||
ID3D12Resource* resource = NULL;
|
||||
DX_CHECK(_device->CreateCommittedResource(
|
||||
const HRESULT hr = _device->CreateCommittedResource(
|
||||
&heapProperty.m_properties
|
||||
, _heapFlags
|
||||
, _resourceDesc
|
||||
@@ -705,7 +707,14 @@ namespace bgfx { namespace d3d12
|
||||
, _clearValue
|
||||
, IID_ID3D12Resource
|
||||
, (void**)&resource
|
||||
) );
|
||||
);
|
||||
|
||||
if (reportDeviceLost(hr) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BX_ASSERT(SUCCEEDED(hr), "CreateCommittedResource FAILED 0x%08x.", uint32_t(hr) );
|
||||
BX_WARN(NULL != resource, "CreateCommittedResource failed (size: %d). Out of memory?"
|
||||
, _resourceDesc->Width
|
||||
);
|
||||
@@ -2322,7 +2331,7 @@ namespace bgfx { namespace d3d12
|
||||
}
|
||||
}
|
||||
|
||||
DX_RELEASE(dred, 0);
|
||||
DX_RELEASE_I(dred);
|
||||
}
|
||||
|
||||
bool handleDeviceLost(HRESULT _hr)
|
||||
@@ -2532,6 +2541,14 @@ namespace bgfx { namespace d3d12
|
||||
|
||||
ID3D12Resource* readback = createCommittedResource(m_device, HeapProperty::ReadBack, total);
|
||||
|
||||
if (NULL == readback)
|
||||
{
|
||||
BX_TRACE("readTexture: failed to create readback buffer, device removed reason 0x%08x."
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t srcWidth = bx::max(1u, texture.m_width >>_mip);
|
||||
const uint32_t srcHeight = bx::max(1u, texture.m_height>>_mip);
|
||||
|
||||
@@ -2570,13 +2587,23 @@ namespace bgfx { namespace d3d12
|
||||
|
||||
uint32_t pitch = bx::min(srcPitch, dstPitch);
|
||||
|
||||
uint8_t* src;
|
||||
readback->Map(0, NULL, (void**)&src);
|
||||
uint8_t* src = NULL;
|
||||
const HRESULT hr = readback->Map(0, NULL, (void**)&src);
|
||||
|
||||
bx::memCopy(dst, dstPitch, src, srcPitch, pitch, numBlockRows);
|
||||
if (FAILED(hr) )
|
||||
{
|
||||
BX_TRACE("readTexture: Map failed 0x%08x, device removed reason 0x%08x."
|
||||
, uint32_t(hr)
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
bx::memCopy(dst, dstPitch, src, srcPitch, pitch, numBlockRows);
|
||||
|
||||
D3D12_RANGE writeRange = { 0, 0 };
|
||||
readback->Unmap(0, &writeRange);
|
||||
D3D12_RANGE writeRange = { 0, 0 };
|
||||
readback->Unmap(0, &writeRange);
|
||||
}
|
||||
|
||||
DX_RELEASE(readback, 0);
|
||||
}
|
||||
@@ -2613,6 +2640,14 @@ namespace bgfx { namespace d3d12
|
||||
|
||||
ID3D12Resource* readback = createCommittedResource(m_device, HeapProperty::ReadBack, stencilOffset + total[1]);
|
||||
|
||||
if (NULL == readback)
|
||||
{
|
||||
BX_TRACE("readTexturePlanar: failed to create readback buffer, device removed reason 0x%08x."
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t srcWidth = bx::max(1u, _texture.m_width >>_mip);
|
||||
const uint32_t srcHeight = bx::max(1u, _texture.m_height>>_mip);
|
||||
|
||||
@@ -2639,47 +2674,57 @@ namespace bgfx { namespace d3d12
|
||||
finish();
|
||||
m_commandList = m_cmd.alloc();
|
||||
|
||||
uint8_t* src;
|
||||
readback->Map(0, NULL, (void**)&src);
|
||||
uint8_t* src = NULL;
|
||||
const HRESULT hr = readback->Map(0, NULL, (void**)&src);
|
||||
|
||||
const uint32_t texelSize = bimg::getBlockInfo(bimg::TextureFormat::Enum(_texture.m_textureFormat) ).blockSize;
|
||||
const uint32_t dstPitch = srcWidth * texelSize;
|
||||
|
||||
for (uint32_t yy = 0; yy < srcHeight; ++yy)
|
||||
if (FAILED(hr) )
|
||||
{
|
||||
const uint8_t* depthRow = src + layout[0].Offset + yy*layout[0].Footprint.RowPitch;
|
||||
const uint8_t* stencilRow = src + layout[1].Offset + yy*layout[1].Footprint.RowPitch;
|
||||
uint8_t* dstRow = (uint8_t*)_data + yy*dstPitch;
|
||||
BX_TRACE("readTexturePlanar: Map failed 0x%08x, device removed reason 0x%08x."
|
||||
, uint32_t(hr)
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint32_t texelSize = bimg::getBlockInfo(bimg::TextureFormat::Enum(_texture.m_textureFormat) ).blockSize;
|
||||
const uint32_t dstPitch = srcWidth * texelSize;
|
||||
|
||||
for (uint32_t xx = 0; xx < srcWidth; ++xx)
|
||||
for (uint32_t yy = 0; yy < srcHeight; ++yy)
|
||||
{
|
||||
uint8_t* texel = dstRow + xx*texelSize;
|
||||
const uint8_t* depthRow = src + layout[0].Offset + yy*layout[0].Footprint.RowPitch;
|
||||
const uint8_t* stencilRow = src + layout[1].Offset + yy*layout[1].Footprint.RowPitch;
|
||||
uint8_t* dstRow = (uint8_t*)_data + yy*dstPitch;
|
||||
|
||||
if (1 == texelSize)
|
||||
for (uint32_t xx = 0; xx < srcWidth; ++xx)
|
||||
{
|
||||
texel[0] = stencilRow[xx];
|
||||
}
|
||||
else
|
||||
{
|
||||
bx::memCopy(texel, depthRow + xx*4, 4);
|
||||
uint8_t* texel = dstRow + xx*texelSize;
|
||||
|
||||
if (4 == texelSize)
|
||||
if (1 == texelSize)
|
||||
{
|
||||
texel[3] = stencilRow[xx];
|
||||
texel[0] = stencilRow[xx];
|
||||
}
|
||||
else
|
||||
{
|
||||
texel[4] = stencilRow[xx];
|
||||
texel[5] = 0;
|
||||
texel[6] = 0;
|
||||
texel[7] = 0;
|
||||
bx::memCopy(texel, depthRow + xx*4, 4);
|
||||
|
||||
if (4 == texelSize)
|
||||
{
|
||||
texel[3] = stencilRow[xx];
|
||||
}
|
||||
else
|
||||
{
|
||||
texel[4] = stencilRow[xx];
|
||||
texel[5] = 0;
|
||||
texel[6] = 0;
|
||||
texel[7] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
D3D12_RANGE writeRange = { 0, 0 };
|
||||
readback->Unmap(0, &writeRange);
|
||||
D3D12_RANGE writeRange = { 0, 0 };
|
||||
readback->Unmap(0, &writeRange);
|
||||
}
|
||||
|
||||
DX_RELEASE(readback, 0);
|
||||
}
|
||||
@@ -2700,6 +2745,14 @@ namespace bgfx { namespace d3d12
|
||||
|
||||
ID3D12Resource* readback = createCommittedResource(m_device, HeapProperty::ReadBack, _size);
|
||||
|
||||
if (NULL == readback)
|
||||
{
|
||||
BX_TRACE("readBuffer: failed to create readback buffer, device removed reason 0x%08x."
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const D3D12_RESOURCE_STATES state = buffer.setState(m_commandList, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
m_commandList->CopyBufferRegion(readback, 0, buffer.m_ptr, _offset, _size);
|
||||
buffer.setState(m_commandList, state);
|
||||
@@ -2708,10 +2761,22 @@ namespace bgfx { namespace d3d12
|
||||
m_commandList = m_cmd.alloc();
|
||||
|
||||
void* src = NULL;
|
||||
readback->Map(0, NULL, (void**)&src);
|
||||
bx::memCopy(_data, src, _size);
|
||||
D3D12_RANGE readbackWriteRange = { 0, 0 };
|
||||
readback->Unmap(0, &readbackWriteRange);
|
||||
const HRESULT hr = readback->Map(0, NULL, (void**)&src);
|
||||
|
||||
if (FAILED(hr) )
|
||||
{
|
||||
BX_TRACE("readBuffer: Map failed 0x%08x, device removed reason 0x%08x."
|
||||
, uint32_t(hr)
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
bx::memCopy(_data, src, _size);
|
||||
|
||||
D3D12_RANGE writeRange = { 0, 0 };
|
||||
readback->Unmap(0, &writeRange);
|
||||
}
|
||||
|
||||
DX_RELEASE(readback, 0);
|
||||
}
|
||||
@@ -2897,6 +2962,20 @@ namespace bgfx { namespace d3d12
|
||||
|
||||
ID3D12Resource* readback = createCommittedResource(m_device, HeapProperty::ReadBack, total);
|
||||
|
||||
if (NULL == readback)
|
||||
{
|
||||
BX_TRACE("requestScreenShot: failed to create readback buffer, device removed reason 0x%08x."
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
|
||||
if (NULL != swapFb)
|
||||
{
|
||||
DX_RELEASE(backBuffer, swapFb->getSwapChainDesc().bufferCount);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
D3D12_BOX box;
|
||||
box.left = 0;
|
||||
box.top = 0;
|
||||
@@ -2917,21 +2996,31 @@ namespace bgfx { namespace d3d12
|
||||
finish();
|
||||
m_commandList = m_cmd.alloc();
|
||||
|
||||
void* data;
|
||||
readback->Map(0, NULL, (void**)&data);
|
||||
void* data = NULL;
|
||||
const HRESULT hr = readback->Map(0, NULL, (void**)&data);
|
||||
|
||||
g_callback->screenShot(_filePath
|
||||
, width
|
||||
, height
|
||||
, layout.Footprint.RowPitch
|
||||
, colorFormat
|
||||
, data
|
||||
, (uint32_t)total
|
||||
, false
|
||||
);
|
||||
if (FAILED(hr) )
|
||||
{
|
||||
BX_TRACE("requestScreenShot: Map failed 0x%08x, device removed reason 0x%08x."
|
||||
, uint32_t(hr)
|
||||
, uint32_t(m_device->GetDeviceRemovedReason() )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_callback->screenShot(_filePath
|
||||
, width
|
||||
, height
|
||||
, layout.Footprint.RowPitch
|
||||
, colorFormat
|
||||
, data
|
||||
, (uint32_t)total
|
||||
, false
|
||||
);
|
||||
|
||||
D3D12_RANGE writeRange = { 0, 0 };
|
||||
readback->Unmap(0, &writeRange);
|
||||
D3D12_RANGE writeRange = { 0, 0 };
|
||||
readback->Unmap(0, &writeRange);
|
||||
}
|
||||
|
||||
DX_RELEASE(readback, 0);
|
||||
|
||||
@@ -4549,6 +4638,13 @@ namespace bgfx { namespace d3d12
|
||||
|
||||
static RendererContextD3D12* s_renderD3D12;
|
||||
|
||||
static bool reportDeviceLost(HRESULT _hr)
|
||||
{
|
||||
return NULL != s_renderD3D12
|
||||
&& s_renderD3D12->handleDeviceLost(_hr)
|
||||
;
|
||||
}
|
||||
|
||||
ID3D12PipelineState* videoGetPipelineState(RendererContextD3D12* _renderer, ProgramHandle _handle)
|
||||
{
|
||||
return _renderer->getPipelineState(_handle);
|
||||
@@ -10033,7 +10129,7 @@ namespace bgfx { namespace d3d12
|
||||
;
|
||||
const bool depthClamp = depthControl.m_depthClamp;
|
||||
|
||||
|
||||
|
||||
|
||||
ID3D12PipelineState* pso = getPipelineState(
|
||||
state
|
||||
|
||||
+40
-32
@@ -1830,34 +1830,38 @@ WGPU_IMPORT
|
||||
|
||||
static void readTextureCb(WGPUMapAsyncStatus _status, WGPUStringView _message, void* _userdata1, void* _userdata2)
|
||||
{
|
||||
BX_ASSERT(WGPUMapAsyncStatus_Success == _status, "%d", _status);
|
||||
|
||||
BX_UNUSED(_status, _message, _userdata2);
|
||||
|
||||
ReadTexture& readTexture = *(ReadTexture*)_userdata1;
|
||||
|
||||
const void* result = (const void*)WGPU_CHECK(wgpuBufferGetConstMappedRange(
|
||||
readTexture.buffer
|
||||
, 0
|
||||
, readTexture.size
|
||||
) );
|
||||
|
||||
if (0 != readTexture.stencilPitch)
|
||||
if (WGPUMapAsyncStatus_Success != _status)
|
||||
{
|
||||
interleaveDepthStencil(readTexture, result);
|
||||
BX_TRACE("readTexture: map failed %d.", _status);
|
||||
trace(_message);
|
||||
}
|
||||
else
|
||||
{
|
||||
bx::gather(
|
||||
readTexture.data
|
||||
, result
|
||||
, readTexture.pitch
|
||||
, readTexture.dataPitch
|
||||
, readTexture.height
|
||||
);
|
||||
}
|
||||
const void* result = (const void*)WGPU_CHECK(wgpuBufferGetConstMappedRange(
|
||||
readTexture.buffer
|
||||
, 0
|
||||
, readTexture.size
|
||||
) );
|
||||
|
||||
WGPU_CHECK(wgpuBufferUnmap(readTexture.buffer) );
|
||||
if (0 != readTexture.stencilPitch)
|
||||
{
|
||||
interleaveDepthStencil(readTexture, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
bx::gather(
|
||||
readTexture.data
|
||||
, result
|
||||
, readTexture.pitch
|
||||
, readTexture.dataPitch
|
||||
, readTexture.height
|
||||
);
|
||||
}
|
||||
|
||||
WGPU_CHECK(wgpuBufferUnmap(readTexture.buffer) );
|
||||
}
|
||||
|
||||
wgpuRelease(readTexture.buffer);
|
||||
|
||||
@@ -1873,21 +1877,25 @@ WGPU_IMPORT
|
||||
|
||||
static void readBufferCb(WGPUMapAsyncStatus _status, WGPUStringView _message, void* _userdata1, void* _userdata2)
|
||||
{
|
||||
BX_ASSERT(WGPUMapAsyncStatus_Success == _status, "%d", _status);
|
||||
|
||||
BX_UNUSED(_status, _message, _userdata2);
|
||||
|
||||
ReadBuffer& readBuffer = *(ReadBuffer*)_userdata1;
|
||||
|
||||
const void* result = (const void*)WGPU_CHECK(wgpuBufferGetConstMappedRange(
|
||||
readBuffer.buffer
|
||||
, 0
|
||||
, readBuffer.size
|
||||
) );
|
||||
if (WGPUMapAsyncStatus_Success != _status)
|
||||
{
|
||||
BX_TRACE("readBuffer: map failed %d.", _status);
|
||||
trace(_message);
|
||||
}
|
||||
else
|
||||
{
|
||||
const void* result = (const void*)WGPU_CHECK(wgpuBufferGetConstMappedRange(
|
||||
readBuffer.buffer
|
||||
, 0
|
||||
, readBuffer.size
|
||||
) );
|
||||
|
||||
bx::memCopy(readBuffer.data, result, readBuffer.size);
|
||||
bx::memCopy(readBuffer.data, result, readBuffer.size);
|
||||
|
||||
WGPU_CHECK(wgpuBufferUnmap(readBuffer.buffer) );
|
||||
WGPU_CHECK(wgpuBufferUnmap(readBuffer.buffer) );
|
||||
}
|
||||
|
||||
wgpuRelease(readBuffer.buffer);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user