| <html> |
| <script> |
| globalThis.testRunner?.waitUntilDone(); |
| const log = globalThis.$vm?.print ?? console.log; |
| |
| const veryBrightVideo = `data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAvG1kYXQAAAAfTgEFGkdWStxcTEM/lO/FETzRQ6gD7gAA7gIAA3EYgAAAAEgoAa8iNjAkszOL+e58c//cEe//0TT//scp1n/381P/RWP/zOW4QtxorfVogeh8nQDbQAAAAwAQMCcWUTAAAAMAAAMAAAMA84AAAAAVAgHQAyu+KT35E7gAADFgAAADABLQAAAAEgIB4AiS76MTkNbgAAF3AAAPSAAAABICAeAEn8+hBOTXYAADUgAAHRAAAAPibW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAKcAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAw10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAKcAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAABAAAAAQAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAACnAAAAAAABAAAAAAKFbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAABdwAAAD6BVxAAAAAAAMWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABDb3JlIE1lZGlhIFZpZGVvAAAAAixtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAHsc3RibAAAARxzdHNkAAAAAAAAAAEAAAEMaHZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAQABAASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAHVodmNDAQIgAAAAsAAAAAAAPPAA/P36+gAACwOgAAEAGEABDAH//wIgAAADALAAAAMAAAMAPBXAkKEAAQAmQgEBAiAAAAMAsAAAAwAAAwA8oBQgQcCTDLYgV7kWVYC1CRAJAICiAAEACUQBwChkuNBTJAAAAApmaWVsAQAAAAATY29scm5jbHgACQAQAAkAAAAAEHBhc3AAAAABAAAAAQAAABRidHJ0AAAAAAAALPwAACz8AAAAKHN0dHMAAAAAAAAAAwAAAAIAAAPoAAAAAQAAAAEAAAABAAAD6AAAABRzdHNzAAAAAAAAAAEAAAABAAAAEHNkdHAAAAAAIBAQGAAAAChjdHRzAAAAAAAAAAMAAAABAAAAAAAAAAEAAAfQAAAAAgAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAQAAAABAAAAJHN0c3oAAAAAAAAAAAAAAAQAAABvAAAAGQAAABYAAAAWAAAAFHN0Y28AAAAAAAAAAQAAACwAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYwLjMuMTAw`; |
| const format = 'bgra8unorm'; |
| const widthAndHeight = 384; |
| |
| function videoWithData() { |
| let video = document.createElement('video'); |
| video.src = veryBrightVideo; |
| video.muted = true; |
| return new Promise(resolve => { |
| video.onloadeddata = () => { |
| video.play(); |
| resolve(video); |
| }; |
| }); |
| } |
| |
| function createCanvasAndContext(device) { |
| let canvas = document.createElement('canvas'); |
| canvas.width = widthAndHeight; |
| canvas.height = widthAndHeight; |
| document.body.append(canvas); |
| let context = canvas.getContext('webgpu'); |
| context.configure({device, format, usage: GPUTextureUsage.RENDER_ATTACHMENT}); |
| return context; |
| } |
| |
| onload = async () => { |
| let adapter = await navigator.gpu.requestAdapter({}); |
| let device = await adapter.requestDevice({}); |
| let context = createCanvasAndContext(device); |
| let context2 = createCanvasAndContext(device); |
| let vertexesF32 = new Float32Array([1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1]); |
| let vertexBuffer = device.createBuffer({size: vertexesF32.byteLength, usage: GPUBufferUsage.VERTEX, mappedAtCreation: true}); |
| new Float32Array(vertexBuffer.getMappedRange()).set(vertexesF32); |
| vertexBuffer.unmap(); |
| let v = await videoWithData(); |
| let code = ` |
| @vertex |
| fn v(@location(0) position : vec4f) -> @builtin(position) vec4f { |
| return position; |
| } |
| |
| @group(0) @binding(0) var s: sampler; |
| @group(0) @binding(1) var et: texture_external; |
| |
| struct S { |
| @location(0) f0: vec4f, |
| @location(1) f1: vec4f, |
| } |
| |
| @fragment |
| fn f(@builtin(position) position: vec4f) -> S { |
| let f0 = 0.5 * textureLoad(et, vec2(u32(position.x/32), u32(position.y/32))); |
| let f1 = 0.8 * textureSampleBaseClampToEdge(et, s, position.xy/${widthAndHeight / 2} - 0.5); |
| return S(f0, f1); |
| } |
| `; |
| let bindGroupLayout = device.createBindGroupLayout({ |
| entries: [ |
| {binding: 0, sampler: {}, visibility: GPUShaderStage.FRAGMENT}, |
| {binding: 1, externalTexture: {}, visibility: GPUShaderStage.FRAGMENT}, |
| ], |
| }); |
| let module = device.createShaderModule({code}); |
| let pipeline = device.createRenderPipeline({ |
| layout: device.createPipelineLayout({bindGroupLayouts: [bindGroupLayout]}), |
| vertex: { |
| module, |
| buffers: [{arrayStride: 8, attributes: [{shaderLocation: 0, offset: 0, format: 'float32x2'}]}], |
| }, |
| fragment: {module, targets: [{format}, {format}]}, |
| }); |
| let sampler0 = device.createSampler({}); |
| |
| log('---'); |
| let oneFrame = async startTime => { |
| log(startTime); |
| device.pushErrorScope('validation'); |
| let commandEncoder = device.createCommandEncoder(); |
| let renderPassEncoder = commandEncoder.beginRenderPass({ |
| colorAttachments: [ |
| { |
| view: context.getCurrentTexture().createView(), |
| clearValue: [0.5, 0.5, 0.5, 0.5], |
| loadOp: 'clear', storeOp: 'store', |
| }, |
| { |
| view: context2.getCurrentTexture().createView(), |
| clearValue: [0.5, 0.5, 0.5, 0.5], |
| loadOp: 'clear', storeOp: 'store', |
| }, |
| ], |
| }); |
| let externalTexture0 = device.importExternalTexture({source: v}); |
| let textureBindGroup = device.createBindGroup({ |
| layout: bindGroupLayout, |
| entries: [ |
| {binding: 0, resource: sampler0}, |
| {binding: 1, resource: externalTexture0}, |
| ], |
| }); |
| renderPassEncoder.setPipeline(pipeline); |
| renderPassEncoder.setVertexBuffer(0, vertexBuffer); |
| renderPassEncoder.setBindGroup(0, textureBindGroup); |
| renderPassEncoder.draw(6); |
| renderPassEncoder.end(); |
| device.queue.submit([commandEncoder.finish()]); |
| await device.queue.onSubmittedWorkDone(); |
| let error = await device.popErrorScope(); |
| if (error) { |
| log(error.message); |
| } else { |
| v.requestVideoFrameCallback(oneFrame); |
| } |
| globalThis.testRunner?.notifyDone(); |
| }; |
| v.requestVideoFrameCallback(oneFrame); |
| }; |
| </script> |
| </html> |