How can I run a WebGPU build locally?

Hi!
Besides using the in-editor “Build and Run” feature, how can I run a WebGPU build locally?

I’m wondering especially since WebGPU is only available in secured contexts (i. e. HTTPS).

Do I need to set up server configuration for this, or is there a simpler way of doing it?

You can use localhost to run webgpu content locally, so any local web server, like python -m http-server or nodejs, will work.

Build and run also works

Thank you for the quick response!
Good to know.

One caveat I noticed is that python -m http-server must be ran after some extra steps when doing testing situations where Code Optimization is enabled in the build settings: Disk Size LTO & Disk Size --“Build.framework.js” becomes “Build.framework.js.br” etc.

Then this error occurs when loading the page:

Unable to load file Build/Build.framework.js! Check that the file exists on the remote server. (also check browser Console and Devtools Network tab to debug)

So a custom python script that uncompresses brotli files seems to be the solution for testing release builds :+1:

Would have been nice, and most likely would have saved me and others for time if this info about running local webgl and webgpu builds could be added to the Web documentation. For instance in: Unity - Manual: Web development and publishing process or Unity - Manual: Server configuration code samples

Yes, thx. :wink:
But for instances where I’m going to be profiling different build versions, it’s nice to have a quick way of just running old and new builds–so an other option than “build and run” is nice to have. :cowboy_hat_face::ok_hand:

if you’re sticking with brotti…

ive hacked this together with a bit of help

import http.server
import socketserver

PORT = 8000

class CrossOriginIsolatedHandler(http.server.SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
        self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
        super().end_headers()
        
    def guess_type(self, path):
        # Use mimetypes to guess type, fallback to default
        base, ext = http.server.os.path.splitext(path)
        if path.endswith(".wasm.br"):
            return "application/wasm"
        elif path.endswith(".js.br") :
            return "application/javascript"
        elif path.endswith(".data.br") :
            return "application/octet-stream"
        elif ext == ".br":
                return "application/x-brotli"

        return super().guess_type(path)

    def send_head(self):
        """Override to add custom headers for certain filetypes"""
        path = self.translate_path(self.path)
        f = None
        if http.server.os.path.isdir(path):
            return super().send_head()

        ctype = self.guess_type(path)
        try:
            f = open(path, 'rb')
        except OSError:
            self.send_error(404, "File not found")
            return None

        self.send_response(200)
        self.send_header("Content-type", ctype)

        # Add extra headers for specific file types
        if path.endswith(".br"):
            self.send_header("Content-Encoding", "br")

        self.end_headers()
        return f



Handler = CrossOriginIsolatedHandler

httpd = socketserver.ThreadingTCPServer(("", PORT), Handler)

print(f"Serving at http://localhost:{PORT}")

try:
    httpd.serve_forever()
except KeyboardInterrupt:
    print("\nShutting down server...")
    httpd.shutdown()

this will run my brotii games and comply to the daft requirements that unity has.

Im no python dude, so Ive got some basis in there if you wanna add the normal compression too

(and i made it to go with)

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using UnityEditor;
using Debug = UnityEngine.Debug;

public class ApplicationPathExample
{
    private static readonly Process p = new();

    [MenuItem("Build/Info")]
    private static void Info()
    {
        Debug.Log(
            EditorUserBuildSettings.GetBuildLocation(EditorUserBuildSettings
                .activeBuildTarget));
        if (p.Responding) Debug.Log("Server is running");
    }


    [MenuItem("Build/Launch Last Built")]
    private static void AppPath()
    {
        try
        {
            if (p.HasExited) StartServer();
        }
        catch (Exception e)
        {
            StartServer();
        }

        var b = new Process();
        b.StartInfo.FileName = "http://localhost:8000/";
        b.Start();
    }

    [MenuItem("Build/EndServer")]
    private static void EndServer()
    {
        try
        {
            p.Kill();
            Debug.Log("Server killed");
        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
        }
    }

    [MenuItem("Build/EndServer", true)]
    public static bool ValidateDevMode()
    {
        // Return true to enable, false to disable
        try
        {
            return (!p.HasExited);
        }
        catch (Exception e)
        {
            return false;
        }
    }


    [MenuItem("Build/DevMode")]
    private static void DevMode()
    {
        var current = UnityEditor.EditorPrefs.GetBool("DeveloperMode");
        UnityEditor.EditorPrefs.SetBool("DeveloperMode", !current);
        Debug.Log($"Devmode is now {(current ? "off" : "on")}");
    }

    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    private static void StartServer()
    {
        Debug.Log("Starting server");
        var apppath = Path.GetDirectoryName(EditorApplication.applicationPath);
        var lastBuildPath =
            EditorUserBuildSettings.GetBuildLocation(EditorUserBuildSettings
                .activeBuildTarget);
        p.StartInfo.FileName = @"c:\users\liz\Documents\Unity\http_server.py";
        p.StartInfo.WorkingDirectory = lastBuildPath;
        p.StartInfo.UseShellExecute = true;
        p.StartInfo.CreateNoWindow = true;
        p.EnableRaisingEvents = true;



        p.Exited += (sender, args) =>
        {
            var proc = sender as Process;
            if (proc != null)
            {
                if (proc.ExitCode != 0)
                    Debug.LogError(
                        $"Server crashed with exit code {proc.ExitCode}");
                else
                    Debug.Log(
                        $"Server exited normally with exit code {proc.ExitCode}");
            }
        };

        p.Start();
        ShowWindow(p.MainWindowHandle, 6); // 6 minimize

        Debug.Log($"Running PID: {p.Id}");
    }
}

here’s also list of solutions,

personally im using custom server,
that i can launch from Explorer context menu,
also can share to LAN IP (so can connect the host from mobile devices),
and it supports https *but its not enabled by default, requires some windows setup initially)