Home > database >  Metal Shader Function that can deal with both RGB and YUV textures
Metal Shader Function that can deal with both RGB and YUV textures

Time:12-30

I'm trying to teach myself the basics of computer graphics on the iPhone and Apple's Metal API. I'm trying to do something pretty basic, but I'm getting a little stuck.

What I want to do is just "texture a quad". Basically, I make a rectangle and I have an image texture that covers the rectangle. I can make that work for the basic case where the image texture just comes from an image of a known format, but I'm having trouble figuring out how to make my code a little more generic and able to handle different formats.

For example, sometimes the image texture comes from an image file, which after decoding it, the pixel data is in the RGB format. Sometimes, my image texture actually comes from a video frame where the data is stored in the YUV format.

Ideally, I'd want to create some sort of "sampler" object or function that can just hand me back an RGB color for a particular texture coordinate. In the code where I prepare for rendering, that's the part with context on which format is getting used, and so it would have enough information to figure out which type of sampler should get used. For example, in the video frame case, it knows that it's working with a video frame and so it creates a YUV sampler and passes it the relevant data. And then from my shader code that just wants to read colors, it can just ask for the color at some particular coordinates, and the YUV sampler would do the proper work to compute the right RGB color. If I passed in an RGB sampler instead, it would just read the RGB data without doing any sort of calculations.

I thought this would be really simple to do? I feel like this has to be a common problem for graphics code that deals with textures in different formats, or colorspaces, or whatever? Am I missing something obvious?

How do you do this without writing a bunch of versions of all of your shaders?

CodePudding user response:

Here are functions for transforming RGBA to YUVA and vice versa on the fly.

float4 rgba2yuva(float4 rgba)
{

    float4 yuva = float4(0.0);

    yuva.x = rgba.r * 0.299   rgba.g * 0.587   rgba.b * 0.114;
    yuva.y = rgba.r * -0.169   rgba.g * -0.331   rgba.b * 0.5   0.5;
    yuva.z = rgba.r * 0.5   rgba.g * -0.419   rgba.b * -0.081   0.5;
    yuva.w = rgba.a;

    return yuva;
}

float4 yuva2rgba(float4 yuva)
{

    float4 rgba = float4(0.0);

    rgba.r = yuva.x * 1.0   yuva.y * 0.0   yuva.z * 1.4;
    rgba.g = yuva.x * 1.0   yuva.y * -0.343   yuva.z * -0.711;
    rgba.b = yuva.x * 1.0   yuva.y * 1.765   yuva.z * 0.0;
    rgba.a = yuva.a;

    return rgba;
}

I adapted the code from here: https://github.com/libretro/glsl-shaders/blob/master/nnedi3/shaders/

Simple OpenGL shaders are quite straightforward to port to Metal. I pretty much just changed the datatype vec4 to float4. If you want a half version, just substitute float4 for half4.

CodePudding user response:

metal shader function ARK, now you can use @Jeshua Lacock to convert between the two.

// tweak your color offsets as desired
#include <metal_stdlib>
using namespace metal;

kernel void YUVColorConversion(texture2d<uint, access::read> yTexture [[texture(0)]],
                               texture2d<uint, access::read> uTexture [[texture(1)]],
                               texture2d<uint, access::read> vTexture [[texture(2)]],
                               texture2d<float, access::write> outTexture [[texture(3)]],
                               uint2 gid [[thread_position_in_grid]])
{
    float3 colorOffset = float3(0, -0.5, -0.5);
    float3x3 colorMatrix = float3x3(
                                    float3(1, 1, 1),
                                    float3(0, -0.344, 1.770),
                                    float3(1.403, -0.714, 0)
                                    );

    uint2 uvCoords = uint2(gid.x / 2, gid.y / 2);
    
    float y = yTexture.read(gid).r / 255.0;
    float u = uTexture.read(uvCoords).r / 255.0;
    float v = vTexture.read(uvCoords).r / 255.0;

    float3 yuv = float3(y, u, v);

    float3 rgb = colorMatrix * (yuv   colorOffset);

    outTexture.write(float4(float3(rgb), 1.0), gid);
}

Good ref here , and then you can build pipelines or variants for processing specifically what you need like here

#include <metal_stdlib>
#include <simd/simd.h>
#include <metal_texture>
#include <metal_matrix>
#include <metal_geometric>
#include <metal_math>
#include <metal_graphics>
#include "AAPLShaderTypes.h"

using namespace metal;

// Variables in constant address space.
constant float3 lightPosition = float3(0.0, 1.0, -1.0);

// Per-vertex input structure
struct VertexInput {
    float3 position [[attribute(AAPLVertexAttributePosition)]];
    float3 normal   [[attribute(AAPLVertexAttributeNormal)]];
    half2  texcoord [[attribute(AAPLVertexAttributeTexcoord)]];
};

// Per-vertex output and per-fragment input
typedef struct {
    float4 position [[position]];
    half2  texcoord;
    half4  color;
} ShaderInOut;

// Vertex shader function
vertex ShaderInOut vertexLight(VertexInput in [[stage_in]],
                               constant AAPLFrameUniforms& frameUniforms [[ buffer(AAPLFrameUniformBuffer) ]],
                               constant AAPLMaterialUniforms& materialUniforms [[ buffer(AAPLMaterialUniformBuffer) ]]) {
    ShaderInOut out;
    
    // Vertex projection and translation
    float4 in_position = float4(in.position, 1.0);
    out.position = frameUniforms.projectionView * in_position;
    
    // Per vertex lighting calculations
    float4 eye_normal = normalize(frameUniforms.normal * float4(in.normal, 0.0));
    float n_dot_l = dot(eye_normal.rgb, normalize(lightPosition));
    n_dot_l = fmax(0.0, n_dot_l);
    out.color = half4(materialUniforms.emissiveColor   n_dot_l);

    // Pass through texture coordinate
    out.texcoord = in.texcoord;
    
    return out;
}

// Fragment shader function
fragment half4 fragmentLight(ShaderInOut in [[stage_in]],
                             texture2d<half>  diffuseTexture [[ texture(AAPLDiffuseTextureIndex) ]]) {
    constexpr sampler defaultSampler;
    
    // Blend texture color with input color and output to framebuffer
    half4 color =  diffuseTexture.sample(defaultSampler, float2(in.texcoord)) * in.color;
    
    return color;
}
  • Related