// Copyright 2017 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "dawn/native/vulkan/CommandBufferVk.h" #include #include #include "dawn/native/BindGroupTracker.h" #include "dawn/native/CommandEncoder.h" #include "dawn/native/CommandValidation.h" #include "dawn/native/Commands.h" #include "dawn/native/DynamicUploader.h" #include "dawn/native/EnumMaskIterator.h" #include "dawn/native/RenderBundle.h" #include "dawn/native/vulkan/AdapterVk.h" #include "dawn/native/vulkan/BindGroupVk.h" #include "dawn/native/vulkan/BufferVk.h" #include "dawn/native/vulkan/CommandRecordingContext.h" #include "dawn/native/vulkan/ComputePipelineVk.h" #include "dawn/native/vulkan/DeviceVk.h" #include "dawn/native/vulkan/FencedDeleter.h" #include "dawn/native/vulkan/PipelineLayoutVk.h" #include "dawn/native/vulkan/QuerySetVk.h" #include "dawn/native/vulkan/RenderPassCache.h" #include "dawn/native/vulkan/RenderPipelineVk.h" #include "dawn/native/vulkan/TextureVk.h" #include "dawn/native/vulkan/UtilsVulkan.h" #include "dawn/native/vulkan/VulkanError.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; } UNREACHABLE(); } bool HasSameTextureCopyExtent(const TextureCopy& srcCopy, const TextureCopy& dstCopy, const Extent3D& copySize) { Extent3D imageExtentSrc = ComputeTextureCopyExtent(srcCopy, copySize); Extent3D 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 Extent3D& 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 = srcCopy.origin.x; region.srcOffset.y = srcCopy.origin.y; switch (srcTexture->GetDimension()) { case wgpu::TextureDimension::e1D: region.srcSubresource.baseArrayLayer = 0; region.srcSubresource.layerCount = 1; region.srcOffset.z = 0; break; case wgpu::TextureDimension::e2D: region.srcSubresource.baseArrayLayer = srcCopy.origin.z; region.srcSubresource.layerCount = copySize.depthOrArrayLayers; region.srcOffset.z = 0; break; case wgpu::TextureDimension::e3D: has3DTextureInCopy = true; region.srcSubresource.baseArrayLayer = 0; region.srcSubresource.layerCount = 1; region.srcOffset.z = srcCopy.origin.z; break; } region.dstOffset.x = dstCopy.origin.x; region.dstOffset.y = dstCopy.origin.y; switch (dstTexture->GetDimension()) { case wgpu::TextureDimension::e1D: region.dstSubresource.baseArrayLayer = 0; region.dstSubresource.layerCount = 1; region.dstOffset.z = 0; break; case wgpu::TextureDimension::e2D: region.dstSubresource.baseArrayLayer = dstCopy.origin.z; region.dstSubresource.layerCount = copySize.depthOrArrayLayers; region.dstOffset.z = 0; break; case wgpu::TextureDimension::e3D: has3DTextureInCopy = true; region.dstSubresource.baseArrayLayer = 0; region.dstSubresource.layerCount = 1; region.dstOffset.z = dstCopy.origin.z; break; } ASSERT(HasSameTextureCopyExtent(srcCopy, dstCopy, copySize)); Extent3D imageExtent = ComputeTextureCopyExtent(dstCopy, copySize); region.extent.width = imageExtent.width; region.extent.height = imageExtent.height; region.extent.depth = has3DTextureInCopy ? copySize.depthOrArrayLayers : 1; return region; } class DescriptorSetTracker : public BindGroupTrackerBase { public: DescriptorSetTracker() = default; void Apply(Device* device, CommandRecordingContext* recordingContext, VkPipelineBindPoint bindPoint) { BeforeApply(); for (BindGroupIndex dirtyIndex : IterateBitSet(mDirtyBindGroupsObjectChangedOrIsDynamic)) { VkDescriptorSet set = ToBackend(mBindGroups[dirtyIndex])->GetHandle(); uint32_t count = static_cast(mDynamicOffsets[dirtyIndex].size()); const uint32_t* dynamicOffset = count > 0 ? mDynamicOffsets[dirtyIndex].data() : nullptr; device->fn.CmdBindDescriptorSets( recordingContext->commandBuffer, bindPoint, ToBackend(mPipelineLayout)->GetHandle(), static_cast(dirtyIndex), 1, &*set, count, dynamicOffset); } AfterApply(); } }; // 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 TransitionAndClearForSyncScope(Device* device, CommandRecordingContext* recordingContext, const SyncScopeResourceUsage& scope) { std::vector bufferBarriers; std::vector imageBarriers; VkPipelineStageFlags srcStages = 0; VkPipelineStageFlags dstStages = 0; for (size_t i = 0; i < scope.buffers.size(); ++i) { Buffer* buffer = ToBackend(scope.buffers[i]); buffer->EnsureDataInitialized(recordingContext); VkBufferMemoryBarrier bufferBarrier; if (buffer->TrackUsageAndGetResourceBarrier(recordingContext, scope.bufferUsages[i], &bufferBarrier, &srcStages, &dstStages)) { bufferBarriers.push_back(bufferBarrier); } } for (size_t i = 0; i < scope.textures.size(); ++i) { Texture* texture = ToBackend(scope.textures[i]); // 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.textureUsages[i].Iterate( [&](const SubresourceRange& range, wgpu::TextureUsage usage) -> MaybeError { if (usage & ~wgpu::TextureUsage::RenderAttachment) { DAWN_TRY(texture->EnsureSubresourceContentInitialized(recordingContext, range)); } return {}; })); texture->TransitionUsageForPass(recordingContext, scope.textureUsages[i], &imageBarriers, &srcStages, &dstStages); } if (bufferBarriers.size() || imageBarriers.size()) { device->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0, nullptr, bufferBarriers.size(), bufferBarriers.data(), imageBarriers.size(), imageBarriers.data()); } return {}; } MaybeError RecordBeginRenderPass(CommandRecordingContext* recordingContext, Device* device, BeginRenderPassCmd* renderPass) { VkCommandBuffer commands = recordingContext->commandBuffer; // Query a VkRenderPass from the cache VkRenderPass renderPassVK = VK_NULL_HANDLE; { RenderPassCacheQuery query; for (ColorAttachmentIndex i : IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) { const auto& attachmentInfo = renderPass->colorAttachments[i]; bool hasResolveTarget = attachmentInfo.resolveTarget != nullptr; query.SetColor(i, attachmentInfo.view->GetFormat().format, attachmentInfo.loadOp, attachmentInfo.storeOp, hasResolveTarget); } if (renderPass->attachmentState->HasDepthStencilAttachment()) { const auto& attachmentInfo = renderPass->depthStencilAttachment; query.SetDepthStencil(attachmentInfo.view->GetTexture()->GetFormat().format, attachmentInfo.depthLoadOp, attachmentInfo.depthStoreOp, attachmentInfo.stencilLoadOp, attachmentInfo.stencilStoreOp, attachmentInfo.depthReadOnly || attachmentInfo.stencilReadOnly); } query.SetSampleCount(renderPass->attachmentState->GetSampleCount()); DAWN_TRY_ASSIGN(renderPassVK, device->GetRenderPassCache()->GetRenderPass(query)); } // Create a framebuffer that will be used once for the render pass and gather the clear // values for the attachments at the same time. std::array clearValues; VkFramebuffer framebuffer = VK_NULL_HANDLE; uint32_t attachmentCount = 0; { // Fill in the attachment info that will be chained in the framebuffer create info. std::array attachments; for (ColorAttachmentIndex i : IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) { auto& attachmentInfo = renderPass->colorAttachments[i]; TextureView* view = ToBackend(attachmentInfo.view.Get()); if (view == nullptr) { continue; } attachments[attachmentCount] = view->GetHandle(); switch (view->GetFormat().GetAspectInfo(Aspect::Color).baseType) { case wgpu::TextureComponentType::Float: { const std::array appliedClearColor = ConvertToFloatColor(attachmentInfo.clearColor); for (uint32_t i = 0; i < 4; ++i) { clearValues[attachmentCount].color.float32[i] = appliedClearColor[i]; } break; } case wgpu::TextureComponentType::Uint: { const std::array appliedClearColor = ConvertToUnsignedIntegerColor(attachmentInfo.clearColor); for (uint32_t i = 0; i < 4; ++i) { clearValues[attachmentCount].color.uint32[i] = appliedClearColor[i]; } break; } case wgpu::TextureComponentType::Sint: { const std::array appliedClearColor = ConvertToSignedIntegerColor(attachmentInfo.clearColor); for (uint32_t i = 0; i < 4; ++i) { clearValues[attachmentCount].color.int32[i] = appliedClearColor[i]; } break; } case wgpu::TextureComponentType::DepthComparison: UNREACHABLE(); } attachmentCount++; } if (renderPass->attachmentState->HasDepthStencilAttachment()) { auto& attachmentInfo = renderPass->depthStencilAttachment; TextureView* view = ToBackend(attachmentInfo.view.Get()); attachments[attachmentCount] = view->GetHandle(); clearValues[attachmentCount].depthStencil.depth = attachmentInfo.clearDepth; clearValues[attachmentCount].depthStencil.stencil = attachmentInfo.clearStencil; attachmentCount++; } for (ColorAttachmentIndex i : IterateBitSet(renderPass->attachmentState->GetColorAttachmentsMask())) { if (renderPass->colorAttachments[i].resolveTarget != nullptr) { TextureView* view = ToBackend(renderPass->colorAttachments[i].resolveTarget.Get()); attachments[attachmentCount] = view->GetHandle(); attachmentCount++; } } // Chain attachments and create the framebuffer VkFramebufferCreateInfo createInfo; createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; createInfo.pNext = nullptr; createInfo.flags = 0; createInfo.renderPass = renderPassVK; createInfo.attachmentCount = attachmentCount; createInfo.pAttachments = AsVkArray(attachments.data()); createInfo.width = renderPass->width; createInfo.height = renderPass->height; createInfo.layers = 1; DAWN_TRY(CheckVkSuccess(device->fn.CreateFramebuffer(device->GetVkDevice(), &createInfo, nullptr, &*framebuffer), "CreateFramebuffer")); // We don't reuse VkFramebuffers so mark the framebuffer for deletion as soon as the // commands currently being recorded are finished. device->GetFencedDeleter()->DeleteWhenUnused(framebuffer); } VkRenderPassBeginInfo beginInfo; beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; beginInfo.pNext = nullptr; beginInfo.renderPass = renderPassVK; beginInfo.framebuffer = framebuffer; beginInfo.renderArea.offset.x = 0; beginInfo.renderArea.offset.y = 0; beginInfo.renderArea.extent.width = renderPass->width; beginInfo.renderArea.extent.height = renderPass->height; beginInfo.clearValueCount = attachmentCount; beginInfo.pClearValues = clearValues.data(); device->fn.CmdBeginRenderPass(commands, &beginInfo, VK_SUBPASS_CONTENTS_INLINE); 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 std::vector& availability) { ASSERT(availability.size() == querySet->GetQueryAvailability().size()); auto currentIt = availability.begin(); auto lastIt = availability.end(); // Traverse the used queries which availability are true. while (currentIt != lastIt) { auto firstTrueIt = std::find(currentIt, lastIt, true); // No used queries need to be reset if (firstTrueIt == lastIt) { break; } auto nextFalseIt = std::find(firstTrueIt, lastIt, false); uint32_t queryIndex = std::distance(availability.begin(), firstTrueIt); uint32_t queryCount = std::distance(firstTrueIt, nextFalseIt); // Reset the queries between firstTrueIt and nextFalseIt (which is at most // lastIt) device->fn.CmdResetQueryPool(commands, ToBackend(querySet)->GetHandle(), queryIndex, queryCount); // Set current iterator to next false currentIt = nextFalseIt; } } void RecordWriteTimestampCmd(CommandRecordingContext* recordingContext, Device* device, QuerySetBase* querySet, uint32_t queryIndex, bool isRenderPass) { 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(), queryIndex, 1); } device->fn.CmdWriteTimestamp(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, ToBackend(querySet)->GetHandle(), queryIndex); } void RecordResolveQuerySetCmd(VkCommandBuffer commands, Device* device, QuerySet* querySet, uint32_t firstQuery, uint32_t queryCount, Buffer* destination, uint64_t destinationOffset) { const std::vector& availability = querySet->GetQueryAvailability(); auto currentIt = availability.begin() + firstQuery; auto lastIt = availability.begin() + firstQuery + queryCount; // Traverse available queries in the range of [firstQuery, firstQuery + queryCount - 1] while (currentIt != lastIt) { auto firstTrueIt = std::find(currentIt, lastIt, true); // No available query found for resolving if (firstTrueIt == lastIt) { break; } auto nextFalseIt = std::find(firstTrueIt, lastIt, false); // The query index of firstTrueIt where the resolving starts uint32_t resolveQueryIndex = std::distance(availability.begin(), firstTrueIt); // The queries count between firstTrueIt and nextFalseIt need to be resolved uint32_t resolveQueryCount = std::distance(firstTrueIt, nextFalseIt); // Calculate destinationOffset based on the current resolveQueryIndex and firstQuery uint32_t resolveDestinationOffset = destinationOffset + (resolveQueryIndex - firstQuery) * sizeof(uint64_t); // Resolve the queries between firstTrueIt and nextFalseIt (which is at most lastIt) device->fn.CmdCopyQueryPoolResults(commands, querySet->GetHandle(), resolveQueryIndex, resolveQueryCount, destination->GetHandle(), resolveDestinationOffset, sizeof(uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); // Set current iterator to next false currentIt = nextFalseIt; } } } // anonymous namespace // static Ref 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 Extent3D& copySize) { ASSERT(srcCopy.texture->GetFormat().CopyCompatibleWith(dstCopy.texture->GetFormat())); ASSERT(srcCopy.aspect == dstCopy.aspect); dawn::native::Format format = srcCopy.texture->GetFormat(); const TexelBlockInfo& blockInfo = format.GetAspectInfo(srcCopy.aspect).block; ASSERT(copySize.width % blockInfo.width == 0); uint32_t widthInBlocks = copySize.width / blockInfo.width; ASSERT(copySize.height % blockInfo.height == 0); uint32_t heightInBlocks = copySize.height / blockInfo.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 = widthInBlocks * heightInBlocks * copySize.depthOrArrayLayers * blockInfo.byteSize; BufferDescriptor tempBufferDescriptor; tempBufferDescriptor.size = tempBufferSize; tempBufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; Device* device = ToBackend(GetDevice()); Ref tempBufferBase; DAWN_TRY_ASSIGN(tempBufferBase, device->CreateBuffer(&tempBufferDescriptor)); Buffer* tempBuffer = ToBackend(tempBufferBase.Get()); BufferCopy tempBufferCopy; tempBufferCopy.buffer = tempBuffer; tempBufferCopy.rowsPerImage = heightInBlocks; tempBufferCopy.offset = 0; tempBufferCopy.bytesPerRow = copySize.width / blockInfo.width * blockInfo.byteSize; 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(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(TransitionAndClearForSyncScope(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]); } return {}; }; size_t nextComputePassNumber = 0; size_t nextRenderPassNumber = 0; Command type; while (mCommands.NextCommandId(&type)) { switch (type) { case Command::CopyBufferToBuffer: { CopyBufferToBufferCmd* copy = mCommands.NextCommand(); 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(); if (copy->copySize.width == 0 || copy->copySize.height == 0 || copy->copySize.depthOrArrayLayers == 0) { // Skip no-op copies. continue; } auto& src = copy->source; auto& dst = copy->destination; ToBackend(src.buffer)->EnsureDataInitialized(recordingContext); VkBufferImageCopy region = ComputeBufferImageCopyRegion(src, dst, copy->copySize); VkImageSubresourceLayers subresource = region.imageSubresource; SubresourceRange range = GetSubresourcesAffectedByCopy(copy->destination, copy->copySize); if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize, subresource.mipLevel)) { // 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, 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, 1, ®ion); break; } case Command::CopyTextureToBuffer: { CopyTextureToBufferCmd* copy = mCommands.NextCommand(); if (copy->copySize.width == 0 || copy->copySize.height == 0 || copy->copySize.depthOrArrayLayers == 0) { // Skip no-op copies. continue; } auto& src = copy->source; auto& dst = copy->destination; ToBackend(dst.buffer)->EnsureDataInitializedAsDestination(recordingContext, copy); VkBufferImageCopy region = ComputeBufferImageCopyRegion(dst, src, 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, 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, 1, ®ion); break; } case Command::CopyTextureToTexture: { CopyTextureToTextureCmd* copy = mCommands.NextCommand(); if (copy->copySize.width == 0 || copy->copySize.height == 0 || copy->copySize.depthOrArrayLayers == 0) { // 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)) { // 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 ASSERT here as a reminder for this possible misuse. ASSERT(!IsRangeOverlapped(src.origin.z, dst.origin.z, copy->copySize.depthOrArrayLayers)); } ToBackend(src.texture) ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc, srcRange); ToBackend(dst.texture) ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst, 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(); 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(); DAWN_TRY(PrepareResourcesForRenderPass( device, recordingContext, GetResourceUsages().renderPasses[nextRenderPassNumber])); LazyClearRenderPassAttachments(cmd); DAWN_TRY(RecordRenderPass(recordingContext, cmd)); nextRenderPassNumber++; break; } case Command::BeginComputePass: { BeginComputePassCmd* cmd = mCommands.NextCommand(); DAWN_TRY( RecordComputePass(recordingContext, cmd, GetResourceUsages().computePasses[nextComputePassNumber])); nextComputePassNumber++; break; } case Command::ResolveQuerySet: { ResolveQuerySetCmd* cmd = mCommands.NextCommand(); QuerySet* querySet = ToBackend(cmd->querySet.Get()); Buffer* destination = ToBackend(cmd->destination.Get()); destination->EnsureDataInitializedAsDestination( recordingContext, cmd->destinationOffset, cmd->queryCount * sizeof(uint64_t)); // 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. auto startIt = querySet->GetQueryAvailability().begin() + cmd->firstQuery; auto endIt = querySet->GetQueryAvailability().begin() + cmd->firstQuery + cmd->queryCount; bool hasUnavailableQueries = std::find(startIt, endIt, false) != endIt; if (hasUnavailableQueries) { destination->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); device->fn.CmdFillBuffer(commands, destination->GetHandle(), cmd->destinationOffset, cmd->queryCount * sizeof(uint64_t), 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(); RecordWriteTimestampCmd(recordingContext, device, cmd->querySet.Get(), cmd->queryIndex, false); break; } case Command::InsertDebugMarker: { if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { InsertDebugMarkerCmd* cmd = mCommands.NextCommand(); const char* label = mCommands.NextData(cmd->length + 1); VkDebugUtilsLabelEXT utilsLabel; utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; utilsLabel.pNext = nullptr; utilsLabel.pLabelName = label; // 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(); device->fn.CmdEndDebugUtilsLabelEXT(commands); } else { SkipCommand(&mCommands, Command::PopDebugGroup); } break; } case Command::PushDebugGroup: { if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { PushDebugGroupCmd* cmd = mCommands.NextCommand(); const char* label = mCommands.NextData(cmd->length + 1); VkDebugUtilsLabelEXT utilsLabel; utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; utilsLabel.pNext = nullptr; utilsLabel.pLabelName = label; // 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(); const uint64_t offset = write->offset; const uint64_t size = write->size; if (size == 0) { continue; } Buffer* dstBuffer = ToBackend(write->buffer.Get()); uint8_t* data = mCommands.NextData(size); Device* device = ToBackend(GetDevice()); UploadHandle uploadHandle; DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate( size, device->GetPendingCommandSerial(), kCopyBufferToBufferOffsetAlignment)); ASSERT(uploadHandle.mappedBuffer != nullptr); memcpy(uploadHandle.mappedBuffer, data, size); dstBuffer->EnsureDataInitializedAsDestination(recordingContext, offset, size); dstBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); VkBufferCopy copy; copy.srcOffset = uploadHandle.startOffset; copy.dstOffset = offset; copy.size = size; device->fn.CmdCopyBuffer(commands, ToBackend(uploadHandle.stagingBuffer)->GetHandle(), dstBuffer->GetHandle(), 1, ©); break; } default: break; } } return {}; } MaybeError CommandBuffer::RecordComputePass(CommandRecordingContext* recordingContext, BeginComputePassCmd* computePassCmd, const ComputePassResourceUsage& resourceUsages) { Device* device = ToBackend(GetDevice()); // If required, split the command buffer any time we detect a dpeth/stencil attachment is // used in a compute pass after being used as a render pass attachment in the same command // buffer. if (device->IsToggleEnabled( Toggle::VulkanSplitCommandBufferOnDepthStencilComputeSampleAfterRenderPass) && !mRenderPassDepthStencilAttachments.empty()) { for (auto texture : resourceUsages.referencedTextures) { if (texture->GetFormat().HasDepthOrStencil() && mRenderPassDepthStencilAttachments.find(texture) != mRenderPassDepthStencilAttachments.end()) { // Identified a potential crash case, split the command buffer. DAWN_TRY(device->SplitRecordingContext(recordingContext)); mRenderPassDepthStencilAttachments.clear(); break; } } } // Write timestamp at the beginning of compute pass if it's set if (computePassCmd->beginTimestamp.querySet.Get() != nullptr) { RecordWriteTimestampCmd(recordingContext, device, computePassCmd->beginTimestamp.querySet.Get(), computePassCmd->beginTimestamp.queryIndex, false); } VkCommandBuffer commands = recordingContext->commandBuffer; uint64_t currentDispatch = 0; DescriptorSetTracker descriptorSets = {}; Command type; while (mCommands.NextCommandId(&type)) { switch (type) { case Command::EndComputePass: { mCommands.NextCommand(); // Write timestamp at the end of compute pass if it's set. if (computePassCmd->endTimestamp.querySet.Get() != nullptr) { RecordWriteTimestampCmd(recordingContext, device, computePassCmd->endTimestamp.querySet.Get(), computePassCmd->endTimestamp.queryIndex, false); } return {}; } case Command::Dispatch: { DispatchCmd* dispatch = mCommands.NextCommand(); DAWN_TRY(TransitionAndClearForSyncScope( device, recordingContext, resourceUsages.dispatchUsages[currentDispatch])); descriptorSets.Apply(device, recordingContext, VK_PIPELINE_BIND_POINT_COMPUTE); device->fn.CmdDispatch(commands, dispatch->x, dispatch->y, dispatch->z); currentDispatch++; break; } case Command::DispatchIndirect: { DispatchIndirectCmd* dispatch = mCommands.NextCommand(); VkBuffer indirectBuffer = ToBackend(dispatch->indirectBuffer)->GetHandle(); DAWN_TRY(TransitionAndClearForSyncScope( device, recordingContext, resourceUsages.dispatchUsages[currentDispatch])); descriptorSets.Apply(device, recordingContext, VK_PIPELINE_BIND_POINT_COMPUTE); device->fn.CmdDispatchIndirect(commands, indirectBuffer, static_cast(dispatch->indirectOffset)); currentDispatch++; break; } case Command::SetBindGroup: { SetBindGroupCmd* cmd = mCommands.NextCommand(); BindGroup* bindGroup = ToBackend(cmd->group.Get()); uint32_t* dynamicOffsets = nullptr; if (cmd->dynamicOffsetCount > 0) { dynamicOffsets = mCommands.NextData(cmd->dynamicOffsetCount); } descriptorSets.OnSetBindGroup(cmd->index, bindGroup, cmd->dynamicOffsetCount, dynamicOffsets); break; } case Command::SetComputePipeline: { SetComputePipelineCmd* cmd = mCommands.NextCommand(); ComputePipeline* pipeline = ToBackend(cmd->pipeline).Get(); device->fn.CmdBindPipeline(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->GetHandle()); descriptorSets.OnSetPipeline(pipeline); break; } case Command::InsertDebugMarker: { if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { InsertDebugMarkerCmd* cmd = mCommands.NextCommand(); const char* label = mCommands.NextData(cmd->length + 1); VkDebugUtilsLabelEXT utilsLabel; utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; utilsLabel.pNext = nullptr; utilsLabel.pLabelName = label; // 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(); device->fn.CmdEndDebugUtilsLabelEXT(commands); } else { SkipCommand(&mCommands, Command::PopDebugGroup); } break; } case Command::PushDebugGroup: { if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { PushDebugGroupCmd* cmd = mCommands.NextCommand(); const char* label = mCommands.NextData(cmd->length + 1); VkDebugUtilsLabelEXT utilsLabel; utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; utilsLabel.pNext = nullptr; utilsLabel.pLabelName = label; // 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(); RecordWriteTimestampCmd(recordingContext, device, cmd->querySet.Get(), cmd->queryIndex, false); break; } default: UNREACHABLE(); } } // EndComputePass should have been called UNREACHABLE(); } MaybeError CommandBuffer::RecordRenderPass(CommandRecordingContext* recordingContext, BeginRenderPassCmd* renderPassCmd) { Device* device = ToBackend(GetDevice()); VkCommandBuffer commands = recordingContext->commandBuffer; DAWN_TRY(RecordBeginRenderPass(recordingContext, device, renderPassCmd)); // If required, track depth/stencil textures used as render pass attachments. if (device->IsToggleEnabled( Toggle::VulkanSplitCommandBufferOnDepthStencilComputeSampleAfterRenderPass) && renderPassCmd->attachmentState->HasDepthStencilAttachment()) { mRenderPassDepthStencilAttachments.insert( renderPassCmd->depthStencilAttachment.view->GetTexture()); } // Write timestamp at the beginning of render pass if it's set. if (renderPassCmd->beginTimestamp.querySet.Get() != nullptr) { RecordWriteTimestampCmd(recordingContext, device, renderPassCmd->beginTimestamp.querySet.Get(), renderPassCmd->beginTimestamp.queryIndex, true); } // 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(renderPassCmd->height); viewport.width = static_cast(renderPassCmd->width); viewport.height = -static_cast(renderPassCmd->height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; device->fn.CmdSetViewport(commands, 0, 1, &viewport); VkRect2D scissorRect; scissorRect.offset.x = 0; scissorRect.offset.y = 0; scissorRect.extent.width = renderPassCmd->width; scissorRect.extent.height = renderPassCmd->height; device->fn.CmdSetScissor(commands, 0, 1, &scissorRect); } DescriptorSetTracker descriptorSets = {}; RenderPipeline* lastPipeline = nullptr; // Tracking for the push constants needed by the ClampFragDepth transform. // TODO(dawn:1125): Avoid the need for this when the depthClamp feature is available, but doing // so would require fixing issue dawn:1576 first to have more dynamic push constant usage. (and // also additional tests that the dirtying logic here is correct so with a Toggle we can test it // on our infra). ClampFragDepthArgs clampFragDepthArgs = {0.0f, 1.0f}; bool clampFragDepthArgsDirty = true; auto ApplyClampFragDepthArgs = [&]() { if (!clampFragDepthArgsDirty || lastPipeline == nullptr) { return; } device->fn.CmdPushConstants(commands, ToBackend(lastPipeline->GetLayout())->GetHandle(), VK_SHADER_STAGE_FRAGMENT_BIT, kClampFragDepthArgsOffset, kClampFragDepthArgsSize, &clampFragDepthArgs); clampFragDepthArgsDirty = false; }; auto EncodeRenderBundleCommand = [&](CommandIterator* iter, Command type) { switch (type) { case Command::Draw: { DrawCmd* draw = iter->NextCommand(); descriptorSets.Apply(device, recordingContext, VK_PIPELINE_BIND_POINT_GRAPHICS); device->fn.CmdDraw(commands, draw->vertexCount, draw->instanceCount, draw->firstVertex, draw->firstInstance); break; } case Command::DrawIndexed: { DrawIndexedCmd* draw = iter->NextCommand(); descriptorSets.Apply(device, recordingContext, VK_PIPELINE_BIND_POINT_GRAPHICS); device->fn.CmdDrawIndexed(commands, draw->indexCount, draw->instanceCount, draw->firstIndex, draw->baseVertex, draw->firstInstance); break; } case Command::DrawIndirect: { DrawIndirectCmd* draw = iter->NextCommand(); Buffer* buffer = ToBackend(draw->indirectBuffer.Get()); descriptorSets.Apply(device, recordingContext, VK_PIPELINE_BIND_POINT_GRAPHICS); device->fn.CmdDrawIndirect(commands, buffer->GetHandle(), static_cast(draw->indirectOffset), 1, 0); break; } case Command::DrawIndexedIndirect: { DrawIndexedIndirectCmd* draw = iter->NextCommand(); Buffer* buffer = ToBackend(draw->indirectBuffer.Get()); ASSERT(buffer != nullptr); descriptorSets.Apply(device, recordingContext, VK_PIPELINE_BIND_POINT_GRAPHICS); device->fn.CmdDrawIndexedIndirect(commands, buffer->GetHandle(), static_cast(draw->indirectOffset), 1, 0); break; } case Command::InsertDebugMarker: { if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { InsertDebugMarkerCmd* cmd = iter->NextCommand(); const char* label = iter->NextData(cmd->length + 1); VkDebugUtilsLabelEXT utilsLabel; utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; utilsLabel.pNext = nullptr; utilsLabel.pLabelName = label; // 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(); device->fn.CmdEndDebugUtilsLabelEXT(commands); } else { SkipCommand(iter, Command::PopDebugGroup); } break; } case Command::PushDebugGroup: { if (device->GetGlobalInfo().HasExt(InstanceExt::DebugUtils)) { PushDebugGroupCmd* cmd = iter->NextCommand(); const char* label = iter->NextData(cmd->length + 1); VkDebugUtilsLabelEXT utilsLabel; utilsLabel.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; utilsLabel.pNext = nullptr; utilsLabel.pLabelName = label; // 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(); BindGroup* bindGroup = ToBackend(cmd->group.Get()); uint32_t* dynamicOffsets = nullptr; if (cmd->dynamicOffsetCount > 0) { dynamicOffsets = iter->NextData(cmd->dynamicOffsetCount); } descriptorSets.OnSetBindGroup(cmd->index, bindGroup, cmd->dynamicOffsetCount, dynamicOffsets); break; } case Command::SetIndexBuffer: { SetIndexBufferCmd* cmd = iter->NextCommand(); VkBuffer indexBuffer = ToBackend(cmd->buffer)->GetHandle(); device->fn.CmdBindIndexBuffer(commands, indexBuffer, cmd->offset, VulkanIndexType(cmd->format)); break; } case Command::SetRenderPipeline: { SetRenderPipelineCmd* cmd = iter->NextCommand(); RenderPipeline* pipeline = ToBackend(cmd->pipeline).Get(); device->fn.CmdBindPipeline(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->GetHandle()); lastPipeline = pipeline; descriptorSets.OnSetPipeline(pipeline); // Apply the deferred min/maxDepth push constants update if needed. ApplyClampFragDepthArgs(); break; } case Command::SetVertexBuffer: { SetVertexBufferCmd* cmd = iter->NextCommand(); VkBuffer buffer = ToBackend(cmd->buffer)->GetHandle(); VkDeviceSize offset = static_cast(cmd->offset); device->fn.CmdBindVertexBuffers(commands, static_cast(cmd->slot), 1, &*buffer, &offset); break; } default: UNREACHABLE(); break; } }; Command type; while (mCommands.NextCommandId(&type)) { switch (type) { case Command::EndRenderPass: { mCommands.NextCommand(); // Write timestamp at the end of render pass if it's set. if (renderPassCmd->endTimestamp.querySet.Get() != nullptr) { RecordWriteTimestampCmd(recordingContext, device, renderPassCmd->endTimestamp.querySet.Get(), renderPassCmd->endTimestamp.queryIndex, true); } device->fn.CmdEndRenderPass(commands); return {}; } case Command::SetBlendConstant: { SetBlendConstantCmd* cmd = mCommands.NextCommand(); const std::array blendConstants = ConvertToFloatColor(cmd->color); device->fn.CmdSetBlendConstants(commands, blendConstants.data()); break; } case Command::SetStencilReference: { SetStencilReferenceCmd* cmd = mCommands.NextCommand(); device->fn.CmdSetStencilReference(commands, VK_STENCIL_FRONT_AND_BACK, cmd->reference); break; } case Command::SetViewport: { SetViewportCmd* cmd = mCommands.NextCommand(); 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 push constants that contain min/maxDepth immediately. This can // be deferred if no pipeline is currently bound. clampFragDepthArgs = {viewport.minDepth, viewport.maxDepth}; clampFragDepthArgsDirty = true; ApplyClampFragDepthArgs(); break; } case Command::SetScissorRect: { SetScissorRectCmd* cmd = mCommands.NextCommand(); VkRect2D rect; rect.offset.x = cmd->x; rect.offset.y = 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(); auto bundles = mCommands.NextData>(cmd->count); for (uint32_t i = 0; i < cmd->count; ++i) { CommandIterator* iter = bundles[i]->GetCommands(); iter->Reset(); while (iter->NextCommandId(&type)) { EncodeRenderBundleCommand(iter, type); } } break; } case Command::BeginOcclusionQuery: { BeginOcclusionQueryCmd* cmd = mCommands.NextCommand(); device->fn.CmdBeginQuery(commands, ToBackend(cmd->querySet.Get())->GetHandle(), cmd->queryIndex, 0); break; } case Command::EndOcclusionQuery: { EndOcclusionQueryCmd* cmd = mCommands.NextCommand(); device->fn.CmdEndQuery(commands, ToBackend(cmd->querySet.Get())->GetHandle(), cmd->queryIndex); break; } case Command::WriteTimestamp: { WriteTimestampCmd* cmd = mCommands.NextCommand(); RecordWriteTimestampCmd(recordingContext, device, cmd->querySet.Get(), cmd->queryIndex, true); break; } default: { EncodeRenderBundleCommand(&mCommands, type); break; } } } // EndRenderPass should have been called UNREACHABLE(); } } // namespace dawn::native::vulkan