I have a program that uses an offscreen OpenGL context to render a mesh of 28943 triangles into a texture. It renders the Z value, producing a height map. Then it downloads the texture using glReadnPixels()
, and saves it to a file.
vertices.txt
contains the vertices in a format like
-5570.08, 2616.11, -83.408,
-5581.21, 2624.23, -96.4702,
-5570.08, 2616.11, -83.408,
-5555.29, 2627.9, -87.1785,
...
each line is the X,Y,Z coordinates of a vertex, and every 3 lines are the vertices that form one triangle. They form a mesh that looks like this, without outlier points or NAN values:
When I change the line outZ = mapPosition.z;
in the vertex shader (in source code below) to outZ = 1000.0
, it correctly renders the following depth map:
All non-background pixels having the value 1000.0.
But, with outZ = mapPosition.z
, the following output gets generated:
The gray sections contain the correct Z values, but the white sections contain invalid values.
With outZ = mapPosition.x;
, it becomes like this. So even the x/y positions of the fragments (pixels) get corrupted:
It seems to happen the same way on with NVidia and Intel GPUs.
This is the full source code. GLContext
creates the offscreen context (with OpenGL 4.6 Core profile), with the proper width/height, and also calls glViewport()
, and this has been tested to work correctly before.
Is there an error in the way that the OpenGL API is used?
#include "common/GLContext.h"
#include <string>
#include <fstream>
#include <iostream>
#include <map>
#include <array>
#include <typeinfo>
#include <Eigen/Dense>
static const char vertexShader[] = R"%%%(
#version 460 core
uniform mat3 alignmentRotation;
uniform vec3 alignmentTranslation;
uniform mat4 projection;
layout(location=0) in vec3 inPosition;
layout(location=0) out float outZ;
void main() {
vec3 localPosition = inPosition;
vec3 worldPosition = (alignmentRotation * localPosition) + alignmentTranslation;
vec3 mapPosition = worldPosition;
mapPosition.x += 10000;
mapPosition.y += 3000;
gl_Position = projection * vec4(mapPosition, 1.0);
outZ = mapPosition.z;
//outZ = 1000.0;
//outZ = mapPosition.x;
}
)%%%";
static const char fragmentShader[] = R"%%%(
#version 460 core
layout(location=0) in float inZ;
layout(location=0) out float outZ;
void main() {
outZ = inZ;
}
)%%%";
static void GLAPIENTRY GLDebugMessageHandler(GLenum source, GLenum type, GLenum id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
std::cout << message << std::endl;
}
class GLProgram {
public:
GLProgram(GLuint id) : id_(id) { }
~GLProgram() {
glDeleteProgram(id_);
}
GLint UniformLocation(const std::string& name) {
GLint location = GL_CHK(glGetUniformLocation(id_, name.c_str()));
return location;
}
operator GLuint () const { return id_; }
private:
std::map<std::string, GLint> uniformLocations_;
GLuint id_ = 0;
};
struct GLShaderSource {
GLuint type;
std::string data;
};
using GLShaderSources = std::vector<GLShaderSource>;
static GLProgram BuildGLProgram(const GLShaderSources& shaderSources) {
GLuint program = GL_CHK(glCreateProgram());
for(const GLShaderSource& shaderSource : shaderSources) {
GLuint shader = GL_CHK(glCreateShader(shaderSource.type));
GL_CHK(glAttachShader(program, shader));
GL_CHK(glDeleteShader(shader));
std::string source = shaderSource.data;
const GLchar* sourceData = source.c_str();
GLint sourceLength = (GLint)source.length();
GL_CHK(glShaderSource(shader, 1, &sourceData, &sourceLength));
GL_CHK(glCompileShader(shader));
GLint compileStatus;
GL_CHK(glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus));
if(compileStatus != GL_TRUE) {
GLint infoLogSize;
GL_CHK(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogSize));
std::string infoLog;
infoLog.resize(infoLogSize - 1);
GL_CHK(glGetShaderInfoLog(shader, infoLogSize, NULL, infoLog.data()));
GL_CHK(glDeleteProgram(program));
std::cout << "failed to build GL program: " << infoLog << std::endl;
std::abort();
}
}
GL_CHK(glLinkProgram(program));
GLint linkStatus;
GL_CHK(glGetProgramiv(program, GL_LINK_STATUS, &linkStatus));
if(linkStatus != GL_TRUE) {
GLint infoLogSize;
GL_CHK(glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogSize));
std::string infoLog;
infoLog.resize(infoLogSize - 1);
GL_CHK(glGetProgramInfoLog(program, infoLogSize, NULL, infoLog.data()));
GL_CHK(glDeleteProgram(program));
std::cout << "failed to link GL program: " << infoLog << std::endl;
std::abort();
}
return GLProgram(program);
}
int main() try {
// vertices data
static GLfloat vertices[][3] = {
#include "vertices.txt"
};
const std::size_t numVertices = sizeof(vertices) / sizeof(GLfloat[3]);
// transformation
Eigen::Matrix4f alignment;
alignment <<
1, 0, 0, 3292.34,
0, 1, 0, -20.297,
0, 0, 1, 2173.21,
0, 0, 0, 1;
// parameters
const std::size_t mapSize = 512;
const float pixelSize = 20.0;
const std::size_t mapWidth = mapSize, mapHeight = mapSize;
const float minZ = -1e5;
const float maxZ = +1e5;
const std::size_t maxNumInputTriangles = 100000;
const std::size_t maxNumInputVertices = 100000;
const Eigen::Vector2f minimalXY(-10000, -3000);
// create offscreen GL context
GLContextConfiguration glConfig;
glConfig.width = mapSize;
glConfig.height = mapSize;
glConfig.debug = true;
GLContext glContext(glConfig);
std::cout << glContext.Information().rendererName << std::endl;
glContext.MakeCurrent();
// debug
GL_CHK(glEnable(GL_DEBUG_OUTPUT));
GL_CHK(glDebugMessageCallback(GLDebugMessageHandler, NULL));
// generate opengl objects
GLuint vertexArray = 0;
GLuint vertexBuffer = 0;
GLuint framebuffer = 0;
GLuint texture = 0;
GL_CHK(glGenVertexArrays(1, &vertexArray));
GL_CHK(glGenBuffers(1, &vertexBuffer));
GL_CHK(glGenFramebuffers(1, &framebuffer));
GL_CHK(glGenTextures(1, &texture));
// input buffer
{
GL_CHK(glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer));
GL_CHK(glBufferStorage(GL_ARRAY_BUFFER, numVertices * sizeof(GLfloat[3]), NULL, GL_DYNAMIC_STORAGE_BIT));
GL_CHK(glBindBuffer(GL_ARRAY_BUFFER, 0));
}
// vertex array
{
GL_CHK(glBindVertexArray(vertexArray));
GL_CHK(glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer));
std::size_t stride = 3 * sizeof(GLfloat);
GL_CHK(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, 0));
GL_CHK(glEnableVertexAttribArray(0));
GL_CHK(glBindBuffer(GL_ARRAY_BUFFER, 0));
GL_CHK(glBindVertexArray(0));
}
// texture
GL_CHK(glBindTexture(GL_TEXTURE_2D, texture));
GL_CHK(glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32F /*GL_RGBA32F*/, mapWidth, mapHeight));
GL_CHK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
GL_CHK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
GL_CHK(glBindTexture(GL_TEXTURE_2D, 0));
// create program
GLShaderSources shaderSources = {
{ GL_VERTEX_SHADER, vertexShader },
{ GL_FRAGMENT_SHADER, fragmentShader }
};
GLProgram mappingShaderProgram = BuildGLProgram(shaderSources);
// set program uniforms
{
GL_CHK(glUseProgram(mappingShaderProgram));
Eigen::Matrix3f rotation = alignment.block<3, 3>(0, 0);
Eigen::Vector3f translation = alignment.block<3, 1>(0, 3);
GL_CHK(glUniformMatrix3fv(mappingShaderProgram.UniformLocation("alignmentRotation"), 1, GL_FALSE, (const GLfloat*)rotation.data()));
GL_CHK(glUniform3fv(mappingShaderProgram.UniformLocation("alignmentTranslation"), 1, (const GLfloat*)translation.data()));
// projection matrix
float maxX = mapWidth * pixelSize;
float maxY = mapHeight * pixelSize;
Eigen::Matrix4f projectionMatrix; projectionMatrix <<
2.0 / maxX, 0.0, 0.0, -1.0,
0.0, 2.0 / maxY, 0.0, -1.0,
0.0, 0.0, 2.0 / (maxZ - minZ), -(maxZ + minZ) / (maxZ - minZ),
0.0, 0.0, 0.0, 1.0;
GL_CHK(glUniformMatrix4fv(mappingShaderProgram.UniformLocation("projection"), 1, GL_FALSE, (const GLfloat*)projectionMatrix.data()));
GL_CHK(glUseProgram(0));
}
GL_CHK(glFinish());
// clear texture
{
std::array<GLfloat, 4> background{ 0.0f, 0.0f, 0.0f, 1.0f };
GL_CHK(glClearTexImage(
texture,
0,
GL_RGBA,
GL_FLOAT,
background.data()
));
}
// bind texture to framebuffer
GL_CHK(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
GL_CHK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0));
std::vector<GLenum> drawBuffers{ GL_COLOR_ATTACHMENT0 };
GL_CHK(glDrawBuffers(drawBuffers.size(), drawBuffers.data()));
GL_CHK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
// blending
GL_CHK(glDisablei(GL_BLEND, 0));
GL_CHK(glDisable(GL_BLEND));
// depth clamping
GL_CHK(glDisable(GL_DEPTH_CLAMP));
GL_CHK(glDisable(GL_DEPTH_TEST));
// upload data and draw
GL_CHK(glBindVertexArray(vertexArray));
GL_CHK(glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer));
GL_CHK(glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(GLfloat[3]), vertices));
GL_CHK(glBindBuffer(GL_ARRAY_BUFFER, 0));
glFinish();
std::size_t numTriangles = numVertices / 3;
GL_CHK(glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer));
GL_CHK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer));
GL_CHK(glUseProgram(mappingShaderProgram));
GL_CHK(glDrawArrays(GL_TRIANGLES, 0, 3 * numTriangles));
GL_CHK(glUseProgram(0));
GL_CHK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
GL_CHK(glBindBuffer(GL_ARRAY_BUFFER, 0));
GL_CHK(glBindVertexArray(0));
GL_CHK(glFlush());
GL_CHK(glFinish());
// read texture pixels
std::vector<GLfloat> rgbaBuffer(mapWidth * mapHeight * 4);
GL_CHK(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
GL_CHK(glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE));
GL_CHK(glPixelStorei(GL_PACK_SWAP_BYTES, GL_FALSE));
GL_CHK(glPixelStorei(GL_PACK_ALIGNMENT, 1));
GL_CHK(glPixelStorei(GL_PACK_LSB_FIRST, GL_FALSE));
GL_CHK(glReadnPixels(
0, 0,
mapWidth, mapHeight,
GL_RED,
GL_FLOAT,
mapWidth * mapHeight * sizeof(GLfloat),
rgbaBuffer.data()
));
GL_CHK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
// export to uint16 image
{
Eigen::Matrix<std::uint16_t, Eigen::Dynamic, Eigen::Dynamic> map(mapSize, mapSize);
for(GLsizei row = 0; row < mapHeight; ++row) {
const auto* ptr = rgbaBuffer.data() + (row * mapWidth);
for(GLsizei col = 0; col < mapWidth; ++col, ptr += 1) {
float value = ptr[0];
if(value > 65535) value = 65535;
else if(value < 0.0) value = 0.0;
map(row, col) = static_cast<std::uint16_t>(value);
}
}
std::ofstream mapFile("map.bin");
mapFile.write(reinterpret_cast<const char*>(map.data()), mapHeight * mapWidth * sizeof(std::uint16_t));
}
} catch(const std::exception& ex) {
std::cout << "exception " << typeid(ex).name() << ": " << ex.what() << std::endl;
return EXIT_FAILURE;
}