mirror of https://github.com/buggins/dlangui.git
Add multisampling support
This commit is contained in:
parent
d92f668fbe
commit
c9ccaad2ed
|
@ -15,6 +15,9 @@ extern (C) int UIAppMain(string[] args) {
|
||||||
Platform.instance.GLVersionMajor = 2;
|
Platform.instance.GLVersionMajor = 2;
|
||||||
Platform.instance.GLVersionMinor = 1;
|
Platform.instance.GLVersionMinor = 1;
|
||||||
|
|
||||||
|
// Enable multisampling
|
||||||
|
Platform.instance.multisamples = 16;
|
||||||
|
|
||||||
// create window
|
// create window
|
||||||
Window window = Platform.instance.createWindow("DlangUI OpenGL Example", null, WindowFlag.Resizable, 800, 700);
|
Window window = Platform.instance.createWindow("DlangUI OpenGL Example", null, WindowFlag.Resizable, 800, 700);
|
||||||
|
|
||||||
|
@ -193,6 +196,9 @@ class MyOpenglWidget : VerticalLayout {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkgl!glEnable(GL_MULTISAMPLE);
|
||||||
|
checkgl!glEnable(GL_POLYGON_SMOOTH);
|
||||||
|
|
||||||
checkgl!glEnable(GL_CULL_FACE);
|
checkgl!glEnable(GL_CULL_FACE);
|
||||||
checkgl!glEnable(GL_DEPTH_TEST);
|
checkgl!glEnable(GL_DEPTH_TEST);
|
||||||
checkgl!glCullFace(GL_BACK);
|
checkgl!glCullFace(GL_BACK);
|
||||||
|
|
|
@ -937,7 +937,8 @@ final class GLSupport {
|
||||||
|
|
||||||
/// call glFlush
|
/// call glFlush
|
||||||
void flushGL() {
|
void flushGL() {
|
||||||
checkgl!glFlush();
|
// TODO: Is this really needed?
|
||||||
|
// checkgl!glFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool generateMipmap(int dx, int dy, ubyte * pixels, int level, ref ubyte[] dst) {
|
bool generateMipmap(int dx, int dy, ubyte * pixels, int level, ref ubyte[] dst) {
|
||||||
|
|
|
@ -280,8 +280,8 @@ class Window : CustomEventTarget {
|
||||||
return;
|
return;
|
||||||
WindowState state = windowState;
|
WindowState state = windowState;
|
||||||
Rect rect = windowRect;
|
Rect rect = windowRect;
|
||||||
if (state == WindowState.fullscreen
|
if (state == WindowState.fullscreen
|
||||||
|| state == WindowState.minimized
|
|| state == WindowState.minimized
|
||||||
|| state == WindowState.maximized
|
|| state == WindowState.maximized
|
||||||
|| state == WindowState.normal) {
|
|| state == WindowState.normal) {
|
||||||
//
|
//
|
||||||
|
@ -310,7 +310,7 @@ class Window : CustomEventTarget {
|
||||||
rect.right = w;
|
rect.right = w;
|
||||||
rect.bottom = h;
|
rect.bottom = h;
|
||||||
if (correctWindowPositionOnScreen(rect) && (state == WindowState.fullscreen
|
if (correctWindowPositionOnScreen(rect) && (state == WindowState.fullscreen
|
||||||
|| state == WindowState.minimized
|
|| state == WindowState.minimized
|
||||||
|| state == WindowState.maximized
|
|| state == WindowState.maximized
|
||||||
|| state == WindowState.normal)) {
|
|| state == WindowState.normal)) {
|
||||||
setWindowState(state, false, rect);
|
setWindowState(state, false, rect);
|
||||||
|
@ -1124,7 +1124,7 @@ class Window : CustomEventTarget {
|
||||||
@property CursorType overrideCursorType() {
|
@property CursorType overrideCursorType() {
|
||||||
return _overrideCursorType;
|
return _overrideCursorType;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool dispatchMouseEvent(Widget root, MouseEvent event, ref bool cursorIsSet) {
|
protected bool dispatchMouseEvent(Widget root, MouseEvent event, ref bool cursorIsSet) {
|
||||||
// only route mouse events to visible widgets
|
// only route mouse events to visible widgets
|
||||||
if (root.visibility != Visibility.Visible)
|
if (root.visibility != Visibility.Visible)
|
||||||
|
@ -1483,12 +1483,12 @@ class Window : CustomEventTarget {
|
||||||
if (event.action == MouseAction.Move || event.action == MouseAction.Leave) {
|
if (event.action == MouseAction.Move || event.action == MouseAction.Leave) {
|
||||||
processed = checkRemoveTracking(event);
|
processed = checkRemoveTracking(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cursorIsSet = false;
|
bool cursorIsSet = false;
|
||||||
if (overrideCursorType != CursorType.NotSet) {
|
if (overrideCursorType != CursorType.NotSet) {
|
||||||
cursorIsSet = true;
|
cursorIsSet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res) {
|
if (!res) {
|
||||||
bool insideOneOfPopups = false;
|
bool insideOneOfPopups = false;
|
||||||
for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
|
for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
|
||||||
|
@ -1843,6 +1843,13 @@ class Platform {
|
||||||
* Note: if the version is invalid or not supported, this value will be set to supported one.
|
* Note: if the version is invalid or not supported, this value will be set to supported one.
|
||||||
*/
|
*/
|
||||||
int GLVersionMinor = 2;
|
int GLVersionMinor = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenGL multisamples amount.
|
||||||
|
* Note: 0, 2, 4, 8, or 16 are accepted.
|
||||||
|
* TODO: Not supported on Linux
|
||||||
|
*/
|
||||||
|
int multisamples = 0;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* close window
|
* close window
|
||||||
|
|
|
@ -59,11 +59,58 @@ extern (C) int UIAppMain(string[] args);
|
||||||
|
|
||||||
immutable WIN_CLASS_NAME = "DLANGUI_APP";
|
immutable WIN_CLASS_NAME = "DLANGUI_APP";
|
||||||
|
|
||||||
|
/* This is a pretty dirty hack to get multisampling to work */
|
||||||
|
private __gshared bool isInitialized = false;
|
||||||
|
|
||||||
__gshared HINSTANCE _hInstance;
|
__gshared HINSTANCE _hInstance;
|
||||||
__gshared int _cmdShow;
|
__gshared int _cmdShow;
|
||||||
|
|
||||||
static if (ENABLE_OPENGL) {
|
static if (ENABLE_OPENGL) {
|
||||||
bool setupPixelFormat(HDC hDC)
|
|
||||||
|
// WGL stuff
|
||||||
|
|
||||||
|
// WGL_ARB_pixel_format
|
||||||
|
enum WGL_DRAW_TO_WINDOW_ARB = 0x2001;
|
||||||
|
enum WGL_DRAW_TO_BITMAP_ARB = 0x2002;
|
||||||
|
enum WGL_ACCELERATION_ARB = 0x2003;
|
||||||
|
enum WGL_SUPPORT_GDI_ARB = 0x200F;
|
||||||
|
enum WGL_SUPPORT_OPENGL_ARB = 0x2010;
|
||||||
|
enum WGL_DOUBLE_BUFFER_ARB = 0x2011;
|
||||||
|
enum WGL_STEREO_ARB = 0x2012;
|
||||||
|
enum WGL_PIXEL_TYPE_ARB = 0x2013;
|
||||||
|
enum WGL_COLOR_BITS_ARB = 0x2014;
|
||||||
|
enum WGL_DEPTH_BITS_ARB = 0x2022;
|
||||||
|
enum WGL_STENCIL_BITS_ARB = 0x2023;
|
||||||
|
|
||||||
|
enum WGL_NO_ACCELERATION_ARB = 0x2025;
|
||||||
|
enum WGL_GENERIC_ACCELERATION_ARB = 0x2026;
|
||||||
|
enum WGL_FULL_ACCELERATION_ARB = 0x2027;
|
||||||
|
|
||||||
|
enum WGL_TYPE_RGBA_ARB = 0x202B;
|
||||||
|
enum WGL_TYPE_COLORINDEX_ARB = 0x202C;
|
||||||
|
|
||||||
|
// WGL_ARB_create_context_profile
|
||||||
|
enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
|
||||||
|
enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
|
||||||
|
enum WGL_CONTEXT_FLAGS_ARB = 0x2094;
|
||||||
|
enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
|
||||||
|
|
||||||
|
// WGL_CONTEXT_FLAGS bits
|
||||||
|
enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
|
||||||
|
enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
|
||||||
|
|
||||||
|
// WGL_CONTEXT_PROFILE_MASK_ARB bits
|
||||||
|
enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
|
||||||
|
enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
|
||||||
|
|
||||||
|
enum GL_NUM_EXTENSIONS = 0x821D;
|
||||||
|
|
||||||
|
enum WGL_ALPHA_BITS_ARB = 0x201B;
|
||||||
|
|
||||||
|
enum WGL_SAMPLE_BUFFERS_ARB = 0x2041;
|
||||||
|
enum WGL_SAMPLES_ARB = 0x2042;
|
||||||
|
|
||||||
|
bool setupPixelFormat(HDC hDC, int multisamples = 0)
|
||||||
{
|
{
|
||||||
PIXELFORMATDESCRIPTOR pfd = {
|
PIXELFORMATDESCRIPTOR pfd = {
|
||||||
PIXELFORMATDESCRIPTOR.sizeof, /* size */
|
PIXELFORMATDESCRIPTOR.sizeof, /* size */
|
||||||
|
@ -72,7 +119,7 @@ static if (ENABLE_OPENGL) {
|
||||||
PFD_DRAW_TO_WINDOW |
|
PFD_DRAW_TO_WINDOW |
|
||||||
PFD_DOUBLEBUFFER, /* support double-buffering */
|
PFD_DOUBLEBUFFER, /* support double-buffering */
|
||||||
PFD_TYPE_RGBA, /* color type */
|
PFD_TYPE_RGBA, /* color type */
|
||||||
16, /* prefered color depth */
|
24, /* prefered color depth */
|
||||||
0, 0, 0, 0, 0, 0, /* color bits (ignored) */
|
0, 0, 0, 0, 0, 0, /* color bits (ignored) */
|
||||||
0, /* no alpha buffer */
|
0, /* no alpha buffer */
|
||||||
0, /* alpha bits (ignored) */
|
0, /* alpha bits (ignored) */
|
||||||
|
@ -87,7 +134,7 @@ static if (ENABLE_OPENGL) {
|
||||||
};
|
};
|
||||||
int pixelFormat;
|
int pixelFormat;
|
||||||
|
|
||||||
pixelFormat = ChoosePixelFormat(hDC, &pfd);
|
pixelFormat = multisamples > 0 ? sharedGLContext.multisampleFormat(hDC, multisamples) : ChoosePixelFormat(hDC, &pfd);
|
||||||
if (pixelFormat == 0) {
|
if (pixelFormat == 0) {
|
||||||
Log.e("ChoosePixelFormat failed.");
|
Log.e("ChoosePixelFormat failed.");
|
||||||
return false;
|
return false;
|
||||||
|
@ -158,6 +205,9 @@ const uint CUSTOM_MESSAGE_ID = WM_USER + 1;
|
||||||
|
|
||||||
static if (ENABLE_OPENGL) {
|
static if (ENABLE_OPENGL) {
|
||||||
|
|
||||||
|
alias BOOL function(HDC hdc, const(int)* attributes, const(FLOAT)* fAttributes, UINT maxFormats, int* pixelFormat, UINT *numFormats) PFNWGLCHOOSEPIXELFORMATARBPROC;
|
||||||
|
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
|
||||||
|
|
||||||
/// Shared opengl context helper
|
/// Shared opengl context helper
|
||||||
struct SharedGLContext {
|
struct SharedGLContext {
|
||||||
import bindbc.opengl;
|
import bindbc.opengl;
|
||||||
|
@ -183,14 +233,8 @@ static if (ENABLE_OPENGL) {
|
||||||
_hGLRC = wglCreateContext(hDC);
|
_hGLRC = wglCreateContext(hDC);
|
||||||
if (_hGLRC) {
|
if (_hGLRC) {
|
||||||
bind(hDC);
|
bind(hDC);
|
||||||
bool initialized = initGLSupport(Platform.instance.GLVersionMajor < 3);
|
wglChoosePixelFormatARB = cast(PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
|
||||||
unbind(hDC);
|
unbind(hDC);
|
||||||
if (!initialized) {
|
|
||||||
uninit();
|
|
||||||
Log.e("Failed to init OpenGL shaders");
|
|
||||||
_error = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
_error = true;
|
_error = true;
|
||||||
|
@ -202,6 +246,45 @@ static if (ENABLE_OPENGL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool initGLBindings(HDC hDC)
|
||||||
|
{
|
||||||
|
bind(hDC);
|
||||||
|
scope(exit) unbind(hDC);
|
||||||
|
bool initialized = initGLSupport(Platform.instance.GLVersionMajor < 3);
|
||||||
|
if (!initialized) {
|
||||||
|
uninit();
|
||||||
|
Log.e("Failed to init OpenGL shaders");
|
||||||
|
_error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper function to reinit a context to use multisampling
|
||||||
|
bool reinit(HDC hDC, int samples)
|
||||||
|
{
|
||||||
|
if(setupPixelFormat(hDC, samples))
|
||||||
|
{
|
||||||
|
_hPalette = setupPalette(hDC);
|
||||||
|
_hGLRC = wglCreateContext(hDC);
|
||||||
|
if (_hGLRC) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.e("Cannot reinit pixel format");
|
||||||
|
_error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void uninit() {
|
void uninit() {
|
||||||
if (_hGLRC) {
|
if (_hGLRC) {
|
||||||
wglDeleteContext(_hGLRC);
|
wglDeleteContext(_hGLRC);
|
||||||
|
@ -223,6 +306,42 @@ static if (ENABLE_OPENGL) {
|
||||||
void swapBuffers(HDC hDC) {
|
void swapBuffers(HDC hDC) {
|
||||||
SwapBuffers(hDC);
|
SwapBuffers(hDC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int multisampleFormat(HDC hdc, int samples)
|
||||||
|
{
|
||||||
|
bind(hdc);
|
||||||
|
GLint pixelFormat;
|
||||||
|
BOOL valid;
|
||||||
|
GLuint numFormats;
|
||||||
|
|
||||||
|
float[] fattribs = [0.0f, 0.0f];
|
||||||
|
|
||||||
|
int[] attribs =
|
||||||
|
[
|
||||||
|
WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
|
||||||
|
WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
|
||||||
|
WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
|
||||||
|
WGL_COLOR_BITS_ARB,24,
|
||||||
|
WGL_ALPHA_BITS_ARB,8,
|
||||||
|
WGL_DEPTH_BITS_ARB,16,
|
||||||
|
WGL_STENCIL_BITS_ARB,0,
|
||||||
|
WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
|
||||||
|
WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
|
||||||
|
WGL_SAMPLES_ARB, samples,
|
||||||
|
WGL_CONTEXT_MAJOR_VERSION_ARB, Platform.instance.GLVersionMajor,
|
||||||
|
WGL_CONTEXT_MINOR_VERSION_ARB, Platform.instance.GLVersionMinor,
|
||||||
|
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
|
||||||
|
WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
|
||||||
|
0
|
||||||
|
];
|
||||||
|
|
||||||
|
valid = wglChoosePixelFormatARB(hdc, attribs.ptr, fattribs.ptr, 1, &pixelFormat, &numFormats);
|
||||||
|
if(!valid)
|
||||||
|
Log.e("wglMakeCurrent is failed. GetLastError=%x".format(GetLastError()));
|
||||||
|
|
||||||
|
unbind(hdc);
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OpenGL context to share between windows
|
/// OpenGL context to share between windows
|
||||||
|
@ -297,7 +416,7 @@ class Win32Window : Window {
|
||||||
parenthwnd, // parent window handle
|
parenthwnd, // parent window handle
|
||||||
null, // window menu handle
|
null, // window menu handle
|
||||||
_hInstance, // program instance handle
|
_hInstance, // program instance handle
|
||||||
cast(void*)this); // creation parameters
|
platform.multisamples ? cast(void*)null : cast(void*)this); // creation parameters
|
||||||
static if (ENABLE_OPENGL) {
|
static if (ENABLE_OPENGL) {
|
||||||
/* initialize OpenGL rendering */
|
/* initialize OpenGL rendering */
|
||||||
HDC hDC = GetDC(_hwnd);
|
HDC hDC = GetDC(_hwnd);
|
||||||
|
@ -305,8 +424,34 @@ class Win32Window : Window {
|
||||||
if (openglEnabled) {
|
if (openglEnabled) {
|
||||||
useOpengl = sharedGLContext.init(hDC);
|
useOpengl = sharedGLContext.init(hDC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(platform.multisamples != 0)
|
||||||
|
{
|
||||||
|
sharedGLContext.uninit();
|
||||||
|
ReleaseDC(_hwnd, hDC);
|
||||||
|
|
||||||
|
// Recreate window with multisampling (copy-paste from above)
|
||||||
|
DestroyWindow(_hwnd);
|
||||||
|
_hwnd = CreateWindowW(toUTF16z(WIN_CLASS_NAME), // window class name
|
||||||
|
toUTF16z(windowCaption), // window caption
|
||||||
|
ws, // window style
|
||||||
|
x, // initial x position
|
||||||
|
y, // initial y position
|
||||||
|
_dx, // initial x size
|
||||||
|
_dy, // initial y size
|
||||||
|
parenthwnd, // parent window handle
|
||||||
|
null, // window menu handle
|
||||||
|
_hInstance, // program instance handle
|
||||||
|
cast(void*)this); // creation parameters
|
||||||
|
|
||||||
|
hDC = GetDC(_hwnd);
|
||||||
|
useOpengl = sharedGLContext.reinit(hDC, platform.multisamples);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sharedGLContext.initGLBindings(hDC);
|
||||||
|
isInitialized = true;
|
||||||
|
|
||||||
RECT rect;
|
RECT rect;
|
||||||
GetWindowRect(_hwnd, &rect);
|
GetWindowRect(_hwnd, &rect);
|
||||||
handleWindowStateChange(WindowState.unspecified, Rect(rect.left, rect.top, _dx, _dy));
|
handleWindowStateChange(WindowState.unspecified, Rect(rect.left, rect.top, _dx, _dy));
|
||||||
|
@ -322,7 +467,6 @@ class Win32Window : Window {
|
||||||
HDC hdc2 = BeginPaint(_hwnd, &ps);
|
HDC hdc2 = BeginPaint(_hwnd, &ps);
|
||||||
EndPaint(_hwnd, &ps);
|
EndPaint(_hwnd, &ps);
|
||||||
|
|
||||||
|
|
||||||
import bindbc.opengl; //3.gl3;
|
import bindbc.opengl; //3.gl3;
|
||||||
import bindbc.opengl; //3.wgl;
|
import bindbc.opengl; //3.wgl;
|
||||||
import dlangui.graphics.gldrawbuf;
|
import dlangui.graphics.gldrawbuf;
|
||||||
|
@ -360,7 +504,7 @@ class Win32Window : Window {
|
||||||
}
|
}
|
||||||
buf.afterDrawing();
|
buf.afterDrawing();
|
||||||
sharedGLContext.swapBuffers(hdc);
|
sharedGLContext.swapBuffers(hdc);
|
||||||
//sharedGLContext.unbind(hdc);
|
sharedGLContext.unbind(hdc);
|
||||||
destroy(buf);
|
destroy(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1487,10 +1631,13 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
CREATESTRUCT * pcreateStruct = cast(CREATESTRUCT*)lParam;
|
CREATESTRUCT * pcreateStruct = cast(CREATESTRUCT*)lParam;
|
||||||
window = cast(Win32Window)pcreateStruct.lpCreateParams;
|
window = cast(Win32Window)pcreateStruct.lpCreateParams;
|
||||||
void * ptr = cast(void*) window;
|
if(window !is null)
|
||||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr);
|
{
|
||||||
window._hwnd = hwnd;
|
void * ptr = cast(void*) window;
|
||||||
window.onCreate();
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr);
|
||||||
|
window._hwnd = hwnd;
|
||||||
|
window.onCreate();
|
||||||
|
}
|
||||||
//window.handleUnknownWindowMessage(message, wParam, lParam);
|
//window.handleUnknownWindowMessage(message, wParam, lParam);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1499,7 +1646,7 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||||
//window.handleUnknownWindowMessage(message, wParam, lParam);
|
//window.handleUnknownWindowMessage(message, wParam, lParam);
|
||||||
window.onDestroy();
|
window.onDestroy();
|
||||||
}
|
}
|
||||||
if (w32platform.windowCount == 0)
|
if (w32platform.windowCount == 0 && isInitialized)
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
return 0;
|
return 0;
|
||||||
case WM_WINDOWPOSCHANGED:
|
case WM_WINDOWPOSCHANGED:
|
||||||
|
|
Loading…
Reference in New Issue