WebGPUBackend.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. import { GPUFeatureName, GPUTextureFormat, GPULoadOp, GPUStoreOp, GPUIndexFormat, GPUTextureViewDimension } from './utils/WebGPUConstants.js';
  2. import WebGPUNodeBuilder from './nodes/WGSLNodeBuilder.js';
  3. import Backend from '../common/Backend.js';
  4. import { DepthFormat, WebGPUCoordinateSystem } from 'three';
  5. import WebGPUUtils from './utils/WebGPUUtils.js';
  6. import WebGPUAttributeUtils from './utils/WebGPUAttributeUtils.js';
  7. import WebGPUBindingUtils from './utils/WebGPUBindingUtils.js';
  8. import WebGPUPipelineUtils from './utils/WebGPUPipelineUtils.js';
  9. import WebGPUTextureUtils from './utils/WebGPUTextureUtils.js';
  10. // statics
  11. let _staticAdapter = null;
  12. if ( navigator.gpu !== undefined ) {
  13. _staticAdapter = await navigator.gpu.requestAdapter();
  14. }
  15. //
  16. class WebGPUBackend extends Backend {
  17. constructor( parameters = {} ) {
  18. super( parameters );
  19. // some parameters require default values other than "undefined"
  20. this.parameters.antialias = ( parameters.antialias === true );
  21. if ( this.parameters.antialias === true ) {
  22. this.parameters.sampleCount = ( parameters.sampleCount === undefined ) ? 4 : parameters.sampleCount;
  23. } else {
  24. this.parameters.sampleCount = 1;
  25. }
  26. this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits;
  27. this.adapter = null;
  28. this.device = null;
  29. this.context = null;
  30. this.colorBuffer = null;
  31. this.depthBuffer = null;
  32. this.utils = new WebGPUUtils( this );
  33. this.attributeUtils = new WebGPUAttributeUtils( this );
  34. this.bindingUtils = new WebGPUBindingUtils( this );
  35. this.pipelineUtils = new WebGPUPipelineUtils( this );
  36. this.textureUtils = new WebGPUTextureUtils( this );
  37. }
  38. async init( renderer ) {
  39. await super.init( renderer );
  40. //
  41. const parameters = this.parameters;
  42. const adapterOptions = {
  43. powerPreference: parameters.powerPreference
  44. };
  45. const adapter = await navigator.gpu.requestAdapter( adapterOptions );
  46. if ( adapter === null ) {
  47. throw new Error( 'WebGPUBackend: Unable to create WebGPU adapter.' );
  48. }
  49. // feature support
  50. const features = Object.values( GPUFeatureName );
  51. const supportedFeatures = [];
  52. for ( const name of features ) {
  53. if ( adapter.features.has( name ) ) {
  54. supportedFeatures.push( name );
  55. }
  56. }
  57. const deviceDescriptor = {
  58. requiredFeatures: supportedFeatures,
  59. requiredLimits: parameters.requiredLimits
  60. };
  61. const device = await adapter.requestDevice( deviceDescriptor );
  62. const context = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgpu' );
  63. this.adapter = adapter;
  64. this.device = device;
  65. this.context = context;
  66. this.updateSize();
  67. }
  68. get coordinateSystem() {
  69. return WebGPUCoordinateSystem;
  70. }
  71. async getArrayBuffer( attribute ) {
  72. return await this.attributeUtils.getArrayBuffer( attribute );
  73. }
  74. beginRender( renderContext ) {
  75. const renderContextData = this.get( renderContext );
  76. const device = this.device;
  77. const descriptor = {
  78. colorAttachments: [ {
  79. view: null
  80. } ],
  81. depthStencilAttachment: {
  82. view: null
  83. }
  84. };
  85. const colorAttachment = descriptor.colorAttachments[ 0 ];
  86. const depthStencilAttachment = descriptor.depthStencilAttachment;
  87. const antialias = this.parameters.antialias;
  88. if ( renderContext.texture !== null ) {
  89. const textureData = this.get( renderContext.texture );
  90. const depthTextureData = this.get( renderContext.depthTexture );
  91. // @TODO: Support RenderTarget with antialiasing.
  92. colorAttachment.view = textureData.texture.createView( {
  93. baseMipLevel: 0,
  94. mipLevelCount: 1,
  95. baseArrayLayer: renderContext.activeCubeFace,
  96. dimension: GPUTextureViewDimension.TwoD
  97. } );
  98. depthStencilAttachment.view = depthTextureData.texture.createView();
  99. if ( renderContext.stencil && renderContext.depthTexture.format === DepthFormat ) {
  100. renderContext.stencil = false;
  101. }
  102. } else {
  103. if ( antialias === true ) {
  104. colorAttachment.view = this.colorBuffer.createView();
  105. colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
  106. } else {
  107. colorAttachment.view = this.context.getCurrentTexture().createView();
  108. colorAttachment.resolveTarget = undefined;
  109. }
  110. depthStencilAttachment.view = this.depthBuffer.createView();
  111. }
  112. if ( renderContext.clearColor ) {
  113. colorAttachment.clearValue = renderContext.clearColorValue;
  114. colorAttachment.loadOp = GPULoadOp.Clear;
  115. colorAttachment.storeOp = GPUStoreOp.Store;
  116. } else {
  117. colorAttachment.loadOp = GPULoadOp.Load;
  118. colorAttachment.storeOp = GPUStoreOp.Store;
  119. }
  120. //
  121. if ( renderContext.depth ) {
  122. if ( renderContext.clearDepth ) {
  123. depthStencilAttachment.depthClearValue = renderContext.clearDepthValue;
  124. depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
  125. depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
  126. } else {
  127. depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
  128. depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
  129. }
  130. }
  131. if ( renderContext.stencil ) {
  132. if ( renderContext.clearStencil ) {
  133. depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue;
  134. depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
  135. depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
  136. } else {
  137. depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
  138. depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
  139. }
  140. }
  141. //
  142. const encoder = device.createCommandEncoder( { label: 'renderContext_' + renderContext.id } );
  143. const currentPass = encoder.beginRenderPass( descriptor );
  144. //
  145. renderContextData.descriptor = descriptor;
  146. renderContextData.encoder = encoder;
  147. renderContextData.currentPass = currentPass;
  148. //
  149. if ( renderContext.viewport ) {
  150. this.updateViewport( renderContext );
  151. }
  152. if ( renderContext.scissor ) {
  153. const { x, y, width, height } = renderContext.scissorValue;
  154. currentPass.setScissorRect( x, y, width, height );
  155. }
  156. }
  157. finishRender( renderContext ) {
  158. const renderContextData = this.get( renderContext );
  159. renderContextData.currentPass.end();
  160. this.device.queue.submit( [ renderContextData.encoder.finish() ] );
  161. //
  162. if ( renderContext.texture !== null && renderContext.texture.generateMipmaps === true ) {
  163. this.textureUtils.generateMipmaps( renderContext.texture );
  164. }
  165. }
  166. updateViewport( renderContext ) {
  167. const { currentPass } = this.get( renderContext );
  168. const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue;
  169. currentPass.setViewport( x, y, width, height, minDepth, maxDepth );
  170. }
  171. clear( renderContext, color, depth, stencil ) {
  172. const device = this.device;
  173. const renderContextData = this.get( renderContext );
  174. const { descriptor } = renderContextData;
  175. depth = depth && renderContext.depth;
  176. stencil = stencil && renderContext.stencil;
  177. const colorAttachment = descriptor.colorAttachments[ 0 ];
  178. const antialias = this.parameters.antialias;
  179. // @TODO: Include render target in clear operation.
  180. if ( antialias === true ) {
  181. colorAttachment.view = this.colorBuffer.createView();
  182. colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
  183. } else {
  184. colorAttachment.view = this.context.getCurrentTexture().createView();
  185. colorAttachment.resolveTarget = undefined;
  186. }
  187. descriptor.depthStencilAttachment.view = this.depthBuffer.createView();
  188. if ( color ) {
  189. colorAttachment.loadOp = GPULoadOp.Clear;
  190. colorAttachment.clearValue = renderContext.clearColorValue;
  191. }
  192. if ( depth ) {
  193. descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
  194. descriptor.depthStencilAttachment.depthClearValue = renderContext.clearDepthValue;
  195. }
  196. if ( stencil ) {
  197. descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
  198. descriptor.depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue;
  199. }
  200. renderContextData.encoder = device.createCommandEncoder( {} );
  201. renderContextData.currentPass = renderContextData.encoder.beginRenderPass( descriptor );
  202. renderContextData.currentPass.end();
  203. device.queue.submit( [ renderContextData.encoder.finish() ] );
  204. }
  205. // compute
  206. beginCompute( computeGroup ) {
  207. const groupGPU = this.get( computeGroup );
  208. groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( {} );
  209. groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass();
  210. }
  211. compute( computeGroup, computeNode, bindings, pipeline ) {
  212. const { passEncoderGPU } = this.get( computeGroup );
  213. // pipeline
  214. const pipelineGPU = this.get( pipeline ).pipeline;
  215. passEncoderGPU.setPipeline( pipelineGPU );
  216. // bind group
  217. const bindGroupGPU = this.get( bindings ).group;
  218. passEncoderGPU.setBindGroup( 0, bindGroupGPU );
  219. passEncoderGPU.dispatchWorkgroups( computeNode.dispatchCount );
  220. }
  221. finishCompute( computeGroup ) {
  222. const groupData = this.get( computeGroup );
  223. groupData.passEncoderGPU.end();
  224. this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] );
  225. }
  226. // render object
  227. draw( renderObject, info ) {
  228. const { object, geometry, context, pipeline } = renderObject;
  229. const bindingsData = this.get( renderObject.getBindings() );
  230. const contextData = this.get( context );
  231. const pipelineGPU = this.get( pipeline ).pipeline;
  232. // pipeline
  233. const passEncoderGPU = contextData.currentPass;
  234. passEncoderGPU.setPipeline( pipelineGPU );
  235. // bind group
  236. const bindGroupGPU = bindingsData.group;
  237. passEncoderGPU.setBindGroup( 0, bindGroupGPU );
  238. // index
  239. const index = renderObject.getIndex();
  240. const hasIndex = ( index !== null );
  241. if ( hasIndex === true ) {
  242. const buffer = this.get( index ).buffer;
  243. const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
  244. passEncoderGPU.setIndexBuffer( buffer, indexFormat );
  245. }
  246. // vertex buffers
  247. const attributes = renderObject.getAttributes();
  248. for ( let i = 0, l = attributes.length; i < l; i ++ ) {
  249. const buffer = this.get( attributes[ i ] ).buffer;
  250. passEncoderGPU.setVertexBuffer( i, buffer );
  251. }
  252. // draw
  253. const drawRange = geometry.drawRange;
  254. const firstVertex = drawRange.start;
  255. const instanceCount = this.getInstanceCount( renderObject );
  256. if ( hasIndex === true ) {
  257. const indexCount = ( drawRange.count !== Infinity ) ? drawRange.count : index.count;
  258. passEncoderGPU.drawIndexed( indexCount, instanceCount, firstVertex, 0, 0 );
  259. info.update( object, indexCount, instanceCount );
  260. } else {
  261. const positionAttribute = geometry.attributes.position;
  262. const vertexCount = ( drawRange.count !== Infinity ) ? drawRange.count : positionAttribute.count;
  263. passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 );
  264. info.update( object, vertexCount, instanceCount );
  265. }
  266. }
  267. // cache key
  268. needsUpdate( renderObject ) {
  269. const renderObjectGPU = this.get( renderObject );
  270. const { object, material } = renderObject;
  271. const utils = this.utils;
  272. const sampleCount = utils.getSampleCount( renderObject.context );
  273. const colorSpace = utils.getCurrentColorSpace( renderObject.context );
  274. const colorFormat = utils.getCurrentColorFormat( renderObject.context );
  275. const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
  276. const primitiveTopology = utils.getPrimitiveTopology( object, material );
  277. let needsUpdate = false;
  278. if ( renderObjectGPU.sampleCount !== sampleCount || renderObjectGPU.colorSpace !== colorSpace ||
  279. renderObjectGPU.colorFormat !== colorFormat || renderObjectGPU.depthStencilFormat !== depthStencilFormat ||
  280. renderObjectGPU.primitiveTopology !== primitiveTopology ) {
  281. renderObjectGPU.sampleCount = sampleCount;
  282. renderObjectGPU.colorSpace = colorSpace;
  283. renderObjectGPU.colorFormat = colorFormat;
  284. renderObjectGPU.depthStencilFormat = depthStencilFormat;
  285. renderObjectGPU.primitiveTopology = primitiveTopology;
  286. needsUpdate = true;
  287. }
  288. return needsUpdate;
  289. }
  290. getCacheKey( renderObject ) {
  291. const { object, material } = renderObject;
  292. const utils = this.utils;
  293. const renderContext = renderObject.context;
  294. return [
  295. utils.getSampleCount( renderContext ),
  296. utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),
  297. utils.getPrimitiveTopology( object, material )
  298. ].join();
  299. }
  300. // textures
  301. createSampler( texture ) {
  302. this.textureUtils.createSampler( texture );
  303. }
  304. destroySampler( texture ) {
  305. this.textureUtils.destroySampler( texture );
  306. }
  307. createDefaultTexture( texture ) {
  308. this.textureUtils.createDefaultTexture( texture );
  309. }
  310. createTexture( texture ) {
  311. this.textureUtils.createTexture( texture );
  312. }
  313. updateTexture( texture ) {
  314. this.textureUtils.updateTexture( texture );
  315. }
  316. destroyTexture( texture ) {
  317. this.textureUtils.destroyTexture( texture );
  318. }
  319. // node builder
  320. createNodeBuilder( object, renderer ) {
  321. return new WebGPUNodeBuilder( object, renderer );
  322. }
  323. // program
  324. createProgram( program ) {
  325. const programGPU = this.get( program );
  326. programGPU.module = {
  327. module: this.device.createShaderModule( { code: program.code, label: program.stage } ),
  328. entryPoint: 'main'
  329. };
  330. }
  331. destroyProgram( program ) {
  332. this.delete( program );
  333. }
  334. // pipelines
  335. createRenderPipeline( renderObject ) {
  336. this.pipelineUtils.createRenderPipeline( renderObject );
  337. }
  338. createComputePipeline( computePipeline ) {
  339. this.pipelineUtils.createComputePipeline( computePipeline );
  340. }
  341. // bindings
  342. createBindings( bindings, pipeline ) {
  343. this.bindingUtils.createBindings( bindings, pipeline );
  344. }
  345. updateBindings( bindings, pipeline ) {
  346. this.bindingUtils.createBindings( bindings, pipeline );
  347. }
  348. updateBinding( binding ) {
  349. this.bindingUtils.updateBinding( binding );
  350. }
  351. // attributes
  352. createIndexAttribute( attribute ) {
  353. this.attributeUtils.createAttribute( attribute, GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
  354. }
  355. createAttribute( attribute ) {
  356. this.attributeUtils.createAttribute( attribute, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
  357. }
  358. createStorageAttribute( attribute ) {
  359. this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
  360. }
  361. updateAttribute( attribute ) {
  362. this.attributeUtils.updateAttribute( attribute );
  363. }
  364. destroyAttribute( attribute ) {
  365. this.attributeUtils.destroyAttribute( attribute );
  366. }
  367. // canvas
  368. updateSize() {
  369. this._configureContext();
  370. this._setupColorBuffer();
  371. this._setupDepthBuffer();
  372. }
  373. // utils public
  374. hasFeature( name ) {
  375. const adapter = this.adapter || _staticAdapter;
  376. //
  377. const features = Object.values( GPUFeatureName );
  378. if ( features.includes( name ) === false ) {
  379. throw new Error( 'THREE.WebGPURenderer: Unknown WebGPU GPU feature: ' + name );
  380. }
  381. //
  382. return adapter.features.has( name );
  383. }
  384. copyFramebufferToTexture( framebufferTexture, renderContext ) {
  385. const renderContextData = this.get( renderContext );
  386. const { encoder, descriptor } = renderContextData;
  387. const sourceGPU = this.context.getCurrentTexture();
  388. const destinationGPU = this.get( framebufferTexture ).texture;
  389. renderContextData.currentPass.end();
  390. encoder.copyTextureToTexture(
  391. {
  392. texture: sourceGPU
  393. },
  394. {
  395. texture: destinationGPU
  396. },
  397. [
  398. framebufferTexture.image.width,
  399. framebufferTexture.image.height
  400. ]
  401. );
  402. descriptor.colorAttachments[ 0 ].loadOp = GPULoadOp.Load;
  403. if ( renderContext.depth ) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
  404. if ( renderContext.stencil ) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
  405. renderContextData.currentPass = encoder.beginRenderPass( descriptor );
  406. }
  407. // utils
  408. _configureContext() {
  409. this.context.configure( {
  410. device: this.device,
  411. format: GPUTextureFormat.BGRA8Unorm,
  412. usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
  413. alphaMode: 'premultiplied'
  414. } );
  415. }
  416. _setupColorBuffer() {
  417. if ( this.colorBuffer ) this.colorBuffer.destroy();
  418. const { width, height } = this.getDrawingBufferSize();
  419. //const format = navigator.gpu.getPreferredCanvasFormat(); // @TODO: Move to WebGPUUtils
  420. this.colorBuffer = this.device.createTexture( {
  421. label: 'colorBuffer',
  422. size: {
  423. width: width,
  424. height: height,
  425. depthOrArrayLayers: 1
  426. },
  427. sampleCount: this.parameters.sampleCount,
  428. format: GPUTextureFormat.BGRA8Unorm,
  429. usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
  430. } );
  431. }
  432. _setupDepthBuffer() {
  433. if ( this.depthBuffer ) this.depthBuffer.destroy();
  434. const { width, height } = this.getDrawingBufferSize();
  435. this.depthBuffer = this.device.createTexture( {
  436. label: 'depthBuffer',
  437. size: {
  438. width: width,
  439. height: height,
  440. depthOrArrayLayers: 1
  441. },
  442. sampleCount: this.parameters.sampleCount,
  443. format: GPUTextureFormat.Depth24PlusStencil8,
  444. usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
  445. } );
  446. }
  447. }
  448. export default WebGPUBackend;