mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-15 03:20:30 +00:00
480 lines
20 KiB
C#
480 lines
20 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using osu.Framework.Extensions.EnumExtensions;
|
|
using osu.Framework.Extensions.ObjectExtensions;
|
|
using osu.Framework.Graphics.Rendering;
|
|
using osu.Framework.Graphics.Textures;
|
|
using osu.Framework.Logging;
|
|
using osuTK.Graphics;
|
|
using SharpGen.Runtime;
|
|
using Veldrid;
|
|
using Veldrid.MetalBindings;
|
|
using Veldrid.OpenGLBinding;
|
|
using Vortice.Direct3D11;
|
|
using Vortice.DXGI;
|
|
using Vulkan;
|
|
using GetPName = Veldrid.OpenGLBinding.GetPName;
|
|
using GraphicsBackend = Veldrid.GraphicsBackend;
|
|
using PrimitiveTopology = Veldrid.PrimitiveTopology;
|
|
using StencilOperation = Veldrid.StencilOperation;
|
|
using StringName = Veldrid.OpenGLBinding.StringName;
|
|
using VertexAttribPointerType = osuTK.Graphics.ES30.VertexAttribPointerType;
|
|
|
|
namespace osu.Framework.Graphics.Veldrid
|
|
{
|
|
internal static class VeldridExtensions
|
|
{
|
|
public static RgbaFloat ToRgbaFloat(this Color4 colour) => new RgbaFloat(colour.R, colour.G, colour.B, colour.A);
|
|
|
|
public static BlendFactor ToBlendFactor(this BlendingType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case BlendingType.DstAlpha:
|
|
return BlendFactor.DestinationAlpha;
|
|
|
|
case BlendingType.DstColor:
|
|
return BlendFactor.DestinationColor;
|
|
|
|
case BlendingType.SrcAlpha:
|
|
return BlendFactor.SourceAlpha;
|
|
|
|
case BlendingType.SrcColor:
|
|
return BlendFactor.SourceColor;
|
|
|
|
case BlendingType.OneMinusDstAlpha:
|
|
return BlendFactor.InverseDestinationAlpha;
|
|
|
|
case BlendingType.OneMinusDstColor:
|
|
return BlendFactor.InverseDestinationColor;
|
|
|
|
case BlendingType.OneMinusSrcAlpha:
|
|
return BlendFactor.InverseSourceAlpha;
|
|
|
|
case BlendingType.OneMinusSrcColor:
|
|
return BlendFactor.InverseSourceColor;
|
|
|
|
case BlendingType.One:
|
|
return BlendFactor.One;
|
|
|
|
case BlendingType.Zero:
|
|
return BlendFactor.Zero;
|
|
|
|
case BlendingType.ConstantColor:
|
|
return BlendFactor.BlendFactor;
|
|
|
|
case BlendingType.OneMinusConstantColor:
|
|
return BlendFactor.InverseBlendFactor;
|
|
|
|
// todo: veldrid has no support for those, we may want to consider removing them from BlendingType enum (we don't even provide a blend factor in the parameters).
|
|
case BlendingType.ConstantAlpha:
|
|
case BlendingType.OneMinusConstantAlpha:
|
|
case BlendingType.SrcAlphaSaturate:
|
|
default:
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public static BlendFunction ToBlendFunction(this BlendingEquation equation)
|
|
{
|
|
switch (equation)
|
|
{
|
|
case BlendingEquation.Add:
|
|
return BlendFunction.Add;
|
|
|
|
case BlendingEquation.Subtract:
|
|
return BlendFunction.Subtract;
|
|
|
|
case BlendingEquation.ReverseSubtract:
|
|
return BlendFunction.ReverseSubtract;
|
|
|
|
case BlendingEquation.Min:
|
|
return BlendFunction.Minimum;
|
|
|
|
case BlendingEquation.Max:
|
|
return BlendFunction.Maximum;
|
|
|
|
default:
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public static ColorWriteMask ToColorWriteMask(this BlendingMask mask)
|
|
{
|
|
ColorWriteMask writeMask = ColorWriteMask.None;
|
|
|
|
if (mask.HasFlagFast(BlendingMask.Red)) writeMask |= ColorWriteMask.Red;
|
|
if (mask.HasFlagFast(BlendingMask.Green)) writeMask |= ColorWriteMask.Green;
|
|
if (mask.HasFlagFast(BlendingMask.Blue)) writeMask |= ColorWriteMask.Blue;
|
|
if (mask.HasFlagFast(BlendingMask.Alpha)) writeMask |= ColorWriteMask.Alpha;
|
|
|
|
return writeMask;
|
|
}
|
|
|
|
public static PixelFormat[] ToPixelFormats(this RenderBufferFormat[] renderBufferFormats)
|
|
{
|
|
var pixelFormats = new PixelFormat[renderBufferFormats.Length];
|
|
|
|
for (int i = 0; i < pixelFormats.Length; i++)
|
|
{
|
|
switch (renderBufferFormats[i])
|
|
{
|
|
case RenderBufferFormat.D16:
|
|
pixelFormats[i] = PixelFormat.R16_UNorm;
|
|
break;
|
|
|
|
case RenderBufferFormat.D32:
|
|
pixelFormats[i] = PixelFormat.R32_Float;
|
|
break;
|
|
|
|
case RenderBufferFormat.D24S8:
|
|
pixelFormats[i] = PixelFormat.D24_UNorm_S8_UInt;
|
|
break;
|
|
|
|
case RenderBufferFormat.D32S8:
|
|
pixelFormats[i] = PixelFormat.D32_Float_S8_UInt;
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentException($"Unsupported render buffer format: {renderBufferFormats[i]}", nameof(renderBufferFormats));
|
|
}
|
|
}
|
|
|
|
return pixelFormats;
|
|
}
|
|
|
|
public static SamplerFilter ToSamplerFilter(this TextureFilteringMode mode)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case TextureFilteringMode.Linear:
|
|
return SamplerFilter.MinLinear_MagLinear_MipLinear;
|
|
|
|
case TextureFilteringMode.Nearest:
|
|
return SamplerFilter.MinPoint_MagPoint_MipPoint;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(mode));
|
|
}
|
|
}
|
|
|
|
public static ComparisonKind ToComparisonKind(this BufferTestFunction function)
|
|
{
|
|
switch (function)
|
|
{
|
|
case BufferTestFunction.Always:
|
|
return ComparisonKind.Always;
|
|
|
|
case BufferTestFunction.Never:
|
|
return ComparisonKind.Never;
|
|
|
|
case BufferTestFunction.LessThan:
|
|
return ComparisonKind.Less;
|
|
|
|
case BufferTestFunction.Equal:
|
|
return ComparisonKind.Equal;
|
|
|
|
case BufferTestFunction.LessThanOrEqual:
|
|
return ComparisonKind.LessEqual;
|
|
|
|
case BufferTestFunction.GreaterThan:
|
|
return ComparisonKind.Greater;
|
|
|
|
case BufferTestFunction.NotEqual:
|
|
return ComparisonKind.NotEqual;
|
|
|
|
case BufferTestFunction.GreaterThanOrEqual:
|
|
return ComparisonKind.GreaterEqual;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(function));
|
|
}
|
|
}
|
|
|
|
public static StencilOperation ToStencilOperation(this Rendering.StencilOperation operation)
|
|
{
|
|
switch (operation)
|
|
{
|
|
case Rendering.StencilOperation.Zero:
|
|
return StencilOperation.Zero;
|
|
|
|
case Rendering.StencilOperation.Invert:
|
|
return StencilOperation.Invert;
|
|
|
|
case Rendering.StencilOperation.Keep:
|
|
return StencilOperation.Keep;
|
|
|
|
case Rendering.StencilOperation.Replace:
|
|
return StencilOperation.Replace;
|
|
|
|
case Rendering.StencilOperation.Increase:
|
|
return StencilOperation.IncrementAndClamp;
|
|
|
|
case Rendering.StencilOperation.Decrease:
|
|
return StencilOperation.DecrementAndClamp;
|
|
|
|
case Rendering.StencilOperation.IncreaseWrap:
|
|
return StencilOperation.IncrementAndWrap;
|
|
|
|
case Rendering.StencilOperation.DecreaseWrap:
|
|
return StencilOperation.DecrementAndWrap;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(operation));
|
|
}
|
|
}
|
|
|
|
public static VertexElementFormat ToVertexElementFormat(this VertexAttribPointerType type, int count)
|
|
{
|
|
switch (type)
|
|
{
|
|
case VertexAttribPointerType.Byte when count == 2:
|
|
return VertexElementFormat.SByte2;
|
|
|
|
case VertexAttribPointerType.Byte when count == 4:
|
|
return VertexElementFormat.SByte4;
|
|
|
|
case VertexAttribPointerType.UnsignedByte when count == 2:
|
|
return VertexElementFormat.Byte2;
|
|
|
|
case VertexAttribPointerType.UnsignedByte when count == 4:
|
|
return VertexElementFormat.Byte4;
|
|
|
|
case VertexAttribPointerType.Short when count == 2:
|
|
return VertexElementFormat.Short2;
|
|
|
|
case VertexAttribPointerType.Short when count == 4:
|
|
return VertexElementFormat.Short4;
|
|
|
|
case VertexAttribPointerType.UnsignedShort when count == 2:
|
|
return VertexElementFormat.UShort2;
|
|
|
|
case VertexAttribPointerType.UnsignedShort when count == 4:
|
|
return VertexElementFormat.UShort4;
|
|
|
|
case VertexAttribPointerType.Int when count == 1:
|
|
return VertexElementFormat.Int1;
|
|
|
|
case VertexAttribPointerType.Int when count == 2:
|
|
return VertexElementFormat.Int2;
|
|
|
|
case VertexAttribPointerType.Int when count == 3:
|
|
return VertexElementFormat.Int3;
|
|
|
|
case VertexAttribPointerType.Int when count == 4:
|
|
return VertexElementFormat.Int4;
|
|
|
|
case VertexAttribPointerType.UnsignedInt when count == 1:
|
|
return VertexElementFormat.UInt1;
|
|
|
|
case VertexAttribPointerType.UnsignedInt when count == 2:
|
|
return VertexElementFormat.UInt2;
|
|
|
|
case VertexAttribPointerType.UnsignedInt when count == 3:
|
|
return VertexElementFormat.UInt3;
|
|
|
|
case VertexAttribPointerType.UnsignedInt when count == 4:
|
|
return VertexElementFormat.UInt4;
|
|
|
|
case VertexAttribPointerType.Float when count == 1:
|
|
return VertexElementFormat.Float1;
|
|
|
|
case VertexAttribPointerType.Float when count == 2:
|
|
return VertexElementFormat.Float2;
|
|
|
|
case VertexAttribPointerType.Float when count == 3:
|
|
return VertexElementFormat.Float3;
|
|
|
|
case VertexAttribPointerType.Float when count == 4:
|
|
return VertexElementFormat.Float4;
|
|
|
|
case VertexAttribPointerType.HalfFloat when count == 1:
|
|
return VertexElementFormat.Half1;
|
|
|
|
case VertexAttribPointerType.HalfFloat when count == 2:
|
|
return VertexElementFormat.Half2;
|
|
|
|
case VertexAttribPointerType.HalfFloat when count == 4:
|
|
return VertexElementFormat.Half4;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
|
}
|
|
}
|
|
|
|
public static PrimitiveTopology ToPrimitiveTopology(this Rendering.PrimitiveTopology type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case Rendering.PrimitiveTopology.Points:
|
|
return PrimitiveTopology.PointList;
|
|
|
|
case Rendering.PrimitiveTopology.Lines:
|
|
return PrimitiveTopology.LineList;
|
|
|
|
case Rendering.PrimitiveTopology.LineStrip:
|
|
return PrimitiveTopology.LineStrip;
|
|
|
|
case Rendering.PrimitiveTopology.Triangles:
|
|
return PrimitiveTopology.TriangleList;
|
|
|
|
case Rendering.PrimitiveTopology.TriangleStrip:
|
|
return PrimitiveTopology.TriangleStrip;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(type));
|
|
}
|
|
}
|
|
|
|
public static GraphicsPipelineDescription Clone(this GraphicsPipelineDescription pipeline)
|
|
{
|
|
pipeline.BlendState.AttachmentStates = (BlendAttachmentDescription[])pipeline.BlendState.AttachmentStates.Clone();
|
|
pipeline.ShaderSet.Shaders = (Shader[])pipeline.ShaderSet.Shaders.Clone();
|
|
pipeline.ShaderSet.VertexLayouts = (VertexLayoutDescription[])pipeline.ShaderSet.VertexLayouts.Clone();
|
|
|
|
for (int i = 0; i < pipeline.ShaderSet.VertexLayouts.Length; i++)
|
|
pipeline.ShaderSet.VertexLayouts[i].Elements = (VertexElementDescription[])pipeline.ShaderSet.VertexLayouts[i].Elements.Clone();
|
|
|
|
pipeline.ShaderSet.Specializations = (SpecializationConstant[]?)pipeline.ShaderSet.Specializations?.Clone();
|
|
pipeline.ResourceLayouts = (ResourceLayout[])pipeline.ResourceLayouts.Clone();
|
|
pipeline.Outputs.ColorAttachments = (OutputAttachmentDescription[])pipeline.Outputs.ColorAttachments.Clone();
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
public static void LogD3D11(this GraphicsDevice device, out int maxTextureSize)
|
|
{
|
|
Debug.Assert(device.BackendType == GraphicsBackend.Direct3D11);
|
|
|
|
var info = device.GetD3D11Info();
|
|
var dxgiAdapter = MarshallingHelpers.FromPointer<IDXGIAdapter>(info.Adapter).AsNonNull();
|
|
var d3d11Device = MarshallingHelpers.FromPointer<ID3D11Device>(info.Device).AsNonNull();
|
|
|
|
maxTextureSize = ID3D11Resource.MaximumTexture2DSize;
|
|
|
|
Logger.Log($@"Direct3D 11 Initialized
|
|
Direct3D 11 Feature Level: {d3d11Device.FeatureLevel.ToString().Replace("Level_", string.Empty).Replace("_", ".")}
|
|
Direct3D 11 Adapter: {dxgiAdapter.Description.Description}
|
|
Direct3D 11 Dedicated Video Memory: {dxgiAdapter.Description.DedicatedVideoMemory / 1024 / 1024} MB
|
|
Direct3D 11 Dedicated System Memory: {dxgiAdapter.Description.DedicatedSystemMemory / 1024 / 1024} MB
|
|
Direct3D 11 Shared System Memory: {dxgiAdapter.Description.SharedSystemMemory / 1024 / 1024} MB");
|
|
}
|
|
|
|
public static unsafe void LogOpenGL(this GraphicsDevice device, out int maxTextureSize)
|
|
{
|
|
var info = device.GetOpenGLInfo();
|
|
|
|
string version = string.Empty;
|
|
string renderer = string.Empty;
|
|
string glslVersion = string.Empty;
|
|
string vendor = string.Empty;
|
|
string extensions = string.Empty;
|
|
|
|
int glMaxTextureSize = 0;
|
|
|
|
info.ExecuteOnGLThread(() =>
|
|
{
|
|
version = Marshal.PtrToStringUTF8((IntPtr)OpenGLNative.glGetString(StringName.Version)) ?? string.Empty;
|
|
renderer = Marshal.PtrToStringUTF8((IntPtr)OpenGLNative.glGetString(StringName.Renderer)) ?? string.Empty;
|
|
vendor = Marshal.PtrToStringUTF8((IntPtr)OpenGLNative.glGetString(StringName.Vendor)) ?? string.Empty;
|
|
glslVersion = Marshal.PtrToStringUTF8((IntPtr)OpenGLNative.glGetString(StringName.ShadingLanguageVersion)) ?? string.Empty;
|
|
extensions = string.Join(' ', info.Extensions);
|
|
|
|
int size;
|
|
OpenGLNative.glGetIntegerv(GetPName.MaxTextureSize, &size);
|
|
glMaxTextureSize = size;
|
|
});
|
|
|
|
maxTextureSize = glMaxTextureSize;
|
|
|
|
Logger.Log($@"GL Initialized
|
|
GL Version: {version}
|
|
GL Renderer: {renderer}
|
|
GL Shader Language version: {glslVersion}
|
|
GL Vendor: {vendor}
|
|
GL Extensions: {extensions}");
|
|
}
|
|
|
|
public static unsafe void LogVulkan(this GraphicsDevice device, out int maxTextureSize)
|
|
{
|
|
Debug.Assert(device.BackendType == GraphicsBackend.Vulkan);
|
|
|
|
var info = device.GetVulkanInfo();
|
|
IntPtr physicalDevice = info.PhysicalDevice;
|
|
|
|
uint instanceExtensionsCount = 0;
|
|
var result = VulkanNative.vkEnumerateInstanceExtensionProperties((byte*)null, ref instanceExtensionsCount, IntPtr.Zero);
|
|
|
|
var instanceExtensions = new VkExtensionProperties[(int)instanceExtensionsCount];
|
|
if (result == VkResult.Success && instanceExtensionsCount > 0)
|
|
VulkanNative.vkEnumerateInstanceExtensionProperties((byte*)null, ref instanceExtensionsCount, ref instanceExtensions[0]);
|
|
|
|
uint deviceExetnsionsCount = 0;
|
|
result = VulkanNative.vkEnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, ref deviceExetnsionsCount, IntPtr.Zero);
|
|
|
|
var deviceExtensions = new VkExtensionProperties[(int)deviceExetnsionsCount];
|
|
if (result == VkResult.Success && deviceExetnsionsCount > 0)
|
|
VulkanNative.vkEnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, ref deviceExetnsionsCount, ref deviceExtensions[0]);
|
|
|
|
VkPhysicalDeviceProperties properties;
|
|
VulkanNative.vkGetPhysicalDeviceProperties(physicalDevice, &properties);
|
|
|
|
maxTextureSize = (int)properties.limits.maxImageDimension2D;
|
|
|
|
string vulkanName = RuntimeInfo.IsApple ? "MoltenVK" : "Vulkan";
|
|
|
|
List<string?> extensionNames = new List<string?>();
|
|
|
|
foreach (var ext in instanceExtensions)
|
|
extensionNames.Add(Marshal.PtrToStringUTF8((IntPtr)ext.extensionName));
|
|
foreach (var ext in deviceExtensions)
|
|
extensionNames.Add(Marshal.PtrToStringUTF8((IntPtr)ext.extensionName));
|
|
|
|
string apiVersion = $"{properties.apiVersion >> 22}.{(properties.apiVersion >> 12) & 0x3FFU}.{properties.apiVersion & 0xFFFU}";
|
|
string driverVersion;
|
|
|
|
// https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/1e6ca6e3c0763daabd6a101b860ab4354a07f5d3/functions.php#L293-L325
|
|
if (properties.vendorID == 0x10DE) // NVIDIA's versioning convention
|
|
driverVersion = $"{properties.driverVersion >> 22}.{(properties.driverVersion >> 14) & 0x0FFU}.{(properties.driverVersion >> 6) & 0x0FFU}.{properties.driverVersion & 0x003U}";
|
|
else if (properties.vendorID == 0x8086 && RuntimeInfo.OS == RuntimeInfo.Platform.Windows) // Intel's versioning convention on Windows
|
|
driverVersion = $"{properties.driverVersion >> 22}.{properties.driverVersion & 0x3FFFU}";
|
|
else // Vulkan's convention
|
|
driverVersion = $"{properties.driverVersion >> 22}.{(properties.driverVersion >> 12) & 0x3FFU}.{properties.driverVersion & 0xFFFU}";
|
|
|
|
Logger.Log($@"{vulkanName} Initialized
|
|
{vulkanName} API Version: {apiVersion}
|
|
{vulkanName} Driver Version: {driverVersion}
|
|
{vulkanName} Device: {Marshal.PtrToStringUTF8((IntPtr)properties.deviceName)}
|
|
{vulkanName} Extensions: {string.Join(',', extensionNames)}");
|
|
}
|
|
|
|
public static void LogMetal(this GraphicsDevice device, out int maxTextureSize)
|
|
{
|
|
Debug.Assert(device.BackendType == GraphicsBackend.Metal);
|
|
|
|
var info = device.GetMetalInfo();
|
|
|
|
string[] featureSetParts = info.MaxFeatureSet.ToString().Split('_');
|
|
string featureDevice = featureSetParts[0];
|
|
string featureFamily = featureSetParts[1].Replace("GPUFamily", string.Empty);
|
|
string featureVersion = featureSetParts[2];
|
|
|
|
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
|
|
if (info.MaxFeatureSet <= MTLFeatureSet.iOS_GPUFamily4_v1)
|
|
maxTextureSize = info.MaxFeatureSet <= MTLFeatureSet.iOS_GPUFamily1_v4 ? 8192 : 16384;
|
|
else if (info.MaxFeatureSet <= MTLFeatureSet.tvOS_GPUFamily2_v1)
|
|
maxTextureSize = info.MaxFeatureSet <= MTLFeatureSet.tvOS_GPUFamily1_v3 ? 8192 : 16384;
|
|
else
|
|
maxTextureSize = 16384;
|
|
|
|
Logger.Log($@"Metal Initialized
|
|
Metal Feature Set: {featureDevice} GPU family {featureFamily} ({featureVersion})");
|
|
}
|
|
}
|
|
}
|