зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1677183 - Implement WebGPU's Queue.copyExternalImageToTexture. r=jgilbert,jimb,emilio
Differential Revision: https://phabricator.services.mozilla.com/D145269
This commit is contained in:
Родитель
1a455850f9
Коммит
2b1c7294dc
|
@ -272,6 +272,7 @@ class WebGLImageConverter {
|
|||
WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA8)
|
||||
WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA16F)
|
||||
WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::RGBA32F)
|
||||
WEBGLIMAGECONVERTER_CASE_DSTFORMAT(WebGLTexelFormat::BGRA8)
|
||||
|
||||
default:
|
||||
MOZ_ASSERT(false, "unhandled case. Coding mistake?");
|
||||
|
@ -380,20 +381,6 @@ bool ConvertImage(size_t width, size_t height, const void* srcBegin,
|
|||
if (srcFormat == dstFormat &&
|
||||
premultOp == WebGLTexelPremultiplicationOp::None) {
|
||||
// Fast exit path: we just have to memcpy all the rows.
|
||||
//
|
||||
// The case where absolutely nothing needs to be done is supposed to have
|
||||
// been handled earlier (in TexImage2D_base, etc).
|
||||
//
|
||||
// So the case we're handling here is when even though no format conversion
|
||||
// is needed, we still might have to flip vertically and/or to adjust to a
|
||||
// different stride.
|
||||
|
||||
// We ignore canSkipPremult for this perf trap, since it's an avoidable perf
|
||||
// cliff under the WebGL API user's control.
|
||||
MOZ_ASSERT(
|
||||
(srcPremultiplied != dstPremultiplied || shouldYFlip ||
|
||||
srcStride != dstStride),
|
||||
"Performance trap -- should handle this case earlier to avoid memcpy");
|
||||
|
||||
const auto bytesPerPixel = TexelBytesForFormat(srcFormat);
|
||||
const size_t bytesPerRow = bytesPerPixel * width;
|
||||
|
|
|
@ -1231,6 +1231,45 @@ pack<WebGLTexelFormat::RGBA8, WebGLTexelPremultiplicationOp::Unpremultiply,
|
|||
dst[3] = src[3];
|
||||
}
|
||||
|
||||
template <>
|
||||
MOZ_ALWAYS_INLINE void
|
||||
pack<WebGLTexelFormat::BGRA8, WebGLTexelPremultiplicationOp::None, uint8_t,
|
||||
uint8_t>(const uint8_t* __restrict src, uint8_t* __restrict dst) {
|
||||
dst[0] = src[2];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[0];
|
||||
dst[3] = src[3];
|
||||
}
|
||||
|
||||
template <>
|
||||
MOZ_ALWAYS_INLINE void
|
||||
pack<WebGLTexelFormat::BGRA8, WebGLTexelPremultiplicationOp::Premultiply,
|
||||
uint8_t, uint8_t>(const uint8_t* __restrict src, uint8_t* __restrict dst) {
|
||||
float scaleFactor = src[3] / 255.0f;
|
||||
uint8_t srcR = static_cast<uint8_t>(src[0] * scaleFactor);
|
||||
uint8_t srcG = static_cast<uint8_t>(src[1] * scaleFactor);
|
||||
uint8_t srcB = static_cast<uint8_t>(src[2] * scaleFactor);
|
||||
dst[0] = srcB;
|
||||
dst[1] = srcG;
|
||||
dst[2] = srcR;
|
||||
dst[3] = src[3];
|
||||
}
|
||||
|
||||
// FIXME: this routine is lossy and must be removed.
|
||||
template <>
|
||||
MOZ_ALWAYS_INLINE void
|
||||
pack<WebGLTexelFormat::BGRA8, WebGLTexelPremultiplicationOp::Unpremultiply,
|
||||
uint8_t, uint8_t>(const uint8_t* __restrict src, uint8_t* __restrict dst) {
|
||||
float scaleFactor = src[3] ? 255.0f / src[3] : 1.0f;
|
||||
uint8_t srcR = static_cast<uint8_t>(src[0] * scaleFactor);
|
||||
uint8_t srcG = static_cast<uint8_t>(src[1] * scaleFactor);
|
||||
uint8_t srcB = static_cast<uint8_t>(src[2] * scaleFactor);
|
||||
dst[0] = srcB;
|
||||
dst[1] = srcG;
|
||||
dst[2] = srcR;
|
||||
dst[3] = src[3];
|
||||
}
|
||||
|
||||
template <>
|
||||
MOZ_ALWAYS_INLINE void
|
||||
pack<WebGLTexelFormat::RGBA16F, WebGLTexelPremultiplicationOp::None, uint16_t,
|
||||
|
|
|
@ -68,6 +68,7 @@ EXPORTS.mozilla.dom += [
|
|||
"WebGLCommandQueue.h",
|
||||
"WebGLIpdl.h",
|
||||
"WebGLParent.h",
|
||||
"WebGLTexelConversions.h",
|
||||
"WebGLTypes.h",
|
||||
"XRWebGLLayer.h",
|
||||
]
|
||||
|
|
|
@ -7,10 +7,19 @@
|
|||
#include "mozilla/dom/UnionTypes.h"
|
||||
#include "Queue.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "CommandBuffer.h"
|
||||
#include "CommandEncoder.h"
|
||||
#include "ipc/WebGPUChild.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "mozilla/dom/ImageBitmap.h"
|
||||
#include "mozilla/dom/OffscreenCanvas.h"
|
||||
#include "mozilla/dom/WebGLTexelConversions.h"
|
||||
#include "mozilla/dom/WebGLTypes.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
|
||||
namespace mozilla::webgpu {
|
||||
|
||||
|
@ -143,4 +152,251 @@ void Queue::WriteTexture(const dom::GPUImageCopyTexture& aDestination,
|
|||
}
|
||||
}
|
||||
|
||||
static WebGLTexelFormat ToWebGLTexelFormat(gfx::SurfaceFormat aFormat) {
|
||||
switch (aFormat) {
|
||||
case gfx::SurfaceFormat::B8G8R8A8:
|
||||
case gfx::SurfaceFormat::B8G8R8X8:
|
||||
return WebGLTexelFormat::BGRA8;
|
||||
case gfx::SurfaceFormat::R8G8B8A8:
|
||||
case gfx::SurfaceFormat::R8G8B8X8:
|
||||
return WebGLTexelFormat::RGBA8;
|
||||
default:
|
||||
return WebGLTexelFormat::FormatNotSupportingAnyConversion;
|
||||
}
|
||||
}
|
||||
|
||||
static WebGLTexelFormat ToWebGLTexelFormat(dom::GPUTextureFormat aFormat) {
|
||||
// TODO: We need support for Rbg10a2unorm as well.
|
||||
switch (aFormat) {
|
||||
case dom::GPUTextureFormat::R8unorm:
|
||||
return WebGLTexelFormat::R8;
|
||||
case dom::GPUTextureFormat::R16float:
|
||||
return WebGLTexelFormat::R16F;
|
||||
case dom::GPUTextureFormat::R32float:
|
||||
return WebGLTexelFormat::R32F;
|
||||
case dom::GPUTextureFormat::Rg8unorm:
|
||||
return WebGLTexelFormat::RG8;
|
||||
case dom::GPUTextureFormat::Rg16float:
|
||||
return WebGLTexelFormat::RG16F;
|
||||
case dom::GPUTextureFormat::Rg32float:
|
||||
return WebGLTexelFormat::RG32F;
|
||||
case dom::GPUTextureFormat::Rgba8unorm:
|
||||
case dom::GPUTextureFormat::Rgba8unorm_srgb:
|
||||
return WebGLTexelFormat::RGBA8;
|
||||
case dom::GPUTextureFormat::Bgra8unorm:
|
||||
case dom::GPUTextureFormat::Bgra8unorm_srgb:
|
||||
return WebGLTexelFormat::BGRA8;
|
||||
case dom::GPUTextureFormat::Rgba16float:
|
||||
return WebGLTexelFormat::RGBA16F;
|
||||
case dom::GPUTextureFormat::Rgba32float:
|
||||
return WebGLTexelFormat::RGBA32F;
|
||||
default:
|
||||
return WebGLTexelFormat::FormatNotSupportingAnyConversion;
|
||||
}
|
||||
}
|
||||
|
||||
void Queue::CopyExternalImageToTexture(
|
||||
const dom::GPUImageCopyExternalImage& aSource,
|
||||
const dom::GPUImageCopyTextureTagged& aDestination,
|
||||
const dom::GPUExtent3D& aCopySize, ErrorResult& aRv) {
|
||||
const auto dstFormat = ToWebGLTexelFormat(aDestination.mTexture->mFormat);
|
||||
if (dstFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
|
||||
aRv.ThrowInvalidStateError("Unsupported destination format");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t surfaceFlags = nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
|
||||
SurfaceFromElementResult sfeResult;
|
||||
if (aSource.mSource.IsImageBitmap()) {
|
||||
const auto& bitmap = aSource.mSource.GetAsImageBitmap();
|
||||
if (bitmap->IsClosed()) {
|
||||
aRv.ThrowInvalidStateError("Detached ImageBitmap");
|
||||
return;
|
||||
}
|
||||
|
||||
sfeResult = nsLayoutUtils::SurfaceFromImageBitmap(bitmap, surfaceFlags);
|
||||
} else if (aSource.mSource.IsHTMLCanvasElement()) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
const auto& canvas = aSource.mSource.GetAsHTMLCanvasElement();
|
||||
if (canvas->Width() == 0 || canvas->Height() == 0) {
|
||||
aRv.ThrowInvalidStateError("Zero-sized HTMLCanvasElement");
|
||||
return;
|
||||
}
|
||||
|
||||
sfeResult = nsLayoutUtils::SurfaceFromElement(canvas, surfaceFlags);
|
||||
} else if (aSource.mSource.IsOffscreenCanvas()) {
|
||||
const auto& canvas = aSource.mSource.GetAsOffscreenCanvas();
|
||||
if (canvas->Width() == 0 || canvas->Height() == 0) {
|
||||
aRv.ThrowInvalidStateError("Zero-sized OffscreenCanvas");
|
||||
return;
|
||||
}
|
||||
|
||||
sfeResult = nsLayoutUtils::SurfaceFromOffscreenCanvas(canvas, surfaceFlags);
|
||||
}
|
||||
|
||||
if (!sfeResult.mCORSUsed) {
|
||||
nsIGlobalObject* global = mParent->GetOwnerGlobal();
|
||||
nsIPrincipal* dstPrincipal = global ? global->PrincipalOrNull() : nullptr;
|
||||
if (!sfeResult.mPrincipal || !dstPrincipal ||
|
||||
!dstPrincipal->Subsumes(sfeResult.mPrincipal)) {
|
||||
aRv.ThrowSecurityError("Cross-origin elements require CORS!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sfeResult.mIsWriteOnly) {
|
||||
aRv.ThrowSecurityError("Write only source data not supported!");
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<gfx::SourceSurface> surface = sfeResult.GetSourceSurface();
|
||||
if (!surface) {
|
||||
aRv.ThrowInvalidStateError("No surface available from source");
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
|
||||
if (!dataSurface) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
bool srcPremultiplied;
|
||||
switch (sfeResult.mAlphaType) {
|
||||
case gfxAlphaType::Premult:
|
||||
srcPremultiplied = true;
|
||||
break;
|
||||
case gfxAlphaType::NonPremult:
|
||||
srcPremultiplied = false;
|
||||
break;
|
||||
case gfxAlphaType::Opaque:
|
||||
// No (un)premultiplication necessary so match the output.
|
||||
srcPremultiplied = aDestination.mPremultipliedAlpha;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto surfaceFormat = dataSurface->GetFormat();
|
||||
const auto srcFormat = ToWebGLTexelFormat(surfaceFormat);
|
||||
if (srcFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
|
||||
gfxCriticalError() << "Unsupported surface format from source "
|
||||
<< surfaceFormat;
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
gfx::DataSourceSurface::ScopedMap map(dataSurface,
|
||||
gfx::DataSourceSurface::READ);
|
||||
if (!map.IsMapped()) {
|
||||
aRv.ThrowInvalidStateError("Cannot map surface from source");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aSource.mOrigin.IsGPUOrigin2DDict()) {
|
||||
aRv.ThrowInvalidStateError("Cannot get origin from source");
|
||||
return;
|
||||
}
|
||||
|
||||
ffi::WGPUExtent3d extent = {};
|
||||
CommandEncoder::ConvertExtent3DToFFI(aCopySize, &extent);
|
||||
if (extent.depth_or_array_layers > 1) {
|
||||
aRv.ThrowOperationError("Depth is greater than 1");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t srcOriginX;
|
||||
uint32_t srcOriginY;
|
||||
if (aSource.mOrigin.IsRangeEnforcedUnsignedLongSequence()) {
|
||||
const auto& seq = aSource.mOrigin.GetAsRangeEnforcedUnsignedLongSequence();
|
||||
srcOriginX = seq.Length() > 0 ? seq[0] : 0;
|
||||
srcOriginY = seq.Length() > 1 ? seq[1] : 0;
|
||||
} else if (aSource.mOrigin.IsGPUOrigin2DDict()) {
|
||||
const auto& dict = aSource.mOrigin.GetAsGPUOrigin2DDict();
|
||||
srcOriginX = dict.mX;
|
||||
srcOriginY = dict.mY;
|
||||
} else {
|
||||
MOZ_CRASH("Unexpected origin type!");
|
||||
}
|
||||
|
||||
const auto checkedMaxWidth = CheckedInt<uint32_t>(srcOriginX) + extent.width;
|
||||
const auto checkedMaxHeight =
|
||||
CheckedInt<uint32_t>(srcOriginY) + extent.height;
|
||||
if (!checkedMaxWidth.isValid() || !checkedMaxHeight.isValid()) {
|
||||
aRv.ThrowOperationError("Offset and copy size exceed integer bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
const gfx::IntSize surfaceSize = dataSurface->GetSize();
|
||||
const auto surfaceWidth = AssertedCast<uint32_t>(surfaceSize.width);
|
||||
const auto surfaceHeight = AssertedCast<uint32_t>(surfaceSize.height);
|
||||
if (surfaceWidth < checkedMaxWidth.value() ||
|
||||
surfaceHeight < checkedMaxHeight.value()) {
|
||||
aRv.ThrowOperationError("Offset and copy size exceed surface bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dstWidth = extent.width;
|
||||
const auto dstHeight = extent.height;
|
||||
if (dstWidth == 0 || dstHeight == 0) {
|
||||
aRv.ThrowOperationError("Destination size is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aDestination.mTexture->mBytesPerBlock) {
|
||||
// TODO(bug 1781071) This should emmit a GPUValidationError on the device
|
||||
// timeline.
|
||||
aRv.ThrowInvalidStateError("Invalid destination format");
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: This assumes bytes per block == bytes per pixel which is the case
|
||||
// here because the spec only allows non-compressed texture formats for the
|
||||
// destination.
|
||||
const auto dstStride = CheckedInt<uint32_t>(extent.width) *
|
||||
aDestination.mTexture->mBytesPerBlock.value();
|
||||
const auto dstByteLength = dstStride * extent.height;
|
||||
if (!dstStride.isValid() || !dstByteLength.isValid()) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
ipc::Shmem shmem;
|
||||
if (!mBridge->AllocShmem(dstByteLength.value(), &shmem)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t pixelSize = gfx::BytesPerPixel(surfaceFormat);
|
||||
auto* dstBegin = shmem.get<uint8_t>();
|
||||
const auto* srcBegin =
|
||||
map.GetData() + srcOriginX * pixelSize + srcOriginY * map.GetStride();
|
||||
const auto srcOriginPos = gl::OriginPos::TopLeft;
|
||||
const auto srcStride = AssertedCast<uint32_t>(map.GetStride());
|
||||
const auto dstOriginPos =
|
||||
aSource.mFlipY ? gl::OriginPos::BottomLeft : gl::OriginPos::TopLeft;
|
||||
bool wasTrivial;
|
||||
|
||||
if (!ConvertImage(dstWidth, dstHeight, srcBegin, srcStride, srcOriginPos,
|
||||
srcFormat, srcPremultiplied, dstBegin, dstStride.value(),
|
||||
dstOriginPos, dstFormat, aDestination.mPremultipliedAlpha,
|
||||
&wasTrivial)) {
|
||||
MOZ_ASSERT_UNREACHABLE("ConvertImage failed!");
|
||||
mBridge->DeallocShmem(shmem);
|
||||
aRv.ThrowInvalidStateError(
|
||||
nsPrintfCString("Failed to convert source to destination format "
|
||||
"(%i/%i), please file a bug!",
|
||||
(int)srcFormat, (int)dstFormat));
|
||||
return;
|
||||
}
|
||||
|
||||
ffi::WGPUImageDataLayout dataLayout = {0, dstStride.value(), dstHeight};
|
||||
ffi::WGPUImageCopyTexture copyView = {};
|
||||
CommandEncoder::ConvertTextureCopyViewToFFI(aDestination, ©View);
|
||||
ipc::ByteBuf bb;
|
||||
ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb));
|
||||
if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
|
||||
std::move(shmem))) {
|
||||
MOZ_CRASH("IPC failure");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla::webgpu
|
||||
|
|
|
@ -53,6 +53,11 @@ class Queue final : public ObjectBase, public ChildOf<Device> {
|
|||
const dom::GPUImageDataLayout& aDataLayout,
|
||||
const dom::GPUExtent3D& aSize, ErrorResult& aRv);
|
||||
|
||||
void CopyExternalImageToTexture(
|
||||
const dom::GPUImageCopyExternalImage& aSource,
|
||||
const dom::GPUImageCopyTextureTagged& aDestination,
|
||||
const dom::GPUExtent3D& aCopySize, ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
virtual ~Queue();
|
||||
void Cleanup() {}
|
||||
|
|
|
@ -88,6 +88,7 @@ Texture::Texture(Device* const aParent, RawId aId,
|
|||
const dom::GPUTextureDescriptor& aDesc)
|
||||
: ChildOf(aParent),
|
||||
mId(aId),
|
||||
mFormat(aDesc.mFormat),
|
||||
mBytesPerBlock(GetBytesPerBlock(aDesc.mFormat)) {}
|
||||
|
||||
Texture::~Texture() { Cleanup(); }
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace mozilla {
|
|||
namespace dom {
|
||||
struct GPUTextureDescriptor;
|
||||
struct GPUTextureViewDescriptor;
|
||||
enum class GPUTextureFormat : uint8_t;
|
||||
} // namespace dom
|
||||
|
||||
namespace webgpu {
|
||||
|
@ -35,6 +36,7 @@ class Texture final : public ObjectBase, public ChildOf<Device> {
|
|||
const dom::GPUTextureDescriptor& aDesc);
|
||||
Device* GetParentDevice() { return mParent; }
|
||||
const RawId mId;
|
||||
const dom::GPUTextureFormat mFormat;
|
||||
const Maybe<uint8_t> mBytesPerBlock;
|
||||
|
||||
WeakPtr<CanvasContext> mTargetContext;
|
||||
|
|
|
@ -24,6 +24,8 @@ fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version
|
|||
fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
|
||||
[test_submit_render_empty.worker.html]
|
||||
fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
|
||||
[test_queue_copyExternalImageToTexture.html]
|
||||
fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
|
||||
[test_queue_write.html]
|
||||
fail-if = (os == 'linux' && os_version == '18.04') || (os == 'win' && os_version == '6.1')
|
||||
[test_error_scope.html]
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
'use strict';
|
||||
|
||||
ok(
|
||||
SpecialPowers.getBoolPref("dom.webgpu.enabled"),
|
||||
"WebGPU pref should be enabled."
|
||||
);
|
||||
ok(
|
||||
SpecialPowers.getBoolPref("gfx.offscreencanvas.enabled"),
|
||||
"OffscreenCanvas pref should be enabled."
|
||||
);
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function requestAnimationFramePromise() {
|
||||
return new Promise(requestAnimationFrame);
|
||||
}
|
||||
|
||||
function createSourceCanvasWebgl() {
|
||||
const offscreenCanvas = new OffscreenCanvas(200, 200);
|
||||
const gl = offscreenCanvas.getContext("webgl");
|
||||
|
||||
const COLOR_VALUE = 127.0 / 255.0;
|
||||
const ALPHA_VALUE = 127.0 / 255.0;
|
||||
|
||||
gl.enable(gl.SCISSOR_TEST);
|
||||
|
||||
gl.scissor(0, 0, 100, 100);
|
||||
gl.clearColor(COLOR_VALUE, 0.0, 0.0, ALPHA_VALUE);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
gl.scissor(100, 0, 100, 100);
|
||||
gl.clearColor(0.0, COLOR_VALUE, 0.0, ALPHA_VALUE);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
gl.scissor(0, 100, 100, 100);
|
||||
gl.clearColor(0.0, 0.0, COLOR_VALUE, ALPHA_VALUE);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
gl.scissor(100, 100, 100, 100);
|
||||
gl.clearColor(0.0, 0.0, 0.0, ALPHA_VALUE);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
return {
|
||||
source: offscreenCanvas,
|
||||
origin: { x: 0, y: 0 },
|
||||
flipY: true,
|
||||
};
|
||||
}
|
||||
|
||||
function createSourceCanvas2d() {
|
||||
const offscreenCanvas = new OffscreenCanvas(200, 200);
|
||||
const context = offscreenCanvas.getContext("2d");
|
||||
|
||||
context.fillStyle = "rgba(255,0,0,0.498)";
|
||||
context.fillRect(0, 0, 100, 100);
|
||||
|
||||
context.fillStyle = "rgba(0,255,0,0.498)";
|
||||
context.fillRect(100, 0, 100, 100);
|
||||
|
||||
context.fillStyle = "rgba(0,0,255,0.498)";
|
||||
context.fillRect(0, 100, 100, 100);
|
||||
|
||||
context.fillStyle = "rgba(0,0,0,0.498)";
|
||||
context.fillRect(100, 100, 100, 100);
|
||||
|
||||
return {
|
||||
source: offscreenCanvas,
|
||||
origin: { x: 0, y: 0 },
|
||||
flipY: false,
|
||||
};
|
||||
}
|
||||
|
||||
function createSourceImageBitmap() {
|
||||
const sourceCanvas = createSourceCanvas2d();
|
||||
return {
|
||||
source: sourceCanvas.source.transferToImageBitmap(),
|
||||
origin: { x: 0, y: 0 },
|
||||
flipY: false,
|
||||
};
|
||||
}
|
||||
|
||||
async function mapDestTexture(device, source, destFormat, premultiply, copySize) {
|
||||
const bytesPerRow = 256 * 4; // 256 aligned for 200 pixels
|
||||
const texture = device.createTexture({
|
||||
format: destFormat,
|
||||
size: copySize,
|
||||
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
|
||||
});
|
||||
|
||||
device.queue.copyExternalImageToTexture(
|
||||
source,
|
||||
{ texture, premultipliedAlpha: premultiply },
|
||||
copySize,
|
||||
);
|
||||
|
||||
const buffer = device.createBuffer({
|
||||
size: 1024 * 200,
|
||||
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
const encoder = device.createCommandEncoder();
|
||||
encoder.copyTextureToBuffer(
|
||||
{ texture },
|
||||
{ buffer, bytesPerRow },
|
||||
copySize,
|
||||
);
|
||||
device.queue.submit([encoder.finish()]);
|
||||
|
||||
await buffer.mapAsync(GPUMapMode.READ);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async function verifyBuffer(test, device, source, format, premultiply, copyDim, topLeftPixelData) {
|
||||
try {
|
||||
const buffer = await mapDestTexture(device, source, format, premultiply, copyDim);
|
||||
const arrayBuffer = buffer.getMappedRange();
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < topLeftPixelData.length; ++i) {
|
||||
is(view[i], topLeftPixelData[i], test + " " + format + " (" + source.origin.x + "," + source.origin.y + ") channel " + i);
|
||||
}
|
||||
} catch(e) {
|
||||
ok(false, "WebGPU exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
async function verifySourceCanvas(test, device, source) {
|
||||
await verifyBuffer(test, device, source, "rgba8unorm", /* premultiply */ true, { width: 200, height: 200 }, [127, 0, 0, 127]);
|
||||
await verifyBuffer(test, device, source, "bgra8unorm", /* premultiply */ true, { width: 200, height: 200 }, [0, 0, 127, 127]);
|
||||
await verifyBuffer(test, device, source, "rgba8unorm", /* premultiply */ false, { width: 200, height: 200 }, [255, 0, 0, 127]);
|
||||
await verifyBuffer(test, device, source, "bgra8unorm", /* premultiply */ false, { width: 200, height: 200 }, [0, 0, 255, 127]);
|
||||
|
||||
// The copy is flipped but the origin is relative to the original source data,
|
||||
// so we need to invert for WebGL.
|
||||
const topRightPixelData = test === "webgl" ? [0, 0, 0, 127] : [0, 127, 0, 127];
|
||||
const topRightOrigin = { origin: { x: 100, y: 0 } };
|
||||
await verifyBuffer(test, device, { ...source, ...topRightOrigin }, "bgra8unorm", /* premultiply */ true, { width: 100, height: 100 }, topRightPixelData);
|
||||
|
||||
const bottomLeftPixelData = test === "webgl" ? [0, 0, 127, 127] : [127, 0, 0, 127];
|
||||
const bottomLeftOrigin = { origin: { x: 0, y: 100 } };
|
||||
await verifyBuffer(test, device, { ...source, ...bottomLeftOrigin }, "bgra8unorm", /* premultiply */ true, { width: 100, height: 100 }, bottomLeftPixelData);
|
||||
}
|
||||
|
||||
async function writeDestCanvas(source2d, sourceWebgl, sourceImageBitmap) {
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
const device = await adapter.requestDevice();
|
||||
await verifySourceCanvas("2d", device, source2d);
|
||||
await verifySourceCanvas("imageBitmap", device, sourceImageBitmap);
|
||||
await verifySourceCanvas("webgl", device, sourceWebgl);
|
||||
}
|
||||
|
||||
async function runTest() {
|
||||
try {
|
||||
const source2d = createSourceCanvas2d();
|
||||
const sourceWebgl = createSourceCanvasWebgl();
|
||||
const sourceImageBitmap = createSourceImageBitmap();
|
||||
await requestAnimationFramePromise();
|
||||
await requestAnimationFramePromise();
|
||||
await writeDestCanvas(source2d, sourceWebgl, sourceImageBitmap);
|
||||
SimpleTest.finish();
|
||||
} catch(e) {
|
||||
ok(false, "Uncaught exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
runTest();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -928,6 +928,12 @@ dictionary GPUImageCopyBuffer : GPUImageDataLayout {
|
|||
required GPUBuffer buffer;
|
||||
};
|
||||
|
||||
dictionary GPUImageCopyExternalImage {
|
||||
required (ImageBitmap or HTMLCanvasElement or OffscreenCanvas) source;
|
||||
GPUOrigin2D origin = {};
|
||||
boolean flipY = false;
|
||||
};
|
||||
|
||||
dictionary GPUImageCopyTexture {
|
||||
required GPUTexture texture;
|
||||
GPUIntegerCoordinate mipLevel = 0;
|
||||
|
@ -935,6 +941,11 @@ dictionary GPUImageCopyTexture {
|
|||
GPUTextureAspect aspect = "all";
|
||||
};
|
||||
|
||||
dictionary GPUImageCopyTextureTagged : GPUImageCopyTexture {
|
||||
//GPUPredefinedColorSpace colorSpace = "srgb"; //TODO
|
||||
boolean premultipliedAlpha = false;
|
||||
};
|
||||
|
||||
dictionary GPUImageBitmapCopyView {
|
||||
//required ImageBitmap imageBitmap; //TODO
|
||||
GPUOrigin2D origin;
|
||||
|
@ -1165,6 +1176,12 @@ interface GPUQueue {
|
|||
BufferSource data,
|
||||
GPUImageDataLayout dataLayout,
|
||||
GPUExtent3D size);
|
||||
|
||||
[Throws]
|
||||
void copyExternalImageToTexture(
|
||||
GPUImageCopyExternalImage source,
|
||||
GPUImageCopyTextureTagged destination,
|
||||
GPUExtent3D copySize);
|
||||
};
|
||||
GPUQueue includes GPUObjectBase;
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include "mozilla/dom/HTMLMediaElementBinding.h"
|
||||
#include "mozilla/dom/HTMLVideoElement.h"
|
||||
#include "mozilla/dom/InspectorFontFace.h"
|
||||
#include "mozilla/dom/ImageBitmap.h"
|
||||
#include "mozilla/dom/KeyframeEffect.h"
|
||||
#include "mozilla/dom/SVGViewportElement.h"
|
||||
#include "mozilla/dom/UIEvent.h"
|
||||
|
@ -7114,6 +7115,35 @@ SurfaceFromElementResult nsLayoutUtils::SurfaceFromOffscreenCanvas(
|
|||
return result;
|
||||
}
|
||||
|
||||
SurfaceFromElementResult nsLayoutUtils::SurfaceFromImageBitmap(
|
||||
mozilla::dom::ImageBitmap* aImageBitmap, uint32_t aSurfaceFlags) {
|
||||
SurfaceFromElementResult result;
|
||||
RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
|
||||
BackendType::SKIA, IntSize(1, 1), SurfaceFormat::B8G8R8A8);
|
||||
|
||||
// An ImageBitmap, not being a DOM element, only has `origin-clean`
|
||||
// (via our `IsWriteOnly`), and does not participate in CORS.
|
||||
// Right now we mark this by setting mCORSUsed to true.
|
||||
result.mCORSUsed = true;
|
||||
result.mIsWriteOnly = aImageBitmap->IsWriteOnly();
|
||||
result.mSourceSurface = aImageBitmap->PrepareForDrawTarget(dt);
|
||||
|
||||
if (result.mSourceSurface) {
|
||||
result.mSize = result.mIntrinsicSize = result.mSourceSurface->GetSize();
|
||||
result.mHasSize = true;
|
||||
result.mAlphaType = IsOpaque(result.mSourceSurface->GetFormat())
|
||||
? gfxAlphaType::Opaque
|
||||
: gfxAlphaType::Premult;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> global = aImageBitmap->GetParentObject();
|
||||
if (global) {
|
||||
result.mPrincipal = global->PrincipalOrNull();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static RefPtr<SourceSurface> ScaleSourceSurface(SourceSurface& aSurface,
|
||||
const IntSize& aTargetSize) {
|
||||
const IntSize surfaceSize = aSurface.GetSize();
|
||||
|
|
|
@ -96,6 +96,7 @@ class Event;
|
|||
class HTMLImageElement;
|
||||
class HTMLCanvasElement;
|
||||
class HTMLVideoElement;
|
||||
class ImageBitmap;
|
||||
class InspectorFontFace;
|
||||
class OffscreenCanvas;
|
||||
class Selection;
|
||||
|
@ -2217,6 +2218,8 @@ class nsLayoutUtils {
|
|||
RefPtr<DrawTarget> target = nullptr;
|
||||
return SurfaceFromOffscreenCanvas(aOffscreenCanvas, aSurfaceFlags, target);
|
||||
}
|
||||
static mozilla::SurfaceFromElementResult SurfaceFromImageBitmap(
|
||||
mozilla::dom::ImageBitmap* aImageBitmap, uint32_t aSurfaceFlags);
|
||||
|
||||
static mozilla::SurfaceFromElementResult SurfaceFromElement(
|
||||
mozilla::dom::Element* aElement,
|
||||
|
@ -2261,6 +2264,11 @@ class nsLayoutUtils {
|
|||
static mozilla::SurfaceFromElementResult SurfaceFromElement(
|
||||
mozilla::dom::HTMLCanvasElement* aElement, uint32_t aSurfaceFlags,
|
||||
RefPtr<DrawTarget>& aTarget);
|
||||
static mozilla::SurfaceFromElementResult SurfaceFromElement(
|
||||
mozilla::dom::HTMLCanvasElement* aElement, uint32_t aSurfaceFlags) {
|
||||
RefPtr<DrawTarget> target = nullptr;
|
||||
return SurfaceFromElement(aElement, aSurfaceFlags, target);
|
||||
}
|
||||
static mozilla::SurfaceFromElementResult SurfaceFromElement(
|
||||
mozilla::dom::HTMLVideoElement* aElement, uint32_t aSurfaceFlags,
|
||||
RefPtr<DrawTarget>& aTarget);
|
||||
|
|
Загрузка…
Ссылка в новой задаче