Skip to content
Open
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
21 changes: 21 additions & 0 deletions docs/components/renderer.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ It also configures presentation attributes when entering WebVR/WebXR.
| Property | Description | Default Value |
|-------------------------|---------------------------------------------------------------------------------|---------------|
| antialias | Whether to perform antialiasing. If `auto`, antialiasing is disabled on mobile. | auto |
| backend | Backend used by `THREE.WebGPURenderer`: auto (WebGPU if available, WebGL 2 otherwise) or webgl. Ignored with the default build using `THREE.WebGLRenderer`. | auto |
| colorManagement | Whether to use a color-managed linear workflow. | true |
| highRefreshRate | Increases frame rate from the default (for browsers that support control of frame rate). | false |
| foveationLevel | Amount of foveation used in VR to improve perf, from 0 (min) to 1 (max). | 1 |
Expand All @@ -36,6 +37,7 @@ It also configures presentation attributes when entering WebVR/WebXR.
| maxCanvasHeight | Maximum canvas height. Behaves the same as maxCanvasWidth. | -1 |
| multiviewStereo | Enables the use of the OCULUS_multiview extension. | false |
| logarithmicDepthBuffer | Whether to use a logarithmic depth buffer. | auto |
| reversedDepthBuffer | Whether to use a reversed depth buffer. | false |
| precision | Fragment shader [precision][precision] : low, medium or high. | high |
| alpha | Whether the canvas should contain an alpha buffer. | true |
| stencil | Whether the canvas should contain a stencil buffer. | false |
Expand All @@ -50,6 +52,16 @@ It also configures presentation attributes when entering WebVR/WebXR.
When enabled, smooths jagged edges on curved lines and diagonals at moderate performance cost.
By default, antialiasing is disabled on mobile devices.

### backend

Only applies when A-Frame runs with a three.js build that exposes `THREE.WebGPURenderer`
(for example by aliasing `three` to `three.webgpu.js` in an importmap, see the source of the
[webgpu example](https://aframe.io/aframe/examples/showcase/webgpu/)). With the default
`auto` value, the WebGPU backend is used when available, falling back to WebGL 2 otherwise.
Set `renderer="backend: webgl"` to force the WebGL 2 backend of `THREE.WebGPURenderer`
(the `forceWebGL` option in three.js). This property is ignored with the default A-Frame
build that uses `THREE.WebGLRenderer`.

### colorManagement

Color management provides more accurate rendering and reduces the likelihood that scenes
Expand Down Expand Up @@ -100,6 +112,13 @@ Some more background on how A-Frame sorts objects for rendering can be found [he
A logarithmic depth buffer may provide better sorting and rendering in scenes containing very
large differences of scale and distance.

### reversedDepthBuffer

A reversed depth buffer distributes depth precision more evenly and can eliminate z-fighting
in scenes with large differences of scale and distance, with better performance than a
logarithmic depth buffer. With `THREE.WebGLRenderer` this requires the `EXT_clip_control`
WebGL extension. It is also supported by `THREE.WebGPURenderer` on both backends.

### Precision

Set precision in fragment shaders. Main use is to address issues in older hardware / drivers. Adreno 300 series GPU based phones are [particularly problematic](https://github.com/mrdoob/three.js/issues/14137). You can set to `mediump` as a workaround. It will improve performance, in mobile in particular but be aware that might cause visual artifacts in shaders / textures.
Expand All @@ -110,4 +129,6 @@ Whether the canvas should contain an alpha buffer. If this is true the renderer

### multiviewStereo

When using `THREE.WebGPURenderer`, this property is passed as the `multiview` option of the renderer.

Performance improvement for applications that are CPU limited and draw count bound. Most experiences will get a free perf gain from this extension at not visual cost but there are limitations to consider. multiview builds on the multisampled render to texture extension that discards the frame buffer if there are other texture operations during rendering. Problem outlined in https://github.com/KhronosGroup/WebGL/issues/2912. Until browsers and drivers allow more control of when multisample is resolved we have a workaround with some drawbacks. As a temporary solution when enabling multiview the upload of texture data is deferred until the rendering of the main scene has ended, adding one extra frame of latency to texture uploads. Scenarios affected are for example skeletal meshes that upload bone textures with TexImage. With the workadound in place all bone animations will lag by one frame. Another issue is rendering mirror reflexions or rendering another view in the middle of the scene. The logic would have to move to the beginning of the frame to make sure it's not interrupted by the multiview frame. Because of the limitations this flag is disabled by default so developers can address any issues before enabling.
61 changes: 46 additions & 15 deletions src/core/scene/a-scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,29 @@ export class AScene extends AEntity {
setupRenderer () {
var self = this;
var renderer;
var rendererConfig = this.getRendererConfig();

// Trick Webpack so that it can't statically determine the exact export used.
// Otherwise it will conclude that one of the two exports can't be found in THREE.
// Only one needs to exist, and this should be determined at runtime.
var rendererImpl = ['WebGLRenderer', 'WebGPURenderer'].find(function (x) { return THREE[x]; });
renderer = this.renderer = new THREE[rendererImpl](rendererConfig);
if (!renderer.xr.setPoseTarget) {
// setPoseTarget is an A-Frame specific patch in super-three to the WebXRManager
// used by WebGLRenderer. It will need to be implemented similarly in super-three
// for the XRManager used by WebGPURenderer when WebXR support for WebGPURenderer
// is added.
renderer.xr.setPoseTarget = function () {};
}
renderer.setPixelRatio(window.devicePixelRatio);

if (this.camera) { renderer.xr.setPoseTarget(this.camera.el.object3D); }
this.addEventListener('camera-set-active', function () {
renderer.xr.setPoseTarget(self.camera.el.object3D);
});
}

getRendererConfig () {
var rendererAttr;
var rendererAttrString;
var rendererConfig;
Expand Down Expand Up @@ -567,7 +590,18 @@ export class AScene extends AEntity {
}

if (rendererAttr.multiviewStereo) {
// WebGLRenderer names this option multiviewStereo, WebGPURenderer names it multiview.
rendererConfig.multiviewStereo = rendererAttr.multiviewStereo === 'true';
rendererConfig.multiview = rendererConfig.multiviewStereo;
}

if (rendererAttr.reversedDepthBuffer) {
rendererConfig.reversedDepthBuffer = rendererAttr.reversedDepthBuffer === 'true';
}

if (rendererAttr.backend) {
// Only used by WebGPURenderer; webgl forces the WebGL 2 backend.
rendererConfig.forceWebGL = rendererAttr.backend === 'webgl';
}

this.maxCanvasSize = {
Expand All @@ -580,20 +614,7 @@ export class AScene extends AEntity {
};
}

// Trick Webpack so that it can't statically determine the exact export used.
// Otherwise it will conclude that one of the two exports can't be found in THREE.
// Only one needs to exist, and this should be determined at runtime.
var rendererImpl = ['WebGLRenderer', 'WebGPURenderer'].find(function (x) { return THREE[x]; });
renderer = this.renderer = new THREE[rendererImpl](rendererConfig);
if (!renderer.xr.setPoseTarget) {
renderer.xr.setPoseTarget = function () {};
}
renderer.setPixelRatio(window.devicePixelRatio);

if (this.camera) { renderer.xr.setPoseTarget(this.camera.el.object3D); }
this.addEventListener('camera-set-active', function () {
renderer.xr.setPoseTarget(self.camera.el.object3D);
});
return rendererConfig;
}

/**
Expand All @@ -618,9 +639,19 @@ export class AScene extends AEntity {

// Kick off render loop.
if (sceneEl.renderer) {
if (renderer.init) {
// WebGPURenderer initializes its backend asynchronously,
// wait for it before starting the render loop.
renderer.init().then(startRenderLoop);
} else {
startRenderLoop();
}
}

function startRenderLoop () {
if (window.performance) { window.performance.mark('render-started'); }
loadingScreen.remove();
renderer.setAnimationLoop(this.render);
renderer.setAnimationLoop(sceneEl.render);
sceneEl.renderStarted = true;
sceneEl.emit('renderstart');
}
Expand Down
2 changes: 2 additions & 0 deletions src/systems/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ var warn = debug('components:renderer:warn');
export var System = registerSystem('renderer', {
schema: {
antialias: {default: 'auto', oneOf: ['true', 'false', 'auto']},
backend: {default: 'auto', oneOf: ['auto', 'webgl']},
highRefreshRate: {default: utils.device.isOculusBrowser()},
logarithmicDepthBuffer: {default: 'auto', oneOf: ['true', 'false', 'auto']},
maxCanvasWidth: {default: -1},
maxCanvasHeight: {default: -1},
multiviewStereo: {default: false},
reversedDepthBuffer: {default: false},
exposure: {default: 1, if: {toneMapping: ['ACESFilmic', 'linear', 'reinhard', 'cineon', 'AgX', 'neutral']}},
toneMapping: {default: 'no', oneOf: ['no', 'ACESFilmic', 'linear', 'reinhard', 'cineon', 'AgX', 'neutral']},
precision: {default: 'high', oneOf: ['high', 'medium', 'low']},
Expand Down
90 changes: 90 additions & 0 deletions tests/core/scene/a-scene.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,96 @@ helpers.getSkipCISuite()('a-scene (with renderer)', function () {
});
});

suite('getRendererConfig', function () {
var sceneEl;

setup(function () {
sceneEl = document.createElement('a-scene');
});

test('default configuration', function () {
var config = sceneEl.getRendererConfig();
assert.strictEqual(config.alpha, true);
assert.strictEqual(config.logarithmicDepthBuffer, false);
assert.strictEqual(config.powerPreference, 'high-performance');
assert.notOk('multiviewStereo' in config);
assert.notOk('multiview' in config);
assert.notOk('reversedDepthBuffer' in config);
assert.notOk('forceWebGL' in config);
});

test('multiviewStereo is also passed as the multiview option used by WebGPURenderer', function () {
sceneEl.setAttribute('renderer', 'multiviewStereo: true');
var config = sceneEl.getRendererConfig();
assert.strictEqual(config.multiviewStereo, true);
assert.strictEqual(config.multiview, true);
});

test('multiviewStereo false', function () {
sceneEl.setAttribute('renderer', 'multiviewStereo: false');
var config = sceneEl.getRendererConfig();
assert.strictEqual(config.multiviewStereo, false);
assert.strictEqual(config.multiview, false);
});

test('reversedDepthBuffer true', function () {
sceneEl.setAttribute('renderer', 'reversedDepthBuffer: true');
var config = sceneEl.getRendererConfig();
assert.strictEqual(config.reversedDepthBuffer, true);
});

test('reversedDepthBuffer false', function () {
sceneEl.setAttribute('renderer', 'reversedDepthBuffer: false');
var config = sceneEl.getRendererConfig();
assert.strictEqual(config.reversedDepthBuffer, false);
});

test('backend webgl forces the WebGL backend of WebGPURenderer', function () {
sceneEl.setAttribute('renderer', 'backend: webgl');
var config = sceneEl.getRendererConfig();
assert.strictEqual(config.forceWebGL, true);
});

test('backend auto does not force the WebGL backend', function () {
sceneEl.setAttribute('renderer', 'backend: auto');
var config = sceneEl.getRendererConfig();
assert.strictEqual(config.forceWebGL, false);
});
});

suite('render loop start', function () {
test('starts the render loop synchronously with WebGLRenderer', function (done) {
var el = document.createElement('a-scene');
el.addEventListener('renderstart', function () {
assert.ok(el.renderStarted);
done();
});
document.body.appendChild(el);
});

test('waits for async renderer init (WebGPURenderer) before starting the render loop', function (done) {
var el = document.createElement('a-scene');
var resolveInit;
// Mimic WebGPURenderer which initializes its backend asynchronously via init().
el.renderer = Object.create(AScene.prototype.renderer);
el.renderer.init = function () {
return new Promise(function (resolve) { resolveInit = resolve; });
};
el.addEventListener('loaded', function () {
setTimeout(function () {
assert.ok(resolveInit, 'renderer init was called');
assert.notOk(el.renderStarted, 'render loop does not start before init resolves');
resolveInit();
});
});
el.addEventListener('renderstart', function () {
assert.ok(el.renderStarted);
done();
});
document.body.appendChild(el);
});
});

suite('scenes', function () {
var sceneEl;

Expand Down
Loading