blob: dbf1f56a5946e5dcfd556f9f67df0f163c09ede7 [file] [log] [blame]
// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "dawn/native/ExternalTexture.h"
#include <algorithm>
#include <utility>
#include "dawn/common/Log.h"
#include "dawn/native/Buffer.h"
#include "dawn/native/Device.h"
#include "dawn/native/ObjectType_autogen.h"
#include "dawn/native/Queue.h"
#include "dawn/native/Texture.h"
#include "dawn/native/dawn_platform.h"
#include "dawn/native/utils/WGPUHelpers.h"
namespace dawn::native {
MaybeError ValidateExternalTexturePlane(const TextureViewBase* textureView) {
DAWN_INVALID_IF(
(textureView->GetUsage() & wgpu::TextureUsage::TextureBinding) == 0,
"The external texture plane (%s) usage (%s) doesn't include the required usage (%s)",
textureView, textureView->GetUsage(), wgpu::TextureUsage::TextureBinding);
DAWN_INVALID_IF(textureView->GetDimension() != wgpu::TextureViewDimension::e2D,
"The external texture plane (%s) dimension (%s) is not 2D.", textureView,
textureView->GetDimension());
DAWN_INVALID_IF(textureView->GetLevelCount() > 1,
"The external texture plane (%s) mip level count (%u) is not 1.", textureView,
textureView->GetLevelCount());
DAWN_INVALID_IF(textureView->GetTexture()->GetSampleCount() != 1,
"The external texture plane (%s) sample count (%u) is not one.", textureView,
textureView->GetTexture()->GetSampleCount());
return {};
}
MaybeError ValidateExternalTextureDescriptor(const DeviceBase* device,
const ExternalTextureDescriptor* descriptor) {
DAWN_ASSERT(descriptor);
DAWN_ASSERT(descriptor->plane0);
DAWN_TRY(device->ValidateObject(descriptor->plane0));
DAWN_INVALID_IF(!descriptor->gamutConversionMatrix,
"The gamut conversion matrix must be non-null.");
DAWN_INVALID_IF(!descriptor->srcTransferFunctionParameters,
"The source transfer function parameters must be non-null.");
DAWN_INVALID_IF(!descriptor->dstTransferFunctionParameters,
"The destination transfer function parameters must be non-null.");
DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane0));
auto CheckPlaneFormat = [](const DeviceBase* device, const Format& format,
uint32_t requiredComponentCount) -> MaybeError {
DAWN_INVALID_IF(format.aspects != Aspect::Color, "The format (%s) is not a color format.",
format.format);
// The Unorm16FormatsForExternalTexture feature allows using R/RG16Unorm to create
// ExternalTextures even if they are not filterable float. This is a hack to allow YUV HDR
// SharedTextureMemory to be used without enabling R/RG16Unorm for anything else.
bool skipUnorm16 = device->HasFeature(Feature::Unorm16FormatsForExternalTexture) &&
(format.format == wgpu::TextureFormat::R16Unorm ||
format.format == wgpu::TextureFormat::RG16Unorm);
if (!skipUnorm16) {
DAWN_INVALID_IF(!IsSubset(SampleTypeBit::Float,
format.GetAspectInfo(Aspect::Color).supportedSampleTypes),
"The format (%s) is not filterable float.", format.format);
}
DAWN_INVALID_IF(format.componentCount != requiredComponentCount,
"The format (%s) component count (%u) is not %u.", format.format,
format.componentCount, requiredComponentCount);
return {};
};
if (descriptor->plane1) {
DAWN_INVALID_IF(
!descriptor->yuvToRgbConversionMatrix,
"When more than one plane is set, the YUV-to-RGB conversion matrix must be non-null.");
DAWN_TRY(device->ValidateObject(descriptor->plane1));
DAWN_TRY(ValidateExternalTexturePlane(descriptor->plane1));
// Y + UV case.
DAWN_TRY_CONTEXT(CheckPlaneFormat(device, descriptor->plane0->GetFormat(), 1),
"validating the format of plane 0 (%s)", descriptor->plane0);
DAWN_TRY_CONTEXT(CheckPlaneFormat(device, descriptor->plane1->GetFormat(), 2),
"validating the format of plane 1 (%s)", descriptor->plane1);
} else if (descriptor->plane0->GetFormat().format == wgpu::TextureFormat::OpaqueYCbCrAndroid) {
// Special case for OpaqueYCbCrAndroid
DAWN_INVALID_IF(!device->HasFeature(Feature::OpaqueYCbCrAndroidForExternalTexture),
"%s isn't enabled while plane0 (%s) has format %s.",
wgpu::FeatureName::OpaqueYCbCrAndroidForExternalTexture, descriptor->plane0,
wgpu::TextureFormat::OpaqueYCbCrAndroid);
DAWN_INVALID_IF(descriptor->plane0->HasYCbCrDescriptor(),
"%s was created with a YCbCrVkDescriptor.", descriptor->plane0);
} else {
// RGBA case.
DAWN_TRY_CONTEXT(CheckPlaneFormat(device, descriptor->plane0->GetFormat(), 4),
"validating the format of plane 0 (%s)", descriptor->plane0);
}
DAWN_INVALID_IF(descriptor->cropSize.width == 0 || descriptor->cropSize.height == 0,
"cropSize %s has 0 on width or height.", descriptor->cropSize);
const Extent3D textureSize = descriptor->plane0->GetSingleSubresourceVirtualSize();
DAWN_INVALID_IF(descriptor->cropSize.width > textureSize.width ||
descriptor->cropSize.height > textureSize.height,
"cropSize %s exceeds the texture size, defined by Plane0 size (%u, %u).",
descriptor->cropSize, textureSize.width, textureSize.height);
DAWN_INVALID_IF(descriptor->cropOrigin.x > textureSize.width - descriptor->cropSize.width ||
descriptor->cropOrigin.y > textureSize.height - descriptor->cropSize.height,
"cropRect[Origin: %s, Size: %s] exceeds plane0's size (%u, %u).",
descriptor->cropOrigin, descriptor->cropSize, textureSize.width,
textureSize.height);
DAWN_INVALID_IF(descriptor->apparentSize.width == 0 || descriptor->apparentSize.height == 0,
"apparentSize (%u, %u) is empty.", descriptor->apparentSize.width,
descriptor->apparentSize.height);
DAWN_INVALID_IF(descriptor->apparentSize.width > device->GetLimits().v1.maxTextureDimension2D,
"apparentSize.width (%u) is larger than maxTextureDimension2D (%u)",
descriptor->apparentSize.width, device->GetLimits().v1.maxTextureDimension2D);
DAWN_INVALID_IF(descriptor->apparentSize.height > device->GetLimits().v1.maxTextureDimension2D,
"apparentSize.height (%u) is larger than maxTextureDimension2D (%u)",
descriptor->apparentSize.height, device->GetLimits().v1.maxTextureDimension2D);
return {};
}
namespace {
ExternalTextureParams ComputeExternalTextureParams(const ExternalTextureDescriptor* descriptor) {
using math::Mat3x2f;
using math::Mat3x3f;
using math::Vec2f;
using math::Vec2u;
ExternalTextureParams params;
params.numPlanes = descriptor->plane1 == nullptr ? 1 : 2;
params.doYuvToRgbConversionOnly = descriptor->doYuvToRgbConversionOnly ? 1 : 0;
// YUV-to-RGB conversion is performed by multiplying the source YUV values with a 4x3 matrix
// passed from Chromium. The matrix was originally sourced from /skia/src/core/SkYUVMath.cpp.
// This matrix is only used in multiplanar scenarios.
if (params.numPlanes == 2) {
DAWN_ASSERT(descriptor->yuvToRgbConversionMatrix);
const float* yMat = descriptor->yuvToRgbConversionMatrix;
std::copy(yMat, yMat + 12, params.yuvToRgbConversionMatrix.begin());
}
// Gamut correction is performed by multiplying a 3x3 matrix passed from Chromium. The
// matrix was computed by multiplying the appropriate source and destination gamut
// matrices sourced from ui/gfx/color_space.cc.
const float* gMat = descriptor->gamutConversionMatrix;
params.gamutConversionMatrix = {gMat[0], gMat[1], gMat[2], 0.0f, //
gMat[3], gMat[4], gMat[5], 0.0f, //
gMat[6], gMat[7], gMat[8], 0.0f};
// Gamma decode/encode is performed by the logic:
// if (abs(v) < params.D) {
// return sign(v) * (params.C * abs(v) + params.F);
// }
// return pow(A * x + B, G) + E
//
// Constants are passed from Chromium and originally sourced from ui/gfx/color_space.cc
const float* srcFn = descriptor->srcTransferFunctionParameters;
std::copy(srcFn, srcFn + 7, params.gammaDecodingParams.begin());
const float* dstFn = descriptor->dstTransferFunctionParameters;
std::copy(dstFn, dstFn + 7, params.gammaEncodingParams.begin());
// Compute the various transforms and bounds used for sampling and loading operations. They make
// them appear as if operating on a `apparentSize` texture but instead they are all happening in
// a transformed and cropped rectangle of the planes. Perform all 2D operations in homogeneous
// coordinates (in 3D with Z fixed to 1) so that translations can be expressed with matrices.
// Extract all the relevant sizes as float to avoid extra casts in later computations.
Extent3D plane0Extent = descriptor->plane0->GetSingleSubresourceVirtualSize();
Extent3D plane1Extent = {1, 1, 1};
if (params.numPlanes == 2) {
plane1Extent = descriptor->plane1->GetSingleSubresourceVirtualSize();
}
auto plane0Size = Vec2f(plane0Extent.width, plane0Extent.height);
auto plane1Size = Vec2f(plane1Extent.width, plane1Extent.height);
auto cropOrigin = Vec2f(descriptor->cropOrigin.x, descriptor->cropOrigin.y);
auto cropSize = Vec2f(descriptor->cropSize.width, descriptor->cropSize.height);
// Offset the coordinates so the center texel is at the origin, so we can apply rotations and
// y-flips. After translation, coordinates range from [-0.5 .. +0.5] in both U and V.
Mat3x3f sampleTransform = Mat3x3f::Translation({-0.5, -0.5});
// The video frame metadata both rotation and mirroring information. The rotation happens before
// the mirroring when processing the video frame, so do the inverse order when converting UV
// coordinates.
if (descriptor->mirrored) {
sampleTransform = Mul(Mat3x3f::ScaleHomogeneous({-1, 1}), sampleTransform);
}
// Apply rotations as needed for the sampling coordinate. This may also rotate the
// shader-apparent size of the texture.
Vec2u loadBounds = {descriptor->apparentSize.width - 1, descriptor->apparentSize.height - 1};
switch (descriptor->rotation) {
case wgpu::ExternalTextureRotation::Rotate0Degrees:
break;
case wgpu::ExternalTextureRotation::Rotate90Degrees:
std::swap(loadBounds[0], loadBounds[1]);
sampleTransform = Mul(Mat3x3f({0, -1, 0}, // x -> -y'
{+1, 0, 0}, // y -> x'
{0, 0, 1}),
sampleTransform);
break;
case wgpu::ExternalTextureRotation::Rotate180Degrees:
sampleTransform = Mul(Mat3x3f({-1, 0, 0}, // x -> -x'
{0, -1, 0}, // y -> -y'
{0, 0, 1}),
sampleTransform);
break;
case wgpu::ExternalTextureRotation::Rotate270Degrees:
std::swap(loadBounds[0], loadBounds[1]);
sampleTransform = Mul(Mat3x3f({0, 1, 0}, // x -> y
{-1, 0, 0}, // y -> -x'
{0, 0, 1}),
sampleTransform);
break;
}
// Offset the coordinates so the bottom-left texel is at origin.
// After translation, coordinates range from [0 .. 1] in both U and V.
sampleTransform = Mul(Mat3x3f::Translation({0.5, 0.5}), sampleTransform);
// Finally, scale and translate based on the crop rect.
Vec2f rectScale = cropSize / plane0Size;
Vec2f rectOffset = cropOrigin / plane0Size;
sampleTransform = Mul(Mat3x3f::ScaleHomogeneous(rectScale), sampleTransform);
sampleTransform = Mul(Mat3x3f::Translation(rectOffset), sampleTransform);
params.sampleTransform = Mat3x2f::CropOrExpandFrom(sampleTransform);
// Compute the load transformation matrix by using toTexels * sampleTransform * toNormalized
// Note that coords starts from 0 so the max value is size - 1. Note that we use at least 1 for
// the loadBounds to avoid a division by 0 (since it is not possible to map [0, 0] to [0, 1]).
// The load coordinate will be set to 0 because of clamping in the shader so it will stay 0
// after normalization.
{
Mat3x3f toTexels = Mat3x3f::ScaleHomogeneous(plane0Size - Vec2f(1, 1));
Mat3x3f toNormalized =
Mat3x3f::ScaleHomogeneous(Vec2f(1, 1) / Max(Vec2f(loadBounds), Vec2f(1, 1)));
Mat3x3f loadTransform = Mul(toTexels, Mul(sampleTransform, toNormalized));
params.loadTransform = Mat3x2f::CropOrExpandFrom(loadTransform);
}
// Compute the clamping for each plane individually: to avoid bleeding of OOB texels due to
// interpolation we need to offset by a half texel in, which depends on the size of the plane.
{
Vec2f plane0HalfTexel = Vec2f(0.5f, 0.5f) / plane0Size;
Vec2f plane1HalfTexel = Vec2f(0.5f, 0.5f) / plane1Size;
params.samplePlane0RectMin = rectOffset + plane0HalfTexel;
params.samplePlane1RectMin = rectOffset + plane1HalfTexel;
params.samplePlane0RectMax = rectOffset + rectScale - plane0HalfTexel;
params.samplePlane1RectMax = rectOffset + rectScale - plane1HalfTexel;
}
params.plane1CoordFactor = plane1Size / plane0Size;
params.apparentSize = loadBounds;
return params;
}
} // anonymous namespace
ResultOrError<Ref<BufferBase>> MakeParamsBufferForSimpleView(DeviceBase* device,
Ref<TextureViewBase> textureView) {
const Extent3D textureSize = textureView->GetSingleSubresourceVirtualSize();
std::array<float, 12> placeholderConstantArray;
// Make a fake ExternalTextureDescriptor for the view that reuses the code computing uniform
// parameters passed to the shader.
ExternalTextureDescriptor desc = {};
desc.plane0 = textureView.Get();
desc.cropOrigin = {0, 0};
desc.cropSize = {textureSize.width, textureSize.height};
desc.apparentSize = {textureSize.width, textureSize.height};
desc.doYuvToRgbConversionOnly = true;
desc.srcTransferFunctionParameters = placeholderConstantArray.data();
desc.dstTransferFunctionParameters = placeholderConstantArray.data();
desc.gamutConversionMatrix = placeholderConstantArray.data();
ExternalTextureParams params = ComputeExternalTextureParams(&desc);
return utils::CreateBufferFromData(device, "Dawn_Simple_Texture_View_Params_Buffer",
wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst,
{params});
}
// static
ResultOrError<Ref<ExternalTextureBase>> ExternalTextureBase::Create(
DeviceBase* device,
const ExternalTextureDescriptor* descriptor) {
Ref<ExternalTextureBase> externalTexture =
AcquireRef(new ExternalTextureBase(device, descriptor));
DAWN_TRY(externalTexture->Initialize(device, descriptor));
return std::move(externalTexture);
}
ExternalTextureBase::ExternalTextureBase(DeviceBase* device,
const ExternalTextureDescriptor* descriptor)
: ApiObjectBase(device, descriptor->label), mState(ExternalTextureState::Active) {
GetObjectTrackingList()->Track(this);
}
// Error external texture cannot be used in bind group.
ExternalTextureBase::ExternalTextureBase(DeviceBase* device,
ObjectBase::ErrorTag tag,
StringView label)
: ApiObjectBase(device, tag, label), mState(ExternalTextureState::Destroyed) {}
ExternalTextureBase::~ExternalTextureBase() = default;
MaybeError ExternalTextureBase::Initialize(DeviceBase* device,
const ExternalTextureDescriptor* descriptor) {
// Store any passed in TextureViews associated with individual planes.
mTextureViews[0] = descriptor->plane0;
if (descriptor->plane1) {
mTextureViews[1] = descriptor->plane1;
} else {
DAWN_TRY_ASSIGN(mTextureViews[1],
device->GetOrCreatePlaceholderTextureViewForExternalTexture());
}
// We must create a buffer to store parameters needed by a shader that operates on this
// external texture.
ExternalTextureParams params = ComputeExternalTextureParams(descriptor);
DAWN_TRY_ASSIGN(mParamsBuffer,
utils::CreateBufferFromData(
device, "Dawn_External_Texture_Params_Buffer",
wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst, {params}));
return {};
}
const std::array<Ref<TextureViewBase>, kMaxPlanesPerFormat>& ExternalTextureBase::GetTextureViews()
const {
return mTextureViews;
}
MaybeError ExternalTextureBase::ValidateCanUseInSubmitNow() const {
DAWN_ASSERT(!IsError());
DAWN_INVALID_IF(mState != ExternalTextureState::Active,
"External texture %s used in a submit is not active.", this);
for (uint32_t i = 0; i < kMaxPlanesPerFormat; ++i) {
if (mTextureViews[i] != nullptr) {
DAWN_TRY_CONTEXT(mTextureViews[i]->GetTexture()->ValidateCanUseInSubmitNow(),
"Validate plane %u of %s can be used in a submit.", i, this);
}
}
return {};
}
MaybeError ExternalTextureBase::ValidateRefresh() {
DAWN_TRY(GetDevice()->ValidateObject(this));
DAWN_INVALID_IF(mState == ExternalTextureState::Destroyed, "%s is destroyed.", this);
return {};
}
MaybeError ExternalTextureBase::ValidateExpire() {
DAWN_TRY(GetDevice()->ValidateObject(this));
DAWN_INVALID_IF(mState != ExternalTextureState::Active, "%s is not active.", this);
return {};
}
void ExternalTextureBase::APIRefresh() {
if (GetDevice()->ConsumedError(ValidateRefresh(), "calling %s.Refresh()", this)) {
return;
}
mState = ExternalTextureState::Active;
}
void ExternalTextureBase::APIExpire() {
if (GetDevice()->ConsumedError(ValidateExpire(), "calling %s.Expire()", this)) {
return;
}
mState = ExternalTextureState::Expired;
}
void ExternalTextureBase::APIDestroy() {
Destroy();
}
void ExternalTextureBase::DestroyImpl(DestroyReason reason) {
// TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
// - It may be called if the texture is explicitly destroyed with APIDestroy.
// This case is NOT thread-safe and needs proper synchronization with other
// simultaneous uses of the texture.
// - It may be called when the last ref to the texture is dropped and the texture
// is implicitly destroyed. This case is thread-safe because there are no
// other threads using the texture since there are no other live refs.
mState = ExternalTextureState::Destroyed;
}
// static
Ref<ExternalTextureBase> ExternalTextureBase::MakeError(DeviceBase* device, StringView label) {
return AcquireRef(new ExternalTextureBase(device, ObjectBase::kError, label));
}
BufferBase* ExternalTextureBase::GetParamsBuffer() const {
return mParamsBuffer.Get();
}
ObjectType ExternalTextureBase::GetType() const {
return ObjectType::ExternalTexture;
}
} // namespace dawn::native