mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Merge pull request #88 from lucannez64/fix/x11-vrr
Fix Metal VSync and VRR presentation together
This commit is contained in:
10 files changed
+741
-80
No files matched your search
@@ -623,3 +623,76 @@ launch, window placement, click alignment and edge panning before rollout.
|
||||
|
||||
After installing the tested X11 and CoreGraphics binaries locally, the user
|
||||
reported that everything was working (2026-10-16). Fullscreen policy was unchanged.
|
||||
|
||||
## VRR presentation regression (#46)
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s tests -p 'test_x11_vrr.py'
|
||||
```
|
||||
|
||||
The host fixtures exercise the patched eligibility, X11 property updates and
|
||||
EGL surface ownership transitions. They cover hidden/titled/partial views,
|
||||
multiple views, parent recreation, child fallback and current-context handling.
|
||||
|
||||
Build `build-riot-coregraphics-runtime.py`, `build-riot-x11-runtime.py` and
|
||||
`build-riot-metal-presentation.py` together. Stage all three updated frameworks
|
||||
(CoreGraphics, AppKit's X11 backend, QuartzCore) when testing isolated binaries.
|
||||
Run `riot-metal-presentation-check` with `RIOT_PRESENTATION_PARENT=1` to create
|
||||
an owned borderless game-like window. It verifies the parent drawable is
|
||||
borrowed. Add `RIOT_PRESENTATION_RESIZE=1` to verify fallback to a child at
|
||||
half width and borrowing the parent again when restored. Add
|
||||
`RIOT_PRESENTATION_NESTED=1` for nested layers.
|
||||
The default probe still exercises the independent child-window path.
|
||||
|
||||
For physical VRR validation, enable adaptive sync in the monitor and desktop,
|
||||
then manually test fullscreen and borderless League with a frame cap inside
|
||||
the monitor's VRR range (for example 157 FPS on a 165 Hz display), with VSync
|
||||
off. Check the monitor's live refresh display as well as the game FPS counter.
|
||||
Also test leaving fullscreen, restoring it, resizing, and alt-tab. Report GPU,
|
||||
driver, compositor and session type. A stable FPS counter or the X11 property
|
||||
alone does not prove that the monitor is using variable refresh.
|
||||
|
||||
Local native validation on 2026-10-19 passed all three modes (child, parent
|
||||
with resize, nested parent with resize), including Vulkan-to-GL pixel readback
|
||||
and ten presentations per mode, using staged frameworks in a private prefix.
|
||||
Physical VRR and in-game performance on the reporter's Mutter setup remain
|
||||
unverified.
|
||||
|
||||
## Metal VSync regression (#46)
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s tests -p 'test_metal_vsync.py'
|
||||
```
|
||||
|
||||
These host checks compile the patched layer-tree policy, flush body and CGL
|
||||
swap methods against inert fixtures. They cover nested layers, VSync toggles,
|
||||
shared-window requests, independent contexts, late attachment and failed
|
||||
context/interval changes. They do not measure physical display refresh.
|
||||
|
||||
For manual validation after updating with the clients closed, compare the
|
||||
same scene with in-game VSync on and off. With VSync on, presentation should
|
||||
follow the display/compositor cadence; with it off, the game's frame cap
|
||||
should continue to work. Record monitor refresh, compositor, X11/Xwayland,
|
||||
GPU/driver and window mode. The window presentation probe above checks the
|
||||
API wiring; an in-game/compositor timing comparison remains necessary.
|
||||
|
||||
## Combined VSync and VRR testing
|
||||
|
||||
The `test/vsync-vrr` branch combines PRs #81 and #88, resolving their shared
|
||||
compositor-patch and presentation-probe changes. The probe toggles
|
||||
`displaySyncEnabled` and checks the CGL interval in child, parent and nested
|
||||
parent modes; the parent modes also support resize fallback/restoration.
|
||||
All three native modes passed locally in a private prefix, together with ten
|
||||
host regression checks and validation of all 31 component patches.
|
||||
|
||||
After closing the game and both clients:
|
||||
|
||||
```sh
|
||||
git fetch origin
|
||||
git switch --track origin/test/vsync-vrr
|
||||
./scripts/update.sh
|
||||
```
|
||||
|
||||
Test VSync with desktop VRR disabled, then test VRR with VSync off and a cap
|
||||
inside the monitor's adaptive range. Finally test both enabled. Report game
|
||||
FPS and the monitor's live refresh reading separately.
|
||||
@@ -541,3 +541,40 @@ Recovery requires every matching client to be at least 60 seconds old and a
|
||||
complete Hyprland window record (`class`, `title`, `mapped`, `hidden`). A second
|
||||
process scan after window inspection must find the same client identities and
|
||||
no game. Missing information or shutdown failure leaves normal launch in charge.
|
||||
|
||||
## Variable refresh rate (VRR)
|
||||
|
||||
Full-window borderless game layers now use the opaque managed X11 parent
|
||||
surface for presentation. Previously they swapped an independent child while
|
||||
the managed parent remained static. Using one surface lets the compositor
|
||||
track the game's updates for VRR and avoids scanning out an empty parent.
|
||||
Partial views, titled windows and other applications retain child drawables.
|
||||
The child fallback retains the compositor-bypass prohibition that prevents
|
||||
black screens. The fullscreen parent path advertises `_VARIABLE_REFRESH=1`;
|
||||
leaving fullscreen clears that request. Desktop/driver policy still decides
|
||||
whether to enable adaptive sync.
|
||||
|
||||
With the game and both clients closed, rebuild with `./scripts/update.sh`;
|
||||
CoreGraphics, QuartzCore and the X11 backend must be updated together. Enable
|
||||
VRR in the desktop and monitor, and compare the monitor's live refresh reading
|
||||
to a frame cap below its maximum refresh, initially with VSync off. VRR and
|
||||
VSync are separate: this change does not implement an in-game frame limiter.
|
||||
See [the VRR probe and manual checks](TESTING.md#vrr-presentation-regression-46).
|
||||
|
||||
Standalone child, parent and nested-parent presentation probes passed locally;
|
||||
monitor-level VRR and the reporter's in-game behavior still need confirmation.
|
||||
|
||||
## VSync and frame caps (#46)
|
||||
|
||||
The Metal layer's `displaySyncEnabled` property previously had no effect on
|
||||
the window swap interval. A game that delegates pacing to VSync could run
|
||||
uncapped when VSync was enabled. The compositor now applies that property to
|
||||
the window before swapping, including nested Metal layers and setting changes.
|
||||
If several Metal layers share a window, any layer requesting VSync enables it
|
||||
for that window. Other windows retain independent swap settings.
|
||||
|
||||
Exit the game and both clients, then run `./scripts/update.sh` to rebuild and
|
||||
install the QuartzCore and OpenGL changes. Until updated, disable in-game
|
||||
VSync to use the game's frame cap, as the issue reporter confirmed. Physical
|
||||
scanout timing still depends on the driver and desktop compositor; the fix
|
||||
has standalone regression coverage but needs an in-game timing retest.
|
||||
+251
-41
@@ -5031,7 +5031,7 @@ index 0000000..31fa646
|
||||
+
|
||||
+#endif
|
||||
diff --git a/AppKit/X11.backend/X11Window.h b/AppKit/X11.backend/X11Window.h
|
||||
index bfe3839a..48cfac4c 100644
|
||||
index bfe3839a..bc592711 100644
|
||||
--- a/AppKit/X11.backend/X11Window.h
|
||||
+++ b/AppKit/X11.backend/X11Window.h
|
||||
@@ -26,7 +26,7 @@
|
||||
@@ -5043,7 +5043,7 @@ index bfe3839a..48cfac4c 100644
|
||||
|
||||
@interface X11Window : CGWindow {
|
||||
int _level; // TODO: care about this value
|
||||
@@ -47,8 +47,32 @@
|
||||
@@ -47,8 +47,33 @@
|
||||
BOOL _mapped;
|
||||
CGPoint _lastMotionPos;
|
||||
BOOL _isModal;
|
||||
@@ -5053,6 +5053,7 @@ index bfe3839a..48cfac4c 100644
|
||||
+ BOOL _hasAlpha; // created with the 32-bit ARGB visual
|
||||
+ BOOL _wasMapped; // the visual is settled at the first map
|
||||
BOOL _hasShadow;
|
||||
+ id _layerDrawableOwner;
|
||||
+ NSTimer *_remoteFrameTimer;
|
||||
+ Window _remoteFrameWindow;
|
||||
+ GC _remoteFrameGC;
|
||||
@@ -5076,19 +5077,22 @@ index bfe3839a..48cfac4c 100644
|
||||
|
||||
@public
|
||||
XIC _xic;
|
||||
@@ -60,9 +84,9 @@
|
||||
@@ -60,9 +85,12 @@
|
||||
- (Visual *) visual;
|
||||
- (Drawable) drawable;
|
||||
- (NSPoint) transformPoint: (NSPoint) pos;
|
||||
-- (O2Rect) transformFrame: (O2Rect) frame;
|
||||
|
||||
- (Window) windowHandle;
|
||||
+- (void *) layerDrawableFor: (id) owner;
|
||||
+- (void) acquireLayerDrawable: (id) owner;
|
||||
+- (void) releaseLayerDrawable: (id) owner;
|
||||
+- (BOOL) handleRemoteFrameExpose: (Window) window;
|
||||
|
||||
- (void) frameChanged;
|
||||
- (void) setLastKnownCursorPosition: (CGPoint) point;
|
||||
diff --git a/AppKit/X11.backend/X11Window.m b/AppKit/X11.backend/X11Window.m
|
||||
index f4925b66..e458d96f 100644
|
||||
index f4925b6..b565b91 100644
|
||||
--- a/AppKit/X11.backend/X11Window.m
|
||||
+++ b/AppKit/X11.backend/X11Window.m
|
||||
@@ -18,11 +18,14 @@
|
||||
@@ -5455,7 +5459,7 @@ index f4925b66..e458d96f 100644
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
@@ -279,24 +498,30 @@ static NSData *makeWindowIcon() {
|
||||
@@ -279,24 +498,59 @@ static NSData *makeWindowIcon() {
|
||||
return _styleMask;
|
||||
}
|
||||
|
||||
@@ -5492,6 +5496,35 @@ index f4925b66..e458d96f 100644
|
||||
}
|
||||
+ return NO;
|
||||
+}
|
||||
+
|
||||
+/* Keep the game on one opaque managed surface. Mutter's VRR frame clock
|
||||
+ * follows that surface's repaint events, not an independently swapped child.
|
||||
+ * _VARIABLE_REFRESH also advertises eligibility to X11 drivers that use it. */
|
||||
+- (void) updateLayerPresentationHints: (BOOL) fullscreen {
|
||||
+ if (!X11IsLeagueGameWindow(_display, _window, _delegate)) return;
|
||||
+ unsigned long adaptive = fullscreen && _layerDrawableOwner != nil;
|
||||
+ unsigned long bypass = fullscreen && !_layerDrawableOwner ? 2 : 0;
|
||||
+ XChangeProperty(_display, _window, XInternAtom(_display, "_VARIABLE_REFRESH", False),
|
||||
+ XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&adaptive, 1);
|
||||
+ XChangeProperty(_display, _window, XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", False),
|
||||
+ XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&bypass, 1);
|
||||
+}
|
||||
+
|
||||
+- (void *) layerDrawableFor: (id) owner {
|
||||
+ return (!_layerDrawableOwner || _layerDrawableOwner == owner) && !_hasAlpha && X11IsLeagueGameWindow(_display, _window, _delegate) ? _cglWindow : NULL;
|
||||
+}
|
||||
+
|
||||
+- (void) acquireLayerDrawable: (id) owner {
|
||||
+ if (_layerDrawableOwner && _layerDrawableOwner != owner) return;
|
||||
+ _layerDrawableOwner = owner;
|
||||
+ [self updateLayerPresentationHints: _fullscreenRequested];
|
||||
+}
|
||||
+
|
||||
+- (void) releaseLayerDrawable: (id) owner {
|
||||
+ if (_layerDrawableOwner != owner) return;
|
||||
+ _layerDrawableOwner = nil;
|
||||
+ [self updateLayerPresentationHints: _fullscreenRequested];
|
||||
+}
|
||||
|
||||
+- (void) requestFullscreen: (BOOL) fullscreen force: (BOOL) force {
|
||||
+ if (fullscreen) {
|
||||
@@ -5502,7 +5535,7 @@ index f4925b66..e458d96f 100644
|
||||
if (!_mapped) {
|
||||
long states[2];
|
||||
int states_cnt = 0;
|
||||
@@ -304,7 +529,7 @@ static NSData *makeWindowIcon() {
|
||||
@@ -304,7 +558,7 @@ static NSData *makeWindowIcon() {
|
||||
states[states_cnt++] =
|
||||
(long) XInternAtom(_display, "_NET_WM_STATE_MODAL", False);
|
||||
}
|
||||
@@ -5511,7 +5544,7 @@ index f4925b66..e458d96f 100644
|
||||
states[states_cnt++] = (long) XInternAtom(
|
||||
_display, "_NET_WM_STATE_FULLSCREEN", False);
|
||||
}
|
||||
@@ -312,14 +537,13 @@ static NSData *makeWindowIcon() {
|
||||
@@ -312,14 +566,13 @@ static NSData *makeWindowIcon() {
|
||||
XInternAtom(_display, "_NET_WM_STATE", False), XA_ATOM,
|
||||
32, PropModeReplace, (const unsigned char *) states,
|
||||
states_cnt);
|
||||
@@ -5528,11 +5561,12 @@ index f4925b66..e458d96f 100644
|
||||
event.data.l[1] =
|
||||
(long) XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", False);
|
||||
event.data.l[3] = 1;
|
||||
@@ -327,6 +551,47 @@ static NSData *makeWindowIcon() {
|
||||
@@ -327,6 +580,48 @@ static NSData *makeWindowIcon() {
|
||||
SubstructureNotifyMask | SubstructureRedirectMask,
|
||||
(XEvent *) &event);
|
||||
}
|
||||
+ _fullscreenRequested = fullscreen;
|
||||
+ [self updateLayerPresentationHints: fullscreen];
|
||||
+ XFlush(_display);
|
||||
+}
|
||||
+
|
||||
@@ -5576,7 +5610,7 @@ index f4925b66..e458d96f 100644
|
||||
}
|
||||
|
||||
- (void) setStyleMask: (NSUInteger) mask {
|
||||
@@ -338,7 +603,6 @@ static NSData *makeWindowIcon() {
|
||||
@@ -338,7 +633,6 @@ static NSData *makeWindowIcon() {
|
||||
}
|
||||
|
||||
+ (void) removeDecorationForWindow: (Window) w onDisplay: (Display *) dpy {
|
||||
@@ -5584,7 +5618,7 @@ index f4925b66..e458d96f 100644
|
||||
struct {
|
||||
unsigned long flags;
|
||||
unsigned long functions;
|
||||
@@ -356,11 +620,25 @@ static NSData *makeWindowIcon() {
|
||||
@@ -356,11 +650,25 @@ static NSData *makeWindowIcon() {
|
||||
|
||||
- (void) ensureMapped {
|
||||
if (!_mapped) {
|
||||
@@ -5610,7 +5644,7 @@ index f4925b66..e458d96f 100644
|
||||
- (void) showWindowWithoutActivation {
|
||||
[self ensureMapped];
|
||||
}
|
||||
@@ -377,15 +655,27 @@ static NSData *makeWindowIcon() {
|
||||
@@ -377,15 +685,27 @@ static NSData *makeWindowIcon() {
|
||||
- (void) syncDelegateProperties {
|
||||
long mask = KeyPressMask | KeyReleaseMask | ExposureMask |
|
||||
StructureNotifyMask | EnterWindowMask | LeaveWindowMask |
|
||||
@@ -5643,7 +5677,7 @@ index f4925b66..e458d96f 100644
|
||||
// TODO: background color
|
||||
}
|
||||
|
||||
@@ -393,6 +683,28 @@ static NSData *makeWindowIcon() {
|
||||
@@ -393,6 +713,28 @@ static NSData *makeWindowIcon() {
|
||||
// This is essentially dealloc; we release our contexts
|
||||
// and windows, but unlike dealloc, this method can be called
|
||||
// several times, so set everything to nil/NULL/0.
|
||||
@@ -5672,7 +5706,7 @@ index f4925b66..e458d96f 100644
|
||||
[_context release];
|
||||
_context = nil;
|
||||
|
||||
@@ -421,6 +733,392 @@ static NSData *makeWindowIcon() {
|
||||
@@ -421,6 +763,392 @@ static NSData *makeWindowIcon() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6065,7 +6099,7 @@ index f4925b66..e458d96f 100644
|
||||
- (Window) windowHandle {
|
||||
return _window;
|
||||
}
|
||||
@@ -470,14 +1168,32 @@ static NSData *makeWindowIcon() {
|
||||
@@ -470,14 +1198,32 @@ static NSData *makeWindowIcon() {
|
||||
const char *text = [title cString];
|
||||
XStringListToTextProperty((char **) &text, 1, &prop);
|
||||
XSetWMName(_display, _window, &prop);
|
||||
@@ -6099,7 +6133,7 @@ index f4925b66..e458d96f 100644
|
||||
}
|
||||
|
||||
- (void) setHasShadow: (BOOL) value {
|
||||
@@ -488,6 +1204,25 @@ static NSData *makeWindowIcon() {
|
||||
@@ -488,6 +1234,25 @@ static NSData *makeWindowIcon() {
|
||||
_level = value;
|
||||
}
|
||||
|
||||
@@ -6125,7 +6159,7 @@ index f4925b66..e458d96f 100644
|
||||
- (void) setOpaque: (BOOL) value {
|
||||
_isOpaque = value;
|
||||
}
|
||||
@@ -518,7 +1253,7 @@ static NSData *makeWindowIcon() {
|
||||
@@ -518,7 +1283,7 @@ static NSData *makeWindowIcon() {
|
||||
|
||||
- (void) placeAboveWindow: (NSInteger) otherNumber {
|
||||
X11Window *other = [X11Window windowWithWindowNumber: otherNumber];
|
||||
@@ -6134,7 +6168,7 @@ index f4925b66..e458d96f 100644
|
||||
|
||||
if (!other) {
|
||||
XRaiseWindow(_display, _window);
|
||||
@@ -530,7 +1265,7 @@ static NSData *makeWindowIcon() {
|
||||
@@ -530,7 +1295,7 @@ static NSData *makeWindowIcon() {
|
||||
|
||||
- (void) placeBelowWindow: (NSInteger) otherNumber {
|
||||
X11Window *other = [X11Window windowWithWindowNumber: otherNumber];
|
||||
@@ -6143,7 +6177,7 @@ index f4925b66..e458d96f 100644
|
||||
|
||||
if (!other) {
|
||||
XLowerWindow(_display, _window);
|
||||
@@ -541,8 +1276,23 @@ static NSData *makeWindowIcon() {
|
||||
@@ -541,8 +1306,23 @@ static NSData *makeWindowIcon() {
|
||||
}
|
||||
|
||||
- (void) makeKey {
|
||||
@@ -6168,7 +6202,7 @@ index f4925b66..e458d96f 100644
|
||||
}
|
||||
|
||||
- (void) makeMain {
|
||||
@@ -559,7 +1309,29 @@ static NSData *makeWindowIcon() {
|
||||
@@ -559,7 +1339,29 @@ static NSData *makeWindowIcon() {
|
||||
XRaiseWindow(_display, _window);
|
||||
}
|
||||
|
||||
@@ -6198,7 +6232,7 @@ index f4925b66..e458d96f 100644
|
||||
Atom wmState = XInternAtom(_display, "_NET_WM_STATE", FALSE);
|
||||
Atom hiddenState = XInternAtom(_display, "_NET_WM_STATE_HIDDEN", FALSE);
|
||||
|
||||
@@ -593,8 +1365,10 @@ static NSData *makeWindowIcon() {
|
||||
@@ -593,8 +1395,10 @@ static NSData *makeWindowIcon() {
|
||||
if (_cglContext == NULL) {
|
||||
CGLError error;
|
||||
|
||||
@@ -6211,10 +6245,12 @@ index f4925b66..e458d96f 100644
|
||||
NSLog(@"CGLCreateContext failed at %s %d with error %d", __FILE__,
|
||||
__LINE__, error);
|
||||
|
||||
@@ -615,6 +1389,12 @@ static NSData *makeWindowIcon() {
|
||||
@@ -615,6 +1419,14 @@ static NSData *makeWindowIcon() {
|
||||
}
|
||||
|
||||
- (void) openGLFlushBuffer {
|
||||
+ // QuartzCore owns presentation while a full-window game layer borrows it.
|
||||
+ if (_layerDrawableOwner) return;
|
||||
+ /* A remote compositor surface owns this drawable while it is present.
|
||||
+ * Swapping the local Onyx surface would erase it until the next timer
|
||||
+ * tick. Keep local presentation for windows without a usable remote frame. */
|
||||
@@ -6224,7 +6260,7 @@ index f4925b66..e458d96f 100644
|
||||
CGLError error;
|
||||
|
||||
CGLContextObj prevContext = CGLGetCurrentContext();
|
||||
@@ -652,7 +1432,7 @@ static NSData *makeWindowIcon() {
|
||||
@@ -652,7 +1464,7 @@ static NSData *makeWindowIcon() {
|
||||
}
|
||||
|
||||
- (O2Rect) frame {
|
||||
@@ -6233,7 +6269,7 @@ index f4925b66..e458d96f 100644
|
||||
}
|
||||
|
||||
static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
|
||||
@@ -666,6 +1446,7 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
|
||||
@@ -666,6 +1478,7 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
|
||||
}
|
||||
|
||||
- (void) frameChanged {
|
||||
@@ -6241,7 +6277,7 @@ index f4925b66..e458d96f 100644
|
||||
XErrorHandler previousHandler = XSetErrorHandler(ignoreBadWindow);
|
||||
@try {
|
||||
Window root, parent;
|
||||
@@ -710,19 +1491,13 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
|
||||
@@ -710,19 +1523,13 @@ static int ignoreBadWindow(Display *display, XErrorEvent *errorEvent) {
|
||||
[_deviceDictionary addEntriesFromDictionary: entries];
|
||||
}
|
||||
|
||||
@@ -9629,13 +9665,13 @@ index 5dcac115..5de4f03f 100644
|
||||
+
|
||||
@end
|
||||
diff --git a/QuartzCore/CALayerContext.m b/QuartzCore/CALayerContext.m
|
||||
index 5dce8c5..3a7f937 100644
|
||||
--- a/QuartzCore/CALayerContext.m
|
||||
+++ b/QuartzCore/CALayerContext.m
|
||||
@@ -1,47 +1,77 @@
|
||||
@@ -1,47 +1,89 @@
|
||||
#import <Foundation/NSString.h>
|
||||
+#import <Foundation/NSMutableArray.h>
|
||||
#import <QuartzCore/CALayer.h>
|
||||
+#import <QuartzCore/CAMetalLayer.h>
|
||||
#import <QuartzCore/CALayerContext.h>
|
||||
#import <QuartzCore/CARenderer.h>
|
||||
#import "CALayerInternal.h"
|
||||
@@ -9657,6 +9693,17 @@ index 5dce8c5..3a7f937 100644
|
||||
+ return NO;
|
||||
+}
|
||||
+
|
||||
+static GLint metalSwapInterval(CALayer *layer) {
|
||||
+ GLint interval = -1;
|
||||
+ if ([[layer class] isSubclassOfClass: [CAMetalLayerInternal class]])
|
||||
+ interval = [(CAMetalLayer *)layer displaySyncEnabled] ? 1 : 0;
|
||||
+ for (CALayer *child in layer.sublayers) {
|
||||
+ GLint childInterval = metalSwapInterval(child);
|
||||
+ if (childInterval > interval) interval = childInterval;
|
||||
+ }
|
||||
+ return interval;
|
||||
+}
|
||||
+
|
||||
+/* The X11 transport presents Chromium's flattened full-window surface.
|
||||
+ * Avoid uploading and compositing that same window again into a covered GL
|
||||
+ * child. Restrict this to the explicitly configured native presentation path. */
|
||||
@@ -9700,10 +9747,11 @@ index 5dce8c5..3a7f937 100644
|
||||
- kCGLNoError)
|
||||
- NSLog(@"CGLCreateContext failed with %d in %s %d", error, __FILE__,
|
||||
- __LINE__);
|
||||
-
|
||||
+ self = [super init];
|
||||
+ if (self == nil)
|
||||
+ return nil;
|
||||
|
||||
+
|
||||
+ _deleteTextureIds = [[NSMutableArray alloc] init];
|
||||
_frame = rect;
|
||||
-
|
||||
@@ -9722,14 +9770,15 @@ index 5dce8c5..3a7f937 100644
|
||||
+ [_deleteTextureIds release];
|
||||
[_renderer release];
|
||||
CGLReleaseContext(_glContext);
|
||||
- CGLDestroyWindow(_cglWindow);
|
||||
+ CGLReleasePixelFormat(_pixelFormat);
|
||||
CGLDestroyWindow(_cglWindow);
|
||||
+ if (_ownsCglWindow) CGLDestroyWindow(_cglWindow);
|
||||
[_subwindow release];
|
||||
- [_layer release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -51,12 +81,22 @@
|
||||
@@ -51,12 +93,22 @@
|
||||
}
|
||||
|
||||
- (void) setLayer: (CALayer *) layer {
|
||||
@@ -9752,12 +9801,18 @@ index 5dce8c5..3a7f937 100644
|
||||
}
|
||||
|
||||
- (void) setSubwindow: (CGSubWindow*) subwindow
|
||||
@@ -68,16 +108,50 @@
|
||||
@@ -64,20 +116,55 @@
|
||||
CGSubWindow* oldSubwindow = _subwindow;
|
||||
|
||||
if (_cglWindow) {
|
||||
- CGLDestroyWindow(_cglWindow);
|
||||
+ if (_ownsCglWindow) CGLDestroyWindow(_cglWindow);
|
||||
}
|
||||
|
||||
_subwindow = [subwindow retain];
|
||||
- _cglWindow = CGLGetWindow([_subwindow nativeWindow]);
|
||||
+ _cglWindow = NULL;
|
||||
+ _ownsCglWindow = NO;
|
||||
+ if (_subwindow != nil) {
|
||||
+ if (_glContext == NULL) {
|
||||
+ CGLPixelFormatAttribute attributes[] = { 0 };
|
||||
@@ -9776,7 +9831,7 @@ index 5dce8c5..3a7f937 100644
|
||||
+ }
|
||||
+ }
|
||||
+ if (_glContext != NULL)
|
||||
+ _cglWindow = CGLGetWindow([_subwindow nativeWindow]);
|
||||
+ [self updateDrawable];
|
||||
+ }
|
||||
|
||||
[_subwindow show];
|
||||
@@ -9804,7 +9859,7 @@ index 5dce8c5..3a7f937 100644
|
||||
}
|
||||
|
||||
- (void) assignTextureIdsToLayerTree: (CALayer *) layer {
|
||||
@@ -93,8 +167,27 @@
|
||||
@@ -93,8 +180,43 @@
|
||||
[self assignTextureIdsToLayerTree: child];
|
||||
}
|
||||
|
||||
@@ -9821,6 +9876,22 @@ index 5dce8c5..3a7f937 100644
|
||||
+ for (CALayer *child in layer.sublayers)
|
||||
+ [self prepareMetalLayers: child];
|
||||
+}
|
||||
+
|
||||
+- (void) updateDrawable {
|
||||
+ if (!_subwindow || !_glContext) return;
|
||||
+ [_subwindow setAllowsParentDrawable: hasMetalLayer(_layer)];
|
||||
+ void *shared = [_subwindow sharedCGLWindow];
|
||||
+ if (shared && shared == _cglWindow && !_ownsCglWindow) return;
|
||||
+ if (!shared && _cglWindow && _ownsCglWindow) return;
|
||||
+ CGLContextObj previous = CGLGetCurrentContext();
|
||||
+ if (previous == _glContext) CGLSetCurrentContext(NULL);
|
||||
+ if (_ownsCglWindow) CGLDestroyWindow(_cglWindow);
|
||||
+ _cglWindow = shared ? shared : CGLGetWindow([_subwindow nativeWindow]);
|
||||
+ _ownsCglWindow = !shared && _cglWindow != NULL;
|
||||
+ // Update CGL's surface binding even when another context was current.
|
||||
+ CGLContextMakeCurrentAndAttachToWindow(_glContext, _cglWindow);
|
||||
+ if (previous != _glContext) CGLSetCurrentContext(previous);
|
||||
+}
|
||||
+
|
||||
- (void) renderLayer: (CALayer *) layer {
|
||||
+ CFTimeInterval renderLayerStart = CACurrentMediaTime();
|
||||
@@ -9832,28 +9903,31 @@ index 5dce8c5..3a7f937 100644
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glShadeModel(GL_SMOOTH);
|
||||
@@ -118,27 +211,62 @@
|
||||
@@ -118,27 +240,63 @@
|
||||
|
||||
[_deleteTextureIds removeAllObjects];
|
||||
|
||||
+ CFTimeInterval assignStart = CACurrentMediaTime();
|
||||
+ darlingQCTiming("renderLayerSetup", assignStart - renderLayerStart);
|
||||
[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;
|
||||
- [mtl prepareRender];
|
||||
- }
|
||||
-
|
||||
+ darlingQCTiming("assignTextureIds", CACurrentMediaTime() - assignStart);
|
||||
+
|
||||
+ [self prepareMetalLayers: layer];
|
||||
|
||||
+
|
||||
+ CFTimeInterval rendererStart = CACurrentMediaTime();
|
||||
[_renderer render];
|
||||
+ darlingQCTiming("rendererRender", CACurrentMediaTime() - rendererStart);
|
||||
}
|
||||
|
||||
- (void) render {
|
||||
+ [self updateDrawable];
|
||||
+ if (_cglWindow == NULL || _layer == nil)
|
||||
+ return;
|
||||
+ if(skipLocalRender(_layer, _frame.size))return;
|
||||
@@ -9900,7 +9974,7 @@ index 5dce8c5..3a7f937 100644
|
||||
}
|
||||
|
||||
- (void) startTimerIfNeeded {
|
||||
@@ -147,7 +275,7 @@
|
||||
@@ -147,7 +305,7 @@
|
||||
target: self
|
||||
selector: @selector(timer:)
|
||||
userInfo: nil
|
||||
@@ -9909,14 +9983,25 @@ index 5dce8c5..3a7f937 100644
|
||||
}
|
||||
|
||||
- (void) deleteTextureId: (NSNumber *) textureId {
|
||||
@@ -155,7 +283,9 @@
|
||||
@@ -155,7 +313,20 @@
|
||||
}
|
||||
|
||||
- (void) flush {
|
||||
- CGLFlushDrawable(_glContext);
|
||||
+ if(skipLocalRender(_layer, _frame.size))return;
|
||||
+ if (_glContext != NULL && _cglWindow != NULL)
|
||||
+ if (_glContext != NULL && _cglWindow != NULL) {
|
||||
+ CGLContextObj previous = CGLGetCurrentContext();
|
||||
+ // EGL applies the interval to the current draw surface, so attach this
|
||||
+ // window before setting it (including after late window attachment).
|
||||
+ if (CGLContextMakeCurrentAndAttachToWindow(_glContext, _cglWindow) != kCGLNoError)
|
||||
+ return;
|
||||
+ GLint interval = metalSwapInterval(_layer);
|
||||
+ if (interval >= 0)
|
||||
+ CGLSetParameter(_glContext, kCGLCPSwapInterval, &interval);
|
||||
+ CGLFlushDrawable(_glContext);
|
||||
+ if (previous != _glContext)
|
||||
+ CGLSetCurrentContext(previous);
|
||||
+ }
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -11242,10 +11327,14 @@ index 49b03483..5ebbf535 100644
|
||||
- (void) display;
|
||||
- (void) displayIfNeeded;
|
||||
diff --git a/QuartzCore/include/QuartzCore/CALayerContext.h b/QuartzCore/include/QuartzCore/CALayerContext.h
|
||||
index e4b0c342..d61c37ba 100644
|
||||
index e4b0c342..69b39250 100644
|
||||
--- a/QuartzCore/include/QuartzCore/CALayerContext.h
|
||||
+++ b/QuartzCore/include/QuartzCore/CALayerContext.h
|
||||
@@ -20,6 +20,7 @@
|
||||
@@ -17,9 +17,11 @@
|
||||
NSTimer *_timer;
|
||||
CGSubWindow* _subwindow;
|
||||
void* _cglWindow;
|
||||
+ BOOL _ownsCglWindow;
|
||||
}
|
||||
|
||||
@property(readonly) CGLContextObj glContext;
|
||||
@@ -11510,3 +11599,124 @@ index 53bacc46..3c6adfb7 100644
|
||||
- (void) beginSheetForDirectory: (NSString *) path
|
||||
file: (NSString *) name
|
||||
modalForWindow: (NSWindow *) docWindow
|
||||
diff --git a/CoreGraphics/CGSubWindow.m b/CoreGraphics/CGSubWindow.m
|
||||
index 008ccd74..6692deb5 100644
|
||||
--- a/CoreGraphics/CGSubWindow.m
|
||||
+++ b/CoreGraphics/CGSubWindow.m
|
||||
@@ -8,6 +8,10 @@
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+// Backends may lend an existing EGL surface for full-window layer rendering.
|
||||
+- (void *) sharedCGLWindow { return NULL; }
|
||||
+- (void) setAllowsParentDrawable: (BOOL) allowed {}
|
||||
+
|
||||
- (void) show {
|
||||
O2InvalidAbstractInvocation();
|
||||
}
|
||||
diff --git a/CoreGraphics/include/CoreGraphics/CGSubWindow.h b/CoreGraphics/include/CoreGraphics/CGSubWindow.h
|
||||
index de7a3496..79ee0d31 100644
|
||||
--- a/CoreGraphics/include/CoreGraphics/CGSubWindow.h
|
||||
+++ b/CoreGraphics/include/CoreGraphics/CGSubWindow.h
|
||||
@@ -6,6 +6,9 @@
|
||||
@interface CGSubWindow : NSObject
|
||||
|
||||
- (void *) nativeWindow;
|
||||
+// Borrowed surface; the subwindow/backend retains ownership.
|
||||
+- (void *) sharedCGLWindow;
|
||||
+- (void) setAllowsParentDrawable: (BOOL) allowed;
|
||||
|
||||
- (void) show;
|
||||
- (void) hide;
|
||||
diff --git a/AppKit/X11.backend/X11SubWindow.m b/AppKit/X11.backend/X11SubWindow.m
|
||||
index 738ab457..6596dab5 100644
|
||||
--- a/AppKit/X11.backend/X11SubWindow.m
|
||||
+++ b/AppKit/X11.backend/X11SubWindow.m
|
||||
@@ -1,6 +1,7 @@
|
||||
#import "X11SubWindow.h"
|
||||
#import "X11Display.h"
|
||||
#import "X11Window.h"
|
||||
+#import <AppKit/NSWindow.h>
|
||||
|
||||
@implementation X11SubWindow
|
||||
|
||||
@@ -15,8 +16,9 @@
|
||||
}
|
||||
|
||||
- (id) initWithParentWindow: (X11Window *) parent frame: (CGRect) frame {
|
||||
- _parent = parent;
|
||||
+ _parent = [parent retain];
|
||||
frame = [self convertFrame: frame];
|
||||
+ _drawableFrame = frame;
|
||||
|
||||
_display = [(X11Display *) [NSDisplay currentDisplay] display];
|
||||
|
||||
@@ -31,7 +33,9 @@
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
+ if (_usingParentDrawable) [_parent releaseLayerDrawable: self];
|
||||
XDestroyWindow(_display, _window);
|
||||
+ [_parent release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -39,16 +43,41 @@
|
||||
return (void *) _window;
|
||||
}
|
||||
|
||||
+- (void) setAllowsParentDrawable: (BOOL) allowed {
|
||||
+ _allowsParentDrawable = allowed;
|
||||
+}
|
||||
+
|
||||
+- (void *) sharedCGLWindow {
|
||||
+ // Only a full-window borderless game view may draw into the managed parent.
|
||||
+ // Other views keep independent child drawables and their own coordinates.
|
||||
+ CGRect parentFrame = [_parent frame];
|
||||
+ BOOL coversParent = _allowsParentDrawable && _visible && !([_parent styleMask] & NSTitledWindowMask) &&
|
||||
+ CGRectEqualToRect(_drawableFrame, CGRectMake(0, 0, parentFrame.size.width, parentFrame.size.height));
|
||||
+ void *surface = coversParent ? [_parent layerDrawableFor: self] : NULL;
|
||||
+ BOOL usingParent = surface != NULL;
|
||||
+ if (usingParent != _usingParentDrawable) {
|
||||
+ if (usingParent) [_parent acquireLayerDrawable: self];
|
||||
+ else [_parent releaseLayerDrawable: self];
|
||||
+ _usingParentDrawable = usingParent;
|
||||
+ }
|
||||
+ if (_visible && !usingParent) XMapWindow(_display, _window);
|
||||
+ else XUnmapWindow(_display, _window);
|
||||
+ return surface;
|
||||
+}
|
||||
+
|
||||
- (void) show {
|
||||
- XMapWindow(_display, _window);
|
||||
+ _visible = YES;
|
||||
+ [self sharedCGLWindow];
|
||||
}
|
||||
|
||||
- (void) hide {
|
||||
- XUnmapWindow(_display, _window);
|
||||
+ _visible = NO;
|
||||
+ [self sharedCGLWindow];
|
||||
}
|
||||
|
||||
- (void) setFrame: (CGRect) frame {
|
||||
frame = [self convertFrame: frame];
|
||||
+ _drawableFrame = frame;
|
||||
|
||||
XMoveResizeWindow(_display, _window, frame.origin.x, frame.origin.y,
|
||||
frame.size.width, frame.size.height);
|
||||
diff --git a/AppKit/X11.backend/X11SubWindow.h b/AppKit/X11.backend/X11SubWindow.h
|
||||
index 65b63a3e..e5b913b1 100644
|
||||
--- a/AppKit/X11.backend/X11SubWindow.h
|
||||
+++ b/AppKit/X11.backend/X11SubWindow.h
|
||||
@@ -7,6 +7,10 @@
|
||||
X11Window *_parent;
|
||||
Display *_display;
|
||||
Window _window;
|
||||
+ CGRect _drawableFrame;
|
||||
+ BOOL _visible;
|
||||
+ BOOL _allowsParentDrawable;
|
||||
+ BOOL _usingParentDrawable;
|
||||
}
|
||||
|
||||
- initWithParentWindow: (X11Window *) parent frame: (CGRect) frame;
|
||||
+29
-29
@@ -6765,7 +6765,7 @@ index deca16b87..d75d5e22d 100644
|
||||
|
||||
set_property(TARGET OpenGL APPEND_STRING PROPERTY LINK_FLAGS
|
||||
diff --git a/src/frameworks/OpenGL/OpenGL.c b/src/frameworks/OpenGL/OpenGL.c
|
||||
index ffd7fe054..f25fd9aa5 100644
|
||||
index ffd7fe054..a40a634d3 100644
|
||||
--- a/src/frameworks/OpenGL/OpenGL.c
|
||||
+++ b/src/frameworks/OpenGL/OpenGL.c
|
||||
@@ -2,11 +2,22 @@
|
||||
@@ -6866,7 +6866,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
static EGLConfig config;
|
||||
static int num_config;
|
||||
|
||||
@@ -48,10 +124,18 @@ struct _CGLContextObj {
|
||||
@@ -48,10 +124,19 @@ struct _CGLContextObj {
|
||||
GLuint retain_count;
|
||||
pthread_mutex_t lock;
|
||||
EGLContext egl_context;
|
||||
@@ -6876,6 +6876,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
// EGL has no function for getting the current swap interval,
|
||||
// so we need to save the last set value. The default is 1.
|
||||
int swap_interval;
|
||||
+ int swap_interval_requested;
|
||||
+ EGLSurface native_present_surface;
|
||||
+ EGLContext native_present_context;
|
||||
+ DarlingNativePresentTarget *native_present_target;
|
||||
@@ -6885,7 +6886,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
};
|
||||
|
||||
struct _CGLPixelFormatObj {
|
||||
@@ -59,6 +143,24 @@ struct _CGLPixelFormatObj {
|
||||
@@ -59,6 +144,24 @@ struct _CGLPixelFormatObj {
|
||||
CGLPixelFormatAttribute *attributes;
|
||||
};
|
||||
|
||||
@@ -6910,7 +6911,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
|
||||
switch (attr) {
|
||||
case kCGLPFAAuxBuffers:
|
||||
@@ -69,6 +171,9 @@ static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
|
||||
@@ -69,6 +172,9 @@ static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
|
||||
case kCGLPFAAccumSize:
|
||||
case kCGLPFARendererID:
|
||||
case kCGLPFADisplayMask:
|
||||
@@ -6920,7 +6921,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
@@ -78,6 +183,7 @@ static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
|
||||
@@ -78,6 +184,7 @@ static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
|
||||
__attribute__((constructor))
|
||||
static void _CGLInitialize(void)
|
||||
{
|
||||
@@ -6928,7 +6929,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
g_displays = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
|
||||
}
|
||||
|
||||
@@ -93,41 +199,71 @@ static int attributes_count(const CGLPixelFormatAttribute *attrs) {
|
||||
@@ -93,41 +200,71 @@ static int attributes_count(const CGLPixelFormatAttribute *attrs) {
|
||||
|
||||
CGLError CGLRegisterNativeDisplay(void *native_display) {
|
||||
|
||||
@@ -7008,7 +7009,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
|
||||
pthread_mutex_lock(&g_displaysMutex);
|
||||
CFDictionaryAddValue(g_displays, (const void*)(unsigned long) cid, rv);
|
||||
@@ -139,6 +275,8 @@ static struct _CGLDisplay* getCGLDisplay(CGSConnectionID cid)
|
||||
@@ -139,6 +276,8 @@ static struct _CGLDisplay* getCGLDisplay(CGSConnectionID cid)
|
||||
|
||||
CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, CGSSurfaceID sid)
|
||||
{
|
||||
@@ -7017,7 +7018,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
struct _CGLDisplay* disp = getCGLDisplay(cid);
|
||||
if (!disp)
|
||||
return kCGLBadConnection;
|
||||
@@ -152,7 +290,7 @@ CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, C
|
||||
@@ -152,7 +291,7 @@ CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, C
|
||||
if (!window)
|
||||
return kCGLBadWindow;
|
||||
|
||||
@@ -7026,7 +7027,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
if (gl->egl_surface == EGL_NO_SURFACE)
|
||||
return kCGLBadState;
|
||||
return kCGLNoError;
|
||||
@@ -161,7 +299,7 @@ CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, C
|
||||
@@ -161,7 +300,7 @@ CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, C
|
||||
CGLWindowRef CGLGetWindow(void *native_window) {
|
||||
|
||||
EGLNativeWindowType window = (EGLNativeWindowType) native_window;
|
||||
@@ -7035,7 +7036,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
return NULL;
|
||||
@@ -170,15 +308,53 @@ CGLWindowRef CGLGetWindow(void *native_window) {
|
||||
@@ -170,15 +309,53 @@ CGLWindowRef CGLGetWindow(void *native_window) {
|
||||
return (CGLWindowRef) surface;
|
||||
}
|
||||
|
||||
@@ -7091,7 +7092,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
}
|
||||
|
||||
static pthread_key_t current_context_key;
|
||||
@@ -198,18 +374,42 @@ CGLContextObj CGLGetCurrentContext(void) {
|
||||
@@ -198,18 +375,42 @@ CGLContextObj CGLGetCurrentContext(void) {
|
||||
return pthread_getspecific(key);
|
||||
}
|
||||
|
||||
@@ -7139,7 +7140,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
}
|
||||
|
||||
CGLError CGLSetFullScreen(CGLContextObj ctx) {
|
||||
@@ -223,11 +423,13 @@ CGLError CGLChoosePixelFormat(
|
||||
@@ -223,11 +424,13 @@ CGLError CGLChoosePixelFormat(
|
||||
CGLPixelFormatObj *result,
|
||||
GLint *number_of_screens
|
||||
) {
|
||||
@@ -7154,7 +7155,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
for (int i = 0; i < count; i++) {
|
||||
format->attributes[i] = attrs[i];
|
||||
}
|
||||
@@ -251,6 +453,10 @@ CGLError CGLDescribePixelFormat(
|
||||
@@ -251,6 +454,10 @@ CGLError CGLDescribePixelFormat(
|
||||
CGLPixelFormatAttribute attr,
|
||||
GLint *value
|
||||
) {
|
||||
@@ -7165,7 +7166,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
for (int i = 0; format->attributes[i] != 0; i++) {
|
||||
int has_arg = attribute_has_argument(format->attributes[i]);
|
||||
|
||||
@@ -303,37 +509,98 @@ GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat) {
|
||||
@@ -303,37 +510,99 @@ GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat) {
|
||||
return pixelFormat->retain_count;
|
||||
}
|
||||
|
||||
@@ -7227,6 +7228,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
+ context->egl_config = contextConfig;
|
||||
context->egl_surface = NULL;
|
||||
context->swap_interval = 1;
|
||||
+ context->swap_interval_requested = 0;
|
||||
+ context->native_present_surface=EGL_NO_SURFACE;
|
||||
+ context->native_present_context=EGL_NO_CONTEXT;
|
||||
+ context->native_present_target=NULL;
|
||||
@@ -7267,7 +7269,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -342,11 +609,17 @@ void CGLReleaseContext(CGLContextObj context) {
|
||||
@@ -342,11 +611,17 @@ void CGLReleaseContext(CGLContextObj context) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7288,7 +7290,7 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
|
||||
if (CGLGetCurrentContext() == context) {
|
||||
CGLSetCurrentContext(NULL);
|
||||
@@ -354,7 +627,11 @@ void CGLReleaseContext(CGLContextObj context) {
|
||||
@@ -354,7 +629,11 @@ void CGLReleaseContext(CGLContextObj context) {
|
||||
|
||||
pthread_mutex_destroy(&(context->lock));
|
||||
|
||||
@@ -7301,12 +7303,9 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
|
||||
free(context);
|
||||
}
|
||||
@@ -383,9 +660,330 @@ CGLError CGLUnlockContext(CGLContextObj context) {
|
||||
return kCGLNoError;
|
||||
@@ -384,8 +663,325 @@ CGLError CGLUnlockContext(CGLContextObj context) {
|
||||
}
|
||||
|
||||
+static int swapIntervalRequested;
|
||||
+
|
||||
CGLError CGLFlushDrawable(CGLContextObj context) {
|
||||
- eglSwapBuffers(display,context->egl_surface);
|
||||
- return kCGLNoError;
|
||||
@@ -7315,11 +7314,9 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
+ /* darling-lol: EGL defaults to a swap interval of 1. When the window is covered or the compositor throttles it
|
||||
+ * (Xwayland answers about once a second), every swap blocks that long and the game's frame rate collapses to ~1 fps.
|
||||
+ * macOS apps get an unthrottled flush unless they ask for vsync through kCGLCPSwapInterval, which is handled in
|
||||
+ * CGLSetParameter; apply "no vsync" once per context here, while it is current. */
|
||||
+ static CGLContextObj swapIntervalContext;
|
||||
+ if (!swapIntervalRequested && context != swapIntervalContext && p_eglSwapInterval && CGLGetCurrentContext() == context) {
|
||||
+ * CGLSetParameter; apply "no vsync" to unconfigured contexts while current. */
|
||||
+ if (!context->swap_interval_requested && p_eglSwapInterval && CGLGetCurrentContext() == context) {
|
||||
+ p_eglSwapInterval(context->egl_display, 0);
|
||||
+ swapIntervalContext = context;
|
||||
+ }
|
||||
+ return p_eglSwapBuffers(context->egl_display, context->egl_surface) ? kCGLNoError : kCGLBadState;
|
||||
+}
|
||||
@@ -7634,18 +7631,21 @@ index ffd7fe054..f25fd9aa5 100644
|
||||
}
|
||||
|
||||
CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, const GLint *value) {
|
||||
@@ -394,8 +992,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
|
||||
|
||||
@@ -395,9 +991,11 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
|
||||
if (parameter == kCGLCPSwapInterval)
|
||||
{
|
||||
+ swapIntervalRequested = 1;
|
||||
GLint v = *value;
|
||||
- EGLBoolean success = eglSwapInterval(display, v);
|
||||
- if (success)
|
||||
+ EGLBoolean success = p_eglSwapInterval(display, v);
|
||||
if (success)
|
||||
+ if (success) {
|
||||
context->swap_interval = v;
|
||||
+ context->swap_interval_requested = 1;
|
||||
+ }
|
||||
return success ? kCGLNoError : kCGLBadValue;
|
||||
@@ -417,14 +1016,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G
|
||||
}
|
||||
fprintf(stderr, "CGLSetParameter unimplemented for parameter %d\n", parameter);
|
||||
@@ -417,14 +1015,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G
|
||||
return kCGLNoError;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
"""Exercise the patched presentation policy and CGL call order without a GPU."""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
import test_runtime_compatibility as sources
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which('c++'), 'C++ compiler required')
|
||||
class MetalVSyncChecks(unittest.TestCase):
|
||||
patched = sources.CompatibilityChecks.patched
|
||||
|
||||
def test_explicit_vsync_is_scoped_to_each_cgl_context(self):
|
||||
source = self.patched('darling', 'src/frameworks/OpenGL/OpenGL.c')
|
||||
methods = '\n'.join(sources.function(source, name) for name in ('CGLFlushDrawable', 'CGLSetParameter'))
|
||||
fixture = r'''
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
using GLint=int;
|
||||
using CGLError=int;
|
||||
using CGLContextParameter=int;
|
||||
using EGLBoolean=int;
|
||||
constexpr int kCGLNoError=0, kCGLBadContext=1, kCGLBadState=2, kCGLBadAddress=3, kCGLBadValue=4;
|
||||
constexpr int kCGLCPSwapInterval=1;
|
||||
struct Context { int egl_display=1, egl_surface=1, swap_interval=1, swap_interval_requested=0; };
|
||||
using CGLContextObj=Context*;
|
||||
static Context* current;
|
||||
static int display=1, applied=-1;
|
||||
static bool fail=false;
|
||||
static int swapInterval(int, int value) { if(fail) return 0; applied=value; return 1; }
|
||||
static auto p_eglSwapInterval=swapInterval;
|
||||
static int p_eglSwapBuffers(int,int) { return 1; }
|
||||
static Context* CGLGetCurrentContext() { return current; }
|
||||
''' + methods + r'''
|
||||
int main() {
|
||||
Context a, b;
|
||||
current=&a;
|
||||
int interval=1;
|
||||
assert(CGLSetParameter(&a,kCGLCPSwapInterval,&interval)==0 && applied==1);
|
||||
CGLFlushDrawable(&a); assert(applied==1);
|
||||
current=&b;
|
||||
CGLFlushDrawable(&b); assert(applied==0); // Another window retains its default.
|
||||
current=&a;
|
||||
interval=0;
|
||||
assert(CGLSetParameter(&a,kCGLCPSwapInterval,&interval)==0 && applied==0);
|
||||
interval=1;
|
||||
assert(CGLSetParameter(&a,kCGLCPSwapInterval,&interval)==0 && applied==1);
|
||||
current=&b; fail=true;
|
||||
assert(CGLSetParameter(&b,kCGLCPSwapInterval,&interval)==kCGLBadValue);
|
||||
assert(!b.swap_interval_requested); // Failed requests must not change policy.
|
||||
fail=false; CGLFlushDrawable(&b); assert(applied==0);
|
||||
assert(CGLFlushDrawable(nullptr)==kCGLBadContext);
|
||||
}
|
||||
'''
|
||||
self.run_cpp(fixture)
|
||||
|
||||
def test_nested_layers_toggles_windows_and_context_failures(self):
|
||||
source = self.patched('cocotron', 'QuartzCore/CALayerContext.m')
|
||||
policy = sources.function(source, 'metalSwapInterval')
|
||||
# Translate only Objective-C message/iteration syntax; compile the actual
|
||||
# policy and flush body against inert layer and CGL fixtures.
|
||||
policy = policy.replace('[[layer class] isSubclassOfClass: [CAMetalLayerInternal class]]', 'layer->metal')
|
||||
policy = policy.replace('[(CAMetalLayer *)layer displaySyncEnabled]', 'layer->sync')
|
||||
policy = policy.replace('for (CALayer *child in layer.sublayers)', 'for (CALayer *child : layer->children)')
|
||||
flush = source.split('- (void) flush {', 1)[1].split('\n}\n', 1)[0]
|
||||
fixture = r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
using GLint = int;
|
||||
using CGLContextObj = int;
|
||||
constexpr int kCGLNoError = 0, kCGLCPSwapInterval = 1;
|
||||
constexpr int NULL_CONTEXT = 0;
|
||||
struct CALayer { bool metal=false, sync=true; std::vector<CALayer*> children; };
|
||||
struct Context { int interval=-2, swaps=0; } contexts[3];
|
||||
static int current=2, attached=0;
|
||||
static bool fail=false, skip=false;
|
||||
static int CGLGetCurrentContext() { return current; }
|
||||
static int CGLSetCurrentContext(int context) { current=context; return 0; }
|
||||
static int CGLContextMakeCurrentAndAttachToWindow(int context, int window) {
|
||||
if(fail) return 1;
|
||||
current=context; attached=window; return 0;
|
||||
}
|
||||
static int CGLSetParameter(int context, int parameter, const int* interval) {
|
||||
assert(current==context && attached==context && parameter==kCGLCPSwapInterval);
|
||||
contexts[context].interval=*interval; return 0;
|
||||
}
|
||||
static int CGLFlushDrawable(int context) {
|
||||
assert(current==context && attached==context);
|
||||
++contexts[context].swaps; return 0;
|
||||
}
|
||||
static bool skipLocalRender(CALayer*, int) { return skip; }
|
||||
'''
|
||||
fixture += policy
|
||||
fixture += '\nstruct Window { int _glContext=1, _cglWindow=1, _frame=0; CALayer* _layer; void flush() {'
|
||||
fixture += flush.replace('NULL', 'NULL_CONTEXT').replace('_frame.size', '_frame') + '\n} };\n'
|
||||
fixture += r'''
|
||||
int main() {
|
||||
CALayer root, container, metal, other;
|
||||
metal.metal=other.metal=true;
|
||||
root.children={&container}; container.children={&metal};
|
||||
assert(metalSwapInterval(&root)==1);
|
||||
Window a{1,1,0,&root};
|
||||
a.flush(); assert(contexts[1].interval==1 && current==2);
|
||||
metal.sync=false;
|
||||
a.flush(); assert(contexts[1].interval==0 && current==2);
|
||||
container.children.push_back(&other);
|
||||
a.flush(); assert(contexts[1].interval==1);
|
||||
other.sync=false;
|
||||
a.flush(); assert(contexts[1].interval==0);
|
||||
CALayer plain;
|
||||
assert(metalSwapInterval(&plain)==-1);
|
||||
Window b{2,2,0,&plain};
|
||||
b.flush(); assert(contexts[2].interval==-2);
|
||||
other.sync=true;
|
||||
a.flush(); assert(contexts[1].interval==1 && contexts[2].interval==-2);
|
||||
a._cglWindow=0;
|
||||
int swaps=contexts[1].swaps;
|
||||
a.flush(); assert(contexts[1].swaps==swaps);
|
||||
a._cglWindow=1; fail=true;
|
||||
a.flush(); assert(contexts[1].swaps==swaps && current==2);
|
||||
fail=false; skip=true;
|
||||
a.flush(); assert(contexts[1].swaps==swaps);
|
||||
skip=false; current=0;
|
||||
a.flush(); assert(current==0);
|
||||
current=1; a.flush(); assert(current==1);
|
||||
root.children.clear();
|
||||
assert(metalSwapInterval(&root)==-1);
|
||||
}
|
||||
'''
|
||||
self.run_cpp(fixture)
|
||||
|
||||
def run_cpp(self, fixture):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory)
|
||||
(path/'check.cpp').write_text(fixture)
|
||||
result = subprocess.run(['c++', '-std=c++17', str(path/'check.cpp'), '-o', str(path/'check')], capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
subprocess.run([str(path/'check')], check=True, timeout=10)
|
||||
@@ -0,0 +1,164 @@
|
||||
"""Compile the patched VRR eligibility and drawable ownership methods with inert fixtures."""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
import test_runtime_compatibility as sources
|
||||
|
||||
|
||||
def body(source, signature):
|
||||
return source.split(signature + ' {', 1)[1].split('\n}', 1)[0]
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which('c++'), 'C++ compiler required')
|
||||
class VrrChecks(unittest.TestCase):
|
||||
patched = sources.CompatibilityChecks.patched
|
||||
|
||||
def run_cpp(self, source):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory)
|
||||
(path/'check.cpp').write_text('#include <cstddef>\n' + source)
|
||||
result = subprocess.run(['c++', '-std=c++17', str(path/'check.cpp'), '-o', str(path/'check')], capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
subprocess.run([str(path/'check')], check=True, timeout=10)
|
||||
|
||||
def test_hints_only_allow_vrr_for_fullscreen_parent_presentation(self):
|
||||
source = self.patched('cocotron', 'AppKit/X11.backend/X11Window.m')
|
||||
method = body(source, '- (void) updateLayerPresentationHints: (BOOL) fullscreen')
|
||||
getter = body(source, '- (void *) layerDrawableFor: (id) owner')
|
||||
acquire = body(source, '- (void) acquireLayerDrawable: (id) owner').replace('[self updateLayerPresentationHints: _fullscreenRequested]', 'hints(_fullscreenRequested)')
|
||||
release = body(source, '- (void) releaseLayerDrawable: (id) owner').replace('[self updateLayerPresentationHints: _fullscreenRequested]', 'hints(_fullscreenRequested)')
|
||||
self.run_cpp(r'''
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
using BOOL=bool;
|
||||
constexpr auto nil=nullptr;
|
||||
constexpr int False=0, XA_CARDINAL=6, PropModeReplace=0;
|
||||
static bool game=true;
|
||||
static int adaptive=-1,bypass=-1,calls=0;
|
||||
static bool X11IsLeagueGameWindow(int,int,int) { return game; }
|
||||
static const char* XInternAtom(int,const char* name,int) { return name; }
|
||||
static int XChangeProperty(int,int,const char* name,int type,int bits,int mode,unsigned char* data,int count) {
|
||||
assert(type==XA_CARDINAL && bits==32 && mode==PropModeReplace && count==1);
|
||||
int value=*(unsigned long*)data; ++calls;
|
||||
if(!strcmp(name,"_VARIABLE_REFRESH")) adaptive=value;
|
||||
else { assert(!strcmp(name,"_NET_WM_BYPASS_COMPOSITOR")); bypass=value; }
|
||||
return 0;
|
||||
}
|
||||
struct Window {
|
||||
int _display=1,_window=1,_delegate=1;
|
||||
void* _layerDrawableOwner=nullptr;
|
||||
void* _cglWindow=(void*)3; bool _hasAlpha=false,_fullscreenRequested=true;
|
||||
void hints(BOOL fullscreen) {
|
||||
''' + method + '\n }\n void* drawable(void* owner) {' + getter + '\n}\n void acquire(void* owner) {' + acquire + '\n}\n void release(void* owner) {' + release + r'''
|
||||
}
|
||||
};
|
||||
int main() {
|
||||
Window w;
|
||||
w.hints(true); assert(adaptive==0 && bypass==2);
|
||||
w._layerDrawableOwner=&w;
|
||||
w.hints(true); assert(adaptive==1 && bypass==0);
|
||||
w.hints(false); assert(adaptive==0 && bypass==0);
|
||||
w._layerDrawableOwner=nullptr;
|
||||
w.hints(true); assert(adaptive==0 && bypass==2);
|
||||
int before=calls; game=false; w.hints(true); assert(calls==before);
|
||||
assert(!w.drawable(&w)); game=true;
|
||||
int a,b;
|
||||
assert(w.drawable(&a)==(void*)3);
|
||||
w.acquire(&a); assert(w.drawable(&a)==(void*)3 && !w.drawable(&b));
|
||||
w.acquire(&b); assert(w._layerDrawableOwner==&a);
|
||||
w.release(&b); assert(w._layerDrawableOwner==&a);
|
||||
w.release(&a); assert(!w._layerDrawableOwner && w.drawable(&b)==(void*)3);
|
||||
w._hasAlpha=true; assert(!w.drawable(&a));
|
||||
}
|
||||
''')
|
||||
|
||||
def test_subwindows_borrow_only_when_visible_borderless_and_covering_parent(self):
|
||||
source = self.patched('cocotron', 'AppKit/X11.backend/X11SubWindow.m')
|
||||
method = body(source, '- (void *) sharedCGLWindow')
|
||||
replacements = {'[_parent frame]':'_parent->frame', '[_parent styleMask]':'_parent->style',
|
||||
'[_parent layerDrawableFor: self]':'_parent->drawable(this)',
|
||||
'[_parent acquireLayerDrawable: self]':'_parent->acquire(this)',
|
||||
'[_parent releaseLayerDrawable: self]':'_parent->release(this)'}
|
||||
for old,new in replacements.items():
|
||||
method=method.replace(old,new)
|
||||
self.run_cpp(r'''
|
||||
#include <cassert>
|
||||
using BOOL=bool;
|
||||
struct CGRect { struct {double x,y;} origin; struct {double width,height;} size; };
|
||||
static CGRect CGRectMake(double x,double y,double w,double h) {return {{x,y},{w,h}};}
|
||||
static bool CGRectEqualToRect(CGRect a,CGRect b) {return a.origin.x==b.origin.x && a.origin.y==b.origin.y && a.size.width==b.size.width && a.size.height==b.size.height;}
|
||||
constexpr int NSTitledWindowMask=1;
|
||||
static bool mapped;
|
||||
static void XMapWindow(int,int) {mapped=true;}
|
||||
static void XUnmapWindow(int,int) {mapped=false;}
|
||||
struct Parent {
|
||||
CGRect frame=CGRectMake(0,0,800,600); int style=0; bool eligible=true;
|
||||
void* owner=nullptr; void* surface=this;
|
||||
void* drawable(void* requester) {return eligible && (!owner || owner==requester) ? surface : nullptr;}
|
||||
void acquire(void* requester) {assert(!owner); owner=requester;}
|
||||
void release(void* requester) {assert(owner==requester); owner=nullptr;}
|
||||
};
|
||||
struct Child {
|
||||
Parent* _parent; CGRect _drawableFrame=CGRectMake(0,0,800,600);
|
||||
BOOL _visible=true,_usingParentDrawable=false,_allowsParentDrawable=true; int _display=1,_window=2;
|
||||
void* shared() {
|
||||
''' + method + r'''
|
||||
}
|
||||
};
|
||||
int main() {
|
||||
Parent p; Child a{&p}; Child b{&p};
|
||||
assert(a.shared()==p.surface && !mapped && p.owner==&a);
|
||||
assert(a.shared()==p.surface); // Repeated lookup does not reacquire.
|
||||
assert(b.shared()==nullptr && mapped); // A second view cannot share ownership.
|
||||
a._visible=false; assert(!a.shared() && !mapped && !p.owner);
|
||||
a._visible=true; assert(a.shared()==p.surface);
|
||||
a._drawableFrame.size.width=400;
|
||||
assert(!a.shared() && mapped && !p.owner);
|
||||
a._drawableFrame=CGRectMake(0,0,800,600); p.style=NSTitledWindowMask;
|
||||
assert(!a.shared() && mapped);
|
||||
p.style=0; p.eligible=false; assert(!a.shared() && mapped);
|
||||
p.eligible=true; a._allowsParentDrawable=false; assert(!a.shared() && mapped);
|
||||
a._allowsParentDrawable=true; a._drawableFrame.origin.x=1; assert(!a.shared() && mapped);
|
||||
a._drawableFrame.origin.x=0; assert(a.shared()==p.surface && !mapped);
|
||||
}
|
||||
''')
|
||||
|
||||
def test_switching_surfaces_destroys_only_owned_child_drawables(self):
|
||||
source = self.patched('cocotron', 'QuartzCore/CALayerContext.m')
|
||||
method = body(source, '- (void) updateDrawable')
|
||||
method=method.replace('[_subwindow setAllowsParentDrawable: hasMetalLayer(_layer)]', '_subwindow->allow(hasMetalLayer(_layer))')
|
||||
method=method.replace('[_subwindow sharedCGLWindow]', '_subwindow->shared').replace('[_subwindow nativeWindow]', '_subwindow->native')
|
||||
self.run_cpp(r'''
|
||||
#include <cassert>
|
||||
using BOOL=bool;
|
||||
using CGLContextObj=int;
|
||||
static int current=0,destroyed=0,created=0;
|
||||
static void* destroyedSurface;
|
||||
static int CGLGetCurrentContext() {return current;}
|
||||
static void CGLSetCurrentContext(int c) {current=c;}
|
||||
static void CGLDestroyWindow(void* surface) {assert(surface==(void*)10); destroyedSurface=surface; ++destroyed;}
|
||||
static void* CGLGetWindow(void*) {++created; return (void*)10;}
|
||||
static void CGLContextMakeCurrentAndAttachToWindow(int c,void*) {current=c;}
|
||||
static bool hasMetalLayer(int layer) {return layer!=0;}
|
||||
struct Subwindow {void* shared=nullptr; void* native=(void*)2; void allow(bool enabled) {assert(enabled);} };
|
||||
struct Context {
|
||||
Subwindow* _subwindow; int _layer=1; int _glContext=1; void* _cglWindow=nullptr; bool _ownsCglWindow=false;
|
||||
void update() {
|
||||
''' + method.replace('CGLSetCurrentContext(NULL)', 'CGLSetCurrentContext(0)') + r'''
|
||||
}
|
||||
};
|
||||
int main() {
|
||||
Subwindow sub; Context c{&sub};
|
||||
c.update(); assert(created==1 && c._ownsCglWindow); // Ordinary clients still get a child.
|
||||
c.update(); assert(created==1 && destroyed==0);
|
||||
current=1; sub.shared=(void*)20;
|
||||
c.update(); assert(destroyed==1 && destroyedSurface==(void*)10 && !c._ownsCglWindow && current==1);
|
||||
c.update(); assert(destroyed==1);
|
||||
sub.shared=(void*)30; c.update(); assert(destroyed==1 && c._cglWindow==(void*)30);
|
||||
sub.shared=nullptr; c.update(); assert(created==2 && destroyed==1 && c._ownsCglWindow);
|
||||
current=2; sub.shared=(void*)20; c.update(); assert(current==2 && destroyed==2);
|
||||
c._subwindow=nullptr; c.update(); assert(destroyed==2);
|
||||
}
|
||||
''')
|
||||
@@ -10,12 +10,13 @@ for line in (folder / 'CMakeFiles/CoreGraphics.dir/flags.make').read_text().spli
|
||||
if line.startswith('C_') and ' = ' in line:
|
||||
key, value = line.split(' = ', 1)
|
||||
flags[key] = shlex.split(value)
|
||||
obj = root / 'build/riot-CGDirectDisplay.o'
|
||||
link = shlex.split((folder / 'CMakeFiles/CoreGraphics.dir/link.txt').read_text())
|
||||
for name in ('CGDirectDisplay', 'CGSubWindow'):
|
||||
obj = root / f'build/riot-{name}.o'
|
||||
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
||||
'-O2', '-Wno-macro-redefined', '-c',
|
||||
str(root / 'build/darling-src/src/external/cocotron/CoreGraphics/CGDirectDisplay.m'),
|
||||
str(root / f'build/darling-src/src/external/cocotron/CoreGraphics/{name}.m'),
|
||||
'-o', str(obj)], check=True)
|
||||
link = shlex.split((folder / 'CMakeFiles/CoreGraphics.dir/link.txt').read_text())
|
||||
link[link.index('CMakeFiles/CoreGraphics.dir/CGDirectDisplay.m.o')] = str(obj)
|
||||
link[link.index(f'CMakeFiles/CoreGraphics.dir/{name}.m.o')] = str(obj)
|
||||
link[link.index('-o') + 1] = str(root / 'build/riot-CoreGraphics')
|
||||
subprocess.run(link, cwd=folder, check=True)
|
||||
@@ -14,7 +14,7 @@ for line in (framework / 'CMakeFiles/X11_backend.dir/flags.make').read_text().sp
|
||||
key, value = line.split(' = ', 1)
|
||||
flags[key] = shlex.split(value)
|
||||
link = shlex.split((framework / 'CMakeFiles/X11_backend.dir/link.txt').read_text())
|
||||
for name in ('X11Display', 'X11Window'):
|
||||
for name in ('X11Display', 'X11Window', 'X11SubWindow'):
|
||||
obj = project / f'build/riot-{name}.o'
|
||||
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'], '-O3', '-march=native', '-mtune=generic', '-Wno-macro-redefined', '-c', str(project / f'build/darling-src/src/external/cocotron/AppKit/X11.backend/{name}.m'), '-o', str(obj)], check=True)
|
||||
link[link.index(f'CMakeFiles/X11_backend.dir/X11.backend/{name}.m.o')] = str(obj)
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
#import <OpenGL/gl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
extern "C" void _dispatch_main_queue_callback_4CF(void *);
|
||||
|
||||
@interface PresentationCheckWindow : NSWindow
|
||||
@end
|
||||
@implementation PresentationCheckWindow
|
||||
- (BOOL)canBecomeKeyWindow { return YES; }
|
||||
@end
|
||||
|
||||
static void verifyPresentation(bool value, const char* message) {
|
||||
if (!value) { fprintf(stderr, "FAIL: %s\n", message); exit(1); }
|
||||
@@ -18,7 +26,12 @@ static void pump(NSApplication* app) {
|
||||
NSEvent* event = [app nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast]
|
||||
inMode:NSDefaultRunLoopMode dequeue:YES];
|
||||
if (event) [app sendEvent:event];
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.005]];
|
||||
// A timed NSRunLoop wait can miss its deadline on Darling when the
|
||||
// X connection is quiet. Parent presentation no longer supplies child
|
||||
// Expose events to wake it; service sources without a blocking wait.
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
|
||||
_dispatch_main_queue_callback_4CF(nullptr);
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
@@ -35,6 +48,7 @@ int main() {
|
||||
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
||||
verifyPresentation(device != nil, "device");
|
||||
CAMetalLayer* layer = [CAMetalLayer layer];
|
||||
verifyPresentation(layer.displaySyncEnabled == YES, "VSync enabled by default");
|
||||
layer.device = device;
|
||||
layer.drawableSize = CGSizeMake(160,96);
|
||||
layer.frame = CGRectMake(0,0,160,96);
|
||||
@@ -52,12 +66,17 @@ int main() {
|
||||
} 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";
|
||||
const bool parentDrawable = getenv("RIOT_PRESENTATION_PARENT") != nullptr;
|
||||
NSWindow* window = [[PresentationCheckWindow alloc] initWithContentRect:NSMakeRect(50,50,160,96)
|
||||
styleMask:(parentDrawable ? NSBorderlessWindowMask : NSTitledWindowMask) backing:NSBackingStoreBuffered defer:parentDrawable];
|
||||
window.title = parentDrawable ? @"Presentation (TM) Client" : @"Metal presentation check";
|
||||
window.contentView = view;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
pump(app);
|
||||
if (parentDrawable) {
|
||||
void *borrowed = [[(CALayerContext*)[layer _context] subwindow] sharedCGLWindow];
|
||||
verifyPresentation(borrowed != nullptr, "full-window game layer borrows managed parent drawable");
|
||||
}
|
||||
id<MTLCommandQueue> queue = [device newCommandQueue];
|
||||
const unsigned frameCount = captureTest ? 130u : getenv("RIOT_PRESENTATION_STRESS") ? 120u : 10u;
|
||||
for (unsigned frame = 0; frame < frameCount; ++frame) {
|
||||
@@ -66,6 +85,7 @@ int main() {
|
||||
verifyPresentation(flag != nullptr, "request second bounded capture");
|
||||
fclose(flag);
|
||||
}
|
||||
layer.displaySyncEnabled = (frame % 3) != 1;
|
||||
@autoreleasepool {
|
||||
id<CAMetalDrawable> drawable = [layer nextDrawable];
|
||||
verifyPresentation(drawable != nil, "drawable after late window/context attachment");
|
||||
@@ -82,6 +102,23 @@ int main() {
|
||||
[commands commit];
|
||||
[commands waitUntilCompleted];
|
||||
pump(app);
|
||||
GLint interval = -1;
|
||||
CGLGetParameter([(CALayerContext*)[layer _context] glContext], kCGLCPSwapInterval, &interval);
|
||||
verifyPresentation(interval == (layer.displaySyncEnabled ? 1 : 0), "Metal VSync reaches window swap interval");
|
||||
if (parentDrawable) {
|
||||
void *borrowed = [[(CALayerContext*)[layer _context] subwindow] sharedCGLWindow];
|
||||
verifyPresentation(borrowed != nullptr, "parent drawable remains borrowed");
|
||||
if (frame == 3 && getenv("RIOT_PRESENTATION_RESIZE")) {
|
||||
CALayerContext* context = (CALayerContext*)[layer _context];
|
||||
CGRect original = [view frame];
|
||||
[context setFrame:CGRectMake(0,0,original.size.width / 2,original.size.height)];
|
||||
[context render]; [context flush];
|
||||
verifyPresentation([[context subwindow] sharedCGLWindow] == nullptr, "partial view falls back to child drawable");
|
||||
[context setFrame:original];
|
||||
[context render]; [context flush];
|
||||
verifyPresentation([[context subwindow] sharedCGLWindow] != nullptr, "restored view borrows parent drawable again");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CGLContextObj previous = CGLGetCurrentContext();
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"revision": "c8d38d16a9f613d300157bebbab2b9501bc0c274",
|
||||
"repository": "https://github.com/darlinghq/darling-cocotron.git",
|
||||
"patch": "patches/cocotron.patch",
|
||||
"modified_files": 104,
|
||||
"modified_files": 115,
|
||||
"new_files": [
|
||||
"QuartzCore/include/QuartzCore/CATransformLayer.h",
|
||||
"AppKit/NSGeometryTrace.h"
|
||||
|
||||
Reference in new issue
Block a user