GLCanvas.m 18.5 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

46
  NSArray *_contentData;
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 91 92 93 94 95 96 97 98 99 100
- (void)dealloc
{
  _bridge = nil;
  _images = nil;
  _preloaded = nil;
  _captureConfigs = nil;
  _contentData = nil;
  _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 327 328 329
  // TODO: we need to refactor how this stuff work...
  // we should no longer do any rasterize work if all the work is to be done in syncContentTextures

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

- (void)syncContentTextures
{
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas syncContentTextures", nil);
  NSMutableArray *contentData = _contentData.mutableCopy;
  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];
        
        int width = (int) CVPixelBufferGetWidth(buffer);
        int height = (int) CVPixelBufferGetHeight(buffer);
        contentData[i] = [[GLImageData alloc]
                   initWithData:CVPixelBufferGetBaseAddress(buffer)
                   withWidth:width
                   withHeight:height];
      }
      
      [_contentTextures[i] setPixels:contentData[i]];
    }
388
  }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
389
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
390 391
}

392 393 394 395 396 397 398 399 400 401
- (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
402 403 404

//// Draw

405
- (void) autoRedrawUpdate
406
{
407 408 409 410
  if ([self haveRemainingToPreload]) {
    return;
  }
  if ([_nbContentTextures intValue] > 0) {
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
411
    [self rasterizeContent];
412
  }
413 414 415
  [self setNeedsDisplay];
}

416 417
- (void)drawRect:(CGRect)rect
{
418
  self.layer.opaque = _opaque;
419

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
420 421
  if (_neverRendered) {
    _neverRendered = false;
422 423
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
424
  }
425

426
  if (_needSync) {
427
    NSError *error;
428 429 430 431 432
    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];
433 434 435 436
    }
    else {
      _needSync = false;
    }
437
  }
438

439
  if ([self haveRemainingToPreload]) {
440 441
    return;
  }
442

443 444
  BOOL needsDeferredRendering = [_nbContentTextures intValue] > 0 && !_autoRedraw;
  if (needsDeferredRendering && !_deferredRendering) {
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
445
    _deferredRendering = true;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
446
    [self performSelectorOnMainThread:@selector(rasterizeContent) withObject:nil waitUntilDone:NO];
447
  }
448 449
  else {
    _deferredRendering = false;
450
    [self render];
451 452
    if (!_captureScheduled && [_captureConfigs count] > 0) {
      _captureScheduled = true;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
453
      [self performSelectorOnMainThread:@selector(capture) withObject:nil waitUntilDone:NO];
454
    }
455 456 457
  }
}

458
-(void) capture
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
459
{
460 461
  _captureScheduled = false;
  if (!self.onGLCaptureFrame) return;
462

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

465 466 467
  for (CaptureConfig *config in _captureConfigs) {
    id result;
    id error;
468

469 470
    BOOL isPng = [config.type isEqualToString:@"png"];
    BOOL isJpeg = !isPng && ([config.type isEqualToString:@"jpeg"] || [config.type isEqualToString:@"jpg"]);
471

472 473
    BOOL isBase64 = [config.format isEqualToString:@"base64"];
    BOOL isFile = !isBase64 && [config.format isEqualToString:@"file"];
474

475 476 477 478
    NSData *frameData =
    isPng ? UIImagePNGRepresentation(frameImage) :
    isJpeg ? UIImageJPEGRepresentation(frameImage, [config.quality floatValue]) :
    nil;
479

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
    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];
    }
499

500 501 502 503 504 505
    NSMutableDictionary *response = [[NSMutableDictionary alloc] init];
    response[@"config"] = [config dictionary];
    if (error) response[@"error"] = error;
    if (result) response[@"result"] = result;
    self.onGLCaptureFrame(response);
  }
506

507
  _captureConfigs = [[NSMutableArray alloc] init];
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
508 509
}

510
- (void)render
511
{
512 513
  GLRenderData *rd = _renderData;
  if (!rd) return;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
514
  RCT_PROFILE_BEGIN_EVENT(0, @"GLCanvas render", nil);
515

516
  @autoreleasepool {
517

518 519 520
    void (^recDraw) (GLRenderData *renderData);
    __block __weak void (^weak_recDraw) (GLRenderData *renderData);
    weak_recDraw = recDraw = ^void(GLRenderData *renderData) {
521 522
      int w = renderData.width;
      int h = renderData.height;
523

524 525
      for (GLRenderData *child in renderData.contextChildren)
        weak_recDraw(child);
526

527
      for (GLRenderData *child in renderData.children)
528
        weak_recDraw(child);
529

530 531 532
      RCT_PROFILE_BEGIN_EVENT(0, @"node", nil);

      RCT_PROFILE_BEGIN_EVENT(0, @"bind fbo", nil);
533
      if (renderData.fboId == -1) {
534 535 536 537
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
        glViewport(0, 0, w, h);
      }
      else {
538
        GLFBO *fbo = [_bridge.rnglContext getFBO:[NSNumber numberWithInt:renderData.fboId]];
539 540 541
        [fbo setShapeWithWidth:w withHeight:h];
        [fbo bind];
      }
542
      RCT_PROFILE_END_EVENT(0, @"gl", nil);
543

544
      RCT_PROFILE_BEGIN_EVENT(0, @"bind shader", nil);
545
      [renderData.shader bind];
546
      RCT_PROFILE_END_EVENT(0, @"gl", nil);
547

548
      RCT_PROFILE_BEGIN_EVENT(0, @"bind textures", nil);
549 550 551 552 553
      for (NSString *uniformName in renderData.textures) {
        GLTexture *texture = renderData.textures[uniformName];
        int unit = [((NSNumber *)renderData.uniforms[uniformName]) intValue];
        [texture bind:unit];
      }
554
      RCT_PROFILE_END_EVENT(0, @"gl", nil);
555

556
      RCT_PROFILE_BEGIN_EVENT(0, @"bind set uniforms", nil);
557 558 559
      for (NSString *uniformName in renderData.uniforms) {
        [renderData.shader setUniform:uniformName withValue:renderData.uniforms[uniformName]];
      }
560
      RCT_PROFILE_END_EVENT(0, @"gl", nil);
561

562
      RCT_PROFILE_BEGIN_EVENT(0, @"draw", nil);
563
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
564 565
      glClearColor(0.0, 0.0, 0.0, 0.0);
      glClear(GL_COLOR_BUFFER_BIT);
566
      glDrawArrays(GL_TRIANGLES, 0, 6);
567 568 569
      RCT_PROFILE_END_EVENT(0, @"gl", nil);

      RCT_PROFILE_END_EVENT(0, @"gl", nil);
570
    };
571

572
    // DRAWING THE SCENE
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
573
    
574
    [self syncContentTextures];
575

576
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
577
    glEnable(GL_BLEND);
578
    recDraw(rd);
579
    glDisable(GL_BLEND);
580
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
581
    glBindBuffer(GL_ARRAY_BUFFER, 0);
582

583 584 585 586
    if (_dirtyOnLoad && ![self haveRemainingToPreload]) {
      _dirtyOnLoad = false;
      [self dispatchOnLoad];
    }
587
  }
588

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
589
  RCT_PROFILE_END_EVENT(0, @"gl", nil);
590 591
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
592 593 594 595
//// utility methods

- (void)onImageLoad:(NSString *)loaded
{
596 597 598 599 600 601 602
  [_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
603 604 605 606 607 608 609 610 611 612 613
}

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

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
- (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
{
633
  if (self.onGLLoad) self.onGLLoad(@{});
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
634 635 636 637
}

- (void)dispatchOnProgress: (double)progress withLoaded:(int)loaded withTotal:(int)total
{
638
  if (self.onGLProgress) self.onGLProgress(
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
639 640 641 642 643
                                           @{
                                             @"progress": @(RCTZeroIfNaN(progress)),
                                             @"loaded": @(RCTZeroIfNaN(loaded)),
                                             @"total": @(RCTZeroIfNaN(total))
                                             });
644 645
}

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