GLCanvas.m 16 KB
Newer Older
1 2 3 4

#import "RCTBridge.h"
#import "RCTUtils.h"
#import "RCTConvert.h"
5
#import "RCTEventDispatcher.h"
6
#import "RCTLog.h"
Dima's avatar
Dima committed
7
#import "RCTProfile.h"
8
#import "RNGLContext.h"
9 10 11 12 13
#import "GLCanvas.h"
#import "GLShader.h"
#import "GLTexture.h"
#import "GLImage.h"
#import "GLRenderData.h"
14
#import "UIView+React.h"
15

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
16 17 18 19 20 21 22 23 24 25 26 27 28
NSString* srcResource (id res)
{
  NSString *src;
  if ([res isKindOfClass:[NSString class]]) {
    src = [RCTConvert NSString:res];
  } else {
    BOOL isStatic = [RCTConvert BOOL:res[@"isStatic"]];
    src = [RCTConvert NSString:res[@"path"]];
    if (!src || isStatic) src = [RCTConvert NSString:res[@"uri"]];
  }
  return src;
}

29 30 31 32 33 34 35 36 37 38
NSArray* diff (NSArray* a, NSArray* b) {
  NSMutableArray *arr = [[NSMutableArray alloc] init];
  for (NSString* k in a) {
    if (![b containsObject:k]) {
      [arr addObject:k];
    }
  }
  return arr;
}

39 40 41 42
// For reference, see implementation of gl-shader's GLCanvas

@implementation GLCanvas
{
43
  RCTBridge *_bridge;
44

45
  GLRenderData *_renderData;
46

47
  NSArray *_contentData;
48
  NSArray *_contentTextures;
49
  NSDictionary *_images; // This caches the currently used images (imageSrc -> GLReactImage)
50

51
  BOOL _opaque; // opaque prop (if false, the GLCanvas will become transparent)
52

53
  BOOL _deferredRendering; // This flag indicates a render has been deferred to the next frame (when using contents)
54

55
  GLint defaultFBO;
56

57
  NSMutableArray *_preloaded;
58 59
  BOOL _dirtyOnLoad;
  BOOL _neverRendered;
60

61
  NSTimer *animationTimer;
62

63
  BOOL _needSync;
64 65 66
  
  NSMutableArray *_captureConfigs;
  BOOL _captureScheduled;
67 68 69 70 71 72 73
}

- (instancetype)initWithBridge:(RCTBridge *)bridge
{
  if ((self = [super init])) {
    _bridge = bridge;
    _images = @{};
74
    _preloaded = [[NSMutableArray alloc] init];
75 76
    _captureConfigs = [[NSMutableArray alloc] init];
    _captureScheduled = false;
77 78
    _dirtyOnLoad = true;
    _neverRendered = true;
79
    self.context = [bridge.rnglContext getContext];
80 81 82 83 84 85
  }
  return self;
}

RCT_NOT_IMPLEMENTED(-init)

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
86 87
//// Props Setters

88
- (void) requestCaptureFrame: (CaptureConfig *)config
89 90
{
  [self setNeedsDisplay];
91 92 93 94 95 96
  for (CaptureConfig *existing in _captureConfigs) {
    if ([existing isEqualToCaptureConfig:config]) {
      return;
    }
  }
  [_captureConfigs addObject:config];
97 98
}

99 100 101
-(void)setImagesToPreload:(NSArray *)imagesToPreload
{
  _imagesToPreload = imagesToPreload;
102
  [self requestSyncData];
103
}
104 105 106 107 108 109 110

- (void)setOpaque:(BOOL)opaque
{
  _opaque = opaque;
  [self setNeedsDisplay];
}

111 112
- (void)setRenderId:(NSNumber *)renderId
{
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
113
  if ([_nbContentTextures intValue] > 0) {
114 115 116 117
    [self setNeedsDisplay];
  }
}

118 119
- (void)setAutoRedraw:(BOOL)autoRedraw
{
120
  _autoRedraw = autoRedraw;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
121 122 123 124 125 126
  [self performSelectorOnMainThread:@selector(syncAutoRedraw) withObject:nil waitUntilDone:false];
}

- (void)syncAutoRedraw
{
  if (_autoRedraw) {
127
    if (!animationTimer)
128
      animationTimer =
129 130
      [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
                                       target:self
131
                                     selector:@selector(autoRedrawUpdate)
132 133 134 135 136 137 138 139 140 141
                                     userInfo:nil
                                      repeats:YES];
  }
  else {
    if (animationTimer) {
      [animationTimer invalidate];
    }
  }
}

142
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
143
{
144 145 146 147
  self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone);
  if (pointerEvents == RCTPointerEventsBoxNone) {
    self.accessibilityViewIsModal = NO;
  }
148 149
}

150 151 152 153 154 155
- (void)setPixelRatio:(NSNumber *)pixelRatio
{
  self.contentScaleFactor = [pixelRatio floatValue];
  [self setNeedsDisplay];
}

156 157 158
- (void)setData:(GLData *)data
{
  _data = data;
159
  _renderData = nil;
160 161 162
  [self requestSyncData];
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
163 164 165 166 167 168 169
- (void)setNbContentTextures:(NSNumber *)nbContentTextures
{
  _nbContentTextures = nbContentTextures;
}

//// Sync methods (called from props setters)

170 171
- (void)requestSyncData
{
172 173
  _needSync = true;
  [self setNeedsDisplay];
174 175
}

176
- (bool)syncData:(NSError **)error
177 178
{
  @autoreleasepool {
179

180 181
    NSDictionary *prevImages = _images;
    NSMutableDictionary *images = [[NSMutableDictionary alloc] init];
182

183 184 185
    GLRenderData * (^traverseTree) (GLData *data);
    __block __weak GLRenderData * (^weak_traverseTree)(GLData *data);
    weak_traverseTree = traverseTree = ^GLRenderData *(GLData *data) {
186 187
      NSNumber *width = data.width;
      NSNumber *height = data.height;
188
      NSNumber *pixelRatio = data.pixelRatio;
189
      int fboId = [data.fboId intValue];
190

191 192
      NSMutableArray *contextChildren = [[NSMutableArray alloc] init];
      for (GLData *child in data.contextChildren) {
193 194 195
        GLRenderData *node = weak_traverseTree(child);
        if (node == nil) return nil;
        [contextChildren addObject:node];
196
      }
197

198 199
      NSMutableArray *children = [[NSMutableArray alloc] init];
      for (GLData *child in data.children) {
200 201 202
        GLRenderData *node = weak_traverseTree(child);
        if (node == nil) return nil;
        [children addObject:node];
203
      }
204

205
      GLShader *shader = [_bridge.rnglContext getShader:data.shader];
206
      if (shader == nil) return nil;
207
      if (![shader ensureCompiles:error]) return nil;
208

209 210 211 212 213 214 215
      NSDictionary *uniformTypes = [shader uniformTypes];
      NSMutableDictionary *uniforms = [[NSMutableDictionary alloc] init];
      NSMutableDictionary *textures = [[NSMutableDictionary alloc] init];
      int units = 0;
      for (NSString *uniformName in data.uniforms) {
        id value = [data.uniforms objectForKey:uniformName];
        GLenum type = [uniformTypes[uniformName] intValue];
216 217


218
        if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE) {
219
          uniforms[uniformName] = [NSNumber numberWithInt:units++];
220
          if ([value isEqual:[NSNull null]]) {
221
            GLTexture *emptyTexture = [[GLTexture alloc] init];
222
            [emptyTexture setPixels:nil];
223
            textures[uniformName] = emptyTexture;
224
          }
225 226 227 228 229 230 231 232
          else {
            NSString *type = [RCTConvert NSString:value[@"type"]];
            if ([type isEqualToString:@"content"]) {
              int id = [[RCTConvert NSNumber:value[@"id"]] intValue];
              if (id >= [_contentTextures count]) {
                [self resizeUniformContentTextures:id+1];
              }
              textures[uniformName] = _contentTextures[id];
233
            }
234 235
            else if ([type isEqualToString:@"fbo"]) {
              NSNumber *id = [RCTConvert NSNumber:value[@"id"]];
236
              GLFBO *fbo = [_bridge.rnglContext getFBO:id];
237 238 239 240 241 242 243
              textures[uniformName] = fbo.color[0];
            }
            else if ([type isEqualToString:@"uri"]) {
              NSString *src = srcResource(value);
              if (!src) {
                RCTLogError(@"texture uniform '%@': Invalid uri format '%@'", uniformName, value);
              }
244

245 246 247 248 249 250 251
              GLImage *image = images[src];
              if (image == nil) {
                image = prevImages[src];
                if (image != nil)
                  images[src] = image;
              }
              if (image == nil) {
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
252
                __weak GLCanvas *weakSelf = self;
253
                image = [[GLImage alloc] initWithBridge:_bridge withOnLoad:^{
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
254
                  if (weakSelf) [weakSelf onImageLoad:src];
255 256
                }];
                image.src = src;
257
                images[src] = image;
258 259
              }
              textures[uniformName] = [image getTexture];
260
            }
261 262
            else {
              RCTLogError(@"texture uniform '%@': Unexpected type '%@'", uniformName, type);
263 264 265 266 267 268 269
            }
          }
        }
        else {
          uniforms[uniformName] = value;
        }
      }
270

271 272 273 274 275
      int maxTextureUnits;
      glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
      if (units > maxTextureUnits) {
        RCTLogError(@"Maximum number of texture reach. got %i >= max %i", units, maxTextureUnits);
      }
276

277 278 279 280 281
      for (NSString *uniformName in shader.uniformTypes) {
        if (uniforms[uniformName] == nil) {
          RCTLogError(@"All defined uniforms must be provided. Missing '%@'", uniformName);
        }
      }
282

283 284 285 286
      return [[GLRenderData alloc]
              initWithShader:shader
              withUniforms:uniforms
              withTextures:textures
287 288
              withWidth:(int)([width floatValue] * [pixelRatio floatValue])
              withHeight:(int)([height floatValue] * [pixelRatio floatValue])
289 290
              withFboId:fboId
              withContextChildren:contextChildren
291
              withChildren:children];
292
    };
293

294 295 296 297
    GLRenderData *res = traverseTree(_data);
    if (res != nil) {
      _renderData = traverseTree(_data);
      _images = images;
298 299 300
      for (NSString *src in diff([prevImages allKeys], [images allKeys])) {
        [_preloaded removeObject:src];
      }
301
      return true;
302
    }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
303
    else {
304
      return false;
305
    }
306 307 308
  }
}

309
- (void)syncContentData
310
{
311
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas syncContentData", nil);
312 313 314 315 316
  NSMutableArray *contentData = [[NSMutableArray alloc] init];
  int nb = [_nbContentTextures intValue];
  for (int i = 0; i < nb; i++) {
    UIView *view = self.superview.subviews[i]; // We take siblings by index (closely related to the JS code)
    GLImageData *imgData = nil;
317
    if (view) {
318 319 320
      UIView *v = [view.subviews count] == 1 ?
      view.subviews[0] :
      view;
321
      imgData = [GLImageData genPixelsWithView:v withPixelRatio:self.contentScaleFactor];
322
    } else {
323
      imgData = nil;
324
    }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
325
    if (imgData) contentData[i] = imgData;
326 327
  }
  _contentData = contentData;
328
  _deferredRendering = false;
329 330
  [self setNeedsDisplay];
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
331 332 333 334 335 336 337 338
}


- (void)syncContentTextures
{
  unsigned long max = MIN([_contentData count], [_contentTextures count]);
  for (int i=0; i<max; i++) {
    [_contentTextures[i] setPixels:_contentData[i]];
339 340 341
  }
}

342 343 344 345 346 347 348 349 350 351
- (BOOL)haveRemainingToPreload
{
  for (id res in _imagesToPreload) {
    if (![_preloaded containsObject:srcResource(res)]) {
      return true;
    }
  }
  return false;
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
352 353 354

//// Draw

355
- (void) autoRedrawUpdate
356
{
357 358 359 360 361
  if ([self haveRemainingToPreload]) {
    return;
  }
  if ([_nbContentTextures intValue] > 0) {
    [self syncContentData];
362
  }
363 364 365
  [self setNeedsDisplay];
}

366 367
- (void)drawRect:(CGRect)rect
{
368
  self.layer.opaque = _opaque;
369

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
370 371
  if (_neverRendered) {
    _neverRendered = false;
372 373
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
374
  }
375

376
  if (_needSync) {
377 378 379 380 381 382 383 384
    NSError *error;
    if(![self syncData:&error] && error==nil) {
      // the data is not ready, retry in one tick
      [self setNeedsDisplay];
    }
    else {
      _needSync = false;
    }
385
  }
386

387
  if ([self haveRemainingToPreload]) {
388 389
    return;
  }
390

391
  bool willRender = !_deferredRendering;
392

393
  if ([_nbContentTextures intValue] > 0 && !_autoRedraw) {
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
394
    _deferredRendering = true;
395
    [self performSelectorOnMainThread:@selector(syncContentData) withObject:nil waitUntilDone:NO];
396
  }
397

398
  if (willRender) {
399
    [self render];
400 401
    if (!_captureScheduled && [_captureConfigs count] > 0) {
      _captureScheduled = true;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
402
      [self performSelectorOnMainThread:@selector(capture) withObject:nil waitUntilDone:NO];
403
    }
404 405 406
  }
}

407
-(void) capture
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
408
{
409 410 411
  _captureScheduled = false;
  if (!self.onGLCaptureFrame) return;
  
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
412
  UIImage *frameImage = [self snapshot];
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
  
  for (CaptureConfig *config in _captureConfigs) {
    id result;
    id error;
    
    BOOL isPng = [config.type isEqualToString:@"png"];
    BOOL isJpeg = !isPng && ([config.type isEqualToString:@"jpeg"] || [config.type isEqualToString:@"jpg"]);
    
    BOOL isBase64 = [config.format isEqualToString:@"base64"];
    BOOL isFile = !isBase64 && [config.format isEqualToString:@"file"];
    
    NSData *frameData =
    isPng ? UIImagePNGRepresentation(frameImage) :
    isJpeg ? UIImageJPEGRepresentation(frameImage, [config.quality floatValue]) :
    nil;
    
    if (!frameData) {
      error = [NSString stringWithFormat:@"Unsupported capture type '%@'", config.type];
    }
    else if (isBase64) {
      NSString *base64 = [frameData base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
      result = [NSString stringWithFormat:@"data:image/%@;base64,%@", config.type, base64];
    }
    else if (isFile) {
      NSError *e;
      if (![frameData writeToFile:config.filePath options:0 error:&e]) {
        error = [NSString stringWithFormat:@"Could not write file: %@", e.localizedDescription];
      }
      else {
        result = [NSString stringWithFormat:@"file://%@", config.filePath];
      }
    }
    else {
      error = [NSString stringWithFormat:@"Unsupported capture format '%@'", config.format];
    }
    
    NSMutableDictionary *response = [[NSMutableDictionary alloc] init];
    response[@"config"] = [config dictionary];
    if (error) response[@"error"] = error;
    if (result) response[@"result"] = result;
    self.onGLCaptureFrame(response);
  }
  
  _captureConfigs = [[NSMutableArray alloc] init];
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
457 458
}

459
- (void)render
460
{
461 462
  GLRenderData *rd = _renderData;
  if (!rd) return;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
463
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas render", nil);
464

465
  @autoreleasepool {
466

467 468 469
    void (^recDraw) (GLRenderData *renderData);
    __block __weak void (^weak_recDraw) (GLRenderData *renderData);
    weak_recDraw = recDraw = ^void(GLRenderData *renderData) {
470 471
      int w = renderData.width;
      int h = renderData.height;
472

473 474
      for (GLRenderData *child in renderData.contextChildren)
        weak_recDraw(child);
475

476
      for (GLRenderData *child in renderData.children)
477
        weak_recDraw(child);
478

479
      if (renderData.fboId == -1) {
480 481 482 483
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
        glViewport(0, 0, w, h);
      }
      else {
484
        GLFBO *fbo = [_bridge.rnglContext getFBO:[NSNumber numberWithInt:renderData.fboId]];
485 486 487
        [fbo setShapeWithWidth:w withHeight:h];
        [fbo bind];
      }
488

489
      [renderData.shader bind];
490

491 492 493 494 495
      for (NSString *uniformName in renderData.textures) {
        GLTexture *texture = renderData.textures[uniformName];
        int unit = [((NSNumber *)renderData.uniforms[uniformName]) intValue];
        [texture bind:unit];
      }
496

497 498 499
      for (NSString *uniformName in renderData.uniforms) {
        [renderData.shader setUniform:uniformName withValue:renderData.uniforms[uniformName]];
      }
500

501
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
502 503
      glClearColor(0.0, 0.0, 0.0, 0.0);
      glClear(GL_COLOR_BUFFER_BIT);
504 505
      glDrawArrays(GL_TRIANGLES, 0, 6);
    };
506

507
    // DRAWING THE SCENE
508

509
    [self syncContentTextures];
510

511
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
512
    glEnable(GL_BLEND);
513
    recDraw(rd);
514
    glDisable(GL_BLEND);
515
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
516
    glBindBuffer(GL_ARRAY_BUFFER, 0);
517

518 519 520 521
    if (_dirtyOnLoad && ![self haveRemainingToPreload]) {
      _dirtyOnLoad = false;
      [self dispatchOnLoad];
    }
522
  }
523

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
524
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
525 526
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
527 528 529 530
//// utility methods

- (void)onImageLoad:(NSString *)loaded
{
531 532 533 534 535 536 537
  [_preloaded addObject:loaded];
  int count = [self countPreloaded];
  int total = (int) [_imagesToPreload count];
  double progress = ((double) count) / ((double) total);
  [self dispatchOnProgress:progress withLoaded:count withTotal:total];
  _dirtyOnLoad = true;
  [self requestSyncData];
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
538 539 540 541 542 543 544 545 546 547 548
}

- (int)countPreloaded
{
  int nb = 0;
  for (id toload in _imagesToPreload) {
    if ([_preloaded containsObject:srcResource(toload)])
      nb++;
  }
  return nb;
}
549

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
- (void)resizeUniformContentTextures:(int)n
{
  int length = (int) [_contentTextures count];
  if (length == n) return;
  if (n < length) {
    _contentTextures = [_contentTextures subarrayWithRange:NSMakeRange(0, n)];
  }
  else {
    NSMutableArray *contentTextures = [[NSMutableArray alloc] initWithArray:_contentTextures];
    for (int i = (int) [_contentTextures count]; i < n; i++) {
      [contentTextures addObject:[[GLTexture alloc] init]];
    }
    _contentTextures = contentTextures;
  }
}

- (void)dispatchOnLoad
{
568
  if (self.onGLLoad) self.onGLLoad(@{});
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
569 570 571 572
}

- (void)dispatchOnProgress: (double)progress withLoaded:(int)loaded withTotal:(int)total
{
573
  if (self.onGLProgress) self.onGLProgress(
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
574 575 576 577 578
                                           @{
                                             @"progress": @(RCTZeroIfNaN(progress)),
                                             @"loaded": @(RCTZeroIfNaN(loaded)),
                                             @"total": @(RCTZeroIfNaN(total))
                                             });
579 580
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
581
@end