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_ConfigureXRDeviceinitializes XR support within the runtimeTransmuteUnity_GetMainControllerInputSourceretrieves controller identifiersTransmuteUnity_SetInputSourceRayPoseupdates input source poses from Unity's tracking systemTransmuteUnity_SetViewerTransformFromTRSsynchronizes the viewer's position and orientation with the JSARxrDevicesubsystem
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, theUnityEmbedderclass definition, render-event handling, and XR/input APIs.src/runtime/embedder.hpp– BaseTrEmbedderclass that defines the generic embedder lifecycle inherited byUnityEmbedder.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 thejsar_runtimelibrary target, compiling the plug-in for specific platforms.
Summary
- JSAR integrates with Unity through a native plug-in compiled as
jsar_runtime(.soor.dylib). - The
UnityEmbedderclass insrc/runtime/unity_entry.cppbridges Unity'sIUnityInterfaceswith the Transmute runtime. - Graphics backends are auto-detected via
RHIFactory::CreateRHIto match Unity's active renderer (OpenGL, Vulkan, Metal). - XR synchronization uses functions like
TransmuteUnity_SetViewerTransformFromTRSto mirror Unity's tracking data in JSAR. - Content management relies on document IDs returned by
TransmuteUnity_Open, which wraps the internalContentManager. - Rendering integration uses
TransmuteUnity_GetRenderEventFuncandGL.IssuePluginEventto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →