iOS: Metal, CoreImage and GL Shaders

Viewed 338

I would like to switch the camera I’m working on, from GL (GPUImage) to Metal.

I tried to figure out what may be wrong with the Metal shader I’m using in order to apply a lookup filter to a MTLtexture, but I have some banding issues, blue lines, especially when displaying dark gradients.

These blue banding issues appear when applying the metal shader, not when using CoreImage. The rest of the image is just fine. Here is an example using a lookup image to produce high-contrast:

Left:CoreImage Shader; Right: Metal Shader (source image is the exactly same for the two examples)

As you can see, Metal seems to be less precise than CoreImage and produces these strange blue bands.

I'd like to know if this is a normal issue, considering both shaders are an adaptation of Brad Larson's GPUImage work.

The Metal Shader:

kernel void LookupFilterKernel(texture2d<float, access::read> sourceTexture [[texture(0)]],
                        texture2d<float, access::write> outTexture [[texture(1)]],
                        texture2d<float, access::read> lutTexture [[texture(2)]],
                        uint2 gid [[thread_position_in_grid]]){

 float4 color = sourceTexture.read(gid);

 float blueColor = color.b * 63.0;

 float2 quad1;
 quad1.y = floor(floor(blueColor) / 8.0);
 quad1.x = floor(blueColor) - (quad1.y * 8.0);

 float2 quad2;
 quad2.y = floor(ceil(blueColor) / 8.0);
 quad2.x = ceil(blueColor) - (quad2.y * 8.0);

 float2 texPos1;
 texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);
 texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);

 float2 texPos2;
 texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);
 texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);

 float4 newColor1 = lutTexture.read(uint2(texPos1.x * 512 , texPos2.y * 512));
 float4 newColor2 = lutTexture.read(uint2(texPos2.x * 512, texPos2.y * 512 ));

 float4 newColor = mix(newColor1, newColor2, fract(blueColor));


 float4 finalColor = mix(color, float4(newColor.rgb, color.w), 1.0);

 outTexture.write(finalColor, gid);
}

And the CoreImage Shader:

kernel vec4 LookupFilterKernel(sampler inputImage, sampler inputLUT, float intensity) {

 vec4 textureColor = sample(inputImage,samplerCoord(inputImage));
 textureColor = clamp(textureColor, vec4(0.0), vec4(1.0));

 float blueColor = textureColor.b * 63.0;

 vec2 quad1;
 quad1.y = floor(floor(blueColor) / 8.0);
 quad1.x = floor(blueColor) - (quad1.y * 8.0);

 vec2 quad2;
 quad2.y = floor(ceil(blueColor) / 8.0);
 quad2.x = ceil(blueColor) - (quad2.y * 8.0);

 vec2 texPos1;
 texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
 texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);

 vec2 texPos2;
 texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
 texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);

 texPos1.y = 1.0 - texPos1.y;
 texPos2.y = 1.0 - texPos2.y;

 vec4 inputLUTExtent = samplerExtent(inputLUT);

 vec4 newColor1 = sample(inputLUT, samplerTransform(inputLUT, texPos1 * vec2(512.0) + inputLUTExtent.xy));
 vec4 newColor2 = sample(inputLUT, samplerTransform(inputLUT, texPos2 * vec2(512.0) + inputLUTExtent.xy));

 vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
 return mix(textureColor, vec4(newColor.rgb, textureColor.a), intensity);
}

The source texture is a MTLTexture im receiving from the camera's captureOutput function.

If you have any idea why I have this problem or if you need more code / explanations, please let me know.

Many thanks !

EDIT 1:

Ok so after Frank's answer, I tried to fix these gamma issues. So I linearized the LUT texture and the source texture, updating the shader:

static float linearTosRGB(float c) {
return powr(c, 1./2.2);
}

static float srgbToLinear(float c) {
if (c <= 0.04045)
    return c / 12.92;
else
    return powr((c + 0.055) / 1.055, 2.4);
}

kernel void LookupFilterKernel(texture2d<float, access::read> sourceTexture [[texture(0)]],
                        texture2d<float, access::write> outTexture [[texture(1)]],
                        texture2d<float, access::read> lutTexture [[texture(2)]],
                        uint2 gid [[thread_position_in_grid]]){

float4 color = sourceTexture.read(gid);
color.r = srgbToLinear(color.r);
color.g = srgbToLinear(color.g);
color.b = srgbToLinear(color.b);

float blueColor = color.b * 63.0;

float2 quad1;
quad1.y = floor(floor(blueColor) / 8.0);
quad1.x = floor(blueColor) - (quad1.y * 8.0);

float2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);

float2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);

float2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);

float4 newColor1 = lutTexture.read(uint2(texPos1.x * 512 , texPos2.y * 512));
float4 newColor2 = lutTexture.read(uint2(texPos2.x * 512, texPos2.y * 512 ));
float4 newColor = mix(newColor1, newColor2, fract(blueColor));

float4 finalColor = mix(color, float4(newColor.rgb, color.w), 1.0);
finalColor.r = linearTosRGB(finalColor.r);
finalColor.g = linearTosRGB(finalColor.g);
finalColor.b = linearTosRGB(finalColor.b);

outTexture.write(finalColor, gid);
}

The LUT texture is previously transformed into linear gamma with this shader:

kernel void sRGBToLinearKernel(texture2d<float, access::read> sourceTexture [[texture(0)]],
                           texture2d<float, access::write> outTexture [[texture(1)]],
                           uint2 gid [[thread_position_in_grid]]){
float4 color = sourceTexture.read(gid);
color.r = srgbToLinear(color.r);
color.g = srgbToLinear(color.g);
color.b = srgbToLinear(color.b);
outTexture.write(color, gid);
}

And ... it didn't fixed anything. I still have this strange banding. Maybe i'm doing something wrong ?

EDIT 2:

I tried to put the pixel format to MTLPixelFormatRGBA16Float, but result is still wrong. I realized that if I changed this line in the shader:

float4 newColor = mix(newColor1, newColor2, fract(blueColor));

to:

float4 newColor = mix(newColor1, newColor2, 1.0);

the blue bands disappear. I'm assuming that the calculation of quad1 isn't right and produces these bands. So my question is: why this occurs only on metal shader and not in CoreImage or GPUImage's ones ?

0 Answers
Related