From 8deddda832f09b5fb975c17ae848f24bf5d6ff46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 1 Jan 2016 17:41:16 +0100 Subject: [PATCH] Forward gl compile status to gl-react for inline shader feature --- .../com/projectseptember/RNGL/GLCanvas.java | 11 ++- .../com/projectseptember/RNGL/GLShader.java | 36 ++++--- .../RNGL/GLShaderCompilationFailed.java | 12 +++ .../projectseptember/RNGL/RNGLContext.java | 67 ++++++++++++- ios/GLCanvas.m | 17 +++- ios/GLShader.h | 11 ++- ios/GLShader.m | 66 +++++++------ ios/RNGL.xcodeproj/project.pbxproj | 2 +- ios/RNGLContext.m | 93 ++++++++++++++++++- src/index.js | 4 +- 10 files changed, 261 insertions(+), 58 deletions(-) create mode 100644 android/src/main/java/com/projectseptember/RNGL/GLShaderCompilationFailed.java diff --git a/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java b/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java index 6bfa8fc..8350291 100644 --- a/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java +++ b/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java @@ -113,7 +113,7 @@ public class GLCanvas extends GLSurfaceView if (!shaders.containsKey(id)) { GLShaderData shaderData = rnglContext.getShader(id); if (shaderData == null) return null; - shaders.put(id, new GLShader(shaderData)); + shaders.put(id, new GLShader(shaderData, id, rnglContext)); } return shaders.get(id); } @@ -315,8 +315,13 @@ public class GLCanvas extends GLSurfaceView execute(new Runnable() { public void run() { // FIXME: maybe should set a flag so we don't do it twice?? - if (!syncData()) - requestSyncData(); + try { + if (!syncData()) + requestSyncData(); + } + catch (GLShaderCompilationFailed e) { + // This is ignored. It will be handled by RNGLContext.shaderFailedToCompile + } } }); } diff --git a/android/src/main/java/com/projectseptember/RNGL/GLShader.java b/android/src/main/java/com/projectseptember/RNGL/GLShader.java index e19dab7..15f95f6 100644 --- a/android/src/main/java/com/projectseptember/RNGL/GLShader.java +++ b/android/src/main/java/com/projectseptember/RNGL/GLShader.java @@ -21,29 +21,30 @@ public class GLShader { private int pointerLoc; // The "pointer" attribute is used to iterate over vertex private Map uniformLocations; // The uniform locations cache - public GLShader(String name, String vert, String frag) { - this.name = name; - this.vert = vert; - this.frag = frag; - } + private Integer id; + private RNGLContext rnglContext; + private GLShaderCompilationFailed compilationFailed; - public GLShader(GLShaderData data) { + public GLShader(GLShaderData data, Integer id, RNGLContext rnglContext) { this.name = data.name; this.vert = data.vert; this.frag = data.frag; + this.id = id; + this.rnglContext = rnglContext; } @Override protected void finalize() throws Throwable { super.finalize(); if (buffer != null) { + // TODO: need to check if this works properly glDeleteProgram(program); glDeleteBuffers(1, buffer, 0); } } public void runtimeException (String msg) { - throw new RuntimeException("Shader '"+name+"': "+msg); + throw new GLShaderCompilationFailed(name, msg); } public void bind () { @@ -64,7 +65,7 @@ public class GLShader { glGetProgramiv(program, GL_VALIDATE_STATUS, validSuccess, 0); if (validSuccess[0] == GL_FALSE) { glGetProgramInfoLog(program); - runtimeException("Validation failed " + glGetProgramInfoLog(program)); + runtimeException(glGetProgramInfoLog(program)); } } @@ -133,7 +134,7 @@ public class GLShader { int compileSuccess[] = new int[1]; glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, compileSuccess, 0); if (compileSuccess[0] == GL_FALSE) { - runtimeException("failed to compile: " + glGetShaderInfoLog(shaderHandle)); + runtimeException(glGetShaderInfoLog(shaderHandle)); return -1; } return shaderHandle; @@ -157,7 +158,7 @@ public class GLShader { this.uniformLocations = locations; } - private void makeProgram () { + private void makeProgram () throws GLShaderCompilationFailed { int vertex = compileShader(vert, GL_VERTEX_SHADER); if (vertex == -1) return; @@ -172,7 +173,7 @@ public class GLShader { int[] linkSuccess = new int[1]; glGetProgramiv(program, GL_LINK_STATUS, linkSuccess, 0); if (linkSuccess[0] == GL_FALSE) { - runtimeException("Linking failed "+glGetProgramInfoLog(program)); + runtimeException(glGetProgramInfoLog(program)); } glUseProgram(program); @@ -208,7 +209,18 @@ public class GLShader { } public boolean ensureCompile() { - if (!isReady()) makeProgram(); + if (!isReady()) { + if (compilationFailed != null) throw compilationFailed; + try { + makeProgram(); + rnglContext.shaderSucceedToCompile(id, uniformTypes); + } + catch (GLShaderCompilationFailed e) { + compilationFailed = e; + rnglContext.shaderFailedToCompile(id, e); + throw e; + } + } return isReady(); } } diff --git a/android/src/main/java/com/projectseptember/RNGL/GLShaderCompilationFailed.java b/android/src/main/java/com/projectseptember/RNGL/GLShaderCompilationFailed.java new file mode 100644 index 0000000..2fc50dc --- /dev/null +++ b/android/src/main/java/com/projectseptember/RNGL/GLShaderCompilationFailed.java @@ -0,0 +1,12 @@ +package com.projectseptember.RNGL; + +public class GLShaderCompilationFailed extends RuntimeException { + public final String shaderName; + public final String compileError; + + public GLShaderCompilationFailed(String shaderName, String compileError) { + super("Shader '"+shaderName+"': "+compileError); + this.compileError = compileError; + this.shaderName = shaderName; + } +} diff --git a/android/src/main/java/com/projectseptember/RNGL/RNGLContext.java b/android/src/main/java/com/projectseptember/RNGL/RNGLContext.java index 4c78d98..5817372 100644 --- a/android/src/main/java/com/projectseptember/RNGL/RNGLContext.java +++ b/android/src/main/java/com/projectseptember/RNGL/RNGLContext.java @@ -1,9 +1,15 @@ package com.projectseptember.RNGL; +import android.util.Log; + +import static android.opengl.GLES20.*; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; import java.util.HashMap; import java.util.Map; @@ -20,6 +26,7 @@ public class RNGLContext extends ReactContextBaseJavaModule { private Map shaders = new HashMap<>(); private Map fbos = new HashMap<>(); + private Map onCompileCallbacks = new HashMap<>(); public RNGLContext (ReactApplicationContext reactContext) { super(reactContext); @@ -35,9 +42,67 @@ public class RNGLContext extends ReactContextBaseJavaModule { } @ReactMethod - public void addShader (final Integer id, final ReadableMap config) { + public void addShader (final Integer id, final ReadableMap config, final Callback onCompile) { final String frag = config.getString("frag"); final String name = config.getString("name"); shaders.put(id, new GLShaderData(name, STATIC_VERT, frag)); + if (onCompile != null) { + onCompileCallbacks.put(id, onCompile); + } + } + + @ReactMethod + public void removeShader (final Integer id) { + GLShaderData shader = shaders.remove(id); + if (shader == null) { + throw new Error("removeShader("+id+"): shader does not exist"); + } + } + + public void shaderFailedToCompile(Integer id, GLShaderCompilationFailed e) { + Callback onCompile = onCompileCallbacks.get(id); + if (onCompile == null) { + Log.e("RNGLContext", e.getMessage()); + } + else { + onCompile.invoke(e.compileError); + } + } + + public void shaderSucceedToCompile(Integer id, Map uniformTypes) { + Callback onCompile = onCompileCallbacks.get(id); + onCompileCallbacks.remove(id); + if (onCompile != null) { + WritableMap res = Arguments.createMap(); + WritableMap uniforms = Arguments.createMap(); + for (String key : uniformTypes.keySet()) { + uniforms.putString(key, glTypeString(uniformTypes.get(key))); + } + res.putMap("uniforms", uniforms); + onCompile.invoke(null, res); + } + } + + static String glTypeString (int type) { + switch (type) { + case GL_FLOAT: return "float"; + case GL_FLOAT_VEC2: return "vec2"; + case GL_FLOAT_VEC3: return "vec3"; + case GL_FLOAT_VEC4: return "vec4"; + case GL_INT: return "int"; + case GL_INT_VEC2: return "ivec2"; + case GL_INT_VEC3: return "ivec3"; + case GL_INT_VEC4: return "ivec4"; + case GL_BOOL: return "bool"; + case GL_BOOL_VEC2: return "bvec2"; + case GL_BOOL_VEC3: return "bvec3"; + case GL_BOOL_VEC4: return "bvec4"; + case GL_FLOAT_MAT2: return "mat2"; + case GL_FLOAT_MAT3: return "mat3"; + case GL_FLOAT_MAT4: return "mat4"; + case GL_SAMPLER_2D: return "sampler2D"; + case GL_SAMPLER_CUBE: return "samplerCube"; + } + return ""; } } diff --git a/ios/GLCanvas.m b/ios/GLCanvas.m index 4703b40..edca826 100644 --- a/ios/GLCanvas.m +++ b/ios/GLCanvas.m @@ -173,7 +173,7 @@ RCT_NOT_IMPLEMENTED(-init) [self setNeedsDisplay]; } -- (void)syncData +- (bool)syncData:(NSError **)error { @autoreleasepool { @@ -204,6 +204,7 @@ RCT_NOT_IMPLEMENTED(-init) GLShader *shader = [_bridge.rnglContext getShader:data.shader]; if (shader == nil) return nil; + if (![shader ensureCompiles:error]) return nil; NSDictionary *uniformTypes = [shader uniformTypes]; NSMutableDictionary *uniforms = [[NSMutableDictionary alloc] init]; @@ -292,16 +293,15 @@ RCT_NOT_IMPLEMENTED(-init) GLRenderData *res = traverseTree(_data); if (res != nil) { - _needSync = false; _renderData = traverseTree(_data); _images = images; for (NSString *src in diff([prevImages allKeys], [images allKeys])) { [_preloaded removeObject:src]; } + return true; } else { - // the data is not ready, retry in one tick - [self setNeedsDisplay]; + return false; } } } @@ -374,7 +374,14 @@ RCT_NOT_IMPLEMENTED(-init) } if (_needSync) { - [self syncData]; + NSError *error; + if(![self syncData:&error] && error==nil) { + // the data is not ready, retry in one tick + [self setNeedsDisplay]; + } + else { + _needSync = false; + } } if ([self haveRemainingToPreload]) { diff --git a/ios/GLShader.h b/ios/GLShader.h index 2983552..bee570c 100644 --- a/ios/GLShader.h +++ b/ios/GLShader.h @@ -1,6 +1,12 @@ #import #import "RCTBridgeModule.h" +NS_ENUM(NSInteger) { + GLContextFailure = 87001, + GLLinkingFailure = 87002, + GLCompileFailure = 87003 +}; + @interface GLShader: NSObject @property EAGLContext *context; @@ -18,10 +24,7 @@ */ - (void) bind; -/** - * Check the shader validity - */ -- (void) validate; +- (bool) ensureCompiles: (NSError**)error; /** * Set the value of an uniform diff --git a/ios/GLShader.m b/ios/GLShader.m index 2271400..23eab38 100644 --- a/ios/GLShader.m +++ b/ios/GLShader.m @@ -5,7 +5,7 @@ #import "RCTConvert.h" #import "GLShader.h" -GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shaderType) { +GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shaderType, NSError **error) { GLuint shaderHandle = glCreateShader(shaderType); @@ -20,8 +20,10 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade if (compileSuccess == GL_FALSE) { GLchar messages[256]; glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); - NSString *messageString = [NSString stringWithUTF8String:messages]; - RCTLogError(@"Shader '%@' failed to compile: %@", shaderName, messageString); + *error = [[NSError alloc] + initWithDomain:[NSString stringWithUTF8String:messages] + code:GLCompileFailure + userInfo:nil]; return -1; } @@ -41,6 +43,7 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade GLint pointerLoc; // The "pointer" attribute is used to iterate over vertex NSDictionary *_uniformTypes; // The types of the GLSL uniforms (N.B: array are not supported) NSDictionary *_uniformLocations; // The uniform locations cache + NSError *_error; } - (instancetype)initWithContext: (EAGLContext*)context withName:(NSString *)name withVert:(NSString *)vert withFrag:(NSString *)frag @@ -51,7 +54,10 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade _context = context; _vert = vert; _frag = frag; - [self makeProgram]; + NSError *error; + if (![self makeProgram:&error]) { + _error = error; + } } return self; } @@ -62,10 +68,10 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade glDeleteBuffers(1, &buffer); } -- (bool) ensureContext +- (bool) ensureContext: (NSError **)error { if (![EAGLContext setCurrentContext:_context]) { - RCTLogError(@"Shader '%@': Failed to set current OpenGL context", _name); + *error = [[NSError alloc] initWithDomain:@"Failed to set current OpenGL context" code:GLContextFailure userInfo:nil]; return false; } return true; @@ -73,7 +79,11 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade - (void) bind { - if (![self ensureContext]) return; + NSError *error; + if (![self ensureContext:&error]) { + RCTLogError(@"%@", error.domain); + return; + } if ( glIsProgram(program) != GL_TRUE ){ RCTLogError(@"Shader '%@': not a program!", _name); return; @@ -307,19 +317,6 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade } } -- (void) validate -{ - glValidateProgram(program); - GLint validSuccess; - glGetProgramiv(program, GL_VALIDATE_STATUS, &validSuccess); - if (validSuccess == GL_FALSE) { - GLchar messages[256]; - glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); - NSString *messageString = [NSString stringWithUTF8String:messages]; - RCTLogError(@"Shader '%@': Validation failed %@", _name, messageString); - } -} - - (void) computeMeta { NSMutableDictionary *uniforms = @{}.mutableCopy; @@ -342,15 +339,22 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade _uniformLocations = locations; } -- (void) makeProgram +- (bool) ensureCompiles: (NSError **)error +{ + if (_error == nil) return true; + *error = _error; + return false; +} + +- (bool) makeProgram: (NSError **)error { - if (![self ensureContext]) return; + if (![self ensureContext:error]) return false; - GLuint vertex = compileShader(_name, _vert, GL_VERTEX_SHADER); - if (vertex == -1) return; + GLuint vertex = compileShader(_name, _vert, GL_VERTEX_SHADER, error); + if (vertex == -1) return false; - GLuint fragment = compileShader(_name, _frag, GL_FRAGMENT_SHADER); - if (fragment == -1) return; + GLuint fragment = compileShader(_name, _frag, GL_FRAGMENT_SHADER, error); + if (fragment == -1) return false; program = glCreateProgram(); glAttachShader(program, vertex); @@ -362,9 +366,11 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade if (linkSuccess == GL_FALSE) { GLchar messages[256]; glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); - NSString *messageString = [NSString stringWithUTF8String:messages]; - RCTLogError(@"Shader '%@': Linking failed %@", _name, messageString); - return; + *error = [[NSError alloc] + initWithDomain:[NSString stringWithUTF8String:messages] + code:GLLinkingFailure + userInfo:nil]; + return false; } glUseProgram(program); @@ -384,6 +390,8 @@ GLuint compileShader (NSString* shaderName, NSString* shaderString, GLenum shade 1.0, 1.0 }; glBufferData(GL_ARRAY_BUFFER, sizeof(buf), buf, GL_STATIC_DRAW); + + return true; } diff --git a/ios/RNGL.xcodeproj/project.pbxproj b/ios/RNGL.xcodeproj/project.pbxproj index e9470b6..3624ab0 100644 --- a/ios/RNGL.xcodeproj/project.pbxproj +++ b/ios/RNGL.xcodeproj/project.pbxproj @@ -50,7 +50,7 @@ 346089C61BEFD0A500C90DB5 /* GLRenderData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLRenderData.h; sourceTree = ""; }; 346089C71BEFD0A500C90DB5 /* GLRenderData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLRenderData.m; sourceTree = ""; }; 346089C81BEFD0A500C90DB5 /* GLShader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLShader.h; sourceTree = ""; }; - 346089C91BEFD0A500C90DB5 /* GLShader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLShader.m; sourceTree = ""; }; + 346089C91BEFD0A500C90DB5 /* GLShader.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = GLShader.m; sourceTree = ""; tabWidth = 2; }; 346089CA1BEFD0A500C90DB5 /* GLTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLTexture.h; sourceTree = ""; }; 346089CB1BEFD0A500C90DB5 /* GLTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = GLTexture.m; sourceTree = ""; tabWidth = 2; }; 346089CC1BEFD0A500C90DB5 /* RCTConvert+GLData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+GLData.h"; sourceTree = ""; }; diff --git a/ios/RNGLContext.m b/ios/RNGLContext.m index 689bb50..909aac3 100644 --- a/ios/RNGLContext.m +++ b/ios/RNGLContext.m @@ -45,9 +45,73 @@ RCT_EXPORT_MODULE() return _context; } +- (void)_addShader:(nonnull NSNumber *)id +withConfig:(NSDictionary *)config +withOnCompile:(RCTResponseSenderBlock)onCompile +{ + NSString *frag = [RCTConvert NSString:config[@"frag"]]; + NSString *name = [RCTConvert NSString:config[@"name"]]; + if (!frag) { + RCTLogError(@"Shader '%@': missing frag field", name); + return; + } + GLShader *shader = [[GLShader alloc] initWithContext:_context withName:name withVert:fullViewportVert withFrag:frag]; + NSError *error; + bool success = [shader ensureCompiles:&error]; + if (onCompile) { + if (!success) { + onCompile(@[error.domain]); + } + else { + onCompile(@[[NSNull null], + @{ + @"uniforms": shader.uniformTypes + }]); + } + } + else { + if (!success) { + RCTLogError(@"Shader '%@': %@", name, error.domain); + } + } + _shaders[id] = shader; +} + static NSString* fullViewportVert = @"attribute vec2 position;varying vec2 uv;void main() {gl_Position = vec4(position,0.0,1.0);uv = vec2(0.5, 0.5) * (position+vec2(1.0, 1.0));}"; -RCT_EXPORT_METHOD(addShader:(nonnull NSNumber *)id withConfig:(NSDictionary *)config) { +NSString* glTypeString (int type) { + switch (type) { + case GL_FLOAT: return @"float"; + case GL_FLOAT_VEC2: return @"vec2"; + case GL_FLOAT_VEC3: return @"vec3"; + case GL_FLOAT_VEC4: return @"vec4"; + case GL_INT: return @"int"; + case GL_INT_VEC2: return @"ivec2"; + case GL_INT_VEC3: return @"ivec3"; + case GL_INT_VEC4: return @"ivec4"; + case GL_BOOL: return @"bool"; + case GL_BOOL_VEC2: return @"bvec2"; + case GL_BOOL_VEC3: return @"bvec3"; + case GL_BOOL_VEC4: return @"bvec4"; + case GL_FLOAT_MAT2: return @"mat2"; + case GL_FLOAT_MAT3: return @"mat3"; + case GL_FLOAT_MAT4: return @"mat4"; + case GL_SAMPLER_2D: return @"sampler2D"; + case GL_SAMPLER_CUBE: return @"samplerCube"; + } + return @""; +} +NSDictionary* glTypesString (NSDictionary *types) { + NSMutableDictionary *dict = types.mutableCopy; + for (NSString *key in [dict allKeys]) { + dict[key] = glTypeString([dict[key] intValue]); + } + return dict; +} + +RCT_EXPORT_METHOD(addShader:(nonnull NSNumber *)id + withConfig:(NSDictionary *)config + withOnCompile:(RCTResponseSenderBlock)onCompile) { NSString *frag = [RCTConvert NSString:config[@"frag"]]; NSString *name = [RCTConvert NSString:config[@"name"]]; if (!frag) { @@ -55,9 +119,36 @@ RCT_EXPORT_METHOD(addShader:(nonnull NSNumber *)id withConfig:(NSDictionary *)co return; } GLShader *shader = [[GLShader alloc] initWithContext:_context withName:name withVert:fullViewportVert withFrag:frag]; + NSError *error; + bool success = [shader ensureCompiles:&error]; + if (onCompile) { + if (!success) { + onCompile(@[error.domain]); + } + else { + onCompile(@[[NSNull null], + @{ + @"uniforms": glTypesString(shader.uniformTypes) + }]); + } + } + else { + if (!success) { + RCTLogError(@"Shader '%@': %@", name, error.domain); + } + } _shaders[id] = shader; } +RCT_EXPORT_METHOD(removeShader:(nonnull NSNumber *)id) { + GLShader *shader = [_shaders objectForKey:id]; + if (!shader) { + RCTLogError(@"removeShader(%@): shader does not exist", id); + return; + } + [_shaders removeObjectForKey:id]; +} + @end @implementation RCTBridge (RNGLContext) diff --git a/src/index.js b/src/index.js index 98ec5c7..5fcd139 100644 --- a/src/index.js +++ b/src/index.js @@ -10,8 +10,8 @@ See README install instructions. React.NativeModules.RNGLContext is %s`, RNGLContext); // Hook Shaders to RNGLContext -Shaders.list().map(id => RNGLContext.addShader(id, Shaders.get(id))); -Shaders.on("add", (id, shader) => RNGLContext.addShader(id, shader)); +Shaders.on("add", (id, shader, onCompile) => RNGLContext.addShader(id, shader, onCompile)); +Shaders.on("remove", id => RNGLContext.removeShader(id)); module.exports = { Surface -- 2.26.2