Files
osu-framework/osu.Framework/Graphics/Veldrid/VeldridExtensions.cs
2024-02-02 03:44:24 +09:00

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})");
}
}
}