GLCanvas.m 13.2 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
// For reference, see implementation of gl-shader's GLCanvas

@implementation GLCanvas
{
33
  RCTBridge *_bridge;
34
  
35 36
  GLRenderData *_renderData;
  
37 38 39
  BOOL _captureFrameRequested;
  
  NSArray *_contentData;
40
  NSArray *_contentTextures;
41 42 43 44
  NSDictionary *_images; // This caches the currently used images (imageSrc -> GLReactImage)
  
  BOOL _opaque; // opaque prop (if false, the GLCanvas will become transparent)
  
45
  BOOL _deferredRendering; // This flag indicates a render has been deferred to the next frame (when using contents)
46 47
  
  GLint defaultFBO;
48 49 50
  
  NSMutableArray *_preloaded;
  BOOL _preloadingDone;
51 52
  
  NSTimer *animationTimer;
53 54
  
  BOOL _needSync;
55 56 57 58 59 60 61
}

- (instancetype)initWithBridge:(RCTBridge *)bridge
{
  if ((self = [super init])) {
    _bridge = bridge;
    _images = @{};
62
    _preloaded = [[NSMutableArray alloc] init];
63
    _captureFrameRequested = false;
64
    _preloadingDone = false;
65
    self.context = [bridge.rnglContext getContext];
66
    self.contentScaleFactor = RCTScreenScale();
67 68 69 70 71 72
  }
  return self;
}

RCT_NOT_IMPLEMENTED(-init)

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
73 74
//// Props Setters

75
- (void) requestCaptureFrame
76
{
77
  _captureFrameRequested = true;
78 79 80
  [self setNeedsDisplay];
}

81 82 83 84
-(void)setImagesToPreload:(NSArray *)imagesToPreload
{
  if (_preloadingDone) return;
  if ([imagesToPreload count] == 0) {
85
    [self dispatchOnLoad];
86 87
    _preloadingDone = true;
  }
88 89 90
  else {
    _preloadingDone = false;
  }
91 92
  _imagesToPreload = imagesToPreload;
}
93 94 95 96 97 98 99

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

100 101
- (void)setRenderId:(NSNumber *)renderId
{
102
  if (_nbContentTextures > 0) {
103 104 105 106
    [self setNeedsDisplay];
  }
}

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
- (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];
    }
  }
}

125
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
126
{
127 128 129 130
  self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone);
  if (pointerEvents == RCTPointerEventsBoxNone) {
    self.accessibilityViewIsModal = NO;
  }
131 132
}

133 134 135 136 137 138
- (void)setData:(GLData *)data
{
  _data = data;
  [self requestSyncData];
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
139 140 141 142 143 144 145
- (void)setNbContentTextures:(NSNumber *)nbContentTextures
{
  _nbContentTextures = nbContentTextures;
}

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

146 147
- (void)requestSyncData
{
148 149
  _needSync = true;
  [self setNeedsDisplay];
150 151 152 153 154 155
}

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

277
- (void)syncContentData
278
{
279 280 281 282 283
  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;
284
    if (view) {
285 286 287 288
      UIView *v = [view.subviews count] == 1 ?
      view.subviews[0] :
      view;
      imgData = [GLImageData genPixelsWithView:v];
289
    } else {
290
      imgData = nil;
291
    }
292 293 294 295 296 297 298 299 300 301 302
    contentData[i] = imgData;
  }
  _contentData = contentData;
}


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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
306 307 308

//// Draw

309 310
- (void)drawRect:(CGRect)rect
{
311
  __weak GLCanvas *weakSelf = self;
312 313 314 315 316 317
  if (_needSync) {
    _needSync = false;
    [self syncData];
  }
  
  self.layer.opaque = _opaque;
318
  
319 320 321 322 323
  if (!_preloadingDone) {
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    return;
  }
324
  BOOL needsDeferredRendering = _nbContentTextures > 0 && !_autoRedraw;
325 326
  if (needsDeferredRendering && !_deferredRendering) {
    dispatch_async(dispatch_get_main_queue(), ^{
327
      if (!weakSelf) return;
328
      _deferredRendering = true;
329
      [self syncContentData];
330
      [weakSelf setNeedsDisplay];
331 332 333
    });
  }
  else {
Dima's avatar
Dima committed
334
    RCTProfileBeginEvent(0, @"GLCanvas render", nil);
335
    [self render];
Dima's avatar
Dima committed
336
    RCTProfileEndEvent(0, @"gl", nil);
337
    _deferredRendering = false;
338
    
339 340
    if (_captureFrameRequested) {
      _captureFrameRequested = false;
341 342 343 344 345 346
      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,%@",
347 348
         [frameData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]];        
        if (weakSelf.onGLCaptureFrame) weakSelf.onGLCaptureFrame(@{ @"frame": frame });
349 350
      });
    }
351 352 353
  }
}

354
- (void)render
355 356 357 358 359 360 361 362 363 364 365 366
{
  if (!_renderData) return;
  
  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;
      
367 368 369
      for (GLRenderData *child in renderData.contextChildren)
        weak_recDraw(child);
      
370
      for (GLRenderData *child in renderData.children)
371
        weak_recDraw(child);
372
      
373
      if (renderData.fboId == -1) {
374 375 376 377
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
        glViewport(0, 0, w, h);
      }
      else {
378
        GLFBO *fbo = [_bridge.rnglContext getFBO:[NSNumber numberWithInt:renderData.fboId]];
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
        [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]];
      }
      
395
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
396 397
      glClearColor(0.0, 0.0, 0.0, 0.0);
      glClear(GL_COLOR_BUFFER_BIT);
398 399 400
      glDrawArrays(GL_TRIANGLES, 0, 6);
    };
    
401 402
    // DRAWING THE SCENE
    
403
    [self syncContentTextures];
404
    
405
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
406
    glEnable(GL_BLEND);
407 408
    recDraw(_renderData);
    glDisable(GL_BLEND);
409 410 411 412
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
  }
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
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
//// utility methods

- (void)onImageLoad:(NSString *)loaded
{
  if (!_preloadingDone) {
    [_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];
    if (count == total) {
      [self dispatchOnLoad];
      _preloadingDone = true;
      [self requestSyncData];
    }
  }
  else {
    // Any texture image load will trigger a future re-sync of data (if no preloaded)
    [self requestSyncData];
  }
}

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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
- (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
{
463
  if (self.onGLLoad) self.onGLLoad(@{});
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
464 465 466 467
}

- (void)dispatchOnProgress: (double)progress withLoaded:(int)loaded withTotal:(int)total
{
468
  if (self.onGLProgress) self.onGLProgress(
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
469
  @{
470 471 472 473
    @"progress": @(RCTZeroIfNaN(progress)),
    @"loaded": @(RCTZeroIfNaN(loaded)),
    @"total": @(RCTZeroIfNaN(total))
    });
474 475
}

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