| // Copyright 2025 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include "src/dawn/native/webgpu/CaptureContext.h" | |
| #include <concepts> | |
| #include <vector> | |
| #include "src/dawn/common/StringViewUtils.h" | |
| #include "src/dawn/native/BlockInfo.h" | |
| #include "src/dawn/native/CommandBuffer.h" | |
| #include "src/dawn/native/Commands.h" | |
| #include "src/dawn/native/Device.h" | |
| #include "src/dawn/native/ObjectBase.h" | |
| #include "src/dawn/native/Surface.h" | |
| #include "src/dawn/native/webgpu/BufferWGPU.h" | |
| #include "src/dawn/native/webgpu/DeviceWGPU.h" | |
| #include "src/dawn/native/webgpu/QuerySetWGPU.h" | |
| #include "src/dawn/native/webgpu/QueueWGPU.h" | |
| #include "src/dawn/native/webgpu/Serialization.h" | |
| #include "src/dawn/native/webgpu/TextureWGPU.h" | |
| #include "src/utils/compiler.h" | |
| #include "src/utils/numeric.h" | |
| namespace dawn::native::webgpu { | |
| CaptureContext::ScopedContentWriter::ScopedContentWriter(CaptureContext& context) | |
| : mContext(context) {} | |
| CaptureContext::ScopedContentWriter::~ScopedContentWriter() { | |
| uint64_t offset = mBytesWritten % 4; | |
| if (offset) { | |
| static char zero[3] = {0}; | |
| uint64_t paddingNeeded = 4 - offset; | |
| mContext->WriteContentBytes(zero, paddingNeeded); | |
| } | |
| } | |
| void CaptureContext::ScopedContentWriter::WriteContentBytes(const void* data, size_t size) { | |
| mContext->WriteContentBytes(data, size); | |
| mBytesWritten += size; | |
| } | |
| MaybeError CaptureContext::CaptureCreation(schema::ObjectId id, | |
| const std::string& label, | |
| RecordableObject* object) { | |
| schema::RootCommandCreateResourceCmd cmd{{ | |
| .data = {{ | |
| .resource = {{ | |
| .type = object->GetObjectType(), | |
| .id = id, | |
| .label = label, | |
| }}, | |
| }}, | |
| }}; | |
| Serialize(*this, cmd); | |
| return object->CaptureCreationParameters(*this); | |
| } | |
| template <> | |
| void CaptureContext::CaptureSetLabel(Device* object, const std::string& label) { | |
| schema::RootCommandSetLabelCmd data{{ | |
| .data{{ | |
| .id = schema::kDeviceId, | |
| .type = schema::ObjectType::Device, | |
| .label = label, | |
| }}, | |
| }}; | |
| Serialize(*this, data); | |
| } | |
| template <> | |
| void CaptureContext::CaptureSetLabel(Surface* object, const std::string& label) { | |
| schema::ObjectId surfaceId = AddResourceAndGetId(object); | |
| if (surfaceId == 0) { | |
| return; | |
| } | |
| schema::RootCommandSetLabelCmd data{{ | |
| .data{{ | |
| .id = surfaceId, | |
| .type = schema::ObjectType::Surface, | |
| .label = label, | |
| }}, | |
| }}; | |
| Serialize(*this, data); | |
| } | |
| void CaptureContext::CaptureSurfaceConfigure(Surface* surface, const SurfaceConfiguration* config) { | |
| schema::ObjectId surfaceId = AddResourceAndGetId(surface); | |
| if (surfaceId == 0) { | |
| return; | |
| } | |
| std::vector<wgpu::TextureFormat> viewFormats{config->viewFormats.begin(), | |
| config->viewFormats.end()}; | |
| schema::RootCommandSurfaceConfigureCmd cmd{{ | |
| .data = {{ | |
| .surfaceId = surfaceId, | |
| .config = {{ | |
| .deviceId = schema::kDeviceId, | |
| .format = config->format, | |
| .usage = config->usage, | |
| .viewFormats = viewFormats, | |
| .alphaMode = config->alphaMode, | |
| .width = config->width, | |
| .height = config->height, | |
| .presentMode = config->presentMode, | |
| }}, | |
| }}, | |
| }}; | |
| Serialize(*this, cmd); | |
| } | |
| void CaptureContext::CaptureSurfaceUnconfigure(Surface* surface) { | |
| schema::ObjectId surfaceId = AddResourceAndGetId(surface); | |
| if (surfaceId == 0) { | |
| return; | |
| } | |
| schema::RootCommandSurfaceUnconfigureCmd cmd{{ | |
| .data = {{ | |
| .surfaceId = surfaceId, | |
| }}, | |
| }}; | |
| Serialize(*this, cmd); | |
| } | |
| void CaptureContext::CaptureSurfacePresent(Surface* surface) { | |
| schema::ObjectId surfaceId = AddResourceAndGetId(surface); | |
| if (surfaceId == 0) { | |
| return; | |
| } | |
| schema::RootCommandSurfacePresentCmd cmd{{ | |
| .data = {{ | |
| .surfaceId = surfaceId, | |
| }}, | |
| }}; | |
| Serialize(*this, cmd); | |
| } | |
| MaybeError CaptureContext::CaptureSurfaceGetCurrentTexture(Surface* surface, Texture* texture) { | |
| schema::ObjectId surfaceId = AddResourceAndGetId(surface); | |
| if (surfaceId == 0) { | |
| return {}; | |
| } | |
| schema::ObjectId textureId; | |
| DAWN_TRY_ASSIGN(textureId, AddResourceAndGetId(texture)); | |
| if (textureId == 0) { | |
| return {}; | |
| } | |
| schema::RootCommandSurfaceGetCurrentTextureCmd cmd{{ | |
| .data = {{ | |
| .surfaceId = surfaceId, | |
| .textureId = textureId, | |
| }}, | |
| }}; | |
| Serialize(*this, cmd); | |
| return {}; | |
| } | |
| schema::ObjectId CaptureContext::AddResourceAndGetId(Surface* surface) { | |
| DAWN_ASSERT(surface != nullptr); | |
| Ref<Surface> ref(surface); | |
| auto it = mSurfaceIds.find(ref); | |
| if (it == mSurfaceIds.end()) { | |
| schema::ObjectId id = mNextObjectId++; | |
| mSurfaceIds[std::move(ref)] = id; | |
| return id; | |
| } | |
| return it->second; | |
| } | |
| schema::ObjectId CaptureContext::GetId(Surface* surface) { | |
| if (surface == nullptr) { | |
| return 0; | |
| } | |
| auto it = mSurfaceIds.find(Ref<Surface>(surface)); | |
| DAWN_ASSERT(it != mSurfaceIds.end()); | |
| return it->second; | |
| } | |
| CaptureContext::CaptureContext(Device* device, | |
| std::ostream& commandStream, | |
| std::ostream& contentStream) | |
| : mDevice(device), mCommandStream(commandStream), mContentStream(contentStream) {} | |
| void CaptureContext::ReleaseReferences() { | |
| mObjectIds.clear(); | |
| mSurfaceIds.clear(); | |
| if (mDevice) { | |
| auto& wgpu = mDevice->wgpu; | |
| if (mCopyBuffer) { | |
| wgpu->bufferDestroy(mCopyBuffer); | |
| wgpu->bufferRelease(mCopyBuffer); | |
| } | |
| mDevice = nullptr; | |
| } | |
| } | |
| CaptureContext::~CaptureContext() { | |
| ReleaseReferences(); | |
| } | |
| WGPUBuffer CaptureContext::GetCopyBuffer() { | |
| if (!mCopyBuffer) { | |
| WGPUBufferDescriptor desc = {}; | |
| // Add a label to the copy buffer for better debugging experience. | |
| desc.label = ToOutputStringView("Capture Copy Buffer"); | |
| desc.size = kCopyBufferSize; | |
| desc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead; | |
| mCopyBuffer = mDevice->wgpu->deviceCreateBuffer(mDevice->GetInnerHandle(), &desc); | |
| } | |
| return mCopyBuffer; | |
| } | |
| void CaptureContext::WriteContentBytes(const void* data, size_t size) { | |
| mContentStream->write(reinterpret_cast<const char*>(data), sign_cast(size)); | |
| } | |
| void CaptureContext::WriteCommandBytes(const void* data, size_t size) { | |
| mCommandStream->write(reinterpret_cast<const char*>(data), sign_cast(size)); | |
| mCommandBytesWritten += size; | |
| } | |
| MaybeError CaptureContext::CaptureQueueWriteBuffer(Buffer* buffer, | |
| uint64_t bufferOffset, | |
| Span<const std::byte> data) { | |
| schema::ObjectId id; | |
| DAWN_TRY_ASSIGN(id, AddResourceAndGetId(buffer)); | |
| schema::RootCommandWriteBufferCmd cmd{{ | |
| .data = {{ | |
| .bufferId = id, | |
| .bufferOffset = bufferOffset, | |
| .size = data.size(), | |
| }}, | |
| }}; | |
| Serialize(*this, cmd); | |
| WriteContentBytes(data.data(), data.size()); | |
| return {}; | |
| } | |
| MaybeError CaptureContext::CaptureQueueWriteTexture(const TexelCopyTextureInfo& destination, | |
| Span<const std::byte> data, | |
| const TexelCopyBufferLayout& dataLayout, | |
| const TexelExtent3D& writeSizePixel) { | |
| DAWN_TRY(AddResource(ToBackend(destination.texture))); | |
| schema::RootCommandWriteTextureCmd cmd{{ | |
| .data = {{ | |
| .destination = ToSchema(*this, destination), | |
| .layout = ToSchema(dataLayout), | |
| .size = ToSchema(writeSizePixel), | |
| .dataSize = data.size(), | |
| }}, | |
| }}; | |
| Serialize(*this, cmd); | |
| CaptureContext::ScopedContentWriter writer(*this); | |
| writer.WriteContentBytes(data.data(), data.size()); | |
| return {}; | |
| } | |
| wgpu::TextureAspect ToDawn(const Aspect aspect) { | |
| switch (aspect) { | |
| case Aspect::Depth: | |
| return wgpu::TextureAspect::DepthOnly; | |
| case Aspect::Stencil: | |
| return wgpu::TextureAspect::StencilOnly; | |
| case Aspect::Plane0: | |
| return wgpu::TextureAspect::Plane0Only; | |
| case Aspect::Plane1: | |
| return wgpu::TextureAspect::Plane1Only; | |
| case Aspect::Plane2: | |
| return wgpu::TextureAspect::Plane2Only; | |
| default: | |
| return wgpu::TextureAspect::All; | |
| } | |
| } | |
| schema::Origin3D ToSchema(const TexelOrigin3D& origin) { | |
| return {{ | |
| .x = dchecked_cast<uint32_t>(origin.x), | |
| .y = dchecked_cast<uint32_t>(origin.y), | |
| .z = dchecked_cast<uint32_t>(origin.z), | |
| }}; | |
| } | |
| schema::Origin2D ToSchema(const Origin2D& origin) { | |
| return {{ | |
| .x = origin.x, | |
| .y = origin.y, | |
| }}; | |
| } | |
| schema::Extent3D ToSchema(const TexelExtent3D& extent) { | |
| return {{ | |
| .width = dchecked_cast<uint32_t>(extent.width), | |
| .height = dchecked_cast<uint32_t>(extent.height), | |
| .depthOrArrayLayers = dchecked_cast<uint32_t>(extent.depthOrArrayLayers), | |
| }}; | |
| } | |
| schema::Extent2D ToSchema(const Extent2D& extent) { | |
| return {{ | |
| .width = extent.width, | |
| .height = extent.height, | |
| }}; | |
| } | |
| schema::Color ToSchema(const Color& color) { | |
| return {{ | |
| .r = color.r, | |
| .g = color.g, | |
| .b = color.b, | |
| .a = color.a, | |
| }}; | |
| } | |
| schema::TexelCopyBufferLayout ToSchema(const BufferCopy& bufferCopy, | |
| const TypedTexelBlockInfo& blockInfo) { | |
| return {{ | |
| .offset = bufferCopy.offset, | |
| .bytesPerRow = static_cast<uint32_t>(blockInfo.ToBytes(bufferCopy.blocksPerRow)), | |
| .rowsPerImage = dchecked_cast<uint32_t>(bufferCopy.rowsPerImage), | |
| }}; | |
| } | |
| schema::TexelCopyBufferInfo ToSchema(CaptureContext& captureContext, | |
| const BufferCopy& bufferCopy, | |
| const TypedTexelBlockInfo& blockInfo) { | |
| return {{ | |
| .bufferId = captureContext.GetId(bufferCopy.buffer), | |
| .layout = ToSchema(bufferCopy, blockInfo), | |
| }}; | |
| } | |
| schema::TexelCopyTextureInfo ToSchema(CaptureContext& captureContext, | |
| const TextureCopy& textureCopy) { | |
| return {{ | |
| .textureId = captureContext.GetId(textureCopy.texture), | |
| .mipLevel = textureCopy.mipLevel, | |
| .origin = ToSchema(textureCopy.origin), | |
| .aspect = ToDawn(textureCopy.aspect), | |
| }}; | |
| } | |
| schema::TexelCopyBufferLayout ToSchema(const TexelCopyBufferLayout& layout) { | |
| return {{ | |
| .offset = layout.offset, | |
| .bytesPerRow = layout.bytesPerRow, | |
| .rowsPerImage = layout.rowsPerImage, | |
| }}; | |
| } | |
| schema::TexelCopyTextureInfo ToSchema(CaptureContext& captureContext, | |
| const TexelCopyTextureInfo& info) { | |
| return {{ | |
| .textureId = captureContext.GetId(info.texture), | |
| .mipLevel = info.mipLevel, | |
| .origin = ToSchema(info.origin), | |
| .aspect = info.aspect, | |
| }}; | |
| } | |
| schema::TimestampWrites ToSchema(CaptureContext& captureContext, | |
| const TimestampWrites& timestampWrites) { | |
| return {{ | |
| .querySetId = captureContext.GetId(timestampWrites.querySet), | |
| .beginningOfPassWriteIndex = uint32_t{timestampWrites.beginningOfPassWriteIndex}, | |
| .endOfPassWriteIndex = uint32_t{timestampWrites.endOfPassWriteIndex}, | |
| }}; | |
| } | |
| schema::ProgrammableStage ToSchema(CaptureContext& captureContext, const ProgrammableStage& stage) { | |
| std::vector<schema::PipelineConstant> constants; | |
| for (const auto& [name, value] : stage.constants) { | |
| constants.push_back({{ | |
| .name = name, | |
| .value = value, | |
| }}); | |
| } | |
| return {{ | |
| .moduleId = captureContext.GetId(stage.module.Get()), | |
| .entryPoint = stage.entryPoint, | |
| .constants = constants, | |
| }}; | |
| } | |
| } // namespace dawn::native::webgpu |