1

I am trying to play around with WebGPU in GWT 2.9.0 using JsInterop and I face some problems trying to map all the WebGPU interfaces to Java. The definitions I refer to are located at https://www.w3.org/TR/webgpu/#idl-index

1) How do I map an unsigned long long?

There is a typedef: typedef [EnforceRange] unsigned long long GPUSize64; that is used for example here:

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

if I wrap it like

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

I get an error saying:

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

given that my higher level API only exposes an int here for compatibility with other APIs i could probably just use an int, but what is the correct solution to map the GPUSize64?

2) How do I wrap a dictonary?

When I try to translate the following definition

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

like so:

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

and then use it in the following way:

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

I can compile fine but get an error at runtime saying

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.

What confuses me is that it says "Failed to read the 'width' property from 'GPUExtent3DDict'" twice which hints that it expects something nested and probably has to do with the last line in the typedef about the "sequence or GPUExtent3DDict" which I dont understand. When I instead define GPUExtent3D that way:

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
}

and then use it like:

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

It works just fine, but I would like to do it the JsInterop way instead of extent JavaScriptObject. How would I go about that?

3) How to map an enum?

I also found a working solution here and I would like to hear if this is recommended or maybe deprecated / old way of doing it Given an enum declaration:

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

Can I just map it like

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

Or is there a way to use a java enum with @JsEnum for this (I tried but had problems with the dash that is used in the value)

Thanks a lot and have a nice day!

Samwise
  • 86
  • 6

3 Answers3

3

Firstly - If you want a ready-made jsinterop binding for WebGPU (and all the other browser APIs) that is built by processing the webidl from the specs and building the binding from the webidl then 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

The specific questions:

  • How do you represent a "unsigned long long"? This is either a double or an int ... or a Double if the value is optional. None of these representations match exactly what the API expects but as long as the values are mapped consistently and sourced from external sources (i.e. gltf or other formats) then there is no significant penalty. I map it to int when the type is not optional and Double when optional. A bit ugly but the best that works in GWT-land (although J2CL will soon have a way to represent native longs IIRC)
  • How do I wrap a dictionary? As the underlying representation is just a javascript object there are many, many many different representations possible. This differs whether you are targeting j2cl or GWT but for GWT you generally you need something like below. In my generator I go a step further and define a builder as in GPUExtent3DDict (but note that this source is an example of the J2CL variant and has some slight differences from the code described below) and this makes construction of dictionary data much easier. i.e. 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);
}
  • How to map an enum? As enums are really just bags of string values, a class with string constants is adequate. However if you want better usability (i.e. tab completion in IDEs such as IntelliJ IDEA) then I typically model them as below. And then annotate the parameters/return values that are typed as the enum with @GPUPowerPreference
@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 );
    }
  }
}

For what it is worth. Working with WebGPU in java has proven to be a life saver, particularly as it is evolving rapidly at the moment and when the spec changes, compilation errors help you locate the problems ;) Good luck!

Peter Donald
  • 461
  • 2
  • 5
  • Thanks a lot! After translating about 4 dictioniaries/enums per hand I also wrote a generator that parsed the IDL (the generator as well as the code it generates is super ugly and hacky) but the idea for the enums as well as the builder-style dictionaries are awesome, i guess the builder-style will make wgpu almost feel like using lwjgls vulkan binding lol – Samwise Nov 17 '21 at 17:45
1

few month ago i did elemental2 like webgpu wrappers based on my hand-written externs.


You can take a look here :

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



Simple demo : https://github.com/treblereel/j2cl-tests/blob/webgpu/src/main/java/org/treblereel/App.java


It’s j2cl-based, but pretty close to how you can do it with gwt2. 

The main disadvantage of it, webgpu api isn’t stable, so it’s a very difficult to keep it out to date. 

Better to generate externs from idl:

@peter-donald are working on better implementations of Web API’s, but it’s not based on elemental2. https://github.com/akasha/akasha

1

Adding on to the other answers, since they are missing one piece that I think may be important in at least some specific cases.

  1. How do I map an unsigned long long?

First, please note that a Java long is not equivalent, since while Java longs are 64bits wide, they are signed.

From my read of the spec, it is complicated, but flexible. 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.

Ok, so next what is ConvertToInt? See 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.

The end effect of this is that you can pass a String with your unsigned 64 bit integer value to these APIs, and it will be interpreted internally. So, as long as your value fits in a Java long, you can use Long.toString(value) to generate the string value. I suggest offering native double, int, and String overloads, and also a long method annotated with @JsOverlay, which would then delegate to the String overload.

Colin Alworth
  • 17,801
  • 2
  • 26
  • 39
  • The toString "hack" (well not a hack because it is specified as you worked out) is probably quite handy in situations where really needed. I cannot accept 2 answers but thank you very much – Samwise Nov 17 '21 at 17:48
  • Peter answered more completely than me, I'm happy to have him get the credit, I just wanted to add this one idea since it wasnt obvious in any of the samples i had seen, but appears to be expected by the spec. – Colin Alworth Nov 17 '21 at 21:02