From 769fbde9aa2e7e10c6484d47fbda70d094f34b16 Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Fri, 12 Feb 2016 18:28:45 -0600 Subject: [PATCH 1/5] Add EditorConfig file --- .editorconfig | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fa00987 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +# EditorConfig: http://editorconfig.org + +[*.{m,h}] +indent_style = space +indent_size = 4 From bfa03120db497acc2e7b73483489f7472b3118d0 Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Mon, 15 Feb 2016 15:39:19 -0600 Subject: [PATCH 2/5] Handle OpenGL errors, also don't map/unmap a buffer so often. --- Source/PPSSignatureView.m | 367 +++++++++++++++++++++----------------- 1 file changed, 201 insertions(+), 166 deletions(-) diff --git a/Source/PPSSignatureView.m b/Source/PPSSignatureView.m index 9ad6d1e..5fd812a 100644 --- a/Source/PPSSignatureView.m +++ b/Source/PPSSignatureView.m @@ -1,6 +1,8 @@ #import "PPSSignatureView.h" #import +#define ERROR_OPENGL 1 + #define STROKE_WIDTH_MIN 0.004 // Stroke width determined by touch velocity #define STROKE_WIDTH_MAX 0.030 #define STROKE_WIDTH_SMOOTHING 0.5 // Low pass filter alpha @@ -28,25 +30,51 @@ // Maximum verteces in signature static const int maxLength = MAXIMUM_VERTECES; +static inline GLvoid *mapVertexBuffer(GLuint bufferToMap, NSError **error) { + glBindBuffer(GL_ARRAY_BUFFER, bufferToMap); + + GLvoid *data = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES); + + if (data == NULL && error != NULL) { + GLenum glError = glGetError(); + + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + userInfo[@"GL_ERROR"] = @(glError); + + *error = [[NSError alloc] initWithDomain:@"PPSSignatureView" code:ERROR_OPENGL userInfo:userInfo]; + } + + return data; +} // Append vertex to array buffer -static inline void addVertex(uint *length, PPSSignaturePoint v) { +static inline void addVertex(GLvoid *mappedBuffer, uint *length, PPSSignaturePoint vertex) { if ((*length) >= maxLength) { return; } - - GLvoid *data = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES); - memcpy(data + sizeof(PPSSignaturePoint) * (*length), &v, sizeof(PPSSignaturePoint)); - glUnmapBufferOES(GL_ARRAY_BUFFER); - + + memcpy(mappedBuffer + sizeof(PPSSignaturePoint) * (*length), &vertex, sizeof(PPSSignaturePoint)); (*length)++; } +static inline void unmapVertexBuffer(GLuint *mappedBuffer) { + if (mappedBuffer != NULL) { + GLboolean result = glUnmapBufferOES(GL_ARRAY_BUFFER); + + if (result == GL_FALSE) { + // GL docs say this indicates some kind of corruption, and the buffer should be reinitialized. + // TODO(rgrimm): Reinitialize the buffer + } + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + static inline CGPoint QuadraticPointInCurve(CGPoint start, CGPoint end, CGPoint controlPoint, float percent) { double a = pow((1.0 - percent), 2.0); double b = 2.0 * percent * (1.0 - percent); double c = pow(percent, 2.0); - + return (CGPoint) { a * start.x + b * controlPoint.x + c * end.x, a * start.y + b * controlPoint.y + c * end.y @@ -83,26 +111,26 @@ @interface PPSSignatureView () { // OpenGL state EAGLContext *context; GLKBaseEffect *effect; - + GLuint vertexArray; GLuint vertexBuffer; GLuint dotsArray; GLuint dotsBuffer; - - - // Array of verteces, with current length + + + // Array of vertices, with current length PPSSignaturePoint SignatureVertexData[maxLength]; uint length; - + PPSSignaturePoint SignatureDotsData[maxLength]; uint dotsLength; - - + + // Width of line at current and previous vertex float penThickness; float previousThickness; - - + + // Previous points for quadratic bezier computations CGPoint previousPoint; CGPoint previousMidPoint; @@ -118,42 +146,41 @@ @implementation PPSSignatureView - (void)commonInit { context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - + if (context) { time(NULL); - + self.backgroundColor = [UIColor whiteColor]; self.opaque = NO; - + self.context = context; self.drawableDepthFormat = GLKViewDrawableDepthFormat24; self.enableSetNeedsDisplay = YES; - + // Turn on antialiasing self.drawableMultisample = GLKViewDrawableMultisample4X; - + [self setupGL]; - + // Capture touches UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; pan.maximumNumberOfTouches = pan.minimumNumberOfTouches = 1; pan.cancelsTouchesInView = YES; [self addGestureRecognizer:pan]; - + // For dotting your i's UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; tap.cancelsTouchesInView = YES; [self addGestureRecognizer:tap]; - + // Erase with long press UILongPressGestureRecognizer *longer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; longer.cancelsTouchesInView = YES; [self addGestureRecognizer:longer]; - + } else [NSException raise:@"NSOpenGLES2ContextException" format:@"Failed to create OpenGL ES2 context"]; } - - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) [self commonInit]; @@ -171,7 +198,7 @@ - (id)initWithFrame:(CGRect)frame context:(EAGLContext *)ctx - (void)dealloc { [self tearDownGL]; - + if ([EAGLContext currentContext] == context) { [EAGLContext setCurrentContext:nil]; } @@ -185,7 +212,7 @@ - (void)drawRect:(CGRect)rect glClear(GL_COLOR_BUFFER_BIT); [effect prepareToDraw]; - + // Drawing of signature lines if (length > 2) { glBindVertexArrayOES(vertexArray); @@ -203,12 +230,10 @@ - (void)erase { length = 0; dotsLength = 0; self.hasSignature = NO; - + [self setNeedsDisplay]; } - - - (UIImage *)signatureImage { if (!self.hasSignature) @@ -219,7 +244,7 @@ - (UIImage *)signatureImage // self.strokeColor = [UIColor whiteColor]; // [self setNeedsDisplay]; UIImage *screenshot = [self snapshot]; - + // self.strokeColor = nil; // // self.hidden = NO; @@ -232,42 +257,47 @@ - (UIImage *)signatureImage - (void)tap:(UITapGestureRecognizer *)t { CGPoint l = [t locationInView:self]; - + if (t.state == UIGestureRecognizerStateRecognized) { - glBindBuffer(GL_ARRAY_BUFFER, dotsBuffer); - - PPSSignaturePoint touchPoint = ViewPointToGL(l, self.bounds, (GLKVector3){1, 1, 1}); - addVertex(&dotsLength, touchPoint); - - PPSSignaturePoint centerPoint = touchPoint; - centerPoint.color = StrokeColor; - addVertex(&dotsLength, centerPoint); - - static int segments = 20; - GLKVector2 radius = (GLKVector2){ - clamp(0.00001, 0.02, penThickness * generateRandom(0.5, 1.5)), - clamp(0.00001, 0.02, penThickness * generateRandom(0.5, 1.5)) - }; - GLKVector2 velocityRadius = radius; - float angle = 0; - - for (int i = 0; i <= segments; i++) { - - PPSSignaturePoint p = centerPoint; - p.vertex.x += velocityRadius.x * cosf(angle); - p.vertex.y += velocityRadius.y * sinf(angle); - - addVertex(&dotsLength, p); - addVertex(&dotsLength, centerPoint); - - angle += M_PI * 2.0 / segments; + NSError *error; + GLuint *mappedBuffer = mapVertexBuffer(dotsBuffer, &error); + + if (mappedBuffer == NULL) { + // TODO(rgrimm): Handle the error condition + } else { + PPSSignaturePoint touchPoint = ViewPointToGL(l, self.bounds, (GLKVector3) {1, 1, 1}); + addVertex(mappedBuffer, &dotsLength, touchPoint); + + PPSSignaturePoint centerPoint = touchPoint; + centerPoint.color = StrokeColor; + addVertex(mappedBuffer, &dotsLength, centerPoint); + + static int segments = 20; + GLKVector2 radius = (GLKVector2) { + clamp(0.00001, 0.02, penThickness * generateRandom(0.5, 1.5)), + clamp(0.00001, 0.02, penThickness * generateRandom(0.5, 1.5)) + }; + GLKVector2 velocityRadius = radius; + float angle = 0; + + for (int i = 0; i <= segments; i++) { + + PPSSignaturePoint p = centerPoint; + p.vertex.x += velocityRadius.x * cosf(angle); + p.vertex.y += velocityRadius.y * sinf(angle); + + addVertex(mappedBuffer, &dotsLength, p); + addVertex(mappedBuffer, &dotsLength, centerPoint); + + angle += M_PI * 2.0 / segments; + } + + addVertex(mappedBuffer, &dotsLength, touchPoint); } - - addVertex(&dotsLength, touchPoint); - - glBindBuffer(GL_ARRAY_BUFFER, 0); + + unmapVertexBuffer(mappedBuffer); } - + [self setNeedsDisplay]; } @@ -277,86 +307,93 @@ - (void)longPress:(UILongPressGestureRecognizer *)lp { } - (void)pan:(UIPanGestureRecognizer *)p { - - glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); - - CGPoint v = [p velocityInView:self]; - CGPoint l = [p locationInView:self]; - - currentVelocity = ViewPointToGL(v, self.bounds, (GLKVector3){0,0,0}); - float distance = 0.; - if (previousPoint.x > 0) { - distance = sqrtf((l.x - previousPoint.x) * (l.x - previousPoint.x) + (l.y - previousPoint.y) * (l.y - previousPoint.y)); - } - - float velocityMagnitude = sqrtf(v.x*v.x + v.y*v.y); - float clampedVelocityMagnitude = clamp(VELOCITY_CLAMP_MIN, VELOCITY_CLAMP_MAX, velocityMagnitude); - float normalizedVelocity = (clampedVelocityMagnitude - VELOCITY_CLAMP_MIN) / (VELOCITY_CLAMP_MAX - VELOCITY_CLAMP_MIN); - - float lowPassFilterAlpha = STROKE_WIDTH_SMOOTHING; - float newThickness = (STROKE_WIDTH_MAX - STROKE_WIDTH_MIN) * (1 - normalizedVelocity) + STROKE_WIDTH_MIN; - penThickness = penThickness * lowPassFilterAlpha + newThickness * (1 - lowPassFilterAlpha); - - if ([p state] == UIGestureRecognizerStateBegan) { - - previousPoint = l; - previousMidPoint = l; - - PPSSignaturePoint startPoint = ViewPointToGL(l, self.bounds, (GLKVector3){1, 1, 1}); - previousVertex = startPoint; - previousThickness = penThickness; - - addVertex(&length, startPoint); - addVertex(&length, previousVertex); - - self.hasSignature = YES; - - } else if ([p state] == UIGestureRecognizerStateChanged) { - - CGPoint mid = CGPointMake((l.x + previousPoint.x) / 2.0, (l.y + previousPoint.y) / 2.0); - - if (distance > QUADRATIC_DISTANCE_TOLERANCE) { - // Plot quadratic bezier instead of line - unsigned int i; - - int segments = (int) distance / 1.5; - - float startPenThickness = previousThickness; - float endPenThickness = penThickness; + + NSError *error; + GLuint *mappedBuffer = mapVertexBuffer(vertexBuffer, &error); + + if (mappedBuffer == NULL) { + // TODO(rgrimm): Handle the error condition + } else { + + CGPoint velocity = [p velocityInView:self]; + CGPoint location = [p locationInView:self]; + + currentVelocity = ViewPointToGL(velocity, self.bounds, (GLKVector3) {0, 0, 0}); + float distance = 0.; + if (previousPoint.x > 0) { + distance = sqrtf((location.x - previousPoint.x) * (location.x - previousPoint.x) + (location.y - previousPoint.y) * (location.y - previousPoint.y)); + } + + float velocityMagnitude = sqrtf(velocity.x * velocity.x + velocity.y * velocity.y); + float clampedVelocityMagnitude = clamp(VELOCITY_CLAMP_MIN, VELOCITY_CLAMP_MAX, velocityMagnitude); + float normalizedVelocity = (clampedVelocityMagnitude - VELOCITY_CLAMP_MIN) / (VELOCITY_CLAMP_MAX - VELOCITY_CLAMP_MIN); + + float lowPassFilterAlpha = STROKE_WIDTH_SMOOTHING; + float newThickness = (STROKE_WIDTH_MAX - STROKE_WIDTH_MIN) * (1 - normalizedVelocity) + STROKE_WIDTH_MIN; + penThickness = penThickness * lowPassFilterAlpha + newThickness * (1 - lowPassFilterAlpha); + + if ([p state] == UIGestureRecognizerStateBegan) { + + previousPoint = location; + previousMidPoint = location; + + PPSSignaturePoint startPoint = ViewPointToGL(location, self.bounds, (GLKVector3) {1, 1, 1}); + previousVertex = startPoint; previousThickness = penThickness; - - for (i = 0; i < segments; i++) - { - penThickness = startPenThickness + ((endPenThickness - startPenThickness) / segments) * i; - - CGPoint quadPoint = QuadraticPointInCurve(previousMidPoint, mid, previousPoint, (float)i / (float)(segments)); - - PPSSignaturePoint v = ViewPointToGL(quadPoint, self.bounds, StrokeColor); - [self addTriangleStripPointsForPrevious:previousVertex next:v]; - - previousVertex = v; + + addVertex(mappedBuffer, &length, startPoint); + addVertex(mappedBuffer, &length, previousVertex); + + self.hasSignature = YES; + + } else if ([p state] == UIGestureRecognizerStateChanged) { + + CGPoint mid = CGPointMake((location.x + previousPoint.x) / 2.0, (location.y + previousPoint.y) / 2.0); + + if (distance > QUADRATIC_DISTANCE_TOLERANCE) { + // Plot quadratic bezier instead of line + unsigned int i; + + int segments = (int) distance / 1.5; + + float startPenThickness = previousThickness; + float endPenThickness = penThickness; + previousThickness = penThickness; + + for (i = 0; i < segments; i++) { + penThickness = startPenThickness + ((endPenThickness - startPenThickness) / segments) * i; + + CGPoint quadPoint = QuadraticPointInCurve(previousMidPoint, mid, previousPoint, (float) i / (float) (segments)); + + PPSSignaturePoint vertex = ViewPointToGL(quadPoint, self.bounds, StrokeColor); + [self addTriangleStripPointsInMappedBuffer:mappedBuffer previous:previousVertex next:vertex]; + + previousVertex = vertex; + } + } else if (distance > 1.0) { + + PPSSignaturePoint vertex = ViewPointToGL(location, self.bounds, StrokeColor); + [self addTriangleStripPointsInMappedBuffer:mappedBuffer previous:previousVertex next:vertex]; + + previousVertex = vertex; + previousThickness = penThickness; } - } else if (distance > 1.0) { - - PPSSignaturePoint v = ViewPointToGL(l, self.bounds, StrokeColor); - [self addTriangleStripPointsForPrevious:previousVertex next:v]; - - previousVertex = v; - previousThickness = penThickness; + + previousPoint = location; + previousMidPoint = mid; + + } else if (p.state == UIGestureRecognizerStateEnded | p.state == UIGestureRecognizerStateCancelled) { + + PPSSignaturePoint vertex = ViewPointToGL(location, self.bounds, (GLKVector3) {1, 1, 1}); + addVertex(mappedBuffer, &length, vertex); + + previousVertex = vertex; + addVertex(mappedBuffer, &length, previousVertex); } - - previousPoint = l; - previousMidPoint = mid; - - } else if (p.state == UIGestureRecognizerStateEnded | p.state == UIGestureRecognizerStateCancelled) { - - PPSSignaturePoint v = ViewPointToGL(l, self.bounds, (GLKVector3){1, 1, 1}); - addVertex(&length, v); - - previousVertex = v; - addVertex(&length, previousVertex); } - + + unmapVertexBuffer(mappedBuffer); + [self setNeedsDisplay]; } @@ -381,7 +418,7 @@ - (void)updateStrokeColor { - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; - + CGFloat red, green, blue, alpha, white; if ([backgroundColor getRed:&red green:&green blue:&blue alpha:&alpha]) { clearColor[0] = red; @@ -404,73 +441,71 @@ - (void)bindShaderAttributes { - (void)setupGL { [EAGLContext setCurrentContext:context]; - + effect = [[GLKBaseEffect alloc] init]; - + [self updateStrokeColor]; - + glDisable(GL_DEPTH_TEST); - + // Signature Lines glGenVertexArraysOES(1, &vertexArray); glBindVertexArrayOES(vertexArray); - + glGenBuffers(1, &vertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(SignatureVertexData), SignatureVertexData, GL_DYNAMIC_DRAW); [self bindShaderAttributes]; - - + + // Signature Dots glGenVertexArraysOES(1, &dotsArray); glBindVertexArrayOES(dotsArray); - + glGenBuffers(1, &dotsBuffer); glBindBuffer(GL_ARRAY_BUFFER, dotsBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(SignatureDotsData), SignatureDotsData, GL_DYNAMIC_DRAW); [self bindShaderAttributes]; - - + + glBindVertexArrayOES(0); // Perspective GLKMatrix4 ortho = GLKMatrix4MakeOrtho(-1, 1, -1, 1, 0.1f, 2.0f); effect.transform.projectionMatrix = ortho; - + GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -1.0f); effect.transform.modelviewMatrix = modelViewMatrix; - + length = 0; penThickness = 0.003; previousPoint = CGPointMake(-100, -100); } - - -- (void)addTriangleStripPointsForPrevious:(PPSSignaturePoint)previous next:(PPSSignaturePoint)next { +- (void)addTriangleStripPointsInMappedBuffer:(GLuint *)mappedBuffer previous:(PPSSignaturePoint)previous next:(PPSSignaturePoint)next { float toTravel = penThickness / 2.0; - + for (int i = 0; i < 2; i++) { GLKVector3 p = perpendicular(previous, next); GLKVector3 p1 = next.vertex; GLKVector3 ref = GLKVector3Add(p1, p); - + float distance = GLKVector3Distance(p1, ref); float difX = p1.x - ref.x; float difY = p1.y - ref.y; float ratio = -1.0 * (toTravel / distance); - + difX = difX * ratio; difY = difY * ratio; - + PPSSignaturePoint stripPoint = { { p1.x + difX, p1.y + difY, 0.0 }, StrokeColor }; - addVertex(&length, stripPoint); - + addVertex(mappedBuffer, &length, stripPoint); + toTravel *= -1; } } @@ -479,13 +514,13 @@ - (void)addTriangleStripPointsForPrevious:(PPSSignaturePoint)previous next:(PPSS - (void)tearDownGL { [EAGLContext setCurrentContext:context]; - + glDeleteVertexArraysOES(1, &vertexArray); glDeleteBuffers(1, &vertexBuffer); - + glDeleteVertexArraysOES(1, &dotsArray); glDeleteBuffers(1, &dotsBuffer); - + effect = nil; } From 81fdf64df040ace1519d973707cbe57ba1a2f668 Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Mon, 15 Feb 2016 16:39:30 -0600 Subject: [PATCH 3/5] Use more standard creation of NSError object --- Source/PPSSignatureView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PPSSignatureView.m b/Source/PPSSignatureView.m index 5fd812a..d59dcd7 100644 --- a/Source/PPSSignatureView.m +++ b/Source/PPSSignatureView.m @@ -41,7 +41,7 @@ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[@"GL_ERROR"] = @(glError); - *error = [[NSError alloc] initWithDomain:@"PPSSignatureView" code:ERROR_OPENGL userInfo:userInfo]; + *error = [NSError errorWithDomain:@"PPSSignatureVire" code:ERROR_OPENGL userInfo:userInfo]; } return data; From 3c576061fcd57b0eb5bb33375849e14a9767c53a Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Mon, 15 Feb 2016 16:42:44 -0600 Subject: [PATCH 4/5] Whoops, use proper pointer type. --- Source/PPSSignatureView.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/PPSSignatureView.m b/Source/PPSSignatureView.m index d59dcd7..7056e46 100644 --- a/Source/PPSSignatureView.m +++ b/Source/PPSSignatureView.m @@ -57,7 +57,7 @@ static inline void addVertex(GLvoid *mappedBuffer, uint *length, PPSSignaturePoi (*length)++; } -static inline void unmapVertexBuffer(GLuint *mappedBuffer) { +static inline void unmapVertexBuffer(GLvoid *mappedBuffer) { if (mappedBuffer != NULL) { GLboolean result = glUnmapBufferOES(GL_ARRAY_BUFFER); @@ -260,7 +260,7 @@ - (void)tap:(UITapGestureRecognizer *)t { if (t.state == UIGestureRecognizerStateRecognized) { NSError *error; - GLuint *mappedBuffer = mapVertexBuffer(dotsBuffer, &error); + GLvoid *mappedBuffer = mapVertexBuffer(dotsBuffer, &error); if (mappedBuffer == NULL) { // TODO(rgrimm): Handle the error condition @@ -309,7 +309,7 @@ - (void)longPress:(UILongPressGestureRecognizer *)lp { - (void)pan:(UIPanGestureRecognizer *)p { NSError *error; - GLuint *mappedBuffer = mapVertexBuffer(vertexBuffer, &error); + GLvoid *mappedBuffer = mapVertexBuffer(vertexBuffer, &error); if (mappedBuffer == NULL) { // TODO(rgrimm): Handle the error condition @@ -484,7 +484,7 @@ - (void)setupGL previousPoint = CGPointMake(-100, -100); } -- (void)addTriangleStripPointsInMappedBuffer:(GLuint *)mappedBuffer previous:(PPSSignaturePoint)previous next:(PPSSignaturePoint)next { +- (void)addTriangleStripPointsInMappedBuffer:(GLvoid *)mappedBuffer previous:(PPSSignaturePoint)previous next:(PPSSignaturePoint)next { float toTravel = penThickness / 2.0; for (int i = 0; i < 2; i++) { From 985e1f4ba99d3de8ffccfe1a58983b35c29fc8c7 Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Mon, 15 Feb 2016 16:50:47 -0600 Subject: [PATCH 5/5] Fix typo --- Source/PPSSignatureView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PPSSignatureView.m b/Source/PPSSignatureView.m index 7056e46..3083ef8 100644 --- a/Source/PPSSignatureView.m +++ b/Source/PPSSignatureView.m @@ -41,7 +41,7 @@ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[@"GL_ERROR"] = @(glError); - *error = [NSError errorWithDomain:@"PPSSignatureVire" code:ERROR_OPENGL userInfo:userInfo]; + *error = [NSError errorWithDomain:@"PPSSignatureView" code:ERROR_OPENGL userInfo:userInfo]; } return data;