mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Merge upstream changes: nested Metal layers and X11 cursor XFlush
This commit is contained in:
commit
08a2153308
4 files changed
+76
-33
No files matched your search
+1
-1
@@ -82,7 +82,7 @@ Useful probes (build tokenized Python helpers from `build/runtime/tools/`):
|
||||
- `riot-event-coalescing-check`: preserve key events and latest mouse motion.
|
||||
- `m327-cgl-resolver-check`: first calls through dynamically looked-up GL functions retain floating-point arguments; expected `first_sampler_linear=1 first_color_arguments=1 errors=0,0`. Build with `build-m58f327-opengl.py` and run with `run-riot-native-check.py m327-cgl-resolver-check`.
|
||||
- `m327-cgl-direct-check`: with `RCLOL_GPU_DIRECT_OTHER_WINDOW_TEST=1` and an owned 337x339 native target, interleave 338x339 fallback surfaces; expected `active_presenter_preserved=1 fallback_frames=357`. This needs the native presentation fixture; it is not a `run-riot-native-check.py` choice.
|
||||
- `riot-metal-presentation-check`: Vulkan clear reaches the GL texture, including late window/context attachment.
|
||||
- `riot-metal-presentation-check`: Vulkan clear reaches the GL texture, including late window/context attachment. Set `RIOT_PRESENTATION_NESTED=1` to place the Metal layer beneath two ordinary layers and verify that its drawables are still presented and recycled past the three-drawable pool limit.
|
||||
- `riot-metal-concurrency-check`: submission ordering, resource lifetime and concurrent recording.
|
||||
|
||||
`RIOT_PRESENTATION_STRESS=1` makes the presentation probe run 120 frames. With the game and clients closed, force async encoding for this standalone probe using the guest flag `/tmp/metal-async-encode`, then remove it. The tested old path leaked 120 synchronization FDs over 120 frames; the fixed path leaked zero. Inspect the probe's host `/proc/PID/fd` for `anon_inode:syncobj_file` if reproducing that test.
|
||||
|
||||
@@ -218,10 +218,20 @@ The first import of a drawable into the presenting OpenGL context, and the first
|
||||
|
||||
A non-zero error or `FAILED` means the frames cannot reach the window. Before 2026-10-16 the import did not mark the memory as dedicated, which the extension requires; Mesa ignores it, and it is the suspected cause of the NVIDIA black screen (not yet confirmed). An NVIDIA log of 2026-10-16 showed `glTiling=optimal` with `setup_error=0x501 storage_error=0x501 FAILED`: NVIDIA's GL rejects `GL_LINEAR_TILING_EXT`, so the linear Vulkan image could not be imported. Since then the internal image and the GL texture use optimal tiling when `GL_VENDOR` contains NVIDIA (`vkTiling=optimal glTiling=optimal ok` is the expected line); everything else keeps linear. If the game shows a black screen on NVIDIA, report it with this file.
|
||||
|
||||
An additional NVIDIA report on `68f816d` showed successful optimal-tiling import but repeated `Metal Device timed out giving us a Drawable to present` messages. Its [presentation trace](https://github.com/lucannez64/darling-lol/issues/3#issuecomment-6214732591) shows all three drawables queued and all three main-thread presentation callbacks running, followed by timeouts with three frames still queued. The compositor only prepared a Metal layer when it was the root layer, skipping nested Metal layers. It now prepares Metal layers throughout the tree. A standalone nested-layer reproduction exhausts the pool before this correction and passes ten presentations with pixel readback after it; the root-layer probe also passes. Confirmation on the reporter's NVIDIA system is still needed.
|
||||
|
||||
### Host-side checks
|
||||
|
||||
These are host settings, not game settings. Run on AC power, and use a performance power profile (`powerprofilesctl set performance`, or `asusctl profile -P Performance` on ASUS laptops). On the tested desktop the game is limited mostly by its own main thread on the CPU, not by the GPU (see [status](STATUS.md#game-fps-2026-10-1314)), so laptop CPU power limits matter as much as the GPU choice.
|
||||
|
||||
## Mouse latency despite good FPS
|
||||
|
||||
[Issue #10](https://github.com/lucannez64/darling-lol/issues/10) reports mouse latency with FPS comparable to Windows. FPS alone does not measure input-to-display latency, and the report does not yet identify which stage adds the delay.
|
||||
|
||||
The X11 backend previously called `XSync` after every cursor-shape update, waiting for an X server reply on the input thread. It now uses `XFlush` to send the change immediately without waiting. This removes an avoidable round trip during cursor updates. A local manual retest on 2026-10-16 found that the mouse problem no longer occurred with VSync enabled. Input-to-display latency has not been measured, and confirmation from the original reporter is still pending. Rebuild and install with `./scripts/update.sh` after exiting the game and both clients.
|
||||
|
||||
For a manual comparison, keep the scene, FPS cap, display refresh and compositor settings the same. Record whether the delay affects the cursor itself, camera movement, or actions after clicking, and compare the in-game VSync setting on and off. Include the desktop/compositor, X11 or Xwayland session, GPU/driver, monitor refresh and fullscreen/windowed mode in the report. Do not automate game input. The fullscreen cursor-shape problem below is a separate known symptom.
|
||||
|
||||
## Cursor stops changing shape in fullscreen
|
||||
|
||||
On Hyprland the game's cursor can keep its first shape (for example, it never switches to the attack cursor) until you leave and re-enter fullscreen. Darling sends every change to the X server; what freezes the shape on screen is Hyprland's direct scanout of the fullscreen Xwayland window. Turn it off in `hyprland.conf`:
|
||||
|
||||
+51
-30
@@ -2327,7 +2327,16 @@ index 5ae29bf4..358e606b 100644
|
||||
NSLog(@"missing color for %@", colorName);
|
||||
return [NSColor redColor];
|
||||
}
|
||||
@@ -921,15 +1037,9 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -754,4 +870,7 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
XDefineCursor(_display, w, c);
|
||||
- XSync(_display, False);
|
||||
+ /* Send cursor changes immediately without waiting for an X server
|
||||
+ * reply on the input thread. X requests on this connection remain
|
||||
+ * ordered; no server result is consumed here. */
|
||||
+ XFlush(_display);
|
||||
}
|
||||
}
|
||||
@@ -921,15 +1040,9 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
}
|
||||
|
||||
- (NSPoint) mouseLocation {
|
||||
@@ -2345,7 +2354,8 @@ index 5ae29bf4..358e606b 100644
|
||||
}
|
||||
|
||||
- (void) setWindow: (id) window forID: (XID) i {
|
||||
@@ -962,20 +1072,85 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -962,20 +1075,85 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
|
||||
{
|
||||
#ifndef DARLING
|
||||
[[NSRunLoop currentRunLoop] addInputSource: _inputSource forMode: mode];
|
||||
@@ -2436,7 +2446,7 @@ index 5ae29bf4..358e606b 100644
|
||||
}
|
||||
|
||||
- (NSEventModifierFlags) modifierFlagsForState: (unsigned int) state {
|
||||
@@ -1010,17 +1182,93 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1010,17 +1185,93 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2530,7 +2540,7 @@ index 5ae29bf4..358e606b 100644
|
||||
NSEventModifierFlags modifierFlags = [self modifierFlagsForState: ev->xkey.state];
|
||||
char buf[20] = {0};
|
||||
KeySym keySym;
|
||||
@@ -1038,6 +1286,18 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1038,6 +1289,18 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
buf[strLen] = 0;
|
||||
}
|
||||
|
||||
@@ -2549,7 +2559,7 @@ index 5ae29bf4..358e606b 100644
|
||||
id str = [[NSString alloc] initWithCString: buf
|
||||
encoding: NSUTF8StringEncoding];
|
||||
NSPoint pos =
|
||||
@@ -1077,6 +1337,29 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1077,6 +1340,29 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
break;
|
||||
|
||||
case ButtonPress:;
|
||||
@@ -2579,7 +2589,7 @@ index 5ae29bf4..358e606b 100644
|
||||
NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
|
||||
|
||||
if (now - lastClickTimeStamp < [self doubleClickInterval]) {
|
||||
@@ -1096,10 +1379,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1096,10 +1382,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
case Button3:
|
||||
type = NSRightMouseDown;
|
||||
break;
|
||||
@@ -2590,7 +2600,7 @@ index 5ae29bf4..358e606b 100644
|
||||
default:
|
||||
type = NSOtherMouseDown;
|
||||
}
|
||||
@@ -1113,14 +1392,22 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1113,14 +1395,22 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
clickCount: clickCount
|
||||
deltaX: 0.0
|
||||
deltaY: 0.0];
|
||||
@@ -2614,7 +2624,7 @@ index 5ae29bf4..358e606b 100644
|
||||
CGFloat deltaY = 0.0;
|
||||
|
||||
switch (ev->xbutton.button) {
|
||||
@@ -1130,14 +1417,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1130,14 +1420,6 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
case Button3:
|
||||
type = NSRightMouseUp;
|
||||
break;
|
||||
@@ -2629,7 +2639,7 @@ index 5ae29bf4..358e606b 100644
|
||||
default:
|
||||
type = NSOtherMouseUp;
|
||||
}
|
||||
@@ -1151,14 +1430,24 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1151,14 +1433,24 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
clickCount: clickCount
|
||||
deltaX: 0.0
|
||||
deltaY: deltaY];
|
||||
@@ -2655,7 +2665,7 @@ index 5ae29bf4..358e606b 100644
|
||||
|
||||
CGPoint lastMotionPos = [window mouseLocationOutsideOfEventStream];
|
||||
pos = [window
|
||||
@@ -1195,14 +1476,29 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1195,14 +1479,29 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
|
||||
+ [self pointerMoved];
|
||||
if (ev->xmotion.state & Button1Mask) {
|
||||
@@ -2689,7 +2699,7 @@ index 5ae29bf4..358e606b 100644
|
||||
mouseEventWithType: type
|
||||
location: pos
|
||||
modifierFlags:
|
||||
@@ -1211,25 +1507,40 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1211,25 +1510,40 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
clickCount: 1
|
||||
deltaX: deltaX
|
||||
deltaY: deltaY];
|
||||
@@ -2731,7 +2741,7 @@ index 5ae29bf4..358e606b 100644
|
||||
break;
|
||||
|
||||
case FocusIn:
|
||||
@@ -1243,28 +1554,42 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1243,28 +1557,42 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
checkForAppDeactivation: NO];
|
||||
lastFocusedWindow = nil;
|
||||
}
|
||||
@@ -2774,7 +2784,7 @@ index 5ae29bf4..358e606b 100644
|
||||
O2Rect rect = NSMakeRect(ev->xexpose.x, ev->xexpose.y,
|
||||
ev->xexpose.width, ev->xexpose.height);
|
||||
|
||||
@@ -1301,11 +1626,20 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1301,11 +1629,20 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
break;
|
||||
|
||||
case UnmapNotify:
|
||||
@@ -2799,7 +2809,7 @@ index 5ae29bf4..358e606b 100644
|
||||
break;
|
||||
|
||||
case MapRequest:
|
||||
@@ -1321,6 +1655,7 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1321,6 +1658,7 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
[delegate platformWindow: window
|
||||
frameChanged: [window frame]
|
||||
didSize: YES];
|
||||
@@ -2807,7 +2817,7 @@ index 5ae29bf4..358e606b 100644
|
||||
break;
|
||||
|
||||
case ConfigureRequest:
|
||||
@@ -1344,6 +1679,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
@@ -1344,6 +1682,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) {
|
||||
break;
|
||||
|
||||
case PropertyNotify:
|
||||
@@ -2816,7 +2826,7 @@ index 5ae29bf4..358e606b 100644
|
||||
if ([window respondsToSelector: @selector(propertyNotify:)]) {
|
||||
[window propertyNotify: &ev->xproperty];
|
||||
}
|
||||
@@ -1483,6 +1820,64 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
||||
@@ -1483,6 +1823,64 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
||||
XSync(_display, False);
|
||||
}
|
||||
|
||||
@@ -2881,7 +2891,7 @@ index 5ae29bf4..358e606b 100644
|
||||
- (void) grabMouse: (BOOL) doGrab {
|
||||
if (doGrab) {
|
||||
NSWindow *nswin = [NSApp keyWindow];
|
||||
@@ -1495,9 +1890,10 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
||||
@@ -1495,9 +1893,10 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
||||
const unsigned int mask = ButtonPressMask | ButtonReleaseMask |
|
||||
PointerMotionMask | FocusChangeMask;
|
||||
int result = XGrabPointer(_display, win, False, mask, GrabModeAsync,
|
||||
@@ -2893,7 +2903,7 @@ index 5ae29bf4..358e606b 100644
|
||||
NSLog(@"XGrabPointer() succeeded for window %lu\n", win);
|
||||
|
||||
NSRect frame = [xwin transformFrame: nswin.frame];
|
||||
@@ -1522,6 +1918,9 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
||||
@@ -1522,6 +1921,9 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top,
|
||||
} else {
|
||||
XUngrabPointer(_display, CurrentTime);
|
||||
_cursorGrabbed = NO;
|
||||
@@ -6877,7 +6887,7 @@ index 5dcac115..5de4f03f 100644
|
||||
+
|
||||
@end
|
||||
diff --git a/QuartzCore/CALayerContext.m b/QuartzCore/CALayerContext.m
|
||||
index 5dce8c56..e329b5f8 100644
|
||||
index 5dce8c56..dee2b80c 100644
|
||||
--- a/QuartzCore/CALayerContext.m
|
||||
+++ b/QuartzCore/CALayerContext.m
|
||||
@@ -1,47 +1,58 @@
|
||||
@@ -7033,18 +7043,30 @@ index 5dce8c56..e329b5f8 100644
|
||||
}
|
||||
|
||||
- (void) assignTextureIdsToLayerTree: (CALayer *) layer {
|
||||
@@ -93,7 +148,10 @@
|
||||
@@ -93,7 +148,22 @@
|
||||
[self assignTextureIdsToLayerTree: child];
|
||||
}
|
||||
|
||||
+extern void darlingQCTiming(const char* what, double seconds);
|
||||
+
|
||||
+- (void) prepareMetalLayers: (CALayer *) layer {
|
||||
+ // Metal layers may be nested beneath ordinary backing layers. Every queued
|
||||
+ // drawable must be consumed before CARenderer samples the layer tree.
|
||||
+ if ([[layer class] isSubclassOfClass: [CAMetalLayerInternal class]]) {
|
||||
+ CFTimeInterval prepStart = CACurrentMediaTime();
|
||||
+ [(CAMetalLayerInternal*)layer prepareRender];
|
||||
+ darlingQCTiming("prepareRenderTotal", CACurrentMediaTime() - prepStart);
|
||||
+ }
|
||||
+ for (CALayer *child in layer.sublayers)
|
||||
+ [self prepareMetalLayers: child];
|
||||
+}
|
||||
+
|
||||
- (void) renderLayer: (CALayer *) layer {
|
||||
+ CFTimeInterval renderLayerStart = CACurrentMediaTime();
|
||||
CGLContextMakeCurrentAndAttachToWindow(_glContext, _cglWindow);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
@@ -118,27 +176,69 @@
|
||||
@@ -118,27 +188,63 @@
|
||||
|
||||
[_deleteTextureIds removeAllObjects];
|
||||
|
||||
@@ -7053,13 +7075,12 @@ index 5dce8c56..e329b5f8 100644
|
||||
[self assignTextureIdsToLayerTree: layer];
|
||||
+ darlingQCTiming("assignTextureIds", CACurrentMediaTime() - assignStart);
|
||||
|
||||
// this is where the Metal layer renders to an internal texture for us to use
|
||||
if ([[layer class] isSubclassOfClass: [CAMetalLayerInternal class]]) {
|
||||
CAMetalLayerInternal* mtl = (CAMetalLayerInternal*)layer;
|
||||
+ CFTimeInterval prepStart = CACurrentMediaTime();
|
||||
[mtl prepareRender];
|
||||
+ darlingQCTiming("prepareRenderTotal", CACurrentMediaTime() - prepStart);
|
||||
}
|
||||
- // this is where the Metal layer renders to an internal texture for us to use
|
||||
- if ([[layer class] isSubclassOfClass: [CAMetalLayerInternal class]]) {
|
||||
- CAMetalLayerInternal* mtl = (CAMetalLayerInternal*)layer;
|
||||
- [mtl prepareRender];
|
||||
- }
|
||||
+ [self prepareMetalLayers: layer];
|
||||
|
||||
+ CFTimeInterval rendererStart = CACurrentMediaTime();
|
||||
[_renderer render];
|
||||
@@ -7114,7 +7135,7 @@ index 5dce8c56..e329b5f8 100644
|
||||
}
|
||||
|
||||
- (void) startTimerIfNeeded {
|
||||
@@ -147,7 +247,7 @@
|
||||
@@ -147,7 +253,7 @@
|
||||
target: self
|
||||
selector: @selector(timer:)
|
||||
userInfo: nil
|
||||
@@ -7123,7 +7144,7 @@ index 5dce8c56..e329b5f8 100644
|
||||
}
|
||||
|
||||
- (void) deleteTextureId: (NSNumber *) textureId {
|
||||
@@ -155,7 +255,10 @@
|
||||
@@ -155,7 +261,10 @@
|
||||
}
|
||||
|
||||
- (void) flush {
|
||||
|
||||
@@ -40,7 +40,18 @@ int main() {
|
||||
layer.frame = CGRectMake(0,0,160,96);
|
||||
NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0,0,160,96)];
|
||||
view.wantsLayer = YES;
|
||||
view.layer = layer;
|
||||
const bool nested = getenv("RIOT_PRESENTATION_NESTED") != nullptr;
|
||||
if (nested) {
|
||||
CALayer* root = [CALayer layer];
|
||||
root.frame = layer.frame;
|
||||
CALayer* container = [CALayer layer];
|
||||
container.frame = layer.frame;
|
||||
[container addSublayer:layer];
|
||||
[root addSublayer:container];
|
||||
view.layer = root;
|
||||
} else {
|
||||
view.layer = layer;
|
||||
}
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(50,50,160,96)
|
||||
styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO];
|
||||
window.title = @"Metal presentation check";
|
||||
@@ -92,7 +103,8 @@ int main() {
|
||||
fprintf(stderr, "PASS: bounded presentation capture rearmed without restarting\n");
|
||||
}
|
||||
fprintf(stderr, "PASS: Vulkan-to-OpenGL presentation pixels\n");
|
||||
fprintf(stderr, "PASS: late context attachment and %u Metal drawable presentations\n", frameCount);
|
||||
fprintf(stderr, "PASS: %s layer, late context attachment and %u Metal drawable presentations\n",
|
||||
nested ? "nested" : "root", frameCount);
|
||||
[window orderOut:nil];
|
||||
[queue release]; [view release]; [window release]; [device release];
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user