gl-react
Version:
Universal React library, write and compose WebGL shaders, implement complex effects using a descriptive paradigm
599 lines (482 loc) • 18.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.list = exports.default = void 0;
var _invariant = _interopRequireDefault(require("invariant"));
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _glShader = _interopRequireDefault(require("gl-shader"));
var _Bus = _interopRequireDefault(require("./Bus"));
var _Shaders = _interopRequireDefault(require("./Shaders"));
var _Visitors = _interopRequireDefault(require("./Visitors"));
var _webgltextureLoader = require("webgltexture-loader");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const __DEV__ = process.env.NODE_ENV === "development";
const SurfacePropTypes = {
children: _propTypes.default.any.isRequired,
style: _propTypes.default.any,
preload: _propTypes.default.array,
onLoad: _propTypes.default.func,
onLoadError: _propTypes.default.func,
onContextLost: _propTypes.default.func,
onContextRestored: _propTypes.default.func,
visitor: _propTypes.default.object
};
let surfaceId = 0;
const _instances = [];
const list = () => _instances.slice(0);
exports.list = list;
const allSurfaceProps = Object.keys(SurfacePropTypes);
var _default = ({
GLView,
RenderLessElement,
mapRenderableContent,
requestFrame,
cancelFrame
}) => {
var _class;
/**
* **Renders the final tree of [Node](#node) in a WebGL Canvas / OpenGLView /...**
*
* `<Surface>` performs the final GL draws for a given implementation.
*
* `width` and `height` props are required for `gl-react-dom` and `gl-react-headless`, but are not supported for React Native, where the paradigm is to use `style` (and either use flexbox or set a width/height from there).
*
* > Surface is the only component that isn't "universal",
* therefore **Surface is exposed by the platform implementation**
* (`gl-react-dom` / `gl-react-native` / ...),
* unlike the rest of the API exposed through `gl-react`.
* Each platform have its own implementation but most props are shared.
* If you write a gl-react library, you shouldn't use `<Surface>` but only
* let the final user doing it. Therefore your code should remain platform-independant.
*
* @class Surface
* @extends Component
* @prop {any} children - a tree of React Element that renders some [Node](#node) and/or [Bus](#bus).
* @prop {number} [width] **(only for DOM)** - width of the Surface. multiplied by `pixelRatio` for the actual canvas pixel size.
* @prop {number} [height] **(only for DOM)** - height of the Surface. multiplied by `pixelRatio` for the actual canvas pixel size.
* @prop {object} [style] - CSS styles that get passed to the underlying `<canvas/>` or `<View/>`
* @prop {Array<any>} [preload] - an array of things to preload before the Surface start rendering. Help avoiding blinks and providing required textures to render an initial state.
* @prop {function} [onLoad] - a callback called when Surface is ready and just after it rendered.
* @prop {function(error:Error):void} [onLoadError] - a callback called when the Surface was not able to load initially.
* @prop {function} [onContextLost] - a callback called when the Surface context was lost.
* @prop {function} [onContextRestored] - a callback called when the Surface was restored and ready.
* @prop {Visitor} [visitor] - an internal visitor used for logs and tests.
*
* @prop {WebGLContextAttributes} [webglContextAttributes] **(gl-react-dom only)** a optional set of attributes to init WebGL with.
* @prop {number} [pixelRatio=window.devicePixelRatio] **(gl-react-dom only)** allows to override the pixelRatio. (default `devicePixelRatio`)
*
* @example
*
* <Surface width={300} height={200}>
* <Node shader={shaders.helloGL} />
* </Surface>
*
* @example
*
* <Surface width={200} height={100}>
* <HelloGL />
* </Surface>
*
* @example
*
* <Surface width={200} height={100}>
* <Blur factor={2}>
* <Negative>
* https://i.imgur.com/wxqlQkh.jpg
* </Negative>
* </Blur>
* </Surface>
*/
return _class = class Surface extends _react.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "id", ++surfaceId);
_defineProperty(this, "gl", void 0);
_defineProperty(this, "buffer", void 0);
_defineProperty(this, "loaderResolver", void 0);
_defineProperty(this, "glView", void 0);
_defineProperty(this, "root", void 0);
_defineProperty(this, "shaders", {});
_defineProperty(this, "_preparingGL", []);
_defineProperty(this, "_needsRedraw", false);
_defineProperty(this, "state", {
ready: false,
rebootId: 0,
debug: false
});
_defineProperty(this, "RenderLessElement", RenderLessElement);
_defineProperty(this, "mapRenderableContent", mapRenderableContent);
_defineProperty(this, "redraw", () => {
this._needsRedraw = true;
});
_defineProperty(this, "flush", () => {
this._draw();
});
_defineProperty(this, "_emptyTexture", void 0);
_defineProperty(this, "_onContextCreate", gl => {
const onSuccess = () => {
this.setState({
ready: true
}, () => {
try {
this._handleLoad();
} catch (e) {
this._handleError(e);
}
});
};
this._prepareGL(gl, onSuccess, this._handleError);
});
_defineProperty(this, "_onContextFailure", e => {
this._handleError(e);
});
_defineProperty(this, "_onContextLost", () => {
if (this.props.onContextLost) this.props.onContextLost();
this._stopLoop();
this._destroyGL();
if (this.root) this.root._onContextLost();
});
_defineProperty(this, "_onContextRestored", gl => {
if (this.root) this.root._onContextRestored(gl);
this._prepareGL(gl, this._handleRestoredSuccess, this._handleRestoredFailure);
});
_defineProperty(this, "_onRef", ref => {
this.glView = ref;
});
_defineProperty(this, "_handleError", e => {
const {
onLoadError
} = this.props;
if (onLoadError) onLoadError(e);else {
console.error(e);
}
});
_defineProperty(this, "_handleRestoredFailure", () => {// there is nothing we can do. it's a dead end.
});
_defineProperty(this, "_handleRestoredSuccess", () => {
this.redraw();
this.flush();
this._startLoop();
if (this.props.onContextRestored) this.props.onContextRestored();
});
_defineProperty(this, "_handleLoad", () => {
if (!this.root) {
console.warn(this.getGLName() + " children does not contain any discoverable Node");
}
const {
onLoad
} = this.props;
this.redraw();
this.flush();
this._startLoop();
if (onLoad) onLoad();
});
_defineProperty(this, "_loopRaf", void 0);
}
getChildContext() {
return {
glParent: this,
glSurface: this,
glSizable: this
};
}
componentDidMount() {
_instances.push(this);
this.getVisitors().forEach(v => v.onSurfaceMount(this));
}
componentWillUnmount() {
this._stopLoop();
this._destroyGL();
const i = _instances.indexOf(this);
if (i !== -1) _instances.splice(i, 1);
this.getVisitors().forEach(v => v.onSurfaceUnmount(this));
}
componentDidUpdate() {
this.redraw();
}
render() {
const {
props,
state: {
ready,
rebootId,
debug
}
} = this;
const {
children,
style
} = props; // We allow to pass-in all props we don't know so you can hook to DOM events.
const rest = {};
Object.keys(props).forEach(key => {
if (allSurfaceProps.indexOf(key) === -1) {
rest[key] = props[key];
}
});
return /*#__PURE__*/_react.default.createElement(GLView, _extends({
key: rebootId,
debug: debug,
ref: this._onRef,
onContextCreate: this._onContextCreate,
onContextFailure: this._onContextFailure,
onContextLost: this._onContextLost,
onContextRestored: this._onContextRestored,
style: style
}, rest), ready ? children : null);
}
rebootForDebug() {
// FIXME: there is a bug somewhere that breaks rendering if this is called at startup time.
this._stopLoop();
this._destroyGL();
this.setState(({
rebootId
}) => ({
rebootId: rebootId + 1,
ready: false,
debug: true
}));
}
getVisitors() {
return _Visitors.default.get().concat(this.props.visitor || []);
}
getGLSize() {
const {
gl
} = this;
return [gl ? gl.drawingBufferWidth : 0, gl ? gl.drawingBufferHeight : 0];
}
getGLName() {
return `Surface#${this.id}`;
}
getGLShortName() {
return "Surface";
}
/**
* see https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL
* @param {string} mimeType (optional) the image MimeType
* @param {number} quality (optional) the image quality
* @memberof Surface
* @instance
*/
captureAsDataURL(...args) {
const {
glView
} = this;
(0, _invariant.default)(glView, "GLView is mounted");
(0, _invariant.default)(glView.captureAsDataURL, "captureAsDataURL is not defined in %s", GLView.displayName || GLView.name);
return glView.captureAsDataURL(...args);
}
/**
* see https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
* @param {string} mimeType (optional) the image MimeType
* @param {number} quality (optional) the image quality
* @memberof Surface
* @instance
*/
captureAsBlob(...args) {
const {
glView
} = this;
(0, _invariant.default)(glView, "GLView is mounted");
(0, _invariant.default)(glView.captureAsBlob, "captureAsBlob is not defined in %s", GLView.displayName || GLView.name);
return glView.captureAsBlob(...args);
}
/**
* capture the root Node pixels. Make sure you have set `preserveDrawingBuffer: true` in `webglContextAttributes` prop.
* @memberof Surface
* @instance
*/
capture(x, y, w, h) {
(0, _invariant.default)(this.root, "Surface#capture: surface is not yet ready or don't have any root Node");
return this.root.capture(x, y, w, h);
}
/**
* Schedule a redraw of the Surface.
* @memberof Surface
* @instance
* @function
*/
glIsAvailable() {
return !!this.gl;
}
getEmptyTexture() {
let {
gl,
_emptyTexture
} = this;
(0, _invariant.default)(gl, "getEmptyTexture called while gl was not defined");
if (!_emptyTexture) {
this._emptyTexture = _emptyTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, _emptyTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
}
return _emptyTexture;
}
_destroyGL() {
const {
gl
} = this;
if (gl) {
this.gl = null;
if (this._emptyTexture) {
gl.deleteTexture(this._emptyTexture);
this._emptyTexture = null;
}
if (this.loaderResolver) {
this.loaderResolver.dispose();
}
for (let k in this.shaders) {
this.shaders[k].dispose();
}
this.shaders = {};
gl.deleteBuffer(this.buffer);
this.getVisitors().map(v => v.onSurfaceGLContextChange(this, null));
}
}
_prepareGL(gl, onSuccess, onError) {
this.gl = gl;
this.getVisitors().map(v => v.onSurfaceGLContextChange(this, gl));
this.loaderResolver = new _webgltextureLoader.LoaderResolver(gl);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 4, 4, -1]), // see a-big-triangle
gl.STATIC_DRAW);
this.buffer = buffer;
const {
preload
} = this.props;
const all = [];
(preload || []).forEach(raw => {
if (!raw) {
console.warn("Can't preload value", raw);
return;
}
const {
loader,
input
} = this._resolveTextureLoader(raw);
if (!loader) {
console.warn("Can't preload input", raw, input);
return;
}
const loadedAlready = loader.get(input);
if (loadedAlready) return;
all.push(loader.load(input));
});
this._preparingGL = all;
if (all.length > 0) {
Promise.all(all).then(onSuccess, onError); // FIXME make sure this never finish if _prepareGL is called again.
} else {
onSuccess();
}
}
_addGLNodeChild(node) {
(0, _invariant.default)(!this.root, "Surface can only contains a single root. Got: %s", this.root && this.root.getGLName());
this.root = node;
node._addDependent(this);
this.redraw();
}
_removeGLNodeChild(node) {
this.root = null;
this.redraw();
}
_resolveTextureLoader(raw) {
let input = raw;
let loader = this.loaderResolver && this.loaderResolver.resolve(input);
return {
loader,
input
};
}
_makeShader({
frag,
vert
}, name) {
const {
gl
} = this;
(0, _invariant.default)(gl, "gl is not available");
const shader = (0, _glShader.default)(gl, vert, frag);
for (let key in shader.attributes) {
shader.attributes[key].pointer();
}
return shader;
}
_getShader(shaderId) {
const {
shaders
} = this;
return shaders[shaderId.id] || (shaders[shaderId.id] = this._makeShader(_Shaders.default.get(shaderId), _Shaders.default.getName(shaderId)));
}
_bindRootNode() {
const {
gl
} = this;
(0, _invariant.default)(gl, "gl context not available");
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const [width, height] = this.getGLSize();
gl.viewport(0, 0, width, height);
}
_startLoop() {
cancelFrame(this._loopRaf);
const loop = () => {
this._loopRaf = requestFrame(loop);
if (this._needsRedraw) this._draw();
};
this._loopRaf = requestFrame(loop);
}
_stopLoop() {
cancelFrame(this._loopRaf);
}
_draw() {
const {
gl,
root,
glView
} = this;
(0, _invariant.default)(glView, "GLView is mounted");
const visitors = this.getVisitors();
if (!gl || !root || !this._needsRedraw) {
visitors.forEach(v => v.onSurfaceDrawSkipped(this));
return;
}
this._needsRedraw = false;
visitors.forEach(v => v.onSurfaceDrawStart(this));
if (glView.beforeDraw) glView.beforeDraw(gl);
try {
root._draw();
} catch (e) {
let silent = false;
visitors.forEach(v => {
silent = v.onSurfaceDrawError(e) || silent;
});
if (!silent) {
if (__DEV__ && glView.debugError && e.longMessage
/* duck typing an "interesting" GLError (from lib gl-shader) */
) {
glView.debugError(e);
} else {
console.warn(e);
throw e;
}
}
return;
}
if (glView.afterDraw) glView.afterDraw(gl);
visitors.forEach(v => v.onSurfaceDrawEnd(this));
}
}, _defineProperty(_class, "propTypes", SurfacePropTypes), _defineProperty(_class, "childContextTypes", {
glSurface: _propTypes.default.object.isRequired,
glParent: _propTypes.default.object.isRequired,
glSizable: _propTypes.default.object.isRequired
}), _class;
};
exports.default = _default;
//# sourceMappingURL=createSurface.js.map