I am trying to figure out why I'm not able to write into 3D textures using the (now built-in) shader_image_load_store extension.
I created two simple examples (in python to make it easier): one to write into a 2D texture, that works, and one to write into a 3D texture that does not work
the (working) 2D version is as following:
#! /usr/bin/env python
from PyQt4 import QtGui, QtCore
from PyQt4.QtOpenGL import *
from OpenGL.GL import *
from OpenGL.GLU import *
import sys
iTexSize = 256
_vsClearSource = """
#version 440 compatibility
void main() {
gl_Position = ftransform();
gl_FrontColor = gl_Color;
}
"""
_fsClearSource = """
#version 440 compatibility
uniform int iPrimitiveCount;
uniform int iSliceIndex;
layout(size4x32, binding=0) coherent uniform image2D volColorVolume;
const int iMaxTexSize = 255;
void main() {
ivec2 ivecVolumeCoordinate = ivec2(gl_FragCoord.x, gl_FragCoord.y ); //, iSliceIndex);
vec4 vecVolumeValue = vec4(0,1,0,1); // vec4( float(iSlabIndex)/float(iPrimitiveCount)); //,0.0,0.0,0.0);
imageStore(volColorVolume, ivecVolumeCoordinate, vecVolumeValue);
gl_FragData[0] = vec4(1,0,1,1);
}
"""
_fsFillSource = """
#version 440 compatibility
uniform int iPrimitiveCount;
uniform int iSliceIndex;
layout(size4x32, binding=0) coherent uniform image2D volColorVolume;
const int iMaxTexSize = 255;
void main() {
ivec2 ivecVolumeCoordinate = ivec2(gl_FragCoord.x, gl_FragCoord.y );
vec4 vecVolumeValue = vec4( float(gl_FragCoord.x) / float(iMaxTexSize) , float(gl_FragCoord.y) / float(iMaxTexSize) , 0 , 1 );
imageStore(volColorVolume, ivecVolumeCoordinate, vecVolumeValue);
gl_FragData[0] = vec4(1,0,1,1);
}
"""
class Viewer3DWidget(QGLWidget):
def __init__(self, parent):
QGLWidget.__init__(self, parent)
self.uWidth = 0
self.uHeight = 0
self.texColorTexture = None
self.fboRendering = None
self.texColorVolume = None
self.vecBackgroundColor = (1.,1.,1.)
self.vecDrawBuffers = [ GL_COLOR_ATTACHMENT0 , GL_COLOR_ATTACHMENT1 , GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 ]
self.fSurfacesSpacing = 1.0
self.fSurfacesTransparency = 1.0
def initializeGL(self):
self.shaShaderFill = QGLShaderProgram(self.context())
self.shaShaderFill.addShaderFromSourceCode(QGLShader.Vertex, _vsClearSource)
self.shaShaderFill.addShaderFromSourceCode(QGLShader.Fragment, _fsFillSource)
self.shaShaderFill.link()
self.shaShaderClear = QGLShaderProgram(self.context())
self.shaShaderClear.addShaderFromSourceCode(QGLShader.Vertex, _vsClearSource)
self.shaShaderClear.addShaderFromSourceCode(QGLShader.Fragment, _fsClearSource)
self.shaShaderClear.link()
glClearColor(1.0, 1.0, 1.0, 1.0)
glClearDepth(1.0)
def initRenderTargets(self):
global iTexSize
if (self.texColorTexture is None):
self.texColorTexture = glGenTextures( 1 )
if (self.fboRendering is None):
self.fboRendering = glGenFramebuffers(1)
glBindTexture( GL_TEXTURE_RECTANGLE, self.texColorTexture )
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA32F, iTexSize, iTexSize, 0, GL_RGBA, GL_FLOAT, None)
glBindTexture(GL_TEXTURE_RECTANGLE, 0);
glBindFramebuffer(GL_FRAMEBUFFER, self.fboRendering)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, self.texColorTexture, 0)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
def deleteRenderTargets(self):
if (self.fboAccumulation is not None):
glDeleteFramebuffers(1,self.fboRendering)
self.fboAccumulation = None
if (self.texColorTexture is not None):
glDeleteTextures( self.texColorTexture )
self.texColorTexture = None
def initColorVolume(self):
if (self.texColorVolume is None):
self.texColorVolume = glGenTextures( 1 )
glBindTexture( GL_TEXTURE_2D, self.texColorVolume )
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
#glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, iTexSize, iTexSize, 0, GL_RGBA, GL_FLOAT, None);
glBindTexture(GL_TEXTURE_2D, 0);
def fillVolume(self, bClear):
global iTexSize
shaShader = self.shaShaderClear
if(not bClear):
shaShader = self.shaShaderFill
if (not self.fboRendering):
self.initRenderTargets()
if (not self.texColorVolume):
self.initColorVolume()
glMatrixMode( GL_PROJECTION )
glLoadIdentity()
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glBindImageTexture(0,self.texColorVolume,0,GL_FALSE,0,GL_READ_WRITE,GL_RGBA32F);
glBindFramebuffer(GL_FRAMEBUFFER, self.fboRendering);
glDrawBuffers(1, self.vecDrawBuffers);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
shaShader.bind()
shaShader.setUniformValue("iPrimitiveCount", iTexSize)
shaShader.setUniformValue("volColorVolume", 0)
for i in range(iTexSize):
shaShader.setUniformValue("iSliceIndex", i)
glBegin(GL_QUADS);
glVertex2f(-1.0, -1.0);
glVertex2f(1.0, -1.0);
glVertex2f(1.0, 1.0);
glVertex2f(-1.0, 1.0);
glEnd();
#sync
glMemoryBarrier(GL_ALL_BARRIER_BITS);
glBindImageTexture(0,0,0,GL_FALSE,0,GL_READ_WRITE,GL_RGBA32F);
shaShader.release()
glBindFramebuffer(GL_FRAMEBUFFER, 0);
def paintGL(self):
if (self.uWidth is 0):
return
if (not self.fboRendering):
self.initRenderTargets()
self.initColorVolume()
glMatrixMode( GL_PROJECTION )
glLoadIdentity()
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.fillVolume(True)
#draw into the volume
self.fillVolume(False)
#slice the volume
self.displayTexture()
glFlush()
def displayTexture(self): #essentially not useable here
glMatrixMode( GL_PROJECTION )
glLoadIdentity()
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glDisable(GL_BLEND)
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glColor(1.0, 1.0,1.0)
glEnable(GL_TEXTURE_2D);
glBindTexture( GL_TEXTURE_2D, self.texColorVolume )
glBegin(GL_QUADS);
glTexCoord2f(0,0) #,0.5)
glVertex2f(-1.0, -1.0);
glTexCoord2f(1,0) #,0.5)
glVertex2f(1.0, -1.0);
glTexCoord2f(1,1) #,0.5)
glVertex2f(1.0, 1.0);
glTexCoord2f(0,1) #,0.5)
glVertex2f(-1.0, 1.0);
glEnd();
glBindTexture( GL_TEXTURE_2D, 0 )
def resizeGL(self, widthInPixels, heightInPixels):
if ((widthInPixels is not self.uWidth) or (heightInPixels is not self.uHeight)):
self.uWidth = widthInPixels
self.uHeight = heightInPixels
glViewport(0, 0, widthInPixels, heightInPixels)
self.update()
class TestImageLoadStore2D(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setWindowTitle('TestImageLoadStore2D')
self.statusBar().showMessage("Hello there")
exit = QtGui.QAction("Exit", self)
exit.setShortcut("Ctrl+Q")
exit.setStatusTip('Exit application')
self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
self.viewer3D = Viewer3DWidget(self)
self.setCentralWidget(self.viewer3D)
self.resize(500,500)
def closeEvent(self, event):
event.accept()
if __name__ == '__main__':
# app = QtGui.QApplication(['Python Qt OpenGL Demo'])
app = QtGui.QApplication(sys.argv)
window = TestImageLoadStore2D()
window.show()
sys.exit(app.exec_())
while the non-working 3D version is as following:
#! /usr/bin/env python
from PyQt4 import QtGui, QtCore
from PyQt4.QtOpenGL import *
from OpenGL.GL import *
from OpenGL.GLU import *
import sys
iTexSize = 256
_vsClearSource = """
#version 440 compatibility
void main() {
gl_Position = ftransform();
gl_FrontColor = gl_Color;
}
"""
_fsClearSource = """
#version 440 compatibility
uniform int iPrimitiveCount;
uniform int iSliceIndex;
layout(size4x32, binding=0) writeonly uniform image3D volColorVolume;
//layout(rgba32f, binding=0) writeonly uniform image3D volColorVolume;
const int iMaxTexSize = 255;
void main() {
ivec3 ivecVolumeCoordinate = ivec3(gl_FragCoord.x, gl_FragCoord.y, 0); //iSliceIndex);
vec4 vecVolumeValue = vec4(0,1,0,1); // vec4( float(iSlabIndex)/float(iPrimitiveCount)); //,0.0,0.0,0.0);
imageStore(volColorVolume, ivecVolumeCoordinate, vecVolumeValue);
memoryBarrier();
gl_FragData[0] = vec4(1,0,1,1);
}
"""
_fsFillSource = """
#version 440 compatibility
uniform int iPrimitiveCount;
uniform int iSliceIndex;
layout(size4x32, binding=0) writeonly uniform image3D volColorVolume;
const int iMaxTexSize = 255;
void main() {
ivec3 ivecVolumeCoordinate = ivec3(gl_FragCoord.x, gl_FragCoord.y, 0); //iSliceIndex);
vec4 vecVolumeValue = vec4( float(gl_FragCoord.x) / float(iMaxTexSize) , float(gl_FragCoord.y) / float(iMaxTexSize) , float(iSliceIndex)/float(iPrimitiveCount) , 1 );
imageStore(volColorVolume, ivecVolumeCoordinate, vecVolumeValue);
memoryBarrier();
gl_FragData[0] = vec4(1,0,1,1);
}
"""
class Viewer3DWidget(QGLWidget):
def __init__(self, parent):
QGLWidget.__init__(self, parent)
self.uWidth = 0
self.uHeight = 0
self.texColorTexture = None
self.fboRendering = None
self.texColorVolume = None
self.vecBackgroundColor = (1.,1.,1.)
self.vecDrawBuffers = [ GL_COLOR_ATTACHMENT0 , GL_COLOR_ATTACHMENT1 , GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 ]
self.fSurfacesSpacing = 1.0
self.fSurfacesTransparency = 1.0
self.fZCoord = 0.0
def setZCoordinate(self, fZCoordinate):
self.fZCoord = fZCoordinate
self.update()
def initializeGL(self):
self.shaShaderFill = QGLShaderProgram(self.context())
self.shaShaderFill.addShaderFromSourceCode(QGLShader.Vertex, _vsClearSource)
self.shaShaderFill.addShaderFromSourceCode(QGLShader.Fragment, _fsFillSource)
self.shaShaderFill.link()
self.shaShaderClear = QGLShaderProgram(self.context())
self.shaShaderClear.addShaderFromSourceCode(QGLShader.Vertex, _vsClearSource)
self.shaShaderClear.addShaderFromSourceCode(QGLShader.Fragment, _fsClearSource)
self.shaShaderClear.link()
glClearColor(1.0, 1.0, 1.0, 1.0)
glClearDepth(1.0)
def initRenderTargets(self):
global iTexSize
if (self.texColorTexture is None):
self.texColorTexture = glGenTextures( 1 )
if (self.fboRendering is None):
self.fboRendering = glGenFramebuffers(1)
glBindTexture( GL_TEXTURE_RECTANGLE, self.texColorTexture )
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA32F, iTexSize, iTexSize, 0, GL_RGBA, GL_FLOAT, None)
glBindTexture(GL_TEXTURE_RECTANGLE, 0);
glBindFramebuffer(GL_FRAMEBUFFER, self.fboRendering)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, self.texColorTexture, 0)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
def deleteRenderTargets(self):
if (self.fboAccumulation is not None):
glDeleteFramebuffers(1,self.fboRendering)
self.fboAccumulation = None
if (self.texColorTexture is not None):
glDeleteTextures( self.texColorTexture )
self.texColorTexture = None
def initColorVolume(self):
if (self.texColorVolume is None):
self.texColorVolume = glGenTextures( 1 )
glBindTexture( GL_TEXTURE_3D, self.texColorVolume )
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA32F, iTexSize, iTexSize, iTexSize, 0, GL_RGBA, GL_FLOAT, None);
glBindTexture(GL_TEXTURE_3D, 0);
def fillVolume(self, bClear):
global iTexSize
shaShader = self.shaShaderClear
if(not bClear):
shaShader = self.shaShaderFill
if (not self.fboRendering):
self.initRenderTargets()
if (not self.texColorVolume):
self.initColorVolume()
glMatrixMode( GL_PROJECTION )
glLoadIdentity()
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glBindImageTexture(0,self.texColorVolume,0,GL_FALSE,0,GL_WRITE_ONLY,GL_RGBA32F);
glBindFramebuffer(GL_FRAMEBUFFER, self.fboRendering);
glDrawBuffers(1, self.vecDrawBuffers);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
shaShader.bind()
shaShader.setUniformValue("iPrimitiveCount", iTexSize)
shaShader.setUniformValue("volColorVolume", 0)
for i in range(iTexSize):
shaShader.setUniformValue("iSliceIndex", i)
glBegin(GL_QUADS);
glVertex2f(-1.0, -1.0);
glVertex2f(1.0, -1.0);
glVertex2f(1.0, 1.0);
glVertex2f(-1.0, 1.0);
glEnd();
#sync
glMemoryBarrier(GL_ALL_BARRIER_BITS);
glMemoryBarrier(GL_ALL_BARRIER_BITS);
glBindImageTexture(0,0,0,GL_FALSE,0,GL_WRITE_ONLY,GL_RGBA32F);
shaShader.release()
glBindFramebuffer(GL_FRAMEBUFFER, 0);
def paintGL(self):
if (self.uWidth is 0):
return
if (not self.fboRendering):
self.initRenderTargets()
self.initColorVolume()
glMatrixMode( GL_PROJECTION )
glLoadIdentity()
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.fillVolume(True)
#draw into the volume
#self.fillVolume(False)
#slice the volume
self.displayTexture()
glFlush()
def displayTexture(self): #essentially not useable here
glMatrixMode( GL_PROJECTION )
glLoadIdentity()
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glDisable(GL_BLEND)
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glColor(1.0, 1.0,1.0)
glEnable(GL_TEXTURE_3D);
glBindTexture( GL_TEXTURE_3D, self.texColorVolume )
fZCoord = self.fZCoord
glBegin(GL_QUADS);
glTexCoord3f(0,0,fZCoord)
glVertex2f(-1.0, -1.0);
glTexCoord3f(1,0,fZCoord)
glVertex2f(1.0, -1.0);
glTexCoord3f(1,1,fZCoord)
glVertex2f(1.0, 1.0);
glTexCoord3f(0,1,fZCoord)
glVertex2f(-1.0, 1.0);
glEnd();
glBindTexture( GL_TEXTURE_3D, 0 )
def resizeGL(self, widthInPixels, heightInPixels):
if ((widthInPixels is not self.uWidth) or (heightInPixels is not self.uHeight)):
self.uWidth = widthInPixels
self.uHeight = heightInPixels
glViewport(0, 0, widthInPixels, heightInPixels)
self.update()
class TestImageLoadStore3D(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setWindowTitle('TestImageLoadStore3D')
self.statusBar().showMessage("Hello there")
exit = QtGui.QAction("Exit", self)
exit.setShortcut("Ctrl+Q")
exit.setStatusTip('Exit application')
self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
self.setToolTip('This is a window, or <b>something</b>')
self.viewer3D = Viewer3DWidget(self)
parentWidget = QtGui.QWidget()
slider1 = QtGui.QSlider(QtCore.Qt.Horizontal, None)
slider1.setRange(0,10000)
slider1.setValue(5000)
slider1.setMaximumWidth(120)
slider1.valueChanged.connect(self.slider1Handler)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(slider1)
vbox.addStretch(1)
self.viewer3D.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding )
hbox = QtGui.QHBoxLayout()
hbox.addLayout(vbox)
hbox.addWidget(self.viewer3D)
parentWidget.setLayout(hbox)
self.setCentralWidget(parentWidget)
self.resize(500,500)
def closeEvent(self, event):
event.accept()
def slider1Handler(self, iVal):
fVal = iVal / 10000.0
#print "zcoord: ",fVal
self.viewer3D.setZCoordinate(fVal)
if __name__ == '__main__':
# app = QtGui.QApplication(['Python Qt OpenGL Demo'])
app = QtGui.QApplication(sys.argv)
window = TestImageLoadStore3D()
window.show()
sys.exit(app.exec_())
the code is not extra neat, also because i copy/pasted/modified some older pyopengl code i had. The problem is that the values written in the 3D texture make absolute no sense. I ran it using the latest version of PyOpenGL (the experimental one), a Quadro K5000 and the latest drivers (332.76), that also provide the support for OpenGL 4.4 .
I'm not sure what i might be doing wrong, also because i haven't found many examples of writing into 3D textures (actually none, and i also looked in the latest version of the red book)
Can someone enlighten me?
Your problem is here in your frag shader:
layout(size4x32, binding=0) writeonly uniform image3D volColorVolume;
You are binding the 3D texture via
glBindImageTexture(0,self.texColorVolume,0,GL_FALSE,0,GL_WRITE_ONLY,GL_RGBA32F);
Let me quote from the OpenGL 4.4 spec, section 8.26 "Texture Image Loads and Stores" (emphasis mine):
If the texture identified by texture is a one-dimensional array, two-dimensional array, three-dimensional, cube map, cube map array, or two-dimensional multisample array texture, it is possible to bind either the entire texture level or a single layer or face of the texture level. If layered is TRUE, the entire level is bound. If layered is FALSE, only the single layer identified by layer will be bound. When layered is FALSE, the single bound layer is treated as a different texture target for image accesses:
- one-dimensional array texture layers are treated as one-dimensional textures;
- two-dimensional array, three-dimensional, cube map, cube map array texture layers are treated as two-dimensional textures; and
- two-dimensional multisample array textures are treated as two-dimensional multisample textures.
So, if you just bind one layer of a 3D texture with the layered
parameter set to GL_FALSE
(as you are currently doing), it will act as if it is just a 2D texture, so either just use an image2D
and access it with 2D coords, or bind it with layered
set to GL_TRUE
and a valid range of layers you will be able to write into a specific layer/slices of your texture using an image3D
and 3-dimensional image coordinates.