mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-13 11:20:31 +00:00
212 lines
8.0 KiB
C#
212 lines
8.0 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.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Versioning;
|
|
using osuTK.Graphics;
|
|
using osuTK.Graphics.ES30;
|
|
using SDL;
|
|
using static SDL.SDL3;
|
|
|
|
namespace osu.Framework.Platform.SDL3
|
|
{
|
|
internal unsafe class SDL3GraphicsSurface : IGraphicsSurface, IOpenGLGraphicsSurface, IMetalGraphicsSurface, ILinuxGraphicsSurface, IAndroidGraphicsSurface
|
|
{
|
|
private readonly SDL3Window window;
|
|
|
|
private SDL_GLContextState* context;
|
|
|
|
public IntPtr WindowHandle => window.WindowHandle;
|
|
|
|
public GraphicsSurfaceType Type { get; }
|
|
|
|
public SDL3GraphicsSurface(SDL3Window window, GraphicsSurfaceType surfaceType)
|
|
{
|
|
this.window = window;
|
|
Type = surfaceType;
|
|
|
|
switch (surfaceType)
|
|
{
|
|
case GraphicsSurfaceType.OpenGL:
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_RED_SIZE, 8).ThrowIfFailed();
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_GREEN_SIZE, 8).ThrowIfFailed();
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_BLUE_SIZE, 8).ThrowIfFailed();
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_ACCUM_ALPHA_SIZE, 0).ThrowIfFailed();
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DEPTH_SIZE, 16).ThrowIfFailed();
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_STENCIL_SIZE, 8).ThrowIfFailed();
|
|
break;
|
|
|
|
case GraphicsSurfaceType.Vulkan:
|
|
case GraphicsSurfaceType.Metal:
|
|
case GraphicsSurfaceType.Direct3D11:
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentException($"Unexpected graphics surface: {Type}.", nameof(surfaceType));
|
|
}
|
|
}
|
|
|
|
public void Initialise()
|
|
{
|
|
if (Type == GraphicsSurfaceType.OpenGL)
|
|
initialiseOpenGL();
|
|
}
|
|
|
|
public Size GetDrawableSize()
|
|
{
|
|
int width, height;
|
|
SDL_GetWindowSizeInPixels(window.SDLWindowHandle, &width, &height).ThrowIfFailed();
|
|
return new Size(width, height);
|
|
}
|
|
|
|
#region OpenGL-specific implementation
|
|
|
|
private void initialiseOpenGL()
|
|
{
|
|
if (RuntimeInfo.IsMobile)
|
|
{
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL_GLProfile.SDL_GL_CONTEXT_PROFILE_ES).ThrowIfFailed();
|
|
|
|
// Minimum OpenGL version for ES profile:
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MAJOR_VERSION, 3).ThrowIfFailed();
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MINOR_VERSION, 0).ThrowIfFailed();
|
|
}
|
|
else
|
|
{
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL_GLProfile.SDL_GL_CONTEXT_PROFILE_CORE).ThrowIfFailed();
|
|
|
|
// Minimum OpenGL version for core profile:
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MAJOR_VERSION, 3).ThrowIfFailed();
|
|
SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MINOR_VERSION, 2).ThrowIfFailed();
|
|
}
|
|
|
|
context = SDL_GL_CreateContext(window.SDLWindowHandle);
|
|
|
|
if (context == null)
|
|
throw new InvalidOperationException($"Failed to create an SDL3 GL context ({SDL_GetError()})");
|
|
|
|
SDL_GL_MakeCurrent(window.SDLWindowHandle, context).ThrowIfFailed();
|
|
|
|
loadBindings();
|
|
}
|
|
|
|
private void loadBindings()
|
|
{
|
|
loadEntryPoints(new osuTK.Graphics.OpenGL.GL());
|
|
loadEntryPoints(new osuTK.Graphics.OpenGL4.GL());
|
|
loadEntryPoints(new osuTK.Graphics.ES11.GL());
|
|
loadEntryPoints(new osuTK.Graphics.ES20.GL());
|
|
loadEntryPoints(new GL());
|
|
}
|
|
|
|
private void loadEntryPoints(GraphicsBindingsBase bindings)
|
|
{
|
|
var type = bindings.GetType();
|
|
var pointsInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointsInstance");
|
|
var namesInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointNamesInstance");
|
|
var offsetsInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointNameOffsetsInstance");
|
|
|
|
IntPtr[]? entryPointsInstance = (IntPtr[]?)pointsInfo.GetValue(bindings);
|
|
byte[]? entryPointNamesInstance = (byte[]?)namesInfo.GetValue(bindings);
|
|
int[]? entryPointNameOffsetsInstance = (int[]?)offsetsInfo.GetValue(bindings);
|
|
|
|
Debug.Assert(entryPointsInstance != null);
|
|
Debug.Assert(entryPointNameOffsetsInstance != null);
|
|
|
|
fixed (byte* name = entryPointNamesInstance)
|
|
{
|
|
for (int i = 0; i < entryPointsInstance.Length; i++)
|
|
{
|
|
byte* ptr = name + entryPointNameOffsetsInstance[i];
|
|
string? str = Marshal.PtrToStringAnsi(new IntPtr(ptr));
|
|
|
|
Debug.Assert(str != null);
|
|
entryPointsInstance[i] = SDL_GL_GetProcAddress(str);
|
|
}
|
|
}
|
|
|
|
pointsInfo.SetValue(bindings, entryPointsInstance);
|
|
}
|
|
|
|
int? IOpenGLGraphicsSurface.BackbufferFramebuffer
|
|
{
|
|
get
|
|
{
|
|
if (window.SDLWindowHandle == null)
|
|
return null;
|
|
|
|
var props = SDL_GetWindowProperties(window.SDLWindowHandle);
|
|
|
|
if (SDL_HasProperty(props, SDL_PROP_WINDOW_UIKIT_OPENGL_FRAMEBUFFER_NUMBER))
|
|
return (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_UIKIT_OPENGL_FRAMEBUFFER_NUMBER, 0);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// cache value locally as requesting from SDL is not free.
|
|
// it is assumed that we are the only thing changing vsync modes.
|
|
private bool? verticalSync;
|
|
|
|
bool IOpenGLGraphicsSurface.VerticalSync
|
|
{
|
|
get
|
|
{
|
|
if (verticalSync != null)
|
|
return verticalSync.Value;
|
|
|
|
int interval;
|
|
SDL_GL_GetSwapInterval(&interval);
|
|
return (verticalSync = interval != 0).Value;
|
|
}
|
|
set
|
|
{
|
|
SDL_GL_SetSwapInterval(value ? 1 : 0);
|
|
verticalSync = value;
|
|
}
|
|
}
|
|
|
|
IntPtr IOpenGLGraphicsSurface.WindowContext => (IntPtr)context;
|
|
IntPtr IOpenGLGraphicsSurface.CurrentContext => (IntPtr)SDL_GL_GetCurrentContext();
|
|
|
|
void IOpenGLGraphicsSurface.SwapBuffers() => SDL_GL_SwapWindow(window.SDLWindowHandle);
|
|
void IOpenGLGraphicsSurface.CreateContext() => SDL_GL_CreateContext(window.SDLWindowHandle);
|
|
void IOpenGLGraphicsSurface.DeleteContext(IntPtr context) => SDL_GL_DestroyContext((SDL_GLContextState*)context);
|
|
void IOpenGLGraphicsSurface.MakeCurrent(IntPtr context) => SDL_GL_MakeCurrent(window.SDLWindowHandle, (SDL_GLContextState*)context);
|
|
void IOpenGLGraphicsSurface.ClearCurrent() => SDL_GL_MakeCurrent(window.SDLWindowHandle, null);
|
|
IntPtr IOpenGLGraphicsSurface.GetProcAddress(string symbol) => SDL_GL_GetProcAddress(symbol);
|
|
|
|
#endregion
|
|
|
|
#region Metal-specific implementation
|
|
|
|
IntPtr IMetalGraphicsSurface.CreateMetalView() => SDL_Metal_CreateView(window.SDLWindowHandle);
|
|
|
|
#endregion
|
|
|
|
#region Linux-specific implementation
|
|
|
|
bool ILinuxGraphicsSurface.IsWayland => window.IsWayland;
|
|
|
|
[SupportedOSPlatform("linux")]
|
|
IntPtr ILinuxGraphicsSurface.DisplayHandle => window.DisplayHandle;
|
|
|
|
#endregion
|
|
|
|
#region Android-specific implementation
|
|
|
|
[SupportedOSPlatform("android")]
|
|
IntPtr IAndroidGraphicsSurface.JniEnvHandle => SDL_GetAndroidJNIEnv().ThrowIfFailed();
|
|
|
|
[SupportedOSPlatform("android")]
|
|
IntPtr IAndroidGraphicsSurface.SurfaceHandle => window.SurfaceHandle;
|
|
|
|
#endregion
|
|
}
|
|
}
|