-
Notifications
You must be signed in to change notification settings - Fork 313
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Video Uploading with WebCodecs sample (#247)
- Loading branch information
1 parent
ae7cdc0
commit 99123cf
Showing
5 changed files
with
196 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import { makeSample, SampleInit } from '../../components/SampleLayout'; | ||
|
||
import fullscreenTexturedQuadWGSL from '../../shaders/fullscreenTexturedQuad.wgsl'; | ||
import sampleExternalTextureWGSL from '../../shaders/sampleExternalTexture.frag.wgsl'; | ||
|
||
const init: SampleInit = async ({ canvas, pageState }) => { | ||
// Set video element | ||
const video = document.createElement('video'); | ||
video.loop = true; | ||
video.autoplay = true; | ||
video.muted = true; | ||
video.src = new URL( | ||
'../../../assets/video/pano.webm', | ||
import.meta.url | ||
).toString(); | ||
await video.play(); | ||
|
||
const adapter = await navigator.gpu.requestAdapter(); | ||
const device = await adapter.requestDevice(); | ||
|
||
if (!pageState.active) return; | ||
|
||
const context = canvas.getContext('webgpu') as GPUCanvasContext; | ||
const devicePixelRatio = window.devicePixelRatio || 1; | ||
canvas.width = canvas.clientWidth * devicePixelRatio; | ||
canvas.height = canvas.clientHeight * devicePixelRatio; | ||
const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); | ||
|
||
context.configure({ | ||
device, | ||
format: presentationFormat, | ||
alphaMode: 'premultiplied', | ||
}); | ||
|
||
const pipeline = device.createRenderPipeline({ | ||
layout: 'auto', | ||
vertex: { | ||
module: device.createShaderModule({ | ||
code: fullscreenTexturedQuadWGSL, | ||
}), | ||
entryPoint: 'vert_main', | ||
}, | ||
fragment: { | ||
module: device.createShaderModule({ | ||
code: sampleExternalTextureWGSL, | ||
}), | ||
entryPoint: 'main', | ||
targets: [ | ||
{ | ||
format: presentationFormat, | ||
}, | ||
], | ||
}, | ||
primitive: { | ||
topology: 'triangle-list', | ||
}, | ||
}); | ||
|
||
const sampler = device.createSampler({ | ||
magFilter: 'linear', | ||
minFilter: 'linear', | ||
}); | ||
|
||
function getVideoFrameFromVideoElement(video) { | ||
return new Promise((resolve) => { | ||
const videoTrack = video.captureStream().getVideoTracks()[0]; | ||
const trackProcessor = new MediaStreamTrackProcessor({ | ||
track: videoTrack, | ||
}); | ||
const transformer = new TransformStream({ | ||
transform(videoFrame) { | ||
videoTrack.stop(); | ||
resolve(videoFrame); | ||
}, | ||
flush(controller) { | ||
controller.terminate(); | ||
}, | ||
}); | ||
const trackGenerator = new MediaStreamTrackGenerator({ | ||
kind: 'video', | ||
}); | ||
trackProcessor.readable | ||
.pipeThrough(transformer) | ||
.pipeTo(trackGenerator.writable); | ||
}); | ||
} | ||
|
||
async function frame() { | ||
// Sample is no longer the active page. | ||
if (!pageState.active) return; | ||
|
||
const videoFrame = await getVideoFrameFromVideoElement(video); | ||
|
||
const uniformBindGroup = device.createBindGroup({ | ||
layout: pipeline.getBindGroupLayout(0), | ||
entries: [ | ||
{ | ||
binding: 1, | ||
resource: sampler, | ||
}, | ||
{ | ||
binding: 2, | ||
resource: device.importExternalTexture({ | ||
source: videoFrame as any, // eslint-disable-line @typescript-eslint/no-explicit-any | ||
}), | ||
}, | ||
], | ||
}); | ||
|
||
const commandEncoder = device.createCommandEncoder(); | ||
const textureView = context.getCurrentTexture().createView(); | ||
|
||
const renderPassDescriptor: GPURenderPassDescriptor = { | ||
colorAttachments: [ | ||
{ | ||
view: textureView, | ||
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, | ||
loadOp: 'clear', | ||
storeOp: 'store', | ||
}, | ||
], | ||
}; | ||
|
||
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); | ||
passEncoder.setPipeline(pipeline); | ||
passEncoder.setBindGroup(0, uniformBindGroup); | ||
passEncoder.draw(6, 1, 0, 0); | ||
passEncoder.end(); | ||
device.queue.submit([commandEncoder.finish()]); | ||
|
||
if ('requestVideoFrameCallback' in video) { | ||
video.requestVideoFrameCallback(frame); | ||
} else { | ||
requestAnimationFrame(frame); | ||
} | ||
} | ||
|
||
if ('requestVideoFrameCallback' in video) { | ||
video.requestVideoFrameCallback(frame); | ||
} else { | ||
requestAnimationFrame(frame); | ||
} | ||
}; | ||
|
||
const VideoUploadingWebCodecs: () => JSX.Element = () => | ||
makeSample({ | ||
name: 'Video Uploading with WebCodecs (Experimental)', | ||
description: `This example shows how to upload a WebCodecs VideoFrame to WebGPU. | ||
Support for using a VideoFrame as the source for a GPUExternalTexture requires | ||
running Chrome with the "WebGPU Developer Features" flag or the WebGPU WebCodecs | ||
integration origin trial. | ||
See https://developer.chrome.com/origintrials/#/view_trial/1705738358866575361 | ||
`, | ||
originTrial: | ||
'Auo9JMDbdn/Jg1pd8liB9Ofp1OLzi9mecxjBBfjv/3f8O8775CXgcTobX4t6KYxMC1wnO4Z7MWArPSptGtkD2woAAABZeyJvcmlnaW4iOiJodHRwczovL3dlYmdwdS5naXRodWIuaW86NDQzIiwiZmVhdHVyZSI6IldlYkdQVVdlYkNvZGVjcyIsImV4cGlyeSI6MTcwMTk5MzU5OX0=', | ||
init, | ||
sources: [ | ||
{ | ||
name: __filename.substring(__dirname.length + 1), | ||
contents: __SOURCE__, | ||
}, | ||
{ | ||
name: '../../shaders/fullscreenTexturedQuad.wgsl', | ||
contents: fullscreenTexturedQuadWGSL, | ||
editable: true, | ||
}, | ||
{ | ||
name: '../../shaders/sampleExternalTexture.wgsl', | ||
contents: sampleExternalTextureWGSL, | ||
editable: true, | ||
}, | ||
], | ||
filename: __filename, | ||
}); | ||
|
||
export default VideoUploadingWebCodecs; |