WebGPU JsInterop 包装器

WebGPU JsInterop wrapper

我正在尝试使用 JsInterop 在 GWT 2.9.0 中使用 WebGPU,但在尝试将所有 WebGPU 接口映射到 Java 时遇到了一些问题。我所指的定义位于 https://www.w3.org/TR/webgpu/#idl-index

1) 如何映射一个 unsigned long long?

有一个类型定义:typedef [EnforceRange] unsigned long long GPUSize64; 例如这里使用的:

interface mixin GPURenderEncoderBase { 
    //...other declarations left out...
    undefined drawIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset);
};

如果我把它包装成

@JsType(isNative = true, namespace = JsPackage.GLOBAL)
public class GPURenderEncoderBase {
    //...other declarations left out...
    @JsMethod
    public final native void drawIndirect(GPUBuffer indirectBuffer, long indirectOffset);
}

我收到一条错误消息:

Parameter 'sourceOffset': type 'long' is not safe to access in JSNI code

鉴于我的更高级别 API 仅在此处公开一个 int 以与其他 APIs 兼容,我可能只使用一个 int,但是映射 GPUSize64 的正确解决方案是什么?

2) 如何包装字典?

当我尝试翻译以下定义时

dictionary GPUExtent3DDict {
    required GPUIntegerCoordinate width;
    GPUIntegerCoordinate height = 1;
    GPUIntegerCoordinate depthOrArrayLayers = 1;
};
typedef (sequence<GPUIntegerCoordinate> or GPUExtent3DDict) GPUExtent3D;

像这样:

@JsType(isNative = false, namespace = JsPackage.GLOBAL)
public class GPUExtent3D {
    public int width;
    public int height = 1;
    public int depthOrArrayLayers = 1;
}

然后按如下方式使用:

    ...
    GPUExtent3D size = new GPUExtent3D();
    size.width = canvasWidth;
    size.height = canvasHeight;
    GPUCanvasConfiguration config = new GPUCanvasConfiguration();
    config.size = size;
    gpuCanvasContext.configure(config);

我可以正常编译,但在运行时出现错误

Uncaught (in promise) TypeError: Failed to execute 'configure' on 'GPUCanvasContext': Failed to read the 'size' property from 'GPUCanvasConfiguration': Failed to read the 'width' property from 'GPUExtent3DDict': Failed to read the 'width' property from 'GPUExtent3DDict': Required member is undefined.

让我感到困惑的是,它说“无法从 'GPUExtent3DDict' 读取 'width' 属性”两次,这暗示它期望嵌套的东西并且可能与最后一个有关关于我不明白的“序列或 GPUExtent3DDict”的 typedef 行。 当我以这种方式定义 GPUExtent3D 时:

public final class GPUExtent3D extends JavaScriptObject {
    public static final native GPUExtent3D createNew() /*-{
        return {height: 1, depthOrArrayLayers: 1};
    }-*/;

    protected GPUExtent3D() {}

    public final native void width(int width) /*-{
        this["width"] = width;
    }-*/;

    //...same for height and depthOrArrayLayers
}

然后像这样使用它:

    ...
    GPUExtent3D size = GPUExtent3D.createNew();
    size.width(canvasWidth);
    size.height(canvasHeight);
    size.depthOrArrayLayers(1);
    GPUCanvasConfiguration config = new GPUCanvasConfiguration();
    config.size = size;
    gpuCanvasContext.configure(config);

它工作得很好,但我想用 JsInterop 方式而不是扩展 JavaScriptObject。我该怎么做?

3) 如何映射枚举?

我在这里也找到了一个可行的解决方案,我想听听这是推荐的还是不推荐的/旧的方法 给定枚举声明:

enum GPUPowerPreference {
    "low-power",
    "high-performance"
};

我可以像这样映射它吗

public final class GPUPowerPreference {
    public static final String LOW_POWER = "low-power";
    public static final String HIGH_POWER = "high-power";
    private GPUPowerPreference() {}
}

或者有没有办法为此使用带有@JsEnum 的java 枚举(我试过了,但值中使用的破折号有问题)

非常感谢,祝您有愉快的一天!

几个月前,我根据手写的 externs 做了 elemental2,比如 webgpu 包装器。

你可以看这里:

https://github.com/treblereel/elemental2-experimental/tree/main/java/org/treblereel/gwt/elemental2



简单演示: https://github.com/treblereel/j2cl-tests/blob/webgpu/src/main/java/org/treblereel/App.java

它基于 j2cl,但与使用 gwt2 的方式非常接近。 它的主要缺点是 webgpu api 不稳定,因此很难保持最新状态。最好从 idl 生成 externs:

@peter-donald 正在致力于 Web API 的更好实现,但它不是基于 elemental2。 https://github.com/akasha/akasha

首先 - 如果您想要一个现成的 WebGPU(以及所有其他浏览器 APIs)的 jsinterop 绑定,它是通过处理规范中的 webidl 并从 webidl 构建绑定而构建的,那么 Akasha provides that in both GWT2.9 and J2CL compatible variants (The Java API is the same but the annotations and innerts differ slightly between the two variants). The coordinate for the latest version of akasha variant for GWT is org.realityforge.akasha:akasha-gwt:jar:0.29 and an example of a simple textured spinning cube is available at Main.java

具体问题:

  • 你如何表示“unsigned long long”?这是一个doubleint ...或Double 如果该值是可选的。这些表示中的 None 与 API 的预期完全匹配,但只要值的映射一致并且来自外部来源(即 gltf 或其他格式),则不会有重大损失。当类型不是可选时,我将其映射到 int,当类型是可选时,我将其映射到 Double。有点难看,但在 GWT 领域是最好的(尽管 J2CL 很快就会有一种方法来表示本地 longs IIRC)
  • 如何包装字典? 由于底层表示只是一个 javascript 对象,所以有很多很多不同的表示可能。无论您是针对 j2cl 还是 GWT,这都不同,但对于 GWT,您通常需要如下内容。在我的生成器中,我更进一步并定义了一个构建器,如 GPUExtent3DDict 中所示(但请注意,此源是 J2CL 变体的示例,与下面描述的代码有一些细微差别),这使得字典数据的构造容易多了。即 GPUExtent3DDict.width( 10 ).height( 10 ).depthOrArrayLayers( 1 )
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "?")
public interface GPUExtent3DDict {
 @JsProperty(name = "width")
  int width();

  @JsProperty
  void setWidth(int width);

  @JsProperty(name = "depthOrArrayLayers")
  int depthOrArrayLayers();

  @JsProperty
  void setDepthOrArrayLayers(int depthOrArrayLayers);

  @JsProperty(name = "height")
  int height();

  @JsProperty
  void setHeight(int height);
}
  • 如何映射枚举? 由于枚举实际上只是字符串值的包,因此带有字符串常量的 class 就足够了。但是,如果您想要更好的可用性(即 IntelliJ IDEA 等 IDE 中的选项卡完成),那么我通常将它们建模如下。然后用 @GPUPowerPreference
  • 注释作为枚举键入的 parameters/return 值
@MagicConstant(valuesFromClass = GPUPowerPreference.class)
public @interface GPUPowerPreference {
  @Nonnull
  String high_performance = "high-performance";

  @Nonnull
  String low_power = "low-power";

  final class Util {
    private Util() {
    }

    @GPUPowerPreference
    public static String requireValid(final String value) {
      assertValid( value );
      return value;
    }

    public static void assertValid(@Nonnull final String value) {
      assert isValid( value );
    }

    public static boolean isValid(@Nonnull final String value) {
      return GPUPowerPreference.high_performance.equals( value ) || GPUPowerPreference.low_power.equals( value );
    }
  }
}

物有所值。在 java 中使用 WebGPU 已被证明是一种救命稻草,特别是它目前正在迅速发展,当规范发生变化时,编译错误可以帮助您找到问题 ;) 祝你好运!

添加到其他答案中,因为它们缺少一个我认为至少在某些特定情况下可能很重要的部分。

  1. How do I map an unsigned long long?

首先,请注意 Java long 不是 等价物,因为 Java 长整型是 64 位宽,它们是有符号的.

根据我对规范的阅读,它很复杂,但很灵活。 https://webidl.spec.whatwg.org/#es-unsigned-long-long:

An ECMAScript value V is converted to an IDL unsigned long long value by running the following algorithm:

  1. Let x be ? ConvertToInt(V, 64, "unsigned").
  2. Return the IDL unsigned long long value that represents the same numeric value as x.

The result of converting an IDL unsigned long long value to an ECMAScript value is a Number value that represents the closest numeric value to the unsigned long long, choosing the numeric value with an even significand if there are two equally close values. If the unsigned long long is less than or equal to 253 − 1, then the Number will be able to represent exactly the same value as the unsigned long long.

好的,接下来 ConvertToInt 是什么?参见 https://webidl.spec.whatwg.org/#abstract-opdef-converttoint, but it establishes some bounds (based on the 64 and "unsigned"), and then uses ToNumber, a more standard part of the ecmascript spec https://tc39.es/ecma262/#sec-tonumber. Among other inputs, this can handle a String, via StringToNumber https://tc39.es/ecma262/#sec-stringtonumber

这样做的最终效果是,您可以将带有无符号 64 位整数值的字符串传递给这些 API,并将在内部进行解释。所以,只要你的值符合 Java long,你就可以使用 Long.toString(value) 来生成字符串值。我建议提供本机 doubleintString 重载,以及使用 @JsOverlay 注释的 long 方法,然后它将委托给 String 重载。