blob: 894fb772d434cbb5514417202e23daba84a086c6 [file] [log] [blame]
//* Copyright 2024 The Dawn & Tint Authors
//*
//* Redistribution and use in source and binary forms, with or without
//* modification, are permitted provided that the following conditions are met:
//*
//* 1. Redistributions of source code must retain the above copyright notice, this
//* list of conditions and the following disclaimer.
//*
//* 2. Redistributions in binary form must reproduce the above copyright notice,
//* this list of conditions and the following disclaimer in the documentation
//* and/or other materials provided with the distribution.
//*
//* 3. Neither the name of the copyright holder nor the names of its
//* contributors may be used to endorse or promote products derived from
//* this software without specific prior written permission.
//*
//* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
//* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
//* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
//* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% from 'art/api_jni_types.cpp' import arg_to_jni_type, convert_to_kotlin, jni_signature with context %}
{% macro define_kotlin_record_structure(struct_name, members) %}
struct {{struct_name}} {
{% for member in kotlin_record_members(members) %}
//* HACK: Hardcode that ANativeWindow is a jlong instead of an actual pointer. Instead
//* of this, we should have manually written method that directly creates the
//* wgpu::Surface from the Java Surface.
{% if member.name.get() == "window" and member.type.name.get() == "void *"%}
jlong {{ as_varName(member.name) }};
{% else %}
{{ arg_to_jni_type(member) }} {{ as_varName(member.name) }};
{% endif %}
{% endfor%}
};
{% endmacro %}
{% macro define_kotlin_to_struct_conversion(function_name, kotlin_name, struct_name, members,is_structure_converter=False) %}
inline void {{function_name}}(JNIContext* c, const {{kotlin_name}}& inStruct, {{struct_name}}* outStruct) {
JNIEnv* env = c->env;
JNIClasses* classes = JNIClasses::getInstance(env);
*outStruct = {};
{% for member in kotlin_record_members(members) %}
{
{% if member.type.category == 'callback function' %}
if (inStruct.{{ as_varName(member.name) }})
{
auto& callbackInfo = outStruct->{{as_varName(member.name)}}Info;
callbackInfo = {};
//* Check if the member's corresponding callback info has a callbackMode.
{% set callbackInfoType = find_by_name(by_category["callback info"], member.type.name.get() ~ " info") %}
{% do assert(callbackInfoType, "callbackInfoType must exist but was not found") %}
{% if find_by_name(callbackInfoType.members, 'mode') %}
callbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
{% endif %}
//* The callback functions require each argument converting.
//* A custom native callback is generated to wrap the Kotlin callback.
callbackInfo.callback = [](
{%- for callbackArg in member.type.arguments %}
{{- as_annotated_cType(callbackArg) }}{{ ', ' if not loop.last }}
{%- endfor -%}
//* 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' %}
//* User data is used to carry the JNI context (env) for use by the
//* callback.
std::unique_ptr<UserData> userData1{static_cast<UserData *>({{ userdata }})};
JNIEnv *env = NULL;
JavaVM* jvm = userData1->jvm;
//* Deal with difference in signatures between Oracle's jni.h and Android's.
#ifdef _JAVASOFT_JNI_H_ //* Oracle's jni.h violates the JNI spec.
jvm->AttachCurrentThread(reinterpret_cast<void**>(&env), NULL);
#else
jvm->AttachCurrentThread(&env, NULL);
#endif
if (env->ExceptionCheck()) {
return;
}
JNIClasses* classes = JNIClasses::getInstance(env);
{# Determine the type of Runnable we are building (Structure vs Generic) #}
{%- set info = namespace(mode='standard', class='gpuCallbackRunnable', sig='', has_err=false) -%}
{%- set vars = namespace(status='0', msg=none, load=none, type=none) -%}
{# Detect ErrorType #}
{%- for arg in kotlin_record_members(member.type.arguments) -%}
{%- if arg.type.name.get() == 'error type' %}
{% set info.has_err = true %}
{% endif -%}
{%- endfor -%}
{# Determine Initial Mode/Signature #}
{%- if is_structure_converter -%}
{%- set info.mode = 'structure' -%}
{%- set info.class = member.type.name.camelCase() + 'Runnable' -%}
{%- set sb = namespace(v='(L' + jni_name(member.type) + ';') -%}
{%- for arg in kotlin_record_members(member.type.arguments) %}
{% set sb.v = sb.v + jni_signature(arg) %}
{% endfor -%}
{%- set info.sig = sb.v + ')V' -%}
{%- elif info.has_err -%}
{%- set info.mode = 'error' -%}
{%- set info.class = 'gpuCallbackErrorTypeRunnable' -%}
{%- set info.sig = '(Landroidx/webgpu/GPURequestCallback;IILjava/lang/String;)V' -%}
{%- else -%}
{# Default to Standard, might verify to Void later #}
{%- set info.sig = '(Landroidx/webgpu/GPURequestCallback;ILjava/lang/String;Ljava/lang/Object;)V' -%}
{%- endif -%}
{# Generate the C++ variable conversions (e.g. _status, _message) #}
{%- for arg in kotlin_record_members(member.type.arguments) -%}
{{ convert_to_kotlin(arg.name.camelCase(), '_' + arg.name.camelCase(), 'input->' + arg.length.name.camelCase() if arg.length.name, arg) }}
{%- set vName = '_' + arg.name.camelCase() -%}
{%- set aName = arg.name.get() -%}
{%- if aName == 'status' %}
{% set vars.status = vName %}
{%- elif aName == 'message' %}
{% set vars.msg = vName %}
{%- elif aName == 'type' %}
{% set vars.type = vName %}
{%- else %}
{% set vars.load = vName %}
{%- endif -%}
{%- endfor -%}
{# Handle missing strings for Generics #}
{%- if info.mode != 'structure' -%}
{%- if vars.msg is none -%}
jstring _empty_msg = env->NewStringUTF("");
{%- set vars.msg = '_empty_msg' -%}
{%- endif -%}
{# If Standard mode but no payload found -> Switch to Void #}
{%- if info.mode == 'standard' and vars.load is none -%}
{%- set info.mode = 'void' -%}
{%- set info.class = 'gpuCallbackVoidRunnable' -%}
{%- set info.sig = '(Landroidx/webgpu/GPURequestCallback;ILjava/lang/String;)V' -%}
{%- endif -%}
{%- endif -%}
jclass executorClass = env->FindClass("java/util/concurrent/Executor");
jmethodID executeMethodID = env->GetMethodID(executorClass, "execute", "(Ljava/lang/Runnable;)V");
jmethodID runCtor = env->GetMethodID(classes->{{ info.class }}, "<init>", "{{ info.sig }}");
jobject runnable = env->NewObject(classes->{{ info.class }}, runCtor, userData1->callback
{%- if info.mode == 'structure' -%}
{%- for arg in kotlin_record_members(member.type.arguments) -%}
{%- if member.type.category != 'kotlin type' %}
, _{{ arg.name.camelCase() }}
{% endif -%}
{%- endfor -%}
{%- elif info.mode == 'error' -%}
, {{ vars.status }}, {{ vars.type }}, {{ vars.msg }}
{%- elif info.mode == 'void' -%}
, {{ vars.status }}, {{ vars.msg }}
{%- else -%}
, {{ vars.status }}, {{ vars.msg }}, {{ vars.load }}
{%- endif -%}
);
env->CallVoidMethod(userData1->executor, executeMethodID, runnable);
};
//* The user data is owned by the callback and freed when it is called.
callbackInfo.{{ userdata }} = std::unique_ptr<UserData>(new UserData({
.callback = env->NewGlobalRef(inStruct.{{member.name.camelCase()}}),
.executor = env->NewGlobalRef(inStruct.{{as_varName(member.name)}}Executor),
.jvm = c->jvm
})).release();
}
{% elif member.type.category != 'kotlin type' %}
auto& in = inStruct.{{member.name.camelCase()}};
auto& out = outStruct->{{member.name.camelCase()}};
{% endif %}
{% if member.constant_length == 1 %}
{% if member.type.category == 'structure' %}
if (in != nullptr) {
auto converted = c->Alloc<{{as_cType(member.type.name)}}>();
ToNative(c, env, in, converted);
out = converted;
}
{% elif member.type.name.get() == 'void' %}
out = reinterpret_cast<void*>(in);
{% else %}
{{ unreachable_code() }}
{% endif %}
{% elif member.length %}
auto& outLength = outStruct->{{member.length.name.camelCase()}};
{% if member.constant_length %}
{{ unreachable_code() }}
{% endif %}
//* Convert container, including the length field.
{% if member.type.name.get() == 'uint32_t' or member.type.category in ['bitmask', 'enum'] %}
out = reinterpret_cast<const {{ as_cType(member.type.name) }}*>(c->GetIntArrayElements(in));
outLength = env->GetArrayLength(in);
{% elif member.type.name.get() == 'void' %}
out = env->GetDirectBufferAddress(in);
outLength = env->GetDirectBufferCapacity(in);
{% else %}
//* These container types are represented in Kotlin as arrays of objects.
outLength = env->GetArrayLength(in);
auto array = c->AllocArray<{{ as_cType(member.type.name) }}>(outLength);
out = array;
{% if member.type.category == 'object' %}
jclass memberClass = classes->{{ member.type.name.camelCase() }};
jmethodID getHandle = env->GetMethodID(memberClass, "getHandle", "()J");
for (int idx = 0; idx != outLength; idx++) {
jobject element = env->GetObjectArrayElement(in, idx);
array[idx] = reinterpret_cast<{{ as_cType(member.type.name) }}>(
env->CallLongMethod(element, getHandle));
}
{% elif member.type.category == 'structure' %}
for (int idx = 0; idx != outLength; idx++) {
ToNative(c, env, env->GetObjectArrayElement(in, idx), &array[idx]);
}
{% else %}
{{ unreachable_code() }}
{% endif %}
{% endif %}
//* From here members are single values.
{% elif member.type.category == 'object' %}
if (in != nullptr) {
jclass memberClass = classes->{{ member.type.name.camelCase() }};
jmethodID getHandle = env->GetMethodID(memberClass, "getHandle", "()J");
out = reinterpret_cast<{{as_cType(member.type.name)}}>(
env->CallLongMethod(in, getHandle));
} else {
out = nullptr;
}
{% elif member.type.category == 'callback function' %}
//* Do nothing for this case.
{% elif member.type.category == 'structure' %}
//* Mandatory structure.
ToNative(c, env, in, &out);
{% elif member.name.get() == "window" and member.type.name.get() == "void *" %}
//* HACK: Hardcode that ANativeWindow is a jlong instead of an actual pointer. Instead
//* of this, we should have manually written method that directly creates the
//* wgpu::Surface from the Java Surface.
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 != 'kotlin type' %}
{{ unreachable_code() }}
{% endif %}
}
{% endfor -%}
}
{% endmacro %}