diff --git a/packages/org.gamecult.eve.unity-scene/README.md b/packages/org.gamecult.eve.unity-scene/README.md index e68c649..220184b 100644 --- a/packages/org.gamecult.eve.unity-scene/README.md +++ b/packages/org.gamecult.eve.unity-scene/README.md @@ -22,6 +22,10 @@ resource declarations; effects that sample camera color request an intermediate color texture and never read the back buffer. Do not schedule these effects from `RenderPipelineManager` callbacks. The provider owns programs, assets, and parameters; the Universal Renderer owns when and where they execute. +Provider volume programs render through both Game and SceneView cameras. Each +camera owns separate raymarch and temporal-history targets, so editor preview +cannot resize or contaminate the playable camera's history. Presentation-only +exposure remains bound to the leased Game camera. Advertised input bindings are performed from the provider's semantic action catalog. Direct and chord gestures submit once per press edge; held values diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityRendererFeature.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityRendererFeature.cs index c94cf54..562eaf8 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityRendererFeature.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityRendererFeature.cs @@ -52,8 +52,11 @@ namespace GameCult.Eve.UnityScene public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { var camera = renderingData.cameraData.camera; - if (camera == null || renderingData.cameraData.cameraType != CameraType.Game) return; + if (camera == null || !SupportsCamera(renderingData.cameraData.cameraType)) return; EveUnityRenderPassRegistry.Enqueue(camera, renderer); } + + internal static bool SupportsCamera(CameraType cameraType) => + cameraType == CameraType.Game || cameraType == CameraType.SceneView; } } diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs b/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs index 12f098d..24597aa 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs @@ -24,11 +24,8 @@ namespace GameCult.Eve.UnityScene.Fields private IReadOnlyDictionary? _programMetadata; private readonly Dictionary _assetTexturesByPort = new Dictionary(StringComparer.Ordinal); - private RenderTexture? _raymarchTexture; - private readonly RenderTexture?[] _historyTextures = new RenderTexture?[2]; - private int _historyTextureIndex = -1; - private Matrix4x4 _previousViewProjection; - private Matrix4x4 _previousView; + private readonly Dictionary _cameraRenderStates = + new Dictionary(); private EveUnityFieldsVolumePass? _renderPass; private string _activeNodeId = ""; private long _rasterizedFrameId = -1; @@ -118,13 +115,16 @@ namespace GameCult.Eve.UnityScene.Fields bool IEveUnityRenderPassSource.TryEnqueuePass(Camera camera, ScriptableRenderer renderer) { if (_host == null || _document == null || camera == null || - _host.ActiveCameraTransform != camera.transform) + !CanRenderCamera(_host, camera)) return false; _renderPass ??= new EveUnityFieldsVolumePass(this); renderer.EnqueuePass(_renderPass); return true; } + internal static bool CanRenderCamera(EveUnityPlayableWorldClientHost host, Camera camera) => + camera.cameraType == CameraType.SceneView || host.ActiveCameraTransform == camera.transform; + private void RenderVolume( UnsafeCommandBuffer commands, Camera camera, @@ -132,14 +132,15 @@ namespace GameCult.Eve.UnityScene.Fields TextureHandle depthTarget) { if (_host == null || _document == null || camera == null || - _host.ActiveCameraTransform != camera.transform) + !CanRenderCamera(_host, camera)) return; var field = ActiveField(); if (field == null || !TryRenderLayers(_document, camera, out _)) return; camera.depthTextureMode |= DepthTextureMode.Depth; - EnsureVolumeTextures(camera, field); - if (_raymarchTexture == null || !ApplyViewportTextureScaleBindings(field, camera)) return; - ApplyQuality(field, _historyTextureIndex < 0); + var state = CameraState(camera); + EnsureVolumeTextures(camera, field, state); + if (state.RaymarchTexture == null || !ApplyViewportTextureScaleBindings(field, camera)) return; + ApplyQuality(field, state.HistoryTextureIndex < 0); var gpuProjection = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true); var nonRenderTargetProjection = GL.GetGPUProjectionMatrix(camera.projectionMatrix, false); @@ -154,33 +155,33 @@ namespace GameCult.Eve.UnityScene.Fields var temporalPass = ProgramPass("temporal"); var useTemporalHistory = temporalPass >= 0; var historyWriteIndex = -1; - RenderTexture finalCloudTexture = _raymarchTexture; + RenderTexture finalCloudTexture = state.RaymarchTexture; if (useTemporalHistory) { - historyWriteIndex = _historyTextureIndex < 0 ? 0 : (_historyTextureIndex + 1) % _historyTextures.Length; - var historyReadTexture = _historyTextureIndex < 0 - ? _raymarchTexture - : _historyTextures[_historyTextureIndex]; - finalCloudTexture = _historyTextures[historyWriteIndex]!; - SetTexturePort("currentSample", _raymarchTexture); + historyWriteIndex = state.HistoryTextureIndex < 0 ? 0 : (state.HistoryTextureIndex + 1) % state.HistoryTextures.Length; + var historyReadTexture = state.HistoryTextureIndex < 0 + ? state.RaymarchTexture + : state.HistoryTextures[state.HistoryTextureIndex]; + finalCloudTexture = state.HistoryTextures[historyWriteIndex]!; + SetTexturePort("currentSample", state.RaymarchTexture); SetTexturePort("history", historyReadTexture!); SetMatrixPort( "previousViewProjection", - _historyTextureIndex < 0 + state.HistoryTextureIndex < 0 ? viewProjection : ResolvePreviousViewProjection( _programMetadata, - _previousViewProjection, + state.PreviousViewProjection, gpuProjection, nonRenderTargetProjection, - _previousView)); - SetFloatPort("resetHistory", _historyTextureIndex < 0 ? 1f : 0f); + state.PreviousView)); + SetFloatPort("resetHistory", state.HistoryTextureIndex < 0 ? 1f : 0f); } SetTexturePort("cloud", finalCloudTexture); - commands.SetRenderTarget(_raymarchTexture); - commands.SetViewport(new Rect(0f, 0f, _raymarchTexture.width, _raymarchTexture.height)); + commands.SetRenderTarget(state.RaymarchTexture); + commands.SetViewport(new Rect(0f, 0f, state.RaymarchTexture.width, state.RaymarchTexture.height)); commands.ClearRenderTarget(false, true, Color.clear); commands.DrawProcedural(Matrix4x4.identity, _material!, raymarchPass, MeshTopology.Triangles, 3, 1); if (useTemporalHistory) @@ -198,9 +199,9 @@ namespace GameCult.Eve.UnityScene.Fields commands.DrawProcedural(Matrix4x4.identity, _material!, compositePass, MeshTopology.Triangles, 3, 1); if (useTemporalHistory) { - _historyTextureIndex = historyWriteIndex; - _previousViewProjection = viewProjection; - _previousView = camera.worldToCameraMatrix; + state.HistoryTextureIndex = historyWriteIndex; + state.PreviousViewProjection = viewProjection; + state.PreviousView = camera.worldToCameraMatrix; } CompositeCount++; } @@ -406,31 +407,51 @@ namespace GameCult.Eve.UnityScene.Fields if (_layers == null) _layers = gameObject.AddComponent(); } - private void EnsureVolumeTextures(Camera camera, EveUnityFieldVolumeProjection field) + private CameraRenderState CameraState(Camera camera) + { + var cameraId = camera.GetInstanceID(); + if (_cameraRenderStates.TryGetValue(cameraId, out var state)) return state; + state = new CameraRenderState(); + _cameraRenderStates.Add(cameraId, state); + return state; + } + + private void EnsureVolumeTextures( + Camera camera, + EveUnityFieldVolumeProjection field, + CameraRenderState state) { var downsample = Mathf.Clamp(NonNegativeInt(field, "downsample", 1), 0, 3); var width = Mathf.Max(1, camera.pixelWidth >> downsample); var height = Mathf.Max(1, camera.pixelHeight >> downsample); - var resetHistory = EnsureRenderTexture(ref _raymarchTexture, width, height, "Eve Fields Volume Raymarch"); + var resetHistory = EnsureRenderTexture( + ref state.RaymarchTexture, + width, + height, + $"Eve Fields Volume Raymarch {camera.GetInstanceID()}"); if (HasTemporalProgram()) { - resetHistory |= EnsureRenderTexture(ref _historyTextures[0], width, height, "Eve Fields Volume History A"); - resetHistory |= EnsureRenderTexture(ref _historyTextures[1], width, height, "Eve Fields Volume History B"); + resetHistory |= EnsureRenderTexture(ref state.HistoryTextures[0], width, height, "Eve Fields Volume History A"); + resetHistory |= EnsureRenderTexture(ref state.HistoryTextures[1], width, height, "Eve Fields Volume History B"); } else { - ReleaseRenderTexture(ref _historyTextures[0]); - ReleaseRenderTexture(ref _historyTextures[1]); + ReleaseRenderTexture(ref state.HistoryTextures[0]); + ReleaseRenderTexture(ref state.HistoryTextures[1]); resetHistory = true; } - if (resetHistory) ResetTemporalHistory(); + if (resetHistory) ResetTemporalHistory(state); } private void ReleaseRenderState() { - ReleaseRenderTexture(ref _raymarchTexture); - ReleaseRenderTexture(ref _historyTextures[0]); - ReleaseRenderTexture(ref _historyTextures[1]); + foreach (var state in _cameraRenderStates.Values) + { + ReleaseRenderTexture(ref state.RaymarchTexture); + ReleaseRenderTexture(ref state.HistoryTextures[0]); + ReleaseRenderTexture(ref state.HistoryTextures[1]); + } + _cameraRenderStates.Clear(); if (_material != null) { Destroy(_material); _material = null; } _sourceShader = null; _programMetadata = null; @@ -467,7 +488,7 @@ namespace GameCult.Eve.UnityScene.Fields { var cameraData = frameData.Get(); if (cameraData.camera == null || _owner._host == null || - _owner._host.ActiveCameraTransform != cameraData.camera.transform) + !CanRenderCamera(_owner._host, cameraData.camera)) return; var resourceData = frameData.Get(); @@ -491,9 +512,23 @@ namespace GameCult.Eve.UnityScene.Fields private void ResetTemporalHistory() { - _historyTextureIndex = -1; - _previousViewProjection = Matrix4x4.identity; - _previousView = Matrix4x4.identity; + foreach (var state in _cameraRenderStates.Values) ResetTemporalHistory(state); + } + + private static void ResetTemporalHistory(CameraRenderState state) + { + state.HistoryTextureIndex = -1; + state.PreviousViewProjection = Matrix4x4.identity; + state.PreviousView = Matrix4x4.identity; + } + + private sealed class CameraRenderState + { + internal RenderTexture? RaymarchTexture; + internal readonly RenderTexture?[] HistoryTextures = new RenderTexture?[2]; + internal int HistoryTextureIndex = -1; + internal Matrix4x4 PreviousViewProjection = Matrix4x4.identity; + internal Matrix4x4 PreviousView = Matrix4x4.identity; } public static Matrix4x4 ResolvePreviousViewProjection( diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs index 9e93c2a..33130a0 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs @@ -159,6 +159,13 @@ namespace GameCult.Eve.UnityScene.Tests public void UrpRendererFeatureOwnsGenericWorldEffectScheduling() { Assert.That(typeof(EveUnityRendererFeature).IsSubclassOf(typeof(ScriptableRendererFeature)), Is.True); + var supportsCamera = typeof(EveUnityRendererFeature).GetMethod( + "SupportsCamera", + BindingFlags.NonPublic | BindingFlags.Static); + Assert.That(supportsCamera, Is.Not.Null); + Assert.That(supportsCamera!.Invoke(null, new object[] { CameraType.Game }), Is.True); + Assert.That(supportsCamera.Invoke(null, new object[] { CameraType.SceneView }), Is.True); + Assert.That(supportsCamera.Invoke(null, new object[] { CameraType.Preview }), Is.False); AssertRenderOrder(typeof(EveUnityFieldsVolumeRenderer), 100); AssertRenderOrder(typeof(EveUnityFieldsParticleRenderer), 200); AssertRenderOrder(typeof(EveUnityAdaptiveExposureRenderer), 300); diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index f7589b9..7c390d9 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.66", + "version": "0.3.67", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {