Skip to content

Commit ca73de7

Browse files
committed
Fix crashes
Based off the pull request at BradLarson#243
1 parent 3a2275e commit ca73de7

24 files changed

+276
-135
lines changed

framework/Source/Apple/PictureInput.swift

Lines changed: 76 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,40 @@ import UIKit
1010
import Cocoa
1111
#endif
1212

13+
public enum PictureInputError: Error, CustomStringConvertible {
14+
case zeroSizedImageError
15+
case dataProviderNilError
16+
case noSuchImageError(imageName: String)
17+
18+
public var errorDescription: String {
19+
switch self {
20+
case .zeroSizedImageError:
21+
return "Tried to pass in a zero-sized image"
22+
case .dataProviderNilError:
23+
return "Unable to retrieve image dataProvider"
24+
case .noSuchImageError(let imageName):
25+
return "No such image named: \(imageName) in your application bundle"
26+
}
27+
}
28+
29+
public var description: String {
30+
return "<\(type(of: self)): errorDescription = \(self.errorDescription)>"
31+
}
32+
}
33+
1334
public class PictureInput: ImageSource {
1435
public let targets = TargetContainer()
15-
var imageFramebuffer:Framebuffer!
36+
var imageFramebuffer:Framebuffer?
37+
public var framebufferUserInfo:[AnyHashable:Any]?
1638
var hasProcessedImage:Bool = false
1739

18-
public init(image:CGImage, smoothlyScaleOutput:Bool = false, orientation:ImageOrientation = .portrait) {
40+
public init(image:CGImage, smoothlyScaleOutput:Bool = false, orientation:ImageOrientation = .portrait) throws {
1941
// TODO: Dispatch this whole thing asynchronously to move image loading off main thread
2042
let widthOfImage = GLint(image.width)
2143
let heightOfImage = GLint(image.height)
2244

2345
// If passed an empty image reference, CGContextDrawImage will fail in future versions of the SDK.
24-
guard((widthOfImage > 0) && (heightOfImage > 0)) else { fatalError("Tried to pass in a zero-sized image") }
46+
guard((widthOfImage > 0) && (heightOfImage > 0)) else { throw PictureInputError.zeroSizedImageError }
2547

2648
var widthToUseForTexture = widthOfImage
2749
var heightToUseForTexture = heightOfImage
@@ -67,12 +89,12 @@ public class PictureInput: ImageSource {
6789
if (bitmapInfo.contains(.byteOrder32Little)) {
6890
/* Little endian, for alpha-first we can use this bitmap directly in GL */
6991
if ((alphaInfo != CGImageAlphaInfo.premultipliedFirst) && (alphaInfo != CGImageAlphaInfo.first) && (alphaInfo != CGImageAlphaInfo.noneSkipFirst)) {
70-
shouldRedrawUsingCoreGraphics = true
92+
shouldRedrawUsingCoreGraphics = true
7193
}
7294
} else if ((bitmapInfo.contains(CGBitmapInfo())) || (bitmapInfo.contains(.byteOrder32Big))) {
7395
/* Big endian, for alpha-last we can use this bitmap directly in GL */
7496
if ((alphaInfo != CGImageAlphaInfo.premultipliedLast) && (alphaInfo != CGImageAlphaInfo.last) && (alphaInfo != CGImageAlphaInfo.noneSkipLast)) {
75-
shouldRedrawUsingCoreGraphics = true
97+
shouldRedrawUsingCoreGraphics = true
7698
} else {
7799
/* Can access directly using GL_RGBA pixel format */
78100
format = GL_RGBA
@@ -82,36 +104,30 @@ public class PictureInput: ImageSource {
82104
}
83105
}
84106

85-
// CFAbsoluteTime elapsedTime, startTime = CFAbsoluteTimeGetCurrent();
86-
87-
if (shouldRedrawUsingCoreGraphics) {
88-
// For resized or incompatible image: redraw
89-
imageData = UnsafeMutablePointer<GLubyte>.allocate(capacity:Int(widthToUseForTexture * heightToUseForTexture) * 4)
90-
91-
let genericRGBColorspace = CGColorSpaceCreateDeviceRGB()
107+
try sharedImageProcessingContext.runOperationSynchronously {
108+
// CFAbsoluteTime elapsedTime, startTime = CFAbsoluteTimeGetCurrent();
92109

93-
let imageContext = CGContext(data:imageData, width:Int(widthToUseForTexture), height:Int(heightToUseForTexture), bitsPerComponent:8, bytesPerRow:Int(widthToUseForTexture) * 4, space:genericRGBColorspace, bitmapInfo:CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)
94-
// CGContextSetBlendMode(imageContext, kCGBlendModeCopy); // From Technical Q&A QA1708: http://developer.apple.com/library/ios/#qa/qa1708/_index.html
95-
imageContext?.draw(image, in:CGRect(x:0.0, y:0.0, width:CGFloat(widthToUseForTexture), height:CGFloat(heightToUseForTexture)))
96-
} else {
97-
// Access the raw image bytes directly
98-
dataFromImageDataProvider = image.dataProvider?.data
99-
#if os(iOS)
100-
imageData = UnsafeMutablePointer<GLubyte>(mutating:CFDataGetBytePtr(dataFromImageDataProvider))
101-
#else
102-
imageData = UnsafeMutablePointer<GLubyte>(mutating:CFDataGetBytePtr(dataFromImageDataProvider)!)
103-
#endif
104-
}
105-
106-
sharedImageProcessingContext.runOperationSynchronously{
107-
do {
108-
// TODO: Alter orientation based on metadata from photo
109-
self.imageFramebuffer = try Framebuffer(context:sharedImageProcessingContext, orientation:orientation, size:GLSize(width:widthToUseForTexture, height:heightToUseForTexture), textureOnly:true)
110-
} catch {
111-
fatalError("ERROR: Unable to initialize framebuffer of size (\(widthToUseForTexture), \(heightToUseForTexture)) with error: \(error)")
110+
if (shouldRedrawUsingCoreGraphics) {
111+
// For resized or incompatible image: redraw
112+
imageData = UnsafeMutablePointer<GLubyte>.allocate(capacity:Int(widthToUseForTexture * heightToUseForTexture) * 4)
113+
114+
let genericRGBColorspace = CGColorSpaceCreateDeviceRGB()
115+
116+
let imageContext = CGContext(data: imageData, width: Int(widthToUseForTexture), height: Int(heightToUseForTexture), bitsPerComponent: 8, bytesPerRow: Int(widthToUseForTexture) * 4, space: genericRGBColorspace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)
117+
// CGContextSetBlendMode(imageContext, kCGBlendModeCopy); // From Technical Q&A QA1708: http://developer.apple.com/library/ios/#qa/qa1708/_index.html
118+
imageContext?.draw(image, in:CGRect(x:0.0, y:0.0, width:CGFloat(widthToUseForTexture), height:CGFloat(heightToUseForTexture)))
119+
} else {
120+
// Access the raw image bytes directly
121+
guard let data = image.dataProvider?.data else { throw PictureInputError.dataProviderNilError }
122+
dataFromImageDataProvider = data
123+
imageData = UnsafeMutablePointer<GLubyte>(mutating:CFDataGetBytePtr(dataFromImageDataProvider))
112124
}
113-
114-
glBindTexture(GLenum(GL_TEXTURE_2D), self.imageFramebuffer.texture)
125+
126+
// TODO: Alter orientation based on metadata from photo
127+
self.imageFramebuffer = try Framebuffer(context:sharedImageProcessingContext, orientation:orientation, size:GLSize(width:widthToUseForTexture, height:heightToUseForTexture), textureOnly:true)
128+
self.imageFramebuffer!.lock()
129+
130+
glBindTexture(GLenum(GL_TEXTURE_2D), self.imageFramebuffer!.texture)
115131
if (smoothlyScaleOutput) {
116132
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR_MIPMAP_LINEAR)
117133
}
@@ -130,45 +146,53 @@ public class PictureInput: ImageSource {
130146
}
131147

132148
#if canImport(UIKit)
133-
public convenience init(image:UIImage, smoothlyScaleOutput:Bool = false, orientation:ImageOrientation = .portrait) {
134-
self.init(image:image.cgImage!, smoothlyScaleOutput:smoothlyScaleOutput, orientation:orientation)
149+
public convenience init(image:UIImage, smoothlyScaleOutput:Bool = false, orientation:ImageOrientation = .portrait) throws {
150+
try self.init(image:image.cgImage!, smoothlyScaleOutput:smoothlyScaleOutput, orientation:orientation)
135151
}
136152
#else
137153
public convenience init(image:NSImage, smoothlyScaleOutput:Bool = false, orientation:ImageOrientation = .portrait) {
138-
self.init(image:image.cgImage(forProposedRect:nil, context:nil, hints:nil)!, smoothlyScaleOutput:smoothlyScaleOutput, orientation:orientation)
154+
try self.init(image:image.cgImage(forProposedRect:nil, context:nil, hints:nil)!, smoothlyScaleOutput:smoothlyScaleOutput, orientation:orientation)
139155
}
140156
#endif
141157

142-
public convenience init(imageName:String, smoothlyScaleOutput:Bool = false, orientation:ImageOrientation = .portrait) {
158+
public convenience init(imageName:String, smoothlyScaleOutput:Bool = false, orientation:ImageOrientation = .portrait) throws {
143159
#if canImport(UIKit)
144-
guard let image = UIImage(named:imageName) else { fatalError("No such image named: \(imageName) in your application bundle") }
145-
self.init(image:image.cgImage!, smoothlyScaleOutput:smoothlyScaleOutput, orientation:orientation)
160+
guard let image = UIImage(named:imageName) else { throw PictureInputError.noSuchImageError(imageName: imageName) }
161+
try self.init(image:image.cgImage!, smoothlyScaleOutput:smoothlyScaleOutput, orientation:orientation)
146162
#else
147-
guard let image = NSImage(named:NSImage.Name(imageName)) else { fatalError("No such image named: \(imageName) in your application bundle") }
148-
self.init(image:image.cgImage(forProposedRect:nil, context:nil, hints:nil)!, smoothlyScaleOutput:smoothlyScaleOutput, orientation:orientation)
163+
guard let image = NSImage(named:NSImage.Name(imageName)) else { throw PictureInputError.noSuchImageError(imageName: imageName) }
164+
try self.init(image:image.cgImage(forProposedRect:nil, context:nil, hints:nil)!, smoothlyScaleOutput:smoothlyScaleOutput, orientation:orientation)
149165
#endif
150166
}
151167

168+
deinit {
169+
self.imageFramebuffer?.unlock()
170+
}
171+
152172
public func processImage(synchronously:Bool = false) {
153173
if synchronously {
154-
sharedImageProcessingContext.runOperationSynchronously{
155-
sharedImageProcessingContext.makeCurrentContext()
156-
self.updateTargetsWithFramebuffer(self.imageFramebuffer)
157-
self.hasProcessedImage = true
174+
sharedImageProcessingContext.runOperationSynchronously {
175+
if let framebuffer = self.imageFramebuffer {
176+
sharedImageProcessingContext.makeCurrentContext()
177+
self.updateTargetsWithFramebuffer(framebuffer)
178+
self.hasProcessedImage = true
179+
}
158180
}
159181
} else {
160-
sharedImageProcessingContext.runOperationAsynchronously{
161-
sharedImageProcessingContext.makeCurrentContext()
162-
self.updateTargetsWithFramebuffer(self.imageFramebuffer)
163-
self.hasProcessedImage = true
182+
sharedImageProcessingContext.runOperationAsynchronously {
183+
if let framebuffer = self.imageFramebuffer {
184+
sharedImageProcessingContext.makeCurrentContext()
185+
self.updateTargetsWithFramebuffer(framebuffer)
186+
self.hasProcessedImage = true
187+
}
164188
}
165189
}
166190
}
167191

168192
public func transmitPreviousImage(to target:ImageConsumer, atIndex:UInt) {
169-
if hasProcessedImage {
170-
imageFramebuffer.lock()
171-
target.newFramebufferAvailable(imageFramebuffer, fromSourceIndex:atIndex)
193+
if let framebuffer = self.imageFramebuffer, hasProcessedImage {
194+
framebuffer.lock()
195+
target.newFramebufferAvailable(framebuffer, fromSourceIndex:atIndex)
172196
}
173197
}
174198
}

framework/Source/Apple/PictureOutput.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public class PictureOutput: ImageConsumer {
6464
renderFramebuffer.unlock()
6565
guard let dataProvider = CGDataProvider(dataInfo:nil, data:data, size:imageByteSize, releaseData: dataProviderReleaseCallback) else {fatalError("Could not allocate a CGDataProvider")}
6666
let defaultRGBColorSpace = CGColorSpaceCreateDeviceRGB()
67-
return CGImage(width:Int(framebuffer.size.width), height:Int(framebuffer.size.height), bitsPerComponent:8, bitsPerPixel:32, bytesPerRow:4 * Int(framebuffer.size.width), space:defaultRGBColorSpace, bitmapInfo:CGBitmapInfo() /*| CGImageAlphaInfo.Last*/, provider:dataProvider, decode:nil, shouldInterpolate:false, intent:.defaultIntent)!
67+
return CGImage(width:Int(framebuffer.size.width), height:Int(framebuffer.size.height), bitsPerComponent:8, bitsPerPixel:32, bytesPerRow:4 * Int(framebuffer.size.width), space:defaultRGBColorSpace, bitmapInfo:CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue), provider:dataProvider, decode:nil, shouldInterpolate:false, intent:.defaultIntent)!
6868
}
6969

7070
public func newFramebufferAvailable(_ framebuffer:Framebuffer, fromSourceIndex:UInt) {
@@ -140,15 +140,15 @@ public extension ImageSource {
140140
}
141141

142142
public extension PlatformImageType {
143-
func filterWithOperation<T:ImageProcessingOperation>(_ operation:T) -> PlatformImageType {
144-
return filterWithPipeline{input, output in
143+
func filterWithOperation<T:ImageProcessingOperation>(_ operation:T) throws -> PlatformImageType {
144+
return try filterWithPipeline{input, output in
145145
input --> operation --> output
146146
}
147147
}
148148

149-
func filterWithPipeline(_ pipeline:(PictureInput, PictureOutput) -> ()) -> PlatformImageType {
149+
func filterWithPipeline(_ pipeline:(PictureInput, PictureOutput) -> ()) throws -> PlatformImageType {
150150
var outputImage:PlatformImageType?
151-
let picture = PictureInput(image:self)
151+
let picture = try PictureInput(image:self)
152152
let pictureOutput = PictureOutput()
153153
pictureOutput.onlyCaptureNextFrame = true
154154
pictureOutput.imageAvailableCallback = {image in

framework/Source/Apple/RenderView-UIKit.swift

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class RenderView:UIView, ImageConsumer {
2020
return sharedImageProcessingContext.passthroughShader
2121
}()
2222

23-
// TODO: Need to set viewport to appropriate size, resize viewport on view reshape
23+
private var internalLayer: CAEAGLLayer!
2424

2525
required public init?(coder:NSCoder) {
2626
super.init(coder:coder)
@@ -37,20 +37,36 @@ public class RenderView:UIView, ImageConsumer {
3737
return CAEAGLLayer.self
3838
}
3939
}
40+
41+
override public var bounds: CGRect {
42+
didSet {
43+
// Check if the size changed
44+
if(oldValue.size != self.bounds.size) {
45+
// Destroy the displayFramebuffer so we render at the correct size for the next frame
46+
sharedImageProcessingContext.runOperationAsynchronously{
47+
self.destroyDisplayFramebuffer()
48+
}
49+
}
50+
}
51+
}
4052

4153
func commonInit() {
4254
self.contentScaleFactor = UIScreen.main.scale
4355

4456
let eaglLayer = self.layer as! CAEAGLLayer
4557
eaglLayer.isOpaque = true
46-
eaglLayer.drawableProperties = [String(describing: NSNumber(value:false)): kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8: kEAGLDrawablePropertyColorFormat]
58+
eaglLayer.drawableProperties = [kEAGLDrawablePropertyRetainedBacking: false, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8]
59+
60+
self.internalLayer = eaglLayer
4761
}
4862

4963
deinit {
50-
destroyDisplayFramebuffer()
64+
sharedImageProcessingContext.runOperationSynchronously{
65+
destroyDisplayFramebuffer()
66+
}
5167
}
5268

53-
func createDisplayFramebuffer() {
69+
func createDisplayFramebuffer() -> Bool {
5470
var newDisplayFramebuffer:GLuint = 0
5571
glGenFramebuffers(1, &newDisplayFramebuffer)
5672
displayFramebuffer = newDisplayFramebuffer
@@ -61,39 +77,53 @@ public class RenderView:UIView, ImageConsumer {
6177
displayRenderbuffer = newDisplayRenderbuffer
6278
glBindRenderbuffer(GLenum(GL_RENDERBUFFER), displayRenderbuffer!)
6379

64-
sharedImageProcessingContext.context.renderbufferStorage(Int(GL_RENDERBUFFER), from:self.layer as! CAEAGLLayer)
80+
// Without the flush you will occasionally get a warning from UIKit and when that happens the RenderView just stays black.
81+
// "CoreAnimation: [EAGLContext renderbufferStorage:fromDrawable:] was called from a non-main thread in an implicit transaction!
82+
// Note that this may be unsafe without an explicit CATransaction or a call to [CATransaction flush]."
83+
// I tried a transaction and that doesn't work and this is probably why --> http://danielkbx.com/post/108060601989/catransaction-flush
84+
// Using flush is important because it guarantees the view is layed out at the correct size before it is drawn to since this is being done on a background thread.
85+
// Its possible the size of the view was changed right before we got here and would result in us drawing to the view at the old size
86+
// and then the view size would change to the new size at the next layout pass and distort our already drawn image.
87+
// Since we do not call this function often we do not need to worry about the performance impact of calling flush.
88+
CATransaction.flush()
89+
sharedImageProcessingContext.context.renderbufferStorage(Int(GL_RENDERBUFFER), from:self.internalLayer)
6590

6691
var backingWidth:GLint = 0
6792
var backingHeight:GLint = 0
6893
glGetRenderbufferParameteriv(GLenum(GL_RENDERBUFFER), GLenum(GL_RENDERBUFFER_WIDTH), &backingWidth)
6994
glGetRenderbufferParameteriv(GLenum(GL_RENDERBUFFER), GLenum(GL_RENDERBUFFER_HEIGHT), &backingHeight)
7095
backingSize = GLSize(width:backingWidth, height:backingHeight)
7196

72-
guard ((backingWidth > 0) && (backingHeight > 0)) else {
73-
fatalError("View had a zero size")
97+
guard (backingWidth > 0 && backingHeight > 0) else {
98+
print("WARNING: View had a zero size")
99+
100+
if(self.internalLayer.bounds.width > 0 && self.internalLayer.bounds.height > 0) {
101+
print("WARNING: View size \(self.internalLayer.bounds) may be too large ")
102+
}
103+
return false
74104
}
75105

76106
glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), displayRenderbuffer!)
77107

78108
let status = glCheckFramebufferStatus(GLenum(GL_FRAMEBUFFER))
79109
if (status != GLenum(GL_FRAMEBUFFER_COMPLETE)) {
80-
fatalError("Display framebuffer creation failed with error: \(FramebufferCreationError(errorCode:status))")
110+
print("WARNING: Display framebuffer creation failed with error: \(FramebufferCreationError(errorCode:status))")
111+
return false
81112
}
113+
114+
return true
82115
}
83116

84117
func destroyDisplayFramebuffer() {
85-
sharedImageProcessingContext.runOperationSynchronously{
86-
if let displayFramebuffer = self.displayFramebuffer {
87-
var temporaryFramebuffer = displayFramebuffer
88-
glDeleteFramebuffers(1, &temporaryFramebuffer)
89-
self.displayFramebuffer = nil
90-
}
91-
92-
if let displayRenderbuffer = self.displayRenderbuffer {
93-
var temporaryRenderbuffer = displayRenderbuffer
94-
glDeleteRenderbuffers(1, &temporaryRenderbuffer)
95-
self.displayRenderbuffer = nil
96-
}
118+
if let displayFramebuffer = self.displayFramebuffer {
119+
var temporaryFramebuffer = displayFramebuffer
120+
glDeleteFramebuffers(1, &temporaryFramebuffer)
121+
self.displayFramebuffer = nil
122+
}
123+
if let displayRenderbuffer = self.displayRenderbuffer {
124+
var temporaryRenderbuffer = displayRenderbuffer
125+
glDeleteRenderbuffers(1, &temporaryRenderbuffer)
126+
self.displayRenderbuffer = nil
97127
}
98128
}
99129

@@ -103,8 +133,10 @@ public class RenderView:UIView, ImageConsumer {
103133
}
104134

105135
public func newFramebufferAvailable(_ framebuffer:Framebuffer, fromSourceIndex:UInt) {
106-
if (displayFramebuffer == nil) {
107-
self.createDisplayFramebuffer()
136+
if (self.displayFramebuffer == nil && !self.createDisplayFramebuffer()) {
137+
// Bail if we couldn't successfully create the displayFramebuffer
138+
framebuffer.unlock()
139+
return
108140
}
109141
self.activateDisplayFramebuffer()
110142

0 commit comments

Comments
 (0)