Skip to content

Commit a063a72

Browse files
WebGPURenderer: Preserve material side during async compilation
1 parent 302c62f commit a063a72

10 files changed

Lines changed: 68 additions & 56 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: 23 additions & 33 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
@@ -920,7 +920,7 @@ class Renderer {
920920
const previousRenderContext = this._currentRenderContext;
921921
const previousRenderObjectFunction = this._currentRenderObjectFunction;
922922
const previousHandleObjectFunction = this._handleObjectFunction;
923-
const previousCompilationPromises = this._compilationPromises;
923+
const previousCompilationRenderObjects = this._compilationRenderObjects;
924924

925925
//
926926

@@ -936,14 +936,14 @@ class Renderer {
936936
const renderContext = this._renderContexts.get( renderTarget, this._mrt );
937937
const activeMipmapLevel = this._activeMipmapLevel;
938938

939-
const compilationPromises = [];
939+
const compilationRenderObjects = [];
940940

941941
this._currentRenderContext = renderContext;
942942
this._currentRenderObjectFunction = this.renderObject;
943943

944944
this._handleObjectFunction = this._createObjectPipeline;
945945

946-
this._compilationPromises = compilationPromises;
946+
this._compilationRenderObjects = compilationRenderObjects;
947947

948948
nodeFrame.renderId ++;
949949

@@ -1036,7 +1036,7 @@ class Renderer {
10361036

10371037
}
10381038

1039-
// process render lists - _createObjectPipeline will push async promises to _compilationPromises
1039+
// process render lists - _createObjectPipeline will push render objects to _compilationRenderObjects
10401040

10411041
const opaqueObjects = renderList.opaque;
10421042
const transparentObjects = renderList.transparent;
@@ -1053,16 +1053,12 @@ class Renderer {
10531053
this._currentRenderContext = previousRenderContext;
10541054
this._currentRenderObjectFunction = previousRenderObjectFunction;
10551055
this._handleObjectFunction = previousHandleObjectFunction;
1056-
this._compilationPromises = previousCompilationPromises;
1056+
this._compilationRenderObjects = previousCompilationRenderObjects;
10571057

10581058
// Process compilation work items sequentially to avoid freezing
10591059
// Yields between objects to keep animation smooth
10601060

1061-
for ( const item of compilationPromises ) {
1062-
1063-
const renderObject = this._objects.get( item.object, item.material, item.scene, item.camera, item.lightsNode, item.renderContext, item.clippingContext, item.passId );
1064-
renderObject.drawRange = item.object.geometry.drawRange;
1065-
renderObject.group = item.group;
1061+
for ( const renderObject of compilationRenderObjects ) {
10661062

10671063
// Use async node building to yield to main thread
10681064
await this._nodes.getForRenderAsync( renderObject );
@@ -1077,6 +1073,7 @@ class Renderer {
10771073
// Wait for pipeline creation
10781074
const pipelinePromises = [];
10791075
this._pipelines.getForRender( renderObject, pipelinePromises );
1076+
10801077
if ( pipelinePromises.length > 0 ) {
10811078

10821079
await Promise.all( pipelinePromises );
@@ -3806,31 +3803,24 @@ class Renderer {
38063803
*/
38073804
_createObjectPipeline( object, material, scene, camera, lightsNode, group, clippingContext, passId ) {
38083805

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

3812-
// Store work items instead of promises - will be processed sequentially
3813-
this._compilationPromises.push( {
3814-
object,
3815-
material,
3816-
scene,
3817-
camera,
3818-
lightsNode,
3819-
group,
3820-
clippingContext,
3821-
passId,
3822-
renderContext: this._currentRenderContext
3823-
} );
3810+
const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
3811+
renderObject.drawRange = object.geometry.drawRange;
3812+
renderObject.group = group;
3813+
3814+
// if in async compilation mode, queue the render object for sequential execution
3815+
3816+
if ( this._compilationRenderObjects !== null ) {
3817+
3818+
this._compilationRenderObjects.push( renderObject );
38243819

38253820
return;
38263821

38273822
}
38283823

3829-
// Sync path
3830-
const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
3831-
renderObject.drawRange = object.geometry.drawRange;
3832-
renderObject.group = group;
3833-
38343824
//
38353825

38363826
this._nodes.updateBefore( renderObject );
@@ -3840,7 +3830,7 @@ class Renderer {
38403830
this._nodes.updateForRender( renderObject );
38413831
this._bindings.updateForRender( renderObject );
38423832

3843-
this._pipelines.getForRender( renderObject, this._compilationPromises );
3833+
this._pipelines.getForRender( renderObject );
38443834

38453835
this._nodes.updateAfter( renderObject );
38463836

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

@@ -823,12 +823,12 @@ class WebGPUPipelineUtils {
823823
* for the pipeline creation.
824824
*
825825
* @private
826-
* @param {Object3D} object - The 3D object.
827-
* @param {BufferGeometry} geometry - The geometry.
828-
* @param {Material} material - The material.
826+
* @param {RenderObject} renderObject - The render object.
829827
* @return {Object} The primitive state.
830828
*/
831-
_getPrimitiveState( object, geometry, material ) {
829+
_getPrimitiveState( renderObject ) {
830+
831+
const { object, geometry, material, materialSide } = renderObject;
832832

833833
const descriptor = {};
834834
const utils = this.backend.utils;
@@ -845,15 +845,15 @@ class WebGPUPipelineUtils {
845845

846846
//
847847

848-
let flipSided = ( material.side === BackSide );
848+
let flipSided = ( materialSide === BackSide );
849849

850850
if ( object.isMesh && object.matrixWorld.determinantAffine() < 0 ) flipSided = ! flipSided;
851851

852852
descriptor.frontFace = ( flipSided === true ) ? GPUFrontFace.CW : GPUFrontFace.CCW;
853853

854854
//
855855

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

858858
return descriptor;
859859

0 commit comments

Comments
 (0)