Bug 1616715 - SurfaceFromElement gets alpha Premult unless opt-in to NonPremult. r=lsalzman

Differential Revision: https://phabricator.services.mozilla.com/D63421

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jeff Gilbert 2020-02-21 02:27:08 +00:00
parent 74a66ab98d
commit 011c0a1fd7
8 changed files with 112 additions and 71 deletions

View File

@ -18,6 +18,7 @@
#include "mozilla/layers/OOPCanvasRenderer.h"
#include "mozilla/layers/TextureClientSharedSurface.h"
#include "mozilla/Preferences.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_webgl.h"
#include "nsContentUtils.h"
#include "nsIGfxInfo.h"
@ -883,9 +884,106 @@ ClientWebGLContext::SetContextOptions(JSContext* cx,
void ClientWebGLContext::DidRefresh() { Run<RPROC(DidRefresh)>(); }
already_AddRefed<gfx::SourceSurface> ClientWebGLContext::GetSurfaceSnapshot(
gfxAlphaType* out_alphaType) {
auto ret = Run<RPROC(GetSurfaceSnapshot)>(out_alphaType);
return ret.forget();
gfxAlphaType* const out_alphaType) {
const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
if (IsContextLost()) return nullptr;
const auto notLost =
mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
const auto& options = mNotLost->info.options;
const auto& state = State();
const auto drawFbWas = state.mBoundDrawFb;
const auto readFbWas = state.mBoundReadFb;
const auto alignmentWas = Run<RPROC(GetParameter)>(LOCAL_GL_PACK_ALIGNMENT);
if (!alignmentWas) return nullptr;
const auto size = DrawingBufferSize();
// -
BindFramebuffer(LOCAL_GL_FRAMEBUFFER, nullptr);
PixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
auto reset = MakeScopeExit([&] {
if (drawFbWas == readFbWas) {
BindFramebuffer(LOCAL_GL_FRAMEBUFFER, drawFbWas);
} else {
BindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, drawFbWas);
BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, readFbWas);
}
PixelStorei(LOCAL_GL_PACK_ALIGNMENT, *alignmentWas);
});
const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
: gfx::SurfaceFormat::B8G8R8X8;
const auto stride = size.x * 4;
RefPtr<gfx::DataSourceSurface> surf =
gfx::Factory::CreateDataSourceSurfaceWithStride(
{size.x, size.y}, surfFormat, stride, /*zero=*/true);
MOZ_ASSERT(surf);
if (NS_WARN_IF(!surf)) return nullptr;
{
const gfx::DataSourceSurface::ScopedMap map(
surf, gfx::DataSourceSurface::READ_WRITE);
if (!map.IsMapped()) {
MOZ_ASSERT(false);
return nullptr;
}
MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride);
const auto range = Range<uint8_t>(map.GetData(), stride * size.y);
auto view = RawBufferView(range);
Run<RPROC(ReadPixels)>(0, 0, size.x, size.y, LOCAL_GL_RGBA,
LOCAL_GL_UNSIGNED_BYTE, view);
// -
const auto swapRowRedBlue = [&](uint8_t* const row) {
for (const auto x : IntegerRange(size.x)) {
std::swap(row[4 * x], row[4 * x + 2]);
}
};
std::vector<uint8_t> tempRow(stride);
for (const auto srcY : IntegerRange(size.y / 2)) {
const auto dstY = size.y - 1 - srcY;
const auto srcRow = (range.begin() + (stride * srcY)).get();
const auto dstRow = (range.begin() + (stride * dstY)).get();
memcpy(tempRow.data(), dstRow, stride);
memcpy(dstRow, srcRow, stride);
swapRowRedBlue(dstRow);
memcpy(srcRow, tempRow.data(), stride);
swapRowRedBlue(srcRow);
}
if (size.y & 1) {
const auto midY = size.y / 2; // size.y = 3 => midY = 1
const auto midRow = (range.begin() + (stride * midY)).get();
swapRowRedBlue(midRow);
}
}
gfxAlphaType srcAlphaType;
if (!options.alpha) {
srcAlphaType = gfxAlphaType::Opaque;
} else if (options.premultipliedAlpha) {
srcAlphaType = gfxAlphaType::Premult;
} else {
srcAlphaType = gfxAlphaType::NonPremult;
}
if (out_alphaType) {
*out_alphaType = srcAlphaType;
} else {
// Expects Opaque or Premult
if (srcAlphaType == gfxAlphaType::NonPremult) {
gfxUtils::PremultiplyDataSurface(surf, surf);
}
}
return surf.forget();
}
UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer(int32_t* out_format) {

View File

@ -196,11 +196,6 @@ class HostWebGLContext final : public SupportsWeakPtr<HostWebGLContext> {
void DidRefresh() { mContext->DidRefresh(); }
RefPtr<gfx::SourceSurface> GetSurfaceSnapshot(
gfxAlphaType* out_alphaType) const {
return mContext->GetSurfaceSnapshot(out_alphaType);
}
void GenerateError(const GLenum error, const std::string& text) const {
mContext->GenerateErrorImpl(error, text);
}

View File

@ -1252,56 +1252,6 @@ void WebGLContext::LoseContext(const webgl::ContextLossReason reason) {
mHost->OnContextLoss(reason);
}
RefPtr<gfx::SourceSurface> WebGLContext::GetSurfaceSnapshot(
gfxAlphaType* const out_alphaType) {
const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
if (IsContextLost()) return nullptr;
if (!BindDefaultFBForRead()) return nullptr;
const auto surfFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8
: gfx::SurfaceFormat::B8G8R8X8;
const auto& size = mDefaultFB->mSize;
RefPtr<gfx::DataSourceSurface> surf;
surf = gfx::Factory::CreateDataSourceSurfaceWithStride(size, surfFormat,
size.width * 4);
if (NS_WARN_IF(!surf)) return nullptr;
ReadPixelsIntoDataSurface(gl, surf);
gfxAlphaType alphaType;
if (!mOptions.alpha) {
alphaType = gfxAlphaType::Opaque;
} else if (mOptions.premultipliedAlpha) {
alphaType = gfxAlphaType::Premult;
} else {
alphaType = gfxAlphaType::NonPremult;
}
if (out_alphaType) {
*out_alphaType = alphaType;
} else {
// Expects Opaque or Premult
if (alphaType == gfxAlphaType::NonPremult) {
gfxUtils::PremultiplyDataSurface(surf, surf);
}
}
RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTarget(
gfxPlatform::GetPlatform()->GetSoftwareBackend(), size,
gfx::SurfaceFormat::B8G8R8A8);
if (!dt) return nullptr;
dt->SetTransform(
gfx::Matrix::Translation(0.0, size.height).PreScale(1.0, -1.0));
const gfx::Rect rect{0, 0, float(size.width), float(size.height)};
dt->DrawSurface(surf, rect, rect, gfx::DrawSurfaceOptions(),
gfx::DrawOptions(1.0f, gfx::CompositionOp::OP_SOURCE));
return dt->Snapshot();
}
void WebGLContext::DidRefresh() {
if (gl) {
gl->FlushIfHeavyGLCallsSinceLastFlush();

View File

@ -374,9 +374,6 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr<WebGLContext> {
void SetCompositableHost(RefPtr<layers::CompositableHost>& aCompositableHost);
RefPtr<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(
gfxAlphaType* out_alphaType);
/**
* An abstract base class to be implemented by callers wanting to be notified
* that a refresh has occurred. Callers must ensure an observer is removed

View File

@ -52,7 +52,6 @@ DEFINE_ASYNC(HostWebGLContext::DeleteTransformFeedback)
DEFINE_ASYNC(HostWebGLContext::DeleteVertexArray)
DEFINE_ASYNC(HostWebGLContext::ClearVRFrame)
DEFINE_SYNC(HostWebGLContext::GetSurfaceSnapshot)
DEFINE_SYNC(HostWebGLContext::GetVRFrame)
DEFINE_ASYNC(HostWebGLContext::Disable)

View File

@ -170,14 +170,12 @@ UniquePtr<webgl::TexUnpackBlob> WebGLContext::FromDomElem(
// same as drawImage.
uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
nsLayoutUtils::SFE_WANT_IMAGE_SURFACE |
nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR;
nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
if (mPixelStore.mColorspaceConversion == LOCAL_GL_NONE)
flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
if (!mPixelStore.mPremultiplyAlpha)
flags |= nsLayoutUtils::SFE_PREFER_NO_PREMULTIPLY_ALPHA;
RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
auto sfer = nsLayoutUtils::SurfaceFromElement(
const_cast<dom::Element*>(&elem), flags, idealDrawTarget);

View File

@ -7580,7 +7580,7 @@ nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY;
if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION)
frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
if (aSurfaceFlags & SFE_ALLOW_NON_PREMULT) {
frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
}
@ -7666,7 +7666,12 @@ nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
IntSize size = aElement->GetSize();
result.mSourceSurface = aElement->GetSurfaceSnapshot(&result.mAlphaType);
auto pAlphaType = &result.mAlphaType;
if (!(aSurfaceFlags & SFE_ALLOW_NON_PREMULT)) {
pAlphaType =
nullptr; // Coersce GetSurfaceSnapshot to give us Opaque/Premult only.
}
result.mSourceSurface = aElement->GetSurfaceSnapshot(pAlphaType);
if (!result.mSourceSurface) {
// If the element doesn't have a context then we won't get a snapshot. The
// canvas spec wants us to not error and just draw nothing, so return an

View File

@ -2125,9 +2125,8 @@ class nsLayoutUtils {
SFE_WANT_FIRST_FRAME_IF_IMAGE = 1 << 1,
/* Whether we should skip colorspace/gamma conversion */
SFE_NO_COLORSPACE_CONVERSION = 1 << 2,
/* Specifies that the caller wants either OPAQUE or NON_PREMULT mAlphaType,
if this is can be done efficiently. */
SFE_PREFER_NO_PREMULTIPLY_ALPHA = 1 << 3,
/* Caller handles SFER::mAlphaType = NonPremult */
SFE_ALLOW_NON_PREMULT = 1 << 3,
/* Whether we should skip getting a surface for vector images and
return a DirectDrawInfo containing an imgIContainer instead. */
SFE_NO_RASTERIZING_VECTORS = 1 << 4,