Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CommandEncoder.h"
#include "Buffer.h"
#include "CommandBuffer.h"
#include "ComputePassEncoder.h"
#include "Device.h"
#include "ExternalTexture.h"
#include "RenderPassEncoder.h"
#include "TextureView.h"
#include "Utility.h"
#include "ipc/WebGPUChild.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/dom/WebGPUBinding.h"
#include "mozilla/webgpu/CanvasContext.h"
#include "mozilla/webgpu/ffi/wgpu.h"
namespace mozilla::webgpu {
GPU_IMPL_CYCLE_COLLECTION(CommandEncoder, mParent, mExternalTextures)
GPU_IMPL_JS_WRAP(CommandEncoder)
void CommandEncoder::ConvertTextureDataLayoutToFFI(
const dom::GPUTexelCopyBufferLayout& aLayout,
ffi::WGPUTexelCopyBufferLayout* aLayoutFFI) {
*aLayoutFFI = {};
aLayoutFFI->offset = aLayout.mOffset;
if (aLayout.mBytesPerRow.WasPassed()) {
aLayoutFFI->bytes_per_row = &aLayout.mBytesPerRow.Value();
} else {
aLayoutFFI->bytes_per_row = nullptr;
}
if (aLayout.mRowsPerImage.WasPassed()) {
aLayoutFFI->rows_per_image = &aLayout.mRowsPerImage.Value();
} else {
aLayoutFFI->rows_per_image = nullptr;
}
}
void CommandEncoder::ConvertTextureCopyViewToFFI(
const dom::GPUTexelCopyTextureInfo& aCopy,
ffi::WGPUTexelCopyTextureInfo* aViewFFI) {
*aViewFFI = {};
aViewFFI->texture = aCopy.mTexture->GetId();
aViewFFI->mip_level = aCopy.mMipLevel;
const auto& origin = aCopy.mOrigin;
if (origin.IsRangeEnforcedUnsignedLongSequence()) {
const auto& seq = origin.GetAsRangeEnforcedUnsignedLongSequence();
aViewFFI->origin.x = seq.Length() > 0 ? seq[0] : 0;
aViewFFI->origin.y = seq.Length() > 1 ? seq[1] : 0;
aViewFFI->origin.z = seq.Length() > 2 ? seq[2] : 0;
} else if (origin.IsGPUOrigin3DDict()) {
const auto& dict = origin.GetAsGPUOrigin3DDict();
aViewFFI->origin.x = dict.mX;
aViewFFI->origin.y = dict.mY;
aViewFFI->origin.z = dict.mZ;
} else {
MOZ_CRASH("Unexpected origin type");
}
aViewFFI->aspect = ConvertTextureAspect(aCopy.mAspect);
}
static ffi::WGPUTexelCopyTextureInfo ConvertTextureCopyView(
const dom::GPUTexelCopyTextureInfo& aCopy) {
ffi::WGPUTexelCopyTextureInfo view = {};
CommandEncoder::ConvertTextureCopyViewToFFI(aCopy, &view);
return view;
}
CommandEncoder::CommandEncoder(Device* const aParent, RawId aId)
: ObjectBase(aParent->GetChild(), aId,
ffi::wgpu_client_drop_command_encoder),
ChildOf(aParent),
mState(CommandEncoderState::Open) {}
CommandEncoder::~CommandEncoder() = default;
void CommandEncoder::TrackPresentationContext(
WeakPtr<CanvasContext> aTargetContext) {
if (aTargetContext) {
mPresentationContexts.AppendElement(aTargetContext);
}
}
void CommandEncoder::CopyBufferToBuffer(
const Buffer& aSource, BufferAddress aSourceOffset,
const Buffer& aDestination, BufferAddress aDestinationOffset,
const dom::Optional<BufferAddress>& aSize) {
// In Javascript, `size === undefined` means "copy from source offset to end
// of buffer". wgpu_command_encoder_copy_buffer_to_buffer uses a value of
// UINT64_MAX to encode this. If the requested copy size was UINT64_MAX, fudge
// it to a different value that will still be rejected for misalignment on the
// device timeline.
BufferAddress size;
if (aSize.WasPassed()) {
if (aSize.Value() == std::numeric_limits<uint64_t>::max()) {
size = std::numeric_limits<uint64_t>::max() - 4;
} else {
size = aSize.Value();
}
} else {
size = std::numeric_limits<uint64_t>::max();
}
ffi::wgpu_command_encoder_copy_buffer_to_buffer(
GetClient(), mParent->GetId(), GetId(), aSource.GetId(), aSourceOffset,
aDestination.GetId(), aDestinationOffset, size);
}
void CommandEncoder::CopyBufferToTexture(
const dom::GPUTexelCopyBufferInfo& aSource,
const dom::GPUTexelCopyTextureInfo& aDestination,
const dom::GPUExtent3D& aCopySize) {
ffi::WGPUTexelCopyBufferLayout src_layout = {};
CommandEncoder::ConvertTextureDataLayoutToFFI(aSource, &src_layout);
ffi::wgpu_command_encoder_copy_buffer_to_texture(
GetClient(), mParent->GetId(), GetId(), aSource.mBuffer->GetId(),
&src_layout, ConvertTextureCopyView(aDestination),
ConvertExtent(aCopySize));
TrackPresentationContext(aDestination.mTexture->mTargetContext);
}
void CommandEncoder::CopyTextureToBuffer(
const dom::GPUTexelCopyTextureInfo& aSource,
const dom::GPUTexelCopyBufferInfo& aDestination,
const dom::GPUExtent3D& aCopySize) {
ffi::WGPUTexelCopyBufferLayout dstLayout = {};
CommandEncoder::ConvertTextureDataLayoutToFFI(aDestination, &dstLayout);
ffi::wgpu_command_encoder_copy_texture_to_buffer(
GetClient(), mParent->GetId(), GetId(), ConvertTextureCopyView(aSource),
aDestination.mBuffer->GetId(), &dstLayout, ConvertExtent(aCopySize));
}
void CommandEncoder::CopyTextureToTexture(
const dom::GPUTexelCopyTextureInfo& aSource,
const dom::GPUTexelCopyTextureInfo& aDestination,
const dom::GPUExtent3D& aCopySize) {
ffi::wgpu_command_encoder_copy_texture_to_texture(
GetClient(), mParent->GetId(), GetId(), ConvertTextureCopyView(aSource),
ConvertTextureCopyView(aDestination), ConvertExtent(aCopySize));
TrackPresentationContext(aDestination.mTexture->mTargetContext);
}
void CommandEncoder::ClearBuffer(const Buffer& aBuffer, const uint64_t aOffset,
const dom::Optional<uint64_t>& aSize) {
uint64_t sizeVal = 0xdeaddead;
uint64_t* size = nullptr;
if (aSize.WasPassed()) {
sizeVal = aSize.Value();
size = &sizeVal;
}
ffi::wgpu_command_encoder_clear_buffer(GetClient(), mParent->GetId(), GetId(),
aBuffer.GetId(), aOffset, size);
}
void CommandEncoder::PushDebugGroup(const nsAString& aString) {
NS_ConvertUTF16toUTF8 marker(aString);
ffi::wgpu_command_encoder_push_debug_group(GetClient(), mParent->GetId(),
GetId(), &marker);
}
void CommandEncoder::PopDebugGroup() {
ffi::wgpu_command_encoder_pop_debug_group(GetClient(), mParent->GetId(),
GetId());
}
void CommandEncoder::InsertDebugMarker(const nsAString& aString) {
NS_ConvertUTF16toUTF8 marker(aString);
ffi::wgpu_command_encoder_insert_debug_marker(GetClient(), mParent->GetId(),
GetId(), &marker);
}
already_AddRefed<ComputePassEncoder> CommandEncoder::BeginComputePass(
const dom::GPUComputePassDescriptor& aDesc) {
auto id = ffi::wgpu_client_make_compute_pass_encoder_id(GetClient());
RefPtr<ComputePassEncoder> pass = new ComputePassEncoder(this, id, aDesc);
pass->SetLabel(aDesc.mLabel);
if (mState == CommandEncoderState::Ended) {
// Because we do not call wgpu until the pass is ended, we need to generate
// this error ourselves in order to report it at the correct time.
const auto* message = "Encoding must not have ended";
ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message);
pass->Invalidate();
} else if (mState == CommandEncoderState::Locked) {
// This is not sufficient to handle this case properly. Invalidity
// needs to be transferred from the pass to the encoder when the pass
// ends. Bug 1971650.
pass->Invalidate();
} else {
mState = CommandEncoderState::Locked;
}
return pass.forget();
}
already_AddRefed<RenderPassEncoder> CommandEncoder::BeginRenderPass(
const dom::GPURenderPassDescriptor& aDesc) {
dom::GPURenderPassDescriptor desc{aDesc};
auto coerceToViewInPlace =
[](dom::OwningGPUTextureOrGPUTextureView& texOrView)
-> RefPtr<TextureView> {
RefPtr<TextureView> view;
switch (texOrView.GetType()) {
case dom::OwningGPUTextureOrGPUTextureView::Type::eGPUTexture: {
dom::GPUTextureViewDescriptor defaultDesc{};
RefPtr<Texture> tex = texOrView.GetAsGPUTexture();
texOrView.SetAsGPUTextureView() = tex->CreateView(defaultDesc);
break;
}
case dom::OwningGPUTextureOrGPUTextureView::Type::eGPUTextureView:
// Nothing to do, great!
break;
}
view = texOrView.GetAsGPUTextureView();
return view;
};
for (auto& at : desc.mColorAttachments) {
TrackPresentationContext(coerceToViewInPlace(at.mView)->GetTargetContext());
if (at.mResolveTarget.WasPassed()) {
TrackPresentationContext(
coerceToViewInPlace(at.mResolveTarget.Value())->GetTargetContext());
}
}
if (desc.mDepthStencilAttachment.WasPassed()) {
coerceToViewInPlace(desc.mDepthStencilAttachment.Value().mView);
}
auto id = ffi::wgpu_client_make_render_pass_encoder_id(GetClient());
RefPtr<RenderPassEncoder> pass = new RenderPassEncoder(this, id, desc);
pass->SetLabel(desc.mLabel);
if (mState == CommandEncoderState::Ended) {
// Because we do not call wgpu until the pass is ended, we need to generate
// this error ourselves in order to report it at the correct time.
const auto* message = "Encoding must not have ended";
ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message);
pass->Invalidate();
} else if (mState == CommandEncoderState::Locked) {
// This is not sufficient to handle this case properly. Invalidity
// needs to be transferred from the pass to the encoder when the pass
// ends. Bug 1971650.
pass->Invalidate();
} else {
mState = CommandEncoderState::Locked;
}
return pass.forget();
}
void CommandEncoder::ResolveQuerySet(QuerySet& aQuerySet, uint32_t aFirstQuery,
uint32_t aQueryCount,
webgpu::Buffer& aDestination,
uint64_t aDestinationOffset) {
ffi::wgpu_command_encoder_resolve_query_set(
GetClient(), mParent->GetId(), GetId(), aQuerySet.GetId(), aFirstQuery,
aQueryCount, aDestination.GetId(), aDestinationOffset);
}
void CommandEncoder::EndComputePass(
ffi::WGPURecordedComputePass& aPass, CanvasContextArray& aCanvasContexts,
Span<RefPtr<ExternalTexture>> aExternalTextures) {
if (mState != CommandEncoderState::Locked) {
const auto* message = "Encoder is not currently locked";
ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message);
return;
}
mState = CommandEncoderState::Open;
for (const auto& context : aCanvasContexts) {
TrackPresentationContext(context);
}
mExternalTextures.AppendElements(aExternalTextures);
ffi::wgpu_compute_pass_finish(GetClient(), mParent->GetId(), GetId(), &aPass);
}
void CommandEncoder::EndRenderPass(
ffi::WGPURecordedRenderPass& aPass, CanvasContextArray& aCanvasContexts,
Span<RefPtr<ExternalTexture>> aExternalTextures) {
if (mState != CommandEncoderState::Locked) {
const auto* message = "Encoder is not currently locked";
ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message);
return;
}
mState = CommandEncoderState::Open;
for (const auto& context : aCanvasContexts) {
TrackPresentationContext(context);
}
mExternalTextures.AppendElements(aExternalTextures);
ffi::wgpu_render_pass_finish(GetClient(), mParent->GetId(), GetId(), &aPass);
}
already_AddRefed<CommandBuffer> CommandEncoder::Finish(
const dom::GPUCommandBufferDescriptor& aDesc) {
ffi::WGPUCommandBufferDescriptor desc = {};
webgpu::StringHelper label(aDesc.mLabel);
desc.label = label.Get();
if (mState == CommandEncoderState::Locked) {
// Most errors that could occur here will be raised by wgpu. But since we
// don't tell wgpu about passes until they are ended, we need to raise an
// error if the application left a pass open.
const auto* message =
"Encoder is locked by a previously created render/compute pass";
ffi::wgpu_report_validation_error(GetClient(), mParent->GetId(), message);
}
RawId command_buffer_id = ffi::wgpu_command_encoder_finish(
GetClient(), mParent->GetId(), GetId(), &desc);
mState = CommandEncoderState::Ended;
RefPtr<CommandBuffer> comb = new CommandBuffer(
mParent, command_buffer_id, std::move(mPresentationContexts),
std::move(mExternalTextures));
comb->SetLabel(aDesc.mLabel);
return comb.forget();
}
} // namespace mozilla::webgpu