Skip to content

Commit

Permalink
Disable OpenGL for SDL2 window backend and introduce ANGLE egl_backend
Browse files Browse the repository at this point in the history
  • Loading branch information
misl6 committed Dec 28, 2023
1 parent 7146d0f commit f291b2d
Show file tree
Hide file tree
Showing 12 changed files with 737 additions and 88 deletions.
196 changes: 112 additions & 84 deletions kivy/core/window/_window_sdl2.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ from kivy.config import Config
from kivy.logger import Logger
from kivy import platform
from kivy.graphics.cgl cimport *
from kivy.graphics.egl_backend.egl_angle cimport EGLANGLE

from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free

Expand All @@ -27,25 +28,31 @@ IF UNAME_SYSNAME == 'Windows':
cdef int _event_filter(void *userdata, SDL_Event *event) with gil:
return (<_WindowSDL2Storage>userdata).cb_event_filter(event)


cdef class _WindowSDL2Storage:
cdef SDL_Window *win
cdef SDL_GLContext ctx
cdef SDL_Surface *surface
cdef SDL_Surface *icon
cdef int win_flags
cdef object event_filter
cdef EGLANGLE egl_angle_storage
cdef object gl_backend_name

def __cinit__(self):
self.win = NULL
self.ctx = NULL
self.surface = NULL
self.win_flags = 0
self.event_filter = None
self.gl_backend_name = None

def set_event_filter(self, event_filter):
self.event_filter = event_filter

@property
def gl_context_is_sdl2_managed(self):
return self.gl_backend_name != "angle"

cdef int cb_event_filter(self, SDL_Event *event):
# must return 0 to eat the event, 1 to add it into the event queue
cdef str name = None
Expand Down Expand Up @@ -75,8 +82,69 @@ cdef class _WindowSDL2Storage:
def die(self):
raise RuntimeError(<bytes> SDL_GetError())

cdef _setup_sdl_metal_window(self):
cdef void * _sdlcalayer
view = SDL_Metal_CreateView(self.win)
_sdlcalayer = SDL_Metal_GetLayer(view)
self.egl_angle_storage = <EGLANGLE>EGLANGLE()
self.egl_angle_storage.set_native_layer(_sdlcalayer)
self.egl_angle_storage.create_context()

def _set_sdl_gl_common_attributes(self):
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16)
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8)
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8)
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, KIVY_SDL_GL_ALPHA_SIZE)
SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0)
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1)

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0)

if self.gl_backend_name == "angle_sdl2":
Logger.info("Window: Activate GLES2/ANGLE context")
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 4)
SDL_SetHint(SDL_HINT_VIDEO_WIN_D3DCOMPILER, "none")

cdef SDL_Window * _setup_sdl_window(self, x, y, width, height, multisamples, shaped):

if multisamples:
if self.gl_context_is_sdl2_managed:
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1)
SDL_GL_SetAttribute(
SDL_GL_MULTISAMPLESAMPLES, min(multisamples, 4)
)
else:
# Non-SDL GL context, so we can't set the multisample
# attributes.
return NULL
else:
if self.gl_context_is_sdl2_managed:
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0)
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0)

if shaped:
return SDL_CreateShapedWindow(
NULL, x, y, width, height, self.win_flags
)
else:
return SDL_CreateWindow(
NULL, x, y, width, height, self.win_flags
)

def setup_window(self, x, y, width, height, borderless, fullscreen, resizable, state, gl_backend):
self.win_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI
self.gl_backend_name = gl_backend

self.win_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI

if self.gl_context_is_sdl2_managed:
self.win_flags |= SDL_WINDOW_OPENGL

if not self.gl_context_is_sdl2_managed and platform in ["macosx", "ios"]:
self.win_flags |= SDL_WINDOW_METAL

if resizable:
self.win_flags |= SDL_WINDOW_RESIZABLE
Expand Down Expand Up @@ -138,90 +206,47 @@ cdef class _WindowSDL2Storage:

SDL_SetHint(SDL_HINT_ORIENTATIONS, <bytes>(orientations.encode('utf-8')))

SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16)
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8)
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8)
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, KIVY_SDL_GL_ALPHA_SIZE)
SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0)
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1)

if gl_backend == "angle_sdl2":
Logger.info("Window: Activate GLES2/ANGLE context")
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 4)
SDL_SetHint(SDL_HINT_VIDEO_WIN_D3DCOMPILER, "none")

if x is None:
x = SDL_WINDOWPOS_UNDEFINED
if y is None:
y = SDL_WINDOWPOS_UNDEFINED

if self.gl_context_is_sdl2_managed:
self._set_sdl_gl_common_attributes()

# Multisampling:
# (The number of samples is limited to 4, because greater values
# aren't supported with some video drivers.)
cdef int multisamples, shaped
multisamples = Config.getint('graphics', 'multisamples')
cdef int config_multisamples, config_shaped
config_multisamples = Config.getint('graphics', 'multisamples')

# we need to tell the window to be shaped before creation, therefore
# it's a config property like e.g. fullscreen
shaped = Config.getint('graphics', 'shaped')

if multisamples > 0 and shaped > 0:
# try to create shaped window with multisampling:
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1)
SDL_GL_SetAttribute(
SDL_GL_MULTISAMPLESAMPLES, min(multisamples, 4)
)
self.win = SDL_CreateShapedWindow(
NULL, x, y, width, height, self.win_flags
)
if not self.win:
# if an error occurred, create only shaped window:
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0)
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0)
self.win = SDL_CreateShapedWindow(
NULL, x, y, width, height, self.win_flags
)
if not self.win:
# if everything fails, create an ordinary window:
self.win = SDL_CreateWindow(
NULL, x, y, width, height, self.win_flags
)
elif multisamples > 0:
# try to create window with multisampling:
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1)
SDL_GL_SetAttribute(
SDL_GL_MULTISAMPLESAMPLES, min(multisamples, 4)
)
self.win = SDL_CreateWindow(
NULL, x, y, width, height, self.win_flags
)
if not self.win:
# if an error occurred, create window without multisampling:
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0)
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0)
self.win = SDL_CreateWindow(
NULL, x, y, width, height, self.win_flags
)
elif shaped > 0:
# try to create shaped window:
self.win = SDL_CreateShapedWindow(
NULL, x, y, width, height, self.win_flags
)
if not self.win:
# if an error occurred, create an ordinary window:
self.win = SDL_CreateWindow(
NULL, x, y, width, height, self.win_flags
)
else:
self.win = SDL_CreateWindow(
NULL, x, y, width, height, self.win_flags
)
config_shaped = Config.getint('graphics', 'shaped')

# Since we can't know if a window can be shaped and can have
# multisampling, we need to try all combinations.
# The order is important: we try to create a shaped window with
# multisampling first, then a shaped window without multisampling,
# then an ordinary window with multisampling, and finally an ordinary
# window without multisampling.
sdl_window_configs = []
if config_multisamples and config_shaped:
sdl_window_configs.append((config_multisamples, config_shaped))
if config_shaped:
sdl_window_configs.append((0, config_shaped))
if config_multisamples:
sdl_window_configs.append((config_multisamples, 0))
sdl_window_configs.append((0, 0))

for multisamples, shaped in sdl_window_configs:
win = self._setup_sdl_window(x, y, width, height, multisamples, shaped)
if win:
self.win = win
break

# post-creation fix for shaped window
if shaped > 0 and self.is_window_shaped():
if self.is_window_shaped():
# because SDL just set it to (-1000, -1000)
# -> can't use UNDEFINED nor CENTER after window creation
self.set_window_pos(100, 100)
Expand All @@ -234,17 +259,17 @@ cdef class _WindowSDL2Storage:
if not self.win:
self.die()

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0)

if gl_backend != "mock":
self.ctx = SDL_GL_CreateContext(self.win)
if not self.ctx:
if self.gl_backend_name != "mock":
if self.gl_context_is_sdl2_managed:
self.ctx = SDL_GL_CreateContext(self.win)
else:
self._setup_sdl_metal_window()
if not self.ctx and self.gl_context_is_sdl2_managed:
self.die()

# vsync
vsync = Config.get('graphics', 'vsync')
if vsync and vsync != 'none':
if self.gl_context_is_sdl2_managed and vsync and vsync != 'none':
vsync = Config.getint('graphics', 'vsync')

Logger.debug(f'WindowSDL: setting vsync interval=={vsync}')
Expand Down Expand Up @@ -319,15 +344,15 @@ cdef class _WindowSDL2Storage:

def _get_gl_size(self):
cdef int w, h
SDL_GL_GetDrawableSize(self.win, &w, &h)
SDL_GetWindowSizeInPixels(self.win, &w, &h)
return w, h

def resize_display_mode(self, w, h):
cdef SDL_DisplayMode mode
cdef int draw_w, draw_h
SDL_GetWindowDisplayMode(self.win, &mode)
if USE_IOS and self.ctx:
SDL_GL_GetDrawableSize(self.win, &draw_w, &draw_h)
SDL_GetWindowSizeInPixels(self.win, &draw_w, &draw_h)
mode.w = draw_w
mode.h = draw_h
SDL_SetWindowDisplayMode(self.win, &mode)
Expand Down Expand Up @@ -783,8 +808,11 @@ cdef class _WindowSDL2Storage:
# released. Calling SDL_GL_SwapWindow with the GIL released allow the
# other thread to run (e.g. to process the event filter callback) and
# release the mutex SDL_GL_SwapWindow is waiting for.
with nogil:
SDL_GL_SwapWindow(self.win)
if self.gl_context_is_sdl2_managed:
with nogil:
SDL_GL_SwapWindow(self.win)
else:
self.egl_angle_storage.swap_buffers()

def save_bytes_in_png(self, filename, data, int width, int height):
cdef SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(
Expand Down
6 changes: 4 additions & 2 deletions kivy/graphics/cgl.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ At runtime the following backends are available and can be set using
* ``angle_sdl2`` -- Available on Windows with Python 3.5+.
Unavailable when ``USE_SDL2=0``. Requires ``kivy_deps.sdl2`` and
``kivy_deps.angle`` be installed.
* ``angle`` -- Available on macOS and iOS. Unavailable when ``USE_SDL2=0``.
Requires ``angle`` libEGL and libGLESv2 libraries (and includes) during Kivy build.
* ``mock`` -- Always available. Doesn't actually do anything.
Expand Down Expand Up @@ -75,7 +77,7 @@ cpdef cgl_get_backend_name(allowed=[], ignored=[]):
if name:
return name.lower()

for name in ('glew', 'sdl2', 'gl', 'mock'):
for name in ('glew', 'angle', 'sdl2', 'gl', 'mock'):
if allowed and name not in allowed:
continue
if name in ignored:
Expand Down Expand Up @@ -109,7 +111,7 @@ cpdef cgl_init(allowed=[], ignored=[]):
raise Exception("CGL: ANGLE backend can be used only on Windows")
backend = "sdl2"

if cgl_name not in {'glew', 'sdl2', 'angle_sdl2', 'mock', 'gl'}:
if cgl_name not in {'glew', 'angle', 'sdl2', 'angle_sdl2', 'mock', 'gl'}:
raise ValueError('{} is not a recognized GL backend'.format(backend))

mod = importlib.import_module("kivy.graphics.cgl_backend.cgl_{}".format(backend))
Expand Down
Loading

0 comments on commit f291b2d

Please sign in to comment.