Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft. flo-bit.dev/shapecraft
Something went wrong. Try again.
7.8 kB · 342 lines
JavaScript
at commit da69385e
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343import DataMap from '../../common/DataMap.js';import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './WebGPUConstants.js';
class WebGPUTexturePassUtils extends DataMap {
constructor( device ) {
super();
this.device = device;
const mipmapVertexSource = `struct VarysStruct { @builtin( position ) Position: vec4<f32>, @location( 0 ) vTex : vec2<f32>};
@vertexfn 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>;
@fragmentfn 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>;
@fragmentfn main( @location( 0 ) vTex : vec2<f32> ) -> @location( 0 ) vec4<f32> {
return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );
}`; this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } ); this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad()
// We'll need a new pipeline for every texture format used. this.transferPipelines = {}; this.flipYPipelines = {};
this.mipmapVertexShaderModule = device.createShaderModule( { label: 'mipmapVertex', code: mipmapVertexSource } );
this.mipmapFragmentShaderModule = device.createShaderModule( { label: 'mipmapFragment', code: mipmapFragmentSource } );
this.flipYFragmentShaderModule = device.createShaderModule( { label: 'flipYFragment', code: flipYFragmentSource } );
}
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 ) {
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, 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, 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, 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, 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();
}
}
}
export default WebGPUTexturePassUtils;