Skip to content
Advertisement

WebGPU JsInterop wrapper

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!

Answer

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!

Advertisement