diff --git a/debugger/texture-debugger/TextureUtils.cs b/debugger/texture-debugger/TextureUtils.cs
new file mode 100644
index 0000000000..5fe45fa7e0
--- /dev/null
+++ b/debugger/texture-debugger/TextureUtils.cs
@@ -0,0 +1,123 @@
+using System.Drawing;
+using System.Linq;
+using UnityEngine;
+using Graphics = UnityEngine.Graphics;
+using Object = UnityEngine.Object;
+
+namespace JetBrains.Debugger.Worker.Plugins.Unity.Presentation.Texture
+{
+ public class TexturePixelsInfo
+ {
+ public int Width;
+ public int Height;
+ public int[] Pixels;
+ public int OriginalWidth;
+ public int OriginalHeight;
+ public string GraphicsTextureFormat;
+ public string TextureFormat;
+
+
+ public TexturePixelsInfo(Size size, Color32[] pixels, Texture2D texture2D)
+ {
+ Pixels = pixels.Select(c => c.ToHex()).ToArray();
+ Width = size.Width;
+ Height = size.Height;
+ TextureFormat = texture2D.format.ToString();
+ GraphicsTextureFormat = texture2D.graphicsFormat.ToString();
+ OriginalWidth = texture2D.width;
+ OriginalHeight = texture2D.height;
+ }
+ }
+
+ public static class UnityTextureAdapter
+ {
+ public static int ToHex(this Color32 c)
+ {
+ return (c.r << 16) | (c.g << 8) | (c.b);
+ }
+
+ public static string GetPixelsInString(Texture2D texture2D)
+ {
+ return GetPixelsInString(texture2D, new Size(texture2D.width, texture2D.height));
+ }
+
+ public static string GetPixelsInString(Texture2D texture2D, Size size)
+ {
+ size = GetTextureConvertedSize(texture2D, size);
+ var color32 = GetPixels(texture2D, size);
+ return JsonUtility.ToJson(color32, true);
+ }
+
+ private static TexturePixelsInfo GetPixels(Texture2D texture2d, Size size)
+ {
+ var targetTexture = CreateTargetTexture(size);
+ CopyTexture(texture2d, targetTexture);
+ var pixels = targetTexture.GetPixels32();
+ var texturePixelsInfo = new TexturePixelsInfo(new Size(targetTexture.width, targetTexture.height)
+ , pixels
+ , texture2d);
+ Object.DestroyImmediate(targetTexture);
+ return texturePixelsInfo;
+ }
+
+ private static byte[] GetRawBytes(Texture2D texture2d, Size size)
+ {
+ var targetTexture = CreateTargetTexture(size);
+ CopyTexture(texture2d, targetTexture);
+ var rawTextureData = targetTexture.GetRawTextureData();
+ Object.DestroyImmediate(targetTexture);
+ return rawTextureData;
+ }
+
+ private static void CopyTexture(UnityEngine.Texture texture2d, Texture2D targetTexture)
+ {
+ var renderTexture = RenderTexture.GetTemporary(
+ targetTexture.width,
+ targetTexture.height,
+ 0,
+ RenderTextureFormat.ARGB32
+ );
+
+ // Blit the pixels on texture to the RenderTexture
+ Graphics.Blit(texture2d, renderTexture);
+
+ // Backup the currently set RenderTexture
+ var previous = RenderTexture.active;
+
+ // Set the current RenderTexture to the temporary one we created
+ RenderTexture.active = renderTexture;
+
+ // Create a new readable Texture2D to copy the pixels to it
+
+ // Copy the pixels from the RenderTexture to the new Texture
+ targetTexture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
+ targetTexture.Apply();
+
+ // Reset the active RenderTexture
+ RenderTexture.active = previous;
+
+ // Release the temporary RenderTexture
+ RenderTexture.ReleaseTemporary(renderTexture);
+ }
+
+ private static Texture2D CreateTargetTexture(Size size)
+ {
+ var texture2D = new Texture2D(size.Width, size.Height, TextureFormat.RGBA32, false);
+ return texture2D;
+ }
+
+ private static Size GetTextureConvertedSize(UnityEngine.Texture texture2d, Size size)
+ {
+ var texture2dWidth = texture2d.width;
+ var texture2dHeight = texture2d.height;
+
+ var divider = 1;
+ while (texture2dWidth / divider > size.Width && texture2dHeight / divider > size.Height)
+ divider *= 2;
+
+ var targetTextureWidth = texture2dWidth / divider;
+ var targetTextureHeight = texture2dHeight / divider;
+ return new Size(targetTextureWidth, targetTextureHeight);
+ }
+ }
+}
\ No newline at end of file
diff --git a/debugger/texture-debugger/texture-debugger.csproj b/debugger/texture-debugger/texture-debugger.csproj
new file mode 100644
index 0000000000..f3f96122b8
--- /dev/null
+++ b/debugger/texture-debugger/texture-debugger.csproj
@@ -0,0 +1,32 @@
+
+
+
+ True
+ False
+
+
+
+
+
+ net472
+ JetBrains.ReSharper.Plugins.Unity.Rider.Debugger.Presentation.Texture
+ JetBrains.Debugger.Worker.Plugins.Unity.Presentation.Texture
+ 9
+ ..\..\sign.snk
+ enable
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resharper/resharper-unity.sln b/resharper/resharper-unity.sln
index d3bba570c9..5eb0d77a6e 100644
--- a/resharper/resharper-unity.sln
+++ b/resharper/resharper-unity.sln
@@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EditorPlugin.SinceUnity.201
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EditorPlugin.SinceUnity.2019.2", "..\unity\EditorPlugin\EditorPlugin.SinceUnity.2019.2.csproj", "{117CAF5D-7FC0-4626-98E0-E81572964E56}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "texture-debugger", "..\debugger\texture-debugger\texture-debugger.csproj", "{DF886D30-1FE9-4296-9202-BCE567BFF2E5}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\debugger\usbmuxd\usbmuxd.projitems*{4e49cd68-6ba2-4765-80ac-d82537a0996b}*SharedItemsImports = 5
@@ -144,6 +146,10 @@ Global
{117CAF5D-7FC0-4626-98E0-E81572964E56}.Debug|Any CPU.Build.0 = Debug|Any CPU
{117CAF5D-7FC0-4626-98E0-E81572964E56}.Release|Any CPU.ActiveCfg = Release|Any CPU
{117CAF5D-7FC0-4626-98E0-E81572964E56}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF886D30-1FE9-4296-9202-BCE567BFF2E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF886D30-1FE9-4296-9202-BCE567BFF2E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF886D30-1FE9-4296-9202-BCE567BFF2E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF886D30-1FE9-4296-9202-BCE567BFF2E5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -170,6 +176,7 @@ Global
{CA4516C7-2795-4F5D-B13C-EC5E46F19C68} = {27FAB5DF-2272-44E7-9BCE-41508F24C07B}
{27D56D2A-B11F-4BAA-B26B-1A73A055B398} = {27FAB5DF-2272-44E7-9BCE-41508F24C07B}
{117CAF5D-7FC0-4626-98E0-E81572964E56} = {27FAB5DF-2272-44E7-9BCE-41508F24C07B}
+ {DF886D30-1FE9-4296-9202-BCE567BFF2E5} = {D61F53F1-F209-4313-9FF2-B1DBD286BE11}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F132919A-F861-4C4F-923A-C956B82D9EE6}
diff --git a/rider/build.gradle.kts b/rider/build.gradle.kts
index b53f578bc2..4a40a19e99 100644
--- a/rider/build.gradle.kts
+++ b/rider/build.gradle.kts
@@ -93,6 +93,11 @@ val debuggerDllFiles = files(
"../resharper/build/debugger/bin/$buildConfiguration/net472/JetBrains.ReSharper.Plugins.Unity.Rider.Debugger.pdb"
)
+val textureDebuggerDllFiles = files(
+ "../resharper/build/texture-debugger/bin/$buildConfiguration/net472/JetBrains.ReSharper.Plugins.Unity.Rider.Debugger.Presentation.Texture.dll",
+ "../resharper/build/texture-debugger/bin/$buildConfiguration/net472/JetBrains.ReSharper.Plugins.Unity.Rider.Debugger.Presentation.Texture.pdb"
+)
+
val listIosUsbDevicesFiles = files(
"../resharper/build/ios-list-usb-devices/bin/$buildConfiguration/net7.0/JetBrains.Rider.Unity.ListIosUsbDevices.dll",
"../resharper/build/ios-list-usb-devices/bin/$buildConfiguration/net7.0/JetBrains.Rider.Unity.ListIosUsbDevices.pdb",
@@ -900,6 +905,7 @@ See CHANGELOG.md in the JetBrains/resharper-unity GitHub repo for more details a
doLast {
dotnetDllFiles.forEach { if (!it.exists()) error("File $it does not exist") }
debuggerDllFiles.forEach { if (!it.exists()) error("File $it does not exist") }
+ textureDebuggerDllFiles.forEach { if (!it.exists()) error("File $it does not exist") }
listIosUsbDevicesFiles.forEach { if (!it.exists()) error("File $it does not exist") }
unityEditorDllFiles.forEach { if (!it.exists()) error("File $it does not exist") }
}
@@ -908,6 +914,7 @@ See CHANGELOG.md in the JetBrains/resharper-unity GitHub repo for more details a
dotnetDllFiles.forEach { from(it) { into("${pluginName}/dotnet") } }
debuggerDllFiles.forEach { from(it) { into("${pluginName}/dotnetDebuggerWorker") } }
+ textureDebuggerDllFiles.forEach { from(it) { into("${pluginName}/DotFiles") } }
listIosUsbDevicesFiles.forEach { from(it) { into("${pluginName}/DotFiles") } }
unityEditorDllFiles.forEach { from(it) { into("${pluginName}/EditorPlugin") } }
diff --git a/rider/src/main/kotlin/com/jetbrains/rider/plugins/unity/debugger/visualizers/UnityDebuggerTexturePresenter.kt b/rider/src/main/kotlin/com/jetbrains/rider/plugins/unity/debugger/visualizers/UnityDebuggerTexturePresenter.kt
new file mode 100644
index 0000000000..2da0ccb534
--- /dev/null
+++ b/rider/src/main/kotlin/com/jetbrains/rider/plugins/unity/debugger/visualizers/UnityDebuggerTexturePresenter.kt
@@ -0,0 +1,228 @@
+package com.jetbrains.rider.plugins.unity.debugger.visualizers
+
+import com.google.gson.Gson
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.rd.createNestedDisposable
+import com.intellij.openapi.util.NlsContexts
+import com.intellij.ui.ErrorLabel
+import com.intellij.ui.components.JBLoadingPanel
+import com.intellij.ui.components.JBPanel
+import com.intellij.ui.components.panels.VerticalLayout
+import com.intellij.util.ui.ImageUtil
+import com.intellij.xdebugger.XDebugSession
+import com.intellij.xdebugger.XDebuggerManager
+import com.intellij.xdebugger.evaluation.XDebuggerEvaluator
+import com.intellij.xdebugger.frame.XValue
+import com.intellij.xdebugger.frame.XValueNode
+import com.intellij.xdebugger.frame.XValuePlace
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
+import com.jetbrains.rd.framework.RdTaskResult
+import com.jetbrains.rd.ide.model.ValuePropertiesModelBase
+import com.jetbrains.rd.util.lifetime.Lifetime
+import com.jetbrains.rd.util.printlnError
+import com.jetbrains.rd.util.reactive.adviseOnce
+import com.jetbrains.rider.RiderEnvironment
+import com.jetbrains.rider.debugger.DotNetValue
+import com.jetbrains.rider.debugger.visualizers.RiderDebuggerPresenterTab
+import com.jetbrains.rider.debugger.visualizers.RiderDebuggerValuePresenter
+import com.jetbrains.rider.model.debuggerWorker.ComputeObjectPropertiesArg
+import com.jetbrains.rider.model.debuggerWorker.FailedObjectProperties
+import com.jetbrains.rider.model.debuggerWorker.ObjectPropertiesProxy
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.intellij.images.editor.impl.ImageEditorManagerImpl.createImageEditorUI
+import java.awt.BorderLayout
+import java.awt.GraphicsConfiguration
+import java.awt.GraphicsEnvironment
+import java.awt.Rectangle
+import java.awt.geom.AffineTransform
+import java.awt.image.BufferedImage
+import java.awt.image.ColorModel
+import javax.swing.JComponent
+import javax.swing.JLabel
+import javax.swing.JPanel
+import javax.swing.SwingConstants
+
+class UnityDebuggerTexturePresenter : RiderDebuggerValuePresenter {
+
+ @Suppress("PropertyName")
+ data class TextureInfo(val Pixels: List,
+ val Width: Int,
+ val Height: Int,
+ val OriginalWidth: Int,
+ val OriginalHeight: Int,
+ val GraphicsTextureFormat: String,
+ val TextureFormat: String
+ )
+
+ override fun isApplicable(node: XValueNode, properties: ObjectPropertiesProxy, place: XValuePlace, session: XDebugSession): Boolean {
+ return properties.instanceType.definitionTypeFullName == "UnityEngine.Texture2D"
+ }
+
+ override fun getPriority(): Int {
+ return 0
+ }
+
+ override fun shouldIgnorePropertiesReevaluation(
+ node: XValueNode,
+ properties: ObjectPropertiesProxy,
+ place: XValuePlace,
+ session: XDebugSession
+ ): Boolean {
+ return true
+ }
+
+
+ override fun createTabs(
+ node: XValueNode,
+ properties: ObjectPropertiesProxy,
+ place: XValuePlace,
+ session: XDebugSession,
+ lifetime: Lifetime
+ ): List {
+ val parentPanel = JBPanel>(VerticalLayout(5))
+ val jbLoadingPanel = JBLoadingPanel(BorderLayout(), lifetime.createNestedDisposable())
+ parentPanel.add(jbLoadingPanel)
+ jbLoadingPanel.startLoading()
+ parentPanel.revalidate()
+ parentPanel.repaint()
+
+ val bundledFile = RiderEnvironment.getBundledFile(
+ "JetBrains.ReSharper.Plugins.Unity.Rider.Debugger.Presentation.Texture.dll",
+ pluginClass = javaClass
+ )
+
+ val evaluationRequest = "System.Reflection.Assembly.LoadFile(@\"${bundledFile.absolutePath}\")"
+ val project = session.project
+ evaluate(project, evaluationRequest, lifetime,
+ successfullyEvaluated = { evaluateTextureAndShow(node, project, jbLoadingPanel, parentPanel, lifetime) },
+ evaluationFailed = {
+ showErrorMessage(
+ jbLoadingPanel,
+ parentPanel,
+ "Can't load texture helpers dll:\n$it"
+ )
+ })
+
+ val name = (node as XValueNodeImpl).rawValue!!
+ return listOf(RiderDebuggerPresenterTab(name, name, parentPanel, null))
+ }
+
+ private fun evaluateTextureAndShow(node: XValueNode,
+ project: Project,
+ jbLoadingPanel: JBLoadingPanel,
+ parentPanel: JBPanel>,
+ lifetime: Lifetime) {
+ val nodeName = (node as XValueNodeImpl).name!!
+ val evaluationRequest = "JetBrains.Debugger.Worker.Plugins.Unity.Presentation.Texture.UnityTextureAdapter.GetPixelsInString($nodeName as UnityEngine.Texture2D)"
+ evaluate(project, evaluationRequest, lifetime,
+ successfullyEvaluated = { showTexture(it, jbLoadingPanel, parentPanel) },
+ evaluationFailed = { showErrorMessage(jbLoadingPanel, parentPanel, "Can't get texture debug information:\n$it") })
+ }
+
+ private fun showTexture(it: ValuePropertiesModelBase,
+ jbLoadingPanel: JBLoadingPanel,
+ parentPanel: JBPanel>) {
+ val textureInfo = parseTextureEvaluationResult(it)
+
+ if (textureInfo == null)
+ showErrorMessage(jbLoadingPanel, parentPanel, "Can't parse texture info")
+ else {
+ val texturePanel = createPanelWithImage(textureInfo)
+ jbLoadingPanel.stopLoading()
+ parentPanel.remove(jbLoadingPanel)
+
+ parentPanel.apply {
+ add(JLabel("Texture Size: ${textureInfo.OriginalWidth}x${textureInfo.OriginalHeight}"))
+ add(JLabel("Texture Format: ${textureInfo.TextureFormat}"))
+ add(JLabel("Texture Graphics Format: ${textureInfo.GraphicsTextureFormat}"))
+ }
+ parentPanel.add(texturePanel)
+ }
+ parentPanel.revalidate()
+ parentPanel.repaint()
+ }
+
+ private fun parseTextureEvaluationResult(it: ValuePropertiesModelBase): TextureInfo? {
+ val json = it.value[0].value
+ return Gson().fromJson(json, TextureInfo::class.java)
+ }
+
+ private fun showErrorMessage(jbLoadingPanel: JBLoadingPanel,
+ parentPanel: JBPanel>,
+ @NlsContexts.Label errorMessage: String) {
+ jbLoadingPanel.stopLoading()
+ parentPanel.remove(jbLoadingPanel)
+ parentPanel.add(ErrorLabel(errorMessage).apply {
+ verticalAlignment = SwingConstants.TOP
+ horizontalAlignment = SwingConstants.LEFT
+ })
+ printlnError(errorMessage)
+ }
+
+
+ @Suppress("INACCESSIBLE_TYPE")
+ private fun createPanelWithImage(textureInfo: TextureInfo): JPanel {
+
+ val defaultConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.defaultConfiguration
+ val dummyGraphicsConfiguration = object : GraphicsConfiguration() {
+ override fun getDevice() = defaultConfiguration.device
+ override fun getColorModel() = ColorModel.getRGBdefault()
+ override fun getColorModel(transparency: Int) = ColorModel.getRGBdefault()
+ override fun getDefaultTransform() = AffineTransform()
+ override fun getNormalizingTransform() = AffineTransform()
+ override fun getBounds() = Rectangle(0, 0, textureInfo.Width, textureInfo.Height)
+ }
+
+ val bufferedImage = ImageUtil.createImage(dummyGraphicsConfiguration,
+ textureInfo.Width, textureInfo.Height, BufferedImage.TYPE_INT_RGB)
+
+ for (y in 0 until textureInfo.Height) {
+ for (x in 0 until textureInfo.Width) {
+ val toInt = textureInfo.Pixels[y * textureInfo.Height + x]
+ bufferedImage.setRGB(x, textureInfo.Height - y - 1, toInt)
+ }
+ }
+
+ return createImageEditorUI(bufferedImage)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun evaluate(project: Project,
+ evaluationRequest: String,
+ lifetime: Lifetime,
+ successfullyEvaluated: (value: ValuePropertiesModelBase) -> Unit,
+ evaluationFailed: (errorMessage: String) -> Unit) {
+ val evaluator = XDebuggerManager.getInstance(project).currentSession!!.currentStackFrame!!.evaluator!!
+
+ evaluator.evaluate(evaluationRequest,
+ object : XDebuggerEvaluator.XEvaluationCallback {
+ override fun errorOccurred(errorMessage: String) = evaluationFailed(errorMessage)
+ override fun evaluated(loadDllresult: XValue) {
+ if (loadDllresult is DotNetValue) {
+ loadDllresult.objectProxy.computeObjectProperties.start(
+ lifetime,
+ ComputeObjectPropertiesArg(allowInvoke = true,
+ allowCrossThread = true,
+ ellipsizeStrings = false,
+ ellipsizedLength = null,
+ nameAliases = emptyList(),
+ extraInfo = null,
+ allowDisabledMethodsInvoke = null)
+ ).result.adviseOnce(lifetime) {
+ when (it) {
+ is RdTaskResult.Success -> {
+ val value = it.value
+ if (value is FailedObjectProperties)
+ evaluationFailed(value.value.joinToString("\n"))
+ else
+ successfullyEvaluated(value)
+ }
+ is RdTaskResult.Cancelled -> evaluationFailed("Cancelled $it")
+ is RdTaskResult.Fault -> evaluationFailed("Fault $it")
+ }
+ }
+ }
+ }
+ }, null)
+ }
+}
\ No newline at end of file
diff --git a/rider/src/main/resources/META-INF/plugin.xml b/rider/src/main/resources/META-INF/plugin.xml
index 4e6d915258..04fb223428 100644
--- a/rider/src/main/resources/META-INF/plugin.xml
+++ b/rider/src/main/resources/META-INF/plugin.xml
@@ -205,6 +205,7 @@
+