Bug 1771374 - Support display-p3 output via DirectComposition on Windows. r=sotaro,lsalzman

Differential Revision: https://phabricator.services.mozilla.com/D150275
This commit is contained in:
Kelsey Gilbert 2022-06-29 23:38:22 +00:00
parent e3f48ef228
commit c4f86e2e33
32 changed files with 2894 additions and 585 deletions

View File

@ -865,10 +865,7 @@ ClientWebGLContext::SetContextOptions(JSContext* cx,
}
if (attributes.mColorSpace.WasPassed()) {
newOpts.colorSpace = attributes.mColorSpace.Value();
}
if (StaticPrefs::gfx_color_management_native_srgb()) {
newOpts.ignoreColorSpace = false;
newOpts.colorSpace = Some(attributes.mColorSpace.Value());
}
// Don't do antialiasing if we've disabled MSAA.

View File

@ -114,7 +114,6 @@ bool WebGLContextOptions::operator==(const WebGLContextOptions& r) const {
eq &= (xrCompatible == r.xrCompatible);
eq &= (powerPreference == r.powerPreference);
eq &= (colorSpace == r.colorSpace);
eq &= (ignoreColorSpace == r.ignoreColorSpace);
return eq;
}
@ -900,10 +899,14 @@ constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
}
inline gfx::ColorSpace2 ToColorSpace2(const WebGLContextOptions& options) {
if (options.ignoreColorSpace) {
return gfx::ColorSpace2::UNKNOWN;
auto ret = gfx::ColorSpace2::UNKNOWN;
if (StaticPrefs::gfx_color_management_native_srgb()) {
ret = gfx::ColorSpace2::SRGB;
}
return gfx::ToColorSpace2(options.colorSpace);
if (options.colorSpace) {
ret = gfx::ToColorSpace2(*options.colorSpace);
}
return ret;
}
// -

View File

@ -248,7 +248,12 @@ struct ParamTraits<mozilla::WebGLContextOptions> final
using T = mozilla::WebGLContextOptions;
static bool Validate(const T& val) {
return ValidateParam(val.powerPreference) && ValidateParam(val.colorSpace);
bool ok = true;
ok &= ValidateParam(val.powerPreference);
if (val.colorSpace) {
ok &= ValidateParam(*val.colorSpace);
}
return ok;
}
};

View File

@ -359,8 +359,7 @@ struct WebGLContextOptions {
bool xrCompatible = false;
dom::WebGLPowerPreference powerPreference =
dom::WebGLPowerPreference::Default;
dom::PredefinedColorSpace colorSpace = dom::PredefinedColorSpace::Srgb;
bool ignoreColorSpace = true; // Our legacy behavior.
Maybe<dom::PredefinedColorSpace> colorSpace;
bool shouldResistFingerprinting = true;
bool enableDebugRendererInfo = false;

View File

@ -1047,8 +1047,9 @@ D3D11DXVA2Manager::CopyToImage(IMFSample* aVideoSample,
NS_ENSURE_TRUE(aOutImage, E_POINTER);
MOZ_ASSERT(mTextureClientAllocator);
RefPtr<D3D11ShareHandleImage> image = new D3D11ShareHandleImage(
gfx::IntSize(mWidth, mHeight), aRegion, mYUVColorSpace, mColorRange);
RefPtr<D3D11ShareHandleImage> image =
new D3D11ShareHandleImage(gfx::IntSize(mWidth, mHeight), aRegion,
ToColorSpace2(mYUVColorSpace), mColorRange);
// Retrieve the DXGI_FORMAT for the current video sample.
RefPtr<IMFMediaBuffer> buffer;
@ -1173,7 +1174,7 @@ HRESULT D3D11DXVA2Manager::WrapTextureWithImage(IMFSample* aVideoSample,
RefPtr<D3D11TextureIMFSampleImage> image = new D3D11TextureIMFSampleImage(
aVideoSample, texture, arrayIndex, gfx::IntSize(mWidth, mHeight), aRegion,
mYUVColorSpace, mColorRange);
ToColorSpace2(mYUVColorSpace), mColorRange);
image->AllocateTextureClient(mKnowsCompositor, mIMFSampleUsageInfo);
RefPtr<IMFSampleWrapper> wrapper = image->GetIMFSampleWrapper();

View File

@ -386,18 +386,50 @@ enum class YUVRangedColorSpace : uint8_t {
// one.
// Some times Worse Is Better.
enum class ColorSpace2 : uint8_t {
UNKNOWN, // Eventually we will remove this.
UNKNOWN, // Really "DISPLAY". Eventually we will remove this.
SRGB,
DISPLAY_P3,
BT601_525, // aka smpte170m NTSC
BT709, // Same gamut as SRGB, but different gamma.
BT601_625 =
BT709, // aka bt470bg PAL. Basically BT709, just Xg is 0.290 not 0.300.
BT2020,
DISPLAY_P3,
_First = UNKNOWN,
_Last = DISPLAY_P3,
_Last = BT2020,
};
inline ColorSpace2 ToColorSpace2(const YUVColorSpace in) {
switch (in) {
case YUVColorSpace::BT601:
return ColorSpace2::BT601_525;
case YUVColorSpace::BT709:
return ColorSpace2::BT709;
case YUVColorSpace::BT2020:
return ColorSpace2::BT2020;
case YUVColorSpace::Identity:
return ColorSpace2::SRGB;
}
MOZ_ASSERT_UNREACHABLE();
}
inline YUVColorSpace ToYUVColorSpace(const ColorSpace2 in) {
switch (in) {
case ColorSpace2::BT601_525:
return YUVColorSpace::BT601;
case ColorSpace2::BT709:
return YUVColorSpace::BT709;
case ColorSpace2::BT2020:
return YUVColorSpace::BT2020;
case ColorSpace2::SRGB:
return YUVColorSpace::Identity;
case ColorSpace2::UNKNOWN:
case ColorSpace2::DISPLAY_P3:
MOZ_CRASH("Bad ColorSpace2 for ToYUVColorSpace");
}
MOZ_ASSERT_UNREACHABLE();
}
struct FromYUVRangedColorSpaceT final {
const YUVColorSpace space;
const ColorRange range;

150
gfx/gl/AutoMappable.h Normal file
View File

@ -0,0 +1,150 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_AUTO_MAPPABLE_H
#define MOZILLA_AUTO_MAPPABLE_H
// Here be dragons.
#include <functional>
namespace mozilla::gfx {
template<class T>
size_t Hash(const T&);
template<class T>
struct StaticStdHasher {
static auto HashImpl(const T& v) {
return std::hash<T>()(v);
}
};
template<class T>
struct StaticHasher {
static auto HashImpl(const T& v) {
return v.hash();
}
};
template<class T>
struct StaticHasher<std::optional<T>> {
static size_t HashImpl(const std::optional<T>& v) {
if (!v) return 0;
return Hash(*v);
}
};
template<> struct StaticHasher<int> : public StaticStdHasher<int> {};
template<> struct StaticHasher<bool> : public StaticStdHasher<bool> {};
template<> struct StaticHasher<float> : public StaticStdHasher<float> {};
template<class T>
size_t Hash(const T& v) {
return StaticHasher<T>::HashImpl(v);
}
//-
// From Boost: https://www.boost.org/doc/libs/1_37_0/doc/html/hash/reference.html#boost.hash_combine
inline size_t HashCombine(size_t seed, const size_t hash) {
seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
// -
// See https://codereview.stackexchange.com/questions/136770/hashing-a-tuple-in-c17
template<class... Args, size_t... Ids>
size_t HashTupleN(const std::tuple<Args...>& tup, const std::index_sequence<Ids...>&) {
size_t seed = 0;
for (const auto& hash : {Hash(std::get<Ids>(tup))...}) {
seed = HashCombine(seed, hash);
}
return seed;
}
template<class... Args>
size_t HashTuple(const std::tuple<Args...>& tup) {
return HashTupleN(tup, std::make_index_sequence<sizeof...(Args)>());
}
// -
template<class T>
auto MembersEq(const T& a, const T& b) {
const auto atup = a.Members();
const auto btup = b.Members();
return atup == btup;
}
template<class T>
auto MembersLt(const T& a, const T& b) {
const auto atup = a.Members();
const auto btup = b.Members();
return atup == btup;
}
template<class T>
auto MembersHash(const T& a) {
const auto atup = a.Members();
return HashTuple(atup);
}
template<class T>
struct MembersHasher final {
auto operator()(const T& v) const {
return v.hash();
}
};
/** E.g.:
struct Foo {
int i;
bool b;
auto Members() const { return std::tie(i, b); }
INLINE_AUTO_MAPPABLE(Foo)
};
std::unordered_set<T, T::Hasher> easy;
**/
#define INLINE_DERIVE_MEMBERS_EQ(T) \
friend bool operator==(const T& a, const T& b) { \
return mozilla::gfx::MembersEq(a, b); \
} \
friend bool operator!=(const T& a, const T& b) { \
return !operator==(a, b); \
}
#define INLINE_AUTO_MAPPABLE(T) \
friend bool operator<(const T& a, const T& b) { \
return mozilla::gfx::MembersLt(a, b); \
} \
INLINE_DERIVE_MEMBERS_EQ(T) \
size_t hash() const { \
return mozilla::gfx::MembersHash(*reinterpret_cast<const T*>(this)); \
} \
using Hasher = mozilla::gfx::MembersHasher<T>;
// -
/** E.g.:
```
struct Foo : public AutoMappable<Foo> {
int i;
bool b;
auto Members() const { return std::tie(i, b); }
};
std::unordered_set<T, T::Hasher> easy;
```
`easy.insert({{}, 2, true});`
The initial {} is needed for aggregate initialization of AutoMappable<Foo>.
Use INLINE_AUTO_MAPPABLE if this is too annoying.
**/
template<class T>
struct AutoMappable {
INLINE_AUTO_MAPPABLE(T)
};
} // namespace mozilla::gfx
#endif // MOZILLA_AUTO_MAPPABLE_H

242
gfx/gl/Colorspaces.cpp Normal file
View File

@ -0,0 +1,242 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// We are going to be doing so, so many transforms, so descriptive labels are
// critical.
#include "Colorspaces.h"
namespace mozilla::color {
// tf = { k * linear | linear < b
// { a * pow(linear, 1/g) - (1-a) | linear >= b
float TfFromLinear(const PiecewiseGammaDesc& desc, const float linear) {
if (linear < desc.b) {
return linear * desc.k;
}
float ret = linear;
ret = powf(ret, 1.0f / desc.g);
ret *= desc.a;
ret -= (desc.a - 1);
return ret;
}
float LinearFromTf(const PiecewiseGammaDesc& desc, const float tf) {
const auto linear_if_low = tf / desc.k;
if (linear_if_low < desc.b) {
return linear_if_low;
}
float ret = tf;
ret += (desc.a - 1);
ret /= desc.a;
ret = powf(ret, 1.0f * desc.g);
return ret;
}
// -
mat3 YuvFromRgb(const YuvLumaCoeffs& yc) {
// Y is always [0,1]
// U and V are signed, and could be either [-1,+1] or [-0.5,+0.5].
// Specs generally use [-0.5,+0.5], so we use that too.
// E.g.
// y = 0.2126*r + 0.7152*g + 0.0722*b
// u = (b - y) / (u_range = u_max - u_min) // u_min = -u_max
// = (b - y) / (u(0,0,1) - u(1,1,0))
// = (b - y) / (2 * u(0,0,1))
// = (b - y) / (2 * u.b))
// = (b - y) / (2 * (1 - 0.0722))
// = (-0.2126*r + -0.7152*g + (1-0.0722)*b) / 1.8556
// v = (r - y) / 1.5748;
// = ((1-0.2126)*r + -0.7152*g + -0.0722*b) / 1.5748
const auto y = vec3({yc.r, yc.g, yc.b});
const auto u = vec3({0,0,1}) - y;
const auto v = vec3({1,0,0}) - y;
// From rows:
return mat3({y,
u / (2 * u.z()),
v / (2 * v.x())});
}
mat4 YuvFromYcbcr(const YcbcrDesc& d) {
// E.g.
// y = (yy - 16) / (235 - 16); // 16->0, 235->1
// u = (cb - 128) / (240 - 16); // 16->-0.5, 128->0, 240->+0.5
// v = (cr - 128) / (240 - 16);
const auto yRange = d.y1 - d.y0;
const auto uHalfRange = d.uPlusHalf - d.u0;
const auto uRange = 2 * uHalfRange;
const auto ycbcrFromYuv = mat4{{
vec4{{yRange, 0, 0, d.y0}},
{{0, uRange, 0, d.u0}},
{{0, 0, uRange, d.u0}},
{{0,0,0,1}}
}};
const auto yuvFromYcbcr = inverse(ycbcrFromYuv);
return yuvFromYcbcr;
}
mat3 XyzFromLinearRgb(const Chromaticities& c) {
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
// Given red (xr, yr), green (xg, yg), blue (xb, yb),
// and whitepoint (XW, YW, ZW)
// [ X ] [ R ]
// [ Y ] = M x [ G ]
// [ Z ] [ B ]
// [ Sr*Xr Sg*Xg Sb*Xb ]
// M = [ Sr*Yr Sg*Yg Sb*Yb ]
// [ Sr*Zr Sg*Zg Sb*Zb ]
// Xr = xr / yr
// Yr = 1
// Zr = (1 - xr - yr) / yr
// Xg = xg / yg
// Yg = 1
// Zg = (1 - xg - yg) / yg
// Xb = xb / yb
// Yb = 1
// Zb = (1 - xb - yb) / yb
// [ Sr ] [ Xr Xg Xb ]^-1 [ XW ]
// [ Sg ] = [ Yr Yg Yb ] x [ YW ]
// [ Sb ] [ Zr Zg Zb ] [ ZW ]
const auto xrgb = vec3({c.rx, c.gx, c.bx});
const auto yrgb = vec3({c.ry, c.gy, c.by});
const auto Xrgb = xrgb / yrgb;
const auto Yrgb = vec3(1);
const auto Zrgb = (vec3(1) - xrgb - yrgb) / yrgb;
const auto XYZrgb = mat3({
Xrgb,
Yrgb,
Zrgb});
const auto XYZrgb_inv = inverse(XYZrgb);
const auto XYZwhitepoint = vec3({c.wx, c.wy, 1 - c.wx - c.wy});
const auto Srgb = XYZrgb_inv * XYZwhitepoint;
const auto M = mat3({
Srgb * Xrgb,
Srgb * Yrgb,
Srgb * Zrgb});
return M;
}
// -
ColorspaceTransform
ColorspaceTransform::Create(const ColorspaceDesc& src,
const ColorspaceDesc& dst) {
auto ct = ColorspaceTransform{src, dst};
ct.srcTf = src.tf;
ct.dstTf = dst.tf;
const auto RgbTfFrom = [&](const ColorspaceDesc& cs) {
auto rgbFrom = mat4::Identity();
if (cs.yuv) {
const auto yuvFromYcbcr = YuvFromYcbcr(cs.yuv->ycbcr);
const auto yuvFromRgb = YuvFromRgb(cs.yuv->yCoeffs);
const auto rgbFromYuv = inverse(yuvFromRgb);
const auto rgbFromYuv4 = mat4(rgbFromYuv);
const auto rgbFromYcbcr = rgbFromYuv4 * yuvFromYcbcr;
rgbFrom = rgbFromYcbcr;
}
return rgbFrom;
};
ct.srcRgbTfFromSrc = RgbTfFrom(src);
const auto dstRgbTfFromDst = RgbTfFrom(dst);
ct.dstFromDstRgbTf = inverse(dstRgbTfFromDst);
// -
ct.dstRgbLinFromSrcRgbLin = mat3::Identity();
if (!(src.chrom == dst.chrom)) {
const auto xyzFromSrcRgbLin = XyzFromLinearRgb(src.chrom);
const auto xyzFromDstRgbLin = XyzFromLinearRgb(dst.chrom);
const auto dstRgbLinFromXyz = inverse(xyzFromDstRgbLin);
ct.dstRgbLinFromSrcRgbLin = dstRgbLinFromXyz * xyzFromSrcRgbLin;
}
return ct;
}
vec3 ColorspaceTransform::DstFromSrc(const vec3 src) const {
const auto srcRgbTf = srcRgbTfFromSrc * vec4(src, 1);
auto srcRgbLin = srcRgbTf;
if (srcTf) {
srcRgbLin.x( LinearFromTf(*srcTf, srcRgbTf.x()) );
srcRgbLin.y( LinearFromTf(*srcTf, srcRgbTf.y()) );
srcRgbLin.z( LinearFromTf(*srcTf, srcRgbTf.z()) );
}
const auto dstRgbLin = dstRgbLinFromSrcRgbLin * vec3(srcRgbLin);
auto dstRgbTf = dstRgbLin;
if (dstTf) {
dstRgbTf.x( TfFromLinear(*dstTf, dstRgbLin.x()) );
dstRgbTf.y( TfFromLinear(*dstTf, dstRgbLin.y()) );
dstRgbTf.z( TfFromLinear(*dstTf, dstRgbLin.z()) );
}
const auto dst4 = dstFromDstRgbTf * vec4(dstRgbTf, 1);
return vec3(dst4);
}
// -
std::optional<mat4> ColorspaceTransform::ToMat4() const {
mat4 fromSrc = srcRgbTfFromSrc;
if (srcTf) return {};
fromSrc = mat4(dstRgbLinFromSrcRgbLin) * fromSrc;
if (dstTf) return {};
fromSrc = dstFromDstRgbTf * fromSrc;
return fromSrc;
}
Lut3 ColorspaceTransform::ToLut3(const ivec3 size) const {
auto lut = Lut3::Create(size);
lut.SetMap([&](const vec3& srcVal) {
return DstFromSrc(srcVal);
});
return lut;
}
vec3 Lut3::Sample(const vec3 in01) const {
const auto coord = vec3(size-1) * in01;
const auto p0 = floor(coord);
const auto dp = coord - p0;
const auto ip0 = ivec3(p0);
// Trilinear
const auto f000 = Fetch(ip0 + ivec3({0,0,0}));
const auto f100 = Fetch(ip0 + ivec3({1,0,0}));
const auto f010 = Fetch(ip0 + ivec3({0,1,0}));
const auto f110 = Fetch(ip0 + ivec3({1,1,0}));
const auto f001 = Fetch(ip0 + ivec3({0,0,1}));
const auto f101 = Fetch(ip0 + ivec3({1,0,1}));
const auto f011 = Fetch(ip0 + ivec3({0,1,1}));
const auto f111 = Fetch(ip0 + ivec3({1,1,1}));
const auto fx00 = mix(f000, f100, dp.x());
const auto fx10 = mix(f010, f110, dp.x());
const auto fx01 = mix(f001, f101, dp.x());
const auto fx11 = mix(f011, f111, dp.x());
const auto fxy0 = mix(fx00, fx10, dp.y());
const auto fxy1 = mix(fx01, fx11, dp.y());
const auto fxyz = mix(fxy0, fxy1, dp.z());
return fxyz;
}
} // namespace mozilla::color

694
gfx/gl/Colorspaces.h Normal file
View File

@ -0,0 +1,694 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_GL_COLORSPACES_H_
#define MOZILLA_GFX_GL_COLORSPACES_H_
// Reference: https://hackmd.io/0wkiLmP7RWOFjcD13M870A
// We are going to be doing so, so many transforms, so descriptive labels are
// critical.
// Colorspace background info: https://hackmd.io/0wkiLmP7RWOFjcD13M870A
#include <algorithm>
#include <array>
#include <cmath>
#include <optional>
#include "AutoMappable.h"
#include "mozilla/Attributes.h"
#ifdef DEBUG
#define ASSERT(EXPR) do { \
if (!(EXPR)) { \
__builtin_trap(); \
} \
} while (false)
#else
#define ASSERT(EXPR) (void)(EXPR)
#endif
namespace mozilla::color {
struct YuvLumaCoeffs final {
float r = 0.2126;
float g = 0.7152;
float b = 0.0722;
auto Members() const {
return std::tie(r, g, b);
}
INLINE_AUTO_MAPPABLE(YuvLumaCoeffs)
static constexpr auto Rec709() {
return YuvLumaCoeffs();
}
static constexpr auto Rec2020() {
return YuvLumaCoeffs{0.2627, 0.6780, 0.0593};
}
};
struct PiecewiseGammaDesc final {
// tf = { k * linear | linear < b
// { a * pow(linear, 1/g) - (1-a) | linear >= b
// Default to Srgb
float a = 1.055;
float b = 0.04045 / 12.92;
float g = 2.4;
float k = 12.92;
auto Members() const {
return std::tie(a, b, g, k);
}
INLINE_AUTO_MAPPABLE(PiecewiseGammaDesc)
static constexpr auto Srgb() {
return PiecewiseGammaDesc();
}
static constexpr auto DisplayP3() { return Srgb(); }
static constexpr auto Rec709() {
return PiecewiseGammaDesc{
1.099,
0.018,
1.0 / 0.45, // ~2.222
4.5,
};
}
static constexpr auto Rec2020_10bit() {
return Rec709();
}
static constexpr auto Rec2020_12bit() {
return PiecewiseGammaDesc{
1.0993,
0.0181,
1.0 / 0.45, // ~2.222
4.5,
};
}
};
struct YcbcrDesc final {
float y0 = 16 / 255.0;
float y1 = 235 / 255.0;
float u0 = 128 / 255.0;
float uPlusHalf = 240 / 255.0;
auto Members() const {
return std::tie(y0, y1, u0, uPlusHalf);
}
INLINE_AUTO_MAPPABLE(YcbcrDesc)
static constexpr auto Narrow8() { // AKA limited/studio/tv
return YcbcrDesc();
}
static constexpr auto Full8() { // AKA pc
return YcbcrDesc{
0 / 255.0,
255 / 255.0,
128 / 255.0,
254 / 255.0,
};
}
static constexpr auto Float() { // Best for a LUT
return YcbcrDesc{0.0, 1.0, 0.5, 1.0};
}
};
struct Chromaticities final {
float rx = 0.640;
float ry = 0.330;
float gx = 0.300;
float gy = 0.600;
float bx = 0.150;
float by = 0.060;
// D65:
static constexpr float wx = 0.3127;
static constexpr float wy = 0.3290;
auto Members() const {
return std::tie(rx, ry, gx, gy, bx, by);
}
INLINE_AUTO_MAPPABLE(Chromaticities)
// -
static constexpr auto Rec709() { // AKA limited/studio/tv
return Chromaticities();
}
static constexpr auto Srgb() { return Rec709(); }
static constexpr auto Rec601_625_Pal() {
auto ret = Rec709();
ret.gx = 0.290;
return ret;
}
static constexpr auto Rec601_525_Ntsc() {
return Chromaticities{
0.630, 0.340, // r
0.310, 0.595, // g
0.155, 0.070, // b
};
}
static constexpr auto Rec2020() {
return Chromaticities{
0.708, 0.292, // r
0.170, 0.797, // g
0.131, 0.046, // b
};
}
static constexpr auto DisplayP3() {
return Chromaticities{
0.680, 0.320, // r
0.265, 0.690, // g
0.150, 0.060, // b
};
}
};
// -
struct YuvDesc final {
YuvLumaCoeffs yCoeffs;
YcbcrDesc ycbcr;
auto Members() const {
return std::tie(yCoeffs, ycbcr);
}
INLINE_AUTO_MAPPABLE(YuvDesc);
};
struct ColorspaceDesc final {
Chromaticities chrom;
std::optional<PiecewiseGammaDesc> tf;
std::optional<YuvDesc> yuv;
auto Members() const {
return std::tie(chrom, tf, yuv);
}
INLINE_AUTO_MAPPABLE(ColorspaceDesc);
};
// -
template<class TT, int NN>
struct avec final {
using T = TT;
static constexpr auto N = NN;
std::array<T,N> data = {};
// -
constexpr avec() = default;
constexpr avec(const avec&) = default;
constexpr avec(const avec<T, N-1>& v, T a) {
for (int i = 0; i < N-1; i++) {
data[i] = v[i];
}
data[N-1] = a;
}
constexpr avec(const avec<T, N-2>& v, T a, T b) {
for (int i = 0; i < N-2; i++) {
data[i] = v[i];
}
data[N-2] = a;
data[N-1] = b;
}
MOZ_IMPLICIT constexpr avec(const std::array<T,N>& data) {
this->data = data;
}
explicit constexpr avec(const T v) {
for (int i = 0; i < N; i++) {
data[i] = v;
}
}
template<class T2, int N2>
explicit constexpr avec(const avec<T2, N2>& v) {
const auto n = std::min(N, N2);
for (int i = 0; i < n; i++) {
data[i] = static_cast<T>(v[i]);
}
}
// -
const auto& operator[](const size_t n) const { return data[n]; }
auto& operator[](const size_t n) { return data[n]; }
template<int i>
constexpr auto get() const {
return (i < N) ? data[i] : 0;
}
constexpr auto x() const { return get<0>(); }
constexpr auto y() const { return get<1>(); }
constexpr auto z() const { return get<2>(); }
constexpr auto w() const { return get<3>(); }
constexpr auto xyz() const {
return vec3({x(), y(), z()});
}
template<int i>
void set(const T v) {
if (i < N) {
data[i] = v;
}
}
void x(const T v) { set<0>(v); }
void y(const T v) { set<1>(v); }
void z(const T v) { set<2>(v); }
void w(const T v) { set<3>(v); }
// -
#define _(OP) \
friend avec operator OP(const avec a, const avec b) { \
avec c; \
for (int i = 0; i < N; i++) { \
c[i] = a[i] OP b[i]; \
} \
return c; \
} \
friend avec operator OP(const avec a, const T b) { \
avec c; \
for (int i = 0; i < N; i++) { \
c[i] = a[i] OP b; \
} \
return c; \
} \
friend avec operator OP(const T a, const avec b) { \
avec c; \
for (int i = 0; i < N; i++) { \
c[i] = a OP b[i]; \
} \
return c; \
}
_(+)
_(-)
_(*)
_(/)
#undef _
friend bool operator==(const avec a, const avec b) {
bool eq = true;
for (int i = 0; i < N; i++) {
eq &= (a[i] == b[i]);
}
return eq;
}
};
using vec3 = avec<float, 3>;
using vec4 = avec<float, 4>;
using ivec3 = avec<int32_t, 3>;
using ivec4 = avec<int32_t, 4>;
template<class T, int N>
T dot(const avec<T, N>& a, const avec<T, N>& b) {
const auto c = a * b;
T ret = 0;
for (int i = 0; i < N; i++) {
ret += c[i];
}
return ret;
}
template<class V>
V mix(const V& zero, const V& one, const float val) {
return zero * (1 - val) + one * val;
}
template<class T, int N>
auto min(const avec<T, N>& a, const avec<T, N>& b) {
auto ret = avec<T, N>{};
for (int i = 0; i < ret.N; i++) {
ret[i] = std::min(a[i], b[i]);
}
return ret;
}
template<class T, int N>
auto max(const avec<T, N>& a, const avec<T, N>& b) {
auto ret = avec<T, N>{};
for (int i = 0; i < ret.N; i++) {
ret[i] = std::max(a[i], b[i]);
}
return ret;
}
template<class T, int N>
auto floor(const avec<T, N>& a) {
auto ret = avec<T, N>{};
for (int i = 0; i < ret.N; i++) {
ret[i] = floorf(a[i]);
}
return ret;
}
template<class T, int N>
auto round(const avec<T, N>& a) {
auto ret = avec<T, N>{};
for (int i = 0; i < ret.N; i++) {
ret[i] = roundf(a[i]);
}
return ret;
}
template<class T>
inline auto t_abs(const T& a) {
return abs(a);
}
template<>
inline auto t_abs<float>(const float& a) {
return fabs(a);
}
template<class T, int N>
auto abs(const avec<T, N>& a) {
auto ret = avec<T, N>{};
for (int i = 0; i < ret.N; i++) {
ret[i] = t_abs(a[i]);
}
return ret;
}
// -
template<int Y_Rows, int X_Cols>
struct mat final {
static constexpr int y_rows = Y_Rows;
static constexpr int x_cols = X_Cols;
static constexpr auto Identity() {
auto ret = mat{};
for (int x = 0; x < x_cols; x++) {
for (int y = 0; y < y_rows; y++) {
ret.at(x,y) = (x == y ? 1 : 0);
}
}
return ret;
}
std::array<avec<float, X_Cols>, Y_Rows> rows = {}; // row-major
// -
constexpr mat() {}
explicit constexpr mat(const std::array<avec<float, X_Cols>, Y_Rows>& rows) {
this->rows = rows;
}
template<int Y_Rows2, int X_Cols2>
explicit constexpr mat(const mat<Y_Rows2, X_Cols2>& m) {
*this = Identity();
for (int x = 0; x < std::min(X_Cols, X_Cols2); x++) {
for (int y = 0; y < std::min(Y_Rows, Y_Rows2); y++) {
at(x,y) = m.at(x, y);
}
}
}
const auto& at(const int x, const int y) const { return rows.at(y)[x]; }
auto& at(const int x, const int y) { return rows.at(y)[x]; }
friend auto operator*(const mat& a,
const avec<float, X_Cols>& b_colvec) {
avec<float, Y_Rows> c_colvec;
for (int i = 0; i < y_rows; i++) {
c_colvec[i] = dot(a.rows.at(i), b_colvec);
}
return c_colvec;
}
friend auto operator*(const mat& a, const float b) {
mat c;
for (int x = 0; x < x_cols; x++) {
for (int y = 0; y < y_rows; y++) {
c.at(x,y) = a.at(x,y) * b;
}
}
return c;
}
friend auto operator/(const mat& a, const float b) {
return a * (1 / b);
}
template<int BCols, int BRows = X_Cols>
friend auto operator*(const mat& a,
const mat<BRows, BCols>& b) {
const auto bt = transpose(b);
const auto& b_cols = bt.rows;
mat<Y_Rows, BCols> c;
for (int x = 0; x < BCols; x++) {
for (int y = 0; y < Y_Rows; y++) {
c.at(x,y) = dot(a.rows.at(y), b_cols.at(x));
}
}
return c;
}
};
using mat3 = mat<3,3>;
using mat4 = mat<4,4>;
inline float determinant(const mat<1,1>& m) {
return m.at(0,0);
}
template<class T>
float determinant(const T& m) {
static_assert(T::x_cols == T::y_rows);
float ret = 0;
for (int i = 0; i < T::x_cols; i++) {
const auto cofact = cofactor(m, i, 0);
ret += m.at(i, 0) * cofact;
}
return ret;
}
// -
template<class T>
float cofactor(const T& m, const int x_col, const int y_row) {
ASSERT(0 <= x_col && x_col < T::x_cols);
ASSERT(0 <= y_row && y_row < T::y_rows);
auto cofactor = minor_val(m, x_col, y_row);
if ((x_col+y_row) % 2 == 1) {
cofactor *= -1;
}
return cofactor;
}
// -
// Unfortunately, can't call this `minor(...)` because there is
// `#define minor(dev) gnu_dev_minor (dev)`
// in /usr/include/x86_64-linux-gnu/sys/sysmacros.h:62
template<class T>
float minor_val(const T& a, const int skip_x, const int skip_y) {
ASSERT(0 <= skip_x && skip_x < T::x_cols);
ASSERT(0 <= skip_y && skip_y < T::y_rows);
// A minor matrix is a matrix without its x_col and y_row.
mat<T::y_rows-1, T::x_cols-1> b;
int x_skips = 0;
for (int ax = 0; ax < T::x_cols; ax++) {
if (ax == skip_x) {
x_skips = 1;
continue;
}
int y_skips = 0;
for (int ay = 0; ay < T::y_rows; ay++) {
if (ay == skip_y) {
y_skips = 1;
continue;
}
b.at(ax - x_skips, ay - y_skips) = a.at(ax, ay);
}
}
const auto minor = determinant(b);
return minor;
}
// -
/// The matrix of cofactors.
template<class T>
auto comatrix(const T& a) {
auto b = T{};
for (int x = 0; x < T::x_cols; x++) {
for (int y = 0; y < T::y_rows; y++) {
b.at(x, y) = cofactor(a, x, y);
}
}
return b;
}
// -
template<class T>
auto transpose(const T& a) {
auto b = mat<T::x_cols, T::y_rows>{};
for (int x = 0; x < T::x_cols; x++) {
for (int y = 0; y < T::y_rows; y++) {
b.at(y, x) = a.at(x, y);
}
}
return b;
}
// -
template<class T>
inline T inverse(const T& a) {
const auto det = determinant(a);
const auto comat = comatrix(a);
const auto adjugate = transpose(comat);
const auto inv = adjugate / det;
return inv;
}
// -
template<class F>
void ForEachIntWithin(const ivec3 size, const F& f) {
ivec3 p;
for (p.z(0); p.z() < size.z(); p.z(p.z()+1)) {
for (p.y(0); p.y() < size.y(); p.y(p.y()+1)) {
for (p.x(0); p.x() < size.x(); p.x(p.x()+1)) {
f(p);
}
}
}
}
template<class F>
void ForEachSampleWithin(const ivec3 size, const F& f) {
const auto div = vec3(size - 1);
ForEachIntWithin(size, [&](const ivec3& isrc) {
const auto fsrc = vec3(isrc) / div;
f(fsrc);
});
}
// -
struct Lut3 final {
ivec3 size;
std::vector<vec3> data;
// -
static Lut3 Create(const ivec3 size) {
Lut3 lut;
lut.size = size;
lut.data.resize(size.x() * size.y() * size.z());
return lut;
}
// -
/// p: [0, N-1] (clamps)
size_t Index(ivec3 p) const {
const auto scales = ivec3({
1,
size.x(),
size.x() * size.y()
});
p = max(ivec3(0), min(p, size - 1)); // clamp
return dot(p, scales);
}
// -
template<class F>
void SetMap(const F& dstFromSrc01) {
ForEachIntWithin(size, [&](const ivec3 p) {
const auto i = Index(p);
const auto src01 = vec3(p) / vec3(size - 1);
const auto dstVal = dstFromSrc01(src01);
data.at(i) = dstVal;
});
}
// -
/// p: [0, N-1] (clamps)
vec3 Fetch(ivec3 p) const {
const auto i = Index(p);
return data.at(i);
}
/// in01: [0.0, 1.0] (clamps)
vec3 Sample(vec3 in01) const;
};
// -
/**
Naively, it would be ideal to map directly from ycbcr to rgb,
but headroom and footroom are problematic: For e.g. narrow-range-8-bit,
our naive LUT would start at absolute y=0/255. However, values only start
at y=16/255, and depending on where your first LUT sample is, you might get
very poor approximations for y=16/255.
Further, even for full-range-8-bit, y=-0.5 is encoded as 1/255. U and v
aren't *as* important as y, but we should try be accurate for the min and
max values. Additionally, it would be embarassing to get whites/greys wrong,
so preserving u=0.0 should also be a goal.
Finally, when using non-linear transfer functions, the linear approximation of a
point between two samples will be fairly inaccurate.
We preserve min and max by choosing our input range such that min and max are
the endpoints of their LUT axis.
We preserve accuracy (at and around) mid by choosing odd sizes for dimentions.
But also, the LUT is surprisingly robust, so check if the simple version works
before adding complexity!
**/
struct ColorspaceTransform final {
ColorspaceDesc srcSpace;
ColorspaceDesc dstSpace;
mat4 srcRgbTfFromSrc;
std::optional<PiecewiseGammaDesc> srcTf;
mat3 dstRgbLinFromSrcRgbLin;
std::optional<PiecewiseGammaDesc> dstTf;
mat4 dstFromDstRgbTf;
static ColorspaceTransform Create(const ColorspaceDesc& src,
const ColorspaceDesc& dst);
// -
vec3 DstFromSrc(vec3 src) const;
std::optional<mat4> ToMat4() const;
Lut3 ToLut3(const ivec3 size) const;
Lut3 ToLut3() const {
auto defaultSize = ivec3({31,31,15}); // Order of importance: G, R, B
if (srcSpace.yuv) {
defaultSize = ivec3({31,15,31}); // Y, Cb, Cr
}
return ToLut3(defaultSize);
}
};
} // namespace mozilla::color
#undef ASSERT
#endif // MOZILLA_GFX_GL_COLORSPACES_H_

View File

@ -15,6 +15,7 @@
#include "gfxUtils.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/Matrix.h"
@ -50,140 +51,176 @@ namespace gl {
// --
const char* const kFragHeader_Tex2D =
"\
#define SAMPLER sampler2D \n\
#if __VERSION__ >= 130 \n\
#define TEXTURE texture \n\
#else \n\
#define TEXTURE texture2D \n\
#endif \n\
";
const char* const kFragHeader_Tex2DRect =
"\
#define SAMPLER sampler2DRect \n\
#if __VERSION__ >= 130 \n\
#define TEXTURE texture \n\
#else \n\
#define TEXTURE texture2DRect \n\
#endif \n\
";
const char* const kFragHeader_TexExt =
"\
#extension GL_OES_EGL_image_external : require \n\
#if __VERSION__ >= 130 \n\
#define TEXTURE texture \n\
#else \n\
#define TEXTURE texture2D \n\
#endif \n\
#define SAMPLER samplerExternalOES \n\
";
static const char kFragPreprocHeader[] = R"(
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
#define MAXP highp
#endif
#else
#define MAXP highp
#endif
#ifndef MAXP
#define MAXP mediump
#endif
const char* const kFragBody_RGBA =
"\
VARYING vec2 vTexCoord0; \n\
uniform SAMPLER uTex0; \n\
\n\
void main(void) \n\
{ \n\
FRAG_COLOR = TEXTURE(uTex0, vTexCoord0); \n\
} \n\
";
const char* const kFragBody_BGRA =
"\
VARYING vec2 vTexCoord0; \n\
uniform SAMPLER uTex0; \n\
\n\
void main(void) \n\
{ \n\
FRAG_COLOR = TEXTURE(uTex0, vTexCoord0).bgra; \n\
} \n\
";
const char* const kFragBody_CrYCb =
"\
VARYING vec2 vTexCoord0; \n\
uniform SAMPLER uTex0; \n\
uniform MAT4X3 uColorMatrix; \n\
\n\
void main(void) \n\
{ \n\
vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).gbr, \n\
1.0); \n\
FRAG_COLOR = vec4((uColorMatrix * yuv).rgb, 1.0); \n\
} \n\
";
const char* const kFragBody_NV12 =
"\
VARYING vec2 vTexCoord0; \n\
VARYING vec2 vTexCoord1; \n\
uniform SAMPLER uTex0; \n\
uniform SAMPLER uTex1; \n\
uniform MAT4X3 uColorMatrix; \n\
\n\
void main(void) \n\
{ \n\
vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).x, \n\
TEXTURE(uTex1, vTexCoord1).xy, \n\
1.0); \n\
FRAG_COLOR = vec4((uColorMatrix * yuv).rgb, 1.0); \n\
} \n\
";
const char* const kFragBody_PlanarYUV =
"\
VARYING vec2 vTexCoord0; \n\
VARYING vec2 vTexCoord1; \n\
uniform SAMPLER uTex0; \n\
uniform SAMPLER uTex1; \n\
uniform SAMPLER uTex2; \n\
uniform MAT4X3 uColorMatrix; \n\
\n\
void main(void) \n\
{ \n\
vec4 yuv = vec4(TEXTURE(uTex0, vTexCoord0).x, \n\
TEXTURE(uTex1, vTexCoord1).x, \n\
TEXTURE(uTex2, vTexCoord1).x, \n\
1.0); \n\
FRAG_COLOR = vec4((uColorMatrix * yuv).rgb, 1.0); \n\
} \n\
";
#if __VERSION__ >= 130
#define VARYING in
#else
#define VARYING varying
#endif
#if __VERSION__ >= 120
#define MAT4X3 mat4x3
#else
#define MAT4X3 mat4
#endif
)";
// -
const char* const kFragHeader_Tex2D = R"(
#define SAMPLER sampler2D
#if __VERSION__ >= 130
#define TEXTURE texture
#else
#define TEXTURE texture2D
#endif
)";
const char* const kFragHeader_Tex2DRect = R"(
#define SAMPLER sampler2DRect
#if __VERSION__ >= 130
#define TEXTURE texture
#else
#define TEXTURE texture2DRect
#endif
)";
const char* const kFragHeader_TexExt = R"(
#extension GL_OES_EGL_image_external : enable
#extension GL_OES_EGL_image_external_essl3 : enable
#if __VERSION__ >= 130
#define TEXTURE texture
#else
#define TEXTURE texture2D
#endif
#define SAMPLER samplerExternalOES
)";
// -
static const char kFragDeclHeader[] = R"(
precision PRECISION float;
#if __VERSION__ >= 130
#define FRAG_COLOR oFragColor
out vec4 FRAG_COLOR;
#else
#define FRAG_COLOR gl_FragColor
#endif
)";
// -
const char* const kFragSample_OnePlane = R"(
VARYING mediump vec2 vTexCoord0;
uniform PRECISION SAMPLER uTex0;
vec4 metaSample() {
vec4 src = TEXTURE(uTex0, vTexCoord0);
return src;
}
)";
// Ideally this would just change the color-matrix it uses, but this is
// acceptable debt for now.
const char* const kFragSample_OnePlane_YUV_via_GBR = R"(
VARYING mediump vec2 vTexCoord0;
uniform PRECISION SAMPLER uTex0;
vec4 metaSample() {
vec4 src = TEXTURE(uTex0, vTexCoord0);
return src;
}
)";
const char* const kFragSample_TwoPlane = R"(
VARYING mediump vec2 vTexCoord0;
VARYING mediump vec2 vTexCoord1;
uniform PRECISION SAMPLER uTex0;
uniform PRECISION SAMPLER uTex1;
vec4 metaSample() {
vec4 src = TEXTURE(uTex0, vTexCoord0); // Keep r and a.
src.gb = TEXTURE(uTex1, vTexCoord1).rg;
return src;
}
)";
const char* const kFragSample_ThreePlane = R"(
VARYING mediump vec2 vTexCoord0;
VARYING mediump vec2 vTexCoord1;
uniform PRECISION SAMPLER uTex0;
uniform PRECISION SAMPLER uTex1;
uniform PRECISION SAMPLER uTex2;
vec4 metaSample() {
vec4 src = TEXTURE(uTex0, vTexCoord0); // Keep r and a.
src.g = TEXTURE(uTex1, vTexCoord1).r;
src.b = TEXTURE(uTex2, vTexCoord1).r;
return src;
}
)";
// -
const char* const kFragConvert_None = R"(
vec3 metaConvert(vec3 src) {
return src;
}
)";
const char* const kFragConvert_BGR = R"(
vec3 metaConvert(vec3 src) {
return src.bgr;
}
)";
const char* const kFragConvert_ColorMatrix = R"(
uniform mediump MAT4X3 uColorMatrix;
vec3 metaConvert(vec3 src) {
return (uColorMatrix * vec4(src, 1)).rgb;
}
)";
const char* const kFragConvert_ColorLut = R"(
uniform PRECISION sampler3D uColorLut;
vec3 metaConvert(vec3 src) {
// Half-texel filtering hazard!
// E.g. For texture size of 2,
// E.g. 0.5/2=0.25 is still sampling 100% of texel 0, 0% of texel 1.
// For the LUT, we need 0.5/2=0.25 to filter 25/75 texel 0 and 1.
// That is, we need to adjust our sampling point such that it's 0.25 of the
// way from texel 0's center to texel 1's center.
// We need, for N=2:
// v=0.0|N=2 => v'=0.5/2
// v=1.0|N=2 => v'=1.5/2
// For N=3:
// v=0.0|N=3 => v'=0.5/3
// v=1.0|N=3 => v'=2.5/3
// => v' = ( 0.5 + v * (3 - 1) )/3
vec3 size = vec3(textureSize(uColorLut, 0));
src = (0.5 + src * (size - 1.0)) / size;
return texture(uColorLut, src).rgb;
}
)";
// -
static const char kFragBody[] = R"(
void main(void) {
vec4 src = metaSample();
vec3 dst = metaConvert(src.rgb);
FRAG_COLOR = vec4(dst, src.a);
}
)";
// --
template <uint8_t N>
/*static*/ Mat<N> Mat<N>::Zero() {
Mat<N> ret;
for (auto& x : ret.m) {
x = 0.0f;
}
return ret;
}
template <uint8_t N>
/*static*/ Mat<N> Mat<N>::I() {
auto ret = Mat<N>::Zero();
for (uint8_t i = 0; i < N; i++) {
ret.at(i, i) = 1.0f;
}
return ret;
}
template <uint8_t N>
Mat<N> Mat<N>::operator*(const Mat<N>& r) const {
Mat<N> ret;
for (uint8_t x = 0; x < N; x++) {
for (uint8_t y = 0; y < N; y++) {
float sum = 0.0f;
for (uint8_t i = 0; i < N; i++) {
sum += at(i, y) * r.at(x, i);
}
ret.at(x, y) = sum;
}
}
return ret;
}
Mat3 SubRectMat3(const float x, const float y, const float w, const float h) {
auto ret = Mat3::Zero();
auto ret = Mat3{};
ret.at(0, 0) = w;
ret.at(1, 1) = h;
ret.at(2, 0) = x;
@ -212,10 +249,10 @@ Mat3 SubRectMat3(const gfx::IntRect& bigSubrect, const gfx::IntSize& smallSize,
// --
ScopedSaveMultiTex::ScopedSaveMultiTex(GLContext* const gl,
const uint8_t texCount,
const std::vector<uint8_t>& texUnits,
const GLenum texTarget)
: mGL(*gl),
mTexCount(texCount),
mTexUnits(texUnits),
mTexTarget(texTarget),
mOldTexUnit(mGL.GetIntAs<GLenum>(LOCAL_GL_ACTIVE_TEXTURE)) {
GLenum texBinding;
@ -223,6 +260,9 @@ ScopedSaveMultiTex::ScopedSaveMultiTex(GLContext* const gl,
case LOCAL_GL_TEXTURE_2D:
texBinding = LOCAL_GL_TEXTURE_BINDING_2D;
break;
case LOCAL_GL_TEXTURE_3D:
texBinding = LOCAL_GL_TEXTURE_BINDING_3D;
break;
case LOCAL_GL_TEXTURE_RECTANGLE:
texBinding = LOCAL_GL_TEXTURE_BINDING_RECTANGLE;
break;
@ -233,21 +273,23 @@ ScopedSaveMultiTex::ScopedSaveMultiTex(GLContext* const gl,
gfxCriticalError() << "Unhandled texTarget: " << texTarget;
}
for (uint8_t i = 0; i < mTexCount; i++) {
mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + i);
for (int i = 0; i < int(mTexUnits.size()); i++) {
const auto& unit = mTexUnits[i];
mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + unit);
if (mGL.IsSupported(GLFeature::sampler_objects)) {
mOldTexSampler[i] = mGL.GetIntAs<GLuint>(LOCAL_GL_SAMPLER_BINDING);
mGL.fBindSampler(i, 0);
mGL.fBindSampler(unit, 0);
}
mOldTex[i] = mGL.GetIntAs<GLuint>(texBinding);
}
}
ScopedSaveMultiTex::~ScopedSaveMultiTex() {
for (uint8_t i = 0; i < mTexCount; i++) {
mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + i);
for (int i = mTexUnits.size() - 1; i >= 0; i--) { // reverse
const auto& unit = mTexUnits[i];
mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + unit);
if (mGL.IsSupported(GLFeature::sampler_objects)) {
mGL.fBindSampler(i, mOldTexSampler[i]);
mGL.fBindSampler(unit, mOldTexSampler[i]);
}
mGL.fBindTexture(mTexTarget, mOldTex[i]);
}
@ -379,6 +421,7 @@ DrawBlitProg::DrawBlitProg(const GLBlitHelper* const parent, const GLuint prog)
mLoc_uDestMatrix(mParent.mGL->fGetUniformLocation(mProg, "uDestMatrix")),
mLoc_uTexMatrix0(mParent.mGL->fGetUniformLocation(mProg, "uTexMatrix0")),
mLoc_uTexMatrix1(mParent.mGL->fGetUniformLocation(mProg, "uTexMatrix1")),
mLoc_uColorLut(mParent.mGL->fGetUniformLocation(mProg, "uColorLut")),
mLoc_uColorMatrix(
mParent.mGL->fGetUniformLocation(mProg, "uColorMatrix")) {
const auto& gl = mParent.mGL;
@ -443,28 +486,34 @@ void DrawBlitProg::Draw(const BaseArgs& args,
gl->fUniformMatrix3fv(mLoc_uDestMatrix, 1, false, destMatrix.m);
gl->fUniformMatrix3fv(mLoc_uTexMatrix0, 1, false, args.texMatrix0.m);
if (args.texUnitForColorLut) {
gl->fUniform1i(mLoc_uColorLut, *args.texUnitForColorLut);
}
MOZ_ASSERT(bool(argsYUV) == (mLoc_uColorMatrix != -1));
if (argsYUV) {
gl->fUniformMatrix3fv(mLoc_uTexMatrix1, 1, false, argsYUV->texMatrix1.m);
const auto& colorMatrix =
gfxUtils::YuvToRgbMatrix4x4ColumnMajor(argsYUV->colorSpace);
float mat4x3[4 * 3];
switch (mType_uColorMatrix) {
case LOCAL_GL_FLOAT_MAT4:
gl->fUniformMatrix4fv(mLoc_uColorMatrix, 1, false, colorMatrix);
break;
case LOCAL_GL_FLOAT_MAT4x3:
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 3; y++) {
mat4x3[3 * x + y] = colorMatrix[4 * x + y];
if (mLoc_uColorMatrix != -1) {
const auto& colorMatrix =
gfxUtils::YuvToRgbMatrix4x4ColumnMajor(*argsYUV->colorSpaceForMatrix);
float mat4x3[4 * 3];
switch (mType_uColorMatrix) {
case LOCAL_GL_FLOAT_MAT4:
gl->fUniformMatrix4fv(mLoc_uColorMatrix, 1, false, colorMatrix);
break;
case LOCAL_GL_FLOAT_MAT4x3:
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 3; y++) {
mat4x3[3 * x + y] = colorMatrix[4 * x + y];
}
}
}
gl->fUniformMatrix4x3fv(mLoc_uColorMatrix, 1, false, mat4x3);
break;
default:
gfxCriticalError() << "Bad mType_uColorMatrix: "
<< gfx::hexa(mType_uColorMatrix);
gl->fUniformMatrix4x3fv(mLoc_uColorMatrix, 1, false, mat4x3);
break;
default:
gfxCriticalError()
<< "Bad mType_uColorMatrix: " << gfx::hexa(mType_uColorMatrix);
}
}
}
@ -550,11 +599,15 @@ GLBlitHelper::GLBlitHelper(GLContext* const gl)
const auto glslVersion = mGL->ShadingLanguageVersion();
// Always use 100 on ES because some devices have OES_EGL_image_external but
// not OES_EGL_image_external_essl3. We could just use 100 in that particular
// case, but this is a lot easier and is not harmful to other usages.
if (mGL->IsGLES()) {
mDrawBlitProg_VersionLine = nsCString("#version 100\n");
// If you run into problems on old android devices, it might be because some devices have OES_EGL_image_external but not OES_EGL_image_external_essl3.
// We could just use 100 in that particular case, but then we lose out on e.g. sampler3D.
// Let's just try 300 for now, and if we get regressions we'll add an essl100 fallback.
if (glslVersion >= 300) {
mDrawBlitProg_VersionLine = nsCString("#version 300 es\n");
} else {
mDrawBlitProg_VersionLine = nsCString("#version 100\n");
}
} else if (glslVersion >= 130) {
mDrawBlitProg_VersionLine = nsPrintfCString("#version %u\n", glslVersion);
}
@ -625,36 +678,46 @@ const DrawBlitProg* GLBlitHelper::GetDrawBlitProg(
const DrawBlitProg* GLBlitHelper::CreateDrawBlitProg(
const DrawBlitProg::Key& key) const {
const char kFragHeader_Global[] =
"\
#ifdef GL_ES \n\
#ifdef GL_FRAGMENT_PRECISION_HIGH \n\
precision highp float; \n\
#else \n\
precision mediump float; \n\
#endif \n\
#endif \n\
\n\
#if __VERSION__ >= 130 \n\
#define VARYING in \n\
#define FRAG_COLOR oFragColor \n\
out vec4 FRAG_COLOR; \n\
#else \n\
#define VARYING varying \n\
#define FRAG_COLOR gl_FragColor \n\
#endif \n\
\n\
#if __VERSION__ >= 120 \n\
#define MAT4X3 mat4x3 \n\
#else \n\
#define MAT4X3 mat4 \n\
#endif \n\
";
const auto precisionPref = StaticPrefs::gfx_blithelper_precision();
const char* precision;
switch (precisionPref) {
case 0:
precision = "lowp";
break;
case 1:
precision = "mediump";
break;
default:
if (precisionPref != 2) {
NS_WARNING("gfx.blithelper.precision clamped to 2.");
}
precision = "MAXP";
break;
}
nsPrintfCString precisionLine("\n#define PRECISION %s\n", precision);
// -
const ScopedShader fs(mGL, LOCAL_GL_FRAGMENT_SHADER);
const char* const parts[] = {mDrawBlitProg_VersionLine.get(), key.fragHeader,
kFragHeader_Global, key.fragBody};
mGL->fShaderSource(fs, ArrayLength(parts), parts, nullptr);
std::vector<const char*> parts;
{
parts.push_back(mDrawBlitProg_VersionLine.get());
parts.push_back(kFragPreprocHeader);
if (key.fragHeader) {
parts.push_back(key.fragHeader);
}
parts.push_back(precisionLine.BeginReading());
parts.push_back(kFragDeclHeader);
for (const auto& part : key.fragParts) {
if (part) {
parts.push_back(part);
}
}
parts.push_back(kFragBody);
}
mGL->fShaderSource(fs, parts.size(), parts.data(), nullptr);
mGL->fCompileShader(fs);
const auto prog = mGL->fCreateProgram();
@ -698,11 +761,19 @@ const DrawBlitProg* GLBlitHelper::CreateDrawBlitProg(
mGL->fGetShaderInfoLog(fs, fsLogLen, nullptr, fsLog.get());
fsLog[fsLogLen] = 0;
gfxCriticalError() << "DrawBlitProg link failed:\n"
<< "progLog: " << progLog.get() << "\n"
<< "vsLog: " << vsLog.get() << "\n"
<< "fsLog: " << fsLog.get() << "\n";
MOZ_CRASH();
const auto logs =
std::string("DrawBlitProg link failed:\n") + "progLog: " + progLog.get() +
"\n" + "vsLog: " + vsLog.get() + "\n" + "fsLog: " + fsLog.get() + "\n";
gfxCriticalError() << logs;
printf_stderr("Frag source:\n");
int i = 0;
for (const auto& part : parts) {
printf_stderr("// parts[%i]:\n%s\n", i, part);
i += 1;
}
MOZ_CRASH("DrawBlitProg link failed");
}
// -----------------------------------------------------------------------------
@ -865,7 +936,8 @@ bool GLBlitHelper::Blit(const java::GeckoSurfaceTexture::Ref& surfaceTexture,
const auto transform3 = Mat3::I();
const auto srcOrigin = OriginPos::TopLeft;
const bool yFlip = (srcOrigin != destOrigin);
const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, kFragBody_RGBA});
const auto& prog = GetDrawBlitProg(
{kFragHeader_TexExt, kFragSample_OnePlane, kFragConvert_None});
const DrawBlitProg::BaseArgs baseArgs = {transform3, yFlip, destSize,
Nothing()};
prog->Draw(baseArgs, nullptr);
@ -895,7 +967,8 @@ bool GuessDivisors(const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
bool GLBlitHelper::BlitPlanarYCbCr(const PlanarYCbCrData& yuvData,
const gfx::IntSize& destSize,
const OriginPos destOrigin) {
const auto& prog = GetDrawBlitProg({kFragHeader_Tex2D, kFragBody_PlanarYUV});
const auto& prog = GetDrawBlitProg(
{kFragHeader_Tex2D, {kFragSample_ThreePlane, kFragConvert_ColorMatrix}});
if (!mYuvUploads[0]) {
mGL->fGenTextures(3, mYuvUploads);
@ -957,7 +1030,7 @@ bool GLBlitHelper::BlitPlanarYCbCr(const PlanarYCbCrData& yuvData,
// --
const ScopedSaveMultiTex saveTex(mGL, 3, LOCAL_GL_TEXTURE_2D);
const ScopedSaveMultiTex saveTex(mGL, {0, 1, 2}, LOCAL_GL_TEXTURE_2D);
const ResetUnpackState reset(mGL);
const gfx::IntSize yTexSize(yuvData.mYStride, yuvData.YDataSize().height);
const gfx::IntSize uvTexSize(yuvData.mCbCrStride,
@ -1008,7 +1081,7 @@ bool GLBlitHelper::BlitPlanarYCbCr(const PlanarYCbCrData& yuvData,
const DrawBlitProg::BaseArgs baseArgs = {SubRectMat3(clipRect, yTexSize),
yFlip, destSize, Nothing()};
const DrawBlitProg::YUVArgs yuvArgs = {
SubRectMat3(clipRect, uvTexSize, divisors), yuvData.mYUVColorSpace};
SubRectMat3(clipRect, uvTexSize, divisors), Some(yuvData.mYUVColorSpace)};
prog->Draw(baseArgs, &yuvArgs);
return true;
}
@ -1058,7 +1131,7 @@ bool GLBlitHelper::BlitImage(MacIOSurface* const iosurf,
// TODO: The colorspace is known by the IOSurface, why override it?
// See GetYUVColorSpace/GetFullRange()
DrawBlitProg::YUVArgs yuvArgs;
yuvArgs.colorSpace = iosurf->GetYUVColorSpace();
yuvArgs.colorSpaceForMatrix = Some(iosurf->GetYUVColorSpace());
const DrawBlitProg::YUVArgs* pYuvArgs = nullptr;
@ -1068,9 +1141,12 @@ bool GLBlitHelper::BlitImage(MacIOSurface* const iosurf,
}
const GLenum texTarget = LOCAL_GL_TEXTURE_RECTANGLE;
const char* const fragHeader = kFragHeader_Tex2DRect;
const ScopedSaveMultiTex saveTex(mGL, planes, texTarget);
std::vector<uint8_t> texUnits;
for (uint8_t i = 0; i < planes; i++) {
texUnits.push_back(i);
}
const ScopedSaveMultiTex saveTex(mGL, texUnits, texTarget);
const ScopedTexture tex0(mGL);
const ScopedTexture tex1(mGL);
const ScopedTexture tex2(mGL);
@ -1083,7 +1159,7 @@ bool GLBlitHelper::BlitImage(MacIOSurface* const iosurf,
pixelFormat);
}
const char* fragBody;
const char* fragSample;
switch (planes) {
case 1:
switch (pixelFormat) {
@ -1095,11 +1171,11 @@ bool GLBlitHelper::BlitImage(MacIOSurface* const iosurf,
case kCVPixelFormatType_32RGBA:
case kCVPixelFormatType_64ARGB:
case kCVPixelFormatType_48RGB:
fragBody = kFragBody_RGBA;
fragSample = kFragSample_OnePlane;
break;
case kCVPixelFormatType_422YpCbCr8:
case kCVPixelFormatType_422YpCbCr8_yuvs:
fragBody = kFragBody_CrYCb;
fragSample = kFragSample_OnePlane_YUV_via_GBR;
pYuvArgs = &yuvArgs;
break;
default: {
@ -1110,19 +1186,19 @@ bool GLBlitHelper::BlitImage(MacIOSurface* const iosurf,
str = IntAsAscii(pixelFormat);
}
gfxCriticalError() << "Unhandled kCVPixelFormatType_*: " << str;
}
// Probably YUV though
fragBody = kFragBody_CrYCb;
fragSample = kFragSample_OnePlane_YUV_via_GBR;
pYuvArgs = &yuvArgs;
break;
}
}
break;
case 2:
fragBody = kFragBody_NV12;
fragSample = kFragSample_TwoPlane;
pYuvArgs = &yuvArgs;
break;
case 3:
fragBody = kFragBody_PlanarYUV;
fragSample = kFragSample_ThreePlane;
pYuvArgs = &yuvArgs;
break;
default:
@ -1148,7 +1224,11 @@ bool GLBlitHelper::BlitImage(MacIOSurface* const iosurf,
}
}
const auto& prog = GetDrawBlitProg({fragHeader, fragBody});
const auto& prog = GetDrawBlitProg({
kFragHeader_Tex2DRect,
{fragSample,
kFragConvert_ColorMatrix},
});
prog->Draw(baseArgs, pYuvArgs);
return true;
}
@ -1176,10 +1256,13 @@ void GLBlitHelper::DrawBlitTextureToFramebuffer(const GLuint srcTex,
gfxCriticalError() << "Unexpected srcTarget: " << srcTarget;
return;
}
const char* fragBody = srcIsBGRA ? kFragBody_BGRA : kFragBody_RGBA;
const auto& prog = GetDrawBlitProg({fragHeader, fragBody});
const auto fragConvert = srcIsBGRA ? kFragConvert_BGR : kFragConvert_None;
const auto& prog = GetDrawBlitProg({
fragHeader,
{kFragSample_OnePlane, fragConvert},
});
const ScopedSaveMultiTex saveTex(mGL, 1, srcTarget);
const ScopedSaveMultiTex saveTex(mGL, {0}, srcTarget);
mGL->fBindTexture(srcTarget, srcTex);
const bool yFlip = false;
@ -1337,28 +1420,35 @@ bool GLBlitHelper::Blit(DMABufSurface* surface, const gfx::IntSize& destSize,
// TODO: The colorspace is known by the DMABUFSurface, why override it?
// See GetYUVColorSpace/GetFullRange()
DrawBlitProg::YUVArgs yuvArgs;
yuvArgs.colorSpace = surface->GetYUVColorSpace();
yuvArgs.colorSpaceForMatrix = Some(surface->GetYUVColorSpace());
const DrawBlitProg::YUVArgs* pYuvArgs = nullptr;
const auto planes = surface->GetTextureCount();
const GLenum texTarget = LOCAL_GL_TEXTURE_2D;
const ScopedSaveMultiTex saveTex(mGL, planes, texTarget);
std::vector<uint8_t> texUnits;
for (uint8_t i = 0; i < planes; i++) {
texUnits.push_back(i);
}
const ScopedSaveMultiTex saveTex(mGL, texUnits, texTarget);
const auto pixelFormat = surface->GetSurfaceType();
const char* fragBody;
const char* fragSample;
auto fragConvert = kFragConvert_None;
switch (pixelFormat) {
case DMABufSurface::SURFACE_RGBA:
fragBody = kFragBody_RGBA;
fragSample = kFragSample_OnePlane;
break;
case DMABufSurface::SURFACE_NV12:
fragBody = kFragBody_NV12;
fragSample = kFragSample_TwoPlane;
pYuvArgs = &yuvArgs;
fragConvert = kFragConvert_ColorMatrix;
break;
case DMABufSurface::SURFACE_YUV420:
fragBody = kFragBody_PlanarYUV;
fragSample = kFragSample_ThreePlane;
pYuvArgs = &yuvArgs;
fragConvert = kFragConvert_ColorMatrix;
break;
default:
gfxCriticalError() << "Unexpected pixel format: " << pixelFormat;
@ -1376,7 +1466,8 @@ bool GLBlitHelper::Blit(DMABufSurface* surface, const gfx::IntSize& destSize,
baseArgs.texMatrix0 = SubRectMat3(0, 0, 1, 1);
yuvArgs.texMatrix1 = SubRectMat3(0, 0, 1, 1);
const auto& prog = GetDrawBlitProg({kFragHeader_Tex2D, fragBody});
const auto& prog =
GetDrawBlitProg({kFragHeader_Tex2D, {fragSample, fragConvert}});
prog->Draw(baseArgs, pYuvArgs);
return true;
@ -1394,5 +1485,105 @@ bool GLBlitHelper::BlitImage(layers::DMABUFSurfaceImage* srcImage,
}
#endif
// -
template <size_t N>
static void PushUnorm(uint32_t* const out, const float inVal) {
const uint32_t mask = (1 << N) - 1;
auto fval = inVal;
fval = std::max(0.0f, std::min(fval, 1.0f));
fval *= mask;
fval = roundf(fval);
auto ival = static_cast<uint32_t>(fval);
ival &= mask;
*out <<= N;
*out |= ival;
}
static uint32_t toRgb10A2(const color::vec4& val) {
// R in LSB
uint32_t ret = 0;
PushUnorm<2>(&ret, val.w());
PushUnorm<10>(&ret, val.z());
PushUnorm<10>(&ret, val.y());
PushUnorm<10>(&ret, val.x());
return ret;
}
std::shared_ptr<gl::Texture> GLBlitHelper::GetColorLutTex(
const ColorLutKey& key) const {
auto& weak = mColorLutTexMap[key];
auto strong = weak.lock();
if (!strong) {
auto& gl = *mGL;
strong = std::make_shared<gl::Texture>(gl);
weak = strong;
const auto ct = color::ColorspaceTransform::Create(key.src, key.dst);
// -
const auto minLutSize = color::ivec3{2};
const auto maxLutSize = color::ivec3{256};
auto lutSize = minLutSize;
if (ct.srcSpace.yuv) {
lutSize.x(StaticPrefs::gfx_blithelper_lut_size_ycbcr_y());
lutSize.y(StaticPrefs::gfx_blithelper_lut_size_ycbcr_cb());
lutSize.z(StaticPrefs::gfx_blithelper_lut_size_ycbcr_cr());
} else {
lutSize.x(StaticPrefs::gfx_blithelper_lut_size_rgb_r());
lutSize.y(StaticPrefs::gfx_blithelper_lut_size_rgb_g());
lutSize.z(StaticPrefs::gfx_blithelper_lut_size_rgb_b());
}
lutSize = max(minLutSize, min(lutSize, maxLutSize)); // Clamp
const auto lut = ct.ToLut3(lutSize);
const auto& size = lut.size;
// -
constexpr GLenum target = LOCAL_GL_TEXTURE_3D;
const auto bind = gl::ScopedBindTexture(&gl, strong->name, target);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_R, LOCAL_GL_CLAMP_TO_EDGE);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
bool useFloat16 = true;
if (useFloat16) {
// Use rgba16f, which we can thankfully upload as rgba32f
static_assert(sizeof(color::vec4) == sizeof(float) * 4);
std::vector<color::vec4> uploadData;
uploadData.reserve(lut.data.size());
for (const auto& src : lut.data) {
const auto dst = color::vec4{src, 1};
uploadData.push_back(dst);
}
gl.fTexStorage3D(target, 1, LOCAL_GL_RGBA16F, size.x(), size.y(),
size.z());
gl.fTexSubImage3D(target, 0, 0, 0, 0, size.x(), size.y(), size.z(),
LOCAL_GL_RGBA, LOCAL_GL_FLOAT, uploadData.data());
} else {
// Use Rgb10A2
std::vector<uint32_t> uploadData;
uploadData.reserve(lut.data.size());
for (const auto& src : lut.data) {
const auto dst = toRgb10A2({src, 1});
uploadData.push_back(dst);
}
gl.fTexStorage3D(target, 1, LOCAL_GL_RGB10_A2, size.x(), size.y(),
size.z());
gl.fTexSubImage3D(target, 0, 0, 0, 0, size.x(), size.y(), size.z(),
LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV,
uploadData.data());
}
}
return strong;
}
} // namespace gl
} // namespace mozilla

View File

@ -7,8 +7,12 @@
#ifndef GLBLITHELPER_H_
#define GLBLITHELPER_H_
#include <array>
#include <cstdint>
#include <map>
#include <memory>
#include <unordered_map>
#include "Colorspaces.h"
#include "GLConsts.h"
#include "GLContextTypes.h"
#include "GLTypes.h"
@ -78,8 +82,9 @@ class DMABUFSurfaceImage;
namespace gl {
class BindAnglePlanes;
class GLContext;
class GLBlitHelper;
class GLContext;
class Texture;
bool GuessDivisors(const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
gfx::IntSize* const out_divisors);
@ -90,10 +95,27 @@ struct Mat {
float& at(const uint8_t x, const uint8_t y) { return m[N * x + y]; }
static Mat<N> Zero();
static Mat<N> I();
static Mat<N> I() {
auto ret = Mat<N>{};
for (uint8_t i = 0; i < N; i++) {
ret.at(i, i) = 1.0f;
}
return ret;
}
Mat<N> operator*(const Mat<N>& r) const;
Mat<N> operator*(const Mat<N>& r) const {
Mat<N> ret;
for (uint8_t x = 0; x < N; x++) {
for (uint8_t y = 0; y < N; y++) {
float sum = 0.0f;
for (uint8_t i = 0; i < N; i++) {
sum += at(i, y) * r.at(x, i);
}
ret.at(x, y) = sum;
}
}
return ret;
}
};
typedef Mat<3> Mat3;
@ -108,17 +130,18 @@ class DrawBlitProg final {
const GLint mLoc_uDestMatrix;
const GLint mLoc_uTexMatrix0;
const GLint mLoc_uTexMatrix1;
const GLint mLoc_uColorLut;
const GLint mLoc_uColorMatrix;
GLenum mType_uColorMatrix = 0;
public:
struct Key final {
const char* const fragHeader;
const char* const fragBody;
const char* fragHeader = nullptr;
std::array<const char*, 2> fragParts = {};
bool operator<(const Key& x) const {
if (fragHeader != x.fragHeader) return fragHeader < x.fragHeader;
return fragBody < x.fragBody;
auto Members() const { return std::tie(fragHeader, fragParts); }
friend bool operator<(const Key& a, const Key& b) {
return a.Members() < b.Members();
}
};
@ -131,10 +154,11 @@ class DrawBlitProg final {
gfx::IntSize
destSize; // Always needed for (at least) setting the viewport.
Maybe<gfx::IntRect> destRect;
Maybe<uint32_t> texUnitForColorLut;
};
struct YUVArgs final {
Mat3 texMatrix1;
gfx::YUVColorSpace colorSpace;
Maybe<gfx::YUVColorSpace> colorSpaceForMatrix;
};
void Draw(const BaseArgs& args, const YUVArgs* argsYUV = nullptr) const;
@ -142,14 +166,15 @@ class DrawBlitProg final {
class ScopedSaveMultiTex final {
GLContext& mGL;
const uint8_t mTexCount;
const std::vector<uint8_t> mTexUnits;
const GLenum mTexTarget;
const GLuint mOldTexUnit;
GLuint mOldTexSampler[3];
GLuint mOldTex[3];
public:
ScopedSaveMultiTex(GLContext* gl, uint8_t texCount, GLenum texTarget);
ScopedSaveMultiTex(GLContext* gl, const std::vector<uint8_t>& texUnits,
GLenum texTarget);
~ScopedSaveMultiTex();
};
@ -171,6 +196,23 @@ class GLBlitHelper final {
gfx::IntSize mYuvUploads_YSize = {0, 0};
gfx::IntSize mYuvUploads_UVSize = {0, 0};
public:
struct ColorLutKey {
color::ColorspaceDesc src;
color::ColorspaceDesc dst;
auto Members() const { return std::tie(src, dst); }
INLINE_AUTO_MAPPABLE(ColorLutKey)
};
private:
mutable std::unordered_map<ColorLutKey, std::weak_ptr<gl::Texture>,
ColorLutKey::Hasher>
mColorLutTexMap;
public:
std::shared_ptr<gl::Texture> GetColorLutTex(const ColorLutKey& key) const;
#ifdef XP_WIN
mutable RefPtr<ID3D11Device> mD3D11;
@ -270,13 +312,21 @@ class GLBlitHelper final {
#endif
};
// -
// For DrawBlitProg::Key::fragParts
extern const char* const kFragHeader_Tex2D;
extern const char* const kFragHeader_Tex2DRect;
extern const char* const kFragHeader_TexExt;
extern const char* const kFragBody_RGBA;
extern const char* const kFragBody_CrYCb;
extern const char* const kFragBody_NV12;
extern const char* const kFragBody_PlanarYUV;
extern const char* const kFragSample_OnePlane;
extern const char* const kFragSample_TwoPlane;
extern const char* const kFragSample_ThreePlane;
extern const char* const kFragConvert_None;
extern const char* const kFragConvert_BGR;
extern const char* const kFragConvert_ColorMatrix;
extern const char* const kFragConvert_ColorLut;
} // namespace gl
} // namespace mozilla

View File

@ -84,7 +84,16 @@ class BindAnglePlanes final {
const EGLAttrib* const* postAttribsList = nullptr)
: mParent(*parent),
mNumPlanes(numPlanes),
mMultiTex(mParent.mGL, mNumPlanes, LOCAL_GL_TEXTURE_EXTERNAL),
mMultiTex(
mParent.mGL,
[&]() {
std::vector<uint8_t> ret;
for (int i = 0; i < numPlanes; i++) {
ret.push_back(i);
}
return ret;
}(),
LOCAL_GL_TEXTURE_EXTERNAL),
mTempTexs{0},
mStreams{0},
mSuccess(true) {
@ -230,7 +239,7 @@ bool GLBlitHelper::BlitDescriptor(const layers::SurfaceDescriptorD3D10& desc,
const auto srcOrigin = OriginPos::BottomLeft;
const gfx::IntRect clipRect(0, 0, clipSize.width, clipSize.height);
const auto colorSpace = desc.yUVColorSpace();
const auto colorSpace = desc.colorSpace();
if (format != gfx::SurfaceFormat::NV12 &&
format != gfx::SurfaceFormat::P010 &&
@ -286,13 +295,29 @@ bool GLBlitHelper::BlitDescriptor(const layers::SurfaceDescriptorD3D10& desc,
const gfx::IntSize uvSize(ySize.width / divisors.width,
ySize.height / divisors.height);
const auto yuvColorSpace = [&]() {
switch (colorSpace) {
case gfx::ColorSpace2::UNKNOWN:
case gfx::ColorSpace2::SRGB:
case gfx::ColorSpace2::DISPLAY_P3:
MOZ_CRASH("Expected BT* colorspace");
case gfx::ColorSpace2::BT601_525:
return gfx::YUVColorSpace::BT601;
case gfx::ColorSpace2::BT709:
return gfx::YUVColorSpace::BT709;
case gfx::ColorSpace2::BT2020:
return gfx::YUVColorSpace::BT2020;
}
MOZ_ASSERT_UNREACHABLE();
}();
const bool yFlip = destOrigin != srcOrigin;
const DrawBlitProg::BaseArgs baseArgs = {SubRectMat3(clipRect, ySize), yFlip,
destSize, Nothing()};
const DrawBlitProg::YUVArgs yuvArgs = {
SubRectMat3(clipRect, uvSize, divisors), colorSpace};
SubRectMat3(clipRect, uvSize, divisors), Some(yuvColorSpace)};
const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, kFragBody_NV12});
const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, {kFragSample_TwoPlane, kFragConvert_ColorMatrix}});
prog->Draw(baseArgs, &yuvArgs);
return true;
}
@ -340,9 +365,10 @@ bool GLBlitHelper::BlitAngleYCbCr(const WindowsHandle (&handleList)[3],
const DrawBlitProg::BaseArgs baseArgs = {SubRectMat3(clipRect, ySize), yFlip,
destSize, Nothing()};
const DrawBlitProg::YUVArgs yuvArgs = {
SubRectMat3(clipRect, uvSize, divisors), colorSpace};
SubRectMat3(clipRect, uvSize, divisors), Some(colorSpace)};
const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, kFragBody_PlanarYUV});
const auto& prog =
GetDrawBlitProg({kFragHeader_TexExt, {kFragSample_ThreePlane, kFragConvert_ColorMatrix}});
prog->Draw(baseArgs, &yuvArgs);
return true;
}

View File

@ -143,7 +143,7 @@ SharedSurface_ANGLEShareHandle::ToSurfaceDescriptor() {
const auto format = gfx::SurfaceFormat::B8G8R8A8;
return Some(layers::SurfaceDescriptorD3D10(
(WindowsHandle)mShareHandle, /* gpuProcessTextureId */ Nothing(),
/* arrayIndex */ 0, format, mDesc.size, gfx::YUVColorSpace::Identity,
/* arrayIndex */ 0, format, mDesc.size, mDesc.colorSpace,
gfx::ColorRange::FULL));
}

View File

@ -439,7 +439,7 @@ SharedSurface_D3D11Interop::ToSurfaceDescriptor() {
const auto format = gfx::SurfaceFormat::B8G8R8A8;
return Some(layers::SurfaceDescriptorD3D10(
WindowsHandle(mData.dxgiHandle), /* gpuProcessTextureId */ Nothing(),
/* arrayIndex */ 0, format, mDesc.size, gfx::YUVColorSpace::Identity,
/* arrayIndex */ 0, format, mDesc.size, mDesc.colorSpace,
gfx::ColorRange::FULL));
}

View File

@ -0,0 +1,404 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "Colorspaces.h"
namespace mozilla::color {
mat4 YuvFromYcbcr(const YcbcrDesc&);
float TfFromLinear(const PiecewiseGammaDesc&, float linear);
float LinearFromTf(const PiecewiseGammaDesc&, float tf);
} // namespace mozilla::color
using namespace mozilla::color;
auto Calc8From8(const ColorspaceTransform& ct, const ivec3 in8) {
const auto in = vec3(in8) / vec3(255);
const auto out = ct.DstFromSrc(in);
const auto out8 = ivec3(round(out * vec3(255)));
return out8;
}
auto Sample8From8(const Lut3& lut, const vec3 in8) {
const auto in = in8 / vec3(255);
const auto out = lut.Sample(in);
const auto out8 = ivec3(round(out * vec3(255)));
return out8;
}
TEST(Colorspaces, YcbcrDesc_Narrow8)
{
const auto m = YuvFromYcbcr(YcbcrDesc::Narrow8());
const auto Yuv8 = [&](const ivec3 ycbcr8) {
const auto ycbcr = vec4(vec3(ycbcr8) / 255, 1);
const auto yuv = m * ycbcr;
return ivec3(round(yuv * 255));
};
EXPECT_EQ(Yuv8({{ 16, 128, 128}}), (ivec3{{0, 0, 0}}));
EXPECT_EQ(Yuv8({{ 17, 128, 128}}), (ivec3{{1, 0, 0}}));
// y = 0.5 => (16 + 235) / 2 = 125.5
EXPECT_EQ(Yuv8({{125, 128, 128}}), (ivec3{{127, 0, 0}}));
EXPECT_EQ(Yuv8({{126, 128, 128}}), (ivec3{{128, 0, 0}}));
EXPECT_EQ(Yuv8({{234, 128, 128}}), (ivec3{{254, 0, 0}}));
EXPECT_EQ(Yuv8({{235, 128, 128}}), (ivec3{{255, 0, 0}}));
// Check that we get the naive out-of-bounds behavior we'd expect:
EXPECT_EQ(Yuv8({{ 15, 128, 128}}), (ivec3{{-1,0,0}}));
EXPECT_EQ(Yuv8({{236, 128, 128}}), (ivec3{{256, 0, 0}}));
}
TEST(Colorspaces, YcbcrDesc_Full8)
{
const auto m = YuvFromYcbcr(YcbcrDesc::Full8());
const auto Yuv8 = [&](const ivec3 ycbcr8) {
const auto ycbcr = vec4(vec3(ycbcr8) / 255, 1);
const auto yuv = m * ycbcr;
return ivec3(round(yuv * 255));
};
EXPECT_EQ(Yuv8({{ 0, 128, 128}}), (ivec3{{0, 0, 0}}));
EXPECT_EQ(Yuv8({{ 1, 128, 128}}), (ivec3{{1, 0, 0}}));
EXPECT_EQ(Yuv8({{127, 128, 128}}), (ivec3{{127, 0, 0}}));
EXPECT_EQ(Yuv8({{128, 128, 128}}), (ivec3{{128, 0, 0}}));
EXPECT_EQ(Yuv8({{254, 128, 128}}), (ivec3{{254, 0, 0}}));
EXPECT_EQ(Yuv8({{255, 128, 128}}), (ivec3{{255, 0, 0}}));
}
TEST(Colorspaces, YcbcrDesc_Float)
{
const auto m = YuvFromYcbcr(YcbcrDesc::Float());
const auto Yuv8 = [&](const vec3 ycbcr8) {
const auto ycbcr = vec4(vec3(ycbcr8) / 255, 1);
const auto yuv = m * ycbcr;
return ivec3(round(yuv * 255));
};
EXPECT_EQ(Yuv8({{ 0, 0.5*255, 0.5*255}}), (ivec3{{0, 0, 0}}));
EXPECT_EQ(Yuv8({{ 1, 0.5*255, 0.5*255}}), (ivec3{{1, 0, 0}}));
EXPECT_EQ(Yuv8({{127, 0.5*255, 0.5*255}}), (ivec3{{127, 0, 0}}));
EXPECT_EQ(Yuv8({{128, 0.5*255, 0.5*255}}), (ivec3{{128, 0, 0}}));
EXPECT_EQ(Yuv8({{254, 0.5*255, 0.5*255}}), (ivec3{{254, 0, 0}}));
EXPECT_EQ(Yuv8({{255, 0.5*255, 0.5*255}}), (ivec3{{255, 0, 0}}));
}
TEST(Colorspaces, ColorspaceTransform_Rec709Narrow)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Narrow8()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{},
};
const auto ct = ColorspaceTransform::Create(src, dst);
EXPECT_EQ(Calc8From8(ct, {{ 16, 128, 128}}), (ivec3{0}));
EXPECT_EQ(Calc8From8(ct, {{ 17, 128, 128}}), (ivec3{1}));
EXPECT_EQ(Calc8From8(ct, {{126, 128, 128}}), (ivec3{128}));
EXPECT_EQ(Calc8From8(ct, {{234, 128, 128}}), (ivec3{254}));
EXPECT_EQ(Calc8From8(ct, {{235, 128, 128}}), (ivec3{255}));
// Check that we get the naive out-of-bounds behavior we'd expect:
EXPECT_EQ(Calc8From8(ct, {{ 15, 128, 128}}), (ivec3{-1}));
EXPECT_EQ(Calc8From8(ct, {{236, 128, 128}}), (ivec3{256}));
}
TEST(Colorspaces, LutSample_Rec709Float)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Float()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{},
};
const auto lut = ColorspaceTransform::Create(src, dst).ToLut3();
EXPECT_EQ(Sample8From8(lut, {{ 0, 0.5*255, 0.5*255}}), (ivec3{0}));
EXPECT_EQ(Sample8From8(lut, {{ 1, 0.5*255, 0.5*255}}), (ivec3{1}));
EXPECT_EQ(Sample8From8(lut, {{127, 0.5*255, 0.5*255}}), (ivec3{127}));
EXPECT_EQ(Sample8From8(lut, {{128, 0.5*255, 0.5*255}}), (ivec3{128}));
EXPECT_EQ(Sample8From8(lut, {{254, 0.5*255, 0.5*255}}), (ivec3{254}));
EXPECT_EQ(Sample8From8(lut, {{255, 0.5*255, 0.5*255}}), (ivec3{255}));
}
TEST(Colorspaces, LutSample_Rec709Narrow)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Narrow8()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{},
};
const auto lut = ColorspaceTransform::Create(src, dst).ToLut3();
EXPECT_EQ(Sample8From8(lut, {{ 16, 128, 128}}), (ivec3{0}));
EXPECT_EQ(Sample8From8(lut, {{ 17, 128, 128}}), (ivec3{1}));
EXPECT_EQ(Sample8From8(lut, {{(235+16)/2, 128, 128}}), (ivec3{127}));
EXPECT_EQ(Sample8From8(lut, {{(235+16)/2 + 1, 128, 128}}), (ivec3{128}));
EXPECT_EQ(Sample8From8(lut, {{234, 128, 128}}), (ivec3{254}));
EXPECT_EQ(Sample8From8(lut, {{235, 128, 128}}), (ivec3{255}));
}
TEST(Colorspaces, LutSample_Rec709Full)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Full8()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{},
};
const auto lut = ColorspaceTransform::Create(src, dst).ToLut3();
EXPECT_EQ(Sample8From8(lut, {{ 0, 128, 128}}), (ivec3{0}));
EXPECT_EQ(Sample8From8(lut, {{ 1, 128, 128}}), (ivec3{1}));
EXPECT_EQ(Sample8From8(lut, {{ 16, 128, 128}}), (ivec3{16}));
EXPECT_EQ(Sample8From8(lut, {{128, 128, 128}}), (ivec3{128}));
EXPECT_EQ(Sample8From8(lut, {{235, 128, 128}}), (ivec3{235}));
EXPECT_EQ(Sample8From8(lut, {{254, 128, 128}}), (ivec3{254}));
EXPECT_EQ(Sample8From8(lut, {{255, 128, 128}}), (ivec3{255}));
}
TEST(Colorspaces, PiecewiseGammaDesc_Srgb)
{
const auto tf = PiecewiseGammaDesc::Srgb();
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0x00 / 255.0) * 255 )), 0x00);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0x01 / 255.0) * 255 )), 0x0d);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0x37 / 255.0) * 255 )), 0x80);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0x80 / 255.0) * 255 )), 0xbc);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0xfd / 255.0) * 255 )), 0xfe);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0xfe / 255.0) * 255 )), 0xff);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0xff / 255.0) * 255 )), 0xff);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x00 / 255.0) * 255 )), 0x00);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x01 / 255.0) * 255 )), 0x00);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x06 / 255.0) * 255 )), 0x00);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x07 / 255.0) * 255 )), 0x01);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x0d / 255.0) * 255 )), 0x01);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x80 / 255.0) * 255 )), 0x37);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0xbc / 255.0) * 255 )), 0x80);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0xfe / 255.0) * 255 )), 0xfd);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0xff / 255.0) * 255 )), 0xff);
}
TEST(Colorspaces, PiecewiseGammaDesc_Rec709)
{
const auto tf = PiecewiseGammaDesc::Rec709();
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0x00 / 255.0) * 255 )), 0x00);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0x01 / 255.0) * 255 )), 0x05);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0x43 / 255.0) * 255 )), 0x80);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0x80 / 255.0) * 255 )), 0xb4);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0xfd / 255.0) * 255 )), 0xfe);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0xfe / 255.0) * 255 )), 0xff);
EXPECT_EQ(int(roundf( TfFromLinear(tf, 0xff / 255.0) * 255 )), 0xff);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x00 / 255.0) * 255 )), 0x00);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x01 / 255.0) * 255 )), 0x00);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x02 / 255.0) * 255 )), 0x00);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x03 / 255.0) * 255 )), 0x01);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x05 / 255.0) * 255 )), 0x01);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0x80 / 255.0) * 255 )), 0x43);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0xb4 / 255.0) * 255 )), 0x80);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0xfe / 255.0) * 255 )), 0xfd);
EXPECT_EQ(int(roundf( LinearFromTf(tf, 0xff / 255.0) * 255 )), 0xff);
}
TEST(Colorspaces, ColorspaceTransform_PiecewiseGammaDesc)
{
const auto src = ColorspaceDesc{
Chromaticities::Srgb(),
{},
{},
};
const auto dst = ColorspaceDesc{
Chromaticities::Srgb(),
PiecewiseGammaDesc::Srgb(),
{},
};
const auto toGamma = ColorspaceTransform::Create(src, dst);
const auto toLinear = ColorspaceTransform::Create(dst, src);
EXPECT_EQ(Calc8From8(toGamma, ivec3{0x00}), (ivec3{0x00}));
EXPECT_EQ(Calc8From8(toGamma, ivec3{0x01}), (ivec3{0x0d}));
EXPECT_EQ(Calc8From8(toGamma, ivec3{0x37}), (ivec3{0x80}));
EXPECT_EQ(Calc8From8(toGamma, ivec3{0x80}), (ivec3{0xbc}));
EXPECT_EQ(Calc8From8(toGamma, ivec3{0xfd}), (ivec3{0xfe}));
EXPECT_EQ(Calc8From8(toGamma, ivec3{0xff}), (ivec3{0xff}));
EXPECT_EQ(Calc8From8(toLinear, ivec3{0x00}), (ivec3{0x00}));
EXPECT_EQ(Calc8From8(toLinear, ivec3{0x0d}), (ivec3{0x01}));
EXPECT_EQ(Calc8From8(toLinear, ivec3{0x80}), (ivec3{0x37}));
EXPECT_EQ(Calc8From8(toLinear, ivec3{0xbc}), (ivec3{0x80}));
EXPECT_EQ(Calc8From8(toLinear, ivec3{0xfe}), (ivec3{0xfd}));
EXPECT_EQ(Calc8From8(toLinear, ivec3{0xff}), (ivec3{0xff}));
}
// -
// Actual end-to-end tests
TEST(Colorspaces, SrgbFromRec709)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Narrow8()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Srgb(),
PiecewiseGammaDesc::Srgb(),
{},
};
const auto ct = ColorspaceTransform::Create(src, dst);
EXPECT_EQ(Calc8From8(ct, ivec3{{16, 128, 128}}), (ivec3{0}));
EXPECT_EQ(Calc8From8(ct, ivec3{{17, 128, 128}}), (ivec3{3}));
EXPECT_EQ(Calc8From8(ct, ivec3{{115, 128, 128}}), (ivec3{128}));
EXPECT_EQ(Calc8From8(ct, ivec3{{126, 128, 128}}), (ivec3{140}));
EXPECT_EQ(Calc8From8(ct, ivec3{{234, 128, 128}}), (ivec3{254}));
EXPECT_EQ(Calc8From8(ct, ivec3{{235, 128, 128}}), (ivec3{255}));
}
TEST(Colorspaces, SrgbFromDisplayP3)
{
const auto p3C = ColorspaceDesc{
Chromaticities::DisplayP3(),
PiecewiseGammaDesc::DisplayP3(),
};
const auto srgbC = ColorspaceDesc{
Chromaticities::Srgb(),
PiecewiseGammaDesc::Srgb(),
};
const auto srgbLinearC = ColorspaceDesc{
Chromaticities::Srgb(),
{},
};
const auto srgbFromP3 = ColorspaceTransform::Create(p3C, srgbC);
const auto srgbLinearFromP3 = ColorspaceTransform::Create(p3C, srgbLinearC);
// E.g. https://colorjs.io/apps/convert/?color=color(display-p3%200.4%200.8%200.4)&precision=4
auto srgb = srgbFromP3.DstFromSrc(vec3{{0.4, 0.8, 0.4}});
EXPECT_NEAR(srgb.x(), 0.179, 0.001);
EXPECT_NEAR(srgb.y(), 0.812, 0.001);
EXPECT_NEAR(srgb.z(), 0.342, 0.001);
auto srgbLinear = srgbLinearFromP3.DstFromSrc(vec3{{0.4, 0.8, 0.4}});
EXPECT_NEAR(srgbLinear.x(), 0.027, 0.001);
EXPECT_NEAR(srgbLinear.y(), 0.624, 0.001);
EXPECT_NEAR(srgbLinear.z(), 0.096, 0.001);
}
// -
struct Stats {
double mean = 0;
double variance = 0;
double min = 1.0/0;
double max = -1.0/0;
template<class T>
static Stats For(const T& iterable) {
auto ret = Stats{};
for (const auto& cur : iterable) {
ret.mean += cur;
ret.min = std::min(ret.min, cur);
ret.max = std::max(ret.max, cur);
}
ret.mean /= iterable.size();
for (const auto& cur : iterable) {
ret.variance += pow(cur - ret.mean, 2);
}
ret.variance /= iterable.size();
return ret;
}
constexpr double standardDeviation() const {
return sqrt(variance);
}
};
static Stats StatsForLutError(const ColorspaceTransform& ct,
const ivec3 srcQuants,
const ivec3 dstQuants) {
const auto lut = ct.ToLut3();
const auto dstScale = vec3(dstQuants-1);
std::vector<double> quantErrors;
quantErrors.reserve(srcQuants.x() * srcQuants.y() * srcQuants.z());
ForEachSampleWithin(srcQuants, [&](const vec3& src) {
const auto sampled = lut.Sample(src);
const auto actual = ct.DstFromSrc(src);
const auto isampled = ivec3(round(sampled * dstScale));
const auto iactual = ivec3(round(actual * dstScale));
const auto ierr = abs(isampled - iactual);
const auto quantError = dot(ierr, ivec3{1});
quantErrors.push_back(quantError);
if (quantErrors.size() % 100000 == 0) {
printf("%zu of %zu\n", quantErrors.size(), quantErrors.capacity());
}
});
const auto quantErrStats = Stats::For(quantErrors);
return quantErrStats;
}
TEST(Colorspaces, LutError_Rec709Full_Rec709Rgb)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Full8()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{},
};
const auto ct = ColorspaceTransform::Create(src, dst);
const auto stats = StatsForLutError(ct, ivec3{64}, ivec3{256});
EXPECT_NEAR(stats.mean, 0.000, 0.001);
EXPECT_NEAR(stats.standardDeviation(), 0.008, 0.001);
EXPECT_NEAR(stats.min, 0, 0.001);
EXPECT_NEAR(stats.max, 1, 0.001);
}
TEST(Colorspaces, LutError_Rec709Full_Srgb)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Full8()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Srgb(),
PiecewiseGammaDesc::Srgb(),
{},
};
const auto ct = ColorspaceTransform::Create(src, dst);
const auto stats = StatsForLutError(ct, ivec3{64}, ivec3{256});
EXPECT_NEAR(stats.mean, 0.530, 0.001);
EXPECT_NEAR(stats.standardDeviation(), 1.674, 0.001);
EXPECT_NEAR(stats.min, 0, 0.001);
EXPECT_NEAR(stats.max, 17, 0.001);
}

15
gfx/gl/gtest/moz.build Normal file
View File

@ -0,0 +1,15 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
LOCAL_INCLUDES += [
"/gfx/gl",
]
UNIFIED_SOURCES += [
"TestColorspaces.cpp",
]
FINAL_LIBRARY = "xul-gtest"

View File

@ -22,6 +22,8 @@ if CONFIG["MOZ_GL_PROVIDER"]:
EXPORTS += [
"AndroidSurfaceTexture.h",
"AutoMappable.h",
"Colorspaces.h",
"ForceDiscreteGPUHelperCGL.h",
"GfxTexturesReporter.h",
"GLBlitHelper.h",
@ -117,6 +119,7 @@ if CONFIG["MOZ_WAYLAND"]:
UNIFIED_SOURCES += [
"AndroidSurfaceTexture.cpp",
"Colorspaces.cpp",
"GfxTexturesReporter.cpp",
"GLBlitHelper.cpp",
"GLContext.cpp",
@ -138,6 +141,10 @@ SOURCES += [
"GLScreenBuffer.cpp",
]
TEST_DIRS += [
"gtest",
]
include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"

View File

@ -714,6 +714,12 @@ struct ParamTraits<mozilla::gfx::YUVRangedColorSpace>
mozilla::gfx::YUVRangedColorSpace::_First,
mozilla::gfx::YUVRangedColorSpace::_Last> {};
template <>
struct ParamTraits<mozilla::gfx::ColorSpace2>
: public ContiguousEnumSerializerInclusive<
mozilla::gfx::ColorSpace2, mozilla::gfx::ColorSpace2::_First,
mozilla::gfx::ColorSpace2::_Last> {};
template <>
struct ParamTraits<mozilla::StereoMode>
: public ContiguousEnumSerializer<mozilla::StereoMode,

View File

@ -25,19 +25,19 @@ using namespace gfx;
D3D11ShareHandleImage::D3D11ShareHandleImage(const gfx::IntSize& aSize,
const gfx::IntRect& aRect,
gfx::YUVColorSpace aColorSpace,
gfx::ColorSpace2 aColorSpace,
gfx::ColorRange aColorRange)
: Image(nullptr, ImageFormat::D3D11_SHARE_HANDLE_TEXTURE),
mSize(aSize),
mPictureRect(aRect),
mYUVColorSpace(aColorSpace),
mColorSpace(aColorSpace),
mColorRange(aColorRange) {}
bool D3D11ShareHandleImage::AllocateTexture(D3D11RecycleAllocator* aAllocator,
ID3D11Device* aDevice) {
if (aAllocator) {
mTextureClient =
aAllocator->CreateOrRecycleClient(mYUVColorSpace, mColorRange, mSize);
aAllocator->CreateOrRecycleClient(mColorSpace, mColorRange, mSize);
if (mTextureClient) {
D3D11TextureData* textureData = GetData();
MOZ_DIAGNOSTIC_ASSERT(textureData, "Wrong TextureDataType");
@ -83,7 +83,7 @@ class MOZ_RAII D3D11TextureClientAllocationHelper
: public ITextureClientAllocationHelper {
public:
D3D11TextureClientAllocationHelper(gfx::SurfaceFormat aFormat,
gfx::YUVColorSpace aColorSpace,
gfx::ColorSpace2 aColorSpace,
gfx::ColorRange aColorRange,
const gfx::IntSize& aSize,
TextureAllocationFlags aAllocFlags,
@ -91,7 +91,7 @@ class MOZ_RAII D3D11TextureClientAllocationHelper
TextureFlags aTextureFlags)
: ITextureClientAllocationHelper(aFormat, aSize, BackendSelector::Content,
aTextureFlags, aAllocFlags),
mYUVColorSpace(aColorSpace),
mColorSpace(aColorSpace),
mColorRange(aColorRange),
mDevice(aDevice) {}
@ -106,7 +106,7 @@ class MOZ_RAII D3D11TextureClientAllocationHelper
return (aTextureClient->GetFormat() != gfx::SurfaceFormat::NV12 &&
aTextureClient->GetFormat() != gfx::SurfaceFormat::P010 &&
aTextureClient->GetFormat() != gfx::SurfaceFormat::P016) ||
(textureData->GetYUVColorSpace() == mYUVColorSpace &&
(textureData->mColorSpace == mColorSpace &&
textureData->GetColorRange() == mColorRange &&
textureData->GetTextureAllocationFlags() == mAllocationFlags);
}
@ -118,14 +118,14 @@ class MOZ_RAII D3D11TextureClientAllocationHelper
if (!data) {
return nullptr;
}
data->SetYUVColorSpace(mYUVColorSpace);
data->mColorSpace = mColorSpace;
data->SetColorRange(mColorRange);
return MakeAndAddRef<TextureClient>(data, mTextureFlags,
aAllocator->GetTextureForwarder());
}
private:
const gfx::YUVColorSpace mYUVColorSpace;
const gfx::ColorSpace2 mColorSpace;
const gfx::ColorRange mColorRange;
const RefPtr<ID3D11Device> mDevice;
};
@ -158,7 +158,7 @@ void D3D11RecycleAllocator::SetPreferredSurfaceFormat(
}
already_AddRefed<TextureClient> D3D11RecycleAllocator::CreateOrRecycleClient(
gfx::YUVColorSpace aColorSpace, gfx::ColorRange aColorRange,
gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange,
const gfx::IntSize& aSize) {
// When CompositorDevice or ContentDevice is updated,
// we could not reuse old D3D11Textures. It could cause video flickering.

View File

@ -28,7 +28,7 @@ class D3D11RecycleAllocator final : public TextureClientRecycleAllocator {
gfx::SurfaceFormat aPreferredFormat);
already_AddRefed<TextureClient> CreateOrRecycleClient(
gfx::YUVColorSpace aColorSpace, gfx::ColorRange aColorRange,
gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange,
const gfx::IntSize& aSize);
void SetPreferredSurfaceFormat(gfx::SurfaceFormat aPreferredFormat);
@ -52,7 +52,7 @@ class D3D11RecycleAllocator final : public TextureClientRecycleAllocator {
class D3D11ShareHandleImage final : public Image {
public:
D3D11ShareHandleImage(const gfx::IntSize& aSize, const gfx::IntRect& aRect,
gfx::YUVColorSpace aColorSpace,
gfx::ColorSpace2 aColorSpace,
gfx::ColorRange aColorRange);
virtual ~D3D11ShareHandleImage() = default;
@ -66,7 +66,6 @@ class D3D11ShareHandleImage final : public Image {
ID3D11Texture2D* GetTexture() const;
gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; }
gfx::ColorRange GetColorRange() const { return mColorRange; }
private:
@ -80,7 +79,11 @@ class D3D11ShareHandleImage final : public Image {
gfx::IntSize mSize;
gfx::IntRect mPictureRect;
gfx::YUVColorSpace mYUVColorSpace;
public:
const gfx::ColorSpace2 mColorSpace;
private:
gfx::ColorRange mColorRange;
RefPtr<TextureClient> mTextureClient;
RefPtr<ID3D11Texture2D> mTexture;

View File

@ -35,14 +35,14 @@ void IMFSampleWrapper::ClearVideoSample() { mVideoSample = nullptr; }
D3D11TextureIMFSampleImage::D3D11TextureIMFSampleImage(
IMFSample* aVideoSample, ID3D11Texture2D* aTexture, uint32_t aArrayIndex,
const gfx::IntSize& aSize, const gfx::IntRect& aRect,
gfx::YUVColorSpace aColorSpace, gfx::ColorRange aColorRange)
gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange)
: Image(nullptr, ImageFormat::D3D11_TEXTURE_IMF_SAMPLE),
mVideoSample(IMFSampleWrapper::Create(aVideoSample)),
mTexture(aTexture),
mArrayIndex(aArrayIndex),
mSize(aSize),
mPictureRect(aRect),
mYUVColorSpace(aColorSpace),
mColorSpace(aColorSpace),
mColorRange(aColorRange) {
MOZ_ASSERT(XRE_IsGPUProcess());
}
@ -50,7 +50,7 @@ D3D11TextureIMFSampleImage::D3D11TextureIMFSampleImage(
void D3D11TextureIMFSampleImage::AllocateTextureClient(
KnowsCompositor* aKnowsCompositor, RefPtr<IMFSampleUsageInfo> aUsageInfo) {
mTextureClient = D3D11TextureData::CreateTextureClient(
mTexture, mArrayIndex, mSize, gfx::SurfaceFormat::NV12, mYUVColorSpace,
mTexture, mArrayIndex, mSize, gfx::SurfaceFormat::NV12, mColorSpace,
mColorRange, aKnowsCompositor, aUsageInfo);
MOZ_ASSERT(mTextureClient);
}

View File

@ -59,7 +59,7 @@ class D3D11TextureIMFSampleImage final : public Image {
D3D11TextureIMFSampleImage(IMFSample* aVideoSample, ID3D11Texture2D* aTexture,
uint32_t aArrayIndex, const gfx::IntSize& aSize,
const gfx::IntRect& aRect,
gfx::YUVColorSpace aColorSpace,
gfx::ColorSpace2 aColorSpace,
gfx::ColorRange aColorRange);
virtual ~D3D11TextureIMFSampleImage() = default;
@ -74,7 +74,6 @@ class D3D11TextureIMFSampleImage final : public Image {
ID3D11Texture2D* GetTexture() const;
RefPtr<IMFSampleWrapper> GetIMFSampleWrapper();
gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; }
gfx::ColorRange GetColorRange() const { return mColorRange; }
private:
@ -90,11 +89,15 @@ class D3D11TextureIMFSampleImage final : public Image {
// IMFTransform.
RefPtr<IMFSampleWrapper> mVideoSample;
RefPtr<ID3D11Texture2D> mTexture;
public:
const uint32_t mArrayIndex;
const gfx::IntSize mSize;
const gfx::IntRect mPictureRect;
const gfx::YUVColorSpace mYUVColorSpace;
const gfx::ColorSpace2 mColorSpace;
const gfx::ColorRange mColorRange;
private:
RefPtr<TextureClient> mTextureClient;
};

View File

@ -82,7 +82,7 @@ already_AddRefed<IDirect3DSurface9> DXGID3D9TextureData::GetD3D9Surface()
bool DXGID3D9TextureData::Serialize(SurfaceDescriptor& aOutDescriptor) {
SurfaceDescriptorD3D10 desc(
(WindowsHandle)(mHandle), /* gpuProcessTextureId */ Nothing(),
/* arrayIndex */ 0, mFormat, GetSize(), gfx::YUVColorSpace::Identity,
/* arrayIndex */ 0, mFormat, GetSize(), gfx::ColorSpace2::SRGB,
gfx::ColorRange::FULL);
// In reality, with D3D9 we will only ever deal with RGBA textures.
bool isYUV = mFormat == gfx::SurfaceFormat::NV12 ||
@ -91,7 +91,7 @@ bool DXGID3D9TextureData::Serialize(SurfaceDescriptor& aOutDescriptor) {
if (isYUV) {
gfxCriticalError() << "Unexpected YUV format for DXGID3D9TextureData: "
<< mFormat;
desc.yUVColorSpace() = gfx::YUVColorSpace::BT601;
desc.colorSpace() = gfx::ColorSpace2::BT601_525;
desc.colorRange() = gfx::ColorRange::LIMITED;
}
aOutDescriptor = desc;

View File

@ -8,6 +8,7 @@
#define mozilla_gfx_layers_d3d11_HelpersD3D11_h
#include <d3d11.h>
#include <array>
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
@ -50,6 +51,16 @@ static inline bool WaitForFrameGPUQuery(ID3D11Device* aDevice,
return success;
}
inline void ClearResource(ID3D11Device* const device, ID3D11Resource* const res,
const std::array<float,4>& vals) {
RefPtr<ID3D11RenderTargetView> rtv;
(void)device->CreateRenderTargetView(res, nullptr, getter_AddRefs(rtv));
RefPtr<ID3D11DeviceContext> context;
device->GetImmediateContext(getter_AddRefs(context));
context->ClearRenderTargetView(rtv, vals.data());
}
} // namespace layers
} // namespace mozilla

View File

@ -402,7 +402,7 @@ bool D3D11TextureData::SerializeSpecific(
}
*aOutDesc = SurfaceDescriptorD3D10((WindowsHandle)sharedHandle,
mGpuProcessTextureId, mArrayIndex, mFormat,
mSize, mYUVColorSpace, mColorRange);
mSize, mColorSpace, mColorRange);
return true;
}
@ -425,13 +425,13 @@ void D3D11TextureData::GetSubDescriptor(
/* static */
already_AddRefed<TextureClient> D3D11TextureData::CreateTextureClient(
ID3D11Texture2D* aTexture, uint32_t aIndex, gfx::IntSize aSize,
gfx::SurfaceFormat aFormat, gfx::YUVColorSpace aColorSpace,
gfx::SurfaceFormat aFormat, gfx::ColorSpace2 aColorSpace,
gfx::ColorRange aColorRange, KnowsCompositor* aKnowsCompositor,
RefPtr<IMFSampleUsageInfo> aUsageInfo) {
D3D11TextureData* data = new D3D11TextureData(
aTexture, aIndex, aSize, aFormat,
TextureAllocationFlags::ALLOC_MANUAL_SYNCHRONIZATION);
data->SetYUVColorSpace(aColorSpace);
data->mColorSpace = aColorSpace;
data->SetColorRange(aColorRange);
RefPtr<TextureClient> textureClient = MakeAndAddRef<TextureClient>(
@ -812,7 +812,7 @@ DXGITextureHostD3D11::DXGITextureHostD3D11(
mSize(aDescriptor.size()),
mHandle(aDescriptor.handle()),
mFormat(aDescriptor.format()),
mYUVColorSpace(aDescriptor.yUVColorSpace()),
mColorSpace(aDescriptor.colorSpace()),
mColorRange(aDescriptor.colorRange()),
mIsLocked(false) {}
@ -964,7 +964,7 @@ void DXGITextureHostD3D11::UnlockInternal() {
void DXGITextureHostD3D11::CreateRenderTexture(
const wr::ExternalImageId& aExternalImageId) {
RefPtr<wr::RenderTextureHost> texture = new wr::RenderDXGITextureHost(
mHandle, mGpuProcessTextureId, mArrayIndex, mFormat, mYUVColorSpace,
mHandle, mGpuProcessTextureId, mArrayIndex, mFormat, mColorSpace,
mColorRange, mSize);
wr::RenderThread::Get()->RegisterExternalImage(aExternalImageId,
texture.forget());
@ -1092,7 +1092,7 @@ void DXGITextureHostD3D11::PushDisplayItems(
aBounds, aClip, true, aImageKeys[0], aImageKeys[1],
GetFormat() == gfx::SurfaceFormat::NV12 ? wr::ColorDepth::Color8
: wr::ColorDepth::Color16,
wr::ToWrYuvColorSpace(mYUVColorSpace),
wr::ToWrYuvColorSpace(ToYUVColorSpace(mColorSpace)),
wr::ToWrColorRange(mColorRange), aFilter, preferCompositorSurface,
SupportsExternalCompositing(aBuilder.GetBackendType()));
break;
@ -1109,8 +1109,7 @@ bool DXGITextureHostD3D11::SupportsExternalCompositing(
return true;
}
// XXX Add P010 and P016 support.
if (GetFormat() == gfx::SurfaceFormat::NV12 &&
gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()) {
if (gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()) {
return true;
}
return false;

View File

@ -60,7 +60,7 @@ class D3D11TextureData final : public TextureData {
static already_AddRefed<TextureClient> CreateTextureClient(
ID3D11Texture2D* aTexture, uint32_t aIndex, gfx::IntSize aSize,
gfx::SurfaceFormat aFormat, gfx::YUVColorSpace aColorSpace,
gfx::SurfaceFormat aFormat, gfx::ColorSpace2 aColorSpace,
gfx::ColorRange aColorRange, KnowsCompositor* aKnowsCompositor,
RefPtr<IMFSampleUsageInfo> aUsageInfo);
@ -95,10 +95,6 @@ class D3D11TextureData final : public TextureData {
bool Serialize(SurfaceDescriptor& aOutDescrptor) override;
void GetSubDescriptor(RemoteDecoderVideoSubDescriptor* aOutDesc) override;
gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; }
void SetYUVColorSpace(gfx::YUVColorSpace aColorSpace) {
mYUVColorSpace = aColorSpace;
}
gfx::ColorRange GetColorRange() const { return mColorRange; }
void SetColorRange(gfx::ColorRange aColorRange) { mColorRange = aColorRange; }
@ -136,7 +132,11 @@ class D3D11TextureData final : public TextureData {
RefPtr<gfx::DrawTarget> mDrawTarget;
const gfx::IntSize mSize;
const gfx::SurfaceFormat mFormat;
gfx::YUVColorSpace mYUVColorSpace = gfx::YUVColorSpace::Identity;
public:
gfx::ColorSpace2 mColorSpace = gfx::ColorSpace2::SRGB;
private:
gfx::ColorRange mColorRange = gfx::ColorRange::LIMITED;
bool mNeedsClear = false;
const bool mHasSynchronization;
@ -349,9 +349,6 @@ class DXGITextureHostD3D11 : public TextureHost {
void UnlockWithoutCompositor() override;
gfx::IntSize GetSize() const override { return mSize; }
gfx::YUVColorSpace GetYUVColorSpace() const override {
return mYUVColorSpace;
}
gfx::ColorRange GetColorRange() const override { return mColorRange; }
already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override;
@ -392,7 +389,11 @@ class DXGITextureHostD3D11 : public TextureHost {
gfx::IntSize mSize;
WindowsHandle mHandle;
gfx::SurfaceFormat mFormat;
const gfx::YUVColorSpace mYUVColorSpace;
public:
const gfx::ColorSpace2 mColorSpace;
protected:
const gfx::ColorRange mColorRange;
bool mIsLocked;
};

View File

@ -34,7 +34,7 @@ namespace layers {
uint32_t arrayIndex;
SurfaceFormat format;
IntSize size;
YUVColorSpace yUVColorSpace;
ColorSpace2 colorSpace;
ColorRange colorRange;
};

File diff suppressed because it is too large Load Diff

View File

@ -7,11 +7,14 @@
#ifndef MOZILLA_GFX_DCLAYER_TREE_H
#define MOZILLA_GFX_DCLAYER_TREE_H
#include "WinUtils.h"
#include <DXGIType.h>
#include <dxgiformat.h>
#include <unordered_map>
#include <vector>
#include <windows.h>
#include "Colorspaces.h"
#include "GLTypes.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/layers/OverlayInfo.h"
@ -34,12 +37,14 @@ struct IDCompositionVisual2;
struct IDXGIDecodeSwapChain;
struct IDXGIResource;
struct IDXGISwapChain1;
struct IDXGISwapChain3;
struct IDCompositionVirtualSurface;
namespace mozilla {
namespace gl {
class GLContext;
class Texture;
}
namespace wr {
@ -50,8 +55,9 @@ namespace wr {
class DCTile;
class DCSurface;
class DCSurfaceVideo;
class DCSurfaceSwapChain;
class RenderTextureHost;
class RenderDXGITextureHost;
struct GpuOverlayInfo {
bool mSupportsOverlays = false;
@ -119,8 +125,8 @@ class DCLayerTree {
ID3D11VideoProcessorEnumerator* GetVideoProcessorEnumerator() const {
return mVideoProcessorEnumerator;
}
bool EnsureVideoProcessor(const gfx::IntSize& aInputSize,
const gfx::IntSize& aOutputSize);
bool EnsureVideoProcessorAtLeast(const gfx::IntSize& aInputSize,
const gfx::IntSize& aOutputSize);
DCSurface* GetSurface(wr::NativeSurfaceId aId) const;
@ -247,7 +253,7 @@ class DCSurface {
void UpdateAllocatedRect();
void DirtyAllocatedRect();
virtual DCSurfaceVideo* AsDCSurfaceVideo() { return nullptr; }
virtual DCSurfaceSwapChain* AsDCSurfaceSwapChain() { return nullptr; }
protected:
DCLayerTree* mDCLayerTree;
@ -272,33 +278,87 @@ class DCSurface {
RefPtr<IDCompositionVirtualSurface> mVirtualSurface;
};
class DCSurfaceVideo : public DCSurface {
struct RaiiHANDLE final {
const HANDLE val;
explicit RaiiHANDLE(HANDLE val) : val(val) {}
operator HANDLE() const { return val; }
~RaiiHANDLE() {
if (val) {
::CloseHandle(val);
}
}
};
struct CspaceAndRange final {
gfx::ColorSpace2 space;
Maybe<gfx::ColorRange> yuvRange;
auto Members() const { return std::tie(space, yuvRange); }
INLINE_DERIVE_MEMBERS_EQ(CspaceAndRange);
};
struct CspaceTransformPlan final {
struct WithVideoProcessor final {
DXGI_COLOR_SPACE_TYPE srcSpace;
Maybe<DXGI_COLOR_SPACE_TYPE> dstYuvSpace;
DXGI_COLOR_SPACE_TYPE dstRgbSpace;
};
struct WithGLBlitHelper final {
color::ColorspaceDesc srcSpace;
color::ColorspaceDesc dstSpace;
DXGI_COLOR_SPACE_TYPE dstDxgiSpace;
DXGI_FORMAT dstDxgiFormat;
};
Maybe<WithVideoProcessor> videoProcessor;
Maybe<WithGLBlitHelper> blitHelper;
};
class DCSurfaceSwapChain : public DCSurface {
public:
DCSurfaceVideo(bool aIsOpaque, DCLayerTree* aDCLayerTree);
DCSurfaceSwapChain(bool aIsOpaque, DCLayerTree* aDCLayerTree);
void AttachExternalImage(wr::ExternalImageId aExternalImage);
bool CalculateSwapChainSize(gfx::Matrix& aTransform);
void PresentVideo();
Maybe<gfx::Matrix> EnsurePresented(const gfx::Matrix&);
DCSurfaceVideo* AsDCSurfaceVideo() override { return this; }
DCSurfaceSwapChain* AsDCSurfaceSwapChain() override { return this; }
struct Src final {
RefPtr<RenderDXGITextureHost> texture;
gfx::IntSize size;
gfx::SurfaceFormat format;
CspaceAndRange space;
// When RenderTextureHost, swapChainSize or VideoSwapChain are updated,
// then DCSurfaceSwapChain::Present() needs to be called.
bool needsPresent = true;
};
struct Dest final {
RefPtr<IDXGISwapChain3> swapChain; // Destination
UniquePtr<RaiiHANDLE> swapChainSurfaceHandle;
gfx::IntSize size;
DXGI_FORMAT format;
DXGI_COLOR_SPACE_TYPE space;
};
struct PlanAndDest final {
CspaceAndRange srcSpace;
CspaceTransformPlan plan;
Maybe<Dest> dest;
mutable std::shared_ptr<gl::Texture> lut;
bool needsPresent = true;
};
protected:
DXGI_FORMAT GetSwapChainFormat();
bool CreateVideoSwapChain();
bool CallVideoProcessorBlt();
void ReleaseDecodeSwapChainResources();
bool CallVideoProcessorBlt() const;
bool CallBlitHelper() const;
RefPtr<ID3D11VideoProcessorOutputView> mOutputView;
RefPtr<IDXGIResource> mDecodeResource;
RefPtr<IDXGISwapChain1> mVideoSwapChain;
RefPtr<IDXGIDecodeSwapChain> mDecodeSwapChain;
HANDLE mSwapChainSurfaceHandle = 0;
gfx::IntSize mVideoSize;
gfx::IntSize mSwapChainSize;
DXGI_FORMAT mSwapChainFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
bool mFailedYuvSwapChain = false;
RefPtr<RenderTextureHost> mRenderTextureHost;
RefPtr<RenderTextureHost> mPrevTexture;
// -
Maybe<DXGI_FORMAT> mOverlayFormat;
Maybe<Src> mSrc;
Maybe<PlanAndDest> mDest;
};
class DCTile {

View File

@ -20,7 +20,7 @@ namespace wr {
RenderDXGITextureHost::RenderDXGITextureHost(
WindowsHandle aHandle, Maybe<uint64_t>& aGpuProcessTextureId,
uint32_t aArrayIndex, gfx::SurfaceFormat aFormat,
gfx::YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange,
gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange,
gfx::IntSize aSize)
: mHandle(aHandle),
mGpuProcessTextureId(aGpuProcessTextureId),
@ -29,7 +29,7 @@ RenderDXGITextureHost::RenderDXGITextureHost(
mStream(0),
mTextureHandle{0},
mFormat(aFormat),
mYUVColorSpace(aYUVColorSpace),
mColorSpace(aColorSpace),
mColorRange(aColorRange),
mSize(aSize),
mLocked(false) {

View File

@ -22,7 +22,7 @@ class RenderDXGITextureHost final : public RenderTextureHostSWGL {
RenderDXGITextureHost(WindowsHandle aHandle,
Maybe<uint64_t>& aGpuProcessTextureId,
uint32_t aArrayIndex, gfx::SurfaceFormat aFormat,
gfx::YUVColorSpace aYUVColorSpace,
gfx::ColorSpace2,
gfx::ColorRange aColorRange, gfx::IntSize aSize);
wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
@ -58,7 +58,7 @@ class RenderDXGITextureHost final : public RenderTextureHostSWGL {
PlaneInfo& aPlaneInfo) override;
void UnmapPlanes() override;
gfx::YUVRangedColorSpace GetYUVColorSpace() const override {
return ToYUVRangedColorSpace(mYUVColorSpace, GetColorRange());
return ToYUVRangedColorSpace(ToYUVColorSpace(mColorSpace), mColorRange);
}
bool EnsureD3D11Texture2D(ID3D11Device* aDevice);
@ -108,11 +108,13 @@ class RenderDXGITextureHost final : public RenderTextureHostSWGL {
// handles for Y and CbCr data.
GLuint mTextureHandle[2];
public:
const gfx::SurfaceFormat mFormat;
const gfx::YUVColorSpace mYUVColorSpace;
const gfx::ColorSpace2 mColorSpace;
const gfx::ColorRange mColorRange;
const gfx::IntSize mSize;
private:
bool mLocked;
};

View File

@ -5232,6 +5232,37 @@
value: false
mirror: once
- name: gfx.blithelper.precision
type: RelaxedAtomicUint32
value: 2 # { 0: lowp, 1: mediump, 2: highp }
mirror: always
- name: gfx.blithelper.lut-size.rgb.b
type: RelaxedAtomicUint32
value: 15
mirror: always
- name: gfx.blithelper.lut-size.rgb.g
type: RelaxedAtomicUint32
value: 31
mirror: always
- name: gfx.blithelper.lut-size.rgb.r
type: RelaxedAtomicUint32
value: 31
mirror: always
- name: gfx.blithelper.lut-size.ycbcr.cb
type: RelaxedAtomicUint32
value: 15
mirror: always
- name: gfx.blithelper.lut-size.ycbcr.cr
type: RelaxedAtomicUint32
value: 31
mirror: always
- name: gfx.blithelper.lut-size.ycbcr.y
type: RelaxedAtomicUint32
value: 31
mirror: always
# Nb: we ignore this pref on release and beta.
- name: gfx.blocklist.all
type: int32_t