[[block]] struct Uniforms { u_scale : vec2; u_offset : vec2; }; [[binding(0), group(0)]] var uniforms : Uniforms; struct VertexOutputs { [[location(0)]] texcoords : vec2; [[builtin(position)]] position : vec4; }; [[stage(vertex)]] fn vs_main( [[builtin(vertex_index)]] VertexIndex : u32 ) -> VertexOutputs { var texcoord = array, 3>( vec2(-0.5, 0.0), vec2( 1.5, 0.0), vec2( 0.5, 2.0)); var output : VertexOutputs; output.position = vec4((texcoord[VertexIndex] * 2.0 - vec2(1.0, 1.0)), 0.0, 1.0); // Y component of scale is calculated by the copySizeHeight / textureHeight. Only // flipY case can get negative number. var flipY = uniforms.u_scale.y < 0.0; // Texture coordinate takes top-left as origin point. We need to map the // texture to triangle carefully. if (flipY) { // We need to get the mirror positions(mirrored based on y = 0.5) on flip cases. // Adopt transform to src texture and then mapping it to triangle coord which // do a +1 shift on Y dimension will help us got that mirror position perfectly. output.texcoords = (texcoord[VertexIndex] * uniforms.u_scale + uniforms.u_offset) * vec2(1.0, -1.0) + vec2(0.0, 1.0); } else { // For the normal case, we need to get the exact position. // So mapping texture to triangle firstly then adopt the transform. output.texcoords = (texcoord[VertexIndex] * vec2(1.0, -1.0) + vec2(0.0, 1.0)) * uniforms.u_scale + uniforms.u_offset; } return output; } [[binding(1), group(0)]] var mySampler: sampler; [[binding(2), group(0)]] var myTexture: texture_2d; [[stage(fragment)]] fn fs_main( [[location(0)]] texcoord : vec2 ) -> [[location(0)]] vec4 { // Clamp the texcoord and discard the out-of-bound pixels. var clampedTexcoord = clamp(texcoord, vec2(0.0, 0.0), vec2(1.0, 1.0)); if (!all(clampedTexcoord == texcoord)) { discard; } var srcColor = textureSample(myTexture, mySampler, texcoord); // Swizzling of texture formats when sampling / rendering is handled by the // hardware so we don't need special logic in this shader. This is covered by tests. return srcColor; }