-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Support for indirect draw calls for WebGPU #7777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
236ce73
Support for indirect draw calls for WebGPU
57b1796
types
8d88260
done
b52ce7f
rename
e360261
Update src/platform/graphics/graphics-device.js
mvaligursky c475cf5
comment
204d581
text
f950822
updated
e6ba83e
mesh instance - better docs on instancing and indirect drewing
d0d33af
Update src/scene/mesh-instance.js
mvaligursky d186eb7
Update src/scene/mesh-instance.js
mvaligursky 216ed59
Update src/scene/mesh-instance.js
mvaligursky File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
37 changes: 37 additions & 0 deletions
37
examples/src/examples/compute/indirect-draw.compute-shader.wgsl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Indexed draw call parameters | ||
struct DrawIndexedIndirectArgs { | ||
indexCount: u32, | ||
instanceCount: u32, | ||
firstIndex: u32, | ||
baseVertex: i32, | ||
firstInstance: u32 | ||
}; | ||
|
||
// Binding 0: uniform buffer holding draw call metadata and runtime config | ||
struct Uniforms { | ||
indirectMetaData: vec4i, // .x = indexCount, .y = firstIndex, .z = baseVertex | ||
time: f32, // current time in seconds | ||
maxInstanceCount: u32, // max number of instances | ||
indirectSlot: u32 // index into indirectDrawBuffer | ||
}; | ||
@group(0) @binding(0) var<uniform> uniforms: Uniforms; | ||
|
||
// Binding 1: storage buffer to write draw args | ||
@group(0) @binding(1) var<storage, read_write> indirectDrawBuffer: array<DrawIndexedIndirectArgs>; | ||
|
||
@compute @workgroup_size(1) | ||
fn main(@builtin(global_invocation_id) gid: vec3u) { | ||
let metaData = uniforms.indirectMetaData; | ||
|
||
// Generate oscillating instance count using a sine wave | ||
let wave = abs(sin(uniforms.time)); | ||
let visibleCount = u32(wave * f32(uniforms.maxInstanceCount)); | ||
|
||
// generate draw call parameters based on metadata. Supply computed number of instances. | ||
let index = uniforms.indirectSlot; | ||
indirectDrawBuffer[index].indexCount = u32(metaData.x); | ||
indirectDrawBuffer[index].instanceCount = visibleCount; | ||
indirectDrawBuffer[index].firstIndex = u32(metaData.y); | ||
indirectDrawBuffer[index].baseVertex = metaData.z; | ||
indirectDrawBuffer[index].firstInstance = 0u; | ||
} |
190 changes: 190 additions & 0 deletions
190
examples/src/examples/compute/indirect-draw.example.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
// @config DESCRIPTION This example shows a basic usage of indirect drawing, and the compute shader changes the number of instances that are rendered. | ||
// @config WEBGL_DISABLED | ||
import files from 'examples/files'; | ||
import { deviceType, rootPath } from 'examples/utils'; | ||
import * as pc from 'playcanvas'; | ||
|
||
const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); | ||
window.focus(); | ||
|
||
const assets = { | ||
helipad: new pc.Asset( | ||
'helipad-env-atlas', | ||
'texture', | ||
{ url: `${rootPath}/static/assets/cubemaps/helipad-env-atlas.png` }, | ||
{ type: pc.TEXTURETYPE_RGBP, mipmaps: false } | ||
) | ||
}; | ||
|
||
const gfxOptions = { | ||
deviceTypes: [deviceType] | ||
}; | ||
|
||
const device = await pc.createGraphicsDevice(canvas, gfxOptions); | ||
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); | ||
|
||
const createOptions = new pc.AppOptions(); | ||
createOptions.graphicsDevice = device; | ||
|
||
createOptions.componentSystems = [pc.RenderComponentSystem, pc.CameraComponentSystem]; | ||
createOptions.resourceHandlers = [pc.TextureHandler]; | ||
|
||
const app = new pc.AppBase(canvas); | ||
app.init(createOptions); | ||
|
||
// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size | ||
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); | ||
app.setCanvasResolution(pc.RESOLUTION_AUTO); | ||
|
||
// Ensure canvas is resized when window changes size | ||
const resize = () => app.resizeCanvas(); | ||
window.addEventListener('resize', resize); | ||
app.on('destroy', () => { | ||
window.removeEventListener('resize', resize); | ||
}); | ||
|
||
const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); | ||
assetListLoader.load(() => { | ||
app.start(); | ||
|
||
// setup skydome | ||
app.scene.skyboxMip = 2; | ||
app.scene.exposure = 0.7; | ||
app.scene.envAtlas = assets.helipad.resource; | ||
|
||
// Create an Entity with a camera component | ||
const camera = new pc.Entity(); | ||
camera.addComponent('camera', { | ||
toneMapping: pc.TONEMAP_ACES | ||
}); | ||
app.root.addChild(camera); | ||
camera.translate(0, 0, 10); | ||
|
||
// create standard material that will be used on the instanced spheres | ||
const material = new pc.StandardMaterial(); | ||
material.diffuse = new pc.Color(1, 1, 0.5); | ||
material.gloss = 1; | ||
material.metalness = 1; | ||
material.useMetalness = true; | ||
material.update(); | ||
|
||
// Create a Entity with a sphere render component and the material | ||
const sphere = new pc.Entity('InstancingEntity'); | ||
sphere.addComponent('render', { | ||
material: material, | ||
type: 'sphere' | ||
}); | ||
app.root.addChild(sphere); | ||
|
||
// number of instances to render | ||
const instanceCount = 1000; | ||
|
||
// store matrices for individual instances into array | ||
const matrices = new Float32Array(instanceCount * 16); | ||
let matrixIndex = 0; | ||
|
||
const radius = 5; | ||
const pos = new pc.Vec3(); | ||
const rot = new pc.Quat(); | ||
const scl = new pc.Vec3(); | ||
const matrix = new pc.Mat4(); | ||
|
||
for (let i = 0; i < instanceCount; i++) { | ||
// generate positions / scales and rotations | ||
pos.set( | ||
Math.random() * radius - radius * 0.5, | ||
Math.random() * radius - radius * 0.5, | ||
Math.random() * radius - radius * 0.5 | ||
); | ||
scl.set(0.2, 0.2, 0.2); | ||
rot.setFromEulerAngles(0, 0, 0); | ||
matrix.setTRS(pos, rot, scl); | ||
|
||
// copy matrix elements into array of floats | ||
for (let m = 0; m < 16; m++) matrices[matrixIndex++] = matrix.data[m]; | ||
} | ||
|
||
// create static vertex buffer containing the matrices | ||
const vbFormat = pc.VertexFormat.getDefaultInstancingFormat(app.graphicsDevice); | ||
const vertexBuffer = new pc.VertexBuffer(app.graphicsDevice, vbFormat, instanceCount, { | ||
data: matrices | ||
}); | ||
|
||
// initialize instancing using the vertex buffer on meshInstance of the created sphere | ||
const sphereMeshInst = sphere.render.meshInstances[0]; | ||
sphereMeshInst.setInstancing(vertexBuffer); | ||
|
||
// create a compute shader which will be used to update the number of instances to be rendered each frame | ||
const shader = device.supportsCompute ? | ||
new pc.Shader(device, { | ||
name: 'ComputeShader', | ||
shaderLanguage: pc.SHADERLANGUAGE_WGSL, | ||
cshader: files['compute-shader.wgsl'], | ||
|
||
// format of a uniform buffer used by the compute shader | ||
computeUniformBufferFormats: { | ||
ub: new pc.UniformBufferFormat(device, [ | ||
|
||
// metadata about the mesh (how many indicies it has and similar, used to generate draw call parameters) | ||
new pc.UniformFormat('indirectMetaData', pc.UNIFORMTYPE_IVEC4), | ||
|
||
// time to animate number of visible instances | ||
new pc.UniformFormat('time', pc.UNIFORMTYPE_FLOAT), | ||
|
||
// maximum number of instances | ||
new pc.UniformFormat('maxInstanceCount', pc.UNIFORMTYPE_UINT), | ||
|
||
// indirect slot into storage buffer which stored draw call parameters | ||
new pc.UniformFormat('indirectSlot', pc.UNIFORMTYPE_UINT) | ||
]) | ||
}, | ||
|
||
// format of a bind group, providing resources for the compute shader | ||
computeBindGroupFormat: new pc.BindGroupFormat(device, [ | ||
// a uniform buffer we provided format for | ||
new pc.BindUniformBufferFormat('ub', pc.SHADERSTAGE_COMPUTE), | ||
|
||
// the buffer with indirect draw arguments | ||
new pc.BindStorageBufferFormat('indirectDrawBuffer', pc.SHADERSTAGE_COMPUTE) | ||
]) | ||
}) : | ||
null; | ||
|
||
// Create an instance of the compute shader, and provide it with uniform values that do not change each frame | ||
const compute = new pc.Compute(device, shader, 'ComputeModifyVB'); | ||
compute.setParameter('maxInstanceCount', instanceCount); | ||
compute.setParameter('indirectMetaData', sphereMeshInst.getIndirectMetaData()); | ||
|
||
// Set an update function on the app's update event | ||
let angle = 0; | ||
let time = 0; | ||
app.on('update', (dt) => { | ||
time += dt; | ||
|
||
// obtain available slot in the indirect draw buffer - this needs to be done each frame | ||
const indirectSlot = app.graphicsDevice.getIndirectDrawSlot(); | ||
|
||
// and assign it to the mesh instance for all cameras (null parameter) | ||
sphereMeshInst.setIndirect(null, indirectSlot); | ||
|
||
// give compute shader the indirect draw buffer - this can change between frames, so assign it each frame | ||
compute.setParameter('indirectDrawBuffer', app.graphicsDevice.indirectDrawBuffer); | ||
|
||
// update compute shader parameters | ||
compute.setParameter('time', time); | ||
compute.setParameter('indirectSlot', indirectSlot); | ||
|
||
// set up the compute dispatch | ||
compute.setupDispatch(1); | ||
|
||
// dispatch the compute shader | ||
device.computeDispatch([compute], 'ComputeIndirectDraw'); | ||
|
||
// orbit camera around | ||
angle += dt * 0.2; | ||
camera.setLocalPosition(8 * Math.sin(angle), 0, 8 * Math.cos(angle)); | ||
camera.lookAt(pc.Vec3.ZERO); | ||
}); | ||
}); | ||
|
||
export { app }; |
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.