Я использую OpenGL 2.0 и FBO для копирования некоторых данных из текстуры RGBA в текстуру RGB, и я столкнулся с проблемой, из-за которой иногда он «повреждает» первые несколько младших битов некоторых компонентов пикселя во время копирования.
Копия текстуры разбита на несколько этапов, и я изменяю размер FBO.
Сначала я подумал, что, возможно, это проблема, связанная с тем, как я изменял размер FBO, или с тем, как отбирается текстура, но проблема не всегда возникает, и когда это происходит, она никогда не возникает для каждого Пиксель скопирован, и при этом он никогда не встречается для всех компонентов каждого проблемного пикселя. Другими словами, это кажется почти случайным, за исключением того, что действительно является детерминированным, поскольку одни и те же ошибки возникают, если одни и те же входные значения с плавающей запятой используются во время каждого запуска программы.
Кроме того, проблема никогда не возникает, если я всегда использую размер FBO 1×1 (что вводит в заблуждение, потому что это заставляет меня думать, что это проблема выборки, но, опять же, это, вероятно, не так, поскольку каждый компонент каждого проблемного пикселя «поврежден»). К сожалению, использование размера FBO 1×1 абсолютно бесполезно в реальном мире, где я собираюсь скопировать текстуру, содержащую что-то более нескольких пикселей.
Проблема возникает в Windows 7 и Ubuntu, и проблема возникает, когда я использую MSVC ++ или gd’s std rand () или Mersenne Twister для генерации входных значений текстуры (не то, как я должен генерировать значения, это имеет значение, поскольку операции копирования по определению независимы о том, как данные, которые будут скопированы, были сгенерированы заранее) ..
Я написал тестовую программу (см. Код ниже), в которой между запусками программы ничего не меняется, кроме входных значений текстуры.
У кого-нибудь есть AMD 6310 (или вообще какое-либо другое оборудование), на котором он может запустить эту тестовую программу? Вам придется запустить его несколько раз, так как иногда он выдает ошибку, а иногда нет. Мне просто любопытно, если это когда-нибудь выдает ошибку на вашем оборудовании. Я просто не могу определить закономерность, и мое наивное мышление состоит в том, что это должно либо работать постоянно, либо никогда — не так спорадически.
Я также полностью согласен с тем, что в конечном итоге это может быть связано с тем, как я использую OpenGL для копирования. На самом деле это будет облегчением, поскольку это будет означать, что есть простое и надежное решение. Я надеюсь, что это так.
Возможно, у меня где-то есть какие-то посторонние вызовы glTexParameteri, но я пытался использовать метод «лучше безопасно, чем потом сожалеть», работая над этой тестовой программой.
В любом случае, проблема приводит к тому, что некоторые компоненты пикселя имеют ошибку порядка ~ 1e-8. Да, это очень маленькая ошибка, но это совершенно неприемлемо для того, что я делаю.
#include <iostream>
using std::cout;
using std::endl;
#include <iomanip>
using std::ios_base;
using std::setprecision;
using std::fixed;
#include <vector>
using std::vector;
#include <string>
using std::string;
#include <utility>
using std::pair;
#include <cstdlib> // MSVC++ chokes if you don't include this before glut.h
#include <ctime>
#include <GL/glew.h>
#include <GL/glut.h>
// Automatically link in the GLUT and GLEW libraries if compiling on MSVC++
#ifdef _MSC_VER
#pragma comment(lib, "glew32")
#pragma comment(lib, "glut32")
#endiffloat dist(float a, float b);
bool initialize_fragment_shader(const string &fragment_shader_code, GLint &shader, string &error);
void get_chunk_sizes(const size_t num_pixels, vector< pair<size_t, size_t> > &chunk_sizes, const bool force_1x1_chunks, const bool force_square_chunks = false);
string float_bits_string(const float f)
{
long long unsigned int bit_mask = 1;
long long unsigned int intval = *(long unsigned int*)&f;
string bits;
for(size_t i = 0; i < 32; i++, bit_mask <<= 1)
{
if(intval & bit_mask)
bits += '1';
else
bits += '0';
}
bits = string(bits.rbegin(), bits.rend());
return bits;
}int main(int argc, char **argv)
{
// This program uses an FBO and a fragment shader to copy RGBA pixels from an input array into an RGB output array.
// It breaks up the entire pixel copy process into many smaller chunks of a varying number of pixels per chunk.
// See line 165 to change the number of pixels in the array (right now it's hard-coded to be 7 pixels total).
// If the chunk sizes are forced to be 1x1 pixels, then there are never any problems with the copy.
// See line 186 to change whether the chunks are forced to be 1x1 pixels or not (right not they are not being forced as such).
//
// If the chunk sizes are not forced to be 1x1 pixels, then almost all of the time (but not quite always)
// there is a small problem with at least one component of one of the pixels during the copy:
//
// The copy is off by a small, non-zero value of practically constant magnitude (on the order of ~1e-8).
//
// Since the values of the pixel components are the only thing that change between runs of the program,
// the problem seems to be entirely dependent on the values of the pixel components themselves. This is totally
// unexpected -- it should always work or always fail to the same degree, regardless of the pixel component values.
// While looking at the bit patterns of the problematic pixel component values, it seems that it is always only the
// first three to five lowest order bits that do not get copied successfully.
//
// Note that if the values of the pixel components do not change between runs, then the same errors occur,
// and so the problem seems to be entirely deterministic. Right now the components are set via PRNG, and are done in a way
// so that all of the bits of precision are used (see lines 173 - 176).
// See line 86 to alter the PRNG seed.// 1) Initialize pseudo-random number generator.
srand(time(0)); // srand(0);// 2) Initialize OpenGL and related objects.
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA);
GLint glut_window_handle = glutCreateWindow("");
if(! ( GLEW_OK == glewInit() &&
GLEW_VERSION_2_0 &&
GLEW_ARB_framebuffer_object &&
GLEW_ARB_texture_rectangle ) )
{
return -1;
}
GLint shader_handle = 0;
GLuint fbo_handle = 0;
GLuint tex_fbo_handle = 0;
GLuint tex_in_handle = 0;
GLuint tex_out_handle = 0;
const GLint tex_in_internal_format = GL_RGBA32F_ARB;
const GLint tex_in_format = GL_RGBA;
const GLint tex_out_internal_format = GL_RGB32F_ARB;
const GLint tex_out_format = GL_RGB;
const GLint var_type = GL_FLOAT;
string code;
code += "#version 110\n";
code += "uniform sampler2D input_tex;\n";
code += "void main(void)\n";
code += "{\n";
code += " vec4 p = texture2D(input_tex, gl_TexCoord[0].st);\n";
code += " gl_FragData[0].rgb = vec3(p.xyz);\n";
code += "}\n";
string error;
if(false == initialize_fragment_shader(code, shader_handle, error))
{
cout << error << endl;
return -2;
}
glGenTextures(1, &tex_in_handle);
glBindTexture(GL_TEXTURE_2D, tex_in_handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenTextures(1, &tex_out_handle);
glBindTexture(GL_TEXTURE_2D, tex_out_handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenFramebuffersEXT(1, &fbo_handle);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenTextures(1, &tex_fbo_handle);
glBindTexture(GL_TEXTURE_2D, tex_fbo_handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glUseProgram(shader_handle);
glUniform1i(glGetUniformLocation(shader_handle, "input_tex"), 0); // Use texture 0.// 3) Set up input -- an array of RGBA float pixels with pseudorandom values.
size_t num_pixels = 7; // = rand() % 50 + 1;
size_t num_input_channels = 4;
vector<float> input(num_pixels*num_input_channels, 0);
for(size_t i = 0; i < num_pixels; i++)
{
size_t input_index = i*num_input_channels;
input[input_index + 0] = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
input[input_index + 1] = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
input[input_index + 2] = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
input[input_index + 3] = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
}// 4) Break up processing of input into chunks.
vector< pair<size_t, size_t> > chunks;#ifdef FORCE_1x1_CHUNKS
get_chunk_sizes(num_pixels, chunks, true, true);
#else
get_chunk_sizes(num_pixels, chunks, false, true);
#endifsize_t num_pixels_remaining = num_pixels;
size_t num_output_channels = 3;
vector<float> output(num_pixels*num_output_channels, 0);
for(size_t i = 0; i < chunks.size(); i++)
{
cout << "Pixels remaining: " << num_pixels_remaining << ", processing chunk size: " << chunks[i].first << " x " << chunks[i].second << " = " << chunks[i].first*chunks[i].second << endl;
const size_t tex_size_x = chunks[i].first;
const size_t tex_size_y = chunks[i].second;
const size_t index = num_pixels - num_pixels_remaining;
const size_t input_index = index*num_input_channels;
const size_t output_index = index*num_output_channels;
// Set the FBO size to match the current chunk size.
glBindTexture(GL_TEXTURE_2D, tex_fbo_handle);
glTexImage2D(GL_TEXTURE_2D, 0, tex_out_internal_format, tex_size_x, tex_size_y, 0, tex_out_format, var_type, 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex_fbo_handle, 0);
// Write to GPU memory.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex_in_handle);
glTexImage2D(GL_TEXTURE_2D, 0, tex_in_internal_format, tex_size_x, tex_size_y, 0, tex_in_format, var_type, &input[input_index]);
// Calculate by "drawing".
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 1, 0, 1, 0, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0, 0, tex_size_x, tex_size_y);
glBegin(GL_QUADS);
glTexCoord2f(0, 1); glVertex2f(0, 1);
glTexCoord2f(0, 0); glVertex2f(0, 0);
glTexCoord2f(1, 0); glVertex2f(1, 0);
glTexCoord2f(1, 1); glVertex2f(1, 1);
glEnd();
// Read from GPU memory.
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
glReadPixels(0, 0, tex_size_x, tex_size_y, tex_out_format, var_type, &output[output_index]);
num_pixels_remaining -= tex_size_x*tex_size_y;
}// 5) Analyze largest distance between input and output -- it should be zero, but it is not zero
// if the chunk sizes are not forced to be 1x1.
float largest_dist = 0;
cout << setprecision(18);
cout << endl << "Comparing input and output: " << endl;
for(size_t i = 0; i < num_pixels; i++)
{
size_t input_index = i*num_input_channels;
size_t output_index = i*num_output_channels;
float dist0 = dist(input[input_index + 0], output[output_index + 0]);
float dist1 = dist(input[input_index + 1], output[output_index + 1]);
float dist2 = dist(input[input_index + 2], output[output_index + 2]);
if(dist0 > largest_dist)
largest_dist = dist0;
if(dist1 > largest_dist)
largest_dist = dist1;
if(dist2 > largest_dist)
largest_dist = dist2;
if(dist0 != 0)
{
cout << endl;
cout << "**** Copy error at pixel " << i + 1 << " first component" << endl;
cout << "\tInput: " << input[input_index + 0] << '\n' << "\tOutput: " << output[output_index + 0] << endl;
cout << "\tInput (as bits): " << float_bits_string(input[input_index + 0]) << '\n' << "\tOutput (as bits): " << float_bits_string(output[output_index + 0]) << endl;
cout << endl;
}
else
{
cout << "OK at pixel " << i + 1 << " first component" << endl;
// cout << "\tInput: " << input[input_index + 0] << '\n' << "\tOutput: " << output[output_index + 0] << endl;
}
if(dist1 != 0)
{
cout << endl;
cout << "**** Copy error at pixel " << i + 1 << " second component" << endl;
cout << "\tInput: " << input[input_index + 1] << '\n' << "\tOutput: " << output[output_index + 1] << endl;
cout << "\tInput (as bits): " << float_bits_string(input[input_index + 1]) << '\n' << "\tOutput (as bits): " << float_bits_string(output[output_index + 1]) << endl;
cout << endl;
}
else
{
cout << "OK at pixel " << i + 1 << " second component" << endl;
// cout << "\tInput: " << input[input_index + 1] << '\n' << "\tOutput: " << output[output_index + 1] << endl;
}
if(dist2 != 0)
{
cout << endl;
cout << "**** Copy error at pixel " << i + 1 << " third component" << endl;
cout << "\tInput: " << input[input_index + 2] << '\n' << "\tOutput: " << output[output_index + 2] << endl;
cout << "\tInput (as bits): " << float_bits_string(input[input_index + 2]) << '\n' << "\tOutput (as bits): " << float_bits_string(output[output_index + 2]) << endl;
cout << endl;
}
else
{
cout << "OK at pixel " << i + 1 << " third component" << endl;
// cout << "\tInput: " << input[input_index + 2] << '\n' << "\tOutput: " << output[output_index + 2] << endl;
}
}
if(0 != largest_dist)
cout << "\nLargest copy error: " << largest_dist << endl;
else
cout << "\nNo copy errors." << endl;// 6) Cleanup OpenGL and related objects.
glDeleteTextures(1, &tex_in_handle);
glDeleteTextures(1, &tex_out_handle);
glDeleteTextures(1, &tex_fbo_handle);
glDeleteFramebuffersEXT(1, &fbo_handle);
glUseProgram(0);
glDeleteProgram(shader_handle);
glutDestroyWindow(glut_window_handle);
return 0;
}
float dist(float a, float b)
{
return fabsf(b - a);
}
bool initialize_fragment_shader(const string &fragment_shader_code, GLint &shader, string &error)
{
error = "";
// Compile shader.
const char *cch = 0;
GLint status = GL_FALSE;
GLint frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &(cch = fragment_shader_code.c_str()), 0);
glCompileShader(frag);
glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
if(GL_FALSE == status)
{
error = "Fragment shader compile error.\n";
vector<GLchar> buf(4096, '\0');
glGetShaderInfoLog(frag, 4095, 0, &buf[0]);
for(size_t i = 0; i < buf.size(); i++)
if(0 != buf[i])
error += buf[i];
error += '\n';
return false;
}
// Link to get final shader.
shader = glCreateProgram();
glAttachShader(shader, frag);
glLinkProgram(shader);
glGetProgramiv(shader, GL_LINK_STATUS, &status);
if(GL_FALSE == status)
{
error = "Program link error.\n";
vector<GLchar> buf(4096, '\0');
glGetShaderInfoLog(shader, 4095, 0, &buf[0]);
for(size_t i = 0; i < buf.size(); i++)
if(0 != buf[i])
error += buf[i];
error += '\n';
glDetachShader(shader, frag);
glDeleteShader(frag);
return false;
}
// Cleanup.
glDetachShader(shader, frag);
glDeleteShader(frag);
return true;
}
void get_chunk_sizes(const size_t num_pixels, vector< pair<size_t, size_t> > &chunk_sizes, const bool force_1x1_chunks, const bool force_square_chunks)
{
chunk_sizes.clear();
size_t num_pixels_remaining = num_pixels;
GLint max_tex_size = 0;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size);
size_t curr_tex_x = max_tex_size;
size_t curr_tex_y = max_tex_size;
if(true == force_1x1_chunks)
curr_tex_x = curr_tex_y = 1;
while(0 < num_pixels_remaining)
{
if(num_pixels_remaining < curr_tex_x*curr_tex_y)
{
if(true == force_square_chunks)
{
curr_tex_x /= 2;
curr_tex_y /= 2;
}
else
{
if(curr_tex_x == curr_tex_y)
curr_tex_y /= 2;
else
curr_tex_x /= 2;
}
}
else
{
pair<size_t, size_t> p(curr_tex_x, curr_tex_y);
chunk_sizes.push_back(p);
num_pixels_remaining -= curr_tex_x*curr_tex_y;
}
}
}
Спасибо, что нашли время, чтобы прочитать / ответить. Я попытался вручную отключить GL_DITHER, а также GL_ALPHA, но безуспешно. Я предполагаю, что опция дизеринга не имела никакого эффекта, потому что это поплавки.
В любом случае, с тех пор я обнаружил, что проблема не возникает на графических процессорах Intel HD серии и, возможно, на других графических процессорах не AMD. Это заставляет меня задуматься, не является ли это проблемой с моим GPU 6310, GPU 6310 в целом или просто ошибкой драйвера 6310.
Таким образом, есть ответ: это, вероятно, не проблема с тем, как используется OpenGL; Вероятно, это проблема графического процессора AMD 6310 или его драйвера.
Других решений пока нет …