GLCanvas.m 13.7 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 46
  GLRenderData *_renderData;
  
47 48 49
  BOOL _captureFrameRequested;
  
  NSArray *_contentData;
50
  NSArray *_contentTextures;
51 52 53 54
  NSDictionary *_images; // This caches the currently used images (imageSrc -> GLReactImage)
  
  BOOL _opaque; // opaque prop (if false, the GLCanvas will become transparent)
  
55
  BOOL _deferredRendering; // This flag indicates a render has been deferred to the next frame (when using contents)
56 57
  
  GLint defaultFBO;
58 59
  
  NSMutableArray *_preloaded;
60 61
  BOOL _dirtyOnLoad;
  BOOL _neverRendered;
62 63
  
  NSTimer *animationTimer;
64 65
  
  BOOL _needSync;
66 67 68 69 70 71 72
}

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

RCT_NOT_IMPLEMENTED(-init)

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

87
- (void) requestCaptureFrame
88
{
89
  _captureFrameRequested = true;
90 91 92
  [self setNeedsDisplay];
}

93 94 95
-(void)setImagesToPreload:(NSArray *)imagesToPreload
{
  _imagesToPreload = imagesToPreload;
96
  [self requestSyncData];
97
}
98 99 100 101 102 103 104

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

105 106
- (void)setRenderId:(NSNumber *)renderId
{
107
  if (_nbContentTextures > 0) {
108 109 110 111
    [self setNeedsDisplay];
  }
}

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
- (void)setAutoRedraw:(BOOL)autoRedraw
{
  if (autoRedraw) {
    if (!animationTimer)
      animationTimer = // FIXME: can we do better than this?
      [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
                                       target:self
                                     selector:@selector(setNeedsDisplay)
                                     userInfo:nil
                                      repeats:YES];
  }
  else {
    if (animationTimer) {
      [animationTimer invalidate];
    }
  }
}

130
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
131
{
132 133 134 135
  self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone);
  if (pointerEvents == RCTPointerEventsBoxNone) {
    self.accessibilityViewIsModal = NO;
  }
136 137
}

138 139 140
- (void)setData:(GLData *)data
{
  _data = data;
141
  _renderData = nil;
142 143 144
  [self requestSyncData];
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
145 146 147 148 149 150 151
- (void)setNbContentTextures:(NSNumber *)nbContentTextures
{
  _nbContentTextures = nbContentTextures;
}

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

152 153
- (void)requestSyncData
{
154 155
  _needSync = true;
  [self setNeedsDisplay];
156 157 158 159 160
}

- (void)syncData
{
  @autoreleasepool {
161
    
162 163 164
    NSDictionary *prevImages = _images;
    NSMutableDictionary *images = [[NSMutableDictionary alloc] init];
    
165 166 167
    GLRenderData * (^traverseTree) (GLData *data);
    __block __weak GLRenderData * (^weak_traverseTree)(GLData *data);
    weak_traverseTree = traverseTree = ^GLRenderData *(GLData *data) {
168 169
      NSNumber *width = data.width;
      NSNumber *height = data.height;
170 171 172 173
      int fboId = [data.fboId intValue];
      
      NSMutableArray *contextChildren = [[NSMutableArray alloc] init];
      for (GLData *child in data.contextChildren) {
174 175 176
        GLRenderData *node = weak_traverseTree(child);
        if (node == nil) return nil;
        [contextChildren addObject:node];
177
      }
178 179 180
      
      NSMutableArray *children = [[NSMutableArray alloc] init];
      for (GLData *child in data.children) {
181 182 183
        GLRenderData *node = weak_traverseTree(child);
        if (node == nil) return nil;
        [children addObject:node];
184 185
      }
      
186
      GLShader *shader = [_bridge.rnglContext getShader:data.shader];
187
      if (shader == nil) return nil;
188 189 190 191 192 193 194 195 196
      
      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];
        
197
        
198
        if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE) {
199
          uniforms[uniformName] = [NSNumber numberWithInt:units++];
200
          if ([value isEqual:[NSNull null]]) {
201
            GLTexture *emptyTexture = [[GLTexture alloc] init];
202
            [emptyTexture setPixels:nil];
203
            textures[uniformName] = emptyTexture;
204
          }
205 206 207 208 209 210 211 212
          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];
213
            }
214 215
            else if ([type isEqualToString:@"fbo"]) {
              NSNumber *id = [RCTConvert NSNumber:value[@"id"]];
216
              GLFBO *fbo = [_bridge.rnglContext getFBO:id];
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
              textures[uniformName] = fbo.color[0];
            }
            else if ([type isEqualToString:@"uri"]) {
              NSString *src = srcResource(value);
              if (!src) {
                RCTLogError(@"texture uniform '%@': Invalid uri format '%@'", uniformName, value);
              }
              
              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
232
                __weak GLCanvas *weakSelf = self;
233
                image = [[GLImage alloc] initWithBridge:_bridge withOnLoad:^{
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
234
                  if (weakSelf) [weakSelf onImageLoad:src];
235 236
                }];
                image.src = src;
237
                images[src] = image;
238 239
              }
              textures[uniformName] = [image getTexture];
240
            }
241 242
            else {
              RCTLogError(@"texture uniform '%@': Unexpected type '%@'", uniformName, type);
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
            }
          }
        }
        else {
          uniforms[uniformName] = value;
        }
      }
      
      int maxTextureUnits;
      glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
      if (units > maxTextureUnits) {
        RCTLogError(@"Maximum number of texture reach. got %i >= max %i", units, maxTextureUnits);
      }
      
      for (NSString *uniformName in shader.uniformTypes) {
        if (uniforms[uniformName] == nil) {
          RCTLogError(@"All defined uniforms must be provided. Missing '%@'", uniformName);
        }
      }
262
      
263 264 265 266 267 268 269 270
      return [[GLRenderData alloc]
              initWithShader:shader
              withUniforms:uniforms
              withTextures:textures
              withWidth:width
              withHeight:height
              withFboId:fboId
              withContextChildren:contextChildren
271
              withChildren:children];
272 273
    };
    
274 275 276 277
    GLRenderData *res = traverseTree(_data);
    if (res != nil) {
      _renderData = traverseTree(_data);
      _images = images;
278 279 280
      for (NSString *src in diff([prevImages allKeys], [images allKeys])) {
        [_preloaded removeObject:src];
      }
281
    }
282 283 284
  }
}

285
- (void)syncContentData
286
{
287
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas syncContentData", nil);
288 289 290 291 292
  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;
293
    if (view) {
294 295 296 297
      UIView *v = [view.subviews count] == 1 ?
      view.subviews[0] :
      view;
      imgData = [GLImageData genPixelsWithView:v];
298
    } else {
299
      imgData = nil;
300
    }
301 302 303
    contentData[i] = imgData;
  }
  _contentData = contentData;
304 305 306
  _deferredRendering = true;
  [self setNeedsDisplay];
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
307 308 309 310 311 312 313 314
}


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

318 319 320 321 322 323 324 325 326 327
- (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
328 329 330

//// Draw

331 332
- (void)drawRect:(CGRect)rect
{
333
  __weak GLCanvas *weakSelf = self;
334 335 336 337 338 339
  if (_needSync) {
    _needSync = false;
    [self syncData];
  }
  
  self.layer.opaque = _opaque;
340
  
341 342 343 344 345 346
  if ([self haveRemainingToPreload]) {
    if (_neverRendered) {
      _neverRendered = false;
      glClearColor(0.0, 0.0, 0.0, 0.0);
      glClear(GL_COLOR_BUFFER_BIT);
    }
347 348
    return;
  }
349 350
  _neverRendered = false;
  
351
  BOOL needsDeferredRendering = _nbContentTextures > 0 && !_autoRedraw;
352
  if (needsDeferredRendering && !_deferredRendering) {
353
    [self performSelectorOnMainThread:@selector(syncContentData) withObject:nil waitUntilDone:NO];
354 355
  }
  else {
356
    RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas render", nil);
357
    [self render];
358
    RCT_PROFILE_END_EVENT(0, @"gl", nil);
359
    _deferredRendering = false;
360
    
361 362
    if (_captureFrameRequested) {
      _captureFrameRequested = false;
363 364 365 366 367 368
      dispatch_async(dispatch_get_main_queue(), ^{ // snapshot not allowed in render tick. defer it.
        if (!weakSelf) return;
        UIImage *frameImage = [weakSelf snapshot];
        NSData *frameData = UIImagePNGRepresentation(frameImage);
        NSString *frame =
        [NSString stringWithFormat:@"data:image/png;base64,%@",
369
         [frameData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]];
370
        if (weakSelf.onGLCaptureFrame) weakSelf.onGLCaptureFrame(@{ @"frame": frame });
371 372
      });
    }
373 374 375
  }
}

376
- (void)render
377
{
378 379
  GLRenderData *rd = _renderData;
  if (!rd) return;
380 381 382 383 384 385 386 387 388 389
  
  CGFloat scale = RCTScreenScale();
  
  @autoreleasepool {
    void (^recDraw) (GLRenderData *renderData);
    __block __weak void (^weak_recDraw) (GLRenderData *renderData);
    weak_recDraw = recDraw = ^void(GLRenderData *renderData) {
      float w = [renderData.width floatValue] * scale;
      float h = [renderData.height floatValue] * scale;
      
390 391 392
      for (GLRenderData *child in renderData.contextChildren)
        weak_recDraw(child);
      
393
      for (GLRenderData *child in renderData.children)
394
        weak_recDraw(child);
395
      
396
      if (renderData.fboId == -1) {
397 398 399 400
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
        glViewport(0, 0, w, h);
      }
      else {
401
        GLFBO *fbo = [_bridge.rnglContext getFBO:[NSNumber numberWithInt:renderData.fboId]];
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
        [fbo setShapeWithWidth:w withHeight:h];
        [fbo bind];
      }
      
      [renderData.shader bind];
      
      for (NSString *uniformName in renderData.textures) {
        GLTexture *texture = renderData.textures[uniformName];
        int unit = [((NSNumber *)renderData.uniforms[uniformName]) intValue];
        [texture bind:unit];
      }
      
      for (NSString *uniformName in renderData.uniforms) {
        [renderData.shader setUniform:uniformName withValue:renderData.uniforms[uniformName]];
      }
      
418
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
419 420
      glClearColor(0.0, 0.0, 0.0, 0.0);
      glClear(GL_COLOR_BUFFER_BIT);
421 422 423
      glDrawArrays(GL_TRIANGLES, 0, 6);
    };
    
424 425
    // DRAWING THE SCENE
    
426
    [self syncContentTextures];
427
    
428
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
429
    glEnable(GL_BLEND);
430
    recDraw(rd);
431
    glDisable(GL_BLEND);
432
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
433
    glBindBuffer(GL_ARRAY_BUFFER, 0);
434 435 436 437 438
    
    if (_dirtyOnLoad && ![self haveRemainingToPreload]) {
      _dirtyOnLoad = false;
      [self dispatchOnLoad];
    }
439 440 441
  }
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
442 443 444 445
//// utility methods

- (void)onImageLoad:(NSString *)loaded
{
446 447 448 449 450 451 452
  [_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
453 454 455 456 457 458 459 460 461 462 463
}

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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
- (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
{
483
  if (self.onGLLoad) self.onGLLoad(@{});
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
484 485 486 487
}

- (void)dispatchOnProgress: (double)progress withLoaded:(int)loaded withTotal:(int)total
{
488
  if (self.onGLProgress) self.onGLProgress(
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
489
  @{
490 491 492 493
    @"progress": @(RCTZeroIfNaN(progress)),
    @"loaded": @(RCTZeroIfNaN(loaded)),
    @"total": @(RCTZeroIfNaN(total))
    });
494 495
}

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