blob: 0013ba85f1d4a2ea8a2e37645b11d3642c4cfd31 [file] [log] [blame] [edit]
// Copyright 2020 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn/native/CopyTextureForBrowserHelper.h"
#include <unordered_set>
#include <utility>
#include "dawn/common/Log.h"
#include "dawn/native/BindGroup.h"
#include "dawn/native/BindGroupLayout.h"
#include "dawn/native/Buffer.h"
#include "dawn/native/CommandBuffer.h"
#include "dawn/native/CommandEncoder.h"
#include "dawn/native/CommandValidation.h"
#include "dawn/native/Device.h"
#include "dawn/native/ExternalTexture.h"
#include "dawn/native/InternalPipelineStore.h"
#include "dawn/native/Queue.h"
#include "dawn/native/RenderPassEncoder.h"
#include "dawn/native/RenderPipeline.h"
#include "dawn/native/Sampler.h"
#include "dawn/native/Texture.h"
#include "dawn/native/ValidationUtils_autogen.h"
#include "dawn/native/utils/WGPUHelpers.h"
namespace dawn::native {
namespace {
static const char sCopyForBrowserShader[] = R"(
struct GammaTransferParamsInternal {
G: f32,
A: f32,
B: f32,
C: f32,
D: f32,
E: f32,
F: f32,
padding: u32,
};
struct Uniforms { // offset align size
scale: vec2f, // 0 8 8
offset: vec2f, // 8 8 8
steps_mask: u32, // 16 4 4
// implicit padding; // 20 12
conversion_matrix: mat3x3<f32>, // 32 16 48
gamma_decoding_params: GammaTransferParamsInternal, // 80 4 32
gamma_encoding_params: GammaTransferParamsInternal, // 112 4 32
gamma_decoding_for_dst_srgb_params: GammaTransferParamsInternal, // 144 4 32
};
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
struct VertexOutputs {
@location(0) texcoords : vec2f,
@builtin(position) position : vec4f,
};
// Chromium uses unified equation to construct gamma decoding function
// and gamma encoding function.
// The logic is:
// if x < D
// linear = C * x + F
// nonlinear = pow(A * x + B, G) + E
// (https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/color_transform.cc;l=541)
// Expand the equation with sign() to make it handle all gamma conversions.
fn gamma_conversion(v: f32, params: GammaTransferParamsInternal) -> f32 {
// Linear part: C * x + F
if (abs(v) < params.D) {
return sign(v) * (params.C * abs(v) + params.F);
}
// Gamma part: pow(A * x + B, G) + E
return sign(v) * (pow(params.A * abs(v) + params.B, params.G) + params.E);
}
@vertex
fn vs_main(
@builtin(vertex_index) VertexIndex : u32
) -> VertexOutputs {
var texcoord = array(
vec2f(-0.5, 0.0),
vec2f( 1.5, 0.0),
vec2f( 0.5, 2.0));
var output : VertexOutputs;
output.position = vec4f((texcoord[VertexIndex] * 2.0 - vec2f(1.0, 1.0)), 0.0, 1.0);
output.texcoords = texcoord[VertexIndex] * uniforms.scale + uniforms.offset;
return output;
}
@binding(1) @group(0) var mySampler: sampler;
// Resource used in copyTexture entry point only.
@binding(2) @group(0) var mySourceTexture: texture_2d<f32>;
// Resource used in copyExternalTexture entry point only.
@binding(2) @group(0) var mySourceExternalTexture: texture_external;
fn discardIfOutsideOfCopy(texcoord : vec2f) {
var clampedTexcoord =
clamp(texcoord, vec2f(0.0, 0.0), vec2f(1.0, 1.0));
if (!all(clampedTexcoord == texcoord)) {
discard;
}
}
fn transform(srcColor : vec4f) -> vec4f {
var color = srcColor;
let kUnpremultiplyStep = 0x01u;
let kDecodeToLinearStep = 0x02u;
let kConvertToDstGamutStep = 0x04u;
let kEncodeToGammaStep = 0x08u;
let kPremultiplyStep = 0x10u;
let kDecodeForSrgbDstFormat = 0x20u;
let kClearSrcAlphaToOne = 0x40u;
// Unpremultiply step. Appling color space conversion op on premultiplied source texture
// also needs to unpremultiply first.
// This step is exclusive with clear src alpha to one step.
if (bool(uniforms.steps_mask & kUnpremultiplyStep)) {
if (color.a != 0.0) {
color = vec4f(color.rgb / color.a, color.a);
}
}
// Linearize the source color using the source color space’s
// transfer function if it is non-linear.
if (bool(uniforms.steps_mask & kDecodeToLinearStep)) {
color = vec4f(gamma_conversion(color.r, uniforms.gamma_decoding_params),
gamma_conversion(color.g, uniforms.gamma_decoding_params),
gamma_conversion(color.b, uniforms.gamma_decoding_params),
color.a);
}
// Convert unpremultiplied, linear source colors to the destination gamut by
// multiplying by a 3x3 matrix. Calculate transformFromXYZD50 * transformToXYZD50
// in CPU side and upload the final result in uniforms.
if (bool(uniforms.steps_mask & kConvertToDstGamutStep)) {
color = vec4f(uniforms.conversion_matrix * color.rgb, color.a);
}
// Encode that color using the inverse of the destination color
// space’s transfer function if it is non-linear.
if (bool(uniforms.steps_mask & kEncodeToGammaStep)) {
color = vec4f(gamma_conversion(color.r, uniforms.gamma_encoding_params),
gamma_conversion(color.g, uniforms.gamma_encoding_params),
gamma_conversion(color.b, uniforms.gamma_encoding_params),
color.a);
}
// Premultiply step.
// This step is exclusive with clear src alpha to one step.
if (bool(uniforms.steps_mask & kPremultiplyStep)) {
color = vec4f(color.rgb * color.a, color.a);
}
// Decode for copying from non-srgb formats to srgb formats
if (bool(uniforms.steps_mask & kDecodeForSrgbDstFormat)) {
color = vec4f(gamma_conversion(color.r, uniforms.gamma_decoding_for_dst_srgb_params),
gamma_conversion(color.g, uniforms.gamma_decoding_for_dst_srgb_params),
gamma_conversion(color.b, uniforms.gamma_decoding_for_dst_srgb_params),
color.a);
}
// Clear alpha to one step.
// This step is exclusive with premultiply/unpremultiply step.
if (bool(uniforms.steps_mask & kClearSrcAlphaToOne)) {
color.a = 1.0;
}
return color;
}
@fragment
fn copyTexture(@location(0) texcoord : vec2f
) -> @location(0) vec4f {
var color = textureSample(mySourceTexture, mySampler, texcoord);
// TODO(crbug.com/tint/1723): Discard before sampling should be valid.
discardIfOutsideOfCopy(texcoord);
return transform(color);
}
@fragment
fn copyExternalTexture(@location(0) texcoord : vec2f
) -> @location(0) vec4f {
var color = textureSampleBaseClampToEdge(mySourceExternalTexture, mySampler, texcoord);
// TODO(crbug.com/tint/1723): Discard before sampling should be valid.
discardIfOutsideOfCopy(texcoord);
return transform(color);
}
)";
// Follow the same order of skcms_TransferFunction
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/include/third_party/skcms/skcms.h;l=46;
struct GammaTransferParamsInternal {
float G = 0.0;
float A = 0.0;
float B = 0.0;
float C = 0.0;
float D = 0.0;
float E = 0.0;
float F = 0.0;
uint32_t padding = 0;
};
struct Uniform {
float scaleX;
float scaleY;
float offsetX;
float offsetY;
uint32_t stepsMask = 0;
const std::array<uint32_t, 3> padding = {}; // 12 bytes padding
std::array<float, 12> conversionMatrix = {};
GammaTransferParamsInternal gammaDecodingParams = {};
GammaTransferParamsInternal gammaEncodingParams = {};
GammaTransferParamsInternal gammaDecodingForDstSrgbParams = {};
};
static_assert(sizeof(Uniform) == 176);
enum class SourceTextureType { Texture2D, ExternalTexture };
struct TextureInfo {
Origin3D origin;
Extent3D size;
};
// TODO(crbug.com/dawn/856): Expand copyTextureForBrowser to support any
// non-depth, non-stencil, non-compressed texture format pair copy.
MaybeError ValidateCopyTextureSourceFormat(const wgpu::TextureFormat srcFormat) {
switch (srcFormat) {
case wgpu::TextureFormat::BGRA8Unorm:
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::RGBA16Float:
break;
default:
return DAWN_VALIDATION_ERROR("Source texture format (%s) is not supported.", srcFormat);
}
return {};
}
MaybeError ValidateCopyForBrowserDestinationFormat(const wgpu::TextureFormat dstFormat) {
switch (dstFormat) {
case wgpu::TextureFormat::R8Unorm:
case wgpu::TextureFormat::R16Float:
case wgpu::TextureFormat::R32Float:
case wgpu::TextureFormat::RG8Unorm:
case wgpu::TextureFormat::RG16Float:
case wgpu::TextureFormat::RG32Float:
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::RGBA8UnormSrgb:
case wgpu::TextureFormat::BGRA8Unorm:
case wgpu::TextureFormat::BGRA8UnormSrgb:
case wgpu::TextureFormat::RGB10A2Unorm:
case wgpu::TextureFormat::RGBA16Float:
case wgpu::TextureFormat::RGBA32Float:
break;
default:
return DAWN_VALIDATION_ERROR("Destination texture format (%s) is not supported.",
dstFormat);
}
return {};
}
RenderPipelineBase* GetCachedCopyTexturePipeline(InternalPipelineStore* store,
wgpu::TextureFormat dstFormat) {
auto pipeline = store->copyTextureForBrowserPipelines.find(dstFormat);
if (pipeline != store->copyTextureForBrowserPipelines.end()) {
return pipeline->second.Get();
}
return nullptr;
}
RenderPipelineBase* GetCachedCopyExternalTexturePipeline(InternalPipelineStore* store,
wgpu::TextureFormat dstFormat) {
auto pipeline = store->copyExternalTextureForBrowserPipelines.find(dstFormat);
if (pipeline != store->copyExternalTextureForBrowserPipelines.end()) {
return pipeline->second.Get();
}
return nullptr;
}
ResultOrError<Ref<RenderPipelineBase>> CreateCopyForBrowserPipeline(
DeviceBase* device,
wgpu::TextureFormat dstFormat,
ShaderModuleBase* shaderModule,
const char* fragmentEntryPoint) {
// Prepare vertex stage.
VertexState vertex = {};
vertex.module = shaderModule;
vertex.entryPoint = "vs_main";
// Prepare frgament stage.
FragmentState fragment = {};
fragment.module = shaderModule;
fragment.entryPoint = fragmentEntryPoint;
// Prepare color state.
ColorTargetState target = {};
target.format = dstFormat;
// Create RenderPipeline.
RenderPipelineDescriptor renderPipelineDesc = {};
// Generate the layout based on shader modules.
renderPipelineDesc.layout = nullptr;
renderPipelineDesc.vertex = vertex;
renderPipelineDesc.fragment = &fragment;
renderPipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
fragment.targetCount = 1;
fragment.targets = &target;
return device->CreateRenderPipeline(&renderPipelineDesc);
}
ResultOrError<ShaderModuleBase*> GetOrCreateCopyForBrowserShaderModule(
DeviceBase* device,
InternalPipelineStore* store) {
if (store->copyForBrowser == nullptr) {
DAWN_TRY_ASSIGN(store->copyForBrowser,
utils::CreateShaderModule(device, sCopyForBrowserShader));
}
return store->copyForBrowser.Get();
}
ResultOrError<RenderPipelineBase*> GetOrCreateCopyTextureForBrowserPipeline(
DeviceBase* device,
wgpu::TextureFormat dstFormat) {
InternalPipelineStore* store = device->GetInternalPipelineStore();
if (GetCachedCopyTexturePipeline(store, dstFormat) == nullptr) {
ShaderModuleBase* shaderModule;
DAWN_TRY_ASSIGN(shaderModule, GetOrCreateCopyForBrowserShaderModule(device, store));
Ref<RenderPipelineBase> pipeline;
DAWN_TRY_ASSIGN(
pipeline, CreateCopyForBrowserPipeline(device, dstFormat, shaderModule, "copyTexture"));
store->copyTextureForBrowserPipelines.insert({dstFormat, std::move(pipeline)});
}
return GetCachedCopyTexturePipeline(store, dstFormat);
}
ResultOrError<RenderPipelineBase*> GetOrCreateCopyExternalTextureForBrowserPipeline(
DeviceBase* device,
wgpu::TextureFormat dstFormat) {
InternalPipelineStore* store = device->GetInternalPipelineStore();
if (GetCachedCopyExternalTexturePipeline(store, dstFormat) == nullptr) {
ShaderModuleBase* shaderModule;
DAWN_TRY_ASSIGN(shaderModule, GetOrCreateCopyForBrowserShaderModule(device, store));
Ref<RenderPipelineBase> pipeline;
DAWN_TRY_ASSIGN(pipeline, CreateCopyForBrowserPipeline(device, dstFormat, shaderModule,
"copyExternalTexture"));
store->copyExternalTextureForBrowserPipelines.insert({dstFormat, std::move(pipeline)});
}
return GetCachedCopyExternalTexturePipeline(store, dstFormat);
}
// Whether the format of dst texture of CopyTextureForBrowser() is srgb or non-srgb.
bool IsSrgbDstFormat(wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::RGBA8UnormSrgb:
case wgpu::TextureFormat::BGRA8UnormSrgb:
return true;
default:
return false;
}
}
template <typename T>
MaybeError DoCopyForBrowser(DeviceBase* device,
const TextureInfo* sourceInfo,
T* sourceResource,
const ImageCopyTexture* destination,
const Extent3D* copySize,
const CopyTextureForBrowserOptions* options,
RenderPipelineBase* pipeline) {
// TODO(crbug.com/dawn/856): In D3D12 and Vulkan, compatible texture format can directly
// copy to each other. This can be a potential fast path.
// Noop copy
if (copySize->width == 0 || copySize->height == 0 || copySize->depthOrArrayLayers == 0) {
return {};
}
// Prepare bind group layout.
Ref<BindGroupLayoutBase> layout;
DAWN_TRY_ASSIGN(layout, pipeline->GetBindGroupLayout(0));
// Prepare binding 0 resource: uniform buffer.
Uniform uniformData = {
copySize->width / static_cast<float>(sourceInfo->size.width),
copySize->height / static_cast<float>(sourceInfo->size.height), // scale
sourceInfo->origin.x / static_cast<float>(sourceInfo->size.width),
sourceInfo->origin.y / static_cast<float>(sourceInfo->size.height) // offset
};
// The NDC to framebuffer space transform maps inverts the Y coordinate such that NDC [-1, 1]
// (resp [-1, -1]) maps to framebuffer space [0, 0] (resp [0, height-1]). So we need to undo
// this flip when converting positions to texcoords.
// https://www.w3.org/TR/webgpu/#coordinate-systems
if (!options->flipY) {
uniformData.scaleY *= -1.0;
uniformData.offsetY += copySize->height / static_cast<float>(sourceInfo->size.height);
}
uint32_t stepsMask = 0u;
// Steps to do color space conversion
// From https://skia.org/docs/user/color/
// - unpremultiply if the source color is premultiplied; Alpha is not involved in color
// management, and we need to divide it out if it’s multiplied in.
// - linearize the source color using the source color space’s transfer function
// - convert those unpremultiplied, linear source colors to XYZ D50 gamut by multiplying by
// a 3x3 matrix.
// - convert those XYZ D50 colors to the destination gamut by multiplying by a 3x3 matrix.
// - encode that color using the inverse of the destination color space’s transfer function.
// - premultiply by alpha if the destination is premultiplied.
// The reason to choose XYZ D50 as intermediate color space:
// From http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html
// "Since the Lab TIFF specification, the ICC profile specification and
// Adobe Photoshop all use a D50"
constexpr uint32_t kUnpremultiplyStep = 0x01;
constexpr uint32_t kDecodeToLinearStep = 0x02;
constexpr uint32_t kConvertToDstGamutStep = 0x04;
constexpr uint32_t kEncodeToGammaStep = 0x08;
constexpr uint32_t kPremultiplyStep = 0x10;
constexpr uint32_t kDecodeForSrgbDstFormat = 0x20;
constexpr uint32_t kClearSrcAlphaToOne = 0x40;
if (options->srcAlphaMode == wgpu::AlphaMode::Premultiplied) {
if (options->needsColorSpaceConversion ||
options->dstAlphaMode == wgpu::AlphaMode::Unpremultiplied) {
stepsMask |= kUnpremultiplyStep;
}
} else if (options->srcAlphaMode == wgpu::AlphaMode::Opaque) {
// Simply clear src alpha channel to 1.0
stepsMask |= kClearSrcAlphaToOne;
}
if (options->needsColorSpaceConversion) {
stepsMask |= kDecodeToLinearStep;
const float* decodingParams = options->srcTransferFunctionParameters;
uniformData.gammaDecodingParams = {decodingParams[0], decodingParams[1], decodingParams[2],
decodingParams[3], decodingParams[4], decodingParams[5],
decodingParams[6]};
stepsMask |= kConvertToDstGamutStep;
const float* matrix = options->conversionMatrix;
uniformData.conversionMatrix = {{
matrix[0],
matrix[1],
matrix[2],
0.0,
matrix[3],
matrix[4],
matrix[5],
0.0,
matrix[6],
matrix[7],
matrix[8],
0.0,
}};
stepsMask |= kEncodeToGammaStep;
const float* encodingParams = options->dstTransferFunctionParameters;
uniformData.gammaEncodingParams = {encodingParams[0], encodingParams[1], encodingParams[2],
encodingParams[3], encodingParams[4], encodingParams[5],
encodingParams[6]};
}
if (options->dstAlphaMode == wgpu::AlphaMode::Premultiplied) {
if (options->needsColorSpaceConversion ||
options->srcAlphaMode == wgpu::AlphaMode::Unpremultiplied) {
stepsMask |= kPremultiplyStep;
}
}
// Copy to *-srgb texture should keep the bytes exactly the same as copy
// to non-srgb texture. Add an extra decode-to-linear step so that after the
// sampler of *-srgb format texture applying encoding, the bytes keeps the same
// as non-srgb format texture.
// NOTE: CopyTextureForBrowser() doesn't need to accept *-srgb format texture as
// source input. But above operation also valid for *-srgb format texture input and
// non-srgb format dst texture.
// TODO(crbug.com/dawn/1195): Reinterpret to non-srgb texture view on *-srgb texture
// and use it as render attachment when possible.
// TODO(crbug.com/dawn/1195): Opt the condition for this extra step. It is possible to
// bypass this extra step in some cases.
bool isSrgbDstFormat = IsSrgbDstFormat(destination->texture->GetFormat().format);
if (isSrgbDstFormat) {
stepsMask |= kDecodeForSrgbDstFormat;
// Get gamma-linear conversion params from https://en.wikipedia.org/wiki/SRGB with some
// mathematics. Order: {G, A, B, C, D, E, F, }
uniformData.gammaDecodingForDstSrgbParams = {
2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 4.045e-02, 0.0, 0.0};
}
// Upload uniform data
uniformData.stepsMask = stepsMask;
Ref<BufferBase> uniformBuffer;
DAWN_TRY_ASSIGN(
uniformBuffer,
utils::CreateBufferFromData(device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform,
{uniformData}));
// Prepare binding 1 resource: sampler
// Use default configuration, filterMode set to Nearest for min and mag.
SamplerDescriptor samplerDesc = {};
Ref<SamplerBase> sampler;
DAWN_TRY_ASSIGN(sampler, device->CreateSampler(&samplerDesc));
// Create bind group after all binding entries are set.
UsageValidationMode mode =
options->internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
Ref<BindGroupBase> bindGroup;
DAWN_TRY_ASSIGN(bindGroup, utils::MakeBindGroup(
device, layout,
{{0, uniformBuffer}, {1, sampler}, {2, sourceResource}}, mode));
// Create command encoder.
CommandEncoderDescriptor commandEncoderDesc;
DawnEncoderInternalUsageDescriptor internalUsageDesc;
if (options->internalUsage) {
internalUsageDesc.useInternalUsages = true;
commandEncoderDesc.nextInChain = &internalUsageDesc;
}
Ref<CommandEncoder> encoder;
DAWN_TRY_ASSIGN(encoder, device->CreateCommandEncoder(&commandEncoderDesc));
// Prepare dst texture view as color Attachment.
TextureViewDescriptor dstTextureViewDesc;
dstTextureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
dstTextureViewDesc.baseMipLevel = destination->mipLevel;
dstTextureViewDesc.mipLevelCount = 1;
dstTextureViewDesc.baseArrayLayer = destination->origin.z;
dstTextureViewDesc.arrayLayerCount = 1;
Ref<TextureViewBase> dstView;
DAWN_TRY_ASSIGN(dstView, device->CreateTextureView(destination->texture, &dstTextureViewDesc));
// Prepare render pass color attachment descriptor.
RenderPassColorAttachment colorAttachmentDesc;
colorAttachmentDesc.view = dstView.Get();
colorAttachmentDesc.loadOp = wgpu::LoadOp::Load;
colorAttachmentDesc.storeOp = wgpu::StoreOp::Store;
colorAttachmentDesc.clearValue = {0.0, 0.0, 0.0, 1.0};
// Create render pass.
RenderPassDescriptor renderPassDesc;
renderPassDesc.colorAttachmentCount = 1;
renderPassDesc.colorAttachments = &colorAttachmentDesc;
Ref<RenderPassEncoder> passEncoder = encoder->BeginRenderPass(&renderPassDesc);
// Start pipeline and encode commands to complete
// the copy from src texture to dst texture with transformation.
passEncoder->APISetPipeline(pipeline);
passEncoder->APISetBindGroup(0, bindGroup.Get());
passEncoder->APISetViewport(destination->origin.x, destination->origin.y, copySize->width,
copySize->height, 0.0, 1.0);
passEncoder->APIDraw(3);
passEncoder->End();
// Finsh encoding.
Ref<CommandBufferBase> commandBuffer;
DAWN_TRY_ASSIGN(commandBuffer, encoder->Finish());
CommandBufferBase* submitCommandBuffer = commandBuffer.Get();
// Submit command buffer.
device->GetQueue()->APISubmit(1, &submitCommandBuffer);
return {};
}
MaybeError ValidateCopyForBrowserDestination(DeviceBase* device,
const ImageCopyTexture& destination,
const Extent3D& copySize,
const CopyTextureForBrowserOptions& options) {
DAWN_TRY(device->ValidateObject(destination.texture));
DAWN_INVALID_IF(destination.texture->GetTextureState() == TextureBase::TextureState::Destroyed,
"Destination texture %s is destroyed.", destination.texture);
DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, destination, copySize),
"validating the ImageCopyTexture for the destination");
DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, destination, copySize),
"validating that the copy fits in the destination");
UsageValidationMode mode =
options.internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
DAWN_TRY(ValidateCanUseAs(destination.texture, wgpu::TextureUsage::CopyDst, mode));
DAWN_TRY(ValidateCanUseAs(destination.texture, wgpu::TextureUsage::RenderAttachment, mode));
DAWN_INVALID_IF(destination.texture->GetSampleCount() > 1,
"The destination texture sample count (%u) is not 1.",
destination.texture->GetSampleCount());
DAWN_TRY(ValidateCopyForBrowserDestinationFormat(destination.texture->GetFormat().format));
// The valid destination formats are all color formats.
DAWN_INVALID_IF(
destination.aspect != wgpu::TextureAspect::All,
"Destination %s aspect (%s) doesn't select all the aspects of the destination format.",
destination.texture, destination.aspect);
return {};
}
MaybeError ValidateCopyForBrowserOptions(const CopyTextureForBrowserOptions& options) {
DAWN_INVALID_IF(options.nextInChain != nullptr, "nextInChain must be nullptr");
DAWN_TRY(ValidateAlphaMode(options.srcAlphaMode));
DAWN_TRY(ValidateAlphaMode(options.dstAlphaMode));
if (options.needsColorSpaceConversion) {
DAWN_INVALID_IF(options.srcTransferFunctionParameters == nullptr,
"srcTransferFunctionParameters is nullptr when doing color conversion");
DAWN_INVALID_IF(options.conversionMatrix == nullptr,
"conversionMatrix is nullptr when doing color conversion");
DAWN_INVALID_IF(options.dstTransferFunctionParameters == nullptr,
"dstTransferFunctionParameters is nullptr when doing color conversion");
}
return {};
}
} // anonymous namespace
MaybeError ValidateCopyTextureForBrowser(DeviceBase* device,
const ImageCopyTexture* source,
const ImageCopyTexture* destination,
const Extent3D* copySize,
const CopyTextureForBrowserOptions* options) {
// Validate source
DAWN_TRY(device->ValidateObject(source->texture));
DAWN_INVALID_IF(source->texture->GetTextureState() == TextureBase::TextureState::Destroyed,
"Source texture %s is destroyed.", source->texture);
DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, *source, *copySize),
"validating the ImageCopyTexture for the source");
DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *source, *copySize),
"validating that the copy fits in the source");
DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", source->origin.z);
DAWN_INVALID_IF(source->texture->GetSampleCount() > 1,
"The source texture sample count (%u) is not 1. ",
source->texture->GetSampleCount());
DAWN_INVALID_IF(
options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
"The internalUsage is true while the dawn-internal-usages feature is not enabled.");
UsageValidationMode mode =
options->internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc, mode));
DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::TextureBinding, mode));
DAWN_TRY(ValidateCopyTextureSourceFormat(source->texture->GetFormat().format));
// Validate destination
DAWN_TRY(ValidateCopyForBrowserDestination(device, *destination, *copySize, *options));
// Validate copy common rules and copySize.
DAWN_INVALID_IF(copySize->depthOrArrayLayers > 1, "Copy is for more than one array layer (%u)",
copySize->depthOrArrayLayers);
DAWN_TRY(ValidateTextureToTextureCopyCommonRestrictions(*source, *destination, *copySize));
// Validate options
DAWN_TRY(ValidateCopyForBrowserOptions(*options));
return {};
}
MaybeError ValidateCopyExternalTextureForBrowser(DeviceBase* device,
const ImageCopyExternalTexture* source,
const ImageCopyTexture* destination,
const Extent3D* copySize,
const CopyTextureForBrowserOptions* options) {
// Validate source
DAWN_TRY(device->ValidateObject(source->externalTexture));
DAWN_TRY(source->externalTexture->ValidateCanUseInSubmitNow());
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
// overflows.
DAWN_INVALID_IF(
static_cast<uint64_t>(source->origin.x) + static_cast<uint64_t>(copySize->width) >
static_cast<uint64_t>(sourceSize.width) ||
static_cast<uint64_t>(source->origin.y) + static_cast<uint64_t>(copySize->height) >
static_cast<uint64_t>(sourceSize.height) ||
static_cast<uint64_t>(source->origin.z) > 0,
"Texture copy range (origin: %s, copySize: %s) touches outside of %s source size (%s).",
&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(
options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
"The internalUsage is true while the dawn-internal-usages feature is not enabled.");
// Validate destination
DAWN_TRY(ValidateCopyForBrowserDestination(device, *destination, *copySize, *options));
// Validate copySize
DAWN_INVALID_IF(copySize->depthOrArrayLayers > 1, "Copy is for more than one array layer (%u)",
copySize->depthOrArrayLayers);
// Validate options
DAWN_TRY(ValidateCopyForBrowserOptions(*options));
return {};
}
MaybeError DoCopyExternalTextureForBrowser(DeviceBase* device,
const ImageCopyExternalTexture* source,
const ImageCopyTexture* destination,
const Extent3D* copySize,
const CopyTextureForBrowserOptions* options) {
TextureInfo info;
info.origin = source->origin;
info.size = {source->naturalSize.width, source->naturalSize.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;
DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyExternalTextureForBrowserPipeline(
device, destination->texture->GetFormat().format));
return DoCopyForBrowser<ExternalTextureBase>(device, &info, source->externalTexture,
destination, copySize, options, pipeline);
}
MaybeError DoCopyTextureForBrowser(DeviceBase* device,
const ImageCopyTexture* source,
const ImageCopyTexture* destination,
const Extent3D* copySize,
const CopyTextureForBrowserOptions* options) {
TextureInfo info;
info.origin = source->origin;
info.size = source->texture->GetSize();
Ref<TextureViewBase> srcTextureView = nullptr;
TextureViewDescriptor srcTextureViewDesc = {};
srcTextureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
srcTextureViewDesc.baseMipLevel = source->mipLevel;
srcTextureViewDesc.mipLevelCount = 1;
srcTextureViewDesc.arrayLayerCount = 1;
DAWN_TRY_ASSIGN(srcTextureView,
device->CreateTextureView(source->texture, &srcTextureViewDesc));
RenderPipelineBase* pipeline;
DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyTextureForBrowserPipeline(
device, destination->texture->GetFormat().format));
return DoCopyForBrowser<TextureViewBase>(device, &info, srcTextureView.Get(), destination,
copySize, options, pipeline);
}
} // namespace dawn::native