mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Merge VSync and VRR fixes for combined testing
This commit is contained in:
commit
6bfdfbcac7
6 files changed
+265
-42
No files matched your search
@@ -567,3 +567,42 @@ 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.
|
||||
@@ -563,3 +563,18 @@ 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.
|
||||
+38
-13
@@ -9525,13 +9525,13 @@ index 5dcac115..5de4f03f 100644
|
||||
+
|
||||
@end
|
||||
diff --git a/QuartzCore/CALayerContext.m b/QuartzCore/CALayerContext.m
|
||||
index 5dce8c56..77b729c7 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"
|
||||
@@ -9553,6 +9553,17 @@ index 5dce8c56..77b729c7 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. */
|
||||
@@ -9596,10 +9607,11 @@ index 5dce8c56..77b729c7 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;
|
||||
-
|
||||
@@ -9626,7 +9638,7 @@ index 5dce8c56..77b729c7 100644
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -51,12 +81,22 @@
|
||||
@@ -51,12 +93,22 @@
|
||||
}
|
||||
|
||||
- (void) setLayer: (CALayer *) layer {
|
||||
@@ -9649,7 +9661,7 @@ index 5dce8c56..77b729c7 100644
|
||||
}
|
||||
|
||||
- (void) setSubwindow: (CGSubWindow*) subwindow
|
||||
@@ -64,20 +104,55 @@
|
||||
@@ -64,20 +116,55 @@
|
||||
CGSubWindow* oldSubwindow = _subwindow;
|
||||
|
||||
if (_cglWindow) {
|
||||
@@ -9707,7 +9719,7 @@ index 5dce8c56..77b729c7 100644
|
||||
}
|
||||
|
||||
- (void) assignTextureIdsToLayerTree: (CALayer *) layer {
|
||||
@@ -93,8 +168,43 @@
|
||||
@@ -93,8 +180,43 @@
|
||||
[self assignTextureIdsToLayerTree: child];
|
||||
}
|
||||
|
||||
@@ -9751,22 +9763,24 @@ index 5dce8c56..77b729c7 100644
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glShadeModel(GL_SMOOTH);
|
||||
@@ -118,27 +228,63 @@
|
||||
@@ -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);
|
||||
@@ -9820,7 +9834,7 @@ index 5dce8c56..77b729c7 100644
|
||||
}
|
||||
|
||||
- (void) startTimerIfNeeded {
|
||||
@@ -147,7 +293,7 @@
|
||||
@@ -147,7 +305,7 @@
|
||||
target: self
|
||||
selector: @selector(timer:)
|
||||
userInfo: nil
|
||||
@@ -9829,14 +9843,25 @@ index 5dce8c56..77b729c7 100644
|
||||
}
|
||||
|
||||
- (void) deleteTextureId: (NSNumber *) textureId {
|
||||
@@ -155,7 +301,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
|
||||
|
||||
+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)
|
||||
@@ -48,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);
|
||||
@@ -84,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");
|
||||
@@ -100,6 +102,9 @@ 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");
|
||||
|
||||
Reference in new issue
Block a user