@@ -15,6 +15,18 @@ struct VsOut {
1515 @location(0) uv: vec2f,
1616};
1717
18+ struct Uniforms {
19+ // Per-axis scale applied to UVs *around the center* so that the canvas
20+ // samples a sub-rectangle of the texture matching the canvas aspect ratio.
21+ // 'cover' fit: one axis is 1.0, the other is canvasAR / textureAR (or its
22+ // reciprocal), whichever is < 1 — i.e. we crop on the longer axis.
23+ uvScale: vec2f,
24+ };
25+
26+ @group(0) @binding(0) var srcTex: texture_2d<f32>;
27+ @group(0) @binding(1) var srcSampler: sampler;
28+ @group(0) @binding(2) var<uniform> u: Uniforms;
29+
1830@vertex
1931fn vs_main(@builtin(vertex_index) vid: u32) -> VsOut {
2032 // Full-screen triangle.
@@ -34,12 +46,10 @@ fn vs_main(@builtin(vertex_index) vid: u32) -> VsOut {
3446 return out;
3547}
3648
37- @group(0) @binding(0) var srcTex: texture_2d<f32>;
38- @group(0) @binding(1) var srcSampler: sampler;
39-
4049@fragment
4150fn fs_main(in: VsOut) -> @location(0) vec4f {
42- return textureSample(srcTex, srcSampler, in.uv);
51+ let uv = vec2f(0.5) + (in.uv - vec2f(0.5)) * u.uvScale;
52+ return textureSample(srcTex, srcSampler, uv);
4353}
4454` ;
4555
@@ -144,9 +154,24 @@ export const SharedTextureMemory = () => {
144154 memory : GPUSharedTextureMemory ;
145155 texture : GPUTexture ;
146156 bindGroup : GPUBindGroup ;
157+ uniformBuffer : GPUBuffer ;
147158 } ;
148159 let current : Bound | null = null ;
149160
161+ // 'cover' fit: scale UVs around their center so the longer axis of the
162+ // texture is cropped to match the canvas aspect ratio.
163+ const computeUvScale = ( texW : number , texH : number ) : [ number , number ] => {
164+ const canvasAR = canvas . width / canvas . height ;
165+ const texAR = texW / texH ;
166+ if ( texAR > canvasAR ) {
167+ // Texture is wider than the canvas: crop horizontally.
168+ return [ canvasAR / texAR , 1 ] ;
169+ } else {
170+ // Texture is taller than (or equal to) the canvas: crop vertically.
171+ return [ 1 , texAR / canvasAR ] ;
172+ }
173+ } ;
174+
150175 const bindFrame = ( frame : VideoFrame ) : Bound | null => {
151176 try {
152177 const memory = device . importSharedTextureMemory ( {
@@ -159,14 +184,21 @@ export const SharedTextureMemory = () => {
159184 frame . release ( ) ;
160185 return null ;
161186 }
187+ const uniformBuffer = device . createBuffer ( {
188+ size : 16 , // vec2<f32> padded to 16-byte uniform alignment
189+ usage : GPUBufferUsage . UNIFORM | GPUBufferUsage . COPY_DST ,
190+ } ) ;
191+ const [ sx , sy ] = computeUvScale ( frame . width , frame . height ) ;
192+ device . queue . writeBuffer ( uniformBuffer , 0 , new Float32Array ( [ sx , sy ] ) ) ;
162193 const bindGroup = device . createBindGroup ( {
163194 layout : pipeline . getBindGroupLayout ( 0 ) ,
164195 entries : [
165196 { binding : 0 , resource : texture . createView ( ) } ,
166197 { binding : 1 , resource : sampler } ,
198+ { binding : 2 , resource : { buffer : uniformBuffer } } ,
167199 ] ,
168200 } ) ;
169- return { frame, memory, texture, bindGroup } ;
201+ return { frame, memory, texture, bindGroup, uniformBuffer } ;
170202 } catch ( e ) {
171203 console . warn ( "[SharedTextureMemory] bindFrame failed:" , e ) ;
172204 frame . release ( ) ;
@@ -177,6 +209,7 @@ export const SharedTextureMemory = () => {
177209 const releaseBound = ( b : Bound ) => {
178210 b . memory . endAccess ( b . texture ) ;
179211 b . texture . destroy ( ) ;
212+ b . uniformBuffer . destroy ( ) ;
180213 b . frame . release ( ) ;
181214 } ;
182215
0 commit comments