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 24 import android.content.pm.FeatureInfo; 25 import android.content.pm.PackageManager; 26 import android.support.test.InstrumentationRegistry; 27 import android.support.test.filters.SmallTest; 28 import android.support.test.runner.AndroidJUnit4; 29 import android.util.Log; 30 31 import org.json.JSONArray; 32 import org.json.JSONException; 33 import org.json.JSONObject; 34 import org.junit.Before; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 38 import java.io.UnsupportedEncodingException; 39 import com.android.compatibility.common.util.CddTest; 40 41 /** 42 * Test that the Vulkan loader is present, supports the required extensions, and that system 43 * features accurately indicate the capabilities of the Vulkan driver if one exists. 44 */ 45 @SmallTest 46 @RunWith(AndroidJUnit4.class) 47 public class VulkanFeaturesTest { 48 49 static { 50 System.loadLibrary("ctsgraphics_jni"); 51 } 52 53 private static final String TAG = VulkanFeaturesTest.class.getSimpleName(); 54 private static final boolean DEBUG = false; 55 56 // Require patch version 3 for Vulkan 1.0: It was the first publicly available version, 57 // and there was an important bugfix relative to 1.0.2. 58 private static final int VULKAN_1_0 = 0x00400003; // 1.0.3 59 private static final int VULKAN_1_1 = 0x00401000; // 1.1.0 60 61 private static final String VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME = 62 "VK_ANDROID_external_memory_android_hardware_buffer"; 63 private static final int VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION = 2; 64 private static final int VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT = 0x8; 65 private static final int VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT = 0x10; 66 67 private PackageManager mPm; 68 private FeatureInfo mVulkanHardwareLevel = null; 69 private FeatureInfo mVulkanHardwareVersion = null; 70 private FeatureInfo mVulkanHardwareCompute = null; 71 private JSONObject mVulkanDevices[]; 72 private JSONObject mBestDevice = null; 73 74 @Before 75 public void setup() throws Throwable { 76 mPm = InstrumentationRegistry.getTargetContext().getPackageManager(); 77 FeatureInfo features[] = mPm.getSystemAvailableFeatures(); 78 if (features != null) { 79 for (FeatureInfo feature : features) { 80 if (PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL.equals(feature.name)) { 81 mVulkanHardwareLevel = feature; 82 if (DEBUG) { 83 Log.d(TAG, feature.name + "=" + feature.version); 84 } 85 } else if (PackageManager.FEATURE_VULKAN_HARDWARE_VERSION.equals(feature.name)) { 86 mVulkanHardwareVersion = feature; 87 if (DEBUG) { 88 Log.d(TAG, feature.name + "=0x" + Integer.toHexString(feature.version)); 89 } 90 } else if (PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE.equals(feature.name)) { 91 mVulkanHardwareCompute = feature; 92 if (DEBUG) { 93 Log.d(TAG, feature.name + "=" + feature.version); 94 } 95 } 96 } 97 } 98 99 mVulkanDevices = getVulkanDevices(); 100 mBestDevice = getBestDevice(); 101 } 102 @CddTest(requirement="7.1.4.2/C-1-1,C-2-1") 103 @Test 104 public void testVulkanHardwareFeatures() throws JSONException { 105 if (DEBUG) { 106 Log.d(TAG, "Inspecting " + mVulkanDevices.length + " devices"); 107 } 108 if (mVulkanDevices.length == 0) { 109 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 110 " is supported, but no Vulkan physical devices are available", 111 mVulkanHardwareLevel); 112 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 113 " is supported, but no Vulkan physical devices are available", 114 mVulkanHardwareLevel); 115 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 116 " is supported, but no Vulkan physical devices are available", 117 mVulkanHardwareCompute); 118 return; 119 } 120 assertNotNull("Vulkan physical devices are available, but system feature " + 121 PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + " is not supported", 122 mVulkanHardwareLevel); 123 assertNotNull("Vulkan physical devices are available, but system feature " + 124 PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + " is not supported", 125 mVulkanHardwareVersion); 126 if (mVulkanHardwareLevel == null || mVulkanHardwareVersion == null) { 127 return; 128 } 129 130 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 131 " version " + mVulkanHardwareLevel.version + " is not one of the defined " + 132 " versions [0..1]", 133 mVulkanHardwareLevel.version >= 0 && mVulkanHardwareLevel.version <= 1); 134 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 135 " version 0x" + Integer.toHexString(mVulkanHardwareVersion.version) + " is not" + 136 " one of the versions allowed", 137 isHardwareVersionAllowed(mVulkanHardwareVersion.version)); 138 if (mVulkanHardwareCompute != null) { 139 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 140 " version " + mVulkanHardwareCompute.version + 141 " is not one of the versions allowed", 142 mVulkanHardwareCompute.version == 0); 143 } 144 145 int bestDeviceLevel = determineHardwareLevel(mBestDevice); 146 int bestComputeLevel = determineHardwareCompute(mBestDevice); 147 int bestDeviceVersion = determineHardwareVersion(mBestDevice); 148 149 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 150 " version " + mVulkanHardwareLevel.version + " doesn't match best physical device " + 151 " hardware level " + bestDeviceLevel, 152 bestDeviceLevel, mVulkanHardwareLevel.version); 153 assertTrue( 154 "System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 155 " version 0x" + Integer.toHexString(mVulkanHardwareVersion.version) + 156 " isn't close enough (same major and minor version, less or equal patch version)" + 157 " to best physical device version 0x" + Integer.toHexString(bestDeviceVersion), 158 isVersionCompatible(bestDeviceVersion, mVulkanHardwareVersion.version)); 159 if (mVulkanHardwareCompute == null) { 160 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 161 " not present, but required features are supported", 162 bestComputeLevel, -1); 163 } else { 164 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 165 " version " + mVulkanHardwareCompute.version + 166 " doesn't match best physical device (version: " + bestComputeLevel + ")", 167 bestComputeLevel, mVulkanHardwareCompute.version); 168 } 169 } 170 171 @CddTest(requirement="7.9.2/C-1-5") 172 @Test 173 public void testVulkan1_1Requirements() throws JSONException { 174 if (mVulkanHardwareVersion == null || mVulkanHardwareVersion.version < VULKAN_1_1) 175 return; 176 assertTrue("Devices with Vulkan 1.1 must support SYNC_FD external semaphores", 177 hasHandleType(mBestDevice.getJSONArray("externalSemaphoreProperties"), 178 VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, 179 "externalSemaphoreFeatures", 0x3 /* importable + exportable */)); 180 assertTrue("Devices with Vulkan 1.1 must support SYNC_FD external fences", 181 hasHandleType(mBestDevice.getJSONArray("externalFenceProperties"), 182 VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT, 183 "externalFenceFeatures", 0x3 /* importable + exportable */)); 184 assertTrue("Devices with Vulkan 1.1 must support sampler YCbCr conversion", 185 mBestDevice.getJSONObject("samplerYcbcrConversionFeatures") 186 .getInt("samplerYcbcrConversion") != 0); 187 } 188 189 @Test 190 public void testVulkanVersionForVrHighPerformance() { 191 if (!mPm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) 192 return; 193 assertTrue( 194 "VR high-performance devices must support Vulkan 1.0 with Hardware Level 0, " + 195 "but this device does not.", 196 mVulkanHardwareVersion != null && mVulkanHardwareVersion.version >= VULKAN_1_0 && 197 mVulkanHardwareLevel != null && mVulkanHardwareLevel.version >= 0); 198 } 199 200 private JSONObject getBestDevice() throws JSONException { 201 JSONObject bestDevice = null; 202 int bestDeviceLevel = -1; 203 int bestComputeLevel = -1; 204 int bestDeviceVersion = -1; 205 for (JSONObject device : mVulkanDevices) { 206 int level = determineHardwareLevel(device); 207 int compute = determineHardwareCompute(device); 208 int version = determineHardwareVersion(device); 209 if (DEBUG) { 210 Log.d(TAG, device.getJSONObject("properties").getString("deviceName") + 211 ": level=" + level + " compute=" + compute + 212 " version=0x" + Integer.toHexString(version)); 213 } 214 if (level >= bestDeviceLevel && compute >= bestComputeLevel && 215 version >= bestDeviceVersion) { 216 bestDevice = device; 217 bestDeviceLevel = level; 218 bestComputeLevel = compute; 219 bestDeviceVersion = version; 220 } 221 } 222 return bestDevice; 223 } 224 225 private int determineHardwareLevel(JSONObject device) throws JSONException { 226 JSONObject features = device.getJSONObject("features"); 227 boolean textureCompressionETC2 = features.getInt("textureCompressionETC2") != 0; 228 boolean fullDrawIndexUint32 = features.getInt("fullDrawIndexUint32") != 0; 229 boolean imageCubeArray = features.getInt("imageCubeArray") != 0; 230 boolean independentBlend = features.getInt("independentBlend") != 0; 231 boolean geometryShader = features.getInt("geometryShader") != 0; 232 boolean tessellationShader = features.getInt("tessellationShader") != 0; 233 boolean sampleRateShading = features.getInt("sampleRateShading") != 0; 234 boolean textureCompressionASTC_LDR = features.getInt("textureCompressionASTC_LDR") != 0; 235 boolean fragmentStoresAndAtomics = features.getInt("fragmentStoresAndAtomics") != 0; 236 boolean shaderImageGatherExtended = features.getInt("shaderImageGatherExtended") != 0; 237 boolean shaderUniformBufferArrayDynamicIndexing = features.getInt("shaderUniformBufferArrayDynamicIndexing") != 0; 238 boolean shaderSampledImageArrayDynamicIndexing = features.getInt("shaderSampledImageArrayDynamicIndexing") != 0; 239 if (!textureCompressionETC2) { 240 return -1; 241 } 242 if (!fullDrawIndexUint32 || 243 !imageCubeArray || 244 !independentBlend || 245 !geometryShader || 246 !tessellationShader || 247 !sampleRateShading || 248 !textureCompressionASTC_LDR || 249 !fragmentStoresAndAtomics || 250 !shaderImageGatherExtended || 251 !shaderUniformBufferArrayDynamicIndexing || 252 !shaderSampledImageArrayDynamicIndexing) { 253 return 0; 254 } 255 return 1; 256 } 257 258 private int determineHardwareCompute(JSONObject device) throws JSONException { 259 boolean variablePointers = device.getJSONObject("VK_KHR_variable_pointers") 260 .getJSONObject("variablePointerFeaturesKHR") 261 .getInt("variablePointers") != 0; 262 JSONObject limits = device.getJSONObject("properties").getJSONObject("limits"); 263 int maxPerStageDescriptorStorageBuffers = limits.getInt("maxPerStageDescriptorStorageBuffers"); 264 if (DEBUG) { 265 Log.d(TAG, device.getJSONObject("properties").getString("deviceName") + 266 ": variablePointers=" + variablePointers + 267 " maxPerStageDescriptorStorageBuffers=" + maxPerStageDescriptorStorageBuffers); 268 } 269 if (!variablePointers || maxPerStageDescriptorStorageBuffers < 16) 270 return -1; 271 return 0; 272 } 273 274 private int determineHardwareVersion(JSONObject device) throws JSONException { 275 return device.getJSONObject("properties").getInt("apiVersion"); 276 } 277 278 private boolean isVersionCompatible(int expected, int actual) { 279 int expectedMajor = (expected >> 22) & 0x3FF; 280 int expectedMinor = (expected >> 12) & 0x3FF; 281 int expectedPatch = (expected >> 0) & 0xFFF; 282 int actualMajor = (actual >> 22) & 0x3FF; 283 int actualMinor = (actual >> 12) & 0x3FF; 284 int actualPatch = (actual >> 0) & 0xFFF; 285 return (actualMajor == expectedMajor) && 286 (actualMinor == expectedMinor) && 287 (actualPatch <= expectedPatch); 288 } 289 290 private boolean isHardwareVersionAllowed(int actual) { 291 // Limit which system feature hardware versions are allowed. If a new major/minor version 292 // is released, we don't want devices claiming support for it until tests for the new 293 // version are available. And only claiming support for a base patch level per major/minor 294 // pair reduces fragmentation seen by developers. Patch-level changes are supposed to be 295 // forwards and backwards compatible; if a developer *really* needs to alter behavior based 296 // on the patch version, they can do so at runtime, but must be able to handle previous 297 // patch versions. 298 final int[] ALLOWED_HARDWARE_VERSIONS = { 299 VULKAN_1_0, 300 VULKAN_1_1, 301 }; 302 for (int expected : ALLOWED_HARDWARE_VERSIONS) { 303 if (actual == expected) { 304 return true; 305 } 306 } 307 return false; 308 } 309 310 private boolean hasExtension(JSONObject device, String name, int minVersion) 311 throws JSONException { 312 JSONArray extensions = device.getJSONArray("extensions"); 313 for (int i = 0; i < extensions.length(); i++) { 314 JSONObject ext = extensions.getJSONObject(i); 315 if (ext.getString("extensionName").equals(name) && 316 ext.getInt("specVersion") >= minVersion) 317 return true; 318 } 319 return false; 320 } 321 322 private boolean hasHandleType(JSONArray handleTypes, int type, 323 String featuresName, int requiredFeatures) throws JSONException { 324 for (int i = 0; i < handleTypes.length(); i++) { 325 JSONArray typeRecord = handleTypes.getJSONArray(i); 326 if (typeRecord.getInt(0) == type) { 327 JSONObject typeInfo = typeRecord.getJSONObject(1); 328 if ((typeInfo.getInt(featuresName) & requiredFeatures) == requiredFeatures) 329 return true; 330 } 331 } 332 return false; 333 } 334 335 private static native String nativeGetVkJSON(); 336 337 private JSONObject[] getVulkanDevices() throws JSONException, UnsupportedEncodingException { 338 JSONArray vkjson = (new JSONObject(nativeGetVkJSON())).getJSONArray("devices"); 339 JSONObject[] devices = new JSONObject[vkjson.length()]; 340 for (int i = 0; i < vkjson.length(); i++) { 341 devices[i] = vkjson.getJSONObject(i); 342 } 343 return devices; 344 } 345 } 346