blob: 557d5bb485e60a862e681cfe7a07c4d4594cb33f [file] [log] [blame]
/*
* Copyright (c) 2023-2025 The Khronos Group Inc.
* Copyright (c) 2023-2025 Valve Corporation
* Copyright (c) 2023-2025 LunarG, Inc.
* Copyright (c) 2023-2025 Collabora, Inc.
*
* 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
*/
#include "../framework/layer_validation_tests.h"
#include "../framework/pipeline_helper.h"
#include "../framework/descriptor_helper.h"
class PositiveSparseImage : public VkLayerTest {};
TEST_F(PositiveSparseImage, MultipleBinds) {
TEST_DESCRIPTION("Bind 2 memory ranges to one image using vkQueueBindSparse, destroy the image and then free the memory");
AddRequiredFeature(vkt::Feature::sparseBinding);
RETURN_IF_SKIP(Init());
auto index = m_device->graphics_queue_node_index_;
if (!(m_device->Physical().queue_properties_[index].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT)) {
GTEST_SKIP() << "Graphics queue does not have sparse binding bit";
}
VkImageCreateInfo image_create_info = vku::InitStructHelper();
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM;
image_create_info.extent = {64, 64, 1};
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
image_create_info.flags = VK_IMAGE_CREATE_SPARSE_BINDING_BIT;
vkt::Image image(*m_device, image_create_info, vkt::no_mem);
VkMemoryRequirements memory_reqs;
vk::GetImageMemoryRequirements(device(), image, &memory_reqs);
// Find an image big enough to allow sparse mapping of 2 memory regions
// Increase the image size until it is at least twice the
// size of the required alignment, to ensure we can bind both
// allocated memory blocks to the image on aligned offsets.
while (memory_reqs.size < (memory_reqs.alignment * 2)) {
image.Destroy();
image_create_info.extent.width *= 2;
image_create_info.extent.height *= 2;
image.InitNoMemory(*m_device, image_create_info);
vk::GetImageMemoryRequirements(device(), image, &memory_reqs);
}
// Allocate 2 memory regions of minimum alignment size, bind one at 0, the other
// at the end of the first
VkMemoryAllocateInfo memory_info = vku::InitStructHelper();
memory_info.allocationSize = memory_reqs.alignment;
bool pass = m_device->Physical().SetMemoryType(memory_reqs.memoryTypeBits, &memory_info, 0);
ASSERT_TRUE(pass);
vkt::DeviceMemory memory_one(*m_device, memory_info);
vkt::DeviceMemory memory_two(*m_device, memory_info);
std::array<VkSparseMemoryBind, 2> binds = {};
binds[0].memory = memory_one;
binds[0].memoryOffset = 0;
binds[0].resourceOffset = 0;
binds[0].size = memory_info.allocationSize;
binds[1].memory = memory_two;
binds[1].memoryOffset = 0;
binds[1].resourceOffset = memory_info.allocationSize;
binds[1].size = memory_info.allocationSize;
VkSparseImageOpaqueMemoryBindInfo opaqueBindInfo;
opaqueBindInfo.image = image;
opaqueBindInfo.bindCount = size32(binds);
opaqueBindInfo.pBinds = binds.data();
VkBindSparseInfo bindSparseInfo = vku::InitStructHelper();
bindSparseInfo.imageOpaqueBindCount = 1;
bindSparseInfo.pImageOpaqueBinds = &opaqueBindInfo;
vk::QueueBindSparse(m_default_queue->handle(), 1, &bindSparseInfo, VK_NULL_HANDLE);
// Wait for operations to finish before destroying anything
m_default_queue->Wait();
}
TEST_F(PositiveSparseImage, BindFreeMemory) {
TEST_DESCRIPTION("Test using a sparse image after freeing memory that was bound to it.");
AddRequiredFeature(vkt::Feature::sparseBinding);
AddRequiredFeature(vkt::Feature::sparseResidencyImage2D);
RETURN_IF_SKIP(Init());
auto index = m_device->graphics_queue_node_index_;
if (!(m_device->Physical().queue_properties_[index].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT)) {
GTEST_SKIP() << "Graphics queue does not have sparse binding bit";
}
VkImageCreateInfo image_create_info = vku::InitStructHelper();
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM;
image_create_info.extent = {512, 512, 1};
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_create_info.flags = VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
vkt::Image image(*m_device, image_create_info, vkt::no_mem);
VkMemoryRequirements memory_reqs;
vk::GetImageMemoryRequirements(device(), image, &memory_reqs);
VkMemoryAllocateInfo memory_info = vku::InitStructHelper();
memory_info.allocationSize = memory_reqs.size;
bool pass = m_device->Physical().SetMemoryType(memory_reqs.memoryTypeBits, &memory_info, 0);
ASSERT_TRUE(pass);
vkt::DeviceMemory memory(*m_device, memory_info);
VkSparseMemoryBind bind;
bind.flags = 0;
bind.memory = memory;
bind.memoryOffset = 0;
bind.resourceOffset = 0;
bind.size = memory_info.allocationSize;
VkSparseImageOpaqueMemoryBindInfo opaqueBindInfo;
opaqueBindInfo.image = image;
opaqueBindInfo.bindCount = 1;
opaqueBindInfo.pBinds = &bind;
VkBindSparseInfo bindSparseInfo = vku::InitStructHelper();
bindSparseInfo.imageOpaqueBindCount = 1;
bindSparseInfo.pImageOpaqueBinds = &opaqueBindInfo;
// Bind to the memory
vk::QueueBindSparse(m_default_queue->handle(), 1, &bindSparseInfo, VK_NULL_HANDLE);
// Bind back to NULL
bind.memory = VK_NULL_HANDLE;
vk::QueueBindSparse(m_default_queue->handle(), 1, &bindSparseInfo, VK_NULL_HANDLE);
m_default_queue->Wait();
// Free the memory, then use the image in a new command buffer
memory.Destroy();
m_command_buffer.Begin();
VkImageMemoryBarrier img_barrier = vku::InitStructHelper();
img_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
img_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
img_barrier.image = image;
img_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
img_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr,
0, nullptr, 1, &img_barrier);
const VkClearColorValue clear_color = {{0.0f, 0.0f, 0.0f, 1.0f}};
VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vk::CmdClearColorImage(m_command_buffer, image, VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &range);
m_command_buffer.End();
m_default_queue->Submit(m_command_buffer);
// Wait for operations to finish before destroying anything
m_default_queue->Wait();
}
TEST_F(PositiveSparseImage, BindMetadata) {
TEST_DESCRIPTION("Bind memory for the metadata aspect of a sparse image");
AddRequiredFeature(vkt::Feature::sparseBinding);
AddRequiredFeature(vkt::Feature::sparseResidencyImage2D);
RETURN_IF_SKIP(Init());
auto index = m_device->graphics_queue_node_index_;
if (!(m_device->Physical().queue_properties_[index].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT)) {
GTEST_SKIP() << "Graphics queue does not have sparse binding bit";
}
// Create a sparse image
VkImageCreateInfo image_create_info = vku::InitStructHelper();
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM;
image_create_info.extent = {64, 64, 1};
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
image_create_info.flags = VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
vkt::Image image(*m_device, image_create_info, vkt::no_mem);
// Query image memory requirements
VkMemoryRequirements memory_reqs;
vk::GetImageMemoryRequirements(device(), image, &memory_reqs);
// Query sparse memory requirements
uint32_t sparse_reqs_count = 0;
vk::GetImageSparseMemoryRequirements(device(), image, &sparse_reqs_count, nullptr);
std::vector<VkSparseImageMemoryRequirements> sparse_reqs(sparse_reqs_count);
vk::GetImageSparseMemoryRequirements(device(), image, &sparse_reqs_count, sparse_reqs.data());
// Find requirements for metadata aspect
const VkSparseImageMemoryRequirements *metadata_reqs = nullptr;
for (auto const &aspect_sparse_reqs : sparse_reqs) {
if ((aspect_sparse_reqs.formatProperties.aspectMask & VK_IMAGE_ASPECT_METADATA_BIT) != 0) {
metadata_reqs = &aspect_sparse_reqs;
}
}
if (!metadata_reqs) {
GTEST_SKIP() << "Sparse image does not require memory for metadata";
}
// Allocate memory for the metadata
VkMemoryAllocateInfo metadata_memory_info = vku::InitStructHelper();
metadata_memory_info.allocationSize = metadata_reqs->imageMipTailSize;
m_device->Physical().SetMemoryType(memory_reqs.memoryTypeBits, &metadata_memory_info, 0);
vkt::DeviceMemory metadata_memory(*m_device, metadata_memory_info);
// Bind metadata
VkSparseMemoryBind sparse_bind = {};
sparse_bind.resourceOffset = metadata_reqs->imageMipTailOffset;
sparse_bind.size = metadata_reqs->imageMipTailSize;
sparse_bind.memory = metadata_memory;
sparse_bind.memoryOffset = 0;
sparse_bind.flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT;
VkSparseImageOpaqueMemoryBindInfo opaque_bind_info = {};
opaque_bind_info.image = image;
opaque_bind_info.bindCount = 1;
opaque_bind_info.pBinds = &sparse_bind;
VkBindSparseInfo bind_info = vku::InitStructHelper();
bind_info.imageOpaqueBindCount = 1;
bind_info.pImageOpaqueBinds = &opaque_bind_info;
vk::QueueBindSparse(m_default_queue->handle(), 1, &bind_info, VK_NULL_HANDLE);
// Wait for operations to finish before destroying anything
m_default_queue->Wait();
}
TEST_F(PositiveSparseImage, OpImageSparse) {
TEST_DESCRIPTION("Use OpImageSparse* operations at draw time");
AddRequiredFeature(vkt::Feature::sparseBinding);
AddRequiredFeature(vkt::Feature::sparseResidencyImage2D);
AddRequiredFeature(vkt::Feature::shaderResourceResidency);
RETURN_IF_SKIP(Init());
auto index = m_device->graphics_queue_node_index_;
if (!(m_device->Physical().queue_properties_[index].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT)) {
GTEST_SKIP() << "Graphics queue does not have sparse binding bit";
}
InitRenderTarget();
VkImageCreateInfo image_create_info = vku::InitStructHelper();
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM;
image_create_info.extent = {64, 64, 1};
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
image_create_info.flags = VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
vkt::Image image(*m_device, image_create_info, vkt::no_mem);
vkt::ImageView image_view = image.CreateView();
vkt::Sampler sampler(*m_device, SafeSaneSamplerCreateInfo());
OneOffDescriptorSet ds(m_device, {
{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
});
ds.WriteDescriptorImageInfo(0, image_view, sampler, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
ds.UpdateDescriptorSets();
const char *fsSource = R"glsl(
#version 450
#extension GL_ARB_sparse_texture2 : enable
layout(set = 0, binding = 0) uniform sampler2D s2D;
layout(location = 0) out vec4 outColor;
void main() {
vec4 texel = vec4(1.0);
int resident = 0;
resident |= sparseTextureARB(s2D, vec2(0.0), texel);
resident |= sparseTextureLodARB(s2D, vec2(0.0), 2.0, texel);
resident |= sparseTexelFetchARB(s2D, ivec2(0), 2, texel);
outColor = sparseTexelsResidentARB(resident) ? vec4(0.0) : vec4(1.0);
}
)glsl";
VkShaderObj vs(*m_device, kVertexMinimalGlsl, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj fs(*m_device, fsSource, VK_SHADER_STAGE_FRAGMENT_BIT);
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&ds.layout_});
pipe.CreateGraphicsPipeline();
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe.pipeline_layout_, 0, 1, &ds.set_, 0, nullptr);
vk::CmdDraw(m_command_buffer, 3, 1, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
}
TEST_F(PositiveSparseImage, BindImage) {
AddRequiredFeature(vkt::Feature::sparseBinding);
AddRequiredFeature(vkt::Feature::sparseResidencyImage2D);
RETURN_IF_SKIP(Init());
if (m_device->QueuesWithSparseCapability().empty()) {
GTEST_SKIP() << "Required SPARSE_BINDING queue families not present";
}
VkImageCreateInfo image_create_info = vku::InitStructHelper();
image_create_info.flags = VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
image_create_info.extent = {512, 64, 1};
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
vkt::Image image(*m_device, image_create_info, vkt::no_mem);
VkSparseImageMemoryBind image_memory_bind = {};
image_memory_bind.subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_memory_bind.extent = image_create_info.extent;
VkSparseImageMemoryBindInfo image_memory_bind_info = {};
image_memory_bind_info.image = image;
image_memory_bind_info.bindCount = 1;
image_memory_bind_info.pBinds = &image_memory_bind;
VkBindSparseInfo bind_info = vku::InitStructHelper();
bind_info.imageBindCount = 1;
bind_info.pImageBinds = &image_memory_bind_info;
vkt::Queue *sparse_queue = m_device->QueuesWithSparseCapability()[0];
vk::QueueBindSparse(sparse_queue->handle(), 1, &bind_info, VK_NULL_HANDLE);
sparse_queue->Wait();
}