1 /* 2 * Copyright 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assume.assumeTrue; 24 25 import android.content.pm.FeatureInfo; 26 import android.content.pm.PackageManager; 27 import android.util.Log; 28 29 import androidx.test.InstrumentationRegistry; 30 import androidx.test.filters.SmallTest; 31 import androidx.test.runner.AndroidJUnit4; 32 33 import com.android.compatibility.common.util.CddTest; 34 import com.android.compatibility.common.util.PropertyUtil; 35 36 import org.json.JSONArray; 37 import org.json.JSONException; 38 import org.json.JSONObject; 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 43 /** 44 * Test that the Vulkan loader is present, supports the required extensions, and that system 45 * features accurately indicate the capabilities of the Vulkan driver if one exists. 46 */ 47 @SmallTest 48 @RunWith(AndroidJUnit4.class) 49 public class VulkanFeaturesTest { 50 51 static { 52 System.loadLibrary("ctsgraphics_jni"); 53 } 54 55 private static final String TAG = VulkanFeaturesTest.class.getSimpleName(); 56 private static final boolean DEBUG = false; 57 58 // Require patch version 3 for Vulkan 1.0: It was the first publicly available version, 59 // and there was an important bugfix relative to 1.0.2. 60 private static final int VULKAN_1_0 = 0x00400003; // 1.0.3 61 private static final int VULKAN_1_1 = 0x00401000; // 1.1.0 62 63 private static final String VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME = 64 "VK_ANDROID_external_memory_android_hardware_buffer"; 65 private static final int VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION = 2; 66 private static final int VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT = 0x8; 67 private static final int VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT = 0x10; 68 private static final int VK_PHYSICAL_DEVICE_TYPE_CPU = 4; 69 70 private static final int API_LEVEL_BEFORE_ANDROID_HARDWARE_BUFFER_REQ = 28; 71 72 private PackageManager mPm; 73 private FeatureInfo mVulkanHardwareLevel = null; 74 private FeatureInfo mVulkanHardwareVersion = null; 75 private FeatureInfo mVulkanHardwareCompute = null; 76 private JSONObject mVkJSON = null; 77 private JSONObject mVulkanDevices[]; 78 private JSONObject mBestDevice = null; 79 80 @Before setup()81 public void setup() throws Throwable { 82 mPm = InstrumentationRegistry.getTargetContext().getPackageManager(); 83 FeatureInfo features[] = mPm.getSystemAvailableFeatures(); 84 if (features != null) { 85 for (FeatureInfo feature : features) { 86 if (PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL.equals(feature.name)) { 87 mVulkanHardwareLevel = feature; 88 if (DEBUG) { 89 Log.d(TAG, feature.name + "=" + feature.version); 90 } 91 } else if (PackageManager.FEATURE_VULKAN_HARDWARE_VERSION.equals(feature.name)) { 92 mVulkanHardwareVersion = feature; 93 if (DEBUG) { 94 Log.d(TAG, feature.name + "=0x" + Integer.toHexString(feature.version)); 95 } 96 } else if (PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE.equals(feature.name)) { 97 mVulkanHardwareCompute = feature; 98 if (DEBUG) { 99 Log.d(TAG, feature.name + "=" + feature.version); 100 } 101 } 102 } 103 } 104 105 mVkJSON = new JSONObject(nativeGetVkJSON()); 106 mVulkanDevices = getVulkanDevices(mVkJSON); 107 mBestDevice = getBestDevice(); 108 } 109 @CddTest(requirement = "7.1.4.2/C-1-1,C-2-1") 110 @Test testVulkanHardwareFeatures()111 public void testVulkanHardwareFeatures() throws JSONException { 112 if (DEBUG) { 113 Log.d(TAG, "Inspecting " + mVulkanDevices.length + " devices"); 114 } 115 if (mVulkanDevices.length == 0) { 116 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 117 " is supported, but no Vulkan physical devices are available", 118 mVulkanHardwareLevel); 119 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 120 " is supported, but no Vulkan physical devices are available", 121 mVulkanHardwareLevel); 122 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 123 " is supported, but no Vulkan physical devices are available", 124 mVulkanHardwareCompute); 125 return; 126 } 127 128 if (hasOnlyCpuDevice()) { 129 return; 130 } 131 132 assertNotNull("Vulkan physical devices are available, but system feature " + 133 PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + " is not supported", 134 mVulkanHardwareLevel); 135 assertNotNull("Vulkan physical devices are available, but system feature " + 136 PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + " is not supported", 137 mVulkanHardwareVersion); 138 if (mVulkanHardwareLevel == null || mVulkanHardwareVersion == null) { 139 return; 140 } 141 142 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 143 " version " + mVulkanHardwareLevel.version + " is not one of the defined " + 144 " versions [0..1]", 145 mVulkanHardwareLevel.version >= 0 && mVulkanHardwareLevel.version <= 1); 146 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 147 " version 0x" + Integer.toHexString(mVulkanHardwareVersion.version) + " is not" + 148 " one of the versions allowed", 149 isHardwareVersionAllowed(mVulkanHardwareVersion.version)); 150 if (mVulkanHardwareCompute != null) { 151 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 152 " version " + mVulkanHardwareCompute.version + 153 " is not one of the versions allowed", 154 mVulkanHardwareCompute.version == 0); 155 } 156 157 int bestDeviceLevel = determineHardwareLevel(mBestDevice); 158 int bestComputeLevel = determineHardwareCompute(mBestDevice); 159 int bestDeviceVersion = determineHardwareVersion(mBestDevice); 160 161 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 162 " version " + mVulkanHardwareLevel.version + " doesn't match best physical device " + 163 " hardware level " + bestDeviceLevel, 164 bestDeviceLevel, mVulkanHardwareLevel.version); 165 assertTrue( 166 "System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 167 " version 0x" + Integer.toHexString(mVulkanHardwareVersion.version) + 168 " isn't close enough (same major and minor version, less or equal patch version)" + 169 " to best physical device version 0x" + Integer.toHexString(bestDeviceVersion), 170 isVersionCompatible(bestDeviceVersion, mVulkanHardwareVersion.version)); 171 if (mVulkanHardwareCompute == null) { 172 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 173 " not present, but required features are supported", 174 bestComputeLevel, -1); 175 } else { 176 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 177 " version " + mVulkanHardwareCompute.version + 178 " doesn't match best physical device (version: " + bestComputeLevel + ")", 179 bestComputeLevel, mVulkanHardwareCompute.version); 180 } 181 } 182 183 @CddTest(requirement = "7.1.4.2/C-3-1") 184 @Test testVulkan1_1Requirements()185 public void testVulkan1_1Requirements() throws JSONException { 186 if (mVulkanHardwareVersion == null || mVulkanHardwareVersion.version < VULKAN_1_1 187 || !PropertyUtil.isVendorApiLevelNewerThan( 188 API_LEVEL_BEFORE_ANDROID_HARDWARE_BUFFER_REQ)) { 189 return; 190 } 191 assertTrue("Devices with Vulkan 1.1 must support sampler YCbCr conversion", 192 mBestDevice.getJSONObject("samplerYcbcrConversionFeatures") 193 .getInt("samplerYcbcrConversion") != 0); 194 195 if (hasOnlyCpuDevice()) { 196 return; 197 } 198 assertTrue("Devices with Vulkan 1.1 must support " + 199 VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME + 200 " (version >= " + VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION + 201 ")", 202 hasDeviceExtension(mBestDevice, 203 VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, 204 VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION)); 205 assertTrue("Devices with Vulkan 1.1 must support SYNC_FD external semaphores", 206 hasHandleType(mBestDevice.getJSONArray("externalSemaphoreProperties"), 207 VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, 208 "externalSemaphoreFeatures", 0x3 /* importable + exportable */)); 209 assertTrue("Devices with Vulkan 1.1 must support SYNC_FD external fences", 210 hasHandleType(mBestDevice.getJSONArray("externalFenceProperties"), 211 VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT, 212 "externalFenceFeatures", 0x3 /* importable + exportable */)); 213 } 214 215 @CddTest(requirement = "7.1.4.2/C-1-7") 216 @Test testVulkanRequiredExtensions()217 public void testVulkanRequiredExtensions() throws JSONException { 218 assumeTrue("Skipping because Vulkan is not supported", mVulkanDevices.length > 0); 219 220 assertVulkanInstanceExtension("VK_KHR_surface", 25); 221 assertVulkanInstanceExtension("VK_KHR_android_surface", 6); 222 assertVulkanDeviceExtension("VK_KHR_swapchain", 68); 223 assertVulkanDeviceExtension("VK_KHR_incremental_present", 1); 224 } 225 226 @CddTest(requirement = "7.9.2/C-1-5") 227 @Test testVulkanVersionForVrHighPerformance()228 public void testVulkanVersionForVrHighPerformance() { 229 if (!mPm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) 230 return; 231 assertTrue( 232 "VR high-performance devices must support Vulkan 1.0 with Hardware Level 0, " + 233 "but this device does not.", 234 mVulkanHardwareVersion != null && mVulkanHardwareVersion.version >= VULKAN_1_0 && 235 mVulkanHardwareLevel != null && mVulkanHardwareLevel.version >= 0); 236 } 237 238 @CddTest(requirement = "7.1.4.2/C-1-11") 239 @Test testVulkanBlockedExtensions()240 public void testVulkanBlockedExtensions() throws JSONException { 241 assertNoVulkanDeviceExtension("VK_KHR_performance_query"); 242 assertNoVulkanDeviceExtension("VK_KHR_video_queue"); 243 assertNoVulkanDeviceExtension("VK_KHR_video_decode_queue"); 244 assertNoVulkanDeviceExtension("VK_KHR_video_encode_queue"); 245 } 246 getBestDevice()247 private JSONObject getBestDevice() throws JSONException { 248 JSONObject bestDevice = null; 249 int bestDeviceLevel = -1; 250 int bestComputeLevel = -1; 251 int bestDeviceVersion = -1; 252 for (JSONObject device : mVulkanDevices) { 253 int level = determineHardwareLevel(device); 254 int compute = determineHardwareCompute(device); 255 int version = determineHardwareVersion(device); 256 if (DEBUG) { 257 Log.d(TAG, device.getJSONObject("properties").getString("deviceName") + 258 ": level=" + level + " compute=" + compute + 259 " version=0x" + Integer.toHexString(version)); 260 } 261 if (level >= bestDeviceLevel && compute >= bestComputeLevel && 262 version >= bestDeviceVersion) { 263 bestDevice = device; 264 bestDeviceLevel = level; 265 bestComputeLevel = compute; 266 bestDeviceVersion = version; 267 } 268 } 269 return bestDevice; 270 } 271 hasOnlyCpuDevice()272 private boolean hasOnlyCpuDevice() throws JSONException { 273 for (JSONObject device : mVulkanDevices) { 274 if (device.getJSONObject("properties").getInt("deviceType") 275 != VK_PHYSICAL_DEVICE_TYPE_CPU) { 276 return false; 277 } 278 } 279 return true; 280 } 281 determineHardwareLevel(JSONObject device)282 private int determineHardwareLevel(JSONObject device) throws JSONException { 283 JSONObject features = device.getJSONObject("features"); 284 boolean textureCompressionETC2 = features.getInt("textureCompressionETC2") != 0; 285 boolean fullDrawIndexUint32 = features.getInt("fullDrawIndexUint32") != 0; 286 boolean imageCubeArray = features.getInt("imageCubeArray") != 0; 287 boolean independentBlend = features.getInt("independentBlend") != 0; 288 boolean geometryShader = features.getInt("geometryShader") != 0; 289 boolean tessellationShader = features.getInt("tessellationShader") != 0; 290 boolean sampleRateShading = features.getInt("sampleRateShading") != 0; 291 boolean textureCompressionASTC_LDR = features.getInt("textureCompressionASTC_LDR") != 0; 292 boolean fragmentStoresAndAtomics = features.getInt("fragmentStoresAndAtomics") != 0; 293 boolean shaderImageGatherExtended = features.getInt("shaderImageGatherExtended") != 0; 294 boolean shaderUniformBufferArrayDynamicIndexing = features.getInt("shaderUniformBufferArrayDynamicIndexing") != 0; 295 boolean shaderSampledImageArrayDynamicIndexing = features.getInt("shaderSampledImageArrayDynamicIndexing") != 0; 296 if (!textureCompressionETC2) { 297 return -1; 298 } 299 if (!fullDrawIndexUint32 || 300 !imageCubeArray || 301 !independentBlend || 302 !geometryShader || 303 !tessellationShader || 304 !sampleRateShading || 305 !textureCompressionASTC_LDR || 306 !fragmentStoresAndAtomics || 307 !shaderImageGatherExtended || 308 !shaderUniformBufferArrayDynamicIndexing || 309 !shaderSampledImageArrayDynamicIndexing) { 310 return 0; 311 } 312 return 1; 313 } 314 determineHardwareCompute(JSONObject device)315 private int determineHardwareCompute(JSONObject device) throws JSONException { 316 boolean variablePointers = false; 317 try { 318 variablePointers = device.getJSONObject("variablePointerFeatures") 319 .getInt("variablePointers") != 0; 320 } catch (JSONException exp) { 321 try { 322 variablePointers = device.getJSONObject("VK_KHR_variable_pointers") 323 .getJSONObject("variablePointerFeaturesKHR") 324 .getInt("variablePointers") != 0; 325 } catch (JSONException exp2) { 326 variablePointers = false; 327 } 328 } 329 JSONObject limits = device.getJSONObject("properties").getJSONObject("limits"); 330 int maxPerStageDescriptorStorageBuffers = limits.getInt("maxPerStageDescriptorStorageBuffers"); 331 if (DEBUG) { 332 Log.d(TAG, device.getJSONObject("properties").getString("deviceName") + 333 ": variablePointers=" + variablePointers + 334 " maxPerStageDescriptorStorageBuffers=" + maxPerStageDescriptorStorageBuffers); 335 } 336 if (!variablePointers || maxPerStageDescriptorStorageBuffers < 16) 337 return -1; 338 return 0; 339 } 340 determineHardwareVersion(JSONObject device)341 private int determineHardwareVersion(JSONObject device) throws JSONException { 342 return device.getJSONObject("properties").getInt("apiVersion"); 343 } 344 isVersionCompatible(int expected, int actual)345 private boolean isVersionCompatible(int expected, int actual) { 346 int expectedMajor = (expected >> 22) & 0x3FF; 347 int expectedMinor = (expected >> 12) & 0x3FF; 348 int expectedPatch = (expected >> 0) & 0xFFF; 349 int actualMajor = (actual >> 22) & 0x3FF; 350 int actualMinor = (actual >> 12) & 0x3FF; 351 int actualPatch = (actual >> 0) & 0xFFF; 352 return (actualMajor == expectedMajor) && 353 (actualMinor == expectedMinor) && 354 (actualPatch <= expectedPatch); 355 } 356 isHardwareVersionAllowed(int actual)357 private boolean isHardwareVersionAllowed(int actual) { 358 // Limit which system feature hardware versions are allowed. If a new major/minor version 359 // is released, we don't want devices claiming support for it until tests for the new 360 // version are available. And only claiming support for a base patch level per major/minor 361 // pair reduces fragmentation seen by developers. Patch-level changes are supposed to be 362 // forwards and backwards compatible; if a developer *really* needs to alter behavior based 363 // on the patch version, they can do so at runtime, but must be able to handle previous 364 // patch versions. 365 final int[] ALLOWED_HARDWARE_VERSIONS = { 366 VULKAN_1_0, 367 VULKAN_1_1, 368 }; 369 for (int expected : ALLOWED_HARDWARE_VERSIONS) { 370 if (actual == expected) { 371 return true; 372 } 373 } 374 return false; 375 } 376 assertVulkanDeviceExtension(final String name, final int minVersion)377 private void assertVulkanDeviceExtension(final String name, final int minVersion) 378 throws JSONException { 379 assertTrue( 380 String.format( 381 "Devices with Vulkan must support device extension %s (version >= %d)", 382 name, 383 minVersion), 384 hasDeviceExtension(mBestDevice, name, minVersion)); 385 } 386 assertNoVulkanDeviceExtension(final String name)387 private void assertNoVulkanDeviceExtension(final String name) 388 throws JSONException { 389 for (JSONObject device : mVulkanDevices) { 390 assertTrue( 391 String.format("Devices must not support Vulkan device extension %s", name), 392 !hasDeviceExtension(device, name, 0)); 393 } 394 } 395 assertVulkanInstanceExtension(final String name, final int minVersion)396 private void assertVulkanInstanceExtension(final String name, final int minVersion) 397 throws JSONException { 398 assertTrue( 399 String.format( 400 "Devices with Vulkan must support instance extension %s (version >= %d)", 401 name, 402 minVersion), 403 hasInstanceExtension(name, minVersion)); 404 } 405 hasDeviceExtension( final JSONObject device, final String name, final int minVersion)406 private static boolean hasDeviceExtension( 407 final JSONObject device, 408 final String name, 409 final int minVersion) throws JSONException { 410 final JSONArray deviceExtensions = device.getJSONArray("extensions"); 411 return hasExtension(deviceExtensions, name, minVersion); 412 } 413 hasInstanceExtension( final String name, final int minVersion)414 private boolean hasInstanceExtension( 415 final String name, 416 final int minVersion) throws JSONException { 417 // Instance extensions are in the top-level vkjson object. 418 final JSONArray instanceExtensions = mVkJSON.getJSONArray("extensions"); 419 return hasExtension(instanceExtensions, name, minVersion); 420 } 421 hasExtension( final JSONArray extensions, final String name, final int minVersion)422 private static boolean hasExtension( 423 final JSONArray extensions, 424 final String name, 425 final int minVersion) throws JSONException { 426 for (int i = 0; i < extensions.length(); i++) { 427 JSONObject ext = extensions.getJSONObject(i); 428 if (ext.getString("extensionName").equals(name) && 429 ext.getInt("specVersion") >= minVersion) 430 return true; 431 } 432 return false; 433 } 434 hasHandleType(JSONArray handleTypes, int type, String featuresName, int requiredFeatures)435 private boolean hasHandleType(JSONArray handleTypes, int type, 436 String featuresName, int requiredFeatures) throws JSONException { 437 for (int i = 0; i < handleTypes.length(); i++) { 438 JSONArray typeRecord = handleTypes.getJSONArray(i); 439 if (typeRecord.getInt(0) == type) { 440 JSONObject typeInfo = typeRecord.getJSONObject(1); 441 if ((typeInfo.getInt(featuresName) & requiredFeatures) == requiredFeatures) 442 return true; 443 } 444 } 445 return false; 446 } 447 nativeGetVkJSON()448 private static native String nativeGetVkJSON(); 449 getVulkanDevices(final JSONObject vkJSON)450 private static JSONObject[] getVulkanDevices(final JSONObject vkJSON) throws JSONException { 451 JSONArray devicesArray = vkJSON.getJSONArray("devices"); 452 JSONObject[] devices = new JSONObject[devicesArray.length()]; 453 for (int i = 0; i < devicesArray.length(); i++) { 454 devices[i] = devicesArray.getJSONObject(i); 455 } 456 return devices; 457 } 458 } 459