Kotlin: allow use of new callback info and callback function.
Convert the error handling in the tests to the non-deprecated
method, now that will work.
Also enable handling of legacy callback structures (structures
with names ending 'callback info').
Bug: 373837184, 352710628
Test: ErrorTest.*
Change-Id: Ia4e1cee21d347d0916ffc912b88e0bb2fa7a9281
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/217136
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Jim Blackler <jimblackler@google.com>
Reviewed-by: Alex Benton <bentonian@google.com>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index 96d700f..82951ae 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -712,11 +712,6 @@
def kotlin_record_members(members):
for member in members:
- # Skip over callback infos as we haven't implemented support for them yet.
- # TODO(352710628) support converting callback info.
- if member.type.category in ['callback info']:
- continue
-
# length parameters are omitted because Kotlin containers have 'length'.
if member in [m.length for m in members]:
continue
@@ -763,9 +758,6 @@
return True
def include_structure(structure):
- # TODO(352710628) support converting callback info.
- if structure.name.canonical_case().endswith(" callback info"):
- return False
if structure.name.canonical_case() == "string view":
return False
return True
@@ -1431,7 +1423,8 @@
]
by_category = params_kotlin['by_category']
- for structure in by_category['structure']:
+ for structure in by_category['structure'] + by_category[
+ 'callback info']:
if structure.name.get() != "string view":
renders.append(
FileRender('art/api_kotlin_structure.kt',
@@ -1448,7 +1441,8 @@
[RENDER_PARAMS_BASE, params_kotlin, {
'obj': obj
}]))
- for function_pointer in by_category['function pointer']:
+ for function_pointer in (by_category['function pointer'] +
+ by_category['callback function']):
renders.append(
FileRender('art/api_kotlin_function_pointer.kt',
'java/' + jni_name(function_pointer) + '.kt', [
diff --git a/generator/templates/art/api_jni_types.cpp b/generator/templates/art/api_jni_types.cpp
index 4d4e654..c50e890 100644
--- a/generator/templates/art/api_jni_types.cpp
+++ b/generator/templates/art/api_jni_types.cpp
@@ -27,7 +27,7 @@
{% macro arg_to_jni_type(arg) %}
{%- if arg.length and arg.length != 'constant' -%}
- {%- if arg.type.category in ['bitmask', 'enum', 'function pointer', 'object', 'structure'] -%}
+ {%- if arg.type.category in ['bitmask', 'callback function', 'enum', 'function pointer', 'object', 'callback info', 'structure'] -%}
jobjectArray
{%- elif arg.type.name.get() == 'void' -%}
jobject
@@ -44,7 +44,7 @@
{% macro to_jni_type(type) %}
{%- if type.name.get() == "string view" -%}
jstring
- {%- elif type.category in ['function pointer', 'object', 'structure'] -%}
+ {%- elif type.category in ['callback function', 'function pointer', 'object', 'callback info', 'structure'] -%}
jobject
{%- elif type.category in ['bitmask', 'enum'] -%}
jint
@@ -69,7 +69,7 @@
{% endmacro %}
{% macro jni_signature_single_value(type) %}
- {%- if type.category in ['function pointer', 'object', 'structure'] -%}
+ {%- if type.category in ['function pointer', 'object', 'callback function', 'callback info', 'structure'] -%}
L{{ jni_name(type) }};
{%- elif type.category in ['bitmask', 'enum'] -%}
{{ jni_signatures['int32_t'] }}//* JvmInline makes lone bitmask/enums appear as integer to JNI.
@@ -96,7 +96,7 @@
{% if size is string %}
{% if member.type.name.get() in ['void const *', 'void *'] %}
jobject {{ output }} = toByteBuffer(env, {{ input }}, {{ size }});
- {% elif member.type.category in ['bitmask', 'enum', 'object', 'structure'] %}
+ {% elif member.type.category in ['bitmask', 'enum', 'object', 'callback info', 'structure'] %}
//* Native container converted to a Kotlin container.
jobjectArray {{ output }} = env->NewObjectArray(
{{ size }},
@@ -118,13 +118,16 @@
jmethodID init = env->GetMethodID(clz, "<init>", "(J)V");
{{ output }} = env->NewObject(clz, init, reinterpret_cast<jlong>({{ input }}));
}
- {% elif member.type.category == 'structure' %}
+ {% elif member.type.category in ['callback info', 'structure'] %}
jobject {{ output }} = ToKotlin(env, {{ '&' if member.annotation not in ['*', 'const*'] }}{{ input }});
{% elif member.type.name.get() == 'void *' %}
jlong {{ output }} = reinterpret_cast<jlong>({{ input }});
{% elif member.type.category in ['bitmask', 'enum', 'native'] %}
//* We use Kotlin value classes for bitmask and enum, and they get inlined as lone values.
{{ to_jni_type(member.type) }} {{ output }} = static_cast<{{ to_jni_type(member.type) }}>({{ input }});
+ {% elif member.type.category in ['callback function', 'function pointer'] %}
+ jobject {{ output }} = nullptr;
+ dawn::WarningLog() << "while converting {{ as_cType(member.type.name) }}: Native callbacks cannot be converted to Kotlin";
{% else %}
{{ unreachable_code() }}
{% endif %}
diff --git a/generator/templates/art/api_kotlin_types.kt b/generator/templates/art/api_kotlin_types.kt
index f666094..a29cf60 100644
--- a/generator/templates/art/api_kotlin_types.kt
+++ b/generator/templates/art/api_kotlin_types.kt
@@ -42,14 +42,14 @@
{%- endif -%}
{%- elif arg.length and arg.length != 'constant' %}
{# * annotation can mean an array, e.g. an output argument #}
- {%- if type.category in ['bitmask', 'enum', 'function pointer', 'object', 'structure'] -%}
+ {%- if type.category in ['bitmask', 'callback function', 'callback info', 'enum', 'function pointer', 'object', 'structure'] -%}
Array<{{ type.name.CamelCase() }}>{{ ' = arrayOf()' if emit_defaults }}
{%- elif type.name.get() in ['int', 'int32_t', 'uint32_t'] -%}
IntArray{{ ' = intArrayOf()' if emit_defaults }}
{%- else -%}
{{ unreachable_code() }}
{% endif %}
- {%- elif type.category in ['function pointer', 'object'] %}
+ {%- elif type.category in ['callback function', 'function pointer', 'object'] %}
{{- type.name.CamelCase() }}
{%- if optional or default_value %}?{{ ' = null' if emit_defaults }}{% endif %}
{%- elif type.category == 'structure' or type.category == 'callback info' %}
diff --git a/generator/templates/art/kotlin_record_conversion.cpp b/generator/templates/art/kotlin_record_conversion.cpp
index 46d799d..c805f64 100644
--- a/generator/templates/art/kotlin_record_conversion.cpp
+++ b/generator/templates/art/kotlin_record_conversion.cpp
@@ -115,7 +115,7 @@
} else {
out = nullptr;
}
- {% elif member.type.category == 'structure' %}
+ {% elif member.type.category in ['callback info', 'structure'] %}
//* Mandatory structure.
ToNative(c, env, in, &out);
{% elif member.name.get() == "window" and member.type.name.get() == "void *" %}
@@ -125,20 +125,33 @@
out = reinterpret_cast<{{as_cType(member.type.name)}}>(static_cast<uintptr_t>(in));
{% elif member.type.category in ["native", "enum", "bitmask"] %}
out = static_cast<{{as_cType(member.type.name)}}>(in);
- {% elif member.type.category == 'function pointer' %}
- //* Function pointers themselves require each argument converting.
+ {% elif member.type.category in ['callback function', 'function pointer'] %}
+ //* Function pointers and callback functions require each argument converting.
//* A custom native callback is generated to wrap the Kotlin callback.
out = [](
{%- for callbackArg in member.type.arguments %}
- {{ as_annotated_cType(callbackArg) }}{{ ',' if not loop.last }}
- {%- endfor %}) {
- UserData* userData1 = static_cast<UserData *>(userdata);
+ {{- as_annotated_cType(callbackArg) }}{{ ', ' if not loop.last }}
+ {%- endfor -%}
+ {%- if member.type.category == 'function pointer' -%}
+ //* We rely on the function pointer definitions (dawn.json) always
+ //* including a parameter named 'userdata' as the final parameter.
+ {%- set userdata = 'userdata' -%}
+ {%- else %}
+ //* Callback functions do not specify user data params in dawn.json.
+ //* However, the C API always supplements two parameters with the names
+ //* below.
+ , void* userdata1, void* userdata2
+ {%- set userdata = 'userdata1' -%}
+ {%- endif %}) {
+ //* User data is used to carry the JNI context (env) for use by the
+ //* callback.
+ UserData* userData1 = static_cast<UserData *>({{ userdata }});
JNIEnv *env = userData1->env;
if (env->ExceptionCheck()) {
return;
}
- {%- for callbackArg in kotlin_record_members(member.type.arguments) -%}
+ {% for callbackArg in kotlin_record_members(member.type.arguments) -%}
{{ convert_to_kotlin(callbackArg.name.camelCase(),
'_' + callbackArg.name.camelCase(),
'input->' + callbackArg.length.name.camelCase() if callbackArg.length.name,
@@ -155,11 +168,11 @@
//* Call the callback with all converted parameters.
env->CallVoidMethod(userData1->callback, callbackMethod
{%- for callbackArg in kotlin_record_members(member.type.arguments) %}
- ,_{{ callbackArg.name.camelCase() }}
+ {{- ', ' }}_{{ callbackArg.name.camelCase() }}
{%- endfor %});
};
//* TODO(b/330293719): free associated resources.
- outStruct->userdata = new UserData(
+ outStruct->{{ userdata }} = new UserData(
{.env = env, .callback = env->NewGlobalRef(in)});
{% else %}
diff --git a/generator/templates/art/methods.cpp b/generator/templates/art/methods.cpp
index be896c9..1029be9 100644
--- a/generator/templates/art/methods.cpp
+++ b/generator/templates/art/methods.cpp
@@ -40,11 +40,6 @@
namespace dawn::kotlin_api {
-struct UserData {
- JNIEnv *env;
- jobject callback;
-};
-
jobject toByteBuffer(JNIEnv *env, const void* address, jlong size) {
if (!address) {
return nullptr;
diff --git a/generator/templates/art/structures.cpp b/generator/templates/art/structures.cpp
index 3aa57b9..fc910e6 100644
--- a/generator/templates/art/structures.cpp
+++ b/generator/templates/art/structures.cpp
@@ -35,6 +35,7 @@
#include <webgpu/webgpu.h>
#include "dawn/common/Assert.h"
+#include "dawn/common/Log.h"
#include "JNIContext.h"
// Converts Kotlin objects representing Dawn structures into native structures that can be passed
@@ -72,24 +73,6 @@
*result = reinterpret_cast<T*>(env->CallObjectMethod(obj, getter));
}
-// Special-case noop handling of the two callback info that are part of other structures.
-// TODO(352710628) support converting callback info.
-void ToNative(JNIContext* c, JNIEnv* env, jobject obj, WGPUDeviceLostCallbackInfo* info) {
- *info = {};
-}
-
-void ToNative(JNIContext* c, JNIEnv* env, jobject obj, WGPUUncapturedErrorCallbackInfo* info) {
- *info = {};
-}
-
-jobject ToKotlin(JNIEnv *env, const WGPUDeviceLostCallbackInfo* input) {
- return nullptr;
-}
-
-jobject ToKotlin(JNIEnv *env, const WGPUUncapturedErrorCallbackInfo* input) {
- return nullptr;
-}
-
// Special-case [Nullable]StringView
void ToNative(JNIContext* c, JNIEnv* env, jstring obj, WGPUStringView* s) {
if (obj == nullptr) {
@@ -110,7 +93,7 @@
return env->NewStringUTF(nullTerminated.c_str());
}
-{%- for structure in by_category['structure'] if include_structure(structure) %}
+{%- for structure in by_category['structure'] + by_category['callback info'] if include_structure(structure) %}
//* Native -> Kotlin converter.
//* TODO(b/354411474): Filter the structures for which to add a ToKotlin conversion.
diff --git a/generator/templates/art/structures.h b/generator/templates/art/structures.h
index 89bc8c1..4f7efb4 100644
--- a/generator/templates/art/structures.h
+++ b/generator/templates/art/structures.h
@@ -31,11 +31,16 @@
class JNIContext;
+struct UserData {
+ JNIEnv *env;
+ jobject callback;
+};
+
// Converts Kotlin objects representing Dawn structures into native structures that can be passed
// into the native Dawn API.
jobject ToKotlin(JNIEnv* env, const WGPUStringView* s);
-{% for structure in by_category['structure'] if include_structure(structure) %}
+{% for structure in by_category['structure'] + by_category['callback info'] if include_structure(structure) %}
jobject ToKotlin(JNIEnv *env, const {{ as_cType(structure.name) }}* input);
void ToNative(JNIContext* c, JNIEnv* env, jobject obj, {{ as_cType(structure.name) }}* converted);
{% endfor %}
diff --git a/tools/android/BUILD.gn b/tools/android/BUILD.gn
index 44f8e54..b773570 100644
--- a/tools/android/BUILD.gn
+++ b/tools/android/BUILD.gn
@@ -64,7 +64,9 @@
"java/android/dawn/BufferDescriptor.kt",
"java/android/dawn/BufferMapAsyncStatus.kt",
"java/android/dawn/BufferMapCallback.kt",
+ "java/android/dawn/BufferMapCallback2.kt",
"java/android/dawn/BufferMapCallbackInfo.kt",
+ "java/android/dawn/BufferMapCallbackInfo2.kt",
"java/android/dawn/BufferMapState.kt",
"java/android/dawn/BufferUsage.kt",
"java/android/dawn/CallbackMode.kt",
@@ -78,7 +80,9 @@
"java/android/dawn/CompareFunction.kt",
"java/android/dawn/CompilationInfo.kt",
"java/android/dawn/CompilationInfoCallback.kt",
+ "java/android/dawn/CompilationInfoCallback2.kt",
"java/android/dawn/CompilationInfoCallbackInfo.kt",
+ "java/android/dawn/CompilationInfoCallbackInfo2.kt",
"java/android/dawn/CompilationInfoRequestStatus.kt",
"java/android/dawn/CompilationMessage.kt",
"java/android/dawn/CompilationMessageType.kt",
@@ -92,16 +96,22 @@
"java/android/dawn/ConstantEntry.kt",
"java/android/dawn/Constants.kt",
"java/android/dawn/CreateComputePipelineAsyncCallback.kt",
+ "java/android/dawn/CreateComputePipelineAsyncCallback2.kt",
"java/android/dawn/CreateComputePipelineAsyncCallbackInfo.kt",
+ "java/android/dawn/CreateComputePipelineAsyncCallbackInfo2.kt",
"java/android/dawn/CreatePipelineAsyncStatus.kt",
"java/android/dawn/CreateRenderPipelineAsyncCallback.kt",
+ "java/android/dawn/CreateRenderPipelineAsyncCallback2.kt",
"java/android/dawn/CreateRenderPipelineAsyncCallbackInfo.kt",
+ "java/android/dawn/CreateRenderPipelineAsyncCallbackInfo2.kt",
"java/android/dawn/CullMode.kt",
"java/android/dawn/DepthStencilState.kt",
"java/android/dawn/Device.kt",
"java/android/dawn/DeviceDescriptor.kt",
"java/android/dawn/DeviceLostCallback.kt",
+ "java/android/dawn/DeviceLostCallback2.kt",
"java/android/dawn/DeviceLostCallbackInfo.kt",
+ "java/android/dawn/DeviceLostCallbackInfo2.kt",
"java/android/dawn/DeviceLostCallbackNew.kt",
"java/android/dawn/DeviceLostReason.kt",
"java/android/dawn/ErrorCallback.kt",
@@ -132,7 +142,9 @@
"java/android/dawn/PipelineLayout.kt",
"java/android/dawn/PipelineLayoutDescriptor.kt",
"java/android/dawn/PopErrorScopeCallback.kt",
+ "java/android/dawn/PopErrorScopeCallback2.kt",
"java/android/dawn/PopErrorScopeCallbackInfo.kt",
+ "java/android/dawn/PopErrorScopeCallbackInfo2.kt",
"java/android/dawn/PopErrorScopeStatus.kt",
"java/android/dawn/PowerPreference.kt",
"java/android/dawn/PresentMode.kt",
@@ -145,7 +157,9 @@
"java/android/dawn/Queue.kt",
"java/android/dawn/QueueDescriptor.kt",
"java/android/dawn/QueueWorkDoneCallback.kt",
+ "java/android/dawn/QueueWorkDoneCallback2.kt",
"java/android/dawn/QueueWorkDoneCallbackInfo.kt",
+ "java/android/dawn/QueueWorkDoneCallbackInfo2.kt",
"java/android/dawn/QueueWorkDoneStatus.kt",
"java/android/dawn/RenderBundle.kt",
"java/android/dawn/RenderBundleDescriptor.kt",
@@ -154,17 +168,21 @@
"java/android/dawn/RenderPassColorAttachment.kt",
"java/android/dawn/RenderPassDepthStencilAttachment.kt",
"java/android/dawn/RenderPassDescriptor.kt",
- "java/android/dawn/RenderPassMaxDrawCount.kt",
"java/android/dawn/RenderPassEncoder.kt",
+ "java/android/dawn/RenderPassMaxDrawCount.kt",
"java/android/dawn/RenderPassTimestampWrites.kt",
"java/android/dawn/RenderPipeline.kt",
"java/android/dawn/RenderPipelineDescriptor.kt",
"java/android/dawn/RequestAdapterCallback.kt",
+ "java/android/dawn/RequestAdapterCallback2.kt",
"java/android/dawn/RequestAdapterCallbackInfo.kt",
+ "java/android/dawn/RequestAdapterCallbackInfo2.kt",
"java/android/dawn/RequestAdapterOptions.kt",
"java/android/dawn/RequestAdapterStatus.kt",
"java/android/dawn/RequestDeviceCallback.kt",
+ "java/android/dawn/RequestDeviceCallback2.kt",
"java/android/dawn/RequestDeviceCallbackInfo.kt",
+ "java/android/dawn/RequestDeviceCallbackInfo2.kt",
"java/android/dawn/RequestDeviceStatus.kt",
"java/android/dawn/RequiredLimits.kt",
"java/android/dawn/SType.kt",
@@ -205,7 +223,9 @@
"java/android/dawn/TextureView.kt",
"java/android/dawn/TextureViewDescriptor.kt",
"java/android/dawn/TextureViewDimension.kt",
+ "java/android/dawn/UncapturedErrorCallback.kt",
"java/android/dawn/UncapturedErrorCallbackInfo.kt",
+ "java/android/dawn/UncapturedErrorCallbackInfo2.kt",
"java/android/dawn/VertexAttribute.kt",
"java/android/dawn/VertexBufferLayout.kt",
"java/android/dawn/VertexFormat.kt",
diff --git a/tools/android/webgpu/src/androidTest/java/android/dawn/DawnTestLauncher.kt b/tools/android/webgpu/src/androidTest/java/android/dawn/DawnTestLauncher.kt
index f91cd40..00d6265 100644
--- a/tools/android/webgpu/src/androidTest/java/android/dawn/DawnTestLauncher.kt
+++ b/tools/android/webgpu/src/androidTest/java/android/dawn/DawnTestLauncher.kt
@@ -1,10 +1,17 @@
package android.dawn
-import android.dawn.helper.*
+import android.dawn.CallbackMode.Companion.WaitAnyOnly
+import android.dawn.helper.DawnException
+import android.dawn.helper.Util
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
+class DeviceLostException(val device: Device, val reason: DeviceLostReason, message: String) :
+ Exception(message)
+
+class UncapturedErrorException(val device: Device, val type: ErrorType, message: String) : Exception(message)
+
fun dawnTestLauncher(
requiredFeatures: Array<FeatureName> = arrayOf(),
callback: suspend (device: Device) -> Unit
@@ -26,13 +33,20 @@
instance.requestAdapter().adapter ?: throw DawnException("No adapter available")
val device = adapter.requestDevice(
- DeviceDescriptor(requiredFeatures = requiredFeatures)
+ DeviceDescriptor(
+ requiredFeatures = requiredFeatures,
+ deviceLostCallbackInfo2 = DeviceLostCallbackInfo2(
+ callback = DeviceLostCallback2
+ { device, reason, message ->
+ throw DeviceLostException(device, reason, message)
+ },
+ mode = WaitAnyOnly
+ ),
+ uncapturedErrorCallbackInfo2 = UncapturedErrorCallbackInfo2 { device, type, message ->
+ throw UncapturedErrorException(device, type, message)
+ })
).device ?: throw DawnException("No device available")
- device.setUncapturedErrorCallback { type, message ->
- throw DawnException(message)
- }
-
callback(device)
device.close()
@@ -43,6 +57,18 @@
runBlocking {
eventProcessor.join()
}
- instance.close()
+ var caughtDeviceLostException = false;
+ try {
+ instance.close()
+ } catch (ignored: DeviceLostException) {
+ // For some reason we receive a device lost callback even though the only device has
+ // been closed. b/381416258
+ caughtDeviceLostException = true;
+ }
+ assert(caughtDeviceLostException) {
+ "When this assert stops passing, it indicates that the DeviceLostException we're " +
+ "catching above is no longer being erroneously thrown, so we can safely " +
+ "remove the try/catch and treat the DLE as a genuine error."
+ }
}
-}
\ No newline at end of file
+}
diff --git a/tools/android/webgpu/src/androidTest/java/android/dawn/ErrorTest.kt b/tools/android/webgpu/src/androidTest/java/android/dawn/ErrorTest.kt
new file mode 100644
index 0000000..4bbf137
--- /dev/null
+++ b/tools/android/webgpu/src/androidTest/java/android/dawn/ErrorTest.kt
@@ -0,0 +1,28 @@
+package android.dawn
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ErrorTest {
+ @Test
+ /**
+ * Test that an invalid parameter raises an error that is converted to a Kotlin exception by
+ * the adapter in DawnTestLauncher.
+ */
+ fun errorTest() {
+ dawnTestLauncher { device ->
+ assertThrows(UncapturedErrorException::class.java) {
+ device.createTexture(
+ TextureDescriptor(
+ usage = TextureUsage.None,
+ size = Extent3D(0),
+ format = TextureFormat.Undefined // Invalid parameter for createTexture().
+ )
+ )
+ }
+ }
+ }
+}