blob: 2de3bfae3f6f332ece5981eddeaff77b44d09800 [file] [log] [blame]
package androidx.webgpu
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.webgpu.WebGpuTestConstants.EMULATOR_TESTS_MIN_API_LEVEL
import androidx.webgpu.helper.initLibrary
import androidx.webgpu.GPU.createInstance
import java.util.concurrent.Executor
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@SmallTest
class AdapterTest {
private lateinit var instance: GPUInstance
private lateinit var adapter: GPUAdapter
@get:Rule
val apiSkipRule = ApiLevelSkipRule()
@Before
fun setup() = runBlocking {
initLibrary()
instance = createInstance()
adapter = instance.requestAdapter()
}
@After
fun teardown() {
adapter.close()
instance.close()
}
/**
* Helper map for default limit values as defined by the WebGPU spec.
* A full implementation would have all limits.
*/
companion object {
private val kDefaultLimits = mapOf(
"maxTextureDimension2D" to 8192,
"maxBindGroups" to 4,
"minUniformBufferOffsetAlignment" to 256
)
}
@Test
@ApiRequirement(minApi = EMULATOR_TESTS_MIN_API_LEVEL, onlySkipOnEmulator = true)
fun adapterBackendTest() {
val adapterInfo = adapter.getInfo()
assertEquals(
"The backend type should be Vulkan",
BackendType.Vulkan, adapterInfo.backendType
)
}
private suspend fun requestTestDevice(
featureToTest: IntArray = intArrayOf(),
limits: GPULimits = GPULimits(),
): GPUDevice {
val descriptor = GPUDeviceDescriptor(
requiredFeatures = featureToTest,
requiredLimits = limits,
deviceLostCallback = DeviceLostCallback { device, reason, message ->
throw DeviceLostException(device, reason, message)
},
deviceLostCallbackExecutor = Executor(Runnable::run),
uncapturedErrorCallback = UncapturedErrorCallback { _, type, message ->
throw RuntimeException("Uncaptured error: $type, $message")
},
uncapturedErrorCallbackExecutor = Executor(Runnable::run),
)
return adapter.requestDevice(descriptor)
}
/**
* Verifies that requesting a device with default parameters returns a device
* with Core features enabled.
*/
@Test
fun requestDeviceWithDefaultParametersEnablesCoreFeatures() {
val device = runBlocking { adapter.requestDevice() }
try {
val features = device.getFeatures().features
assertArrayEquals(intArrayOf(FeatureName.CoreFeaturesAndLimits), features)
} finally {
device.destroy()
}
}
/**
* Verifies that requesting a device with default parameters returns a device
* with the correct, specification-defined default limits.
*/
@Test
fun requestDeviceWithDefaultParametersReturnsDefaultLimits() {
val device = runBlocking { adapter.requestDevice() }
try {
val limits = device.getLimits()
assertEquals(
"maxTextureDimension2D should be the default value",
kDefaultLimits["maxTextureDimension2D"],
limits.maxTextureDimension2D
)
assertEquals(
"maxBindGroups should be the default value",
kDefaultLimits["maxBindGroups"],
limits.maxBindGroups
)
} finally {
device.destroy()
}
}
/**
* Tests that an adapter can only grant a device once. Subsequent requests fail.
*
* CTS Test: requestAdapter_stale
* @see <a href="https://github.com/gpuweb/cts/blob/main/src/webgpu/api/operation/adapter/requestAdapter.spec.ts">WebGPU CTS Test</a>
*/
@Test
fun requestDeviceFailsAfterSuccessfulRequest() {
val device = runBlocking { adapter.requestDevice() }
device.destroy()
assertThrows(
"Adapter should be consumed after one device request", WebGpuException::class.java
) {
runBlocking {
val secondDeviceStatus = adapter.requestDevice()
}
}
}
/**
* Tests requesting a device with a feature that the adapter supports.
*
* CTSTest: features,known
* @see <a href="https://github.com/gpuweb/cts/blob/main/src/webgpu/api/operation/adapter/requestAdapter.spec.ts">WebGPU CTS Test</a>
*/
@Test
fun requestDeviceWithSupportedFeatureSucceeds() {
val featureToTest = FeatureName.CoreFeaturesAndLimits
val device = runBlocking { requestTestDevice(intArrayOf(featureToTest)) }
val deviceFeatures = device.getFeatures()
assert(deviceFeatures.features.contains(featureToTest)) {
"Device should have the requested feature: $featureToTest"
}
runCatching {
device.destroy()
}
}
/**
* Tests that requesting a limit better than what the adapter supports fails.
*
* CTSTest: 'limit,better_than_supported'
* @see <a href="https://github.com/gpuweb/cts/blob/main/src/webgpu/api/operation/adapter/requestAdapter.spec.ts">WebGPU CTS Test</a>
*/
@Test
fun requestDeviceWithBetterThanSupportedLimitFails() {
val adapterLimits = adapter.getLimits()
val betterLimit = adapterLimits.maxBindGroups + 1
assertThrows("Requesting a better limit should fail", DeviceLostException::class.java) {
runBlocking {
requestTestDevice(limits = GPULimits(maxBindGroups = betterLimit))
}
}
}
/**
* Tests that requesting a worse limit than the spec default gets clamped to the default.
*
* CTSTest: 'limit,worse_than_default'
* @see <a href="https://github.com/gpuweb/cts/blob/main/src/webgpu/api/operation/adapter/requestAdapter.spec.ts">WebGPU CTS Test</a>
*/
@Test
fun requestDeviceWithWorseThanDefaultLimitClamps() {
val worseLimit = kDefaultLimits.getValue("maxBindGroups") - 1
assert(worseLimit > 0) // Ensure the value is still valid, just worse.
val device = runBlocking { requestTestDevice(limits = GPULimits(maxBindGroups = worseLimit)) }
val deviceLimits = device.getLimits()
assertEquals(
"Device limit should be clamped to the default",
kDefaultLimits.getValue("maxBindGroups"),
deviceLimits.maxBindGroups
)
runCatching { device.destroy() }
}
/**
* Tests that requesting a limit with a value that is not a power of two fails.
*
* CTSTest: 'limit,out_of_range'
* @see <a href="https://github.com/gpuweb/cts/blob/main/src/webgpu/api/operation/adapter/requestAdapter.spec.ts">WebGPU CTS Test</a>
*
*/
@Test
fun requestDeviceWithInvalidAlignmentLimitFails() {
// minUniformBufferOffsetAlignment must be a power of 2. 255 is not.
val invalidAlignment = 255
assertThrows(
"Alignment limit not a power of 2 should fail", DeviceLostException::class.java
) {
runBlocking {
requestTestDevice(
limits = GPULimits(
minUniformBufferOffsetAlignment = invalidAlignment
)
)
}
}
}
}