Skip to content

Instantly share code, notes, and snippets.

@kssreeram
Created April 17, 2024 08:14
Show Gist options
  • Save kssreeram/725c535aadbbd7024730746279b60627 to your computer and use it in GitHub Desktop.
Save kssreeram/725c535aadbbd7024730746279b60627 to your computer and use it in GitHub Desktop.
Testing mouse lag when using Metal
#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