blob: edf9a94f4d2ececa983f6b7e2c040527f149ee58 [file]
// Copyright 2013 The Flutter 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 <algorithm>
#include <cassert>
#include <memory>
#include <optional>
#include <string_view>
#include "flutter/flutter_vma/flutter_skia_vma.h"
#include "flutter/fml/closure.h"
#include "flutter/fml/logging.h"
#include "flutter/shell/common/context_options.h"
#include "flutter/testing/test_vulkan_context.h"
#include "flutter/vulkan/vulkan_skia_proc_table.h"
#include "flutter/fml/memory/ref_ptr.h"
#include "flutter/fml/native_library.h"
#include "flutter/vulkan/swiftshader_path.h"
#include "third_party/skia/include/gpu/ganesh/GrDirectContext.h"
#include "third_party/skia/include/gpu/ganesh/vk/GrVkDirectContext.h"
#include "third_party/skia/include/gpu/vk/VulkanBackendContext.h"
#include "third_party/skia/include/gpu/vk/VulkanExtensions.h"
#include "vulkan/vulkan_core.h"
namespace flutter::testing {
namespace {
/// The surface extensions Impeller requires of an embedder's instance:
/// VK_KHR_surface plus at least one window-system extension. Neither is used to
/// present here -- the tests render into images -- but Impeller refuses a
/// context without them. Only extensions the ICD offers are returned, so an ICD
/// without them still yields a context for the Skia tests.
std::vector<std::string> SurfaceInstanceExtensions(
const vulkan::VulkanProcTable& vk) {
uint32_t count = 0;
if (vk.EnumerateInstanceExtensionProperties(nullptr, &count, nullptr) !=
VK_SUCCESS ||
count == 0) {
return {};
}
std::vector<VkExtensionProperties> properties(count);
if (vk.EnumerateInstanceExtensionProperties(
nullptr, &count, properties.data()) != VK_SUCCESS) {
return {};
}
const auto supported = [&](std::string_view name) {
return std::any_of(properties.begin(), properties.begin() + count,
[&](const VkExtensionProperties& p) {
return name == p.extensionName;
});
};
if (!supported(VK_KHR_SURFACE_EXTENSION_NAME)) {
return {};
}
// Any one of them satisfies Impeller; which exist depends on the ICD and the
// host, so ask for the first that is offered rather than assuming Linux.
for (const char* wsi : {"VK_KHR_xcb_surface", "VK_KHR_xlib_surface",
"VK_KHR_wayland_surface", "VK_KHR_win32_surface",
"VK_EXT_metal_surface", "VK_MVK_macos_surface"}) {
if (supported(wsi)) {
return {VK_KHR_SURFACE_EXTENSION_NAME, wsi};
}
}
return {};
}
} // namespace
TestVulkanContext::TestVulkanContext() {
// ---------------------------------------------------------------------------
// Initialize basic Vulkan state using the Swiftshader ICD.
// ---------------------------------------------------------------------------
const char* vulkan_icd = VULKAN_SO_PATH;
// TODO(96949): Clean this up and pass a native library directly to
// VulkanProcTable.
if (!fml::NativeLibrary::Create(VULKAN_SO_PATH)) {
FML_LOG(ERROR) << "Couldn't find Vulkan ICD \"" << vulkan_icd
<< "\", trying \"libvulkan.so\" instead.";
vulkan_icd = "libvulkan.so";
}
FML_LOG(INFO) << "Using Vulkan ICD: " << vulkan_icd;
vk_ = fml::MakeRefCounted<vulkan::VulkanProcTable>(vulkan_icd);
if (!vk_ || !vk_->HasAcquiredMandatoryProcAddresses()) {
FML_LOG(ERROR) << "Proc table has not acquired mandatory proc addresses.";
return;
}
enabled_instance_extensions_ = SurfaceInstanceExtensions(*vk_);
application_ = std::make_unique<vulkan::VulkanApplication>(
*vk_, "Flutter Unittests", enabled_instance_extensions_,
VK_MAKE_VERSION(1, 0, 0), VK_MAKE_VERSION(1, 1, 0), true);
if (!application_->IsValid()) {
FML_LOG(ERROR) << "Failed to initialize basic Vulkan state.";
return;
}
if (!vk_->AreInstanceProcsSetup()) {
FML_LOG(ERROR) << "Failed to acquire full proc table.";
return;
}
device_ = CreateLogicalDevice();
if (!device_ || !device_->IsValid()) {
FML_LOG(ERROR) << "Failed to create compatible logical device.";
return;
}
for (const auto& name : enabled_instance_extensions_) {
enabled_instance_extension_names_.push_back(name.c_str());
}
for (const auto& name : enabled_device_extensions_) {
enabled_device_extension_names_.push_back(name.c_str());
}
// ---------------------------------------------------------------------------
// Create a Skia context.
// For creating SkSurfaces from VkImages and snapshotting them, etc.
// ---------------------------------------------------------------------------
VkPhysicalDeviceFeatures features;
if (!device_->GetPhysicalDeviceFeatures(&features)) {
FML_LOG(ERROR) << "Failed to get physical device features.";
return;
}
auto get_proc = vulkan::CreateSkiaGetProc(vk_);
if (get_proc == nullptr) {
FML_LOG(ERROR) << "Failed to create Vulkan getProc for Skia.";
return;
}
sk_sp<skgpu::VulkanMemoryAllocator> allocator =
flutter::FlutterSkiaVulkanMemoryAllocator::Make(
VK_MAKE_VERSION(1, 1, 0), application_->GetInstance(),
device_->GetPhysicalDeviceHandle(), device_->GetHandle(), vk_, true);
skgpu::VulkanExtensions extensions;
skgpu::VulkanBackendContext backend_context = {};
backend_context.fInstance = application_->GetInstance();
backend_context.fPhysicalDevice = device_->GetPhysicalDeviceHandle();
backend_context.fDevice = device_->GetHandle();
backend_context.fQueue = device_->GetQueueHandle();
backend_context.fGraphicsQueueIndex = device_->GetGraphicsQueueIndex();
backend_context.fMaxAPIVersion = VK_MAKE_VERSION(1, 1, 0);
backend_context.fDeviceFeatures = &features;
backend_context.fVkExtensions = &extensions;
backend_context.fGetProc = get_proc;
backend_context.fMemoryAllocator = allocator;
GrContextOptions options =
MakeDefaultContextOptions(ContextType::kRender, GrBackendApi::kVulkan);
options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo;
context_ = GrDirectContexts::MakeVulkan(backend_context, options);
}
std::unique_ptr<vulkan::VulkanDevice> TestVulkanContext::CreateLogicalDevice() {
const VkInstance instance = application_->GetInstance();
uint32_t device_count = 0;
if (vk_->EnumeratePhysicalDevices(instance, &device_count, nullptr) !=
VK_SUCCESS ||
device_count == 0) {
return nullptr;
}
std::vector<VkPhysicalDevice> physical_devices(device_count);
if (vk_->EnumeratePhysicalDevices(instance, &device_count,
physical_devices.data()) != VK_SUCCESS) {
return nullptr;
}
// Impeller's embedder path also requires VK_KHR_swapchain on the device.
// The proc table cannot enumerate device extensions, so ask for it and fall
// back to a device without it if the ICD refuses.
const std::vector<std::vector<std::string>> attempts =
enabled_instance_extensions_.empty()
? std::vector<std::vector<std::string>>{{}}
: std::vector<std::vector<std::string>>{
{VK_KHR_SWAPCHAIN_EXTENSION_NAME}, {}};
for (VkPhysicalDevice physical_device : physical_devices) {
uint32_t family_count = 0;
vk_->GetPhysicalDeviceQueueFamilyProperties(physical_device, &family_count,
nullptr);
std::vector<VkQueueFamilyProperties> families(family_count);
vk_->GetPhysicalDeviceQueueFamilyProperties(physical_device, &family_count,
families.data());
uint32_t queue_family = family_count;
for (uint32_t i = 0; i < family_count; i++) {
if (families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
queue_family = i;
break;
}
}
if (queue_family == family_count) {
continue;
}
const float priority = 1.0f;
const VkDeviceQueueCreateInfo queue_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = queue_family,
.queueCount = 1,
.pQueuePriorities = &priority,
};
for (const auto& extensions : attempts) {
std::vector<const char*> names;
names.reserve(extensions.size());
for (const auto& name : extensions) {
names.push_back(name.c_str());
}
const VkDeviceCreateInfo create_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_info,
.enabledExtensionCount = static_cast<uint32_t>(names.size()),
.ppEnabledExtensionNames = names.data(),
};
VkDevice device = VK_NULL_HANDLE;
if (vk_->CreateDevice(physical_device, &create_info, nullptr, &device) !=
VK_SUCCESS) {
continue;
}
if (!vk_->SetupDeviceProcAddresses(
vulkan::VulkanHandle<VkDevice>(device))) {
vk_->DestroyDevice(device, nullptr);
continue;
}
VkQueue queue = VK_NULL_HANDLE;
vk_->GetDeviceQueue(device, queue_family, 0, &queue);
auto result = std::make_unique<vulkan::VulkanDevice>(
*vk_, vulkan::VulkanHandle<VkPhysicalDevice>(physical_device),
vulkan::VulkanHandle<VkDevice>(device,
[vk = vk_](VkDevice d) {
vk->DeviceWaitIdle(d);
vk->DestroyDevice(d, nullptr);
}),
queue_family, vulkan::VulkanHandle<VkQueue>(queue));
if (!result->IsValid()) {
continue;
}
enabled_device_extensions_ = extensions;
if (extensions.empty() && !enabled_instance_extensions_.empty()) {
// Without the device extension Impeller cannot use this context, so
// the instance list would only mislead it.
enabled_instance_extensions_.clear();
}
return result;
}
}
return nullptr;
}
TestVulkanContext::~TestVulkanContext() {
if (context_) {
context_->releaseResourcesAndAbandonContext();
}
}
namespace {
std::optional<uint32_t> FindMemoryTypeIndex(
const vulkan::VulkanProcTable& vk,
VkPhysicalDevice physical_device,
uint32_t memory_type_bits,
VkMemoryPropertyFlags required_properties) {
VkPhysicalDeviceMemoryProperties memory_properties;
vk.GetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);
for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++) {
if ((memory_type_bits & (1u << i)) == 0) {
continue;
}
if ((memory_properties.memoryTypes[i].propertyFlags &
required_properties) == required_properties) {
return i;
}
}
return std::nullopt;
}
} // namespace
std::optional<TestVulkanImage> TestVulkanContext::CreateImage(
const DlISize& size) const {
return CreateImage(size, VK_FORMAT_R8G8B8A8_UNORM);
}
std::optional<TestVulkanImage> TestVulkanContext::CreateImage(
const DlISize& size,
VkFormat format) const {
TestVulkanImage result;
VkImageCreateInfo info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = VkExtent3D{static_cast<uint32_t>(size.width),
static_cast<uint32_t>(size.height), 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage image;
if (VK_CALL_LOG_ERROR(VK_CALL_LOG_ERROR(
vk_->CreateImage(device_->GetHandle(), &info, nullptr, &image)))) {
return std::nullopt;
}
result.image_ = vulkan::VulkanHandle<VkImage>(
image, [&vk = vk_, &device = device_](VkImage image) {
vk->DestroyImage(device->GetHandle(), image, nullptr);
});
VkMemoryRequirements mem_req;
vk_->GetImageMemoryRequirements(device_->GetHandle(), image, &mem_req);
VkMemoryAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_req.size;
std::optional<uint32_t> memory_type_index = FindMemoryTypeIndex(
*vk_, device_->GetPhysicalDeviceHandle(), mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (!memory_type_index.has_value()) {
FML_LOG(ERROR) << "No device local memory type found for image.";
return std::nullopt;
}
alloc_info.memoryTypeIndex = memory_type_index.value();
VkDeviceMemory memory;
if (VK_CALL_LOG_ERROR(vk_->AllocateMemory(device_->GetHandle(), &alloc_info,
nullptr, &memory)) != VK_SUCCESS) {
return std::nullopt;
}
result.memory_ = vulkan::VulkanHandle<VkDeviceMemory>{
memory, [&vk = vk_, &device = device_](VkDeviceMemory memory) {
vk->FreeMemory(device->GetHandle(), memory, nullptr);
}};
if (VK_CALL_LOG_ERROR(VK_CALL_LOG_ERROR(vk_->BindImageMemory(
device_->GetHandle(), result.image_, result.memory_, 0)))) {
return std::nullopt;
}
result.context_ =
fml::RefPtr<TestVulkanContext>(const_cast<TestVulkanContext*>(this));
return result;
}
std::optional<TestVulkanImage> TestVulkanContext::CreateNV12Image(
const DlISize& size,
const uint8_t* y_data,
const uint8_t* uv_data) const {
const uint32_t width = static_cast<uint32_t>(size.width);
const uint32_t height = static_cast<uint32_t>(size.height);
const VkFormat nv12_format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
// Dynamically load procs not in VulkanProcTable.
auto vkGetInstanceProcAddr = vk_->NativeGetInstanceProcAddr();
auto vkGetPhysicalDeviceImageFormatProperties =
reinterpret_cast<PFN_vkGetPhysicalDeviceImageFormatProperties>(
vkGetInstanceProcAddr(application_->GetInstance(),
"vkGetPhysicalDeviceImageFormatProperties"));
auto vkCmdCopyBufferToImage =
reinterpret_cast<PFN_vkCmdCopyBufferToImage>(vkGetInstanceProcAddr(
application_->GetInstance(), "vkCmdCopyBufferToImage"));
VkImageFormatProperties format_props;
if (vkGetPhysicalDeviceImageFormatProperties(
device_->GetPhysicalDeviceHandle(), nv12_format, VK_IMAGE_TYPE_2D,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 0,
&format_props) != VK_SUCCESS) {
FML_LOG(ERROR) << "NV12 format not supported by device.";
return std::nullopt;
}
VkImageCreateInfo image_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = nv12_format,
.extent = {width, height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage image;
if (VK_CALL_LOG_ERROR(vk_->CreateImage(device_->GetHandle(), &image_info,
nullptr, &image))) {
return std::nullopt;
}
TestVulkanImage result;
result.image_ = vulkan::VulkanHandle<VkImage>(
image, [&vk = vk_, &device = device_](VkImage img) {
vk->DestroyImage(device->GetHandle(), img, nullptr);
});
VkMemoryRequirements mem_req;
vk_->GetImageMemoryRequirements(device_->GetHandle(), image, &mem_req);
VkMemoryAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_req.size;
std::optional<uint32_t> nv12_memory_type_index = FindMemoryTypeIndex(
*vk_, device_->GetPhysicalDeviceHandle(), mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (!nv12_memory_type_index.has_value()) {
FML_LOG(ERROR) << "No device local memory type found for NV12 image.";
return std::nullopt;
}
alloc_info.memoryTypeIndex = nv12_memory_type_index.value();
VkDeviceMemory memory;
if (VK_CALL_LOG_ERROR(vk_->AllocateMemory(device_->GetHandle(), &alloc_info,
nullptr, &memory)) != VK_SUCCESS) {
return std::nullopt;
}
result.memory_ = vulkan::VulkanHandle<VkDeviceMemory>{
memory, [&vk = vk_, &device = device_](VkDeviceMemory mem) {
vk->FreeMemory(device->GetHandle(), mem, nullptr);
}};
if (VK_CALL_LOG_ERROR(
vk_->BindImageMemory(device_->GetHandle(), image, memory, 0))) {
return std::nullopt;
}
// Create a staging buffer for Y + UV data.
size_t y_size = width * height;
size_t uv_width = width / 2;
size_t uv_height = height / 2;
size_t uv_size = uv_width * uv_height * 2;
size_t total_staging_size = y_size + uv_size;
VkBufferCreateInfo buffer_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = total_staging_size,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
VkBuffer staging_buffer;
if (VK_CALL_LOG_ERROR(vk_->CreateBuffer(device_->GetHandle(), &buffer_info,
nullptr, &staging_buffer))) {
return std::nullopt;
}
fml::ScopedCleanupClosure destroy_staging_buffer(
[&vk = vk_, &device = device_, &staging_buffer]() {
vk->DestroyBuffer(device->GetHandle(), staging_buffer, nullptr);
});
VkMemoryRequirements buffer_mem_req;
vk_->GetBufferMemoryRequirements(device_->GetHandle(), staging_buffer,
&buffer_mem_req);
VkMemoryAllocateInfo buffer_alloc_info{};
buffer_alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
buffer_alloc_info.allocationSize = buffer_mem_req.size;
std::optional<uint32_t> host_visible_index = FindMemoryTypeIndex(
*vk_, device_->GetPhysicalDeviceHandle(), buffer_mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (!host_visible_index.has_value()) {
FML_LOG(ERROR) << "No host visible/coherent memory type found for staging "
"buffer.";
return std::nullopt;
}
buffer_alloc_info.memoryTypeIndex = host_visible_index.value();
VkDeviceMemory buffer_memory;
if (VK_CALL_LOG_ERROR(vk_->AllocateMemory(
device_->GetHandle(), &buffer_alloc_info, nullptr, &buffer_memory)) !=
VK_SUCCESS) {
return std::nullopt;
}
fml::ScopedCleanupClosure free_buffer_memory(
[&vk = vk_, &device = device_, &buffer_memory]() {
vk->FreeMemory(device->GetHandle(), buffer_memory, nullptr);
});
vk_->BindBufferMemory(device_->GetHandle(), staging_buffer, buffer_memory, 0);
// Map and copy data.
void* mapped = nullptr;
if (VK_CALL_LOG_ERROR(vk_->MapMemory(device_->GetHandle(), buffer_memory, 0,
total_staging_size, 0, &mapped))) {
return std::nullopt;
}
memcpy(mapped, y_data, y_size);
memcpy(static_cast<uint8_t*>(mapped) + y_size, uv_data, uv_size);
vk_->UnmapMemory(device_->GetHandle(), buffer_memory);
// Create command buffer for copy operations.
VkCommandPoolCreateInfo pool_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = device_->GetGraphicsQueueIndex(),
};
VkCommandPool command_pool;
if (VK_CALL_LOG_ERROR(vk_->CreateCommandPool(device_->GetHandle(), &pool_info,
nullptr, &command_pool))) {
return std::nullopt;
}
fml::ScopedCleanupClosure destroy_command_pool(
[&vk = vk_, &device = device_, &command_pool]() {
vk->DestroyCommandPool(device->GetHandle(), command_pool, nullptr);
});
VkCommandBufferAllocateInfo cmd_alloc_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = command_pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer cmd_buffer;
vk_->AllocateCommandBuffers(device_->GetHandle(), &cmd_alloc_info,
&cmd_buffer);
VkCommandBufferBeginInfo begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vk_->BeginCommandBuffer(cmd_buffer, &begin_info);
// Barrier: UNDEFINED -> TRANSFER_DST_OPTIMAL for both planes.
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
// Plane 0 (Y)
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
vk_->CmdPipelineBarrier(cmd_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,
nullptr, 1, &barrier);
// Plane 1 (UV)
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT;
vk_->CmdPipelineBarrier(cmd_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,
nullptr, 1, &barrier);
// Copy Y data to plane 0.
VkBufferImageCopy y_copy = {};
y_copy.bufferOffset = 0;
y_copy.bufferRowLength = 0;
y_copy.bufferImageHeight = 0;
y_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT;
y_copy.imageSubresource.mipLevel = 0;
y_copy.imageSubresource.baseArrayLayer = 0;
y_copy.imageSubresource.layerCount = 1;
y_copy.imageOffset = {0, 0, 0};
y_copy.imageExtent = {width, height, 1};
vkCmdCopyBufferToImage(cmd_buffer, staging_buffer, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &y_copy);
// Copy UV data to plane 1.
VkBufferImageCopy uv_copy = {};
uv_copy.bufferOffset = y_size;
uv_copy.bufferRowLength = 0;
uv_copy.bufferImageHeight = 0;
uv_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT;
uv_copy.imageSubresource.mipLevel = 0;
uv_copy.imageSubresource.baseArrayLayer = 0;
uv_copy.imageSubresource.layerCount = 1;
uv_copy.imageOffset = {0, 0, 0};
uv_copy.imageExtent = {static_cast<uint32_t>(uv_width),
static_cast<uint32_t>(uv_height), 1};
vkCmdCopyBufferToImage(cmd_buffer, staging_buffer, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &uv_copy);
// Barrier: TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL for both planes.
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT;
vk_->CmdPipelineBarrier(cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,
0, nullptr, 1, &barrier);
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT;
vk_->CmdPipelineBarrier(cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,
0, nullptr, 1, &barrier);
vk_->EndCommandBuffer(cmd_buffer);
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &cmd_buffer;
vk_->QueueSubmit(device_->GetQueueHandle(), 1, &submit_info, VK_NULL_HANDLE);
vk_->QueueWaitIdle(device_->GetQueueHandle());
result.context_ =
fml::RefPtr<TestVulkanContext>(const_cast<TestVulkanContext*>(this));
return result;
}
sk_sp<GrDirectContext> TestVulkanContext::GetGrDirectContext() const {
return context_;
}
} // namespace flutter::testing