How to pass an array of floats to SkSl and access the

64 views
Skip to first unread message

Paweł Niemirski

unread,
Aug 2, 2022, 8:32:24 AMAug 2
to skia-discuss
Hi Skia Team,

I'm working on a custom Skia effect for Android OS.
To implement the effect I want, I need to pass a float array that contains some values ​​needed to calculate the output colors.

My first approach - uniform vec2

In my first approach, I pass the data as |uniform vec2|.

c++:
std::vector<std::array<float, 2>> coord;
SkRuntimeShaderBuilder builder(std::move(effect));
builder.uniform("uCoord").set(coord.data(), size);

SkSl:
uniform vec2 uCoord[20];

The problem appeared when I tried to access a given element using a non-constant value as an index.

int idx = calculete_coord();
uCoord[idx];                   <- index expression must be constant

My second approach - uniform shader/texture

In the second approach, I pass the data as |uniform shader|.

c++:
sk_sp<SkImage> coord;
builder.child("uCoord") = coord->makeShader(SkSamplingOptions());

SkSl:
uniform shader uCoord;

To get the value of a given element (pixel), I use "sample" function.

vec4 value = sample(uCoord, position);

The problem with the |sample| function is that I can use a value to determine the output color (use it as a return from the main), but it does not give the correct values ​​when I try to use it in my calculation.

Did I miss something or is it just a limitation?
Is there any other way to pass some values ​​to SkSl and access them?

BR,
Pawel

Brian Osman

unread,
Aug 2, 2022, 9:33:52 AMAug 2
to skia-d...@googlegroups.com
If you need to use a computed (non-constant) index, then the second approach will be required. What do you mean by the values being incorrect? I'm guessing that you put your array of data into an image (via SkBitmap?), and then sampled from that? Some things to remember if you make an image shader that you sample like this:

1) You probably want to specify SkSamplingOptions{}, so that you don't get any filtering of the image data.
2) Unlike OpenGL, Skia's images are always sampled using pixel coordinates (not normalized 0-1 coords). However, Skia does use the common convention that the centers are at half-pixel offsets. So to sample the top-left pixel in an image shader, you'd want to pass (0.5, 0.5) as coords. The next (to the right) pixel would be (1.5, 0.5), etc.
3) If you happen to be using any color management in your Skia application, then making an image shader to hold your data will apply color space conversion, which might not be what you want. You can prevent that from happening by creating a "raw" image shader instead [1].

I see that you're using sample(), so you must be on an older branch. That was renamed (so it would now look like `uCoord.eval(coord)`). You may need to update to a newer branch to get raw shader support, for example.


--
You received this message because you are subscribed to the Google Groups "skia-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to skia-discuss...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/skia-discuss/578ed6b4-495f-49dd-9ac1-e96e47d58e67n%40googlegroups.com.

Paweł Niemirski

unread,
Aug 2, 2022, 10:38:57 AMAug 2
to skia-discuss
I'm guessing that you put your array of data into an image (via SkBitmap?), and then sampled from that?
Yes, that's right.


What do you mean by the values being incorrect?

Let me give you an example that shows what the problem is.
Assuming the first element of array/texture is vec4(1, 2, 3, 255), the code below will produce the image that has one color - (1,2,3,255).

uniform shader uCoord;

vec4 main(float2 p) {
    vec4 coord = sample(uCoord, float2(0.5, 0.5));  // get first element
    return coord;
}

Now let's add a simple comparison as shown below.

vec4 main(float2 p) {
    vec4 coord = sample(uCoord, float2(0.5, 0.5));
    if (coord.r == 1) {                   <- it should be true
        return vec4(0,255,0,255);
    }

    return coord;                  <- but still returns here
}

In the example above, I would expect the output to be vec4 (0, 255, 0, 255), but it is still (1, 2, 3, 255).


3) If you happen to be using any color management in your Skia application, then making an image shader to hold your data will apply color space conversion, which might not be what you want. You can prevent that from happening by creating a "raw" image shader instead [1].

I'm afraid that the raw shader is not available in the Android implementation. I couldn't find makeRawShader at https://cs.android.com/.


I see that you're using sample(), so you must be on an older branch. That was renamed (so it would now look like `uCoord.eval(coord)`). You may need to update to a newer branch to get raw shader support, for example.

I tried to use the eval function, but got the error - "cannot swizzle value of type 'shader". I believe Android still uses sample instead of eval, at least in the implementation of the stretch effect [1].

[1] https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/effects/StretchEffect.cpp?q=stretchEffec&ss=android%2Fplatform%2Fsuperproject


Brian Osman

unread,
Aug 2, 2022, 11:07:04 AMAug 2
to skia-d...@googlegroups.com
Ah, I think the problem might just be the scale of the values. If you make a texture with colors that are expressed as bytes (all values 0-255), the GPU (and therefore SkSL) always normalizes the color values. So if you set a pixel in your bitmap to (1, 2, 3, 255), and then sample that, the vec4 you get will actually be (1/255, 2/255, 3/255, 255/255) ==> (~.004, ~.008, ~.012, 1)

Paweł Niemirski

unread,
Aug 2, 2022, 11:36:07 AMAug 2
to skia-discuss
Thank you, Brian. That was the case.
Reply all
Reply to author
Forward
0 new messages