Skip to content

Add WebGL2 support #6035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/core/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export const P2D = 'p2d';
* @final
*/
export const WEBGL = 'webgl';
/**
* One of the two possible values of a WebGL canvas (either WEBGL or WEBGL2),
* which can be used to determine what capabilities the rendering environment
* has.
* @property {String} WEBGL2
* @final
*/
export const WEBGL2 = 'webgl2';

// ENVIRONMENT
/**
Expand Down
58 changes: 58 additions & 0 deletions src/core/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,64 @@ p5.prototype.noCursor = function() {
this._curElement.elt.style.cursor = 'none';
};

/**
* If the sketch was created in WebGL mode, then `weglVersion` will indicate
* which version of WebGL it is using. It will try to create a WebGL2 canvas
* unless you have requested WebGL1 via `setAttributes({ version: 1 })`, and
* will fall back to WebGL1 if WebGL2 is not available.
*
* `webglVersion` will always be either `WEBGL2`, `WEBGL`, or `P2D` if not in
* WebGL mode.
* @property {String} webglVersion
* @readOnly
* @example
* <div><code>
* let myFont;
* function preload() {
* myFont = loadFont('assets/inconsolata.otf');
* }
* function setup() {
* createCanvas(100, 50, WEBGL);
* // Uncomment the following line to see the behavior change in WebGL 1:
* // setAttributes({ version: 1 })
*
* const graphic = createGraphics(30, 30);
* graphic.background(255);
* graphic.noStroke();
* graphic.fill(200);
* graphic.rect(0, 0, graphic.width/2, graphic.height/2);
* graphic.rect(
* graphic.width/2, graphic.height/2,
* graphic.width/2, graphic.height/2
* );
*
* noStroke();
* translate(-width/2, -height/2);
* textureWrap(REPEAT);
* texture(graphic);
* beginShape(QUADS);
* vertex(0, 0, 0, 0, 0);
* vertex(width, 0, 0, width, 0);
* vertex(width, height, 0, width, height);
* vertex(0, height, 0, 0, height);
* endShape();
*
* textFont(myFont);
* textAlign(CENTER, CENTER);
* textSize(30);
* fill(0);
* text('WebGL' + (webglVersion === WEBGL2 ? 2 : 1), 0, 0, width, height);
* }
* </code></div>
*
* @alt
* This example writes either 'WebGL1' or 'WebGL2' on the canvas, depending on
* the capabilities of the device it runs on. If it says WebGL2, the background
* will be checkered. Otherwise, the background will have just one checker tile
* with colors stretched to the edges of the canvas.
*/
p5.prototype.webglVersion = C.P2D;

/**
* System variable that stores the width of the screen display according to The
* default <a href="#/p5/pixelDensity">pixelDensity</a>. This is used to run a
Expand Down
12 changes: 11 additions & 1 deletion src/core/rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ const defaultClass = 'p5Canvas';
* is set to `WEBGL`), the origin is positioned at the center of the canvas.
* See [this issue](https://github.com/processing/p5.js/issues/1545) for more information.
*
* A WebGL canvas will use a WebGL2 context if it is supported by the browser.
* Check the <a href="#/p5/webglVersion">webglVersion</a> property to check what
* version is being used, or call <a href="#/p5/setAttributes">setAttributes({ version: 1 })</a>
* to create a WebGL1 context.
*
* The system variables width and height are set by the parameters passed to this
* function. If <a href="#/p5/createCanvas">createCanvas()</a> is not used, the
* window will be given a default size of 100×100 pixels.
Expand Down Expand Up @@ -202,10 +207,15 @@ p5.prototype.noCanvas = function() {
};

/**
* Creates and returns a new p5.Renderer object. Use this class if you need
* Creates and returns a new p5.Graphics object. Use this class if you need
* to draw into an off-screen graphics buffer. The two parameters define the
* width and height in pixels.
*
* A WebGL p5.Graphics will use a WebGL2 context if it is supported by the browser.
* Check the <a href="#/p5/webglVersion">pg.webglVersion</a> property of the renderer
* to check what version is being used, or call <a href="#/p5/setAttributes">pg.setAttributes({ version: 1 })</a>
* to create a WebGL1 context.
*
* @method createGraphics
* @param {Number} w width of the offscreen graphics buffer
* @param {Number} h height of the offscreen graphics buffer
Expand Down
10 changes: 8 additions & 2 deletions src/webgl/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,10 @@ p5.RendererGL.prototype._applyBlendMode = function() {
break;
case constants.DARKEST:
if (this.blendExt) {
gl.blendEquationSeparate(this.blendExt.MIN_EXT, gl.FUNC_ADD);
gl.blendEquationSeparate(
this.blendExt.MIN || this.blendExt.MIN_EXT,
gl.FUNC_ADD
);
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
} else {
console.warn(
Expand All @@ -1133,7 +1136,10 @@ p5.RendererGL.prototype._applyBlendMode = function() {
break;
case constants.LIGHTEST:
if (this.blendExt) {
gl.blendEquationSeparate(this.blendExt.MAX_EXT, gl.FUNC_ADD);
gl.blendEquationSeparate(
this.blendExt.MAX || this.blendExt.MAX_EXT,
gl.FUNC_ADD
);
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
} else {
console.warn(
Expand Down
6 changes: 5 additions & 1 deletion src/webgl/p5.RendererGL.Retained.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import p5 from '../core/main';
import './p5.RendererGL';
import './p5.RenderBuffer';
import * as constants from '../core/constants';

let hashCount = 0;
/**
Expand Down Expand Up @@ -199,7 +200,10 @@ p5.RendererGL.prototype._drawElements = function(drawMode, gId) {
if (buffers.indexBuffer) {
// If this model is using a Uint32Array we need to ensure the
// OES_element_index_uint WebGL extension is enabled.
if (buffers.indexBufferType === gl.UNSIGNED_INT) {
if (
this._pInst.webglVersion !== constants.WEBGL2 &&
buffers.indexBufferType === gl.UNSIGNED_INT
) {
if (!gl.getExtension('OES_element_index_uint')) {
throw new Error(
'Unable to render a 3d model with > 65535 triangles. Your web browser does not support the WebGL Extension OES_element_index_uint.'
Expand Down
74 changes: 64 additions & 10 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ const lightingShader = readFileSync(
join(__dirname, '/shaders/lighting.glsl'),
'utf-8'
);
const webgl2CompatibilityShader = readFileSync(
join(__dirname, '/shaders/webgl2Compatibility.glsl'),
'utf-8'
);

const defaultShaders = {
immediateVert: readFileSync(
Expand Down Expand Up @@ -61,8 +65,10 @@ const defaultShaders = {
phongFrag:
lightingShader +
readFileSync(join(__dirname, '/shaders/phong.frag'), 'utf-8'),
fontVert: readFileSync(join(__dirname, '/shaders/font.vert'), 'utf-8'),
fontFrag: readFileSync(join(__dirname, '/shaders/font.frag'), 'utf-8'),
fontVert: webgl2CompatibilityShader +
readFileSync(join(__dirname, '/shaders/font.vert'), 'utf-8'),
fontFrag: webgl2CompatibilityShader +
readFileSync(join(__dirname, '/shaders/font.frag'), 'utf-8'),
lineVert:
lineDefs + readFileSync(join(__dirname, '/shaders/line.vert'), 'utf-8'),
lineFrag:
Expand Down Expand Up @@ -125,7 +131,11 @@ p5.RendererGL = function(elt, pInst, isMainCanvas, attr) {

this.curBlendMode = constants.BLEND;
this._cachedBlendMode = undefined;
this.blendExt = this.GL.getExtension('EXT_blend_minmax');
if (this.webglVersion === constants.WEBGL2) {
this.blendExt = this.GL;
} else {
this.blendExt = this.GL.getExtension('EXT_blend_minmax');
}
this._isBlending = false;

this._useSpecularMaterial = false;
Expand Down Expand Up @@ -277,7 +287,8 @@ p5.RendererGL.prototype._setAttributeDefaults = function(pInst) {
antialias: applyAA,
premultipliedAlpha: true,
preserveDrawingBuffer: true,
perPixelLighting: true
perPixelLighting: true,
version: 2
};
if (pInst._glAttributes === null) {
pInst._glAttributes = defaults;
Expand All @@ -288,9 +299,24 @@ p5.RendererGL.prototype._setAttributeDefaults = function(pInst) {
};

p5.RendererGL.prototype._initContext = function() {
this.drawingContext =
this.canvas.getContext('webgl', this._pInst._glAttributes) ||
this.canvas.getContext('experimental-webgl', this._pInst._glAttributes);
if (this._pInst._glAttributes.version !== 1) {
// Unless WebGL1 is explicitly asked for, try to create a WebGL2 context
this.drawingContext =
this.canvas.getContext('webgl2', this._pInst._glAttributes);
}
this.webglVersion = this.drawingContext ? constants.WEBGL2 : constants.WEBGL;
if (this._isMainCanvas) {
// If this is the main canvas, make sure the global `webglVersion` is set
this._pInst._setProperty('webglVersion', this.webglVersion);
}
if (!this.drawingContext) {
// If we were unable to create a WebGL2 context (either because it was
// disabled via `setAttributes({ version: 1 })` or because the device
// doesn't support it), fall back to a WebGL1 context
this.drawingContext =
this.canvas.getContext('webgl', this._pInst._glAttributes) ||
this.canvas.getContext('experimental-webgl', this._pInst._glAttributes);
}
if (this.drawingContext === null) {
throw new Error('Error creating webgl context');
} else {
Expand Down Expand Up @@ -402,6 +428,11 @@ p5.RendererGL.prototype._resetContext = function(options, callback) {
* lighting shader otherwise per-vertex lighting is used.
* default is true.
*
* version - either 1 or 2, to specify which WebGL version to ask for. By
* default, WebGL 2 will be requested. If WebGL2 is not available, it will
* fall back to WebGL 1. You can check what version is used with by looking at
* the global `webglVersion` property.
*
* @method setAttributes
* @for p5
* @param {String} key Name of attribute
Expand Down Expand Up @@ -1251,16 +1282,39 @@ p5.RendererGL.prototype._getLineShader = function() {

p5.RendererGL.prototype._getFontShader = function() {
if (!this._defaultFontShader) {
this.GL.getExtension('OES_standard_derivatives');
if (this.webglVersion === constants.WEBGL) {
this.GL.getExtension('OES_standard_derivatives');
}
this._defaultFontShader = new p5.Shader(
this,
defaultShaders.fontVert,
defaultShaders.fontFrag
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.fontVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious if this float precision is always guaranteed to work well on a device that supports WebGL2. Not sure but it is irrelevant because this design will make adjustments to precision based on the device easier in the future.

defaultShaders.fontFrag
);
}
return this._defaultFontShader;
};

p5.RendererGL.prototype._webGL2CompatibilityPrefix = function(
shaderType,
floatPrecision
) {
let code = '';
if (this.webglVersion === constants.WEBGL2) {
code += '#version 300 es\n#define WEBGL2\n';
}
if (shaderType === 'vert') {
code += '#define VERTEX_SHADER\n';
} else if (shaderType === 'frag') {
code += '#define FRAGMENT_SHADER\n';
}
if (floatPrecision) {
code += `precision ${floatPrecision} float;\n`;
}
return code;
};

p5.RendererGL.prototype._getEmptyTexture = function() {
if (!this._emptyTexture) {
// a plain white texture RGBA, full alpha, single pixel.
Expand Down
25 changes: 20 additions & 5 deletions src/webgl/p5.Texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ p5.Texture = function(renderer, obj, settings) {

settings = settings || {};

if (settings.dataType === gl.FLOAT) {
if (
settings.dataType === gl.FLOAT &&
this._renderer._pInst.webglVersion !== constants.WEBGL2
) {
const ext = gl.getExtension('OES_texture_float');
if (!ext) {
console.log(
Expand Down Expand Up @@ -351,7 +354,10 @@ p5.Texture.prototype.setWrapMode = function(wrapX, wrapY) {
const heightPowerOfTwo = isPowerOfTwo(wrapHeight);

if (wrapX === constants.REPEAT) {
if (widthPowerOfTwo && heightPowerOfTwo) {
if (
this._renderer.webglVersion === constants.WEBGL2 ||
(widthPowerOfTwo && heightPowerOfTwo)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth rolling this into a simple function that returns a boolean just to improve readability. I find the nested conditionals and multiline conditions to be a challenge to read in this section (and many other sections of the GL code). Not a new problem with this PR by any means but one worth keeping in mind.

) {
this.glWrapS = gl.REPEAT;
} else {
console.warn(
Expand All @@ -360,7 +366,10 @@ p5.Texture.prototype.setWrapMode = function(wrapX, wrapY) {
this.glWrapS = gl.CLAMP_TO_EDGE;
}
} else if (wrapX === constants.MIRROR) {
if (widthPowerOfTwo && heightPowerOfTwo) {
if (
this._renderer.webglVersion === constants.WEBGL2 ||
(widthPowerOfTwo && heightPowerOfTwo)
) {
this.glWrapS = gl.MIRRORED_REPEAT;
} else {
console.warn(
Expand All @@ -374,7 +383,10 @@ p5.Texture.prototype.setWrapMode = function(wrapX, wrapY) {
}

if (wrapY === constants.REPEAT) {
if (widthPowerOfTwo && heightPowerOfTwo) {
if (
this._renderer.webglVersion === constants.WEBGL2 ||
(widthPowerOfTwo && heightPowerOfTwo)
) {
this.glWrapT = gl.REPEAT;
} else {
console.warn(
Expand All @@ -383,7 +395,10 @@ p5.Texture.prototype.setWrapMode = function(wrapX, wrapY) {
this.glWrapT = gl.CLAMP_TO_EDGE;
}
} else if (wrapY === constants.MIRROR) {
if (widthPowerOfTwo && heightPowerOfTwo) {
if (
this._renderer.webglVersion === constants.WEBGL2 ||
(widthPowerOfTwo && heightPowerOfTwo)
) {
this.glWrapT = gl.MIRRORED_REPEAT;
} else {
console.warn(
Expand Down
17 changes: 9 additions & 8 deletions src/webgl/shaders/font.frag
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#ifndef WEBGL2
#extension GL_OES_standard_derivatives : enable
precision mediump float;
#endif

#if 0
// simulate integer math using floats
Expand Down Expand Up @@ -35,11 +36,11 @@ uniform ivec2 uGridOffset;
uniform ivec2 uGridSize;
uniform vec4 uMaterialColor;

varying vec2 vTexCoord;
IN vec2 vTexCoord;

// some helper functions
int round(float v) { return ifloor(v + 0.5); }
ivec2 round(vec2 v) { return ifloor(v + 0.5); }
int ROUND(float v) { return ifloor(v + 0.5); }
ivec2 ROUND(vec2 v) { return ifloor(v + 0.5); }
float saturate(float v) { return clamp(v, 0.0, 1.0); }
vec2 saturate(vec2 v) { return clamp(v, 0.0, 1.0); }

Expand All @@ -53,7 +54,7 @@ ivec2 mul(vec2 v1, ivec2 v2) {

// unpack a 16-bit integer from a float vec2
int getInt16(vec2 v) {
ivec2 iv = round(v * 255.0);
ivec2 iv = ROUND(v * 255.0);
return iv.x * INT(128) + iv.y;
}

Expand All @@ -72,7 +73,7 @@ vec4 getTexel(sampler2D sampler, int pos, ivec2 size) {
int y = ifloor(pos / width);
int x = pos - y * width; // pos % width

return texture2D(sampler, (vec2(x, y) + 0.5) / vec2(size));
return TEXTURE(sampler, (vec2(x, y) + 0.5) / vec2(size));
}

void calulateCrossings(vec2 p0, vec2 p1, vec2 p2, out vec2 C1, out vec2 C2) {
Expand Down Expand Up @@ -210,6 +211,6 @@ void main() {
float distance = max(weight.x + weight.y, minDistance); // manhattan approx.
float antialias = abs(dot(coverage, weight) / distance);
float cover = min(abs(coverage.x), abs(coverage.y));
gl_FragColor = vec4(uMaterialColor.rgb, 1.) * uMaterialColor.a;
gl_FragColor *= saturate(max(antialias, cover));
OUT_COLOR = vec4(uMaterialColor.rgb, 1.) * uMaterialColor.a;
OUT_COLOR *= saturate(max(antialias, cover));
}
Loading