1 // Copyright 2020 The Dawn Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "dawn_native/CopyTextureForBrowserHelper.h" 16 17 #include "dawn_native/BindGroup.h" 18 #include "dawn_native/BindGroupLayout.h" 19 #include "dawn_native/Buffer.h" 20 #include "dawn_native/CommandBuffer.h" 21 #include "dawn_native/CommandEncoder.h" 22 #include "dawn_native/CommandValidation.h" 23 #include "dawn_native/Device.h" 24 #include "dawn_native/InternalPipelineStore.h" 25 #include "dawn_native/Queue.h" 26 #include "dawn_native/RenderPassEncoder.h" 27 #include "dawn_native/RenderPipeline.h" 28 #include "dawn_native/Sampler.h" 29 #include "dawn_native/Texture.h" 30 #include "dawn_native/ValidationUtils_autogen.h" 31 #include "dawn_native/utils/WGPUHelpers.h" 32 33 #include <unordered_set> 34 35 namespace dawn_native { 36 namespace { 37 38 static const char sCopyTextureForBrowserShader[] = R"( 39 [[block]] struct Uniforms { 40 u_scale: vec2<f32>; 41 u_offset: vec2<f32>; 42 u_alphaOp: u32; 43 }; 44 45 [[binding(0), group(0)]] var<uniform> uniforms : Uniforms; 46 47 struct VertexOutputs { 48 [[location(0)]] texcoords : vec2<f32>; 49 [[builtin(position)]] position : vec4<f32>; 50 }; 51 52 [[stage(vertex)]] fn vs_main( 53 [[builtin(vertex_index)]] VertexIndex : u32 54 ) -> VertexOutputs { 55 var texcoord = array<vec2<f32>, 3>( 56 vec2<f32>(-0.5, 0.0), 57 vec2<f32>( 1.5, 0.0), 58 vec2<f32>( 0.5, 2.0)); 59 60 var output : VertexOutputs; 61 output.position = vec4<f32>((texcoord[VertexIndex] * 2.0 - vec2<f32>(1.0, 1.0)), 0.0, 1.0); 62 63 // Y component of scale is calculated by the copySizeHeight / textureHeight. Only 64 // flipY case can get negative number. 65 var flipY = uniforms.u_scale.y < 0.0; 66 67 // Texture coordinate takes top-left as origin point. We need to map the 68 // texture to triangle carefully. 69 if (flipY) { 70 // We need to get the mirror positions(mirrored based on y = 0.5) on flip cases. 71 // Adopt transform to src texture and then mapping it to triangle coord which 72 // do a +1 shift on Y dimension will help us got that mirror position perfectly. 73 output.texcoords = (texcoord[VertexIndex] * uniforms.u_scale + uniforms.u_offset) * 74 vec2<f32>(1.0, -1.0) + vec2<f32>(0.0, 1.0); 75 } else { 76 // For the normal case, we need to get the exact position. 77 // So mapping texture to triangle firstly then adopt the transform. 78 output.texcoords = (texcoord[VertexIndex] * 79 vec2<f32>(1.0, -1.0) + vec2<f32>(0.0, 1.0)) * 80 uniforms.u_scale + uniforms.u_offset; 81 } 82 83 return output; 84 } 85 86 [[binding(1), group(0)]] var mySampler: sampler; 87 [[binding(2), group(0)]] var myTexture: texture_2d<f32>; 88 89 [[stage(fragment)]] fn fs_main( 90 [[location(0)]] texcoord : vec2<f32> 91 ) -> [[location(0)]] vec4<f32> { 92 // Clamp the texcoord and discard the out-of-bound pixels. 93 var clampedTexcoord = 94 clamp(texcoord, vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 1.0)); 95 if (!all(clampedTexcoord == texcoord)) { 96 discard; 97 } 98 99 // Swizzling of texture formats when sampling / rendering is handled by the 100 // hardware so we don't need special logic in this shader. This is covered by tests. 101 var srcColor = textureSample(myTexture, mySampler, texcoord); 102 103 // Handle alpha. Alpha here helps on the source content and dst content have 104 // different alpha config. There are three possible ops: DontChange, Premultiply 105 // and Unpremultiply. 106 // TODO(crbug.com/1217153): if wgsl support `constexpr` and allow it 107 // to be case selector, Replace 0u/1u/2u with a constexpr variable with 108 // meaningful name. 109 switch(uniforms.u_alphaOp) { 110 case 0u: { // AlphaOp: DontChange 111 break; 112 } 113 case 1u: { // AlphaOp: Premultiply 114 srcColor = vec4<f32>(srcColor.rgb * srcColor.a, srcColor.a); 115 break; 116 } 117 case 2u: { // AlphaOp: Unpremultiply 118 if (srcColor.a != 0.0) { 119 srcColor = vec4<f32>(srcColor.rgb / srcColor.a, srcColor.a); 120 } 121 break; 122 } 123 default: { 124 break; 125 } 126 } 127 128 return srcColor; 129 } 130 )"; 131 132 struct Uniform { 133 float scaleX; 134 float scaleY; 135 float offsetX; 136 float offsetY; 137 wgpu::AlphaOp alphaOp; 138 }; 139 static_assert(sizeof(Uniform) == 20, ""); 140 141 // TODO(crbug.com/dawn/856): Expand copyTextureForBrowser to support any 142 // non-depth, non-stencil, non-compressed texture format pair copy. Now this API 143 // supports CopyImageBitmapToTexture normal format pairs. ValidateCopyTextureFormatConversion(const wgpu::TextureFormat srcFormat,const wgpu::TextureFormat dstFormat)144 MaybeError ValidateCopyTextureFormatConversion(const wgpu::TextureFormat srcFormat, 145 const wgpu::TextureFormat dstFormat) { 146 switch (srcFormat) { 147 case wgpu::TextureFormat::BGRA8Unorm: 148 case wgpu::TextureFormat::RGBA8Unorm: 149 break; 150 default: 151 return DAWN_FORMAT_VALIDATION_ERROR( 152 "Source texture format (%s) is not supported.", srcFormat); 153 } 154 155 switch (dstFormat) { 156 case wgpu::TextureFormat::R8Unorm: 157 case wgpu::TextureFormat::R16Float: 158 case wgpu::TextureFormat::R32Float: 159 case wgpu::TextureFormat::RG8Unorm: 160 case wgpu::TextureFormat::RG16Float: 161 case wgpu::TextureFormat::RG32Float: 162 case wgpu::TextureFormat::RGBA8Unorm: 163 case wgpu::TextureFormat::BGRA8Unorm: 164 case wgpu::TextureFormat::RGB10A2Unorm: 165 case wgpu::TextureFormat::RGBA16Float: 166 case wgpu::TextureFormat::RGBA32Float: 167 break; 168 default: 169 return DAWN_FORMAT_VALIDATION_ERROR( 170 "Destination texture format (%s) is not supported.", dstFormat); 171 } 172 173 return {}; 174 } 175 GetCachedPipeline(InternalPipelineStore * store,wgpu::TextureFormat dstFormat)176 RenderPipelineBase* GetCachedPipeline(InternalPipelineStore* store, 177 wgpu::TextureFormat dstFormat) { 178 auto pipeline = store->copyTextureForBrowserPipelines.find(dstFormat); 179 if (pipeline != store->copyTextureForBrowserPipelines.end()) { 180 return pipeline->second.Get(); 181 } 182 return nullptr; 183 } 184 GetOrCreateCopyTextureForBrowserPipeline(DeviceBase * device,wgpu::TextureFormat dstFormat)185 ResultOrError<RenderPipelineBase*> GetOrCreateCopyTextureForBrowserPipeline( 186 DeviceBase* device, 187 wgpu::TextureFormat dstFormat) { 188 InternalPipelineStore* store = device->GetInternalPipelineStore(); 189 190 if (GetCachedPipeline(store, dstFormat) == nullptr) { 191 // Create vertex shader module if not cached before. 192 if (store->copyTextureForBrowser == nullptr) { 193 DAWN_TRY_ASSIGN( 194 store->copyTextureForBrowser, 195 utils::CreateShaderModule(device, sCopyTextureForBrowserShader)); 196 } 197 198 ShaderModuleBase* shaderModule = store->copyTextureForBrowser.Get(); 199 200 // Prepare vertex stage. 201 VertexState vertex = {}; 202 vertex.module = shaderModule; 203 vertex.entryPoint = "vs_main"; 204 205 // Prepare frgament stage. 206 FragmentState fragment = {}; 207 fragment.module = shaderModule; 208 fragment.entryPoint = "fs_main"; 209 210 // Prepare color state. 211 ColorTargetState target = {}; 212 target.format = dstFormat; 213 214 // Create RenderPipeline. 215 RenderPipelineDescriptor renderPipelineDesc = {}; 216 217 // Generate the layout based on shader modules. 218 renderPipelineDesc.layout = nullptr; 219 220 renderPipelineDesc.vertex = vertex; 221 renderPipelineDesc.fragment = &fragment; 222 223 renderPipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList; 224 225 fragment.targetCount = 1; 226 fragment.targets = ⌖ 227 228 Ref<RenderPipelineBase> pipeline; 229 DAWN_TRY_ASSIGN(pipeline, device->CreateRenderPipeline(&renderPipelineDesc)); 230 store->copyTextureForBrowserPipelines.insert({dstFormat, std::move(pipeline)}); 231 } 232 233 return GetCachedPipeline(store, dstFormat); 234 } 235 236 } // anonymous namespace 237 ValidateCopyTextureForBrowser(DeviceBase * device,const ImageCopyTexture * source,const ImageCopyTexture * destination,const Extent3D * copySize,const CopyTextureForBrowserOptions * options)238 MaybeError ValidateCopyTextureForBrowser(DeviceBase* device, 239 const ImageCopyTexture* source, 240 const ImageCopyTexture* destination, 241 const Extent3D* copySize, 242 const CopyTextureForBrowserOptions* options) { 243 DAWN_TRY(device->ValidateObject(source->texture)); 244 DAWN_TRY(device->ValidateObject(destination->texture)); 245 246 DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, *source, *copySize), 247 "validating the ImageCopyTexture for the source"); 248 DAWN_TRY_CONTEXT(ValidateImageCopyTexture(device, *destination, *copySize), 249 "validating the ImageCopyTexture for the destination"); 250 251 DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *source, *copySize), 252 "validating that the copy fits in the source"); 253 DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *destination, *copySize), 254 "validating that the copy fits in the destination"); 255 256 DAWN_TRY(ValidateTextureToTextureCopyCommonRestrictions(*source, *destination, *copySize)); 257 258 DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", 259 source->origin.z); 260 DAWN_INVALID_IF(copySize->depthOrArrayLayers > 1, 261 "Copy is for more than one array layer (%u)", copySize->depthOrArrayLayers); 262 263 DAWN_INVALID_IF( 264 source->texture->GetSampleCount() > 1 || destination->texture->GetSampleCount() > 1, 265 "The source texture sample count (%u) or the destination texture sample count (%u) is " 266 "not 1.", 267 source->texture->GetSampleCount(), destination->texture->GetSampleCount()); 268 269 DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc)); 270 DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::TextureBinding)); 271 272 DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::CopyDst)); 273 DAWN_TRY(ValidateCanUseAs(destination->texture, wgpu::TextureUsage::RenderAttachment)); 274 275 DAWN_TRY(ValidateCopyTextureFormatConversion(source->texture->GetFormat().format, 276 destination->texture->GetFormat().format)); 277 278 DAWN_INVALID_IF(options->nextInChain != nullptr, "nextInChain must be nullptr"); 279 DAWN_TRY(ValidateAlphaOp(options->alphaOp)); 280 281 return {}; 282 } 283 DoCopyTextureForBrowser(DeviceBase * device,const ImageCopyTexture * source,const ImageCopyTexture * destination,const Extent3D * copySize,const CopyTextureForBrowserOptions * options)284 MaybeError DoCopyTextureForBrowser(DeviceBase* device, 285 const ImageCopyTexture* source, 286 const ImageCopyTexture* destination, 287 const Extent3D* copySize, 288 const CopyTextureForBrowserOptions* options) { 289 // TODO(crbug.com/dawn/856): In D3D12 and Vulkan, compatible texture format can directly 290 // copy to each other. This can be a potential fast path. 291 292 // Noop copy 293 if (copySize->width == 0 || copySize->height == 0 || copySize->depthOrArrayLayers == 0) { 294 return {}; 295 } 296 297 RenderPipelineBase* pipeline; 298 DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyTextureForBrowserPipeline( 299 device, destination->texture->GetFormat().format)); 300 301 // Prepare bind group layout. 302 Ref<BindGroupLayoutBase> layout; 303 DAWN_TRY_ASSIGN(layout, pipeline->GetBindGroupLayout(0)); 304 305 Extent3D srcTextureSize = source->texture->GetSize(); 306 307 // Prepare binding 0 resource: uniform buffer. 308 Uniform uniformData = { 309 copySize->width / static_cast<float>(srcTextureSize.width), 310 copySize->height / static_cast<float>(srcTextureSize.height), // scale 311 source->origin.x / static_cast<float>(srcTextureSize.width), 312 source->origin.y / static_cast<float>(srcTextureSize.height), // offset 313 wgpu::AlphaOp::DontChange // alphaOp default value: DontChange 314 }; 315 316 // Handle flipY. FlipY here means we flip the source texture firstly and then 317 // do copy. This helps on the case which source texture is flipped and the copy 318 // need to unpack the flip. 319 if (options->flipY) { 320 uniformData.scaleY *= -1.0; 321 uniformData.offsetY += copySize->height / static_cast<float>(srcTextureSize.height); 322 } 323 324 // Set alpha op. 325 uniformData.alphaOp = options->alphaOp; 326 327 Ref<BufferBase> uniformBuffer; 328 DAWN_TRY_ASSIGN( 329 uniformBuffer, 330 utils::CreateBufferFromData( 331 device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform, {uniformData})); 332 333 // Prepare binding 1 resource: sampler 334 // Use default configuration, filterMode set to Nearest for min and mag. 335 SamplerDescriptor samplerDesc = {}; 336 Ref<SamplerBase> sampler; 337 DAWN_TRY_ASSIGN(sampler, device->CreateSampler(&samplerDesc)); 338 339 // Prepare binding 2 resource: sampled texture 340 TextureViewDescriptor srcTextureViewDesc = {}; 341 srcTextureViewDesc.baseMipLevel = source->mipLevel; 342 srcTextureViewDesc.mipLevelCount = 1; 343 srcTextureViewDesc.arrayLayerCount = 1; 344 Ref<TextureViewBase> srcTextureView; 345 DAWN_TRY_ASSIGN(srcTextureView, 346 device->CreateTextureView(source->texture, &srcTextureViewDesc)); 347 348 // Create bind group after all binding entries are set. 349 Ref<BindGroupBase> bindGroup; 350 DAWN_TRY_ASSIGN(bindGroup, utils::MakeBindGroup( 351 device, layout, 352 {{0, uniformBuffer}, {1, sampler}, {2, srcTextureView}})); 353 354 // Create command encoder. 355 CommandEncoderDescriptor encoderDesc = {}; 356 // TODO(dawn:723): change to not use AcquireRef for reentrant object creation. 357 Ref<CommandEncoder> encoder = AcquireRef(device->APICreateCommandEncoder(&encoderDesc)); 358 359 // Prepare dst texture view as color Attachment. 360 TextureViewDescriptor dstTextureViewDesc; 361 dstTextureViewDesc.baseMipLevel = destination->mipLevel; 362 dstTextureViewDesc.mipLevelCount = 1; 363 dstTextureViewDesc.baseArrayLayer = destination->origin.z; 364 dstTextureViewDesc.arrayLayerCount = 1; 365 Ref<TextureViewBase> dstView; 366 DAWN_TRY_ASSIGN(dstView, 367 device->CreateTextureView(destination->texture, &dstTextureViewDesc)); 368 369 // Prepare render pass color attachment descriptor. 370 RenderPassColorAttachment colorAttachmentDesc; 371 372 colorAttachmentDesc.view = dstView.Get(); 373 colorAttachmentDesc.loadOp = wgpu::LoadOp::Load; 374 colorAttachmentDesc.storeOp = wgpu::StoreOp::Store; 375 colorAttachmentDesc.clearColor = {0.0, 0.0, 0.0, 1.0}; 376 377 // Create render pass. 378 RenderPassDescriptor renderPassDesc; 379 renderPassDesc.colorAttachmentCount = 1; 380 renderPassDesc.colorAttachments = &colorAttachmentDesc; 381 // TODO(dawn:723): change to not use AcquireRef for reentrant object creation. 382 Ref<RenderPassEncoder> passEncoder = 383 AcquireRef(encoder->APIBeginRenderPass(&renderPassDesc)); 384 385 // Start pipeline and encode commands to complete 386 // the copy from src texture to dst texture with transformation. 387 passEncoder->APISetPipeline(pipeline); 388 passEncoder->APISetBindGroup(0, bindGroup.Get()); 389 passEncoder->APISetViewport(destination->origin.x, destination->origin.y, copySize->width, 390 copySize->height, 0.0, 1.0); 391 passEncoder->APIDraw(3); 392 passEncoder->APIEndPass(); 393 394 // Finsh encoding. 395 // TODO(dawn:723): change to not use AcquireRef for reentrant object creation. 396 Ref<CommandBufferBase> commandBuffer = AcquireRef(encoder->APIFinish()); 397 CommandBufferBase* submitCommandBuffer = commandBuffer.Get(); 398 399 // Submit command buffer. 400 device->GetQueue()->APISubmit(1, &submitCommandBuffer); 401 402 return {}; 403 } 404 405 } // namespace dawn_native 406