Skip to content

Commit

Permalink
copyToTexture: refactor colorspace/premul conversion into standalone …
Browse files Browse the repository at this point in the history
…helper
  • Loading branch information
kainino0x committed Mar 18, 2022
1 parent 5303b82 commit a5f3280
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 80 deletions.
67 changes: 66 additions & 1 deletion src/webgpu/util/color_space_conversion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assert } from '../../common/util/util.js';
import { assert, unreachable } from '../../common/util/util.js';

import { multiplyMatrices } from './math.js';

Expand Down Expand Up @@ -191,3 +191,68 @@ export function srgbToDisplayP3(pixel: {

return pixel;
}

type InPlaceColorConversion = (rgba: {
R: number;
G: number;
B: number;
readonly A: number; // Alpha never changes during a conversion.
}) => void;

/**
* Returns a function which applies the specified colorspace/premultiplication conversion.
* Does not clamp, so may return values outside of the `dstColorSpace` gamut, due to either
* color space conversion or alpha premultiplication.
*/
export function makeInPlaceColorConversion({
srcPremultiplied,
dstPremultiplied,
srcColorSpace = 'srgb',
dstColorSpace = 'srgb',
}: {
srcPremultiplied: boolean;
dstPremultiplied: boolean;
srcColorSpace?: PredefinedColorSpace;
dstColorSpace?: GPUPredefinedColorSpace;
}): InPlaceColorConversion {
const requireColorSpaceConversion = srcColorSpace !== dstColorSpace;
const requireUnpremultiplyAlpha =
requireColorSpaceConversion || (srcPremultiplied && !dstPremultiplied);
const requirePremultiplyAlpha =
requireColorSpaceConversion || (!srcPremultiplied && dstPremultiplied);

return rgba => {
assert(rgba.A >= 0.0 && rgba.A <= 1.0, 'rgba.A out of bounds');

if (requireUnpremultiplyAlpha) {
if (rgba.A !== 0.0) {
rgba.R /= rgba.A;
rgba.G /= rgba.A;
rgba.B /= rgba.A;
} else {
assert(
rgba.R === 0.0 && rgba.G === 0.0 && rgba.B === 0.0 && rgba.A === 0.0,
'Unpremultiply ops with alpha value 0.0 requires all channels equals to 0.0'
);
}
}
// It's possible RGB are now > 1.
// This technically represents colors outside the src gamut, so no clamping yet.

if (requireColorSpaceConversion) {
// WebGPU currently only supports dstColorSpace = 'srgb'.
if (srcColorSpace === 'display-p3' && dstColorSpace === 'srgb') {
rgba = displayP3ToSrgb(rgba);
} else {
unreachable();
}
}
// Now RGB may also be negative if the src gamut is larger than the dst gamut.

if (requirePremultiplyAlpha) {
rgba.R *= rgba.A;
rgba.G *= rgba.A;
rgba.B *= rgba.A;
}
};
}
93 changes: 27 additions & 66 deletions src/webgpu/util/copy_to_texture.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { assert, memcpy, unreachable } from '../../common/util/util.js';
import { assert, memcpy } from '../../common/util/util.js';
import { RegularTextureFormat } from '../capability_info.js';
import { GPUTest } from '../gpu_test.js';

import { displayP3ToSrgb } from './color_space_conversion.js';
import { makeInPlaceColorConversion } from './color_space_conversion.js';
import { TexelView } from './texture/texel_view.js';
import { TexelCompareOptions, textureContentIsOKByT2B } from './texture/texture_ok.js';

Expand Down Expand Up @@ -37,74 +37,35 @@ export class CopyToTextureUtils extends GPUTest {
width: number,
height: number,
format: RegularTextureFormat,
srcPremultiplied: boolean,
dstPremultiplied: boolean,
isFlipY: boolean,
srcColorSpace: PredefinedColorSpace = 'srgb',
dstColorSpace: GPUPredefinedColorSpace = 'srgb'
conversion: {
srcPremultiplied: boolean;
dstPremultiplied: boolean;
srcColorSpace?: PredefinedColorSpace;
dstColorSpace?: GPUPredefinedColorSpace;
}
): TexelView {
const requireColorSpaceConversion = srcColorSpace !== dstColorSpace;
const requireUnpremultiplyAlpha =
requireColorSpaceConversion || (srcPremultiplied && !dstPremultiplied);
const requirePremultiplyAlpha =
requireColorSpaceConversion || (!srcPremultiplied && dstPremultiplied);
const applyConversion = makeInPlaceColorConversion(conversion);

const divide = 255.0;
return TexelView.fromTexelsAsColors(format, coords => {
assert(coords.x < width && coords.y < height && coords.z === 0, 'out of bounds');
const y = isFlipY ? height - coords.y - 1 : coords.y;
const pixelPos = y * width + coords.x;

let rgba = {
R: sourcePixels[pixelPos * 4] / divide,
G: sourcePixels[pixelPos * 4 + 1] / divide,
B: sourcePixels[pixelPos * 4 + 2] / divide,
A: sourcePixels[pixelPos * 4 + 3] / divide,
};

if (requireUnpremultiplyAlpha) {
if (rgba.A !== 0.0) {
rgba.R /= rgba.A;
rgba.G /= rgba.A;
rgba.B /= rgba.A;
} else {
assert(
rgba.R === 0.0 && rgba.G === 0.0 && rgba.B === 0.0 && rgba.A === 0.0,
'Unpremultiply ops with alpha value 0.0 requires all channels equals to 0.0'
);
}
}

if (requireColorSpaceConversion) {
// WebGPU support 'srgb' as dstColorSpace only.
if (srcColorSpace === 'display-p3' && dstColorSpace === 'srgb') {
rgba = displayP3ToSrgb(rgba);
} else {
unreachable();
}
}

if (requirePremultiplyAlpha) {
rgba.R *= rgba.A;
rgba.G *= rgba.A;
rgba.B *= rgba.A;
}

// Clamp 0.0 around floats to 0.0
if ((rgba.R > 0.0 && rgba.R < 1.0e-8) || (rgba.R < 0.0 && rgba.R > -1.0e-8)) {
rgba.R = 0.0;
}

if ((rgba.G > 0.0 && rgba.G < 1.0e-8) || (rgba.G < 0.0 && rgba.G > -1.0e-8)) {
rgba.G = 0.0;
}

if ((rgba.B > 0.0 && rgba.B < 1.0e-8) || (rgba.B < 0.0 && rgba.B > -1.0e-8)) {
rgba.B = 0.0;
}

return rgba;
});
return TexelView.fromTexelsAsColors(
format,
coords => {
assert(coords.x < width && coords.y < height && coords.z === 0, 'out of bounds');
const y = isFlipY ? height - coords.y - 1 : coords.y;
const pixelPos = y * width + coords.x;

const rgba = {
R: sourcePixels[pixelPos * 4] / divide,
G: sourcePixels[pixelPos * 4 + 1] / divide,
B: sourcePixels[pixelPos * 4 + 2] / divide,
A: sourcePixels[pixelPos * 4 + 3] / divide,
};
applyConversion(rgba);
return rgba;
},
{ clampToFormatRange: true }
);
}

doTestAndCheckResult(
Expand Down
34 changes: 21 additions & 13 deletions src/webgpu/web_platform/copyToTexture/canvas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,11 @@ g.test('copy_contents_from_2d_context_canvas')
width,
height,
expFormat,
false,
dstPremultiplied,
srcDoFlipYDuringCopy
srcDoFlipYDuringCopy,
{
srcPremultiplied: false,
dstPremultiplied,
}
);

t.doTestAndCheckResult(
Expand Down Expand Up @@ -595,9 +597,11 @@ g.test('copy_contents_from_gl_context_canvas')
width,
height,
expFormat,
srcPremultiplied,
dstPremultiplied,
srcDoFlipYDuringCopy
srcDoFlipYDuringCopy,
{
srcPremultiplied,
dstPremultiplied,
}
);

t.doTestAndCheckResult(
Expand Down Expand Up @@ -725,9 +729,11 @@ g.test('copy_contents_from_gpu_context_canvas')
width,
height,
expFormat,
srcPremultiplied,
dstPremultiplied,
srcDoFlipYDuringCopy
srcDoFlipYDuringCopy,
{
srcPremultiplied,
dstPremultiplied,
}
);

t.doTestAndCheckResult(
Expand Down Expand Up @@ -820,11 +826,13 @@ g.test('color_space_conversion')
width,
height,
dstColorFormat,
false,
dstPremultiplied,
srcDoFlipYDuringCopy,
srcColorSpace,
dstColorSpace
{
srcPremultiplied: false,
dstPremultiplied,
srcColorSpace,
dstColorSpace,
}
);

const texelCompareOptions: TexelCompareOptions = {
Expand Down

0 comments on commit a5f3280

Please sign in to comment.