How XGL sets up Leo for rendering (Notes by Paul Pantera) Leo has two buffers through which vertices go before being sent to the float. The software writes into the "Bucket Buffer." The Format Converter, under control of the VCS Ops, reads from the Bucket Buffer and writes into the Vertex Buffer. The twelve memory locations of the Current Vertex in the Vertex Buffer correspond to the 12 VCS Ops. Each VCS Op determines which value from the Bucket Buffer will be written into the corresponding location in the Vertex Buffer, and how the data data will be converted between the Bucket Buffer and the Vertex Buffer. This powerful mechanism allows the software to write the data in arbitrary order into the Bucket Buffer, and let the VCS Ops reorder the data before it's sent to the microcode. The twelve VCS OpCodes (and the 12 Vertex Buffer Locations) also correspond to the 12 locations in the c_vcsops array in the file LeoDataSelectors.cc, where the VCS Ops are set up. The array locations are filled by the routines in LeoDataSelectors and subsequently sent to the Leo to program the format converter. So for each combination of XGL attributes, the Leo can be set up to accept data in different formats. The LeoFloat only accepts a very few, stricly defined vertex formats. Specifically, XYZ + Normal, XYZ + Color, and XYZ + Normal + Color. Facet Normals can also be appended to the packet, which accounts for the maximum packet size of 12 words. It is the job of the VCS OpCodes to convert the user data into one of these strict formats. Each value in the c_vcsops array is assigned a value which is determined by a macro, LC_VCSOP. The first array element will always be a header word, LC_VCSOP(0, LC_VCSOP_HEADER, 0). After that, the same macro is called for each subsequest array element, specifying which value in the Bucket Buffer is to be copied, and the data conversion to be used. Our code uses the LC_VCSOP_SINGLE conversion exclusively, so that field is a non-issue. Every vertex format has (x,y,z) values, and those are always in the first three positions of the Bucket Buffer, so every LeoDataSelectors routine should set up the first four VCSops like this: c_vcsops[0] = LC_VCSOP(0, LC_VCSOP_HEADER, 0); c_vcsops[1] = LC_VCSOP(0, LC_VCSOP_SINGLE, 1); c_vcsops[2] = LC_VCSOP(0, LC_VCSOP_SINGLE, 2); c_vcsops[3] = LC_VCSOP(0, LC_VCSOP_SINGLE, 3); After the first four locations the different cases diverge, but the format is generally the same. If the attribute combination requires position, color, and normal information, the above code will be replicated up to array position 9. If facet normal information is also to be included (for face distinguishing) the array will be completely filled, all the way to the 13th element: // Header, posision, color, normal, and facet normal c_vcsops[0] = LC_VCSOP(0, LC_VCSOP_HEADER, 0); c_vcsops[1] = LC_VCSOP(0, LC_VCSOP_SINGLE, 1); c_vcsops[2] = LC_VCSOP(0, LC_VCSOP_SINGLE, 2); c_vcsops[3] = LC_VCSOP(0, LC_VCSOP_SINGLE, 3); c_vcsops[4] = LC_VCSOP(0, LC_VCSOP_HEADER, 4); c_vcsops[5] = LC_VCSOP(0, LC_VCSOP_SINGLE, 5); c_vcsops[6] = LC_VCSOP(0, LC_VCSOP_SINGLE, 6); c_vcsops[7] = LC_VCSOP(0, LC_VCSOP_SINGLE, 7); c_vcsops[8] = LC_VCSOP(0, LC_VCSOP_HEADER, 8); c_vcsops[9] = LC_VCSOP(0, LC_VCSOP_SINGLE, 9); c_vcsops[10] = LC_VCSOP(0, LC_VCSOP_SINGLE, 10); c_vcsops[11] = LC_VCSOP(0, LC_VCSOP_SINGLE, 11); c_vcsops[12] = LC_VCSOP(0, LC_VCSOP_SINGLE, 12); sp->surf.cvcs.pkt_size = 13; The only case where it gets tricky is where neither a color or a normal needs to be sent down. This would happen, for example, if we were using Context Color with no lighting (see XglDpCtx3dLeo::calcSurfRendCtxNI). In this case, we need to leave space in the Vertex Buffer for a normal, because it can't send down a vertex without either a normal or a color. We call this a "Dummy" normal, and we set Nx, Ny, and Nz to point to Bucket Buffer location zero, which is the header word. It doesn't matter where it points because we are going to program the LF to ignore the normal info. This will look like this: // chained vcsops always has flag word c_vcsops[0] = LC_VCSOP(0, LC_VCSOP_HEADER, 0); c_vcsops[1] = LC_VCSOP(0, LC_VCSOP_SINGLE, 1); c_vcsops[2] = LC_VCSOP(0, LC_VCSOP_SINGLE, 2); c_vcsops[3] = LC_VCSOP(0, LC_VCSOP_SINGLE, 3); sp->surf.cvcs.pkt_size = 4; // Dummy Normal c_vcsops[4] = LC_VCSOP(0, LC_VCSOP_SINGLE, 0); c_vcsops[5] = LC_VCSOP(0, LC_VCSOP_SINGLE, 0); c_vcsops[6] = LC_VCSOP(0, LC_VCSOP_SINGLE, 0); We need to program the VCS ops with a Dummy Normal even if we are sending down a facet normal. This will look like this: // chained vcsops always has flag word c_vcsops[0] = LC_VCSOP(0, LC_VCSOP_HEADER, 0); c_vcsops[1] = LC_VCSOP(0, LC_VCSOP_SINGLE, 1); c_vcsops[2] = LC_VCSOP(0, LC_VCSOP_SINGLE, 2); c_vcsops[3] = LC_VCSOP(0, LC_VCSOP_SINGLE, 3); sp->surf.cvcs.pkt_size = 4; // Dummy Normal c_vcsops[4] = LC_VCSOP(0, LC_VCSOP_SINGLE, 0); c_vcsops[5] = LC_VCSOP(0, LC_VCSOP_SINGLE, 0); c_vcsops[6] = LC_VCSOP(0, LC_VCSOP_SINGLE, 0); // Facet NxNyNz c_vcsops[7] = LC_VCSOP(0, LC_VCSOP_SINGLE, 4); c_vcsops[8] = LC_VCSOP(0, LC_VCSOP_SINGLE, 5); c_vcsops[9] = LC_VCSOP(0, LC_VCSOP_SINGLE, 6); sp->surf.cvcs.pkt_size += 3; Note that there are only 7 words in the Bucket Buffer but we are exanding them into a 10 word vertex because we have to leave space for the Dummy Normal. The next thing we need to do is set up various attributes so that the command knows how to process the data. First we need to tell it how many of the VCSOps are valid. When we only need 6 data we only set up the first 6 VCS operations, leaving the rest invalid. This variable is cvcs_count (aka sp->surf.cvcs.vcs_count), which corresponds to the VCS Opcode Count LC Register. We also need to set the size of the Bucket Buffer packet which we are using. If we aren't using a Dummy Normal, this will be the same as the Vertex Buffer packet size above. We use sp->surf.cvcs.pkt_size to store this information, as demon- strated in the examples above. Next is sp->surf.vmc, which corresponds to the Vertex Mode Control register (pg 5-42) in the LC. This indicates to the LC the size of the packet it's going to be sending to the LF, and whether or not the packet contains a facet normal. There are two values which this variable can take: LC_VMC_XYZ_T1 and LC_VMC_XYZ_T1_T2. The correspond to packet sizes of 6 and 9 words, respectively. The first value is used for XYZ + Normal or XYZ + Color mode, the second is used for XYZ + Normal + Color mode. Each of these values can be orred with LC_VMC_NORMAL, adding three words to the size of the packet, and signifying that a facet normal is going to be sent. The sp->surf.vert_regs.of field corresponds to the LC Output Format register (pg 5-61). It tells the LF the format of the packet, and whether any of the tupples (three word groups) need to be "replicated." The packet format here must correspond to the size in the vmc field (before the 3 words for the facet normal are added). The values of LC_OF_LENGTH_XYZ_T1 and LC_OF_LENGTH_XYZ_T1_T2 correspond to 6 and 9 word packets (this field doesn't care about facet normals). The value LC_OF_REPL_T1 or LC_OF_REPL_T2 can be orred into the field signifying that a tupple should be replicated to each vertex of the triangle. This is used for Facet Color and Facet Illumination to replicate the third vertex's color or Normal to the other two vertices of the triangle. The sp->surf.vert_regs.lfd field corresponds to the LF Dispatch Opcode (pg 5-54 - 5-57). It is either LF_RGB_TRIANGLE, LF_NORM_TRIANGLE, or LF_NORM_RGB_TRIANGLE, signifying which packet type should be sent. This is the field which doesn't support a XYZ only packet format, forcing us to use Dummy Normals if we don't have per-vertex normal or color information. Another field is sp->surf.data_copy_needed, which will almost always be TRUE. LeoDataSelectors.cc was set up to support two different methods of filling the Bucket Buffer. The method described above is the "cached" method where a buffer of vertices is filled and sent when it is full. In certain cases it's faster to send the user data directly, without packaging. This is rarely used (only by a few Michael Deering programs). This is the u_vcsops which is set up in parallel with the c_vcsops array. If something about the XGL attribute setup makes it so that using the user data directly is impossible, sp->surf.data_copy_needed will be set to true, indicating this. The variable cache->leo_facet_mode will be set to true when Facet Normals are being sent down to be used for face distinguishing. It corresponds to the Facet Normals Enable/Disable attribute in the LF Microcode (see pg 61 of the Microcode Docs). Setting this attribute will always be accompanied by setting sp->surf.data_copy_needed because Facet Normals and Vertex Data are sent in two different lists in XGl, and need to be combined by XGL (and therefore the user data can't be used directly). The variable sp->surf.rend_color_src will be set to FACET_COLOR, VERTEX_COLOR, or CTX_COLOR as needed. The variable sp->surf.rend_norm_src can be set to FACET_NORMAL, NO_NORMAL, or CALC_NORMAL, indicating from where the normal information is to be fetched for lighting purposes. I'm not sure where these variables are used. Well, that's about it. This should explain LeoDataSelectors.cc pretty completely. It has been noticed by more than me that this code is Much Ado About Nothing. There are only a few valid VCS formats, and this code could be simplified by precalculating them and just assigning to the proper one in each situation. Someone tried to do this already, but didn't quite complete it (the code is taken out with the DONADA #define). Doing something like this would probably make bugs much easier to find.