using UnityEngine; public partial class MeshPainter { // --- Backend selection / switching --- private void SetupBackend(bool gpu, Color32[][] canonical) { // SetupGpu returns false if the shaders are missing and it fell back to CPU; honor // the real outcome so _gpuActive never claims GPU while _gpu is null (would NRE on // the next SwitchBackend / paint). if (gpu) { _gpuActive = SetupGpu(canonical); } else { SetupCpu(canonical); _gpuActive = false; } } /// Switches the backend at runtime, carrying over all painted maps. private void SwitchBackend(bool toGpu) { var canonical = new Color32[MAP_COUNT][]; for (int mi = 0; mi < MAP_COUNT; mi++) canonical[mi] = ReadCanonical(mi); RebuildBackend(toGpu, canonical); Debug.Log($"[MeshPainter] Backend switched -> {(toGpu ? "GPU" : "CPU")} painting.", this); } /// /// Tears down the active backend and rebuilds it from externally supplied canonical maps (load / /// late-join snapshot / undo). Runs the island/dilation seeding path so edge padding is rebuilt for /// the new content. The input arrays are never mutated (SetupBackend clones its seed). /// private void ReseedCanonical(Color32[][] canonical) => RebuildBackend(useGpu, canonical); /// Tears down the active backend and sets up the requested one on the given maps. private void RebuildBackend(bool gpu, Color32[][] canonical) { if (_gpuActive) TeardownGpu(); else TeardownCpu(); SetupBackend(gpu, canonical); _hasLastScreen = false; } /// Seed source for a map: the supplied canonical data when valid, else the material seed. private Color32[] ResolveSeed(int mi, Color32[][] canonical) { return (canonical != null && canonical[mi] != null && canonical[mi].Length == textureSize * textureSize) ? canonical[mi] : _seedCanonical[mi]; } // Backend-agnostic stroke dispatch (GPU vs CPU), shared by local painting and replayed net stamps. private void BeginBackendStroke() { if (_gpuActive) BeginGpuStroke(); else ResetCpuDirty(); } private void DrawBackendStamp(Vector3 pLocal, float radiusLocal, Vector3 u, Vector3 v, Vector3 n) { if (_gpuActive) DrawBrushGpu(pLocal, radiusLocal, u, v, n); else PaintStamp3D(pLocal, radiusLocal, u, v, n); } private void EndBackendStroke() { if (_gpuActive) EndGpuStroke(); else EndCpuStroke(); } /// Reads a map from the active backend into canonical (material) layout. private Color32[] ReadCanonical(int mi) { if (_gpuActive) { var gm = _gpu[mi]; // MSO: read the packed final mask RT (R=metallic, G=occlusion, A=smoothness). // Others: the display RT. The mask map is linear data; color maps read sRGB. if (mi == MAP_MSO) return ReadRtPixels(gm.final, linear: true); return ReadRtPixels(gm.display, linear: MapIsLinear(mi)); } return (Color32[])_cpu[mi].pixels.Clone(); } private static bool MapIsLinear(int mi) => mi == MAP_MSO; // Material slots per map, as pre-hashed IDs (this runs per stroke end - no per-call allocations). // MSO: one mask map drives three URP slots: _MetallicGlossMap (R/A) + _OcclusionMap (G) + // _ParallaxMap (G = occlusion reused as a 0.5-centred height for parallax/relief). private static readonly int[][] MapPropIds = { new[] { BaseMapId, MainTexId }, // MAP_ALBEDO new[] { MetallicGlossMapId, OcclusionMapId, ParallaxMapId }, // MAP_MSO new[] { EmissionMapId }, // MAP_EM }; private void AssignMapTexture(int mi, Texture tex) { if (!_materialInstance) return; foreach (int id in MapPropIds[mi]) if (_materialInstance.HasProperty(id)) _materialInstance.SetTexture(id, tex); } }