Skip to content

Commit

Permalink
Add WGSL example
Browse files Browse the repository at this point in the history
Add first basic support for creating WGSL shaders in applications. Since
WGSL shaders aren't parsed yet the bindgroup formats need to be
specified manually.
  • Loading branch information
erikdubbelboer committed Jan 28, 2024
1 parent dc499b7 commit 3eebccc
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 1 deletion.
6 changes: 6 additions & 0 deletions examples/scripts/generate-standalone-files.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ ${exampleClass.example.toString()}
* @returns {string}
*/
function getDeviceType() {
if (${exampleClass.WEBGPU_ENABLED === 'force'}) {
preferredDevice = 'webgpu';
}
const last = localStorage.getItem('preferredGraphicsDevice');
if (last !== null) {
if (last === 'webgpu' && ${exampleClass.WEBGPU_ENABLED === false}) {
Expand Down Expand Up @@ -214,6 +217,9 @@ ${exampleClass.example.toString()}
return;
}
const deviceType = app?.graphicsDevice?.deviceType;
if (!deviceType) {
return;
}
if (deviceType === 'null') {
return;
}
Expand Down
1 change: 1 addition & 0 deletions examples/src/examples/graphics/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ export * from "./texture-array.mjs";
export * from "./texture-basis.mjs";
export * from "./transform-feedback.mjs";
export * from "./video-texture.mjs";
export * from "./wgsl-shader.mjs"
143 changes: 143 additions & 0 deletions examples/src/examples/graphics/wgsl-shader.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import * as pc from 'playcanvas';

/**
* @param {import('../../options.mjs').ExampleOptions} options - The example options.
* @returns {Promise<pc.AppBase|null>} The example application.
*/
async function example({ canvas, deviceType, files, glslangPath, twgslPath }) {
const gfxOptions = {
deviceTypes: [deviceType],

// Even though we're using WGSL, we still need to provide glslang
// and twgsl to compile shaders used internally by the engine.
glslangUrl: glslangPath + 'glslang.js',
twgslUrl: twgslPath + 'twgsl.js'
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);

if (!device.isWebGPU) {
console.error('WebGPU is required for this example.');
return null;
}

const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;

createOptions.componentSystems = [
pc.RenderComponentSystem,
pc.CameraComponentSystem
];
createOptions.resourceHandlers = [
// @ts-ignore
pc.TextureHandler,
// @ts-ignore
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);
});

// create box entity
const box = new pc.Entity('cube');
box.addComponent('render', {
type: 'box'
});
app.root.addChild(box);

const shaderDefinition = {
vshader: files['shader.wgsl'],
fshader: files['shader.wgsl'],
shaderLanguage: pc.SHADERLANGUAGE_WGSL,
};
const shader = new pc.Shader(app.graphicsDevice, shaderDefinition);

// For now WGSL shaders need to provide their own formats as they aren't processed.
// This should match your ub_mesh struct in the shader.
shader.meshUniformBufferFormat = new pc.UniformBufferFormat(app.graphicsDevice, [
new pc.UniformFormat('matrix_model', pc.UNIFORMTYPE_MAT4),
new pc.UniformFormat('amount', pc.UNIFORMTYPE_FLOAT)
]);
shader.meshBindGroupFormat = new pc.BindGroupFormat(app.graphicsDevice, [
new pc.BindBufferFormat(pc.UNIFORM_BUFFER_DEFAULT_SLOT_NAME, pc.SHADERSTAGE_VERTEX | pc.SHADERSTAGE_FRAGMENT)
]);

const material = new pc.Material();
material.shader = shader;
box.render.material = material;

// create camera entity
const camera = new pc.Entity('camera');
camera.addComponent('camera', {
clearColor: new pc.Color(0.5, 0.6, 0.9)
});
app.root.addChild(camera);
camera.setPosition(0, 0, 3);

// Rotate the box according to the delta time since the last frame.
// Update the material's 'amount' parameter to animate the color.
let time = 0;
app.on('update', (/** @type {number} */ dt) => {
box.rotate(10 * dt, 20 * dt, 30 * dt);

time += dt;
// animate the amount as a sine wave varying from 0 to 1
material.setParameter('amount', (Math.sin(time * 4) + 1) * 0.5);
});
return app;
}

class WgslShaderExample {
static CATEGORY = 'Graphics';
static WEBGPU_ENABLED = 'force';
static FILES = {
'shader.wgsl': `
struct ub_mesh {
matrix_model : mat4x4f,
amount : f32,
}
struct ub_view {
matrix_viewProjection : mat4x4f,
}
struct VertexOutput {
@builtin(position) position : vec4f,
@location(0) fragPosition: vec4f,
}
@group(0) @binding(0) var<uniform> uvMesh : ub_mesh;
@group(1) @binding(0) var<uniform> uvView : ub_view;
@vertex
fn vertexMain(@location(0) position : vec4f) -> VertexOutput {
var output : VertexOutput;
output.position = uvView.matrix_viewProjection * (uvMesh.matrix_model * position);
output.fragPosition = 0.5 * (position + vec4(1.0));
return output;
}
@fragment
fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
var color : vec3f = input.fragPosition.rgb;
var roloc : vec3f = vec3f(1.0) - color;
return vec4f(mix(color, roloc, uvMesh.amount), 1.0);
}
`
};
static example = example;
}

export { WgslShaderExample };
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export * from './platform/audio/constants.js';
export * from './platform/graphics/constants.js';
export { createGraphicsDevice } from './platform/graphics/graphics-device-create.js';
export { BindGroupFormat, BindBufferFormat, BindTextureFormat, BindStorageTextureFormat } from './platform/graphics/bind-group-format.js';
export { UniformBufferFormat, UniformFormat } from './platform/graphics/uniform-buffer-format.js';
export { BlendState } from './platform/graphics/blend-state.js';
export { Compute } from './platform/graphics/compute.js';
export { DepthState } from './platform/graphics/depth-state.js';
Expand Down
3 changes: 2 additions & 1 deletion src/scene/shader-lib/program-library.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ class ProgramLibrary {
attributes: generatedShaderDef.attributes,
vshader: generatedShaderDef.vshader,
fshader: generatedShaderDef.fshader,
processingOptions: processingOptions
processingOptions: processingOptions,
shaderLanguage: generatedShaderDef.shaderLanguage
};

// add new shader to the processed cache
Expand Down
8 changes: 8 additions & 0 deletions src/scene/shader-lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { shaderChunks } from './chunks/chunks.js';
import { getProgramLibrary } from './get-program-library.js';
import { Debug } from '../../core/debug.js';
import { ShaderGenerator } from './programs/shader-generator.js';
import { SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js';

/**
* Create a shader from named shader chunks.
Expand Down Expand Up @@ -150,6 +151,13 @@ function processShader(shader, processingOptions) {
// generate shader variant - its the same shader, but with different processing options
const variant = library.getProgram(libraryModuleName, {}, processingOptions);

// For now WGSL shaders need to provide their own formats as they aren't processed.
// Make sure to copy these from the original shader.
if (shader.definition.shaderLanguage === SHADERLANGUAGE_WGSL) {
variant.meshUniformBufferFormat = shader.meshUniformBufferFormat;
variant.meshBindGroupFormat = shader.meshBindGroupFormat;
}

// unregister it again
library.unregister(libraryModuleName);

Expand Down

0 comments on commit 3eebccc

Please sign in to comment.