Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Intersect.Client.Core/Interface/Debugging/DebugWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Intersect.Client.Interface.Debugging.Providers;
using Intersect.Client.Localization;
using Intersect.Client.Maps;
using Intersect.Client.MonoGame.NativeInterop.OpenGL;
using Intersect.Framework.Reflection;
using static Intersect.Client.Framework.File_Management.GameContentManager;

Expand Down Expand Up @@ -405,7 +406,13 @@ private Table CreateInfoTableDebugStats(Base parent)
table.AddRow(Strings.Debug.LightsDrawn, name: "LightsDrawnRow").Listen(1, new DelegateDataProvider<int>(() => Graphics.LightsDrawn), NoValue);
table.AddRow(Strings.Debug.InterfaceObjects, name: "InterfaceObjectsRow").Listen(1, new DelegateDataProvider<int?>(() => Interface.CurrentInterface?.NodeCount, delayMilliseconds: 1000), NoValue);

var titleRow = table.AddRow(Strings.Debug.ControlUnderCursor, columnCount: 2, name: "ControlUnderCursorRow", columnIndex: 1);
_ = table.AddRow(Strings.Debug.SectionGPUStatistics, columnCount: 2, name: "SectionGPU", columnIndex: 1);

table.AddRow(Strings.Debug.RenderBufferVRAMFree, name: "GPUVRAMRenderBuffers").Listen(1, new DelegateDataProvider<string>(() => Strings.FormatBytes(GL.AvailableRenderBufferMemory)), NoValue);
table.AddRow(Strings.Debug.TextureVRAMFree, name: "GPUVRAMTextures").Listen(1, new DelegateDataProvider<string>(() => Strings.FormatBytes(GL.AvailableTextureMemory)), NoValue);
table.AddRow(Strings.Debug.VBOVRAMFree, name: "GPUVRAMVBOs").Listen(1, new DelegateDataProvider<string>(() => Strings.FormatBytes(GL.AvailableVBOMemory)), NoValue);

_ = table.AddRow(Strings.Debug.ControlUnderCursor, columnCount: 2, name: "SectionUI", columnIndex: 1);

table.AddRow(Strings.Internals.Type, name: "TypeRow").Listen(1, _nodeUnderCursorProvider, (node, _) => node?.GetType().GetName(), Strings.Internals.NotApplicable);
table.AddRow(Strings.Internals.Name, name: "NameRow").Listen(1, _nodeUnderCursorProvider, (node, _) => node?.ParentQualifiedName, NoValue);
Expand Down Expand Up @@ -435,7 +442,7 @@ private Table CreateInfoTableDebugStats(Base parent)
var rows = table.Children.OfType<TableRow>().ToArray();
foreach (var row in rows)
{
if (row == titleRow && row.GetCellContents(1) is Label titleLabel)
if (row.Name.StartsWith("Section") && row.GetCellContents(1) is Label titleLabel)
{
titleLabel.Padding = titleLabel.Padding with { Top = 8 };
}
Expand Down
37 changes: 37 additions & 0 deletions Intersect.Client.Core/Localization/Strings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,31 @@ public static partial class Strings
private const string StringsFileName = "client_strings.json";
private static char[] mQuantityTrimChars = new char[] { '.', '0' };

private static string[] _unitsBits = [string.Empty, "Ki", "Mi", "Gi", "Ti"];
private static string[] _unitsBytes = [string.Empty, "K", "M", "G", "T"];

public static string FormatBits(long quantity)
{
var log = quantity < 2 ? 0 : Math.Log2(quantity);
var offsetLog = Math.Max(0, Math.Floor(log - 3.3));
var unitIndex = (int)Math.Clamp(Math.Floor(offsetLog / 10), 0, _unitsBits.Length - 1);
var divisor = Math.Pow(2, unitIndex * 10);
var quotient = quantity / divisor;
var unitPrefix = _unitsBits[unitIndex];
return $"{quotient:0.##}{unitPrefix}B";
}

public static string FormatBytes(long quantity)
{
var log = quantity < 10 ? 0 : Math.Floor(Math.Log10(quantity));
var offsetLog = Math.Max(0, log - 1);
var unitIndex = (int)Math.Clamp(Math.Floor(offsetLog / 3), 0, _unitsBytes.Length - 1);
var divisor = Math.Pow(10, unitIndex * 3);
var quotient = quantity / divisor;
var unitPrefix = _unitsBytes[unitIndex];
return $"{quotient:0.##}{unitPrefix}B";
}

public static string FormatQuantityAbbreviated(long value)
{
if (value == 0)
Expand Down Expand Up @@ -930,6 +955,18 @@ public partial struct Credits

public partial struct Debug
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public static LocalizedString SectionGPUStatistics = @"GPU Statistics";

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public static LocalizedString RenderBufferVRAMFree = @"Render Buffer VRAM Free";

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public static LocalizedString TextureVRAMFree = @"Texture VRAM Free";

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public static LocalizedString VBOVRAMFree = @"VBO VRAM Free";

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public static LocalizedString ControlUnderCursor = @"Control Under Cursor";

Expand Down
1 change: 1 addition & 0 deletions Intersect.Client.Core/MonoGame/Graphics/MonoRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Intersect.Client.Interface.Shared;
using Intersect.Client.Localization;
using Intersect.Client.MonoGame.NativeInterop;
using Intersect.Client.MonoGame.NativeInterop.OpenGL;
using Intersect.Client.ThirdParty;
using Intersect.Configuration;
using Intersect.Extensions;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

namespace Intersect.Client.MonoGame.NativeInterop.OpenGL;

[SuppressMessage("ReSharper", "InconsistentNaming")]
public static partial class GL
{
private static bool IsATI_meminfoSupported => IsExtensionSupported("GL_ATI_meminfo");

[StructLayout(LayoutKind.Sequential)]
private struct ATI_meminfo_Tuple
{
public int FreeInPool;
public int LargestFreeBlockInPool;
public int FreeInAuxiliaryPool;
public int LargestFreeBlockInAuxiliaryPool;
}

private static unsafe ATI_meminfo_Tuple glGetATIMemInfo(GLenum name)
{
ATI_meminfo_Tuple data = default;
int* p_data = &data.FreeInPool;
glGetIntegerv_f(name, p_data);
return data;
}
}

/*
VBO_FREE_MEMORY_ATI 0x87FB
TEXTURE_FREE_MEMORY_ATI 0x87FC
RENDERBUFFER_FREE_MEMORY_ATI 0x87FD*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Intersect.Client.MonoGame.NativeInterop.OpenGL;

public static partial class GL
{
private static HashSet<string>? _cachedExtensions;

public static HashSet<string> glGetExtensions()
{
// ReSharper disable once InvertIf
if (_cachedExtensions is not { Count: > 0 })
{
var numExtensions = glGetIntegerv(GLenum.GL_NUM_EXTENSIONS);
var extensions = glGetStrings(GLenum.GL_EXTENSIONS, numExtensions);
_cachedExtensions = extensions.OfType<string>().ToHashSet();
}

return _cachedExtensions;
}

public static bool IsExtensionSupported(string extensionName) => glGetExtensions().Contains(extensionName);
}
70 changes: 70 additions & 0 deletions Intersect.Client.Core/MonoGame/NativeInterop/OpenGL/GL.Memory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
namespace Intersect.Client.MonoGame.NativeInterop.OpenGL;

public static partial class GL
{
/// <summary>
/// Available amount of memory for render buffers in bytes
/// </summary>
public static long AvailableRenderBufferMemory
{
get
{
if (IsATI_meminfoSupported)
{
var info = glGetATIMemInfo(GLenum.RENDERBUFFER_FREE_MEMORY_ATI);
return info.FreeInPool * 1000L;
}

if (IsNVX_gpu_memory_infoSupported)
{
return glGetNVXGPUMemoryInfo(GLenum.GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1000L;
}

return -1;
}
}

/// <summary>
/// Available amount of memory for textures in bytes
/// </summary>
public static long AvailableTextureMemory
{
get
{
if (IsATI_meminfoSupported)
{
var info = glGetATIMemInfo(GLenum.TEXTURE_FREE_MEMORY_ATI);
return info.FreeInPool * 1000L;
}

if (IsNVX_gpu_memory_infoSupported)
{
return glGetNVXGPUMemoryInfo(GLenum.GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1000L;
}

return -1;
}
}

/// <summary>
/// Available amount of memory for VBOs in bytes
/// </summary>
public static long AvailableVBOMemory
{
get
{
if (IsATI_meminfoSupported)
{
var info = glGetATIMemInfo(GLenum.VBO_FREE_MEMORY_ATI);
return info.FreeInPool * 1000L;
}

if (IsNVX_gpu_memory_infoSupported)
{
return glGetNVXGPUMemoryInfo(GLenum.GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1000L;
}

return -1;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Intersect.Client.MonoGame.NativeInterop.OpenGL;

public static partial class GL
{
public static bool IsNVX_gpu_memory_infoSupported => IsExtensionSupported("GL_NVX_gpu_memory_info");

private static long glGetNVXGPUMemoryInfo(GLenum name)
{
return glGetIntegerv(name);
}
}
38 changes: 38 additions & 0 deletions Intersect.Client.Core/MonoGame/NativeInterop/OpenGL/GL.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Runtime.InteropServices;

namespace Intersect.Client.MonoGame.NativeInterop.OpenGL;

public static partial class GL
{
private static TDelegate LoadFunction<TDelegate>(string function)
{
return LoadFunction<TDelegate>(function, false)!;
}

private static T? LoadFunction<T>(string function, bool throwIfNotFound)
{
var ret = Sdl2.SDL_GL_GetProcAddress(function);

if (ret != IntPtr.Zero)
{
return Marshal.GetDelegateForFunctionPointer<T>(ret);
}

if (throwIfNotFound)
{
throw new EntryPointNotFoundException(function);
}

return default;
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate GLenum glGetError_d();

private static readonly glGetError_d glGetError_f = LoadFunction<glGetError_d>(nameof(glGetError));

public static GLenum glGetError()
{
return glGetError_f();
}
}
45 changes: 45 additions & 0 deletions Intersect.Client.Core/MonoGame/NativeInterop/OpenGL/GLenum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Intersect.Client.MonoGame.NativeInterop.OpenGL;

public enum GLenum
{
#region Utility

GL_VENDOR = 0x1F00,
GL_RENDERER = 0x1F01,
GL_VERSION = 0x1F02,
GL_EXTENSIONS = 0x1F03,

#endregion Utility

#region Errors

GL_NO_ERROR = 0,
GL_INVALID_ENUM = 0x0500,
GL_INVALID_VALUE = 0x0501,
GL_INVALID_OPERATION = 0x0502,
GL_STACK_OVERFLOW = 0x0503,
GL_STACK_UNDERFLOW = 0x0504,
GL_OUT_OF_MEMORY = 0x0505,

#endregion Errors

GL_NUM_EXTENSIONS = 0x821D,

#region ATI_meminfo

VBO_FREE_MEMORY_ATI = 0x87FB,
TEXTURE_FREE_MEMORY_ATI = 0x87FC,
RENDERBUFFER_FREE_MEMORY_ATI = 0x87FD,

#endregion ATI_meminfo

#region NVX_gpu_memory_info

GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX = 0x9047,
GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX = 0x9048,
GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX = 0x9049,
GPU_MEMORY_INFO_EVICTION_COUNT_NVX = 0x904A,
GPU_MEMORY_INFO_EVICTED_MEMORY_NVX = 0x904B,

#endregion NVX_gpu_memory_info
}
24 changes: 24 additions & 0 deletions Intersect.Client.Core/MonoGame/NativeInterop/OpenGL/Gl.Helpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Text;

namespace Intersect.Client.MonoGame.NativeInterop.OpenGL;

public static partial class GL
{
private static unsafe string? PointerToUTF8(byte* ptr)
{
if (ptr == default)
{
return null;
}

var end = ptr;
while (*end != 0)
{
++end;
}

var length = (int)(end - ptr);
var str = Encoding.UTF8.GetString(ptr, length);
return str;
}
}
Loading
Loading