mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
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:
parent
d002b86978
commit
24a9bc97bb
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
// -
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
324
dom/canvas/test/reftest/color_quads.html
Normal file
324
dom/canvas/test/reftest/color_quads.html
Normal 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>
|
19
dom/canvas/test/reftest/color_quads.list
Normal file
19
dom/canvas/test/reftest/color_quads.list
Normal 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)
|
BIN
dom/canvas/test/reftest/color_quads.png
Normal file
BIN
dom/canvas/test/reftest/color_quads.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
BIN
dom/canvas/test/reftest/color_quads_401.png
Normal file
BIN
dom/canvas/test/reftest/color_quads_401.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
@ -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
|
||||
|
||||
|
@ -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();
|
||||
|
152
gfx/2d/Types.h
152
gfx/2d/Types.h
@ -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
148
gfx/gl/AutoMappable.h
Normal 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
229
gfx/gl/Colorspaces.cpp
Normal 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
658
gfx/gl/Colorspaces.h
Normal 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_
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ SharedSurface_ANGLEShareHandle::ToSurfaceDescriptor() {
|
||||
const auto format = gfx::SurfaceFormat::B8G8R8A8;
|
||||
return Some(layers::SurfaceDescriptorD3D10(
|
||||
(WindowsHandle)mShareHandle, /* gpuProcessTextureId */ Nothing(),
|
||||
/* arrayIndex */ 0, format, mDesc.size, gfx::YUVColorSpace::Identity,
|
||||
/* arrayIndex */ 0, format, mDesc.size, mDesc.colorSpace,
|
||||
gfx::ColorRange::FULL));
|
||||
}
|
||||
|
||||
|
@ -439,7 +439,7 @@ SharedSurface_D3D11Interop::ToSurfaceDescriptor() {
|
||||
const auto format = gfx::SurfaceFormat::B8G8R8A8;
|
||||
return Some(layers::SurfaceDescriptorD3D10(
|
||||
WindowsHandle(mData.dxgiHandle), /* gpuProcessTextureId */ Nothing(),
|
||||
/* arrayIndex */ 0, format, mDesc.size, gfx::YUVColorSpace::Identity,
|
||||
/* arrayIndex */ 0, format, mDesc.size, mDesc.colorSpace,
|
||||
gfx::ColorRange::FULL));
|
||||
}
|
||||
|
||||
|
405
gfx/gl/gtest/TestColorspaces.cpp
Normal file
405
gfx/gl/gtest/TestColorspaces.cpp
Normal 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
15
gfx/gl/gtest/moz.build
Normal file
@ -0,0 +1,15 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"/gfx/gl",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"TestColorspaces.cpp",
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = "xul-gtest"
|
@ -22,6 +22,8 @@ if CONFIG["MOZ_GL_PROVIDER"]:
|
||||
|
||||
EXPORTS += [
|
||||
"AndroidSurfaceTexture.h",
|
||||
"AutoMappable.h",
|
||||
"Colorspaces.h",
|
||||
"ForceDiscreteGPUHelperCGL.h",
|
||||
"GfxTexturesReporter.h",
|
||||
"GLBlitHelper.h",
|
||||
@ -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"
|
||||
|
@ -714,6 +714,12 @@ struct ParamTraits<mozilla::gfx::YUVRangedColorSpace>
|
||||
mozilla::gfx::YUVRangedColorSpace::_First,
|
||||
mozilla::gfx::YUVRangedColorSpace::_Last> {};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::gfx::ColorSpace2>
|
||||
: public ContiguousEnumSerializerInclusive<
|
||||
mozilla::gfx::ColorSpace2, mozilla::gfx::ColorSpace2::_First,
|
||||
mozilla::gfx::ColorSpace2::_Last> {};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::StereoMode>
|
||||
: public ContiguousEnumSerializer<mozilla::StereoMode,
|
||||
|
@ -25,19 +25,19 @@ using namespace gfx;
|
||||
|
||||
D3D11ShareHandleImage::D3D11ShareHandleImage(const gfx::IntSize& aSize,
|
||||
const gfx::IntRect& aRect,
|
||||
gfx::YUVColorSpace aColorSpace,
|
||||
gfx::ColorSpace2 aColorSpace,
|
||||
gfx::ColorRange aColorRange)
|
||||
: Image(nullptr, ImageFormat::D3D11_SHARE_HANDLE_TEXTURE),
|
||||
mSize(aSize),
|
||||
mPictureRect(aRect),
|
||||
mYUVColorSpace(aColorSpace),
|
||||
mColorSpace(aColorSpace),
|
||||
mColorRange(aColorRange) {}
|
||||
|
||||
bool D3D11ShareHandleImage::AllocateTexture(D3D11RecycleAllocator* aAllocator,
|
||||
ID3D11Device* aDevice) {
|
||||
if (aAllocator) {
|
||||
mTextureClient =
|
||||
aAllocator->CreateOrRecycleClient(mYUVColorSpace, mColorRange, mSize);
|
||||
aAllocator->CreateOrRecycleClient(mColorSpace, mColorRange, mSize);
|
||||
if (mTextureClient) {
|
||||
D3D11TextureData* textureData = GetData();
|
||||
MOZ_DIAGNOSTIC_ASSERT(textureData, "Wrong TextureDataType");
|
||||
@ -83,7 +83,7 @@ class MOZ_RAII D3D11TextureClientAllocationHelper
|
||||
: public ITextureClientAllocationHelper {
|
||||
public:
|
||||
D3D11TextureClientAllocationHelper(gfx::SurfaceFormat aFormat,
|
||||
gfx::YUVColorSpace aColorSpace,
|
||||
gfx::ColorSpace2 aColorSpace,
|
||||
gfx::ColorRange aColorRange,
|
||||
const gfx::IntSize& aSize,
|
||||
TextureAllocationFlags aAllocFlags,
|
||||
@ -91,7 +91,7 @@ class MOZ_RAII D3D11TextureClientAllocationHelper
|
||||
TextureFlags aTextureFlags)
|
||||
: ITextureClientAllocationHelper(aFormat, aSize, BackendSelector::Content,
|
||||
aTextureFlags, aAllocFlags),
|
||||
mYUVColorSpace(aColorSpace),
|
||||
mColorSpace(aColorSpace),
|
||||
mColorRange(aColorRange),
|
||||
mDevice(aDevice) {}
|
||||
|
||||
@ -106,7 +106,7 @@ class MOZ_RAII D3D11TextureClientAllocationHelper
|
||||
return (aTextureClient->GetFormat() != gfx::SurfaceFormat::NV12 &&
|
||||
aTextureClient->GetFormat() != gfx::SurfaceFormat::P010 &&
|
||||
aTextureClient->GetFormat() != gfx::SurfaceFormat::P016) ||
|
||||
(textureData->GetYUVColorSpace() == mYUVColorSpace &&
|
||||
(textureData->mColorSpace == mColorSpace &&
|
||||
textureData->GetColorRange() == mColorRange &&
|
||||
textureData->GetTextureAllocationFlags() == mAllocationFlags);
|
||||
}
|
||||
@ -118,14 +118,14 @@ class MOZ_RAII D3D11TextureClientAllocationHelper
|
||||
if (!data) {
|
||||
return nullptr;
|
||||
}
|
||||
data->SetYUVColorSpace(mYUVColorSpace);
|
||||
data->mColorSpace = mColorSpace;
|
||||
data->SetColorRange(mColorRange);
|
||||
return MakeAndAddRef<TextureClient>(data, mTextureFlags,
|
||||
aAllocator->GetTextureForwarder());
|
||||
}
|
||||
|
||||
private:
|
||||
const gfx::YUVColorSpace mYUVColorSpace;
|
||||
const gfx::ColorSpace2 mColorSpace;
|
||||
const gfx::ColorRange mColorRange;
|
||||
const RefPtr<ID3D11Device> mDevice;
|
||||
};
|
||||
@ -158,7 +158,7 @@ void D3D11RecycleAllocator::SetPreferredSurfaceFormat(
|
||||
}
|
||||
|
||||
already_AddRefed<TextureClient> D3D11RecycleAllocator::CreateOrRecycleClient(
|
||||
gfx::YUVColorSpace aColorSpace, gfx::ColorRange aColorRange,
|
||||
gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange,
|
||||
const gfx::IntSize& aSize) {
|
||||
// When CompositorDevice or ContentDevice is updated,
|
||||
// we could not reuse old D3D11Textures. It could cause video flickering.
|
||||
|
@ -28,7 +28,7 @@ class D3D11RecycleAllocator final : public TextureClientRecycleAllocator {
|
||||
gfx::SurfaceFormat aPreferredFormat);
|
||||
|
||||
already_AddRefed<TextureClient> CreateOrRecycleClient(
|
||||
gfx::YUVColorSpace aColorSpace, gfx::ColorRange aColorRange,
|
||||
gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange,
|
||||
const gfx::IntSize& aSize);
|
||||
|
||||
void SetPreferredSurfaceFormat(gfx::SurfaceFormat aPreferredFormat);
|
||||
@ -52,7 +52,7 @@ class D3D11RecycleAllocator final : public TextureClientRecycleAllocator {
|
||||
class D3D11ShareHandleImage final : public Image {
|
||||
public:
|
||||
D3D11ShareHandleImage(const gfx::IntSize& aSize, const gfx::IntRect& aRect,
|
||||
gfx::YUVColorSpace aColorSpace,
|
||||
gfx::ColorSpace2 aColorSpace,
|
||||
gfx::ColorRange aColorRange);
|
||||
virtual ~D3D11ShareHandleImage() = default;
|
||||
|
||||
@ -66,7 +66,6 @@ class D3D11ShareHandleImage final : public Image {
|
||||
|
||||
ID3D11Texture2D* GetTexture() const;
|
||||
|
||||
gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; }
|
||||
gfx::ColorRange GetColorRange() const { return mColorRange; }
|
||||
|
||||
private:
|
||||
@ -80,7 +79,11 @@ class D3D11ShareHandleImage final : public Image {
|
||||
|
||||
gfx::IntSize mSize;
|
||||
gfx::IntRect mPictureRect;
|
||||
gfx::YUVColorSpace mYUVColorSpace;
|
||||
|
||||
public:
|
||||
const gfx::ColorSpace2 mColorSpace;
|
||||
|
||||
private:
|
||||
gfx::ColorRange mColorRange;
|
||||
RefPtr<TextureClient> mTextureClient;
|
||||
RefPtr<ID3D11Texture2D> mTexture;
|
||||
|
@ -35,14 +35,14 @@ void IMFSampleWrapper::ClearVideoSample() { mVideoSample = nullptr; }
|
||||
D3D11TextureIMFSampleImage::D3D11TextureIMFSampleImage(
|
||||
IMFSample* aVideoSample, ID3D11Texture2D* aTexture, uint32_t aArrayIndex,
|
||||
const gfx::IntSize& aSize, const gfx::IntRect& aRect,
|
||||
gfx::YUVColorSpace aColorSpace, gfx::ColorRange aColorRange)
|
||||
gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange)
|
||||
: Image(nullptr, ImageFormat::D3D11_TEXTURE_IMF_SAMPLE),
|
||||
mVideoSample(IMFSampleWrapper::Create(aVideoSample)),
|
||||
mTexture(aTexture),
|
||||
mArrayIndex(aArrayIndex),
|
||||
mSize(aSize),
|
||||
mPictureRect(aRect),
|
||||
mYUVColorSpace(aColorSpace),
|
||||
mColorSpace(aColorSpace),
|
||||
mColorRange(aColorRange) {
|
||||
MOZ_ASSERT(XRE_IsGPUProcess());
|
||||
}
|
||||
@ -50,7 +50,7 @@ D3D11TextureIMFSampleImage::D3D11TextureIMFSampleImage(
|
||||
void D3D11TextureIMFSampleImage::AllocateTextureClient(
|
||||
KnowsCompositor* aKnowsCompositor, RefPtr<IMFSampleUsageInfo> aUsageInfo) {
|
||||
mTextureClient = D3D11TextureData::CreateTextureClient(
|
||||
mTexture, mArrayIndex, mSize, gfx::SurfaceFormat::NV12, mYUVColorSpace,
|
||||
mTexture, mArrayIndex, mSize, gfx::SurfaceFormat::NV12, mColorSpace,
|
||||
mColorRange, aKnowsCompositor, aUsageInfo);
|
||||
MOZ_ASSERT(mTextureClient);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ class D3D11TextureIMFSampleImage final : public Image {
|
||||
D3D11TextureIMFSampleImage(IMFSample* aVideoSample, ID3D11Texture2D* aTexture,
|
||||
uint32_t aArrayIndex, const gfx::IntSize& aSize,
|
||||
const gfx::IntRect& aRect,
|
||||
gfx::YUVColorSpace aColorSpace,
|
||||
gfx::ColorSpace2 aColorSpace,
|
||||
gfx::ColorRange aColorRange);
|
||||
virtual ~D3D11TextureIMFSampleImage() = default;
|
||||
|
||||
@ -74,7 +74,6 @@ class D3D11TextureIMFSampleImage final : public Image {
|
||||
ID3D11Texture2D* GetTexture() const;
|
||||
RefPtr<IMFSampleWrapper> GetIMFSampleWrapper();
|
||||
|
||||
gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; }
|
||||
gfx::ColorRange GetColorRange() const { return mColorRange; }
|
||||
|
||||
private:
|
||||
@ -90,11 +89,15 @@ class D3D11TextureIMFSampleImage final : public Image {
|
||||
// IMFTransform.
|
||||
RefPtr<IMFSampleWrapper> mVideoSample;
|
||||
RefPtr<ID3D11Texture2D> mTexture;
|
||||
|
||||
public:
|
||||
const uint32_t mArrayIndex;
|
||||
const gfx::IntSize mSize;
|
||||
const gfx::IntRect mPictureRect;
|
||||
const gfx::YUVColorSpace mYUVColorSpace;
|
||||
const gfx::ColorSpace2 mColorSpace;
|
||||
const gfx::ColorRange mColorRange;
|
||||
|
||||
private:
|
||||
RefPtr<TextureClient> mTextureClient;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#define mozilla_gfx_layers_d3d11_HelpersD3D11_h
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <array>
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
@ -50,6 +51,16 @@ static inline bool WaitForFrameGPUQuery(ID3D11Device* aDevice,
|
||||
return success;
|
||||
}
|
||||
|
||||
inline void ClearResource(ID3D11Device* const device, ID3D11Resource* const res,
|
||||
const std::array<float, 4>& vals) {
|
||||
RefPtr<ID3D11RenderTargetView> rtv;
|
||||
(void)device->CreateRenderTargetView(res, nullptr, getter_AddRefs(rtv));
|
||||
|
||||
RefPtr<ID3D11DeviceContext> context;
|
||||
device->GetImmediateContext(getter_AddRefs(context));
|
||||
context->ClearRenderTargetView(rtv, vals.data());
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -402,7 +402,7 @@ bool D3D11TextureData::SerializeSpecific(
|
||||
}
|
||||
*aOutDesc = SurfaceDescriptorD3D10((WindowsHandle)sharedHandle,
|
||||
mGpuProcessTextureId, mArrayIndex, mFormat,
|
||||
mSize, mYUVColorSpace, mColorRange);
|
||||
mSize, mColorSpace, mColorRange);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -425,13 +425,13 @@ void D3D11TextureData::GetSubDescriptor(
|
||||
/* static */
|
||||
already_AddRefed<TextureClient> D3D11TextureData::CreateTextureClient(
|
||||
ID3D11Texture2D* aTexture, uint32_t aIndex, gfx::IntSize aSize,
|
||||
gfx::SurfaceFormat aFormat, gfx::YUVColorSpace aColorSpace,
|
||||
gfx::SurfaceFormat aFormat, gfx::ColorSpace2 aColorSpace,
|
||||
gfx::ColorRange aColorRange, KnowsCompositor* aKnowsCompositor,
|
||||
RefPtr<IMFSampleUsageInfo> aUsageInfo) {
|
||||
D3D11TextureData* data = new D3D11TextureData(
|
||||
aTexture, aIndex, aSize, aFormat,
|
||||
TextureAllocationFlags::ALLOC_MANUAL_SYNCHRONIZATION);
|
||||
data->SetYUVColorSpace(aColorSpace);
|
||||
data->mColorSpace = aColorSpace;
|
||||
data->SetColorRange(aColorRange);
|
||||
|
||||
RefPtr<TextureClient> textureClient = MakeAndAddRef<TextureClient>(
|
||||
@ -812,7 +812,7 @@ DXGITextureHostD3D11::DXGITextureHostD3D11(
|
||||
mSize(aDescriptor.size()),
|
||||
mHandle(aDescriptor.handle()),
|
||||
mFormat(aDescriptor.format()),
|
||||
mYUVColorSpace(aDescriptor.yUVColorSpace()),
|
||||
mColorSpace(aDescriptor.colorSpace()),
|
||||
mColorRange(aDescriptor.colorRange()),
|
||||
mIsLocked(false) {}
|
||||
|
||||
@ -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;
|
||||
|
@ -60,7 +60,7 @@ class D3D11TextureData final : public TextureData {
|
||||
|
||||
static already_AddRefed<TextureClient> CreateTextureClient(
|
||||
ID3D11Texture2D* aTexture, uint32_t aIndex, gfx::IntSize aSize,
|
||||
gfx::SurfaceFormat aFormat, gfx::YUVColorSpace aColorSpace,
|
||||
gfx::SurfaceFormat aFormat, gfx::ColorSpace2 aColorSpace,
|
||||
gfx::ColorRange aColorRange, KnowsCompositor* aKnowsCompositor,
|
||||
RefPtr<IMFSampleUsageInfo> aUsageInfo);
|
||||
|
||||
@ -95,10 +95,6 @@ class D3D11TextureData final : public TextureData {
|
||||
bool Serialize(SurfaceDescriptor& aOutDescrptor) override;
|
||||
void GetSubDescriptor(RemoteDecoderVideoSubDescriptor* aOutDesc) override;
|
||||
|
||||
gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; }
|
||||
void SetYUVColorSpace(gfx::YUVColorSpace aColorSpace) {
|
||||
mYUVColorSpace = aColorSpace;
|
||||
}
|
||||
gfx::ColorRange GetColorRange() const { return mColorRange; }
|
||||
void SetColorRange(gfx::ColorRange aColorRange) { mColorRange = aColorRange; }
|
||||
|
||||
@ -136,7 +132,11 @@ class D3D11TextureData final : public TextureData {
|
||||
RefPtr<gfx::DrawTarget> mDrawTarget;
|
||||
const gfx::IntSize mSize;
|
||||
const gfx::SurfaceFormat mFormat;
|
||||
gfx::YUVColorSpace mYUVColorSpace = gfx::YUVColorSpace::Identity;
|
||||
|
||||
public:
|
||||
gfx::ColorSpace2 mColorSpace = gfx::ColorSpace2::SRGB;
|
||||
|
||||
private:
|
||||
gfx::ColorRange mColorRange = gfx::ColorRange::LIMITED;
|
||||
bool mNeedsClear = false;
|
||||
const bool mHasSynchronization;
|
||||
@ -349,9 +349,6 @@ class DXGITextureHostD3D11 : public TextureHost {
|
||||
void UnlockWithoutCompositor() override;
|
||||
|
||||
gfx::IntSize GetSize() const override { return mSize; }
|
||||
gfx::YUVColorSpace GetYUVColorSpace() const override {
|
||||
return mYUVColorSpace;
|
||||
}
|
||||
gfx::ColorRange GetColorRange() const override { return mColorRange; }
|
||||
|
||||
already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override;
|
||||
@ -392,7 +389,11 @@ class DXGITextureHostD3D11 : public TextureHost {
|
||||
gfx::IntSize mSize;
|
||||
WindowsHandle mHandle;
|
||||
gfx::SurfaceFormat mFormat;
|
||||
const gfx::YUVColorSpace mYUVColorSpace;
|
||||
|
||||
public:
|
||||
const gfx::ColorSpace2 mColorSpace;
|
||||
|
||||
protected:
|
||||
const gfx::ColorRange mColorRange;
|
||||
bool mIsLocked;
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ namespace layers {
|
||||
uint32_t arrayIndex;
|
||||
SurfaceFormat format;
|
||||
IntSize size;
|
||||
YUVColorSpace yUVColorSpace;
|
||||
ColorSpace2 colorSpace;
|
||||
ColorRange colorRange;
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
||||
|
@ -1025,4 +1025,6 @@ class gfxPlatform : public mozilla::layers::MemoryPressureListener {
|
||||
const gfxSkipChars kEmptySkipChars;
|
||||
};
|
||||
|
||||
CMSMode GfxColorManagementMode();
|
||||
|
||||
#endif /* GFX_PLATFORM_H */
|
||||
|
@ -20,7 +20,7 @@ namespace wr {
|
||||
RenderDXGITextureHost::RenderDXGITextureHost(
|
||||
WindowsHandle aHandle, Maybe<uint64_t>& aGpuProcessTextureId,
|
||||
uint32_t aArrayIndex, gfx::SurfaceFormat aFormat,
|
||||
gfx::YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange,
|
||||
gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange,
|
||||
gfx::IntSize aSize)
|
||||
: mHandle(aHandle),
|
||||
mGpuProcessTextureId(aGpuProcessTextureId),
|
||||
@ -29,7 +29,7 @@ RenderDXGITextureHost::RenderDXGITextureHost(
|
||||
mStream(0),
|
||||
mTextureHandle{0},
|
||||
mFormat(aFormat),
|
||||
mYUVColorSpace(aYUVColorSpace),
|
||||
mColorSpace(aColorSpace),
|
||||
mColorRange(aColorRange),
|
||||
mSize(aSize),
|
||||
mLocked(false) {
|
||||
|
@ -22,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;
|
||||
};
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user