mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
140 lines
5.9 KiB
Python
140 lines
5.9 KiB
Python
"""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)
|