mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
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:
parent
84745515a4
commit
bb48a57a8a
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
// -
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
150
gfx/gl/AutoMappable.h
Normal 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
242
gfx/gl/Colorspaces.cpp
Normal 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
694
gfx/gl/Colorspaces.h
Normal 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_
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
404
gfx/gl/gtest/TestColorspaces.cpp
Normal file
404
gfx/gl/gtest/TestColorspaces.cpp
Normal 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
15
gfx/gl/gtest/moz.build
Normal 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"
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user