Reputation: 588
I'm creating a struct
to hold data about a texture that I need for my project. This structure has five members:
The texture atlas width (The amount of textures in one row),
The texture atlas height (The amount of textures in one column),
The texture unit width (The inverse of atlas width),
The texture unit height (The inverse of atlas height)
and an unsigned GLint
storing the textures id.
However, even after initializing these members in the constructor, I receive an ArithmeticException
later in the program due to the atlas width and height both being set to 0.
I've tried rearranging my code and trying other seemingly random changes but none have worked.
The way my project (or at least the relevant part) is set up is that I have a header and source file for my TextureData
struct. I create a constant object of this type in a header named "GeneralData.h". I don't have a source file associated with this header so all of the implementations are contained in there as well.
This is the TextureData code:
// TextureData.h
struct TextureData
{
const int ATLAS_WIDTH;
const int ATLAS_HEIGHT;
const float TEXTURE_UNIT_WIDTH;
const float TEXTURE_UNIT_HEIGHT;
GLuint TEXTURE_ID;
TextureData(int, int);
virtual ~TextureData() {}
};
// TextureData.cpp
#ifndef STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#include <../stb_image.h>
#endif // STB_IMAGE_IMPLEMENTATION
TextureData::TextureData(int atlas_width, int atlas_height):
ATLAS_WIDTH(atlas_width),
ATLAS_HEIGHT(atlas_height),
TEXTURE_UNIT_WIDTH(1.0f / ATLAS_WIDTH),
TEXTURE_UNIT_HEIGHT(1.0f / ATLAS_HEIGHT)
{
glGenTextures(1, &TEXTURE_ID);
glBindTexture(GL_TEXTURE_2D, TEXTURE_ID);
int width;
int height;
unsigned char* pixels = stbi_load("res/block_textures.png", &width, &height, nullptr, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
This is the relevant part of the GeneralData header:
const TextureData BLOCK_TEXTURE(8, 8);
// These functions are called at some point with index = 0.
// Their behaviour should not be undefined
inline float tex_coord_x(int index)
{
return index % BLOCK_TEXTURE.ATLAS_WIDTH * BLOCK_TEXTURE.TEXTURE_UNIT_WIDTH;
}
inline float tex_coord_y(int index)
{
return index / BLOCK_TEXTURE.ATLAS_HEIGHT * BLOCK_TEXTURE.TEXTURE_UNIT_HEIGHT;
}
As stated above I get a runtime exception (arithmetic) because I am dividing by zero. I should be dividing by (or modulus) eight in this case (and by 0.125f in the case of TEXTURE_UNIT_WIDTH).
This is where the functions are called. This is under the function definitions and the BLOCK_TEXTURE definition.
// EDIT:
template <typename T, size_t SIZE, typename FUNCTION>
inline std::array<T, SIZE> make_array (FUNCTION func)
{
std::array<T, SIZE> arr;
unsigned int index = 0;
std::for_each(arr.begin(), arr.end(), [&func, &index, &arr](const T& val){ arr[index] = func(index); index++; });
return arr;
}
const std::array<float, B_LAST * 6> TEXTURE_COORDS = make_array<float, B_LAST * 6>([](unsigned int index){ return index%2 == 0? tex_coord_x(TEXTURE_INDICES[index / 2]) : tex_coord_y(TEXTURE_INDICES[index / 2 + 1]); });
Upvotes: 2
Views: 70
Reputation: 2992
Hmm.
This code works fine after cleaning and putting it in single file. Main hint to your problem is your own statement "This is the relevant part of the GeneralData header". You put your const value into header and included it multiple times. Const values have internal linkage by default (why? no idea...), which means you get multiple versions of your BLOCK_TEXTURE variable. And your code probably uses wrong one, while your debugger caught different one. Compare pointers to this in constructor of BLOCK_TEXTURE and at the moment your exception is raised to be sure. As a solution add extern keyword to turn definition of BLOCK_TEXTURE into declaration:
extern const TextureData BLOCK_TEXTURE;
and define it somewhere in single file:
const TextureData BLOCK_TEXTURE(8, 8);
Note, that this will probably introduce static initialization fiasco to you at some point. Therefore i suggest using static function instead (in header):
static inline GET_BLOCK_TEXTURE() {
static const TextureData BLOCK_TEXTURE(8, 8);
return BLOCK_TEXTURE;
}
This will make sure, that BLOCK_TEXTURE is initialized at first time you will use it.
Upvotes: 2
Reputation: 22219
I believe you have static init fiasco in your code (but I'm not 100% sure, this is some very strange part of C++ and it's UBs)
You define a global variable in a header file. You mentioned in comments that you cannot remove inline
keyword, because you will get multiple definition errors, and that's true. But you also have multiple definitions of your global variables in every translation unit.
Every file including GeneralData.h
has it's own definition of BLOCK_TEXTURE
, so it is possible that one file will access variable from the other file. And it may be not initialized yet.
You can "confirm" (as much as you can confirm UB) static init fiasco by checking whether exception occurs before main
starts or after that. Use debugger and check backtrace when it occurs or simply print something in the very first line of main
.
The solution would be pretty simple: You should define your global variables only in one file.
In GeneralData.h
:
extern const TextureData BLOCK_TEXTURE;
extern const std::array<float, B_LAST * 6> TEXTURE_COORDS ;
In GeneralData.cpp
:
const TextureData BLOCK_TEXTURE(8, 8);
const std::array<float, B_LAST * 6> TEXTURE_COORDS = make_array<float, B_LAST * 6>([](unsigned int index){ return index%2 == 0? tex_coord_x(TEXTURE_INDICES[index / 2]) : tex_coord_y(TEXTURE_INDICES[index / 2 + 1]); });
Upvotes: 1