// Full-screen blit of the guest frontbuffer texture, with aspect-ratio // letterboxing. A three-vertex fullscreen triangle covers the viewport; the // fragment shader samples the frontbuffer and emits sRGB colors directly, // letting the swapchain handle the final gamma step. struct Uniforms { // aspect_correction.xy = scaling factors applied to the clip-space // position to letterbox the frontbuffer into the swapchain window. aspect_correction: vec2, // tint for when the frontbuffer is missing / unknown format. When the // guest has not yet swapped a frame, CPU side uploads a 1x1 texture and // we just want the background color. fallback_rgb: vec3, _pad: f32, }; @group(0) @binding(0) var u: Uniforms; @group(0) @binding(1) var frontbuffer: texture_2d; @group(0) @binding(2) var samp: sampler; struct VsOut { @builtin(position) clip: vec4, @location(0) uv: vec2, @location(1) in_bounds: f32, }; @vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VsOut { // Fullscreen triangle: (-1,-1), (3,-1), (-1,3). var pos = array, 3>( vec2(-1.0, -1.0), vec2( 3.0, -1.0), vec2(-1.0, 3.0), ); let p = pos[vid]; // Scale by the aspect-correction factor so the frontbuffer stays at its // native aspect inside the window. let corrected = p * u.aspect_correction; var out: VsOut; out.clip = vec4(corrected, 0.0, 1.0); // UV runs [0,1] across the unscaled fullscreen triangle's extent // [(-1,-1), (3,-1), (-1,3)] → UV = (p+1)*0.5 mapped with Y flipped. let uv_raw = (p + vec2(1.0, 1.0)) * 0.5; out.uv = vec2(uv_raw.x, 1.0 - uv_raw.y); // Also pass a flag telling the FS whether the original position (before // aspect correction) was inside [-1,1]; outside = letterbox region. let in_bounds = f32(all(abs(corrected) <= vec2(1.0, 1.0))); out.in_bounds = in_bounds; return out; } @fragment fn fs_main(in: VsOut) -> @location(0) vec4 { if (in.uv.x < 0.0 || in.uv.x > 1.0 || in.uv.y < 0.0 || in.uv.y > 1.0) { return vec4(u.fallback_rgb, 1.0); } let sample = textureSample(frontbuffer, samp, in.uv); return vec4(sample.rgb, 1.0); }