Unity Integration for JSAR: Native Plug-in API and Architecture

JSAR provides a native Unity plug-in packaged as a compiled .so or .dylib library that exposes C-style exported functions through src/runtime/unity_entry.cpp, enabling Unity to control the Transmute runtime via the UnityEmbedder class for document management, XR synchronization, and frame rendering.

The m-creativelab/jsar-runtime repository delivers JSAR (the Transmute runtime) as a native Unity plug-in, enabling developers to embed JavaScript XR content directly within Unity applications. This Unity integration for JSAR implements a thin C++ bridge in src/runtime/unity_entry.cpp that connects Unity's native interface pointers with the runtime's graphics, XR, and content management subsystems.

How the Unity Plug-in Architecture Works

The integration follows a layered architecture where Unity loads the native library and exchanges interface pointers with the JSAR runtime through a dedicated embedder class.

Unity Host Layer and Entry Points

When Unity loads the plug-in, it automatically invokes UnityPluginLoad and supplies an IUnityInterfaces pointer. This function, defined in src/runtime/unity_entry.cpp (lines 73-90), initializes the bridge between Unity and JSAR. On Android platforms, the plug-in additionally reads system properties such as JSAR_WEBGL_PLACEHOLDERS and JSAR_DEBUG_ENABLED via OnPlatformSetup to configure environment variables before the runtime starts.

The UnityEmbedder Bridge Class

The UnityEmbedder class (lines 38-66 in src/runtime/unity_entry.cpp) inherits from TrEmbedder (defined in src/runtime/embedder.hpp) and stores references to IUnityGraphics and IUnityLog. This class translates Unity lifecycle events—initialize, shutdown, and render passes—into calls that the JSAR runtime understands, maintaining synchronization between the two environments.

Graphics Device Initialization

During kUnityGfxDeviceEventInitialize, the embedder queries the active renderer type using graphics->GetRenderer() and invokes RHIFactory::CreateRHI to instantiate the correct rendering hardware interface. This mechanism links Unity's backend (OpenGL, Vulkan, or Metal) directly to JSAR's renderer, ensuring frame buffers and graphics contexts remain compatible.

Core Unity Integration APIs

The plug-in exposes a suite of C functions that Unity can invoke via DllImport attributes in C# scripts.

Runtime Configuration and Lifecycle

Before starting the runtime, Unity must call TransmuteUnity_Configure, which parses a JSON string to set the application cache directory, proxy settings, and XR support flags. After configuration, TransmuteUnity_Start bootstraps the runtime and prepares the document environment.

Content Management (Documents)

Unity controls JSAR documents through identifiers returned by TransmuteUnity_Open. This function accepts a URL and a UnityDocumentRequestInit structure (containing disableCache and isPreview booleans) and returns a document ID that can be used with TransmuteUnity_Pause, resume, and close operations. These functions wrap the internal ContentManager to manage the lifecycle of JavaScript contexts.

XR and Input Synchronization

For XR applications, the plug-in provides specific functions to synchronize device state:

  • TransmuteUnity_ConfigureXRDevice initializes XR support within the runtime
  • TransmuteUnity_GetMainControllerInputSource retrieves controller identifiers
  • TransmuteUnity_SetInputSourceRayPose updates input source poses from Unity's tracking system
  • TransmuteUnity_SetViewerTransformFromTRS synchronizes the viewer's position and orientation with the JSAR xrDevice subsystem

These functions ensure that head tracking and controller input in Unity are accurately reflected in the JavaScript XR environment.

Rendering Callbacks

Unity retrieves a render-event function pointer via TransmuteUnity_GetRenderEventFunc. Unity scripts then invoke this pointer each frame using GL.IssuePluginEvent, passing an integer identifier that maps to specific render passes (kBeforeRenderingPass, kOpaquesRenderPass, kAfterRenderingPass). The plug-in forwards these events to JSAR's corresponding callbacks (onBeforeRendering, onOpaquesRenderPass, etc.), allowing JavaScript code to participate in Unity's render loop.

Implementing the Integration in C#

Below is a minimal Unity C# script demonstrating how to import and use the JSAR native plug-in:

using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class JsarBridge : MonoBehaviour
{
    [DllImport("jsar_runtime")]
    private static extern void UnityPluginLoad(IntPtr unityInterfaces);

    [DllImport("jsar_runtime")]
    private static extern IntPtr TransmuteUnity_GetRenderEventFunc();

    [DllImport("jsar_runtime")]
    private static extern bool TransmuteUnity_Configure(string configJson);

    [DllImport("jsar_runtime")]
    private static extern bool TransmuteUnity_Start();

    [DllImport("jsar_runtime")]
    private static extern int TransmuteUnity_Open(string url, UnityDocumentRequestInit init);

    [StructLayout(LayoutKind.Sequential)]
    public struct UnityDocumentRequestInit
    {
        public bool disableCache;
        public bool isPreview;
    }

    void Awake()
    {
        // Unity automatically invokes UnityPluginLoad when the plug-in loads
    }

    void Start()
    {
        var config = "{\"applicationCacheDirectory\":\"/data/local/tmp/jsar\",\"isXRSupported\":true}";
        if (!TransmuteUnity_Configure(config))
            Debug.LogError("JSAR configuration failed");

        if (!TransmuteUnity_Start())
            Debug.LogError("Failed to start JSAR");

        var init = new UnityDocumentRequestInit { disableCache = false, isPreview = false };
        int docId = TransmuteUnity_Open("https://example.com", init);
        Debug.Log($"Opened JSAR document, id={docId}");
    }

    void Update()
    {
        // Trigger JSAR rendering during the opaque render pass
        GL.IssuePluginEvent(TransmuteUnity_GetRenderEventFunc(), 1); // 1 == kOpaquesRenderPass
    }
}

Key Source Files and Build Configuration

The Unity integration spans several source files and build configurations:

  • src/runtime/unity_entry.cpp – Central plug-in implementation containing exported C functions, the UnityEmbedder class definition, render-event handling, and XR/input APIs.
  • src/runtime/embedder.hpp – Base TrEmbedder class that defines the generic embedder lifecycle inherited by UnityEmbedder.
  • src/runtime/renderer/render_api.hpp – Abstract RHI factory used by the embedder to select the correct graphics backend based on Unity's active renderer.
  • src/runtime/xr/device.hpp – XR device abstraction accessed by Unity-specific functions for pose and input handling.
  • thirdparty/headers/Unity/* – Unity native SDK headers (IUnityGraphics.h, IUnityLog.h, etc.) required for compilation.
  • CMakeLists.txt – Build configuration that produces the jsar_runtime library target, compiling the plug-in for specific platforms.

Summary

  • JSAR integrates with Unity through a native plug-in compiled as jsar_runtime (.so or .dylib).
  • The UnityEmbedder class in src/runtime/unity_entry.cpp bridges Unity's IUnityInterfaces with the Transmute runtime.
  • Graphics backends are auto-detected via RHIFactory::CreateRHI to match Unity's active renderer (OpenGL, Vulkan, Metal).
  • XR synchronization uses functions like TransmuteUnity_SetViewerTransformFromTRS to mirror Unity's tracking data in JSAR.
  • Content management relies on document IDs returned by TransmuteUnity_Open, which wraps the internal ContentManager.
  • Rendering integration uses TransmuteUnity_GetRenderEventFunc and GL.IssuePluginEvent to insert JSAR draw calls into Unity's render passes.

Frequently Asked Questions

How does Unity initialize the JSAR runtime?

Unity loads the native plug-in automatically, triggering UnityPluginLoad in src/runtime/unity_entry.cpp with an IUnityInterfaces pointer. The Unity script then calls TransmuteUnity_Configure with a JSON configuration string (specifying cache paths and XR support), followed by TransmuteUnity_Start to boot the runtime according to the source code implementation.

Which graphics APIs are supported by the JSAR Unity plug-in?

The plug-in supports OpenGL, Vulkan, and Metal. During initialization, the UnityEmbedder queries Unity's active renderer via IUnityGraphics->GetRenderer() and uses RHIFactory::CreateRHI to instantiate the matching rendering backend, as implemented in the graphics device initialization logic.

How does the JSAR Unity integration handle XR device input?

The plug-in exposes specific C functions including TransmuteUnity_ConfigureXRDevice, TransmuteUnity_GetMainControllerInputSource, and TransmuteUnity_SetInputSourceRayPose that Unity scripts call to synchronize controller IDs and poses. These functions write directly to the JSAR xrDevice subsystem defined in src/runtime/xr/device.hpp.

What functions does Unity use to control JSAR documents?

Unity opens documents using TransmuteUnity_Open, which accepts a URL and a UnityDocumentRequestInit struct and returns a document ID. This ID is subsequently used with TransmuteUnity_Pause, resume, and close functions to manage the JavaScript context lifecycle through the internal ContentManager.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →