Bug 1785925 - Add LUT and Colorspace support to GLBlitHelper. r=lsalzman,media-playback-reviewers,alwu

This code was originally developed in bug 1771374, but here we omit the
DCLayerTree changes for later.

Differential Revision: https://phabricator.services.mozilla.com/D155027
This commit is contained in:
Kelsey Gilbert 2022-09-06 18:36:34 +00:00
parent d002b86978
commit 24a9bc97bb
40 changed files with 2674 additions and 324 deletions

View File

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

View File

@ -898,10 +898,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.ignoreColorSpace) {
ret = gfx::ToColorSpace2(options.colorSpace);
}
return ret;
}
// -

View File

@ -247,7 +247,10 @@ 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);
ok &= ValidateParam(val.colorSpace);
return ok;
}
};

View File

@ -359,7 +359,7 @@ struct FloatOrInt final // For TexParameter[fi] and friends.
// -
struct WebGLContextOptions {
struct WebGLContextOptions final {
bool alpha = true;
bool depth = true;
bool stencil = false;
@ -372,8 +372,8 @@ struct WebGLContextOptions {
dom::WebGLPowerPreference powerPreference =
dom::WebGLPowerPreference::Default;
bool ignoreColorSpace = true;
dom::PredefinedColorSpace colorSpace = dom::PredefinedColorSpace::Srgb;
bool ignoreColorSpace = true; // Our legacy behavior.
bool shouldResistFingerprinting = true;
bool enableDebugRendererInfo = false;
@ -400,6 +400,8 @@ struct WebGLContextOptions {
// clang-format on
}
// -
WebGLContextOptions();
WebGLContextOptions(const WebGLContextOptions&) = default;

View File

@ -0,0 +1,324 @@
<!DOCTYPE html>
<html>
<!--
# color_quads.html
* The default is a 400x400 2d canvas, with 0, 16, 235, and 255 "gray" outer
quads, and 50%-red, -green, -blue, and -gray inner quads.
* We default to showing the settings pane when loaded without a query string.
This way, someone naively opens this in a browser, they can immediately see
all available options.
* The "Publish" button updates the url, and so causes the settings pane to
hide.
* Clicking on the canvas toggles the settings pane for further editing.
-->
<head>
<meta charset="utf-8">
<title>color_quads.html (2022-07-15)</title>
</head>
<body>
<div id="e_settings">
Image override: <input id="e_img" type="text">
<br>
<br>Canvas Width: <input id="e_width" type="text" value="400">
<br>Canvas Height: <input id="e_height" type="text" value="400">
<br>Canvas Colorspace: <input id="e_cspace" type="text">
<br>Canvas Context Type: <select id="e_context">
<option value="2d" selected="selected">Canvas2D</option>
<option value="webgl">WebGL</option>
</select>
<br>Canvas Context Options: <input id="e_options" type="text" value="{}">
<br>
<br>OuterTopLeft: <input id="e_color_o1" type="text" value="rgb(0,0,0)">
<br>OuterTopRight: <input id="e_color_o2" type="text" value="rgb(16,16,16)">
<br>OuterBottomLeft: <input id="e_color_o3" type="text" value="rgb(235,235,235)">
<br>OuterBottomRight: <input id="e_color_o4" type="text" value="rgb(255,255,255)">
<br>
<br>InnerTopLeft: <input id="e_color_i1" type="text" value="rgb(127,0,0)">
<br>InnerTopRight: <input id="e_color_i2" type="text" value="rgb(0,127,0)">
<br>InnerBottomLeft: <input id="e_color_i3" type="text" value="rgb(0,0,127)">
<br>InnerBottomRight: <input id="e_color_i4" type="text" value="rgb(127,127,127)">
<br><input id="e_publish" type="button" value="Publish">
<hr>
</div>
<div id="e_canvas_holder">
<canvas></canvas>
</div>
<script>
"use strict";
// document.body.style.backgroundColor = '#fdf';
// -
// Click the canvas to toggle the settings pane.
e_canvas_holder.addEventListener("click", () => {
// Toggle display:none to hide/unhide.
e_settings.style.display = e_settings.style.display ? "" : "none";
});
// Hide settings initially if there's a query string in the url.
if (window.location.search.startsWith("?")) {
e_settings.style.display = "none";
}
// -
function map(obj, fn) {
fn = fn || (x => x);
const ret = {};
for (const [k,v] of Object.entries(obj)) {
ret[k] = fn(v, k);
}
return ret;
}
function map_keys_required(obj, keys, fn) {
fn = fn || (x => x);
const ret = {};
for (const k of keys) {
const v = obj[k];
if (v === undefined) throw {k, obj};
ret[k] = fn(v, k);
}
return ret;
}
function set_device_pixel_size(e, device_size) {
const DPR = window.devicePixelRatio;
map_keys_required(device_size, ['width', 'height'], (device, k) => {
const css = device / DPR;
e.style[k] = css + 'px';
});
}
function pad_top_left_to_device_pixels(e) {
const DPR = window.devicePixelRatio;
e.style.padding = '';
let css_rect = e.getBoundingClientRect();
css_rect = map_keys_required(css_rect, ['left', 'top']);
const orig_device_rect = {};
const snapped_padding = map(css_rect, (css, k) => {
const device = orig_device_rect[k] = css * DPR;
const device_snapped = Math.round(device);
let device_padding = device_snapped - device;
// Negative padding is treated as 0.
// We want to pad:
// * 3.9 -> 4.0
// * 3.1 -> 4.0
// * 3.00000001 -> 3.0
if (device_padding < 0.01) {
device_padding += 1;
}
const css_padding = device_padding / DPR;
// console.log({css, k, device, device_snapped, device_padding, css_padding});
return css_padding;
});
e.style.paddingLeft = snapped_padding.left + 'px';
e.style.paddingTop = snapped_padding.top + 'px';
console.log(`[info] At dpr=${DPR}, padding`, css_rect, '(', orig_device_rect, 'device) by', snapped_padding);
}
// -
const SETTING_NODES = {};
e_settings.childNodes.forEach(n => {
if (!n.id) return;
SETTING_NODES[n.id] = n;
n._default = n.value;
});
const URL_PARAMS = new URLSearchParams(window.location.search);
URL_PARAMS.forEach((v,k) => {
const n = SETTING_NODES[k];
if (!n) {
if (k && !k.startsWith('__')) {
console.warn(`Unrecognized setting: ${k} = ${v}`);
}
return;
}
n.value = v;
});
// -
function UNITTEST_STR_EQ(was, expected) {
function to_result(src) {
let result = src;
if (typeof(result) == 'string') {
result = eval(result);
}
let result_str = result.toString();
if (result instanceof Array) {
result_str = '[' + result_str + ']';
}
return {src, result, result_str};
}
was = to_result(was);
expected = to_result(expected);
if (false) {
if (was.result_str != expected.result_str) {
throw {was, expected};
}
console.log(`[unittest] OK `, was.src, ` -> ${was.result_str} (`, expected.src, `)`);
}
console.assert(was.result_str == expected.result_str,
was.src, ` -> ${was.result_str} (`, expected.src, `)`);
}
// -
/// Non-Premult-Alpha, e.g. [1.0, 1.0, 1.0, 0.5]
function parse_css_color_npa(str) {
const m = /(rgba?)\((.*)\)/.exec(str);
if (!m) throw str;
let vals = m[2];
vals = vals.split(',').map(s => parseFloat(s));
if (vals.length == 3) {
vals.push(1.0);
}
for (let i = 0; i < 3; i++) {
vals[i] /= 255;
}
return vals;
}
UNITTEST_STR_EQ(`parse_css_color_npa('rgb(255,255,255)');`, [1,1,1,1]);
UNITTEST_STR_EQ(`parse_css_color_npa('rgba(255,255,255)');`, [1,1,1,1]);
UNITTEST_STR_EQ(`parse_css_color_npa('rgb(20,40,60)');`, '[20/255, 40/255, 60/255, 1]');
UNITTEST_STR_EQ(`parse_css_color_npa('rgb(20,40,60,0.5)');`, '[20/255, 40/255, 60/255, 0.5]');
UNITTEST_STR_EQ(`parse_css_color_npa('rgb(20,40,60,0)');`, '[20/255, 40/255, 60/255, 0]');
// -
let e_canvas;
async function draw() {
while (e_canvas_holder.firstChild) {
e_canvas_holder.removeChild(e_canvas_holder.firstChild);
}
if (e_img.value) {
const img = document.createElement("img");
img.src = e_img.value;
console.log('img.src =', img.src);
await img.decode();
e_canvas_holder.appendChild(img);
set_device_pixel_size(img, {width: img.naturalWidth, height: img.naturalHeight});
pad_top_left_to_device_pixels(img);
return;
}
e_canvas = document.createElement("canvas");
let options = eval(`Object.assign(${e_options.value})`);
options.colorSpace = e_cspace.value || undefined;
const context = e_canvas.getContext(e_context.value, options);
if (context.drawingBufferColorSpace && options.colorSpace) {
context.drawingBufferColorSpace = options.colorSpace;
}
if (context.getContextAttributes) {
options = context.getContextAttributes();
}
console.log({options});
// -
const W = parseInt(e_width.value);
const H = parseInt(e_height.value);
context.canvas.width = W;
context.canvas.height = H;
e_canvas_holder.appendChild(e_canvas);
// If we don't snap to the device pixel grid, borders between color blocks
// will be filtered, and this causes a lot of fuzzy() annotations.
set_device_pixel_size(e_canvas, e_canvas);
pad_top_left_to_device_pixels(e_canvas);
// -
let fillFromElem;
if (context.fillRect) {
const c2d = context;
fillFromElem = (e, left, top, w, h) => {
if (!e.value) return;
c2d.fillStyle = e.value;
c2d.fillRect(left, top, w, h);
};
} else if (context.drawArrays) {
const gl = context;
gl.enable(gl.SCISSOR_TEST);
gl.disable(gl.DEPTH_TEST);
fillFromElem = (e, left, top, w, h) => {
if (!e.value) return;
const rgba = parse_css_color_npa(e.value.trim());
if (false && options.premultipliedAlpha) {
for (let i = 0; i < 3; i++) {
rgba[i] *= rgba[3];
}
}
const bottom = top+h; // in y-down c2d coords
gl.scissor(left, gl.drawingBufferHeight - bottom, w, h);
gl.clearColor(...rgba);
gl.clear(gl.COLOR_BUFFER_BIT);
};
}
// -
const LEFT_HALF = W/2 | 0; // Round
const TOP_HALF = H/2 | 0;
fillFromElem(e_color_o1, 0 , 0 , LEFT_HALF, TOP_HALF);
fillFromElem(e_color_o2, LEFT_HALF, 0 , W-LEFT_HALF, TOP_HALF);
fillFromElem(e_color_o3, 0 , TOP_HALF, LEFT_HALF, H-TOP_HALF);
fillFromElem(e_color_o4, LEFT_HALF, TOP_HALF, W-LEFT_HALF, H-TOP_HALF);
// -
const INNER_SCALE = 1/4;
const W_INNER = W*INNER_SCALE | 0;
const H_INNER = H*INNER_SCALE | 0;
fillFromElem(e_color_i1, LEFT_HALF-W_INNER, TOP_HALF-H_INNER, W_INNER, H_INNER);
fillFromElem(e_color_i2, LEFT_HALF , TOP_HALF-H_INNER, W_INNER, H_INNER);
fillFromElem(e_color_i3, LEFT_HALF-W_INNER, TOP_HALF , W_INNER, H_INNER);
fillFromElem(e_color_i4, LEFT_HALF , TOP_HALF , W_INNER, H_INNER);
}
draw();
// -
Object.values(SETTING_NODES).forEach(x => {
x.addEventListener("change", draw);
});
e_publish.addEventListener("click", () => {
let settings = [];
for (const n of Object.values(SETTING_NODES)) {
if (n.value == n._default) continue;
settings.push(`${n.id}=${n.value}`);
}
settings = settings.join("&");
if (!settings) {
settings = "="; // Empty key-value pair is "publish with default settings"
}
window.location.search = "?" + settings;
});
</script>
</body>
</html>

View File

@ -0,0 +1,19 @@
defaults pref(webgl.force-enabled,true)
# Android&&swgl doesn't support (snapshotting?) webgl, so skip there.
== color_quads.html?= color_quads.html?e_img=color_quads.png
skip-if(Android&&swgl) == color_quads.html?e_context=webgl color_quads.html?=
# Test odd width and height
== color_quads.html?e_width=401&e_height=401 color_quads.html?e_img=color_quads_401.png
skip-if(Android&&swgl) == color_quads.html?e_context=webgl&e_width=401&e_height=401 color_quads.html?e_width=401&e_height=401
# Test various alpha values for webgl.
skip-if(Android&&swgl) fuzzy-if(gtkWidget&&useDrawSnapshot,255-255,30000-30000) == color_quads.html?desc=premult-alpha_&e_context=webgl&e_color_o1=rgb(0,0,0,0.95)&e_color_o2=rgb(16,16,16,0.95)&e_color_o3=rgb(235,235,235,0.95)&e_color_o4=rgb(255,255,255,0.95)&e_color_i4=rgb(0,0,0,0) color_quads.html?e_color_o1=rgb(13,13,13)&e_color_o2=rgb(29,29,29)&e_color_o3=rgb(248,248,248)&e_color_i4=rgb(255,255,255)
skip-if(Android&&swgl) == color_quads.html?desc=no-alpha______&e_context=webgl&e_options={alpha:false}&e_color_o1=rgb(0,0,0,0.95)&e_color_o2=rgb(16,16,16,0.95)&e_color_o3=rgb(235,235,235,0.95)&e_color_o4=rgb(255,255,255,0.95)&e_color_i4=rgb(0,0,0,0) color_quads.html?e_color_i4=rgb(0,0,0)
skip-if(Android&&swgl) skip-if(swgl) skip-if(gtkWidget&&useDrawSnapshot) fuzzy-if(Android&&!swgl,255-255,120000-120000) == color_quads.html?desc=straight-alpha&e_context=webgl&e_options={premultipliedAlpha:false}&e_color_o1=rgb(0,0,0,0.95)&e_color_o2=rgb(16,16,16,0.95)&e_color_o3=rgb(235,235,235,0.95)&e_color_o4=rgb(255,255,255,0.95)&e_color_i4=rgb(0,0,0,0) color_quads.html?e_color_o1=rgb(13,13,13)&e_color_o2=rgb(28,28,28)&e_color_o3=rgb(236,236,236)&e_color_i4=rgb(255,255,255)
skip-if(Android&&swgl) skip-if(!swgl) == color_quads.html?desc=straight-alpha&e_context=webgl&e_options={premultipliedAlpha:false}&e_color_o1=rgb(0,0,0,0.95)&e_color_o2=rgb(16,16,16,0.95)&e_color_o3=rgb(235,235,235,0.95)&e_color_o4=rgb(255,255,255,0.95)&e_color_i4=rgb(0,0,0,0) color_quads.html?e_color_o1=rgb(14,14,14)&e_color_o2=rgb(30,30,30)&e_color_o3=rgb(237,237,237)&e_color_i1=rgb(128,1,1)&e_color_i2=rgb(1,128,1)&e_color_i3=rgb(1,1,128)&e_color_i4=rgb(255,255,255)
skip-if(Android&&swgl) skip-if(!(gtkWidget&&useDrawSnapshot)) == color_quads.html?desc=straight-alpha&e_context=webgl&e_options={premultipliedAlpha:false}&e_color_o1=rgb(0,0,0,0.95)&e_color_o2=rgb(16,16,16,0.95)&e_color_o3=rgb(235,235,235,0.95)&e_color_o4=rgb(255,255,255,0.95)&e_color_i4=rgb(0,0,0,0) color_quads.html?e_color_o1=rgb(13,13,13)&e_color_o2=rgb(29,29,29)&e_color_o3=rgb(237,237,237)&e_color_i4=rgb(255,255,255)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,5 +1,6 @@
# Canvas Filter Reftests
include filters/reftest.list
include color_quads.list
# WebGL Reftests!
defaults pref(webgl.force-enabled,true) skip-if(Android)
@ -216,8 +217,11 @@ fuzzy(0-9,0-40000) skip pref(webgl.prefer-16bpp,true) pref(webgl.force-layers-re
# Force native GL (Windows):
skip pref(webgl.disable-angle,true) == webgl-color-test.html?native-gl wrapper.html?colors-no-alpha.png
# ----------------------------------------------------------------------
# Non-WebGL Reftests!
# Do we correctly handle multiple clip paths?
!= clip-multiple-paths.html clip-multiple-paths-badref.html

View File

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

View File

@ -16,6 +16,7 @@
#include <iosfwd> // for ostream
#include <stddef.h>
#include <stdint.h>
#include <optional>
namespace mozilla {
namespace gfx {
@ -86,7 +87,7 @@ enum class SurfaceFormat : int8_t {
Depth,
// This represents the unknown format.
UNKNOWN,
UNKNOWN, // TODO: Replace uses with Maybe<SurfaceFormat>.
// The following values are endian-independent synonyms. The _UINT32 suffix
// indicates that the name reflects the layout when viewed as a uint32_t
@ -109,6 +110,115 @@ enum class SurfaceFormat : int8_t {
OS_RGBX = X8R8G8B8_UINT32
};
struct SurfaceFormatInfo {
bool hasColor;
bool hasAlpha;
bool isYuv;
std::optional<uint8_t> bytesPerPixel;
};
inline std::optional<SurfaceFormatInfo> Info(const SurfaceFormat aFormat) {
auto info = SurfaceFormatInfo{};
switch (aFormat) {
case SurfaceFormat::B8G8R8A8:
case SurfaceFormat::R8G8B8A8:
case SurfaceFormat::A8R8G8B8:
info.hasColor = true;
info.hasAlpha = true;
break;
case SurfaceFormat::B8G8R8X8:
case SurfaceFormat::R8G8B8X8:
case SurfaceFormat::X8R8G8B8:
case SurfaceFormat::R8G8B8:
case SurfaceFormat::B8G8R8:
case SurfaceFormat::R5G6B5_UINT16:
case SurfaceFormat::R8G8:
case SurfaceFormat::R16G16:
case SurfaceFormat::HSV:
case SurfaceFormat::Lab:
info.hasColor = true;
info.hasAlpha = false;
break;
case SurfaceFormat::A8:
case SurfaceFormat::A16:
info.hasColor = false;
info.hasAlpha = true;
break;
case SurfaceFormat::YUV:
case SurfaceFormat::NV12:
case SurfaceFormat::P016:
case SurfaceFormat::P010:
case SurfaceFormat::YUV422:
info.hasColor = true;
info.hasAlpha = false;
info.isYuv = true;
break;
case SurfaceFormat::Depth:
info.hasColor = false;
info.hasAlpha = false;
info.isYuv = false;
break;
case SurfaceFormat::UNKNOWN:
break;
}
// -
// bytesPerPixel
switch (aFormat) {
case SurfaceFormat::B8G8R8A8:
case SurfaceFormat::R8G8B8A8:
case SurfaceFormat::A8R8G8B8:
case SurfaceFormat::B8G8R8X8:
case SurfaceFormat::R8G8B8X8:
case SurfaceFormat::X8R8G8B8:
case SurfaceFormat::R16G16:
info.bytesPerPixel = 4;
break;
case SurfaceFormat::R8G8B8:
case SurfaceFormat::B8G8R8:
info.bytesPerPixel = 3;
break;
case SurfaceFormat::R5G6B5_UINT16:
case SurfaceFormat::R8G8:
case SurfaceFormat::A16:
case SurfaceFormat::Depth: // uint16_t
info.bytesPerPixel = 2;
break;
case SurfaceFormat::A8:
info.bytesPerPixel = 1;
break;
case SurfaceFormat::HSV:
case SurfaceFormat::Lab:
info.bytesPerPixel = 3 * sizeof(float);
break;
case SurfaceFormat::YUV:
case SurfaceFormat::NV12:
case SurfaceFormat::P016:
case SurfaceFormat::P010:
case SurfaceFormat::YUV422:
case SurfaceFormat::UNKNOWN:
break; // No bytesPerPixel per se.
}
// -
if (aFormat == SurfaceFormat::UNKNOWN) {
return {};
}
return info;
}
std::ostream& operator<<(std::ostream& aOut, const SurfaceFormat& aFormat);
// Represents the bit-shifts required to access color channels when the layout
@ -153,6 +263,7 @@ inline uint32_t operator>>(uint32_t a, SurfaceFormatBit b) {
}
static inline int BytesPerPixel(SurfaceFormat aFormat) {
// TODO: return Info(aFormat).value().bytesPerPixel.value();
switch (aFormat) {
case SurfaceFormat::A8:
return 1;
@ -173,6 +284,7 @@ static inline int BytesPerPixel(SurfaceFormat aFormat) {
}
inline bool IsOpaque(SurfaceFormat aFormat) {
// TODO: return Info(aFormat).value().hasAlpha;
switch (aFormat) {
case SurfaceFormat::B8G8R8X8:
case SurfaceFormat::R8G8B8X8:
@ -386,18 +498,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;

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

@ -0,0 +1,148 @@
/* 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

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

@ -0,0 +1,229 @@
/* 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

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

@ -0,0 +1,658 @@
/* 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 <cstdlib>
#include <optional>
#include <vector>
#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, int N>
auto abs(const avec<T, N>& a) {
auto ret = avec<T, N>{};
for (int i = 0; i < ret.N; i++) {
ret[i] = std::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() = default;
explicit constexpr mat(const std::array<avec<float, X_Cols>, Y_Rows>& rows) {
this->rows = rows;
}
template <int Y_Rows2, int X_Cols2>
explicit constexpr mat(const mat<Y_Rows2, X_Cols2>& m) {
*this = Identity();
for (int x = 0; x < std::min(X_Cols, X_Cols2); x++) {
for (int y = 0; y < std::min(Y_Rows, Y_Rows2); y++) {
at(x, y) = m.at(x, y);
}
}
}
const auto& at(const int x, const int y) const { return rows.at(y)[x]; }
auto& at(const int x, const int y) { return rows.at(y)[x]; }
friend auto operator*(const mat& a, const avec<float, X_Cols>& b_colvec) {
avec<float, Y_Rows> c_colvec;
for (int i = 0; i < y_rows; i++) {
c_colvec[i] = dot(a.rows.at(i), b_colvec);
}
return c_colvec;
}
friend auto operator*(const mat& a, const float b) {
mat c;
for (int x = 0; x < x_cols; x++) {
for (int y = 0; y < y_rows; y++) {
c.at(x, y) = a.at(x, y) * b;
}
}
return c;
}
friend auto operator/(const mat& a, const float b) { return a * (1 / b); }
template <int BCols, int BRows = X_Cols>
friend auto operator*(const mat& a, const mat<BRows, BCols>& b) {
const auto bt = transpose(b);
const auto& b_cols = bt.rows;
mat<Y_Rows, BCols> c;
for (int x = 0; x < BCols; x++) {
for (int y = 0; y < Y_Rows; y++) {
c.at(x, y) = dot(a.rows.at(y), b_cols.at(x));
}
}
return c;
}
};
using mat3 = mat<3, 3>;
using mat4 = mat<4, 4>;
inline float determinant(const mat<1, 1>& m) { return m.at(0, 0); }
template <class T>
float determinant(const T& m) {
static_assert(T::x_cols == T::y_rows);
float ret = 0;
for (int i = 0; i < T::x_cols; i++) {
const auto cofact = cofactor(m, i, 0);
ret += m.at(i, 0) * cofact;
}
return ret;
}
// -
template <class T>
float cofactor(const T& m, const int x_col, const int y_row) {
ASSERT(0 <= x_col && x_col < T::x_cols);
ASSERT(0 <= y_row && y_row < T::y_rows);
auto cofactor = minor_val(m, x_col, y_row);
if ((x_col + y_row) % 2 == 1) {
cofactor *= -1;
}
return cofactor;
}
// -
// Unfortunately, can't call this `minor(...)` because there is
// `#define minor(dev) gnu_dev_minor (dev)`
// in /usr/include/x86_64-linux-gnu/sys/sysmacros.h:62
template <class T>
float minor_val(const T& a, const int skip_x, const int skip_y) {
ASSERT(0 <= skip_x && skip_x < T::x_cols);
ASSERT(0 <= skip_y && skip_y < T::y_rows);
// A minor matrix is a matrix without its x_col and y_row.
mat<T::y_rows - 1, T::x_cols - 1> b;
int x_skips = 0;
for (int ax = 0; ax < T::x_cols; ax++) {
if (ax == skip_x) {
x_skips = 1;
continue;
}
int y_skips = 0;
for (int ay = 0; ay < T::y_rows; ay++) {
if (ay == skip_y) {
y_skips = 1;
continue;
}
b.at(ax - x_skips, ay - y_skips) = a.at(ax, ay);
}
}
const auto minor = determinant(b);
return minor;
}
// -
/// The matrix of cofactors.
template <class T>
auto comatrix(const T& a) {
auto b = T{};
for (int x = 0; x < T::x_cols; x++) {
for (int y = 0; y < T::y_rows; y++) {
b.at(x, y) = cofactor(a, x, y);
}
}
return b;
}
// -
template <class T>
auto transpose(const T& a) {
auto b = mat<T::x_cols, T::y_rows>{};
for (int x = 0; x < T::x_cols; x++) {
for (int y = 0; y < T::y_rows; y++) {
b.at(y, x) = a.at(x, y);
}
}
return b;
}
// -
template <class T>
inline T inverse(const T& a) {
const auto det = determinant(a);
const auto comat = comatrix(a);
const auto adjugate = transpose(comat);
const auto inv = adjugate / det;
return inv;
}
// -
template <class F>
void ForEachIntWithin(const ivec3 size, const F& f) {
ivec3 p;
for (p.z(0); p.z() < size.z(); p.z(p.z() + 1)) {
for (p.y(0); p.y() < size.y(); p.y(p.y() + 1)) {
for (p.x(0); p.x() < size.x(); p.x(p.x() + 1)) {
f(p);
}
}
}
}
template <class F>
void ForEachSampleWithin(const ivec3 size, const F& f) {
const auto div = vec3(size - 1);
ForEachIntWithin(size, [&](const ivec3& isrc) {
const auto fsrc = vec3(isrc) / div;
f(fsrc);
});
}
// -
struct Lut3 final {
ivec3 size;
std::vector<vec3> data;
// -
static Lut3 Create(const ivec3 size) {
Lut3 lut;
lut.size = size;
lut.data.resize(size.x() * size.y() * size.z());
return lut;
}
// -
/// p: [0, N-1] (clamps)
size_t Index(ivec3 p) const {
const auto scales = ivec3({1, size.x(), size.x() * size.y()});
p = max(ivec3(0), min(p, size - 1)); // clamp
return dot(p, scales);
}
// -
template <class F>
void SetMap(const F& dstFromSrc01) {
ForEachIntWithin(size, [&](const ivec3 p) {
const auto i = Index(p);
const auto src01 = vec3(p) / vec3(size - 1);
const auto dstVal = dstFromSrc01(src01);
data.at(i) = dstVal;
});
}
// -
/// p: [0, N-1] (clamps)
vec3 Fetch(ivec3 p) const {
const auto i = Index(p);
return data.at(i);
}
/// in01: [0.0, 1.0] (clamps)
vec3 Sample(vec3 in01) const;
};
// -
/**
Naively, it would be ideal to map directly from ycbcr to rgb,
but headroom and footroom are problematic: For e.g. narrow-range-8-bit,
our naive LUT would start at absolute y=0/255. However, values only start
at y=16/255, and depending on where your first LUT sample is, you might get
very poor approximations for y=16/255.
Further, even for full-range-8-bit, y=-0.5 is encoded as 1/255. U and v
aren't *as* important as y, but we should try be accurate for the min and
max values. Additionally, it would be embarassing to get whites/greys wrong,
so preserving u=0.0 should also be a goal.
Finally, when using non-linear transfer functions, the linear approximation of a
point between two samples will be fairly inaccurate.
We preserve min and max by choosing our input range such that min and max are
the endpoints of their LUT axis.
We preserve accuracy (at and around) mid by choosing odd sizes for dimentions.
But also, the LUT is surprisingly robust, so check if the simple version works
before adding complexity!
**/
struct ColorspaceTransform final {
ColorspaceDesc srcSpace;
ColorspaceDesc dstSpace;
mat4 srcRgbTfFromSrc;
std::optional<PiecewiseGammaDesc> srcTf;
mat3 dstRgbLinFromSrcRgbLin;
std::optional<PiecewiseGammaDesc> dstTf;
mat4 dstFromDstRgbTf;
static ColorspaceTransform Create(const ColorspaceDesc& src,
const ColorspaceDesc& dst);
// -
vec3 DstFromSrc(vec3 src) const;
std::optional<mat4> ToMat4() const;
Lut3 ToLut3(const ivec3 size) const;
Lut3 ToLut3() const {
auto defaultSize = ivec3({31, 31, 15}); // Order of importance: G, R, B
if (srcSpace.yuv) {
defaultSize = ivec3({31, 15, 31}); // Y, Cb, Cr
}
return ToLut3(defaultSize);
}
};
} // namespace mozilla::color
#undef ASSERT
#endif // MOZILLA_GFX_GL_COLORSPACES_H_

View File

@ -14,7 +14,9 @@
#include "ScopedGLHelpers.h"
#include "gfxUtils.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.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 +52,200 @@ 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.
// `extern` so that we don't get ifdef-dependent const-var-unused Werrors.
extern const char* const kFragSample_OnePlane_YUV_via_GBR = R"(
VARYING mediump vec2 vTexCoord0;
uniform PRECISION SAMPLER uTex0;
vec4 metaSample() {
vec4 yuva = TEXTURE(uTex0, vTexCoord0).gbra;
return yuva;
}
)";
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;
}
)";
// -
const char* const kFragMixin_AlphaMultColors = R"(
#define MIXIN_ALPHA_MULT_COLORS
)";
const char* const kFragMixin_AlphaClampColors = R"(
#define MIXIN_ALPHA_CLAMP_COLORS
)";
const char* const kFragMixin_AlphaOne = R"(
#define MIXIN_ALPHA_ONE
)";
// -
static const char kFragBody[] = R"(
void main(void) {
vec4 src = metaSample();
vec4 dst = vec4(metaConvert(src.rgb), src.a);
#ifdef MIXIN_ALPHA_MULT_COLORS
dst.rgb *= dst.a;
#endif
#ifdef MIXIN_ALPHA_CLAMP_COLORS
dst.rgb = min(dst.rgb, vec3(dst.a)); // Ensure valid premult-alpha colors.
#endif
#ifdef MIXIN_ALPHA_ONE
dst.a = 1.0;
#endif
FRAG_COLOR = dst;
}
)";
// --
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,17 +274,22 @@ 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)) {
MOZ_RELEASE_ASSERT(texUnits.size() >= 1);
GLenum texBinding;
switch (mTexTarget) {
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;
@ -234,21 +301,26 @@ ScopedSaveMultiTex::ScopedSaveMultiTex(GLContext* const gl,
MOZ_CRASH();
}
for (uint8_t i = 0; i < mTexCount; i++) {
mGL.fActiveTexture(LOCAL_GL_TEXTURE0 + i);
for (const auto i : IntegerRange(mTexUnits.size())) {
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);
// Unbind in reverse order, in case we have repeats.
// Order matters because we unbound samplers during ctor, so now we have to
// make sure we rebind them in the right order.
for (const auto i : Reversed(IntegerRange(mTexUnits.size()))) {
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]);
}
@ -380,11 +452,12 @@ 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;
MOZ_GL_ASSERT(gl, mLoc_uDestMatrix != -1);
MOZ_GL_ASSERT(gl, mLoc_uTexMatrix0 != -1);
MOZ_GL_ASSERT(gl, mLoc_uDestMatrix != -1); // Required
MOZ_GL_ASSERT(gl, mLoc_uTexMatrix0 != -1); // Required
if (mLoc_uColorMatrix != -1) {
MOZ_GL_ASSERT(gl, mLoc_uTexMatrix1 != -1);
@ -444,28 +517,35 @@ 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,
AssertedCast<GLint>(*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);
}
}
}
@ -551,11 +631,17 @@ 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);
}
@ -626,36 +712,60 @@ 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);
}
const auto PrintFragSource = [&]() {
printf_stderr("Frag source:\n");
int i = 0;
for (const auto& part : parts) {
printf_stderr("// parts[%i]:\n%s\n", i, part);
i += 1;
}
};
if (gfxEnv::MOZ_DUMP_GLBLITHELPER()) {
PrintFragSource();
}
mGL->fShaderSource(fs, AssertedCast<GLint>(parts.size()), parts.data(),
nullptr);
mGL->fCompileShader(fs);
const auto prog = mGL->fCreateProgram();
@ -699,11 +809,14 @@ 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;
PrintFragSource();
MOZ_CRASH("DrawBlitProg link failed");
}
// -----------------------------------------------------------------------------
@ -869,7 +982,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);
@ -899,7 +1013,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);
@ -961,7 +1076,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,
@ -1012,7 +1127,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;
}
@ -1062,7 +1177,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;
@ -1072,9 +1187,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);
@ -1087,7 +1205,7 @@ bool GLBlitHelper::BlitImage(MacIOSurface* const iosurf,
pixelFormat);
}
const char* fragBody;
const char* fragSample;
switch (planes) {
case 1:
switch (pixelFormat) {
@ -1099,11 +1217,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: {
@ -1114,19 +1232,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:
@ -1152,7 +1270,10 @@ 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;
}
@ -1180,10 +1301,14 @@ 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->fActiveTexture(LOCAL_GL_TEXTURE0);
mGL->fBindTexture(srcTarget, srcTex);
const bool yFlip = false;
@ -1341,28 +1466,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;
@ -1380,7 +1512,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;
@ -1398,5 +1531,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(int(StaticPrefs::gfx_blithelper_lut_size_ycbcr_y()));
lutSize.y(int(StaticPrefs::gfx_blithelper_lut_size_ycbcr_cb()));
lutSize.z(int(StaticPrefs::gfx_blithelper_lut_size_ycbcr_cr()));
} else {
lutSize.x(int(StaticPrefs::gfx_blithelper_lut_size_rgb_r()));
lutSize.y(int(StaticPrefs::gfx_blithelper_lut_size_rgb_g()));
lutSize.z(int(StaticPrefs::gfx_blithelper_lut_size_rgb_b()));
}
lutSize = max(minLutSize, min(lutSize, maxLutSize)); // Clamp
const auto lut = ct.ToLut3(lutSize);
const auto& size = lut.size;
// -
constexpr GLenum target = LOCAL_GL_TEXTURE_3D;
const auto bind = gl::ScopedBindTexture(&gl, strong->name, target);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_R, LOCAL_GL_CLAMP_TO_EDGE);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
gl.fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
bool useFloat16 = true;
if (useFloat16) {
// Use rgba16f, which we can thankfully upload as rgba32f
static_assert(sizeof(color::vec4) == sizeof(float) * 4);
std::vector<color::vec4> uploadData;
uploadData.reserve(lut.data.size());
for (const auto& src : lut.data) {
const auto dst = color::vec4{src, 1};
uploadData.push_back(dst);
}
gl.fTexStorage3D(target, 1, LOCAL_GL_RGBA16F, size.x(), size.y(),
size.z());
gl.fTexSubImage3D(target, 0, 0, 0, 0, size.x(), size.y(), size.z(),
LOCAL_GL_RGBA, LOCAL_GL_FLOAT, uploadData.data());
} else {
// Use Rgb10A2
std::vector<uint32_t> uploadData;
uploadData.reserve(lut.data.size());
for (const auto& src : lut.data) {
const auto dst = toRgb10A2({src, 1});
uploadData.push_back(dst);
}
gl.fTexStorage3D(target, 1, LOCAL_GL_RGB10_A2, size.x(), size.y(),
size.z());
gl.fTexSubImage3D(target, 0, 0, 0, 0, size.x(), size.y(), size.z(),
LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV,
uploadData.data());
}
}
return strong;
}
} // namespace gl
} // namespace mozilla

View File

@ -7,8 +7,12 @@
#ifndef GLBLITHELPER_H_
#define GLBLITHELPER_H_
#include <array>
#include <cstdint>
#include <map>
#include <memory>
#include <unordered_map>
#include "Colorspaces.h"
#include "GLConsts.h"
#include "GLContextTypes.h"
#include "GLTypes.h"
@ -78,8 +82,9 @@ class DMABUFSurfaceImage;
namespace gl {
class BindAnglePlanes;
class GLContext;
class GLBlitHelper;
class GLContext;
class Texture;
bool GuessDivisors(const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
gfx::IntSize* const out_divisors);
@ -90,10 +95,27 @@ struct Mat {
float& at(const uint8_t x, const uint8_t y) { return m[N * x + y]; }
static Mat<N> Zero();
static Mat<N> I();
static Mat<N> I() {
auto ret = Mat<N>{};
for (uint8_t i = 0; i < N; i++) {
ret.at(i, i) = 1.0f;
}
return ret;
}
Mat<N> operator*(const Mat<N>& r) const;
Mat<N> operator*(const Mat<N>& r) const {
Mat<N> ret;
for (uint8_t x = 0; x < N; x++) {
for (uint8_t y = 0; y < N; y++) {
float sum = 0.0f;
for (uint8_t i = 0; i < N; i++) {
sum += at(i, y) * r.at(x, i);
}
ret.at(x, y) = sum;
}
}
return ret;
}
};
typedef Mat<3> Mat3;
@ -108,17 +130,18 @@ class DrawBlitProg final {
const GLint mLoc_uDestMatrix;
const GLint mLoc_uTexMatrix0;
const GLint mLoc_uTexMatrix1;
const GLint mLoc_uColorLut;
const GLint mLoc_uColorMatrix;
GLenum mType_uColorMatrix = 0;
public:
struct Key final {
const char* const fragHeader;
const char* const fragBody;
const char* fragHeader = nullptr;
std::array<const char*, 4> 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,25 @@ 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;
extern const char* const kFragMixin_AlphaMultColors;
extern const char* const kFragMixin_AlphaClampColors;
extern const char* const kFragMixin_AlphaOne;
} // namespace gl
} // namespace mozilla

View File

@ -84,7 +84,16 @@ class BindAnglePlanes final {
const EGLAttrib* const* postAttribsList = nullptr)
: mParent(*parent),
mNumPlanes(numPlanes),
mMultiTex(mParent.mGL, mNumPlanes, LOCAL_GL_TEXTURE_EXTERNAL),
mMultiTex(
mParent.mGL,
[&]() {
std::vector<uint8_t> ret;
for (int i = 0; i < numPlanes; i++) {
ret.push_back(i);
}
return ret;
}(),
LOCAL_GL_TEXTURE_EXTERNAL),
mTempTexs{0},
mStreams{0},
mSuccess(true) {
@ -230,7 +239,7 @@ bool GLBlitHelper::BlitDescriptor(const layers::SurfaceDescriptorD3D10& desc,
const auto srcOrigin = OriginPos::BottomLeft;
const gfx::IntRect clipRect(0, 0, clipSize.width, clipSize.height);
const auto colorSpace = desc.yUVColorSpace();
const auto colorSpace = desc.colorSpace();
if (format != gfx::SurfaceFormat::NV12 &&
format != gfx::SurfaceFormat::P010 &&
@ -286,13 +295,30 @@ 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 +366,10 @@ bool GLBlitHelper::BlitAngleYCbCr(const WindowsHandle (&handleList)[3],
const DrawBlitProg::BaseArgs baseArgs = {SubRectMat3(clipRect, ySize), yFlip,
destSize, Nothing()};
const DrawBlitProg::YUVArgs yuvArgs = {
SubRectMat3(clipRect, uvSize, divisors), colorSpace};
SubRectMat3(clipRect, uvSize, divisors), Some(colorSpace)};
const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, kFragBody_PlanarYUV});
const auto& prog = GetDrawBlitProg(
{kFragHeader_TexExt, {kFragSample_ThreePlane, kFragConvert_ColorMatrix}});
prog->Draw(baseArgs, &yuvArgs);
return true;
}

View File

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

View File

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

View File

@ -0,0 +1,405 @@
/* -*- 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"
#include <limits>
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, {{int((235 + 16) / 2), 128, 128}}), (ivec3{127}));
EXPECT_EQ(Sample8From8(lut, {{int((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 = std::numeric_limits<double>::infinity();
double max = -std::numeric_limits<double>::infinity();
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;
}
double standardDeviation() const { return sqrt(variance); }
};
static Stats StatsForLutError(const ColorspaceTransform& ct,
const ivec3 srcQuants, const ivec3 dstQuants) {
const auto lut = ct.ToLut3();
const auto dstScale = vec3(dstQuants - 1);
std::vector<double> quantErrors;
quantErrors.reserve(srcQuants.x() * srcQuants.y() * srcQuants.z());
ForEachSampleWithin(srcQuants, [&](const vec3& src) {
const auto sampled = lut.Sample(src);
const auto actual = ct.DstFromSrc(src);
const auto isampled = ivec3(round(sampled * dstScale));
const auto iactual = ivec3(round(actual * dstScale));
const auto ierr = abs(isampled - iactual);
const auto quantError = dot(ierr, ivec3{1});
quantErrors.push_back(quantError);
if (quantErrors.size() % 100000 == 0) {
printf("%zu of %zu\n", quantErrors.size(), quantErrors.capacity());
}
});
const auto quantErrStats = Stats::For(quantErrors);
return quantErrStats;
}
TEST(Colorspaces, LutError_Rec709Full_Rec709Rgb)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Full8()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{},
};
const auto ct = ColorspaceTransform::Create(src, dst);
const auto stats = StatsForLutError(ct, ivec3{64}, ivec3{256});
EXPECT_NEAR(stats.mean, 0.000, 0.001);
EXPECT_NEAR(stats.standardDeviation(), 0.008, 0.001);
EXPECT_NEAR(stats.min, 0, 0.001);
EXPECT_NEAR(stats.max, 1, 0.001);
}
TEST(Colorspaces, LutError_Rec709Full_Srgb)
{
const auto src = ColorspaceDesc{
Chromaticities::Rec709(),
PiecewiseGammaDesc::Rec709(),
{{YuvLumaCoeffs::Rec709(), YcbcrDesc::Full8()}},
};
const auto dst = ColorspaceDesc{
Chromaticities::Srgb(),
PiecewiseGammaDesc::Srgb(),
{},
};
const auto ct = ColorspaceTransform::Create(src, dst);
const auto stats = StatsForLutError(ct, ivec3{64}, ivec3{256});
EXPECT_NEAR(stats.mean, 0.530, 0.001);
EXPECT_NEAR(stats.standardDeviation(), 1.674, 0.001);
EXPECT_NEAR(stats.min, 0, 0.001);
EXPECT_NEAR(stats.max, 17, 0.001);
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -80,10 +80,10 @@ already_AddRefed<IDirect3DSurface9> DXGID3D9TextureData::GetD3D9Surface()
}
bool DXGID3D9TextureData::Serialize(SurfaceDescriptor& aOutDescriptor) {
SurfaceDescriptorD3D10 desc(
(WindowsHandle)(mHandle), /* gpuProcessTextureId */ Nothing(),
/* arrayIndex */ 0, mFormat, GetSize(), gfx::YUVColorSpace::Identity,
gfx::ColorRange::FULL);
SurfaceDescriptorD3D10 desc((WindowsHandle)(mHandle),
/* gpuProcessTextureId */ Nothing(),
/* 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 ||
mFormat == gfx::SurfaceFormat::P010 ||
@ -91,7 +91,7 @@ bool DXGID3D9TextureData::Serialize(SurfaceDescriptor& aOutDescriptor) {
if (isYUV) {
gfxCriticalError() << "Unexpected YUV format for DXGID3D9TextureData: "
<< mFormat;
desc.yUVColorSpace() = gfx::YUVColorSpace::BT601;
desc.colorSpace() = gfx::ColorSpace2::BT601_525;
desc.colorRange() = gfx::ColorRange::LIMITED;
}
aOutDescriptor = desc;

View File

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

View File

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

View File

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

View File

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

View File

@ -90,6 +90,9 @@ class gfxEnv final {
// (conditioned on DebugDumpPainting()) is a good replacement.
DECL_GFX_ENV(MOZ_DUMP_COMPOSITOR_TEXTURES)
// Dump GLBlitHelper shader source text.
DECL_GFX_ENV(MOZ_DUMP_GLBLITHELPER)
// Paint dumping, only when MOZ_DUMP_PAINTING is defined.
DECL_GFX_ENV(MOZ_DUMP_PAINT)
DECL_GFX_ENV(MOZ_DUMP_PAINT_ITEMS)

View File

@ -24,6 +24,7 @@
#include "mozilla/gfx/CanvasManagerParent.h"
#include "mozilla/gfx/CanvasRenderThread.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/EnumTypeTraits.h"
#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_bidi.h"
@ -2067,6 +2068,14 @@ const mozilla::gfx::ContentDeviceData* gfxPlatform::GetInitContentDeviceData() {
return gContentDeviceInitData;
}
CMSMode GfxColorManagementMode() {
const auto mode = StaticPrefs::gfx_color_management_mode();
if (mode >= 0 && mode < UnderlyingValue(CMSMode::AllCount)) {
return CMSMode(mode);
}
return CMSMode::Off;
}
void gfxPlatform::InitializeCMS() {
if (gCMSInitialized) {
return;
@ -2085,12 +2094,7 @@ void gfxPlatform::InitializeCMS() {
return;
}
{
int32_t mode = StaticPrefs::gfx_color_management_mode();
if (mode >= 0 && mode < int32_t(CMSMode::AllCount)) {
gCMSMode = CMSMode(mode);
}
}
gCMSMode = GfxColorManagementMode();
gCMSsRGBProfile = qcms_profile_sRGB();

View File

@ -1025,4 +1025,6 @@ class gfxPlatform : public mozilla::layers::MemoryPressureListener {
const gfxSkipChars kEmptySkipChars;
};
CMSMode GfxColorManagementMode();
#endif /* GFX_PLATFORM_H */

View File

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

View File

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

View File

@ -12,19 +12,27 @@
namespace mozilla {
namespace wr {
GLenum ToGlTexFilter(const wr::ImageRendering aRendering) {
switch (aRendering) {
case wr::ImageRendering::Auto:
case wr::ImageRendering::CrispEdges:
return LOCAL_GL_LINEAR;
case wr::ImageRendering::Pixelated:
return LOCAL_GL_NEAREST;
case wr::ImageRendering::Sentinel:
break;
}
MOZ_CRASH("Bad wr::ImageRendering");
}
void ActivateBindAndTexParameteri(gl::GLContext* aGL, GLenum aActiveTexture,
GLenum aBindTarget, GLuint aBindTexture,
wr::ImageRendering aRendering) {
aGL->fActiveTexture(aActiveTexture);
aGL->fBindTexture(aBindTarget, aBindTexture);
aGL->fTexParameteri(aBindTarget, LOCAL_GL_TEXTURE_MIN_FILTER,
aRendering == wr::ImageRendering::Pixelated
? LOCAL_GL_NEAREST
: LOCAL_GL_LINEAR);
aGL->fTexParameteri(aBindTarget, LOCAL_GL_TEXTURE_MAG_FILTER,
aRendering == wr::ImageRendering::Pixelated
? LOCAL_GL_NEAREST
: LOCAL_GL_LINEAR);
const auto filter = ToGlTexFilter(aRendering);
aGL->fTexParameteri(aBindTarget, LOCAL_GL_TEXTURE_MIN_FILTER, filter);
aGL->fTexParameteri(aBindTarget, LOCAL_GL_TEXTURE_MAG_FILTER, filter);
}
RenderTextureHost::RenderTextureHost()

View File

@ -33,6 +33,8 @@ class RenderMacIOSurfaceTextureHost;
class RenderBufferTextureHost;
class RenderTextureHostSWGL;
GLenum ToGlTexFilter(wr::ImageRendering);
void ActivateBindAndTexParameteri(gl::GLContext* aGL, GLenum aActiveTexture,
GLenum aBindTarget, GLuint aBindTexture,
wr::ImageRendering aRendering);

View File

@ -5346,6 +5346,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