CopyTextureForBrowser: Support display p3 to Srgb color space conversion

This CL add color space conversion bases for CopyTextureForBrowser.
Theoretically, it could support any color space conversion. But
test cases only cover (Srgb, DisplayP3) to (Srgb).
It could be expanded to more color spaces conversions.

Bug: dawn:1140
Change-Id: I332e6d1f7cf2424fd5f5af83c71fa45c98d2d8ac
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/70780
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Shaobo Yan <shaobo.yan@intel.com>
This commit is contained in:
Yan 2021-12-14 04:51:45 +00:00 committed by Dawn LUCI CQ
parent fa2d418ee6
commit 483bead0e2
5 changed files with 827 additions and 45 deletions

View File

@ -857,20 +857,40 @@
},
"alpha op": {
"category": "enum",
"tags": ["dawn"],
"tags": ["deprecated"],
"values": [
{"value": 0, "name": "dont change"},
{"value": 1, "name": "premultiply"},
{"value": 2, "name": "unpremultiply"}
]
},
"alpha mode": {
"category": "enum",
"tags": ["dawn"],
"values": [
{"value": 0, "name": "premultiplied"},
{"value": 1, "name": "unpremultiplied"}
]
},
"copy texture for browser options": {
"category": "structure",
"extensible": "in",
"tags": ["dawn"],
"_TODO": "support number as length input",
"members": [
{"name": "flipY", "type": "bool", "default": "false"},
{"name": "alphaOp", "type": "alpha op", "default": "dont change"}
{"name": "flip y", "type": "bool", "default": "false"},
{"name": "alpha op", "type": "alpha op", "default": "dont change", "tags": ["deprecated"]},
{"name": "needs color space conversion", "type": "bool", "default": "false"},
{"name": "src alpha mode", "type": "alpha mode", "default": "unpremultiplied"},
{"name": "transfer function parameters count", "type": "uint32_t", "default": "0"},
{"name": "src transfer function parameters", "type": "float", "annotation": "const*",
"length": "transfer function parameters count", "default": "nullptr"},
{"name": "conversion matrix elements count", "type": "uint32_t", "default": "0"},
{"name": "conversion matrix", "type": "float", "annotation": "const*",
"length": "conversion matrix elements count", "default": "nullptr"},
{"name": "dst transfer function parameters", "type": "float", "annotation": "const*",
"length": "transfer function parameters count", "default": "nullptr"},
{"name": "dst alpha mode", "type": "alpha mode", "default": "unpremultiplied"}
]
},
"create compute pipeline async callback": {

View File

@ -14,6 +14,7 @@
#include "dawn_native/CopyTextureForBrowserHelper.h"
#include "common/Log.h"
#include "dawn_native/BindGroup.h"
#include "dawn_native/BindGroupLayout.h"
#include "dawn_native/Buffer.h"
@ -36,10 +37,25 @@ namespace dawn_native {
namespace {
static const char sCopyTextureForBrowserShader[] = R"(
[[block]] struct Uniforms {
u_scale: vec2<f32>;
u_offset: vec2<f32>;
u_alphaOp: u32;
struct GammaTransferParams {
G: f32;
A: f32;
B: f32;
C: f32;
D: f32;
E: f32;
F: f32;
padding: u32;
};
[[block]] struct Uniforms { // offset align size
scale: vec2<f32>; // 0 8 8
offset: vec2<f32>; // 8 8 8
steps_mask: u32; // 16 4 4
// implicit padding; // 20 12
conversion_matrix: mat3x3<f32>; // 32 16 48
gamma_decoding_params: GammaTransferParams; // 80 4 32
gamma_encoding_params: GammaTransferParams; // 112 4 32
};
[[binding(0), group(0)]] var<uniform> uniforms : Uniforms;
@ -49,7 +65,26 @@ namespace dawn_native {
[[builtin(position)]] position : vec4<f32>;
};
[[stage(vertex)]] fn vs_main(
// Chromium uses unified equation to construct gamma decoding function
// and gamma encoding function.
// The logic is:
// if x < D
// linear = C * x + F
// nonlinear = pow(A * x + B, G) + E
// (https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/color_transform.cc;l=541)
// Expand the equation with sign() to make it handle all gamma conversions.
fn gamma_conversion(v: f32, params: GammaTransferParams) -> f32 {
// Linear part: C * x + F
if (abs(v) < params.D) {
return sign(v) * (params.C * abs(v) + params.F);
}
// Gamma part: pow(A * x + B, G) + E
return sign(v) * (pow(params.A * abs(v) + params.B, params.G) + params.E);
}
[[stage(vertex)]]
fn vs_main(
[[builtin(vertex_index)]] VertexIndex : u32
) -> VertexOutputs {
var texcoord = array<vec2<f32>, 3>(
@ -62,7 +97,7 @@ namespace dawn_native {
// Y component of scale is calculated by the copySizeHeight / textureHeight. Only
// flipY case can get negative number.
var flipY = uniforms.u_scale.y < 0.0;
var flipY = uniforms.scale.y < 0.0;
// Texture coordinate takes top-left as origin point. We need to map the
// texture to triangle carefully.
@ -70,14 +105,14 @@ namespace dawn_native {
// We need to get the mirror positions(mirrored based on y = 0.5) on flip cases.
// Adopt transform to src texture and then mapping it to triangle coord which
// do a +1 shift on Y dimension will help us got that mirror position perfectly.
output.texcoords = (texcoord[VertexIndex] * uniforms.u_scale + uniforms.u_offset) *
output.texcoords = (texcoord[VertexIndex] * uniforms.scale + uniforms.offset) *
vec2<f32>(1.0, -1.0) + vec2<f32>(0.0, 1.0);
} else {
// For the normal case, we need to get the exact position.
// So mapping texture to triangle firstly then adopt the transform.
output.texcoords = (texcoord[VertexIndex] *
vec2<f32>(1.0, -1.0) + vec2<f32>(0.0, 1.0)) *
uniforms.u_scale + uniforms.u_offset;
uniforms.scale + uniforms.offset;
}
return output;
@ -86,7 +121,8 @@ namespace dawn_native {
[[binding(1), group(0)]] var mySampler: sampler;
[[binding(2), group(0)]] var myTexture: texture_2d<f32>;
[[stage(fragment)]] fn fs_main(
[[stage(fragment)]]
fn fs_main(
[[location(0)]] texcoord : vec2<f32>
) -> [[location(0)]] vec4<f32> {
// Clamp the texcoord and discard the out-of-bound pixels.
@ -98,45 +134,81 @@ namespace dawn_native {
// Swizzling of texture formats when sampling / rendering is handled by the
// hardware so we don't need special logic in this shader. This is covered by tests.
var srcColor = textureSample(myTexture, mySampler, texcoord);
var color = textureSample(myTexture, mySampler, texcoord);
// Handle alpha. Alpha here helps on the source content and dst content have
// different alpha config. There are three possible ops: DontChange, Premultiply
// and Unpremultiply.
// TODO(crbug.com/1217153): if wgsl support `constexpr` and allow it
// to be case selector, Replace 0u/1u/2u with a constexpr variable with
// meaningful name.
switch(uniforms.u_alphaOp) {
case 0u: { // AlphaOp: DontChange
break;
}
case 1u: { // AlphaOp: Premultiply
srcColor = vec4<f32>(srcColor.rgb * srcColor.a, srcColor.a);
break;
}
case 2u: { // AlphaOp: Unpremultiply
if (srcColor.a != 0.0) {
srcColor = vec4<f32>(srcColor.rgb / srcColor.a, srcColor.a);
}
break;
}
default: {
break;
let kUnpremultiplyStep = 0x01u;
let kDecodeToLinearStep = 0x02u;
let kConvertToDstGamutStep = 0x04u;
let kEncodeToGammaStep = 0x08u;
let kPremultiplyStep = 0x10u;
// Unpremultiply step. Appling color space conversion op on premultiplied source texture
// also needs to unpremultiply first.
if (bool(uniforms.steps_mask & kUnpremultiplyStep)) {
if (color.a != 0.0) {
color = vec4<f32>(color.rgb / color.a, color.a);
}
}
return srcColor;
// Linearize the source color using the source color spaces
// transfer function if it is non-linear.
if (bool(uniforms.steps_mask & kDecodeToLinearStep)) {
color = vec4<f32>(gamma_conversion(color.r, uniforms.gamma_decoding_params),
gamma_conversion(color.g, uniforms.gamma_decoding_params),
gamma_conversion(color.b, uniforms.gamma_decoding_params),
color.a);
}
// Convert unpremultiplied, linear source colors to the destination gamut by
// multiplying by a 3x3 matrix. Calculate transformFromXYZD50 * transformToXYZD50
// in CPU side and upload the final result in uniforms.
if (bool(uniforms.steps_mask & kConvertToDstGamutStep)) {
color = vec4<f32>(uniforms.conversion_matrix * color.rgb, color.a);
}
// Encode that color using the inverse of the destination color
// spaces transfer function if it is non-linear.
if (bool(uniforms.steps_mask & kEncodeToGammaStep)) {
color = vec4<f32>(gamma_conversion(color.r, uniforms.gamma_encoding_params),
gamma_conversion(color.g, uniforms.gamma_encoding_params),
gamma_conversion(color.b, uniforms.gamma_encoding_params),
color.a);
}
// Premultiply step.
if (bool(uniforms.steps_mask & kPremultiplyStep)) {
color = vec4<f32>(color.rgb * color.a, color.a);
}
return color;
}
)";
// Follow the same order of skcms_TransferFunction
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/include/third_party/skcms/skcms.h;l=46;
struct GammaTransferParams {
float G = 0.0;
float A = 0.0;
float B = 0.0;
float C = 0.0;
float D = 0.0;
float E = 0.0;
float F = 0.0;
uint32_t padding = 0;
};
struct Uniform {
float scaleX;
float scaleY;
float offsetX;
float offsetY;
wgpu::AlphaOp alphaOp;
uint32_t stepsMask = 0;
const std::array<uint32_t, 3> padding = {}; // 12 bytes padding
std::array<float, 12> conversionMatrix = {};
GammaTransferParams gammaDecodingParams = {};
GammaTransferParams gammaEncodingParams = {};
};
static_assert(sizeof(Uniform) == 20, "");
static_assert(sizeof(Uniform) == 144, "");
// TODO(crbug.com/dawn/856): Expand copyTextureForBrowser to support any
// non-depth, non-stencil, non-compressed texture format pair copy. Now this API
@ -232,7 +304,6 @@ namespace dawn_native {
return GetCachedPipeline(store, dstFormat);
}
} // anonymous namespace
MaybeError ValidateCopyTextureForBrowser(DeviceBase* device,
@ -276,7 +347,27 @@ namespace dawn_native {
destination->texture->GetFormat().format));
DAWN_INVALID_IF(options->nextInChain != nullptr, "nextInChain must be nullptr");
// TODO(crbug.com/dawn/1140): Remove alphaOp and wgpu::AlphaState::DontChange.
DAWN_TRY(ValidateAlphaOp(options->alphaOp));
DAWN_TRY(ValidateAlphaMode(options->srcAlphaMode));
DAWN_TRY(ValidateAlphaMode(options->dstAlphaMode));
if (options->needsColorSpaceConversion) {
DAWN_INVALID_IF(options->transferFunctionParametersCount != 7u,
"Invalid transfer"
" function parameter count (%u).",
options->transferFunctionParametersCount);
DAWN_INVALID_IF(options->conversionMatrixElementsCount != 9u,
"Invalid conversion matrix elements count (%u).",
options->conversionMatrixElementsCount);
DAWN_INVALID_IF(options->srcTransferFunctionParameters == nullptr,
"srcTransferFunctionParameters is nullptr when doing color conversion");
DAWN_INVALID_IF(options->conversionMatrix == nullptr,
"conversionMatrix is nullptr when doing color conversion");
DAWN_INVALID_IF(options->dstTransferFunctionParameters == nullptr,
"dstTransferFunctionParameters is nullptr when doing color conversion");
}
return {};
}
@ -309,8 +400,7 @@ namespace dawn_native {
copySize->width / static_cast<float>(srcTextureSize.width),
copySize->height / static_cast<float>(srcTextureSize.height), // scale
source->origin.x / static_cast<float>(srcTextureSize.width),
source->origin.y / static_cast<float>(srcTextureSize.height), // offset
wgpu::AlphaOp::DontChange // alphaOp default value: DontChange
source->origin.y / static_cast<float>(srcTextureSize.height) // offset
};
// Handle flipY. FlipY here means we flip the source texture firstly and then
@ -321,8 +411,89 @@ namespace dawn_native {
uniformData.offsetY += copySize->height / static_cast<float>(srcTextureSize.height);
}
// Set alpha op.
uniformData.alphaOp = options->alphaOp;
uint32_t stepsMask = 0u;
// Steps to do color space conversion
// From https://skia.org/docs/user/color/
// - unpremultiply if the source color is premultiplied; Alpha is not involved in color
// management, and we need to divide it out if its multiplied in.
// - linearize the source color using the source color spaces transfer function
// - convert those unpremultiplied, linear source colors to XYZ D50 gamut by multiplying by
// a 3x3 matrix.
// - convert those XYZ D50 colors to the destination gamut by multiplying by a 3x3 matrix.
// - encode that color using the inverse of the destination color spaces transfer function.
// - premultiply by alpha if the destination is premultiplied.
// The reason to choose XYZ D50 as intermediate color space:
// From http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html
// "Since the Lab TIFF specification, the ICC profile specification and
// Adobe Photoshop all use a D50"
constexpr uint32_t kUnpremultiplyStep = 0x01;
constexpr uint32_t kDecodeToLinearStep = 0x02;
constexpr uint32_t kConvertToDstGamutStep = 0x04;
constexpr uint32_t kEncodeToGammaStep = 0x08;
constexpr uint32_t kPremultiplyStep = 0x10;
if (options->srcAlphaMode == wgpu::AlphaMode::Premultiplied) {
if (options->needsColorSpaceConversion ||
options->srcAlphaMode != options->dstAlphaMode) {
stepsMask |= kUnpremultiplyStep;
}
}
if (options->needsColorSpaceConversion) {
stepsMask |= kDecodeToLinearStep;
const float* decodingParams = options->srcTransferFunctionParameters;
uniformData.gammaDecodingParams = {
decodingParams[0], decodingParams[1], decodingParams[2], decodingParams[3],
decodingParams[4], decodingParams[5], decodingParams[6]};
stepsMask |= kConvertToDstGamutStep;
const float* matrix = options->conversionMatrix;
uniformData.conversionMatrix = {{
matrix[0],
matrix[1],
matrix[2],
0.0,
matrix[3],
matrix[4],
matrix[5],
0.0,
matrix[6],
matrix[7],
matrix[8],
0.0,
}};
stepsMask |= kEncodeToGammaStep;
const float* encodingParams = options->dstTransferFunctionParameters;
uniformData.gammaEncodingParams = {
encodingParams[0], encodingParams[1], encodingParams[2], encodingParams[3],
encodingParams[4], encodingParams[5], encodingParams[6]};
}
if (options->dstAlphaMode == wgpu::AlphaMode::Premultiplied) {
if (options->needsColorSpaceConversion ||
options->srcAlphaMode != options->dstAlphaMode) {
stepsMask |= kPremultiplyStep;
}
}
if (options->alphaOp != wgpu::AlphaOp::DontChange) {
dawn::WarningLog() << "CopyTextureForBrowserOption.alphaOp has been deprecated.";
}
// TODO(crbugs.com/dawn/1140): AlphaOp will be deprecated
if (options->alphaOp == wgpu::AlphaOp::Premultiply) {
stepsMask |= kPremultiplyStep;
}
if (options->alphaOp == wgpu::AlphaOp::Unpremultiply) {
stepsMask |= kUnpremultiplyStep;
}
uniformData.stepsMask = stepsMask;
Ref<BufferBase> uniformBuffer;
DAWN_TRY_ASSIGN(

View File

@ -25,6 +25,7 @@
#include "tests/ParamGenerator.h"
#include "tests/ToggleParser.h"
#include "utils/ScopedAutoreleasePool.h"
#include "utils/TextureUtils.h"
#include <dawn_platform/DawnPlatform.h>
#include <gmock/gmock.h>
@ -95,6 +96,9 @@
#define EXPECT_TEXTURE_EQ(...) AddTextureExpectation(__FILE__, __LINE__, __VA_ARGS__)
#define EXPECT_TEXTURE_FLOAT16_EQ(...) \
AddTextureExpectation<float, uint16_t>(__FILE__, __LINE__, __VA_ARGS__)
#define ASSERT_DEVICE_ERROR_MSG(statement, matcher) \
StartExpectDeviceError(matcher); \
statement; \
@ -353,6 +357,30 @@ class DawnTestBase {
// T - expected value Type
// U - actual value Type (defaults = T)
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
const T* expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
wgpu::TextureFormat format,
T tolerance = 0,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
uint32_t texelBlockSize = utils::GetTexelBlockSizeInBytes(format);
uint32_t texelComponentCount = utils::GetWGSLRenderableColorTextureComponentCount(format);
return AddTextureExpectationImpl(
file, line,
new detail::ExpectEq<T, U>(
expectedData,
texelComponentCount * extent.width * extent.height * extent.depthOrArrayLayers,
tolerance),
texture, origin, extent, level, aspect, texelBlockSize, bytesPerRow);
}
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,

View File

@ -28,6 +28,11 @@ namespace {
static constexpr uint64_t kDefaultTextureWidth = 10;
static constexpr uint64_t kDefaultTextureHeight = 1;
enum class ColorSpace : uint32_t {
SRGB = 0x00,
DisplayP3 = 0x01,
};
using Alpha = wgpu::AlphaOp;
DAWN_TEST_PARAM_STRUCT(AlphaTestParams, Alpha);
@ -37,6 +42,10 @@ namespace {
using DstOrigin = wgpu::Origin3D;
using CopySize = wgpu::Extent3D;
using FlipY = bool;
using SrcColorSpace = ColorSpace;
using DstColorSpace = ColorSpace;
using SrcAlphaMode = wgpu::AlphaMode;
using DstAlphaMode = wgpu::AlphaMode;
std::ostream& operator<<(std::ostream& o, wgpu::Origin3D origin) {
o << origin.x << ", " << origin.y << ", " << origin.z;
@ -48,9 +57,98 @@ namespace {
return o;
}
std::ostream& operator<<(std::ostream& o, ColorSpace space) {
o << static_cast<uint32_t>(space);
return o;
}
DAWN_TEST_PARAM_STRUCT(FormatTestParams, SrcFormat, DstFormat);
DAWN_TEST_PARAM_STRUCT(SubRectTestParams, SrcOrigin, DstOrigin, CopySize, FlipY);
DAWN_TEST_PARAM_STRUCT(ColorSpaceTestParams,
DstFormat,
SrcColorSpace,
DstColorSpace,
SrcAlphaMode,
DstAlphaMode);
// Color Space table
struct ColorSpaceInfo {
ColorSpace index;
std::array<float, 9> toXYZD50; // 3x3 row major transform matrix
std::array<float, 9> fromXYZD50; // inverse transform matrix of toXYZD50, precomputed
std::array<float, 7> gammaDecodingParams; // Follow { A, B, G, E, epsilon, C, F } order
std::array<float, 7> gammaEncodingParams; // inverse op of decoding, precomputed
bool isNonLinear;
bool isExtended; // For extended color space.
};
static constexpr size_t kSupportedColorSpaceCount = 2;
static constexpr std::array<ColorSpaceInfo, kSupportedColorSpaceCount> ColorSpaceTable = {{
// sRGB,
// Got primary attributes from https://drafts.csswg.org/css-color/#predefined-sRGB
// Use matrices from
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html#WSMatrices
// Get gamma-linear conversion params from https://en.wikipedia.org/wiki/SRGB with some
// mathematics.
{
//
ColorSpace::SRGB,
{{
//
0.4360747, 0.3850649, 0.1430804, //
0.2225045, 0.7168786, 0.0606169, //
0.0139322, 0.0971045, 0.7141733 //
}},
{{
//
3.1338561, -1.6168667, -0.4906146, //
-0.9787684, 1.9161415, 0.0334540, //
0.0719453, -0.2289914, 1.4052427 //
}},
// {G, A, B, C, D, E, F, }
{{2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 4.045e-02, 0.0, 0.0}},
{{1.0 / 2.4, 1.13711 /*pow(1.055, 2.4)*/, 0.0, 12.92f, 3.1308e-03, -0.055, 0.0}},
true,
true //
},
// Display P3, got primary attributes from
// https://www.w3.org/TR/css-color-4/#valdef-color-display-p3
// Use equations found in
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html,
// Use Bradford method to do D65 to D50 transform.
// Get matrices with help of http://www.russellcottrell.com/photo/matrixCalculator.htm
// Gamma-linear conversion params is the same as Srgb.
{
//
ColorSpace::DisplayP3,
{{
//
0.5151114, 0.2919612, 0.1571274, //
0.2411865, 0.6922440, 0.0665695, //
-0.0010491, 0.0418832, 0.7842659 //
}},
{{
//
2.4039872, -0.9898498, -0.3976181, //
-0.8422138, 1.7988188, 0.0160511, //
0.0481937, -0.0973889, 1.2736887 //
}},
// {G, A, B, C, D, E, F, }
{{2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 4.045e-02, 0.0, 0.0}},
{{1.0 / 2.4, 1.13711 /*pow(1.055, 2.4)*/, 0.0, 12.92f, 3.1308e-03, -0.055, 0.0}},
true,
false //
}
//
}};
} // anonymous namespace
template <typename Parent>
@ -578,21 +676,323 @@ class CopyTextureForBrowser_AlphaOps
}
};
class CopyTextureForBrowser_ColorSpace
: public CopyTextureForBrowserTests<DawnTestWithParams<ColorSpaceTestParams>> {
protected:
const ColorSpaceInfo& GetColorSpaceInfo(ColorSpace colorSpace) {
uint32_t index = static_cast<uint32_t>(colorSpace);
ASSERT(index < ColorSpaceTable.size());
ASSERT(ColorSpaceTable[index].index == colorSpace);
return ColorSpaceTable[index];
}
std::array<float, 9> GetConversionMatrix(ColorSpace src, ColorSpace dst) {
const ColorSpaceInfo& srcColorSpace = GetColorSpaceInfo(src);
const ColorSpaceInfo& dstColorSpace = GetColorSpaceInfo(dst);
const std::array<float, 9> toXYZD50 = srcColorSpace.toXYZD50;
const std::array<float, 9> fromXYZD50 = dstColorSpace.fromXYZD50;
// Fuse the transform matrix. The color space transformation equation is:
// Pixels = fromXYZD50 * toXYZD50 * Pixels.
// Calculate fromXYZD50 * toXYZD50 to simplify
// Add a padding in each row for Mat3x3 in wgsl uniform(mat3x3, Align(16), Size(48)).
std::array<float, 9> fuseMatrix = {};
// Mat3x3 * Mat3x3
for (uint32_t row = 0; row < 3; ++row) {
for (uint32_t col = 0; col < 3; ++col) {
// Transpose the matrix from row major to column major for wgsl.
fuseMatrix[col * 3 + row] = fromXYZD50[row * 3 + 0] * toXYZD50[col] +
fromXYZD50[row * 3 + 1] * toXYZD50[3 + col] +
fromXYZD50[row * 3 + 2] * toXYZD50[3 * 2 + col];
}
}
return fuseMatrix;
}
// TODO(crbug.com/dawn/1140): Generate source data automatically.
std::vector<RGBA8> GetSourceData(wgpu::AlphaMode srcTextureAlphaMode) {
if (srcTextureAlphaMode == wgpu::AlphaMode::Premultiplied) {
return std::vector<RGBA8>{
RGBA8(0, 102, 102, 102), // a = 0.4
RGBA8(102, 0, 0, 102), // a = 0.4
RGBA8(153, 0, 0, 153), // a = 0.6
RGBA8(255, 0, 0, 255), // a = 1.0
RGBA8(153, 0, 153, 153), // a = 0.6
RGBA8(0, 102, 0, 102), // a = 0.4
RGBA8(0, 153, 0, 153), // a = 0.6
RGBA8(0, 255, 0, 255), // a = 1.0
RGBA8(255, 255, 0, 255), // a = 1.0
RGBA8(0, 0, 102, 102), // a = 0.4
RGBA8(0, 0, 153, 153), // a = 0.6
RGBA8(0, 0, 255, 255), // a = 1.0
};
}
return std::vector<RGBA8>{
// Take RGBA8Unorm as example:
// R channel has different values
RGBA8(0, 255, 255, 255), // r = 0.0
RGBA8(102, 0, 0, 255), // r = 0.4
RGBA8(153, 0, 0, 255), // r = 0.6
RGBA8(255, 0, 0, 255), // r = 1.0
// G channel has different values
RGBA8(255, 0, 255, 255), // g = 0.0
RGBA8(0, 102, 0, 255), // g = 0.4
RGBA8(0, 153, 0, 255), // g = 0.6
RGBA8(0, 255, 0, 255), // g = 1.0
// B channel has different values
RGBA8(255, 255, 0, 255), // b = 0.0
RGBA8(0, 0, 102, 255), // b = 0.4
RGBA8(0, 0, 153, 255), // b = 0.6
RGBA8(0, 0, 255, 255), // b = 1.0
};
}
// TODO(crbug.com/dawn/1140): Current expected values are from ColorSync utils
// tool on Mac. Should implement CPU or compute shader algorithm to do color
// conversion and use the result as expected data.
std::vector<float> GetExpectedData(ColorSpace srcColorSpace,
ColorSpace dstColorSpace,
wgpu::AlphaMode srcTextureAlphaMode,
wgpu::AlphaMode dstTextureAlphaMode) {
if (srcTextureAlphaMode == wgpu::AlphaMode::Premultiplied) {
return GetExpectedDataForPremultipliedSource(srcColorSpace, dstColorSpace,
dstTextureAlphaMode);
}
return GetExpectedDataForSeperateSource(srcColorSpace, dstColorSpace);
}
std::vector<float> GeneratePremultipliedResult(std::vector<float> result) {
// Four channels per pixel
for (uint32_t i = 0; i < result.size(); i += 4) {
result[i] *= result[i + 3];
result[i + 1] *= result[i + 3];
result[i + 2] *= result[i + 3];
}
return result;
}
std::vector<float> GetExpectedDataForPremultipliedSource(ColorSpace srcColorSpace,
ColorSpace dstColorSpace,
wgpu::AlphaMode dstTextureAlphaMode) {
if (srcColorSpace == dstColorSpace) {
std::vector<float> expected = {
0.0, 1.0, 1.0, 0.4, //
1.0, 0.0, 0.0, 0.4, //
1.0, 0.0, 0.0, 0.6, //
1.0, 0.0, 0.0, 1.0, //
1.0, 0.0, 1.0, 0.6, //
0.0, 1.0, 0.0, 0.4, //
0.0, 1.0, 0.0, 0.6, //
0.0, 1.0, 0.0, 1.0, //
1.0, 1.0, 0.0, 1.0, //
0.0, 0.0, 1.0, 0.4, //
0.0, 0.0, 1.0, 0.6, //
0.0, 0.0, 1.0, 1.0, //
};
return dstTextureAlphaMode == wgpu::AlphaMode::Premultiplied
? GeneratePremultipliedResult(expected)
: expected;
}
switch (srcColorSpace) {
case ColorSpace::DisplayP3: {
switch (dstColorSpace) {
case ColorSpace::SRGB: {
std::vector<float> expected = {
-0.5118, 1.0183, 1.0085, 0.4, //
1.093, -0.2267, -0.1501, 0.4, //
1.093, -0.2267, -0.1501, 0.6, //
1.093, -0.2267, -0.1501, 1.0, //
1.093, -0.2266, 1.0337, 0.6, //
-0.5118, 1.0183, -0.3107, 0.4, //
-0.5118, 1.0183, -0.3107, 0.6, //
-0.5118, 1.0183, -0.3107, 1.0, //
0.9999, 1.0001, -0.3462, 1.0, //
0.0002, 0.0004, 1.0419, 0.4, //
0.0002, 0.0004, 1.0419, 0.6, //
0.0002, 0.0004, 1.0419, 1.0, //
};
return dstTextureAlphaMode == wgpu::AlphaMode::Premultiplied
? GeneratePremultipliedResult(expected)
: expected;
}
default:
UNREACHABLE();
}
}
default:
break;
}
UNREACHABLE();
}
std::vector<float> GetExpectedDataForSeperateSource(ColorSpace srcColorSpace,
ColorSpace dstColorSpace) {
if (srcColorSpace == dstColorSpace) {
return std::vector<float>{
0.0, 1.0, 1.0, 1.0, //
0.4, 0.0, 0.0, 1.0, //
0.6, 0.0, 0.0, 1.0, //
1.0, 0.0, 0.0, 1.0, //
1.0, 0.0, 1.0, 1.0, //
0.0, 0.4, 0.0, 1.0, //
0.0, 0.6, 0.0, 1.0, //
0.0, 1.0, 0.0, 1.0, //
1.0, 1.0, 0.0, 1.0, //
0.0, 0.0, 0.4, 1.0, //
0.0, 0.0, 0.6, 1.0, //
0.0, 0.0, 1.0, 1.0, //
};
}
switch (srcColorSpace) {
case ColorSpace::DisplayP3: {
switch (dstColorSpace) {
case ColorSpace::SRGB: {
return std::vector<float>{
-0.5118, 1.0183, 1.0085, 1.0, //
0.4401, -0.0665, -0.0337, 1.0, //
0.6578, -0.1199, -0.0723, 1.0, //
1.093, -0.2267, -0.1501, 1.0, //
1.093, -0.2266, 1.0337, 1.0, //
-0.1894, 0.4079, -0.1027, 1.0, //
-0.2969, 0.6114, -0.1720, 1.0, //
-0.5118, 1.0183, -0.3107, 1.0, //
0.9999, 1.0001, -0.3462, 1.0, //
0.0000, 0.0001, 0.4181, 1.0, //
0.0001, 0.0001, 0.6260, 1.0, //
0.0002, 0.0004, 1.0419, 1.0, //
};
}
default:
UNREACHABLE();
}
}
default:
break;
}
UNREACHABLE();
}
void DoColorSpaceConversionTest() {
constexpr uint32_t kWidth = 12;
constexpr uint32_t kHeight = 1;
TextureSpec srcTextureSpec;
srcTextureSpec.textureSize = {kWidth, kHeight};
TextureSpec dstTextureSpec;
dstTextureSpec.textureSize = {kWidth, kHeight};
dstTextureSpec.format = GetParam().mDstFormat;
ColorSpace srcColorSpace = GetParam().mSrcColorSpace;
ColorSpace dstColorSpace = GetParam().mDstColorSpace;
ColorSpaceInfo srcColorSpaceInfo = GetColorSpaceInfo(srcColorSpace);
ColorSpaceInfo dstColorSpaceInfo = GetColorSpaceInfo(dstColorSpace);
std::array<float, 9> matrix = GetConversionMatrix(srcColorSpace, dstColorSpace);
wgpu::CopyTextureForBrowserOptions options = {};
options.needsColorSpaceConversion = srcColorSpace != dstColorSpace;
options.srcAlphaMode = GetParam().mSrcAlphaMode;
options.transferFunctionParametersCount = 7;
options.srcTransferFunctionParameters = srcColorSpaceInfo.gammaDecodingParams.data();
options.conversionMatrixElementsCount = 9;
options.conversionMatrix = matrix.data();
options.dstTransferFunctionParameters = dstColorSpaceInfo.gammaEncodingParams.data();
options.dstAlphaMode = GetParam().mDstAlphaMode;
std::vector<RGBA8> sourceTextureData = GetSourceData(options.srcAlphaMode);
const wgpu::Extent3D& copySize = {kWidth, kHeight};
const utils::TextureDataCopyLayout srcCopyLayout =
utils::GetTextureDataCopyLayoutForTextureAtLevel(
kTextureFormat,
{srcTextureSpec.textureSize.width, srcTextureSpec.textureSize.height},
srcTextureSpec.level);
wgpu::TextureUsage srcUsage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst |
wgpu::TextureUsage::TextureBinding;
wgpu::Texture srcTexture = this->CreateAndInitTexture(
srcTextureSpec, srcUsage, srcCopyLayout, sourceTextureData.data(),
sourceTextureData.size() * sizeof(RGBA8));
// Create dst texture.
wgpu::Texture dstTexture = this->CreateTexture(
dstTextureSpec, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding |
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
// Perform the texture to texture copy
this->RunCopyExternalImageToTexture(srcTextureSpec, srcTexture, dstTextureSpec, dstTexture,
copySize, options);
std::vector<float> expectedData = GetExpectedData(
srcColorSpace, dstColorSpace, options.srcAlphaMode, options.dstAlphaMode);
// The value provided by Apple's ColorSync Utility.
float tolerance = 0.001;
if (dstTextureSpec.format == wgpu::TextureFormat::RGBA16Float) {
EXPECT_TEXTURE_FLOAT16_EQ(expectedData.data(), dstTexture, {0, 0}, {kWidth, kHeight},
dstTextureSpec.format, tolerance);
} else {
EXPECT_TEXTURE_EQ(expectedData.data(), dstTexture, {0, 0}, {kWidth, kHeight},
dstTextureSpec.format, tolerance);
}
}
};
// Verify CopyTextureForBrowserTests works with internal pipeline.
// The case do copy without any transform.
TEST_P(CopyTextureForBrowser_Basic, PassthroughCopy) {
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
DoBasicCopyTest({10, 1});
}
TEST_P(CopyTextureForBrowser_Basic, VerifyCopyOnXDirection) {
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
DoBasicCopyTest({1000, 1});
}
TEST_P(CopyTextureForBrowser_Basic, VerifyCopyOnYDirection) {
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
DoBasicCopyTest({1, 1000});
}
TEST_P(CopyTextureForBrowser_Basic, VerifyCopyFromLargeTexture) {
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
// TODO(crbug.com/dawn/1070): Flaky VK_DEVICE_LOST
DAWN_SUPPRESS_TEST_IF(IsWindows() && IsVulkan() && IsIntel());
@ -600,6 +1000,10 @@ TEST_P(CopyTextureForBrowser_Basic, VerifyCopyFromLargeTexture) {
}
TEST_P(CopyTextureForBrowser_Basic, VerifyFlipY) {
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
wgpu::CopyTextureForBrowserOptions options = {};
options.flipY = true;
@ -607,6 +1011,10 @@ TEST_P(CopyTextureForBrowser_Basic, VerifyFlipY) {
}
TEST_P(CopyTextureForBrowser_Basic, VerifyFlipYInSlimTexture) {
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
wgpu::CopyTextureForBrowserOptions options = {};
options.flipY = true;
@ -626,6 +1034,7 @@ TEST_P(CopyTextureForBrowser_Formats, ColorConversion) {
// Skip OpenGLES backend because it fails on using RGBA8Unorm as
// source texture format.
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
// Skip OpenGL backend on linux because it fails on using *-srgb format as
// dst texture format
@ -651,6 +1060,10 @@ DAWN_INSTANTIATE_TEST_P(
// green texture originally. After the subrect copy, affected part
// in dst texture should be red and other part should remain green.
TEST_P(CopyTextureForBrowser_SubRects, CopySubRect) {
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
// Tests skip due to crbug.com/dawn/592.
DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsBackendValidationEnabled());
@ -673,7 +1086,9 @@ DAWN_INSTANTIATE_TEST_P(CopyTextureForBrowser_SubRects,
TEST_P(CopyTextureForBrowser_AlphaOps, alphaOp) {
// Skip OpenGLES backend because it fails on using RGBA8Unorm as
// source texture format.
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
// Tests skip due to crbug.com/dawn/1104.
DAWN_SUPPRESS_TEST_IF(IsWARP());
@ -686,3 +1101,27 @@ DAWN_INSTANTIATE_TEST_P(
{D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()},
std::vector<wgpu::AlphaOp>({wgpu::AlphaOp::DontChange, wgpu::AlphaOp::Premultiply,
wgpu::AlphaOp::Unpremultiply}));
// Verify |CopyTextureForBrowser| doing color space conversion.
TEST_P(CopyTextureForBrowser_ColorSpace, colorSpaceConversion) {
// TODO(crbug.com/dawn/1232): Program link error on OpenGLES backend
DAWN_SUPPRESS_TEST_IF(IsOpenGLES());
DAWN_SUPPRESS_TEST_IF(IsOpenGL() && IsLinux());
// Tests skip due to crbug.com/dawn/1104.
DAWN_SUPPRESS_TEST_IF(IsWARP());
DoColorSpaceConversionTest();
}
DAWN_INSTANTIATE_TEST_P(CopyTextureForBrowser_ColorSpace,
{D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(),
VulkanBackend()},
std::vector<wgpu::TextureFormat>({wgpu::TextureFormat::RGBA16Float,
wgpu::TextureFormat::RGBA32Float}),
std::vector<ColorSpace>({ColorSpace::SRGB, ColorSpace::DisplayP3}),
std::vector<ColorSpace>({ColorSpace::SRGB}),
std::vector<wgpu::AlphaMode>({wgpu::AlphaMode::Premultiplied,
wgpu::AlphaMode::Unpremultiplied}),
std::vector<wgpu::AlphaMode>({wgpu::AlphaMode::Premultiplied,
wgpu::AlphaMode::Unpremultiplied}));

View File

@ -50,12 +50,12 @@ class CopyTextureForBrowserTest : public ValidationTest {
uint32_t dstLevel,
wgpu::Origin3D dstOrigin,
wgpu::Extent3D extent3D,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All) {
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
wgpu::CopyTextureForBrowserOptions options = {}) {
wgpu::ImageCopyTexture srcImageCopyTexture =
utils::CreateImageCopyTexture(srcTexture, srcLevel, srcOrigin, aspect);
wgpu::ImageCopyTexture dstImageCopyTexture =
utils::CreateImageCopyTexture(dstTexture, dstLevel, dstOrigin, aspect);
wgpu::CopyTextureForBrowserOptions options = {};
if (expectation == utils::Expectation::Success) {
device.GetQueue().CopyTextureForBrowser(&srcImageCopyTexture, &dstImageCopyTexture,
@ -254,3 +254,127 @@ TEST_F(CopyTextureForBrowserTest, InvalidSampleCount) {
TestCopyTextureForBrowser(utils::Expectation::Failure, sourceMultiSampled4x, 0, {0, 0, 0},
destinationMultiSampled1x, 0, {0, 0, 0}, {0, 0, 1});
}
// Test color space conversion related attributes in CopyTextureForBrowserOptions.
TEST_F(CopyTextureForBrowserTest, ColorSpaceConversion_ColorSpace) {
wgpu::Texture source =
Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
wgpu::Texture destination =
Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
wgpu::CopyTextureForBrowserOptions options = {};
std::array<float, 7> srcTransferFunctionParameters = {};
std::array<float, 7> dstTransferFunctionParameters = {};
std::array<float, 9> conversionMatrix = {};
options.needsColorSpaceConversion = true;
options.srcTransferFunctionParameters = srcTransferFunctionParameters.data();
options.dstTransferFunctionParameters = dstTransferFunctionParameters.data();
options.conversionMatrix = conversionMatrix.data();
options.conversionMatrixElementsCount = 9;
options.transferFunctionParametersCount = 7;
// Valid cases
{
wgpu::CopyTextureForBrowserOptions validOptions = options;
TestCopyTextureForBrowser(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, validOptions);
// if no color space conversion, no need to validate related attributes
wgpu::CopyTextureForBrowserOptions noColorSpaceConversion = options;
noColorSpaceConversion.needsColorSpaceConversion = false;
TestCopyTextureForBrowser(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All,
noColorSpaceConversion);
}
// Invalid cases: wrong transferFunctionParametersCount
{
// wrong: transferFunctionParametersCount must be 7
wgpu::CopyTextureForBrowserOptions invalidOptions = options;
invalidOptions.transferFunctionParametersCount = 6;
TestCopyTextureForBrowser(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, invalidOptions);
}
// Invalid cases: wrong conversionMatrixElementsCount
{
// wrong: conversionMatrixElementsCount
wgpu::CopyTextureForBrowserOptions invalidOptions = options;
invalidOptions.transferFunctionParametersCount = 10;
TestCopyTextureForBrowser(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, invalidOptions);
}
// Invalid cases: srcTransferFunctionParameters, dstTransferFunctionParameters or
// conversionMatrix is nullptr
{
wgpu::CopyTextureForBrowserOptions invalidOptions = options;
if (UsesWire()) {
invalidOptions.transferFunctionParametersCount = 0;
}
invalidOptions.srcTransferFunctionParameters = nullptr;
TestCopyTextureForBrowser(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, invalidOptions);
}
{
wgpu::CopyTextureForBrowserOptions invalidOptions = options;
if (UsesWire()) {
invalidOptions.transferFunctionParametersCount = 0;
}
invalidOptions.dstTransferFunctionParameters = nullptr;
TestCopyTextureForBrowser(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, invalidOptions);
}
{
wgpu::CopyTextureForBrowserOptions invalidOptions = options;
if (UsesWire()) {
invalidOptions.conversionMatrixElementsCount = 0;
}
invalidOptions.conversionMatrix = nullptr;
TestCopyTextureForBrowser(utils::Expectation::Failure, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, invalidOptions);
}
}
// Test option.srcAlphaMode/dstAlphaMode
TEST_F(CopyTextureForBrowserTest, ColorSpaceConversion_TextureAlphaState) {
wgpu::Texture source =
Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
wgpu::Texture destination =
Create2DTexture(16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
wgpu::CopyTextureForBrowserOptions options = {};
// Valid src texture alpha state and valid dst texture alpha state
{
options.srcAlphaMode = wgpu::AlphaMode::Premultiplied;
options.dstAlphaMode = wgpu::AlphaMode::Premultiplied;
TestCopyTextureForBrowser(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, options);
options.srcAlphaMode = wgpu::AlphaMode::Premultiplied;
options.dstAlphaMode = wgpu::AlphaMode::Unpremultiplied;
TestCopyTextureForBrowser(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, options);
options.srcAlphaMode = wgpu::AlphaMode::Unpremultiplied;
options.dstAlphaMode = wgpu::AlphaMode::Premultiplied;
TestCopyTextureForBrowser(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, options);
options.srcAlphaMode = wgpu::AlphaMode::Unpremultiplied;
options.dstAlphaMode = wgpu::AlphaMode::Unpremultiplied;
TestCopyTextureForBrowser(utils::Expectation::Success, source, 0, {0, 0, 0}, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All, options);
}
}