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 40 /** 41 * Test that the Vulkan loader is present, supports the required extensions, and that system 42 * features accurately indicate the capabilities of the Vulkan driver if one exists. 43 */ 44 @SmallTest 45 @RunWith(AndroidJUnit4.class) 46 public class VulkanFeaturesTest { 47 48 static { 49 System.loadLibrary("ctsgraphics_jni"); 50 } 51 52 private static final String TAG = VulkanFeaturesTest.class.getSimpleName(); 53 private static final boolean DEBUG = false; 54 55 // Require patch version 3 for Vulkan 1.0: It was the first publicly available version, 56 // and there was an important bugfix relative to 1.0.2. 57 private static final int VULKAN_1_0 = 0x00400003; // 1.0.3 58 59 private PackageManager mPm; 60 private FeatureInfo mVulkanHardwareLevel = null; 61 private FeatureInfo mVulkanHardwareVersion = null; 62 private FeatureInfo mVulkanHardwareCompute = null; 63 private JSONObject mVulkanDevices[]; 64 65 @Before 66 public void setup() throws Throwable { 67 mPm = InstrumentationRegistry.getTargetContext().getPackageManager(); 68 FeatureInfo features[] = mPm.getSystemAvailableFeatures(); 69 if (features != null) { 70 for (FeatureInfo feature : features) { 71 if (PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL.equals(feature.name)) { 72 mVulkanHardwareLevel = feature; 73 if (DEBUG) { 74 Log.d(TAG, feature.name + "=" + feature.version); 75 } 76 } else if (PackageManager.FEATURE_VULKAN_HARDWARE_VERSION.equals(feature.name)) { 77 mVulkanHardwareVersion = feature; 78 if (DEBUG) { 79 Log.d(TAG, feature.name + "=0x" + Integer.toHexString(feature.version)); 80 } 81 } else if (PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE.equals(feature.name)) { 82 mVulkanHardwareCompute = feature; 83 if (DEBUG) { 84 Log.d(TAG, feature.name + "=" + feature.version); 85 } 86 } 87 } 88 } 89 90 mVulkanDevices = getVulkanDevices(); 91 } 92 93 @Test 94 public void testVulkanHardwareFeatures() throws JSONException { 95 if (DEBUG) { 96 Log.d(TAG, "Inspecting " + mVulkanDevices.length + " devices"); 97 } 98 if (mVulkanDevices.length == 0) { 99 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 100 " is supported, but no Vulkan physical devices are available", 101 mVulkanHardwareLevel); 102 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 103 " is supported, but no Vulkan physical devices are available", 104 mVulkanHardwareLevel); 105 assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 106 " is supported, but no Vulkan physical devices are available", 107 mVulkanHardwareCompute); 108 return; 109 } 110 assertNotNull("Vulkan physical devices are available, but system feature " + 111 PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + " is not supported", 112 mVulkanHardwareLevel); 113 assertNotNull("Vulkan physical devices are available, but system feature " + 114 PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + " is not supported", 115 mVulkanHardwareVersion); 116 if (mVulkanHardwareLevel == null || mVulkanHardwareVersion == null) { 117 return; 118 } 119 120 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 121 " version " + mVulkanHardwareLevel.version + " is not one of the defined " + 122 " versions [0..1]", 123 mVulkanHardwareLevel.version >= 0 && mVulkanHardwareLevel.version <= 1); 124 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 125 " version 0x" + Integer.toHexString(mVulkanHardwareVersion.version) + " is not" + 126 " one of the versions allowed", 127 isHardwareVersionAllowed(mVulkanHardwareVersion.version)); 128 if (mVulkanHardwareCompute != null) { 129 assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 130 " version " + mVulkanHardwareCompute.version + 131 " is not one of the versions allowed", 132 mVulkanHardwareCompute.version == 0); 133 } 134 135 JSONObject bestDevice = null; 136 int bestDeviceLevel = -1; 137 int bestComputeLevel = -1; 138 int bestDeviceVersion = -1; 139 for (JSONObject device : mVulkanDevices) { 140 int level = determineHardwareLevel(device); 141 int compute = determineHardwareCompute(device); 142 int version = determineHardwareVersion(device); 143 if (DEBUG) { 144 Log.d(TAG, device.getJSONObject("properties").getString("deviceName") + 145 ": level=" + level + " compute=" + compute + 146 " version=0x" + Integer.toHexString(version)); 147 } 148 if (level >= bestDeviceLevel && compute >= bestComputeLevel && 149 version >= bestDeviceVersion) { 150 bestDevice = device; 151 bestDeviceLevel = level; 152 bestComputeLevel = compute; 153 bestDeviceVersion = version; 154 } 155 } 156 157 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL + 158 " version " + mVulkanHardwareLevel.version + " doesn't match best physical device " + 159 " hardware level " + bestDeviceLevel, 160 bestDeviceLevel, mVulkanHardwareLevel.version); 161 assertTrue( 162 "System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION + 163 " version 0x" + Integer.toHexString(mVulkanHardwareVersion.version) + 164 " isn't close enough (same major and minor version, less or equal patch version)" + 165 " to best physical device version 0x" + Integer.toHexString(bestDeviceVersion), 166 isVersionCompatible(bestDeviceVersion, mVulkanHardwareVersion.version)); 167 if (mVulkanHardwareCompute == null) { 168 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 169 " not present, but required features are supported", 170 bestComputeLevel, -1); 171 } else { 172 assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE + 173 " version " + mVulkanHardwareCompute.version + 174 " doesn't match best physical device hardware compute " + bestComputeLevel, 175 bestComputeLevel, mVulkanHardwareCompute.version); 176 } 177 } 178 179 @Test 180 public void testVulkanVersionForVrHighPerformance() { 181 if (!mPm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) 182 return; 183 assertTrue( 184 "VR high-performance devices must support Vulkan 1.0 with Hardware Level 0, " + 185 "but this device does not.", 186 mVulkanHardwareVersion != null && mVulkanHardwareVersion.version >= VULKAN_1_0 && 187 mVulkanHardwareLevel != null && mVulkanHardwareLevel.version >= 0); 188 } 189 190 private int determineHardwareLevel(JSONObject device) throws JSONException { 191 JSONObject features = device.getJSONObject("features"); 192 boolean textureCompressionETC2 = features.getInt("textureCompressionETC2") != 0; 193 boolean fullDrawIndexUint32 = features.getInt("fullDrawIndexUint32") != 0; 194 boolean imageCubeArray = features.getInt("imageCubeArray") != 0; 195 boolean independentBlend = features.getInt("independentBlend") != 0; 196 boolean geometryShader = features.getInt("geometryShader") != 0; 197 boolean tessellationShader = features.getInt("tessellationShader") != 0; 198 boolean sampleRateShading = features.getInt("sampleRateShading") != 0; 199 boolean textureCompressionASTC_LDR = features.getInt("textureCompressionASTC_LDR") != 0; 200 boolean fragmentStoresAndAtomics = features.getInt("fragmentStoresAndAtomics") != 0; 201 boolean shaderImageGatherExtended = features.getInt("shaderImageGatherExtended") != 0; 202 boolean shaderUniformBufferArrayDynamicIndexing = features.getInt("shaderUniformBufferArrayDynamicIndexing") != 0; 203 boolean shaderSampledImageArrayDynamicIndexing = features.getInt("shaderSampledImageArrayDynamicIndexing") != 0; 204 if (!textureCompressionETC2) { 205 return -1; 206 } 207 if (!fullDrawIndexUint32 || 208 !imageCubeArray || 209 !independentBlend || 210 !geometryShader || 211 !tessellationShader || 212 !sampleRateShading || 213 !textureCompressionASTC_LDR || 214 !fragmentStoresAndAtomics || 215 !shaderImageGatherExtended || 216 !shaderUniformBufferArrayDynamicIndexing || 217 !shaderSampledImageArrayDynamicIndexing) { 218 return 0; 219 } 220 return 1; 221 } 222 223 private int determineHardwareCompute(JSONObject device) throws JSONException { 224 boolean have16bitStorage = false; 225 boolean haveVariablePointers = false; 226 JSONArray extensions = device.getJSONArray("extensions"); 227 for (int i = 0; i < extensions.length(); i++) { 228 String name = extensions.getJSONObject(i).getString("extensionName"); 229 if (name.equals("VK_KHR_16bit_storage")) 230 have16bitStorage = true; 231 else if (name.equals("VK_KHR_variable_pointers")) 232 haveVariablePointers = true; 233 } 234 if (!have16bitStorage || !haveVariablePointers) { 235 return -1; 236 } 237 return 0; 238 } 239 240 private int determineHardwareVersion(JSONObject device) throws JSONException { 241 return device.getJSONObject("properties").getInt("apiVersion"); 242 } 243 244 private boolean isVersionCompatible(int expected, int actual) { 245 int expectedMajor = (expected >> 22) & 0x3FF; 246 int expectedMinor = (expected >> 12) & 0x3FF; 247 int expectedPatch = (expected >> 0) & 0xFFF; 248 int actualMajor = (actual >> 22) & 0x3FF; 249 int actualMinor = (actual >> 12) & 0x3FF; 250 int actualPatch = (actual >> 0) & 0xFFF; 251 return (actualMajor == expectedMajor) && 252 (actualMinor == expectedMinor) && 253 (actualPatch <= expectedPatch); 254 } 255 256 private boolean isHardwareVersionAllowed(int actual) { 257 // Limit which system feature hardware versions are allowed. If a new major/minor version 258 // is released, we don't want devices claiming support for it until tests for the new 259 // version are available. And only claiming support for a base patch level per major/minor 260 // pair reduces fragmentation seen by developers. Patch-level changes are supposed to be 261 // forwards and backwards compatible; if a developer *really* needs to alter behavior based 262 // on the patch version, they can do so at runtime, but must be able to handle previous 263 // patch versions. 264 final int[] ALLOWED_HARDWARE_VERSIONS = { 265 VULKAN_1_0, 266 }; 267 for (int expected : ALLOWED_HARDWARE_VERSIONS) { 268 if (actual == expected) { 269 return true; 270 } 271 } 272 return false; 273 } 274 275 private static native String nativeGetVkJSON(); 276 277 private JSONObject[] getVulkanDevices() throws JSONException, UnsupportedEncodingException { 278 JSONArray vkjson = new JSONArray(nativeGetVkJSON()); 279 JSONObject[] devices = new JSONObject[vkjson.length()]; 280 for (int i = 0; i < vkjson.length(); i++) { 281 devices[i] = vkjson.getJSONObject(i); 282 } 283 return devices; 284 } 285 } 286