Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metal incorrect output #418

Open
thefiredman opened this issue Feb 6, 2024 · 0 comments
Open

Metal incorrect output #418

thefiredman opened this issue Feb 6, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@thefiredman
Copy link

Summery

On MacOS, using Metal as the render API causes an incorrect output when defining or using:

  • FilterMode: (i.e. sampler),
  • Viewports: apply_viewport
  • PrimitiveType: (i.e. PrimitiveType::Lines)
  • Everything else seems to be working but I can't be exactly sure until I've tested everything.

This is illustrated in this minimal example, allowing to easily choose the output OpenGl or Metal:

use miniquad::*;

#[repr(C)]
struct Vec2 {
    x: f32,
    y: f32,
}
#[repr(C)]
struct Vertex {
    pos: Vec2,
    uv: Vec2,
}

struct Stage {
    ctx: Box<dyn RenderingBackend>,

    pipeline: Pipeline,
    bindings: Bindings,
}

impl Stage {
    pub fn new() -> Stage {
        let mut ctx: Box<dyn RenderingBackend> = window::new_rendering_backend();

        #[rustfmt::skip]
        let vertices: [Vertex; 4] = [
            Vertex { pos : Vec2 { x: -0.5, y: -0.5 }, uv: Vec2 { x: 0., y: 0. } },
            Vertex { pos : Vec2 { x:  0.5, y: -0.5 }, uv: Vec2 { x: 1., y: 0. } },
            Vertex { pos : Vec2 { x:  0.5, y:  0.5 }, uv: Vec2 { x: 1., y: 1. } },
            Vertex { pos : Vec2 { x: -0.5, y:  0.5 }, uv: Vec2 { x: 0., y: 1. } },
        ];
        let vertex_buffer = ctx.new_buffer(
            BufferType::VertexBuffer,
            BufferUsage::Immutable,
            BufferSource::slice(&vertices),
        );

        let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
        let index_buffer = ctx.new_buffer(
            BufferType::IndexBuffer,
            BufferUsage::Immutable,
            BufferSource::slice(&indices),
        );

        let check_bytes: &[u8] = &[
            172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0,
            0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0,
            0, 0, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50,
            50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172,
            50, 50, 255, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50,
            255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50,
            50, 255, 0, 0, 0, 0, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0,
            0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0,
            0, 0, 0, 172, 50, 50, 255, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0,
            172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0,
            0, 172, 50, 50, 255, 0, 0, 0, 0, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50,
            255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50,
            50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255,
            0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50,
            255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0,
            0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0,
            0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 172, 50, 50, 255, 0, 0, 0, 0,
            172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0,
            0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 0, 0, 0, 0, 172, 50, 50,
            255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50,
            50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 172, 50, 50, 255,
            0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50,
            255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 0, 0, 0,
            0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0,
            0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255, 0, 0, 0, 0, 172, 50, 50, 255,
        ];
        let texture = ctx.new_texture_from_data_and_format(
            &check_bytes,
            TextureParams {
                format: TextureFormat::RGBA8,
                min_filter: FilterMode::Nearest,
                mag_filter: FilterMode::Nearest,
                width: 12,
                height: 12,
                ..Default::default()
            },
        );

        let bindings = Bindings {
            vertex_buffers: vec![vertex_buffer],
            index_buffer: index_buffer,
            images: vec![texture],
        };

        let shader = ctx
            .new_shader(
                match ctx.info().backend {
                    Backend::OpenGl => ShaderSource::Glsl {
                        vertex: shader::VERTEX,
                        fragment: shader::FRAGMENT,
                    },
                    Backend::Metal => ShaderSource::Msl {
                        program: shader::METAL,
                    },
                },
                shader::meta(),
            )
            .unwrap();

        let pipeline = ctx.new_pipeline_with_params(
            &[BufferLayout::default()],
            &[
                VertexAttribute::new("in_pos", VertexFormat::Float2),
                VertexAttribute::new("in_uv", VertexFormat::Float2),
            ],
            shader,
            PipelineParams {
                primitive_type: PrimitiveType::Lines,
                ..Default::default()
            },
        );

        Stage {
            pipeline,
            bindings,
            ctx,
        }
    }
}

impl EventHandler for Stage {
    fn update(&mut self) {}

    fn draw(&mut self) {
        let t = date::now();

        self.ctx.begin_default_pass(Default::default());

        self.ctx.apply_pipeline(&self.pipeline);
        self.ctx.apply_bindings(&self.bindings);

        // should only show 500, 500 of screen
        self.ctx.apply_viewport(0, 0, 500, 500);

        for i in 0..10 {
            let t = t + i as f64 * 0.3;

            self.ctx
                .apply_uniforms(UniformsSource::table(&shader::Uniforms {
                    offset: (t.sin() as f32 * 0.5, (t * 3.).cos() as f32 * 0.5),
                }));
            self.ctx.draw(0, 6, 1);
        }
        self.ctx.end_render_pass();

        self.ctx.commit_frame();
    }
}

fn main() {
    let mut conf = conf::Conf::default();
    let metal = std::env::args().nth(1).as_deref() == Some("metal");
    conf.platform.apple_gfx_api = if metal {
        conf::AppleGfxApi::Metal
    } else {
        conf::AppleGfxApi::OpenGl
    };

    miniquad::start(conf, move || Box::new(Stage::new()));
}

mod shader {
    use miniquad::*;

    pub const VERTEX: &str = r#"#version 100
    attribute vec2 in_pos;
    attribute vec2 in_uv;

    uniform vec2 offset;

    varying lowp vec2 texcoord;

    void main() {
        gl_Position = vec4(in_pos + offset, 0, 1);
        texcoord = in_uv;
    }"#;

    pub const FRAGMENT: &str = r#"#version 100
    varying lowp vec2 texcoord;

    uniform sampler2D tex;

    void main() {
        gl_FragColor = texture2D(tex, texcoord);
    }"#;

    pub const METAL: &str = r#"
    #include <metal_stdlib>

    using namespace metal;

    struct Uniforms
    {
        float2 offset;
    };

    struct Vertex
    {
        float2 in_pos   [[attribute(0)]];
        float2 in_uv    [[attribute(1)]];
    };

    struct RasterizerData
    {
        float4 position [[position]];
        float2 uv       [[user(locn0)]];
    };

    vertex RasterizerData vertexShader(
      Vertex v [[stage_in]], 
      constant Uniforms& uniforms [[buffer(0)]])
    {
        RasterizerData out;

        out.position = float4(v.in_pos.xy + uniforms.offset, 0.0, 1.0);
        out.uv = v.in_uv;

        return out;
    }

    fragment float4 fragmentShader(RasterizerData in [[stage_in]], texture2d<float> tex [[texture(0)]], sampler texSmplr [[sampler(0)]])
    {
        return tex.sample(texSmplr, in.uv);
    }"#;

    pub fn meta() -> ShaderMeta {
        ShaderMeta {
            images: vec!["tex".to_string()],
            uniforms: UniformBlockLayout {
                uniforms: vec![UniformDesc::new("offset", UniformType::Float2)],
            },
        }
    }

    #[repr(C)]
    pub struct Uniforms {
        pub offset: (f32, f32),
    }
}

Expected Behaviour

The rendering output should adhere to the specified viewport dimensions, maintain correct filter modes for textures, and accurately render primitive types as defined in the rendering pipeline.

Version

0.4.0-alpha.10

Impact

I cannot support Metal for my game

@not-fl3 not-fl3 added the bug Something isn't working label Feb 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants