Skip to content

⬅️ Back to Table of Contents

📄 WebGPUTexturePassUtils.js

📊 Analysis Summary

Metric Count
🔧 Functions 7
🧱 Classes 1
📦 Imports 7
📊 Variables & Constants 11

📚 Table of Contents

🛠️ File Location:

📂 src/renderers/webgpu/utils/WebGPUTexturePassUtils.js

📦 Imports

Name Source
DataMap ../../common/DataMap.js
GPUTextureViewDimension ./WebGPUConstants.js
GPUIndexFormat ./WebGPUConstants.js
GPUFilterMode ./WebGPUConstants.js
GPUPrimitiveTopology ./WebGPUConstants.js
GPULoadOp ./WebGPUConstants.js
GPUStoreOp ./WebGPUConstants.js

Variables & Constants

Name Type Kind Value Exported
mipmapVertexSource "\nstruct VarysStruct {\n\t@builtin( ... let/var ` struct VarysStruct { @builtin( position ) Position: vec4<f32>, @location( 0...
mipmapFragmentSource "\n@group( 0 ) @binding( 0 )\nvar img... let/var ` @group( 0 ) @binding( 0 ) var imgSampler : sampler; @group( 0 ) @binding( 1...
flipYFragmentSource "\n@group( 0 ) @binding( 0 )\nvar img... let/var ` @group( 0 ) @binding( 0 ) var imgSampler : sampler; @group( 0 ) @binding( 1...
pipeline GPURenderPipeline let/var this.transferPipelines[ format ]
pipeline GPURenderPipeline let/var this.flipYPipelines[ format ]
format any let/var textureGPUDescriptor.format
passes any let/var textureData.layers[ baseArrayLayer ] \|\| this._mipmapCreateBundles( textureG...
passes any[] let/var []
passDescriptor { colorAttachments: { view: any; load... let/var { colorAttachments: [ { view: dstView, loadOp: GPULoadOp.Clear, storeOp: GPUS...
levels number let/var passes.length
pass any let/var passes[ i ]

Functions

WebGPUTexturePassUtils.getTransferPipeline(format: string): GPURenderPipeline

JSDoc:

/**
     * Returns a render pipeline for the internal copy render pass. The pass
     * requires a unique render pipeline for each texture format.
     *
     * @param {string} format - The GPU texture format
     * @return {GPURenderPipeline} The GPU render pipeline.
     */

Parameters:

  • format string

Returns: GPURenderPipeline

Calls:

  • this.device.createRenderPipeline
Code
getTransferPipeline( format ) {

        let pipeline = this.transferPipelines[ format ];

        if ( pipeline === undefined ) {

            pipeline = this.device.createRenderPipeline( {
                label: `mipmap-${ format }`,
                vertex: {
                    module: this.mipmapVertexShaderModule,
                    entryPoint: 'main'
                },
                fragment: {
                    module: this.mipmapFragmentShaderModule,
                    entryPoint: 'main',
                    targets: [ { format } ]
                },
                primitive: {
                    topology: GPUPrimitiveTopology.TriangleStrip,
                    stripIndexFormat: GPUIndexFormat.Uint32
                },
                layout: 'auto'
            } );

            this.transferPipelines[ format ] = pipeline;

        }

        return pipeline;

    }

WebGPUTexturePassUtils.getFlipYPipeline(format: string): GPURenderPipeline

JSDoc:

/**
     * Returns a render pipeline for the flipY render pass. The pass
     * requires a unique render pipeline for each texture format.
     *
     * @param {string} format - The GPU texture format
     * @return {GPURenderPipeline} The GPU render pipeline.
     */

Parameters:

  • format string

Returns: GPURenderPipeline

Calls:

  • this.device.createRenderPipeline
Code
getFlipYPipeline( format ) {

        let pipeline = this.flipYPipelines[ format ];

        if ( pipeline === undefined ) {

            pipeline = this.device.createRenderPipeline( {
                label: `flipY-${ format }`,
                vertex: {
                    module: this.mipmapVertexShaderModule,
                    entryPoint: 'main'
                },
                fragment: {
                    module: this.flipYFragmentShaderModule,
                    entryPoint: 'main',
                    targets: [ { format } ]
                },
                primitive: {
                    topology: GPUPrimitiveTopology.TriangleStrip,
                    stripIndexFormat: GPUIndexFormat.Uint32
                },
                layout: 'auto'
            } );

            this.flipYPipelines[ format ] = pipeline;

        }

        return pipeline;

    }

WebGPUTexturePassUtils.flipY(textureGPU: GPUTexture, textureGPUDescriptor: any, baseArrayLayer: number): void

JSDoc:

/**
     * Flip the contents of the given GPU texture along its vertical axis.
     *
     * @param {GPUTexture} textureGPU - The GPU texture object.
     * @param {Object} textureGPUDescriptor - The texture descriptor.
     * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view.
     */

Parameters:

  • textureGPU GPUTexture
  • textureGPUDescriptor any
  • baseArrayLayer number

Returns: void

Calls:

  • this.getTransferPipeline
  • this.getFlipYPipeline
  • this.device.createTexture
  • textureGPU.createView
  • tempTexture.createView
  • this.device.createCommandEncoder
  • pipeline.getBindGroupLayout
  • this.device.createBindGroup
  • commandEncoder.beginRenderPass
  • passEncoder.setPipeline
  • passEncoder.setBindGroup
  • passEncoder.draw
  • passEncoder.end
  • pass
  • this.device.queue.submit
  • commandEncoder.finish
  • tempTexture.destroy
Code
flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {

        const format = textureGPUDescriptor.format;
        const { width, height } = textureGPUDescriptor.size;

        const transferPipeline = this.getTransferPipeline( format );
        const flipYPipeline = this.getFlipYPipeline( format );

        const tempTexture = this.device.createTexture( {
            size: { width, height, depthOrArrayLayers: 1 },
            format,
            usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
        } );

        const srcView = textureGPU.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer
        } );

        const dstView = tempTexture.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer: 0
        } );

        const commandEncoder = this.device.createCommandEncoder( {} );

        const pass = ( pipeline, sourceView, destinationView ) => {

            const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.

            const bindGroup = this.device.createBindGroup( {
                layout: bindGroupLayout,
                entries: [ {
                    binding: 0,
                    resource: this.flipYSampler
                }, {
                    binding: 1,
                    resource: sourceView
                } ]
            } );

            const passEncoder = commandEncoder.beginRenderPass( {
                colorAttachments: [ {
                    view: destinationView,
                    loadOp: GPULoadOp.Clear,
                    storeOp: GPUStoreOp.Store,
                    clearValue: [ 0, 0, 0, 0 ]
                } ]
            } );

            passEncoder.setPipeline( pipeline );
            passEncoder.setBindGroup( 0, bindGroup );
            passEncoder.draw( 4, 1, 0, 0 );
            passEncoder.end();

        };

        pass( transferPipeline, srcView, dstView );
        pass( flipYPipeline, dstView, srcView );

        this.device.queue.submit( [ commandEncoder.finish() ] );

        tempTexture.destroy();

    }

WebGPUTexturePassUtils.generateMipmaps(textureGPU: GPUTexture, textureGPUDescriptor: any, baseArrayLayer: number): void

JSDoc:

/**
     * Generates mipmaps for the given GPU texture.
     *
     * @param {GPUTexture} textureGPU - The GPU texture object.
     * @param {Object} textureGPUDescriptor - The texture descriptor.
     * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view.
     */

Parameters:

  • textureGPU GPUTexture
  • textureGPUDescriptor any
  • baseArrayLayer number

Returns: void

Calls:

  • this.get
  • this._mipmapCreateBundles
  • this.device.createCommandEncoder
  • this._mipmapRunBundles
  • this.device.queue.submit
  • commandEncoder.finish
Code
generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {

        const textureData = this.get( textureGPU );

        if ( textureData.useCount === undefined ) {

            textureData.useCount = 0;
            textureData.layers = [];

        }

        const passes = textureData.layers[ baseArrayLayer ] || this._mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer );

        const commandEncoder = this.device.createCommandEncoder( {} );

        this._mipmapRunBundles( commandEncoder, passes );

        this.device.queue.submit( [ commandEncoder.finish() ] );

        if ( textureData.useCount !== 0 ) textureData.layers[ baseArrayLayer ] = passes;

        textureData.useCount ++;

    }

WebGPUTexturePassUtils._mipmapCreateBundles(textureGPU: GPUTexture, textureGPUDescriptor: any, baseArrayLayer: number): any[]

JSDoc:

/**
     * Since multiple copy render passes are required to generate mipmaps, the passes
     * are managed as render bundles to improve performance.
     *
     * @param {GPUTexture} textureGPU - The GPU texture object.
     * @param {Object} textureGPUDescriptor - The texture descriptor.
     * @param {number} baseArrayLayer - The index of the first array layer accessible to the texture view.
     * @return {Array<Object>} An array of render bundles.
     */

Parameters:

  • textureGPU GPUTexture
  • textureGPUDescriptor any
  • baseArrayLayer number

Returns: any[]

Calls:

  • this.getTransferPipeline
  • pipeline.getBindGroupLayout
  • textureGPU.createView
  • this.device.createBindGroup
  • this.device.createRenderBundleEncoder
  • passEncoder.setPipeline
  • passEncoder.setBindGroup
  • passEncoder.draw
  • passes.push
  • passEncoder.finish
Code
_mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ) {

        const pipeline = this.getTransferPipeline( textureGPUDescriptor.format );

        const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.

        let srcView = textureGPU.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer
        } );

        const passes = [];

        for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {

            const bindGroup = this.device.createBindGroup( {
                layout: bindGroupLayout,
                entries: [ {
                    binding: 0,
                    resource: this.mipmapSampler
                }, {
                    binding: 1,
                    resource: srcView
                } ]
            } );

            const dstView = textureGPU.createView( {
                baseMipLevel: i,
                mipLevelCount: 1,
                dimension: GPUTextureViewDimension.TwoD,
                baseArrayLayer
            } );

            const passDescriptor = {
                colorAttachments: [ {
                    view: dstView,
                    loadOp: GPULoadOp.Clear,
                    storeOp: GPUStoreOp.Store,
                    clearValue: [ 0, 0, 0, 0 ]
                } ]
            };

            const passEncoder = this.device.createRenderBundleEncoder( {
                colorFormats: [ textureGPUDescriptor.format ]
            } );

            passEncoder.setPipeline( pipeline );
            passEncoder.setBindGroup( 0, bindGroup );
            passEncoder.draw( 4, 1, 0, 0 );

            passes.push( {
                renderBundles: [ passEncoder.finish() ],
                passDescriptor
            } );

            srcView = dstView;

        }

        return passes;

    }

WebGPUTexturePassUtils._mipmapRunBundles(commandEncoder: GPUCommandEncoder, passes: any[]): void

JSDoc:

/**
     * Executes the render bundles.
     *
     * @param {GPUCommandEncoder} commandEncoder - The GPU command encoder.
     * @param {Array<Object>} passes - An array of render bundles.
     */

Parameters:

  • commandEncoder GPUCommandEncoder
  • passes any[]

Returns: void

Calls:

  • commandEncoder.beginRenderPass
  • passEncoder.executeBundles
  • passEncoder.end
Code
_mipmapRunBundles( commandEncoder, passes ) {

        const levels = passes.length;

        for ( let i = 0; i < levels; i ++ ) {

            const pass = passes[ i ];

            const passEncoder = commandEncoder.beginRenderPass( pass.passDescriptor );

            passEncoder.executeBundles( pass.renderBundles );

            passEncoder.end();

        }

    }

pass(pipeline: any, sourceView: any, destinationView: any): void

Parameters:

  • pipeline any
  • sourceView any
  • destinationView any

Returns: void

Calls:

  • pipeline.getBindGroupLayout
  • this.device.createBindGroup
  • commandEncoder.beginRenderPass
  • passEncoder.setPipeline
  • passEncoder.setBindGroup
  • passEncoder.draw
  • passEncoder.end
Code
( pipeline, sourceView, destinationView ) => {

            const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.

            const bindGroup = this.device.createBindGroup( {
                layout: bindGroupLayout,
                entries: [ {
                    binding: 0,
                    resource: this.flipYSampler
                }, {
                    binding: 1,
                    resource: sourceView
                } ]
            } );

            const passEncoder = commandEncoder.beginRenderPass( {
                colorAttachments: [ {
                    view: destinationView,
                    loadOp: GPULoadOp.Clear,
                    storeOp: GPUStoreOp.Store,
                    clearValue: [ 0, 0, 0, 0 ]
                } ]
            } );

            passEncoder.setPipeline( pipeline );
            passEncoder.setBindGroup( 0, bindGroup );
            passEncoder.draw( 4, 1, 0, 0 );
            passEncoder.end();

        }

Classes

WebGPUTexturePassUtils

Class Code
class WebGPUTexturePassUtils extends DataMap {

    /**
     * Constructs a new utility object.
     *
     * @param {GPUDevice} device - The WebGPU device.
     */
    constructor( device ) {

        super();

        /**
         * The WebGPU device.
         *
         * @type {GPUDevice}
         */
        this.device = device;

        const mipmapVertexSource = `
struct VarysStruct {
    @builtin( position ) Position: vec4<f32>,
    @location( 0 ) vTex : vec2<f32>
};

@vertex
fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct {

    var Varys : VarysStruct;

    var pos = array< vec2<f32>, 4 >(
        vec2<f32>( -1.0,  1.0 ),
        vec2<f32>(  1.0,  1.0 ),
        vec2<f32>( -1.0, -1.0 ),
        vec2<f32>(  1.0, -1.0 )
    );

    var tex = array< vec2<f32>, 4 >(
        vec2<f32>( 0.0, 0.0 ),
        vec2<f32>( 1.0, 0.0 ),
        vec2<f32>( 0.0, 1.0 ),
        vec2<f32>( 1.0, 1.0 )
    );

    Varys.vTex = tex[ vertexIndex ];
    Varys.Position = vec4<f32>( pos[ vertexIndex ], 0.0, 1.0 );

    return Varys;

}
`;

        const mipmapFragmentSource = `
@group( 0 ) @binding( 0 )
var imgSampler : sampler;

@group( 0 ) @binding( 1 )
var img : texture_2d<f32>;

@fragment
fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {

    return textureSample( img, imgSampler, vTex );

}
`;

        const flipYFragmentSource = `
@group( 0 ) @binding( 0 )
var imgSampler : sampler;

@group( 0 ) @binding( 1 )
var img : texture_2d<f32>;

@fragment
fn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {

    return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );

}
`;

        /**
         * The mipmap GPU sampler.
         *
         * @type {GPUSampler}
         */
        this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } );

        /**
         * The flipY GPU sampler.
         *
         * @type {GPUSampler}
         */
        this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad()

        /**
         * A cache for GPU render pipelines used for copy/transfer passes.
         * Every texture format requires a unique pipeline.
         *
         * @type {Object<string,GPURenderPipeline>}
         */
        this.transferPipelines = {};

        /**
         * A cache for GPU render pipelines used for flipY passes.
         * Every texture format requires a unique pipeline.
         *
         * @type {Object<string,GPURenderPipeline>}
         */
        this.flipYPipelines = {};

        /**
         * The mipmap vertex shader module.
         *
         * @type {GPUShaderModule}
         */
        this.mipmapVertexShaderModule = device.createShaderModule( {
            label: 'mipmapVertex',
            code: mipmapVertexSource
        } );

        /**
         * The mipmap fragment shader module.
         *
         * @type {GPUShaderModule}
         */
        this.mipmapFragmentShaderModule = device.createShaderModule( {
            label: 'mipmapFragment',
            code: mipmapFragmentSource
        } );

        /**
         * The flipY fragment shader module.
         *
         * @type {GPUShaderModule}
         */
        this.flipYFragmentShaderModule = device.createShaderModule( {
            label: 'flipYFragment',
            code: flipYFragmentSource
        } );

    }

    /**
     * Returns a render pipeline for the internal copy render pass. The pass
     * requires a unique render pipeline for each texture format.
     *
     * @param {string} format - The GPU texture format
     * @return {GPURenderPipeline} The GPU render pipeline.
     */
    getTransferPipeline( format ) {

        let pipeline = this.transferPipelines[ format ];

        if ( pipeline === undefined ) {

            pipeline = this.device.createRenderPipeline( {
                label: `mipmap-${ format }`,
                vertex: {
                    module: this.mipmapVertexShaderModule,
                    entryPoint: 'main'
                },
                fragment: {
                    module: this.mipmapFragmentShaderModule,
                    entryPoint: 'main',
                    targets: [ { format } ]
                },
                primitive: {
                    topology: GPUPrimitiveTopology.TriangleStrip,
                    stripIndexFormat: GPUIndexFormat.Uint32
                },
                layout: 'auto'
            } );

            this.transferPipelines[ format ] = pipeline;

        }

        return pipeline;

    }

    /**
     * Returns a render pipeline for the flipY render pass. The pass
     * requires a unique render pipeline for each texture format.
     *
     * @param {string} format - The GPU texture format
     * @return {GPURenderPipeline} The GPU render pipeline.
     */
    getFlipYPipeline( format ) {

        let pipeline = this.flipYPipelines[ format ];

        if ( pipeline === undefined ) {

            pipeline = this.device.createRenderPipeline( {
                label: `flipY-${ format }`,
                vertex: {
                    module: this.mipmapVertexShaderModule,
                    entryPoint: 'main'
                },
                fragment: {
                    module: this.flipYFragmentShaderModule,
                    entryPoint: 'main',
                    targets: [ { format } ]
                },
                primitive: {
                    topology: GPUPrimitiveTopology.TriangleStrip,
                    stripIndexFormat: GPUIndexFormat.Uint32
                },
                layout: 'auto'
            } );

            this.flipYPipelines[ format ] = pipeline;

        }

        return pipeline;

    }

    /**
     * Flip the contents of the given GPU texture along its vertical axis.
     *
     * @param {GPUTexture} textureGPU - The GPU texture object.
     * @param {Object} textureGPUDescriptor - The texture descriptor.
     * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view.
     */
    flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {

        const format = textureGPUDescriptor.format;
        const { width, height } = textureGPUDescriptor.size;

        const transferPipeline = this.getTransferPipeline( format );
        const flipYPipeline = this.getFlipYPipeline( format );

        const tempTexture = this.device.createTexture( {
            size: { width, height, depthOrArrayLayers: 1 },
            format,
            usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
        } );

        const srcView = textureGPU.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer
        } );

        const dstView = tempTexture.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer: 0
        } );

        const commandEncoder = this.device.createCommandEncoder( {} );

        const pass = ( pipeline, sourceView, destinationView ) => {

            const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.

            const bindGroup = this.device.createBindGroup( {
                layout: bindGroupLayout,
                entries: [ {
                    binding: 0,
                    resource: this.flipYSampler
                }, {
                    binding: 1,
                    resource: sourceView
                } ]
            } );

            const passEncoder = commandEncoder.beginRenderPass( {
                colorAttachments: [ {
                    view: destinationView,
                    loadOp: GPULoadOp.Clear,
                    storeOp: GPUStoreOp.Store,
                    clearValue: [ 0, 0, 0, 0 ]
                } ]
            } );

            passEncoder.setPipeline( pipeline );
            passEncoder.setBindGroup( 0, bindGroup );
            passEncoder.draw( 4, 1, 0, 0 );
            passEncoder.end();

        };

        pass( transferPipeline, srcView, dstView );
        pass( flipYPipeline, dstView, srcView );

        this.device.queue.submit( [ commandEncoder.finish() ] );

        tempTexture.destroy();

    }

    /**
     * Generates mipmaps for the given GPU texture.
     *
     * @param {GPUTexture} textureGPU - The GPU texture object.
     * @param {Object} textureGPUDescriptor - The texture descriptor.
     * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view.
     */
    generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {

        const textureData = this.get( textureGPU );

        if ( textureData.useCount === undefined ) {

            textureData.useCount = 0;
            textureData.layers = [];

        }

        const passes = textureData.layers[ baseArrayLayer ] || this._mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer );

        const commandEncoder = this.device.createCommandEncoder( {} );

        this._mipmapRunBundles( commandEncoder, passes );

        this.device.queue.submit( [ commandEncoder.finish() ] );

        if ( textureData.useCount !== 0 ) textureData.layers[ baseArrayLayer ] = passes;

        textureData.useCount ++;

    }

    /**
     * Since multiple copy render passes are required to generate mipmaps, the passes
     * are managed as render bundles to improve performance.
     *
     * @param {GPUTexture} textureGPU - The GPU texture object.
     * @param {Object} textureGPUDescriptor - The texture descriptor.
     * @param {number} baseArrayLayer - The index of the first array layer accessible to the texture view.
     * @return {Array<Object>} An array of render bundles.
     */
    _mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ) {

        const pipeline = this.getTransferPipeline( textureGPUDescriptor.format );

        const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.

        let srcView = textureGPU.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer
        } );

        const passes = [];

        for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {

            const bindGroup = this.device.createBindGroup( {
                layout: bindGroupLayout,
                entries: [ {
                    binding: 0,
                    resource: this.mipmapSampler
                }, {
                    binding: 1,
                    resource: srcView
                } ]
            } );

            const dstView = textureGPU.createView( {
                baseMipLevel: i,
                mipLevelCount: 1,
                dimension: GPUTextureViewDimension.TwoD,
                baseArrayLayer
            } );

            const passDescriptor = {
                colorAttachments: [ {
                    view: dstView,
                    loadOp: GPULoadOp.Clear,
                    storeOp: GPUStoreOp.Store,
                    clearValue: [ 0, 0, 0, 0 ]
                } ]
            };

            const passEncoder = this.device.createRenderBundleEncoder( {
                colorFormats: [ textureGPUDescriptor.format ]
            } );

            passEncoder.setPipeline( pipeline );
            passEncoder.setBindGroup( 0, bindGroup );
            passEncoder.draw( 4, 1, 0, 0 );

            passes.push( {
                renderBundles: [ passEncoder.finish() ],
                passDescriptor
            } );

            srcView = dstView;

        }

        return passes;

    }

    /**
     * Executes the render bundles.
     *
     * @param {GPUCommandEncoder} commandEncoder - The GPU command encoder.
     * @param {Array<Object>} passes - An array of render bundles.
     */
    _mipmapRunBundles( commandEncoder, passes ) {

        const levels = passes.length;

        for ( let i = 0; i < levels; i ++ ) {

            const pass = passes[ i ];

            const passEncoder = commandEncoder.beginRenderPass( pass.passDescriptor );

            passEncoder.executeBundles( pass.renderBundles );

            passEncoder.end();

        }

    }

}

Methods

getTransferPipeline(format: string): GPURenderPipeline
Code
getTransferPipeline( format ) {

        let pipeline = this.transferPipelines[ format ];

        if ( pipeline === undefined ) {

            pipeline = this.device.createRenderPipeline( {
                label: `mipmap-${ format }`,
                vertex: {
                    module: this.mipmapVertexShaderModule,
                    entryPoint: 'main'
                },
                fragment: {
                    module: this.mipmapFragmentShaderModule,
                    entryPoint: 'main',
                    targets: [ { format } ]
                },
                primitive: {
                    topology: GPUPrimitiveTopology.TriangleStrip,
                    stripIndexFormat: GPUIndexFormat.Uint32
                },
                layout: 'auto'
            } );

            this.transferPipelines[ format ] = pipeline;

        }

        return pipeline;

    }
getFlipYPipeline(format: string): GPURenderPipeline
Code
getFlipYPipeline( format ) {

        let pipeline = this.flipYPipelines[ format ];

        if ( pipeline === undefined ) {

            pipeline = this.device.createRenderPipeline( {
                label: `flipY-${ format }`,
                vertex: {
                    module: this.mipmapVertexShaderModule,
                    entryPoint: 'main'
                },
                fragment: {
                    module: this.flipYFragmentShaderModule,
                    entryPoint: 'main',
                    targets: [ { format } ]
                },
                primitive: {
                    topology: GPUPrimitiveTopology.TriangleStrip,
                    stripIndexFormat: GPUIndexFormat.Uint32
                },
                layout: 'auto'
            } );

            this.flipYPipelines[ format ] = pipeline;

        }

        return pipeline;

    }
flipY(textureGPU: GPUTexture, textureGPUDescriptor: any, baseArrayLayer: number): void
Code
flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {

        const format = textureGPUDescriptor.format;
        const { width, height } = textureGPUDescriptor.size;

        const transferPipeline = this.getTransferPipeline( format );
        const flipYPipeline = this.getFlipYPipeline( format );

        const tempTexture = this.device.createTexture( {
            size: { width, height, depthOrArrayLayers: 1 },
            format,
            usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
        } );

        const srcView = textureGPU.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer
        } );

        const dstView = tempTexture.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer: 0
        } );

        const commandEncoder = this.device.createCommandEncoder( {} );

        const pass = ( pipeline, sourceView, destinationView ) => {

            const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.

            const bindGroup = this.device.createBindGroup( {
                layout: bindGroupLayout,
                entries: [ {
                    binding: 0,
                    resource: this.flipYSampler
                }, {
                    binding: 1,
                    resource: sourceView
                } ]
            } );

            const passEncoder = commandEncoder.beginRenderPass( {
                colorAttachments: [ {
                    view: destinationView,
                    loadOp: GPULoadOp.Clear,
                    storeOp: GPUStoreOp.Store,
                    clearValue: [ 0, 0, 0, 0 ]
                } ]
            } );

            passEncoder.setPipeline( pipeline );
            passEncoder.setBindGroup( 0, bindGroup );
            passEncoder.draw( 4, 1, 0, 0 );
            passEncoder.end();

        };

        pass( transferPipeline, srcView, dstView );
        pass( flipYPipeline, dstView, srcView );

        this.device.queue.submit( [ commandEncoder.finish() ] );

        tempTexture.destroy();

    }
generateMipmaps(textureGPU: GPUTexture, textureGPUDescriptor: any, baseArrayLayer: number): void
Code
generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) {

        const textureData = this.get( textureGPU );

        if ( textureData.useCount === undefined ) {

            textureData.useCount = 0;
            textureData.layers = [];

        }

        const passes = textureData.layers[ baseArrayLayer ] || this._mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer );

        const commandEncoder = this.device.createCommandEncoder( {} );

        this._mipmapRunBundles( commandEncoder, passes );

        this.device.queue.submit( [ commandEncoder.finish() ] );

        if ( textureData.useCount !== 0 ) textureData.layers[ baseArrayLayer ] = passes;

        textureData.useCount ++;

    }
_mipmapCreateBundles(textureGPU: GPUTexture, textureGPUDescriptor: any, baseArrayLayer: number): any[]
Code
_mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ) {

        const pipeline = this.getTransferPipeline( textureGPUDescriptor.format );

        const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static.

        let srcView = textureGPU.createView( {
            baseMipLevel: 0,
            mipLevelCount: 1,
            dimension: GPUTextureViewDimension.TwoD,
            baseArrayLayer
        } );

        const passes = [];

        for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) {

            const bindGroup = this.device.createBindGroup( {
                layout: bindGroupLayout,
                entries: [ {
                    binding: 0,
                    resource: this.mipmapSampler
                }, {
                    binding: 1,
                    resource: srcView
                } ]
            } );

            const dstView = textureGPU.createView( {
                baseMipLevel: i,
                mipLevelCount: 1,
                dimension: GPUTextureViewDimension.TwoD,
                baseArrayLayer
            } );

            const passDescriptor = {
                colorAttachments: [ {
                    view: dstView,
                    loadOp: GPULoadOp.Clear,
                    storeOp: GPUStoreOp.Store,
                    clearValue: [ 0, 0, 0, 0 ]
                } ]
            };

            const passEncoder = this.device.createRenderBundleEncoder( {
                colorFormats: [ textureGPUDescriptor.format ]
            } );

            passEncoder.setPipeline( pipeline );
            passEncoder.setBindGroup( 0, bindGroup );
            passEncoder.draw( 4, 1, 0, 0 );

            passes.push( {
                renderBundles: [ passEncoder.finish() ],
                passDescriptor
            } );

            srcView = dstView;

        }

        return passes;

    }
_mipmapRunBundles(commandEncoder: GPUCommandEncoder, passes: any[]): void
Code
_mipmapRunBundles( commandEncoder, passes ) {

        const levels = passes.length;

        for ( let i = 0; i < levels; i ++ ) {

            const pass = passes[ i ];

            const passEncoder = commandEncoder.beginRenderPass( pass.passDescriptor );

            passEncoder.executeBundles( pass.renderBundles );

            passEncoder.end();

        }

    }