├── voronoi.cl
├── pixelize.cl
├── glow.cl
├── README.md
├── split_gaussian_blur.cl
├── normal_to_height.cl
├── weave.cl
└── edge_detect.cl
/voronoi.cl:
--------------------------------------------------------------------------------
1 | /*
2 | @title: voronoi_v01
3 | @author: melmass
4 | */
5 |
6 | #import "random.h"
7 |
8 | #bind layer !&dst float
9 | #bind parm num_points int
10 |
11 | @KERNEL
12 | {
13 | const int2 pos = (int2)(get_global_id(0), get_global_id(1));
14 | const int2 size = (int2)(@dst.xres, @dst.yres);
15 |
16 | if (pos.x >= size.x || pos.y >= size.y) return;
17 |
18 | float minDist = FLT_MAX;
19 | for (int i = 0; i < @num_points; i++) {
20 | float r1 = VEXrandom_1_1(i) * size.x;
21 | float r2 = VEXrandom_1_1(i + 1) * size.y;
22 |
23 | float2 point = (float2)((int)r1 % size.x, (int)r2 % size.y);
24 | float dist = distance((float2)(pos.x, pos.y), point);
25 | if (dist < minDist) {
26 | minDist = dist;
27 |
28 | }
29 |
30 | }
31 |
32 | @dst.setIndex(pos, minDist / size.x);
33 | }
34 |
--------------------------------------------------------------------------------
/pixelize.cl:
--------------------------------------------------------------------------------
1 | /*
2 | @title: pixelize_v01
3 | @author: melmass
4 | */
5 |
6 | #bind layer !&outColor float4
7 | #bind layer src float4
8 |
9 | #bind parm PixelSize int val=10
10 |
11 | // 0 average | 1 nearest neighbor
12 | #bind parm Mode int val=0
13 |
14 |
15 | @KERNEL
16 | {
17 | const int2 pos = (int2)(get_global_id(0), get_global_id(1));
18 | const int2 size = (int2)(@outColor.xres, @outColor.yres);
19 |
20 | if (pos.x >= size.x || pos.y >= size.y) return;
21 |
22 | int blockX = pos.x / @PixelSize;
23 | int blockY = pos.y / @PixelSize;
24 |
25 | int originX = blockX * @PixelSize;
26 | int originY = blockY * @PixelSize;
27 |
28 |
29 | float4 outputColor;
30 |
31 | // average
32 | if (@Mode == 0) {
33 | float4 accumulatedColor = (float4)(0.0f, 0.0f, 0.0f, 0.0f);
34 | int count = 0;
35 | for (int y = 0; y < @PixelSize; y++) {
36 | for (int x = 0; x < @PixelSize; x++) {
37 | int2 blockPos = (int2)(originX + x, originY + y);
38 | if (blockPos.x < size.x && blockPos.y < size.y) {
39 | accumulatedColor += @src.bufferIndex(blockPos);
40 | count++;
41 | }
42 | }
43 | }
44 | outputColor = accumulatedColor / (float)count;
45 |
46 | // nearest neighbor
47 | } else {
48 | int2 nearestPos = (int2)(originX, originY);
49 | if (nearestPos.x >= size.x || nearestPos.y >= size.y) return;
50 | outputColor = @src.bufferIndex(nearestPos);
51 | }
52 |
53 | @outColor.setIndex(pos, outputColor);
54 | }
55 |
--------------------------------------------------------------------------------
/glow.cl:
--------------------------------------------------------------------------------
1 | /*
2 | @title: gaussian_glow_v01
3 | @author: melmass
4 | */
5 |
6 | #bind layer src float4
7 | #bind layer !&dst float4
8 |
9 | #bind parm intensity float
10 | #bind parm threshold float
11 | #bind parm radius float
12 |
13 | float gaussian(float x, float sigma) {
14 | return exp(-(x*x) / (2.0f * sigma*sigma)) / (sqrt(2.0f * M_PI) * sigma);
15 | }
16 |
17 | @KERNEL
18 | {
19 | const int2 pos = (int2)(get_global_id(0), get_global_id(1));
20 | const int2 size = (int2)(@src.xres, @src.yres);
21 |
22 | if (pos.x >= size.x || pos.y >= size.y) return;
23 |
24 | float4 original = @src.bufferIndex(pos);
25 | float brightness = dot(original.rgb, (float3)(0.299f, 0.587f, 0.114f));
26 | float4 glowPixel = (brightness > @threshold) ? original : (float4)(0.0f);
27 |
28 | float4 blurred = (float4)(0.0f);
29 | float weightSum = 0.0f;
30 | int blurRadius = (int)@radius;
31 |
32 | for (int dy = -blurRadius; dy <= blurRadius; dy++) {
33 | for (int dx = -blurRadius; dx <= blurRadius; dx++) {
34 | int2 samplePos = pos + (int2)(dx, dy);
35 | if (samplePos.x >= 0 && samplePos.x < size.x && samplePos.y >= 0 && samplePos.y < size.y) {
36 | float4 sample = @src.bufferIndex(samplePos);
37 | float sampleBrightness = dot(sample.rgb, (float3)(0.299f, 0.587f, 0.114f));
38 | float4 sampleGlow = (sampleBrightness > @threshold) ? sample : (float4)(0.0f);
39 |
40 | float weight = gaussian(length((float2)(dx, dy)), @radius / 3.0f);
41 | blurred += sampleGlow * weight;
42 | weightSum += weight;
43 | }
44 | }
45 | }
46 | blurred /= weightSum;
47 | float4 result = original + blurred * @intensity;
48 | result = clamp(result, 0.0f, 1.0f);
49 | @dst.setIndex(pos, result);
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # COP OpenCL
2 |
3 | Experimenting with copernicus, the long-awaited GPU texture/shader context of Houdini.
4 | The main purpose of this repo is to show various approaches for learning purposes.
5 |
6 | Do not hesitate to reach or PR nodes that others could learn from.
7 |
8 | ## Nodes
9 |
10 | ### Weave
11 |
12 | This is an almost 1/1 copy of the OSL version for 3DSMax by Zap Anderson ([osl source code](https://github.com/ADN-DevTech/3dsMax-OSL-Shaders/blob/master/3ds%20Max%20Shipping%20Shaders/Weave.osl)),
13 | it outputs color, bump, ID and opacity
14 |
15 |
16 |
17 | ### Split Gaussian Blur
18 |
19 | This is very slow for now compared to the built-in gaussian blur.
20 | The main idea is to use a `float4` as `sigma`, allowing to blur each channels (supports RGBA) independently.
21 | For convenience, it also has a global `float` sigma multiplier.
22 |
23 |
24 |
25 | ### Glow
26 |
27 | Naive glow, like the edge detection this should yield better results if done in multiple kernels (for instance extract highlights, blur,pasteback).
28 | Keeping it for reference too.
29 |
30 |
31 |
32 | ### Pixelize
33 |
34 |
35 |
36 |
37 |
38 | ### Normal to Height
39 |
40 | An (approximative) openCL implementation of this [blog post](https://stannum.io/blog/0IwyJ-)
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/split_gaussian_blur.cl:
--------------------------------------------------------------------------------
1 | /*
2 | @title: split_gaussian_blur_v01
3 | @author: melmass
4 | */
5 |
6 | #bind layer src float4
7 | #bind layer !&blurred float4
8 |
9 | #bind parm sigma float4
10 | #bind parm sigmaMultiplier float
11 |
12 |
13 | float gaussian(float x, float sigma) {
14 | return exp(-(x*x) / (2.0f * sigma*sigma)) / (sqrt(2.0f * M_PI) * sigma);
15 | }
16 |
17 | @KERNEL
18 | {
19 | @sigma = max(0.00001f, @sigma) * @sigmaMultiplier;
20 | const int2 pos = (int2)(get_global_id(0), get_global_id(1));
21 | const int2 size = (int2)(@src.xres, @src.yres);
22 |
23 | if (pos.x >= size.x || pos.y >= size.y) return;
24 |
25 | float4 blurredColor = 0.0f;
26 | float sumR = 0.0f;
27 | float sumG = 0.0f;
28 | float sumB = 0.0f;
29 |
30 | float alpha = 0.0f;
31 | float sumAlpha = 0.0f;
32 |
33 | const int radius = (int)(3.0f * max(max(@sigma.x, @sigma.y), max(@sigma.z, @sigma.w)));
34 |
35 | for (int dy = -radius; dy <= radius; dy++) {
36 | for (int dx = -radius; dx <= radius; dx++) {
37 | int2 samplePos = pos + (int2)(dx, dy);
38 | if (samplePos.x >= 0 && samplePos.x < size.x && samplePos.y >= 0 && samplePos.y < size.y) {
39 | float4 sample = @src.bufferIndex(samplePos);
40 |
41 | float weightR = gaussian(length((float2)(dx, dy)), @sigma.x);
42 | float weightG = gaussian(length((float2)(dx, dy)), @sigma.y);
43 | float weightB = gaussian(length((float2)(dx, dy)), @sigma.z);
44 | float weightA = gaussian(length((float2)(dx, dy)), @sigma.w);
45 |
46 | blurredColor.x += sample.x * weightR;
47 | blurredColor.y += sample.y * weightG;
48 | blurredColor.z += sample.z * weightB;
49 | alpha += sample.w * weightA;
50 |
51 | sumR += weightR;
52 | sumG += weightG;
53 | sumB += weightB;
54 | sumAlpha += weightA;
55 | }
56 | }
57 | }
58 |
59 | if (sumR > 0.0f) blurredColor.x /= sumR;
60 | if (sumG > 0.0f) blurredColor.y /= sumG;
61 | if (sumB > 0.0f) blurredColor.z /= sumB;
62 | if (sumAlpha > 0.0f) alpha /= sumAlpha;
63 |
64 | blurredColor.w = alpha;
65 |
66 | @blurred.setIndex(pos, blurredColor);
67 | }
68 |
--------------------------------------------------------------------------------
/normal_to_height.cl:
--------------------------------------------------------------------------------
1 | /*
2 | @title: NormalToHeightMap_v01
3 | @author: melmass
4 | */
5 |
6 | // inspired by https://stannum.io/blog/data/debump.c
7 |
8 | #bind layer src float4
9 | #bind layer !&dst float
10 |
11 | #bind parm kernelSize float val=10.0
12 | #bind parm highPassThreshold float val=0.6
13 | #bind parm normalizeOutput float val=1.0
14 |
15 | #bind parm gradientScale float val=2.0
16 | #bind parm gradientBias float val=50.0
17 |
18 | float ctldisf(float x, float y) { return sqrt(x * x + y * y); }
19 |
20 | @KERNEL
21 | void normalToHeightMap() {
22 |
23 | const int2 pos = (int2)(get_global_id(0), get_global_id(1));
24 | const int2 size = (int2)(@src.xres, @src.yres);
25 |
26 | if (pos.x >= size.x || pos.y >= size.y) return;
27 |
28 | float4 _N = @src.bufferIndex(pos);
29 |
30 | float3 N = _N.rgb;
31 | N = (float3)(2.0,2.0,2.0) * N - (float3)(1.0,1.0,1.0);
32 | if (length(N) < 1e-3) {
33 | @dst.setIndex(pos, 0.0f);
34 | return;
35 | }
36 |
37 | float Gy = 0.0;
38 | float Gx = 0.0;
39 | const int halfKernelSize = @kernelSize / 2;
40 |
41 | for (int y = -halfKernelSize; y <= halfKernelSize; ++y) {
42 | for (int x = -halfKernelSize; x <= halfKernelSize; ++x) {
43 | const int2 neighborPos = pos + (int2)(x, y);
44 |
45 | if (neighborPos.x < 0 || neighborPos.x >= size.x ||
46 | neighborPos.y < 0 || neighborPos.y >= size.y) {
47 | continue;
48 | }
49 |
50 | float4 _Nn = @src.bufferIndex(neighborPos);
51 | float3 Nn = _Nn.rgb;
52 | Nn = (float3)(2.0,2.0,2.0) * Nn - (float3)(1.0,1.0,1.0);
53 |
54 | const float distanceWeight = ctldisf((float)(x), (float)(y)) /
55 | ctldisf((float)halfKernelSize, (float)halfKernelSize);
56 | const float normalConsistency = dot(Nn, N);
57 |
58 | if (normalConsistency > @highPassThreshold) {
59 | Gy += distanceWeight * Nn.y * @gradientScale;
60 | Gx -= distanceWeight * Nn.x * @gradientScale;
61 | }
62 | }
63 | }
64 |
65 | const float gradientLength = sqrt(Gx*Gx + Gy*Gy) + @gradientBias;
66 | if (gradientLength > 1e-3) {
67 | Gy /= gradientLength;
68 | Gx /= gradientLength;
69 | } else {
70 | @dst.setIndex(pos, 0.5f);
71 | return;
72 | }
73 |
74 | float height = 0.5;
75 | for (int x = 0; x < pos.x + 1; ++x) {
76 | height -= Gx * 2.0f / size.x;
77 | }
78 | for (int y = 0; y < pos.y + 1; ++y) {
79 | height += Gy * 2.0f / size.y;
80 | }
81 |
82 | if (@normalizeOutput) {
83 | height = (height - 0.5) / 0.5;
84 | height = clamp(height, -1.0f, 1.0f);
85 | height = (height + 1.0f) * 0.5;
86 | }
87 |
88 | @dst.setIndex(pos, height);
89 | }
90 |
--------------------------------------------------------------------------------
/weave.cl:
--------------------------------------------------------------------------------
1 | /*
2 | @title: weave_v02
3 | @author: melmass
4 | @description: adaptation of the OSL Weave shader by Zap Andersson
5 | */
6 |
7 |
8 | // osl shader:
9 | // https://github.com/ADN-DevTech/3dsMax-OSL-Shaders/blob/master/3ds%20Max%20Shipping%20Shaders/Weave.osl
10 |
11 | #import "mtlx_noise_internal.h"
12 | #import "random.h"
13 |
14 | #bind layer !&outColor float4
15 | #bind layer !&outBump float val=0.5
16 | #bind layer !&outId float3
17 | #bind layer !&outOpacity float
18 |
19 | #bind parm scale float val=0.04
20 | #bind parm width float val=0.5
21 | #bind parm roundness float val=1.0
22 | #bind parm roundnessBump float val=1.0
23 | #bind parm roundShadow float val=0.5
24 | #bind parm weaveBump float val=0.5
25 | #bind parm weaveShadow float val=0.25
26 | #bind parm frizz float val=0.0
27 | #bind parm frizzBump float val=0.0
28 | #bind parm frizzScale float val=0.1
29 | #bind parm bendyness float val=0.2
30 | #bind parm bendynessScale float val=3.0
31 | #bind parm braidAmplitude float val=0.0
32 | #bind parm braidFrequency float val=0.5
33 | #bind parm opacityFade float val=0.0
34 | #bind parm seed int val=1
35 | #bind parm u_color float4 val={0.0, 0.5, 0.5, 1.0}
36 | #bind parm v_color float4 val={0.0, 0.25, 0.5, 1.0}
37 |
38 | @KERNEL
39 | {
40 | const int2 pos = (int2)(get_global_id(0), get_global_id(1));
41 | const int2 size = (int2)(@outColor.xres, @outColor.yres);
42 |
43 | if (pos.x >= size.x || pos.y >= size.y) return;
44 |
45 | @scale = fit01(@scale,0.0f,0.1f);
46 | @frizzScale = max(0.0001f,@frizzScale);
47 | @bendynessScale = max(0.0001f,@bendynessScale);
48 |
49 | float2 uvw = (float2)(pos.x, pos.y) * @scale;
50 |
51 | // add frizz to the width
52 | float frizz = mx_perlin_noise_float_2(uvw / @frizzScale, (int2)(1, 1));
53 | float w2 = @width + frizz * @frizz;
54 | float w = w2 * 0.5f;
55 |
56 | int uf = (int)floor(uvw.x);
57 | int vf = (int)floor(uvw.y);
58 |
59 | // compute bending
60 | float braidOffset = @braidAmplitude * sin(uvw.y * @braidFrequency * M_PI * 2.0f);
61 | float ubend = mx_perlin_noise_float_2(uvw.y / @bendynessScale + 13.0f * uf, (int2)(1, 1)) * @bendyness + braidOffset;
62 | float vbend = mx_perlin_noise_float_2(uvw.x / @bendynessScale + 23.0f * vf, (int2)(1, 1)) * @bendyness;
63 |
64 | // compute thread coordinates
65 | float sx = uvw.x - uf + ubend;
66 | float sy = uvw.y - vf + vbend;
67 |
68 | int onU = (sy > 0.5f - w && sy < 0.5f + w);
69 | int onV = (sx > 0.5f - w && sx < 0.5f + w);
70 |
71 | float uu = (sy - (0.5f - w)) / w2;
72 | float vv = (sx - (0.5f - w)) / w2;
73 |
74 | // odd or even U / V
75 | int oddU = uf % 2;
76 | int oddV = vf % 2;
77 |
78 | // Are we on neither thread?
79 | if (onU == 0 && onV == 0)
80 | {
81 | // set background color to black/transparent per layer
82 | @outColor.setIndex(pos, (float4)(0.0f, 0.0f, 0.0f, 0.0f));
83 | @outBump.setIndex(pos, 0.0f);
84 | @outId.setIndex(pos, (float3)(0.0f, 0.0f, 0.0f));
85 | @outOpacity.setIndex(pos, 0.0f);
86 | return;
87 | }
88 |
89 | int U_on_top = (oddU ^ oddV) == 0;
90 |
91 | int per = 1;
92 |
93 | // both - disambiguate
94 | if (onU && onV)
95 | {
96 | onU = U_on_top;
97 | onV = (onU == 0);
98 | }
99 |
100 | // random ID for thread
101 | int ThreadID = 1 + (int)((mx_cell_noise_float_1((float)uf + @seed + 45, per) * onV + mx_cell_noise_float_1((float)vf + @seed + 32, per) * onU) * 1024.0f) + onU;
102 | float3 IdColor = VEXrandom_1_3((float)ThreadID + @seed);
103 |
104 | // which color to return
105 | float4 Col;
106 | if (onU)
107 | Col = @u_color;
108 | else
109 | Col = @v_color;
110 |
111 | // compute bump and fake "shadowing"
112 | float r = @roundness;
113 | float weave = (onU) ? sin((uvw.x + oddV) * M_PI) : sin((uvw.y + oddU + 1.0f) * M_PI);
114 |
115 | float ubulge = sin(uu * M_PI);
116 | float vbulge = sin(vv * M_PI);
117 |
118 | float bulge = pow((onU ? ubulge : vbulge), r);
119 |
120 | float opacity = max(pow(ubulge, @opacityFade), pow(vbulge, @opacityFade));
121 |
122 | float bump = 0.2f + weave * @weaveBump + bulge * @roundnessBump + frizz * @frizzBump;
123 |
124 | Col *= mix(1.0f, bulge, @roundShadow);
125 | Col *= mix(1.0f, weave, @weaveShadow);
126 |
127 | @outColor.setIndex(pos, Col);
128 | @outBump.setIndex(pos, bump);
129 | @outId.setIndex(pos, IdColor);
130 | @outOpacity.setIndex(pos, opacity);
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/edge_detect.cl:
--------------------------------------------------------------------------------
1 | /*
2 | @title: edge_detect_v01
3 | @author: melmass
4 | */
5 |
6 | // INFO: kept for reference but it should be broke down into multiple kernels to work properly.
7 | // the builtin EdgeDetect COP is built like that and is a great reference.
8 |
9 | #bind layer src float
10 | #bind layer !&dst float
11 |
12 | #bind parm low_threshold float
13 | #bind parm high_threshold float
14 | #bind parm gaussian_sigma float
15 |
16 | float gaussian(float x, float sigma) {
17 | return exp(-(x*x) / (2.0f * sigma*sigma)) / (sqrt(2.0f * M_PI) * sigma);
18 | }
19 |
20 | @KERNEL
21 | {
22 | const int2 pos = (int2)(get_global_id(0), get_global_id(1));
23 | const int2 size = (int2)(@src.xres, @src.yres);
24 |
25 | if (pos.x >= size.x || pos.y >= size.y) return;
26 |
27 | float blurred = 0.0f;
28 | const int radius = (int)(3.0f * @gaussian_sigma);
29 | float sum = 0.0f;
30 |
31 | for (int dy = -radius; dy <= radius; dy++) {
32 | for (int dx = -radius; dx <= radius; dx++) {
33 | int2 samplePos = pos + (int2)(dx, dy);
34 | if (samplePos.x >= 0 && samplePos.x < size.x && samplePos.y >= 0 && samplePos.y < size.y) {
35 | float sample = @src.bufferIndex(samplePos);
36 | float weight = gaussian(length((float2)(dx, dy)), @gaussian_sigma);
37 | blurred += sample * weight;
38 | sum += weight;
39 | }
40 | }
41 | }
42 | blurred /= sum;
43 |
44 | // compute gradient
45 | float gx = 0.0f, gy = 0.0f;
46 | float sobelX[3] = {-1.0f, 0.0f, 1.0f};
47 | float sobelY[3] = {1.0f, 2.0f, 1.0f};
48 |
49 | for (int i = -1; i <= 1; i++) {
50 | for (int j = -1; j <= 1; j++) {
51 | int2 samplePos = pos + (int2)(i, j);
52 | float sampleBlurred = 0.0f;
53 | float sampleSum = 0.0f;
54 |
55 | // hacky way to sample blurred pixel...
56 | for (int dy = -radius; dy <= radius; dy++) {
57 | for (int dx = -radius; dx <= radius; dx++) {
58 | int2 blurPos = samplePos + (int2)(dx, dy);
59 | if (blurPos.x >= 0 && blurPos.x < size.x && blurPos.y >= 0 && blurPos.y < size.y) {
60 | float sample = @src.bufferIndex(blurPos);
61 | float weight = gaussian(length((float2)(dx, dy)), @gaussian_sigma);
62 | sampleBlurred += sample * weight;
63 | sampleSum += weight;
64 | }
65 | }
66 | }
67 | sampleBlurred /= sampleSum;
68 |
69 | gx += sampleBlurred * sobelX[i+1] * sobelY[j+1];
70 | gy += sampleBlurred * sobelY[i+1] * sobelX[j+1];
71 | }
72 | }
73 |
74 | float gradientMagnitude = sqrt(gx*gx + gy*gy);
75 | float gradientDirection = atan2(gy, gx);
76 |
77 | float angle = (gradientDirection > 0 ? gradientDirection : (gradientDirection + M_PI)) * 180.0f / M_PI;
78 | int2 q = (int2)(0, 0), r = (int2)(0, 0);
79 |
80 | if ((angle >= 0 && angle < 22.5) || (angle >= 157.5 && angle <= 180)) {
81 | q = r = (int2)(1, 0);
82 | } else if (angle >= 22.5 && angle < 67.5) {
83 | q = (int2)(1, 1); r = (int2)(-1, -1);
84 | } else if (angle >= 67.5 && angle < 112.5) {
85 | q = r = (int2)(0, 1);
86 | } else if (angle >= 112.5 && angle < 157.5) {
87 | q = (int2)(-1, 1); r = (int2)(1, -1);
88 | }
89 |
90 | float magnitudeQ = 0.0f, magnitudeR = 0.0f;
91 | float sumQ = 0.0f, sumR = 0.0f;
92 |
93 |
94 | for (int dy = -radius; dy <= radius; dy++) {
95 | for (int dx = -radius; dx <= radius; dx++) {
96 | int2 qPos = pos + q + (int2)(dx, dy);
97 | int2 rPos = pos + r + (int2)(dx, dy);
98 | float weight = gaussian(length((float2)(dx, dy)), @gaussian_sigma);
99 |
100 | if (qPos.x >= 0 && qPos.x < size.x && qPos.y >= 0 && qPos.y < size.y) {
101 | magnitudeQ += @src.bufferIndex(qPos) * weight;
102 | sumQ += weight;
103 | }
104 | if (rPos.x >= 0 && rPos.x < size.x && rPos.y >= 0 && rPos.y < size.y) {
105 | magnitudeR += @src.bufferIndex(rPos) * weight;
106 | sumR += weight;
107 | }
108 | }
109 | }
110 | magnitudeQ /= sumQ;
111 | magnitudeR /= sumR;
112 |
113 | if (gradientMagnitude < magnitudeQ || gradientMagnitude < magnitudeR) {
114 | gradientMagnitude = 0.0f;
115 | }
116 |
117 | // edge
118 | float result;
119 | if (gradientMagnitude > @high_threshold) {
120 | result = 1.0f;
121 | } else if (gradientMagnitude > @low_threshold) {
122 | result = 0.5f;
123 | } else {
124 | result = 0.0f;
125 | }
126 |
127 | @dst.setIndex(pos, result);
128 | }
129 |
--------------------------------------------------------------------------------