Текстуры Qt/openGL работают неправильно

63
4

Я встречаю странное поведение с python3/qt5/openGL при использовании текстур openGL.

Открытие двух QOpenGLWidgets в двух отдельных окнах приводит к смешению текстуры openGL между двумя виджетами - изменение текстуры в первом виджетах приводит к изменению текстуры во втором виджете. Хотя они должны иметь разные контексты openGL..!

Предоставляется код, демонстрирующий эту проблему (см. Ниже) - сначала попробуйте сначала, прежде чем предлагать решение. Код компактно демонстрирует два случая:

1) Создание двух QOpenGLWidgets, которые используют один и тот же родительский виджет

    Работает нормально - нет "смешивания текстур"

2) Создание двух QOpenGLWidgets без родителей

    Виджеты открываются как отдельные окна Система Qt/OpenGL испортила текстуры OpenGL: изменение текстуры в первом окне неожиданно меняет текстуру второго окна! (Не забудьте изменить размер/щелкнуть по второму окну, чтобы увидеть это)

Чтобы исправить регистр (2), я также попытался заставить двух виджетов использовать один и тот же OpenGLContext.. но без успеха.

http://doc.qt.io/qt-5/qopenglwidget.html

Говорит следующее:

Также возможно создание дополнительных экземпляров QOpenGLContext, которые совместно используют ресурсы, такие как текстуры с контекстом QOpenGLWidget. "Просто" передайте указатель, возвращенный из контекста(), в QOpenGLContext :: setShareContext() перед вызовом QOpenGLContext :: create()

Да, я могу создать экземпляр QOpenGLContext для совместного использования, но нет способа заставить QOpenGLWidget использовать этот настраиваемый контекст: QOpenGLWidget автоматически создает QOpenGLContext где-нибудь (где?).. Я могу получить к нему доступ только в initializeGL (с помощью self.context() "), но в этот момент метод контекста".create() "уже был вызван..! .. и, таким образом, невозможно каким-либо образом наполнить мой пользовательский QOpenGLContext.

Я также пробовал следующее: мы создаем переменную класса

glcontext=None

В виджетах (см. Сначала полный пример ниже) Метод initializeGL Я пытаюсь сделать это:

if (VideoImageWidget.glcontext==None):
print("VideoImageWidget: initializeGL: creating context for sharing")
VideoImageWidget.glcontext=QtGui.QOpenGLContext()
ok=VideoImageWidget.glcontext.create()
print("VideoImageWidget: initializeGL: created context for sharing",VideoImageWidget.glcontext,ok)

context=self.context()
print("VideoImageWidget: initializeGL: automatically created context:",context)
context.setShareContext(VideoImageWidget.glcontext)
ok=context.create() # must call this ..
print("VideoImageWidget: initializeGL: recreated my context:",ok)

Но это не работает. В VideoImageWidget больше нет изображения.

Это беспорядок! Помогите оценить!

Связанный:

Можно ли использовать один и тот же контекст OpenGL между окнами верхнего уровня в Qt?

Как использовать контекст или данные OpenGL?

Демо-программа:

import sys
import time
from PyQt5 import QtWidgets, QtCore, QtGui # Qt5
from OpenGL.GL import *
from PIL import Image

"""
Demonstrating a bug (?) in QOpenGLWidget / Qt OpenGL insfrastructure :

You need:
* to have two tiff images ("base.tif" and "2.tif") in the same directory
* to remove/install some libraries:
sudo apt-get install python3-pyqt5 pip3
sudo apt-get remove python3-opengl # we want the most recent version of the opengl bindings
sudo pip3 install PyOpenGL PyOpenGL_accelerate
sudo pip3 install imutils

Usage:
* look for the tag "TOGGLE HERE" below to switch between nested QWidgets / individual windows
* run program with
python3 context_test.py

What going on here?
* Press the button a few times : a new image appears to the first widget
* The image should appear only to the first widget
* .. but it appears in both widgets if we set "nested=False", i.e. when the widgets constitute individual windows
* .. confirm this by clicking / resizing the second window after clicking the button a few times

Why this happens?
* Qt creates some sort of "top-level" (?) opengl context that is referring to the same texture ids = bug ?

This code is licensed under the do-with-it-whatever-you-want license, written by Sampsa Riikonen, 2017
"""

def getImg(fname):
im =QtGui.QImage(fname)
im =im.convertToFormat(QtGui.QImage.Format_RGB888)
ix =im.width()
iy =im.height()
ptr=im.bits()
ptr.setsize(im.byteCount())
return ptr.asstring(), ix, iy

class VideoImageWidget(QtWidgets.QOpenGLWidget): # http://doc.qt.io/qt-5/qopenglwidget.html # Qt5
def __init__(self,parent=None):
super().__init__(parent=parent)
self.parent=parent

self.baseimage, self.ix, self.iy =getImg("base.tif")

self.gl_format=GL_RGB
self.ratio =1
self.picratio =1

def changeTexture(self,image,ix,iy):
glBindTexture(GL_TEXTURE_2D, self.tex) # this is the texture we will manipulate
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, self.gl_format, ix, iy, 0, self.gl_format, GL_UNSIGNED_BYTE, image) # load bitmap to texture
self.picratio=self.iy/self.ix

def resetTexture(self):
self.changeTexture(self.baseimage,self.ix,self.iy)

def initializeGL(self):
# "This function should set up any required OpenGL resources and state"
glEnable(GL_TEXTURE_2D)
self.tex = glGenTextures(1) # create a new texture
# https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenTextures.xml
# "it is guaranteed that none of the returned names was in use immediately before the call"
print("VideoImageWidget: glGenTextures returned:",self.tex)
self.resetTexture()

def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Clear The Screen And The Depth Buffer
glLoadIdentity() # Reset The View
glBindTexture(GL_TEXTURE_2D, self.tex) # this is the texture we will manipulate
glBegin(GL_QUADS)
dz=0
r=self.ratio/self.picratio # screen h/w // picture h/w
if (r<1): # screen wider than image
dy=1
dx=r
elif (r>1): # screen taller than image
dx=1
dy=1/r
else:
dx=1
dy=1
glTexCoord2f(0.0, 0.0); glVertex3f(-dx, dy, dz)
glTexCoord2f(1.0, 0.0); glVertex3f( dx, dy, dz)
glTexCoord2f(1.0, 1.0); glVertex3f( dx,-dy, dz)
glTexCoord2f(0.0, 1.0); glVertex3f(-dx,-dy, dz)
glEnd()

def resizeGL(self, width, height):
"""Called upon window resizing: reinitialize the viewport.
"""
glViewport(0, 0, width, height)
glLoadIdentity()
glOrtho(-1, 1, 1, -1, -1, 1)
self.ratio=height/width

@QtCore.pyqtSlot(object)
def frameReceived(self,frame):
buf =frame[0]
width =frame[1]
height=frame[2]
print("VideoImageWidget updating with frame",width,height)
self.changeTexture(buf,width,height)
self.update()

class MyGui(QtWidgets.QMainWindow):

f1 = QtCore.pyqtSignal(object)
f2 = QtCore.pyqtSignal(object)

def __init__(self,parent=None):
super().__init__(parent)
self.cw=QtWidgets.QWidget(self)
self.setCentralWidget(self.cw)

self.lay = QtWidgets.QVBoxLayout(self.cw)

self.b = QtWidgets.QPushButton("Send frame",self.cw)

# *** TOGGLE HERE ***
# nested=True # *** widgets sitting in the QMainWindow
nested=False # *** individual windows

self.lay.addWidget(self.b)
if (nested):
self.v1 = VideoImageWidget(parent=self.cw)
self.v2 = VideoImageWidget(parent=self.cw)
self.lay.addWidget(self.v1)
self.lay.addWidget(self.v2)
else:
self.v1 = VideoImageWidget(parent=None)
self.v2 = VideoImageWidget(parent=None)
self.v1.show()
self.v2.show()

self.b.clicked. connect(self.clicked)
self.f1. connect(self.v1.frameReceived)

self.newimage, self.ix, self.iy =getImg("2.tif")

@QtCore.pyqtSlot()
def clicked(self):
print("emitting frame")
self.f1.emit([self.newimage, self.ix, self.iy]) # update _only_ the first VideoImageWidget

if (__name__=="__main__"):
app=QtWidgets.QApplication([])

# *** Set this to apply context sharing ***
# app.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
"""
.. but that does not work for textures

See the last comment on this post:
/questions/4965938/is-it-possible-to-use-the-same-opengl-context-between-top-level-windows-in-qt
"I've realised that this does not solve the problem in the question, as it does not actually share the context, but enables sharing of a subset of resources .."

That is said also in the qt docs, but in an extremely implicit way..
http://doc.qt.io/qt-5/qopenglwidget.html
"Creating extra QOpenGLContext instances that share resources like textures .. "
"""

print("OpenGL context sharing status:",app.testAttribute(QtCore.Qt.AA_ShareOpenGLContexts))
mg=MyGui()
mg.show()
app.exec_()

спросил(а) 2021-01-25T20:27:12+03:00 5 месяцев назад
1
Решение
76

Я нашел способ исправить пример, но я понятия не имею, является ли это "правильным" решением, потому что я почти ничего не знаю о OpenGL (возможно, это помогло).

Во всяком случае, все, что я сделал, было следующим:

@QtCore.pyqtSlot(object)
def frameReceived(self, frame):
self.makeCurrent()
...

Согласно документам для makeCurrent:

Нет необходимости вызывать эту функцию в большинстве случаев, потому что она вызывается автоматически перед вызовом paintGL().

Таким образом, вопрос выглядит следующим образом: какие "другие случаи" упоминаются здесь, и что делает их разными...

ответил(а) 2021-01-25T20:27:12+03:00 5 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема