playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
208 lines (207 loc) • 5.08 kB
JavaScript
import { Vec2 } from "../../../core/math/vec2.js";
class WebgpuXrBridge {
_binding = null;
_layer = null;
_cachedFramebufferSize = new Vec2();
constructor(xrBridge) {
this.xrBridge = xrBridge;
}
destroy(device) {
this._binding = null;
this._layer = null;
this._cachedFramebufferSize.set(0, 0);
device._clearXrState();
}
beginFrame(frame, referenceSpace) {
const device = this.xrBridge.device;
device._clearXrState();
if (!this._binding || !this._layer || !referenceSpace) {
return;
}
const pose = frame.getViewerPose(referenceSpace);
if (!pose || !pose.views?.length) {
return;
}
const subImages = device.xrSubImages;
for (let i = 0; i < pose.views.length; i++) {
let sub;
try {
sub = this._binding.getViewSubImage(this._layer, pose.views[i]);
} catch (e) {
this.xrBridge._onBindingError?.(e);
return;
}
const colorTexture = sub?.colorTexture;
if (!colorTexture) continue;
let viewDescriptor = null;
if (typeof sub.getViewDescriptor === "function") {
try {
const desc = sub.getViewDescriptor();
if (desc) {
viewDescriptor = { ...desc };
}
} catch (e) {
}
}
subImages.push({
colorTexture,
viewDescriptor,
viewport: sub.viewport,
viewFormat: viewDescriptor?.format ?? colorTexture.format
});
}
const first = subImages[0];
if (first) {
device.xrColorTexture = first.colorTexture;
device.xrColorTextureViewFormat = first.viewFormat;
this._cachedFramebufferSize.set(first.colorTexture.width, first.colorTexture.height);
}
}
endFrame() {
const device = this.xrBridge.device;
device?._clearXrState();
}
get presentationLayer() {
return this._layer;
}
get graphicsBinding() {
return this._binding;
}
getFramebufferSize(_frame, out) {
const layer = this._layer;
if (layer) {
const lw = layer.textureWidth ?? layer.width;
const lh = layer.textureHeight ?? layer.height;
if (lw > 0 && lh > 0) {
out.set(lw, lh);
return;
}
}
if (this._cachedFramebufferSize.x > 0 && this._cachedFramebufferSize.y > 0) {
out.copy(this._cachedFramebufferSize);
return;
}
out.set(0, 0);
}
getViewport(_frame, xrView) {
if (this._binding && this._layer) {
try {
const sub = this._binding.getViewSubImage(this._layer, xrView);
if (sub?.viewport) {
return sub.viewport;
}
} catch {
}
}
return { x: 0, y: 0, width: 0, height: 0 };
}
attachPresentation(session, options) {
const XRGPUBindingCtor = globalThis.XRGPUBinding;
if (typeof XRGPUBindingCtor === "undefined") {
this.xrBridge._onBindingError?.(new Error("XRGPUBinding is not available in this browser."));
return;
}
const device = this.xrBridge.device;
const wgpu = device.wgpu;
try {
this._binding = new XRGPUBindingCtor(session, wgpu);
} catch (ex) {
this.xrBridge._onBindingError?.(ex);
return;
}
const colorFormat = this._binding.getPreferredColorFormat();
const layerOpts = {
colorFormat,
scaleFactor: options.framebufferScaleFactor
};
try {
this._layer = this._binding.createProjectionLayer({
...layerOpts,
textureType: "texture-array"
});
} catch {
this._layer = this._binding.createProjectionLayer({
...layerOpts,
textureType: "texture"
});
}
session.updateRenderState({
layers: [this._layer],
depthNear: options.depthNear,
depthFar: options.depthFar
});
}
syncCameraColorTexture(xrCamera, texture) {
if (!this._binding?.getCameraImage) {
return;
}
const src = this._binding.getCameraImage(xrCamera);
if (!src) {
return;
}
const dst = texture.impl?.gpuTexture;
if (!dst) {
return;
}
const device = this.xrBridge.device;
const encoder = device.getCommandEncoder();
const width = xrCamera.width;
const height = xrCamera.height;
encoder.copyTextureToTexture(
{ texture: src },
{ texture: dst },
[width, height, 1]
);
}
syncCameraDepthTexture(depthInfo, _texture, _depthPixelFormat) {
if (depthInfo?.texture) {
}
}
releasePresentation() {
this._binding = null;
}
onGraphicsDeviceLost() {
const bridge = this.xrBridge;
const session = bridge._session;
if (!session) {
return;
}
const rs = session.renderState;
const device = bridge.device;
this._binding = null;
this._layer = null;
this._cachedFramebufferSize.set(0, 0);
device._clearXrState();
session.updateRenderState({
layers: [],
depthNear: rs.depthNear,
depthFar: rs.depthFar
});
}
onGraphicsDeviceRestored() {
const bridge = this.xrBridge;
if (!bridge._session) {
return;
}
const eventHandler = bridge.eventHandler;
setTimeout(() => {
if (!bridge._session) {
return;
}
try {
const rs = bridge._session.renderState;
bridge.attachPresentation(bridge._session, {
framebufferScaleFactor: bridge._framebufferScaleFactor,
depthNear: rs.depthNear,
depthFar: rs.depthFar,
onBindingError: bridge._onBindingError
});
} catch (ex) {
eventHandler.fire("error", ex);
}
}, 0);
}
}
export {
WebgpuXrBridge
};