| // 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 "dawn/replay/Replay.h" |
| |
| #include <webgpu/webgpu_cpp.h> |
| |
| #include <algorithm> |
| #include <iostream> |
| #include <memory> |
| #include <ranges> |
| #include <span> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "dawn/common/Constants.h" |
| #include "dawn/replay/BlitBufferToDepthTexture.h" |
| #include "dawn/replay/Capture.h" |
| #include "dawn/replay/Deserialization.h" |
| #include "src/dawn/replay/ReplayImpl.h" |
| |
| namespace dawn::replay { |
| |
| namespace schema { |
| #define DAWN_REPLAY_ENUM_TO_STRING_MEMBER(NAME, ...) \ |
| case EnumType::NAME: \ |
| return #NAME; |
| |
| #define DAWN_REPLAY_ENUM_TO_STRING(NAME, MEMBERS) \ |
| const char* EnumToString(schema::NAME value) { \ |
| using EnumType = NAME; \ |
| switch (value) { \ |
| MEMBERS(DAWN_REPLAY_ENUM_TO_STRING_MEMBER) \ |
| default: \ |
| return "UNKNOWN"; \ |
| } \ |
| } \ |
| std::ostream& operator<<(std::ostream& os, schema::NAME value) { \ |
| return os << EnumToString(value); \ |
| } |
| |
| DAWN_REPLAY_OBJECT_TYPES_ENUM(DAWN_REPLAY_ENUM_TO_STRING) |
| DAWN_REPLAY_COMMAND_BUFFER_COMMANDS_ENUM(DAWN_REPLAY_ENUM_TO_STRING) |
| DAWN_REPLAY_ROOT_COMMANDS_ENUM(DAWN_REPLAY_ENUM_TO_STRING) |
| |
| #undef DAWN_REPLAY_ENUM_TO_STRING_MEMBER |
| #undef DAWN_REPLAY_ENUM_TO_STRING |
| |
| } // namespace schema |
| |
| Replay::~Replay() = default; |
| |
| std::unique_ptr<Replay> Replay::Create(wgpu::Device device, std::unique_ptr<Capture> capture) { |
| return ReplayImpl::Create(device, std::move(capture)); |
| } |
| |
| template <typename T> |
| T Replay::GetObjectByLabel(std::string_view label) const { |
| if (auto* impl = static_cast<const ReplayImpl*>(this)) { |
| return impl->GetObjectByLabel<T>(label); |
| } |
| return nullptr; |
| } |
| |
| bool Replay::Play() { |
| if (auto* impl = static_cast<ReplayImpl*>(this)) { |
| auto result = impl->Play(); |
| return result.IsSuccess(); |
| } |
| return false; |
| } |
| |
| #define DAWN_REPLAY_GET_OBJECT_BY_LABEL(NAME) \ |
| template wgpu::NAME Replay::GetObjectByLabel<wgpu::NAME>(std::string_view label) const; |
| DAWN_REPLAY_OBJECT_TYPES(DAWN_REPLAY_GET_OBJECT_BY_LABEL) |
| #undef DAWN_REPLAY_GET_OBJECT_BY_LABEL |
| |
| #define DAWN_REPLAY_RESOURCE_VARIANT(NAME) wgpu::NAME, |
| typedef std::variant<DAWN_REPLAY_OBJECT_TYPES(DAWN_REPLAY_RESOURCE_VARIANT) std::monostate> |
| Resource; |
| #undef DAWN_REPLAY_RESOURCE_VARIANT |
| |
| struct LabeledResource { |
| std::string label; |
| Resource resource; |
| }; |
| |
| class DawnResourceVisitor : public ResourceVisitor { |
| public: |
| using ResourceVisitor::operator(); |
| explicit DawnResourceVisitor(DawnRootCommandVisitor* visitor) : mVisitor(visitor) {} |
| |
| #define DAWN_REPLAY_RESOURCE_VISITOR_OVERRIDE(ENUM, TYPE) \ |
| MaybeError operator()(const TYPE& data) override; |
| DAWN_REPLAY_RESOURCE_DATA_MAP(DAWN_REPLAY_RESOURCE_VISITOR_OVERRIDE) |
| #undef DAWN_REPLAY_RESOURCE_VISITOR_OVERRIDE |
| |
| private: |
| template <typename T> |
| MaybeError Create(const T& data); |
| |
| DawnRootCommandVisitor* mVisitor; |
| }; |
| |
| class DawnRootCommandVisitor : public RootCommandVisitor { |
| public: |
| using RootCommandVisitor::operator(); |
| explicit DawnRootCommandVisitor(wgpu::Device device) |
| : mDevice(device), mResourceVisitor(this) {} |
| |
| MaybeError operator()(const CreateResourceData& data) override { |
| mCurrentResourceId = data.resource.id; |
| mCurrentResourceLabel = data.resource.label; |
| return std::visit(mResourceVisitor, data.data); |
| } |
| |
| MaybeError operator()(const schema::RootCommandWriteBufferCmdData& data) override; |
| MaybeError operator()(const schema::RootCommandWriteTextureCmdData& data) override; |
| MaybeError operator()(const schema::RootCommandQueueSubmitCmdData& data) override; |
| MaybeError operator()(const schema::RootCommandSetLabelCmdData& data) override; |
| MaybeError operator()(const schema::RootCommandInitTextureCmdData& data) override; |
| MaybeError operator()(const schema::RootCommandEndCmdData& data) override { return {}; } |
| |
| ResourceVisitor& GetResourceVisitor() override { return mResourceVisitor; } |
| |
| wgpu::Device GetDevice() const { return mDevice; } |
| schema::ObjectId GetCurrentResourceId() const { return mCurrentResourceId; } |
| const std::string& GetCurrentResourceLabel() const { return mCurrentResourceLabel; } |
| |
| template <typename T> |
| T GetObjectById(schema::ObjectId id) const { |
| if (id == 0) { |
| return nullptr; |
| } |
| auto iter = mResources.find(id); |
| if (iter == mResources.end()) { |
| return nullptr; |
| } |
| const T* p = std::get_if<T>(&iter->second.resource); |
| return p ? *p : nullptr; |
| } |
| |
| template <typename T> |
| T GetObjectByLabel(std::string_view label) const { |
| auto isLabel = [label](const LabeledResource& res) { return res.label == label; }; |
| auto resourcesWithMachingLabel = |
| mResources | std::views::values | std::views::filter(isLabel); |
| for (const auto& res : resourcesWithMachingLabel) { |
| const T* p = std::get_if<T>(&res.resource); |
| if (p) { |
| return *p; |
| } |
| } |
| return nullptr; |
| } |
| |
| template <typename T> |
| const std::string& GetLabel(const T& object) const { |
| if (object == nullptr) { |
| return kNotFound; |
| } |
| return GetLabel(GetObjectId(object)); |
| } |
| |
| const std::string& GetLabel(schema::ObjectId id) const { |
| auto iter = mResources.find(id); |
| if (iter == mResources.end()) { |
| return kNotFound; |
| } |
| return iter->second.label; |
| } |
| |
| template <typename T> |
| schema::ObjectId GetObjectId(const T& object) const { |
| auto iter = mResourceToIdMap.find(object.Get()); |
| return iter != mResourceToIdMap.end() ? iter->second : 0; |
| } |
| |
| template <typename T> |
| void AddResource(schema::ObjectId id, const std::string& label, T resource) { |
| mResources.emplace(id, LabeledResource{label, resource}); |
| mResourceToIdMap.emplace(resource.Get(), id); |
| } |
| |
| void AddResource(schema::ObjectId id, const std::string& label, Resource resource) { |
| std::visit( |
| [&](auto& r) { |
| if constexpr (!std::is_same_v<std::decay_t<decltype(r)>, std::monostate>) { |
| mResources.emplace(id, LabeledResource{label, r}); |
| mResourceToIdMap.emplace(r.Get(), id); |
| } |
| }, |
| resource); |
| } |
| |
| MaybeError SetLabel(schema::ObjectId id, schema::ObjectType type, const std::string& label); |
| |
| BlitBufferToDepthTexture& GetBlitBufferToDepthTexture() { return mBlitBufferToDepthTexture; } |
| |
| void SetContentReadHead(ReadHead* readHead) override { mContentReadHead = readHead; } |
| |
| private: |
| wgpu::Device mDevice; |
| DawnResourceVisitor mResourceVisitor; |
| |
| using IdToResourceMap = absl::flat_hash_map<schema::ObjectId, LabeledResource>; |
| IdToResourceMap mResources; |
| |
| using ResourcePtrToIdMap = absl::flat_hash_map<const void*, schema::ObjectId>; |
| ResourcePtrToIdMap mResourceToIdMap; |
| |
| BlitBufferToDepthTexture mBlitBufferToDepthTexture; |
| |
| schema::ObjectId mCurrentResourceId; |
| std::string mCurrentResourceLabel; |
| |
| ReadHead* mContentReadHead = nullptr; |
| |
| std::string kNotFound; |
| }; |
| |
| namespace { |
| |
| bool IsSwizzleIdentity(const wgpu::TextureComponentSwizzle& swizzle) { |
| return (swizzle.r == wgpu::ComponentSwizzle::R || |
| swizzle.r == wgpu::ComponentSwizzle::Undefined) && |
| (swizzle.g == wgpu::ComponentSwizzle::G || |
| swizzle.g == wgpu::ComponentSwizzle::Undefined) && |
| (swizzle.b == wgpu::ComponentSwizzle::B || |
| swizzle.b == wgpu::ComponentSwizzle::Undefined) && |
| (swizzle.a == wgpu::ComponentSwizzle::A || |
| swizzle.a == wgpu::ComponentSwizzle::Undefined); |
| } |
| |
| wgpu::Origin3D ToWGPU(const schema::Origin3D& origin) { |
| return wgpu::Origin3D{ |
| .x = origin.x, |
| .y = origin.y, |
| .z = origin.z, |
| }; |
| } |
| |
| wgpu::Origin2D ToWGPU(const schema::Origin2D& origin) { |
| return wgpu::Origin2D{ |
| .x = origin.x, |
| .y = origin.y, |
| }; |
| } |
| |
| wgpu::Extent3D ToWGPU(const schema::Extent3D& extent) { |
| return wgpu::Extent3D{ |
| .width = extent.width, |
| .height = extent.height, |
| .depthOrArrayLayers = extent.depthOrArrayLayers, |
| }; |
| } |
| |
| wgpu::Extent2D ToWGPU(const schema::Extent2D& extent) { |
| return wgpu::Extent2D{ |
| .width = extent.width, |
| .height = extent.height, |
| }; |
| } |
| |
| wgpu::Color ToWGPU(const schema::Color& color) { |
| return wgpu::Color{ |
| .r = color.r, |
| .g = color.g, |
| .b = color.b, |
| .a = color.a, |
| }; |
| } |
| |
| wgpu::PassTimestampWrites ToWGPU(const DawnRootCommandVisitor& replay, |
| const schema::TimestampWrites& writes) { |
| return wgpu::PassTimestampWrites{ |
| .nextInChain = nullptr, |
| .querySet = replay.GetObjectById<wgpu::QuerySet>(writes.querySetId), |
| .beginningOfPassWriteIndex = writes.beginningOfPassWriteIndex, |
| .endOfPassWriteIndex = writes.endOfPassWriteIndex, |
| }; |
| } |
| |
| std::vector<wgpu::ConstantEntry> ToWGPU(const std::vector<schema::PipelineConstant>& constants) { |
| std::vector<wgpu::ConstantEntry> entries; |
| entries.reserve(constants.size()); |
| std::transform(constants.begin(), constants.end(), std::back_inserter(entries), |
| [](const auto& constant) { |
| return wgpu::ConstantEntry{ |
| .key = wgpu::StringView(constant.name), |
| .value = constant.value, |
| }; |
| }); |
| return entries; |
| } |
| |
| wgpu::TexelCopyBufferLayout ToWGPU(const schema::TexelCopyBufferLayout& info) { |
| return wgpu::TexelCopyBufferLayout{ |
| .offset = info.offset, |
| .bytesPerRow = info.bytesPerRow, |
| .rowsPerImage = info.rowsPerImage, |
| }; |
| } |
| |
| wgpu::TexelCopyBufferInfo ToWGPU(const DawnRootCommandVisitor& replay, |
| const schema::TexelCopyBufferInfo& info) { |
| return wgpu::TexelCopyBufferInfo{ |
| .layout = ToWGPU(info.layout), |
| .buffer = replay.GetObjectById<wgpu::Buffer>(info.bufferId), |
| }; |
| } |
| |
| wgpu::TexelCopyTextureInfo ToWGPU(const DawnRootCommandVisitor& replay, |
| const schema::TexelCopyTextureInfo& info) { |
| return wgpu::TexelCopyTextureInfo{ |
| .texture = replay.GetObjectById<wgpu::Texture>(info.textureId), |
| .mipLevel = info.mipLevel, |
| .origin = ToWGPU(info.origin), |
| .aspect = static_cast<wgpu::TextureAspect>(info.aspect), |
| }; |
| } |
| |
| wgpu::StencilFaceState ToWGPU(const schema::StencilFaceState& state) { |
| return wgpu::StencilFaceState{ |
| .compare = state.compare, |
| .failOp = state.failOp, |
| .depthFailOp = state.depthFailOp, |
| .passOp = state.passOp, |
| }; |
| } |
| |
| wgpu::BlendComponent ToWGPU(const schema::BlendComponent& component) { |
| return wgpu::BlendComponent{ |
| .operation = component.operation, |
| .srcFactor = component.srcFactor, |
| .dstFactor = component.dstFactor, |
| }; |
| } |
| |
| wgpu::BlendState ToWGPU(const schema::BlendState& state) { |
| return wgpu::BlendState{ |
| .color = ToWGPU(state.color), |
| .alpha = ToWGPU(state.alpha), |
| }; |
| } |
| |
| bool IsBlendComponentEnabled(const wgpu::BlendComponent& component) { |
| return component.operation != wgpu::BlendOperation::Add || |
| component.srcFactor != wgpu::BlendFactor::One || |
| component.dstFactor != wgpu::BlendFactor::Zero; |
| } |
| |
| bool IsBlendEnabled(const wgpu::BlendState& blend) { |
| return IsBlendComponentEnabled(blend.color) || IsBlendComponentEnabled(blend.alpha); |
| } |
| |
| MaybeError ReadContentIntoBuffer(ReadHead& readHead, |
| wgpu::Device device, |
| wgpu::Buffer buffer, |
| uint64_t bufferOffset, |
| uint64_t size) { |
| const uint32_t* data; |
| DAWN_TRY_ASSIGN(data, readHead.GetData(size)); |
| |
| device.GetQueue().WriteBuffer(buffer, bufferOffset, data, size); |
| return {}; |
| } |
| |
| MaybeError ReadContentIntoTexture(const DawnRootCommandVisitor& replay, |
| ReadHead& readHead, |
| wgpu::Device device, |
| const schema::RootCommandWriteTextureCmdData& cmdData) { |
| const uint64_t dataSize = (cmdData.dataSize + 3) & ~3; |
| |
| const uint32_t* data; |
| DAWN_TRY_ASSIGN(data, readHead.GetData(dataSize)); |
| |
| wgpu::TexelCopyTextureInfo dst = ToWGPU(replay, cmdData.destination); |
| wgpu::TexelCopyBufferLayout layout = ToWGPU(cmdData.layout); |
| wgpu::Extent3D size = ToWGPU(cmdData.size); |
| device.GetQueue().WriteTexture(&dst, data, cmdData.dataSize, &layout, &size); |
| return {}; |
| } |
| |
| bool TextureFormatNeedsBlit(wgpu::TextureFormat format, wgpu::TextureAspect aspect) { |
| switch (format) { |
| case wgpu::TextureFormat::Depth24Plus: |
| case wgpu::TextureFormat::Depth32Float: |
| return true; |
| case wgpu::TextureFormat::Depth24PlusStencil8: |
| case wgpu::TextureFormat::Depth32FloatStencil8: { |
| return aspect == wgpu::TextureAspect::DepthOnly; |
| } |
| default: |
| return false; |
| } |
| } |
| |
| MaybeError InitializeTexture(const DawnRootCommandVisitor& replay, |
| BlitBufferToDepthTexture& blitBufferToDepthTexture, |
| ReadHead& readHead, |
| wgpu::Device device, |
| const schema::RootCommandInitTextureCmdData& cmdData) { |
| const uint64_t dataSize = (cmdData.dataSize + 3) & ~3; |
| |
| const uint32_t* data; |
| DAWN_TRY_ASSIGN(data, readHead.GetData(dataSize)); |
| |
| wgpu::TexelCopyTextureInfo dst = ToWGPU(replay, cmdData.destination); |
| wgpu::TexelCopyBufferLayout layout = ToWGPU(cmdData.layout); |
| wgpu::Extent3D size = ToWGPU(cmdData.size); |
| |
| if (TextureFormatNeedsBlit(dst.texture.GetFormat(), dst.aspect)) { |
| DAWN_TRY(blitBufferToDepthTexture.Blit(device, dst, data, cmdData.dataSize, layout, size)); |
| } else { |
| device.GetQueue().WriteTexture(&dst, data, cmdData.dataSize, &layout, &size); |
| } |
| return {}; |
| } |
| |
| struct BindGroupEntryVisitor { |
| const DawnRootCommandVisitor& replay; |
| std::vector<wgpu::BindGroupEntry>& entries; |
| std::vector<std::unique_ptr<wgpu::ExternalTextureBindingEntry>>& externalTextureBindingEntries; |
| |
| template <typename T> |
| MaybeError operator()(const T& data) { |
| wgpu::BindGroupEntry entry; |
| entry.binding = data.binding; |
| DAWN_TRY(FillEntry(entry, data.data)); |
| entries.push_back(entry); |
| return {}; |
| } |
| |
| MaybeError operator()(const std::monostate&) { |
| return DAWN_INTERNAL_ERROR("invalid bind group entry"); |
| } |
| |
| private: |
| MaybeError FillEntry(wgpu::BindGroupEntry& entry, |
| const schema::BindGroupEntryTypeBufferBindingData& data) { |
| entry.buffer = replay.GetObjectById<wgpu::Buffer>(data.bufferId); |
| entry.offset = data.offset; |
| entry.size = data.size; |
| return {}; |
| } |
| |
| MaybeError FillEntry(wgpu::BindGroupEntry& entry, |
| const schema::BindGroupEntryTypeSamplerBindingData& data) { |
| entry.sampler = replay.GetObjectById<wgpu::Sampler>(data.samplerId); |
| return {}; |
| } |
| |
| MaybeError FillEntry(wgpu::BindGroupEntry& entry, |
| const schema::BindGroupEntryTypeTextureBindingData& data) { |
| entry.textureView = replay.GetObjectById<wgpu::TextureView>(data.textureViewId); |
| return {}; |
| } |
| |
| MaybeError FillEntry(wgpu::BindGroupEntry& entry, |
| const schema::BindGroupEntryTypeStorageTextureBindingData& data) { |
| entry.textureView = replay.GetObjectById<wgpu::TextureView>(data.textureViewId); |
| return {}; |
| } |
| |
| MaybeError FillEntry(wgpu::BindGroupEntry& entry, |
| const schema::BindGroupEntryTypeTexelBufferBindingData& data) { |
| return DAWN_INTERNAL_ERROR("texel buffer binding not supported"); |
| } |
| |
| MaybeError FillEntry(wgpu::BindGroupEntry& entry, |
| const schema::BindGroupEntryTypeInputAttachmentBindingInfoData& data) { |
| entry.textureView = replay.GetObjectById<wgpu::TextureView>(data.textureViewId); |
| return {}; |
| } |
| |
| MaybeError FillEntry(wgpu::BindGroupEntry& entry, |
| const schema::BindGroupEntryTypeExternalTextureBindingData& data) { |
| if (data.externalTextureId != 0) { |
| auto& externalBindingEntryPtr = externalTextureBindingEntries.emplace_back( |
| std::make_unique<wgpu::ExternalTextureBindingEntry>()); |
| externalBindingEntryPtr->externalTexture = |
| replay.GetObjectById<wgpu::ExternalTexture>(data.externalTextureId); |
| entry.nextInChain = externalBindingEntryPtr.get(); |
| } else { |
| // External texture binding can bind a regular texture view |
| DAWN_ASSERT(data.textureViewId != 0); |
| entry.textureView = replay.GetObjectById<wgpu::TextureView>(data.textureViewId); |
| } |
| return {}; |
| } |
| |
| template <typename T> |
| MaybeError FillEntry(wgpu::BindGroupEntry& entry, const T&) { |
| return {}; |
| } |
| }; |
| |
| struct BindGroupLayoutEntryVisitor { |
| wgpu::BindGroupLayoutEntry& entry; |
| wgpu::ExternalTextureBindingLayout& externalTextureBindingLayout; |
| |
| template <typename T> |
| MaybeError operator()(const T& data) { |
| entry.binding = data.binding.binding; |
| entry.visibility = data.binding.visibility; |
| entry.bindingArraySize = data.binding.bindingArraySize; |
| return FillEntry(data.data); |
| } |
| |
| MaybeError operator()(const std::monostate&) { |
| return DAWN_INTERNAL_ERROR("invalid bind group layout entry"); |
| } |
| |
| private: |
| MaybeError FillEntry(const schema::BindGroupLayoutEntryTypeBufferBindingData& data) { |
| entry.buffer = { |
| .type = data.type, |
| .hasDynamicOffset = data.hasDynamicOffset, |
| .minBindingSize = data.minBindingSize, |
| }; |
| return {}; |
| } |
| |
| MaybeError FillEntry(const schema::BindGroupLayoutEntryTypeSamplerBindingData& data) { |
| entry.sampler = {.type = data.type}; |
| return {}; |
| } |
| |
| MaybeError FillEntry(const schema::BindGroupLayoutEntryTypeStorageTextureBindingData& data) { |
| entry.storageTexture = { |
| .access = data.access, |
| .format = data.format, |
| .viewDimension = data.viewDimension, |
| }; |
| return {}; |
| } |
| |
| MaybeError FillEntry(const schema::BindGroupLayoutEntryTypeTextureBindingData& data) { |
| entry.texture = { |
| .sampleType = data.sampleType, |
| .viewDimension = data.viewDimension, |
| .multisampled = data.multisampled, |
| }; |
| return {}; |
| } |
| |
| MaybeError FillEntry(const schema::BindGroupLayoutEntryTypeExternalTextureBindingData& data) { |
| entry.nextInChain = &externalTextureBindingLayout; |
| return {}; |
| } |
| |
| template <typename T> |
| MaybeError FillEntry(const T&) { |
| return DAWN_INTERNAL_ERROR("unhandled bind group layout entry type"); |
| } |
| }; |
| |
| ResultOrError<wgpu::BindGroup> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const BindGroupData& data, |
| const std::string& label) { |
| std::vector<wgpu::BindGroupEntry> entries; |
| entries.reserve(data.entries.size()); |
| std::vector<std::unique_ptr<wgpu::ExternalTextureBindingEntry>> externalTextureBindingEntries; |
| |
| for (const auto& entryVariant : data.entries) { |
| DAWN_TRY(std::visit(BindGroupEntryVisitor{replay, entries, externalTextureBindingEntries}, |
| entryVariant)); |
| } |
| |
| wgpu::BindGroupDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .layout = replay.GetObjectById<wgpu::BindGroupLayout>(data.bg.layoutId), |
| .entryCount = entries.size(), |
| .entries = entries.data(), |
| }; |
| wgpu::BindGroup bindGroup = device.CreateBindGroup(&desc); |
| return {bindGroup}; |
| } |
| |
| ResultOrError<wgpu::BindGroupLayout> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const BindGroupLayoutData& data, |
| const std::string& label) { |
| std::vector<wgpu::BindGroupLayoutEntry> entries; |
| entries.reserve(data.entries.size()); |
| |
| // External texture binding layouts are chained structs that are set as a pointer within |
| // the bind group layout entry. We declare an entry here so that it can be used when needed |
| // in each BindGroupLayoutEntry and so it can stay alive until the call to |
| // device.CreateBindGroupLayout. Because ExternalTextureBindingLayout is an empty struct, |
| // there's no issue with using the same struct multiple times. |
| wgpu::ExternalTextureBindingLayout externalTextureBindingLayout; |
| |
| for (const auto& entryVariant : data.entries) { |
| wgpu::BindGroupLayoutEntry entry; |
| DAWN_TRY(std::visit(BindGroupLayoutEntryVisitor{entry, externalTextureBindingLayout}, |
| entryVariant)); |
| entries.push_back(entry); |
| } |
| |
| wgpu::BindGroupLayoutDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .entryCount = entries.size(), |
| .entries = entries.data(), |
| }; |
| wgpu::BindGroupLayout bindGroupLayout = device.CreateBindGroupLayout(&desc); |
| return {bindGroupLayout}; |
| } |
| |
| ResultOrError<wgpu::Buffer> CreateResource(const DawnRootCommandVisitor&, |
| wgpu::Device device, |
| const schema::Buffer& buf, |
| const std::string& label) { |
| wgpu::BufferUsage usage = |
| (buf.usage & (wgpu::BufferUsage::MapRead | wgpu::BufferUsage::MapWrite)) |
| ? buf.usage |
| : (buf.usage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst); |
| |
| // Remap mappable write buffers as CopySrc|CopyDst as we use WriteBuffer to set their contents. |
| if (usage == (wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc)) { |
| usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc; |
| } |
| |
| wgpu::BufferDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .usage = usage, |
| .size = buf.size, |
| }; |
| wgpu::Buffer buffer = device.CreateBuffer(&desc); |
| return {buffer}; |
| } |
| |
| ResultOrError<wgpu::ComputePipeline> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const schema::ComputePipeline& pipeline, |
| const std::string& label) { |
| std::vector<wgpu::ConstantEntry> constants = ToWGPU(pipeline.compute.constants); |
| |
| wgpu::ComputePipelineDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .layout = replay.GetObjectById<wgpu::PipelineLayout>(pipeline.layoutId), |
| .compute = |
| { |
| .module = replay.GetObjectById<wgpu::ShaderModule>(pipeline.compute.moduleId), |
| .entryPoint = wgpu::StringView(pipeline.compute.entryPoint), |
| .constantCount = constants.size(), |
| .constants = constants.data(), |
| }, |
| }; |
| wgpu::ComputePipeline computePipeline = device.CreateComputePipeline(&desc); |
| return {computePipeline}; |
| } |
| |
| ResultOrError<wgpu::ExternalTexture> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const schema::ExternalTexture& tex, |
| const std::string& label) { |
| wgpu::ExternalTextureDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .plane0 = replay.GetObjectById<wgpu::TextureView>(tex.plane0Id), |
| .plane1 = replay.GetObjectById<wgpu::TextureView>(tex.plane1Id), |
| .cropOrigin = ToWGPU(tex.cropOrigin), |
| .cropSize = ToWGPU(tex.cropSize), |
| .apparentSize = ToWGPU(tex.apparentSize), |
| .doYuvToRgbConversionOnly = tex.doYuvToRgbConversionOnly, |
| .yuvToRgbConversionMatrix = tex.yuvToRgbConversionMatrix.data(), |
| .srcTransferFunctionParameters = tex.srcTransferFunctionParameters.data(), |
| .dstTransferFunctionParameters = tex.dstTransferFunctionParameters.data(), |
| .gamutConversionMatrix = tex.gamutConversionMatrix.data(), |
| .mirrored = tex.mirrored, |
| .rotation = tex.rotation, |
| }; |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&desc); |
| return {externalTexture}; |
| } |
| |
| ResultOrError<wgpu::PipelineLayout> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const schema::PipelineLayout& layout, |
| const std::string& label) { |
| std::vector<wgpu::BindGroupLayout> bindGroupLayouts; |
| for (const auto bindGroupLayoutId : layout.bindGroupLayoutIds) { |
| bindGroupLayouts.push_back(replay.GetObjectById<wgpu::BindGroupLayout>(bindGroupLayoutId)); |
| } |
| |
| wgpu::PipelineLayoutDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .bindGroupLayoutCount = bindGroupLayouts.size(), |
| .bindGroupLayouts = bindGroupLayouts.data(), |
| .immediateSize = layout.immediateSize, |
| }; |
| wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(&desc); |
| return {pipelineLayout}; |
| } |
| |
| ResultOrError<wgpu::QuerySet> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const schema::QuerySet& querySetData, |
| const std::string& label) { |
| wgpu::QuerySetDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .type = querySetData.type, |
| .count = querySetData.count, |
| }; |
| wgpu::QuerySet querySet = device.CreateQuerySet(&desc); |
| return {querySet}; |
| } |
| |
| template <typename Base, typename Pass> |
| struct DawnCommandVisitor : Base { |
| const DawnRootCommandVisitor& replay; |
| Pass pass; |
| |
| DawnCommandVisitor(const DawnRootCommandVisitor& r, Pass p) : replay(r), pass(p) {} |
| |
| MaybeError operator()(const schema::CommandBufferCommandSetBindGroupCmdData& data) override { |
| pass.SetBindGroup(data.index, replay.GetObjectById<wgpu::BindGroup>(data.bindGroupId), |
| data.dynamicOffsets.size(), data.dynamicOffsets.data()); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandSetImmediatesCmdData& data) override { |
| pass.SetImmediates(data.offset, data.data.data(), data.data.size()); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandPushDebugGroupCmdData& data) override { |
| pass.PushDebugGroup(wgpu::StringView(data.groupLabel)); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandInsertDebugMarkerCmdData& data) override { |
| pass.InsertDebugMarker(wgpu::StringView(data.markerLabel)); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandPopDebugGroupCmdData& data) override { |
| pass.PopDebugGroup(); |
| return {}; |
| } |
| }; |
| |
| template <typename Base, typename Pass> |
| struct DawnRenderVisitor : DawnCommandVisitor<Base, Pass> { |
| using DawnCommandVisitor<Base, Pass>::DawnCommandVisitor; |
| using DawnCommandVisitor<Base, Pass>::replay; |
| using DawnCommandVisitor<Base, Pass>::pass; |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandSetRenderPipelineCmdData& data) override { |
| pass.SetPipeline(replay.template GetObjectById<wgpu::RenderPipeline>(data.pipelineId)); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandSetVertexBufferCmdData& data) override { |
| pass.SetVertexBuffer(data.slot, replay.template GetObjectById<wgpu::Buffer>(data.bufferId), |
| data.offset, data.size); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandSetIndexBufferCmdData& data) override { |
| pass.SetIndexBuffer(replay.template GetObjectById<wgpu::Buffer>(data.bufferId), data.format, |
| data.offset, data.size); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandDrawCmdData& data) override { |
| pass.Draw(data.vertexCount, data.instanceCount, data.firstVertex, data.firstInstance); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandDrawIndexedCmdData& data) override { |
| pass.DrawIndexed(data.indexCount, data.instanceCount, data.firstIndex, data.baseVertex, |
| data.firstInstance); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandDrawIndirectCmdData& data) override { |
| pass.DrawIndirect(replay.template GetObjectById<wgpu::Buffer>(data.indirectBufferId), |
| data.indirectOffset); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandDrawIndexedIndirectCmdData& data) override { |
| pass.DrawIndexedIndirect(replay.template GetObjectById<wgpu::Buffer>(data.indirectBufferId), |
| data.indirectOffset); |
| return {}; |
| } |
| }; |
| |
| struct DawnRenderBundleVisitor : DawnRenderVisitor<RenderBundleVisitor, wgpu::RenderBundleEncoder> { |
| using DawnRenderVisitor::DawnRenderVisitor; |
| |
| MaybeError operator()(const schema::CommandBufferCommandEndCmdData& data) override { |
| return {}; |
| } |
| }; |
| |
| ResultOrError<wgpu::RenderBundle> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const RenderBundleData& data, |
| const std::string& label) { |
| wgpu::RenderBundleEncoderDescriptor desc{ |
| .colorFormatCount = data.bundle.colorFormats.size(), |
| .colorFormats = data.bundle.colorFormats.data(), |
| .depthStencilFormat = data.bundle.depthStencilFormat, |
| .sampleCount = data.bundle.sampleCount, |
| .depthReadOnly = data.bundle.depthReadOnly, |
| .stencilReadOnly = data.bundle.stencilReadOnly, |
| }; |
| wgpu::RenderBundleEncoder encoder = device.CreateRenderBundleEncoder(&desc); |
| DawnRenderBundleVisitor visitor{replay, encoder}; |
| DAWN_TRY(ProcessRenderBundleCommands(data.readHead, &visitor)); |
| |
| wgpu::RenderBundleDescriptor bundleDesc{ |
| .label = wgpu::StringView(label), |
| }; |
| wgpu::RenderBundle renderBundle = encoder.Finish(&bundleDesc); |
| return {renderBundle}; |
| } |
| |
| ResultOrError<wgpu::RenderPipeline> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const schema::RenderPipeline& pipeline, |
| const std::string& label) { |
| std::vector<wgpu::ConstantEntry> vertexConstants = ToWGPU(pipeline.vertex.program.constants); |
| std::vector<wgpu::ConstantEntry> fragmentConstants = |
| ToWGPU(pipeline.fragment.program.constants); |
| std::vector<wgpu::ColorTargetState> colorTargets; |
| std::array<wgpu::ColorTargetStateExpandResolveTextureDawn, kMaxColorAttachments> |
| expandResolveChains; |
| std::vector<wgpu::BlendState> blendStates(pipeline.fragment.targets.size()); |
| std::vector<wgpu::VertexBufferLayout> buffers; |
| |
| std::vector<wgpu::VertexAttribute> attributes(kMaxVertexAttributes); |
| uint32_t attributeCount = 0; |
| |
| for (const auto& buffer : pipeline.vertex.buffers) { |
| const auto attributesForBuffer = &attributes[attributeCount]; |
| for (const auto& attrib : buffer.attributes) { |
| auto& attr = attributes[attributeCount++]; |
| attr.format = attrib.format; |
| attr.offset = attrib.offset; |
| attr.shaderLocation = attrib.shaderLocation; |
| } |
| buffers.push_back({ |
| .stepMode = buffer.stepMode, |
| .arrayStride = buffer.arrayStride, |
| .attributeCount = buffer.attributes.size(), |
| .attributes = attributesForBuffer, |
| }); |
| } |
| |
| wgpu::FragmentState* fragment = nullptr; |
| wgpu::FragmentState fragmentState; |
| if (pipeline.fragment.program.moduleId) { |
| fragment = &fragmentState; |
| fragmentState.module = |
| replay.GetObjectById<wgpu::ShaderModule>(pipeline.fragment.program.moduleId); |
| fragmentState.entryPoint = wgpu::StringView(pipeline.fragment.program.entryPoint); |
| fragmentState.constantCount = fragmentConstants.size(); |
| fragmentState.constants = fragmentConstants.data(); |
| for (const auto& target : pipeline.fragment.targets) { |
| wgpu::BlendState& blend = blendStates[colorTargets.size()]; |
| blend = ToWGPU(target.blend); |
| |
| wgpu::ChainedStruct* nextInChain = nullptr; |
| if (target.expandResolveMode != schema::ExpandResolveMode::Unused) { |
| auto expandResolve = &expandResolveChains[colorTargets.size()]; |
| expandResolve->enabled = |
| target.expandResolveMode == schema::ExpandResolveMode::Enabled; |
| nextInChain = expandResolve; |
| } |
| colorTargets.push_back({ |
| .nextInChain = nextInChain, |
| .format = target.format, |
| .blend = IsBlendEnabled(blend) ? &blend : nullptr, |
| .writeMask = target.writeMask, |
| }); |
| } |
| fragmentState.targetCount = colorTargets.size(); |
| fragmentState.targets = colorTargets.data(); |
| } |
| |
| wgpu::DepthStencilState* depthStencil = nullptr; |
| wgpu::DepthStencilState depthStencilState; |
| if (pipeline.depthStencil.format != wgpu::TextureFormat::Undefined) { |
| depthStencil = &depthStencilState; |
| depthStencilState.format = pipeline.depthStencil.format; |
| depthStencilState.depthWriteEnabled = pipeline.depthStencil.depthWriteEnabled; |
| depthStencilState.depthCompare = pipeline.depthStencil.depthCompare; |
| depthStencilState.stencilFront = ToWGPU(pipeline.depthStencil.stencilFront); |
| depthStencilState.stencilBack = ToWGPU(pipeline.depthStencil.stencilBack); |
| depthStencilState.stencilReadMask = pipeline.depthStencil.stencilReadMask; |
| depthStencilState.stencilWriteMask = pipeline.depthStencil.stencilWriteMask; |
| depthStencilState.depthBias = pipeline.depthStencil.depthBias; |
| depthStencilState.depthBiasSlopeScale = pipeline.depthStencil.depthBiasSlopeScale; |
| depthStencilState.depthBiasClamp = pipeline.depthStencil.depthBiasClamp; |
| } |
| |
| wgpu::RenderPipelineDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .layout = replay.GetObjectById<wgpu::PipelineLayout>(pipeline.layoutId), |
| .vertex = |
| { |
| .module = |
| replay.GetObjectById<wgpu::ShaderModule>(pipeline.vertex.program.moduleId), |
| .entryPoint = wgpu::StringView(pipeline.vertex.program.entryPoint), |
| .constantCount = vertexConstants.size(), |
| .constants = vertexConstants.data(), |
| .bufferCount = buffers.size(), |
| .buffers = buffers.data(), |
| }, |
| .primitive = |
| { |
| .topology = pipeline.primitive.topology, |
| .stripIndexFormat = pipeline.primitive.stripIndexFormat, |
| .frontFace = pipeline.primitive.frontFace, |
| .cullMode = pipeline.primitive.cullMode, |
| .unclippedDepth = pipeline.primitive.unclippedDepth, |
| }, |
| .depthStencil = depthStencil, |
| .multisample = |
| { |
| .count = pipeline.multisample.count, |
| .mask = pipeline.multisample.mask, |
| .alphaToCoverageEnabled = pipeline.multisample.alphaToCoverageEnabled, |
| }, |
| .fragment = fragment, |
| }; |
| wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(&desc); |
| return {renderPipeline}; |
| } |
| |
| ResultOrError<wgpu::Sampler> CreateResource(const DawnRootCommandVisitor&, |
| wgpu::Device device, |
| const schema::Sampler& sampler, |
| const std::string& label) { |
| wgpu::SamplerDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .addressModeU = sampler.addressModeU, |
| .addressModeV = sampler.addressModeV, |
| .addressModeW = sampler.addressModeW, |
| .magFilter = sampler.magFilter, |
| .minFilter = sampler.minFilter, |
| .mipmapFilter = sampler.mipmapFilter, |
| .lodMinClamp = sampler.lodMinClamp, |
| .lodMaxClamp = sampler.lodMaxClamp, |
| .compare = sampler.compare, |
| .maxAnisotropy = sampler.maxAnisotropy, |
| }; |
| return {device.CreateSampler(&desc)}; |
| } |
| |
| ResultOrError<wgpu::ShaderModule> CreateResource(const DawnRootCommandVisitor&, |
| wgpu::Device device, |
| const schema::ShaderModule& mod, |
| const std::string& label) { |
| // TODO(452840621): Make this use a chain instead of hard coded to WGSL only and handle other |
| // chained structs. |
| wgpu::ShaderSourceWGSL source({ |
| .nextInChain = nullptr, |
| .code = wgpu::StringView(mod.code), |
| }); |
| wgpu::ShaderModuleDescriptor desc{ |
| .nextInChain = &source, |
| .label = wgpu::StringView(label), |
| }; |
| wgpu::ShaderModule shaderModule = device.CreateShaderModule(&desc); |
| return {shaderModule}; |
| } |
| |
| ResultOrError<wgpu::TexelBufferView> CreateResource(const DawnRootCommandVisitor&, |
| wgpu::Device device, |
| const schema::TexelBufferView& view, |
| const std::string& label) { |
| return DAWN_UNIMPLEMENTED_ERROR("TexelBufferView not supported"); |
| } |
| |
| ResultOrError<wgpu::Texture> CreateResource(const DawnRootCommandVisitor&, |
| wgpu::Device device, |
| const schema::Texture& tex, |
| const std::string& label) { |
| wgpu::TextureUsage usage = tex.usage; |
| if (!(usage & wgpu::TextureUsage::TransientAttachment)) { |
| usage |= wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; |
| } |
| |
| wgpu::TextureDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .usage = usage, |
| .dimension = tex.dimension, |
| .size = ToWGPU(tex.size), |
| .format = tex.format, |
| .mipLevelCount = tex.mipLevelCount, |
| .sampleCount = tex.sampleCount, |
| .viewFormatCount = tex.viewFormats.size(), |
| .viewFormats = tex.viewFormats.data(), |
| }; |
| wgpu::Texture texture = device.CreateTexture(&desc); |
| return {texture}; |
| } |
| |
| ResultOrError<wgpu::TextureView> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const schema::TextureView& view, |
| const std::string& label) { |
| wgpu::TextureViewDescriptor desc{ |
| .label = wgpu::StringView(label), |
| .format = view.format, |
| .dimension = view.dimension, |
| .baseMipLevel = view.baseMipLevel, |
| .mipLevelCount = view.mipLevelCount, |
| .baseArrayLayer = view.baseArrayLayer, |
| .arrayLayerCount = view.arrayLayerCount, |
| .aspect = view.aspect, |
| .usage = view.usage, |
| }; |
| |
| wgpu::TextureComponentSwizzleDescriptor swizzleDesc = {}; |
| swizzleDesc.swizzle.r = view.swizzle.r; |
| swizzleDesc.swizzle.g = view.swizzle.g; |
| swizzleDesc.swizzle.b = view.swizzle.b; |
| swizzleDesc.swizzle.a = view.swizzle.a; |
| if (!IsSwizzleIdentity(swizzleDesc.swizzle)) { |
| desc.nextInChain = &swizzleDesc; |
| } |
| |
| wgpu::Texture texture = replay.GetObjectById<wgpu::Texture>(view.textureId); |
| wgpu::TextureView textureView = texture.CreateView(&desc); |
| return {textureView}; |
| } |
| |
| struct DawnComputePassVisitor : DawnCommandVisitor<ComputePassVisitor, wgpu::ComputePassEncoder> { |
| using DawnCommandVisitor::DawnCommandVisitor; |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandSetComputePipelineCmdData& data) override { |
| pass.SetPipeline(replay.GetObjectById<wgpu::ComputePipeline>(data.pipelineId)); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandDispatchCmdData& data) override { |
| pass.DispatchWorkgroups(data.x, data.y, data.z); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandDispatchIndirectCmdData& data) override { |
| pass.DispatchWorkgroupsIndirect(replay.GetObjectById<wgpu::Buffer>(data.bufferId), |
| data.offset); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandWriteTimestampCmdData& data) override { |
| pass.WriteTimestamp(replay.GetObjectById<wgpu::QuerySet>(data.querySetId), data.queryIndex); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandEndCmdData& data) override { |
| pass.End(); |
| return {}; |
| } |
| }; |
| |
| struct DawnRenderPassVisitor : DawnRenderVisitor<RenderPassVisitor, wgpu::RenderPassEncoder> { |
| using DawnRenderVisitor::DawnRenderVisitor; |
| |
| MaybeError operator()(const schema::CommandBufferCommandExecuteBundlesCmdData& data) override { |
| std::vector<wgpu::RenderBundle> bundles; |
| for (auto bundleId : data.bundleIds) { |
| bundles.push_back(replay.GetObjectById<wgpu::RenderBundle>(bundleId)); |
| } |
| pass.ExecuteBundles(bundles.size(), bundles.data()); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandBeginOcclusionQueryCmdData& data) override { |
| pass.BeginOcclusionQuery(data.queryIndex); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandEndOcclusionQueryCmdData& data) override { |
| pass.EndOcclusionQuery(); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandSetBlendConstantCmdData& data) override { |
| wgpu::Color color = ToWGPU(data.color); |
| pass.SetBlendConstant(&color); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandSetScissorRectCmdData& data) override { |
| pass.SetScissorRect(data.x, data.y, data.width, data.height); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandSetStencilReferenceCmdData& data) override { |
| pass.SetStencilReference(data.reference); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandSetViewportCmdData& data) override { |
| pass.SetViewport(data.x, data.y, data.width, data.height, data.minDepth, data.maxDepth); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandWriteTimestampCmdData& data) override { |
| pass.WriteTimestamp(replay.GetObjectById<wgpu::QuerySet>(data.querySetId), data.queryIndex); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandEndCmdData& data) override { |
| pass.End(); |
| return {}; |
| } |
| }; |
| |
| struct DawnEncoderVisitor : EncoderVisitor { |
| const DawnRootCommandVisitor& replay; |
| wgpu::CommandEncoder pass; |
| wgpu::Device device; |
| |
| DawnEncoderVisitor(const DawnRootCommandVisitor& r, wgpu::CommandEncoder p, wgpu::Device d) |
| : replay(r), pass(p), device(d) {} |
| |
| ResultOrError<ComputePassVisitor*> BeginComputePass( |
| const schema::CommandBufferCommandBeginComputePassCmdData& data) override { |
| wgpu::PassTimestampWrites timestampWrites = ToWGPU(replay, data.timestampWrites); |
| wgpu::ComputePassDescriptor desc{ |
| .label = wgpu::StringView(data.label), |
| .timestampWrites = timestampWrites.querySet ? ×tampWrites : nullptr, |
| }; |
| mComputePassEncoder = pass.BeginComputePass(&desc); |
| mComputePassVisitor = std::make_unique<DawnComputePassVisitor>(replay, mComputePassEncoder); |
| return mComputePassVisitor.get(); |
| } |
| |
| ResultOrError<RenderPassVisitor*> BeginRenderPass( |
| const schema::CommandBufferCommandBeginRenderPassCmdData& data) override { |
| wgpu::PassTimestampWrites timestampWrites = ToWGPU(replay, data.timestampWrites); |
| std::vector<wgpu::RenderPassColorAttachment> colorAttachments; |
| |
| for (const auto& attachment : data.colorAttachments) { |
| colorAttachments.push_back(wgpu::RenderPassColorAttachment{ |
| .nextInChain = nullptr, |
| .view = replay.GetObjectById<wgpu::TextureView>(attachment.viewId), |
| .depthSlice = attachment.depthSlice, |
| .resolveTarget = |
| replay.GetObjectById<wgpu::TextureView>(attachment.resolveTargetId), |
| .loadOp = attachment.loadOp, |
| .storeOp = attachment.storeOp, |
| .clearValue = ToWGPU(attachment.clearValue), |
| }); |
| } |
| |
| wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{ |
| .view = replay.GetObjectById<wgpu::TextureView>(data.depthStencilAttachment.viewId), |
| .depthLoadOp = data.depthStencilAttachment.depthLoadOp, |
| .depthStoreOp = data.depthStencilAttachment.depthStoreOp, |
| .depthClearValue = data.depthStencilAttachment.depthClearValue, |
| .depthReadOnly = data.depthStencilAttachment.depthReadOnly, |
| .stencilLoadOp = data.depthStencilAttachment.stencilLoadOp, |
| .stencilStoreOp = data.depthStencilAttachment.stencilStoreOp, |
| .stencilClearValue = data.depthStencilAttachment.stencilClearValue, |
| .stencilReadOnly = data.depthStencilAttachment.stencilReadOnly, |
| }; |
| |
| wgpu::RenderPassDescriptor desc{ |
| .label = wgpu::StringView(data.label), |
| .colorAttachmentCount = colorAttachments.size(), |
| .colorAttachments = colorAttachments.data(), |
| .depthStencilAttachment = |
| depthStencilAttachment.view != nullptr ? &depthStencilAttachment : nullptr, |
| .occlusionQuerySet = replay.GetObjectById<wgpu::QuerySet>(data.occlusionQuerySetId), |
| .timestampWrites = timestampWrites.querySet ? ×tampWrites : nullptr, |
| }; |
| |
| wgpu::RenderPassDescriptorResolveRect resolveRect; |
| if (data.resolveRect.width != 0) { |
| resolveRect.colorOffsetX = data.resolveRect.colorOffsetX; |
| resolveRect.colorOffsetY = data.resolveRect.colorOffsetY; |
| resolveRect.resolveOffsetX = data.resolveRect.resolveOffsetX; |
| resolveRect.resolveOffsetY = data.resolveRect.resolveOffsetY; |
| resolveRect.width = data.resolveRect.width; |
| resolveRect.height = data.resolveRect.height; |
| desc.nextInChain = &resolveRect; |
| } |
| |
| mRenderPassEncoder = pass.BeginRenderPass(&desc); |
| mRenderPassVisitor = std::make_unique<DawnRenderPassVisitor>(replay, mRenderPassEncoder); |
| return mRenderPassVisitor.get(); |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandClearBufferCmdData& data) override { |
| pass.ClearBuffer(replay.GetObjectById<wgpu::Buffer>(data.bufferId), data.offset, data.size); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandCopyBufferToBufferCmdData& data) override { |
| pass.CopyBufferToBuffer( |
| replay.GetObjectById<wgpu::Buffer>(data.srcBufferId), data.srcOffset, |
| replay.GetObjectById<wgpu::Buffer>(data.dstBufferId), data.dstOffset, data.size); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandCopyBufferToTextureCmdData& data) override { |
| wgpu::TexelCopyBufferInfo src = ToWGPU(replay, data.source); |
| wgpu::TexelCopyTextureInfo dst = ToWGPU(replay, data.destination); |
| wgpu::Extent3D copySize = ToWGPU(data.copySize); |
| pass.CopyBufferToTexture(&src, &dst, ©Size); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandCopyTextureToBufferCmdData& data) override { |
| wgpu::TexelCopyTextureInfo src = ToWGPU(replay, data.source); |
| wgpu::TexelCopyBufferInfo dst = ToWGPU(replay, data.destination); |
| wgpu::Extent3D copySize = ToWGPU(data.copySize); |
| pass.CopyTextureToBuffer(&src, &dst, ©Size); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandCopyTextureToTextureCmdData& data) override { |
| wgpu::TexelCopyTextureInfo src = ToWGPU(replay, data.source); |
| wgpu::TexelCopyTextureInfo dst = ToWGPU(replay, data.destination); |
| wgpu::Extent3D copySize = ToWGPU(data.copySize); |
| pass.CopyTextureToTexture(&src, &dst, ©Size); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandResolveQuerySetCmdData& data) override { |
| pass.ResolveQuerySet( |
| replay.GetObjectById<wgpu::QuerySet>(data.querySetId), data.firstQuery, data.queryCount, |
| replay.GetObjectById<wgpu::Buffer>(data.destinationId), data.destinationOffset); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandWriteBufferCmdData& data) override { |
| wgpu::Buffer buffer = replay.GetObjectById<wgpu::Buffer>(data.bufferId); |
| pass.WriteBuffer(buffer, data.bufferOffset, data.data.data(), data.data.size()); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandWriteTimestampCmdData& data) override { |
| pass.WriteTimestamp(replay.GetObjectById<wgpu::QuerySet>(data.querySetId), data.queryIndex); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandPushDebugGroupCmdData& data) override { |
| pass.PushDebugGroup(wgpu::StringView(data.groupLabel)); |
| return {}; |
| } |
| |
| MaybeError operator()( |
| const schema::CommandBufferCommandInsertDebugMarkerCmdData& data) override { |
| pass.InsertDebugMarker(wgpu::StringView(data.markerLabel)); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandPopDebugGroupCmdData& data) override { |
| pass.PopDebugGroup(); |
| return {}; |
| } |
| |
| MaybeError operator()(const schema::CommandBufferCommandEndCmdData& data) override { |
| return {}; |
| } |
| |
| private: |
| wgpu::ComputePassEncoder mComputePassEncoder; |
| std::unique_ptr<DawnComputePassVisitor> mComputePassVisitor; |
| wgpu::RenderPassEncoder mRenderPassEncoder; |
| std::unique_ptr<DawnRenderPassVisitor> mRenderPassVisitor; |
| }; |
| |
| ResultOrError<wgpu::CommandBuffer> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const CommandBufferData& data, |
| const std::string& label) { |
| wgpu::CommandEncoderDescriptor desc{ |
| .label = wgpu::StringView(label), |
| }; |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&desc); |
| DawnEncoderVisitor visitor{replay, encoder, device}; |
| DAWN_TRY(ProcessEncoderCommands(data.readHead, &visitor)); |
| return {encoder.Finish()}; |
| } |
| |
| ResultOrError<wgpu::CommandBuffer> CreateResource(const DawnRootCommandVisitor& replay, |
| wgpu::Device device, |
| const DeviceData& data, |
| const std::string& label) { |
| // This is here to make resource handling generic. We don't create |
| // devices from commands but we reference devices as another resource. |
| return DAWN_INTERNAL_ERROR("Device creation not supported"); |
| } |
| |
| } // anonymous namespace |
| |
| #define DAWN_REPLAY_VISIT_RESOURCE_CREATE(ENUM, TYPE) \ |
| MaybeError DawnResourceVisitor::operator()(const TYPE& data) { \ |
| return Create(data); \ |
| } |
| DAWN_REPLAY_RESOURCE_DATA_MAP(DAWN_REPLAY_VISIT_RESOURCE_CREATE) |
| #undef DAWN_REPLAY_VISIT_RESOURCE_CREATE |
| |
| template <typename T> |
| MaybeError DawnResourceVisitor::Create(const T& data) { |
| auto res = |
| CreateResource(*mVisitor, mVisitor->GetDevice(), data, mVisitor->GetCurrentResourceLabel()); |
| if (res.IsError()) { |
| return res.AcquireError(); |
| } |
| mVisitor->AddResource(mVisitor->GetCurrentResourceId(), mVisitor->GetCurrentResourceLabel(), |
| res.AcquireSuccess()); |
| return {}; |
| } |
| |
| MaybeError DawnRootCommandVisitor::operator()(const schema::RootCommandWriteBufferCmdData& data) { |
| wgpu::Buffer buffer = GetObjectById<wgpu::Buffer>(data.bufferId); |
| return ReadContentIntoBuffer(*mContentReadHead, mDevice, buffer, data.bufferOffset, data.size); |
| } |
| |
| MaybeError DawnRootCommandVisitor::operator()(const schema::RootCommandWriteTextureCmdData& data) { |
| return ReadContentIntoTexture(*this, *mContentReadHead, mDevice, data); |
| } |
| |
| MaybeError DawnRootCommandVisitor::operator()(const schema::RootCommandQueueSubmitCmdData& data) { |
| std::vector<wgpu::CommandBuffer> commandBuffers; |
| commandBuffers.reserve(data.commandBuffers.size()); |
| for (auto id : data.commandBuffers) { |
| commandBuffers.push_back(GetObjectById<wgpu::CommandBuffer>(id)); |
| } |
| mDevice.GetQueue().Submit(commandBuffers.size(), commandBuffers.data()); |
| return {}; |
| } |
| |
| MaybeError DawnRootCommandVisitor::operator()(const schema::RootCommandSetLabelCmdData& data) { |
| return SetLabel(data.id, data.type, data.label); |
| } |
| |
| MaybeError DawnRootCommandVisitor::operator()(const schema::RootCommandInitTextureCmdData& data) { |
| return InitializeTexture(*this, mBlitBufferToDepthTexture, *mContentReadHead, mDevice, data); |
| } |
| |
| MaybeError DawnRootCommandVisitor::SetLabel(schema::ObjectId id, |
| schema::ObjectType type, |
| const std::string& label) { |
| // We update both the object's label and our own copy of the label |
| // as there is no API to get an object's label from WebGPU |
| #define DAWN_SET_LABEL_CASE(NAME) \ |
| case schema::ObjectType::NAME: { \ |
| auto iter = mResources.find(id); \ |
| DAWN_ASSERT(iter != mResources.end()); \ |
| std::get_if<wgpu::NAME>(&iter->second.resource)->SetLabel(wgpu::StringView(label)); \ |
| iter->second.label = label; \ |
| break; \ |
| } |
| |
| #define DAWN_SET_LABEL_GEN(NAME, MEMBERS) MEMBERS(DAWN_SET_LABEL_CASE) |
| |
| switch (type) { |
| DAWN_REPLAY_OBJECT_TYPES_ENUM(DAWN_SET_LABEL_GEN) |
| default: |
| return DAWN_INTERNAL_ERROR("unhandled resource type"); |
| } |
| |
| #undef DAWN_SET_LABEL_GEN |
| #undef DAWN_SET_LABEL_CASE |
| |
| return {}; |
| } |
| |
| std::unique_ptr<ReplayImpl> ReplayImpl::Create(wgpu::Device device, |
| std::unique_ptr<Capture> capture) { |
| auto captureImpl = std::unique_ptr<CaptureImpl>(static_cast<CaptureImpl*>(capture.release())); |
| return std::unique_ptr<ReplayImpl>(new ReplayImpl(device, std::move(captureImpl))); |
| } |
| |
| ReplayImpl::ReplayImpl(wgpu::Device device, std::unique_ptr<CaptureImpl> capture) |
| : mVisitor(new DawnRootCommandVisitor(device)), mCapture(std::move(capture)) { |
| mVisitor->AddResource(schema::kDeviceId, "", device); |
| } |
| |
| MaybeError ReplayImpl::Play() { |
| if (mCapture->Walk(*mVisitor)) { |
| return {}; |
| } |
| return DAWN_INTERNAL_ERROR("Capture walk failed"); |
| } |
| |
| template <typename T> |
| T ReplayImpl::GetObjectByLabel(std::string_view label) const { |
| return mVisitor->GetObjectByLabel<T>(label); |
| } |
| |
| template <typename T> |
| T ReplayImpl::GetObjectById(schema::ObjectId id) const { |
| return mVisitor->GetObjectById<T>(id); |
| } |
| |
| template <typename T> |
| const std::string& ReplayImpl::GetLabel(const T& object) const { |
| return mVisitor->GetLabel(object); |
| } |
| |
| const std::string& ReplayImpl::GetLabel(schema::ObjectId id) const { |
| return mVisitor->GetLabel(id); |
| } |
| |
| } // namespace dawn::replay |