Home | History | Annotate | Download | only in cts
      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