Reputation: 73
Here is normal stencil buffer 2d clipping result and one extra triangle:
const code = `
struct VSIn {
@location(0) pos: vec4f,
};
struct VSOut {
@builtin(position) pos: vec4f,
};
@vertex fn vs(vsIn: VSIn) -> VSOut {
var vsOut: VSOut;
vsOut.pos = vsIn.pos;
return vsOut;
}
@fragment fn fs(vin: VSOut) -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
`;
(async() => {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
alert('need webgpu');
return;
}
const canvas = document.querySelector("canvas")
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
alphaMode: 'opaque',
});
const module = device.createShaderModule({code});
const maskMakingPipeline = device.createRenderPipeline({
label: 'pipeline for rendering the mask',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [
// position
{
arrayStride: 2 * 4, // 2 floats, 4 bytes each
attributes: [
{shaderLocation: 0, offset: 0, format: 'float32x2'},
],
},
],
},
fragment: {
module,
entryPoint: 'fs',
targets: [],
},
// replace the stencil value when we draw
depthStencil: {
format: 'stencil8',
depthCompare: 'always',
depthWriteEnabled: false,
stencilFront: {
passOp:'replace',
},
},
});
const maskedPipeline = device.createRenderPipeline({
label: 'pipeline for rendering only where the mask is',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [
// position
{
arrayStride: 2 * 4, // 2 floats, 4 bytes each
attributes: [
{shaderLocation: 0, offset: 0, format: 'float32x2'},
],
},
],
},
fragment: {
module,
entryPoint: 'fs',
targets: [{format: presentationFormat}],
},
// draw only where stencil value matches
depthStencil: {
depthCompare: 'always',
depthWriteEnabled: false,
format: 'stencil8',
stencilFront: {
compare: 'equal',
},
},
});
const maskVerts = new Float32Array([-1, -1, 1, -1, 1, 1]);
const toBeMaskedVerts = new Float32Array([0, -1, 1, -1, -1, 1]);
const maskVertexBuffer = device.createBuffer({
size: maskVerts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(maskVertexBuffer, 0, maskVerts);
const toBeMaskedVertexBuffer = device.createBuffer({
size: toBeMaskedVerts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(toBeMaskedVertexBuffer, 0, toBeMaskedVerts);
const stencilTexture = device.createTexture({
format: 'stencil8',
size: [canvas.width, canvas.height],
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
const encoder = device.createCommandEncoder();
{
const pass = encoder.beginRenderPass({
colorAttachments: [],
depthStencilAttachment: {
view: stencilTexture.createView(),
stencilClearValue: 0,
stencilLoadOp: 'clear',
stencilStoreOp: 'store',
}
});
// draw the mask
pass.setPipeline(maskMakingPipeline);
pass.setVertexBuffer(0, maskVertexBuffer);
pass.setStencilReference(1);
pass.draw(3);
pass.end();
}
{
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: [0, 0, 0, 1],
loadOp: 'clear',
storeOp: 'store',
}],
depthStencilAttachment: {
view: stencilTexture.createView(),
stencilLoadOp: 'load',
stencilStoreOp: 'store',
}
});
// draw only the mask is
pass.setPipeline(maskedPipeline);
pass.setStencilReference(1);
pass.setVertexBuffer(0, toBeMaskedVertexBuffer);
pass.draw(3);
pass.end();
}
{
const antialias = false // <- changed to true to enable antialias
const sampleCount = 4
const sampleTexture = device.createTexture({
size: [canvas.width, canvas.height],
sampleCount,
format: presentationFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
})
const verts = new Float32Array([0.9, 0.9, 0.9, 0.7, 0.7, 0.9]);
const vertexBuffer = device.createBuffer({
size: verts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(vertexBuffer, 0, verts);
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [{ arrayStride: 8, attributes: [ {shaderLocation: 0, offset: 0, format: 'float32x2'}] }],
},
fragment: {
module,
entryPoint: 'fs',
targets: [{format: presentationFormat}],
},
multisample: antialias ? { count: sampleCount } : undefined,
});
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: antialias ? sampleTexture.createView() : context.getCurrentTexture().createView(),
resolveTarget: antialias ? context.getCurrentTexture().createView() : undefined,
loadOp: 'load',
storeOp: 'store',
}],
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.draw(3);
pass.end();
}
device.queue.submit([encoder.finish()]);
})();
<canvas></canvas>
When antialias is enabled for the small triangle(not the masking triangle/masked triangle), the clipping result becomes invisible, since the clipping result and the small triangle seems unrelated, why does this happen? Can this be solved?
const code = `
struct VSIn {
@location(0) pos: vec4f,
};
struct VSOut {
@builtin(position) pos: vec4f,
};
@vertex fn vs(vsIn: VSIn) -> VSOut {
var vsOut: VSOut;
vsOut.pos = vsIn.pos;
return vsOut;
}
@fragment fn fs(vin: VSOut) -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
`;
(async() => {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
alert('need webgpu');
return;
}
const canvas = document.querySelector("canvas")
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
alphaMode: 'opaque',
});
const module = device.createShaderModule({code});
const maskMakingPipeline = device.createRenderPipeline({
label: 'pipeline for rendering the mask',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [
// position
{
arrayStride: 2 * 4, // 2 floats, 4 bytes each
attributes: [
{shaderLocation: 0, offset: 0, format: 'float32x2'},
],
},
],
},
fragment: {
module,
entryPoint: 'fs',
targets: [],
},
// replace the stencil value when we draw
depthStencil: {
format: 'stencil8',
depthCompare: 'always',
depthWriteEnabled: false,
stencilFront: {
passOp:'replace',
},
},
});
const maskedPipeline = device.createRenderPipeline({
label: 'pipeline for rendering only where the mask is',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [
// position
{
arrayStride: 2 * 4, // 2 floats, 4 bytes each
attributes: [
{shaderLocation: 0, offset: 0, format: 'float32x2'},
],
},
],
},
fragment: {
module,
entryPoint: 'fs',
targets: [{format: presentationFormat}],
},
// draw only where stencil value matches
depthStencil: {
depthCompare: 'always',
depthWriteEnabled: false,
format: 'stencil8',
stencilFront: {
compare: 'equal',
},
},
});
const maskVerts = new Float32Array([-1, -1, 1, -1, 1, 1]);
const toBeMaskedVerts = new Float32Array([0, -1, 1, -1, -1, 1]);
const maskVertexBuffer = device.createBuffer({
size: maskVerts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(maskVertexBuffer, 0, maskVerts);
const toBeMaskedVertexBuffer = device.createBuffer({
size: toBeMaskedVerts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(toBeMaskedVertexBuffer, 0, toBeMaskedVerts);
const stencilTexture = device.createTexture({
format: 'stencil8',
size: [canvas.width, canvas.height],
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
const encoder = device.createCommandEncoder();
{
const pass = encoder.beginRenderPass({
colorAttachments: [],
depthStencilAttachment: {
view: stencilTexture.createView(),
stencilClearValue: 0,
stencilLoadOp: 'clear',
stencilStoreOp: 'store',
}
});
// draw the mask
pass.setPipeline(maskMakingPipeline);
pass.setVertexBuffer(0, maskVertexBuffer);
pass.setStencilReference(1);
pass.draw(3);
pass.end();
}
{
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: [0, 0, 0, 1],
loadOp: 'clear',
storeOp: 'store',
}],
depthStencilAttachment: {
view: stencilTexture.createView(),
stencilLoadOp: 'load',
stencilStoreOp: 'store',
}
});
// draw only the mask is
pass.setPipeline(maskedPipeline);
pass.setStencilReference(1);
pass.setVertexBuffer(0, toBeMaskedVertexBuffer);
pass.draw(3);
pass.end();
}
{
const antialias = true // <- changed to false to disable antialias
const sampleCount = 4
const sampleTexture = device.createTexture({
size: [canvas.width, canvas.height],
sampleCount,
format: presentationFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
})
const verts = new Float32Array([0.9, 0.9, 0.9, 0.7, 0.7, 0.9]);
const vertexBuffer = device.createBuffer({
size: verts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(vertexBuffer, 0, verts);
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [{ arrayStride: 8, attributes: [ {shaderLocation: 0, offset: 0, format: 'float32x2'}] }],
},
fragment: {
module,
entryPoint: 'fs',
targets: [{format: presentationFormat}],
},
multisample: antialias ? { count: sampleCount } : undefined,
});
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: antialias ? sampleTexture.createView() : context.getCurrentTexture().createView(),
resolveTarget: antialias ? context.getCurrentTexture().createView() : undefined,
loadOp: 'load',
storeOp: 'store',
}],
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.draw(3);
pass.end();
}
device.queue.submit([encoder.finish()]);
})();
<canvas></canvas>
Upvotes: 1
Views: 241
Reputation: 73
Stencil texture need to be alias too.
const code = `
struct VSIn {
@location(0) pos: vec4f,
};
struct VSOut {
@builtin(position) pos: vec4f,
};
@vertex fn vs(vsIn: VSIn) -> VSOut {
var vsOut: VSOut;
vsOut.pos = vsIn.pos;
return vsOut;
}
@fragment fn fs(vin: VSOut) -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
`;
(async() => {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
alert('need webgpu');
return;
}
const canvas = document.querySelector("canvas")
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
alphaMode: 'opaque',
});
const module = device.createShaderModule({code});
const antialias = true // <- changed to false to disable antialias
const sampleCount = 4
const sampleTexture = device.createTexture({
size: [canvas.width, canvas.height],
sampleCount,
format: presentationFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
})
const maskMakingPipeline = device.createRenderPipeline({
label: 'pipeline for rendering the mask',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [
// position
{
arrayStride: 2 * 4, // 2 floats, 4 bytes each
attributes: [
{shaderLocation: 0, offset: 0, format: 'float32x2'},
],
},
],
},
fragment: {
module,
entryPoint: 'fs',
targets: [],
},
multisample: {
count: sampleCount,
},
// replace the stencil value when we draw
depthStencil: {
format: 'stencil8',
depthCompare: 'always',
depthWriteEnabled: false,
stencilFront: {
passOp:'replace',
},
},
});
const maskedPipeline = device.createRenderPipeline({
label: 'pipeline for rendering only where the mask is',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [
// position
{
arrayStride: 2 * 4, // 2 floats, 4 bytes each
attributes: [
{shaderLocation: 0, offset: 0, format: 'float32x2'},
],
},
],
},
fragment: {
module,
entryPoint: 'fs',
targets: [{format: presentationFormat}],
},
multisample: {
count: sampleCount,
},
// draw only where stencil value matches
depthStencil: {
depthCompare: 'always',
depthWriteEnabled: false,
format: 'stencil8',
stencilFront: {
compare: 'equal',
},
},
});
const maskVerts = new Float32Array([-1, -1, 1, -1, 1, 1]);
const toBeMaskedVerts = new Float32Array([0, -1, 1, -1, -1, 1]);
const maskVertexBuffer = device.createBuffer({
size: maskVerts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(maskVertexBuffer, 0, maskVerts);
const toBeMaskedVertexBuffer = device.createBuffer({
size: toBeMaskedVerts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(toBeMaskedVertexBuffer, 0, toBeMaskedVerts);
const stencilTexture = device.createTexture({
format: 'stencil8',
sampleCount,
size: [canvas.width, canvas.height],
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
const encoder = device.createCommandEncoder();
{
const pass = encoder.beginRenderPass({
colorAttachments: [],
depthStencilAttachment: {
view: stencilTexture.createView(),
stencilClearValue: 0,
stencilLoadOp: 'clear',
stencilStoreOp: 'store',
}
});
// draw the mask
pass.setPipeline(maskMakingPipeline);
pass.setVertexBuffer(0, maskVertexBuffer);
pass.setStencilReference(1);
pass.draw(3);
pass.end();
}
{
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: sampleTexture.createView(),
resolveTarget: context.getCurrentTexture().createView(),
clearValue: [0, 0, 0, 1],
loadOp: 'clear',
storeOp: 'store',
}],
depthStencilAttachment: {
view: stencilTexture.createView(),
stencilLoadOp: 'load',
stencilStoreOp: 'store',
}
});
// draw only the mask is
pass.setPipeline(maskedPipeline);
pass.setStencilReference(1);
pass.setVertexBuffer(0, toBeMaskedVertexBuffer);
pass.draw(3);
pass.end();
}
{
const verts = new Float32Array([0.9, 0.9, 0.9, 0.7, 0.7, 0.9]);
const vertexBuffer = device.createBuffer({
size: verts.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(vertexBuffer, 0, verts);
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [{ arrayStride: 8, attributes: [ {shaderLocation: 0, offset: 0, format: 'float32x2'}] }],
},
fragment: {
module,
entryPoint: 'fs',
targets: [{format: presentationFormat}],
},
multisample: antialias ? { count: sampleCount } : undefined,
});
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: antialias ? sampleTexture.createView() : context.getCurrentTexture().createView(),
resolveTarget: antialias ? context.getCurrentTexture().createView() : undefined,
loadOp: 'load',
storeOp: 'store',
}],
});
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.draw(3);
pass.end();
}
device.queue.submit([encoder.finish()]);
})();
<canvas></canvas>
Upvotes: 0