| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <fidl/fuchsia.sysmem/cpp/fidl.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <unistd.h> |
| #include <zircon/process.h> |
| |
| #include <limits> |
| #include <memory> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| #include "hwcpipe.h" |
| #include "src/graphics/examples/vkproto/common/command_buffers.h" |
| #include "src/graphics/examples/vkproto/common/command_pool.h" |
| #include "src/graphics/examples/vkproto/common/debug_utils_messenger.h" |
| #include "src/graphics/examples/vkproto/common/device.h" |
| #include "src/graphics/examples/vkproto/common/framebuffers.h" |
| #include "src/graphics/examples/vkproto/common/graphics_pipeline.h" |
| #include "src/graphics/examples/vkproto/common/image_view.h" |
| #include "src/graphics/examples/vkproto/common/instance.h" |
| #include "src/graphics/examples/vkproto/common/physical_device.h" |
| #include "src/graphics/examples/vkproto/common/render_pass.h" |
| #include "src/graphics/examples/vkproto/common/utils.h" |
| |
| #include <vulkan/vulkan.hpp> |
| |
| namespace { |
| |
| static std::string GetObjectName(zx_handle_t handle) { |
| char name[ZX_MAX_NAME_LEN]; |
| zx_status_t status = zx_object_get_property(handle, ZX_PROP_NAME, name, sizeof(name)); |
| return status == ZX_OK ? std::string(name) : std::string(); |
| } |
| |
| zx_koid_t GetKoid(zx_handle_t handle) { |
| zx_info_handle_basic_t info; |
| zx_status_t status = |
| zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); |
| return status == ZX_OK ? info.koid : ZX_KOID_INVALID; |
| } |
| |
| zx_koid_t GetCurrentProcessKoid() { return GetKoid(zx_process_self()); } |
| |
| std::string GetCurrentProcessName() { return GetObjectName(zx_process_self()); } |
| |
| } // namespace |
| |
| static inline uint32_t to_uint32(uint64_t val) { |
| assert(val <= std::numeric_limits<uint32_t>::max()); |
| return static_cast<uint32_t>(val); |
| } |
| |
| uint32_t GetCounterValue(const hwcpipe::GpuMeasurements* gpu, hwcpipe::GpuCounter counter) { |
| auto it = gpu->find(counter); |
| EXPECT_NE(it, gpu->end()); |
| return it->second.get<uint32_t>(); |
| } |
| |
| static bool DrawAllFrames(const vkp::Device& vkp_device, |
| const vkp::CommandBuffers& vkp_command_buffers); |
| |
| static void InitCommandBuffers(const vk::Image* image_for_foreign_transition, uint32_t queue_family, |
| std::unique_ptr<vkp::CommandBuffers>& vkp_command_buffers) { |
| vk::ClearValue clear_color; |
| clear_color.color = std::array<float, 4>({0.5f, 0.0f, 0.5f, 1.0f}); |
| vk::RenderPassBeginInfo render_pass_info; |
| render_pass_info.renderPass = vkp_command_buffers->render_pass(); |
| render_pass_info.renderArea = vk::Rect2D(0 /* offset */, vkp_command_buffers->extent()); |
| render_pass_info.clearValueCount = 1; |
| render_pass_info.pClearValues = &clear_color; |
| |
| const vk::CommandBufferBeginInfo begin_info(vk::CommandBufferUsageFlagBits::eSimultaneousUse, |
| nullptr /* pInheritanceInfo */); |
| const std::vector<vk::UniqueCommandBuffer>& command_buffers = |
| vkp_command_buffers->command_buffers(); |
| for (size_t i = 0; i < command_buffers.size(); ++i) { |
| const vk::CommandBuffer& command_buffer = command_buffers[i].get(); |
| ASSERT_TRUE(command_buffer.begin(&begin_info) == vk::Result::eSuccess); |
| render_pass_info.framebuffer = vkp_command_buffers->framebuffers()[i].get(); |
| |
| // Record commands to render pass. |
| command_buffer.beginRenderPass(&render_pass_info, vk::SubpassContents::eInline); |
| command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, |
| vkp_command_buffers->graphics_pipeline()); |
| command_buffer.draw(3 /* vertexCount */, 1 /* instanceCount */, 0 /* firstVertex */, |
| 0 /* firstInstance */); |
| command_buffer.endRenderPass(); |
| |
| if (image_for_foreign_transition) { |
| vk::ImageMemoryBarrier barrier; |
| barrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite) |
| .setOldLayout(vk::ImageLayout::eTransferSrcOptimal) |
| .setNewLayout(vk::ImageLayout::eTransferSrcOptimal) |
| .setSrcQueueFamilyIndex(queue_family) |
| .setDstQueueFamilyIndex(VK_QUEUE_FAMILY_EXTERNAL) |
| .setSubresourceRange(vk::ImageSubresourceRange() |
| .setAspectMask(vk::ImageAspectFlagBits::eColor) |
| .setLevelCount(1) |
| .setLayerCount(1)) |
| .setImage(*image_for_foreign_transition); |
| |
| command_buffer.pipelineBarrier( |
| vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, |
| vk::DependencyFlags{}, 0 /* memoryBarrierCount */, nullptr /* memoryBarriers */, |
| 0 /* bufferMemoryBarrierCount */, nullptr /* bufferMemoryBarriers */, |
| 1 /* imageMemoryBarrierCount */, &barrier); |
| |
| // This barrier should transition it back |
| vk::ImageMemoryBarrier barrier2(barrier); |
| command_buffer.pipelineBarrier( |
| vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, |
| vk::DependencyFlags{}, 0 /* memoryBarrierCount */, nullptr /* memoryBarriers */, |
| 0 /* bufferMemoryBarrierCount */, nullptr /* bufferMemoryBarriers */, |
| 1 /* imageMemoryBarrierCount */, &barrier2); |
| } |
| |
| EXPECT_EQ(vk::Result::eSuccess, command_buffer.end()); |
| } |
| } |
| |
| // Test that transferring an image to a foreign queue and back doesn't prevent transaction |
| // elimination from working. |
| TEST(TransactionElimination, ForeignQueue) { |
| vkp::Instance vkp_instance(vkp::Instance::Builder() |
| .set_validation_layers_enabled(true) |
| .set_swapchain_enabled(false) |
| .set_extensions({ |
| VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, |
| VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, |
| }) |
| .Build()); |
| |
| vkp::DebugUtilsMessenger vkp_debug_messenger(vkp_instance.shared()); |
| ASSERT_TRUE(vkp_debug_messenger.Init()); |
| |
| vkp::PhysicalDevice vkp_physical_device(vkp_instance.shared()); |
| vkp_physical_device.set_swapchain_enabled(false); |
| ASSERT_TRUE(vkp_physical_device.Init()); |
| |
| vkp::Device vkp_device(vkp_physical_device.get()); |
| vkp_device.set_swapchain_enabled(false); |
| ASSERT_TRUE(vkp_device.Init()); |
| std::shared_ptr<vk::Device> device = vkp_device.shared(); |
| |
| vk::Format image_format; |
| vk::Extent2D extent; |
| |
| std::vector<vk::ImageView> image_views; |
| std::shared_ptr<vkp::ImageView> vkp_offscreen_image_view; |
| vkp_offscreen_image_view = |
| std::make_shared<vkp::ImageView>(device, vkp_physical_device.get(), vk::Extent2D{64, 64}); |
| ASSERT_TRUE(vkp_offscreen_image_view->Init()); |
| |
| image_format = vkp_offscreen_image_view->format(); |
| extent = vkp_offscreen_image_view->extent(); |
| image_views.emplace_back(vkp_offscreen_image_view->get()); |
| |
| auto vkp_render_pass = std::make_shared<vkp::RenderPass>(device, image_format, true); |
| ASSERT_TRUE(vkp_render_pass->Init()); |
| |
| auto vkp_pipeline = std::make_unique<vkp::GraphicsPipeline>(device, extent, vkp_render_pass); |
| ASSERT_TRUE(vkp_pipeline->Init()); |
| |
| auto vkp_framebuffer = |
| std::make_unique<vkp::Framebuffers>(device, extent, vkp_render_pass->get(), image_views); |
| ASSERT_TRUE(vkp_framebuffer->Init()); |
| |
| auto vkp_command_pool = |
| std::make_shared<vkp::CommandPool>(device, vkp_device.queue_family_index()); |
| ASSERT_TRUE(vkp_command_pool->Init()); |
| |
| // First command buffer does a transition to queue family foreign and back. |
| auto vkp_command_buffers = std::make_unique<vkp::CommandBuffers>( |
| device, vkp_command_pool, vkp_framebuffer->framebuffers(), vkp_pipeline->get(), |
| vkp_render_pass->get(), extent); |
| ASSERT_TRUE(vkp_command_buffers->Alloc()); |
| InitCommandBuffers(&(vkp_offscreen_image_view->image().get()), vkp_device.queue_family_index(), |
| vkp_command_buffers); |
| |
| hwcpipe::HWCPipe pipe; |
| pipe.set_enabled_gpu_counters(pipe.gpu_profiler()->supported_counters()); |
| pipe.run(); |
| |
| ASSERT_TRUE(DrawAllFrames(vkp_device, *vkp_command_buffers)); |
| EXPECT_EQ(vk::Result::eSuccess, vkp_device.get().waitIdle()); |
| auto sample = pipe.sample(); |
| EXPECT_EQ(0u, GetCounterValue(sample.gpu, hwcpipe::GpuCounter::TransactionEliminations)); |
| |
| // Second render pass and command buffers do a transition from eTransferSrcOptimal instead of |
| // eUndefined, since otherwise transaction elimination would be disabled. |
| auto vkp_render_pass2 = std::make_shared<vkp::RenderPass>(device, image_format, true); |
| vkp_render_pass2->set_initial_layout(vk::ImageLayout::eTransferSrcOptimal); |
| ASSERT_TRUE(vkp_render_pass2->Init()); |
| |
| auto vkp_command_buffers2 = std::make_unique<vkp::CommandBuffers>( |
| device, vkp_command_pool, vkp_framebuffer->framebuffers(), vkp_pipeline->get(), |
| vkp_render_pass2->get(), extent); |
| ASSERT_TRUE(vkp_command_buffers2->Alloc()); |
| InitCommandBuffers({} /* image_for_foreign_transition */, {} /* queue_family */, |
| vkp_command_buffers2); |
| |
| ASSERT_TRUE(DrawAllFrames(vkp_device, *vkp_command_buffers2)); |
| EXPECT_EQ(vk::Result::eSuccess, vkp_device.get().waitIdle()); |
| auto sample2 = pipe.sample(); |
| constexpr uint32_t kTransactionMinTileSize = 16; |
| constexpr uint32_t kTransactionMaxTileSize = 32; |
| uint32_t eliminated_count = |
| GetCounterValue(sample2.gpu, hwcpipe::GpuCounter::TransactionEliminations); |
| // All transactions should be eliminated. |
| EXPECT_GE((64u / kTransactionMinTileSize) * (64u / kTransactionMinTileSize), eliminated_count); |
| EXPECT_LE((64u / kTransactionMaxTileSize) * (64u / kTransactionMaxTileSize), eliminated_count); |
| } |
| |
| // Test that transferring an image to a foreign queue and back doesn't prevent transaction |
| // elimination from working. |
| TEST(TransactionElimination, ForeignQueueSysmem) { |
| vkp::Instance vkp_instance(vkp::Instance::Builder() |
| .set_validation_layers_enabled(true) |
| .set_swapchain_enabled(false) |
| .set_extensions({ |
| VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, |
| VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, |
| }) |
| .Build()); |
| vk::DispatchLoaderDynamic loader; |
| loader.init(vkp_instance.get(), vkGetInstanceProcAddr); |
| |
| vkp::DebugUtilsMessenger vkp_debug_messenger(vkp_instance.shared()); |
| ASSERT_TRUE(vkp_debug_messenger.Init()); |
| |
| vkp::PhysicalDevice vkp_physical_device(vkp_instance.shared()); |
| vkp_physical_device.set_swapchain_enabled(false); |
| ASSERT_TRUE(vkp_physical_device.Init()); |
| |
| vkp::Device vkp_device(vkp_physical_device.get()); |
| vkp_device.set_swapchain_enabled(false); |
| ASSERT_TRUE(vkp_device.Init()); |
| std::shared_ptr<vk::Device> device = vkp_device.shared(); |
| |
| vk::Format image_format; |
| vk::Extent2D extent; |
| |
| std::vector<vk::ImageView> image_views; |
| std::shared_ptr<vkp::ImageView> vkp_offscreen_image_view; |
| vkp_offscreen_image_view = |
| std::make_shared<vkp::ImageView>(device, vkp_physical_device.get(), vk::Extent2D{64, 64}); |
| |
| { |
| auto sysmem_allocator_end = component::Connect<fuchsia_sysmem::Allocator>(); |
| ASSERT_TRUE(sysmem_allocator_end.is_ok()) << sysmem_allocator_end.status_string(); |
| constexpr auto kFormat = vk::Format::eB8G8R8A8Unorm; |
| fidl::SyncClient sysmem_allocator(std::move(*sysmem_allocator_end)); |
| ASSERT_TRUE(sysmem_allocator |
| ->SetDebugClientInfo(fuchsia_sysmem::AllocatorSetDebugClientInfoRequest{ |
| GetCurrentProcessName(), GetCurrentProcessKoid()}) |
| .is_ok()); |
| auto token_endpoints = fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| ASSERT_TRUE( |
| sysmem_allocator->AllocateSharedCollection(std::move(token_endpoints.server)).is_ok()); |
| fidl::SyncClient token(std::move(token_endpoints.client)); |
| ASSERT_TRUE(token |
| ->SetName(fuchsia_sysmem::NodeSetNameRequest{ |
| 1u, ::testing::UnitTest::GetInstance()->current_test_info()->name()}) |
| .is_ok()); |
| token->Sync(); |
| vk::BufferCollectionCreateInfoFUCHSIA import_info(token.TakeClientEnd().TakeHandle().release()); |
| auto [result, collection] = |
| device->createBufferCollectionFUCHSIAUnique(import_info, nullptr, loader); |
| ASSERT_EQ(vk::Result::eSuccess, result); |
| constexpr uint32_t kWidth = 64; |
| constexpr uint32_t kHeight = 64; |
| |
| auto image_create_info = vk::ImageCreateInfo() |
| .setImageType(vk::ImageType::e2D) |
| .setFormat(vk::Format(kFormat)) |
| .setExtent(vk::Extent3D(kWidth, kHeight, 1)) |
| .setMipLevels(1) |
| .setArrayLayers(1) |
| .setSamples(vk::SampleCountFlagBits::e1) |
| .setTiling(vk::ImageTiling::eOptimal) |
| .setUsage(vk::ImageUsageFlagBits::eColorAttachment | |
| vk::ImageUsageFlagBits::eTransferSrc) |
| .setSharingMode(vk::SharingMode::eExclusive) |
| .setInitialLayout(vk::ImageLayout::eUndefined); |
| vk::SysmemColorSpaceFUCHSIA color_space; |
| color_space.setColorSpace(static_cast<uint32_t>(fuchsia_sysmem::ColorSpaceType::kSrgb)); |
| auto image_format_constraints = |
| vk::ImageFormatConstraintsInfoFUCHSIA() |
| .setSysmemPixelFormat(0u) |
| .setFlags({}) |
| .setPColorSpaces(&color_space) |
| .setColorSpaceCount(1u) |
| .setRequiredFormatFeatures(vk::FormatFeatureFlagBits::eTransferDst | |
| vk::FormatFeatureFlagBits::eColorAttachment); |
| image_format_constraints.imageCreateInfo = image_create_info; |
| vk::ImageConstraintsInfoFUCHSIA constraints_info; |
| constraints_info.formatConstraintsCount = 1; |
| constraints_info.pFormatConstraints = &image_format_constraints; |
| constraints_info.bufferCollectionConstraints.minBufferCount = 1; |
| constraints_info.bufferCollectionConstraints.minBufferCountForCamping = 0; |
| constraints_info.bufferCollectionConstraints.minBufferCountForSharedSlack = 0; |
| |
| result = |
| device->setBufferCollectionImageConstraintsFUCHSIA(*collection, constraints_info, loader); |
| ASSERT_EQ(vk::Result::eSuccess, result); |
| vk::BufferCollectionImageCreateInfoFUCHSIA image_format_fuchsia; |
| image_format_fuchsia.collection = *collection; |
| image_create_info.pNext = &image_format_fuchsia; |
| |
| auto [image_result, image] = device->createImageUnique(image_create_info, nullptr); |
| ASSERT_EQ(vk::Result::eSuccess, image_result); |
| |
| vk::BufferCollectionPropertiesFUCHSIA properties; |
| result = device->getBufferCollectionPropertiesFUCHSIA(*collection, &properties, loader); |
| |
| ASSERT_EQ(vk::Result::eSuccess, result); |
| |
| auto requirements = device->getImageMemoryRequirements(*image); |
| const uint32_t memory_type = |
| __builtin_ctz(properties.memoryTypeBits & requirements.memoryTypeBits); |
| |
| vk::StructureChain<vk::MemoryAllocateInfo, vk::ImportMemoryBufferCollectionFUCHSIA, |
| vk::MemoryDedicatedAllocateInfoKHR> |
| alloc_info(vk::MemoryAllocateInfo() |
| .setAllocationSize(requirements.size) |
| .setMemoryTypeIndex(memory_type), |
| vk::ImportMemoryBufferCollectionFUCHSIA().setCollection(*collection).setIndex(0), |
| vk::MemoryDedicatedAllocateInfoKHR().setImage(*image)); |
| |
| auto [memory_result, memory] = |
| device->allocateMemoryUnique(alloc_info.get<vk::MemoryAllocateInfo>()); |
| ASSERT_EQ(vk::Result::eSuccess, memory_result); |
| ASSERT_EQ(vk::Result::eSuccess, device->bindImageMemory(*image, *memory, 0)); |
| ASSERT_TRUE(vkp_offscreen_image_view->Init(std::move(image), std::move(memory), kFormat)); |
| } |
| |
| image_format = vkp_offscreen_image_view->format(); |
| extent = vkp_offscreen_image_view->extent(); |
| image_views.emplace_back(vkp_offscreen_image_view->get()); |
| |
| auto vkp_render_pass = std::make_shared<vkp::RenderPass>(device, image_format, true); |
| ASSERT_TRUE(vkp_render_pass->Init()); |
| |
| auto vkp_pipeline = std::make_unique<vkp::GraphicsPipeline>(device, extent, vkp_render_pass); |
| ASSERT_TRUE(vkp_pipeline->Init()); |
| |
| auto vkp_framebuffer = |
| std::make_unique<vkp::Framebuffers>(device, extent, vkp_render_pass->get(), image_views); |
| ASSERT_TRUE(vkp_framebuffer->Init()); |
| |
| auto vkp_command_pool = |
| std::make_shared<vkp::CommandPool>(device, vkp_device.queue_family_index()); |
| ASSERT_TRUE(vkp_command_pool->Init()); |
| |
| // First command buffer does a transition to queue family foreign and back. |
| auto vkp_command_buffers = std::make_unique<vkp::CommandBuffers>( |
| device, vkp_command_pool, vkp_framebuffer->framebuffers(), vkp_pipeline->get(), |
| vkp_render_pass->get(), extent); |
| ASSERT_TRUE(vkp_command_buffers->Alloc()); |
| InitCommandBuffers(&(vkp_offscreen_image_view->image().get()), vkp_device.queue_family_index(), |
| vkp_command_buffers); |
| |
| hwcpipe::HWCPipe pipe; |
| pipe.set_enabled_gpu_counters(pipe.gpu_profiler()->supported_counters()); |
| pipe.run(); |
| |
| ASSERT_TRUE(DrawAllFrames(vkp_device, *vkp_command_buffers)); |
| auto sample = pipe.sample(); |
| EXPECT_EQ(0u, GetCounterValue(sample.gpu, hwcpipe::GpuCounter::TransactionEliminations)); |
| |
| // Second render pass and command buffers do a transition from eTransferSrcOptimal instead of |
| // eUndefined, since otherwise transaction elimination would be disabled. |
| auto vkp_render_pass2 = std::make_shared<vkp::RenderPass>(device, image_format, true); |
| vkp_render_pass2->set_initial_layout(vk::ImageLayout::eTransferSrcOptimal); |
| ASSERT_TRUE(vkp_render_pass2->Init()); |
| |
| auto vkp_command_buffers2 = std::make_unique<vkp::CommandBuffers>( |
| device, vkp_command_pool, vkp_framebuffer->framebuffers(), vkp_pipeline->get(), |
| vkp_render_pass2->get(), extent); |
| ASSERT_TRUE(vkp_command_buffers2->Alloc()); |
| InitCommandBuffers({} /* image_for_foreign_transition */, {} /* queue_family */, |
| vkp_command_buffers2); |
| |
| ASSERT_TRUE(DrawAllFrames(vkp_device, *vkp_command_buffers2)); |
| EXPECT_EQ(vk::Result::eSuccess, vkp_device.get().waitIdle()); |
| auto sample2 = pipe.sample(); |
| constexpr uint32_t kTransactionMinTileSize = 16; |
| constexpr uint32_t kTransactionMaxTileSize = 32; |
| uint32_t eliminated_count = |
| GetCounterValue(sample2.gpu, hwcpipe::GpuCounter::TransactionEliminations); |
| // All transactions should be eliminated. |
| EXPECT_GE((64u / kTransactionMinTileSize) * (64u / kTransactionMinTileSize), eliminated_count); |
| EXPECT_LE((64u / kTransactionMaxTileSize) * (64u / kTransactionMaxTileSize), eliminated_count); |
| } |
| |
| bool DrawAllFrames(const vkp::Device& vkp_device, const vkp::CommandBuffers& vkp_command_buffers) { |
| vk::SubmitInfo submit_info; |
| submit_info.commandBufferCount = to_uint32(vkp_command_buffers.command_buffers().size()); |
| std::vector<vk::CommandBuffer> command_buffer(submit_info.commandBufferCount); |
| for (uint32_t i = 0; i < submit_info.commandBufferCount; i++) { |
| command_buffer[i] = vkp_command_buffers.command_buffers()[i].get(); |
| } |
| submit_info.pCommandBuffers = command_buffer.data(); |
| |
| if (vkp_device.queue().submit(1, &submit_info, vk::Fence()) != vk::Result::eSuccess) { |
| RTN_MSG(false, "Failed to submit draw command buffer.\n"); |
| } |
| return true; |
| } |