mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-10 06:56:49 +00:00
Port UMA part 2
This commit is contained in:
@@ -283,11 +283,13 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
|||||||
|
|
||||||
[[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator,
|
[[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator,
|
||||||
const ImageInfo& info, std::span<const VkFormat> view_formats,
|
const ImageInfo& info, std::span<const VkFormat> view_formats,
|
||||||
std::optional<VkFormat> format_override = {}) {
|
std::optional<VkFormat> format_override = {},
|
||||||
|
VkImageUsageFlags host_usage = 0) {
|
||||||
if (info.type == ImageType::Buffer) {
|
if (info.type == ImageType::Buffer) {
|
||||||
return vk::Image{};
|
return vk::Image{};
|
||||||
}
|
}
|
||||||
VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info, format_override);
|
VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info, format_override);
|
||||||
|
image_ci.usage |= host_usage;
|
||||||
const VkImageFormatListCreateInfo image_format_list = {
|
const VkImageFormatListCreateInfo image_format_list = {
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@@ -1042,6 +1044,17 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
|
|||||||
pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
|
||||||
compute_pass_descriptor_queue);
|
compute_pass_descriptor_queue);
|
||||||
}
|
}
|
||||||
|
for (size_t index = 0; index < VideoCore::Surface::MaxPixelFormat; index++) {
|
||||||
|
const auto format = static_cast<PixelFormat>(index);
|
||||||
|
if (!device.IsHostImageCopySupported() || DefaultBlockWidth(format) == 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const VkFormat vk_format =
|
||||||
|
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format).format;
|
||||||
|
host_copy_formats[index] =
|
||||||
|
(device.GetPhysical().GetFormatProperties3(vk_format).optimalTilingFeatures &
|
||||||
|
VK_FORMAT_FEATURE_2_HOST_IMAGE_TRANSFER_BIT_EXT) != 0;
|
||||||
|
}
|
||||||
if (!device.IsKhrImageFormatListSupported()) {
|
if (!device.IsKhrImageFormatListSupported()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1968,10 +1981,7 @@ void TextureCacheRuntime::TickFrame() {
|
|||||||
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
|
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
|
||||||
VAddr cpu_addr_)
|
VAddr cpu_addr_)
|
||||||
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
|
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
|
||||||
runtime{&runtime_},
|
runtime{&runtime_}, aspect_mask(ImageAspectMask(info.format)) {
|
||||||
original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
|
|
||||||
runtime->ViewFormats(info.format))),
|
|
||||||
aspect_mask(ImageAspectMask(info.format)) {
|
|
||||||
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
|
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
|
||||||
switch (Settings::values.accelerate_astc.GetValue()) {
|
switch (Settings::values.accelerate_astc.GetValue()) {
|
||||||
case Settings::AstcDecodeMode::Gpu:
|
case Settings::AstcDecodeMode::Gpu:
|
||||||
@@ -1997,6 +2007,13 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
|
|||||||
WillUseAcceleratedUnswizzle(runtime->device, info)) {
|
WillUseAcceleratedUnswizzle(runtime->device, info)) {
|
||||||
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
|
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
|
||||||
}
|
}
|
||||||
|
VkImageUsageFlags host_usage{};
|
||||||
|
if (False(flags & VideoCommon::ImageFlagBits::Converted) &&
|
||||||
|
runtime->host_copy_formats[static_cast<size_t>(info.format)]) {
|
||||||
|
host_usage = VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT;
|
||||||
|
}
|
||||||
|
original_image = MakeImage(runtime->device, runtime->memory_allocator, info,
|
||||||
|
runtime->ViewFormats(info.format), {}, host_usage);
|
||||||
if (runtime->device.HasDebuggingToolAttached()) {
|
if (runtime->device.HasDebuggingToolAttached()) {
|
||||||
original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
|
original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
|
||||||
}
|
}
|
||||||
@@ -2130,6 +2147,33 @@ void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImag
|
|||||||
UploadMemory(map.buffer, map.offset, copies);
|
UploadMemory(map.buffer, map.offset, copies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Image::UploadHostMemory(std::span<const u8> memory,
|
||||||
|
std::span<const BufferImageCopy> copies) {
|
||||||
|
const auto vk_copies = TransformBufferImageCopies(copies, 0, aspect_mask);
|
||||||
|
boost::container::small_vector<VkMemoryToImageCopyEXT, 16> regions;
|
||||||
|
for (const VkBufferImageCopy2& copy : vk_copies) {
|
||||||
|
regions.push_back(VkMemoryToImageCopyEXT{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_MEMORY_TO_IMAGE_COPY_EXT,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.pHostPointer = memory.data() + copy.bufferOffset,
|
||||||
|
.memoryRowLength = copy.bufferRowLength,
|
||||||
|
.memoryImageHeight = copy.bufferImageHeight,
|
||||||
|
.imageSubresource = copy.imageSubresource,
|
||||||
|
.imageOffset = copy.imageOffset,
|
||||||
|
.imageExtent = copy.imageExtent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
runtime->device.GetLogical().CopyMemoryToImageEXT(VkCopyMemoryToImageInfoEXT{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COPY_MEMORY_TO_IMAGE_INFO_EXT,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.dstImage = *original_image,
|
||||||
|
.dstImageLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
.regionCount = static_cast<u32>(regions.size()),
|
||||||
|
.pRegions = regions.data(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Image::DownloadMemory(VkBuffer buffer, size_t offset,
|
void Image::DownloadMemory(VkBuffer buffer, size_t offset,
|
||||||
std::span<const VideoCommon::BufferImageCopy> copies) {
|
std::span<const VideoCommon::BufferImageCopy> copies) {
|
||||||
std::array buffer_handles{
|
std::array buffer_handles{
|
||||||
@@ -3177,9 +3221,27 @@ void TextureCacheRuntime::AccelerateImageUpload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
||||||
if (!image.ExchangeInitialization()) {
|
if (image.ExchangeInitialization()) {
|
||||||
RecordInitialLayout(scheduler, image.Handle(), image.AspectMask());
|
return;
|
||||||
}
|
}
|
||||||
|
if (!image.CanUploadHostMemory()) {
|
||||||
|
RecordInitialLayout(scheduler, image.Handle(), image.AspectMask());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device.GetLogical().TransitionImageLayoutEXT(VkHostImageLayoutTransitionInfoEXT{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_HOST_IMAGE_LAYOUT_TRANSITION_INFO_EXT,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.image = image.Handle(),
|
||||||
|
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
.subresourceRange{
|
||||||
|
.aspectMask = image.AspectMask(),
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
#include "video_core/texture_cache/texture_cache_base.h"
|
#include "video_core/texture_cache/texture_cache_base.h"
|
||||||
@@ -154,6 +155,7 @@ public:
|
|||||||
std::optional<PitchUnswizzlePass> pitch_unswizzle_pass;
|
std::optional<PitchUnswizzlePass> pitch_unswizzle_pass;
|
||||||
const Settings::ResolutionScalingInfo& resolution;
|
const Settings::ResolutionScalingInfo& resolution;
|
||||||
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
||||||
|
std::bitset<VideoCore::Surface::MaxPixelFormat> host_copy_formats;
|
||||||
|
|
||||||
static constexpr size_t indexing_slots = 8 * sizeof(size_t);
|
static constexpr size_t indexing_slots = 8 * sizeof(size_t);
|
||||||
std::array<vk::Buffer, indexing_slots> buffers{};
|
std::array<vk::Buffer, indexing_slots> buffers{};
|
||||||
@@ -294,6 +296,13 @@ public:
|
|||||||
void UploadMemory(const StagingBufferRef& map,
|
void UploadMemory(const StagingBufferRef& map,
|
||||||
std::span<const VideoCommon::BufferImageCopy> copies);
|
std::span<const VideoCommon::BufferImageCopy> copies);
|
||||||
|
|
||||||
|
void UploadHostMemory(std::span<const u8> memory,
|
||||||
|
std::span<const VideoCommon::BufferImageCopy> copies);
|
||||||
|
|
||||||
|
[[nodiscard]] bool CanUploadHostMemory() const noexcept {
|
||||||
|
return (original_image.UsageFlags() & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
void DownloadMemory(VkBuffer buffer, size_t offset,
|
void DownloadMemory(VkBuffer buffer, size_t offset,
|
||||||
std::span<const VideoCommon::BufferImageCopy> copies);
|
std::span<const VideoCommon::BufferImageCopy> copies);
|
||||||
|
|
||||||
|
|||||||
@@ -1033,6 +1033,18 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
|
|||||||
QueueAsyncDecode(image, image_id);
|
QueueAsyncDecode(image, image_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if constexpr (requires { image.UploadHostMemory(unswizzle_data_buffer, {}); }) {
|
||||||
|
if (True(image.flags & ImageFlagBits::ReorderableUpload) && image.CanUploadHostMemory()) {
|
||||||
|
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead>
|
||||||
|
swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes,
|
||||||
|
&swizzle_data_buffer);
|
||||||
|
unswizzle_data_buffer.resize_destructive(image.unswizzled_size_bytes);
|
||||||
|
const auto copies = FixSmallVectorADL(UnswizzleImage(
|
||||||
|
*gpu_memory, image.gpu_addr, image.info, swizzle_data, unswizzle_data_buffer));
|
||||||
|
image.UploadHostMemory(unswizzle_data_buffer, copies);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image));
|
auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image));
|
||||||
UploadImageContents(image, staging);
|
UploadImageContents(image, staging);
|
||||||
|
|||||||
@@ -433,6 +433,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
const vk::InstanceDispatch& dld_)
|
const vk::InstanceDispatch& dld_)
|
||||||
: instance{instance_}, dld{dld_}, physical{physical_},
|
: instance{instance_}, dld{dld_}, physical{physical_},
|
||||||
format_properties(GetFormatProperties(physical)) {
|
format_properties(GetFormatProperties(physical)) {
|
||||||
|
const VkPhysicalDeviceMemoryProperties memory_properties =
|
||||||
|
physical.GetMemoryProperties().memoryProperties;
|
||||||
|
is_uma = std::all_of(memory_properties.memoryTypes,
|
||||||
|
memory_properties.memoryTypes + memory_properties.memoryTypeCount,
|
||||||
|
[](const VkMemoryType& type) {
|
||||||
|
return (type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0;
|
||||||
|
});
|
||||||
// Get suitability and device properties.
|
// Get suitability and device properties.
|
||||||
const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{});
|
const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{});
|
||||||
|
|
||||||
@@ -487,15 +494,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize;
|
properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize;
|
||||||
|
|
||||||
is_integrated = properties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU;
|
is_integrated = properties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU;
|
||||||
const VkPhysicalDeviceMemoryProperties memory_properties =
|
|
||||||
physical.GetMemoryProperties().memoryProperties;
|
|
||||||
is_uma = std::all_of(memory_properties.memoryTypes,
|
|
||||||
memory_properties.memoryTypes + memory_properties.memoryTypeCount,
|
|
||||||
[](const VkMemoryType& type) {
|
|
||||||
return (type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ==
|
|
||||||
0 ||
|
|
||||||
(type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
supports_d24_depth =
|
supports_d24_depth =
|
||||||
IsFormatSupported(VK_FORMAT_D24_UNORM_S8_UINT,
|
IsFormatSupported(VK_FORMAT_D24_UNORM_S8_UINT,
|
||||||
@@ -1425,6 +1423,12 @@ void Device::RemoveUnsuitableExtensions() {
|
|||||||
RemoveExtensionFeatureIfUnsuitable(extensions.maintenance5, features.maintenance5,
|
RemoveExtensionFeatureIfUnsuitable(extensions.maintenance5, features.maintenance5,
|
||||||
VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
|
VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
|
||||||
|
|
||||||
|
if (!is_uma || instance_version < VK_API_VERSION_1_3 ||
|
||||||
|
!features.host_image_copy.hostImageCopy) {
|
||||||
|
RemoveExtensionFeature(extensions.host_image_copy, features.host_image_copy,
|
||||||
|
VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
extensions.synchronization2 = features.synchronization2.synchronization2;
|
extensions.synchronization2 = features.synchronization2.synchronization2;
|
||||||
extensions.dynamic_rendering = features.dynamic_rendering.dynamicRendering;
|
extensions.dynamic_rendering = features.dynamic_rendering.dynamicRendering;
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||||||
FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
|
FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
|
||||||
FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
|
FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
|
||||||
FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
|
FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
|
||||||
|
FEATURE(EXT, HostImageCopy, HOST_IMAGE_COPY, host_image_copy) \
|
||||||
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
|
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
|
||||||
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
|
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
|
||||||
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
|
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
|
||||||
@@ -988,6 +989,10 @@ FN_MAX_LIMIT_LIST
|
|||||||
return is_uma;
|
return is_uma;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsHostImageCopySupported() const {
|
||||||
|
return extensions.host_image_copy;
|
||||||
|
}
|
||||||
|
|
||||||
bool HasNullDescriptor() const {
|
bool HasNullDescriptor() const {
|
||||||
return features.robustness2.nullDescriptor;
|
return features.robustness2.nullDescriptor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,8 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
|||||||
X(vkGetPipelineExecutablePropertiesKHR);
|
X(vkGetPipelineExecutablePropertiesKHR);
|
||||||
X(vkGetPipelineExecutableStatisticsKHR);
|
X(vkGetPipelineExecutableStatisticsKHR);
|
||||||
X(vkGetSemaphoreCounterValue);
|
X(vkGetSemaphoreCounterValue);
|
||||||
|
X(vkCopyMemoryToImageEXT);
|
||||||
|
X(vkTransitionImageLayoutEXT);
|
||||||
X(vkQueueBindSparse);
|
X(vkQueueBindSparse);
|
||||||
X(vkQueueSubmit2);
|
X(vkQueueSubmit2);
|
||||||
X(vkResetFences);
|
X(vkResetFences);
|
||||||
|
|||||||
@@ -355,6 +355,8 @@ struct DeviceDispatch : InstanceDispatch {
|
|||||||
PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR{};
|
PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR{};
|
||||||
PFN_vkGetQueryPoolResults vkGetQueryPoolResults{};
|
PFN_vkGetQueryPoolResults vkGetQueryPoolResults{};
|
||||||
PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{};
|
PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{};
|
||||||
|
PFN_vkCopyMemoryToImageEXT vkCopyMemoryToImageEXT{};
|
||||||
|
PFN_vkTransitionImageLayoutEXT vkTransitionImageLayoutEXT{};
|
||||||
PFN_vkQueueBindSparse vkQueueBindSparse{};
|
PFN_vkQueueBindSparse vkQueueBindSparse{};
|
||||||
PFN_vkQueueSubmit2 vkQueueSubmit2{};
|
PFN_vkQueueSubmit2 vkQueueSubmit2{};
|
||||||
PFN_vkResetFences vkResetFences{};
|
PFN_vkResetFences vkResetFences{};
|
||||||
@@ -1092,6 +1094,14 @@ public:
|
|||||||
dld->vkResetQueryPool(handle, query_pool, first, count);
|
dld->vkResetQueryPool(handle, query_pool, first, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CopyMemoryToImageEXT(const VkCopyMemoryToImageInfoEXT& info) const {
|
||||||
|
Check(dld->vkCopyMemoryToImageEXT(handle, &info));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransitionImageLayoutEXT(const VkHostImageLayoutTransitionInfoEXT& info) const {
|
||||||
|
Check(dld->vkTransitionImageLayoutEXT(handle, 1, &info));
|
||||||
|
}
|
||||||
|
|
||||||
VkResult GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
|
VkResult GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
|
||||||
void* data, VkDeviceSize stride,
|
void* data, VkDeviceSize stride,
|
||||||
VkQueryResultFlags flags) const noexcept {
|
VkQueryResultFlags flags) const noexcept {
|
||||||
|
|||||||
Reference in New Issue
Block a user