UNPKG

@flyskywhy/react-native-gcanvas

Version:

A C++ native canvas 2D/WebGL component based on gpu opengl glsl shader GCanvas

271 lines (223 loc) 8.79 kB
import GLmethod from '../context/webgl/GLmethod'; const isReactNativeIOS = () => { return GBridge.Platform.OS === 'ios'; }; const isReactNativeAndroid = () => { return GBridge.Platform.OS === 'android'; }; let isDebugging = false; let isComboDisabled = false; const logCommand = (function() { const methodQuery = []; Object.keys(GLmethod).forEach(key => { methodQuery[GLmethod[key]] = key; }); const queryMethod = (id) => { return methodQuery[parseInt(id)] || 'NotFoundMethod'; }; const logCommand = (id, cmds) => { const mId = cmds.split(',')[0]; const mName = queryMethod(mId); console.log(`=== callNative - componentId:${id}; method: ${mName}; cmds: ${cmds}`); }; return logCommand; })(); function joinArray(arr, sep) { let res = ''; for (let i = 0; i < arr.length; i++) { if (i !== 0) { res += sep; } res += arr[i]; } return res; } const commandsCache = {}; const viewport2commands = {}; const GBridge = { GCanvasModule: null, Platform: null, callEnable: (ref, configArray) => { commandsCache[ref] = []; return GBridge.GCanvasModule.enable({ componentId: ref, config: configArray }); }, callEnableDebug: () => { isDebugging = true; }, callEnableDisableCombo: () => { isComboDisabled = true; }, callDisable: (ref) => { GBridge.GCanvasModule.disable(ref); }, callSetContextType: function(componentId, context_type) { GBridge.GCanvasModule.setContextType(context_type, componentId); }, callSetDevicePixelRatio: function(componentId, ratio) { GBridge.GCanvasModule.setDevicePixelRatio(componentId, ratio); }, callResetGlViewport: function(componentId) { GBridge.GCanvasModule.resetGlViewport(componentId); }, callToDataURL: function(componentId, mimeType, quality) { return GBridge.GCanvasModule.toDataURL(componentId, mimeType, quality); }, callReset: function(componentId) { GBridge.GCanvasModule.resetComponent && GBridge.GCanvasModule.resetComponent(componentId); }, callViewport: (componentId, viewportArgs) => { viewport2commands[componentId] = viewportArgs; }, callNative: function( componentId, cmdArgs, isCacheCmd = false, contextType = 'webgl', methodType = 'sync', optionType = 'execWithoutDisplay', ) { if (contextType === '2d') { if (isDebugging) { console.log('>>> exec commands: ' + cmdArgs); } if (methodType === 'sync') { /* call native type description +---------------------------------------------------+ | 32 bit integer | +---------------------------------------------------+ | 31~30 | 29 | (28~0) | | ContextType | MethodType | OptionType | +---------------------------------------------------+ | 1-webgl | 0-async RN | 0-execWithoutDisplay | | 0-2d | 1-sync RN | 1-execWithDisplay | +---------------------------------------------------+ */ // MethodType async RN above means call native async and resume JS // // MethodType sync RN above means call native untill native return then resume JS: // on Android, will waitUtilTimeout() cmd exec indirectly by QueueProc(), then resume JS // on iOS, if with ContextType 2d and OptionType execWithDisplay, will waitUtilTimeout() cmd exec indirectly // by drawInRect(), then resume JS, but actually, since cause low JS FPS as described in getImageData() // of `context/2d/RenderingContext.js`, seems `2d + sync RN + execWithDisplay` situation will not happen; // if with ContextType webgl, ref to below // // OptionType execWithDisplay in 2d above means: // on Android, indirectly exec cmd to generate new graphics then eglSwapBuffers() to display new graphics // on screen by QueueProc() // on iOS, setNeedsDisplay() to display old graphics on screen, and thus will impliedly invoke drawInRect() // to indirectly exec cmd to generate new graphics (can then be getImageData() even not be displayed) // // OptionType execWithDisplay in webgl above means: // on Android, indirectly exec cmd to generate new graphics then eglSwapBuffers() to display new graphics // on screen by QueueProc() // on iOS, directly exec cmd to generate new graphics then setNeedsDisplay() to display new graphics on // screen, and will not impliedly invoke drawInRect() since `component.glkview.delegate = nil;` // // OptionType execWithoutDisplay in 2d or webgl above means: // on Android, just indirectly exec cmd by QueueProc() to generate new graphics // on iOS, just directly exec cmd to generate new graphics let type = optionType === 'execWithDisplay' ? 0x20000001 : 0x20000000; // extendCallNative() is a sync RN method, if want get exec cmd result, must use it const result = GBridge.GCanvasModule.extendCallNative({ contextId: componentId, args: cmdArgs, type, }); const res = result && result.result; if (isDebugging) { console.log('>>> result: ' + res); } return res; } else { let type = optionType === 'execWithDisplay' ? 0x00000001 : 0x00000000; // render() is an async RN method GBridge.GCanvasModule.render(componentId, cmdArgs, type); } } else { // 'webgl' if (isDebugging) { logCommand(componentId, cmdArgs); } if (cmdArgs) { commandsCache[componentId].push(cmdArgs); } if (!isCacheCmd || isComboDisabled) { let commands = joinArray(commandsCache[componentId], ';'); commandsCache[componentId] = []; if (isReactNativeIOS()) { // if no hack additional viewportArgs beyond the commands, glViewport() in C++ will has no // effect in: // 1. `webgl_demo/cube.js` which invoke gl.viewport() every 16ms , and _disableAutoSwap is true // 2. https://github.com/flyskywhy/snakeRN which invoke gl.viewport() twice within pixi.js // damn iOS const viewportArgs = viewport2commands[componentId]; if (viewportArgs) { commands = viewportArgs + ';' + commands; } } if (isDebugging) { console.log('>>> exec commands: ' + commands); } if (methodType === 'sync') { // most gl ops should be sync, otherwise may cause issue like // https://stackoverflow.com/questions/9008291/webgls-getattriblocation-oddly-returns-1 let type = optionType === 'execWithDisplay' ? 0x60000001 : 0x60000000; const result = GBridge.GCanvasModule.extendCallNative({ contextId: componentId, args: commands, type, }); const res = result && result.result; if (isDebugging) { console.log('>>> result: ' + res); } return res; } else { let type = optionType === 'execWithDisplay' ? 0x40000001 : 0x40000000; GBridge.GCanvasModule.render(componentId, commands, type); } } } }, texImage2D(componentId, ...args) { if (isDebugging) { console.log('>>> texImage2D: ' + componentId, ...args); } if (isReactNativeAndroid()) { const [target, level, internalformat, format, type, image] = args; GBridge.GCanvasModule.texImage2D(componentId, target, level, internalformat, format, type, image.src); } }, texSubImage2D(componentId, ...args) { if (isDebugging) { console.log('>>> texSubImage2D: ' + componentId, ...args); } if (isReactNativeAndroid()) { const [target, level, xoffset, yoffset, format, type, image] = args; GBridge.GCanvasModule.texSubImage2D(componentId, target, level, xoffset, yoffset, format, type, image.src); } }, drawCanvas2Canvas(map) { if (isDebugging) { console.log('>>> drawCanvas2Canvas: ', map); } GBridge.GCanvasModule.drawCanvas2Canvas(map); }, bindImageTexture(componentId, src, imageId) { if (isDebugging) { console.log('>>> bindImageTexture: ' + componentId, src, imageId); } GBridge.GCanvasModule.bindImageTexture([src, imageId], componentId, function(e) { }); }, preloadImage([url, id], callback) { GBridge.GCanvasModule.preLoadImage([url, id], function(image) { image.url = url; image.id = id; callback(image); }); } }; export default GBridge;