Merge branch 'perfMode' into Integration
This commit is contained in:
commit
48558316d1
7 files changed
+71
-31
No files matched your search
@@ -3537,6 +3537,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"PerfModeTip" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "- Changing the rendering mode requires restarting the application to ensure a clean start.\n\n- Performance mode is an experimental feature and cannot guarantee a positive impact on the streaming experience.\n\n- HDR under performance mode for devices of limited dynamic range like iPad mini(LCD display) could be broken.\n\n- Tapping “Quit Now” means I acknowledge the above information."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "- 更改渲染模式需要重启应用以确保干净启动。\n\n- 性能模式为实验性功能,无法保证开启后对串流体验产生正向优化。 \n\n- 有限动态范围设备,如采用LCD的iPad mini,开启性能模式后可能无法正常显示HDR画面。\n\n- 点击“立即退出”代表我已知悉以上内容。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "- 更改渲染模式需要重新啟動應用程式以確保乾淨啟動。\n\n- 效能模式為實驗性功能,無法保證開啟後對串流體驗產生正向優化。\n\n- 有限動態範圍裝置,如採用 LCD 的 iPad mini,開啟效能模式後可能無法正常顯示 HDR 畫面。\n\n- 點擊「立即退出」代表我已知悉以上內容。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "- 更改渲染模式需要重新啟動應用程式以確保乾淨啟動。\n\n- 效能模式為實驗性功能,無法保證開啟後對串流體驗產生正向優化。\n\n- 有限動態範圍裝置,如採用 LCD 的 iPad mini,開啟效能模式後可能無法正常顯示 HDR 畫面。\n\n- 點擊「立即退出」代表我已知悉以上內容。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Performance" : {
|
"Performance" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -4275,19 +4304,19 @@
|
|||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "需要重启"
|
"value" : "需要重启App"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hant" : {
|
"zh-Hant" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "需要重新啟動"
|
"value" : "需要重新啟動App"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-HK" : {
|
"zh-HK" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "需要重新啟動"
|
"value" : "需要重新啟動App"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#import "ConnectionCallbacks.h"
|
#import "ConnectionCallbacks.h"
|
||||||
#import "Frame.h"
|
#import "Frame.h"
|
||||||
#import "Plot.h"
|
#import "Plot.h"
|
||||||
|
#import "TemporarySettings.h"
|
||||||
|
|
||||||
@interface MetalVideoRenderer : NSObject
|
@interface MetalVideoRenderer : NSObject
|
||||||
|
|
||||||
@@ -23,7 +24,9 @@
|
|||||||
@property (atomic) BOOL isStopping;
|
@property (atomic) BOOL isStopping;
|
||||||
@property (nonatomic) BOOL hdrEnabled;
|
@property (nonatomic) BOOL hdrEnabled;
|
||||||
|
|
||||||
- (nonnull instancetype)initWithMetalDevice:(nonnull id<MTLDevice>)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat framerate:(float)framerate hdrEnabled:(BOOL)hdrEnabled;
|
- (instancetype _Nonnull )initWithMetalDevice:(id<MTLDevice>_Nonnull)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat settings:(TemporarySettings* _Nonnull )currentSettings;
|
||||||
|
|
||||||
|
|
||||||
- (void)renderFrame:(nonnull Frame *)frame toLayer:(nonnull CAMetalLayer *)layer;
|
- (void)renderFrame:(nonnull Frame *)frame toLayer:(nonnull CAMetalLayer *)layer;
|
||||||
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer API_AVAILABLE(ios(13.0));
|
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer API_AVAILABLE(ios(13.0));
|
||||||
- (void)drawableResize:(CGSize)drawableSize;
|
- (void)drawableResize:(CGSize)drawableSize;
|
||||||
|
|||||||
@@ -130,21 +130,24 @@ CFStringRef __currentColorSpace;
|
|||||||
size_t _lastDrawableHeight;
|
size_t _lastDrawableHeight;
|
||||||
id<MTLBuffer> _CscParamsBuffer;
|
id<MTLBuffer> _CscParamsBuffer;
|
||||||
id<MTLBuffer> _VideoVertexBuffer;
|
id<MTLBuffer> _VideoVertexBuffer;
|
||||||
|
|
||||||
|
CFStringRef _nonFullHdrColorSpace;
|
||||||
|
MTLPixelFormat _nonFullHdrPixelFormat;
|
||||||
|
|
||||||
// https://developer.apple.com/documentation/metal/synchronizing-cpu-and-gpu-work?language=objc
|
// https://developer.apple.com/documentation/metal/synchronizing-cpu-and-gpu-work?language=objc
|
||||||
dispatch_semaphore_t _inFlightSemaphore;
|
dispatch_semaphore_t _inFlightSemaphore;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithMetalDevice:(id<MTLDevice>)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat framerate:(float)framerate hdrEnabled:(BOOL)hdrEnabled {
|
- (instancetype)initWithMetalDevice:(id<MTLDevice>)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat settings:(TemporarySettings* )currentSettings {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
_sq = dispatch_queue_create("com.moonlight.MetalVideoRenderer",
|
_sq = dispatch_queue_create("com.moonlight.MetalVideoRenderer",
|
||||||
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0));
|
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0));
|
||||||
_averageGPUTime = (1.0f / framerate) / 2;
|
_averageGPUTime = (1.0f / currentSettings.framerate.floatValue) / 2;
|
||||||
_device = device;
|
_device = device;
|
||||||
_colorPixelFormat = drawablePixelFormat;
|
_colorPixelFormat = drawablePixelFormat;
|
||||||
_framerate = framerate;
|
_framerate = currentSettings.framerate.floatValue;
|
||||||
_hdrEnabled = hdrEnabled;
|
_hdrEnabled = currentSettings.enableHdr;
|
||||||
_commandQueue = [_device newCommandQueue];
|
_commandQueue = [_device newCommandQueue];
|
||||||
_currentEDRHeadroom = 1.0f;
|
_currentEDRHeadroom = 1.0f;
|
||||||
_lastColorSpace = -1;
|
_lastColorSpace = -1;
|
||||||
@@ -170,6 +173,10 @@ CFStringRef __currentColorSpace;
|
|||||||
_renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0);
|
_renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0);
|
||||||
_renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
_renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||||
|
|
||||||
|
if (@available(iOS 14.0, *)) _nonFullHdrColorSpace = kCGColorSpaceITUR_2100_PQ;
|
||||||
|
else _nonFullHdrColorSpace = kCGColorSpaceITUR_2020;
|
||||||
|
_nonFullHdrPixelFormat = MTLPixelFormatRGBA16Float;
|
||||||
|
|
||||||
// Initialize texture array to NULL
|
// Initialize texture array to NULL
|
||||||
for (int i = 0; i < MAX_VIDEO_PLANES; i++) {
|
for (int i = 0; i < MAX_VIDEO_PLANES; i++) {
|
||||||
_cvMetalTextures[i] = NULL;
|
_cvMetalTextures[i] = NULL;
|
||||||
@@ -399,16 +406,12 @@ CFStringRef __currentColorSpace;
|
|||||||
CFStringRef frame_trc = CFDictionaryGetValue(ext, kCVImageBufferTransferFunctionKey);
|
CFStringRef frame_trc = CFDictionaryGetValue(ext, kCVImageBufferTransferFunctionKey);
|
||||||
if (CFEqual(frame_trc, kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ)) {
|
if (CFEqual(frame_trc, kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ)) {
|
||||||
isHDR = YES;
|
isHDR = YES;
|
||||||
if (@available(iOS 14.0, *)) {
|
newColorSpace = CGColorSpaceCreateWithName(_nonFullHdrColorSpace);
|
||||||
newColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2100_PQ);
|
newPixelFormat = _nonFullHdrPixelFormat;
|
||||||
} else {
|
|
||||||
newColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020);
|
|
||||||
}
|
|
||||||
newPixelFormat = MTLPixelFormatBGRA10_XR;
|
|
||||||
} else {
|
} else {
|
||||||
// SDR 2020, I'm not sure it's possible to stream this though
|
// SDR 2020, I'm not sure it's possible to stream this though
|
||||||
newColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020);
|
newColorSpace = CGColorSpaceCreateWithName(_nonFullHdrColorSpace);
|
||||||
newPixelFormat = MTLPixelFormatBGRA10_XR;
|
newPixelFormat = _nonFullHdrPixelFormat;
|
||||||
}
|
}
|
||||||
if (isHDR) {
|
if (isHDR) {
|
||||||
paramBuffer.cscParams = (fullRange ? k_CscParams_Bt2020Full_10bit : k_CscParams_Bt2020Lim_10bit);
|
paramBuffer.cscParams = (fullRange ? k_CscParams_Bt2020Full_10bit : k_CscParams_Bt2020Lim_10bit);
|
||||||
@@ -494,6 +497,10 @@ CFStringRef __currentColorSpace;
|
|||||||
_lastColorSpace = colorspace;
|
_lastColorSpace = colorspace;
|
||||||
_lastFullRange = fullRange;
|
_lastFullRange = fullRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NSLog(@"layer.pixelFormat %lu", (unsigned long)layer.pixelFormat);
|
||||||
|
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,14 @@
|
|||||||
#import "ImGuiRenderer.h"
|
#import "ImGuiRenderer.h"
|
||||||
#import "MetalVideoRenderer.h"
|
#import "MetalVideoRenderer.h"
|
||||||
#import "MetalView.h"
|
#import "MetalView.h"
|
||||||
|
#import "TemporarySettings.h"
|
||||||
|
|
||||||
@interface MetalViewController : UIViewController <MetalViewDelegate>
|
@interface MetalViewController : UIViewController <MetalViewDelegate>
|
||||||
|
|
||||||
@property (nonatomic) CGRect bounds;
|
@property (nonatomic) CGRect bounds;
|
||||||
|
|
||||||
- (nonnull instancetype)initWithFrame:(CGRect)bounds
|
- (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate settings:(TemporarySettings* _Nonnull )settings metricsHandler:(MetricsHandler _Nonnull)metricsHandler;
|
||||||
framerate:(float)framerate
|
|
||||||
enableHdr:(BOOL)enableHdr
|
|
||||||
metricsHandler:(MetricsHandler _Nonnull)metricsHandler;
|
|
||||||
|
|
||||||
- (void)pauseRendering;
|
- (void)pauseRendering;
|
||||||
- (void)resumeRendering;
|
- (void)resumeRendering;
|
||||||
|
|||||||
@@ -14,20 +14,20 @@
|
|||||||
@implementation MetalViewController {
|
@implementation MetalViewController {
|
||||||
FrameQueue *_frameQueue;
|
FrameQueue *_frameQueue;
|
||||||
float _framerate;
|
float _framerate;
|
||||||
BOOL _enableHdr;
|
TemporarySettings* _currentSettings;
|
||||||
MetalView *_metalView;
|
MetalView *_metalView;
|
||||||
MetalVideoRenderer *_renderer;
|
MetalVideoRenderer *_renderer;
|
||||||
MetricsHandler _metricsHandler;
|
MetricsHandler _metricsHandler;
|
||||||
CADisplayLink *_displayLink;
|
CADisplayLink *_displayLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate enableHdr:(BOOL)enableHdr metricsHandler:(MetricsHandler)metricsHandler {
|
- (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate settings:(TemporarySettings* )settings metricsHandler:(MetricsHandler)metricsHandler {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
_bounds = bounds;
|
_bounds = bounds;
|
||||||
_frameQueue = [FrameQueue sharedInstance];
|
_frameQueue = [FrameQueue sharedInstance];
|
||||||
_framerate = framerate;
|
_framerate = framerate;
|
||||||
_enableHdr = enableHdr;
|
_currentSettings = settings;
|
||||||
_metricsHandler = metricsHandler;
|
_metricsHandler = metricsHandler;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
Log(LOG_W, @"Running on iOS Simulator, using BGRA8Unorm pixel format");
|
Log(LOG_W, @"Running on iOS Simulator, using BGRA8Unorm pixel format");
|
||||||
#else
|
#else
|
||||||
// On real devices, check if we should enable HDR
|
// On real devices, check if we should enable HDR
|
||||||
if (_enableHdr) {
|
if (_currentSettings.enableHdr) {
|
||||||
pixelFormat = MTLPixelFormatBGRA10_XR;
|
pixelFormat = MTLPixelFormatBGRA10_XR;
|
||||||
Log(LOG_I, @"HDR enabled, using BGRA10_XR pixel format");
|
Log(LOG_I, @"HDR enabled, using BGRA10_XR pixel format");
|
||||||
} else {
|
} else {
|
||||||
@@ -80,8 +80,7 @@
|
|||||||
// Initialize the renderer.
|
// Initialize the renderer.
|
||||||
MetalVideoRenderer *renderer = [[MetalVideoRenderer alloc] initWithMetalDevice:device
|
MetalVideoRenderer *renderer = [[MetalVideoRenderer alloc] initWithMetalDevice:device
|
||||||
drawablePixelFormat:pixelFormat
|
drawablePixelFormat:pixelFormat
|
||||||
framerate:self->_framerate
|
settings:_currentSettings];
|
||||||
hdrEnabled:self->_enableHdr];
|
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
Log(LOG_E, @"The renderer couldn't be initialized.");
|
Log(LOG_E, @"The renderer couldn't be initialized.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
MenuSectionView *touchAndControlSection;
|
MenuSectionView *touchAndControlSection;
|
||||||
MenuSectionView *videoSection;
|
MenuSectionView *videoSection;
|
||||||
MenuSectionView *otherSection;
|
MenuSectionView *otherSection;
|
||||||
|
MenuSectionView *experimentalSection;
|
||||||
NSMutableSet* hiddenStacks;
|
NSMutableSet* hiddenStacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,7 +571,6 @@ BOOL isCustomResolution(int resolutionSelected) {
|
|||||||
[self addSetting:self.pipStack ofId:@"pipStack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
|
[self addSetting:self.pipStack ofId:@"pipStack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
|
||||||
[self addSetting:self.framePacingStack ofId:@"framePacingStack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
|
[self addSetting:self.framePacingStack ofId:@"framePacingStack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
|
||||||
[self addSetting:self.frameQueueSizeStack ofId:@"frameQueueSizeStack" withInfoTag:NO withDynamicLabel:YES to:videoSection];
|
[self addSetting:self.frameQueueSizeStack ofId:@"frameQueueSizeStack" withInfoTag:NO withDynamicLabel:YES to:videoSection];
|
||||||
[self addSetting:self.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
|
|
||||||
|
|
||||||
[videoSection addToParentStack:_parentStack];
|
[videoSection addToParentStack:_parentStack];
|
||||||
[videoSection setExpanded:YES];
|
[videoSection setExpanded:YES];
|
||||||
@@ -657,13 +657,16 @@ BOOL isCustomResolution(int resolutionSelected) {
|
|||||||
[otherSection setExpanded:YES];
|
[otherSection setExpanded:YES];
|
||||||
|
|
||||||
|
|
||||||
MenuSectionView *experimentalSection = [[MenuSectionView alloc] init];
|
experimentalSection = [[MenuSectionView alloc] init];
|
||||||
experimentalSection.delegate = self;
|
experimentalSection.delegate = self;
|
||||||
experimentalSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Experimental"];
|
experimentalSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Experimental"];
|
||||||
if (@available(iOS 13.0, *)) {
|
if (@available(iOS 13.0, *)) {
|
||||||
[experimentalSection setSectionWithIcon:[UIImage imageNamed:@"flask"] andSize:20];
|
[experimentalSection setSectionWithIcon:[UIImage imageNamed:@"flask"] andSize:20];
|
||||||
}
|
}
|
||||||
[self addSetting:self.touchMoveEventIntervalStack ofId:@"touchMoveEventIntervalStack" withInfoTag:NO withDynamicLabel:YES to:experimentalSection];
|
[self addSetting:self.touchMoveEventIntervalStack ofId:@"touchMoveEventIntervalStack" withInfoTag:NO withDynamicLabel:YES to:experimentalSection];
|
||||||
|
|
||||||
|
[self addSetting:self.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection];
|
||||||
|
|
||||||
[experimentalSection addToParentStack:_parentStack];
|
[experimentalSection addToParentStack:_parentStack];
|
||||||
[experimentalSection setExpanded:YES];
|
[experimentalSection setExpanded:YES];
|
||||||
}
|
}
|
||||||
@@ -1300,6 +1303,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
|||||||
|
|
||||||
- (void)viewDidLoad {
|
- (void)viewDidLoad {
|
||||||
//[self updateTheme];
|
//[self updateTheme];
|
||||||
|
|
||||||
settingStackWillBeRelocatedToLowestPosition = false;
|
settingStackWillBeRelocatedToLowestPosition = false;
|
||||||
hiddenStacks = [[NSMutableSet alloc] init];
|
hiddenStacks = [[NSMutableSet alloc] init];
|
||||||
|
|
||||||
@@ -1710,12 +1714,11 @@ BOOL isCustomResolution(int resolutionSelected) {
|
|||||||
DataManager* dataMan = [[DataManager alloc] init];
|
DataManager* dataMan = [[DataManager alloc] init];
|
||||||
TemporarySettings* currentSettings = [dataMan getSettings];
|
TemporarySettings* currentSettings = [dataMan getSettings];
|
||||||
NSInteger previousBackend = [currentSettings.renderingBackend integerValue];
|
NSInteger previousBackend = [currentSettings.renderingBackend integerValue];
|
||||||
|
|
||||||
|
|
||||||
// Check if the rendering backend has actually changed
|
// Check if the rendering backend has actually changed
|
||||||
if (previousBackend != sender.selectedSegmentIndex) {
|
if (previousBackend != sender.selectedSegmentIndex) {
|
||||||
// Show alert to prompt user to restart the app
|
// Show alert to prompt user to restart the app
|
||||||
NSString *message = [LocalizationHelper localizedStringForKey:@"Rendering mode change requires app restart"];
|
NSString *message = [LocalizationHelper localizedStringForKey: sender.selectedSegmentIndex == 1 ? @"PerfModeTip" : @"Rendering mode change requires app restart"];
|
||||||
|
|
||||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Restart Required"]
|
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Restart Required"]
|
||||||
message:message
|
message:message
|
||||||
|
|||||||
@@ -714,7 +714,7 @@
|
|||||||
Log(LOG_I, @"StreamFrameViewController creating MetalViewController");
|
Log(LOG_I, @"StreamFrameViewController creating MetalViewController");
|
||||||
self.metalViewController = [[MetalViewController alloc] initWithFrame:self.view.bounds
|
self.metalViewController = [[MetalViewController alloc] initWithFrame:self.view.bounds
|
||||||
framerate:[self->_settings.framerate floatValue]
|
framerate:[self->_settings.framerate floatValue]
|
||||||
enableHdr:self->_settings.enableHdr
|
settings:self->_settings
|
||||||
metricsHandler:self.imguiView.metricsHandler];
|
metricsHandler:self.imguiView.metricsHandler];
|
||||||
self.metalViewController.view.userInteractionEnabled = NO;
|
self.metalViewController.view.userInteractionEnabled = NO;
|
||||||
[self addChildViewController:self.metalViewController];
|
[self addChildViewController:self.metalViewController];
|
||||||
|
|||||||
Reference in new issue
Block a user