GLShader.m 12.6 KB
Newer Older
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
1
#import <GLKit/GLKit.h>
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
2 3 4
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>
#import <React/RCTConvert.h>
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
5 6
#import "GLShader.h"

7
GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shaderType, NSError **error) {
8 9 10 11 12 13 14 15 16 17 18 19 20 21
  
  GLuint shaderHandle = glCreateShader(shaderType);
  
  const char * shaderStringUTF8 = [shaderString UTF8String];
  int shaderStringLength = (int) [shaderString length];
  glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
  
  glCompileShader(shaderHandle);
  
  GLint compileSuccess;
  glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
  if (compileSuccess == GL_FALSE) {
    GLchar messages[256];
    glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
22 23 24 25
    *error = [[NSError alloc]
              initWithDomain:[NSString stringWithUTF8String:messages]
              code:GLCompileFailure
              userInfo:nil];
26 27 28 29 30 31
    return -1;
  }
  
  return shaderHandle;
}

Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
32 33 34 35 36 37 38 39 40 41
/**
 * a GLShader represents the atomic component of GL React Native.
 * It currently statically holds a program that renders 2 static triangles over the full viewport (2D)
 */
@implementation GLShader
{
  EAGLContext *_context; // Context related to this shader
  GLuint program; // Program of the shader
  GLuint buffer; // the buffer currently contains 2 static triangles covering the surface
  GLint pointerLoc; // The "pointer" attribute is used to iterate over vertex
42 43
  NSDictionary *_uniformTypes; // The types of the GLSL uniforms
  NSDictionary *_uniformSizes; // The size of the uniform variable
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
44
  NSDictionary *_uniformLocations; // The uniform locations cache
45
  NSArray *__uniformNames; // The uniforms names
46
  NSError *_error;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
47 48
}

49
- (instancetype)initWithContext: (EAGLContext*)context withName:(NSString *)name withVert:(NSString *)vert withFrag:(NSString *)frag
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
50 51 52
{
  self = [super init];
  if (self) {
53
    _name = name;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
54 55 56
    _context = context;
    _vert = vert;
    _frag = frag;
57 58 59 60
    NSError *error;
    if (![self makeProgram:&error]) {
      _error = error;
    }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
61 62 63 64 65 66 67 68
  }
  return self;
}

- (void)dealloc
{
  glDeleteProgram(program);
  glDeleteBuffers(1, &buffer);
69 70 71 72 73 74 75 76 77
  _name = nil;
  _context = nil;
  _vert = nil;
  _frag = nil;
  _uniformLocations = nil;
  _uniformTypes = nil;
  program = 0;
  buffer = 0;
  pointerLoc = 0;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
78 79
}

80
- (bool) ensureContext: (NSError **)error
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
81
{
82
  if (!_context || ![EAGLContext setCurrentContext:_context]) {
83
    *error = [[NSError alloc] initWithDomain:@"Failed to set current OpenGL context" code:GLContextFailure userInfo:nil];
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
    return false;
  }
  return true;
}

- (void) bind
{
  glUseProgram(program);
  glBindBuffer(GL_ARRAY_BUFFER, buffer);
  glEnableVertexAttribArray(pointerLoc);
  glVertexAttribPointer(pointerLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
}

- (void) setUniform: (NSString *)name withValue:(id)value
{
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
  GLint size = [_uniformSizes[name] intValue];
  GLenum type = [_uniformTypes[name] intValue];
  if (size != 1) {
    NSArray *v = [RCTConvert NSArray:value];
    if (!v || [v count]!=size) {
      RCTLogError(@"Shader '%@': uniform '%@' should be an array of %i elements", _name, name, size);
      return;
    }
    for (int i=0; i<size; i++) {
      NSString *indexName = [NSString stringWithFormat:@"%@[%i]", name, i];
      id indexValue = v[i];
      [self setUniform:indexName withValue:indexValue];
    }
    return;
  }
  
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
115
  if ([_uniformLocations objectForKey:name] == nil) {
116
    RCTLogError(@"Shader '%@': uniform '%@' does not exist", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
117 118 119
    return;
  }
  GLint location = [_uniformLocations[name] intValue];
120
  
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
121 122 123 124 125
  switch (type)
  {
    case GL_FLOAT: {
      NSNumber *v = [RCTConvert NSNumber:value];
      if (!v) {
126
        RCTLogError(@"Shader '%@': uniform '%@' should be a float", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
127 128 129 130 131 132 133 134 135
        return;
      }
      glUniform1f(location, [v floatValue]);
      break;
    }

    case GL_INT: {
      NSNumber *v = [RCTConvert NSNumber:value];
      if (!v) {
136
        RCTLogError(@"Shader '%@': uniform '%@' should be a int", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
        return;
      }
      glUniform1i(location, [v intValue]);
      break;
    }

    case GL_BOOL: {
      BOOL v = [RCTConvert BOOL:value];
      glUniform1i(location, v ? 1 : 0);
      break;
    }

    case GL_FLOAT_VEC2: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=2) {
152
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 2 numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
153 154 155 156 157 158
        return;
      }
      GLfloat arr[2];
      for (int i=0; i<2; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
159
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
160 161 162 163 164 165 166 167 168 169 170
          return;
        }
        arr[i] = [n floatValue];
      }
      glUniform2fv(location, 1, arr);
      break;
    }
      
    case GL_FLOAT_VEC3: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=3) {
171
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 3 numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
172 173 174 175 176 177
        return;
      }
      GLfloat arr[3];
      for (int i=0; i<3; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
178
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
179 180 181 182 183 184 185 186 187 188 189
          return;
        }
        arr[i] = [n floatValue];
      }
      glUniform3fv(location, 1, arr);
      break;
    }
      
    case GL_FLOAT_VEC4: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=4) {
190
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 4 numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
191 192 193 194 195 196
        return;
      }
      GLfloat arr[4];
      for (int i=0; i<4; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
197
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
198 199 200 201 202 203 204 205 206 207 208 209
          return;
        }
        arr[i] = [n floatValue];
      }
      glUniform4fv(location, 1, arr);
      break;
    }
      
    case GL_BOOL_VEC2:
    case GL_INT_VEC2: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=2) {
210
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 2 numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
211 212 213 214 215 216
        return;
      }
      GLint arr[2];
      for (int i=0; i<2; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
217
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
218 219 220 221 222 223 224 225 226 227 228 229
          return;
        }
        arr[i] = [n intValue];
      }
      glUniform2iv(location, 1, arr);
      break;
    }
      
    case GL_BOOL_VEC3:
    case GL_INT_VEC3: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=3) {
230
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 3 numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
231 232 233 234 235 236
        return;
      }
      GLint arr[3];
      for (int i=0; i<3; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
237
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
238 239 240 241 242 243 244 245 246 247 248 249
          return;
        }
        arr[i] = [n intValue];
      }
      glUniform3iv(location, 1, arr);
      break;
    }
      
    case GL_BOOL_VEC4:
    case GL_INT_VEC4: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=4) {
250
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 4 numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
251 252 253 254 255 256
        return;
      }
      GLint arr[4];
      for (int i=0; i<4; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
257
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
258 259 260 261 262 263 264 265 266 267 268
          return;
        }
        arr[i] = [n intValue];
      }
      glUniform4iv(location, 1, arr);
      break;
    }
      
    case GL_FLOAT_MAT2: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=4) {
269
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 4 numbers (matrix)", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
270 271 272 273 274 275
        return;
      }
      GLfloat arr[4];
      for (int i=0; i<4; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
276
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
277 278 279 280 281 282 283 284 285 286 287
          return;
        }
        arr[i] = [n floatValue];
      }
      glUniformMatrix2fv(location, 1, false, arr);
      break;
    }
      
    case GL_FLOAT_MAT3: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=9) {
288
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 9 numbers (matrix)", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
289 290 291 292 293 294
        return;
      }
      GLfloat arr[9];
      for (int i=0; i<9; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
295
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
296 297 298 299 300 301 302 303 304 305 306
          return;
        }
        arr[i] = [n floatValue];
      }
      glUniformMatrix3fv(location, 1, false, arr);
      break;
    }
      
    case GL_FLOAT_MAT4: {
      NSArray *v = [RCTConvert NSArray:value];
      if (!v || [v count]!=16) {
307
        RCTLogError(@"Shader '%@': uniform '%@' should be an array of 16 numbers (matrix)", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
308 309 310 311 312 313
        return;
      }
      GLfloat arr[16];
      for (int i=0; i<16; i++) {
        NSNumber *n = [RCTConvert NSNumber: v[i]];
        if (!n) {
314
          RCTLogError(@"Shader '%@': uniform '%@' array should only contains numbers", _name, name);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
          return;
        }
        arr[i] = [n floatValue];
      }
      glUniformMatrix4fv(location, 1, false, arr);
      break;
    }

    case GL_SAMPLER_CUBE:
    case GL_SAMPLER_2D: {
      NSInteger v = [RCTConvert NSInteger:value];
      glUniform1i(location, (int) v);
      break;
    }

    default:
331
      RCTLogError(@"Shader '%@': uniform '%@': unsupported type %i", _name, name, type);
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
332 333 334 335 336
  }
}

- (void) computeMeta
{
337 338
  NSMutableArray *uniformNames = [[NSMutableArray alloc] init];
  NSMutableDictionary *uniformTypes = @{}.mutableCopy;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
339
  NSMutableDictionary *locations = @{}.mutableCopy;
340
  NSMutableDictionary *sizes = @{}.mutableCopy;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
341 342 343 344 345 346 347 348 349
  int nbUniforms;
  GLchar name[256];
  GLenum type;
  GLint size;
  GLsizei length;
  glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &nbUniforms);
  for (int i=0; i<nbUniforms; i++) {
    glGetActiveUniform(program, i, sizeof(name), &length, &size, &type, name);
    NSString *uniformName = [NSString stringWithUTF8String:name];
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
    if ([uniformName containsString:@"[0]"]) {
      uniformName = [uniformName substringToIndex:[uniformName length]-3];
    }
    uniformTypes[uniformName] = [NSNumber numberWithInt:type];
    sizes[uniformName] = [NSNumber numberWithInt:size];
    [uniformNames addObject:uniformName];
    
    if (size == 1) {
      GLint location = glGetUniformLocation(program, name);
      locations[uniformName] = [NSNumber numberWithInt:location];
    }
    else {
      for (int j=0; j<size; j++) {
        NSString *uniformIndexName = [NSString stringWithFormat:@"%@[%i]", uniformName, j];
        GLint location = glGetUniformLocation(program, [uniformIndexName UTF8String]);
        locations[uniformIndexName] = [NSNumber numberWithInt:location];
        uniformTypes[uniformIndexName] = [NSNumber numberWithInt:type];
        sizes[uniformIndexName] = [NSNumber numberWithInt:1];
      }
    }
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
370
  }
371 372
  
  _uniformTypes = uniformTypes;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
373
  _uniformLocations = locations;
374 375
  _uniformSizes = sizes;
  _uniformNames = uniformNames;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
376 377
}

378 379
- (bool) ensureCompiles: (NSError **)error
{
380 381 382 383 384 385 386
  if (![self ensureContext:error]) {
    return false;
  }
  if (!glIsProgram(program)) {
    *error = [[NSError alloc] initWithDomain:@"not a program" code:GLNotAProgram userInfo:nil];
    return false;
  }
387 388 389 390 391 392
  if (_error == nil) return true;
  *error = _error;
  return false;
}

- (bool) makeProgram: (NSError **)error
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
393
{
394
  if (![self ensureContext:error]) return false;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
395

396 397
  GLuint vertex = compileShader(_name, _vert, GL_VERTEX_SHADER, error);
  if (vertex == -1) return false;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
398

399 400
  GLuint fragment = compileShader(_name, _frag, GL_FRAGMENT_SHADER, error);
  if (fragment == -1) return false;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
401 402 403 404 405 406 407 408 409 410 411

  program = glCreateProgram();
  glAttachShader(program, vertex);
  glAttachShader(program, fragment);
  glLinkProgram(program);

  GLint linkSuccess;
  glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess);
  if (linkSuccess == GL_FALSE) {
    GLchar messages[256];
    glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
412 413 414 415 416
    *error = [[NSError alloc]
              initWithDomain:[NSString stringWithUTF8String:messages]
              code:GLLinkingFailure
              userInfo:nil];
    return false;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
417 418 419 420 421 422 423 424 425 426 427 428
  }

  glUseProgram(program);

  [self computeMeta];

  pointerLoc = glGetAttribLocation(program, "position");

  glGenBuffers(1, &buffer);
  glBindBuffer(GL_ARRAY_BUFFER, buffer);
  GLfloat buf[] = {
    -1.0, -1.0,
429 430
    -1.0, 4.0,
     4.0,  -1.0
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
431 432
  };
  glBufferData(GL_ARRAY_BUFFER, sizeof(buf), buf, GL_STATIC_DRAW);
433 434
    
  return true;
Gaëtan Renaudeau's avatar
Gaëtan Renaudeau committed
435 436 437 438
}


@end