Skip to content

Commit

Permalink
Add WebGLShaderCache. (mrdoob#23022)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mugen87 authored Dec 15, 2021
1 parent 007b2a0 commit 9b86245
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 41 deletions.
7 changes: 7 additions & 0 deletions src/renderers/WebGLRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ function WebGLRenderer( parameters = {} ) {
cubeuvmaps.dispose();
objects.dispose();
bindingStates.dispose();
programCache.dispose();

xr.dispose();

Expand Down Expand Up @@ -657,6 +658,12 @@ function WebGLRenderer( parameters = {} ) {

} );

if ( material.isShaderMaterial ) {

programCache.releaseShaderCache( material );

}

}

}
Expand Down
32 changes: 28 additions & 4 deletions src/renderers/webgl/WebGLPrograms.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { BackSide, DoubleSide, CubeUVRefractionMapping, CubeUVReflectionMapping, LinearEncoding, sRGBEncoding, ObjectSpaceNormalMap, TangentSpaceNormalMap, NoToneMapping, RGBAFormat, UnsignedByteType } from '../../constants.js';
import { Layers } from '../../core/Layers.js';
import { WebGLProgram } from './WebGLProgram.js';
import { WebGLShaderCache } from './WebGLShaderCache.js';
import { ShaderLib } from '../shaders/ShaderLib.js';
import { UniformsUtils } from '../shaders/UniformsUtils.js';
import { hashString } from '../../utils.js';

function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) {

const _programLayers = new Layers();
const _customShaders = new WebGLShaderCache();
const programs = [];

const isWebGL2 = capabilities.isWebGL2;
Expand Down Expand Up @@ -127,6 +128,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
}

let vertexShader, fragmentShader;
let customVertexShaderID, customFragmentShaderID;

if ( shaderID ) {

Expand All @@ -140,6 +142,11 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
vertexShader = material.vertexShader;
fragmentShader = material.fragmentShader;

_customShaders.update( material );

customVertexShaderID = _customShaders.getVertexShaderID( material );
customFragmentShaderID = _customShaders.getFragmentShaderID( material );

}

const currentRenderTarget = renderer.getRenderTarget();
Expand All @@ -158,6 +165,9 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
fragmentShader: fragmentShader,
defines: material.defines,

customVertexShaderID: customVertexShaderID,
customFragmentShaderID: customFragmentShaderID,

isRawShaderMaterial: material.isRawShaderMaterial === true,
glslVersion: material.glslVersion,

Expand Down Expand Up @@ -296,8 +306,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities

} else {

array.push( hashString( parameters.fragmentShader ) );
array.push( hashString( parameters.vertexShader ) );
array.push( parameters.customVertexShaderID );
array.push( parameters.customFragmentShaderID );

}

Expand Down Expand Up @@ -549,14 +559,28 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities

}

function releaseShaderCache( material ) {

_customShaders.remove( material );

}

function dispose() {

_customShaders.dispose();

}

return {
getParameters: getParameters,
getProgramCacheKey: getProgramCacheKey,
getUniforms: getUniforms,
acquireProgram: acquireProgram,
releaseProgram: releaseProgram,
releaseShaderCache: releaseShaderCache,
// Exposed for resource monitoring & error feedback via renderer.info:
programs: programs
programs: programs,
dispose: dispose
};

}
Expand Down
120 changes: 120 additions & 0 deletions src/renderers/webgl/WebGLShaderCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
let _id = 0;

class WebGLShaderCache {

constructor() {

this.shaderCache = new Map();
this.materialCache = new Map();

}

update( material ) {

const vertexShader = material.vertexShader;
const fragmentShader = material.fragmentShader;

const vertexShaderStage = this._getShaderStage( vertexShader );
const fragmentShaderStage = this._getShaderStage( fragmentShader );

const materialShaders = this._getShaderCacheForMaterial( material );

if ( materialShaders.has( vertexShaderStage ) === false ) {

materialShaders.add( vertexShaderStage );
vertexShaderStage.usedTimes ++;

}

if ( materialShaders.has( fragmentShaderStage ) === false ) {

materialShaders.add( fragmentShaderStage );
fragmentShaderStage.usedTimes ++;

}

return this;

}

remove( material ) {

const materialShaders = this.materialCache.get( material );

for ( const shaderStage of materialShaders ) {

shaderStage.usedTimes --;

if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage );

}

this.materialCache.delete( material );

return this;

}

getVertexShaderID( material ) {

return this._getShaderStage( material.vertexShader ).id;

}

getFragmentShaderID( material ) {

return this._getShaderStage( material.fragmentShader ).id;

}

dispose() {

this.shaderCache.clear();
this.materialCache.clear();

}

_getShaderCacheForMaterial( material ) {

const cache = this.materialCache;

if ( cache.has( material ) === false ) {

cache.set( material, new Set() );

}

return cache.get( material );

}

_getShaderStage( code ) {

const cache = this.shaderCache;

if ( cache.has( code ) === false ) {

const stage = new WebGLShaderStage();
cache.set( code, stage );

}

return cache.get( code );

}

}

class WebGLShaderStage {

constructor() {

this.id = _id ++;

this.usedTimes = 0;

}

}

export { WebGLShaderCache };
38 changes: 1 addition & 37 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,40 +54,4 @@ function createElementNS( name ) {

}

/**
* cyrb53 hash for string from: https://stackoverflow.com/a/52171480
*
* Public Domain, @bryc - https://stackoverflow.com/users/815680/bryc
*
* It is roughly similar to the well-known MurmurHash/xxHash algorithms. It uses a combination
* of multiplication and Xorshift to generate the hash, but not as thorough. As a result it's
* faster than either would be in JavaScript and significantly simpler to implement. Keep in
* mind this is not a secure algorithm, if privacy/security is a concern, this is not for you.
*
* @param {string} str
* @param {number} seed, default 0
* @returns number
*/
function hashString( str, seed = 0 ) {

let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;

for ( let i = 0, ch; i < str.length; i ++ ) {

ch = str.charCodeAt( i );

h1 = Math.imul( h1 ^ ch, 2654435761 );

h2 = Math.imul( h2 ^ ch, 1597334677 );

}

h1 = Math.imul( h1 ^ ( h1 >>> 16 ), 2246822507 ) ^ Math.imul( h2 ^ ( h2 >>> 13 ), 3266489909 );

h2 = Math.imul( h2 ^ ( h2 >>> 16 ), 2246822507 ) ^ Math.imul( h1 ^ ( h1 >>> 13 ), 3266489909 );

return 4294967296 * ( 2097151 & h2 ) + ( h1 >>> 0 );

}

export { arrayMin, arrayMax, getTypedArray, createElementNS, hashString };
export { arrayMin, arrayMax, getTypedArray, createElementNS };

0 comments on commit 9b86245

Please sign in to comment.