2.3 비디오 렌더링
이미지를 텍스처로 로드하고 렌더링하는 방법을 배웠습니다. 마찬가지로, 비디오를 텍스처로 로드할 수 있는데, 이는 웹 기반 비디오 처리 애플리케이션을 만드는 데 특히 유용합니다.
비디오는 일련의 이미지로 볼 수 있으므로, 이미지 텍스처와 근본적으로 다르지 않습니다. 핵심 과제는 이미지를 효율적으로 업데이트하는 것입니다. 이 튜토리얼에서는 수동 프레임 업데이트와 외부 텍스처를 통한 자동 업데이트라는 두 가지 방법을 살펴보겠습니다.
수동 프레임 업데이트
플레이그라운드 실행 - 2_03_1_video_1먼저, 비디오 파일 URL이 주어지면 문서에 숨겨진 비디오 태그를 설치하는 함수가 필요합니다. 그런 다음 비디오 재생을 모니터링합니다.
function setupVideo(url) {
const video = document.createElement('video');
let playing = false;
let timeupdate = false;
video.playsInline = true;
video.muted = true;
video.loop = true;
// Waiting for these 2 events ensures
// there is data in the video
video.addEventListener('playing', () => {
playing = true;
checkReady();
}, true);
video.addEventListener('timeupdate', () => {
timeupdate = true;
checkReady();
}, true);
video.src = url;
video.play();
function checkReady() {
if (playing && timeupdate) {
copyVideo = true;
}
}
return video;
}
videoTag = setupVideo('../data/Firefox.mp4');
(async () => {
return new Promise(resolve => {
let timer = setInterval(() => {
if (copyVideo) {
clearInterval(timer);
resolve();
}
}, 300);
})
})().then(() => {
webgpu();
});
playing 및 timeupdate 이벤트를 모두 주시해야 합니다. 두 이벤트가 모두 발생해야 비디오가 시작되었다고 확신할 수 있습니다. 비디오가 시작되기를 기다리지 않으면 비디오 데이터가 없거나 비디오 크기가 할당되지 않을 수 있습니다. 따라서 진행하기 전에 비디오가 시작될 때까지 기다려야 합니다.
const textureDescriptor = {
size: { width: videoTag.videoWidth, height: videoTag.videoHeight },
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
};
const texture = device.createTexture(textureDescriptor);
videoTag.ontimeupdate = async (event) => {
let imageData = await createImageBitmap(videoTag);
//console.log(imageData)
device.queue.copyExternalImageToTexture({ source: imageData }, { texture }, textureDescriptor.size);
};
텍스처 맵의 크기는 비디오 태그의 크기와 일치합니다. 이 텍스처 맵에는 RENDER_ATTACHMENT 사용을 요청해야 한다는 점에 유의하세요. 이는 copyExternalImageToTexture 함수 호출에 필요하기 때문입니다. 마지막으로, timeupdate 이벤트에 대한 이벤트 리스너를 생성합니다. 이 이벤트 핸들러에서는 createImageBitmap 함수를 사용하여 비트맵을 생성하고, copyExternalImageToTexture를 통해 해당 콘텐츠를 텍스처 맵에 로드합니다.
나머지 렌더링 코드는 이전과 동일하므로 자세한 내용은 생략하겠습니다.
외부 텍스처 임포트하기
플레이그라운드 실행 - 2_03_2_video_2이전 예제에서는 비디오 프레임이 업데이트될 때마다 수동으로 데이터를 복사했습니다. 다른 접근 방식은 비디오 태그에서 외부 텍스처를 임포트하는 것입니다. 이 방법은 텍스처 맵을 직접 생성하는 것을 피할 수 있지만, 임포트된 텍스처의 수명을 염두에 두어야 합니다. 비디오가 새 프레임으로 전환되거나 VideoFrame의 close() 함수가 호출되면 텍스처가 만료될 수 있습니다.
function render() {
requestAnimationFrame(render);
if (copyVideo) {
const externalTexture = device.importExternalTexture({ source: videoTag });
let uniformBindGroup = device.createBindGroup({
layout: uniformBindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer: translateMatrixUniformBuffer
}
},
{
binding: 1,
resource: {
buffer: projectionMatrixUniformBuffer
}
},
{
binding: 2,
resource: externalTexture
},
{
binding: 3,
resource:
sampler
}
]
});
commandEncoder = device.createCommandEncoder();
passEncoder = commandEncoder.beginRenderPass(renderPassDesc);
passEncoder.setViewport(0, 0, canvas.width, canvas.height, 0, 1);
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, uniformBindGroup);
passEncoder.setVertexBuffer(0, positionBuffer);
passEncoder.setVertexBuffer(1, texCoordsBuffer);
passEncoder.draw(4, 1);
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);
}
}
requestAnimationFrame(render);
}
텍스처 만료를 방지하려면, 동일한 렌더 함수 호출 내에서 텍스처를 즉시 임포트하고 사용하는 것이 중요합니다. 이러한 필요성 때문에 새 외부 텍스처 맵을 통합하기 위해 각 렌더링 프레임에 대해 바인드 그룹을 생성합니다.