Skip to content
This repository has been archived by the owner on Jan 29, 2025. It is now read-only.

Inconsistency in matrix byte representation #1656

Closed
laptou opened this issue Jan 9, 2022 · 4 comments
Closed

Inconsistency in matrix byte representation #1656

laptou opened this issue Jan 9, 2022 · 4 comments
Labels
area: back-end Outputs of shader conversion kind: bug Something isn't working lang: Metal Metal Shading Language

Comments

@laptou
Copy link
Contributor

laptou commented Jan 9, 2022

I am running the same program on Linux (Vulkan backend) and on macOS (Metal backend) and getting two different results, because I have a uniform containing a transformation matrix and it is not being translated the same way on both platforms.

In this example, the transformation matrix is meant to be applied to the image of the goat.

Linux:
image

macOS:
image

The transform matrix that I wrote:

use glam::Affine2;
let transform = Affine2::from_scale(vec2(0.5, 0.5)) * Affine2::from_translation(vec2(0.2, 0.2));

How I'm converting this into bytes:

#[repr(C, align(16))]
#[derive(Debug, Clone, Copy)]
struct TransformUniform {
    // 2D affine transform matrix with padding bytes
    transform: [f32; 8],

    // offsets to crop the image by
    crop: [f32; 4],
}

impl TransformUniform {
    fn new(crop: [f32; 4], transform: Affine2) -> Self {
        TransformUniform {
            crop,
            transform: [
                transform.x_axis[0],
                transform.x_axis[1],
                transform.y_axis[0],
                transform.y_axis[1],
                transform.z_axis[0],
                transform.z_axis[1],
                0.0,
                0.0,
            ],
        }
    }
}

// ...

let transform_uniform = TransformUniform::new([0.0, 0.0, 1.0, 1.0, transform]);
let uniform_buf = state
            .device
            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
                label: Some("TransformEffect uniform buffer"),
                contents: &{
		            const SIZE: usize = ::std::mem::size_of::<TransformUniform>(); // evaluates to 48 bytes
		            let arr: [u8; SIZE] = unsafe { ::std::mem::transmute(transform) };
		            arr
		        },
                usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
            });

My WGSL shader:

// Vertex shader

struct VertexInput {
    [[location(0)]] position: vec3<f32>;
    [[location(1)]] tex_coords: vec2<f32>;
};

struct VertexOutput {
    [[builtin(position)]] clip_position: vec4<f32>;
    [[location(0)]] tex_coords: vec2<f32>;
};

struct TransformUniform {
    transform: mat2x3<f32>;
    crop: vec4<f32>;
};

[[group(1), binding(0)]]
var<uniform> u_transform: TransformUniform;

[[stage(vertex)]]
fn vs_main(
    in: VertexInput,
) -> VertexOutput {
    var out: VertexOutput;
    out.tex_coords = in.tex_coords;
    var transformed: vec2<f32> = vec3<f32>(in.position.xy, 1.0) * u_transform.transform;
    out.clip_position = vec4<f32>(transformed, 0.0, 1.0);
    return out;
}

// Fragment shader

[[group(0), binding(0)]]
var t_input: texture_2d<f32>;

[[group(0), binding(1)]]
var s_input: sampler;

[[stage(fragment)]]
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
    var transformed: vec2<f32> = in.tex_coords * (u_transform.crop.zw - u_transform.crop.xy) + u_transform.crop.xy;
    var color: vec4<f32> = textureSample(t_input, s_input, transformed);

    return color;
}

The (vertex) shader that Vulkan sees, according to Nvidia Nsight (automatically decompiled to GLSL):

#version 450

struct VertexInput
{
    vec3 position;
    vec2 tex_coords;
};

struct VertexOutput
{
    vec4 clip_position;
    vec2 tex_coords;
};

struct TransformUniform
{
    mat2x3 transform;
    vec4 crop;
};

layout(set = 1, binding = 0, std140) uniform u_transform
{
    TransformUniform _m0;
} u_transform_1;

layout(location = 0) in vec3 position;
layout(location = 1) in vec2 tex_coords;
layout(location = 0) out vec2 tex_coords_1;

void main()
{
    VertexInput _23 = VertexInput(position, tex_coords);
    gl_PointSize = 1.0;
    VertexOutput _out;
    _out.tex_coords = _23.tex_coords;
    vec2 transformed = vec3(_23.position.xy, 1.0) * u_transform_1._m0.transform;
    _out.clip_position = vec4(transformed, 0.0, 1.0);
    gl_Position = _out.clip_position;
    tex_coords_1 = _out.tex_coords;
}

The matrix that Vulkan sees, according to Nvidia Nsight:
image

The shader that Metal sees, according to Xcode:

// language: metal2.2
#include <metal_stdlib>
#include <simd/simd.h>

struct DefaultConstructible {
    template<typename T>
    operator T() && {
        return T {};
    }
};
struct VertexInput {
    metal::float3 position;
    metal::float2 tex_coords;
};
struct VertexOutput {
    metal::float4 clip_position;
    metal::float2 tex_coords;
};
struct TransformUniform {
    metal::float2x3 transform;
    metal::float4 crop;
};

struct vs_mainInput {
    metal::float3 position [[attribute(0)]];
    metal::float2 tex_coords [[attribute(1)]];
};
struct vs_mainOutput {
    metal::float4 clip_position [[position]];
    metal::float2 tex_coords [[user(loc0), center_perspective]];
};
vertex vs_mainOutput vs_main(
  vs_mainInput varyings [[stage_in]]
, constant TransformUniform& u_transform [[buffer(0)]]
) {
    const VertexInput in = { varyings.position, varyings.tex_coords };
    VertexOutput out;
    metal::float2 transformed;
    out.tex_coords = in.tex_coords;
    metal::float2x3 _e10 = u_transform.transform;
    transformed = metal::float3(in.position.xy, 1.0) * _e10;
    metal::float2 _e14 = transformed;
    out.clip_position = metal::float4(_e14, 0.0, 1.0);
    VertexOutput _e18 = out;
    const auto _tmp = _e18;
    return vs_mainOutput { _tmp.clip_position, _tmp.tex_coords };
}


struct fs_mainInput {
    metal::float2 tex_coords [[user(loc0), center_perspective]];
};
struct fs_mainOutput {
    metal::float4 member_1 [[color(0)]];
};
fragment fs_mainOutput fs_main(
  fs_mainInput varyings_1 [[stage_in]]
, metal::float4 clip_position [[position]]
, constant TransformUniform& u_transform [[buffer(0)]]
, metal::texture2d<float, metal::access::sample> t_input [[texture(0)]]
, metal::sampler s_input [[sampler(0)]]
) {
    const VertexOutput in_1 = { clip_position, varyings_1.tex_coords };
    metal::float2 transformed_1;
    metal::float4 color;
    metal::float4 _e6 = u_transform.crop;
    metal::float4 _e9 = u_transform.crop;
    metal::float4 _e14 = u_transform.crop;
    transformed_1 = (in_1.tex_coords * (_e6.zw - _e9.xy)) + _e14.xy;
    metal::float2 _e18 = transformed_1;
    metal::float4 _e19 = t_input.sample(s_input, _e18);
    color = _e19;
    metal::float4 _e21 = color;
    return fs_mainOutput { _e21 };
}```
@kvark
Copy link
Member

kvark commented Jan 10, 2022

Thank you for filing! Hopefully, using matrices with sizes without "3" in them should work better, as a workaround for now.

@kvark kvark added area: back-end Outputs of shader conversion kind: bug Something isn't working lang: Metal Metal Shading Language labels Jan 10, 2022
@teoxoy
Copy link
Member

teoxoy commented Mar 19, 2022

I don't think this is an issue with the Metal backend. mat2x3 has the same size and alignment in WGSL and MSL.

I think this is a combination of the manual padding not being inserted in the right spots

transform: [
    transform.x_axis[0],
    transform.x_axis[1],
    transform.y_axis[0],
    // 0.0 (4 byte padding should be here)
    transform.y_axis[1],
    transform.z_axis[0],
    transform.z_axis[1],
    // 0.0, (instead of here)
    0.0,
],

and wrong MatrixStride being output in the spir-v backend (see #1781)

therefore working in Vulkan but not in Metal.

@teoxoy
Copy link
Member

teoxoy commented Apr 7, 2022

@laptou could you confirm this is working properly now (using the latest master)?

@teoxoy
Copy link
Member

teoxoy commented Apr 26, 2022

Will close. Feel free to reopen it if you still have this issue.

@teoxoy teoxoy closed this as completed Apr 26, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: back-end Outputs of shader conversion kind: bug Something isn't working lang: Metal Metal Shading Language
Projects
None yet
Development

No branches or pull requests

3 participants