-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Compute shaders can read / write index and vertex buffers #6226
Merged
Merged
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
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
This file contains 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,53 @@ | ||
/** | ||
* @type {import('../../../../types.mjs').ExampleConfig} | ||
*/ | ||
export default { | ||
HIDDEN: true, | ||
WEBGPU_REQUIRED: true, | ||
FILES: { | ||
'compute-shader.wgsl': /* wgsl */ ` | ||
|
||
struct ub_compute { | ||
count: u32, // number of vertices | ||
positionOffset: u32, // offset of the vertex positions in the vertex buffer | ||
normalOffset: u32, // offset of the vertex normals in the vertex buffer | ||
time: f32 // time | ||
} | ||
|
||
// uniform buffer | ||
@group(0) @binding(0) var<uniform> ubCompute : ub_compute; | ||
|
||
// vertex buffer | ||
@group(0) @binding(1) var<storage, read_write> vertices: array<f32>; | ||
|
||
@compute @workgroup_size(64) | ||
fn main(@builtin(global_invocation_id) global_invocation_id: vec3u) { | ||
|
||
// vertex index - ignore if out of bounds (as they get batched into groups of 64) | ||
let index = global_invocation_id.x; | ||
if (index >= ubCompute.count) { return; } | ||
|
||
// read the position from the vertex buffer | ||
let positionOffset = ubCompute.positionOffset + index * 3; | ||
var position = vec3f(vertices[positionOffset], vertices[positionOffset + 1], vertices[positionOffset + 2]); | ||
|
||
// read normal | ||
let normalOffset = ubCompute.normalOffset + index * 3; | ||
let normal = vec3f(vertices[normalOffset], vertices[normalOffset + 1], vertices[normalOffset + 2]); | ||
|
||
// generate position from the normal by offsetting (0,0,0) by normal * strength | ||
let strength = vec3f( | ||
1.0 + sin(ubCompute.time + 10 * position.y) * 0.1, | ||
1.0 + cos(ubCompute.time + 5 * position.x) * 0.1, | ||
1.0 + sin(ubCompute.time + 2 * position.z) * 0.2 | ||
); | ||
position = normal * strength; | ||
|
||
// write the position back to the vertex buffer | ||
vertices[positionOffset + 0] = position.x; | ||
vertices[positionOffset + 1] = position.y; | ||
vertices[positionOffset + 2] = position.z; | ||
} | ||
` | ||
} | ||
}; |
171 changes: 171 additions & 0 deletions
171
examples/src/examples/compute/vertex-update/example.mjs
This file contains 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,171 @@ | ||
import * as pc from 'playcanvas'; | ||
import { deviceType, rootPath } from '@examples/utils'; | ||
import files from '@examples/files'; | ||
|
||
const canvas = document.getElementById('application-canvas'); | ||
if (!(canvas instanceof HTMLCanvasElement)) { | ||
throw new Error('No canvas found'); | ||
} | ||
|
||
const assets = { | ||
color: new pc.Asset('color', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-color.jpg' }), | ||
normal: new pc.Asset('normal', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-normal.jpg' }), | ||
gloss: new pc.Asset('gloss', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-gloss.jpg' }), | ||
orbit: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }), | ||
helipad: new pc.Asset( | ||
'helipad-env-atlas', | ||
'texture', | ||
{ url: rootPath + '/static/assets/cubemaps/table-mountain-env-atlas.png' }, | ||
{ type: pc.TEXTURETYPE_RGBP, mipmaps: false } | ||
) | ||
}; | ||
|
||
const gfxOptions = { | ||
deviceTypes: [deviceType], | ||
glslangUrl: rootPath + '/static/lib/glslang/glslang.js', | ||
twgslUrl: rootPath + '/static/lib/twgsl/twgsl.js' | ||
}; | ||
|
||
const device = await pc.createGraphicsDevice(canvas, gfxOptions); | ||
const createOptions = new pc.AppOptions(); | ||
createOptions.graphicsDevice = device; | ||
createOptions.mouse = new pc.Mouse(document.body); | ||
createOptions.touch = new pc.TouchDevice(document.body); | ||
|
||
createOptions.componentSystems = [ | ||
pc.RenderComponentSystem, | ||
pc.CameraComponentSystem, | ||
pc.LightComponentSystem, | ||
pc.ScriptComponentSystem | ||
]; | ||
|
||
createOptions.resourceHandlers = [ | ||
pc.TextureHandler, | ||
pc.ScriptHandler | ||
]; | ||
|
||
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 = 2; | ||
app.scene.envAtlas = assets.helipad.resource; | ||
|
||
// sphere material | ||
const material = new pc.StandardMaterial(); | ||
material.diffuseMap = assets.color.resource; | ||
material.normalMap = assets.normal.resource; | ||
material.glossMap = assets.gloss.resource; | ||
material.update(); | ||
|
||
// sphere mesh and entity | ||
const entity = new pc.Entity('Sphere'); | ||
app.root.addChild(entity); | ||
|
||
// create hight resolution sphere | ||
const mesh = pc.createSphere(app.graphicsDevice, { | ||
radius: 1, | ||
latitudeBands: 100, | ||
longitudeBands: 100, | ||
storageVertex: true // allow vertex buffer to be accessible by compute shader | ||
}); | ||
|
||
// Add a render component with the mesh | ||
entity.addComponent('render', { | ||
meshInstances: [new pc.MeshInstance(mesh, material)] | ||
}); | ||
app.root.addChild(entity); | ||
|
||
// Create an orbit camera | ||
const cameraEntity = new pc.Entity(); | ||
cameraEntity.addComponent('camera', { | ||
clearColor: new pc.Color(0.4, 0.45, 0.5) | ||
}); | ||
cameraEntity.translate(0, 0, 5); | ||
|
||
// add orbit camera script with a mouse and a touch support | ||
cameraEntity.addComponent('script'); | ||
cameraEntity.script.create("orbitCamera", { | ||
attributes: { | ||
inertiaFactor: 0.2, | ||
focusEntity: entity | ||
} | ||
}); | ||
cameraEntity.script.create("orbitCameraInputMouse"); | ||
cameraEntity.script.create("orbitCameraInputTouch"); | ||
app.root.addChild(cameraEntity); | ||
|
||
// a compute shader that will modify the vertex buffer of the mesh every 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 | ||
computeUniformBufferFormat: new pc.UniformBufferFormat(device, [ | ||
new pc.UniformFormat('count', pc.UNIFORMTYPE_UINT), | ||
new pc.UniformFormat('positionOffset', pc.UNIFORMTYPE_UINT), | ||
new pc.UniformFormat('normalOffset', pc.UNIFORMTYPE_UINT), | ||
new pc.UniformFormat('time', pc.UNIFORMTYPE_FLOAT) | ||
]), | ||
|
||
// 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(pc.UNIFORM_BUFFER_DEFAULT_SLOT_NAME, pc.SHADERSTAGE_COMPUTE), | ||
// the vertex buffer we want to modify | ||
new pc.BindStorageBufferFormat('vb', pc.SHADERSTAGE_COMPUTE) | ||
]) | ||
}) : null; | ||
|
||
// information about the vertex buffer format - offset of position and normal attributes | ||
// Note: data is stored non-interleaved, positions together, normals together, so no need | ||
// to worry about stride | ||
const format = mesh.vertexBuffer.format; | ||
const positionElement = format.elements.find(e => e.name === pc.SEMANTIC_POSITION); | ||
const normalElement = format.elements.find(e => e.name === pc.SEMANTIC_NORMAL); | ||
|
||
// create an instance of the compute shader, and provide it the mesh vertex buffer | ||
const compute = new pc.Compute(device, shader, 'ComputeModifyVB'); | ||
compute.setParameter('vb', mesh.vertexBuffer); | ||
compute.setParameter('count', mesh.vertexBuffer.numVertices); | ||
compute.setParameter('positionOffset', positionElement?.offset / 4); // number of floats offset | ||
compute.setParameter('normalOffset', normalElement?.offset / 4); // number of floats offset | ||
|
||
let time = 0; | ||
app.on('update', function (dt) { | ||
time += dt; | ||
if (entity) { | ||
|
||
// update non-constant parameters each frame | ||
compute.setParameter('time', time); | ||
|
||
// set up both dispatches | ||
compute.setupDispatch(mesh.vertexBuffer.numVertices); | ||
|
||
// dispatch the compute shader | ||
device.computeDispatch([compute]); | ||
|
||
// solid / wireframe | ||
entity.render.renderStyle = Math.floor(time * 0.5) % 2 ? pc.RENDERSTYLE_WIREFRAME : pc.RENDERSTYLE_SOLID; | ||
} | ||
}); | ||
}); | ||
|
||
export { app }; |
Binary file not shown.
Binary file not shown.
This file contains 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 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 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 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 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 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. This constructor signature is getting a bit messy. If you pass options, you need to set a value of
initialData
. which a lot of devs never pass. Feels like if this was written from scratch,usage
andinitialData
would be inoptions
. IIRC,initialData
was added originally as a bit of a hack...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but the devs can pass
uninitialised
for those to get the default values.But maybe it's time we move last 3 or so parameters to options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this, I think a separate PR would be a good idea here too.