2024-07-05 13:23:37 +01:00
|
|
|
from typing import Optional, Callable
|
2022-05-14 15:19:45 +01:00
|
|
|
import logging
|
|
|
|
|
2021-01-16 11:37:24 +00:00
|
|
|
import wx
|
2024-05-29 09:07:17 +01:00
|
|
|
from wx.glcanvas import GLCanvas, GLAttributes, GLContext, GLContextAttrs
|
2021-01-16 11:37:24 +00:00
|
|
|
from OpenGL.GL import (
|
|
|
|
GL_DEPTH_TEST,
|
|
|
|
glEnable,
|
|
|
|
GL_CULL_FACE,
|
|
|
|
glDepthFunc,
|
|
|
|
GL_LEQUAL,
|
|
|
|
GL_BLEND,
|
|
|
|
glBlendFunc,
|
|
|
|
GL_SRC_ALPHA,
|
|
|
|
GL_ONE_MINUS_SRC_ALPHA,
|
2022-05-14 15:19:45 +01:00
|
|
|
glGetString,
|
|
|
|
GL_VERSION,
|
2021-01-16 11:37:24 +00:00
|
|
|
)
|
2024-07-05 13:23:37 +01:00
|
|
|
from OpenGL.GL.ARB.explicit_attrib_location import glInitExplicitAttribLocationARB
|
2021-02-17 11:48:28 +00:00
|
|
|
|
2022-05-14 15:19:45 +01:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
"""OpenGL workflow:
|
|
|
|
The initialisation function should be as minimal as possible. No OpenGL functions should be called here. The OpenGL state is not valid until the window is first shown.
|
|
|
|
You can implement functions that take a while in threads to not block the GUI but they must still not contain OpenGL functions.
|
|
|
|
Upon the window being shown the OpenGL context is activated and the state can be set in _init_opengl
|
|
|
|
Objects that need to bind textures or data should do so in the draw function so they can be sure the context is set.
|
|
|
|
"""
|
2021-01-16 11:37:24 +00:00
|
|
|
|
|
|
|
|
2024-05-29 09:07:17 +01:00
|
|
|
class BaseCanvas(GLCanvas):
|
|
|
|
_context: Optional[GLContext]
|
2022-05-14 15:19:45 +01:00
|
|
|
|
2021-01-16 11:37:24 +00:00
|
|
|
def __init__(self, parent: wx.Window):
|
2022-05-14 15:19:45 +01:00
|
|
|
"""
|
|
|
|
Construct the canvas.
|
|
|
|
No OpenGL interaction should be done here.
|
|
|
|
OpenGL initialisation should be done in _init_opengl which is run after the window is first shown.
|
|
|
|
"""
|
2024-05-29 09:07:17 +01:00
|
|
|
display_attributes = GLAttributes()
|
2021-01-16 11:37:24 +00:00
|
|
|
display_attributes.PlatformDefaults().MinRGBA(8, 8, 8, 8).DoubleBuffer().Depth(
|
|
|
|
24
|
|
|
|
).EndList()
|
|
|
|
super().__init__(
|
|
|
|
parent,
|
|
|
|
display_attributes,
|
|
|
|
size=parent.GetClientSize(),
|
|
|
|
style=wx.WANTS_CHARS,
|
|
|
|
)
|
2023-01-14 09:39:11 +00:00
|
|
|
|
2024-05-29 09:07:17 +01:00
|
|
|
# Amulet-Team/Amulet-Map-Editor#84
|
|
|
|
# Amulet-Team/Amulet-Map-Editor#597
|
|
|
|
# Amulet-Team/Amulet-Map-Editor#856
|
2024-07-05 13:23:37 +01:00
|
|
|
def gl3() -> Optional[GLContext]:
|
|
|
|
ctx_attrs = GLContextAttrs()
|
|
|
|
ctx_attrs.PlatformDefaults()
|
|
|
|
ctx_attrs.OGLVersion(3, 3)
|
|
|
|
ctx_attrs.CoreProfile()
|
|
|
|
ctx_attrs.EndList()
|
|
|
|
ctx = GLContext(self, ctxAttrs=ctx_attrs)
|
|
|
|
if ctx.IsOK():
|
|
|
|
return ctx
|
|
|
|
return None
|
2024-05-29 09:07:17 +01:00
|
|
|
|
2024-07-05 13:23:37 +01:00
|
|
|
def gl2() -> Optional[GLContext]:
|
|
|
|
ctx_attrs = GLContextAttrs()
|
|
|
|
ctx_attrs.PlatformDefaults()
|
|
|
|
ctx_attrs.OGLVersion(2, 1)
|
|
|
|
ctx_attrs.CompatibilityProfile()
|
|
|
|
ctx_attrs.EndList()
|
|
|
|
ctx = GLContext(self, ctxAttrs=ctx_attrs)
|
|
|
|
if ctx.IsOK() and glInitExplicitAttribLocationARB():
|
|
|
|
return ctx
|
|
|
|
return None
|
|
|
|
|
|
|
|
context_constructors: list[Callable[[], Optional[GLContext]]] = [gl3, gl2]
|
|
|
|
context = next((constructor() for constructor in context_constructors), None)
|
|
|
|
if context is None:
|
2022-05-14 15:19:45 +01:00
|
|
|
raise Exception(f"Failed setting up context")
|
|
|
|
|
2024-05-29 09:07:17 +01:00
|
|
|
self._context = context
|
2022-05-14 15:19:45 +01:00
|
|
|
self._init = False
|
|
|
|
|
|
|
|
self.Bind(wx.EVT_SHOW, self._on_show)
|
|
|
|
|
|
|
|
@property
|
2024-05-29 09:07:17 +01:00
|
|
|
def context(self) -> GLContext:
|
2022-05-14 15:19:45 +01:00
|
|
|
return self._context
|
2021-01-16 11:37:24 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def context_identifier(self) -> str:
|
2022-05-14 15:19:45 +01:00
|
|
|
# if not self._init:
|
|
|
|
# raise Exception("Cannot access the context until the window has been shown.")
|
|
|
|
return str(id(self._context))
|
2021-01-16 11:37:24 +00:00
|
|
|
|
2022-05-14 15:19:45 +01:00
|
|
|
def _on_show(self, evt: wx.ShowEvent):
|
|
|
|
if not self._init and evt.IsShown():
|
|
|
|
self._init = True
|
|
|
|
self._init_opengl()
|
|
|
|
|
|
|
|
def _init_opengl(self):
|
|
|
|
"""Set up the OpenGL state after the window is first shown."""
|
|
|
|
self.SetCurrent(self._context)
|
|
|
|
gl_version = glGetString(GL_VERSION)
|
|
|
|
if isinstance(gl_version, bytes):
|
|
|
|
gl_version = gl_version.decode("utf-8")
|
|
|
|
log.info(f"OpenGL Version {gl_version}")
|
2021-01-16 11:37:24 +00:00
|
|
|
glEnable(GL_DEPTH_TEST)
|
|
|
|
glEnable(GL_CULL_FACE)
|
|
|
|
glDepthFunc(GL_LEQUAL)
|
|
|
|
glEnable(GL_BLEND)
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|