Skip to content

Commit a5e207e

Browse files
authored
Merge pull request #1132 from KamilDev/fix/composited-screenshot-wait-for-end-of-frame
fix(screenshot): wait for end-of-frame before composited capture
2 parents 0e02e5d + 6e4302d commit a5e207e

2 files changed

Lines changed: 84 additions & 40 deletions

File tree

MCPForUnity/Editor/Tools/ManageUI.cs

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -816,39 +816,6 @@ private static object SerializeVisualElement(VisualElement element, int depth, i
816816
private static bool s_pendingCaptureDone;
817817
private static bool s_pendingCaptureStarted;
818818

819-
// MonoBehaviour that captures a screenshot at end-of-frame in play mode.
820-
private sealed class MCP_ScreenCapturer : MonoBehaviour
821-
{
822-
private System.Collections.IEnumerator Start()
823-
{
824-
yield return new WaitForEndOfFrame();
825-
826-
if (!ScreenshotUtility.IsScreenCaptureModuleAvailable)
827-
{
828-
Debug.LogError("[MCP] " + ScreenshotUtility.ScreenCaptureModuleNotAvailableError);
829-
ManageUI.s_pendingCaptureTex = null;
830-
ManageUI.s_pendingCaptureDone = false;
831-
ManageUI.s_pendingCaptureStarted = false;
832-
Destroy(gameObject);
833-
yield break;
834-
}
835-
836-
try
837-
{
838-
ManageUI.s_pendingCaptureTex = ScreenCapture.CaptureScreenshotAsTexture();
839-
ManageUI.s_pendingCaptureDone = true;
840-
}
841-
catch (Exception ex)
842-
{
843-
Debug.LogError($"[MCP] ScreenCapture failed: {ex.Message}");
844-
ManageUI.s_pendingCaptureTex = null;
845-
ManageUI.s_pendingCaptureDone = false;
846-
}
847-
ManageUI.s_pendingCaptureStarted = false;
848-
Destroy(gameObject);
849-
}
850-
}
851-
852819
private static object RenderUI(JObject @params)
853820
{
854821
var p = new ToolParams(@params);
@@ -978,11 +945,12 @@ private static object RenderUI(JObject @params)
978945
s_pendingCaptureDone = false;
979946
s_pendingCaptureTex = null;
980947
s_pendingCaptureStarted = true;
981-
var captureGo = new GameObject("__MCP_ScreenCapturer__")
948+
ScreenshotCapturer.Begin(1, tex =>
982949
{
983-
hideFlags = HideFlags.HideAndDontSave
984-
};
985-
captureGo.AddComponent<MCP_ScreenCapturer>();
950+
s_pendingCaptureTex = tex;
951+
s_pendingCaptureDone = true;
952+
s_pendingCaptureStarted = false;
953+
});
986954

987955
return new SuccessResponse(
988956
"Play-mode screenshot capture queued (WaitForEndOfFrame). Call render_ui again to retrieve the rendered image.",

MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ public static bool IsScreenCaptureModuleAvailable
8181
}
8282
}
8383

84+
/// <summary>
85+
/// Reflective invocation of ScreenCapture.CaptureScreenshotAsTexture(int). Returns
86+
/// null when the Screen Capture module is disabled. Centralised so the only direct
87+
/// reference to ScreenCapture lives here.
88+
/// </summary>
89+
internal static Texture2D InvokeCaptureScreenshotAsTexture(int superSize)
90+
{
91+
if (!IsScreenCaptureModuleAvailable || s_captureScreenshotAsTextureMethod == null)
92+
return null;
93+
return s_captureScreenshotAsTextureMethod.Invoke(null, new object[] { superSize }) as Texture2D;
94+
}
95+
8496
/// <summary>
8597
/// Error message to display when Screen Capture module is not available.
8698
/// </summary>
@@ -239,6 +251,35 @@ public static ScreenshotCaptureResult CaptureFromCameraToProjectFolder(
239251
return result;
240252
}
241253

254+
#if UNITY_EDITOR
255+
// Synchronously drive a WaitForEndOfFrame ScreenshotCapturer by pumping the editor's
256+
// player loop. Play-mode only; EditorApplication.Step is a no-op in edit mode.
257+
private static Texture2D CaptureCompositedAfterFrame(int superSize, int timeoutSteps = 5)
258+
{
259+
Texture2D result = null;
260+
bool done = false;
261+
bool callerReturned = false;
262+
ScreenshotCapturer.Begin(superSize, tex =>
263+
{
264+
// Late completion after the spin loop timed out: caller will never consume
265+
// the texture, so destroy it here to avoid leaking a Unity object.
266+
if (callerReturned)
267+
{
268+
if (tex != null) DestroyTexture(tex);
269+
return;
270+
}
271+
result = tex;
272+
done = true;
273+
});
274+
for (int i = 0; i < timeoutSteps && !done; i++)
275+
{
276+
UnityEditor.EditorApplication.Step();
277+
}
278+
callerReturned = true;
279+
return result;
280+
}
281+
#endif
282+
242283
/// <summary>
243284
/// Captures a screenshot using ScreenCapture.CaptureScreenshotAsTexture, which captures the
244285
/// final composited frame including UI Toolkit overlays, post-processing, etc.
@@ -269,9 +310,15 @@ public static ScreenshotCaptureResult CaptureComposited(
269310
int imgW = 0, imgH = 0;
270311
try
271312
{
272-
// Reflective call to ScreenCapture.CaptureScreenshotAsTexture so the file compiles
273-
// even when the Screen Capture module (com.unity.modules.screencapture) is disabled.
274-
tex = s_captureScreenshotAsTextureMethod.Invoke(null, new object[] { result.SuperSize }) as Texture2D;
313+
#if UNITY_EDITOR
314+
// In play mode, inline ScreenCapture reads a backbuffer before UITK has
315+
// composited; route through WaitForEndOfFrame instead.
316+
tex = Application.isPlaying
317+
? CaptureCompositedAfterFrame(result.SuperSize)
318+
: InvokeCaptureScreenshotAsTexture(result.SuperSize);
319+
#else
320+
tex = InvokeCaptureScreenshotAsTexture(result.SuperSize);
321+
#endif
275322
if (tex == null)
276323
{
277324
// Fallback to camera-based if ScreenCapture fails
@@ -787,4 +834,33 @@ private static string GetProjectRootPath()
787834
return root;
788835
}
789836
}
837+
838+
/// <summary>
839+
/// Transient MonoBehaviour that yields WaitForEndOfFrame, calls
840+
/// ScreenCapture.CaptureScreenshotAsTexture, invokes the callback, and self-destructs.
841+
/// </summary>
842+
public sealed class ScreenshotCapturer : MonoBehaviour
843+
{
844+
private int _superSize = 1;
845+
private Action<Texture2D> _onComplete;
846+
847+
/// <summary>Spawns a hidden GameObject, attaches a capturer, returns immediately.</summary>
848+
public static void Begin(int superSize, Action<Texture2D> onComplete)
849+
{
850+
var go = new GameObject("__MCP_ScreenshotCapturer__") { hideFlags = HideFlags.HideAndDontSave };
851+
var c = go.AddComponent<ScreenshotCapturer>();
852+
c._superSize = Mathf.Max(1, superSize);
853+
c._onComplete = onComplete;
854+
}
855+
856+
private System.Collections.IEnumerator Start()
857+
{
858+
yield return new WaitForEndOfFrame();
859+
Texture2D tex = null;
860+
try { tex = ScreenshotUtility.InvokeCaptureScreenshotAsTexture(_superSize); }
861+
catch (Exception ex) { Debug.LogError($"[MCP for Unity] CaptureScreenshotAsTexture failed: {ex.Message}"); }
862+
_onComplete?.Invoke(tex);
863+
Destroy(gameObject);
864+
}
865+
}
790866
}

0 commit comments

Comments
 (0)