Skip to content

Complete port file #19

@ypujante

Description

@ypujante

Hi @kainino0x

I saw your response about "misusing" the port system for your needs at the moment. I understand that this is experimental. I also want to help because I think using webgpu as a proper port is the right way to go (down the line).

Here is the port that I wrote (and you are more than welcome to reuse if you so wish):

# note that the file must be called emdawnwebgpu.py (<port_name>.py)
import os
from typing import Union, Dict, Optional

TAG = 'v2025.04.12-03.06.48'
HASH = 'd5627fb822317a8573ad133100a15917517ad44ca39c6927b4dd37b3a3476082a077f8474dcde348bb42a4058c92b9c25beeffb5bd71b83c44df93efea48c1cf'
ZIP_URL = f'https://github.com/kainino0x/dawn/releases/download/{TAG}/emdawnwebgpu_pkg-{TAG}.zip'

# contrib port information (required)
URL = 'https://dawn.googlesource.com/dawn'
DESCRIPTION = 'Dawn is an open-source and cross-platform implementation of the WebGPU standard'
LICENSE = 'BSD 3-Clause License'

VALID_OPTION_VALUES = {
  'enableCPPBindings': ['true', 'false'],
  'optimizationLevel': ['0', '1', '2', '3', 'g', 's', 'z']  # all -OX possibilities
}

OPTIONS = {
  'enableCPPBindings': 'A boolean to enable CPP bindings (disabled by default)',
  'optimizationLevel': f'Optimization level: {VALID_OPTION_VALUES["optimizationLevel"]} (default to 2)',
}

# user options (from --use-port)
opts: Dict[str, Union[Optional[str], bool]] = {
  'enableCPPBindings': False,
  'optimizationLevel': '2'
}

port_name = 'emdawnwebgpu'


def get_lib_name(settings):
  return f'lib_{port_name}_{TAG}-O{opts["optimizationLevel"]}.a'


def get_root_path(ports):
  return os.path.join(ports.get_dir(), port_name, 'emdawnwebgpu_pkg')


def get_include_path(ports):
  return os.path.join(get_root_path(ports), 'webgpu', 'include')


def get_cpp_include_path(ports):
  return os.path.join(get_root_path(ports), 'webgpu_cpp', 'include')


def get_source_path(ports):
  return os.path.join(get_root_path(ports), 'webgpu', 'src')


def get(ports, settings, shared):
  # get the port
  ports.fetch_project(port_name, ZIP_URL, sha512hash=HASH)

  def create(final):
    source_path = get_source_path(ports)
    include_path = get_include_path(ports)

    includes = [include_path]
    srcs = ['webgpu.cpp']
    flags = ['-std=c++17', '-fno-exceptions']
    flags.append(f'-O{opts["optimizationLevel"]}')

    ports.build_port(source_path, final, port_name, includes=includes, srcs=srcs, flags=flags)

  lib = shared.cache.get_lib(get_lib_name(settings), create, what='port')
  if os.path.getmtime(lib) < os.path.getmtime(__file__):
    clear(ports, settings, shared)
    lib = shared.cache.get_lib(get_lib_name(settings), create, what='port')
  return [lib]


def clear(ports, settings, shared):
  shared.cache.erase_lib(get_lib_name(settings))


def linker_setup(ports, settings):
  if settings.USE_WEBGPU:
    raise Exception('dawn may not be used with -sUSE_WEBGPU=1')

  src_dir = get_source_path(ports)

  settings.JS_LIBRARIES += [
    os.path.join(src_dir, 'library_webgpu_enum_tables.js'),
    os.path.join(src_dir, 'library_webgpu_generated_struct_info.js'),
    os.path.join(src_dir, 'library_webgpu_generated_sig_info.js'),
    os.path.join(src_dir, 'library_webgpu.js'),
  ]
  # TODO(crbug.com/371024051): Emscripten needs a way for us to pass
  # --closure-args too.


def process_args(ports):
  # It's important that these take precedent over Emscripten's builtin
  # system/include/, which also currently as webgpu headers.
  args = ['-isystem', get_include_path(ports)]
  if opts['enableCPPBindings']:
    args += ['-isystem', get_cpp_include_path(ports)]
  return args


def check_option(option, value, error_handler):
  if value not in VALID_OPTION_VALUES[option]:
    error_handler(f'[{option}] can be {list(VALID_OPTION_VALUES[option])}, got [{value}]')
  if isinstance(opts[option], bool):
    value = value == 'true'
  return value


def check_required_option(option, value, error_handler):
  if opts[option] is not None and opts[option] != value:
    error_handler(f'[{option}] is already set with incompatible value [{opts[option]}]')
  return check_option(option, value, error_handler)


def handle_options(options, error_handler):
  for option, value in options.items():
    value = value.lower()
    opts[option] = check_option(option, value, error_handler)


if __name__ == "__main__":
  print(f'''# To compute checksums run this
curl -sfL {ZIP_URL} | shasum -a 512
''')

You use it this way (with the main.cpp file from this repository)

> mkdir /tmp/dawn
> emcc -sASYNCIFY=1 -sASYNCIFY_STACK_SIZE=65536 -sEXPORTED_RUNTIME_METHODS=ccall --use-port=emdawnwebgpu.py:enableCPPBindings=true main_dawn.cpp -o /tmp/dawn/index.html

A few points:

  • by using port options, you do not need 2 separate ports: enabling cpp bindings is an option (and it is false by default)
  • the port file downloads the zip file and does what needs to be done => from a user point of view you only need the port file (not the zip file)
  • this means that the port file is outside the zip file (and could be also an artifact generated part of your CI)
  • I can host this port file in my emscripten-ports project for the time being for people who just wants to use the port without having to download the zip file
  • It is my strong opinion that this port file should become a contrib port in Emscripten once -sUSE_WEBGPU is deprecated so that people can simply install Emscripten and do emcc --use-port=contrib.dawn. Updating the port file on Emscripten usually becomes simply updating the version/hash and that would be fine to do when Dawn has "stable" releases

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions