// Copyright 2017 The NXT 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 "backend/vulkan/CommandBufferVk.h" #include "backend/Commands.h" #include "backend/vulkan/BindGroupVk.h" #include "backend/vulkan/BufferVk.h" #include "backend/vulkan/ComputePipelineVk.h" #include "backend/vulkan/DeviceVk.h" #include "backend/vulkan/PipelineLayoutVk.h" #include "backend/vulkan/RenderPassDescriptorVk.h" #include "backend/vulkan/RenderPipelineVk.h" #include "backend/vulkan/TextureVk.h" namespace backend { namespace vulkan { namespace { VkIndexType VulkanIndexType(nxt::IndexFormat format) { switch (format) { case nxt::IndexFormat::Uint16: return VK_INDEX_TYPE_UINT16; case nxt::IndexFormat::Uint32: return VK_INDEX_TYPE_UINT32; default: UNREACHABLE(); } } VkBufferImageCopy ComputeBufferImageCopyRegion(uint32_t rowPitch, const BufferCopyLocation& bufferLocation, const TextureCopyLocation& textureLocation) { const Texture* texture = ToBackend(textureLocation.texture).Get(); VkBufferImageCopy region; region.bufferOffset = bufferLocation.offset; // In Vulkan the row length is in texels while it is in bytes for NXT region.bufferRowLength = rowPitch / TextureFormatPixelSize(texture->GetFormat()); region.bufferImageHeight = rowPitch * textureLocation.height; region.imageSubresource.aspectMask = texture->GetVkAspectMask(); region.imageSubresource.mipLevel = textureLocation.level; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageOffset.x = textureLocation.x; region.imageOffset.y = textureLocation.y; region.imageOffset.z = textureLocation.z; region.imageExtent.width = textureLocation.width; region.imageExtent.height = textureLocation.height; region.imageExtent.depth = textureLocation.depth; return region; } class DescriptorSetTracker { public: void OnSetBindGroup(uint32_t index, VkDescriptorSet set) { mDirtySets.set(index); mSets[index] = set; } void OnPipelineLayoutChange(PipelineLayout* layout) { if (layout == mCurrentLayout) { return; } if (mCurrentLayout == nullptr) { // We're at the beginning of a pass so all bind groups will be set before any // draw / dispatch. Still clear the dirty sets to avoid leftover dirty sets // from previous passes. mDirtySets.reset(); } else { // Bindgroups that are not inherited will be set again before any draw or // dispatch. Resetting the bits also makes sure we don't have leftover dirty // bindgroups that don't exist in the pipeline layout. mDirtySets &= ~layout->InheritedGroupsMask(mCurrentLayout); } mCurrentLayout = layout; } void Flush(Device* device, VkCommandBuffer commands, VkPipelineBindPoint bindPoint) { for (uint32_t dirtyIndex : IterateBitSet(mDirtySets)) { device->fn.CmdBindDescriptorSets(commands, bindPoint, mCurrentLayout->GetHandle(), dirtyIndex, 1, &mSets[dirtyIndex], 0, nullptr); } mDirtySets.reset(); } private: PipelineLayout* mCurrentLayout = nullptr; std::array mSets; std::bitset mDirtySets; }; } // anonymous namespace CommandBuffer::CommandBuffer(CommandBufferBuilder* builder) : CommandBufferBase(builder), mCommands(builder->AcquireCommands()), mPassResourceUsages(builder->AcquirePassResourceUsage()) { } CommandBuffer::~CommandBuffer() { FreeCommands(&mCommands); } void CommandBuffer::RecordCommands(VkCommandBuffer commands) { Device* device = ToBackend(GetDevice()); // Records the necessary barriers for the resource usage pre-computed by the frontend auto TransitionForPass = [](VkCommandBuffer commands, const PassResourceUsage& usages) { for (size_t i = 0; i < usages.buffers.size(); ++i) { Buffer* buffer = ToBackend(usages.buffers[i]); buffer->TransitionUsageNow(commands, usages.bufferUsages[i]); } for (size_t i = 0; i < usages.textures.size(); ++i) { Texture* texture = ToBackend(usages.textures[i]); texture->TransitionUsageNow(commands, usages.textureUsages[i]); } }; size_t nextPassNumber = 0; Command type; while (mCommands.NextCommandId(&type)) { switch (type) { case Command::CopyBufferToBuffer: { CopyBufferToBufferCmd* copy = mCommands.NextCommand(); auto& src = copy->source; auto& dst = copy->destination; ToBackend(src.buffer) ->TransitionUsageNow(commands, nxt::BufferUsageBit::TransferSrc); ToBackend(dst.buffer) ->TransitionUsageNow(commands, nxt::BufferUsageBit::TransferDst); VkBufferCopy region; region.srcOffset = src.offset; region.dstOffset = dst.offset; region.size = copy->size; VkBuffer srcHandle = ToBackend(src.buffer)->GetHandle(); VkBuffer dstHandle = ToBackend(dst.buffer)->GetHandle(); device->fn.CmdCopyBuffer(commands, srcHandle, dstHandle, 1, ®ion); } break; case Command::CopyBufferToTexture: { CopyBufferToTextureCmd* copy = mCommands.NextCommand(); auto& src = copy->source; auto& dst = copy->destination; ToBackend(src.buffer) ->TransitionUsageNow(commands, nxt::BufferUsageBit::TransferSrc); ToBackend(dst.texture) ->TransitionUsageNow(commands, nxt::TextureUsageBit::TransferDst); VkBuffer srcBuffer = ToBackend(src.buffer)->GetHandle(); VkImage dstImage = ToBackend(dst.texture)->GetHandle(); VkBufferImageCopy region = ComputeBufferImageCopyRegion(copy->rowPitch, src, dst); // The image is written to so the NXT guarantees make sure it is in the // TRANSFER_DST_OPTIMAL layout device->fn.CmdCopyBufferToImage(commands, srcBuffer, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); } break; case Command::CopyTextureToBuffer: { CopyTextureToBufferCmd* copy = mCommands.NextCommand(); auto& src = copy->source; auto& dst = copy->destination; ToBackend(src.texture) ->TransitionUsageNow(commands, nxt::TextureUsageBit::TransferSrc); ToBackend(dst.buffer) ->TransitionUsageNow(commands, nxt::BufferUsageBit::TransferDst); VkImage srcImage = ToBackend(src.texture)->GetHandle(); VkBuffer dstBuffer = ToBackend(dst.buffer)->GetHandle(); VkBufferImageCopy region = ComputeBufferImageCopyRegion(copy->rowPitch, dst, src); // The NXT TransferSrc usage is always mapped to GENERAL device->fn.CmdCopyImageToBuffer(commands, srcImage, VK_IMAGE_LAYOUT_GENERAL, dstBuffer, 1, ®ion); } break; case Command::BeginRenderPass: { BeginRenderPassCmd* cmd = mCommands.NextCommand(); TransitionForPass(commands, mPassResourceUsages[nextPassNumber]); RecordRenderPass(commands, ToBackend(cmd->info.Get())); nextPassNumber++; } break; case Command::BeginComputePass: { mCommands.NextCommand(); TransitionForPass(commands, mPassResourceUsages[nextPassNumber]); RecordComputePass(commands); nextPassNumber++; } break; default: { UNREACHABLE(); } break; } } } void CommandBuffer::RecordComputePass(VkCommandBuffer commands) { Device* device = ToBackend(GetDevice()); DescriptorSetTracker descriptorSets; Command type; while (mCommands.NextCommandId(&type)) { switch (type) { case Command::EndComputePass: { mCommands.NextCommand(); return; } break; case Command::Dispatch: { DispatchCmd* dispatch = mCommands.NextCommand(); descriptorSets.Flush(device, commands, VK_PIPELINE_BIND_POINT_COMPUTE); device->fn.CmdDispatch(commands, dispatch->x, dispatch->y, dispatch->z); } break; case Command::SetBindGroup: { SetBindGroupCmd* cmd = mCommands.NextCommand(); VkDescriptorSet set = ToBackend(cmd->group.Get())->GetHandle(); descriptorSets.OnSetBindGroup(cmd->index, set); } 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.OnPipelineLayoutChange(ToBackend(pipeline->GetLayout())); } break; default: { UNREACHABLE(); } break; } } // EndComputePass should have been called UNREACHABLE(); } void CommandBuffer::RecordRenderPass(VkCommandBuffer commands, RenderPassDescriptor* renderPass) { Device* device = ToBackend(GetDevice()); renderPass->RecordBeginRenderPass(commands); // 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 = 0.0f; viewport.width = static_cast(renderPass->GetWidth()); viewport.height = static_cast(renderPass->GetHeight()); 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 = renderPass->GetWidth(); scissorRect.extent.height = renderPass->GetHeight(); device->fn.CmdSetScissor(commands, 0, 1, &scissorRect); } DescriptorSetTracker descriptorSets; RenderPipeline* lastPipeline = nullptr; Command type; while (mCommands.NextCommandId(&type)) { switch (type) { case Command::EndRenderPass: { mCommands.NextCommand(); device->fn.CmdEndRenderPass(commands); return; } break; case Command::DrawArrays: { DrawArraysCmd* draw = mCommands.NextCommand(); descriptorSets.Flush(device, commands, VK_PIPELINE_BIND_POINT_GRAPHICS); device->fn.CmdDraw(commands, draw->vertexCount, draw->instanceCount, draw->firstVertex, draw->firstInstance); } break; case Command::DrawElements: { DrawElementsCmd* draw = mCommands.NextCommand(); descriptorSets.Flush(device, commands, VK_PIPELINE_BIND_POINT_GRAPHICS); uint32_t vertexOffset = 0; device->fn.CmdDrawIndexed(commands, draw->indexCount, draw->instanceCount, draw->firstIndex, vertexOffset, draw->firstInstance); } break; case Command::SetBindGroup: { SetBindGroupCmd* cmd = mCommands.NextCommand(); VkDescriptorSet set = ToBackend(cmd->group.Get())->GetHandle(); descriptorSets.OnSetBindGroup(cmd->index, set); } break; case Command::SetBlendColor: { SetBlendColorCmd* cmd = mCommands.NextCommand(); float blendConstants[4] = { cmd->r, cmd->g, cmd->b, cmd->a, }; device->fn.CmdSetBlendConstants(commands, blendConstants); } break; case Command::SetIndexBuffer: { SetIndexBufferCmd* cmd = mCommands.NextCommand(); VkBuffer indexBuffer = ToBackend(cmd->buffer)->GetHandle(); // TODO(cwallez@chromium.org): get the index type from the last render pipeline // and rebind if needed on pipeline change ASSERT(lastPipeline != nullptr); VkIndexType indexType = VulkanIndexType(lastPipeline->GetIndexFormat()); device->fn.CmdBindIndexBuffer( commands, indexBuffer, static_cast(cmd->offset), indexType); } break; case Command::SetRenderPipeline: { SetRenderPipelineCmd* cmd = mCommands.NextCommand(); RenderPipeline* pipeline = ToBackend(cmd->pipeline).Get(); device->fn.CmdBindPipeline(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->GetHandle()); lastPipeline = pipeline; descriptorSets.OnPipelineLayoutChange(ToBackend(pipeline->GetLayout())); } break; case Command::SetStencilReference: { SetStencilReferenceCmd* cmd = mCommands.NextCommand(); device->fn.CmdSetStencilReference(commands, VK_STENCIL_FRONT_AND_BACK, cmd->reference); } 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::SetVertexBuffers: { SetVertexBuffersCmd* cmd = mCommands.NextCommand(); auto buffers = mCommands.NextData>(cmd->count); auto offsets = mCommands.NextData(cmd->count); std::array vkBuffers; std::array vkOffsets; for (uint32_t i = 0; i < cmd->count; ++i) { Buffer* buffer = ToBackend(buffers[i].Get()); vkBuffers[i] = buffer->GetHandle(); vkOffsets[i] = static_cast(offsets[i]); } device->fn.CmdBindVertexBuffers(commands, cmd->startSlot, cmd->count, vkBuffers.data(), vkOffsets.data()); } break; default: { UNREACHABLE(); } break; } } // EndRenderPass should have been called UNREACHABLE(); } }} // namespace backend::vulkan