• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = &target;
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