plantain
plantain

Reputation: 73

Why webgpu stencil buffer 2d clipping result invisible when antialias enabled?

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

Answers (1)

plantain
plantain

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

Related Questions