Add `SDL_RenderSetVSync()`
Currently, if an application wants to toggle VSync, they'd have to tear down the renderer and recreate it. This patch fixes that by letting applications call SDL_RenderSetVSync(). This is the same as the patch in #3673, except it applies to all renderers (including PSP, even thought it seems that the VSync flag is disabled for that renderer). Furthermore, the renderer flags also change as well, which #3673 didn't do. It is also an API instead of using hint callbacks (which could be potentially dangerous). Closes #3673.main
parent
b2504b5da6
commit
4549769d7d
|
@ -1694,6 +1694,15 @@ extern DECLSPEC void *SDLCALL SDL_RenderGetMetalLayer(SDL_Renderer * renderer);
|
||||||
*/
|
*/
|
||||||
extern DECLSPEC void *SDLCALL SDL_RenderGetMetalCommandEncoder(SDL_Renderer * renderer);
|
extern DECLSPEC void *SDLCALL SDL_RenderGetMetalCommandEncoder(SDL_Renderer * renderer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle VSync of the given renderer.
|
||||||
|
*
|
||||||
|
* \param renderer The renderer to toggle
|
||||||
|
* \param vsync Non-zero for on, zero for off
|
||||||
|
* \returns a 0 int on success, or non-zero on failure
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_RenderSetVSync(SDL_Renderer* renderer, int vsync);
|
||||||
|
|
||||||
/* Ends C function definitions when using C++ */
|
/* Ends C function definitions when using C++ */
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -819,3 +819,4 @@
|
||||||
#define SDL_GetTextureUserData SDL_GetTextureUserData_REAL
|
#define SDL_GetTextureUserData SDL_GetTextureUserData_REAL
|
||||||
#define SDL_RenderGeometry SDL_RenderGeometry_REAL
|
#define SDL_RenderGeometry SDL_RenderGeometry_REAL
|
||||||
#define SDL_RenderGeometryRaw SDL_RenderGeometryRaw_REAL
|
#define SDL_RenderGeometryRaw SDL_RenderGeometryRaw_REAL
|
||||||
|
#define SDL_RenderSetVSync SDL_RenderSetVSync_REAL
|
||||||
|
|
|
@ -884,3 +884,4 @@ SDL_DYNAPI_PROC(int,SDL_SetTextureUserData,(SDL_Texture *a, void *b),(a,b),retur
|
||||||
SDL_DYNAPI_PROC(void*,SDL_GetTextureUserData,(SDL_Texture *a),(a),return)
|
SDL_DYNAPI_PROC(void*,SDL_GetTextureUserData,(SDL_Texture *a),(a),return)
|
||||||
SDL_DYNAPI_PROC(int,SDL_RenderGeometry,(SDL_Renderer *a, SDL_Texture *b, const SDL_Vertex *c, int d, const int *e, int f),(a,b,c,d,e,f),return)
|
SDL_DYNAPI_PROC(int,SDL_RenderGeometry,(SDL_Renderer *a, SDL_Texture *b, const SDL_Vertex *c, int d, const int *e, int f),(a,b,c,d,e,f),return)
|
||||||
SDL_DYNAPI_PROC(int,SDL_RenderGeometryRaw,(SDL_Renderer *a, SDL_Texture *b, const float *c, int d, const int *e, int f, const float *g, int h, int i, const void *j, int k, int l),(a,b,c,d,e,f,g,h,i,j,k,l),return)
|
SDL_DYNAPI_PROC(int,SDL_RenderGeometryRaw,(SDL_Renderer *a, SDL_Texture *b, const float *c, int d, const int *e, int f, const float *g, int h, int i, const void *j, int k, int l),(a,b,c,d,e,f,g,h,i,j,k,l),return)
|
||||||
|
SDL_DYNAPI_PROC(int,SDL_RenderSetVSync,(SDL_Renderer *a, int b),(a,b),return)
|
||||||
|
|
|
@ -4124,4 +4124,15 @@ SDL_GetBlendModeAlphaOperation(SDL_BlendMode blendMode)
|
||||||
return (SDL_BlendOperation)(((Uint32)blendMode >> 16) & 0xF);
|
return (SDL_BlendOperation)(((Uint32)blendMode >> 16) & 0xF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
SDL_RenderSetVSync(SDL_Renderer * renderer, int vsync)
|
||||||
|
{
|
||||||
|
CHECK_RENDERER_MAGIC(renderer, -1);
|
||||||
|
|
||||||
|
if (renderer->SetVSync) {
|
||||||
|
return renderer->SetVSync(renderer, vsync);
|
||||||
|
}
|
||||||
|
return SDL_Unsupported();
|
||||||
|
}
|
||||||
|
|
||||||
/* vi: set ts=4 sw=4 expandtab: */
|
/* vi: set ts=4 sw=4 expandtab: */
|
||||||
|
|
|
@ -163,6 +163,8 @@ struct SDL_Renderer
|
||||||
|
|
||||||
void (*DestroyRenderer) (SDL_Renderer * renderer);
|
void (*DestroyRenderer) (SDL_Renderer * renderer);
|
||||||
|
|
||||||
|
int (*SetVSync) (SDL_Renderer * renderer, int vsync);
|
||||||
|
|
||||||
int (*GL_BindTexture) (SDL_Renderer * renderer, SDL_Texture *texture, float *texw, float *texh);
|
int (*GL_BindTexture) (SDL_Renderer * renderer, SDL_Texture *texture, float *texw, float *texh);
|
||||||
int (*GL_UnbindTexture) (SDL_Renderer * renderer, SDL_Texture *texture);
|
int (*GL_UnbindTexture) (SDL_Renderer * renderer, SDL_Texture *texture);
|
||||||
|
|
||||||
|
|
|
@ -1743,6 +1743,24 @@ D3D_Reset(SDL_Renderer * renderer)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
D3D_SetVSync(SDL_Renderer * renderer, const int vsync)
|
||||||
|
{
|
||||||
|
D3D_RenderData *data = renderer->driverdata;
|
||||||
|
if (vsync) {
|
||||||
|
data->pparams.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
||||||
|
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
} else {
|
||||||
|
data->pparams.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||||
|
renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
}
|
||||||
|
if (D3D_Reset(renderer) < 0) {
|
||||||
|
/* D3D_Reset will call SDL_SetError() */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Renderer *
|
SDL_Renderer *
|
||||||
D3D_CreateRenderer(SDL_Window * window, Uint32 flags)
|
D3D_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
{
|
{
|
||||||
|
@ -1805,6 +1823,7 @@ D3D_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
renderer->RenderPresent = D3D_RenderPresent;
|
renderer->RenderPresent = D3D_RenderPresent;
|
||||||
renderer->DestroyTexture = D3D_DestroyTexture;
|
renderer->DestroyTexture = D3D_DestroyTexture;
|
||||||
renderer->DestroyRenderer = D3D_DestroyRenderer;
|
renderer->DestroyRenderer = D3D_DestroyRenderer;
|
||||||
|
renderer->SetVSync = D3D_SetVSync;
|
||||||
renderer->info = D3D_RenderDriver.info;
|
renderer->info = D3D_RenderDriver.info;
|
||||||
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
||||||
renderer->driverdata = data;
|
renderer->driverdata = data;
|
||||||
|
|
|
@ -2598,6 +2598,21 @@ D3D11_RenderPresent(SDL_Renderer * renderer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
|
||||||
|
/* no-op. */
|
||||||
|
#else
|
||||||
|
static int
|
||||||
|
D3D11_SetVSync(SDL_Renderer * renderer, const int vsync)
|
||||||
|
{
|
||||||
|
if (vsync) {
|
||||||
|
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
} else {
|
||||||
|
renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SDL_Renderer *
|
SDL_Renderer *
|
||||||
D3D11_CreateRenderer(SDL_Window * window, Uint32 flags)
|
D3D11_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
{
|
{
|
||||||
|
@ -2666,6 +2681,7 @@ D3D11_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
if ((flags & SDL_RENDERER_PRESENTVSYNC)) {
|
if ((flags & SDL_RENDERER_PRESENTVSYNC)) {
|
||||||
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
}
|
}
|
||||||
|
renderer->SetVSync = D3D11_SetVSync;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* HACK: make sure the SDL_Renderer references the SDL_Window data now, in
|
/* HACK: make sure the SDL_Renderer references the SDL_Window data now, in
|
||||||
|
|
|
@ -1760,6 +1760,26 @@ METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
|
||||||
return (__bridge void*)data.mtlcmdencoder;
|
return (__bridge void*)data.mtlcmdencoder;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
static int
|
||||||
|
METAL_SetVSync(SDL_Renderer * renderer, const int vsync)
|
||||||
|
{
|
||||||
|
#if (defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)) || TARGET_OS_MACCATALYST
|
||||||
|
if (@available(macOS 10.13, *)) {
|
||||||
|
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
||||||
|
if (vsync) {
|
||||||
|
data.mtllayer.displaySyncEnabled = YES;
|
||||||
|
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
} else {
|
||||||
|
data.mtllayer.displaySyncEnabled = NO;
|
||||||
|
renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return SDL_SetError("This Apple OS does not support displaySyncEnabled!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static SDL_Renderer *
|
static SDL_Renderer *
|
||||||
METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
{ @autoreleasepool {
|
{ @autoreleasepool {
|
||||||
|
@ -2010,6 +2030,7 @@ METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
renderer->RenderPresent = METAL_RenderPresent;
|
renderer->RenderPresent = METAL_RenderPresent;
|
||||||
renderer->DestroyTexture = METAL_DestroyTexture;
|
renderer->DestroyTexture = METAL_DestroyTexture;
|
||||||
renderer->DestroyRenderer = METAL_DestroyRenderer;
|
renderer->DestroyRenderer = METAL_DestroyRenderer;
|
||||||
|
renderer->SetVSync = METAL_SetVSync;
|
||||||
renderer->GetMetalLayer = METAL_GetMetalLayer;
|
renderer->GetMetalLayer = METAL_GetMetalLayer;
|
||||||
renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
|
renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
|
||||||
|
|
||||||
|
|
|
@ -1716,6 +1716,26 @@ GL_UnbindTexture (SDL_Renderer * renderer, SDL_Texture *texture)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
GL_SetVSync(SDL_Renderer * renderer, const int vsync)
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
if (vsync) {
|
||||||
|
retval = SDL_GL_SetSwapInterval(1);
|
||||||
|
} else {
|
||||||
|
retval = SDL_GL_SetSwapInterval(0);
|
||||||
|
}
|
||||||
|
if (retval != 0) {
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
if (SDL_GL_GetSwapInterval() > 0) {
|
||||||
|
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
} else {
|
||||||
|
renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static SDL_Renderer *
|
static SDL_Renderer *
|
||||||
GL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
GL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
|
@ -1785,6 +1805,7 @@ GL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
renderer->RenderPresent = GL_RenderPresent;
|
renderer->RenderPresent = GL_RenderPresent;
|
||||||
renderer->DestroyTexture = GL_DestroyTexture;
|
renderer->DestroyTexture = GL_DestroyTexture;
|
||||||
renderer->DestroyRenderer = GL_DestroyRenderer;
|
renderer->DestroyRenderer = GL_DestroyRenderer;
|
||||||
|
renderer->SetVSync = GL_SetVSync;
|
||||||
renderer->GL_BindTexture = GL_BindTexture;
|
renderer->GL_BindTexture = GL_BindTexture;
|
||||||
renderer->GL_UnbindTexture = GL_UnbindTexture;
|
renderer->GL_UnbindTexture = GL_UnbindTexture;
|
||||||
renderer->info = GL_RenderDriver.info;
|
renderer->info = GL_RenderDriver.info;
|
||||||
|
|
|
@ -1229,6 +1229,27 @@ static int GLES_UnbindTexture (SDL_Renderer * renderer, SDL_Texture *texture)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
GLES_SetVSync(SDL_Renderer * renderer, const int vsync)
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
if (vsync) {
|
||||||
|
retval = SDL_GL_SetSwapInterval(1);
|
||||||
|
} else {
|
||||||
|
retval = SDL_GL_SetSwapInterval(0);
|
||||||
|
}
|
||||||
|
if (retval != 0) {
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
if (SDL_GL_GetSwapInterval() > 0) {
|
||||||
|
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
} else {
|
||||||
|
renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static SDL_Renderer *
|
static SDL_Renderer *
|
||||||
GLES_CreateRenderer(SDL_Window * window, Uint32 flags)
|
GLES_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
{
|
{
|
||||||
|
@ -1294,6 +1315,7 @@ GLES_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
renderer->RenderPresent = GLES_RenderPresent;
|
renderer->RenderPresent = GLES_RenderPresent;
|
||||||
renderer->DestroyTexture = GLES_DestroyTexture;
|
renderer->DestroyTexture = GLES_DestroyTexture;
|
||||||
renderer->DestroyRenderer = GLES_DestroyRenderer;
|
renderer->DestroyRenderer = GLES_DestroyRenderer;
|
||||||
|
renderer->SetVSync = GLES_SetVSync;
|
||||||
renderer->GL_BindTexture = GLES_BindTexture;
|
renderer->GL_BindTexture = GLES_BindTexture;
|
||||||
renderer->GL_UnbindTexture = GLES_UnbindTexture;
|
renderer->GL_UnbindTexture = GLES_UnbindTexture;
|
||||||
renderer->info = GLES_RenderDriver.info;
|
renderer->info = GLES_RenderDriver.info;
|
||||||
|
|
|
@ -2012,6 +2012,26 @@ GLES2_RenderPresent(SDL_Renderer *renderer)
|
||||||
SDL_GL_SwapWindow(renderer->window);
|
SDL_GL_SwapWindow(renderer->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
GLES2_SetVSync(SDL_Renderer * renderer, const int vsync)
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
if (vsync) {
|
||||||
|
retval = SDL_GL_SetSwapInterval(1);
|
||||||
|
} else {
|
||||||
|
retval = SDL_GL_SetSwapInterval(0);
|
||||||
|
}
|
||||||
|
if (retval != 0) {
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
if (SDL_GL_GetSwapInterval() > 0) {
|
||||||
|
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
} else {
|
||||||
|
renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************************************
|
/*************************************************************************************************
|
||||||
* Bind/unbinding of textures
|
* Bind/unbinding of textures
|
||||||
|
@ -2197,6 +2217,7 @@ GLES2_CreateRenderer(SDL_Window *window, Uint32 flags)
|
||||||
renderer->RenderPresent = GLES2_RenderPresent;
|
renderer->RenderPresent = GLES2_RenderPresent;
|
||||||
renderer->DestroyTexture = GLES2_DestroyTexture;
|
renderer->DestroyTexture = GLES2_DestroyTexture;
|
||||||
renderer->DestroyRenderer = GLES2_DestroyRenderer;
|
renderer->DestroyRenderer = GLES2_DestroyRenderer;
|
||||||
|
renderer->SetVSync = GLES2_SetVSync;
|
||||||
renderer->GL_BindTexture = GLES2_BindTexture;
|
renderer->GL_BindTexture = GLES2_BindTexture;
|
||||||
renderer->GL_UnbindTexture = GLES2_UnbindTexture;
|
renderer->GL_UnbindTexture = GLES2_UnbindTexture;
|
||||||
|
|
||||||
|
|
|
@ -921,6 +921,14 @@ PSP_DestroyRenderer(SDL_Renderer * renderer)
|
||||||
SDL_free(renderer);
|
SDL_free(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
PSP_SetVSync(SDL_Renderer * renderer, const int vsync)
|
||||||
|
{
|
||||||
|
PSP_RenderData *data = renderer->driverdata;
|
||||||
|
data->vsync = vsync;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Renderer *
|
SDL_Renderer *
|
||||||
PSP_CreateRenderer(SDL_Window * window, Uint32 flags)
|
PSP_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
{
|
{
|
||||||
|
@ -962,6 +970,7 @@ PSP_CreateRenderer(SDL_Window * window, Uint32 flags)
|
||||||
renderer->RenderPresent = PSP_RenderPresent;
|
renderer->RenderPresent = PSP_RenderPresent;
|
||||||
renderer->DestroyTexture = PSP_DestroyTexture;
|
renderer->DestroyTexture = PSP_DestroyTexture;
|
||||||
renderer->DestroyRenderer = PSP_DestroyRenderer;
|
renderer->DestroyRenderer = PSP_DestroyRenderer;
|
||||||
|
renderer->SetVSync = PSP_SetVSync;
|
||||||
renderer->info = PSP_RenderDriver.info;
|
renderer->info = PSP_RenderDriver.info;
|
||||||
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
||||||
renderer->driverdata = data;
|
renderer->driverdata = data;
|
||||||
|
|
|
@ -201,6 +201,20 @@ StartDrawing(SDL_Renderer *renderer)
|
||||||
data->drawing = SDL_TRUE;
|
data->drawing = SDL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
VITA_GXM_SetVSync(SDL_Renderer * renderer, const int vsync)
|
||||||
|
{
|
||||||
|
VITA_GXM_RenderData *data = renderer->driverdata;
|
||||||
|
if (vsync) {
|
||||||
|
data->displayData.wait_vblank = SDL_TRUE;
|
||||||
|
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
} else {
|
||||||
|
data->displayData.wait_vblank = SDL_FALSE;
|
||||||
|
renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Renderer *
|
SDL_Renderer *
|
||||||
VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags)
|
VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags)
|
||||||
{
|
{
|
||||||
|
@ -241,6 +255,7 @@ VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags)
|
||||||
renderer->RenderPresent = VITA_GXM_RenderPresent;
|
renderer->RenderPresent = VITA_GXM_RenderPresent;
|
||||||
renderer->DestroyTexture = VITA_GXM_DestroyTexture;
|
renderer->DestroyTexture = VITA_GXM_DestroyTexture;
|
||||||
renderer->DestroyRenderer = VITA_GXM_DestroyRenderer;
|
renderer->DestroyRenderer = VITA_GXM_DestroyRenderer;
|
||||||
|
renderer->SetVSync = VITA_GXM_SetVSync;
|
||||||
|
|
||||||
renderer->info = VITA_GXM_RenderDriver.info;
|
renderer->info = VITA_GXM_RenderDriver.info;
|
||||||
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
||||||
|
|
Loading…
Reference in New Issue