As mentioned here, https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/imageLoad.xhtml, there is supposed to exist a gvec4-type. But i can't find anything about it. What is that datatype?
The term gvec4 is used as a catch-all for all vector types: floats, signed integers, unsigned integers, and doubles. The "g" is replaced in the actual type by:
Nothing, for floats. (vec4)
i for signed ints. (ivec4)
u for unsigned ints. (uvec4)
d for doubles. (dvec4)
In this case, imageLoad will return different vector types based on the type of the image. The "g" in the image type will match the "g" in the return type. Floating-point image types (like image2d) make imageLoad return vec4. Signed integer image types (like iimage2d) make imageLoad return ivec4. And so on.
Related
The book states the following:
Unlike the other integer types, there are three distinct basic character types: char, signed char, and unsigned char. In particular, char is not the same type as signed char. Although there are three character types, there are only two representations: signed and unsigned. The (plain) char type uses one of these representations. Which of the other two character representations is equivalent to char depends on the compiler.
This description is confusing. It says that char is not the same as signed char, but then says it uses "one of the two representations: signed and unsigned."
It also states that there are only "2 representations," but three actual types. What is this saying exactly?
This is trying to communicate that compilers are free to decide which interpretation they take, either signed or unsigned, and as a programmer you must understand that it might not be what you expect. If you're used to it being signed, another compiler might be unsigned, and your code might break because of that assumption.
As such, if you need signed or unsigned, use the appropriate keyword, or better, use something like uint8_t to make it abundantly clear what you want.
int and others are only unsigned if so specified. char is a bit of a loose cannon here.
If an integer type is signed it means that it holds negative and positive values and the value 0.
If an integer type is unsigned it means that it holds positive values and the value 0. It cannot hold negative values.
Some types have "more names". E.g. long, long int and signed long int are all names for the same type.
Let's see what is the case with most of the integer types. E.g. int and signed int are the exact same type. These are two names for the same type. This type is signed (holds negative and positive values and the value 0). unsigned int is a different type, it is unsigned, i.e. it only holds positive values and the value zero. This pattern repeats for all integer types (short, long etc)
The exception to this is char: char, unsigned char and signed char are 3 different types. signed char is signed, unsigned char is unsigned and char well it can be either signed or unsigned depending on the compiler and platform.
Now what does it mean that two types are different? For beginners it really doesn't make much of an impact. This fact comes more into play in function overloading and meta programming.
A representation determines how the bits in a memory region should be interpreted if we know it contains objects of a certain type.
For instance: on a platform where int and float are both 4 bytes long, a compiler would interpret the bits in a 4 byte word differently if it was declared to hold a float than if it were declared to hold an int. Both int and float are distinct types that impose some value representation (and interpretation) of bits. But those representations don't have to belong just to those types. If on our platform a long int was also 4 bytes long, then it would almost certainly have the same representation as an int, even though they are distinct types.
This is similar to the case the book is describing. There are two types, singed char and unsgined char. Each imposes its own representation (and interpretation) of the bits in its storage.
And next to those two types, there is another, third type, which is named plainly char and is not the same type as the other two (just like long int is not the same type as an int). However, it's the same size as both of the others, and has the same representation as one of them (which exactly is implementation defined).
Consider the following line:
int mask = 1 << shift_amount;
we know that mask is 4 bytes because it was explicitly declared int, but this 1 that to be shifted has unknown length. If the compiler chose type as char it would be 8 bits, or it could be unsigned short with size 16 bits, so shifting result will really depend on the size of the compiler's decision about how to treat that 1. How does the compiler decide here? And is it safe to leave the code this way or should it instead be:
int flag = 1;
int mask = flag << shift_amount;
1 is an int (typically 4 bytes). If you wanted it to be a type other than int you'd use a suffix, like 1L for long. For more details see https://en.cppreference.com/w/cpp/language/integer_literal.
You can also use a cast like (long)1 or if you want a known fixed length, (int32_t)1.
As Eric Postpischil points out in a comment, values smaller than int like (short)1 are not useful because the left-hand argument to << is promoted to int anyway.
The 2018 C standard says in 6.4.4 3:
Each constant has a type, determined by its form and value, as detailed later.
This means we can always tell what the type of a constant is just from the text of the constant itself, without regard to the expression it appears in. (Here, “constant” actually means a literal: A thing whose value is given by its text. For example 34 and 'A' literally represent the number 34 and the character A, in contrast to an identifier foo that refers to some object.)
(This answer addresses C specifically. The rules described below are different in C++.)
The subclauses of 6.4.4 detail the various kinds of constants (integers, floating-point, enumerations, and characters). An integer constant without a suffix that can be represented in an int is an int, so 1 is an int.
If an integer constant has a suffix or does not fit in an int, then its type is affected by its suffix, its value, and whether it is decimal, octal, or hexadecimal, according to a table in 6.4.4.1 5.
Floating-point constants are double if they have no suffix, float with f or F, and long double with l or L.
Enumeration constants (declared with enum) have type int. (And these are not directly literals as I describe above, because they are names for values, but the name does indicate the value by way of the enum declaration.)
Character constants without a prefix have type int. Constants with prefixes L, u, or U have type wchar_t, char16_t, or char32_t, respectively.
In C++ you can take a pointer of an unsigned int, and cast it to a pointer to a signed int (reinterpret_cast).
unsigned int a = 200;
int b = *(reinterpret_cast<int *>(&a));
I need to store an int generated in a shader as an unsigned int, to be written to a texture with an unsigned integer internal format. Is there any similar alternative to C++'s reinterpret_cast in GLSL?
In C++ (pre-20), signed and unsigned integers are permitted to be represented in very different ways. C++ does not require signed integers to be two's complement; implementations are allowed to use ones complement, or other representations. The only requirement C++ has on signed vs. unsigned is that conversion of all non-negative (or trap) signed values to unsigned values is possible.
And FYI: your code yields UB for violating the strict aliasing rule (accessing an object of type X through a pointer to an unrelated object of type Y). Though this is somewhat common in low-level code, the C++ object model does not really allow it. But I digress.
I brought up all the signed-vs-unsigned stuff because GLSL actually defines the representation of signed integers. In GLSL, a signed integer is two's complement. Because of that, GLSL can define how conversion from the entire range of unsigned value goes to signed values and vice-versa, simply by preserving the bitpattern of the value.
And that's exactly what it does. So instead of having to use casting gymnastics, you simply do an unsigned-to-signed conversion, just as you would have for float-to-signed or whatever:
int i = ...
uint j = uint(i);
This conversion preserves the bit-pattern.
Oh, and C++20 seems to be getting on-board with this too.
GLSL does not support this kind of casting (nor does it support pointers at all). Instead, in GLSL you construct values of a different type with constructor-style syntax:
int a = 5; // set an int to a constant
uint b = uint(a); // "cast" that int to a uint by constructing a uint from it.
At cppref, I see a weird type trait checker: std::has_unique_object_representations
From its description, I cannot imagine any type T that make std::has_unique_object_representations<T>::value is false.
Is there any counter-example?
Understanding the purpose of this trait requires understanding the distinction between an objects "value representation" and its "object representation". From the standard:
The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T). The value representation of an object is the set of bits that hold the value of type T . For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values.
So, object representation is the total storage of an object. But consider the following object:
struct T
{
char c;
int i;
};
On many systems, sizeof(T) will be 8. Why? Because int has to be 4-byte aligned, so the compiler inserts 3 bytes of padding between c and i. Since those three bytes are part of the object's storage (based on sizeof(T)), they are part of the object's object representation.
But those three bytes are not part of its value representation. If you modified those bytes, they would not affect the values of c or i, or anything else about them.
If you wrote an operator== overload for T, changes to those bytes would not affect its results. Which also means that if you did write an operator== overload, it could not be implemented like this:
bool operator==(const T &lhs, const T &rhs)
{
return std::memcmp(&lhs, &rhs, sizeof(T)) == 0;
}
Why not? Because two Ts can have different values for those padding bytes, but still have the same value of c and i. And thus they have the same value representation and thus should be considered equivalent.
has_unique_object_representations is true when T's object representation and its value representation exactly overlay one another (and when T is trivially copyable). So, when would you care about this?
You can write a generalized hashing function that works on any trivially copyable type T by reading its value representation as an array of bytes and hashing them. Well, you could do that, but only if the type doesn't have padding bytes. And that's what has_unique_object_representations tells you: that all of the bytes in the object representation matter.
Also, note that float types, won't necessarily have this value be true, since binary equality and floating-point equality aren't the same thing in IEEE-754. So types which contain floats will also not necessarily have this be true. Indeed, implementations that use one's-complement signed integers, or signed integers with trap representations, will also not have this be true for such types.
I have a use case where I need to convert signed values to unsigned, to make values sortable. I need this for char, short, int, long, and long long
By sortable, I mean that for signed type X, if (a < b) then converting to unsigned the converted(a) < converted(b). Note that in many cases, converting from a negative signed value directly to a unsigned value will make the value greater than 0 and breaks this constraint (two's complement implementations)
The simplest idea for a char is:
unsigned char convert(char x)
{
return (unsigned char)(x ^ 0x80); // flip sign to make it sortable
}
But this seems to be undefined behavior.
While it might be possible to convert to a larger type, add the types MIN value, and convert to the unsigned type, I'm not sure this is any more compliant, and won't work with long long
How can this be done without any undefined behavior for all types?
It seems safe to convert using memcpy, but it's not clear how to maintain sort order in a compliant way.
(Note that this is similar to: No compliant way to convert signed/unsigned of same size except I need the results to be maintain sort order)
You are doing it wrong, because flipping the sign-bit of a signed value isn't actually defined.
Let's use two-bit types:
00 01 10 11 Order for unsigned 0 1 2 3
10 11 00 01 Order for 2s complement -2 -1 0 1
11 (10 00) 01 Order for sign-magnitude -1 (-0 +0) 1
10 (11 00) 01 Order for 1s-complement -1 (-0 +0) 1
What you want to do is convert to unsigned (Which is always defined as value-preserving, with wrap-around), and then add a bias so the most-negative number becomes 0:
int x = whatever;
unsigned r = (unsigned)x - (unsigned)INT_MIN;
Take care: Signed overflow is not defined, so we avoided signed types.
Of course, it doesn't help if the unsigned type has fewer values than the signed one, which is allowed in general, though not for char.
And you need to take special care if you want to preserve a negative 0 as negative.
This is not possible if you want to remain fully portable.
The range of unsigned int is only specified to at least cover the non-negative values of int. The standard allows for implementations where UINT_MAX == INT_MAX. The same applies to all other non-fixed-width integer types.
Given that the range of unsigned int may be smaller than that of int, the pigeonhole principle applies: you have no way of redistributing all values of int to corresponding but different values of unsigned int unless unsigned int can store at least as many different values as int.
To quote N4140 (roughly C++14):
3.9.1 Fundamental types [basic.fundamental]
1 [...] For narrow character types, all bits of the object representation participate in the value representation. For unsigned narrow character types, all possible bit patterns of the value representation represent numbers. These requirements do not hold for other types. [...]
3 For each of the standard signed integer types, there exists a corresponding (but different) standard unsigned integer type: "unsigned char", "unsigned short int", "unsigned int", "unsigned long int", and "unsigned long long int", each of which occupies the same amount of storage and has the same alignment requirements (3.11) as the corresponding signed integer type47; that is, each signed integer type
has the same object representation as its corresponding unsigned integer type. [...] The range of non-negative values of a signed integer type is a
subrange of the corresponding unsigned integer type, and the value representation of each corresponding signed/unsigned type shall be the same. [...]
This guarantees that you don't have a problem for unsigned char. There is no possibility for unsigned char to have any kind of padding bits. It wouldn't make sense for unsigned char to have padding bits: given unsigned char c;, how would you access those padding bits? reinterpret_cast<unsigned char &>(c)? That obviously just gives you c. The only thing similar to padding bits that is possible for unsigned char is something that's completely transparent to the program, for instance when ECC memory is used.
For all the other non-fixed-width integer type, from short to long long, the standard meaning of "subrange" allows for an equal range.
I think I vaguely recall reading that there may have been ancient CPUs that did not provide any native unsigned operations. This would make it very tricky for implementations to properly implement unsigned division, unless they declared that the would-be-sign-bit of unsigned types would be treated as a padding bit. This way, they could simply use the CPU's signed division instruction for either signed or unsigned types.
To keep the ordering that you want, you must add the same amount to all values such that
a) their relative differences are unchanged and
b) all negative values are turned into nonnegative values.
Adding a consistent amount is the only way to do this. If all the values you are sorting are originally of the same signed type T, then the amount to add to ensure any negative value becomes nonnegative must be
"-numeric_limits::min()" or in other words you must subtract the minimum signed value, which is negative.
If you are bringing in different types into the same sort (e.g. sorting char values along with short, int, long, etc.), you might want to make the first step a conversion to the largest signed type you will handle. There is no loss of information to go from a smaller signed type to a larger signed type.
To avoid overflow problems, I would suggest doing the shift (i.e. subtracting the minimum) conditionally.
if (value < 0)
convert by first subtracting the minimum (making nonnegative) and then convert to the unsigned type (which is now completely safe)
else
first convert the already nonnegative value to the unsigned type (completely safe) and then add the same adjustment as a positive value i.e. add numeric_limits::max()+1
T for both is the original signed T. The expression "numeric_limits::max()+1" could be calculated and converted to the new destination type once and then used as a constant in type newT.
I would subtract numeric_limits<T>::min() from each value. This preserves the ordering property you want, and if the underlying representation is 2's complement (i.e., the only sane representation, and the one that is in practice what every non-museum-resident computer uses) will do what you expect, including for the boundary cases when the input value is equal to the most negative or most positive representable integer -- provided that the compiler uses a SUB instruction, and not an ADD instruction (since the positive value -numeric_limits<T>::min() is too large to represent).
Is this standard compliant? No idea. My guess is: Probably not. Feel free to edit if you know.
The formula x-(unsigned)INT_MIN will yield a suitable ranking on all machines where UINT_MAX > INT_MAX. For any pair of signed integers x and y, where x>=y,
(unsigned)x-(unsigned)y will equal the numerical value of x-y; so if y is
INT_MIN, then x>=y for all x, and the aformentioned formula will report the amount by which x is greater than INT_MIN, which is of course ranked the same as x.