Created
April 17, 2024 08:14
-
-
Save kssreeram/725c535aadbbd7024730746279b60627 to your computer and use it in GitHub Desktop.
Testing mouse lag when using Metal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Cocoa/Cocoa.h> | |
#import <MetalKit/MetalKit.h> | |
// | |
// Compile and run: | |
// | |
// clang -O2 -fobjc-arc metal-mouse-lag.m -framework AppKit -framework Metal -framework MetalKit | |
// ./a.out | |
// | |
// | |
// Renderer | |
// | |
typedef struct { | |
id<MTLRenderPipelineState> pipeline; | |
} Renderer; | |
static void initializeRenderer(Renderer *ren, | |
id<MTLDevice> device, | |
MTLPixelFormat pixelFormat) | |
{ | |
NSString *shader = | |
@"#include <metal_stdlib>\n" | |
"using namespace metal;\n" | |
"struct VSIn {\n" | |
" packed_float2 position;\n" | |
" packed_float4 color;\n" | |
"};\n" | |
"struct VSOut {\n" | |
" float4 position [[position]];\n" | |
" float4 color;\n" | |
"};\n" | |
"vertex VSOut vertex_main(\n" | |
" device const VSIn *vertices [[buffer(0)]],\n" | |
" uint vid [[vertex_id]])\n" | |
"{\n" | |
" VSIn vin = vertices[vid];\n" | |
" VSOut vout;\n" | |
" vout.position = float4(vin.position, 1, 1);\n" | |
" vout.color = vin.color;\n" | |
" return vout;\n" | |
"}\n" | |
"fragment float4 fragment_main(\n" | |
" VSOut vert [[stage_in]])\n" | |
"{\n" | |
" return vert.color;\n" | |
"}\n"; | |
id<MTLLibrary> library = [device newLibraryWithSource:shader | |
options:nil | |
error:nil]; | |
assert(library); | |
MTLRenderPipelineDescriptor *desc = [[MTLRenderPipelineDescriptor alloc] init]; | |
desc.vertexFunction = [library newFunctionWithName:@"vertex_main"]; | |
desc.fragmentFunction = [library newFunctionWithName:@"fragment_main"]; | |
desc.colorAttachments[0].pixelFormat = pixelFormat; | |
ren->pipeline = [device newRenderPipelineStateWithDescriptor:desc error:nil]; | |
assert(ren->pipeline); | |
} | |
typedef struct { | |
packed_float2 position; | |
packed_float4 color; | |
} Vertex; | |
static void rectToNDC(NSRect r, NSSize size, | |
NSPoint *ndcP1, NSPoint *ndcP2) | |
{ | |
NSPoint p1 = r.origin; | |
NSPoint p2 = NSMakePoint(p1.x + r.size.width, p1.y + r.size.height); | |
*ndcP1 = NSMakePoint((p1.x/size.width)*2 - 1, (p1.y/size.height)*2 - 1); | |
*ndcP2 = NSMakePoint((p2.x/size.width)*2 - 1, (p2.y/size.height)*2 - 1); | |
} | |
static void renderRects(Renderer *ren, | |
id<MTLRenderCommandEncoder> encoder, | |
NSSize viewSize, NSRect rect1, NSRect rect2) | |
{ | |
NSPoint r1p1, r1p2; | |
rectToNDC(rect1, viewSize, &r1p1, &r1p2); | |
NSPoint r2p1, r2p2; | |
rectToNDC(rect2, viewSize, &r2p1, &r2p2); | |
[encoder setRenderPipelineState:ren->pipeline]; | |
packed_float4 color1 = {1, 0, 0, 1}; | |
packed_float4 color2 = {0, 1, 0, 1}; | |
Vertex vertices[] = { | |
{{r1p1.x, r1p1.y}, color1}, | |
{{r1p2.x, r1p1.y}, color1}, | |
{{r1p2.x, r1p2.y}, color1}, | |
{{r1p1.x, r1p1.y}, color1}, | |
{{r1p2.x, r1p2.y}, color1}, | |
{{r1p1.x, r1p2.y}, color1}, | |
{{r2p1.x, r2p1.y}, color2}, | |
{{r2p2.x, r2p1.y}, color2}, | |
{{r2p2.x, r2p2.y}, color2}, | |
{{r2p1.x, r2p1.y}, color2}, | |
{{r2p2.x, r2p2.y}, color2}, | |
{{r2p1.x, r2p2.y}, color2}, | |
}; | |
NSUInteger nVertexBytes = sizeof(vertices); | |
NSUInteger nVertices = nVertexBytes / sizeof(Vertex); | |
[encoder setVertexBytes:vertices length:nVertexBytes atIndex:0]; | |
[encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:nVertices]; | |
[encoder endEncoding]; | |
} | |
// | |
// MainView | |
// | |
@interface MainView : MTKView | |
@end | |
@implementation MainView { | |
NSRect _rect1; | |
NSRect _rect2; | |
id<MTLCommandQueue> _queue; | |
Renderer _renderer; | |
} | |
- (instancetype)initWithFrame:(CGRect)r device:(id<MTLDevice>)device { | |
self = [super initWithFrame:r device:device]; | |
[self setupState]; | |
return self; | |
} | |
- (void)setupState { | |
_rect1 = NSMakeRect(50, 50, 100, 100); | |
_rect2 = NSMakeRect(50, 50, 100, 100); | |
// Setup tracking-area to receive mouseMoved events. | |
NSTrackingAreaOptions options = | |
NSTrackingInVisibleRect | | |
NSTrackingActiveInKeyWindow | | |
NSTrackingMouseMoved; | |
NSTrackingArea *tracking = [[NSTrackingArea alloc] initWithRect:NSZeroRect | |
options:options | |
owner:self | |
userInfo:nil]; | |
[self addTrackingArea:tracking]; | |
_queue = [self.device newCommandQueue]; | |
initializeRenderer(&_renderer, self.device, self.colorPixelFormat); | |
} | |
- (void)mouseMoved:(NSEvent *)event { | |
NSPoint p = [self convertPoint:event.locationInWindow fromView:nil]; | |
_rect2.origin = NSMakePoint(p.x - _rect2.size.width/2, p.y - _rect2.size.height/2); | |
[self setNeedsDisplay:YES]; | |
} | |
- (void)displayTimer { | |
_rect1 = _rect2; | |
[self setNeedsDisplay:YES]; | |
} | |
- (void)drawRect:(NSRect)dirty { | |
MTLRenderPassDescriptor *pass = self.currentRenderPassDescriptor; | |
if (!pass) { | |
return; | |
} | |
id<MTLCommandBuffer> commands = [_queue commandBuffer]; | |
id<MTLRenderCommandEncoder> encoder = [commands renderCommandEncoderWithDescriptor:pass]; | |
renderRects(&_renderer, encoder, | |
self.bounds.size, _rect1, _rect2); | |
[commands presentDrawable:self.currentDrawable]; | |
[commands commit]; | |
} | |
@end | |
// | |
// TimerView | |
// AppDelegate | |
// main | |
// | |
@interface TimerView : MTKView { | |
@public MainView *mainView; | |
} | |
@end | |
@implementation TimerView | |
- (void)drawRect:(NSRect)dirty { | |
[mainView displayTimer]; | |
} | |
@end | |
@interface AppDelegate : NSObject<NSApplicationDelegate> | |
@end | |
@implementation AppDelegate | |
-(void)applicationDidFinishLaunching:(NSNotification *)notification { | |
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 500, 500) | |
styleMask:NSWindowStyleMaskTitled | |
backing:NSBackingStoreBuffered | |
defer:YES]; | |
window.title = @"Test Mouse Events"; | |
id<MTLDevice> device = MTLCreateSystemDefaultDevice(); | |
// Setup dummy MTKView to drive display timer. | |
NSRect timerFrame = NSMakeRect(0, 0, 1, 1); | |
TimerView *timerView = [[TimerView alloc] initWithFrame:timerFrame device:device]; | |
[window.contentView addSubview:timerView]; | |
// Setup main MTKView. | |
NSRect frame = window.contentView.bounds; | |
MainView *mainView = [[MainView alloc] initWithFrame:frame device:device]; | |
mainView.clearColor = MTLClearColorMake(1, 1, 1, 1); | |
mainView.paused = YES; | |
mainView.enableSetNeedsDisplay = YES; | |
[window.contentView addSubview:mainView]; | |
timerView->mainView = mainView; | |
[window makeKeyAndOrderFront:nil]; | |
} | |
@end | |
int main(int argc, const char * argv[]) { | |
@autoreleasepool { | |
NSApplication *app = NSApplication.sharedApplication; | |
AppDelegate *delegate = [[AppDelegate alloc] init]; | |
app.delegate = delegate; | |
app.activationPolicy = NSApplicationActivationPolicyRegular; | |
[app run]; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment