[Tutor] Malloc, bitwise operations and other nasties

David Ascher da@ski.org
Tue, 9 Mar 1999 11:14:26 -0800 (Pacific Standard Time)


On Tue, 9 Mar 1999, Art Siegel wrote:

> The following is a code snippet from OpenGL demo code I am trying to
> port to Python.

> /* Create a single component texture map */ 
> GLfloat * 
> make_texture(int maxs, int maxt) 
> { 
> int s, t; 
> static GLfloat *texture; 
> texture = (GLfloat *) malloc(maxs * maxt * sizeof(GLfloat)); 
> for (t = 0; t < maxt; t++) { 
> for (s = 0; s < maxs; s++) { 
> texture[s + maxs * t] = ((s >> 4) & 0x1) ^ ((t >> 4) & 0x1); 
> } 
> } 
> return texture; 
> }
> The code creates a checkerboard texture, which gets mapped to an object.
> Between the malloc call (something to do with memory, I know) and the bitwise operators, I am lost.
> I certainly can't see how this becomes a texturemap.
> If I saw the code in Pythonese, I couldl probably begin to understand it. 
> Any help is appreciated.

This is a great example of why C is ugly =)

malloc creates an array of unitialized bytes.  A close equivalent to the
above code in Python would be

texture = [0.0]*(maxs*maxt)
for t in range (maxt):
  for s in range(maxs):
    texture[s + maxs * t] = ((s >> 4) & 0x1) ^ ((t >> 4) & 0x1); 


If you try it with maxs and maxt set to 20, you'll see the 'scan lines':

>>> maxt = maxs = 20
>>> texture = [0.0]*(maxs*maxt)
>>> for t in range (maxt):
...   for s in range(maxs):
...     texture[s + maxs * t] = ((s >> 4) & 0x1) ^ ((t >> 4) & 0x1);
...
>>> print texture
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0 , 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1 , 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0]

However, looking at it like that makes it a bit hard to see how it works.
One could recode it as:

>>> texture = []
>>> for t in range (maxt):
...    line = []              # note we're using lists of lists
...    texture.append(line)   # to make the 'lines'
...    for s in range(maxs):
...       line.append(((s >> 4) & 0x1) ^ ((t >> 4) & 0x1))
...
>>> texture
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], 
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
]

(this looks if you try like the corner of a checkerboard)

How does it work?  The key is clearly the 

  ((s >> 4) & 0x1) ^ ((t >> 4) & 0x1);

s >> 4 is the value of s, shifted to the right by four bits.  When AND'ed
with 1, that tests whether the bit four bits from the right in s is on or
not.  When OR'ed with the equivalent for t, that will be 1 if either the
fourth bit in s or the fourth bit in t are on (or both).  Which means that
columns 16 through 31 and rows 16 through 31 will be "on", and columns 0
through 15 and rows 0 through 15 will be 'off' (with that pattern repeated
ad infinitum).  So the "4" in the right-shift operation determines the
"coarseness" of the texture.  The bit at the '4th position from the right'
is the bit corresponding to 2 to the fourth, or 16.  So in this case the
checkerboard is 16-off, 16-on.  If you make it 5, you'll get 32x32
checkerboards, etc.  

[I'm not happy about this discussion of bit-shifting -- anyone got better
 words to describe binary representations? -- say, Tim, where's that
 module of yours again?]

[To use this lists of lists in PyOpenGL you'll have to flatten it first,
 as per our discussion on the PyOpenGL list, until the next release]

Make sense?

--david