Chester Fritz
Chester Fritz

Reputation: 357

how to get render target dimensions dynamically in a Metal fragment shader?

This Metal shader is based on a tutorial located here

http://metalkit.org/2016/10/01/using-metalkit-part-2-3-2.html

It draws the yellowish and blue gradient seen in the 3rd image on the page. enter image description here

My goal with this shader was to draw this using a fragment/vertex pair rather than a compute shader.

The result of this shader is made visible by a subclass of an MTKView in an MacOS Swift Playground.

shader code:

#include <metal_stdlib>
using namespace metal;

struct Vertex {
    float4 position [[position]];
    float4 color;
};

vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],
                      uint vid [[vertex_id]]) {
    Vertex in = vertices[vid];
    Vertex out;
    out.position = float4(in.position);
    out.color = in.color;
    return out;
}

fragment float4 fragment_func(Vertex vert [[stage_in]],
                          constant float &timer [[buffer(0)]]) {

    float4 fragColor;

    int width = 400;
    int height = 400;

    float2 resolution = float2(width,height);

    float2 uv = vert.position.xy * 0.5 / resolution;

    float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1 - uv.y));

    fragColor = float4(color,1);

    return(fragColor);
}

swift vertex and index code:

        let vertexData = [
            Vertex(pos: [-1.0, -1.0, 0.0,  1.0], col: [1, 0, 0, 1]),
            Vertex(pos: [ 1.0, -1.0, 0.0,  1.0], col: [0, 1, 0, 1]),
            Vertex(pos: [ 1.0,  1.0, 0.0,  1.0], col: [0, 0, 1, 1]),
            Vertex(pos: [-1.0,  1.0, 0.0,  1.0], col: [1, 1, 1, 1])
        ]

        let indexData: [UInt16] = [
            0, 1, 2, 2, 3, 0
        ]

The dimensions of the final image are hardcoded, 400x400. Is there a way to get the render target dimensions dynamically?

Upvotes: 6

Views: 3142

Answers (1)

Ken Thomases
Ken Thomases

Reputation: 90571

I'm not aware of a way to directly query the render target for its dimensions from a fragment function.

One technique would be to pass the dimensions in via a buffer. The app code would then fill that buffer using the render target texture's properties. You're already effectively doing that for your timer parameter. You'd expand that. For example:

struct params
{
    float timer;
    uint2 size;
};

Then, replace float &timer in your function's parameter list with params &params. Replace uses of timer in the function body with params.timer. Use params.size instead of resolution.

Your app code would, of course, have to change how it's setting up buffer 0 to be a struct of the appropriate size and layout, with both the timer and render target dimensions stored into it.

I think it will also work to pass the render target texture in as a parameter to the function (via the render command encoder's fragment texture table). Your fragment function would declare another parameter, such as texture2d<float, access::read> rt [[texture(0)]], to receive that texture parameter. Then, you can call rt.get_width() and rt.get_height() to get its dimensions.

Upvotes: 3

Related Questions