GLCanvas.m 15.8 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 177 178
}

- (void)syncData
{
  @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

208 209 210 211 212 213 214
      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];
215 216


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

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

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

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

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

293 294
    GLRenderData *res = traverseTree(_data);
    if (res != nil) {
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
295
      _needSync = false;
296 297
      _renderData = traverseTree(_data);
      _images = images;
298 299 300
      for (NSString *src in diff([prevImages allKeys], [images allKeys])) {
        [_preloaded removeObject:src];
      }
301
    }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
302 303 304
    else {
      // the data is not ready, retry in one tick
      [self setNeedsDisplay];
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 377 378
  if (_needSync) {
    [self syncData];
  }
379

380
  if ([self haveRemainingToPreload]) {
381 382
    return;
  }
383

384
  bool willRender = !_deferredRendering;
385

386
  if ([_nbContentTextures intValue] > 0 && !_autoRedraw) {
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
387
    _deferredRendering = true;
388
    [self performSelectorOnMainThread:@selector(syncContentData) withObject:nil waitUntilDone:NO];
389
  }
390

391
  if (willRender) {
392
    [self render];
393 394
    if (!_captureScheduled && [_captureConfigs count] > 0) {
      _captureScheduled = true;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
395
      [self performSelectorOnMainThread:@selector(capture) withObject:nil waitUntilDone:NO];
396
    }
397 398 399
  }
}

400
-(void) capture
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
401
{
402 403 404
  _captureScheduled = false;
  if (!self.onGLCaptureFrame) return;
  
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
405
  UIImage *frameImage = [self snapshot];
406 407 408 409 410 411 412 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
  
  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
450 451
}

452
- (void)render
453
{
454 455
  GLRenderData *rd = _renderData;
  if (!rd) return;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
456
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas render", nil);
457

458
  @autoreleasepool {
459

460 461 462
    void (^recDraw) (GLRenderData *renderData);
    __block __weak void (^weak_recDraw) (GLRenderData *renderData);
    weak_recDraw = recDraw = ^void(GLRenderData *renderData) {
463 464
      int w = renderData.width;
      int h = renderData.height;
465

466 467
      for (GLRenderData *child in renderData.contextChildren)
        weak_recDraw(child);
468

469
      for (GLRenderData *child in renderData.children)
470
        weak_recDraw(child);
471

472
      if (renderData.fboId == -1) {
473 474 475 476
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
        glViewport(0, 0, w, h);
      }
      else {
477
        GLFBO *fbo = [_bridge.rnglContext getFBO:[NSNumber numberWithInt:renderData.fboId]];
478 479 480
        [fbo setShapeWithWidth:w withHeight:h];
        [fbo bind];
      }
481

482
      [renderData.shader bind];
483

484 485 486 487 488
      for (NSString *uniformName in renderData.textures) {
        GLTexture *texture = renderData.textures[uniformName];
        int unit = [((NSNumber *)renderData.uniforms[uniformName]) intValue];
        [texture bind:unit];
      }
489

490 491 492
      for (NSString *uniformName in renderData.uniforms) {
        [renderData.shader setUniform:uniformName withValue:renderData.uniforms[uniformName]];
      }
493

494
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
495 496
      glClearColor(0.0, 0.0, 0.0, 0.0);
      glClear(GL_COLOR_BUFFER_BIT);
497 498
      glDrawArrays(GL_TRIANGLES, 0, 6);
    };
499

500
    // DRAWING THE SCENE
501

502
    [self syncContentTextures];
503

504
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
505
    glEnable(GL_BLEND);
506
    recDraw(rd);
507
    glDisable(GL_BLEND);
508
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
509
    glBindBuffer(GL_ARRAY_BUFFER, 0);
510

511 512 513 514
    if (_dirtyOnLoad && ![self haveRemainingToPreload]) {
      _dirtyOnLoad = false;
      [self dispatchOnLoad];
    }
515
  }
516

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
517
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
518 519
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
520 521 522 523
//// utility methods

- (void)onImageLoad:(NSString *)loaded
{
524 525 526 527 528 529 530
  [_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
531 532 533 534 535 536 537 538 539 540 541
}

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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
- (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
{
561
  if (self.onGLLoad) self.onGLLoad(@{});
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
562 563 564 565
}

- (void)dispatchOnProgress: (double)progress withLoaded:(int)loaded withTotal:(int)total
{
566
  if (self.onGLProgress) self.onGLProgress(
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
567 568 569 570 571
                                           @{
                                             @"progress": @(RCTZeroIfNaN(progress)),
                                             @"loaded": @(RCTZeroIfNaN(loaded)),
                                             @"total": @(RCTZeroIfNaN(total))
                                             });
572 573
}

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