[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Public WebGL] WebGLArray iterator proposal



I'd like to propose a new method for the WebGLArray that makes it easier to implement interleaved data structures, and supporting classes.

Using EMCAScript, it is unusually hard to manipulate data on a byte-by-byte basis as the language does not reveal that fine a level of detail about the machine. I'm sure other web-based languages equally try to abstract these details.

In OpenGL using C, it is common to create an interleaved data structure such as this once

struct vertex {
GLfloat x, y, z; //position
GLubyte color[4];
}

Notice that is made of 3x floats and 4x ubytes. On most GL implementations, this is 128 bits in total.

And to write to it, you might do:

--------
GLfloat* pos = &some_vertex.x;
unsigned char* color = &some_vertex.color;
pos[0] = ...;
color[0] = ...;

/* Advance to next vertex */
pos += sizeof(struct vertex) / sizeof(GLfloat);
color += sizeof(struct vertex) / sizeof(GLubyte);

--------
Using C++, you could even wrap the "magic" pointer arithmetic in a nice class.

In EMCAScript, the WebGLArray buffers can be used to do this as above, but is a severely painful process because while you can create a WebGLFloatArray to access the position elements, you can't "skip" the color components nor write to them properly.

--------
pos = WebGLFloatArray( 3 + 1); //3x floats + 1 color (happens to use same storage amount)
pos.set(0, x);
pos.set(1, y);
pos.set(2, z);
//Set color? you'd have to encode the RGBA bytes as a float...That means looking up IEEE 754 spec. and a lot of gross hacks.
--------

The problem is that there is no way to access the bits in 96-127 without treating it as a float. You might consider using slice() to map those bits, but the spec clearly states:
"The returned WebGLArray will be of the same underlying type as the original WebGLArray. "
It would therefore be impossible to slice() the last 32-bit value and convert to a WebGLUnsignedByteArray. Even if you could, you'd have to call slice() and use weird offset math, making it far more annoying than it should be and very error prone.

In a lot of cases, what is really needed is some kind of custom defined iterator. The idea of what you would be trying to do (say, loading from a file), is perhaps load separate chunks of information into a single buffer. So, if you were loading a model and ran across the position data in the file, you'd write the first 3 floats for the position, skip the color, write the next 3 floats, skip the color data and so on. When you came across the color data in the file, you'd want to do the opposite: skip first 3 floats, write 4 color bytes, skip 3 float, write 4 color bytes, etc. Obviously, in C/C++ this would be a trivial operation and be considered basic pointer arithmetic.
In 3D graphics, typically things come in [packed] groups of 3s and 4s, so simply allowing a variable stride between individual elements would not be sufficient. Elements in the array would need to be treated as groups of 1 or more primitive types -- like glVertexAttribArray expects them. This would bring consistency between how data is manipulated and how is expected by the API.

I propose a new function to WebGLArray called "getIterator()" (not particularly attached to the name, but is was all I could think of)

WebGLArray::getIterator(offset, type, number, stride)
offset: Offset in bytes from start of WebGLArray
type: What type of element FLOAT, UNSIGNED_BYTE, BYTE, etc.
number: Number of elements in a group, 1, 2, 3, 4
stride: Stride in bytes between groups

WebGL[Type]Iterator

hasNext(): boolean, does the iterator have another element. Can be implemented with simple pointer comparisons
next(): Advance iterator
get(): returns a WebGL[Type]Array containing the group of elements
set( value ) : Sets the element group at the current location
  value : A sequence<type> of elements
set( array, offset ) : Sets the element group at the current location using an WebGL[Type]Array. The number of elements is implied by the creation.
array : A WebGL[Type]Array
offset : Offset into 'array' from where element-wise copying should begin.

Could add some other functions such as getType(), getNumber(), and getStride() to get creation parameters.

Thus, it would be possible to do something like this [EMCAScript]:

bufsize = sizeofElements * numberOfElements;
buffer = new WebGLByteArray( bufsize );

pos = buffer.getIterator(0, FLOAT, 3, sizeofElements);

for(i=0; i<numberOfElements; i++)
{
  vector = file.readNextPosition();

   pos.set( vector );
   pos.next();

}

delete pos;

color = buffer.getIterator(12, UNSIGNED_BYTE, 4, sizeofElements);

//repeat for color

--------

It could be argued that this kind of detail is too burdensome and/or out-of-scope for the WebGL spec since it has little to do with graphics. The WebGL spec looks as if it is a straightforward adaptation of the OpenGL ES spec, which is written for C. While the C implementation shouldn't define these sorts of details, in the WebGL arena, there is no other way to accomplish this without the use of extremely difficult constructs. Yet, the WebGL spec. defines operations on  data as if these constructs were available:

//Step 1: Somehow set up buffers (the actual problem at hand)


//Step 2: Use them -- well that's no problem at all!
VertexAttribPointer( pos_index, 3, FLOAT, false, sizeofElements, 0);
VertexAttribPointer( color_index, 3, UNSIGNED_BYTE, true, sizeofElements, 12);

Arbitrary interpretation of packed binary data is just something that web-based programming languages were never designed for, and the spec should account for this. It already has to a limited extent by the inclusion the WebGLArray classes, but in their current form, the classes cannot be used for very much.

--------

Possible Issues:

Writing a non-byte type then reading it back as bytes would reveal platform details (byte-ordering). Similarly, writing 4 bytes to a buffer and using it as float could possibly give the wrong value depending on how the bytes are written to the buffer. I can't really think of a good reason an application would do this, but I do list it here for consideration.

--------

Work Arounds:
In order to make use of interleaved vertex attribute data right now, I have to convert all of the types in my interleaved structure to be of the same type, which means the colors would be stored as 4x floating point values -- thus increasing the amount of memory required. This clearly should not be the goal of an adaptation of an embedded spec.

The only work around that does not increase memory would be to have separate array buffers for each attribute, which can hurt performance since multiple reads would be required (1 per vertex attribute) or multiple DMAs depending on implementation. In addition, it's just messy and requires more handles to manage.

--------

Conclusion:
EMCAScript (and other languages) are fundamentally limited in their handling of byte-level data structure manipulations, and WebGL expects that sort of functionality to make appropriate use of some of its functions (e.g. glVertexAttribPointer). The WebGLArray classes which represent contiguous memory blocks are a good idea, but their functionality is too limited to be of use in real-world scenarios.

Patrick Baggett