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"
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 66 67 68 69 70
  }
  return self;
}

RCT_NOT_IMPLEMENTED(-init)

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

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

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

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

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

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

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

131 132 133 134 135 136
- (void)setPixelRatio:(NSNumber *)pixelRatio
{
  self.contentScaleFactor = [pixelRatio floatValue];
  [self setNeedsDisplay];
}

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

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

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

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

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

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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
310 311 312

//// Draw

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

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

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

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

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

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