CommandEncoder.cpp (12592B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "CommandEncoder.h" 7 8 #include "Buffer.h" 9 #include "CommandBuffer.h" 10 #include "ComputePassEncoder.h" 11 #include "Device.h" 12 #include "ExternalTexture.h" 13 #include "RenderPassEncoder.h" 14 #include "TextureView.h" 15 #include "Utility.h" 16 #include "ipc/WebGPUChild.h" 17 #include "mozilla/dom/UnionTypes.h" 18 #include "mozilla/dom/WebGPUBinding.h" 19 #include "mozilla/webgpu/CanvasContext.h" 20 #include "mozilla/webgpu/ffi/wgpu.h" 21 22 namespace mozilla::webgpu { 23 24 GPU_IMPL_CYCLE_COLLECTION(CommandEncoder, mParent, mExternalTextures) 25 GPU_IMPL_JS_WRAP(CommandEncoder) 26 27 void CommandEncoder::ConvertTextureDataLayoutToFFI( 28 const dom::GPUTexelCopyBufferLayout& aLayout, 29 ffi::WGPUTexelCopyBufferLayout* aLayoutFFI) { 30 *aLayoutFFI = {}; 31 aLayoutFFI->offset = aLayout.mOffset; 32 33 if (aLayout.mBytesPerRow.WasPassed()) { 34 aLayoutFFI->bytes_per_row = &aLayout.mBytesPerRow.Value(); 35 } else { 36 aLayoutFFI->bytes_per_row = nullptr; 37 } 38 39 if (aLayout.mRowsPerImage.WasPassed()) { 40 aLayoutFFI->rows_per_image = &aLayout.mRowsPerImage.Value(); 41 } else { 42 aLayoutFFI->rows_per_image = nullptr; 43 } 44 } 45 46 void CommandEncoder::ConvertTextureCopyViewToFFI( 47 const dom::GPUTexelCopyTextureInfo& aCopy, 48 ffi::WGPUTexelCopyTextureInfo* aViewFFI) { 49 *aViewFFI = {}; 50 aViewFFI->texture = aCopy.mTexture->GetId(); 51 aViewFFI->mip_level = aCopy.mMipLevel; 52 const auto& origin = aCopy.mOrigin; 53 if (origin.IsRangeEnforcedUnsignedLongSequence()) { 54 const auto& seq = origin.GetAsRangeEnforcedUnsignedLongSequence(); 55 aViewFFI->origin.x = seq.Length() > 0 ? seq[0] : 0; 56 aViewFFI->origin.y = seq.Length() > 1 ? seq[1] : 0; 57 aViewFFI->origin.z = seq.Length() > 2 ? seq[2] : 0; 58 } else if (origin.IsGPUOrigin3DDict()) { 59 const auto& dict = origin.GetAsGPUOrigin3DDict(); 60 aViewFFI->origin.x = dict.mX; 61 aViewFFI->origin.y = dict.mY; 62 aViewFFI->origin.z = dict.mZ; 63 } else { 64 MOZ_CRASH("Unexpected origin type"); 65 } 66 aViewFFI->aspect = ConvertTextureAspect(aCopy.mAspect); 67 } 68 69 static ffi::WGPUTexelCopyTextureInfo ConvertTextureCopyView( 70 const dom::GPUTexelCopyTextureInfo& aCopy) { 71 ffi::WGPUTexelCopyTextureInfo view = {}; 72 CommandEncoder::ConvertTextureCopyViewToFFI(aCopy, &view); 73 return view; 74 } 75 76 CommandEncoder::CommandEncoder(Device* const aParent, RawId aId) 77 : ObjectBase(aParent->GetChild(), aId, 78 ffi::wgpu_client_drop_command_encoder), 79 ChildOf(aParent), 80 mState(CommandEncoderState::Open) {} 81 82 CommandEncoder::~CommandEncoder() = default; 83 84 void CommandEncoder::TrackPresentationContext( 85 WeakPtr<CanvasContext> aTargetContext) { 86 if (aTargetContext) { 87 mPresentationContexts.AppendElement(aTargetContext); 88 } 89 } 90 91 void CommandEncoder::CopyBufferToBuffer( 92 const Buffer& aSource, BufferAddress aSourceOffset, 93 const Buffer& aDestination, BufferAddress aDestinationOffset, 94 const dom::Optional<BufferAddress>& aSize) { 95 // In Javascript, `size === undefined` means "copy from source offset to end 96 // of buffer". wgpu_command_encoder_copy_buffer_to_buffer uses a value of 97 // UINT64_MAX to encode this. If the requested copy size was UINT64_MAX, fudge 98 // it to a different value that will still be rejected for misalignment on the 99 // device timeline. 100 BufferAddress size; 101 if (aSize.WasPassed()) { 102 if (aSize.Value() == std::numeric_limits<uint64_t>::max()) { 103 size = std::numeric_limits<uint64_t>::max() - 4; 104 } else { 105 size = aSize.Value(); 106 } 107 } else { 108 size = std::numeric_limits<uint64_t>::max(); 109 } 110 111 ffi::wgpu_command_encoder_copy_buffer_to_buffer( 112 GetClient(), mParent->GetId(), GetId(), aSource.GetId(), aSourceOffset, 113 aDestination.GetId(), aDestinationOffset, size); 114 } 115 116 void CommandEncoder::CopyBufferToTexture( 117 const dom::GPUTexelCopyBufferInfo& aSource, 118 const dom::GPUTexelCopyTextureInfo& aDestination, 119 const dom::GPUExtent3D& aCopySize) { 120 ffi::WGPUTexelCopyBufferLayout src_layout = {}; 121 CommandEncoder::ConvertTextureDataLayoutToFFI(aSource, &src_layout); 122 ffi::wgpu_command_encoder_copy_buffer_to_texture( 123 GetClient(), mParent->GetId(), GetId(), aSource.mBuffer->GetId(), 124 &src_layout, ConvertTextureCopyView(aDestination), 125 ConvertExtent(aCopySize)); 126 127 TrackPresentationContext(aDestination.mTexture->mTargetContext); 128 } 129 void CommandEncoder::CopyTextureToBuffer( 130 const dom::GPUTexelCopyTextureInfo& aSource, 131 const dom::GPUTexelCopyBufferInfo& aDestination, 132 const dom::GPUExtent3D& aCopySize) { 133 ffi::WGPUTexelCopyBufferLayout dstLayout = {}; 134 CommandEncoder::ConvertTextureDataLayoutToFFI(aDestination, &dstLayout); 135 ffi::wgpu_command_encoder_copy_texture_to_buffer( 136 GetClient(), mParent->GetId(), GetId(), ConvertTextureCopyView(aSource), 137 aDestination.mBuffer->GetId(), &dstLayout, ConvertExtent(aCopySize)); 138 } 139 void CommandEncoder::CopyTextureToTexture( 140 const dom::GPUTexelCopyTextureInfo& aSource, 141 const dom::GPUTexelCopyTextureInfo& aDestination, 142 const dom::GPUExtent3D& aCopySize) { 143 ffi::wgpu_command_encoder_copy_texture_to_texture( 144 GetClient(), mParent->GetId(), GetId(), ConvertTextureCopyView(aSource), 145 ConvertTextureCopyView(aDestination), ConvertExtent(aCopySize)); 146 147 TrackPresentationContext(aDestination.mTexture->mTargetContext); 148 } 149 150 void CommandEncoder::ClearBuffer(const Buffer& aBuffer, const uint64_t aOffset, 151 const dom::Optional<uint64_t>& aSize) { 152 uint64_t sizeVal = 0xdeaddead; 153 uint64_t* size = nullptr; 154 if (aSize.WasPassed()) { 155 sizeVal = aSize.Value(); 156 size = &sizeVal; 157 } 158 159 ffi::wgpu_command_encoder_clear_buffer(GetClient(), mParent->GetId(), GetId(), 160 aBuffer.GetId(), aOffset, size); 161 } 162 163 void CommandEncoder::PushDebugGroup(const nsAString& aString) { 164 NS_ConvertUTF16toUTF8 marker(aString); 165 ffi::wgpu_command_encoder_push_debug_group(GetClient(), mParent->GetId(), 166 GetId(), &marker); 167 } 168 void CommandEncoder::PopDebugGroup() { 169 ffi::wgpu_command_encoder_pop_debug_group(GetClient(), mParent->GetId(), 170 GetId()); 171 } 172 void CommandEncoder::InsertDebugMarker(const nsAString& aString) { 173 NS_ConvertUTF16toUTF8 marker(aString); 174 ffi::wgpu_command_encoder_insert_debug_marker(GetClient(), mParent->GetId(), 175 GetId(), &marker); 176 } 177 178 already_AddRefed<ComputePassEncoder> CommandEncoder::BeginComputePass( 179 const dom::GPUComputePassDescriptor& aDesc) { 180 auto id = ffi::wgpu_client_make_compute_pass_encoder_id(GetClient()); 181 RefPtr<ComputePassEncoder> pass = new ComputePassEncoder(this, id, aDesc); 182 pass->SetLabel(aDesc.mLabel); 183 if (mState == CommandEncoderState::Ended) { 184 // Because we do not call wgpu until the pass is ended, we need to generate 185 // this error ourselves in order to report it at the correct time. 186 187 const auto* message = "Encoding must not have ended"; 188 ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message); 189 190 pass->Invalidate(); 191 } else if (mState == CommandEncoderState::Locked) { 192 // This is not sufficient to handle this case properly. Invalidity 193 // needs to be transferred from the pass to the encoder when the pass 194 // ends. Bug 1971650. 195 pass->Invalidate(); 196 } else { 197 mState = CommandEncoderState::Locked; 198 } 199 return pass.forget(); 200 } 201 202 already_AddRefed<RenderPassEncoder> CommandEncoder::BeginRenderPass( 203 const dom::GPURenderPassDescriptor& aDesc) { 204 dom::GPURenderPassDescriptor desc{aDesc}; 205 206 auto coerceToViewInPlace = 207 [](dom::OwningGPUTextureOrGPUTextureView& texOrView) 208 -> RefPtr<TextureView> { 209 RefPtr<TextureView> view; 210 switch (texOrView.GetType()) { 211 case dom::OwningGPUTextureOrGPUTextureView::Type::eGPUTexture: { 212 dom::GPUTextureViewDescriptor defaultDesc{}; 213 RefPtr<Texture> tex = texOrView.GetAsGPUTexture(); 214 texOrView.SetAsGPUTextureView() = tex->CreateView(defaultDesc); 215 break; 216 } 217 218 case dom::OwningGPUTextureOrGPUTextureView::Type::eGPUTextureView: 219 // Nothing to do, great! 220 break; 221 } 222 view = texOrView.GetAsGPUTextureView(); 223 return view; 224 }; 225 226 for (auto& at : desc.mColorAttachments) { 227 TrackPresentationContext(coerceToViewInPlace(at.mView)->GetTargetContext()); 228 if (at.mResolveTarget.WasPassed()) { 229 TrackPresentationContext( 230 coerceToViewInPlace(at.mResolveTarget.Value())->GetTargetContext()); 231 } 232 } 233 if (desc.mDepthStencilAttachment.WasPassed()) { 234 coerceToViewInPlace(desc.mDepthStencilAttachment.Value().mView); 235 } 236 237 auto id = ffi::wgpu_client_make_render_pass_encoder_id(GetClient()); 238 RefPtr<RenderPassEncoder> pass = new RenderPassEncoder(this, id, desc); 239 pass->SetLabel(desc.mLabel); 240 if (mState == CommandEncoderState::Ended) { 241 // Because we do not call wgpu until the pass is ended, we need to generate 242 // this error ourselves in order to report it at the correct time. 243 244 const auto* message = "Encoding must not have ended"; 245 ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message); 246 247 pass->Invalidate(); 248 } else if (mState == CommandEncoderState::Locked) { 249 // This is not sufficient to handle this case properly. Invalidity 250 // needs to be transferred from the pass to the encoder when the pass 251 // ends. Bug 1971650. 252 pass->Invalidate(); 253 } else { 254 mState = CommandEncoderState::Locked; 255 } 256 return pass.forget(); 257 } 258 259 void CommandEncoder::ResolveQuerySet(QuerySet& aQuerySet, uint32_t aFirstQuery, 260 uint32_t aQueryCount, 261 webgpu::Buffer& aDestination, 262 uint64_t aDestinationOffset) { 263 ffi::wgpu_command_encoder_resolve_query_set( 264 GetClient(), mParent->GetId(), GetId(), aQuerySet.GetId(), aFirstQuery, 265 aQueryCount, aDestination.GetId(), aDestinationOffset); 266 } 267 268 void CommandEncoder::EndComputePass( 269 ffi::WGPURecordedComputePass& aPass, CanvasContextArray& aCanvasContexts, 270 Span<RefPtr<ExternalTexture>> aExternalTextures) { 271 if (mState != CommandEncoderState::Locked) { 272 const auto* message = "Encoder is not currently locked"; 273 ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message); 274 return; 275 } 276 mState = CommandEncoderState::Open; 277 278 for (const auto& context : aCanvasContexts) { 279 TrackPresentationContext(context); 280 } 281 mExternalTextures.AppendElements(aExternalTextures); 282 283 ffi::wgpu_compute_pass_finish(GetClient(), mParent->GetId(), GetId(), &aPass); 284 } 285 286 void CommandEncoder::EndRenderPass( 287 ffi::WGPURecordedRenderPass& aPass, CanvasContextArray& aCanvasContexts, 288 Span<RefPtr<ExternalTexture>> aExternalTextures) { 289 if (mState != CommandEncoderState::Locked) { 290 const auto* message = "Encoder is not currently locked"; 291 ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message); 292 return; 293 } 294 mState = CommandEncoderState::Open; 295 296 for (const auto& context : aCanvasContexts) { 297 TrackPresentationContext(context); 298 } 299 mExternalTextures.AppendElements(aExternalTextures); 300 301 ffi::wgpu_render_pass_finish(GetClient(), mParent->GetId(), GetId(), &aPass); 302 } 303 304 already_AddRefed<CommandBuffer> CommandEncoder::Finish( 305 const dom::GPUCommandBufferDescriptor& aDesc) { 306 ffi::WGPUCommandBufferDescriptor desc = {}; 307 308 webgpu::StringHelper label(aDesc.mLabel); 309 desc.label = label.Get(); 310 311 if (mState == CommandEncoderState::Locked) { 312 // Most errors that could occur here will be raised by wgpu. But since we 313 // don't tell wgpu about passes until they are ended, we need to raise an 314 // error if the application left a pass open. 315 const auto* message = 316 "Encoder is locked by a previously created render/compute pass"; 317 ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message); 318 } 319 RawId command_buffer_id = ffi::wgpu_command_encoder_finish( 320 GetClient(), mParent->GetId(), GetId(), &desc); 321 322 mState = CommandEncoderState::Ended; 323 324 RefPtr<CommandBuffer> comb = new CommandBuffer( 325 mParent, command_buffer_id, std::move(mPresentationContexts), 326 std::move(mExternalTextures)); 327 comb->SetLabel(aDesc.mLabel); 328 return comb.forget(); 329 } 330 331 } // namespace mozilla::webgpu