mirror of
https://github.com/darlinghq/darling-cocotron.git
synced 2024-11-23 04:00:00 +00:00
55bafce1f1
In its current state, I consider this code to be experimental and unstable. The biggest problem is that there's currently a HUGE memory leak somewhere (over 2GiB in under 5 seconds at 60fps). I'm committing what I have now since I don't feel like debugging that right now and it *does* technically work as-is. The current approach is not great for performance: first, we render to a Metal/Vulkan texture in the desired format then blit that to an RGBA Metal/Vulkan texture shared with OpenGL. Then, we copy *that* to another OpenGL texture in CAMetalLayer to use as the content for the layer (we can't keep the drawable's OpenGL texture, since that has to be recycled). Finally, this is rendered to a subwindow by CARenderer/CALayerContext. We cannot get rid of the last copy since we must render to the subwindow somehow and it's the easiest way to play nice with sublayers. I also don't think we can get rid of the first copy since OpenGL doesn't support some of Metal/Vulkan's texture formats; plus, we can't share optimally tiled images between Vulkan and OpenGL with some vendors' drivers (e.g. AMD). We *could* get rid of the second copy if we were able to accurately determine when the content is finally presented; then we could simply keep the drawable in-use, render it to the subwindow when asked to, and release it once we know the render is complete.
396 lines
11 KiB
Objective-C
396 lines
11 KiB
Objective-C
#import <CoreVideo/CoreVideo.h>
|
|
#import <Onyx2D/O2Surface.h>
|
|
#import <OpenGL/OpenGL.h>
|
|
#import <QuartzCore/CAAnimation.h>
|
|
#import <QuartzCore/CAMediaTimingFunction.h>
|
|
#import <QuartzCore/CARenderer.h>
|
|
#import "CALayerInternal.h"
|
|
|
|
NSString *const kCARendererColorSpace = @"kCARendererColorSpace";
|
|
|
|
@implementation CARenderer
|
|
|
|
- (CGRect) bounds {
|
|
return _bounds;
|
|
}
|
|
|
|
- (void) setBounds: (CGRect) value {
|
|
_bounds = value;
|
|
}
|
|
|
|
@synthesize layer = _rootLayer;
|
|
|
|
- initWithCGLContext: (void *) cglContext options: (NSDictionary *) options {
|
|
_cglContext = cglContext;
|
|
_bounds = CGRectZero;
|
|
_rootLayer = nil;
|
|
return self;
|
|
}
|
|
|
|
+ (CARenderer *) rendererWithCGLContext: (void *) cglContext
|
|
options: (NSDictionary *) options
|
|
{
|
|
return [[[self alloc] initWithCGLContext: cglContext
|
|
options: options] autorelease];
|
|
}
|
|
|
|
static void startAnimationsInLayer(CALayer *layer, CFTimeInterval currentTime) {
|
|
NSArray *keys = [layer animationKeys];
|
|
|
|
for (NSString *key in keys) {
|
|
CAAnimation *check = [layer animationForKey: key];
|
|
|
|
if ([check beginTime] == 0.0)
|
|
[check setBeginTime: currentTime];
|
|
if (currentTime > [check beginTime] + [check duration]) {
|
|
[layer removeAnimationForKey: key];
|
|
}
|
|
}
|
|
|
|
for (CALayer *child in layer.sublayers)
|
|
startAnimationsInLayer(child, currentTime);
|
|
}
|
|
|
|
- (void) beginFrameAtTime: (CFTimeInterval) currentTime
|
|
timeStamp: (CVTimeStamp *) timeStamp
|
|
{
|
|
startAnimationsInLayer(_rootLayer, currentTime);
|
|
}
|
|
|
|
static inline CGFloat cubed(CGFloat value) {
|
|
return value * value * value;
|
|
}
|
|
|
|
static inline CGFloat squared(CGFloat value) {
|
|
return value * value;
|
|
}
|
|
|
|
static CGFloat applyMediaTimingFunction(CAMediaTimingFunction *function,
|
|
CGFloat t)
|
|
{
|
|
CGFloat result;
|
|
CGFloat cp1[2];
|
|
CGFloat cp2[2];
|
|
|
|
[function getControlPointAtIndex: 1 values: cp1];
|
|
[function getControlPointAtIndex: 2 values: cp2];
|
|
|
|
double x = cubed(1.0 - t) * 0.0 + 3 * squared(1 - t) * t * cp1[0] +
|
|
3 * (1 - t) * squared(t) * cp2[0] + cubed(t) * 1.0;
|
|
double y = cubed(1.0 - t) * 0.0 + 3 * squared(1 - t) * t * cp1[1] +
|
|
3 * (1 - t) * squared(t) * cp2[1] + cubed(t) * 1.0;
|
|
|
|
// this is wrong
|
|
return y;
|
|
}
|
|
|
|
static CGFloat mediaTimingScale(CAAnimation *animation,
|
|
CFTimeInterval currentTime)
|
|
{
|
|
CFTimeInterval begin = [animation beginTime];
|
|
CFTimeInterval duration = [animation duration];
|
|
CFTimeInterval delta = currentTime - begin;
|
|
double zeroToOne = delta / duration;
|
|
CAMediaTimingFunction *function = [animation timingFunction];
|
|
|
|
if (function == nil)
|
|
function = [CAMediaTimingFunction
|
|
functionWithName: kCAMediaTimingFunctionDefault];
|
|
|
|
return applyMediaTimingFunction(function, zeroToOne);
|
|
}
|
|
|
|
static CGFloat interpolateFloatInLayerKey(CALayer *layer, NSString *key,
|
|
CFTimeInterval currentTime)
|
|
{
|
|
CAAnimation *animation = [layer animationForKey: key];
|
|
|
|
if (animation == nil)
|
|
return [[layer valueForKey: key] floatValue];
|
|
|
|
if ([animation isKindOfClass: [CABasicAnimation class]]) {
|
|
CABasicAnimation *basic = (CABasicAnimation *) animation;
|
|
|
|
id fromValue = [basic fromValue];
|
|
id toValue = [basic toValue];
|
|
|
|
if (toValue == nil)
|
|
toValue = [layer valueForKey: key];
|
|
|
|
CGFloat fromFloat = [fromValue floatValue];
|
|
CGFloat toFloat = [toValue floatValue];
|
|
|
|
CGFloat resultFloat;
|
|
double timingScale = mediaTimingScale(animation, currentTime);
|
|
|
|
resultFloat = fromFloat + (toFloat - fromFloat) * timingScale;
|
|
|
|
return resultFloat;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static CGPoint interpolatePointInLayerKey(CALayer *layer, NSString *key,
|
|
CFTimeInterval currentTime)
|
|
{
|
|
CAAnimation *animation = [layer animationForKey: key];
|
|
|
|
if (animation == nil)
|
|
return [[layer valueForKey: key] pointValue];
|
|
|
|
if ([animation isKindOfClass: [CABasicAnimation class]]) {
|
|
CABasicAnimation *basic = (CABasicAnimation *) animation;
|
|
|
|
id fromValue = [basic fromValue];
|
|
id toValue = [basic toValue];
|
|
|
|
if (toValue == nil)
|
|
toValue = [layer valueForKey: key];
|
|
|
|
CGPoint fromPoint = [fromValue pointValue];
|
|
CGPoint toPoint = [toValue pointValue];
|
|
|
|
CGPoint resultPoint;
|
|
double timingScale = mediaTimingScale(animation, currentTime);
|
|
|
|
resultPoint.x = fromPoint.x + (toPoint.x - fromPoint.x) * timingScale;
|
|
resultPoint.y = fromPoint.y + (toPoint.y - fromPoint.y) * timingScale;
|
|
|
|
return resultPoint;
|
|
}
|
|
|
|
return CGPointMake(0, 0);
|
|
}
|
|
|
|
static CGRect interpolateRectInLayerKey(CALayer *layer, NSString *key,
|
|
CFTimeInterval currentTime)
|
|
{
|
|
CAAnimation *animation = [layer animationForKey: key];
|
|
|
|
if (animation == nil) {
|
|
return [[layer valueForKey: key] rectValue];
|
|
}
|
|
|
|
if ([animation isKindOfClass: [CABasicAnimation class]]) {
|
|
CABasicAnimation *basic = (CABasicAnimation *) animation;
|
|
|
|
id fromValue = [basic fromValue];
|
|
id toValue = [basic toValue];
|
|
|
|
if (toValue == nil)
|
|
toValue = [layer valueForKey: key];
|
|
|
|
CGRect fromRect = [fromValue rectValue];
|
|
CGRect toRect = [toValue rectValue];
|
|
|
|
double timingScale = mediaTimingScale(animation, currentTime);
|
|
|
|
CGRect resultRect;
|
|
|
|
resultRect.origin.x =
|
|
fromRect.origin.x +
|
|
(toRect.origin.x - fromRect.origin.x) * timingScale;
|
|
resultRect.origin.y =
|
|
fromRect.origin.y +
|
|
(toRect.origin.y - fromRect.origin.y) * timingScale;
|
|
resultRect.size.width =
|
|
fromRect.size.width +
|
|
(toRect.size.width - fromRect.size.width) * timingScale;
|
|
resultRect.size.height =
|
|
fromRect.size.height +
|
|
(toRect.size.height - fromRect.size.height) * timingScale;
|
|
|
|
return resultRect;
|
|
}
|
|
|
|
return CGRectMake(0, 0, 0, 0);
|
|
}
|
|
|
|
static GLint interpolationFromName(NSString *name) {
|
|
if (name == kCAFilterLinear)
|
|
return GL_LINEAR;
|
|
else if (name == kCAFilterNearest)
|
|
return GL_NEAREST;
|
|
else if ([name isEqualToString: kCAFilterLinear])
|
|
return GL_LINEAR;
|
|
else if ([name isEqualToString: kCAFilterNearest])
|
|
return GL_NEAREST;
|
|
else
|
|
return GL_LINEAR;
|
|
}
|
|
|
|
void CATexImage2DCGImage(CGImageRef image) {
|
|
size_t imageWidth = CGImageGetWidth(image);
|
|
size_t imageHeight = CGImageGetHeight(image);
|
|
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image);
|
|
|
|
CGDataProviderRef provider = CGImageGetDataProvider(image);
|
|
CFDataRef data = CGDataProviderCopyData(provider);
|
|
const uint8_t *pixelBytes = CFDataGetBytePtr(data);
|
|
|
|
GLenum glFormat = GL_BGRA;
|
|
GLenum glType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
|
|
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
|
|
CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask;
|
|
|
|
switch (alphaInfo) {
|
|
|
|
case kCGImageAlphaNone:
|
|
break;
|
|
|
|
case kCGImageAlphaPremultipliedLast:
|
|
if (byteOrder == kO2BitmapByteOrder32Big) {
|
|
glFormat = GL_RGBA;
|
|
glType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
}
|
|
break;
|
|
|
|
case kCGImageAlphaPremultipliedFirst: // ARGB
|
|
if (byteOrder == kCGBitmapByteOrder32Little) {
|
|
glFormat = GL_BGRA;
|
|
glType = GL_UNSIGNED_INT_8_8_8_8_REV;
|
|
}
|
|
break;
|
|
|
|
case kCGImageAlphaLast:
|
|
break;
|
|
|
|
case kCGImageAlphaFirst:
|
|
break;
|
|
|
|
case kCGImageAlphaNoneSkipLast:
|
|
break;
|
|
|
|
case kCGImageAlphaNoneSkipFirst:
|
|
break;
|
|
|
|
case kCGImageAlphaOnly:
|
|
break;
|
|
}
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, imageWidth, imageHeight, 0,
|
|
glFormat, glType, pixelBytes);
|
|
}
|
|
|
|
- (void) _renderLayer: (CALayer *) layer
|
|
z: (CGFloat) z
|
|
currentTime: (CFTimeInterval) currentTime
|
|
{
|
|
NSNumber *textureId = [layer _textureId];
|
|
GLuint texture = [textureId unsignedIntValue];
|
|
GLboolean loadPixelData = GL_FALSE;
|
|
|
|
if (texture == 0)
|
|
loadPixelData = GL_TRUE;
|
|
else {
|
|
|
|
if (glIsTexture(texture) == GL_FALSE) {
|
|
loadPixelData = GL_TRUE;
|
|
}
|
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
}
|
|
|
|
if (loadPixelData) {
|
|
CGImageRef image = layer.contents;
|
|
|
|
CATexImage2DCGImage(image);
|
|
|
|
GLint minFilter = interpolationFromName(layer.minificationFilter);
|
|
GLint magFilter = interpolationFromName(layer.magnificationFilter);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
}
|
|
|
|
CGPoint anchorPoint =
|
|
interpolatePointInLayerKey(layer, @"anchorPoint", currentTime);
|
|
CGPoint position =
|
|
interpolatePointInLayerKey(layer, @"position", currentTime);
|
|
CGRect bounds = interpolateRectInLayerKey(layer, @"bounds", currentTime);
|
|
CGFloat opacity =
|
|
interpolateFloatInLayerKey(layer, @"opacity", currentTime);
|
|
|
|
GLfloat textureVertices[4 * 2];
|
|
GLfloat vertices[4 * 3];
|
|
|
|
textureVertices[0] = 0;
|
|
textureVertices[1] = 1;
|
|
textureVertices[2] = 1;
|
|
textureVertices[3] = 1;
|
|
textureVertices[4] = 0;
|
|
textureVertices[5] = 0;
|
|
textureVertices[6] = 1;
|
|
textureVertices[7] = 0;
|
|
|
|
vertices[0] = 0;
|
|
vertices[1] = 0;
|
|
vertices[2] = z;
|
|
|
|
vertices[3] = bounds.size.width;
|
|
vertices[4] = 0;
|
|
vertices[5] = z;
|
|
|
|
vertices[6] = 0;
|
|
vertices[7] = bounds.size.height;
|
|
vertices[8] = z;
|
|
|
|
vertices[9] = bounds.size.width;
|
|
vertices[10] = bounds.size.height;
|
|
vertices[11] = z;
|
|
|
|
glPushMatrix();
|
|
// glTranslatef(width/2,height/2,0);
|
|
glTexCoordPointer(2, GL_FLOAT, 0, textureVertices);
|
|
glVertexPointer(3, GL_FLOAT, 0, vertices);
|
|
|
|
glTranslatef(position.x - (bounds.size.width * anchorPoint.x),
|
|
position.y - (bounds.size.height * anchorPoint.y), 0);
|
|
// glTranslatef(position.x,position.y,0);
|
|
// glScalef(bounds.size.width,bounds.size.height,1);
|
|
|
|
// glRotatef(1,0,0,1);
|
|
glColor4f(opacity, opacity, opacity, opacity);
|
|
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
for (CALayer *child in layer.sublayers)
|
|
[self _renderLayer: child z: z + 1 currentTime: currentTime];
|
|
|
|
glPopMatrix();
|
|
}
|
|
|
|
- (void) render {
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
|
|
glClearColor(0, 0, 0, 1);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LEQUAL);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
glAlphaFunc(GL_GREATER, 0);
|
|
glEnable(GL_ALPHA_TEST);
|
|
|
|
[self _renderLayer: _rootLayer z: 0 currentTime: CACurrentMediaTime()];
|
|
|
|
glFlush();
|
|
}
|
|
|
|
- (void) endFrame {
|
|
}
|
|
|
|
@end
|