Did a few things
This commit is contained in:
13
Assets/UserReporting/Scripts/Plugin/ILogListener.cs
Normal file
13
Assets/UserReporting/Scripts/Plugin/ILogListener.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets.UserReporting.Scripts.Plugin
|
||||
{
|
||||
public interface ILogListener
|
||||
{
|
||||
#region Methods
|
||||
|
||||
void ReceiveLogMessage(string logString, string stackTrace, LogType logType);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
11
Assets/UserReporting/Scripts/Plugin/ILogListener.cs.meta
Normal file
11
Assets/UserReporting/Scripts/Plugin/ILogListener.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51133d588054ae44b8297105d9672ecf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
57
Assets/UserReporting/Scripts/Plugin/LogDispatcher.cs
Normal file
57
Assets/UserReporting/Scripts/Plugin/LogDispatcher.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Assets.UserReporting.Scripts.Plugin
|
||||
{
|
||||
public static class LogDispatcher
|
||||
{
|
||||
#region Static Constructors
|
||||
|
||||
static LogDispatcher()
|
||||
{
|
||||
LogDispatcher.listeners = new List<WeakReference>();
|
||||
Application.logMessageReceivedThreaded += (logString, stackTrace, logType) =>
|
||||
{
|
||||
lock (LogDispatcher.listeners)
|
||||
{
|
||||
int i = 0;
|
||||
while (i < LogDispatcher.listeners.Count)
|
||||
{
|
||||
WeakReference listener = LogDispatcher.listeners[i];
|
||||
ILogListener logListener = listener.Target as ILogListener;
|
||||
if (logListener != null)
|
||||
{
|
||||
logListener.ReceiveLogMessage(logString, stackTrace, logType);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogDispatcher.listeners.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Fields
|
||||
|
||||
private static List<WeakReference> listeners;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Methods
|
||||
|
||||
public static void Register(ILogListener logListener)
|
||||
{
|
||||
lock (LogDispatcher.listeners)
|
||||
{
|
||||
LogDispatcher.listeners.Add(new WeakReference(logListener));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
11
Assets/UserReporting/Scripts/Plugin/LogDispatcher.cs.meta
Normal file
11
Assets/UserReporting/Scripts/Plugin/LogDispatcher.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59d79028203775a4fa6483f0f73d861e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
2156
Assets/UserReporting/Scripts/Plugin/SimpleJson.cs
Normal file
2156
Assets/UserReporting/Scripts/Plugin/SimpleJson.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/UserReporting/Scripts/Plugin/SimpleJson.cs.meta
Normal file
11
Assets/UserReporting/Scripts/Plugin/SimpleJson.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf0f2cc16dbcd0d4b9c03fa534b483e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
32
Assets/UserReporting/Scripts/Plugin/UnityUserReportParser.cs
Normal file
32
Assets/UserReporting/Scripts/Plugin/UnityUserReportParser.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Unity.Cloud.UserReporting.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides static methods for parsing user reports.
|
||||
/// </summary>
|
||||
public static class UnityUserReportParser
|
||||
{
|
||||
#region Static Methods
|
||||
|
||||
/// <summary>
|
||||
/// Parses a user report.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON.</param>
|
||||
/// <returns>The user report.</returns>
|
||||
public static UserReport ParseUserReport(string json)
|
||||
{
|
||||
return SimpleJson.SimpleJson.DeserializeObject<UserReport>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a user report list.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON.</param>
|
||||
/// <returns>The user report list.</returns>
|
||||
public static UserReportList ParseUserReportList(string json)
|
||||
{
|
||||
return SimpleJson.SimpleJson.DeserializeObject<UserReportList>(json);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ee05c129c16524468433a48bab26d51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
151
Assets/UserReporting/Scripts/Plugin/UnityUserReporting.cs
Normal file
151
Assets/UserReporting/Scripts/Plugin/UnityUserReporting.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Unity.Cloud.UserReporting.Client;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Cloud.UserReporting.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a starting point for Unity User Reporting.
|
||||
/// </summary>
|
||||
public static class UnityUserReporting
|
||||
{
|
||||
#region Static Fields
|
||||
|
||||
private static UserReportingClient currentClient;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current client.
|
||||
/// </summary>
|
||||
public static UserReportingClient CurrentClient
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UnityUserReporting.currentClient == null)
|
||||
{
|
||||
UnityUserReporting.Configure();
|
||||
}
|
||||
return UnityUserReporting.currentClient;
|
||||
}
|
||||
private set { UnityUserReporting.currentClient = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Methods
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The endpoint.</param>
|
||||
/// <param name="projectIdentifier">The project identifier.</param>
|
||||
/// <param name="platform">The plaform.</param>
|
||||
/// <param name="configuration">The configuration.</param>
|
||||
public static void Configure(string endpoint, string projectIdentifier, IUserReportingPlatform platform, UserReportingClientConfiguration configuration)
|
||||
{
|
||||
UnityUserReporting.CurrentClient = new UserReportingClient(endpoint, projectIdentifier, platform, configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
/// <param name="endpoint"></param>
|
||||
/// <param name="projectIdentifier"></param>
|
||||
/// <param name="configuration"></param>
|
||||
public static void Configure(string endpoint, string projectIdentifier, UserReportingClientConfiguration configuration)
|
||||
{
|
||||
UnityUserReporting.CurrentClient = new UserReportingClient(endpoint, projectIdentifier, UnityUserReporting.GetPlatform(), configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
/// <param name="projectIdentifier"></param>
|
||||
/// <param name="configuration"></param>
|
||||
public static void Configure(string projectIdentifier, UserReportingClientConfiguration configuration)
|
||||
{
|
||||
UnityUserReporting.Configure("https://userreporting.cloud.unity3d.com", projectIdentifier, UnityUserReporting.GetPlatform(), configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
/// <param name="projectIdentifier"></param>
|
||||
public static void Configure(string projectIdentifier)
|
||||
{
|
||||
UnityUserReporting.Configure("https://userreporting.cloud.unity3d.com", projectIdentifier, UnityUserReporting.GetPlatform(), new UserReportingClientConfiguration());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
public static void Configure()
|
||||
{
|
||||
UnityUserReporting.Configure("https://userreporting.cloud.unity3d.com", Application.cloudProjectId, UnityUserReporting.GetPlatform(), new UserReportingClientConfiguration());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
/// <param name="configuration"></param>
|
||||
public static void Configure(UserReportingClientConfiguration configuration)
|
||||
{
|
||||
UnityUserReporting.Configure("https://userreporting.cloud.unity3d.com", Application.cloudProjectId, UnityUserReporting.GetPlatform(), configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
/// <param name="projectIdentifier"></param>
|
||||
/// <param name="platform"></param>
|
||||
/// <param name="configuration"></param>
|
||||
public static void Configure(string projectIdentifier, IUserReportingPlatform platform, UserReportingClientConfiguration configuration)
|
||||
{
|
||||
UnityUserReporting.Configure("https://userreporting.cloud.unity3d.com", projectIdentifier, platform, configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
/// <param name="platform"></param>
|
||||
/// <param name="configuration"></param>
|
||||
public static void Configure(IUserReportingPlatform platform, UserReportingClientConfiguration configuration)
|
||||
{
|
||||
UnityUserReporting.Configure("https://userreporting.cloud.unity3d.com", Application.cloudProjectId, platform, configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures Unity User Reporting.
|
||||
/// </summary>
|
||||
/// <param name="platform"></param>
|
||||
public static void Configure(IUserReportingPlatform platform)
|
||||
{
|
||||
UnityUserReporting.Configure("https://userreporting.cloud.unity3d.com", Application.cloudProjectId, platform, new UserReportingClientConfiguration());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the platform.
|
||||
/// </summary>
|
||||
/// <returns>The platform.</returns>
|
||||
private static IUserReportingPlatform GetPlatform()
|
||||
{
|
||||
return new UnityUserReportingPlatform();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses an existing client.
|
||||
/// </summary>
|
||||
/// <param name="client">The client.</param>
|
||||
public static void Use(UserReportingClient client)
|
||||
{
|
||||
if (client != null)
|
||||
{
|
||||
UnityUserReporting.CurrentClient = client;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 729c07e53d42a0f46b82d4f2cd5d238d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,633 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Assets.UserReporting.Scripts.Plugin;
|
||||
using Unity.Cloud.UserReporting.Client;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Analytics;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Unity.Cloud.UserReporting.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Unity user reporting platform.
|
||||
/// </summary>
|
||||
public class UnityUserReportingPlatform : IUserReportingPlatform, ILogListener
|
||||
{
|
||||
#region Nested Types
|
||||
|
||||
/// <summary>
|
||||
/// Represents a log message.
|
||||
/// </summary>
|
||||
private struct LogMessage
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log string.
|
||||
/// </summary>
|
||||
public string LogString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log type.
|
||||
/// </summary>
|
||||
public LogType LogType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stack trace.
|
||||
/// </summary>
|
||||
public string StackTrace;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a post operation.
|
||||
/// </summary>
|
||||
private class PostOperation
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback.
|
||||
/// </summary>
|
||||
public Action<bool, byte[]> Callback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the progress callback.
|
||||
/// </summary>
|
||||
public Action<float, float> ProgressCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the web request.
|
||||
/// </summary>
|
||||
public UnityWebRequest WebRequest { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profiler sampler.
|
||||
/// </summary>
|
||||
private struct ProfilerSampler
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the recorder.
|
||||
/// </summary>
|
||||
public Recorder Recorder;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the sampler.
|
||||
/// </summary>
|
||||
/// <returns>The value of the sampler.</returns>
|
||||
public double GetValue()
|
||||
{
|
||||
if (this.Recorder == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return this.Recorder.elapsedNanoseconds / 1000000.0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a screenshot operation.
|
||||
/// </summary>
|
||||
private class ScreenshotOperation
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback.
|
||||
/// </summary>
|
||||
public Action<int, byte[]> Callback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the frame number.
|
||||
/// </summary>
|
||||
public int FrameNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum height.
|
||||
/// </summary>
|
||||
public int MaximumHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum width.
|
||||
/// </summary>
|
||||
public int MaximumWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the PNG data.
|
||||
/// </summary>
|
||||
public byte[] PngData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source.
|
||||
/// </summary>
|
||||
public object Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stage.
|
||||
/// </summary>
|
||||
public ScreenshotStage Stage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the texture.
|
||||
/// </summary>
|
||||
public Texture2D Texture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the texture (resized).
|
||||
/// </summary>
|
||||
public Texture2D TextureResized { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Unity frame.
|
||||
/// </summary>
|
||||
public int UnityFrame { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the wait frames.
|
||||
/// </summary>
|
||||
public int WaitFrames { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a screenshot stage.
|
||||
/// </summary>
|
||||
private enum ScreenshotStage
|
||||
{
|
||||
/// <summary>
|
||||
/// Render.
|
||||
/// </summary>
|
||||
Render = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Read pixels.
|
||||
/// </summary>
|
||||
ReadPixels = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Gets pixels.
|
||||
/// </summary>
|
||||
GetPixels = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Encode to PNG.
|
||||
/// </summary>
|
||||
EncodeToPNG = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Done.
|
||||
/// </summary>
|
||||
Done = 4
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UnityUserReportingPlatform"/> class.
|
||||
/// </summary>
|
||||
public UnityUserReportingPlatform()
|
||||
{
|
||||
this.logMessages = new List<LogMessage>();
|
||||
this.postOperations = new List<PostOperation>();
|
||||
this.screenshotOperations = new List<ScreenshotOperation>();
|
||||
this.screenshotStopwatch = new Stopwatch();
|
||||
|
||||
// Recorders
|
||||
this.profilerSamplers = new List<ProfilerSampler>();
|
||||
Dictionary<string, string> samplerNames = this.GetSamplerNames();
|
||||
foreach (KeyValuePair<string, string> kvp in samplerNames)
|
||||
{
|
||||
Sampler sampler = Sampler.Get(kvp.Key);
|
||||
if (sampler.isValid)
|
||||
{
|
||||
Recorder recorder = sampler.GetRecorder();
|
||||
recorder.enabled = true;
|
||||
ProfilerSampler profilerSampler = new ProfilerSampler();
|
||||
profilerSampler.Name = kvp.Value;
|
||||
profilerSampler.Recorder = recorder;
|
||||
this.profilerSamplers.Add(profilerSampler);
|
||||
}
|
||||
}
|
||||
|
||||
// Log Messages
|
||||
LogDispatcher.Register(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private List<LogMessage> logMessages;
|
||||
|
||||
private List<PostOperation> postOperations;
|
||||
|
||||
private List<ProfilerSampler> profilerSamplers;
|
||||
|
||||
private List<ScreenshotOperation> screenshotOperations;
|
||||
|
||||
private Stopwatch screenshotStopwatch;
|
||||
|
||||
private List<PostOperation> taskOperations;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public T DeserializeJson<T>(string json)
|
||||
{
|
||||
return SimpleJson.SimpleJson.DeserializeObject<T>(json);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void OnEndOfFrame(UserReportingClient client)
|
||||
{
|
||||
// Screenshot Operations
|
||||
int screenshotOperationIndex = 0;
|
||||
while (screenshotOperationIndex < this.screenshotOperations.Count)
|
||||
{
|
||||
ScreenshotOperation screenshotOperation = this.screenshotOperations[screenshotOperationIndex];
|
||||
if (screenshotOperation.Stage == ScreenshotStage.Render && screenshotOperation.WaitFrames < 1)
|
||||
{
|
||||
Camera cameraSource = screenshotOperation.Source as Camera;
|
||||
if (cameraSource != null)
|
||||
{
|
||||
this.screenshotStopwatch.Reset();
|
||||
this.screenshotStopwatch.Start();
|
||||
RenderTexture renderTexture = new RenderTexture(screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, 24);
|
||||
RenderTexture originalTargetTexture = cameraSource.targetTexture;
|
||||
cameraSource.targetTexture = renderTexture;
|
||||
cameraSource.Render();
|
||||
cameraSource.targetTexture = originalTargetTexture;
|
||||
this.screenshotStopwatch.Stop();
|
||||
client.SampleClientMetric("Screenshot.Render", this.screenshotStopwatch.ElapsedMilliseconds);
|
||||
screenshotOperation.Source = renderTexture;
|
||||
screenshotOperation.Stage = ScreenshotStage.ReadPixels;
|
||||
screenshotOperation.WaitFrames = 15;
|
||||
screenshotOperationIndex++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
screenshotOperation.Stage = ScreenshotStage.ReadPixels;
|
||||
}
|
||||
}
|
||||
if (screenshotOperation.Stage == ScreenshotStage.ReadPixels && screenshotOperation.WaitFrames < 1)
|
||||
{
|
||||
this.screenshotStopwatch.Reset();
|
||||
this.screenshotStopwatch.Start();
|
||||
RenderTexture renderTextureSource = screenshotOperation.Source as RenderTexture;
|
||||
if (renderTextureSource != null)
|
||||
{
|
||||
RenderTexture originalActiveTexture = RenderTexture.active;
|
||||
RenderTexture.active = renderTextureSource;
|
||||
screenshotOperation.Texture = new Texture2D(renderTextureSource.width, renderTextureSource.height, TextureFormat.ARGB32, true);
|
||||
screenshotOperation.Texture.ReadPixels(new Rect(0, 0, renderTextureSource.width, renderTextureSource.height), 0, 0);
|
||||
screenshotOperation.Texture.Apply();
|
||||
RenderTexture.active = originalActiveTexture;
|
||||
}
|
||||
else
|
||||
{
|
||||
screenshotOperation.Texture = new Texture2D(Screen.width, Screen.height, TextureFormat.ARGB32, true);
|
||||
screenshotOperation.Texture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
|
||||
screenshotOperation.Texture.Apply();
|
||||
}
|
||||
this.screenshotStopwatch.Stop();
|
||||
client.SampleClientMetric("Screenshot.ReadPixels", this.screenshotStopwatch.ElapsedMilliseconds);
|
||||
screenshotOperation.Stage = ScreenshotStage.GetPixels;
|
||||
screenshotOperation.WaitFrames = 15;
|
||||
screenshotOperationIndex++;
|
||||
continue;
|
||||
}
|
||||
if (screenshotOperation.Stage == ScreenshotStage.GetPixels && screenshotOperation.WaitFrames < 1)
|
||||
{
|
||||
this.screenshotStopwatch.Reset();
|
||||
this.screenshotStopwatch.Start();
|
||||
int maximumWidth = screenshotOperation.MaximumWidth > 32 ? screenshotOperation.MaximumWidth : 32;
|
||||
int maximumHeight = screenshotOperation.MaximumHeight > 32 ? screenshotOperation.MaximumHeight : 32;
|
||||
int width = screenshotOperation.Texture.width;
|
||||
int height = screenshotOperation.Texture.height;
|
||||
int mipLevel = 0;
|
||||
while (width > maximumWidth || height > maximumHeight)
|
||||
{
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
mipLevel++;
|
||||
}
|
||||
screenshotOperation.TextureResized = new Texture2D(width, height);
|
||||
screenshotOperation.TextureResized.SetPixels(screenshotOperation.Texture.GetPixels(mipLevel));
|
||||
screenshotOperation.TextureResized.Apply();
|
||||
this.screenshotStopwatch.Stop();
|
||||
client.SampleClientMetric("Screenshot.GetPixels", this.screenshotStopwatch.ElapsedMilliseconds);
|
||||
screenshotOperation.Stage = ScreenshotStage.EncodeToPNG;
|
||||
screenshotOperation.WaitFrames = 15;
|
||||
screenshotOperationIndex++;
|
||||
continue;
|
||||
}
|
||||
if (screenshotOperation.Stage == ScreenshotStage.EncodeToPNG && screenshotOperation.WaitFrames < 1)
|
||||
{
|
||||
this.screenshotStopwatch.Reset();
|
||||
this.screenshotStopwatch.Start();
|
||||
screenshotOperation.PngData = screenshotOperation.TextureResized.EncodeToPNG();
|
||||
this.screenshotStopwatch.Stop();
|
||||
client.SampleClientMetric("Screenshot.EncodeToPNG", this.screenshotStopwatch.ElapsedMilliseconds);
|
||||
screenshotOperation.Stage = ScreenshotStage.Done;
|
||||
screenshotOperationIndex++;
|
||||
continue;
|
||||
}
|
||||
if (screenshotOperation.Stage == ScreenshotStage.Done && screenshotOperation.WaitFrames < 1)
|
||||
{
|
||||
screenshotOperation.Callback(screenshotOperation.FrameNumber, screenshotOperation.PngData);
|
||||
UnityEngine.Object.Destroy(screenshotOperation.Texture);
|
||||
UnityEngine.Object.Destroy(screenshotOperation.TextureResized);
|
||||
this.screenshotOperations.Remove(screenshotOperation);
|
||||
}
|
||||
screenshotOperation.WaitFrames--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void Post(string endpoint, string contentType, byte[] content, Action<float, float> progressCallback, Action<bool, byte[]> callback)
|
||||
{
|
||||
UnityWebRequest webRequest = new UnityWebRequest(endpoint, "POST");
|
||||
webRequest.uploadHandler = new UploadHandlerRaw(content);
|
||||
webRequest.downloadHandler = new DownloadHandlerBuffer();
|
||||
webRequest.SetRequestHeader("Content-Type", contentType);
|
||||
webRequest.SendWebRequest();
|
||||
UnityUserReportingPlatform.PostOperation postOperation = new UnityUserReportingPlatform.PostOperation();
|
||||
postOperation.WebRequest = webRequest;
|
||||
postOperation.Callback = callback;
|
||||
postOperation.ProgressCallback = progressCallback;
|
||||
this.postOperations.Add(postOperation);
|
||||
}
|
||||
|
||||
public void ReceiveLogMessage(string logString, string stackTrace, LogType logType)
|
||||
{
|
||||
lock (this.logMessages)
|
||||
{
|
||||
LogMessage logMessage = new LogMessage();
|
||||
logMessage.LogString = logString;
|
||||
logMessage.StackTrace = stackTrace;
|
||||
logMessage.LogType = logType;
|
||||
this.logMessages.Add(logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void RunTask(Func<object> task, Action<object> callback)
|
||||
{
|
||||
callback(task());
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void SendAnalyticsEvent(string eventName, Dictionary<string, object> eventData)
|
||||
{
|
||||
Analytics.CustomEvent(eventName, eventData);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public string SerializeJson(object instance)
|
||||
{
|
||||
return SimpleJson.SimpleJson.SerializeObject(instance);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void TakeScreenshot(int frameNumber, int maximumWidth, int maximumHeight, object source, Action<int, byte[]> callback)
|
||||
{
|
||||
ScreenshotOperation screenshotOperation = new ScreenshotOperation();
|
||||
screenshotOperation.FrameNumber = frameNumber;
|
||||
screenshotOperation.MaximumWidth = maximumWidth;
|
||||
screenshotOperation.MaximumHeight = maximumHeight;
|
||||
screenshotOperation.Source = source;
|
||||
screenshotOperation.Callback = callback;
|
||||
screenshotOperation.UnityFrame = Time.frameCount;
|
||||
this.screenshotOperations.Add(screenshotOperation);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void Update(UserReportingClient client)
|
||||
{
|
||||
// Log Messages
|
||||
lock (this.logMessages)
|
||||
{
|
||||
foreach (LogMessage logMessage in this.logMessages)
|
||||
{
|
||||
UserReportEventLevel eventLevel = UserReportEventLevel.Info;
|
||||
if (logMessage.LogType == LogType.Warning)
|
||||
{
|
||||
eventLevel = UserReportEventLevel.Warning;
|
||||
}
|
||||
else if (logMessage.LogType == LogType.Error)
|
||||
{
|
||||
eventLevel = UserReportEventLevel.Error;
|
||||
}
|
||||
else if (logMessage.LogType == LogType.Exception)
|
||||
{
|
||||
eventLevel = UserReportEventLevel.Error;
|
||||
}
|
||||
else if (logMessage.LogType == LogType.Assert)
|
||||
{
|
||||
eventLevel = UserReportEventLevel.Error;
|
||||
}
|
||||
if (client.IsConnectedToLogger)
|
||||
{
|
||||
client.LogEvent(eventLevel, logMessage.LogString, logMessage.StackTrace);
|
||||
}
|
||||
}
|
||||
this.logMessages.Clear();
|
||||
}
|
||||
|
||||
// Metrics
|
||||
if (client.Configuration.MetricsGatheringMode == MetricsGatheringMode.Automatic)
|
||||
{
|
||||
// Sample Automatic Metrics
|
||||
this.SampleAutomaticMetrics(client);
|
||||
|
||||
// Profiler Samplers
|
||||
foreach (ProfilerSampler profilerSampler in this.profilerSamplers)
|
||||
{
|
||||
client.SampleMetric(profilerSampler.Name, profilerSampler.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Post Operations
|
||||
int postOperationIndex = 0;
|
||||
while (postOperationIndex < this.postOperations.Count)
|
||||
{
|
||||
UnityUserReportingPlatform.PostOperation postOperation = this.postOperations[postOperationIndex];
|
||||
if (postOperation.WebRequest.isDone)
|
||||
{
|
||||
bool isError = postOperation.WebRequest.error != null && postOperation.WebRequest.responseCode != 200;
|
||||
if (isError)
|
||||
{
|
||||
string errorMessage = string.Format("UnityUserReportingPlatform.Post: {0} {1}", postOperation.WebRequest.responseCode, postOperation.WebRequest.error);
|
||||
UnityEngine.Debug.Log(errorMessage);
|
||||
client.LogEvent(UserReportEventLevel.Error, errorMessage);
|
||||
}
|
||||
postOperation.ProgressCallback(1, 1);
|
||||
postOperation.Callback(!isError, postOperation.WebRequest.downloadHandler.data);
|
||||
this.postOperations.Remove(postOperation);
|
||||
}
|
||||
else
|
||||
{
|
||||
postOperation.ProgressCallback(postOperation.WebRequest.uploadProgress, postOperation.WebRequest.downloadProgress);
|
||||
postOperationIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtual Methods
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public virtual IDictionary<string, string> GetDeviceMetadata()
|
||||
{
|
||||
Dictionary<string, string> deviceMetadata = new Dictionary<string, string>();
|
||||
|
||||
// Unity
|
||||
deviceMetadata.Add("BuildGUID", Application.buildGUID);
|
||||
deviceMetadata.Add("DeviceModel", SystemInfo.deviceModel);
|
||||
deviceMetadata.Add("DeviceType", SystemInfo.deviceType.ToString());
|
||||
deviceMetadata.Add("DeviceUniqueIdentifier", SystemInfo.deviceUniqueIdentifier);
|
||||
deviceMetadata.Add("DPI", Screen.dpi.ToString(CultureInfo.InvariantCulture));
|
||||
deviceMetadata.Add("GraphicsDeviceName", SystemInfo.graphicsDeviceName);
|
||||
deviceMetadata.Add("GraphicsDeviceType", SystemInfo.graphicsDeviceType.ToString());
|
||||
deviceMetadata.Add("GraphicsDeviceVendor", SystemInfo.graphicsDeviceVendor);
|
||||
deviceMetadata.Add("GraphicsDeviceVersion", SystemInfo.graphicsDeviceVersion);
|
||||
deviceMetadata.Add("GraphicsMemorySize", SystemInfo.graphicsMemorySize.ToString());
|
||||
deviceMetadata.Add("InstallerName", Application.installerName);
|
||||
deviceMetadata.Add("InstallMode", Application.installMode.ToString());
|
||||
deviceMetadata.Add("IsEditor", Application.isEditor.ToString());
|
||||
deviceMetadata.Add("IsFullScreen", Screen.fullScreen.ToString());
|
||||
deviceMetadata.Add("OperatingSystem", SystemInfo.operatingSystem);
|
||||
deviceMetadata.Add("OperatingSystemFamily", SystemInfo.operatingSystemFamily.ToString());
|
||||
deviceMetadata.Add("Orientation", Screen.orientation.ToString());
|
||||
deviceMetadata.Add("Platform", Application.platform.ToString());
|
||||
try
|
||||
{
|
||||
deviceMetadata.Add("QualityLevel", QualitySettings.names[QualitySettings.GetQualityLevel()]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Empty
|
||||
}
|
||||
deviceMetadata.Add("ResolutionWidth", Screen.currentResolution.width.ToString());
|
||||
deviceMetadata.Add("ResolutionHeight", Screen.currentResolution.height.ToString());
|
||||
deviceMetadata.Add("ResolutionRefreshRate", Screen.currentResolution.refreshRate.ToString());
|
||||
deviceMetadata.Add("SystemLanguage", Application.systemLanguage.ToString());
|
||||
deviceMetadata.Add("SystemMemorySize", SystemInfo.systemMemorySize.ToString());
|
||||
deviceMetadata.Add("TargetFrameRate", Application.targetFrameRate.ToString());
|
||||
deviceMetadata.Add("UnityVersion", Application.unityVersion);
|
||||
deviceMetadata.Add("Version", Application.version);
|
||||
|
||||
// Other
|
||||
deviceMetadata.Add("Source", "Unity");
|
||||
|
||||
// Type
|
||||
Type type = this.GetType();
|
||||
deviceMetadata.Add("IUserReportingPlatform", type.Name);
|
||||
|
||||
// Return
|
||||
return deviceMetadata;
|
||||
}
|
||||
|
||||
public virtual Dictionary<string, string> GetSamplerNames()
|
||||
{
|
||||
Dictionary<string, string> samplerNames = new Dictionary<string, string>();
|
||||
samplerNames.Add("AudioManager.FixedUpdate", "AudioManager.FixedUpdateInMilliseconds");
|
||||
samplerNames.Add("AudioManager.Update", "AudioManager.UpdateInMilliseconds");
|
||||
samplerNames.Add("LateBehaviourUpdate", "Behaviors.LateUpdateInMilliseconds");
|
||||
samplerNames.Add("BehaviourUpdate", "Behaviors.UpdateInMilliseconds");
|
||||
samplerNames.Add("Camera.Render", "Camera.RenderInMilliseconds");
|
||||
samplerNames.Add("Overhead", "Engine.OverheadInMilliseconds");
|
||||
samplerNames.Add("WaitForRenderJobs", "Engine.WaitForRenderJobsInMilliseconds");
|
||||
samplerNames.Add("WaitForTargetFPS", "Engine.WaitForTargetFPSInMilliseconds");
|
||||
samplerNames.Add("GUI.Repaint", "GUI.RepaintInMilliseconds");
|
||||
samplerNames.Add("Network.Update", "Network.UpdateInMilliseconds");
|
||||
samplerNames.Add("ParticleSystem.EndUpdateAll", "ParticleSystem.EndUpdateAllInMilliseconds");
|
||||
samplerNames.Add("ParticleSystem.Update", "ParticleSystem.UpdateInMilliseconds");
|
||||
samplerNames.Add("Physics.FetchResults", "Physics.FetchResultsInMilliseconds");
|
||||
samplerNames.Add("Physics.Processing", "Physics.ProcessingInMilliseconds");
|
||||
samplerNames.Add("Physics.ProcessReports", "Physics.ProcessReportsInMilliseconds");
|
||||
samplerNames.Add("Physics.Simulate", "Physics.SimulateInMilliseconds");
|
||||
samplerNames.Add("Physics.UpdateBodies", "Physics.UpdateBodiesInMilliseconds");
|
||||
samplerNames.Add("Physics.Interpolation", "Physics.InterpolationInMilliseconds");
|
||||
samplerNames.Add("Physics2D.DynamicUpdate", "Physics2D.DynamicUpdateInMilliseconds");
|
||||
samplerNames.Add("Physics2D.FixedUpdate", "Physics2D.FixedUpdateInMilliseconds");
|
||||
return samplerNames;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public virtual void ModifyUserReport(UserReport userReport)
|
||||
{
|
||||
// Active Scene
|
||||
Scene activeScene = SceneManager.GetActiveScene();
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("ActiveSceneName", activeScene.name));
|
||||
|
||||
// Main Camera
|
||||
Camera mainCamera = Camera.main;
|
||||
if (mainCamera != null)
|
||||
{
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("MainCameraName", mainCamera.name));
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("MainCameraPosition", mainCamera.transform.position.ToString()));
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("MainCameraForward", mainCamera.transform.forward.ToString()));
|
||||
|
||||
// Looking At
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(mainCamera.transform.position, mainCamera.transform.forward, out hit))
|
||||
{
|
||||
GameObject lookingAt = hit.transform.gameObject;
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("LookingAt", hit.point.ToString()));
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("LookingAtGameObject", lookingAt.name));
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("LookingAtGameObjectPosition", lookingAt.transform.position.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples automatic metrics.
|
||||
/// </summary>
|
||||
/// <param name="client">The client.</param>
|
||||
public virtual void SampleAutomaticMetrics(UserReportingClient client)
|
||||
{
|
||||
// Graphics
|
||||
client.SampleMetric("Graphics.FramesPerSecond", 1.0f / Time.deltaTime);
|
||||
|
||||
// Memory
|
||||
client.SampleMetric("Memory.MonoUsedSizeInBytes", Profiler.GetMonoUsedSizeLong());
|
||||
client.SampleMetric("Memory.TotalAllocatedMemoryInBytes", Profiler.GetTotalAllocatedMemoryLong());
|
||||
client.SampleMetric("Memory.TotalReservedMemoryInBytes", Profiler.GetTotalReservedMemoryLong());
|
||||
client.SampleMetric("Memory.TotalUnusedReservedMemoryInBytes", Profiler.GetTotalUnusedReservedMemoryLong());
|
||||
|
||||
// Battery
|
||||
client.SampleMetric("Battery.BatteryLevelInPercent", SystemInfo.batteryLevel);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f09d98000e755044b888de04e0949aa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,81 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Cloud.UserReporting.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Helps with updating the Unity User Reporting client.
|
||||
/// </summary>
|
||||
public class UnityUserReportingUpdater : IEnumerator
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UnityUserReportingUpdater"/> class.
|
||||
/// </summary>
|
||||
public UnityUserReportingUpdater()
|
||||
{
|
||||
this.waitForEndOfFrame = new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private int step;
|
||||
|
||||
private WaitForEndOfFrame waitForEndOfFrame;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current item.
|
||||
/// </summary>
|
||||
public object Current { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the next item.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether the move was successful.</returns>
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.step == 0)
|
||||
{
|
||||
UnityUserReporting.CurrentClient.Update();
|
||||
this.Current = null;
|
||||
this.step = 1;
|
||||
return true;
|
||||
}
|
||||
if (this.step == 1)
|
||||
{
|
||||
this.Current = this.waitForEndOfFrame;
|
||||
this.step = 2;
|
||||
return true;
|
||||
}
|
||||
if (this.step == 2)
|
||||
{
|
||||
UnityUserReporting.CurrentClient.UpdateOnEndOfFrame();
|
||||
this.Current = null;
|
||||
this.step = 3;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the updater.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
this.step = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 365f6256c05744344ae5900da7929b65
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/UserReporting/Scripts/Plugin/Version2018_3.meta
Normal file
8
Assets/UserReporting/Scripts/Plugin/Version2018_3.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e5d770e8bbb1104f8269dd2bb95937e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,426 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Assets.UserReporting.Scripts.Plugin;
|
||||
using Unity.Cloud.UserReporting.Client;
|
||||
using Unity.Screenshots;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Analytics;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Unity.Cloud.UserReporting.Plugin.Version2018_3
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Unity user reporting platform that supports async operations for screetshotting and user report creation.
|
||||
/// </summary>
|
||||
public class AsyncUnityUserReportingPlatform : IUserReportingPlatform, ILogListener
|
||||
{
|
||||
#region Nested Types
|
||||
|
||||
/// <summary>
|
||||
/// Represents a log message.
|
||||
/// </summary>
|
||||
private struct LogMessage
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log string.
|
||||
/// </summary>
|
||||
public string LogString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log type.
|
||||
/// </summary>
|
||||
public LogType LogType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stack trace.
|
||||
/// </summary>
|
||||
public string StackTrace;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a post operation.
|
||||
/// </summary>
|
||||
private class PostOperation
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback.
|
||||
/// </summary>
|
||||
public Action<bool, byte[]> Callback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the progress callback.
|
||||
/// </summary>
|
||||
public Action<float, float> ProgressCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the web request.
|
||||
/// </summary>
|
||||
public UnityWebRequest WebRequest { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profiler sampler.
|
||||
/// </summary>
|
||||
private struct ProfilerSampler
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the recorder.
|
||||
/// </summary>
|
||||
public Recorder Recorder;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the sampler.
|
||||
/// </summary>
|
||||
/// <returns>The value of the sampler.</returns>
|
||||
public double GetValue()
|
||||
{
|
||||
if (this.Recorder == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return this.Recorder.elapsedNanoseconds / 1000000.0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UnityUserReportingPlatform"/> class.
|
||||
/// </summary>
|
||||
public AsyncUnityUserReportingPlatform()
|
||||
{
|
||||
this.logMessages = new List<AsyncUnityUserReportingPlatform.LogMessage>();
|
||||
this.postOperations = new List<AsyncUnityUserReportingPlatform.PostOperation>();
|
||||
this.screenshotManager = new ScreenshotManager();
|
||||
|
||||
// Recorders
|
||||
this.profilerSamplers = new List<AsyncUnityUserReportingPlatform.ProfilerSampler>();
|
||||
Dictionary<string, string> samplerNames = this.GetSamplerNames();
|
||||
foreach (KeyValuePair<string, string> kvp in samplerNames)
|
||||
{
|
||||
Sampler sampler = Sampler.Get(kvp.Key);
|
||||
if (sampler.isValid)
|
||||
{
|
||||
Recorder recorder = sampler.GetRecorder();
|
||||
recorder.enabled = true;
|
||||
AsyncUnityUserReportingPlatform.ProfilerSampler profilerSampler = new AsyncUnityUserReportingPlatform.ProfilerSampler();
|
||||
profilerSampler.Name = kvp.Value;
|
||||
profilerSampler.Recorder = recorder;
|
||||
this.profilerSamplers.Add(profilerSampler);
|
||||
}
|
||||
}
|
||||
|
||||
// Log Messages
|
||||
LogDispatcher.Register(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private List<AsyncUnityUserReportingPlatform.LogMessage> logMessages;
|
||||
|
||||
private List<AsyncUnityUserReportingPlatform.PostOperation> postOperations;
|
||||
|
||||
private List<AsyncUnityUserReportingPlatform.ProfilerSampler> profilerSamplers;
|
||||
|
||||
private ScreenshotManager screenshotManager;
|
||||
|
||||
private List<AsyncUnityUserReportingPlatform.PostOperation> taskOperations;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public T DeserializeJson<T>(string json)
|
||||
{
|
||||
return SimpleJson.SimpleJson.DeserializeObject<T>(json);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void OnEndOfFrame(UserReportingClient client)
|
||||
{
|
||||
this.screenshotManager.OnEndOfFrame();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void Post(string endpoint, string contentType, byte[] content, Action<float, float> progressCallback, Action<bool, byte[]> callback)
|
||||
{
|
||||
UnityWebRequest webRequest = new UnityWebRequest(endpoint, "POST");
|
||||
webRequest.uploadHandler = new UploadHandlerRaw(content);
|
||||
webRequest.downloadHandler = new DownloadHandlerBuffer();
|
||||
webRequest.SetRequestHeader("Content-Type", contentType);
|
||||
webRequest.SendWebRequest();
|
||||
AsyncUnityUserReportingPlatform.PostOperation postOperation = new AsyncUnityUserReportingPlatform.PostOperation();
|
||||
postOperation.WebRequest = webRequest;
|
||||
postOperation.Callback = callback;
|
||||
postOperation.ProgressCallback = progressCallback;
|
||||
this.postOperations.Add(postOperation);
|
||||
}
|
||||
|
||||
public void ReceiveLogMessage(string logString, string stackTrace, LogType logType)
|
||||
{
|
||||
lock (this.logMessages)
|
||||
{
|
||||
LogMessage logMessage = new LogMessage();
|
||||
logMessage.LogString = logString;
|
||||
logMessage.StackTrace = stackTrace;
|
||||
logMessage.LogType = logType;
|
||||
this.logMessages.Add(logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void RunTask(Func<object> task, Action<object> callback)
|
||||
{
|
||||
callback(task());
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void SendAnalyticsEvent(string eventName, Dictionary<string, object> eventData)
|
||||
{
|
||||
Analytics.CustomEvent(eventName, eventData);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public string SerializeJson(object instance)
|
||||
{
|
||||
return SimpleJson.SimpleJson.SerializeObject(instance);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void TakeScreenshot(int frameNumber, int maximumWidth, int maximumHeight, object source, Action<int, byte[]> callback)
|
||||
{
|
||||
this.screenshotManager.TakeScreenshot(source, frameNumber, maximumWidth, maximumHeight, callback);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public void Update(UserReportingClient client)
|
||||
{
|
||||
// Log Messages
|
||||
lock (this.logMessages)
|
||||
{
|
||||
foreach (AsyncUnityUserReportingPlatform.LogMessage logMessage in this.logMessages)
|
||||
{
|
||||
UserReportEventLevel eventLevel = UserReportEventLevel.Info;
|
||||
if (logMessage.LogType == LogType.Warning)
|
||||
{
|
||||
eventLevel = UserReportEventLevel.Warning;
|
||||
}
|
||||
else if (logMessage.LogType == LogType.Error)
|
||||
{
|
||||
eventLevel = UserReportEventLevel.Error;
|
||||
}
|
||||
else if (logMessage.LogType == LogType.Exception)
|
||||
{
|
||||
eventLevel = UserReportEventLevel.Error;
|
||||
}
|
||||
else if (logMessage.LogType == LogType.Assert)
|
||||
{
|
||||
eventLevel = UserReportEventLevel.Error;
|
||||
}
|
||||
if (client.IsConnectedToLogger)
|
||||
{
|
||||
client.LogEvent(eventLevel, logMessage.LogString, logMessage.StackTrace);
|
||||
}
|
||||
}
|
||||
this.logMessages.Clear();
|
||||
}
|
||||
|
||||
// Metrics
|
||||
if (client.Configuration.MetricsGatheringMode == MetricsGatheringMode.Automatic)
|
||||
{
|
||||
// Sample Automatic Metrics
|
||||
this.SampleAutomaticMetrics(client);
|
||||
|
||||
// Profiler Samplers
|
||||
foreach (AsyncUnityUserReportingPlatform.ProfilerSampler profilerSampler in this.profilerSamplers)
|
||||
{
|
||||
client.SampleMetric(profilerSampler.Name, profilerSampler.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Post Operations
|
||||
int postOperationIndex = 0;
|
||||
while (postOperationIndex < this.postOperations.Count)
|
||||
{
|
||||
AsyncUnityUserReportingPlatform.PostOperation postOperation = this.postOperations[postOperationIndex];
|
||||
if (postOperation.WebRequest.isDone)
|
||||
{
|
||||
bool isError = postOperation.WebRequest.error != null && postOperation.WebRequest.responseCode != 200;
|
||||
if (isError)
|
||||
{
|
||||
string errorMessage = string.Format("UnityUserReportingPlatform.Post: {0} {1}", postOperation.WebRequest.responseCode, postOperation.WebRequest.error);
|
||||
UnityEngine.Debug.Log(errorMessage);
|
||||
client.LogEvent(UserReportEventLevel.Error, errorMessage);
|
||||
}
|
||||
postOperation.ProgressCallback(1, 1);
|
||||
postOperation.Callback(!isError, postOperation.WebRequest.downloadHandler.data);
|
||||
this.postOperations.Remove(postOperation);
|
||||
}
|
||||
else
|
||||
{
|
||||
postOperation.ProgressCallback(postOperation.WebRequest.uploadProgress, postOperation.WebRequest.downloadProgress);
|
||||
postOperationIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtual Methods
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public virtual IDictionary<string, string> GetDeviceMetadata()
|
||||
{
|
||||
Dictionary<string, string> deviceMetadata = new Dictionary<string, string>();
|
||||
|
||||
// Unity
|
||||
deviceMetadata.Add("BuildGUID", Application.buildGUID);
|
||||
deviceMetadata.Add("DeviceModel", SystemInfo.deviceModel);
|
||||
deviceMetadata.Add("DeviceType", SystemInfo.deviceType.ToString());
|
||||
deviceMetadata.Add("DeviceUniqueIdentifier", SystemInfo.deviceUniqueIdentifier);
|
||||
deviceMetadata.Add("DPI", Screen.dpi.ToString(CultureInfo.InvariantCulture));
|
||||
deviceMetadata.Add("GraphicsDeviceName", SystemInfo.graphicsDeviceName);
|
||||
deviceMetadata.Add("GraphicsDeviceType", SystemInfo.graphicsDeviceType.ToString());
|
||||
deviceMetadata.Add("GraphicsDeviceVendor", SystemInfo.graphicsDeviceVendor);
|
||||
deviceMetadata.Add("GraphicsDeviceVersion", SystemInfo.graphicsDeviceVersion);
|
||||
deviceMetadata.Add("GraphicsMemorySize", SystemInfo.graphicsMemorySize.ToString());
|
||||
deviceMetadata.Add("InstallerName", Application.installerName);
|
||||
deviceMetadata.Add("InstallMode", Application.installMode.ToString());
|
||||
deviceMetadata.Add("IsEditor", Application.isEditor.ToString());
|
||||
deviceMetadata.Add("IsFullScreen", Screen.fullScreen.ToString());
|
||||
deviceMetadata.Add("OperatingSystem", SystemInfo.operatingSystem);
|
||||
deviceMetadata.Add("OperatingSystemFamily", SystemInfo.operatingSystemFamily.ToString());
|
||||
deviceMetadata.Add("Orientation", Screen.orientation.ToString());
|
||||
deviceMetadata.Add("Platform", Application.platform.ToString());
|
||||
try
|
||||
{
|
||||
deviceMetadata.Add("QualityLevel", QualitySettings.names[QualitySettings.GetQualityLevel()]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Empty
|
||||
}
|
||||
deviceMetadata.Add("ResolutionWidth", Screen.currentResolution.width.ToString());
|
||||
deviceMetadata.Add("ResolutionHeight", Screen.currentResolution.height.ToString());
|
||||
deviceMetadata.Add("ResolutionRefreshRate", Screen.currentResolution.refreshRate.ToString());
|
||||
deviceMetadata.Add("SystemLanguage", Application.systemLanguage.ToString());
|
||||
deviceMetadata.Add("SystemMemorySize", SystemInfo.systemMemorySize.ToString());
|
||||
deviceMetadata.Add("TargetFrameRate", Application.targetFrameRate.ToString());
|
||||
deviceMetadata.Add("UnityVersion", Application.unityVersion);
|
||||
deviceMetadata.Add("Version", Application.version);
|
||||
|
||||
// Other
|
||||
deviceMetadata.Add("Source", "Unity");
|
||||
|
||||
// Type
|
||||
Type type = this.GetType();
|
||||
deviceMetadata.Add("IUserReportingPlatform", type.Name);
|
||||
|
||||
// Return
|
||||
return deviceMetadata;
|
||||
}
|
||||
|
||||
public virtual Dictionary<string, string> GetSamplerNames()
|
||||
{
|
||||
Dictionary<string, string> samplerNames = new Dictionary<string, string>();
|
||||
samplerNames.Add("AudioManager.FixedUpdate", "AudioManager.FixedUpdateInMilliseconds");
|
||||
samplerNames.Add("AudioManager.Update", "AudioManager.UpdateInMilliseconds");
|
||||
samplerNames.Add("LateBehaviourUpdate", "Behaviors.LateUpdateInMilliseconds");
|
||||
samplerNames.Add("BehaviourUpdate", "Behaviors.UpdateInMilliseconds");
|
||||
samplerNames.Add("Camera.Render", "Camera.RenderInMilliseconds");
|
||||
samplerNames.Add("Overhead", "Engine.OverheadInMilliseconds");
|
||||
samplerNames.Add("WaitForRenderJobs", "Engine.WaitForRenderJobsInMilliseconds");
|
||||
samplerNames.Add("WaitForTargetFPS", "Engine.WaitForTargetFPSInMilliseconds");
|
||||
samplerNames.Add("GUI.Repaint", "GUI.RepaintInMilliseconds");
|
||||
samplerNames.Add("Network.Update", "Network.UpdateInMilliseconds");
|
||||
samplerNames.Add("ParticleSystem.EndUpdateAll", "ParticleSystem.EndUpdateAllInMilliseconds");
|
||||
samplerNames.Add("ParticleSystem.Update", "ParticleSystem.UpdateInMilliseconds");
|
||||
samplerNames.Add("Physics.FetchResults", "Physics.FetchResultsInMilliseconds");
|
||||
samplerNames.Add("Physics.Processing", "Physics.ProcessingInMilliseconds");
|
||||
samplerNames.Add("Physics.ProcessReports", "Physics.ProcessReportsInMilliseconds");
|
||||
samplerNames.Add("Physics.Simulate", "Physics.SimulateInMilliseconds");
|
||||
samplerNames.Add("Physics.UpdateBodies", "Physics.UpdateBodiesInMilliseconds");
|
||||
samplerNames.Add("Physics.Interpolation", "Physics.InterpolationInMilliseconds");
|
||||
samplerNames.Add("Physics2D.DynamicUpdate", "Physics2D.DynamicUpdateInMilliseconds");
|
||||
samplerNames.Add("Physics2D.FixedUpdate", "Physics2D.FixedUpdateInMilliseconds");
|
||||
return samplerNames;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IUserReportingPlatform"/>
|
||||
public virtual void ModifyUserReport(UserReport userReport)
|
||||
{
|
||||
// Active Scene
|
||||
Scene activeScene = SceneManager.GetActiveScene();
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("ActiveSceneName", activeScene.name));
|
||||
|
||||
// Main Camera
|
||||
Camera mainCamera = Camera.main;
|
||||
if (mainCamera != null)
|
||||
{
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("MainCameraName", mainCamera.name));
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("MainCameraPosition", mainCamera.transform.position.ToString()));
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("MainCameraForward", mainCamera.transform.forward.ToString()));
|
||||
|
||||
// Looking At
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(mainCamera.transform.position, mainCamera.transform.forward, out hit))
|
||||
{
|
||||
GameObject lookingAt = hit.transform.gameObject;
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("LookingAt", hit.point.ToString()));
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("LookingAtGameObject", lookingAt.name));
|
||||
userReport.DeviceMetadata.Add(new UserReportNamedValue("LookingAtGameObjectPosition", lookingAt.transform.position.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples automatic metrics.
|
||||
/// </summary>
|
||||
/// <param name="client">The client.</param>
|
||||
public virtual void SampleAutomaticMetrics(UserReportingClient client)
|
||||
{
|
||||
// Graphics
|
||||
client.SampleMetric("Graphics.FramesPerSecond", 1.0f / Time.deltaTime);
|
||||
|
||||
// Memory
|
||||
client.SampleMetric("Memory.MonoUsedSizeInBytes", Profiler.GetMonoUsedSizeLong());
|
||||
client.SampleMetric("Memory.TotalAllocatedMemoryInBytes", Profiler.GetTotalAllocatedMemoryLong());
|
||||
client.SampleMetric("Memory.TotalReservedMemoryInBytes", Profiler.GetTotalReservedMemoryLong());
|
||||
client.SampleMetric("Memory.TotalUnusedReservedMemoryInBytes", Profiler.GetTotalUnusedReservedMemoryLong());
|
||||
|
||||
// Battery
|
||||
client.SampleMetric("Battery.BatteryLevelInPercent", SystemInfo.batteryLevel);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c834f5ac7c3fc54cb35d5393a49ac56
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc211a7e0296020498ad8104967f8f90
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Screenshots
|
||||
{
|
||||
public static class Downsampler
|
||||
{
|
||||
#region Static Methods
|
||||
|
||||
public static byte[] Downsample(byte[] dataRgba, int stride, int maximumWidth, int maximumHeight, out int downsampledStride)
|
||||
{
|
||||
// Preconditions
|
||||
if (stride == 0)
|
||||
{
|
||||
throw new ArgumentException("The stride must be greater than 0.");
|
||||
}
|
||||
if (stride % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("The stride must be evenly divisible by 4.");
|
||||
}
|
||||
if (dataRgba == null)
|
||||
{
|
||||
throw new ArgumentNullException("dataRgba");
|
||||
}
|
||||
if (dataRgba.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("The data length must be greater than 0.");
|
||||
}
|
||||
if (dataRgba.Length % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("The data must be evenly divisible by 4.");
|
||||
}
|
||||
if (dataRgba.Length % stride != 0)
|
||||
{
|
||||
throw new ArgumentException("The data must be evenly divisible by the stride.");
|
||||
}
|
||||
|
||||
// Implementation
|
||||
int width = stride / 4;
|
||||
int height = dataRgba.Length / stride;
|
||||
float ratioX = maximumWidth / (float) width;
|
||||
float ratioY = maximumHeight / (float) height;
|
||||
float ratio = Math.Min(ratioX, ratioY);
|
||||
if (ratio < 1)
|
||||
{
|
||||
int downsampledWidth = (int) Math.Round(width * ratio);
|
||||
int downsampledHeight = (int) Math.Round(height * ratio);
|
||||
float[] downsampledData = new float[downsampledWidth * downsampledHeight * 4];
|
||||
float sampleWidth = width / (float) downsampledWidth;
|
||||
float sampleHeight = height / (float) downsampledHeight;
|
||||
int kernelWidth = (int) Math.Floor(sampleWidth);
|
||||
int kernelHeight = (int) Math.Floor(sampleHeight);
|
||||
int kernelSize = kernelWidth * kernelHeight;
|
||||
for (int y = 0; y < downsampledHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < downsampledWidth; x++)
|
||||
{
|
||||
int destinationIndex = y * downsampledWidth * 4 + x * 4;
|
||||
int sampleLowerX = (int) Math.Floor(x * sampleWidth);
|
||||
int sampleLowerY = (int) Math.Floor(y * sampleHeight);
|
||||
int sampleUpperX = sampleLowerX + kernelWidth;
|
||||
int sampleUpperY = sampleLowerY + kernelHeight;
|
||||
for (int sampleY = sampleLowerY; sampleY < sampleUpperY; sampleY++)
|
||||
{
|
||||
if (sampleY >= height)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int sampleX = sampleLowerX; sampleX < sampleUpperX; sampleX++)
|
||||
{
|
||||
if (sampleX >= width)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int sourceIndex = sampleY * width * 4 + sampleX * 4;
|
||||
downsampledData[destinationIndex] += dataRgba[sourceIndex];
|
||||
downsampledData[destinationIndex + 1] += dataRgba[sourceIndex + 1];
|
||||
downsampledData[destinationIndex + 2] += dataRgba[sourceIndex + 2];
|
||||
downsampledData[destinationIndex + 3] += dataRgba[sourceIndex + 3];
|
||||
}
|
||||
}
|
||||
downsampledData[destinationIndex] /= kernelSize;
|
||||
downsampledData[destinationIndex + 1] /= kernelSize;
|
||||
downsampledData[destinationIndex + 2] /= kernelSize;
|
||||
downsampledData[destinationIndex + 3] /= kernelSize;
|
||||
}
|
||||
}
|
||||
byte[] flippedData = new byte[downsampledWidth * downsampledHeight * 4];
|
||||
for (int y = 0; y < downsampledHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < downsampledWidth; x++)
|
||||
{
|
||||
int sourceIndex = (downsampledHeight - 1 - y) * downsampledWidth * 4 + x * 4;
|
||||
int destinationIndex = y * downsampledWidth * 4 + x * 4;
|
||||
flippedData[destinationIndex] += (byte) downsampledData[sourceIndex];
|
||||
flippedData[destinationIndex + 1] += (byte) downsampledData[sourceIndex + 1];
|
||||
flippedData[destinationIndex + 2] += (byte) downsampledData[sourceIndex + 2];
|
||||
flippedData[destinationIndex + 3] += (byte) downsampledData[sourceIndex + 3];
|
||||
}
|
||||
}
|
||||
downsampledStride = downsampledWidth * 4;
|
||||
return flippedData;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] flippedData = new byte[dataRgba.Length];
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int sourceIndex = (height - 1 - y) * width * 4 + x * 4;
|
||||
int destinationIndex = y * width * 4 + x * 4;
|
||||
flippedData[destinationIndex] += (byte) dataRgba[sourceIndex];
|
||||
flippedData[destinationIndex + 1] += (byte) dataRgba[sourceIndex + 1];
|
||||
flippedData[destinationIndex + 2] += (byte) dataRgba[sourceIndex + 2];
|
||||
flippedData[destinationIndex + 3] += (byte) dataRgba[sourceIndex + 3];
|
||||
}
|
||||
}
|
||||
downsampledStride = width * 4;
|
||||
return flippedData;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40babb20770d5ac45a84d207281b725d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Unity.Screenshots
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides PNG encoding.
|
||||
/// </summary>
|
||||
/// <remarks>This is a minimal implementation of a PNG encoder with no scanline filtering or additional features.</remarks>
|
||||
public static class PngEncoder
|
||||
{
|
||||
#region Nested Types
|
||||
|
||||
public class Crc32
|
||||
{
|
||||
#region Static Fields
|
||||
|
||||
private static UInt32 generator = 0xEDB88320;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public Crc32()
|
||||
{
|
||||
this.checksumTable = Enumerable.Range(0, 256)
|
||||
.Select(i =>
|
||||
{
|
||||
uint tableEntry = (uint)i;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
tableEntry = (tableEntry & 1) != 0 ? Crc32.generator ^ (tableEntry >> 1) : tableEntry >> 1;
|
||||
}
|
||||
|
||||
return tableEntry;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private readonly UInt32[] checksumTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public UInt32 Calculate<T>(IEnumerable<T> byteStream)
|
||||
{
|
||||
return ~byteStream.Aggregate(0xFFFFFFFF, (checksumRegister, currentByte) => this.checksumTable[(checksumRegister & 0xFF) ^ Convert.ToByte(currentByte)] ^ (checksumRegister >> 8));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Constructors
|
||||
|
||||
static PngEncoder()
|
||||
{
|
||||
PngEncoder.crc32 = new Crc32();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Fields
|
||||
|
||||
private static Crc32 crc32;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Methods
|
||||
|
||||
private static uint Adler32(byte[] bytes)
|
||||
{
|
||||
const int mod = 65521;
|
||||
uint a = 1, b = 0;
|
||||
foreach (byte byteValue in bytes)
|
||||
{
|
||||
a = (a + byteValue) % mod;
|
||||
b = (b + a) % mod;
|
||||
}
|
||||
|
||||
return (b << 16) | a;
|
||||
}
|
||||
|
||||
private static void AppendByte(this byte[] data, ref int position, byte value)
|
||||
{
|
||||
data[position] = value;
|
||||
position++;
|
||||
}
|
||||
|
||||
private static void AppendBytes(this byte[] data, ref int position, byte[] value)
|
||||
{
|
||||
foreach (byte valueByte in value)
|
||||
{
|
||||
data.AppendByte(ref position, valueByte);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendChunk(this byte[] data, ref int position, string chunkType, byte[] chunkData)
|
||||
{
|
||||
byte[] chunkTypeBytes = PngEncoder.GetChunkTypeBytes(chunkType);
|
||||
if (chunkTypeBytes != null)
|
||||
{
|
||||
data.AppendInt(ref position, chunkData.Length);
|
||||
data.AppendBytes(ref position, chunkTypeBytes);
|
||||
data.AppendBytes(ref position, chunkData);
|
||||
data.AppendUInt(ref position, PngEncoder.GetChunkCrc(chunkTypeBytes, chunkData));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendInt(this byte[] data, ref int position, int value)
|
||||
{
|
||||
byte[] valueBytes = BitConverter.GetBytes(value);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(valueBytes);
|
||||
}
|
||||
|
||||
data.AppendBytes(ref position, valueBytes);
|
||||
}
|
||||
|
||||
private static void AppendUInt(this byte[] data, ref int position, uint value)
|
||||
{
|
||||
byte[] valueBytes = BitConverter.GetBytes(value);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(valueBytes);
|
||||
}
|
||||
|
||||
data.AppendBytes(ref position, valueBytes);
|
||||
}
|
||||
|
||||
private static byte[] Compress(byte[] bytes)
|
||||
{
|
||||
using (MemoryStream outStream = new MemoryStream())
|
||||
{
|
||||
using (DeflateStream gZipStream = new DeflateStream(outStream, CompressionMode.Compress))
|
||||
using (MemoryStream mStream = new MemoryStream(bytes))
|
||||
{
|
||||
mStream.WriteTo(gZipStream);
|
||||
}
|
||||
|
||||
byte[] compressedBytes = outStream.ToArray();
|
||||
return compressedBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Encode(byte[] dataRgba, int stride)
|
||||
{
|
||||
// Preconditions
|
||||
if (dataRgba == null)
|
||||
{
|
||||
throw new ArgumentNullException("dataRgba");
|
||||
}
|
||||
|
||||
if (dataRgba.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("The data length must be greater than 0.");
|
||||
}
|
||||
|
||||
if (stride == 0)
|
||||
{
|
||||
throw new ArgumentException("The stride must be greater than 0.");
|
||||
}
|
||||
|
||||
if (stride % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("The stride must be evenly divisible by 4.");
|
||||
}
|
||||
|
||||
if (dataRgba.Length % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("The data must be evenly divisible by 4.");
|
||||
}
|
||||
|
||||
if (dataRgba.Length % stride != 0)
|
||||
{
|
||||
throw new ArgumentException("The data must be evenly divisible by the stride.");
|
||||
}
|
||||
|
||||
// Dimensions
|
||||
int pixels = dataRgba.Length / 4;
|
||||
int width = stride / 4;
|
||||
int height = pixels / width;
|
||||
|
||||
// IHDR Chunk
|
||||
byte[] ihdrData = new byte[13];
|
||||
int ihdrPosition = 0;
|
||||
ihdrData.AppendInt(ref ihdrPosition, width);
|
||||
ihdrData.AppendInt(ref ihdrPosition, height);
|
||||
ihdrData.AppendByte(ref ihdrPosition, 8); // Bit depth
|
||||
ihdrData.AppendByte(ref ihdrPosition, 6); // Color type (color + alpha)
|
||||
ihdrData.AppendByte(ref ihdrPosition, 0); // Compression method (always 0)
|
||||
ihdrData.AppendByte(ref ihdrPosition, 0); // Filter method (always 0)
|
||||
ihdrData.AppendByte(ref ihdrPosition, 0); // Interlace method (no interlacing)
|
||||
|
||||
// IDAT Chunk
|
||||
byte[] scanlineData = new byte[dataRgba.Length + height];
|
||||
int scanlineDataPosition = 0;
|
||||
int scanlinePosition = 0;
|
||||
for (int i = 0; i < dataRgba.Length; i++)
|
||||
{
|
||||
if (scanlinePosition >= stride)
|
||||
{
|
||||
scanlinePosition = 0;
|
||||
}
|
||||
|
||||
if (scanlinePosition == 0)
|
||||
{
|
||||
scanlineData.AppendByte(ref scanlineDataPosition, 0);
|
||||
}
|
||||
|
||||
scanlineData.AppendByte(ref scanlineDataPosition, dataRgba[i]);
|
||||
scanlinePosition++;
|
||||
}
|
||||
|
||||
byte[] compressedScanlineData = PngEncoder.Compress(scanlineData);
|
||||
byte[] idatData = new byte[1 + 1 + compressedScanlineData.Length + 4];
|
||||
int idatPosition = 0;
|
||||
idatData.AppendByte(ref idatPosition, 0x78); // Zlib header
|
||||
idatData.AppendByte(ref idatPosition, 0x9C); // Zlib header
|
||||
idatData.AppendBytes(ref idatPosition, compressedScanlineData); // Data
|
||||
idatData.AppendUInt(ref idatPosition, PngEncoder.Adler32(scanlineData)); // Adler32 checksum
|
||||
|
||||
// Png
|
||||
byte[] png = new byte[8 + ihdrData.Length + 12 + idatData.Length + 12 + 12];
|
||||
|
||||
// Position
|
||||
int position = 0;
|
||||
|
||||
// Signature
|
||||
png.AppendByte(ref position, 0x89); // High bit set
|
||||
png.AppendByte(ref position, 0x50); // P
|
||||
png.AppendByte(ref position, 0x4E); // N
|
||||
png.AppendByte(ref position, 0x47); // G
|
||||
png.AppendByte(ref position, 0x0D); // DOS line ending
|
||||
png.AppendByte(ref position, 0x0A); // DOS line ending
|
||||
png.AppendByte(ref position, 0x1A); // DOS end of file
|
||||
png.AppendByte(ref position, 0x0A); // Unix line ending
|
||||
|
||||
// Assemble
|
||||
png.AppendChunk(ref position, "IHDR", ihdrData);
|
||||
png.AppendChunk(ref position, "IDAT", idatData);
|
||||
png.AppendChunk(ref position, "IEND", new byte[0]);
|
||||
|
||||
// Return
|
||||
return png;
|
||||
}
|
||||
|
||||
public static void EncodeAsync(byte[] dataRgba, int stride, Action<Exception, byte[]> callback)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem((state) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] png = PngEncoder.Encode(dataRgba, stride);
|
||||
callback(null, png);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
callback(ex, null);
|
||||
throw;
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
private static uint GetChunkCrc(byte[] chunkTypeBytes, byte[] chunkData)
|
||||
{
|
||||
byte[] combinedBytes = new byte[chunkTypeBytes.Length + chunkData.Length];
|
||||
Array.Copy(chunkTypeBytes, 0, combinedBytes, 0, chunkTypeBytes.Length);
|
||||
Array.Copy(chunkData, 0, combinedBytes, 4, chunkData.Length);
|
||||
return PngEncoder.crc32.Calculate(combinedBytes);
|
||||
}
|
||||
|
||||
private static byte[] GetChunkTypeBytes(string value)
|
||||
{
|
||||
char[] characters = value.ToCharArray();
|
||||
if (characters.Length < 4)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] type = new byte[4];
|
||||
for (int i = 0; i < type.Length; i++)
|
||||
{
|
||||
type[i] = (byte)characters[i];
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5febb852ffdece4ca30c97b63d617de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Screenshots
|
||||
{
|
||||
public class ScreenshotManager
|
||||
{
|
||||
#region Nested Types
|
||||
|
||||
private class ScreenshotOperation
|
||||
{
|
||||
#region Properties
|
||||
|
||||
public Action<int, byte[]> Callback { get; set; }
|
||||
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public int FrameNumber { get; set; }
|
||||
|
||||
public bool IsAwaiting { get; set; }
|
||||
|
||||
public bool IsComplete { get; set; }
|
||||
|
||||
public bool IsInUse { get; set; }
|
||||
|
||||
public int MaximumHeight { get; set; }
|
||||
|
||||
public int MaximumWidth { get; set; }
|
||||
|
||||
public object Source { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Use()
|
||||
{
|
||||
this.Callback = null;
|
||||
this.Data = null;
|
||||
this.FrameNumber = 0;
|
||||
this.IsAwaiting = true;
|
||||
this.IsComplete = false;
|
||||
this.IsInUse = true;
|
||||
this.MaximumHeight = 0;
|
||||
this.MaximumWidth = 0;
|
||||
this.Source = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ScreenshotManager()
|
||||
{
|
||||
this.screenshotRecorder = new ScreenshotRecorder();
|
||||
this.screenshotCallbackDelegate = this.ScreenshotCallback;
|
||||
this.screenshotOperations = new List<ScreenshotOperation>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private Action<byte[], object> screenshotCallbackDelegate;
|
||||
|
||||
private List<ScreenshotOperation> screenshotOperations;
|
||||
|
||||
private ScreenshotRecorder screenshotRecorder;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private ScreenshotOperation GetScreenshotOperation()
|
||||
{
|
||||
foreach (ScreenshotOperation screenshotOperation in this.screenshotOperations)
|
||||
{
|
||||
if (!screenshotOperation.IsInUse)
|
||||
{
|
||||
screenshotOperation.Use();
|
||||
return screenshotOperation;
|
||||
}
|
||||
}
|
||||
ScreenshotOperation newScreenshotOperation = new ScreenshotOperation();
|
||||
newScreenshotOperation.Use();
|
||||
this.screenshotOperations.Add(newScreenshotOperation);
|
||||
return newScreenshotOperation;
|
||||
}
|
||||
|
||||
public void OnEndOfFrame()
|
||||
{
|
||||
foreach (ScreenshotOperation screenshotOperation in this.screenshotOperations)
|
||||
{
|
||||
if (screenshotOperation.IsInUse)
|
||||
{
|
||||
if (screenshotOperation.IsAwaiting)
|
||||
{
|
||||
screenshotOperation.IsAwaiting = false;
|
||||
if (screenshotOperation.Source == null)
|
||||
{
|
||||
this.screenshotRecorder.Screenshot(screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, ScreenshotType.Png, this.screenshotCallbackDelegate, screenshotOperation);
|
||||
}
|
||||
else if (screenshotOperation.Source is Camera)
|
||||
{
|
||||
this.screenshotRecorder.Screenshot(screenshotOperation.Source as Camera, screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, ScreenshotType.Png, this.screenshotCallbackDelegate, screenshotOperation);
|
||||
}
|
||||
else if (screenshotOperation.Source is RenderTexture)
|
||||
{
|
||||
this.screenshotRecorder.Screenshot(screenshotOperation.Source as RenderTexture, screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, ScreenshotType.Png, this.screenshotCallbackDelegate, screenshotOperation);
|
||||
}
|
||||
else if (screenshotOperation.Source is Texture2D)
|
||||
{
|
||||
this.screenshotRecorder.Screenshot(screenshotOperation.Source as Texture2D, screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, ScreenshotType.Png, this.screenshotCallbackDelegate, screenshotOperation);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ScreenshotCallback(null, screenshotOperation);
|
||||
}
|
||||
}
|
||||
else if (screenshotOperation.IsComplete)
|
||||
{
|
||||
screenshotOperation.IsInUse = false;
|
||||
try
|
||||
{
|
||||
if (screenshotOperation != null && screenshotOperation.Callback != null)
|
||||
{
|
||||
screenshotOperation.Callback(screenshotOperation.FrameNumber, screenshotOperation.Data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Do Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScreenshotCallback(byte[] data, object state)
|
||||
{
|
||||
ScreenshotOperation screenshotOperation = state as ScreenshotOperation;
|
||||
if (screenshotOperation != null)
|
||||
{
|
||||
screenshotOperation.Data = data;
|
||||
screenshotOperation.IsComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void TakeScreenshot(object source, int frameNumber, int maximumWidth, int maximumHeight, Action<int, byte[]> callback)
|
||||
{
|
||||
ScreenshotOperation screenshotOperation = this.GetScreenshotOperation();
|
||||
screenshotOperation.FrameNumber = frameNumber;
|
||||
screenshotOperation.MaximumWidth = maximumWidth;
|
||||
screenshotOperation.MaximumHeight = maximumHeight;
|
||||
screenshotOperation.Source = source;
|
||||
screenshotOperation.Callback = callback;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c56568a2c9bad0245b35d93ba6b0e046
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace Unity.Screenshots
|
||||
{
|
||||
public class ScreenshotRecorder
|
||||
{
|
||||
#region Nested Types
|
||||
|
||||
private class ScreenshotOperation
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public ScreenshotOperation()
|
||||
{
|
||||
this.ScreenshotCallbackDelegate = this.ScreenshotCallback;
|
||||
this.EncodeCallbackDelegate = this.EncodeCallback;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
public WaitCallback EncodeCallbackDelegate;
|
||||
|
||||
public Action<AsyncGPUReadbackRequest> ScreenshotCallbackDelegate;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public Action<byte[], object> Callback { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
|
||||
public int Identifier { get; set; }
|
||||
|
||||
public bool IsInUse { get; set; }
|
||||
|
||||
public int MaximumHeight { get; set; }
|
||||
|
||||
public int MaximumWidth { get; set; }
|
||||
|
||||
public NativeArray<byte> NativeData { get; set; }
|
||||
|
||||
public Texture Source { get; set; }
|
||||
|
||||
public object State { get; set; }
|
||||
|
||||
public ScreenshotType Type { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private void EncodeCallback(object state)
|
||||
{
|
||||
byte[] byteData = this.NativeData.ToArray();
|
||||
int downsampledStride;
|
||||
byteData = Downsampler.Downsample(byteData, this.Width * 4, this.MaximumWidth, this.MaximumHeight, out downsampledStride);
|
||||
if (this.Type == ScreenshotType.Png)
|
||||
{
|
||||
byteData = PngEncoder.Encode(byteData, downsampledStride);
|
||||
}
|
||||
if (this.Callback != null)
|
||||
{
|
||||
this.Callback(byteData, this.State);
|
||||
}
|
||||
this.NativeData.Dispose();
|
||||
this.IsInUse = false;
|
||||
}
|
||||
|
||||
private void SavePngToDisk(byte[] byteData)
|
||||
{
|
||||
if (!Directory.Exists("Screenshots"))
|
||||
{
|
||||
Directory.CreateDirectory("Screenshots");
|
||||
}
|
||||
File.WriteAllBytes(string.Format("Screenshots/{0}.png", this.Identifier % 60), byteData);
|
||||
}
|
||||
|
||||
private void ScreenshotCallback(AsyncGPUReadbackRequest request)
|
||||
{
|
||||
if (!request.hasError)
|
||||
{
|
||||
NativeLeakDetection.Mode = NativeLeakDetectionMode.Disabled;
|
||||
NativeArray<byte> data = request.GetData<byte>();
|
||||
NativeArray<byte> persistentData = new NativeArray<byte>(data, Allocator.Persistent);
|
||||
this.Width = request.width;
|
||||
this.Height = request.height;
|
||||
this.NativeData = persistentData;
|
||||
ThreadPool.QueueUserWorkItem(this.EncodeCallbackDelegate, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.Callback != null)
|
||||
{
|
||||
this.Callback(null, this.State);
|
||||
}
|
||||
}
|
||||
if (this.Source != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(this.Source);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Fields
|
||||
|
||||
private static int nextIdentifier;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ScreenshotRecorder()
|
||||
{
|
||||
this.operationPool = new List<ScreenshotOperation>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private List<ScreenshotOperation> operationPool;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private ScreenshotOperation GetOperation()
|
||||
{
|
||||
foreach (ScreenshotOperation operation in this.operationPool)
|
||||
{
|
||||
if (!operation.IsInUse)
|
||||
{
|
||||
operation.IsInUse = true;
|
||||
return operation;
|
||||
}
|
||||
}
|
||||
ScreenshotOperation newOperation = new ScreenshotOperation();
|
||||
newOperation.IsInUse = true;
|
||||
this.operationPool.Add(newOperation);
|
||||
return newOperation;
|
||||
}
|
||||
|
||||
public void Screenshot(int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
|
||||
{
|
||||
Texture2D texture = ScreenCapture.CaptureScreenshotAsTexture();
|
||||
this.Screenshot(texture, maximumWidth, maximumHeight, type, callback, state);
|
||||
}
|
||||
|
||||
public void Screenshot(Camera source, int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
|
||||
{
|
||||
RenderTexture renderTexture = new RenderTexture(maximumWidth, maximumHeight, 24);
|
||||
RenderTexture originalTargetTexture = source.targetTexture;
|
||||
source.targetTexture = renderTexture;
|
||||
source.Render();
|
||||
source.targetTexture = originalTargetTexture;
|
||||
this.Screenshot(renderTexture, maximumWidth, maximumHeight, type, callback, state);
|
||||
}
|
||||
|
||||
public void Screenshot(RenderTexture source, int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
|
||||
{
|
||||
this.ScreenshotInternal(source, maximumWidth, maximumHeight, type, callback, state);
|
||||
}
|
||||
|
||||
public void Screenshot(Texture2D source, int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
|
||||
{
|
||||
this.ScreenshotInternal(source, maximumWidth, maximumHeight, type, callback, state);
|
||||
}
|
||||
|
||||
private void ScreenshotInternal(Texture source, int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
|
||||
{
|
||||
ScreenshotOperation operation = this.GetOperation();
|
||||
operation.Identifier = ScreenshotRecorder.nextIdentifier++;
|
||||
operation.Source = source;
|
||||
operation.MaximumWidth = maximumWidth;
|
||||
operation.MaximumHeight = maximumHeight;
|
||||
operation.Type = type;
|
||||
operation.Callback = callback;
|
||||
operation.State = state;
|
||||
AsyncGPUReadback.Request(source, 0, TextureFormat.RGBA32, operation.ScreenshotCallbackDelegate);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9737edefe34777499da210b11503b2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,9 @@
|
||||
namespace Unity.Screenshots
|
||||
{
|
||||
public enum ScreenshotType
|
||||
{
|
||||
Raw = 0,
|
||||
|
||||
Png = 1
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 244a33b73709b0e4da95c36348a0bfc6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user