GPUDevice: Get rid of framebuffer abstraction
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
<PreprocessorDefinitions Condition="('$(Platform)'=='x64' Or '$(Platform)'=='ARM64')">ENABLE_MMAP_FASTMEM=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="('$(Platform)'=='x64' Or '$(Platform)'=='ARM64')">ENABLE_NEWREC=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\rcheevos\include;$(SolutionDir)dep\rapidjson\include;$(SolutionDir)dep\discord-rpc\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\rcheevos\include;$(SolutionDir)dep\rapidjson\include;$(SolutionDir)dep\discord-rpc\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\rainterface</AdditionalIncludeDirectories>
|
||||
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\xbyak\xbyak</AdditionalIncludeDirectories>
|
||||
|
||||
@@ -1579,7 +1579,7 @@ bool GPU::CompileDisplayPipeline()
|
||||
plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState();
|
||||
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
|
||||
plconfig.blend = GPUPipeline::BlendState::GetNoBlendingState();
|
||||
plconfig.color_format = g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8;
|
||||
plconfig.SetTargetFormats(g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8);
|
||||
plconfig.depth_format = GPUTexture::Format::Unknown;
|
||||
plconfig.samples = 1;
|
||||
plconfig.per_sample_shading = false;
|
||||
@@ -1670,9 +1670,10 @@ bool GPU::PresentDisplay()
|
||||
return RenderDisplay(nullptr, draw_rect, true);
|
||||
}
|
||||
|
||||
bool GPU::RenderDisplay(GPUFramebuffer* target, const Common::Rectangle<s32>& draw_rect, bool postfx)
|
||||
bool GPU::RenderDisplay(GPUTexture* target, const Common::Rectangle<s32>& draw_rect, bool postfx)
|
||||
{
|
||||
GL_SCOPE_FMT("RenderDisplay: {}x{} at {},{}", draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight());
|
||||
GL_SCOPE_FMT("RenderDisplay: {}x{} at {},{}", draw_rect.left, draw_rect.top, draw_rect.GetWidth(),
|
||||
draw_rect.GetHeight());
|
||||
|
||||
if (m_display_texture)
|
||||
m_display_texture->MakeReadyForSampling();
|
||||
@@ -1716,8 +1717,7 @@ bool GPU::RenderDisplay(GPUFramebuffer* target, const Common::Rectangle<s32>& dr
|
||||
break;
|
||||
}
|
||||
|
||||
const GPUTexture::Format hdformat =
|
||||
(target && target->GetRT()) ? target->GetRT()->GetFormat() : g_gpu_device->GetWindowFormat();
|
||||
const GPUTexture::Format hdformat = target ? target->GetFormat() : g_gpu_device->GetWindowFormat();
|
||||
const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetWindowWidth();
|
||||
const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetWindowHeight();
|
||||
const bool really_postfx = (postfx && HasDisplayTexture() && PostProcessing::IsActive() &&
|
||||
@@ -1725,12 +1725,12 @@ bool GPU::RenderDisplay(GPUFramebuffer* target, const Common::Rectangle<s32>& dr
|
||||
if (really_postfx)
|
||||
{
|
||||
g_gpu_device->ClearRenderTarget(PostProcessing::GetInputTexture(), 0);
|
||||
g_gpu_device->SetFramebuffer(PostProcessing::GetInputFramebuffer());
|
||||
g_gpu_device->SetRenderTarget(PostProcessing::GetInputTexture());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target)
|
||||
g_gpu_device->SetFramebuffer(target);
|
||||
g_gpu_device->SetRenderTarget(target);
|
||||
else if (!g_gpu_device->BeginPresent(false))
|
||||
return false;
|
||||
}
|
||||
@@ -2059,16 +2059,10 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangl
|
||||
if (!render_texture)
|
||||
return false;
|
||||
|
||||
std::unique_ptr<GPUFramebuffer> render_fb = g_gpu_device->CreateFramebuffer(render_texture.get());
|
||||
if (!render_fb)
|
||||
return false;
|
||||
|
||||
g_gpu_device->ClearRenderTarget(render_texture.get(), 0);
|
||||
|
||||
// TODO: this should use copy shader instead.
|
||||
RenderDisplay(render_fb.get(), draw_rect, postfx);
|
||||
|
||||
g_gpu_device->SetFramebuffer(nullptr);
|
||||
RenderDisplay(render_texture.get(), draw_rect, postfx);
|
||||
|
||||
const u32 stride = GPUTexture::GetPixelSize(hdformat) * width;
|
||||
out_pixels->resize(width * height);
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
class StateWrapper;
|
||||
|
||||
class GPUDevice;
|
||||
class GPUFramebuffer;
|
||||
class GPUTexture;
|
||||
class GPUPipeline;
|
||||
|
||||
@@ -585,7 +584,7 @@ protected:
|
||||
float* out_top_padding, float* out_scale, float* out_x_scale,
|
||||
bool apply_aspect_ratio = true) const;
|
||||
|
||||
bool RenderDisplay(GPUFramebuffer* target, const Common::Rectangle<s32>& draw_rect, bool postfx);
|
||||
bool RenderDisplay(GPUTexture* target, const Common::Rectangle<s32>& draw_rect, bool postfx);
|
||||
|
||||
s32 m_display_width = 0;
|
||||
s32 m_display_height = 0;
|
||||
|
||||
@@ -317,7 +317,7 @@ bool GPU_HW::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
|
||||
void GPU_HW::RestoreDeviceContext()
|
||||
{
|
||||
g_gpu_device->SetTextureSampler(0, m_vram_read_texture.get(), g_gpu_device->GetNearestSampler());
|
||||
g_gpu_device->SetFramebuffer(m_vram_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_vram_texture.get(), m_vram_depth_texture.get());
|
||||
g_gpu_device->SetViewport(0, 0, m_vram_texture->GetWidth(), m_vram_texture->GetHeight());
|
||||
SetScissor();
|
||||
m_batch_ubo_dirty = true;
|
||||
@@ -649,20 +649,6 @@ bool GPU_HW::CreateBuffers()
|
||||
GL_OBJECT_NAME(m_display_private_texture, "Display Texture");
|
||||
GL_OBJECT_NAME(m_vram_readback_texture, "VRAM Readback Texture");
|
||||
|
||||
// vram framebuffer has both colour and depth
|
||||
if (!(m_vram_framebuffer = g_gpu_device->CreateFramebuffer(m_vram_texture.get(), m_vram_depth_texture.get())) ||
|
||||
!(m_vram_update_depth_framebuffer = g_gpu_device->CreateFramebuffer(m_vram_depth_texture.get())) ||
|
||||
!(m_vram_readback_framebuffer = g_gpu_device->CreateFramebuffer(m_vram_readback_texture.get())) ||
|
||||
!(m_display_framebuffer = g_gpu_device->CreateFramebuffer(m_display_private_texture.get())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GL_OBJECT_NAME(m_vram_framebuffer, "VRAM Framebuffer");
|
||||
GL_OBJECT_NAME(m_vram_update_depth_framebuffer, "VRAM Update Depth Framebuffer");
|
||||
GL_OBJECT_NAME(m_vram_readback_framebuffer, "VRAM Readback Framebuffer");
|
||||
GL_OBJECT_NAME(m_display_framebuffer, "Display Framebuffer");
|
||||
|
||||
if (!(m_vram_upload_buffer =
|
||||
g_gpu_device->CreateTextureBuffer(GPUTextureBuffer::Format::R16UI, GPUDevice::MIN_TEXEL_BUFFER_ELEMENTS)))
|
||||
{
|
||||
@@ -679,11 +665,9 @@ bool GPU_HW::CreateBuffers()
|
||||
GPUTexture::Type::Texture, VRAM_RT_FORMAT)) ||
|
||||
!(m_downsample_render_texture = g_gpu_device->CreateTexture(texture_width, texture_height, 1, 1, 1,
|
||||
GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT)) ||
|
||||
!(m_downsample_framebuffer = g_gpu_device->CreateFramebuffer(m_downsample_render_texture.get())) ||
|
||||
!(m_downsample_weight_texture =
|
||||
g_gpu_device->CreateTexture(texture_width >> (levels - 1), texture_height >> (levels - 1), 1, 1, 1,
|
||||
GPUTexture::Type::RenderTarget, GPUTexture::Format::R8)) ||
|
||||
!(m_downsample_weight_framebuffer = g_gpu_device->CreateFramebuffer(m_downsample_weight_texture.get())))
|
||||
GPUTexture::Type::RenderTarget, GPUTexture::Format::R8)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -693,14 +677,13 @@ bool GPU_HW::CreateBuffers()
|
||||
const u32 downsample_scale = GetBoxDownsampleScale(m_resolution_scale);
|
||||
if (!(m_downsample_render_texture =
|
||||
g_gpu_device->CreateTexture(VRAM_WIDTH * downsample_scale, VRAM_HEIGHT * downsample_scale, 1, 1, 1,
|
||||
GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT)) ||
|
||||
!(m_downsample_framebuffer = g_gpu_device->CreateFramebuffer(m_downsample_render_texture.get())))
|
||||
GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
g_gpu_device->SetFramebuffer(m_vram_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_vram_texture.get(), m_vram_depth_texture.get());
|
||||
SetFullVRAMDirtyRectangle();
|
||||
return true;
|
||||
}
|
||||
@@ -719,15 +702,9 @@ void GPU_HW::DestroyBuffers()
|
||||
ClearDisplayTexture();
|
||||
|
||||
m_vram_upload_buffer.reset();
|
||||
m_downsample_weight_framebuffer.reset();
|
||||
m_downsample_weight_texture.reset();
|
||||
m_downsample_framebuffer.reset();
|
||||
m_downsample_render_texture.reset();
|
||||
m_downsample_texture.reset();
|
||||
m_display_framebuffer.reset();
|
||||
m_vram_readback_framebuffer.reset();
|
||||
m_vram_update_depth_framebuffer.reset();
|
||||
m_vram_framebuffer.reset();
|
||||
m_vram_read_texture.reset();
|
||||
m_vram_depth_texture.reset();
|
||||
m_vram_texture.reset();
|
||||
@@ -833,8 +810,7 @@ bool GPU_HW::CompilePipelines()
|
||||
plconfig.input_layout.vertex_stride = sizeof(BatchVertex);
|
||||
plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState();
|
||||
plconfig.primitive = GPUPipeline::Primitive::Triangles;
|
||||
plconfig.color_format = VRAM_RT_FORMAT;
|
||||
plconfig.depth_format = VRAM_DS_FORMAT;
|
||||
plconfig.SetTargetFormats(VRAM_RT_FORMAT, VRAM_DS_FORMAT);
|
||||
plconfig.samples = m_multisamples;
|
||||
plconfig.per_sample_shading = m_per_sample_shading;
|
||||
plconfig.geometry_shader = nullptr;
|
||||
@@ -1080,8 +1056,7 @@ bool GPU_HW::CompilePipelines()
|
||||
return false;
|
||||
|
||||
plconfig.fragment_shader = fs.get();
|
||||
plconfig.color_format = GPUTexture::Format::Unknown;
|
||||
plconfig.depth_format = VRAM_DS_FORMAT;
|
||||
plconfig.SetTargetFormats(GPUTexture::Format::Unknown, VRAM_DS_FORMAT);
|
||||
plconfig.depth = GPUPipeline::DepthState::GetAlwaysWriteState();
|
||||
plconfig.blend.write_mask = 0;
|
||||
|
||||
@@ -1093,8 +1068,7 @@ bool GPU_HW::CompilePipelines()
|
||||
progress.Increment();
|
||||
}
|
||||
|
||||
plconfig.color_format = VRAM_RT_FORMAT;
|
||||
plconfig.depth_format = GPUTexture::Format::Unknown;
|
||||
plconfig.SetTargetFormats(VRAM_RT_FORMAT);
|
||||
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
|
||||
plconfig.blend = GPUPipeline::BlendState::GetNoBlendingState();
|
||||
plconfig.samples = 1;
|
||||
@@ -1181,7 +1155,7 @@ bool GPU_HW::CompilePipelines()
|
||||
return false;
|
||||
GL_OBJECT_NAME(fs, "Downsample Blur Pass Fragment Shader");
|
||||
plconfig.fragment_shader = fs.get();
|
||||
plconfig.color_format = GPUTexture::Format::R8;
|
||||
plconfig.SetTargetFormats(GPUTexture::Format::R8);
|
||||
if (!(m_downsample_blur_pass_pipeline = g_gpu_device->CreatePipeline(plconfig)))
|
||||
return false;
|
||||
GL_OBJECT_NAME(m_downsample_blur_pass_pipeline, "Downsample Blur Pass Pipeline");
|
||||
@@ -1193,7 +1167,7 @@ bool GPU_HW::CompilePipelines()
|
||||
GL_OBJECT_NAME(fs, "Downsample Composite Pass Fragment Shader");
|
||||
plconfig.layout = GPUPipeline::Layout::MultiTextureAndPushConstants;
|
||||
plconfig.fragment_shader = fs.get();
|
||||
plconfig.color_format = VRAM_RT_FORMAT;
|
||||
plconfig.SetTargetFormats(VRAM_RT_FORMAT);
|
||||
if (!(m_downsample_composite_pass_pipeline = g_gpu_device->CreatePipeline(plconfig)))
|
||||
return false;
|
||||
GL_OBJECT_NAME(m_downsample_composite_pass_pipeline, "Downsample Blur Pass Pipeline");
|
||||
@@ -1310,14 +1284,15 @@ void GPU_HW::UpdateDepthBufferFromMaskBit()
|
||||
|
||||
// Viewport should already be set full, only need to fudge the scissor.
|
||||
g_gpu_device->SetScissor(0, 0, m_vram_texture->GetWidth(), m_vram_texture->GetHeight());
|
||||
g_gpu_device->SetFramebuffer(m_vram_update_depth_framebuffer.get());
|
||||
g_gpu_device->InvalidateRenderTarget(m_vram_depth_texture.get());
|
||||
g_gpu_device->SetRenderTargets(nullptr, 0, m_vram_depth_texture.get());
|
||||
g_gpu_device->SetPipeline(m_vram_update_depth_pipeline.get());
|
||||
g_gpu_device->SetTextureSampler(0, m_vram_texture.get(), g_gpu_device->GetNearestSampler());
|
||||
g_gpu_device->Draw(3, 0);
|
||||
|
||||
// Restore.
|
||||
g_gpu_device->SetTextureSampler(0, m_vram_read_texture.get(), g_gpu_device->GetNearestSampler());
|
||||
g_gpu_device->SetFramebuffer(m_vram_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_vram_texture.get(), m_vram_depth_texture.get());
|
||||
SetScissor();
|
||||
}
|
||||
|
||||
@@ -2061,7 +2036,7 @@ bool GPU_HW::BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u3
|
||||
}
|
||||
}
|
||||
|
||||
g_gpu_device->SetFramebuffer(m_vram_framebuffer.get()); // TODO: needed?
|
||||
g_gpu_device->SetRenderTarget(m_vram_texture.get(), m_vram_depth_texture.get()); // TODO: needed?
|
||||
g_gpu_device->SetTextureSampler(0, m_vram_replacement_texture.get(), g_gpu_device->GetLinearSampler());
|
||||
g_gpu_device->SetPipeline(m_copy_pipeline.get());
|
||||
g_gpu_device->SetViewportAndScissor(dst_x, dst_y, width, height);
|
||||
@@ -2364,7 +2339,7 @@ void GPU_HW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
|
||||
|
||||
// Encode the 24-bit texture as 16-bit.
|
||||
const u32 uniforms[4] = {copy_rect.left, copy_rect.top, copy_rect.GetWidth(), copy_rect.GetHeight()};
|
||||
g_gpu_device->SetFramebuffer(m_vram_readback_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_vram_readback_texture.get());
|
||||
g_gpu_device->SetPipeline(m_vram_readback_pipeline.get());
|
||||
g_gpu_device->SetTextureSampler(0, m_vram_texture.get(), g_gpu_device->GetNearestSampler());
|
||||
g_gpu_device->SetViewportAndScissor(0, 0, encoded_width, encoded_height);
|
||||
@@ -2811,7 +2786,7 @@ void GPU_HW::UpdateDisplay()
|
||||
if (interlaced == InterlacedRenderMode::None)
|
||||
g_gpu_device->InvalidateRenderTarget(m_display_private_texture.get());
|
||||
|
||||
g_gpu_device->SetFramebuffer(m_display_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_display_private_texture.get());
|
||||
g_gpu_device->SetPipeline(
|
||||
m_display_pipelines[BoolToUInt8(m_GPUSTAT.display_area_color_depth_24)][static_cast<u8>(interlaced)].get());
|
||||
g_gpu_device->SetTextureSampler(0, m_vram_texture.get(), g_gpu_device->GetNearestSampler());
|
||||
@@ -2883,7 +2858,7 @@ void GPU_HW::DownsampleFramebufferAdaptive(GPUTexture* source, u32 left, u32 top
|
||||
uniforms.lod = static_cast<float>(level - 1);
|
||||
|
||||
g_gpu_device->ClearRenderTarget(m_downsample_render_texture.get(), 0);
|
||||
g_gpu_device->SetFramebuffer(m_downsample_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_downsample_render_texture.get());
|
||||
g_gpu_device->SetViewportAndScissor(0, 0, level_width, level_height);
|
||||
g_gpu_device->SetPipeline((level == 1) ? m_downsample_first_pass_pipeline.get() :
|
||||
m_downsample_mid_pass_pipeline.get());
|
||||
@@ -2912,7 +2887,7 @@ void GPU_HW::DownsampleFramebufferAdaptive(GPUTexture* source, u32 left, u32 top
|
||||
|
||||
m_downsample_render_texture->MakeReadyForSampling();
|
||||
g_gpu_device->ClearRenderTarget(m_downsample_weight_texture.get(), 0);
|
||||
g_gpu_device->SetFramebuffer(m_downsample_weight_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_downsample_weight_texture.get());
|
||||
g_gpu_device->SetTextureSampler(0, m_downsample_render_texture.get(), g_gpu_device->GetNearestSampler());
|
||||
g_gpu_device->SetViewportAndScissor(0, 0, last_width, last_height);
|
||||
g_gpu_device->SetPipeline(m_downsample_blur_pass_pipeline.get());
|
||||
@@ -2926,7 +2901,7 @@ void GPU_HW::DownsampleFramebufferAdaptive(GPUTexture* source, u32 left, u32 top
|
||||
GL_SCOPE("Composite");
|
||||
|
||||
g_gpu_device->ClearRenderTarget(m_downsample_render_texture.get(), 0);
|
||||
g_gpu_device->SetFramebuffer(m_downsample_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_downsample_render_texture.get());
|
||||
g_gpu_device->SetTextureSampler(0, m_downsample_texture.get(), m_downsample_composite_sampler.get());
|
||||
g_gpu_device->SetTextureSampler(1, m_downsample_weight_texture.get(), m_downsample_lod_sampler.get());
|
||||
g_gpu_device->SetViewportAndScissor(0, 0, width, height);
|
||||
@@ -2953,7 +2928,7 @@ void GPU_HW::DownsampleFramebufferBoxFilter(GPUTexture* source, u32 left, u32 to
|
||||
source->MakeReadyForSampling();
|
||||
|
||||
g_gpu_device->ClearRenderTarget(m_downsample_render_texture.get(), 0);
|
||||
g_gpu_device->SetFramebuffer(m_downsample_framebuffer.get());
|
||||
g_gpu_device->SetRenderTarget(m_downsample_render_texture.get());
|
||||
g_gpu_device->SetPipeline(m_downsample_first_pass_pipeline.get());
|
||||
g_gpu_device->SetTextureSampler(0, source, g_gpu_device->GetNearestSampler());
|
||||
g_gpu_device->SetViewportAndScissor(ds_left, ds_top, ds_width, ds_height);
|
||||
|
||||
@@ -213,11 +213,6 @@ private:
|
||||
std::unique_ptr<GPUTexture> m_vram_replacement_texture;
|
||||
std::unique_ptr<GPUTexture> m_display_private_texture; // TODO: Move to base.
|
||||
|
||||
std::unique_ptr<GPUFramebuffer> m_vram_framebuffer;
|
||||
std::unique_ptr<GPUFramebuffer> m_vram_update_depth_framebuffer;
|
||||
std::unique_ptr<GPUFramebuffer> m_vram_readback_framebuffer;
|
||||
std::unique_ptr<GPUFramebuffer> m_display_framebuffer;
|
||||
|
||||
std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer;
|
||||
std::unique_ptr<GPUTexture> m_vram_write_texture;
|
||||
|
||||
@@ -288,9 +283,7 @@ private:
|
||||
|
||||
std::unique_ptr<GPUTexture> m_downsample_texture;
|
||||
std::unique_ptr<GPUTexture> m_downsample_render_texture;
|
||||
std::unique_ptr<GPUFramebuffer> m_downsample_framebuffer;
|
||||
std::unique_ptr<GPUTexture> m_downsample_weight_texture;
|
||||
std::unique_ptr<GPUFramebuffer> m_downsample_weight_framebuffer;
|
||||
std::unique_ptr<GPUPipeline> m_downsample_first_pass_pipeline;
|
||||
std::unique_ptr<GPUPipeline> m_downsample_mid_pass_pipeline;
|
||||
std::unique_ptr<GPUPipeline> m_downsample_blur_pass_pipeline;
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 10;
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 11;
|
||||
Reference in New Issue
Block a user