GLCanvas.m 18.2 KB
Newer Older
1 2 3
#import "RCTBridge.h"
#import "RCTUtils.h"
#import "RCTConvert.h"
4
#import "RCTEventDispatcher.h"
5
#import "RCTLog.h"
Dima's avatar
Dima committed
6
#import "RCTProfile.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 32 33 34 35 36 37
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;
}

38 39 40 41
// For reference, see implementation of gl-shader's GLCanvas

@implementation GLCanvas
{
42
  RCTBridge *_bridge;
43

44
  GLRenderData *_renderData;
45

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
46
  NSArray *_rasterizedContent;
47
  NSArray *_contentTextures;
48
  NSDictionary *_images; // This caches the currently used images (imageSrc -> GLReactImage)
49

50
  BOOL _opaque; // opaque prop (if false, the GLCanvas will become transparent)
51

52
  BOOL _deferredRendering; // This flag indicates a render has been deferred to the next frame (when using contents)
53

54
  GLint defaultFBO;
55

56
  NSMutableArray *_preloaded;
57 58
  BOOL _dirtyOnLoad;
  BOOL _neverRendered;
59

60
  NSTimer *animationTimer;
61

62
  BOOL _needSync;
63

64 65
  NSMutableArray *_captureConfigs;
  BOOL _captureScheduled;
66 67 68 69 70 71 72
}

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

RCT_NOT_IMPLEMENTED(-init)

85 86 87 88 89 90
- (void)dealloc
{
  _bridge = nil;
  _images = nil;
  _preloaded = nil;
  _captureConfigs = nil;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
91
  _rasterizedContent = nil;
92 93 94 95 96 97 98 99 100
  _contentTextures = nil;
  _data = nil;
  _renderData = nil;
  if (animationTimer) {
    [animationTimer invalidate];
    animationTimer = nil;
  }
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
101 102
//// Props Setters

103
- (void) requestCaptureFrame: (CaptureConfig *)config
104 105
{
  [self setNeedsDisplay];
106 107 108 109 110 111
  for (CaptureConfig *existing in _captureConfigs) {
    if ([existing isEqualToCaptureConfig:config]) {
      return;
    }
  }
  [_captureConfigs addObject:config];
112 113
}

114 115 116
-(void)setImagesToPreload:(NSArray *)imagesToPreload
{
  _imagesToPreload = imagesToPreload;
117
  [self requestSyncData];
118
}
119 120 121 122 123 124 125

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

126 127
- (void)setRenderId:(NSNumber *)renderId
{
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
128
  if ([_nbContentTextures intValue] > 0) {
129 130 131 132
    [self setNeedsDisplay];
  }
}

133 134
- (void)setAutoRedraw:(BOOL)autoRedraw
{
135
  _autoRedraw = autoRedraw;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
136 137 138 139 140 141
  [self performSelectorOnMainThread:@selector(syncAutoRedraw) withObject:nil waitUntilDone:false];
}

- (void)syncAutoRedraw
{
  if (_autoRedraw) {
142
    if (!animationTimer)
143
      animationTimer =
144 145
      [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
                                       target:self
146
                                     selector:@selector(autoRedrawUpdate)
147 148 149 150 151 152 153 154 155 156
                                     userInfo:nil
                                      repeats:YES];
  }
  else {
    if (animationTimer) {
      [animationTimer invalidate];
    }
  }
}

157
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
158
{
159 160 161 162
  self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone);
  if (pointerEvents == RCTPointerEventsBoxNone) {
    self.accessibilityViewIsModal = NO;
  }
163 164
}

165 166 167 168 169 170
- (void)setPixelRatio:(NSNumber *)pixelRatio
{
  self.contentScaleFactor = [pixelRatio floatValue];
  [self setNeedsDisplay];
}

171 172 173
- (void)setData:(GLData *)data
{
  _data = data;
174
  _renderData = nil;
175 176 177
  [self requestSyncData];
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
178 179 180 181 182 183 184
- (void)setNbContentTextures:(NSNumber *)nbContentTextures
{
  _nbContentTextures = nbContentTextures;
}

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

185 186
- (void)requestSyncData
{
187 188
  _needSync = true;
  [self setNeedsDisplay];
189 190
}

191
- (bool)syncData:(NSError **)error
192 193
{
  @autoreleasepool {
194

195 196
    NSDictionary *prevImages = _images;
    NSMutableDictionary *images = [[NSMutableDictionary alloc] init];
197

198 199 200
    GLRenderData * (^traverseTree) (GLData *data);
    __block __weak GLRenderData * (^weak_traverseTree)(GLData *data);
    weak_traverseTree = traverseTree = ^GLRenderData *(GLData *data) {
201 202
      NSNumber *width = data.width;
      NSNumber *height = data.height;
203
      NSNumber *pixelRatio = data.pixelRatio;
204
      int fboId = [data.fboId intValue];
205

206 207
      NSMutableArray *contextChildren = [[NSMutableArray alloc] init];
      for (GLData *child in data.contextChildren) {
208 209 210
        GLRenderData *node = weak_traverseTree(child);
        if (node == nil) return nil;
        [contextChildren addObject:node];
211
      }
212

213 214
      NSMutableArray *children = [[NSMutableArray alloc] init];
      for (GLData *child in data.children) {
215 216 217
        GLRenderData *node = weak_traverseTree(child);
        if (node == nil) return nil;
        [children addObject:node];
218
      }
219

220
      GLShader *shader = [_bridge.rnglContext getShader:data.shader];
221
      if (shader == nil) return nil;
222
      if (![shader ensureCompiles:error]) return nil;
223

224 225 226 227 228 229 230
      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];
231 232


233
        if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE) {
234
          uniforms[uniformName] = [NSNumber numberWithInt:units++];
235
          if ([value isEqual:[NSNull null]]) {
236
            GLTexture *emptyTexture = [[GLTexture alloc] init];
237
            [emptyTexture setPixels:nil];
238
            textures[uniformName] = emptyTexture;
239
          }
240 241 242 243 244 245 246 247
          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];
248
            }
249 250
            else if ([type isEqualToString:@"fbo"]) {
              NSNumber *id = [RCTConvert NSNumber:value[@"id"]];
251
              GLFBO *fbo = [_bridge.rnglContext getFBO:id];
252 253 254 255 256 257 258
              textures[uniformName] = fbo.color[0];
            }
            else if ([type isEqualToString:@"uri"]) {
              NSString *src = srcResource(value);
              if (!src) {
                RCTLogError(@"texture uniform '%@': Invalid uri format '%@'", uniformName, value);
              }
259

260 261 262 263 264 265 266
              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
267
                __weak GLCanvas *weakSelf = self;
268
                image = [[GLImage alloc] initWithBridge:_bridge withOnLoad:^{
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
269
                  if (weakSelf) [weakSelf onImageLoad:src];
270 271
                }];
                image.src = src;
272
                images[src] = image;
273 274
              }
              textures[uniformName] = [image getTexture];
275
            }
276 277
            else {
              RCTLogError(@"texture uniform '%@': Unexpected type '%@'", uniformName, type);
278 279 280 281 282 283 284
            }
          }
        }
        else {
          uniforms[uniformName] = value;
        }
      }
285

286 287 288 289 290
      int maxTextureUnits;
      glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
      if (units > maxTextureUnits) {
        RCTLogError(@"Maximum number of texture reach. got %i >= max %i", units, maxTextureUnits);
      }
291

292 293 294 295 296
      for (NSString *uniformName in shader.uniformTypes) {
        if (uniforms[uniformName] == nil) {
          RCTLogError(@"All defined uniforms must be provided. Missing '%@'", uniformName);
        }
      }
297

298 299 300 301
      return [[GLRenderData alloc]
              initWithShader:shader
              withUniforms:uniforms
              withTextures:textures
302 303
              withWidth:(int)([width floatValue] * [pixelRatio floatValue])
              withHeight:(int)([height floatValue] * [pixelRatio floatValue])
304 305
              withFboId:fboId
              withContextChildren:contextChildren
306
              withChildren:children];
307
    };
308

309 310 311 312
    GLRenderData *res = traverseTree(_data);
    if (res != nil) {
      _renderData = traverseTree(_data);
      _images = images;
313 314 315
      for (NSString *src in diff([prevImages allKeys], [images allKeys])) {
        [_preloaded removeObject:src];
      }
316
      return true;
317
    }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
318
    else {
319
      return false;
320
    }
321 322 323
  }
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
324
- (void)rasterizeContent
325
{
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
326
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas rasterizeContent", nil);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
327
  NSMutableArray *rasterizedContent = [[NSMutableArray alloc] init];
328 329 330 331
  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;
332
    if (view) {
333 334 335
      UIView *v = [view.subviews count] == 1 ?
      view.subviews[0] :
      view;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
336 337 338 339 340 341 342 343
      
      SEL selector = NSSelectorFromString(@"getPixelBuffer");
      if ([v respondsToSelector:selector]) {
        // will do in syncContentTextures at draw() time
      }
      else {
        imgData = [GLImageData genPixelsWithView:v withPixelRatio:self.contentScaleFactor];
      }
344
    }
345
    rasterizedContent[i] = imgData==nil ? [GLImageData empty] : imgData;
346
  }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
347
  _rasterizedContent = rasterizedContent;
348 349
  [self setNeedsDisplay];
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
350 351 352 353
}

- (void)syncContentTextures
{
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas syncContentTextures", nil);
  unsigned long max = MIN([_nbContentTextures intValue], [_contentTextures count]);

  for (int i = 0; i < max; i++) {
    UIView *view = self.superview.subviews[i]; // We take siblings by index (closely related to the JS code)
    if (view) {
      UIView *v = [view.subviews count] == 1 ?
      view.subviews[0] :
      view;
      
      SEL selector = NSSelectorFromString(@"getPixelBuffer");
      if ([v respondsToSelector:selector]) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
                                    [[v class] instanceMethodSignatureForSelector:selector]];
        [invocation setSelector:selector];
        [invocation setTarget:v];
        [invocation invoke];
        CVPixelBufferRef buffer;
        [invocation getReturnValue:&buffer];
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
373 374 375 376
        [_contentTextures[i] setPixelsWithPixelBuffer:buffer];
      }
      else {
        [_contentTextures[i] setPixels:_rasterizedContent[i]];
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
377 378
      }
    }
379
  }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
380
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
381 382
}

383 384 385 386 387 388 389 390 391 392
- (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
393 394 395

//// Draw

396
- (void) autoRedrawUpdate
397
{
398 399 400 401
  if ([self haveRemainingToPreload]) {
    return;
  }
  if ([_nbContentTextures intValue] > 0) {
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
402
    [self rasterizeContent];
403
  }
404 405 406
  [self setNeedsDisplay];
}

407 408
- (void)drawRect:(CGRect)rect
{
409
  self.layer.opaque = _opaque;
410

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
411 412
  if (_neverRendered) {
    _neverRendered = false;
413 414
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
415
  }
416

417
  if (_needSync) {
418
    NSError *error;
419 420 421 422 423
    BOOL syncSuccessful = [self syncData:&error];
    BOOL errorCanBeRecovered = error==nil || (error.code != GLLinkingFailure && error.code != GLCompileFailure);
    if (!syncSuccessful && errorCanBeRecovered) {
      // something failed but is recoverable, retry in one tick
      [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
424 425 426 427
    }
    else {
      _needSync = false;
    }
428
  }
429

430
  if ([self haveRemainingToPreload]) {
431 432
    return;
  }
433

434 435
  BOOL needsDeferredRendering = [_nbContentTextures intValue] > 0 && !_autoRedraw;
  if (needsDeferredRendering && !_deferredRendering) {
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
436
    _deferredRendering = true;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
437
    [self performSelectorOnMainThread:@selector(rasterizeContent) withObject:nil waitUntilDone:NO];
438
  }
439 440
  else {
    _deferredRendering = false;
441
    [self render];
442 443
    if (!_captureScheduled && [_captureConfigs count] > 0) {
      _captureScheduled = true;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
444
      [self performSelectorOnMainThread:@selector(capture) withObject:nil waitUntilDone:NO];
445
    }
446 447 448
  }
}

449
-(void) capture
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
450
{
451 452
  _captureScheduled = false;
  if (!self.onGLCaptureFrame) return;
453

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
454
  UIImage *frameImage = [self snapshot];
455

456 457 458
  for (CaptureConfig *config in _captureConfigs) {
    id result;
    id error;
459

460 461
    BOOL isPng = [config.type isEqualToString:@"png"];
    BOOL isJpeg = !isPng && ([config.type isEqualToString:@"jpeg"] || [config.type isEqualToString:@"jpg"]);
462

463 464
    BOOL isBase64 = [config.format isEqualToString:@"base64"];
    BOOL isFile = !isBase64 && [config.format isEqualToString:@"file"];
465

466 467 468 469
    NSData *frameData =
    isPng ? UIImagePNGRepresentation(frameImage) :
    isJpeg ? UIImageJPEGRepresentation(frameImage, [config.quality floatValue]) :
    nil;
470

471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
    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];
    }
490

491 492 493 494 495 496
    NSMutableDictionary *response = [[NSMutableDictionary alloc] init];
    response[@"config"] = [config dictionary];
    if (error) response[@"error"] = error;
    if (result) response[@"result"] = result;
    self.onGLCaptureFrame(response);
  }
497

498
  _captureConfigs = [[NSMutableArray alloc] init];
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
499 500
}

501
- (void)render
502
{
503 504
  GLRenderData *rd = _renderData;
  if (!rd) return;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
505
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas render", nil);
506

507
  @autoreleasepool {
508

509 510 511
    void (^recDraw) (GLRenderData *renderData);
    __block __weak void (^weak_recDraw) (GLRenderData *renderData);
    weak_recDraw = recDraw = ^void(GLRenderData *renderData) {
512 513
      int w = renderData.width;
      int h = renderData.height;
514

515 516
      for (GLRenderData *child in renderData.contextChildren)
        weak_recDraw(child);
517

518
      for (GLRenderData *child in renderData.children)
519
        weak_recDraw(child);
520

521 522 523
      RCT_PROFILE_BEGIN_EVENT(0, @"node", nil);

      RCT_PROFILE_BEGIN_EVENT(0, @"bind fbo", nil);
524
      if (renderData.fboId == -1) {
525 526 527 528
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
        glViewport(0, 0, w, h);
      }
      else {
529
        GLFBO *fbo = [_bridge.rnglContext getFBO:[NSNumber numberWithInt:renderData.fboId]];
530 531 532
        [fbo setShapeWithWidth:w withHeight:h];
        [fbo bind];
      }
533
      RCT_PROFILE_END_EVENT(0, @"gl", nil);
534

535
      RCT_PROFILE_BEGIN_EVENT(0, @"bind shader", nil);
536
      [renderData.shader bind];
537
      RCT_PROFILE_END_EVENT(0, @"gl", nil);
538

539
      RCT_PROFILE_BEGIN_EVENT(0, @"bind textures", nil);
540 541 542 543 544
      for (NSString *uniformName in renderData.textures) {
        GLTexture *texture = renderData.textures[uniformName];
        int unit = [((NSNumber *)renderData.uniforms[uniformName]) intValue];
        [texture bind:unit];
      }
545
      RCT_PROFILE_END_EVENT(0, @"gl", nil);
546

547
      RCT_PROFILE_BEGIN_EVENT(0, @"bind set uniforms", nil);
548 549 550
      for (NSString *uniformName in renderData.uniforms) {
        [renderData.shader setUniform:uniformName withValue:renderData.uniforms[uniformName]];
      }
551
      RCT_PROFILE_END_EVENT(0, @"gl", nil);
552

553
      RCT_PROFILE_BEGIN_EVENT(0, @"draw", nil);
554
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
555 556
      glClearColor(0.0, 0.0, 0.0, 0.0);
      glClear(GL_COLOR_BUFFER_BIT);
557
      glDrawArrays(GL_TRIANGLES, 0, 6);
558 559 560
      RCT_PROFILE_END_EVENT(0, @"gl", nil);

      RCT_PROFILE_END_EVENT(0, @"gl", nil);
561
    };
562

563
    // DRAWING THE SCENE
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
564
    
565
    [self syncContentTextures];
566

567
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
568
    glEnable(GL_BLEND);
569
    recDraw(rd);
570
    glDisable(GL_BLEND);
571
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
572
    glBindBuffer(GL_ARRAY_BUFFER, 0);
573

574 575 576 577
    if (_dirtyOnLoad && ![self haveRemainingToPreload]) {
      _dirtyOnLoad = false;
      [self dispatchOnLoad];
    }
578
  }
579

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
580
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
581 582
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
583 584 585 586
//// utility methods

- (void)onImageLoad:(NSString *)loaded
{
587 588 589 590 591 592 593
  [_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
594 595 596 597 598 599 600 601 602 603 604
}

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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
- (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
{
624
  if (self.onGLLoad) self.onGLLoad(@{});
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
625 626 627 628
}

- (void)dispatchOnProgress: (double)progress withLoaded:(int)loaded withTotal:(int)total
{
629
  if (self.onGLProgress) self.onGLProgress(
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
630 631 632 633 634
                                           @{
                                             @"progress": @(RCTZeroIfNaN(progress)),
                                             @"loaded": @(RCTZeroIfNaN(loaded)),
                                             @"total": @(RCTZeroIfNaN(total))
                                             });
635 636
}

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