Add natural size in imageCopyExternalTexture

CopyExternalTextureForBrowser() uses external texture visible rect
as source size in previous.

But video frame natural size is the only one developer could get
from HTMLVideoElement and it means the size browser present video
on screen.

This CL add "natural size" in imageCopyExternalTexture and uses this
size as CopyExternalTextureForBrowser() source size.

Bug:dawn:1694

Change-Id: I2a3bfa8e689df11d1d13320d40ad02c7090425e5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/123380
Commit-Queue: Shaobo Yan <shaobo.yan@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Yan,Shaobo 2023-03-24 03:43:56 +00:00 committed by Dawn LUCI CQ
parent a671247872
commit 8150d1cf81
4 changed files with 285 additions and 90 deletions

View File

@ -1508,7 +1508,8 @@
"tags": ["dawn"], "tags": ["dawn"],
"members": [ "members": [
{"name": "external texture", "type": "external texture"}, {"name": "external texture", "type": "external texture"},
{"name": "origin", "type": "origin 3D"} {"name": "origin", "type": "origin 3D"},
{"name": "natural size", "type": "extent 2D"}
] ]
}, },
"index format": { "index format": {

View File

@ -694,18 +694,27 @@ MaybeError ValidateCopyExternalTextureForBrowser(DeviceBase* device,
DAWN_TRY(device->ValidateObject(source->externalTexture)); DAWN_TRY(device->ValidateObject(source->externalTexture));
DAWN_TRY(source->externalTexture->ValidateCanUseInSubmitNow()); DAWN_TRY(source->externalTexture->ValidateCanUseInSubmitNow());
const Extent2D& sourceVisibleSize = source->externalTexture->GetVisibleSize(); Extent2D sourceSize;
// TODO(crbug.com/dawn/1694): Remove this workaround that use visible rect
// if natural size it not set after chromium side changes ready.
if (source->naturalSize.width == 0) {
sourceSize = source->externalTexture->GetVisibleSize();
} else {
sourceSize.width = source->naturalSize.width;
sourceSize.height = source->naturalSize.height;
}
// All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
// overflows. // overflows.
DAWN_INVALID_IF( DAWN_INVALID_IF(
static_cast<uint64_t>(source->origin.x) + static_cast<uint64_t>(copySize->width) > static_cast<uint64_t>(source->origin.x) + static_cast<uint64_t>(copySize->width) >
static_cast<uint64_t>(sourceVisibleSize.width) || static_cast<uint64_t>(sourceSize.width) ||
static_cast<uint64_t>(source->origin.y) + static_cast<uint64_t>(copySize->height) > static_cast<uint64_t>(source->origin.y) + static_cast<uint64_t>(copySize->height) >
static_cast<uint64_t>(sourceVisibleSize.height) || static_cast<uint64_t>(sourceSize.height) ||
static_cast<uint64_t>(source->origin.z) > 0, static_cast<uint64_t>(source->origin.z) > 0,
"Texture copy range (origin: %s, copySize: %s) touches outside of %s visible size (%s).", "Texture copy range (origin: %s, copySize: %s) touches outside of %s source size (%s).",
&source->origin, copySize, source->externalTexture, &sourceVisibleSize); &source->origin, copySize, source->externalTexture, &sourceSize);
DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", source->origin.z); DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", source->origin.z);
DAWN_INVALID_IF( DAWN_INVALID_IF(
options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages), options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
@ -731,8 +740,14 @@ MaybeError DoCopyExternalTextureForBrowser(DeviceBase* device,
const CopyTextureForBrowserOptions* options) { const CopyTextureForBrowserOptions* options) {
TextureInfo info; TextureInfo info;
info.origin = source->origin; info.origin = source->origin;
const Extent2D& visibleSize = source->externalTexture->GetVisibleSize(); info.size = {source->naturalSize.width, source->naturalSize.height, 1};
info.size = {visibleSize.width, visibleSize.height, 1};
// TODO(crbug.com/dawn/1694): Remove this workaround that use visible rect
// if natural size it not set after chromium side changes ready.
if (info.size.width == 0) {
const Extent2D& visibleSize = source->externalTexture->GetVisibleSize();
info.size = {visibleSize.width, visibleSize.height, 1};
}
RenderPipelineBase* pipeline; RenderPipelineBase* pipeline;
DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyExternalTextureForBrowserPipeline( DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyExternalTextureForBrowserPipeline(

View File

@ -40,7 +40,7 @@ wgpu::Texture Create2DTexture(wgpu::Device device,
static constexpr uint32_t kWidth = 4; static constexpr uint32_t kWidth = 4;
static constexpr uint32_t kHeight = 4; static constexpr uint32_t kHeight = 4;
std::array<std::array<utils::RGBA8, 4>, 4> kDefaultSourceRGBA = { std::array<std::array<utils::RGBA8, 4>, 4> kDefaultExpectedRGBA = {
std::array<utils::RGBA8, 4>( std::array<utils::RGBA8, 4>(
{utils::RGBA8::kBlack, utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kRed}), {utils::RGBA8::kBlack, utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kRed}),
std::array<utils::RGBA8, 4>( std::array<utils::RGBA8, 4>(
@ -50,6 +50,36 @@ std::array<std::array<utils::RGBA8, 4>, 4> kDefaultSourceRGBA = {
std::array<utils::RGBA8, 4>( std::array<utils::RGBA8, 4>(
{utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlue})}; {utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlue})};
std::array<std::array<utils::RGBA8, 2>, 2> kDownScaledExpectedRGBA = {
std::array<utils::RGBA8, 2>({utils::RGBA8::kBlack, utils::RGBA8::kRed}),
std::array<utils::RGBA8, 2>({utils::RGBA8::kGreen, utils::RGBA8::kBlue})};
std::array<std::array<utils::RGBA8, 8>, 8> kUpScaledExpectedRGBA = {
std::array<utils::RGBA8, 8>({utils::RGBA8::kBlack, utils::RGBA8::kBlack, utils::RGBA8::kBlack,
utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kRed,
utils::RGBA8::kRed, utils::RGBA8::kRed}),
std::array<utils::RGBA8, 8>({utils::RGBA8::kBlack, utils::RGBA8::kBlack, utils::RGBA8::kBlack,
utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kRed,
utils::RGBA8::kRed, utils::RGBA8::kRed}),
std::array<utils::RGBA8, 8>({utils::RGBA8::kBlack, utils::RGBA8::kBlack, utils::RGBA8::kBlack,
utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kRed,
utils::RGBA8::kRed, utils::RGBA8::kRed}),
std::array<utils::RGBA8, 8>({utils::RGBA8::kBlack, utils::RGBA8::kBlack, utils::RGBA8::kBlack,
utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kRed,
utils::RGBA8::kRed, utils::RGBA8::kRed}),
std::array<utils::RGBA8, 8>({utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kGreen,
utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlue,
utils::RGBA8::kBlue, utils::RGBA8::kBlue}),
std::array<utils::RGBA8, 8>({utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kGreen,
utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlue,
utils::RGBA8::kBlue, utils::RGBA8::kBlue}),
std::array<utils::RGBA8, 8>({utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kGreen,
utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlue,
utils::RGBA8::kBlue, utils::RGBA8::kBlue}),
std::array<utils::RGBA8, 8>({utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kGreen,
utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlue,
utils::RGBA8::kBlue, utils::RGBA8::kBlue})};
template <typename Parent> template <typename Parent>
class CopyExternalTextureForBrowserTests : public Parent { class CopyExternalTextureForBrowserTests : public Parent {
protected: protected:
@ -123,9 +153,10 @@ class CopyExternalTextureForBrowserTests : public Parent {
return this->device.CreateExternalTexture(&externalDesc); return this->device.CreateExternalTexture(&externalDesc);
} }
std::vector<utils::RGBA8> GetDefaultExpectedData(bool flipY, std::vector<utils::RGBA8> GetExpectedData(bool flipY,
wgpu::Origin3D srcOrigin, wgpu::Origin3D srcOrigin,
wgpu::Extent3D rect) { wgpu::Extent3D rect,
wgpu::Extent2D naturalSize) {
std::vector<utils::RGBA8> expected; std::vector<utils::RGBA8> expected;
for (uint32_t rowInRect = 0; rowInRect < rect.height; ++rowInRect) { for (uint32_t rowInRect = 0; rowInRect < rect.height; ++rowInRect) {
for (uint32_t colInRect = 0; colInRect < rect.width; ++colInRect) { for (uint32_t colInRect = 0; colInRect < rect.width; ++colInRect) {
@ -136,7 +167,14 @@ class CopyExternalTextureForBrowserTests : public Parent {
row = (rect.height - rowInRect - 1) + srcOrigin.y; row = (rect.height - rowInRect - 1) + srcOrigin.y;
} }
expected.push_back(kDefaultSourceRGBA[row][col]); // Upscale case
if (naturalSize.width > kWidth) {
expected.push_back(kUpScaledExpectedRGBA[row][col]);
} else if (naturalSize.width < kWidth) {
expected.push_back(kDownScaledExpectedRGBA[row][col]);
} else {
expected.push_back(kDefaultExpectedRGBA[row][col]);
}
} }
} }
@ -144,16 +182,67 @@ class CopyExternalTextureForBrowserTests : public Parent {
} }
}; };
using FlipY = bool; enum class CopyRect {
using SrcOrigin = wgpu::Origin3D; TopLeft,
using DstOrigin = wgpu::Origin3D; TopRight,
BottomLeft,
BottomRight,
FullSize,
};
std::ostream& operator<<(std::ostream& o, wgpu::Origin3D origin) { enum class ScaleType {
o << origin.x << ", " << origin.y << ", " << origin.z; UpScale,
DownScale,
NoScale,
};
using FlipY = bool;
using CopySrcRect = CopyRect;
using CopyDstRect = CopyRect;
std::ostream& operator<<(std::ostream& o, ScaleType scaleType) {
switch (scaleType) {
case ScaleType::UpScale:
o << "UpScale";
break;
case ScaleType::DownScale:
o << "DownScale";
break;
case ScaleType::NoScale:
o << "DefaultSize";
break;
default:
UNREACHABLE();
break;
}
return o; return o;
} }
DAWN_TEST_PARAM_STRUCT(CopyTestParams, SrcOrigin, DstOrigin, FlipY); std::ostream& operator<<(std::ostream& o, CopyRect copyRect) {
switch (copyRect) {
case CopyRect::TopLeft:
o << "TopLeftCopy";
break;
case CopyRect::TopRight:
o << "TopRightCopy";
break;
case CopyRect::BottomLeft:
o << "BottomLeftCopy";
break;
case CopyRect::BottomRight:
o << "BottomRightCopy";
break;
case CopyRect::FullSize:
o << "FullSizeCopy";
break;
default:
UNREACHABLE();
break;
}
return o;
}
DAWN_TEST_PARAM_STRUCT(CopyTestParams, CopySrcRect, CopyDstRect, ScaleType, FlipY);
class CopyExternalTextureForBrowserTests_Basic class CopyExternalTextureForBrowserTests_Basic
: public CopyExternalTextureForBrowserTests<DawnTestWithParams<CopyTestParams>> { : public CopyExternalTextureForBrowserTests<DawnTestWithParams<CopyTestParams>> {
@ -161,23 +250,27 @@ class CopyExternalTextureForBrowserTests_Basic
void DoBasicCopyTest(const wgpu::Origin3D& srcOrigin, void DoBasicCopyTest(const wgpu::Origin3D& srcOrigin,
const wgpu::Origin3D& dstOrigin, const wgpu::Origin3D& dstOrigin,
const wgpu::Extent3D& copySize, const wgpu::Extent3D& copySize,
const wgpu::Extent2D& naturalSize,
const wgpu::Extent3D& dstTextureSize,
const wgpu::CopyTextureForBrowserOptions options = {}) { const wgpu::CopyTextureForBrowserOptions options = {}) {
wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture(); wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
wgpu::ImageCopyExternalTexture srcImageCopyExternalTexture; wgpu::ImageCopyExternalTexture srcImageCopyExternalTexture;
srcImageCopyExternalTexture.externalTexture = externalTexture; srcImageCopyExternalTexture.externalTexture = externalTexture;
srcImageCopyExternalTexture.origin = srcOrigin; srcImageCopyExternalTexture.origin = srcOrigin;
srcImageCopyExternalTexture.naturalSize = naturalSize;
wgpu::Texture dstTexture = wgpu::Texture dstTexture = Create2DTexture(
Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::RGBA8Unorm, device, dstTextureSize.width, dstTextureSize.height, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc |
wgpu::TextureUsage::CopyDst); wgpu::TextureUsage::CopyDst);
wgpu::ImageCopyTexture dstImageCopyTexture = wgpu::ImageCopyTexture dstImageCopyTexture =
utils::CreateImageCopyTexture(dstTexture, 0, dstOrigin); utils::CreateImageCopyTexture(dstTexture, 0, dstOrigin);
queue.CopyExternalTextureForBrowser(&srcImageCopyExternalTexture, &dstImageCopyTexture, queue.CopyExternalTextureForBrowser(&srcImageCopyExternalTexture, &dstImageCopyTexture,
&copySize, &options); &copySize, &options);
std::vector<utils::RGBA8> expected =
GetDefaultExpectedData(options.flipY, srcOrigin, copySize); std::vector<utils::RGBA8> expected = GetExpectedData(
options.flipY, srcImageCopyExternalTexture.origin, copySize, naturalSize);
EXPECT_TEXTURE_EQ(expected.data(), dstTexture, dstOrigin, copySize); EXPECT_TEXTURE_EQ(expected.data(), dstTexture, dstOrigin, copySize);
} }
@ -191,22 +284,104 @@ TEST_P(CopyExternalTextureForBrowserTests_Basic, Copy) {
wgpu::CopyTextureForBrowserOptions options = {}; wgpu::CopyTextureForBrowserOptions options = {};
options.flipY = GetParam().mFlipY; options.flipY = GetParam().mFlipY;
wgpu::Origin3D srcOrigin = GetParam().mSrcOrigin; CopyRect srcCopyRect = GetParam().mCopySrcRect;
wgpu::Origin3D dstOrigin = GetParam().mDstOrigin; CopyRect dstCopyRect = GetParam().mCopyDstRect;
ScaleType scaleType = GetParam().mScaleType;
wgpu::Extent3D copySize = {kWidth, kHeight}; // Test skip due to crbug.com/dawn/1719
DAWN_SUPPRESS_TEST_IF(IsWARP() && srcCopyRect != CopyRect::TopLeft &&
srcCopyRect != CopyRect::FullSize && dstCopyRect != CopyRect::TopLeft &&
dstCopyRect != CopyRect::FullSize && scaleType == ScaleType::DownScale);
if (srcOrigin.x != 0 || srcOrigin.y != 0 || dstOrigin.x != 0 || dstOrigin.y != 0) { float scaleFactor = 1.0;
copySize.width = kWidth / 2;
copySize.height = kHeight / 2; switch (scaleType) {
case ScaleType::UpScale:
scaleFactor = 2.0;
break;
case ScaleType::DownScale:
scaleFactor = 0.5;
break;
case ScaleType::NoScale:
break;
default:
UNREACHABLE();
break;
} }
DoBasicCopyTest(srcOrigin, dstOrigin, copySize, options); float defaultWidth = static_cast<float>(kWidth);
float defaultHeight = static_cast<float>(kHeight);
wgpu::Extent2D naturalSize = {static_cast<uint32_t>(defaultWidth * scaleFactor),
static_cast<uint32_t>(defaultHeight * scaleFactor)};
wgpu::Origin3D srcOrigin = {};
wgpu::Origin3D dstOrigin = {};
// Set copy size to sub rect copy size.
wgpu::Extent3D copySize = {naturalSize.width / 2, naturalSize.height / 2};
switch (srcCopyRect) {
// origin = {0, 0};
case CopyRect::TopLeft:
break;
case CopyRect::TopRight:
srcOrigin.x = naturalSize.width / 2;
srcOrigin.y = 0;
break;
case CopyRect::BottomLeft:
srcOrigin.x = 0;
srcOrigin.y = naturalSize.height / 2;
break;
case CopyRect::BottomRight:
srcOrigin.x = naturalSize.width / 2;
srcOrigin.y = naturalSize.height / 2;
break;
// origin = {0, 0}, copySize = naturalSize
case CopyRect::FullSize:
copySize.width = naturalSize.width;
copySize.height = naturalSize.height;
break;
default:
UNREACHABLE();
break;
}
wgpu::Extent3D dstTextureSize = {copySize.width * 2, copySize.height * 2};
switch (dstCopyRect) {
case CopyRect::TopLeft:
break;
case CopyRect::TopRight:
dstOrigin.x = dstTextureSize.width / 2;
dstOrigin.y = 0;
break;
case CopyRect::BottomLeft:
dstOrigin.x = 0;
dstOrigin.y = dstTextureSize.height / 2;
break;
case CopyRect::BottomRight:
dstOrigin.x = dstTextureSize.width / 2;
dstOrigin.y = dstTextureSize.height / 2;
break;
case CopyRect::FullSize:
if (srcCopyRect != CopyRect::FullSize) {
dstTextureSize.width = copySize.width;
dstTextureSize.height = copySize.height;
}
break;
default:
UNREACHABLE();
break;
}
DoBasicCopyTest(srcOrigin, dstOrigin, copySize, naturalSize, dstTextureSize, options);
} }
DAWN_INSTANTIATE_TEST_P(CopyExternalTextureForBrowserTests_Basic, DAWN_INSTANTIATE_TEST_P(
{D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), CopyExternalTextureForBrowserTests_Basic,
VulkanBackend()}, {D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()},
std::vector<wgpu::Origin3D>({{0, 0}, {2, 0}, {0, 2}, {2, 2}}), std::vector<CopyRect>({CopyRect::TopLeft, CopyRect::TopRight, CopyRect::BottomLeft,
std::vector<wgpu::Origin3D>({{0, 0}, {2, 0}, {0, 2}, {2, 2}}), CopyRect::BottomRight, CopyRect::FullSize}),
std::vector<bool>({false, true})); std::vector<CopyRect>({CopyRect::TopLeft, CopyRect::TopRight, CopyRect::BottomLeft,
CopyRect::BottomRight, CopyRect::FullSize}),
std::vector<ScaleType>({ScaleType::UpScale, ScaleType::DownScale, ScaleType::NoScale}),
std::vector<FlipY>({false, true}));

View File

@ -115,6 +115,7 @@ class CopyExternalTextureForBrowserTest : public ValidationTest {
void TestCopyExternalTextureForBrowser(utils::Expectation expectation, void TestCopyExternalTextureForBrowser(utils::Expectation expectation,
wgpu::ExternalTexture srcExternalTexture, wgpu::ExternalTexture srcExternalTexture,
wgpu::Origin3D srcOrigin, wgpu::Origin3D srcOrigin,
wgpu::Extent2D srcNaturalSize,
wgpu::Texture dstTexture, wgpu::Texture dstTexture,
uint32_t dstLevel, uint32_t dstLevel,
wgpu::Origin3D dstOrigin, wgpu::Origin3D dstOrigin,
@ -124,6 +125,7 @@ class CopyExternalTextureForBrowserTest : public ValidationTest {
wgpu::ImageCopyExternalTexture srcImageCopyExternalTexture; wgpu::ImageCopyExternalTexture srcImageCopyExternalTexture;
srcImageCopyExternalTexture.externalTexture = srcExternalTexture; srcImageCopyExternalTexture.externalTexture = srcExternalTexture;
srcImageCopyExternalTexture.origin = srcOrigin; srcImageCopyExternalTexture.origin = srcOrigin;
srcImageCopyExternalTexture.naturalSize = srcNaturalSize;
wgpu::ImageCopyTexture dstImageCopyTexture = wgpu::ImageCopyTexture dstImageCopyTexture =
utils::CreateImageCopyTexture(dstTexture, dstLevel, dstOrigin, aspect); utils::CreateImageCopyTexture(dstTexture, dstLevel, dstOrigin, aspect);
@ -528,42 +530,42 @@ TEST_F(CopyExternalTextureForBrowserTest, Success) {
// Different copies, including some that touch the OOB condition // Different copies, including some that touch the OOB condition
{ {
// Copy a region along top left boundary // Copy a region along top left boundary
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}); destination, 0, {0, 0, 0}, {4, 4, 1});
// Copy entire texture // Copy entire texture
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {16, 16, 1}); destination, 0, {0, 0, 0}, {16, 16, 1});
// Copy a region along bottom right boundary // Copy a region along bottom right boundary
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {8, 8, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {8, 8, 0}, {16, 16},
destination, 0, {8, 8, 0}, {8, 8, 1}); destination, 0, {8, 8, 0}, {8, 8, 1});
// Copy region into mip // Copy region into mip
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 2, {0, 0, 0}, {4, 4, 1}); destination, 2, {0, 0, 0}, {4, 4, 1});
// Copy between slices // Copy between slices
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 1}, {16, 16, 1}); destination, 0, {0, 0, 1}, {16, 16, 1});
} }
// Empty copies are valid // Empty copies are valid
{ {
// An empty copy // An empty copy
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {0, 0, 1}); destination, 0, {0, 0, 0}, {0, 0, 1});
// An empty copy with depth = 0 // An empty copy with depth = 0
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {0, 0, 0}); destination, 0, {0, 0, 0}, {0, 0, 0});
// An empty copy touching the side of the source texture // An empty copy touching the side of the source texture
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {16, 16, 0}, {0, 0, 1}); destination, 0, {16, 16, 0}, {0, 0, 1});
// An empty copy touching the side of the destination texture // An empty copy touching the side of the destination texture
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {16, 16, 0}, {0, 0, 1}); destination, 0, {16, 16, 0}, {0, 0, 1});
} }
} }
@ -582,12 +584,12 @@ TEST_F(CopyExternalTextureForBrowserTest, IncorrectUsage) {
wgpu::TextureUsage::RenderAttachment); wgpu::TextureUsage::RenderAttachment);
// Incorrect destination usage causes failure: lack |RenderAttachement| usage. // Incorrect destination usage causes failure: lack |RenderAttachement| usage.
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, validSource, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, validSource, {0, 0, 0}, {16, 16},
noRenderAttachmentUsageDestination, 0, {0, 0, 0}, noRenderAttachmentUsageDestination, 0, {0, 0, 0},
{16, 16, 1}); {16, 16, 1});
// Incorrect destination usage causes failure: lack |CopyDst| usage. // Incorrect destination usage causes failure: lack |CopyDst| usage.
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, validSource, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, validSource, {0, 0, 0}, {16, 16},
noCopyDstUsageSource, 0, {0, 0, 0}, {16, 16, 1}); noCopyDstUsageSource, 0, {0, 0, 0}, {16, 16, 1});
} }
@ -601,12 +603,12 @@ TEST_F(CopyExternalTextureForBrowserTest, DestroyedTexture) {
wgpu::Texture destination = wgpu::Texture destination =
Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm, Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment); wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
// Check noop copy // Check noop copy
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {0, 0, 0}, destination, 0, {0, 0, 0}, {0, 0, 0},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
} }
@ -618,12 +620,12 @@ TEST_F(CopyExternalTextureForBrowserTest, DestroyedTexture) {
Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm, Create2DTexture(device, 16, 16, 5, 4, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment); wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
source.Destroy(); source.Destroy();
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
// Check noop copy // Check noop copy
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {0, 0, 0}, destination, 0, {0, 0, 0}, {0, 0, 0},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
} }
@ -636,12 +638,12 @@ TEST_F(CopyExternalTextureForBrowserTest, DestroyedTexture) {
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment); wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
destination.Destroy(); destination.Destroy();
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
// Check noop copy // Check noop copy
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {0, 0, 0}, destination, 0, {0, 0, 0}, {0, 0, 0},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
} }
@ -657,46 +659,46 @@ TEST_F(CopyExternalTextureForBrowserTest, OutOfBounds) {
// OOB on source // OOB on source
{ {
// x + width overflows // x + width overflows
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {1, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {1, 0, 0}, {4, 4},
destination, 0, {0, 0, 0}, {16, 16, 1}); destination, 0, {0, 0, 0}, {4, 4, 1});
// y + height overflows // y + height overflows
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 1, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 1, 0}, {4, 4},
destination, 0, {0, 0, 0}, {16, 16, 1}); destination, 0, {0, 0, 0}, {4, 4, 1});
// copy to multiple slices // copy to multiple slices
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {4, 4},
destination, 0, {0, 0, 2}, {16, 16, 2}); destination, 0, {0, 0, 2}, {4, 4, 2});
// copy origin z value is non-zero. // copy origin z value is non-zero.
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 1}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 1}, {4, 4},
destination, 0, {0, 0, 2}, {16, 16, 1}); destination, 0, {0, 0, 2}, {4, 4, 1});
} }
// OOB on destination // OOB on destination
{ {
// x + width overflows // x + width overflows
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {1, 0, 0}, {16, 16, 1}); destination, 0, {1, 0, 0}, {16, 16, 1});
// y + height overflows // y + height overflows
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 1, 0}, {16, 16, 1}); destination, 0, {0, 1, 0}, {16, 16, 1});
// non-zero mip overflows // non-zero mip overflows
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 1, {0, 0, 0}, {9, 9, 1}); destination, 1, {0, 0, 0}, {9, 9, 1});
// arrayLayer + depth OOB // arrayLayer + depth OOB
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 4}, {16, 16, 1}); destination, 0, {0, 0, 4}, {16, 16, 1});
// empty copy on non-existent mip fails // empty copy on non-existent mip fails
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 6, {0, 0, 0}, {0, 0, 1}); destination, 6, {0, 0, 0}, {0, 0, 1});
// empty copy on non-existent slice fails // empty copy on non-existent slice fails
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 4}, {0, 0, 1}); destination, 0, {0, 0, 4}, {0, 0, 1});
} }
} }
@ -709,8 +711,8 @@ TEST_F(CopyExternalTextureForBrowserTest, InvalidDstFormat) {
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment); wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
// Not supported dst texture format. // Not supported dst texture format.
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, destination, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
0, {0, 0, 0}, {0, 0, 1}); destination, 0, {0, 0, 0}, {0, 0, 1});
} }
// Test destination texture are multisampled. // Test destination texture are multisampled.
@ -721,7 +723,7 @@ TEST_F(CopyExternalTextureForBrowserTest, InvalidSampleCount) {
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment, 4); wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment, 4);
// An empty copy with dst texture sample count > 1 failure. // An empty copy with dst texture sample count > 1 failure.
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destinationMultiSampled4x, 0, {0, 0, 0}, {0, 0, 1}); destinationMultiSampled4x, 0, {0, 0, 0}, {0, 0, 1});
} }
@ -744,14 +746,14 @@ TEST_F(CopyExternalTextureForBrowserTest, ColorSpaceConversion_ColorSpace) {
validOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data(); validOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data();
validOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data(); validOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data();
validOptions.conversionMatrix = conversionMatrix.data(); validOptions.conversionMatrix = conversionMatrix.data();
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, validOptions); wgpu::TextureAspect::All, validOptions);
// if no color space conversion, no need to validate related attributes // if no color space conversion, no need to validate related attributes
wgpu::CopyTextureForBrowserOptions noColorSpaceConversion = options; wgpu::CopyTextureForBrowserOptions noColorSpaceConversion = options;
noColorSpaceConversion.needsColorSpaceConversion = false; noColorSpaceConversion.needsColorSpaceConversion = false;
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, noColorSpaceConversion); wgpu::TextureAspect::All, noColorSpaceConversion);
} }
@ -765,13 +767,13 @@ TEST_F(CopyExternalTextureForBrowserTest, ColorSpaceConversion_ColorSpace) {
std::array<float, 9> conversionMatrix = {}; std::array<float, 9> conversionMatrix = {};
invalidOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data(); invalidOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data();
invalidOptions.conversionMatrix = conversionMatrix.data(); invalidOptions.conversionMatrix = conversionMatrix.data();
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, invalidOptions); wgpu::TextureAspect::All, invalidOptions);
// set to nullptr // set to nullptr
invalidOptions.srcTransferFunctionParameters = nullptr; invalidOptions.srcTransferFunctionParameters = nullptr;
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, invalidOptions); wgpu::TextureAspect::All, invalidOptions);
} }
@ -783,13 +785,13 @@ TEST_F(CopyExternalTextureForBrowserTest, ColorSpaceConversion_ColorSpace) {
std::array<float, 9> conversionMatrix = {}; std::array<float, 9> conversionMatrix = {};
invalidOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data(); invalidOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data();
invalidOptions.conversionMatrix = conversionMatrix.data(); invalidOptions.conversionMatrix = conversionMatrix.data();
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, invalidOptions); wgpu::TextureAspect::All, invalidOptions);
// set to nullptr // set to nullptr
invalidOptions.dstTransferFunctionParameters = nullptr; invalidOptions.dstTransferFunctionParameters = nullptr;
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, invalidOptions); wgpu::TextureAspect::All, invalidOptions);
} }
@ -801,13 +803,13 @@ TEST_F(CopyExternalTextureForBrowserTest, ColorSpaceConversion_ColorSpace) {
std::array<float, 7> dstTransferFunctionParameters = {}; std::array<float, 7> dstTransferFunctionParameters = {};
invalidOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data(); invalidOptions.srcTransferFunctionParameters = srcTransferFunctionParameters.data();
invalidOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data(); invalidOptions.dstTransferFunctionParameters = dstTransferFunctionParameters.data();
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, invalidOptions); wgpu::TextureAspect::All, invalidOptions);
// set to nullptr // set to nullptr
invalidOptions.conversionMatrix = nullptr; invalidOptions.conversionMatrix = nullptr;
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, invalidOptions); wgpu::TextureAspect::All, invalidOptions);
} }
@ -827,28 +829,28 @@ TEST_F(CopyExternalTextureForBrowserTest, ColorSpaceConversion_TextureAlphaState
options.srcAlphaMode = wgpu::AlphaMode::Premultiplied; options.srcAlphaMode = wgpu::AlphaMode::Premultiplied;
options.dstAlphaMode = wgpu::AlphaMode::Premultiplied; options.dstAlphaMode = wgpu::AlphaMode::Premultiplied;
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
options.srcAlphaMode = wgpu::AlphaMode::Premultiplied; options.srcAlphaMode = wgpu::AlphaMode::Premultiplied;
options.dstAlphaMode = wgpu::AlphaMode::Unpremultiplied; options.dstAlphaMode = wgpu::AlphaMode::Unpremultiplied;
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
options.srcAlphaMode = wgpu::AlphaMode::Unpremultiplied; options.srcAlphaMode = wgpu::AlphaMode::Unpremultiplied;
options.dstAlphaMode = wgpu::AlphaMode::Premultiplied; options.dstAlphaMode = wgpu::AlphaMode::Premultiplied;
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
options.srcAlphaMode = wgpu::AlphaMode::Unpremultiplied; options.srcAlphaMode = wgpu::AlphaMode::Unpremultiplied;
options.dstAlphaMode = wgpu::AlphaMode::Unpremultiplied; options.dstAlphaMode = wgpu::AlphaMode::Unpremultiplied;
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
destination, 0, {0, 0, 0}, {4, 4, 1}, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::All, options); wgpu::TextureAspect::All, options);
} }
@ -926,8 +928,9 @@ TEST_F(CopyExternalTextureForBrowserTest, InternalUsage) {
// usage option is on // usage option is on
wgpu::CopyTextureForBrowserOptions options = {}; wgpu::CopyTextureForBrowserOptions options = {};
options.internalUsage = true; options.internalUsage = true;
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, destination, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
0, {0, 0, 0}, {16, 16, 1}, wgpu::TextureAspect::All, options); destination, 0, {0, 0, 0}, {16, 16, 1},
wgpu::TextureAspect::All, options);
} }
// Test that the internal usages are taken into account when interalUsage = true // Test that the internal usages are taken into account when interalUsage = true
@ -941,12 +944,13 @@ TEST_F(CopyExternalTextureForBrowserInternalUsageTest, InternalUsage) {
wgpu::TextureUsage::CopyDst, 1, &internalDesc); wgpu::TextureUsage::CopyDst, 1, &internalDesc);
// Without internal usage option should fail usage validation // Without internal usage option should fail usage validation
TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, destination, TestCopyExternalTextureForBrowser(utils::Expectation::Failure, source, {0, 0, 0}, {16, 16},
0, {0, 0, 0}, {16, 16, 1}); destination, 0, {0, 0, 0}, {16, 16, 1});
// With internal usage option should pass usage validation // With internal usage option should pass usage validation
wgpu::CopyTextureForBrowserOptions options = {}; wgpu::CopyTextureForBrowserOptions options = {};
options.internalUsage = true; options.internalUsage = true;
TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, destination, TestCopyExternalTextureForBrowser(utils::Expectation::Success, source, {0, 0, 0}, {16, 16},
0, {0, 0, 0}, {16, 16, 1}, wgpu::TextureAspect::All, options); destination, 0, {0, 0, 0}, {16, 16, 1},
wgpu::TextureAspect::All, options);
} }