-
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
Implementation of StorageBuffer on WebGPU #6201
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
02105f9
Implementation of StorageBuffer on WebGPU
21494cc
comment
c03d820
allow storage buffer data to be returned to a preallocated typed buffer
0bfef89
comment
66f1dba
Update src/platform/graphics/storage-buffer.js
mvaligursky a237712
Update src/platform/graphics/webgpu/webgpu-buffer.js
mvaligursky 7e2af79
Update src/platform/graphics/webgpu/webgpu-graphics-device.js
mvaligursky 0e49702
changing name to ‘Unnamed’
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,30 @@ | ||
/** | ||
* @type {import('../../../../types.mjs').ExampleConfig} | ||
*/ | ||
export default { | ||
HIDDEN: true, | ||
WEBGPU_REQUIRED: true, | ||
FILES: { | ||
'compute-shader.wgsl': /* wgsl */ ` | ||
|
||
@group(0) @binding(0) var inputTexture: texture_2d<f32>; | ||
// @group(0) @binding(1) is a sampler of the inputTexture, but we don't need it in the shader. | ||
@group(0) @binding(2) var<storage, read_write> bins: array<atomic<u32>>; | ||
|
||
fn luminance(color: vec3f) -> f32 { | ||
return saturate(dot(color, vec3f(0.2126, 0.7152, 0.0722))); | ||
} | ||
|
||
@compute @workgroup_size(1, 1, 1) | ||
fn main(@builtin(global_invocation_id) global_invocation_id: vec3u) { | ||
let numBins = f32(arrayLength(&bins)); | ||
let lastBinIndex = u32(numBins - 1); | ||
let position = global_invocation_id.xy; | ||
let color = textureLoad(inputTexture, position, 0); | ||
let v = luminance(color.rgb); | ||
let bin = min(u32(v * numBins), lastBinIndex); | ||
atomicAdd(&bins[bin], 1u); | ||
} | ||
` | ||
} | ||
}; |
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,178 @@ | ||
import * as pc from 'playcanvas'; | ||
import { deviceType, rootPath } from '@examples/utils'; | ||
import files from '@examples/files'; | ||
|
||
// Note: the example is based on this article: | ||
// https://webgpufundamentals.org/webgpu/lessons/webgpu-compute-shaders-histogram.html | ||
// A simpler but less performant version of the compute shader is used for simplicity. | ||
|
||
const canvas = document.getElementById('application-canvas'); | ||
if (!(canvas instanceof HTMLCanvasElement)) { | ||
throw new Error('No canvas found'); | ||
} | ||
|
||
const assets = { | ||
solid: new pc.Asset('solid', 'container', { url: rootPath + '/static/assets/models/icosahedron.glb' }), | ||
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], | ||
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.componentSystems = [ | ||
pc.RenderComponentSystem, | ||
pc.CameraComponentSystem, | ||
pc.LightComponentSystem, | ||
pc.ScriptComponentSystem | ||
]; | ||
createOptions.resourceHandlers = [ | ||
pc.TextureHandler, | ||
pc.ContainerHandler | ||
]; | ||
|
||
const app = new pc.AppBase(canvas); | ||
app.init(createOptions); | ||
app.start(); | ||
|
||
// 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(() => { | ||
|
||
// set up some general scene rendering properties | ||
app.scene.toneMapping = pc.TONEMAP_ACES; | ||
|
||
// setup skydome | ||
app.scene.skyboxMip = 2; | ||
app.scene.skyboxIntensity = 0.3; | ||
app.scene.envAtlas = assets.helipad.resource; | ||
|
||
// create camera entity | ||
const camera = new pc.Entity('camera'); | ||
camera.addComponent('camera'); | ||
app.root.addChild(camera); | ||
camera.setPosition(0, 0, 5); | ||
|
||
// Enable the camera to render the scene's color map, available as uSceneColorMap in the shaders. | ||
// This allows us to use the rendered scene as an input for the histogram compute shader. | ||
camera.camera.requestSceneColorMap(true); | ||
|
||
// create directional light entity | ||
const light = new pc.Entity('light'); | ||
light.addComponent('light', { | ||
type: 'directional', | ||
color: new pc.Color(1, 1, 1), | ||
intensity: 15 | ||
}); | ||
app.root.addChild(light); | ||
light.setEulerAngles(45, 0, 40); | ||
|
||
// a helper script that rotates the entity | ||
const Rotator = pc.createScript('rotator'); | ||
Rotator.prototype.update = function (/** @type {number} */ dt) { | ||
this.entity.rotate(5 * dt, 10 * dt, -15 * dt); | ||
}; | ||
|
||
// a compute shader that will compute the histogram of the input texture and write the result to the storage buffer | ||
const shader = device.supportsCompute ? new pc.Shader(device, { | ||
name: 'ComputeShader', | ||
shaderLanguage: pc.SHADERLANGUAGE_WGSL, | ||
cshader: files['compute-shader.wgsl'], | ||
|
||
// format of a bind group, providing resources for the compute shader | ||
computeBindGroupFormat: new pc.BindGroupFormat(device, [ | ||
// no uniform buffer | ||
], [ | ||
// input texture - the scene color map | ||
new pc.BindTextureFormat('uSceneColorMap', pc.SHADERSTAGE_COMPUTE) | ||
], [ | ||
// no storage textures | ||
], [ | ||
// output storage buffer | ||
new pc.BindStorageBufferFormat('outBuffer', pc.SHADERSTAGE_COMPUTE) | ||
]) | ||
}) : null; | ||
|
||
// Create a storage buffer to which the compute shader will write the histogram values. | ||
const numBins = 256; | ||
const histogramStorageBuffer = new pc.StorageBuffer( | ||
device, numBins * 4, // 4 bytes per value, storing unsigned int | ||
pc.BUFFERUSAGE_COPY_SRC | // needed for reading back the data to CPU | ||
pc.BUFFERUSAGE_COPY_DST // needed for clearing the buffer | ||
); | ||
|
||
// Create an instance of the compute shader, and set the input and output data. Note that we do | ||
// not provide a value for `uSceneColorMap` as this is done by the engine internally. | ||
const compute = new pc.Compute(device, shader, 'ComputeHistogram'); | ||
compute.setParameter('outBuffer', histogramStorageBuffer); | ||
|
||
// instantiate the spinning mesh | ||
const solid = assets.solid.resource.instantiateRenderEntity(); | ||
solid.addComponent('script'); | ||
solid.script.create('rotator'); | ||
solid.setLocalPosition(0, 0.4, 0); | ||
solid.setLocalScale(0.35, 0.35, 0.35); | ||
app.root.addChild(solid); | ||
|
||
let firstFrame = true; | ||
app.on('update', function (/** @type {number} */ dt) { | ||
|
||
// The update function runs every frame before the frame gets rendered. On the first time it | ||
// runs, the scene color map has not been rendered yet, so we skip the first frame. | ||
if (firstFrame) { | ||
firstFrame = false; | ||
return; | ||
} | ||
|
||
if (device.supportsCompute) { | ||
|
||
// clear the storage buffer, to avoid the accumulation buildup | ||
histogramStorageBuffer.clear(); | ||
|
||
// dispatch the compute shader | ||
compute.setupDispatch(app.graphicsDevice.width, app.graphicsDevice.height); | ||
device.computeDispatch([compute]); | ||
|
||
// Read back the histogram data from the storage buffer. None that the returned promise | ||
// will be resolved later, when the GPU is done running it, and so the histogram on the | ||
// screen will be up to few frames behind. | ||
const histogramData = new Uint32Array(numBins); | ||
histogramStorageBuffer.read(0, undefined, histogramData).then( | ||
(data) => { | ||
// render the histogram using lines | ||
const scale = 1 / 50000; | ||
const positions = []; | ||
for (let x = 0; x < data.length; x++) { | ||
const value = pc.math.clamp(data[x] * scale, 0, 0.2); | ||
positions.push(x * 0.001, -0.35, 4); | ||
positions.push(x * 0.001, value - 0.35, 4); | ||
} | ||
app.drawLineArrays(positions, pc.Color.YELLOW); | ||
} | ||
); | ||
} | ||
}); | ||
}); | ||
|
||
export { app }; |
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.
This array format seems v messy maybe replace with an object with keys to describe each array?
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.
yeah good suggestion, but outside of the scope of this PR.