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