Hi,
Short background: I am developing an application where I am building the UI with wxPython. I’d like wxPython to take care of window and OpenGL context creation. I will use ctypes to call .so/.dlls that will do the heavy lifting (data analysis). My first testcase was ripping all X11-specific stuff from glxgears
putting only the GL calls in an .so. That worked under windows&linux. This is my first foray into OpenGL so I only found out after this exercise that glxgears is more than ancient. However, my attempts to use modern OpenGL are foiled by some issue with the GLContext.
I found a suitable python-only test case using wxPython and pyopengl that demonstrates the issue on stackoverflow. This is my modified version:
#! /usr/bin/env python
# Adapted from:
# https://stackoverflow.com/questions/39734211/how-do-i-get-pyopengl-to-work-with-> a-wxpython-context-based-on-this-c-modern
#
# Works on windows but not on linux
#
# Tested with:
# --------------------------------------
# Windows 7 AMD64
# python : 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)]
# wxpython : 4.1.0 msw (phoenix) wxWidgets 3.1.4
# pyopengl : 3.1.5
# OpenGL : 4.6.0 NVIDIA 460.89
# --------------------------------------
# Fedora 32 kernel 5.9.14-100.fc32.x86_64
# python : 3.8.6 (default, Sep 25 2020, 00:00:00)
# [GCC 10.2.1 20200723 (Red Hat 10.2.1-1)]
# wxpython : 4.1.1 gtk3 (phoenix) wxWidgets 3.1.5
# pyopengl : 3.1.5
# OpenGL : 4.6.0 NVIDIA 455.45.01
# --------------------------------------
#
# On linux the GL context created by wx seems to be partially visible by
# other "code": pyopengl for one, but the same problem occurs in low-level
# code in a .so imported using the ctypes module - the code in the InitGL()
# function works up to the call: glEnableVertexAttribArray()
# - maybe at the driver level the context is known and hence most GL stuff works?
# - but perhaps it is not "visible" in the X11-OpenGL interface, i.e. GLX?
#
# Also of interest: an earlier .so that used the drawing commands from glxgears,
# i.e. OGL 1.x only, did work. In any case, the current pyopengl based example
# demonstrates the issue sufficiently, I think
# I think wx uses libepoxy to load OGL. I tried to CDLL libepoxy, libGL,
# libGLX with RTLD_GLOBAL before importing pyopengl (import OpenGL) and wx
# thinking maybe some global variable was define more than once but to no
# avail
#
# Setting g_exclude_crash_trigger = True allows the code to run by
# not calling glEnableVertexAttribArray() and since it then does not make sense
# glDrawArrays - in this case the window turns up with a dark green background
# so glClear() *is* working
import platform as pfinfo # prevent conflict with OpenGL.platform
import sys
import OpenGL
#OpenGL.CONTEXT_CHECKING = True
from OpenGL.GL import * # the package is called pyopengl
import wx
from wx import glcanvas
import numpy as np
g_exclude_crash_trigger = False
def show_version_info():
print('--------------------------------------')
try:
import distro
print('{} {} kernel {}'.format(distro.name(),distro.version(),pfinfo.uname().release))
except ImportError:
print('{} {} {}'.format(pfinfo.system(), pfinfo.release(), pfinfo.machine()))
print('python : {}'.format(sys.version))
print('wxpython : {}'.format(wx.version()))
print('pyopengl : {}'.format(OpenGL.version.__version__))
print('OpenGL : {}'.format(glGetString(GL_VERSION).decode('utf8')))
print('--------------------------------------')
sys.stdout.flush()
vertexSource = """
#version 130
in vec2 position;
void main()
{
gl_Position = vec4(position, 0.0, 1.0);
}
"""
fragmentSource = """
#version 130
out vec4 outColor;
void main()
{
outColor = vec4(0.2, 0.3, 1.0, 1.0);
}
"""
class OpenGLCanvas(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, size=(640, 480))
self.init = False
#self.context = glcanvas.GLContext(self)
cxtAttrs = glcanvas.GLContextAttrs()
cxtAttrs.CoreProfile().EndList()
self.context = glcanvas.GLContext(self, ctxAttrs=cxtAttrs)
#self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnEraseBackground(self, event):
pass # Do nothing, to avoid flashing on MSW.
def OnPaint(self, event):
dc = wx.PaintDC(self)
self.SetCurrent(self.context)
if not self.init:
# Note: this is the proper time to initialise the OGL stuff. I
# tried other events (EVT_SHOW, EVT_CREATE) that occur before the
# first EVT_PAINT but the GL context does not exist yet
# Note, the wx "view" of the GL context is that it has been created
# and is proper:
assert(self.context.IsOK())
self.InitGL()
self.init = True
self.OnDraw()
def InitGL(self):
# Vertex Input
## Vertex Array Objects
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
## Vertex Buffer Object
vbo = glGenBuffers(1) # Generate 1 buffer
vertices = np.array([0.0, 0.5, 0.5, -0.5, -0.5, -0.5], dtype=np.float32)
## Upload data to GPU
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
# Compile shaders and combining them into a program
## Create and compile the vertex shader
vertexShader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertexShader, vertexSource)
glCompileShader(vertexShader)
## Create and compile the fragment shader
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragmentShader, fragmentSource)
glCompileShader(fragmentShader)
show_version_info()
## Link the vertex and fragment shader into a shader program
shaderProgram = glCreateProgram()
glAttachShader(shaderProgram, vertexShader)
glAttachShader(shaderProgram, fragmentShader)
glBindFragDataLocation(shaderProgram, 0, "outColor")
glLinkProgram(shaderProgram)
glUseProgram(shaderProgram)
# Making the link between vertex data and attributes
posAttrib = glGetAttribLocation(shaderProgram, "position")
glEnableVertexAttribArray(posAttrib)
if not g_exclude_crash_trigger:
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, None)
def OnDraw(self):
# Set clear color
glClearColor(0.0, 0.2, 0.0, 1.0)
#Clear the screen to dark green
glClear(GL_COLOR_BUFFER_BIT)
# draw the triangle
if not g_exclude_crash_trigger:
glDrawArrays(GL_TRIANGLES, 0, 3)
self.SwapBuffers()
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Blue triangle on dark green background", size=(640,480))
canvas = OpenGLCanvas(self)
app = wx.App()
frame = Frame()
frame.Show()
app.MainLoop()
I have put in some details of the issue as comments in the code so that it can all be found in one place. This runs fine under windows but not under linux (that is a first!) and a triangle is seen on a dark green background. It fails on linux with the following error trace:
Traceback (most recent call last):
File "./glctxproblem.py", line 116, in OnPaint
self.InitGL()
File "./glctxproblem.py", line 162, in InitGL
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, None)
File "/home/ichneumwn/.local/lib/python3.8/site-packages/OpenGL/latebind.py", line 63, in __call__
return self.wrapperFunction( self.baseFunction, *args, **named )
File "/home/ichneumwn/.local/lib/python3.8/site-packages/OpenGL/GL/VERSION/GL_2_0.py", line 469, in glVertexAttribPointer
contextdata.setValue( key, array )
File "/home/ichneumwn/.local/lib/python3.8/site-packages/OpenGL/contextdata.py", line 58, in setValue
context = getContext( context )
File "/home/ichneumwn/.local/lib/python3.8/site-packages/OpenGL/contextdata.py", line 40, in getContext
raise error.Error(
OpenGL.error.Error: Attempt to retrieve context when no valid context
Any ideas what might be the issue? Is there a way to get the handle to the GLContext and somehow pass it on to pyopengl explicitly?
Cheers