diff --git a/src/SparkRenderer.ts b/src/SparkRenderer.ts index 7771c0c8..e10bab8f 100644 --- a/src/SparkRenderer.ts +++ b/src/SparkRenderer.ts @@ -808,7 +808,13 @@ export class SparkRenderer extends THREE.Mesh { // Determine render target const currentRenderTarget = renderer.getRenderTarget(); const isXRRenderTarget = checkIsXRRenderTarget(currentRenderTarget); - if (currentRenderTarget) { + // Objects like Reflector reset the renderer's current viewport, so prefer + // the camera's own viewport. + const viewport = + camera.viewport ?? renderer.getCurrentViewport(new THREE.Vector4()); + if (viewport.z > 1 && viewport.w > 1) { + spark.renderSize.set(viewport.z, viewport.w); + } else if (currentRenderTarget) { spark.renderSize.set( currentRenderTarget.width, currentRenderTarget.height, diff --git a/test/unit/SparkRenderer.test.ts b/test/unit/SparkRenderer.test.ts index 5f1599fc..46748786 100644 --- a/test/unit/SparkRenderer.test.ts +++ b/test/unit/SparkRenderer.test.ts @@ -20,6 +20,7 @@ const stubRenderer = { getActiveMipmapLevel: () => 0, setRenderTarget: () => {}, getDrawingBufferSize: (size: THREE.Vector2) => size.set(1, 1), + getCurrentViewport: (viewport: THREE.Vector4) => viewport.set(0, 0, 1, 1), readRenderTargetPixelsAsync: async () => {}, render: () => {}, properties: new WeakMap(), @@ -201,6 +202,36 @@ test("a change during a sort is sorted after it", async () => { expect(sorts.map((s) => s.version)).toEqual([v0 + 1, v0 + 2]); }); +test("a WebXR eye is sized by its viewport, not the shared render target", async () => { + const { spark, camera, frame } = await setup(); + vi.spyOn(stubRenderer, "getRenderTarget").mockReturnValue({ + width: 200, + height: 100, + isXRRenderTarget: true, + texture: { colorSpace: THREE.SRGBColorSpace }, + } as unknown as THREE.WebGLRenderTarget); + // As after a Reflector renders in this eye + vi.spyOn(stubRenderer, "getCurrentViewport").mockImplementation((viewport) => + viewport.set(0, 0, 200, 100), + ); + camera.viewport = new THREE.Vector4(100, 0, 100, 100); + + await frame(); + + expect(spark.renderSize).toEqual(new THREE.Vector2(100, 100)); +}); + +test("a camera drawn with setViewport is sized by that viewport", async () => { + const { spark, frame } = await setup(); + vi.spyOn(stubRenderer, "getCurrentViewport").mockImplementation((viewport) => + viewport.set(50, 0, 50, 40), + ); + + await frame(); + + expect(spark.renderSize).toEqual(new THREE.Vector2(50, 40)); +}); + describe("a LoD tree that fails to initialize", () => { async function render(...lodTrees: unknown[]) { const { spark, frame } = await setup(