FlorianRhiem / pyGLFW

Python bindings for GLFW
MIT License
233 stars 36 forks source link

Attempt to retrieve context when no valid context #62

Closed NaNraptor closed 2 years ago

NaNraptor commented 2 years ago

Im following a very simple code example to draw something on screen using glfw and opengl but I get met with

Traceback (most recent call last):
  File "/home/nanyo/Documents/ProgrammingEnvs/PythonEnvs/pyVisuals/src/__unused/test.py", line 75, in <module>
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, ctypes.c_void_p(0))
  File "/home/nanyo/Documents/ProgrammingEnvs/PythonEnvs/pyVisuals/lib/python3.10/site-packages/OpenGL/latebind.py", line 63, in __call__
    return self.wrapperFunction( self.baseFunction, *args, **named )
  File "/home/nanyo/Documents/ProgrammingEnvs/PythonEnvs/pyVisuals/lib/python3.10/site-packages/OpenGL/GL/VERSION/GL_2_0.py", line 469, in glVertexAttribPointer
    contextdata.setValue( key, array )
  File "/home/nanyo/Documents/ProgrammingEnvs/PythonEnvs/pyVisuals/lib/python3.10/site-packages/OpenGL/contextdata.py", line 58, in setValue
    context = getContext( context )
  File "/home/nanyo/Documents/ProgrammingEnvs/PythonEnvs/pyVisuals/lib/python3.10/site-packages/OpenGL/contextdata.py", line 40, in getContext
    raise error.Error(
OpenGL.error.Error: Attempt to retrieve context when no valid context

I have reduced my code as much as possible so I can post it here, I am doing glfw.make_context_current(window) to make the context current so I am not sure what is going wrong:

import glfw
from OpenGL.GL import *
import numpy as np

glfw.init()

glfw.window_hint(glfw.FOCUSED, glfw.FALSE)

window = glfw.create_window(640, 400, "Window", None, None)
glfw.make_context_current(window)

vertices = [
    0.5,  0.5, 0.0,
    0.5, -0.5, 0.0,
    -0.5, -0.5, 0.0,
    -0.5,  0.5, 0.0
]

indices = [
    0, 1, 3,
    1, 2, 3
]

vertices = np.array(vertices, dtype=np.float32)
indices = np.array(indices, dtype=np.int32)

vertex_shader_source = """
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
"""

frag_shader_source = """
#version 330 core
out vec4 FragColor;
void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
"""

vertex_shader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertex_shader, [vertex_shader_source])
glCompileShader(vertex_shader)
print(glGetShaderiv(vertex_shader, GL_COMPILE_STATUS))

frag_shader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(frag_shader, [frag_shader_source])
glCompileShader(frag_shader)
print(glGetShaderiv(frag_shader, GL_COMPILE_STATUS))

shader_program = glCreateProgram()
glAttachShader(shader_program, vertex_shader)
glAttachShader(shader_program, frag_shader)
glLinkProgram(shader_program)
print(glGetProgramiv(shader_program, GL_LINK_STATUS))

glDeleteShader(vertex_shader)
glDeleteShader(frag_shader)

VAO = glGenVertexArrays(1)
VBO = glGenBuffers(1)
EBO = glGenBuffers(1)

glBindVertexArray(VAO)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW)

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, ctypes.c_void_p(0))
glEnableVertexAttribArray(0)

glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)

glClearColor(1, 0, 0, 1)

while not glfw.window_should_close(window):
    glClear(GL_COLOR_BUFFER_BIT)
    glUseProgram(shader_program)
    glBindVertexArray(VAO)
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
    glfw.swap_buffers(window)
    glfw.poll_events()

glfw.terminate()

Putting:

print(glfw.get_current_context())

just before:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, ctypes.c_void_p(0))

prints <glfw.LP__GLFWwindow object at 0x7f822c987340> meaning that some context was created for that window, as far as I understand the glfw docs.

EDIT: might be worth mentioning that I compiled glfw from source and replaced the wheel package with this newly compiled version as glfw 3.3.x does not work with ubuntu 21 and its wayland version

EDIT2: I am currently investigating if this has potentially something to do with my nvidia drivers and my gpu not showing up on some places as it should. Although I have an igpu capable of OpenGL 4.6 support

NaNraptor commented 2 years ago

After installing nvidia-utils-510 and nvidia-driver-510 on my ubuntu and rebooting the code above works without issue however:

print(glGetString(GL_VENDOR), glGetString(GL_RENDERER))

Shows b'Intel' b'Mesa Intel(R) Graphics (ADL-S GT1)' which is weird since the application still uses the intel igpu but now works without problem... I will investigate and post my finding here, then close the issue as resolved since this seems to be an issue with drivers on linux

EDIT: I actually just realised that the code does NOT work as expected as I only see a blank red screen instead of the stuff i put in the VBO and EBO

NaNraptor commented 2 years ago

As an additional question - how does one force the glfw window to use a certain GPU on linux? On windows one can export NvOptimusEnablement but what about linux?

EDIT: Okay so if using proprietary NVIDIA drivers with a GPU that supports ON-DEMAND rendering one needs : for Vulkan offloading you have to use the following env variable: __NV_PRIME_RENDER_OFFLOAD=1

For OpenGL apps, you have to use these variables: __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia

taken from: https://askubuntu.com/questions/1201072/how-nvidia-on-demand-option-works-in-nvidia-x-server-settings

So this now correctly uses my nvidia gpu and doesnt crash due to a context error, but after a few seconds of flickering the application closes with a core-dumped. Any idea what is going wrong?

With the intel igpu selected it does not crash with core dumped but instead just shows the blank red screen instead of the things that I have instructed it to draw.

Could that be a pyOpenGL issue? or a driver one? Im currently not sure

NaNraptor commented 2 years ago

This might actually be problem with pyGLFW or glfw since I only get Aborted on a user action such as mouse move or keyboard press

FlorianRhiem commented 2 years ago

I doubt that it is a pyGLFW issue, as this package is simply a wrapper around the GLFW library.

Can you enable pyOpenGL error reporting or put print(glGetError()) at the end of the loop, to see if an error gets reported? Your code looks alright, though perhaps you might need to call glBindFragDataLocation to ensure the fragment output is actually used, I don't know how relaxed or strict nvidia drivers on Linux are for that. Though that would only explain the flickering, not the crash.

Also, do you have a traceback of the crash?

NaNraptor commented 2 years ago

No traceback just a message saying Aborted (core dumped)

Putting print(glGetError()) at the end of the loop prints a lot of 0 again, until I either move the mouse or press a button, after I do that I again get met with an immediate Aborted (core dumped) no other error messages whatsoever

NaNraptor commented 2 years ago

If I run it without specifying __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia then I only see a red window but no crash

For completeness sake here are my specs: Specs: intel i9-12900k (with igpu) running mesa igpu drivers Nvidia RTX 3080-ti with Nvidia 510 proprietary drivers Linux 21.10 with the 16.14 kernel

FlorianRhiem commented 2 years ago

If I run it without specifying __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia then I only see a red window but no crash

Try binding a fragment output location with glBindFragDataLocation to see if that's why you don't see the orange square.

NaNraptor commented 2 years ago

Since Im new to this just want to confirm do I call it like so:

...
glBindFragDataLocation(shader_program, 0, "FragColor")
while not glfw.window_should_close(window):
...

or do I pass in frag_shader instead?

FlorianRhiem commented 2 years ago

That looks correct.

NaNraptor commented 2 years ago

If the way I have it at the moment is correct then I see no change at all

That looks correct.

No change at all still just a red screen

Also I just want to thank you for all the help you are providing which might not even directly relate to pyglfw. You rock! :)

NaNraptor commented 2 years ago

Just simply running this:

import glfw
from OpenGL.GL import *

glfw.init()

glfw.window_hint(glfw.FOCUSED, glfw.FALSE)
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_COMPAT_PROFILE)

window = glfw.create_window(640, 400, "Window", None, None)
glfw.make_context_current(window)

glClearColor(1, 0, 0, 1)
print(glGetString(GL_VENDOR), glGetString(GL_RENDERER))

while not glfw.window_should_close(window):
    glClear(GL_COLOR_BUFFER_BIT)
    glfw.swap_buffers(window)
    glfw.poll_events()

glfw.terminate()

works absolutely fine when ran using my nvidia gpu. So I assume the opengl code I have must be breaking something, although I have no idea what.

EDIT: by work I mean, it shows a red screen instead of black and does not abort with a core dump

FlorianRhiem commented 2 years ago

Please replace glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0) with glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None), explanation will follow if that works :)

NaNraptor commented 2 years ago

It works

NaNraptor commented 2 years ago

I cannot BELIEVE I spent nearly 8h on this

NaNraptor commented 2 years ago

Yet again @FlorianRhiem is a god among men and I have no idea what we did to deserve him. Thank you!

FlorianRhiem commented 2 years ago

Welcome to the wonderful world of debugging stupid little OpenGL typos that take ages to find. :)

So, glDrawElements expects as last parameter "a pointer to the location where the indices are stored". In practice, instead of an passing a pointer to an array of indices, you probably want to use a buffer for that, just like you did, but pyOpenGL has to support both use cases. So ctypes.c_void_p(0) is clearly a pointer to an int, and can be passed to OpenGL as such. None can be seen as indicating a null pointer, basically, but 0 needs conversion in some way. And I think in this case the code in pyOpenGL created an array containing the 0, then passed the pointer to that to OpenGL and OpenGL tried to read 6 ints from that.

NaNraptor commented 2 years ago

Right I see, that makes it clear, although some kind of a more useful error message would have been nice :D

Anyway thank you again for the help, do you have some advice on resources where I can obtain your knowledge or is the only way just suffering through experience lol?

FlorianRhiem commented 2 years ago

There are plenty of tutorials for OpenGL in general, but for pitfalls such as this, I think suffering is the way to go. 😂 Though it's not as bad as my statement might've made it seem, it's just that issues like this one that can be pretty disheartening/annoying, when you don't even manage to draw something.

Also: note that you actually got it right first try with glVertexAttribPointer, you're passing a null pointer there.

NaNraptor commented 2 years ago

Yeah I am not sure why I opted in for just typing 0 in there, i guess it was me converting C to python from the tutorial on here learnopengl.com and not realising what im doing.

Do you think there is any point in learning OpenGL btw or would you say learning Vulkan is the way forward?

FlorianRhiem commented 2 years ago

I can't tell you what the future will hold for OpenGL and I don't know your use cases, but take a look at a Vulkan tutorial for drawing a single triangle. Vulkan is a wee bit more verbose and complex, the learning curve isn't any less steep than that of OpenGL. I think even if you don't end up using OpenGL all that much, it can be a useful tool to learn and understand.