I've been attempting to write a two-pass GPU implementation of the Marching Cubes algorithm, similar to the one detailed in the first chapter of GPU Gems 3, using OpenGL and GLSL. However, the call to glDrawArrays in my first pass consistently fails with a GL_INVALID_OPERATION.
I've looked up all the documentation I can find, and found these conditions under which glDrawArrays can throw that error:
GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to an enabled array or to the GL_DRAW_INDIRECT_BUFFER binding and the buffer object's data store is currently mapped.
GL_INVALID_OPERATION is generated if glDrawArrays is executed between the execution of glBegin and the corresponding glEnd.
GL_INVALID_OPERATION will be generated by glDrawArrays or glDrawElements if any two active samplers in the current program object are of different types, but refer to the same texture image unit.
GL_INVALID_OPERATION is generated if a geometry shader is active and mode is incompatible with the input primitive type of the geometry shader in the currently installed program object.
GL_INVALID_OPERATION is generated if mode is GL_PATCHES and no tessellation control shader is active.
GL_INVALID_OPERATION is generated if recording the vertices of a primitive to the buffer objects being used for transform feedback purposes would result in either exceeding the limits of any buffer object’s size, or in exceeding the end position offset + size - 1, as set by glBindBufferRange.
GL_INVALID_OPERATION is generated by glDrawArrays() if no geometry shader is present, transform feedback is active and mode is not one of the allowed modes.
GL_INVALID_OPERATION is generated by glDrawArrays() if a geometry shader is present, transform feedback is active and the output primitive type of the geometry shader does not match the transform feedback primitiveMode.
GL_INVALID_OPERATION is generated if the bound shader program is invalid.
EDIT 10/10/12: GL_INVALID_OPERATION is generated if transform feedback is in use, and the buffer bound to the transform feedback binding point is also bound to the array buffer binding point. This is the problem I was having, due to a typo in which buffer I bound. While the spec does state that this is illegal, it isn't listed under glDrawArrays as one of the reasons it can throw an error, in any documentation I found.
Unfortunately, no one piece of official documentation I can find covers more than 3 of these. I had to collect this list from numerous sources. Points 7 and 8 actually come from the documentation for glBeginTransformFeedback, and point 9 doesn't seem to be documented at all. I found it mentioned in a forum post somewhere. However, I still don't think this list is complete, as none of these seem to explain the error I'm getting.
I'm not mapping any buffers at all in my program, anywhere.
I'm using the Core profile, so glBegin and glEnd aren't even available.
I have two samplers, and they are of different types, but they're definitely mapped to different textures.
A geometry shader is active, but it's input layout is layout (points) in, and glDrawArrays is being called with GL_POINTS.
I'm not using GL_PATCHES or tessellation shaders of any sort.
I've made sure I'm allocating the maximum amount of space my geometry shaders could possible output. Then I tried quadrupling it. Didn't help.
There is a geometry shader. See the next point.
Transform feedback is being used, and there is a geometry shader, but the output layout is layout (points) out and glBeginTransformFeedback is called with GL_POINTS.
I tried inserting a call to glValidateProgram right before the call to glDrawArrays, and it returned GL_TRUE.
The actual OpenGL code is here:
const int SECTOR_SIZE = 32;
const int SECTOR_SIZE_CUBED = SECTOR_SIZE * SECTOR_SIZE * SECTOR_SIZE;
const int CACHE_SIZE = SECTOR_SIZE + 3;
const int CACHE_SIZE_CUBED = CACHE_SIZE * CACHE_SIZE * CACHE_SIZE;
MarchingCubesDoublePass::MarchingCubesDoublePass(ServiceProvider* svc, DensityMap* sourceData) {
this->sourceData = sourceData;
densityCache = new float[CACHE_SIZE_CUBED];
}
MarchingCubesDoublePass::~MarchingCubesDoublePass() {
delete densityCache;
}
void MarchingCubesDoublePass::InitShaders() {
ShaderInfo vertShader, geoShader, fragShader;
vertShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass1.vert", GL_VERTEX_SHADER);
svc->shader->Compile(vertShader);
geoShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass1.geo", GL_GEOMETRY_SHADER);
svc->shader->Compile(geoShader);
shaderPass1 = glCreateProgram();
static const char* outputVaryings[] = { "triangle" };
glTransformFeedbackVaryings(shaderPass1, 1, outputVaryings, GL_SEPARATE_ATTRIBS);
assert(svc->shader->Link(shaderPass1, vertShader, geoShader));
uniPass1DensityMap = glGetUniformLocation(shaderPass1, "densityMap");
uniPass1TriTable = glGetUniformLocation(shaderPass1, "triangleTable");
uniPass1Size = glGetUniformLocation(shaderPass1, "size");
attribPass1VertPosition = glGetAttribLocation(shaderPass1, "vertPosition");
vertShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass2.vert", GL_VERTEX_SHADER);
svc->shader->Compile(vertShader);
geoShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass2.geo", GL_GEOMETRY_SHADER);
svc->shader->Compile(geoShader);
fragShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass2.frag", GL_FRAGMENT_SHADER);
svc->shader->Compile(fragShader);
shaderPass2 = glCreateProgram();
assert(svc->shader->Link(shaderPass2, vertShader, geoShader, fragShader));
uniPass2DensityMap = glGetUniformLocation(shaderPass2, "densityMap");
uniPass2Size = glGetUniformLocation(shaderPass2, "size");
uniPass2Offset = glGetUniformLocation(shaderPass2, "offset");
uniPass2Matrix = glGetUniformLocation(shaderPass2, "matrix");
attribPass2Triangle = glGetAttribLocation(shaderPass2, "triangle");
}
void MarchingCubesDoublePass::InitTextures() {
for (int x = 0; x < CACHE_SIZE; x++) {
for (int y = 0; y < CACHE_SIZE; y++) {
for (int z = 0; z < CACHE_SIZE; z++) {
densityCache[x + y*CACHE_SIZE + z*CACHE_SIZE*CACHE_SIZE] = sourceData->GetDensity(Vector3(x-1, y-1, z-1));
}
}
}
glGenTextures(1, &densityTex);
glBindTexture(GL_TEXTURE_3D, densityTex);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexImage3D(GL_TEXTURE_3D, 0, GL_R32F, CACHE_SIZE, CACHE_SIZE, CACHE_SIZE, 0, GL_RED, GL_FLOAT, densityCache);
glGenTextures(1, &triTableTex);
glBindTexture(GL_TEXTURE_RECTANGLE, triTableTex);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R16I, 16, 256, 0, GL_RED_INTEGER, GL_INT, triTable);
}
void MarchingCubesDoublePass::InitBuffers() {
float* voxelGrid = new float[SECTOR_SIZE_CUBED*3];
unsigned int index = 0;
for (int x = 0; x < SECTOR_SIZE; x++) {
for (int y = 0; y < SECTOR_SIZE; y++) {
for (int z = 0; z < SECTOR_SIZE; z++) {
voxelGrid[index*3 + 0] = x;
voxelGrid[index*3 + 1] = y;
voxelGrid[index*3 + 2] = z;
index++;
}
}
}
glGenBuffers(1, &bufferPass1);
glBindBuffer(GL_ARRAY_BUFFER, bufferPass1);
glBufferData(GL_ARRAY_BUFFER, SECTOR_SIZE_CUBED*3*sizeof(float), voxelGrid, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenBuffers(1, &bufferPass2);
glBindBuffer(GL_ARRAY_BUFFER, bufferPass2);
glBufferData(GL_ARRAY_BUFFER, SECTOR_SIZE_CUBED*5*sizeof(int), NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenVertexArrays(1, &vaoPass1);
glBindVertexArray(vaoPass1);
glBindBuffer(GL_ARRAY_BUFFER, bufferPass1);
glVertexAttribPointer(attribPass1VertPosition, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(attribPass1VertPosition);
glBindVertexArray(0);
glGenVertexArrays(1, &vaoPass2);
glBindVertexArray(vaoPass2);
glBindBuffer(GL_ARRAY_BUFFER, bufferPass2);
glVertexAttribIPointer(attribPass2Triangle, 1, GL_INT, 0, (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(attribPass2Triangle);
glBindVertexArray(0);
glGenQueries(1, &queryNumTriangles);
}
void MarchingCubesDoublePass::Register(Genesis::ServiceProvider* svc, Genesis::Entity* ent) {
this->svc = svc;
this->ent = ent;
svc->scene->RegisterEntity(ent);
InitShaders();
InitTextures();
InitBuffers();
}
void MarchingCubesDoublePass::Unregister() {
if (!ent->GetBehavior<Genesis::Render>()) {
svc->scene->UnregisterEntity(ent);
}
}
void MarchingCubesDoublePass::RenderPass1() {
glEnable(GL_RASTERIZER_DISCARD);
glUseProgram(shaderPass1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_3D, densityTex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, triTableTex);
glUniform1i(uniPass1DensityMap, 0);
glUniform1i(uniPass1TriTable, 1);
glUniform1i(uniPass1Size, SECTOR_SIZE);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufferPass2);
glBindVertexArray(vaoPass2);
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, queryNumTriangles);
glBeginTransformFeedback(GL_POINTS);
GLenum error = glGetError();
glDrawArrays(GL_POINTS, 0, SECTOR_SIZE_CUBED);
error = glGetError();
glEndTransformFeedback();
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
glBindVertexArray(0);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
glUseProgram(0);
glDisable(GL_RASTERIZER_DISCARD);
glGetQueryObjectuiv(queryNumTriangles, GL_QUERY_RESULT, &numTriangles);
}
void MarchingCubesDoublePass::RenderPass2(Matrix mat) {
glUseProgram(shaderPass2);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_3D, densityTex);
glUniform1i(uniPass2DensityMap, 0);
glUniform1i(uniPass2Size, SECTOR_SIZE);
glUniform3f(uniPass2Offset, 0, 0, 0);
mat.UniformMatrix(uniPass2Matrix);
glBindVertexArray(vaoPass2);
glDrawArrays(GL_POINTS, 0, numTriangles);
glBindVertexArray(0);
glUseProgram(0);
}
void MarchingCubesDoublePass::OnRender(Matrix mat) {
RenderPass1();
RenderPass2(mat);
}
The actual error is the call to glDrawArrays in RenderPass1. Worth noting that if I comment out the calls to glBeginTransformFeedback and glEndTransformFeedback, then glDrawArrays stops generating the error. So whatever's wrong, it's probably somehow related to transform feedback.
Edit 8/18/12, 9 PM:
I just found the NVIDIA GLExpert feature in gDEBugger, which I wasn't previously familiar with. When I turned this on, it gave somewhat more substantial information on the GL_INVALID_OPERATION, specifically The current operation is illegal in the current state: Buffer is mapped.. So I'm running into point 1, above. Though I have no idea how.
I have no calls to glMapBuffer, or any related function, anywhere in my code. I set gDEBugger to break on any calls to glMapBuffer, glMapBufferARB, glMapBufferRange, glUnmapBuffer and glUnmapBufferARB, and it didn't break anywhere. Then I added code to the start of RenderPass1 to explicitly unmap bother buffers. Not only did the error not go away, but calls to glUnmapBuffer now both generate The current operation is illegal in the current state: Buffer is unbound or is already unmapped.. So if neither of the buffers I'm using are mapped, where is the error coming from?
Edit 8/19/12, 12 AM:
Based on the error messages I'm getting out of GLExpert in gDEBugger, it appears that calling glBeginTransformFeedback is causing the buffer bound to GL_TRANSFORM_FEEDBACK_BUFFER to become mapped. Specifically, when I click on the buffer in "Textures, Buffers and Images Viewer" it outputs the message The current operation is illegal in the current state: Buffer must be bound and not mapped.. However, if I add this between glBeginTransformFeedback and glEndTransformFeedback:
int bufferBinding;
glGetBufferParameteriv(GL_TRANSFORM_FEEDBACK_BUFFER, GL_BUFFER_MAPPED, &bufferBinding);
printf("Transform feedback buffer binding: %d\n", bufferBinding);
it outputs 0, which would indicate that GL_TRANSFORM_FEEDBACK_BUFFER is not mapped. If this buffer is mapped on another binding point, would this still return 0? Why would glBeginTransformFeedback map the buffer, thus rendering it unusable for transform feedback?
The more I learn here, the more confused I'm becoming.
Edit 10/10/12:
As indicated in my reply below to Nicol Bolas' solution, I found the problem, and it's the same one he found: Due to a stupid typo, I was binding the same buffer to both the input and output binding points.
I found it probably two weeks after posting the question. I'd given up in frustration for a time, and eventually came back and basically re-implemented the whole thing from scratch, regularly comparing bits and pieces the older, non-working one. When I was done, the new version worked, and it was when I searched out the differences that I discovered I'd been binding the wrong buffer.
I figured out your problem: you are rendering to the same buffer that you're sourcing your vertex data.
glBindVertexArray(vaoPass2);
I think you meant vaoPass1
From the spec:
Buffers should not be bound or in use for both transform feedback and other
purposes in the GL. Specifically, if a buffer object is simultaneously bound to a
transform feedback buffer binding point and elsewhere in the GL, any writes to
or reads from the buffer generate undefined values. Examples of such bindings
include ReadPixels to a pixel buffer object binding point and client access to a
buffer mapped with MapBuffer.
Now, you should get undefined values; I'm not sure that a GL error qualifies, but it probably should be an error.
Another (apparently undocumented) case where glDrawArrays and glDrawElements fail with GL_INVALID_OPERATION:
GL_INVALID_OPERATION is generated if a sampler uniform is set to an invalid texture unit identifier. (I had mistakenly performed glUniform1i(location, GL_TEXTURE0); when I meant glUniform1i(location, 0);.)
Another (undocumented) case where glDraw*() calls can fail with GL_INVALID_OPERATION:
GL_INVALID_OPERATION is generated if a sampler uniform is set to a texture unit bound to a texture of the incorrect type. For example, if a uniform sampler2D is set glUniform1i(location, 0);, but GL_TEXTURE0 has a GL_TEXTURE_2D_ARRAY texture bound.
Related
My task is to show several pictures. I implemented it as a class to make several instances. Each instance represents a picture. It compiles shaders, set two triangles and loads picture data in constructor. The main program creates instances and then goes to loop to switch prigramid and call render() method for each instance.
while(true)
for (uint g = 0; g < pictures.size(); g++){
glUseProgram(pictures[g]->ProgramId);
pictures[g]->render();
}
It works well and shows the pictures but I do not like it. It could be done much better.
Here is partial code of the class
Picture::Picture(picPosition* pPosition, const char * fileName)
:BasePicture(pPosition)
{
pos = glGetAttribLocation(ProgramId, "position");
uv = glGetAttribLocation(ProgramId, "texture_vert");
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glGenBuffers(1, &uvbuffer);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
int n;
textureData = stbi_load(fileName, &picWidth, &picHeight, &n, STBI_rgb_alpha);
TextureID = glGetUniformLocation(ProgramId, "myTextureSampler");
glBindTexture(GL_TEXTURE_2D, TextureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glActiveTexture(GL_TEXTURE0);
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
//calculating the vertex matrix using MVP calculated in parent class
for (int i = 0; i < 6; i++)
ModeledVerts.push_back(MVP * verts[i]);
v = glm::value_ptr(ModeledVerts[0]);
}
Picture::~Picture()
{
stbi_image_free(textureData);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &uvbuffer);
}
void Picture::render()
{
glBufferData(GL_ARRAY_BUFFER, 96, v, GL_STATIC_DRAW);
glVertexAttribPointer(pos, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*) 0);
glEnableVertexAttribArray(pos);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(verticesUV), verticesUV, GL_STATIC_DRAW);
glVertexAttribPointer(uv, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*) 0);
glEnableVertexAttribArray(uv);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, picWidth, picHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
I played a lot with the code to make render() function as light as possible but I cannot make it lighter then it is now.
The biggest problem is sending textureData every time (glTexImage2D). The data never gets changed. I tried to move it to constructor but in this case all picture objects show the same picture that was loaded latest. It looks like one instance overrides texture data uploaded before. I am looking for a way to load the picture data once in the constructor and not every time it renders. It looks like there is something in OpenGL API for that but I do not know it yet.
Another improvement could be for taking vertex data set up from render(). That data never changes. But it is not that significant as glTexImage2D call in render().
Could you point me to the OpenGL API to separate shader's data? Or show me what I am doing wrong...
You stated:
It works well and shows the pictures but I do not like it. It could be done much better.
From a design approach I think that this may help you.
Separate the functionality of opening, reading & parsing the image files for texture data from the actual texture struct or class. This would be a sample pseudo code:
struct Texture {
unsigned int width;
unsigned int height;
bool hasTransparency;
GLint id; // ID that is used by OpenGL to setActive, bind, and pass to shaders as either a uniform or sampler2D.
std::string filenameAndPath; // Keep this filename associated with texture so you can prevent trying to reload the same file over and over.
GLuchar* data; // the actual color - pixel texture data.
}
// This function will handle the opening and reading in of the texture data
// it would return back the ID value generated by OpenGL which will also be
// stored into the texture struct. The texture struct is returned by reference so that it can be populated with data.
GLuint loadTexture( const char* filenameAndPath, Texture& texture, /*loading parameters & flags*/ ) {
// Check to see if file exists or is already loaded
if ( fileanameAndPath already exists ) {
// get the existing ID from the already loaded texture
// and just return that.
} else {
// Try to open the new file for reading.
// parse the data for general purposes that will support
// your application. You can simply use `stbi_load` as it is a fairly
// decent third party library.
// Check the internal formatting of how the data is stored
// Compression, pixel orientation etc.
// configure the data to your needs (color format),
// (flipping the pixels either horizontally, vertically or both),
// now copy the actual pixel data into your buffer.
// close the file handle
// save all the information into your struct
// return the ID value that was generated by OpenGL
}
}
The within your main engine code before the render loop you will want to load your texture(s) from file and then you can use this Texture object where needed. Finally in your render loop is where you would want to set the texture(s) to active and bind them to the render target and pass them off to your shaders. In some cases you might want to set them active & bind them before your render loop depending on the type of shader-technique you are implementing.
Answering my own question.
The solution is to use atlas map. Software generates atlas that contains all pictures, upload it once (glTexImage2D) and use coordinates for each picture. That improves performance very significantly as glTexImage2D was called just once.
I'm currently working with a compute shader in OpenGl and my goal is to render from one texture onto another texture with some modifications. However, it does not seem like my compute shader has any effect on the textures at all.
After creating a compute shader I do the following
//Use the compute shader program
(*shaderPtr).useProgram();
//Get the uniform location for a uniform called "sourceTex"
//Then connect it to texture-unit 0
GLuint location = glGetUniformLocation((*shaderPtr).program, "sourceTex");
glUniform1i(location, 0);
//Bind buffers and call compute shader
this->bindAndCompute(bufferA, bufferB);
The bindAndCompute() function looks like this and its purpose is to ready the two buffers to be accessed by the compute shader and then run the compute shader.
bindAndCompute(GLuint sourceBuffer, GLuint targetBuffer){
glBindImageTexture(
0, //Always bind to slot 0
sourceBuffer,
0,
GL_FALSE,
0,
GL_READ_ONLY, //Only read from this texture
GL_RGB16F
);
glBindImageTexture(
1, //Always bind to slot 1
targetBuffer,
0,
GL_FALSE,
0,
GL_WRITE_ONLY, //Only write to this texture
GL_RGB16F
);
//this->height is currently 960
glDispatchCompute(1, this->height, 1); //Call upon shader
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}
And finally, here is the compute shader. I currently only try to set it so that it makes the second texture completely white.
#version 440
#extension GL_ARB_compute_shader : enable
#extension GL_ARB_shader_image_load_store : enable
layout (rgba16, binding=0) uniform image2D sourceTex; //Textures bound to 0 and 1 resp. that are used to
layout (rgba16, binding=1) uniform image2D targetTex; //acquire texture and save changes made to texture
layout (local_size_x=960 , local_size_y=1 , local_size_z=1) in; //Local work-group size
void main(){
vec4 result; //Vec4 to store the value to be written
pxlPos = ivec2(gl_GlobalInvocationID.xy); //Get pxl-pos
/*
result = imageLoad(sourceTex, pxlPos);
...
*/
imageStore(targetTex, pxlPos, vec4(1.0f)); //Write white to texture
}
Now, when I start bufferB is empty. When I run this I expect bufferB to become completely white. However, after this code bufferB remains empty. My conclusion is that either
A: The compute shader does not write to the texture
B: glDispatchCompute() is not run at all
However, i get no errors and the shader does compile as it should. I have checked that I bind the texture correctly when rendering by binding bufferA which I already know what it contains, then running bindAndCompute(bufferA, bufferA) to turn bufferA white. However, bufferA is unaltered. So, I've not been able to figure out why my compute shader has no effect. If anyone has any ideas on what I can try to do it would be appreciated.
End note: This has been my first question asked on this site. I've tried to present only relevant information but I still feel like maybe it became too much text anyway. If there is feedback on how to improve the structure of the question that is welcome as well.
---------------------------------------------------------------------
EDIT:
The textures I send in with sourceBuffer and targetBuffer is defined as following:
glGenTextures(1, *buffer);
glBindTexture(GL_TEXTURE_2D, *buffer);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA16F, //Internal format
this->width,
this->height,
0,
GL_RGBA, //Format read
GL_FLOAT, //Type of values in read format
NULL //source
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
The image format of the images you bind doesn't match the image format in the shader. You bind a RGB16F (48byte per texel) texture, but state in the shader that it is of rgba16 format (64byte per texel).
Formats have to match according to the rules given here. Assuming that you allocated the texture in OpenGL, this means that the total size of each texel have to match. Also note, that 3-channel textures are (without some rather strange exceptions) not supported by image load/store.
As a side-note: The shader will execute and write if the texture format size matches. But what you write might be garbage because your textures are in 16-bit floating point format (RGBA_16F) while you tell the shader that they are in 16-bit unsigned normalized format (rgba16). Although this doesn't directlyy matter for the compute shader, it does matter if you read-back the texture or access it trough a sampler or write data > 1.0f or < 0.0f into it. If you want 16-bit floats, use rgba16f in the compute shader.
I'm currently working to convert a project from using a texture atlas to an array texture, but for the life of me I can't get it working.
Some notes about my environment:
I'm using OpenGL 3.3 core context with GLSL version 3.30
The textures are all 128x128 and rendered perfectly fine when using an atlas (barring the edge artifacts which convinced me to switch)
Problems I believe I've ruled out:
Resolution issues - 128x128, being a power of two, should be fine
Texture loading (it works perfectly as it did before)
Incomplete textures (mipmap issues) - I've gone through the common issues regarding mipmaps and I don't believe OpenGL should be expecting them
Here's my code for creating the array texture:
public void createTextureArray() {
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
int handle = glGenTextures();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, handle);
glPixelStorei(GL_UNPACK_ROW_LENGTH, Texture.SIZE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, Texture.SIZE, Texture.SIZE, textures.size());
try {
int layer = 0;
for (Texture tex : textures.values()) {
// Next few lines are just for loading the texture. They've been ruled out as the issue.
PNGDecoder decoder = new PNGDecoder(ImageHelper.asInputStream(tex.getImage()));
ByteBuffer buffer = BufferUtils.createByteBuffer(decoder.getWidth() * decoder.getHeight() * 4);
decoder.decode(buffer, decoder.getWidth() * 4, PNGDecoder.Format.RGBA);
buffer.flip();
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, decoder.getWidth(), decoder.getHeight(), 1,
GL_RGBA, GL_UNSIGNED_BYTE, buffer);
tex.setLayer(layer);
layer++;
}
} catch (IOException ex) {
ex.printStackTrace();
System.err.println("Failed to create/load texture array");
System.exit(-1);
}
}
The code for creating the VAO/VBO:
private static int prepareVbo(int handle, FloatBuffer vbo) {
IntBuffer vaoHandle = BufferUtils.createIntBuffer(1);
glGenVertexArrays(vaoHandle);
glBindVertexArray(vaoHandle.get());
glBindBuffer(GL_ARRAY_BUFFER, handle);
glBufferData(GL_ARRAY_BUFFER, vbo, GL_STATIC_DRAW);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, GraphicsMain.TEXTURE_REGISTRY.atlasHandle);
glEnableVertexAttribArray(positionAttrIndex);
glEnableVertexAttribArray(texCoordAttrIndex);
glVertexAttribPointer(positionAttrIndex, 3, GL_FLOAT, false, 24, 0);
glVertexAttribPointer(texCoordAttrIndex, 3, GL_FLOAT, false, 24, 12);
glBindVertexArray(0);
vaoHandle.rewind();
return vaoHandle.get();
}
Fragment shader:
#version 330 core
uniform sampler2DArray texArray;
varying vec3 texCoord;
void main() {
gl_FragColor = texture(texArray, texCoord);
}
(texCoord is working fine; it's being passed from the vertex shader correctly.)
I'm about out of ideas, so being still somewhat new to modern OpenGL, I'd like to know if there's anything glaringly wrong with my code.
Some considerations:
you don't need any more to have power of two textures
be sure that every layer has the same number of levels/mipmaps, as the wiki says
the first four glTexParameteri will affect what is bound to GL_TEXTURE_2D_ARRAY at that moment, so you better want to move them after glBindTexture
how can you specify how many textures you want to create with glGenTextures()? If you have the possibility for a more specific method, please use it
GL_UNPACK_ROW_LENGTH if greater than 0, defines the number of pixels in a row. I suppose then Texture.SIZE is not really the texture size but the dimension on one side (128 in your case). Anyway you don't need to set that, you can skip it
set GL_UNPACK_ALIGNMENT to 4 only if your row lenght is a multiple of it. Most of time people set it to 1 before loading a texture to avoid any trouble and then set it back to 4 once done
last argument of glTexStorage3D is expected to be the number of layers, I hope textures.size() better returns that rather than the size (128x128)
glActiveTexture and glBindTexture inside prepareVbo are useless, they are not part of the vao
don't use varying in glsl, it's deprecated, switch to a simple in out
you may want to take inspiration from this sample
use sampler, they give you more flexibility
use Debug Output if available, otherwise glGetError(), some silent errors may not be seen explicitely by the rendering output
you called it prepareVbo but you do initialize in it both vao and vbo
I wrote some code, too long to paste here, that renders into a 3D 1 component float texture via a fragment shader that uses bindless imageLoad and imageStore.
That code is definitely working.
I then needed to work around some GLSL compiler bugs, so wanted to read the 3D texture above back to the host via glGetTexImage. Yes, I did do a glMemoryBarrierEXT(GL_ALL_BARRIER_BITS).
I did check the texture info via glGetTexLevelparameteriv() and everything I see matches. I did check for OpenGL errors, and have none.
Sadly, though, glGetTexImage never seems to read what was written by the fragment shader. Instead, it only returns the fake values I put in when I called glTexImage3D() to create the texture.
Is that expected behavior? The documentation implies otherwise.
If glGetTexImage actually works that way, how can I read back the data in that 3D texture (resident on the device?) Clearly the driver can do that as it does when the texture is made non-resident. Surely there's a simple way to do this simple thing...
I was asking if glGetTexImage was supposed to work that way or not. Here's the code:
void Bindless3DArray::dump_array(Array3D<float> &out)
{
bool was_mapped = m_image_mapped;
if (was_mapped)
unmap_array(); // unmap array so it's accessible to opengl
out.resize(m_depth, m_height, m_width);
glBindTexture(GL_TEXTURE_3D, m_textureid); // from glGenTextures()
#if 0
int w,h,d;
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_WIDTH, &w);
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_HEIGHT, &h);
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_DEPTH, &d);
int internal_format;
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format);
int data_type_r, data_type_g;
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_RED_TYPE, &data_type_r);
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_GREEN_TYPE, &data_type_g);
int size_r, size_g;
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_RED_SIZE, &size_r);
glGetTexLevelParameteriv(GL_TEXTURE_3D, 0, GL_TEXTURE_GREEN_SIZE, &size_g);
#endif
glGetTexImage(GL_TEXTURE_3D, 0, GL_RED, GL_FLOAT, &out(0,0,0));
glBindTexture(GL_TEXTURE_3D, 0);
CHECK_GLERROR();
if (was_mapped)
map_array_to_cuda(); // restore state
}
Here's the code that creates the bindless array:
void Bindless3DArray::allocate(int w, int h, int d, ElementType t)
{
if (!m_textureid)
glGenTextures(1, &m_textureid);
m_type = t;
m_width = w;
m_height = h;
m_depth = d;
glBindTexture(GL_TEXTURE_3D, m_textureid);
CHECK_GLERROR();
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0); // ensure only 1 miplevel is allocated
CHECK_GLERROR();
Array3D<float> foo(d, h, w);
// DEBUG -- glGetTexImage returns THIS data, not what's on device
for (int z=0; z<m_depth; ++z)
for (int y=0; y<m_height; ++y)
for (int x=0; x<m_width; ++x)
foo(z,y,x) = 3.14159;
//-- Texture creation
if (t == ElementInteger)
glTexImage3D(GL_TEXTURE_3D, 0, GL_R32UI, w, h, d, 0, GL_RED_INTEGER, GL_INT, 0);
else if (t == ElementFloat)
glTexImage3D(GL_TEXTURE_3D, 0, GL_R32F, w, h, d, 0, GL_RED, GL_FLOAT, &foo(0,0,0));
else
throw "Invalid type for Bindless3DArray";
CHECK_GLERROR();
m_handle = glGetImageHandleNV(m_textureid, 0, true, 0, (t == ElementInteger) ? GL_R32UI : GL_R32F);
glMakeImageHandleResidentNV(m_handle, GL_READ_WRITE);
CHECK_GLERROR();
#ifdef USE_CUDA
checkCuda(cudaGraphicsGLRegisterImage(&m_image_resource, m_textureid, GL_TEXTURE_3D, cudaGraphicsRegisterFlagsSurfaceLoadStore));
#endif
}
I allocate the array, render to it via an OpenGL fragment program, and then I call dump_array() to read the data back. Sadly, I only get what I loaded in the allocate call.
The render program looks like
void App::clear_deepz()
{
deepz_clear_program.bind();
deepz_clear_program.setUniformValue("sentinel", SENTINEL);
deepz_clear_program.setUniformValue("deepz", deepz_array.handle());
deepz_clear_program.setUniformValue("sem", semaphore_array.handle());
run_program();
glMemoryBarrierEXT(GL_ALL_BARRIER_BITS);
// glMemoryBarrierEXT(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
// glMemoryBarrierEXT(GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV);
deepz_clear_program.release();
}
and the fragment program is:
#version 420\n
in vec4 gl_FragCoord;
uniform float sentinel;
coherent uniform layout(size1x32) image3D deepz;
coherent uniform layout(size1x32) uimage3D sem;
void main(void)
{
ivec3 coords = ivec3(gl_FragCoord.x, gl_FragCoord.y, 0);
imageStore(deepz, coords, vec4(sentinel));
imageStore(sem, coords, ivec4(0));
discard; // don't write to FBO at all
}
discard; // don't write to FBO at all
That's not what discard means. Oh, it does mean that. But it also means that all Image Load/Store writes will be discarded too. Indeed, odds are, the compiler will see that statement and just do nothing for the entire fragment shader.
If you want to just execute the fragment shader, you can employ the GL 4.3 feature (available on your NVIDIA hardware) of having an empty framebuffer object. Or you could use a compute shader. If you can't use GL 4.3 yet, then use a write mask to turn off all color writes.
As Nicol mentions above, if you want side effects only of image load and store, the proper way is to use an empty frame buffer object.
The bug of mixing glGetTexImage() and bindless textures was in fact a driver bug, and has been fixed as of driver version 335.23. I filed the bug and have confirmed my code is now working properly.
Note I am using empty frame buffer objects in the code, and don't use "discard" any more.
EDIT: Think I've narrowed down the problem. Skip to the running section.
I'm trying to sample a 3d texture in my vertex shader, I'm going to use the texel values as corner value in Marching Cubes. The issue I'm having is that no matter what method I use to sample it, I always get (0,0,0,0). I've tried using texelFetch and texture3D and neither seem to work.
I'm also using transform feedback, but as far as I'm aware that shouldn't cause this issue.
Shader setup:
glEnable(GL_TEXTURE_3D);
Shader vertListTriangles(GL_VERTEX_SHADER_ARB);
vertListTriangles.setSource(lst_tri_vert); //Util to load from file.
vertListTriangles.compile();
vertListTriangles.errorCheck(); //Prints errors to console if they exist - shader compiles fine.
Shader geomListTriangles(GL_GEOMETRY_SHADER_ARB);
geomListTriangles.setSource(lst_tri_geom); //Util to load from file
geomListTriangles.compile();
geomListTriangles.errorCheck(); //Prints errors to console if they exist - shader compiles fine.
program.attach(vertListTriangles);
program.attach(geomListTriangles);
//Setup transform feedback varyings, also works as expected.
const GLchar* varyings1[1];
varyings1[0] = "gTriangle";
glTransformFeedbackVaryings(program.getID(), 1, varyings1, GL_INTERLEAVED_ATTRIBS);
program.link();
program.checkLink(); //Prints link errors to console - program links fine aparently.
Texture setup:
glBindTexture(GL_TEXTURE_3D, textureID);
errorCheck("texture bind"); //<- Detects GL errors, I actually get a GL_INVALID_OPERATION here, not sure if its the cause of the problem though as all subsuquent binds go smoothly.
if(!(glIsTexture(textureID)==GL_TRUE)) consolePrint("Texture Binding Failed."); //Oddly the texture never registers as failed despite the previous error message.
//Generate Texture
GLfloat volumeData[32768*3];
for(int z = 0; z < 32; z++)
{
for(int y = 0; y < 32; y++)
{
for(int x = 0; x < 32; x++)
{
//Set all 1s for testing purposes
volumeData[(x*3)+(y*96)+(z*3072)] = 1.0f;
volumeData[(x*3)+(y*96)+(z*3072)+1] = 1.0f;
volumeData[(x*3)+(y*96)+(z*3072)+2] = 1.0f;
}
}
}
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB8, 32, 32, 32, 0, GL_RGB,
GL_FLOAT, volumeData);
glBindTexture(GL_TEXTURE_3D, 0);
Running Shader:
EDIT: Here it gets interesting. If I specify an incorrect uniform name or comment out the below lines it appears to work.
program.use();
//Disable Rastering
glEnable(GL_RASTERIZER_DISCARD);
//Input buffer: Initial vertices
glBindBuffer(GL_ARRAY_BUFFER, mInitialDataBuffer);
glEnableVertexAttribArray(0);
glVertexAttribIPointer(0, 1, GL_UNSIGNED_INT, 0, 0); //Initial input is array of uints
//Output buffer: Triangles
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTriangleBuffer); //Triangle Markers, in the form of uints. NOT actual triangles.
//Texture setup
//If I comment out from here....
GLint sampler = glGetUniformLocation(program.getID(), "densityVol");
glUniform1i(sampler, GL_TEXTURE0);
glActiveTexture(GL_TEXTURE0);
//To here. It appears to work.
glBindTexture(GL_TEXTURE_3D, textureID);
//Just using this to debug texture.
//test is all 1s, so the texture is uploading correctly.
GLfloat test[32768*3];
memset(test, 0, sizeof(test));
glGetTexImage(GL_TEXTURE_3D, 0, GL_RGB, GL_FLOAT, test);
//Transform Feedback and Draw
glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, 29790);
glEndTransformFeedback();
//Re-enable Rastering and cleanup
glDisable(GL_RASTERIZER_DISCARD);
glDisableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
My code is a little more spread out in reality, but I hope I managed to edit it into something cohesive. Anyway if I map to the output buffer it does indeed output some information, however it processes as if all the texture data is 0s. I hacked the shader to just output some test results instead but I can't find any evidence the shader is using the texture correctly:
#version 410
#extension GL_EXT_gpu_shader4 : require
layout (location = 0) in int x_y_z;
uniform sampler3D densityVol;
out Voxel
{
/*
Each triangle is the edges it joins. There are 12 edges and so we need 12 bits. 4 For each edge.
There are up to 32 voxels, which means we need 6 bits for each coord, which is 18.
30 bits total.
int format 00xxxxxxyyyyyyzzzzzz111122223333
*/
uint triangles[5];
uint triangleCount;
} vVoxel;
//... Omitted some huge ref tables.
void main()
{
vec4 sample0 = texture3D(densityVol, vec3(0.1,0.1,0.1) );
vec4 sample1 = texture3D(densityVol, vec3(0.9,0.9,0.9) );
vec4 sample2 = texture3D(densityVol, vec3(0.1,0.1,0.9) );
vec4 sample3 = texture3D(densityVol, vec3(0.9,0.9,0.1) );
if(sample0.r > 0.0f)
{
vVoxel.triangles[1] = 1;
}
if(sample1.r > 0.0f)
{
vVoxel.triangles[2] = 2;
}
if(sample2.r > 0.0f)
{
vVoxel.triangles[3] = 3;
}
if(sample3.r > 0.0f)
{
vVoxel.triangles[4] = 4;
}
vVoxel.triangleCount = 5;
}
Not the best designed test, but I didn't want to write something from scratch. If I change the if clauses to if(true) the test outputs correctly. When the shader is compiled as above, the buffer is blank. I'm using a GS for pass through.
Can anyone see an obvious mistake in there? I've been stumped for about 2 hours now and I can't see what I'm doing different from many of the GLSL texturing tutorials.
Okay figured it out.
glUniform1i(sampler, GL_TEXTURE0);
The GL_TEXTURE0 is incorrect here.
glUniform1i(sampler, 0);
Is how it should be.