GLCanvas.m 13.1 KB
Newer Older
1 2 3 4

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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
15 16 17 18 19 20 21 22 23 24 25 26 27
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;
}

28 29 30 31
// For reference, see implementation of gl-shader's GLCanvas

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

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

RCT_NOT_IMPLEMENTED(-init)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

//// Draw

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

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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
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
//// 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;
}
441

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

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

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