WebGPUBackend.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. import { GPUFeatureName, GPUTextureFormat, GPULoadOp, GPUStoreOp, GPUIndexFormat } 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. depthStencilAttachment.view = depthTextureData.texture.createView();
  94. if ( renderContext.stencil && renderContext.depthTexture.format === DepthFormat ) {
  95. renderContext.stencil = false;
  96. }
  97. } else {
  98. if ( antialias === true ) {
  99. colorAttachment.view = this.colorBuffer.createView();
  100. colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
  101. } else {
  102. colorAttachment.view = this.context.getCurrentTexture().createView();
  103. colorAttachment.resolveTarget = undefined;
  104. }
  105. depthStencilAttachment.view = this.depthBuffer.createView();
  106. }
  107. if ( renderContext.clearColor ) {
  108. colorAttachment.clearValue = renderContext.clearColorValue;
  109. colorAttachment.loadOp = GPULoadOp.Clear;
  110. colorAttachment.storeOp = GPUStoreOp.Store;
  111. } else {
  112. colorAttachment.loadOp = GPULoadOp.Load;
  113. colorAttachment.storeOp = GPUStoreOp.Store;
  114. }
  115. //
  116. if ( renderContext.depth ) {
  117. if ( renderContext.clearDepth ) {
  118. depthStencilAttachment.depthClearValue = renderContext.clearDepthValue;
  119. depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
  120. depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
  121. } else {
  122. depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
  123. depthStencilAttachment.depthStoreOp = GPUStoreOp.Store;
  124. }
  125. }
  126. if ( renderContext.stencil ) {
  127. if ( renderContext.clearStencil ) {
  128. depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue;
  129. depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
  130. depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
  131. } else {
  132. depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
  133. depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store;
  134. }
  135. }
  136. //
  137. const encoder = device.createCommandEncoder( { label: 'renderContext_' + renderContext.id } );
  138. const currentPass = encoder.beginRenderPass( descriptor );
  139. //
  140. renderContextData.descriptor = descriptor;
  141. renderContextData.encoder = encoder;
  142. renderContextData.currentPass = currentPass;
  143. //
  144. if ( renderContext.viewport ) {
  145. this.updateViewport( renderContext );
  146. }
  147. if ( renderContext.scissor ) {
  148. const { x, y, width, height } = renderContext.scissorValue;
  149. currentPass.setScissorRect( x, y, width, height );
  150. }
  151. }
  152. finishRender( renderContext ) {
  153. const renderContextData = this.get( renderContext );
  154. renderContextData.currentPass.end();
  155. this.device.queue.submit( [ renderContextData.encoder.finish() ] );
  156. }
  157. updateViewport( renderContext ) {
  158. const { currentPass } = this.get( renderContext );
  159. const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue;
  160. currentPass.setViewport( x, y, width, height, minDepth, maxDepth );
  161. }
  162. clear( renderContext, color, depth, stencil ) {
  163. const device = this.device;
  164. const renderContextData = this.get( renderContext );
  165. const { descriptor } = renderContextData;
  166. depth = depth && renderContext.depth;
  167. stencil = stencil && renderContext.stencil;
  168. const colorAttachment = descriptor.colorAttachments[ 0 ];
  169. const antialias = this.parameters.antialias;
  170. // @TODO: Include render target in clear operation.
  171. if ( antialias === true ) {
  172. colorAttachment.view = this.colorBuffer.createView();
  173. colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
  174. } else {
  175. colorAttachment.view = this.context.getCurrentTexture().createView();
  176. colorAttachment.resolveTarget = undefined;
  177. }
  178. descriptor.depthStencilAttachment.view = this.depthBuffer.createView();
  179. if ( color ) {
  180. colorAttachment.loadOp = GPULoadOp.Clear;
  181. colorAttachment.clearValue = renderContext.clearColorValue;
  182. }
  183. if ( depth ) {
  184. descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Clear;
  185. descriptor.depthStencilAttachment.depthClearValue = renderContext.clearDepthValue;
  186. }
  187. if ( stencil ) {
  188. descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear;
  189. descriptor.depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue;
  190. }
  191. renderContextData.encoder = device.createCommandEncoder( {} );
  192. renderContextData.currentPass = renderContextData.encoder.beginRenderPass( descriptor );
  193. renderContextData.currentPass.end();
  194. device.queue.submit( [ renderContextData.encoder.finish() ] );
  195. }
  196. // compute
  197. beginCompute( computeGroup ) {
  198. const groupGPU = this.get( computeGroup );
  199. groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( {} );
  200. groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass();
  201. }
  202. compute( computeGroup, computeNode, bindings, pipeline ) {
  203. const { passEncoderGPU } = this.get( computeGroup );
  204. // pipeline
  205. const pipelineGPU = this.get( pipeline ).pipeline;
  206. passEncoderGPU.setPipeline( pipelineGPU );
  207. // bind group
  208. const bindGroupGPU = this.get( bindings ).group;
  209. passEncoderGPU.setBindGroup( 0, bindGroupGPU );
  210. passEncoderGPU.dispatchWorkgroups( computeNode.dispatchCount );
  211. }
  212. finishCompute( computeGroup ) {
  213. const groupData = this.get( computeGroup );
  214. groupData.passEncoderGPU.end();
  215. this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] );
  216. }
  217. // render object
  218. draw( renderObject, info ) {
  219. const { object, geometry, context, pipeline } = renderObject;
  220. const bindingsData = this.get( renderObject.getBindings() );
  221. const contextData = this.get( context );
  222. const pipelineGPU = this.get( pipeline ).pipeline;
  223. // pipeline
  224. const passEncoderGPU = contextData.currentPass;
  225. passEncoderGPU.setPipeline( pipelineGPU );
  226. // bind group
  227. const bindGroupGPU = bindingsData.group;
  228. passEncoderGPU.setBindGroup( 0, bindGroupGPU );
  229. // index
  230. const index = renderObject.getIndex();
  231. const hasIndex = ( index !== null );
  232. if ( hasIndex === true ) {
  233. const buffer = this.get( index ).buffer;
  234. const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32;
  235. passEncoderGPU.setIndexBuffer( buffer, indexFormat );
  236. }
  237. // vertex buffers
  238. const attributes = renderObject.getAttributes();
  239. for ( let i = 0, l = attributes.length; i < l; i ++ ) {
  240. const buffer = this.get( attributes[ i ] ).buffer;
  241. passEncoderGPU.setVertexBuffer( i, buffer );
  242. }
  243. // draw
  244. const drawRange = geometry.drawRange;
  245. const firstVertex = drawRange.start;
  246. const instanceCount = this.getInstanceCount( renderObject );
  247. if ( hasIndex === true ) {
  248. const indexCount = ( drawRange.count !== Infinity ) ? drawRange.count : index.count;
  249. passEncoderGPU.drawIndexed( indexCount, instanceCount, firstVertex, 0, 0 );
  250. info.update( object, indexCount, instanceCount );
  251. } else {
  252. const positionAttribute = geometry.attributes.position;
  253. const vertexCount = ( drawRange.count !== Infinity ) ? drawRange.count : positionAttribute.count;
  254. passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 );
  255. info.update( object, vertexCount, instanceCount );
  256. }
  257. }
  258. // cache key
  259. needsUpdate( renderObject ) {
  260. const renderObjectGPU = this.get( renderObject );
  261. const { object, material } = renderObject;
  262. const utils = this.utils;
  263. const sampleCount = utils.getSampleCount();
  264. const colorSpace = utils.getCurrentColorSpace( renderObject.context );
  265. const colorFormat = utils.getCurrentColorFormat( renderObject.context );
  266. const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
  267. const primitiveTopology = utils.getPrimitiveTopology( object, material );
  268. let needsUpdate = false;
  269. if ( renderObjectGPU.sampleCount !== sampleCount || renderObjectGPU.colorSpace !== colorSpace ||
  270. renderObjectGPU.colorFormat !== colorFormat || renderObjectGPU.depthStencilFormat !== depthStencilFormat ||
  271. renderObjectGPU.primitiveTopology !== primitiveTopology ) {
  272. renderObjectGPU.sampleCount = sampleCount;
  273. renderObjectGPU.colorSpace = colorSpace;
  274. renderObjectGPU.colorFormat = colorFormat;
  275. renderObjectGPU.depthStencilFormat = depthStencilFormat;
  276. renderObjectGPU.primitiveTopology = primitiveTopology;
  277. needsUpdate = true;
  278. }
  279. return needsUpdate;
  280. }
  281. getCacheKey( renderObject ) {
  282. const { object, material } = renderObject;
  283. const utils = this.utils;
  284. const renderContext = renderObject.context;
  285. return [
  286. utils.getSampleCount(),
  287. utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),
  288. utils.getPrimitiveTopology( object, material )
  289. ].join();
  290. }
  291. // textures
  292. createSampler( texture ) {
  293. this.textureUtils.createSampler( texture );
  294. }
  295. destroySampler( texture ) {
  296. this.textureUtils.destroySampler( texture );
  297. }
  298. createDefaultTexture( texture ) {
  299. this.textureUtils.createDefaultTexture( texture );
  300. }
  301. createTexture( texture ) {
  302. this.textureUtils.createTexture( texture );
  303. }
  304. updateTexture( texture ) {
  305. this.textureUtils.updateTexture( texture );
  306. }
  307. destroyTexture( texture ) {
  308. this.textureUtils.destroyTexture( texture );
  309. }
  310. // node builder
  311. createNodeBuilder( object, renderer ) {
  312. return new WebGPUNodeBuilder( object, renderer );
  313. }
  314. // program
  315. createProgram( program ) {
  316. const programGPU = this.get( program );
  317. programGPU.module = {
  318. module: this.device.createShaderModule( { code: program.code, label: program.stage } ),
  319. entryPoint: 'main'
  320. };
  321. }
  322. destroyProgram( program ) {
  323. this.delete( program );
  324. }
  325. // pipelines
  326. createRenderPipeline( renderObject ) {
  327. this.pipelineUtils.createRenderPipeline( renderObject );
  328. }
  329. createComputePipeline( computePipeline ) {
  330. this.pipelineUtils.createComputePipeline( computePipeline );
  331. }
  332. // bindings
  333. createBindings( bindings, pipeline ) {
  334. this.bindingUtils.createBindings( bindings, pipeline );
  335. }
  336. updateBindings( bindings, pipeline ) {
  337. this.bindingUtils.createBindings( bindings, pipeline );
  338. }
  339. updateBinding( binding ) {
  340. this.bindingUtils.updateBinding( binding );
  341. }
  342. // attributes
  343. createIndexAttribute( attribute ) {
  344. this.attributeUtils.createAttribute( attribute, GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
  345. }
  346. createAttribute( attribute ) {
  347. this.attributeUtils.createAttribute( attribute, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
  348. }
  349. createStorageAttribute( attribute ) {
  350. this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST );
  351. }
  352. updateAttribute( attribute ) {
  353. this.attributeUtils.updateAttribute( attribute );
  354. }
  355. destroyAttribute( attribute ) {
  356. this.attributeUtils.destroyAttribute( attribute );
  357. }
  358. // canvas
  359. updateSize() {
  360. this._configureContext();
  361. this._setupColorBuffer();
  362. this._setupDepthBuffer();
  363. }
  364. // utils public
  365. hasFeature( name ) {
  366. const adapter = this.adapter || _staticAdapter;
  367. //
  368. const features = Object.values( GPUFeatureName );
  369. if ( features.includes( name ) === false ) {
  370. throw new Error( 'THREE.WebGPURenderer: Unknown WebGPU GPU feature: ' + name );
  371. }
  372. //
  373. return adapter.features.has( name );
  374. }
  375. copyFramebufferToTexture( framebufferTexture, renderContext ) {
  376. const renderContextData = this.get( renderContext );
  377. const { encoder, descriptor } = renderContextData;
  378. const sourceGPU = this.context.getCurrentTexture();
  379. const destinationGPU = this.get( framebufferTexture ).texture;
  380. renderContextData.currentPass.end();
  381. encoder.copyTextureToTexture(
  382. {
  383. texture: sourceGPU
  384. },
  385. {
  386. texture: destinationGPU
  387. },
  388. [
  389. framebufferTexture.image.width,
  390. framebufferTexture.image.height
  391. ]
  392. );
  393. descriptor.colorAttachments[ 0 ].loadOp = GPULoadOp.Load;
  394. if ( renderContext.depth ) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load;
  395. if ( renderContext.stencil ) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load;
  396. renderContextData.currentPass = encoder.beginRenderPass( descriptor );
  397. }
  398. // utils
  399. _configureContext() {
  400. this.context.configure( {
  401. device: this.device,
  402. format: GPUTextureFormat.BGRA8Unorm,
  403. usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
  404. alphaMode: 'premultiplied'
  405. } );
  406. }
  407. _setupColorBuffer() {
  408. if ( this.colorBuffer ) this.colorBuffer.destroy();
  409. const { width, height } = this.getDrawingBufferSize();
  410. //const format = navigator.gpu.getPreferredCanvasFormat(); // @TODO: Move to WebGPUUtils
  411. this.colorBuffer = this.device.createTexture( {
  412. label: 'colorBuffer',
  413. size: {
  414. width: width,
  415. height: height,
  416. depthOrArrayLayers: 1
  417. },
  418. sampleCount: this.parameters.sampleCount,
  419. format: GPUTextureFormat.BGRA8Unorm,
  420. usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
  421. } );
  422. }
  423. _setupDepthBuffer() {
  424. if ( this.depthBuffer ) this.depthBuffer.destroy();
  425. const { width, height } = this.getDrawingBufferSize();
  426. this.depthBuffer = this.device.createTexture( {
  427. label: 'depthBuffer',
  428. size: {
  429. width: width,
  430. height: height,
  431. depthOrArrayLayers: 1
  432. },
  433. sampleCount: this.parameters.sampleCount,
  434. format: GPUTextureFormat.Depth24PlusStencil8,
  435. usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
  436. } );
  437. }
  438. }
  439. export default WebGPUBackend;