Skip to content

Commit 3c297d7

Browse files
WebGPURenderer: Preserve material side during async compilation
1 parent bb24eff commit 3c297d7

10 files changed

Lines changed: 69 additions & 57 deletions

File tree

91 Bytes
Loading

src/nodes/core/NodeBuilder.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ class NodeBuilder {
116116
*/
117117
this.material = ( object && object.material ) || null;
118118

119+
/**
120+
* The side of the material the shader is built for. Nodes must use this property
121+
* instead of `material.side` since the renderer temporarily overwrites the material's
122+
* side when it renders double-sided transparent materials in two passes. The value is
123+
* derived from the render object so it stays valid when the shader is built at a later
124+
* point (e.g. in `compileAsync()`).
125+
*
126+
* @type {?number}
127+
*/
128+
this.materialSide = this.material !== null ? this.material.side : null;
129+
119130
/**
120131
* The geometry of the 3D object.
121132
*

src/nodes/display/FrontFacingNode.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ class FrontFacingNode extends Node {
4141

4242
//
4343

44-
const { material } = builder;
45-
46-
if ( material.side === BackSide ) {
44+
if ( builder.materialSide === BackSide ) {
4745

4846
return 'false';
4947

@@ -88,15 +86,13 @@ export const faceDirection = /*@__PURE__*/ float( frontFacing ).mul( 2.0 ).sub(
8886
* @param {Node<vec3>} vector - The vector to process.
8987
* @returns {Node<vec3>} The processed vector.
9088
*/
91-
export const negateOnBackSide = /*@__PURE__*/ Fn( ( [ vector ], { material } ) => {
92-
93-
const side = material.side;
89+
export const negateOnBackSide = /*@__PURE__*/ Fn( ( [ vector ], { materialSide } ) => {
9490

95-
if ( side === BackSide ) {
91+
if ( materialSide === BackSide ) {
9692

9793
vector = vector.mul( - 1.0 );
9894

99-
} else if ( side === DoubleSide ) {
95+
} else if ( materialSide === DoubleSide ) {
10096

10197
vector = vector.mul( faceDirection );
10298

src/nodes/functions/PhysicalLightingModel.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ const applyIorToRoughness = /*@__PURE__*/ Fn( ( [ roughness, ior ] ) => {
7070
const viewportBackSideTexture = /*@__PURE__*/ viewportMipTexture();
7171
const viewportFrontSideTexture = /*@__PURE__*/ viewportOpaqueMipTexture();
7272

73-
const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], { material } ) => {
73+
const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], { materialSide } ) => {
7474

75-
const vTexture = material.side === BackSide ? viewportBackSideTexture : viewportFrontSideTexture;
75+
const vTexture = materialSide === BackSide ? viewportBackSideTexture : viewportFrontSideTexture;
7676

7777
const transmissionSample = vTexture.sample( fragCoord );
7878
//const transmissionSample = viewportMipTexture( fragCoord );

src/renderers/common/RenderObject.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,19 @@ class RenderObject {
119119
*/
120120
this.material = material;
121121

122+
/**
123+
* The side of the material this render object is rendered with.
124+
*
125+
* The renderer temporarily overwrites `material.side` when double-sided transparent
126+
* materials are rendered in two passes. Since render objects are unique per pass
127+
* (see the `backSide` pass ID), the side can be stored per render object which keeps
128+
* it valid even when the material has been restored in the meantime (e.g. when the
129+
* shader is built at a later point in `compileAsync()`).
130+
*
131+
* @type {number}
132+
*/
133+
this.materialSide = material.side;
134+
122135
/**
123136
* The scene the 3D object belongs to.
124137
*

src/renderers/common/RenderObjects.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class RenderObjects {
113113
// update references
114114

115115
renderObject.camera = camera;
116+
renderObject.materialSide = material.side;
116117

117118
//
118119

src/renderers/common/Renderer.js

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -653,13 +653,13 @@ class Renderer {
653653
this._initPromise = null;
654654

655655
/**
656-
* An array of compilation promises which are used in `compileAsync()`.
656+
* An array of render objects which are precompiled in `compileAsync()`.
657657
*
658658
* @private
659-
* @type {?Array<Promise>}
659+
* @type {?Array<RenderObject>}
660660
* @default null
661661
*/
662-
this._compilationPromises = null;
662+
this._compilationRenderObjects = null;
663663

664664
/**
665665
* Whether the renderer is currently precompiling a render object in
@@ -921,7 +921,7 @@ class Renderer {
921921
const previousRenderContext = this._currentRenderContext;
922922
const previousRenderObjectFunction = this._currentRenderObjectFunction;
923923
const previousHandleObjectFunction = this._handleObjectFunction;
924-
const previousCompilationPromises = this._compilationPromises;
924+
const previousCompilationRenderObjects = this._compilationRenderObjects;
925925

926926
//
927927

@@ -939,14 +939,14 @@ class Renderer {
939939
const renderContext = this._renderContexts.get( renderTarget, this._mrt );
940940
const activeMipmapLevel = this._activeMipmapLevel;
941941

942-
const compilationPromises = [];
942+
const compilationRenderObjects = [];
943943

944944
this._currentRenderContext = renderContext;
945945
this._currentRenderObjectFunction = this.renderObject;
946946

947947
this._handleObjectFunction = this._createObjectPipeline;
948948

949-
this._compilationPromises = compilationPromises;
949+
this._compilationRenderObjects = compilationRenderObjects;
950950

951951
nodeFrame.renderId ++;
952952

@@ -1039,7 +1039,7 @@ class Renderer {
10391039

10401040
}
10411041

1042-
// process render lists - _createObjectPipeline will push async promises to _compilationPromises
1042+
// process render lists - _createObjectPipeline will push render objects to _compilationRenderObjects
10431043

10441044
const opaqueObjects = renderList.opaque;
10451045
const transparentObjects = renderList.transparent;
@@ -1056,19 +1056,15 @@ class Renderer {
10561056
this._currentRenderContext = previousRenderContext;
10571057
this._currentRenderObjectFunction = previousRenderObjectFunction;
10581058
this._handleObjectFunction = previousHandleObjectFunction;
1059-
this._compilationPromises = previousCompilationPromises;
1059+
this._compilationRenderObjects = previousCompilationRenderObjects;
10601060

10611061
// Process compilation work items sequentially to avoid freezing
10621062
// Yields between objects to keep animation smooth
10631063

1064-
const total = compilationPromises.length;
1064+
const total = compilationRenderObjects.length;
10651065
let loaded = 0;
10661066

1067-
for ( const item of compilationPromises ) {
1068-
1069-
const renderObject = this._objects.get( item.object, item.material, item.scene, item.camera, item.lightsNode, item.renderContext, item.clippingContext, item.passId );
1070-
renderObject.drawRange = item.object.geometry.drawRange;
1071-
renderObject.group = item.group;
1067+
for ( const renderObject of compilationRenderObjects ) {
10721068

10731069
// Use async node building to yield to main thread
10741070
await this._nodes.getForRenderAsync( renderObject );
@@ -1083,6 +1079,7 @@ class Renderer {
10831079
// Wait for pipeline creation
10841080
const pipelinePromises = [];
10851081
this._pipelines.getForRender( renderObject, pipelinePromises );
1082+
10861083
if ( pipelinePromises.length > 0 ) {
10871084

10881085
await Promise.all( pipelinePromises );
@@ -3906,31 +3903,24 @@ class Renderer {
39063903
*/
39073904
_createObjectPipeline( object, material, scene, camera, lightsNode, group, clippingContext, passId ) {
39083905

3909-
// If in async compilation mode, queue the work for sequential execution
3910-
if ( this._compilationPromises !== null ) {
3906+
// the render object must be requested during the traversal since the renderer temporarily
3907+
// overwrites `material.side` for the back side pass. `RenderObject.materialSide` preserves
3908+
// that value for the deferred compilation in `compileAsync()`.
39113909

3912-
// Store work items instead of promises - will be processed sequentially
3913-
this._compilationPromises.push( {
3914-
object,
3915-
material,
3916-
scene,
3917-
camera,
3918-
lightsNode,
3919-
group,
3920-
clippingContext,
3921-
passId,
3922-
renderContext: this._currentRenderContext
3923-
} );
3910+
const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
3911+
renderObject.drawRange = object.geometry.drawRange;
3912+
renderObject.group = group;
3913+
3914+
// if in async compilation mode, queue the render object for sequential execution
3915+
3916+
if ( this._compilationRenderObjects !== null ) {
3917+
3918+
this._compilationRenderObjects.push( renderObject );
39243919

39253920
return;
39263921

39273922
}
39283923

3929-
// Sync path
3930-
const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
3931-
renderObject.drawRange = object.geometry.drawRange;
3932-
renderObject.group = group;
3933-
39343924
//
39353925

39363926
this._nodes.updateBefore( renderObject );
@@ -3940,7 +3930,7 @@ class Renderer {
39403930
this._nodes.updateForRender( renderObject );
39413931
this._bindings.updateForRender( renderObject );
39423932

3943-
this._pipelines.getForRender( renderObject, this._compilationPromises );
3933+
this._pipelines.getForRender( renderObject );
39443934

39453935
this._nodes.updateAfter( renderObject );
39463936

src/renderers/common/nodes/NodeManager.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ class NodeManager extends DataMap {
171171

172172
nodeBuilder.scene = renderObject.scene;
173173
nodeBuilder.material = material;
174+
nodeBuilder.materialSide = renderObject.materialSide;
174175
nodeBuilder.camera = renderObject.camera;
175176
nodeBuilder.context.material = material;
176177
nodeBuilder.lightsNode = renderObject.lightsNode;

src/renderers/webgpu/WebGPUBackend.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2087,7 +2087,7 @@ class WebGPUBackend extends Backend {
20872087

20882088
const data = this.get( renderObject );
20892089

2090-
const { object, material } = renderObject;
2090+
const { object, material, materialSide } = renderObject;
20912091

20922092
const utils = this.utils;
20932093

@@ -2108,7 +2108,7 @@ class WebGPUBackend extends Backend {
21082108
data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc ||
21092109
data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass ||
21102110
data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask ||
2111-
data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage ||
2111+
data.side !== materialSide || data.alphaToCoverage !== material.alphaToCoverage ||
21122112
data.sampleCount !== sampleCount || data.colorSpace !== colorSpace ||
21132113
data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat ||
21142114
data.primitiveTopology !== primitiveTopology ||
@@ -2125,7 +2125,7 @@ class WebGPUBackend extends Backend {
21252125
data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc;
21262126
data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass;
21272127
data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask;
2128-
data.side = material.side; data.alphaToCoverage = material.alphaToCoverage;
2128+
data.side = materialSide; data.alphaToCoverage = material.alphaToCoverage;
21292129
data.sampleCount = sampleCount;
21302130
data.colorSpace = colorSpace;
21312131
data.colorFormat = colorFormat;
@@ -2150,7 +2150,7 @@ class WebGPUBackend extends Backend {
21502150
*/
21512151
getRenderCacheKey( renderObject ) {
21522152

2153-
const { object, material } = renderObject;
2153+
const { object, material, materialSide } = renderObject;
21542154

21552155
const utils = this.utils;
21562156
const renderContext = renderObject.context;
@@ -2169,7 +2169,7 @@ class WebGPUBackend extends Backend {
21692169
material.stencilWrite, material.stencilFunc,
21702170
material.stencilFail, material.stencilZFail, material.stencilZPass,
21712171
material.stencilFuncMask, material.stencilWriteMask,
2172-
material.side,
2172+
materialSide,
21732173
frontFaceCW,
21742174
utils.getSampleCountRenderContext( renderContext ),
21752175
utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),

src/renderers/webgpu/utils/WebGPUPipelineUtils.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class WebGPUPipelineUtils {
7171
*/
7272
createRenderPipeline( renderObject, promises ) {
7373

74-
const { object, material, geometry, pipeline } = renderObject;
74+
const { material, pipeline } = renderObject;
7575
const { vertexProgram, fragmentProgram } = pipeline;
7676

7777
const backend = this.backend;
@@ -193,7 +193,7 @@ class WebGPUPipelineUtils {
193193
const vertexModule = backend.get( vertexProgram ).module;
194194
const fragmentModule = backend.get( fragmentProgram ).module;
195195

196-
const primitiveState = this._getPrimitiveState( object, geometry, material );
196+
const primitiveState = this._getPrimitiveState( renderObject );
197197
const depthCompare = this._getDepthCompare( material );
198198
const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
199199

@@ -888,12 +888,12 @@ class WebGPUPipelineUtils {
888888
* for the pipeline creation.
889889
*
890890
* @private
891-
* @param {Object3D} object - The 3D object.
892-
* @param {BufferGeometry} geometry - The geometry.
893-
* @param {Material} material - The material.
891+
* @param {RenderObject} renderObject - The render object.
894892
* @return {Object} The primitive state.
895893
*/
896-
_getPrimitiveState( object, geometry, material ) {
894+
_getPrimitiveState( renderObject ) {
895+
896+
const { object, geometry, material, materialSide } = renderObject;
897897

898898
const descriptor = {};
899899
const utils = this.backend.utils;
@@ -910,15 +910,15 @@ class WebGPUPipelineUtils {
910910

911911
//
912912

913-
let flipSided = ( material.side === BackSide );
913+
let flipSided = ( materialSide === BackSide );
914914

915915
if ( object.isMesh && object.matrixWorld.determinantAffine() < 0 ) flipSided = ! flipSided;
916916

917917
descriptor.frontFace = ( flipSided === true ) ? GPUFrontFace.CW : GPUFrontFace.CCW;
918918

919919
//
920920

921-
descriptor.cullMode = ( material.side === DoubleSide ) ? GPUCullMode.None : GPUCullMode.Back;
921+
descriptor.cullMode = ( materialSide === DoubleSide ) ? GPUCullMode.None : GPUCullMode.Back;
922922

923923
return descriptor;
924924

0 commit comments

Comments
 (0)