1 // copyright (c) 2023 the android open source project
2 //
3 // licensed under the apache license, version 2.0 (the "license");
4 // you may not use this file except in compliance with the license.
5 // you may obtain a copy of the license at
6 //
7 // http://www.apache.org/licenses/license-2.0
8 //
9 // unless required by applicable law or agreed to in writing, software
10 // distributed under the license is distributed on an "as is" basis,
11 // without warranties or conditions of any kind, either express or implied.
12 // see the license for the specific language governing permissions and
13 // limitations under the license.
14
15 // This is an integration test that verifies that the compute pipeline is correctly restored after
16 // we perform texture decompression.
17
18 #include <gmock/gmock.h>
19
20 #include <cstdint>
21 #include <vector>
22
23 #include "stream-servers/vulkan/testing/VulkanTestHelper.h"
24
25 namespace goldfish_vk::testing {
26 namespace {
27
28 // Compiled code for the following shader:
29 //
30 // #version 450
31 // void main() {}
32 inline constexpr uint32_t emptyShader[] = {
33 0x07230203, 0x00010300, 0x000d000b, 0x00000006, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
34 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
35 0x0005000f, 0x00000005, 0x00000004, 0x6e69616d, 0x00000000, 0x00060010, 0x00000004, 0x00000011,
36 0x00000001, 0x00000001, 0x00000001, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47,
37 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576,
38 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669,
39 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003,
40 0x00000002, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005,
41 0x000100fd, 0x00010038};
42
43 class ComputePipelineRestorationTest : public ::testing::Test {
44 protected:
SetUp()45 void SetUp() override {
46 vkTest = std::make_unique<VulkanTestHelper>();
47 vkTest->failOnValidationErrors(false);
48 vkTest->initialize(
49 {.astcLdrEmulationMode = AstcEmulationMode::Gpu,
50 .appInfo = VkApplicationInfo{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
51 .pApplicationName = "pipeline_restoration_test",
52 .pEngineName = "pipeline_restoration_test",
53 .apiVersion = VK_API_VERSION_1_1},
54 .deviceFeatures = {
55 .textureCompressionASTC_LDR = true,
56 }});
57
58 vk = &vkTest->vk();
59 device = vkTest->device();
60 }
61
62 // Tears the test down, and make sure there are no validation errors
TearDown()63 void TearDown() override {
64 vk->vkDestroyImage(device, image, nullptr);
65 vk->vkFreeMemory(device, imageMemory, nullptr);
66
67 vkTest->destroy();
68 ASSERT_FALSE(vkTest->hasValidationErrors());
69 vkTest.reset();
70 vk = nullptr;
71 device = VK_NULL_HANDLE;
72 }
73
74 // Creates an arbitrary 128x128 ASTC image
createImage()75 void createImage() {
76 VkImageCreateInfo imageInfo = {
77 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
78 .imageType = VK_IMAGE_TYPE_2D,
79 .format = VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
80 .extent = {128, 128, 1},
81 .mipLevels = 1,
82 .arrayLayers = 1,
83 .samples = VK_SAMPLE_COUNT_1_BIT,
84 .tiling = VK_IMAGE_TILING_OPTIMAL,
85 .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
86 VK_IMAGE_USAGE_SAMPLED_BIT,
87 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
88 };
89 VK_CHECK(vk->vkCreateImage(device, &imageInfo, nullptr, &image));
90
91 VkMemoryRequirements memRequirements;
92 vk->vkGetImageMemoryRequirements(device, image, &memRequirements);
93
94 VkMemoryAllocateInfo allocInfo = {
95 .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
96 .allocationSize = memRequirements.size,
97 .memoryTypeIndex = vkTest->findMemoryType(memRequirements.memoryTypeBits,
98 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
99 };
100 VK_CHECK(vk->vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory));
101 VK_CHECK(vk->vkBindImageMemory(device, image, imageMemory, 0));
102 }
103
104 // Tries to decompress an image, which will trigger the pipeline restoration mechanism
decompressImage(VkCommandBuffer cmdBuf)105 void decompressImage(VkCommandBuffer cmdBuf) {
106 if (image == VK_NULL_HANDLE) {
107 createImage();
108 }
109
110 // Transition the image to TRANSFER_DST, pretend we write some data to it
111 vkTest->transitionImageLayout(cmdBuf, image, VK_IMAGE_LAYOUT_UNDEFINED,
112 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
113 // Then transition to SHADER_READ, as if we were going to read from it. This will trigger
114 // the decompression code
115 vkTest->transitionImageLayout(cmdBuf, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
116 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
117 }
118
119 std::unique_ptr<VulkanTestHelper> vkTest;
120 VkDecoderTestDispatch* vk = nullptr;
121 VkDevice device = VK_NULL_HANDLE;
122 VkImage image = VK_NULL_HANDLE;
123 VkDeviceMemory imageMemory = VK_NULL_HANDLE;
124 };
125
TEST_F(ComputePipelineRestorationTest,ShouldNotRestoreDestroyedPipeline)126 TEST_F(ComputePipelineRestorationTest, ShouldNotRestoreDestroyedPipeline) {
127 // Create the shader module
128 VkShaderModuleCreateInfo shaderInfo = {
129 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
130 .codeSize = sizeof(emptyShader),
131 .pCode = emptyShader,
132 };
133 VkShaderModule shader;
134 VK_CHECK(vk->vkCreateShaderModule(device, &shaderInfo, nullptr, &shader));
135
136 // Create the descriptor set layout. The shader has no bindings.
137 VkDescriptorSetLayoutCreateInfo dsLayoutInfo = {
138 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
139 };
140 VkDescriptorSetLayout descriptorSetLayout;
141 VK_CHECK(vk->vkCreateDescriptorSetLayout(device, &dsLayoutInfo, nullptr, &descriptorSetLayout));
142 VkDescriptorSetLayout unboxedDSLayout = unbox_VkDescriptorSetLayout(descriptorSetLayout);
143
144 // Create the pipeline layout
145 VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
146 .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
147 .setLayoutCount = 1,
148 .pSetLayouts = &unboxedDSLayout,
149 };
150 VkPipelineLayout pipelineLayout;
151 VK_CHECK(vk->vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout));
152
153 // Create the compute pipeline
154 VkComputePipelineCreateInfo computePipelineInfo = {
155 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
156 .stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
157 .stage = VK_SHADER_STAGE_COMPUTE_BIT,
158 .module = unbox_VkShaderModule(shader),
159 .pName = "main"},
160 .layout = unbox_VkPipelineLayout(pipelineLayout),
161 };
162 VkPipeline pipeline;
163 VK_CHECK(
164 vk->vkCreateComputePipelines(device, nullptr, 1, &computePipelineInfo, nullptr, &pipeline));
165 vk->vkDestroyShaderModule(device, shader, nullptr);
166
167 // Create a command buffer
168 VkCommandBufferAllocateInfo allocInfo = {
169 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
170 .commandPool = unbox_VkCommandPool(vkTest->commandPool()),
171 .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
172 .commandBufferCount = 1,
173 };
174 VkCommandBuffer cmdBuf;
175 vk->vkAllocateCommandBuffers(device, &allocInfo, &cmdBuf);
176 auto unboxedCmdBuf = unbox_VkCommandBuffer(cmdBuf);
177
178 // Begin command buffer and bind the pipeline to it
179 VkCommandBufferBeginInfo beginInfo = {
180 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
181 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
182 };
183 vk->vkBeginCommandBuffer(cmdBuf, &beginInfo);
184 vk->vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
185 vk->vkEndCommandBuffer(cmdBuf);
186
187 // Submit the command buffer
188 VkSubmitInfo submitInfo = {
189 .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
190 .commandBufferCount = 1,
191 .pCommandBuffers = &unboxedCmdBuf,
192 };
193 vk->vkQueueSubmit(vkTest->graphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE);
194 vk->vkQueueWaitIdle(vkTest->graphicsQueue());
195
196 // Now destroy the pipeline. This is legal since the command buffer is fully submitted.
197 vk->vkDestroyPipeline(device, pipeline, nullptr);
198 vk->vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
199
200 // Now begin the command buffer again
201 vk->vkBeginCommandBuffer(cmdBuf, &beginInfo);
202
203 // If we do image decompression now, it should not try to restore the now-destroyed pipeline
204 decompressImage(cmdBuf);
205
206 // Submit the command buffer and destroy everything
207 vk->vkEndCommandBuffer(cmdBuf);
208 vk->vkQueueSubmit(vkTest->graphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE);
209 vk->vkQueueWaitIdle(vkTest->graphicsQueue());
210 vk->vkFreeCommandBuffers(device, vkTest->commandPool(), 1, &unboxedCmdBuf);
211 vk->vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
212 }
213
214 } // namespace
215 } // namespace goldfish_vk::testing