| // Copyright 2017 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/vulkan/CommandBufferVk.h" | |
| #include <algorithm> | |
| #include <concepts> | |
| #include <limits> | |
| #include <memory> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/functional/function_ref.h" | |
| #include "src/dawn/common/Enumerator.h" | |
| #include "src/dawn/common/Range.h" | |
| #include "src/dawn/native/BindGroupTracker.h" | |
| #include "src/dawn/native/CommandEncoder.h" | |
| #include "src/dawn/native/CommandValidation.h" | |
| #include "src/dawn/native/Commands.h" | |
| #include "src/dawn/native/DynamicUploader.h" | |
| #include "src/dawn/native/EnumMaskIterator.h" | |
| #include "src/dawn/native/Error.h" | |
| #include "src/dawn/native/ExternalTexture.h" | |
| #include "src/dawn/native/ImmediatesTracker.h" | |
| #include "src/dawn/native/RenderBundle.h" | |
| #include "src/dawn/native/vulkan/BindGroupVk.h" | |
| #include "src/dawn/native/vulkan/BufferVk.h" | |
| #include "src/dawn/native/vulkan/CommandRecordingContextVk.h" | |
| #include "src/dawn/native/vulkan/ComputePipelineVk.h" | |
| #include "src/dawn/native/vulkan/DeviceVk.h" | |
| #include "src/dawn/native/vulkan/FencedDeleter.h" | |
| #include "src/dawn/native/vulkan/FramebufferCache.h" | |
| #include "src/dawn/native/vulkan/FramebufferFetchHelper.h" | |
| #include "src/dawn/native/vulkan/ImmediatesLayoutVk.h" | |
| #include "src/dawn/native/vulkan/PhysicalDeviceVk.h" | |
| #include "src/dawn/native/vulkan/PipelineLayoutVk.h" | |
| #include "src/dawn/native/vulkan/QuerySetVk.h" | |
| #include "src/dawn/native/vulkan/QueueVk.h" | |
| #include "src/dawn/native/vulkan/RenderPassCache.h" | |
| #include "src/dawn/native/vulkan/RenderPipelineVk.h" | |
| #include "src/dawn/native/vulkan/ResolveTextureLoadingUtilsVk.h" | |
| #include "src/dawn/native/vulkan/ResourceTableVk.h" | |
| #include "src/dawn/native/vulkan/TextureVk.h" | |
| #include "src/dawn/native/vulkan/UtilsVulkan.h" | |
| #include "src/dawn/native/vulkan/VulkanError.h" | |
| #include "src/utils/compiler.h" | |
| #include "src/utils/numeric.h" | |
| namespace dawn::native::vulkan { | |
| namespace { | |
| VkIndexType VulkanIndexType(wgpu::IndexFormat format) { | |
| switch (format) { | |
| case wgpu::IndexFormat::Uint16: | |
| return VK_INDEX_TYPE_UINT16; | |
| case wgpu::IndexFormat::Uint32: | |
| return VK_INDEX_TYPE_UINT32; | |
| case wgpu::IndexFormat::Undefined: | |
| break; | |
| } | |
| DAWN_UNREACHABLE(); | |
| } | |
| std::vector<VkBufferImageCopy> ComputePerArrayLayerBufferImageCopyRegions( | |
| const BufferCopy& bufferCopy, | |
| const TextureCopy& textureCopy, | |
| const TexelExtent3D& copySize, | |
| const TypedTexelBlockInfo& blockInfo) { | |
| const uint64_t bytesPerImage = | |
| blockInfo.ToBytes(bufferCopy.blocksPerRow) * static_cast<uint64_t>(bufferCopy.rowsPerImage); | |
| const uint32_t layerCount = | |
| static_cast<uint32_t>(static_cast<uint64_t>(copySize.depthOrArrayLayers)); | |
| TexelExtent3D singleLayerSize = copySize; | |
| singleLayerSize.depthOrArrayLayers = TexelCount{1u}; | |
| std::vector<VkBufferImageCopy> regions; | |
| regions.reserve(layerCount); | |
| for (uint32_t layer = 0; layer < layerCount; ++layer) { | |
| TextureCopy layerTextureCopy = textureCopy; | |
| layerTextureCopy.origin.z = textureCopy.origin.z + TexelCount{layer}; | |
| VkBufferImageCopy region = ComputeBufferImageCopyRegion(bufferCopy, layerTextureCopy, | |
| blockInfo.ToBlock(singleLayerSize)); | |
| region.bufferOffset = bufferCopy.offset + uint64_t{layer} * bytesPerImage; | |
| regions.push_back(region); | |
| } | |
| return regions; | |
| } | |
| bool HasSameTextureCopyExtent(const TextureCopy& srcCopy, | |
| const TextureCopy& dstCopy, | |
| const TexelExtent3D& copySize) { | |
| TexelExtent3D imageExtentSrc = ComputeTextureCopyExtent(srcCopy, copySize); | |
| TexelExtent3D imageExtentDst = ComputeTextureCopyExtent(dstCopy, copySize); | |
| return imageExtentSrc.width == imageExtentDst.width && | |
| imageExtentSrc.height == imageExtentDst.height && | |
| imageExtentSrc.depthOrArrayLayers == imageExtentDst.depthOrArrayLayers; | |
| } | |
| VkImageCopy ComputeImageCopyRegion(const TextureCopy& srcCopy, | |
| const TextureCopy& dstCopy, | |
| const TexelExtent3D& copySize, | |
| Aspect aspect) { | |
| const Texture* srcTexture = ToBackend(srcCopy.texture.Get()); | |
| const Texture* dstTexture = ToBackend(dstCopy.texture.Get()); | |
| VkImageCopy region; | |
| region.srcSubresource.aspectMask = VulkanAspectMask(aspect); | |
| region.srcSubresource.mipLevel = srcCopy.mipLevel; | |
| region.dstSubresource.aspectMask = VulkanAspectMask(aspect); | |
| region.dstSubresource.mipLevel = dstCopy.mipLevel; | |
| bool has3DTextureInCopy = false; | |
| region.srcOffset.x = dchecked_cast<int32_t>(srcCopy.origin.x); | |
| region.srcOffset.y = dchecked_cast<int32_t>(srcCopy.origin.y); | |
| switch (srcTexture->GetDimension()) { | |
| case wgpu::TextureDimension::Undefined: | |
| DAWN_UNREACHABLE(); | |
| case wgpu::TextureDimension::e1D: | |
| region.srcSubresource.baseArrayLayer = 0; | |
| region.srcSubresource.layerCount = 1; | |
| region.srcOffset.z = 0; | |
| break; | |
| case wgpu::TextureDimension::e2D: | |
| region.srcSubresource.baseArrayLayer = dchecked_cast<uint32_t>(srcCopy.origin.z); | |
| region.srcSubresource.layerCount = dchecked_cast<uint32_t>(copySize.depthOrArrayLayers); | |
| region.srcOffset.z = 0; | |
| break; | |
| case wgpu::TextureDimension::e3D: | |
| has3DTextureInCopy = true; | |
| region.srcSubresource.baseArrayLayer = 0; | |
| region.srcSubresource.layerCount = 1; | |
| region.srcOffset.z = dchecked_cast<int32_t>(srcCopy.origin.z); | |
| break; | |
| } | |
| region.dstOffset.x = dchecked_cast<int32_t>(dstCopy.origin.x); | |
| region.dstOffset.y = dchecked_cast<int32_t>(dstCopy.origin.y); | |
| switch (dstTexture->GetDimension()) { | |
| case wgpu::TextureDimension::Undefined: | |
| DAWN_UNREACHABLE(); | |
| case wgpu::TextureDimension::e1D: | |
| region.dstSubresource.baseArrayLayer = 0; | |
| region.dstSubresource.layerCount = 1; | |
| region.dstOffset.z = 0; | |
| break; | |
| case wgpu::TextureDimension::e2D: | |
| region.dstSubresource.baseArrayLayer = dchecked_cast<uint32_t>(dstCopy.origin.z); | |
| region.dstSubresource.layerCount = dchecked_cast<uint32_t>(copySize.depthOrArrayLayers); | |
| region.dstOffset.z = 0; | |
| break; | |
| case wgpu::TextureDimension::e3D: | |
| has3DTextureInCopy = true; | |
| region.dstSubresource.baseArrayLayer = 0; | |
| region.dstSubresource.layerCount = 1; | |
| region.dstOffset.z = dchecked_cast<int32_t>(dstCopy.origin.z); | |
| break; | |
| } | |
| DAWN_ASSERT(HasSameTextureCopyExtent(srcCopy, dstCopy, copySize)); | |
| TexelExtent3D imageExtent = ComputeTextureCopyExtent(dstCopy, copySize); | |
| region.extent.width = dchecked_cast<uint32_t>(imageExtent.width); | |
| region.extent.height = dchecked_cast<uint32_t>(imageExtent.height); | |
| region.extent.depth = | |
| has3DTextureInCopy ? dchecked_cast<uint32_t>(copySize.depthOrArrayLayers) : 1; | |
| return region; | |
| } | |
| // Returns the render area from `renderPassCmd` aligned to `granularity`. | |
| VkRect2D GetAlignedRenderArea(VkExtent2D granularity, BeginRenderPassCmd* renderPassCmd) { | |
| VkRect2D renderArea; | |
| if (renderPassCmd->forceFullRenderArea) { | |
| renderArea.offset = {}; | |
| renderArea.extent = {renderPassCmd->width, renderPassCmd->height}; | |
| return renderArea; | |
| } | |
| if (granularity.width == 0 || granularity.width == 1) { | |
| renderArea.offset.x = sign_cast(renderPassCmd->renderArea.x); | |
| renderArea.extent.width = renderPassCmd->renderArea.width; | |
| } else { | |
| renderArea.offset.x = AlignDown(sign_cast(renderPassCmd->renderArea.x), granularity.width); | |
| uint32_t right = | |
| Align(renderPassCmd->renderArea.x + renderPassCmd->renderArea.width, granularity.width); | |
| if (right > renderPassCmd->width) { | |
| right = renderPassCmd->width; | |
| } | |
| renderArea.extent.width = right - sign_cast(renderArea.offset.x); | |
| } | |
| if (granularity.height == 0 || granularity.height == 1) { | |
| renderArea.offset.y = sign_cast(renderPassCmd->renderArea.y); | |
| renderArea.extent.height = renderPassCmd->renderArea.height; | |
| } else { | |
| renderArea.offset.y = AlignDown(sign_cast(renderPassCmd->renderArea.y), granularity.height); | |
| uint32_t bottom = Align(renderPassCmd->renderArea.y + renderPassCmd->renderArea.height, | |
| granularity.height); | |
| if (bottom > renderPassCmd->height) { | |
| bottom = renderPassCmd->height; | |
| } | |
| renderArea.extent.height = bottom - sign_cast(renderArea.offset.y); | |
| } | |
| return renderArea; | |
| } | |
| // Returns the render area from `renderPassCmd` aligned to render area granularity for | |
| // `renderPassVk`. | |
| VkRect2D GetAlignedRenderArea(Device* device, | |
| VkRenderPass renderPassVk, | |
| BeginRenderPassCmd* renderPassCmd) { | |
| VkExtent2D granularity; | |
| device->fn.GetRenderAreaGranularity(device->GetVkDevice(), renderPassVk, &granularity); | |
| return GetAlignedRenderArea(granularity, renderPassCmd); | |
| } | |
| class DescriptorSetTracker : public BindGroupTrackerBase<true> { | |
| public: | |
| bool AreLayoutsCompatible() override { | |
| return mLastAppliedPipeline != nullptr && | |
| mLastAppliedPipeline->GetLayout() == mPipeline->GetLayout() && | |
| mLastAppliedImmediateSize == mImmediateSize; | |
| } | |
| const BindGroupBase* GetBindGroup(BindGroupIndex index) const { return mBindGroups[index]; } | |
| void DirtyAll() { | |
| mDirtyBindGroupsObjectChangedOrIsDynamic.set(); | |
| mDirtyBindGroups.set(); | |
| mLastResourceTable = nullptr; | |
| } | |
| template <typename VkPipelineType> | |
| void OnSetPipeline(VkPipelineType* pipeline) { | |
| BindGroupTrackerBase::OnSetPipeline(pipeline); | |
| mVkLayout = pipeline->GetVkLayout(); | |
| mImmediateSize = pipeline->GetImmediateSize(); | |
| mUsesResourceTable = pipeline->GetLayout()->UsesResourceTable(); | |
| if constexpr (std::derived_from<VkPipelineType, RenderPipelineBase>) { | |
| mFramebufferFetchEnabled = pipeline->UsesFramebufferFetch(); | |
| } | |
| } | |
| void SetFramebufferFetchDescriptorSet(VkDescriptorSet set) { | |
| mFramebufferFetchDescriptorSet = set; | |
| } | |
| void SetResourceTable(ResourceTable* resourceTable) { mResourceTable = resourceTable; } | |
| void Apply(const VulkanFunctions& vk, | |
| VkCommandBuffer commandBuffer, | |
| VkPipelineBindPoint bindPoint) { | |
| Apply(vk, commandBuffer, bindPoint, | |
| [&](BindGroupIndex i) { return ToBackend(mBindGroups[i])->GetHandle(); }); | |
| } | |
| template <typename F> | |
| void Apply(const VulkanFunctions& vk, | |
| VkCommandBuffer commandBuffer, | |
| VkPipelineBindPoint bindPoint, | |
| F GetDescriptorSet) { | |
| BeforeApply(); | |
| BindGroupMask dirtyBindGroups = mDirtyBindGroupsObjectChangedOrIsDynamic; | |
| // Changing push constant range invalidates all descriptor sets. | |
| // Also clear the last resource table so it gets rebound below. | |
| if (mLastAppliedImmediateSize != mImmediateSize) { | |
| dirtyBindGroups = mBindGroupLayoutsMask; | |
| mLastFramebufferFetchEnabled = false; | |
| mLastResourceTable = nullptr; | |
| } | |
| BindGroupIndex startOfBindGroups{0u}; | |
| if (mLastFramebufferFetchEnabled != mFramebufferFetchEnabled) { | |
| // When the use of framebuffer fetch changes between pipelines, dirty all bind groups | |
| // because they shift by 1. | |
| dirtyBindGroups = mBindGroupLayoutsMask; | |
| mLastResourceTable = nullptr; | |
| if (mFramebufferFetchEnabled) { | |
| DAWN_ASSERT(mFramebufferFetchDescriptorSet != VK_NULL_HANDLE); | |
| vk.CmdBindDescriptorSets(commandBuffer, bindPoint, mVkLayout, | |
| static_cast<uint32_t>(startOfBindGroups), 1, | |
| &*mFramebufferFetchDescriptorSet, 0, nullptr); | |
| } | |
| } | |
| if (mFramebufferFetchEnabled) { | |
| ++startOfBindGroups; | |
| } | |
| // When the usage of the resource table changes between pipelines, or the resource table | |
| // itself is changed, dirty all bind groups because they shift by 1 (the resource table | |
| // occupies VkDescriptorSet 0 or 1). | |
| if (mLastUsesResourceTable != mUsesResourceTable || mLastResourceTable != mResourceTable) { | |
| dirtyBindGroups = mBindGroupLayoutsMask; | |
| // Set the resource table as the first set if it starts being used. | |
| if (mUsesResourceTable) { | |
| DAWN_ASSERT(mResourceTable != nullptr); | |
| VkDescriptorSet set = mResourceTable->GetHandle(); | |
| vk.CmdBindDescriptorSets(commandBuffer, bindPoint, mVkLayout, | |
| static_cast<uint32_t>(startOfBindGroups), 1, &*set, 0, | |
| nullptr); | |
| } | |
| } | |
| if (mUsesResourceTable) { | |
| ++startOfBindGroups; | |
| } | |
| for (BindGroupIndex dirtyBGIndex : dirtyBindGroups) { | |
| VkDescriptorSet set = GetDescriptorSet(dirtyBGIndex); | |
| uint32_t setIndex = uint32_t{dirtyBGIndex + startOfBindGroups}; | |
| const auto dynamicOffsetSpan = GetDynamicOffsets(dirtyBGIndex); | |
| uint32_t count = static_cast<uint32_t>(dynamicOffsetSpan.size()); | |
| const uint32_t* dynamicOffset = count > 0 ? dynamicOffsetSpan.data() : nullptr; | |
| vk.CmdBindDescriptorSets(commandBuffer, bindPoint, mVkLayout, setIndex, 1, &*set, count, | |
| dynamicOffset); | |
| } | |
| // Update PipelineLayout | |
| AfterApply(); | |
| mLastAppliedImmediateSize = mImmediateSize; | |
| mLastUsesResourceTable = mUsesResourceTable; | |
| mLastResourceTable = mResourceTable; | |
| mLastFramebufferFetchEnabled = mFramebufferFetchEnabled; | |
| } | |
| RAW_PTR_EXCLUSION VkPipelineLayout mVkLayout; | |
| raw_ptr<ResourceTable> mLastResourceTable = nullptr; | |
| raw_ptr<ResourceTable> mResourceTable = nullptr; | |
| bool mLastUsesResourceTable = false; | |
| bool mUsesResourceTable = false; | |
| bool mFramebufferFetchEnabled = false; | |
| bool mLastFramebufferFetchEnabled = false; | |
| VkDescriptorSet mFramebufferFetchDescriptorSet = VK_NULL_HANDLE; | |
| uint32_t mLastAppliedImmediateSize = 0; | |
| uint32_t mImmediateSize = 0; | |
| }; | |
| class RenderImmediatesTracker | |
| : public UserImmediatesTrackerBase<RenderImmediates, RenderPipelineBase> { | |
| public: | |
| RenderImmediatesTracker() = default; | |
| void SetClampFragDepth(float minClampFragDepth, float maxClampFragDepth) { | |
| ClampFragDepthArgs fragDepthArgs; | |
| fragDepthArgs.minClampFragDepth = minClampFragDepth; | |
| fragDepthArgs.maxClampFragDepth = maxClampFragDepth; | |
| UpdateImmediates(offsetof(RenderImmediates, clampFragDepth), fragDepthArgs); | |
| } | |
| }; | |
| using ComputeImmediatesTracker = UserImmediatesTrackerBase<ComputeImmediates, ComputePipelineBase>; | |
| template <typename T> | |
| class ImmediateTracker : public T { | |
| public: | |
| ImmediateTracker() = default; | |
| void DirtyAll() { this->mDirty.set(); } | |
| void Apply(const VulkanFunctions& vk, VkCommandBuffer commandBuffer) { | |
| DAWN_ASSERT(this->mLastPipeline != nullptr); | |
| Apply(vk, commandBuffer, ToBackend(this->mLastPipeline)->GetVkLayout(), | |
| this->mLastPipeline->GetImmediateMask()); | |
| } | |
| void Apply(const VulkanFunctions& vk, | |
| VkCommandBuffer commandBuffer, | |
| VkPipelineLayout layout, | |
| ImmediateMask pipelineMask) { | |
| for (auto&& [offset, size] : IterateRanges(this->mDirty & pipelineMask)) { | |
| uint32_t immediateContentStartOffset = | |
| static_cast<uint32_t>(offset) * kImmediateElementByteSize; | |
| uint32_t pushConstantRangeStartOffset = | |
| GetImmediateIndexInPipeline(static_cast<uint32_t>(offset), pipelineMask) * | |
| kImmediateElementByteSize; | |
| vk.CmdPushConstants(commandBuffer, layout, kImmediateShaderStages, | |
| pushConstantRangeStartOffset, size * kImmediateElementByteSize, | |
| this->mContent.template Get<uint32_t>(immediateContentStartOffset)); | |
| } | |
| // Reset all dirty bits after uploading. | |
| this->mDirty.reset(); | |
| } | |
| }; | |
| // Records the necessary barriers for a synchronization scope using the resource usage | |
| // data pre-computed in the frontend. Also performs lazy initialization if required. | |
| MaybeError PrepareResourcesForSyncScope(Device* device, | |
| CommandRecordingContext* recordingContext, | |
| const SyncScopeResourceUsage& scope) { | |
| // Apply pending updates to all resource tables used in usages scope. | |
| // This has to be done before transitioning resources. | |
| // TODO(crbug.com/529883743): Consider folding the logic in GatherWritableTextures into the | |
| // scope.textures loop below | |
| if (!scope.usedResourceTables.empty()) { | |
| auto writables = GatherWritableTextures(scope); | |
| for (auto& resourceTable : scope.usedResourceTables) { | |
| DAWN_TRY(ToBackend(resourceTable)->ApplyPendingUpdates(recordingContext, writables)); | |
| } | |
| } | |
| // Separate barriers with vertex stages in destination stages from all other barriers. | |
| // This avoids creating unnecessary fragment->vertex dependencies when merging barriers. | |
| // Eg. merging a compute->vertex barrier and a fragment->fragment barrier would create | |
| // a compute|fragment->vertex|fragment barrier. | |
| static constexpr VkPipelineStageFlags kVertexStages = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | | |
| VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | | |
| VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; | |
| struct ImageBarriers { | |
| std::vector<VkImageMemoryBarrier> imageBarriers; | |
| VkPipelineStageFlags srcStages = 0; | |
| VkPipelineStageFlags dstStages = 0; | |
| }; | |
| ImageBarriers vertexImageBarriers; | |
| ImageBarriers nonVertexImageBarriers; | |
| BufferBarrier vertexBufferBarrier; | |
| BufferBarrier nonVertexBufferBarrier; | |
| for (size_t i = 0; i < scope.buffers.size(); ++i) { | |
| Buffer* buffer = ToBackend(scope.buffers[i]); | |
| buffer->EnsureDataInitialized(recordingContext); | |
| // `kIndirectBufferForFrontendValidation` is only for the front-end validation and should be | |
| // totally ignored in the backend resource tracking because: | |
| // 1. When old usage contains kIndirectBufferForFrontendValidation while new usage just | |
| // removes kIndirectBufferForFrontendValidation, the barrier is actually unnecessary. | |
| // 2. When usage == kIndirectBufferForFrontendValidation, dstStages would be NONE, which is | |
| // not allowed by Vulkan SPEC unless synchronization2 is enabled. | |
| // We remove `kIndirectBufferForFrontendValidation` in this function instead of in the | |
| // function `BufferVk::TrackUsageAndGetResourceBarrier()` because on Vulkan backend this | |
| // is the only place that we need to handle `kIndirectBufferForFrontendValidation`. | |
| wgpu::BufferUsage usage = | |
| scope.bufferSyncInfos[i].usage & (~kIndirectBufferForFrontendValidation); | |
| recordingContext->CheckBufferNeedsEagerTransition(buffer, usage); | |
| BufferBarrier barrier = | |
| buffer->TrackUsageAndGetResourceBarrier(usage, scope.bufferSyncInfos[i].shaderStages); | |
| if (barrier.dstStages & kVertexStages) { | |
| vertexBufferBarrier.Merge(barrier); | |
| } else { | |
| nonVertexBufferBarrier.Merge(barrier); | |
| } | |
| } | |
| auto MergeImageBarriers = [](ImageBarriers* barriers, VkPipelineStageFlags srcStages, | |
| VkPipelineStageFlags dstStages, | |
| const std::vector<VkImageMemoryBarrier>& imageBarriers) { | |
| barriers->srcStages |= srcStages; | |
| barriers->dstStages |= dstStages; | |
| barriers->imageBarriers.insert(barriers->imageBarriers.end(), imageBarriers.begin(), | |
| imageBarriers.end()); | |
| }; | |
| // TODO(crbug.com/dawn/851): Add image barriers directly to the correct vector. | |
| std::vector<VkImageMemoryBarrier> imageBarriers; | |
| for (size_t i = 0; i < scope.textures.size(); ++i) { | |
| Texture* texture = ToBackend(scope.textures[i]); | |
| VkPipelineStageFlags srcStages = 0; | |
| VkPipelineStageFlags dstStages = 0; | |
| // Clear subresources that are not render attachments. Render attachments will be | |
| // cleared in RecordBeginRenderPass by setting the loadop to clear when the texture | |
| // subresource has not been initialized before the render pass. | |
| DAWN_TRY(scope.textureSyncInfos[i].Iterate( | |
| [&](const SubresourceRange& range, const TextureSyncInfo& syncInfo) -> MaybeError { | |
| if (syncInfo.usage & ~wgpu::TextureUsage::RenderAttachment) { | |
| DAWN_TRY(texture->EnsureSubresourceContentInitialized(recordingContext, range)); | |
| } | |
| return {}; | |
| })); | |
| texture->TransitionUsageForPass(recordingContext, scope.textureSyncInfos[i], &imageBarriers, | |
| &srcStages, &dstStages); | |
| if (!imageBarriers.empty()) { | |
| MergeImageBarriers( | |
| (dstStages & kVertexStages) ? &vertexImageBarriers : &nonVertexImageBarriers, | |
| srcStages, dstStages, imageBarriers); | |
| imageBarriers.clear(); | |
| } | |
| } | |
| for (const ImageBarriers& barriers : {vertexImageBarriers, nonVertexImageBarriers}) { | |
| if (!barriers.imageBarriers.empty()) { | |
| device->fn.CmdPipelineBarrier( | |
| recordingContext->commandBuffer, barriers.srcStages, barriers.dstStages, 0, 0, | |
| nullptr, 0, nullptr, barriers.imageBarriers.size(), barriers.imageBarriers.data()); | |
| } | |
| } | |
| recordingContext->EmitBufferBarrierIfNecessary(device, vertexBufferBarrier); | |
| recordingContext->EmitBufferBarrierIfNecessary(device, nonVertexBufferBarrier); | |
| return {}; | |
| } | |
| // Reset the query sets used on render pass because the reset command must be called outside | |
| // render pass. | |
| void ResetUsedQuerySetsOnRenderPass(Device* device, | |
| VkCommandBuffer commands, | |
| QuerySetBase* querySet, | |
| const ityp::vector<QueryIndex, bool>& availability) { | |
| DAWN_ASSERT(availability.size() == querySet->GetQueryCount()); | |
| ForEachAvailableQueryRange( | |
| QueryIndex(0u), availability.size(), [&](QueryIndex i) { return availability[i]; }, | |
| [&](QueryIndex start, QueryIndex count) { | |
| device->fn.CmdResetQueryPool(commands, ToBackend(querySet)->GetHandle(), | |
| uint32_t{start}, uint32_t{count}); | |
| }); | |
| } | |
| void RecordWriteTimestampCmd(CommandRecordingContext* recordingContext, | |
| Device* device, | |
| QuerySetBase* querySet, | |
| QueryIndex queryIndex, | |
| bool isRenderPass, | |
| VkPipelineStageFlagBits pipelineStage) { | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| // The queries must be reset between uses, and the reset command cannot be called in render | |
| // pass. | |
| if (!isRenderPass) { | |
| device->fn.CmdResetQueryPool(commands, ToBackend(querySet)->GetHandle(), | |
| uint32_t{queryIndex}, 1u); | |
| } | |
| device->fn.CmdWriteTimestamp(commands, pipelineStage, ToBackend(querySet)->GetHandle(), | |
| uint32_t{queryIndex}); | |
| } | |
| void RecordResolveQuerySetCmd(VkCommandBuffer commands, | |
| Device* device, | |
| QuerySet* querySet, | |
| QueryIndex firstQuery, | |
| QueryIndex queryCount, | |
| Buffer* destination, | |
| uint64_t destinationOffset) { | |
| ForEachAvailableQueryRange( | |
| firstQuery, queryCount, [&](QueryIndex i) { return querySet->IsQueryAvailable(i); }, | |
| [&](QueryIndex start, QueryIndex count) { | |
| // Compute the offset for this range of available queries in the buffer. | |
| VkDeviceSize resolveBufferOffset = | |
| destinationOffset + ToQueryStorageSize(start - firstQuery); | |
| // Resolve the queries between firstTrueIt and nextFalseIt (which is at most lastIt) | |
| device->fn.CmdCopyQueryPoolResults(commands, querySet->GetHandle(), uint32_t{start}, | |
| uint32_t{count}, destination->GetHandle(), | |
| resolveBufferOffset, kSingleQueryStorageSize, | |
| VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); | |
| }); | |
| } | |
| VkClearValue ToVkClearValue(dawn::native::Color clearColor, TextureComponentType baseType) { | |
| VkClearValue clearValue; | |
| switch (baseType) { | |
| case TextureComponentType::Float: { | |
| const std::array<float, 4> appliedClearColor = ConvertToFloatColor(clearColor); | |
| // TODO(https://crbug.com/532554331): Use Span's constructor from C-style arrays. | |
| Span<float> floatValues = DAWN_UNSAFE_TODO(Span<float>(clearValue.color.float32, 4u)); | |
| floatValues.CopyFrom(appliedClearColor); | |
| break; | |
| } | |
| case TextureComponentType::Uint: { | |
| const std::array<uint32_t, 4> appliedClearColor = | |
| ConvertToUnsignedIntegerColor(clearColor); | |
| // TODO(https://crbug.com/532554331): Use Span's constructor from C-style arrays. | |
| Span<uint32_t> u32Values = | |
| DAWN_UNSAFE_TODO(Span<uint32_t>(clearValue.color.uint32, 4u)); | |
| u32Values.CopyFrom(appliedClearColor); | |
| break; | |
| } | |
| case TextureComponentType::Sint: { | |
| const std::array<int32_t, 4> appliedClearColor = | |
| ConvertToSignedIntegerColor(clearColor); | |
| // TODO(https://crbug.com/532554331): Use Span's constructor from C-style arrays. | |
| Span<int32_t> i32Values = DAWN_UNSAFE_TODO(Span<int32_t>(clearValue.color.int32, 4u)); | |
| i32Values.CopyFrom(appliedClearColor); | |
| break; | |
| } | |
| } | |
| return clearValue; | |
| } | |
| // A number of WebGPU commands cannot be immediately turned into Vulkan commands and instead just | |
| // dirty state which needs to be applied just before the Draw/Dispatch. This State class factors the | |
| // tracking logic of both Compute and Render passes. | |
| template <typename BaseImmediateTracker, typename Pipeline, VkPipelineBindPoint PipelineBindPoint> | |
| struct ProgrammablePassState : public StackAllocated { | |
| ProgrammablePassState(const VulkanFunctions& vk, CommandRecordingContext* recordingContext) | |
| : vk(vk), recordingContext(recordingContext) {} | |
| void OnSetPipeline(Pipeline* pipeline) { | |
| lastPipeline = pipeline; | |
| descriptorSets.OnSetPipeline<Pipeline>(pipeline); | |
| immediates.OnSetPipeline(pipeline); | |
| } | |
| // Synchronizes all the dirty state before doing the operation. | |
| template <typename F> | |
| MaybeError SyncAndRun(F&& DoOperation) { | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| if (lastAppliedPipeline != lastPipeline) { | |
| if (lastPipeline->RequiresSpecialization()) { | |
| return RunWithSpecialization(DoOperation); | |
| } | |
| vk.CmdBindPipeline(commands, PipelineBindPoint, lastPipeline->GetHandle()); | |
| lastPipeline->ApplyDynamicState(commands, lastAppliedPipeline); | |
| lastAppliedPipeline = lastPipeline; | |
| } | |
| descriptorSets.Apply(vk, commands, PipelineBindPoint); | |
| immediates.Apply(vk, commands); | |
| DoOperation(vk, commands); | |
| return {}; | |
| } | |
| using PipelineSpecialization = Pipeline::Specialization; | |
| PipelineSpecialization ComputeSpecializationBaseOnState() const { | |
| PipelineSpecialization s; | |
| // Add the specialization to use a static samplers for external textures that need them. | |
| PipelineBase::SamplerForExternalTextureMap samplerForET = | |
| lastPipeline->ComputeSamplerForExternalTextureMap(); | |
| for (const auto& [etBindPoint, maybeSamplerBindPoint] : samplerForET) { | |
| const BindGroupBase* etBindGroup = descriptorSets.GetBindGroup(etBindPoint.group); | |
| // Multiplanar external textures never use a static sampler. (et may be null when a 2D | |
| // view is bound directly in lieu of an external texture). | |
| const ExternalTextureBase* et = | |
| etBindGroup->GetBoundExternalTexture(etBindPoint.binding).Get(); | |
| if (et != nullptr && !et->HasSingleView()) { | |
| continue; | |
| } | |
| // Get the ExternalTextureBindingInfo from which we can get the indices for its plane0 | |
| // and staticSampler bind points. | |
| const auto& bindingInfo = | |
| etBindGroup->GetLayout()->GetAPIBindingInfo(etBindPoint.binding); | |
| const auto& etInfo = std::get<ExternalTextureBindingInfo>(bindingInfo.bindingLayout); | |
| DAWN_ASSERT(etInfo.staticSampler.has_value()); | |
| const TextureView* view = | |
| ToBackend(etBindGroup->GetBindingAsTextureView(etInfo.plane0)); | |
| DAWN_ASSERT(view != nullptr); | |
| // Use static samplers for YCbCr external textures. However when the toggle is enabled, | |
| // we use a static sampler for all the single-planar external textures, which helps with | |
| // testing the code paths on any Vulkan-capable device. | |
| if (view->GetFormat().format != wgpu::TextureFormat::OpaqueYCbCrAndroid && | |
| !lastPipeline->GetDevice()->IsToggleEnabled( | |
| Toggle::VulkanForceStaticSamplersForExternalTextures)) { | |
| continue; | |
| } | |
| // Find the sampler it's used with, its filtering parameters will be copied in the | |
| // static sampler (or use "near" if no sampler is used with the external texture). | |
| const Sampler* sampler = nullptr; | |
| if (maybeSamplerBindPoint) { | |
| sampler = ToBackend(descriptorSets.GetBindGroup(maybeSamplerBindPoint->group) | |
| ->GetBindingAsSampler(maybeSamplerBindPoint->binding)); | |
| } | |
| // Tell both the shader that we'll be using a YCbCr external texture at the bindpoint, | |
| // and tell BindGroupLayouts to use a specific static sampler. | |
| s.ycbcrExternalTextures.insert(etBindPoint); | |
| s.layout.bindGroups[etBindPoint.group].staticSamplers[*etInfo.staticSampler] = | |
| StaticSamplerSpecialization::From(view, sampler); | |
| } | |
| return s; | |
| } | |
| // The handling of specialization of pipelines is split to a separate method that's not | |
| // templated, to try to avoid inlining, so as to not hugely increase the stack size of the | |
| // callees. | |
| MaybeError RunWithSpecialization( | |
| absl::FunctionRef<void(const VulkanFunctions&, VkCommandBuffer)> DoOperation) { | |
| // Make sure to reapply all the state in case the specialization changes something in how | |
| // the state should be applied (for example with the VkPipelineLayout). | |
| DirtyAll(); | |
| // At the moment the only specialization is for using static samplers in ExternalTextures. | |
| DAWN_ASSERT(lastPipeline->GetDevice()->NeedsStaticSamplerForExternalTexture() && | |
| lastPipeline->GetLayout()->HasExternalTextures()); | |
| PipelineSpecialization specialization = ComputeSpecializationBaseOnState(); | |
| // Recreate the descriptor sets using the specialized VkDescriptorSetLayout. | |
| PerBindGroup<std::unique_ptr<OwnedDescriptorSet>> newDescriptorSets; | |
| for (BindGroupIndex group : lastPipeline->GetLayout()->GetBindGroupLayoutsMask()) { | |
| auto bgl = ToBackend(lastPipeline->GetLayout()->GetBindGroupLayout(group)); | |
| auto boundBG = ToBackend(descriptorSets.GetBindGroup(group)); | |
| DAWN_TRY_ASSIGN( | |
| newDescriptorSets[group], | |
| bgl->GetSpecializedSetFor(boundBG, specialization.layout.bindGroups[group])); | |
| } | |
| // Get the specialized pipeline. | |
| PipelineHandles pipelineHandles; | |
| DAWN_TRY_ASSIGN(pipelineHandles, | |
| lastPipeline->GetOrCreateSpecializedHandle(std::move(specialization))); | |
| // Reapply all the state and run the operation. | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| vk.CmdBindPipeline(commands, PipelineBindPoint, pipelineHandles.pipeline); | |
| descriptorSets.Apply(vk, commands, PipelineBindPoint, | |
| [&](BindGroupIndex i) { return newDescriptorSets[i]->GetHandle(); }); | |
| immediates.Apply(vk, commands, pipelineHandles.layout, lastPipeline->GetImmediateMask()); | |
| DoOperation(vk, commands); | |
| // Make sure none of the state applied for the specialization is used in further commands. | |
| DirtyAll(); | |
| return {}; | |
| } | |
| void DirtyAll() { | |
| descriptorSets.DirtyAll(); | |
| immediates.DirtyAll(); | |
| lastAppliedPipeline = nullptr; | |
| } | |
| const VulkanFunctions& vk; | |
| CommandRecordingContext* recordingContext; | |
| DescriptorSetTracker descriptorSets; | |
| ImmediateTracker<BaseImmediateTracker> immediates; | |
| Pipeline* lastPipeline = nullptr; | |
| Pipeline* lastAppliedPipeline = nullptr; | |
| }; | |
| using RenderPassState = | |
| ProgrammablePassState<RenderImmediatesTracker, RenderPipeline, VK_PIPELINE_BIND_POINT_GRAPHICS>; | |
| using ComputePassState = ProgrammablePassState<ComputeImmediatesTracker, | |
| ComputePipeline, | |
| VK_PIPELINE_BIND_POINT_COMPUTE>; | |
| } // anonymous namespace | |
| MaybeError RecordBeginDynamicRenderPass(CommandRecordingContext* recordingContext, | |
| Device* device, | |
| BeginRenderPassCmd* renderPass) { | |
| // Needed to work around some quirks introduced by vulkan_platform.h which causes some platforms | |
| // to hit compiler errors if Vulkan struct members are assigned to VK_NULL_HANDLE directly. | |
| static const VkImageView nullImageView = VK_NULL_HANDLE; | |
| VkRenderingInfoKHR renderInfo; | |
| renderInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; | |
| renderInfo.pNext = nullptr; | |
| renderInfo.flags = 0; | |
| // TODO(https://crbug.com/489152883): If VK_KHR_maintenance5 is available this should call | |
| // vkGetRenderingAreaGranularity(). Otherwise a good approach might be to take a guess at the | |
| // maximum granularity for the GPU. This needs some more thought and using 32x32 granularity is | |
| // stop gap. | |
| VkExtent2D granularity{32, 32}; | |
| renderInfo.renderArea = GetAlignedRenderArea(granularity, renderPass); | |
| renderInfo.layerCount = 1; | |
| renderInfo.viewMask = 0; | |
| renderInfo.pDepthAttachment = nullptr; | |
| renderInfo.pStencilAttachment = nullptr; | |
| PerColorAttachment<VkRenderingAttachmentInfoKHR> colorAttachments; | |
| ColorAttachmentMask attachmentMask = renderPass->attachmentState->GetColorAttachmentsMask(); | |
| ColorAttachmentIndex attachmentCount = GetHighestBitIndexPlusOne(attachmentMask); | |
| // Initialize all potentially used color attachments with null/default values, which allows the | |
| // attachments to be sparse. | |
| for (auto i : Range(attachmentCount)) { | |
| auto& colorAttachment = colorAttachments[i]; | |
| colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; | |
| colorAttachment.pNext = nullptr; | |
| colorAttachment.imageView = nullImageView; | |
| colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; | |
| colorAttachment.resolveMode = VK_RESOLVE_MODE_NONE; | |
| colorAttachment.resolveImageView = nullImageView; | |
| colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; | |
| colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; | |
| } | |
| // Set the color attachments used by this pass. May be sparse. | |
| for (auto i : attachmentMask) { | |
| auto& attachmentInfo = renderPass->colorAttachments[i]; | |
| TextureView* view = ToBackend(attachmentInfo.view.Get()); | |
| if (view == nullptr) { | |
| continue; | |
| } | |
| auto& colorAttachment = colorAttachments[i]; | |
| if (view->GetDimension() == wgpu::TextureViewDimension::e3D) { | |
| VkImageView handleFor2DViewOn3D; | |
| DAWN_TRY_ASSIGN(handleFor2DViewOn3D, | |
| view->GetOrCreate2DViewOn3D(attachmentInfo.depthSlice)); | |
| colorAttachment.imageView = handleFor2DViewOn3D; | |
| } else { | |
| colorAttachment.imageView = view->GetHandle(); | |
| } | |
| if (attachmentInfo.resolveTarget != nullptr) { | |
| TextureView* resolveView = ToBackend(attachmentInfo.resolveTarget.Get()); | |
| colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; | |
| colorAttachment.resolveImageView = resolveView->GetHandle(); | |
| colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; | |
| } else if (renderPass->msaaRenderToSingleSampled && | |
| view->GetTexture()->GetSampleCount() == 1) { | |
| // VUID-VkRenderingAttachmentInfo-None-12256: If MSRTSS is used and the attachment has | |
| // a sample count of 1, the resolve mode can't be VK_RESOLVE_MODE_NONE. | |
| colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; | |
| colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; | |
| } | |
| colorAttachment.loadOp = VulkanAttachmentLoadOp(attachmentInfo.loadOp); | |
| colorAttachment.storeOp = VulkanAttachmentStoreOp(attachmentInfo.storeOp); | |
| colorAttachment.clearValue = ToVkClearValue( | |
| attachmentInfo.clearColor, view->GetFormat().GetAspectInfo(Aspect::Color).baseType); | |
| } | |
| renderInfo.colorAttachmentCount = static_cast<uint32_t>(attachmentCount); | |
| renderInfo.pColorAttachments = colorAttachments.data(); | |
| // Set the depth/stencil attachment used by this pass. | |
| VkRenderingAttachmentInfoKHR depthAttachment; | |
| VkRenderingAttachmentInfoKHR stencilAttachment; | |
| if (renderPass->attachmentState->HasDepthStencilAttachment()) { | |
| const auto& attachmentInfo = renderPass->depthStencilAttachment; | |
| TextureView* view = ToBackend(attachmentInfo.view.Get()); | |
| DAWN_ASSERT(view); | |
| const Format& dsFormat = view->GetTexture()->GetFormat(); | |
| VkImageLayout imageLayout = VulkanImageLayoutForDepthStencilAttachment( | |
| dsFormat, attachmentInfo.depthReadOnly, attachmentInfo.stencilReadOnly); | |
| if (dsFormat.HasDepth()) { | |
| depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; | |
| depthAttachment.pNext = nullptr; | |
| depthAttachment.imageView = view->GetHandle(); | |
| depthAttachment.imageLayout = imageLayout; | |
| depthAttachment.resolveMode = VK_RESOLVE_MODE_NONE; | |
| depthAttachment.resolveImageView = nullImageView; | |
| depthAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| depthAttachment.loadOp = VulkanAttachmentLoadOp(attachmentInfo.depthLoadOp); | |
| depthAttachment.storeOp = VulkanAttachmentStoreOp(attachmentInfo.depthStoreOp); | |
| depthAttachment.clearValue.depthStencil.depth = attachmentInfo.clearDepth; | |
| renderInfo.pDepthAttachment = &depthAttachment; | |
| } | |
| if (dsFormat.HasStencil()) { | |
| stencilAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; | |
| stencilAttachment.pNext = nullptr; | |
| stencilAttachment.imageView = view->GetHandle(); | |
| stencilAttachment.imageLayout = imageLayout; | |
| stencilAttachment.resolveMode = VK_RESOLVE_MODE_NONE; | |
| stencilAttachment.resolveImageView = nullImageView; | |
| stencilAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| stencilAttachment.loadOp = VulkanAttachmentLoadOp(attachmentInfo.stencilLoadOp); | |
| stencilAttachment.storeOp = VulkanAttachmentStoreOp(attachmentInfo.stencilStoreOp); | |
| stencilAttachment.clearValue.depthStencil.stencil = attachmentInfo.clearStencil; | |
| renderInfo.pStencilAttachment = &stencilAttachment; | |
| } | |
| } | |
| VkMultisampledRenderToSingleSampledInfoEXT msrtss = {}; | |
| if (renderPass->msaaRenderToSingleSampled) { | |
| msrtss.sType = VK_STRUCTURE_TYPE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_INFO_EXT; | |
| msrtss.pNext = nullptr; | |
| msrtss.multisampledRenderToSingleSampledEnable = VK_TRUE; | |
| msrtss.rasterizationSamples = | |
| VulkanSampleCount(renderPass->attachmentState->GetSampleCount()); | |
| renderInfo.pNext = &msrtss; | |
| } | |
| // TODO(crbug.com/463893794): Handle ExpandResolveTexture. | |
| device->fn.CmdBeginRenderingKHR(recordingContext->commandBuffer, &renderInfo); | |
| return {}; | |
| } | |
| MaybeError RecordBeginRenderPass(CommandRecordingContext* recordingContext, | |
| Device* device, | |
| BeginRenderPassCmd* renderPass) { | |
| if (device->GetRenderPassType() == VulkanRenderPassType::DynamicRendering) { | |
| return RecordBeginDynamicRenderPass(recordingContext, device, renderPass); | |
| } | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| // Query a VkRenderPass from the cache | |
| VkRenderPass renderPassVK = VK_NULL_HANDLE; | |
| uint32_t renderPassId = 0; | |
| { | |
| RenderPassCacheQuery query; | |
| for (auto i : renderPass->attachmentState->GetColorAttachmentsMask()) { | |
| const auto& attachmentInfo = renderPass->colorAttachments[i]; | |
| bool hasResolveTarget = attachmentInfo.resolveTarget != nullptr; | |
| bool renderToSingleSampled = renderPass->msaaRenderToSingleSampled && | |
| attachmentInfo.view->GetTexture()->GetSampleCount() == 1; | |
| query.SetColor(i, attachmentInfo.view->GetFormat().format, attachmentInfo.loadOp, | |
| attachmentInfo.storeOp, hasResolveTarget, renderToSingleSampled); | |
| } | |
| if (renderPass->attachmentState->HasDepthStencilAttachment()) { | |
| const auto& attachmentInfo = renderPass->depthStencilAttachment; | |
| query.SetDepthStencil(attachmentInfo.view->GetTexture()->GetFormat().format, | |
| attachmentInfo.depthLoadOp, attachmentInfo.depthStoreOp, | |
| attachmentInfo.depthReadOnly, attachmentInfo.stencilLoadOp, | |
| attachmentInfo.stencilStoreOp, attachmentInfo.stencilReadOnly); | |
| } | |
| query.SetSampleCount(renderPass->attachmentState->GetSampleCount()); | |
| RenderPassCache::RenderPassInfo renderPassInfo; | |
| DAWN_TRY_ASSIGN(renderPassInfo, device->GetRenderPassCache()->GetRenderPass(query)); | |
| renderPassVK = renderPassInfo.renderPass; | |
| renderPassId = renderPassInfo.uniqueId; | |
| } | |
| // Query a framebuffer from the cache and gather the clear values for the attachments at the | |
| // same time. | |
| FramebufferCacheQuery framebufferQuery; | |
| VkFramebuffer framebuffer = VK_NULL_HANDLE; | |
| { | |
| framebufferQuery.SetRenderPass(renderPassId, renderPass->width, renderPass->height); | |
| // Fill in the attachment info that will be chained in the framebuffer create info. | |
| for (auto i : renderPass->attachmentState->GetColorAttachmentsMask()) { | |
| auto& attachmentInfo = renderPass->colorAttachments[i]; | |
| TextureView* view = ToBackend(attachmentInfo.view.Get()); | |
| if (view == nullptr) { | |
| continue; | |
| } | |
| VkClearValue clearValue = ToVkClearValue( | |
| attachmentInfo.clearColor, view->GetFormat().GetAspectInfo(Aspect::Color).baseType); | |
| uint32_t depthSlice = view->GetDimension() == wgpu::TextureViewDimension::e3D | |
| ? attachmentInfo.depthSlice | |
| : 0; | |
| DAWN_TRY(framebufferQuery.AddAttachment(view, clearValue, depthSlice)); | |
| } | |
| if (renderPass->attachmentState->HasDepthStencilAttachment()) { | |
| auto& attachmentInfo = renderPass->depthStencilAttachment; | |
| TextureView* view = ToBackend(attachmentInfo.view.Get()); | |
| VkClearValue clearValue; | |
| clearValue.depthStencil.depth = attachmentInfo.clearDepth; | |
| clearValue.depthStencil.stencil = attachmentInfo.clearStencil; | |
| DAWN_TRY(framebufferQuery.AddAttachment(view, clearValue)); | |
| } | |
| for (auto i : renderPass->attachmentState->GetColorAttachmentsMask()) { | |
| if (renderPass->colorAttachments[i].resolveTarget != nullptr) { | |
| TextureView* view = ToBackend(renderPass->colorAttachments[i].resolveTarget.Get()); | |
| DAWN_TRY(framebufferQuery.AddAttachment(view)); | |
| } | |
| } | |
| DAWN_TRY_ASSIGN( | |
| framebuffer, | |
| device->GetFramebufferCache()->GetOrCreate( | |
| framebufferQuery, | |
| [&](const FramebufferCacheQuery& query) -> ResultOrError<VkFramebuffer> { | |
| VkFramebufferCreateInfo createInfo; | |
| createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; | |
| createInfo.pNext = nullptr; | |
| createInfo.flags = 0; | |
| createInfo.renderPass = renderPassVK; | |
| createInfo.attachmentCount = query.attachmentCount; | |
| createInfo.pAttachments = AsVkArray(query.attachments.data()); | |
| createInfo.width = query.width; | |
| createInfo.height = query.height; | |
| createInfo.layers = 1; | |
| VkFramebuffer framebuffer; | |
| DAWN_TRY(CheckVkSuccess( | |
| device->fn.CreateFramebuffer(device->GetVkDevice(), &createInfo, nullptr, | |
| &*framebuffer), | |
| "CreateFramebuffer")); | |
| return framebuffer; | |
| })); | |
| } | |
| VkRenderPassBeginInfo beginInfo; | |
| beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; | |
| beginInfo.pNext = nullptr; | |
| beginInfo.renderPass = renderPassVK; | |
| beginInfo.framebuffer = framebuffer; | |
| beginInfo.renderArea = GetAlignedRenderArea(device, renderPassVK, renderPass); | |
| beginInfo.clearValueCount = framebufferQuery.attachmentCount; | |
| beginInfo.pClearValues = framebufferQuery.clearValues.data(); | |
| if (renderPass->attachmentState->GetExpandResolveInfo().attachmentsToExpandResolve.any()) { | |
| DAWN_TRY(BeginRenderPassAndExpandResolveTextureWithDraw(device, recordingContext, | |
| renderPass, beginInfo)); | |
| } else { | |
| device->fn.CmdBeginRenderPass(commands, &beginInfo, VK_SUBPASS_CONTENTS_INLINE); | |
| } | |
| return {}; | |
| } | |
| void RecordEndRenderPass(CommandRecordingContext* recordingContext, Device* device) { | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| if (device->GetRenderPassType() == VulkanRenderPassType::DynamicRendering) { | |
| device->fn.CmdEndRenderingKHR(commands); | |
| } else { | |
| device->fn.CmdEndRenderPass(commands); | |
| } | |
| } | |
| // static | |
| Ref<CommandBuffer> CommandBuffer::Create(CommandEncoder* encoder, | |
| const CommandBufferDescriptor* descriptor) { | |
| return AcquireRef(new CommandBuffer(encoder, descriptor)); | |
| } | |
| CommandBuffer::CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor) | |
| : CommandBufferBase(encoder, descriptor) {} | |
| MaybeError CommandBuffer::RecordCopyImageWithTemporaryBuffer( | |
| CommandRecordingContext* recordingContext, | |
| const TextureCopy& srcCopy, | |
| const TextureCopy& dstCopy, | |
| const TexelExtent3D& texelCopySize) { | |
| DAWN_ASSERT(srcCopy.texture->GetFormat().CopyCompatibleWith(dstCopy.texture->GetFormat())); | |
| DAWN_ASSERT(srcCopy.aspect == dstCopy.aspect); | |
| const TypedTexelBlockInfo& blockInfo = GetBlockInfo(srcCopy); | |
| const BlockExtent3D copySize = blockInfo.ToBlock(texelCopySize); | |
| BlockCount widthInBlocks = copySize.width; | |
| BlockCount heightInBlocks = copySize.height; | |
| // Create the temporary buffer. Note that We don't need to respect WebGPU's 256 alignment | |
| // because it isn't a hard constraint in Vulkan. | |
| uint64_t tempBufferSize = | |
| blockInfo.ToBytes(widthInBlocks * heightInBlocks * copySize.depthOrArrayLayers); | |
| BufferDescriptor tempBufferDescriptor; | |
| tempBufferDescriptor.size = tempBufferSize; | |
| tempBufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; | |
| Device* device = ToBackend(GetDevice()); | |
| Ref<BufferBase> tempBufferBase; | |
| DAWN_TRY_ASSIGN(tempBufferBase, device->CreateBuffer(&tempBufferDescriptor)); | |
| Buffer* tempBuffer = ToBackend(tempBufferBase.Get()); | |
| auto scopedUseTempBuffer = tempBuffer->UseInternal(); | |
| BufferCopy tempBufferCopy; | |
| tempBufferCopy.buffer = tempBuffer; | |
| tempBufferCopy.rowsPerImage = heightInBlocks; | |
| tempBufferCopy.offset = 0; | |
| tempBufferCopy.blocksPerRow = widthInBlocks; | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| VkImage srcImage = ToBackend(srcCopy.texture)->GetHandle(); | |
| VkImage dstImage = ToBackend(dstCopy.texture)->GetHandle(); | |
| tempBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); | |
| VkBufferImageCopy srcToTempBufferRegion = | |
| ComputeBufferImageCopyRegion(tempBufferCopy, srcCopy, copySize); | |
| // The Dawn CopySrc usage is always mapped to GENERAL | |
| device->fn.CmdCopyImageToBuffer(commands, srcImage, VK_IMAGE_LAYOUT_GENERAL, | |
| tempBuffer->GetHandle(), 1, &srcToTempBufferRegion); | |
| tempBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc); | |
| VkBufferImageCopy tempBufferToDstRegion = | |
| ComputeBufferImageCopyRegion(tempBufferCopy, dstCopy, copySize); | |
| // Dawn guarantees dstImage be in the TRANSFER_DST_OPTIMAL layout after the | |
| // copy command. | |
| device->fn.CmdCopyBufferToImage(commands, tempBuffer->GetHandle(), dstImage, | |
| VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, | |
| &tempBufferToDstRegion); | |
| recordingContext->tempBuffers.emplace_back(std::move(tempBuffer)); | |
| return {}; | |
| } | |
| MaybeError CommandBuffer::RecordCommands(CommandRecordingContext* recordingContext) { | |
| Device* device = ToBackend(GetDevice()); | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| // Records the necessary barriers for the resource usage pre-computed by the frontend. | |
| // And resets the used query sets which are rewritten on the render pass. | |
| auto PrepareResourcesForRenderPass = [](Device* device, | |
| CommandRecordingContext* recordingContext, | |
| const RenderPassResourceUsage& usages) -> MaybeError { | |
| DAWN_TRY(PrepareResourcesForSyncScope(device, recordingContext, usages)); | |
| // Reset all query set used on current render pass together before beginning render pass | |
| // because the reset command must be called outside render pass | |
| for (size_t i = 0; i < usages.querySets.size(); ++i) { | |
| ResetUsedQuerySetsOnRenderPass(device, recordingContext->commandBuffer, | |
| usages.querySets[i], usages.queryAvailabilities[i]); | |
| } | |
| if (device->IsToggleEnabled(Toggle::VulkanAddWorkToEmptyResolvePass)) { | |
| DAWN_TRY(device->PrepareEmptyPassQuerySet(recordingContext)); | |
| } | |
| return {}; | |
| }; | |
| PassIndex nextComputePassNumber{0u}; | |
| PassIndex nextRenderPassNumber{0u}; | |
| Command type; | |
| while (mCommands.NextCommandId(&type)) { | |
| switch (type) { | |
| case Command::CopyBufferToBuffer: { | |
| CopyBufferToBufferCmd* copy = mCommands.NextCommand<CopyBufferToBufferCmd>(); | |
| if (copy->size == 0) { | |
| // Skip no-op copies. | |
| break; | |
| } | |
| Buffer* srcBuffer = ToBackend(copy->source.Get()); | |
| Buffer* dstBuffer = ToBackend(copy->destination.Get()); | |
| srcBuffer->EnsureDataInitialized(recordingContext); | |
| dstBuffer->EnsureDataInitializedAsDestination(recordingContext, | |
| copy->destinationOffset, copy->size); | |
| srcBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc); | |
| dstBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); | |
| VkBufferCopy region; | |
| region.srcOffset = copy->sourceOffset; | |
| region.dstOffset = copy->destinationOffset; | |
| region.size = copy->size; | |
| VkBuffer srcHandle = srcBuffer->GetHandle(); | |
| VkBuffer dstHandle = dstBuffer->GetHandle(); | |
| device->fn.CmdCopyBuffer(commands, srcHandle, dstHandle, 1, ®ion); | |
| break; | |
| } | |
| case Command::CopyBufferToTexture: { | |
| CopyBufferToTextureCmd* copy = mCommands.NextCommand<CopyBufferToTextureCmd>(); | |
| if (copy->copySize.IsEmpty()) { | |
| // Skip no-op copies. | |
| continue; | |
| } | |
| auto& src = copy->source; | |
| auto& dst = copy->destination; | |
| ToBackend(src.buffer)->EnsureDataInitialized(recordingContext); | |
| const TypedTexelBlockInfo& blockInfo = GetBlockInfo(dst); | |
| std::vector<VkBufferImageCopy> regions; | |
| if (device->IsToggleEnabled(Toggle::VulkanSplitBufferTextureCopyForArrayLayers) && | |
| copy->copySize.depthOrArrayLayers > TexelCount{1u}) { | |
| regions = ComputePerArrayLayerBufferImageCopyRegions(src, dst, copy->copySize, | |
| blockInfo); | |
| } else { | |
| regions.push_back( | |
| ComputeBufferImageCopyRegion(src, dst, blockInfo.ToBlock(copy->copySize))); | |
| } | |
| SubresourceRange range = | |
| GetSubresourcesAffectedByCopy(copy->destination, copy->copySize); | |
| if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize, dst.mipLevel, | |
| dst.aspect)) { | |
| // Since texture has been overwritten, it has been "initialized" | |
| dst.texture->SetIsSubresourceContentInitialized(true, range); | |
| } else { | |
| DAWN_TRY(ToBackend(dst.texture) | |
| ->EnsureSubresourceContentInitialized(recordingContext, range)); | |
| } | |
| ToBackend(src.buffer) | |
| ->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc); | |
| ToBackend(dst.texture) | |
| ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst, | |
| wgpu::ShaderStage::None, range); | |
| VkBuffer srcBuffer = ToBackend(src.buffer)->GetHandle(); | |
| VkImage dstImage = ToBackend(dst.texture)->GetHandle(); | |
| // Dawn guarantees dstImage be in the TRANSFER_DST_OPTIMAL layout after the | |
| // copy command. | |
| device->fn.CmdCopyBufferToImage( | |
| commands, srcBuffer, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | |
| static_cast<uint32_t>(regions.size()), regions.data()); | |
| break; | |
| } | |
| case Command::CopyTextureToBuffer: { | |
| CopyTextureToBufferCmd* copy = mCommands.NextCommand<CopyTextureToBufferCmd>(); | |
| if (copy->copySize.IsEmpty()) { | |
| // Skip no-op copies. | |
| continue; | |
| } | |
| auto& src = copy->source; | |
| auto& dst = copy->destination; | |
| ToBackend(dst.buffer)->EnsureDataInitializedAsDestination(recordingContext, copy); | |
| const TypedTexelBlockInfo& blockInfo = GetBlockInfo(src); | |
| std::vector<VkBufferImageCopy> regions; | |
| if (device->IsToggleEnabled(Toggle::VulkanSplitBufferTextureCopyForArrayLayers) && | |
| copy->copySize.depthOrArrayLayers > TexelCount{1u}) { | |
| regions = ComputePerArrayLayerBufferImageCopyRegions(dst, src, copy->copySize, | |
| blockInfo); | |
| } else { | |
| regions.push_back( | |
| ComputeBufferImageCopyRegion(dst, src, blockInfo.ToBlock(copy->copySize))); | |
| } | |
| SubresourceRange range = | |
| GetSubresourcesAffectedByCopy(copy->source, copy->copySize); | |
| DAWN_TRY(ToBackend(src.texture) | |
| ->EnsureSubresourceContentInitialized(recordingContext, range)); | |
| ToBackend(src.texture) | |
| ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc, | |
| wgpu::ShaderStage::None, range); | |
| ToBackend(dst.buffer) | |
| ->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); | |
| VkImage srcImage = ToBackend(src.texture)->GetHandle(); | |
| VkBuffer dstBuffer = ToBackend(dst.buffer)->GetHandle(); | |
| // The Dawn CopySrc usage is always mapped to GENERAL | |
| device->fn.CmdCopyImageToBuffer(commands, srcImage, VK_IMAGE_LAYOUT_GENERAL, | |
| dstBuffer, static_cast<uint32_t>(regions.size()), | |
| regions.data()); | |
| break; | |
| } | |
| case Command::CopyTextureToTexture: { | |
| CopyTextureToTextureCmd* copy = mCommands.NextCommand<CopyTextureToTextureCmd>(); | |
| if (copy->copySize.IsEmpty()) { | |
| // Skip no-op copies. | |
| continue; | |
| } | |
| TextureCopy& src = copy->source; | |
| TextureCopy& dst = copy->destination; | |
| SubresourceRange srcRange = GetSubresourcesAffectedByCopy(src, copy->copySize); | |
| SubresourceRange dstRange = GetSubresourcesAffectedByCopy(dst, copy->copySize); | |
| DAWN_TRY(ToBackend(src.texture) | |
| ->EnsureSubresourceContentInitialized(recordingContext, srcRange)); | |
| if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize, dst.mipLevel, | |
| dst.aspect)) { | |
| // Since destination texture has been overwritten, it has been "initialized" | |
| dst.texture->SetIsSubresourceContentInitialized(true, dstRange); | |
| } else { | |
| DAWN_TRY(ToBackend(dst.texture) | |
| ->EnsureSubresourceContentInitialized(recordingContext, dstRange)); | |
| } | |
| if (src.texture.Get() == dst.texture.Get() && src.mipLevel == dst.mipLevel) { | |
| // When there are overlapped subresources, the layout of the overlapped | |
| // subresources should all be GENERAL instead of what we set now. Currently | |
| // it is not allowed to copy with overlapped subresources, but we still | |
| // add the DAWN_ASSERT here as a reminder for this possible misuse. | |
| DAWN_ASSERT(!IsRangeOverlapped(src.origin.z, dst.origin.z, | |
| copy->copySize.depthOrArrayLayers)); | |
| } | |
| ToBackend(src.texture) | |
| ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc, | |
| wgpu::ShaderStage::None, srcRange); | |
| ToBackend(dst.texture) | |
| ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst, | |
| wgpu::ShaderStage::None, dstRange); | |
| // In some situations we cannot do texture-to-texture copies with vkCmdCopyImage | |
| // because as Vulkan SPEC always validates image copies with the virtual size of | |
| // the image subresource, when the extent that fits in the copy region of one | |
| // subresource but does not fit in the one of another subresource, we will fail | |
| // to find a valid extent to satisfy the requirements on both source and | |
| // destination image subresource. For example, when the source is the first | |
| // level of a 16x16 texture in BC format, and the destination is the third level | |
| // of a 60x60 texture in the same format, neither 16x16 nor 15x15 is valid as | |
| // the extent of vkCmdCopyImage. | |
| // Our workaround for this issue is replacing the texture-to-texture copy with | |
| // one texture-to-buffer copy and one buffer-to-texture copy. | |
| bool copyUsingTemporaryBuffer = | |
| device->IsToggleEnabled( | |
| Toggle::UseTemporaryBufferInCompressedTextureToTextureCopy) && | |
| src.texture->GetFormat().isCompressed && | |
| !HasSameTextureCopyExtent(src, dst, copy->copySize); | |
| if (!copyUsingTemporaryBuffer) { | |
| VkImage srcImage = ToBackend(src.texture)->GetHandle(); | |
| VkImage dstImage = ToBackend(dst.texture)->GetHandle(); | |
| Aspect aspects = ToBackend(src.texture)->GetDisjointVulkanAspects(); | |
| for (Aspect aspect : IterateEnumMask(aspects)) { | |
| VkImageCopy region = | |
| ComputeImageCopyRegion(src, dst, copy->copySize, aspect); | |
| // Dawn guarantees dstImage be in the TRANSFER_DST_OPTIMAL layout after | |
| // the copy command. | |
| device->fn.CmdCopyImage(commands, srcImage, VK_IMAGE_LAYOUT_GENERAL, | |
| dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, | |
| ®ion); | |
| } | |
| } else { | |
| DAWN_TRY(RecordCopyImageWithTemporaryBuffer(recordingContext, src, dst, | |
| copy->copySize)); | |
| } | |
| break; | |
| } | |
| case Command::ClearBuffer: { | |
| ClearBufferCmd* cmd = mCommands.NextCommand<ClearBufferCmd>(); | |
| if (cmd->size == 0) { | |
| // Skip no-op fills. | |
| break; | |
| } | |
| Buffer* dstBuffer = ToBackend(cmd->buffer.Get()); | |
| bool clearedToZero = dstBuffer->EnsureDataInitializedAsDestination( | |
| recordingContext, cmd->offset, cmd->size); | |
| if (!clearedToZero) { | |
| dstBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); | |
| device->fn.CmdFillBuffer(recordingContext->commandBuffer, | |
| dstBuffer->GetHandle(), cmd->offset, cmd->size, 0u); | |
| } | |
| break; | |
| } | |
| case Command::BeginRenderPass: { | |
| BeginRenderPassCmd* cmd = mCommands.NextCommand<BeginRenderPassCmd>(); | |
| auto& usage = GetResourceUsages().renderPasses[nextRenderPassNumber]; | |
| DAWN_TRY(PrepareResourcesForRenderPass(device, recordingContext, usage)); | |
| DAWN_TRY(LazyClearRenderPassAttachments( | |
| device, cmd, [&](TextureBase* texture, const SubresourceRange& range) { | |
| return ToBackend(texture)->EnsureSubresourceContentInitialized( | |
| recordingContext, range); | |
| })); | |
| DAWN_TRY(RecordRenderPass(recordingContext, cmd, usage, nextRenderPassNumber)); | |
| recordingContext->hasRecordedRenderPass = true; | |
| nextRenderPassNumber++; | |
| break; | |
| } | |
| case Command::BeginComputePass: { | |
| BeginComputePassCmd* cmd = mCommands.NextCommand<BeginComputePassCmd>(); | |
| // If required, split the command buffer any time a compute pass follows a render | |
| // pass to work around a Qualcomm bug. | |
| if (recordingContext->hasRecordedRenderPass && | |
| device->IsToggleEnabled( | |
| Toggle::VulkanSplitCommandBufferOnComputePassAfterRenderPass)) { | |
| // Identified a potential crash case, split the command buffer. | |
| DAWN_TRY( | |
| ToBackend(device->GetQueue())->SplitRecordingContext(recordingContext)); | |
| commands = recordingContext->commandBuffer; | |
| } | |
| DAWN_TRY( | |
| RecordComputePass(recordingContext, cmd, | |
| GetResourceUsages().computePasses[nextComputePassNumber])); | |
| nextComputePassNumber++; | |
| break; | |
| } | |
| case Command::ResolveQuerySet: { | |
| ResolveQuerySetCmd* cmd = mCommands.NextCommand<ResolveQuerySetCmd>(); | |
| QuerySet* querySet = ToBackend(cmd->querySet.Get()); | |
| Buffer* destination = ToBackend(cmd->destination.Get()); | |
| destination->EnsureDataInitializedAsDestination( | |
| recordingContext, cmd->destinationOffset, ToQueryStorageSize(cmd->queryCount)); | |
| // vkCmdCopyQueryPoolResults only can retrieve available queries because | |
| // VK_QUERY_RESULT_WAIT_BIT is set. In order to resolve the unavailable queries | |
| // as 0s, we need to clear the resolving region of the destination buffer to 0s. | |
| bool clearNeeded = | |
| !querySet->AreAllQueriesAvailable(cmd->firstQuery, cmd->queryCount); | |
| // Workaround for resolving overlapping queries to a same buffer on Intel Gen12 GPUs | |
| // due to Mesa driver issue. | |
| // See http://crbug.com/dawn/1823 for more information. | |
| clearNeeded |= device->IsToggleEnabled(Toggle::ClearBufferBeforeResolveQueries); | |
| if (clearNeeded) { | |
| destination->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); | |
| device->fn.CmdFillBuffer(commands, destination->GetHandle(), | |
| cmd->destinationOffset, | |
| ToQueryStorageSize(cmd->queryCount), 0u); | |
| } | |
| destination->TransitionUsageNow(recordingContext, wgpu::BufferUsage::QueryResolve); | |
| RecordResolveQuerySetCmd(commands, device, querySet, cmd->firstQuery, | |
| cmd->queryCount, destination, cmd->destinationOffset); | |
| break; | |
| } | |
| case Command::WriteTimestamp: { | |
| WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>(); | |
| RecordWriteTimestampCmd(recordingContext, device, cmd->querySet.Get(), | |
| cmd->queryIndex, false, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); | |
| UpdateQueryAvailability(cmd); | |
| break; | |
| } | |
| case Command::InsertDebugMarker: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| InsertDebugMarkerCmd* cmd = mCommands.NextCommand<InsertDebugMarkerCmd>(); | |
| std::string_view label = NextNullTerminatedString(&mCommands, cmd->length); | |
| VkDebugUtilsLabelEXT utilsLabel; | |
| utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; | |
| utilsLabel.pNext = nullptr; | |
| utilsLabel.pLabelName = label.data(); | |
| // Default color to black | |
| utilsLabel.color[0] = 0.0; | |
| utilsLabel.color[1] = 0.0; | |
| utilsLabel.color[2] = 0.0; | |
| utilsLabel.color[3] = 1.0; | |
| device->fn.CmdInsertDebugUtilsLabelEXT(commands, &utilsLabel); | |
| } else { | |
| SkipCommand(&mCommands, Command::InsertDebugMarker); | |
| } | |
| break; | |
| } | |
| case Command::PopDebugGroup: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| mCommands.NextCommand<PopDebugGroupCmd>(); | |
| device->fn.CmdEndDebugUtilsLabelEXT(commands); | |
| } else { | |
| SkipCommand(&mCommands, Command::PopDebugGroup); | |
| } | |
| break; | |
| } | |
| case Command::PushDebugGroup: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| PushDebugGroupCmd* cmd = mCommands.NextCommand<PushDebugGroupCmd>(); | |
| std::string_view label = NextNullTerminatedString(&mCommands, cmd->length); | |
| VkDebugUtilsLabelEXT utilsLabel; | |
| utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; | |
| utilsLabel.pNext = nullptr; | |
| utilsLabel.pLabelName = label.data(); | |
| // Default color to black | |
| utilsLabel.color[0] = 0.0; | |
| utilsLabel.color[1] = 0.0; | |
| utilsLabel.color[2] = 0.0; | |
| utilsLabel.color[3] = 1.0; | |
| device->fn.CmdBeginDebugUtilsLabelEXT(commands, &utilsLabel); | |
| } else { | |
| SkipCommand(&mCommands, Command::PushDebugGroup); | |
| } | |
| break; | |
| } | |
| case Command::WriteBuffer: { | |
| WriteBufferCmd* write = mCommands.NextCommand<WriteBufferCmd>(); | |
| const uint64_t offset = write->offset; | |
| Span<const uint8_t> data = mCommands.NextData<uint8_t>(write->size); | |
| if (data.empty()) { | |
| continue; | |
| } | |
| Buffer* dstBuffer = ToBackend(write->buffer.Get()); | |
| // TODO(https://crbug.com/534203108): Spanify WithUploadReservation. | |
| DAWN_UNSAFE_TODO(DAWN_TRY(device->GetDynamicUploader()->WithUploadReservation( | |
| data.size(), kCopyBufferToBufferOffsetAlignment, | |
| [&](UploadReservation reservation) -> MaybeError { | |
| memcpy(reservation.mappedPointer, data.data(), data.size()); | |
| dstBuffer->EnsureDataInitializedAsDestination(recordingContext, offset, | |
| data.size()); | |
| dstBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); | |
| VkBufferCopy copy; | |
| copy.srcOffset = reservation.offsetInBuffer; | |
| copy.dstOffset = offset; | |
| copy.size = data.size(); | |
| device->fn.CmdCopyBuffer(commands, | |
| ToBackend(reservation.buffer)->GetHandle(), | |
| dstBuffer->GetHandle(), 1, ©); | |
| return {}; | |
| }))); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| return {}; | |
| } | |
| MaybeError CommandBuffer::RecordComputePass(CommandRecordingContext* recordingContext, | |
| BeginComputePassCmd* computePassCmd, | |
| const ComputePassResourceUsage& resourceUsages) { | |
| Device* device = ToBackend(GetDevice()); | |
| // Write timestamp at the beginning of compute pass if it's set | |
| if (computePassCmd->timestampWrites.beginningOfPassWriteIndex != kQuerySetIndexUndefinedTyped) { | |
| RecordWriteTimestampCmd(recordingContext, device, | |
| computePassCmd->timestampWrites.querySet.Get(), | |
| computePassCmd->timestampWrites.beginningOfPassWriteIndex, false, | |
| VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); | |
| } | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| uint64_t currentDispatch = 0; | |
| ComputePassState state(device->fn, recordingContext); | |
| Command type; | |
| while (mCommands.NextCommandId(&type)) { | |
| switch (type) { | |
| case Command::EndComputePass: { | |
| mCommands.NextCommand<EndComputePassCmd>(); | |
| // Write timestamp at the end of compute pass if it's set. | |
| if (computePassCmd->timestampWrites.endOfPassWriteIndex != | |
| kQuerySetIndexUndefinedTyped) { | |
| RecordWriteTimestampCmd(recordingContext, device, | |
| computePassCmd->timestampWrites.querySet.Get(), | |
| computePassCmd->timestampWrites.endOfPassWriteIndex, | |
| false, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); | |
| } | |
| UpdateQueryAvailability(computePassCmd->timestampWrites); | |
| return {}; | |
| } | |
| case Command::Dispatch: { | |
| DispatchCmd* dispatch = mCommands.NextCommand<DispatchCmd>(); | |
| DAWN_TRY(PrepareResourcesForSyncScope( | |
| device, recordingContext, resourceUsages.dispatchUsages[currentDispatch])); | |
| currentDispatch++; | |
| DAWN_TRY(state.SyncAndRun([&](const VulkanFunctions& vk, VkCommandBuffer commands) { | |
| vk.CmdDispatch(commands, dispatch->x, dispatch->y, dispatch->z); | |
| })); | |
| break; | |
| } | |
| case Command::DispatchIndirect: { | |
| DispatchIndirectCmd* dispatch = mCommands.NextCommand<DispatchIndirectCmd>(); | |
| VkBuffer indirectBuffer = ToBackend(dispatch->indirectBuffer)->GetHandle(); | |
| DAWN_TRY(PrepareResourcesForSyncScope( | |
| device, recordingContext, resourceUsages.dispatchUsages[currentDispatch])); | |
| currentDispatch++; | |
| DAWN_TRY(state.SyncAndRun([&](const VulkanFunctions& vk, VkCommandBuffer commands) { | |
| vk.CmdDispatchIndirect(commands, indirectBuffer, | |
| static_cast<VkDeviceSize>(dispatch->indirectOffset)); | |
| })); | |
| break; | |
| } | |
| case Command::SetBindGroup: { | |
| SetBindGroupCmd* cmd = mCommands.NextCommand<SetBindGroupCmd>(); | |
| BindGroup* bindGroup = ToBackend(cmd->group.Get()); | |
| Span<const uint32_t> dynamicOffsets; | |
| if (cmd->dynamicOffsetCount != 0) { | |
| dynamicOffsets = mCommands.NextData<uint32_t>(cmd->dynamicOffsetCount); | |
| } | |
| // TODO(https://crbug.com/532944732): Spanify DescriptorSetTracker. | |
| state.descriptorSets.OnSetBindGroup(cmd->index, bindGroup, dynamicOffsets.size(), | |
| dynamicOffsets.data()); | |
| break; | |
| } | |
| case Command::SetComputePipeline: { | |
| SetComputePipelineCmd* cmd = mCommands.NextCommand<SetComputePipelineCmd>(); | |
| state.OnSetPipeline(ToBackend(cmd->pipeline).Get()); | |
| break; | |
| } | |
| case Command::InsertDebugMarker: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| InsertDebugMarkerCmd* cmd = mCommands.NextCommand<InsertDebugMarkerCmd>(); | |
| std::string_view label = NextNullTerminatedString(&mCommands, cmd->length); | |
| VkDebugUtilsLabelEXT utilsLabel; | |
| utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; | |
| utilsLabel.pNext = nullptr; | |
| utilsLabel.pLabelName = label.data(); | |
| // Default color to black | |
| utilsLabel.color[0] = 0.0; | |
| utilsLabel.color[1] = 0.0; | |
| utilsLabel.color[2] = 0.0; | |
| utilsLabel.color[3] = 1.0; | |
| device->fn.CmdInsertDebugUtilsLabelEXT(commands, &utilsLabel); | |
| } else { | |
| SkipCommand(&mCommands, Command::InsertDebugMarker); | |
| } | |
| break; | |
| } | |
| case Command::PopDebugGroup: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| mCommands.NextCommand<PopDebugGroupCmd>(); | |
| device->fn.CmdEndDebugUtilsLabelEXT(commands); | |
| } else { | |
| SkipCommand(&mCommands, Command::PopDebugGroup); | |
| } | |
| break; | |
| } | |
| case Command::PushDebugGroup: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| PushDebugGroupCmd* cmd = mCommands.NextCommand<PushDebugGroupCmd>(); | |
| std::string_view label = NextNullTerminatedString(&mCommands, cmd->length); | |
| VkDebugUtilsLabelEXT utilsLabel; | |
| utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; | |
| utilsLabel.pNext = nullptr; | |
| utilsLabel.pLabelName = label.data(); | |
| // Default color to black | |
| utilsLabel.color[0] = 0.0; | |
| utilsLabel.color[1] = 0.0; | |
| utilsLabel.color[2] = 0.0; | |
| utilsLabel.color[3] = 1.0; | |
| device->fn.CmdBeginDebugUtilsLabelEXT(commands, &utilsLabel); | |
| } else { | |
| SkipCommand(&mCommands, Command::PushDebugGroup); | |
| } | |
| break; | |
| } | |
| case Command::WriteTimestamp: { | |
| WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>(); | |
| RecordWriteTimestampCmd(recordingContext, device, cmd->querySet.Get(), | |
| cmd->queryIndex, false, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); | |
| UpdateQueryAvailability(cmd); | |
| break; | |
| } | |
| case Command::SetImmediates: { | |
| SetImmediatesCmd* cmd = mCommands.NextCommand<SetImmediatesCmd>(); | |
| DAWN_ASSERT(cmd->size > 0); | |
| Span<const uint8_t> data = mCommands.NextData<uint8_t>(cmd->size); | |
| // TODO(https://crbug.com/532946455): Spanify ImmediateTracker. | |
| state.immediates.SetImmediates(cmd->offset, data.data(), data.size()); | |
| break; | |
| } | |
| case Command::SetResourceTable: { | |
| SetResourceTableCmd* cmd = mCommands.NextCommand<SetResourceTableCmd>(); | |
| state.descriptorSets.SetResourceTable(ToBackend(cmd->table.Get())); | |
| break; | |
| } | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| // EndComputePass should have been called | |
| DAWN_UNREACHABLE(); | |
| } | |
| MaybeError CommandBuffer::RecordRenderPass(CommandRecordingContext* recordingContext, | |
| BeginRenderPassCmd* renderPassCmd, | |
| const RenderPassResourceUsage& usage, | |
| PassIndex renderPassIndex) { | |
| Device* device = ToBackend(GetDevice()); | |
| VkCommandBuffer commands = recordingContext->commandBuffer; | |
| const IndirectDrawMetadata& metadata = GetIndirectDrawMetadata()[renderPassIndex]; | |
| IndirectDrawIndex indirectDrawIndex{0u}; | |
| // Write timestamp at the beginning of render pass if it's set. | |
| // We've observed that this must be called before the render pass or the timestamps produced | |
| // are nonsensical on multiple Android devices. | |
| if (renderPassCmd->timestampWrites.beginningOfPassWriteIndex != kQuerySetIndexUndefinedTyped) { | |
| RecordWriteTimestampCmd(recordingContext, device, | |
| renderPassCmd->timestampWrites.querySet.Get(), | |
| renderPassCmd->timestampWrites.beginningOfPassWriteIndex, true, | |
| VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); | |
| } | |
| DAWN_TRY(RecordBeginRenderPass(recordingContext, device, renderPassCmd)); | |
| RenderPassState state(device->fn, recordingContext); | |
| if (usage.usesFramebufferFetch) { | |
| VkDescriptorSet set; | |
| DAWN_TRY_ASSIGN( | |
| set, device->GetFramebufferFetchHelper()->GetDescriptorsForRenderPass(renderPassCmd)); | |
| state.descriptorSets.SetFramebufferFetchDescriptorSet(set); | |
| } | |
| // Set the default value for the dynamic state | |
| { | |
| device->fn.CmdSetLineWidth(commands, 1.0f); | |
| device->fn.CmdSetDepthBounds(commands, 0.0f, 1.0f); | |
| device->fn.CmdSetStencilReference(commands, VK_STENCIL_FRONT_AND_BACK, 0); | |
| float blendConstants[4] = { | |
| 0.0f, | |
| 0.0f, | |
| 0.0f, | |
| 0.0f, | |
| }; | |
| device->fn.CmdSetBlendConstants(commands, blendConstants); | |
| // The viewport and scissor default to cover all of the attachments | |
| VkViewport viewport; | |
| viewport.x = 0.0f; | |
| viewport.y = static_cast<float>(renderPassCmd->height); | |
| viewport.width = static_cast<float>(renderPassCmd->width); | |
| viewport.height = -static_cast<float>(renderPassCmd->height); | |
| viewport.minDepth = 0.0f; | |
| viewport.maxDepth = 1.0f; | |
| device->fn.CmdSetViewport(commands, 0, 1, &viewport); | |
| VkRect2D scissorRect; | |
| scissorRect.offset.x = static_cast<int32_t>(renderPassCmd->renderArea.x); | |
| scissorRect.offset.y = static_cast<int32_t>(renderPassCmd->renderArea.y); | |
| scissorRect.extent.width = renderPassCmd->renderArea.width; | |
| scissorRect.extent.height = renderPassCmd->renderArea.height; | |
| device->fn.CmdSetScissor(commands, 0, 1, &scissorRect); | |
| // Apply default frag depth | |
| state.immediates.SetClampFragDepth(0.0, 1.0); | |
| } | |
| // Tracks the number of commands that do significant GPU work (a draw or query write) this pass. | |
| uint32_t workCommandCount = 0; | |
| auto EncodeRenderBundleCommand = [&](CommandIterator* iter, Command type) -> MaybeError { | |
| switch (type) { | |
| case Command::Draw: { | |
| workCommandCount++; | |
| DrawCmd* draw = iter->NextCommand<DrawCmd>(); | |
| DAWN_TRY(state.SyncAndRun([&](const VulkanFunctions& vk, VkCommandBuffer commands) { | |
| vk.CmdDraw(commands, draw->vertexCount, draw->instanceCount, draw->firstVertex, | |
| draw->firstInstance); | |
| })); | |
| break; | |
| } | |
| case Command::DrawIndexed: { | |
| workCommandCount++; | |
| DrawIndexedCmd* draw = iter->NextCommand<DrawIndexedCmd>(); | |
| DAWN_TRY(state.SyncAndRun([&](const VulkanFunctions& vk, VkCommandBuffer commands) { | |
| vk.CmdDrawIndexed(commands, draw->indexCount, draw->instanceCount, | |
| draw->firstIndex, draw->baseVertex, draw->firstInstance); | |
| })); | |
| break; | |
| } | |
| case Command::DrawIndirect: { | |
| workCommandCount++; | |
| DrawIndirectCmd* draw = iter->NextCommand<DrawIndirectCmd>(); | |
| IndirectDrawMetadata::ValidatedIndirectDraw validatedDraw = | |
| metadata.GetValidatedIndirectDraw(draw, indirectDrawIndex++); | |
| Buffer* indirectBuffer = ToBackend(validatedDraw.indirectBuffer.Get()); | |
| DAWN_ASSERT(indirectBuffer != nullptr); | |
| DAWN_TRY(state.SyncAndRun([&](const VulkanFunctions& vk, VkCommandBuffer commands) { | |
| vk.CmdDrawIndirect(commands, indirectBuffer->GetHandle(), | |
| validatedDraw.indirectOffset, 1, 0); | |
| })); | |
| break; | |
| } | |
| case Command::DrawIndexedIndirect: { | |
| workCommandCount++; | |
| DrawIndexedIndirectCmd* draw = iter->NextCommand<DrawIndexedIndirectCmd>(); | |
| IndirectDrawMetadata::ValidatedIndirectDraw validatedDraw = | |
| metadata.GetValidatedIndirectDraw(draw, indirectDrawIndex++); | |
| Buffer* indirectBuffer = ToBackend(validatedDraw.indirectBuffer.Get()); | |
| DAWN_ASSERT(indirectBuffer != nullptr); | |
| DAWN_TRY(state.SyncAndRun([&](const VulkanFunctions& vk, VkCommandBuffer commands) { | |
| vk.CmdDrawIndexedIndirect(commands, indirectBuffer->GetHandle(), | |
| validatedDraw.indirectOffset, 1, 0); | |
| })); | |
| break; | |
| } | |
| case Command::MultiDrawIndirect: { | |
| workCommandCount++; | |
| MultiDrawIndirectCmd* cmd = iter->NextCommand<MultiDrawIndirectCmd>(); | |
| Buffer* indirectBuffer = ToBackend(cmd->indirectBuffer.Get()); | |
| DAWN_ASSERT(indirectBuffer != nullptr); | |
| // Count buffer is optional | |
| Buffer* countBuffer = ToBackend(cmd->drawCountBuffer.Get()); | |
| DAWN_TRY(state.SyncAndRun([&](const VulkanFunctions& vk, VkCommandBuffer commands) { | |
| if (countBuffer == nullptr) { | |
| vk.CmdDrawIndirect(commands, indirectBuffer->GetHandle(), | |
| static_cast<VkDeviceSize>(cmd->indirectOffset), | |
| cmd->maxDrawCount, kDrawIndirectSize); | |
| } else { | |
| vk.CmdDrawIndirectCountKHR(commands, indirectBuffer->GetHandle(), | |
| static_cast<VkDeviceSize>(cmd->indirectOffset), | |
| countBuffer->GetHandle(), | |
| static_cast<VkDeviceSize>(cmd->drawCountOffset), | |
| cmd->maxDrawCount, kDrawIndirectSize); | |
| } | |
| })); | |
| break; | |
| } | |
| case Command::MultiDrawIndexedIndirect: { | |
| workCommandCount++; | |
| MultiDrawIndexedIndirectCmd* cmd = iter->NextCommand<MultiDrawIndexedIndirectCmd>(); | |
| Buffer* indirectBuffer = ToBackend(cmd->indirectBuffer.Get()); | |
| DAWN_ASSERT(indirectBuffer != nullptr); | |
| // Count buffer is optional | |
| Buffer* countBuffer = ToBackend(cmd->drawCountBuffer.Get()); | |
| DAWN_TRY(state.SyncAndRun([&](const VulkanFunctions& vk, VkCommandBuffer commands) { | |
| if (countBuffer == nullptr) { | |
| vk.CmdDrawIndexedIndirect(commands, indirectBuffer->GetHandle(), | |
| static_cast<VkDeviceSize>(cmd->indirectOffset), | |
| cmd->maxDrawCount, kDrawIndexedIndirectSize); | |
| } else { | |
| vk.CmdDrawIndexedIndirectCountKHR( | |
| commands, indirectBuffer->GetHandle(), | |
| static_cast<VkDeviceSize>(cmd->indirectOffset), | |
| countBuffer->GetHandle(), | |
| static_cast<VkDeviceSize>(cmd->drawCountOffset), cmd->maxDrawCount, | |
| kDrawIndexedIndirectSize); | |
| } | |
| })); | |
| break; | |
| } | |
| case Command::InsertDebugMarker: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| InsertDebugMarkerCmd* cmd = iter->NextCommand<InsertDebugMarkerCmd>(); | |
| std::string_view label = NextNullTerminatedString(iter, cmd->length); | |
| VkDebugUtilsLabelEXT utilsLabel; | |
| utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; | |
| utilsLabel.pNext = nullptr; | |
| utilsLabel.pLabelName = label.data(); | |
| // Default color to black | |
| utilsLabel.color[0] = 0.0; | |
| utilsLabel.color[1] = 0.0; | |
| utilsLabel.color[2] = 0.0; | |
| utilsLabel.color[3] = 1.0; | |
| device->fn.CmdInsertDebugUtilsLabelEXT(commands, &utilsLabel); | |
| } else { | |
| SkipCommand(iter, Command::InsertDebugMarker); | |
| } | |
| break; | |
| } | |
| case Command::PopDebugGroup: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| iter->NextCommand<PopDebugGroupCmd>(); | |
| device->fn.CmdEndDebugUtilsLabelEXT(commands); | |
| } else { | |
| SkipCommand(iter, Command::PopDebugGroup); | |
| } | |
| break; | |
| } | |
| case Command::PushDebugGroup: { | |
| if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { | |
| PushDebugGroupCmd* cmd = iter->NextCommand<PushDebugGroupCmd>(); | |
| std::string_view label = NextNullTerminatedString(iter, cmd->length); | |
| VkDebugUtilsLabelEXT utilsLabel; | |
| utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; | |
| utilsLabel.pNext = nullptr; | |
| utilsLabel.pLabelName = label.data(); | |
| // Default color to black | |
| utilsLabel.color[0] = 0.0; | |
| utilsLabel.color[1] = 0.0; | |
| utilsLabel.color[2] = 0.0; | |
| utilsLabel.color[3] = 1.0; | |
| device->fn.CmdBeginDebugUtilsLabelEXT(commands, &utilsLabel); | |
| } else { | |
| SkipCommand(iter, Command::PushDebugGroup); | |
| } | |
| break; | |
| } | |
| case Command::SetBindGroup: { | |
| SetBindGroupCmd* cmd = iter->NextCommand<SetBindGroupCmd>(); | |
| BindGroup* bindGroup = ToBackend(cmd->group.Get()); | |
| Span<const uint32_t> dynamicOffsets; | |
| if (cmd->dynamicOffsetCount != 0) { | |
| dynamicOffsets = iter->NextData<uint32_t>(cmd->dynamicOffsetCount); | |
| } | |
| // TODO(https://crbug.com/532944732): Spanify DescriptorSetTracker. | |
| state.descriptorSets.OnSetBindGroup(cmd->index, bindGroup, dynamicOffsets.size(), | |
| dynamicOffsets.data()); | |
| break; | |
| } | |
| case Command::SetIndexBuffer: { | |
| SetIndexBufferCmd* cmd = iter->NextCommand<SetIndexBufferCmd>(); | |
| VkBuffer indexBuffer = ToBackend(cmd->buffer)->GetHandle(); | |
| device->fn.CmdBindIndexBuffer(commands, indexBuffer, cmd->offset, | |
| VulkanIndexType(cmd->format)); | |
| break; | |
| } | |
| case Command::SetRenderPipeline: { | |
| SetRenderPipelineCmd* cmd = iter->NextCommand<SetRenderPipelineCmd>(); | |
| state.OnSetPipeline(ToBackend(cmd->pipeline).Get()); | |
| break; | |
| } | |
| case Command::SetVertexBuffer: { | |
| SetVertexBufferCmd* cmd = iter->NextCommand<SetVertexBufferCmd>(); | |
| VkBuffer buffer = ToBackend(cmd->buffer)->GetHandle(); | |
| VkDeviceSize offset = static_cast<VkDeviceSize>(cmd->offset); | |
| device->fn.CmdBindVertexBuffers(commands, static_cast<uint8_t>(cmd->slot), 1, | |
| &*buffer, &offset); | |
| break; | |
| } | |
| case Command::SetImmediates: { | |
| SetImmediatesCmd* cmd = iter->NextCommand<SetImmediatesCmd>(); | |
| DAWN_ASSERT(cmd->size > 0); | |
| Span<const uint8_t> data = iter->NextData<uint8_t>(cmd->size); | |
| // TODO(https://crbug.com/532946455): Spanify ImmediateTracker. | |
| state.immediates.SetImmediates(cmd->offset, data.data(), data.size()); | |
| break; | |
| } | |
| case Command::SetResourceTable: { | |
| SetResourceTableCmd* cmd = iter->NextCommand<SetResourceTableCmd>(); | |
| state.descriptorSets.SetResourceTable(ToBackend(cmd->table.Get())); | |
| break; | |
| } | |
| default: | |
| DAWN_UNREACHABLE(); | |
| break; | |
| } | |
| return {}; | |
| }; | |
| Command type; | |
| while (mCommands.NextCommandId(&type)) { | |
| switch (type) { | |
| case Command::EndRenderPass: { | |
| mCommands.NextCommand<EndRenderPassCmd>(); | |
| // If no work-producing commands were executed during the render pass and the | |
| // VulkanAddWorkToEmptyResolvePass toggle is enabled, add a small amount of work | |
| // in the form of performing an occlusion query before ending the pass. This avoids | |
| // a driver bug that fails to resolve render targets in empty passes. | |
| if (workCommandCount == 0 && | |
| device->IsToggleEnabled(Toggle::VulkanAddWorkToEmptyResolvePass)) { | |
| Ref<QuerySetBase> emptyQuerySet = device->UseEmptyPassQuerySet(); | |
| device->fn.CmdBeginQuery(commands, ToBackend(emptyQuerySet)->GetHandle(), 0, 0); | |
| device->fn.CmdEndQuery(commands, ToBackend(emptyQuerySet)->GetHandle(), 0); | |
| } | |
| RecordEndRenderPass(recordingContext, device); | |
| // Write timestamp at the end of render pass if it's set. | |
| // We've observed that this must be called after the render pass ends or the | |
| // timestamps produced are nonsensical on multiple Android devices. | |
| if (renderPassCmd->timestampWrites.endOfPassWriteIndex != | |
| kQuerySetIndexUndefinedTyped) { | |
| RecordWriteTimestampCmd(recordingContext, device, | |
| renderPassCmd->timestampWrites.querySet.Get(), | |
| renderPassCmd->timestampWrites.endOfPassWriteIndex, | |
| true, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); | |
| } | |
| UpdateQueryAvailability(renderPassCmd->timestampWrites); | |
| return {}; | |
| } | |
| case Command::SetBlendConstant: { | |
| SetBlendConstantCmd* cmd = mCommands.NextCommand<SetBlendConstantCmd>(); | |
| const std::array<float, 4> blendConstants = ConvertToFloatColor(cmd->color); | |
| device->fn.CmdSetBlendConstants(commands, blendConstants.data()); | |
| break; | |
| } | |
| case Command::SetStencilReference: { | |
| SetStencilReferenceCmd* cmd = mCommands.NextCommand<SetStencilReferenceCmd>(); | |
| device->fn.CmdSetStencilReference(commands, VK_STENCIL_FRONT_AND_BACK, | |
| cmd->reference); | |
| break; | |
| } | |
| case Command::SetViewport: { | |
| SetViewportCmd* cmd = mCommands.NextCommand<SetViewportCmd>(); | |
| VkViewport viewport; | |
| viewport.x = cmd->x; | |
| viewport.y = cmd->y + cmd->height; | |
| viewport.width = cmd->width; | |
| viewport.height = -cmd->height; | |
| viewport.minDepth = cmd->minDepth; | |
| viewport.maxDepth = cmd->maxDepth; | |
| // Vulkan disallows width = 0, but VK_KHR_maintenance1 which we require allows | |
| // height = 0 so use that to do an empty viewport. | |
| if (viewport.width == 0) { | |
| viewport.height = 0; | |
| // Set the viewport x range to a range that's always valid. | |
| viewport.x = 0; | |
| viewport.width = 1; | |
| } | |
| device->fn.CmdSetViewport(commands, 0, 1, &viewport); | |
| // Try applying the immediate data that contain min/maxDepth immediately. This can | |
| // be deferred if no pipeline is currently bound. | |
| state.immediates.SetClampFragDepth(viewport.minDepth, viewport.maxDepth); | |
| break; | |
| } | |
| case Command::SetScissorRect: { | |
| SetScissorRectCmd* cmd = mCommands.NextCommand<SetScissorRectCmd>(); | |
| VkRect2D rect; | |
| rect.offset.x = sign_cast(cmd->x); | |
| rect.offset.y = sign_cast(cmd->y); | |
| rect.extent.width = cmd->width; | |
| rect.extent.height = cmd->height; | |
| device->fn.CmdSetScissor(commands, 0, 1, &rect); | |
| break; | |
| } | |
| case Command::ExecuteBundles: { | |
| ExecuteBundlesCmd* cmd = mCommands.NextCommand<ExecuteBundlesCmd>(); | |
| auto bundles = mCommands.NextData<Ref<RenderBundleBase>>(cmd->count); | |
| for (const auto& bundle : bundles) { | |
| CommandIterator* iter = bundle->GetCommands(); | |
| iter->Reset(); | |
| while (iter->NextCommandId(&type)) { | |
| DAWN_TRY(EncodeRenderBundleCommand(iter, type)); | |
| } | |
| } | |
| break; | |
| } | |
| case Command::BeginOcclusionQuery: { | |
| workCommandCount++; | |
| BeginOcclusionQueryCmd* cmd = mCommands.NextCommand<BeginOcclusionQueryCmd>(); | |
| device->fn.CmdBeginQuery(commands, ToBackend(cmd->querySet.Get())->GetHandle(), | |
| uint32_t{cmd->queryIndex}, 0); | |
| break; | |
| } | |
| case Command::EndOcclusionQuery: { | |
| workCommandCount++; | |
| EndOcclusionQueryCmd* cmd = mCommands.NextCommand<EndOcclusionQueryCmd>(); | |
| device->fn.CmdEndQuery(commands, ToBackend(cmd->querySet.Get())->GetHandle(), | |
| uint32_t{cmd->queryIndex}); | |
| UpdateQueryAvailability(cmd); | |
| break; | |
| } | |
| case Command::WriteTimestamp: { | |
| workCommandCount++; | |
| WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>(); | |
| RecordWriteTimestampCmd(recordingContext, device, cmd->querySet.Get(), | |
| cmd->queryIndex, true, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); | |
| UpdateQueryAvailability(cmd); | |
| break; | |
| } | |
| default: { | |
| DAWN_TRY(EncodeRenderBundleCommand(&mCommands, type)); | |
| break; | |
| } | |
| } | |
| } | |
| // EndRenderPass should have been called | |
| DAWN_UNREACHABLE(); | |
| } | |
| } // namespace dawn::native::vulkan |