Gawain: remove GL enum from primitive API
[blender.git] / intern / gawain / src / immediate.c
1
2 // Gawain immediate mode work-alike
3 //
4 // This code is part of the Gawain library, with modifications
5 // specific to integration with Blender.
6 //
7 // Copyright 2016 Mike Erwin
8 //
9 // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
10 // the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
11
12 #include "immediate.h"
13 #include "attrib_binding.h"
14 #include "buffer_id.h"
15 #include <string.h>
16
17 // necessary functions from matrix API
18 extern void gpuBindMatrices(GLuint program);
19 extern bool gpuMatricesDirty(void);
20
21 typedef struct {
22         // TODO: organize this struct by frequency of change (run-time)
23
24 #if IMM_BATCH_COMBO
25         Batch* batch;
26 #endif
27
28         // current draw call
29         GLubyte* buffer_data;
30         unsigned buffer_offset;
31         unsigned buffer_bytes_mapped;
32         unsigned vertex_ct;
33         bool strict_vertex_ct;
34         PrimitiveType prim_type;
35
36         VertexFormat vertex_format;
37
38         // current vertex
39         unsigned vertex_idx;
40         GLubyte* vertex_data;
41         uint16_t unassigned_attrib_bits; // which attributes of current vertex have not been given values?
42
43         GLuint vbo_id;
44         GLuint vao_id;
45         
46         GLuint bound_program;
47         AttribBinding attrib_binding;
48         uint16_t prev_enabled_attrib_bits; // <-- only affects this VAO, so we're ok
49 } Immediate;
50
51 // size of internal buffer -- make this adjustable?
52 #define IMM_BUFFER_SIZE (4 * 1024 * 1024)
53
54 static bool initialized = false;
55 static Immediate imm;
56
57 void immInit(void)
58         {
59 #if TRUST_NO_ONE
60         assert(!initialized);
61 #endif
62
63         memset(&imm, 0, sizeof(Immediate));
64
65         imm.vbo_id = buffer_id_alloc();
66         glBindBuffer(GL_ARRAY_BUFFER, imm.vbo_id);
67         glBufferData(GL_ARRAY_BUFFER, IMM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW);
68
69 #if APPLE_LEGACY
70         glBufferParameteriAPPLE(GL_ARRAY_BUFFER, GL_BUFFER_SERIALIZED_MODIFY_APPLE, GL_FALSE);
71         glBufferParameteriAPPLE(GL_ARRAY_BUFFER, GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE);
72 #endif
73
74         imm.prim_type = PRIM_NONE;
75         imm.strict_vertex_ct = true;
76
77         glBindBuffer(GL_ARRAY_BUFFER, 0);
78         initialized = true;
79
80         immActivate();
81         }
82
83 void immActivate(void)
84         {
85 #if TRUST_NO_ONE
86         assert(initialized);
87         assert(imm.prim_type == PRIM_NONE); // make sure we're not between a Begin/End pair
88         assert(imm.vao_id == 0);
89 #endif
90
91         imm.vao_id = vao_id_alloc();
92         }
93
94 void immDeactivate(void)
95         {
96 #if TRUST_NO_ONE
97         assert(initialized);
98         assert(imm.prim_type == PRIM_NONE); // make sure we're not between a Begin/End pair
99         assert(imm.vao_id != 0);
100 #endif
101
102         vao_id_free(imm.vao_id);
103         imm.vao_id = 0;
104         imm.prev_enabled_attrib_bits = 0;
105         }
106
107 void immDestroy(void)
108         {
109         immDeactivate();
110         buffer_id_free(imm.vbo_id);
111         initialized = false;
112         }
113
114 VertexFormat* immVertexFormat(void)
115         {
116         VertexFormat_clear(&imm.vertex_format);
117         return &imm.vertex_format;
118         }
119
120 void immBindProgram(GLuint program)
121         {
122 #if TRUST_NO_ONE
123         assert(imm.bound_program == 0);
124         assert(glIsProgram(program));
125 #endif
126
127         if (!imm.vertex_format.packed)
128                 VertexFormat_pack(&imm.vertex_format);
129
130         glUseProgram(program);
131         get_attrib_locations(&imm.vertex_format, &imm.attrib_binding, program);
132         imm.bound_program = program;
133
134         gpuBindMatrices(program);
135         }
136
137 void immUnbindProgram(void)
138         {
139 #if TRUST_NO_ONE
140         assert(imm.bound_program != 0);
141 #endif
142
143         glUseProgram(0);
144         imm.bound_program = 0;
145         }
146
147 #if TRUST_NO_ONE
148 static bool vertex_count_makes_sense_for_primitive(unsigned vertex_ct, PrimitiveType prim_type)
149         {
150         // does vertex_ct make sense for this primitive type?
151         if (vertex_ct == 0)
152                 return false;
153
154         switch (prim_type)
155                 {
156                 case PRIM_POINTS:
157                         return true;
158                 case PRIM_LINES:
159                         return vertex_ct % 2 == 0;
160                 case PRIM_LINE_STRIP:
161                 case PRIM_LINE_LOOP:
162                         return vertex_ct >= 2;
163                 case PRIM_LINE_STRIP_ADJACENCY:
164                         return vertex_ct >= 4;
165                 case PRIM_TRIANGLES:
166                         return vertex_ct % 3 == 0;
167                 case PRIM_TRIANGLE_STRIP:
168                 case PRIM_TRIANGLE_FAN:
169                         return vertex_ct >= 3;
170   #ifdef WITH_GL_PROFILE_COMPAT
171                 case PRIM_QUADS_XXX:
172                         return vertex_ct % 4 == 0;
173   #endif
174                 default:
175                         return false;
176                 }
177         }
178 #endif
179
180 void immBegin(PrimitiveType prim_type, unsigned vertex_ct)
181         {
182 #if TRUST_NO_ONE
183         assert(initialized);
184         assert(imm.prim_type == PRIM_NONE); // make sure we haven't already begun
185         assert(vertex_count_makes_sense_for_primitive(vertex_ct, prim_type));
186 #endif
187
188         imm.prim_type = prim_type;
189         imm.vertex_ct = vertex_ct;
190         imm.vertex_idx = 0;
191         imm.unassigned_attrib_bits = imm.attrib_binding.enabled_bits;
192
193         // how many bytes do we need for this draw call?
194         const unsigned bytes_needed = vertex_buffer_size(&imm.vertex_format, vertex_ct);
195
196 #if TRUST_NO_ONE
197         assert(bytes_needed <= IMM_BUFFER_SIZE);
198 #endif
199
200         glBindBuffer(GL_ARRAY_BUFFER, imm.vbo_id);
201
202         // does the current buffer have enough room?
203         const unsigned available_bytes = IMM_BUFFER_SIZE - imm.buffer_offset;
204         // ensure vertex data is aligned
205         const unsigned pre_padding = padding(imm.buffer_offset, imm.vertex_format.stride); // might waste a little space, but it's safe
206         if ((bytes_needed + pre_padding) <= available_bytes)
207                 imm.buffer_offset += pre_padding;
208         else
209                 {
210                 // orphan this buffer & start with a fresh one
211 #if APPLE_LEGACY
212                 glBufferData(GL_ARRAY_BUFFER, IMM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW);
213 #else
214                 if (GLEW_VERSION_4_3 || GLEW_ARB_invalidate_subdata)
215                         glInvalidateBufferData(imm.vbo_id);
216                 else
217                         glMapBufferRange(GL_ARRAY_BUFFER, 0, IMM_BUFFER_SIZE, GL_MAP_INVALIDATE_BUFFER_BIT);
218 #endif
219
220                 imm.buffer_offset = 0;
221                 }
222
223 //      printf("mapping %u to %u\n", imm.buffer_offset, imm.buffer_offset + bytes_needed - 1);
224
225 #if APPLE_LEGACY
226         imm.buffer_data = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY) + imm.buffer_offset;
227 #else
228         imm.buffer_data = glMapBufferRange(GL_ARRAY_BUFFER, imm.buffer_offset, bytes_needed,
229                                            GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | (imm.strict_vertex_ct ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT));
230 #endif
231
232 #if TRUST_NO_ONE
233         assert(imm.buffer_data != NULL);
234 #endif
235
236         imm.buffer_bytes_mapped = bytes_needed;
237         imm.vertex_data = imm.buffer_data;
238         }
239
240 void immBeginAtMost(PrimitiveType prim_type, unsigned vertex_ct)
241         {
242 #if TRUST_NO_ONE
243         assert(vertex_ct > 0);
244 #endif
245
246         imm.strict_vertex_ct = false;
247         immBegin(prim_type, vertex_ct);
248         }
249
250 #if IMM_BATCH_COMBO
251
252 Batch* immBeginBatch(PrimitiveType prim_type, unsigned vertex_ct)
253         {
254 #if TRUST_NO_ONE
255         assert(initialized);
256         assert(imm.prim_type == PRIM_NONE); // make sure we haven't already begun
257         assert(vertex_count_makes_sense_for_primitive(vertex_ct, prim_type));
258 #endif
259
260         imm.prim_type = prim_type;
261         imm.vertex_ct = vertex_ct;
262         imm.vertex_idx = 0;
263         imm.unassigned_attrib_bits = imm.attrib_binding.enabled_bits;
264
265         VertexBuffer* verts = VertexBuffer_create_with_format(&imm.vertex_format);
266         VertexBuffer_allocate_data(verts, vertex_ct);
267
268         imm.buffer_bytes_mapped = VertexBuffer_size(verts);
269         imm.vertex_data = verts->data;
270
271         imm.batch = Batch_create(prim_type, verts, NULL);
272         imm.batch->phase = BUILDING;
273
274         Batch_set_program(imm.batch, imm.bound_program);
275
276         return imm.batch;
277         }
278
279 Batch* immBeginBatchAtMost(PrimitiveType prim_type, unsigned vertex_ct)
280         {
281         imm.strict_vertex_ct = false;
282         return immBeginBatch(prim_type, vertex_ct);
283         }
284
285 #endif // IMM_BATCH_COMBO
286
287 static void immDrawSetup(void)
288         {
289         // set up VAO -- can be done during Begin or End really
290         glBindVertexArray(imm.vao_id);
291
292         // enable/disable vertex attribs as needed
293         if (imm.attrib_binding.enabled_bits != imm.prev_enabled_attrib_bits)
294                 {
295                 for (unsigned loc = 0; loc < MAX_VERTEX_ATTRIBS; ++loc)
296                         {
297                         bool is_enabled = imm.attrib_binding.enabled_bits & (1 << loc);
298                         bool was_enabled = imm.prev_enabled_attrib_bits & (1 << loc);
299
300                         if (is_enabled && !was_enabled)
301                                 {
302 //                              printf("enabling attrib %u\n", loc);
303                                 glEnableVertexAttribArray(loc);
304                                 }
305                         else if (was_enabled && !is_enabled)
306                                 {
307 //                              printf("disabling attrib %u\n", loc);
308                                 glDisableVertexAttribArray(loc);
309                                 }
310                         }
311
312                 imm.prev_enabled_attrib_bits = imm.attrib_binding.enabled_bits;
313                 }
314
315         const unsigned stride = imm.vertex_format.stride;
316
317         for (unsigned a_idx = 0; a_idx < imm.vertex_format.attrib_ct; ++a_idx)
318                 {
319                 const Attrib* a = imm.vertex_format.attribs + a_idx;
320
321                 const unsigned offset = imm.buffer_offset + a->offset;
322                 const GLvoid* pointer = (const GLubyte*)0 + offset;
323
324                 const unsigned loc = read_attrib_location(&imm.attrib_binding, a_idx);
325
326 //              printf("specifying attrib %u '%s' with offset %u, stride %u\n", loc, a->name, offset, stride);
327
328                 switch (a->fetch_mode)
329                         {
330                         case KEEP_FLOAT:
331                         case CONVERT_INT_TO_FLOAT:
332                                 glVertexAttribPointer(loc, a->comp_ct, a->gl_comp_type, GL_FALSE, stride, pointer);
333                                 break;
334                         case NORMALIZE_INT_TO_FLOAT:
335                                 glVertexAttribPointer(loc, a->comp_ct, a->gl_comp_type, GL_TRUE, stride, pointer);
336                                 break;
337                         case KEEP_INT:
338                                 glVertexAttribIPointer(loc, a->comp_ct, a->gl_comp_type, stride, pointer);
339                         }
340                 }
341
342         if (gpuMatricesDirty())
343                 gpuBindMatrices(imm.bound_program);
344         }
345
346 void immEnd(void)
347         {
348 #if TRUST_NO_ONE
349         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
350 #endif
351
352         unsigned buffer_bytes_used;
353         if (imm.strict_vertex_ct)
354                 {
355 #if TRUST_NO_ONE
356                 assert(imm.vertex_idx == imm.vertex_ct); // with all vertices defined
357 #endif
358                 buffer_bytes_used = imm.buffer_bytes_mapped;
359                 }
360         else
361                 {
362 #if TRUST_NO_ONE
363                 assert(imm.vertex_idx <= imm.vertex_ct);
364 #endif
365                 // printf("used %u of %u verts,", imm.vertex_idx, imm.vertex_ct);
366                 if (imm.vertex_idx == imm.vertex_ct)
367                         {
368                         buffer_bytes_used = imm.buffer_bytes_mapped;
369                         }
370                 else
371                         {
372 #if TRUST_NO_ONE
373                         assert(imm.vertex_idx == 0 || vertex_count_makes_sense_for_primitive(imm.vertex_idx, imm.prim_type));
374 #endif
375                         imm.vertex_ct = imm.vertex_idx;
376                         buffer_bytes_used = vertex_buffer_size(&imm.vertex_format, imm.vertex_ct);
377                         // unused buffer bytes are available to the next immBegin
378                         // printf(" %u of %u bytes\n", buffer_bytes_used, imm.buffer_bytes_mapped);
379                         }
380 #if !APPLE_LEGACY
381                 // tell OpenGL what range was modified so it doesn't copy the whole mapped range
382                 // printf("flushing %u to %u\n", imm.buffer_offset, imm.buffer_offset + buffer_bytes_used - 1);
383                 glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, buffer_bytes_used);
384 #endif
385                 }
386
387 #if IMM_BATCH_COMBO
388         if (imm.batch)
389                 {
390                 if (buffer_bytes_used != imm.buffer_bytes_mapped)
391                         {
392                         VertexBuffer_resize_data(imm.batch->verts[0], imm.vertex_ct);
393                         // TODO: resize only if vertex count is much smaller
394                         }
395
396                 imm.batch->phase = READY_TO_DRAW;
397                 imm.batch = NULL; // don't free, batch belongs to caller
398                 }
399         else
400 #endif
401                 {
402 #if APPLE_LEGACY
403                 // tell OpenGL what range was modified so it doesn't copy the whole buffer
404                 // printf("flushing %u to %u\n", imm.buffer_offset, imm.buffer_offset + buffer_bytes_used - 1);
405                 glFlushMappedBufferRangeAPPLE(GL_ARRAY_BUFFER, imm.buffer_offset, buffer_bytes_used);
406 #endif
407                 glUnmapBuffer(GL_ARRAY_BUFFER);
408
409                 if (imm.vertex_ct > 0)
410                         {
411                         immDrawSetup();
412                         glDrawArrays(convert_prim_type_to_gl(imm.prim_type), 0, imm.vertex_ct);
413                         }
414
415                 glBindBuffer(GL_ARRAY_BUFFER, 0);
416                 glBindVertexArray(0);
417
418                 // prep for next immBegin
419                 imm.buffer_offset += buffer_bytes_used;
420                 }
421
422         // prep for next immBegin
423         imm.prim_type = PRIM_NONE;
424         imm.strict_vertex_ct = true;
425         }
426
427 static void setAttribValueBit(unsigned attrib_id)
428         {
429         uint16_t mask = 1 << attrib_id;
430
431 #if TRUST_NO_ONE
432         assert(imm.unassigned_attrib_bits & mask); // not already set
433 #endif
434
435         imm.unassigned_attrib_bits &= ~mask;
436         }
437
438
439 // --- generic attribute functions ---
440
441 void immAttrib1f(unsigned attrib_id, float x)
442         {
443         Attrib* attrib = imm.vertex_format.attribs + attrib_id;
444
445 #if TRUST_NO_ONE
446         assert(attrib_id < imm.vertex_format.attrib_ct);
447         assert(attrib->comp_type == COMP_F32);
448         assert(attrib->comp_ct == 1);
449         assert(imm.vertex_idx < imm.vertex_ct);
450         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
451 #endif
452
453         setAttribValueBit(attrib_id);
454
455         float* data = (float*)(imm.vertex_data + attrib->offset);
456 //      printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm.buffer_data, data);
457
458         data[0] = x;
459         }
460
461 void immAttrib2f(unsigned attrib_id, float x, float y)
462         {
463         Attrib* attrib = imm.vertex_format.attribs + attrib_id;
464
465 #if TRUST_NO_ONE
466         assert(attrib_id < imm.vertex_format.attrib_ct);
467         assert(attrib->comp_type == COMP_F32);
468         assert(attrib->comp_ct == 2);
469         assert(imm.vertex_idx < imm.vertex_ct);
470         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
471 #endif
472
473         setAttribValueBit(attrib_id);
474
475         float* data = (float*)(imm.vertex_data + attrib->offset);
476 //      printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm.buffer_data, data);
477
478         data[0] = x;
479         data[1] = y;
480         }
481
482 void immAttrib3f(unsigned attrib_id, float x, float y, float z)
483         {
484         Attrib* attrib = imm.vertex_format.attribs + attrib_id;
485
486 #if TRUST_NO_ONE
487         assert(attrib_id < imm.vertex_format.attrib_ct);
488         assert(attrib->comp_type == COMP_F32);
489         assert(attrib->comp_ct == 3);
490         assert(imm.vertex_idx < imm.vertex_ct);
491         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
492 #endif
493
494         setAttribValueBit(attrib_id);
495
496         float* data = (float*)(imm.vertex_data + attrib->offset);
497 //      printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm.buffer_data, data);
498
499         data[0] = x;
500         data[1] = y;
501         data[2] = z;
502         }
503
504 void immAttrib4f(unsigned attrib_id, float x, float y, float z, float w)
505         {
506         Attrib* attrib = imm.vertex_format.attribs + attrib_id;
507
508 #if TRUST_NO_ONE
509         assert(attrib_id < imm.vertex_format.attrib_ct);
510         assert(attrib->comp_type == COMP_F32);
511         assert(attrib->comp_ct == 4);
512         assert(imm.vertex_idx < imm.vertex_ct);
513         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
514 #endif
515
516         setAttribValueBit(attrib_id);
517
518         float* data = (float*)(imm.vertex_data + attrib->offset);
519 //      printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm.buffer_data, data);
520
521         data[0] = x;
522         data[1] = y;
523         data[2] = z;
524         data[3] = w;
525         }
526
527 void immAttrib2i(unsigned attrib_id, int x, int y)
528         {
529         Attrib* attrib = imm.vertex_format.attribs + attrib_id;
530
531 #if TRUST_NO_ONE
532         assert(attrib_id < imm.vertex_format.attrib_ct);
533         assert(attrib->comp_type == COMP_I32);
534         assert(attrib->comp_ct == 2);
535         assert(imm.vertex_idx < imm.vertex_ct);
536         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
537 #endif
538
539         setAttribValueBit(attrib_id);
540
541         int* data = (int*)(imm.vertex_data + attrib->offset);
542
543         data[0] = x;
544         data[1] = y;
545         }
546
547 void immAttrib2s(unsigned attrib_id, short x, short y)
548         {
549         Attrib* attrib = imm.vertex_format.attribs + attrib_id;
550
551 #if TRUST_NO_ONE
552         assert(attrib_id < imm.vertex_format.attrib_ct);
553         assert(attrib->comp_type == COMP_I16);
554         assert(attrib->comp_ct == 2);
555         assert(imm.vertex_idx < imm.vertex_ct);
556         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
557 #endif
558
559         setAttribValueBit(attrib_id);
560
561         short* data = (short*)(imm.vertex_data + attrib->offset);
562
563         data[0] = x;
564         data[1] = y;
565         }
566
567 void immAttrib3fv(unsigned attrib_id, const float data[3])
568         {
569         immAttrib3f(attrib_id, data[0], data[1], data[2]);
570         }
571
572 void immAttrib4fv(unsigned attrib_id, const float data[4])
573         {
574         immAttrib4f(attrib_id, data[0], data[1], data[2], data[3]);
575         }
576
577 void immAttrib3ub(unsigned attrib_id, unsigned char r, unsigned char g, unsigned char b)
578         {
579         Attrib* attrib = imm.vertex_format.attribs + attrib_id;
580
581 #if TRUST_NO_ONE
582         assert(attrib_id < imm.vertex_format.attrib_ct);
583         assert(attrib->comp_type == COMP_U8);
584         assert(attrib->comp_ct == 3);
585         assert(imm.vertex_idx < imm.vertex_ct);
586         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
587 #endif
588
589         setAttribValueBit(attrib_id);
590
591         GLubyte* data = imm.vertex_data + attrib->offset;
592 //      printf("%s %td %p\n", __FUNCTION__, data - imm.buffer_data, data);
593
594         data[0] = r;
595         data[1] = g;
596         data[2] = b;
597         }
598
599 void immAttrib4ub(unsigned attrib_id, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
600         {
601         Attrib* attrib = imm.vertex_format.attribs + attrib_id;
602
603 #if TRUST_NO_ONE
604         assert(attrib_id < imm.vertex_format.attrib_ct);
605         assert(attrib->comp_type == COMP_U8);
606         assert(attrib->comp_ct == 4);
607         assert(imm.vertex_idx < imm.vertex_ct);
608         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
609 #endif
610
611         setAttribValueBit(attrib_id);
612
613         GLubyte* data = imm.vertex_data + attrib->offset;
614 //      printf("%s %td %p\n", __FUNCTION__, data - imm.buffer_data, data);
615
616         data[0] = r;
617         data[1] = g;
618         data[2] = b;
619         data[3] = a;
620         }
621
622 void immAttrib3ubv(unsigned attrib_id, const unsigned char data[3])
623         {
624         immAttrib3ub(attrib_id, data[0], data[1], data[2]);
625         }
626
627 void immAttrib4ubv(unsigned attrib_id, const unsigned char data[4])
628         {
629         immAttrib4ub(attrib_id, data[0], data[1], data[2], data[3]);
630         }
631
632 void immSkipAttrib(unsigned attrib_id)
633         {
634 #if TRUST_NO_ONE
635         assert(attrib_id < imm.vertex_format.attrib_ct);
636         assert(imm.vertex_idx < imm.vertex_ct);
637         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
638 #endif
639
640         setAttribValueBit(attrib_id);
641         }
642
643 static void immEndVertex(void) // and move on to the next vertex
644         {
645 #if TRUST_NO_ONE
646         assert(imm.prim_type != PRIM_NONE); // make sure we're between a Begin/End pair
647         assert(imm.vertex_idx < imm.vertex_ct);
648 #endif
649
650         // have all attribs been assigned values?
651         // if not, copy value from previous vertex
652         if (imm.unassigned_attrib_bits)
653                 {
654 #if TRUST_NO_ONE
655                 assert(imm.vertex_idx > 0); // first vertex must have all attribs specified
656 #endif
657
658                 for (unsigned a_idx = 0; a_idx < imm.vertex_format.attrib_ct; ++a_idx)
659                         {
660                         if ((imm.unassigned_attrib_bits >> a_idx) & 1)
661                                 {
662                                 const Attrib* a = imm.vertex_format.attribs + a_idx;
663
664 //                              printf("copying %s from vertex %u to %u\n", a->name, imm.vertex_idx - 1, imm.vertex_idx);
665
666                                 GLubyte* data = imm.vertex_data + a->offset;
667                                 memcpy(data, data - imm.vertex_format.stride, a->sz);
668                                 // TODO: consolidate copy of adjacent attributes
669                                 }
670                         }
671                 }
672
673         imm.vertex_idx++;
674         imm.vertex_data += imm.vertex_format.stride;
675         imm.unassigned_attrib_bits = imm.attrib_binding.enabled_bits;
676         }
677
678 void immVertex2f(unsigned attrib_id, float x, float y)
679         {
680         immAttrib2f(attrib_id, x, y);
681         immEndVertex();
682         }
683
684 void immVertex3f(unsigned attrib_id, float x, float y, float z)
685         {
686         immAttrib3f(attrib_id, x, y, z);
687         immEndVertex();
688         }
689
690 void immVertex2i(unsigned attrib_id, int x, int y)
691         {
692         immAttrib2i(attrib_id, x, y);
693         immEndVertex();
694         }
695
696 void immVertex2s(unsigned attrib_id, short x, short y)
697         {
698         immAttrib2s(attrib_id, x, y);
699         immEndVertex();
700         }
701
702 void immVertex2fv(unsigned attrib_id, const float data[2])
703         {
704         immAttrib2f(attrib_id, data[0], data[1]);
705         immEndVertex();
706         }
707
708 void immVertex3fv(unsigned attrib_id, const float data[3])
709         {
710         immAttrib3f(attrib_id, data[0], data[1], data[2]);
711         immEndVertex();
712         }
713
714 void immVertex2iv(unsigned attrib_id, const int data[2])
715         {
716         immAttrib2i(attrib_id, data[0], data[1]);
717         immEndVertex();
718         }
719
720
721 // --- generic uniform functions ---
722
723 void immUniform1f(const char* name, float x)
724         {
725         int loc = glGetUniformLocation(imm.bound_program, name);
726
727 #if TRUST_NO_ONE
728         assert(loc != -1);
729 #endif
730
731         glUniform1f(loc, x);
732         }
733
734 void immUniform2f(const char* name, float x, float y)
735 {
736         int loc = glGetUniformLocation(imm.bound_program, name);
737
738 #if TRUST_NO_ONE
739         assert(loc != -1);
740 #endif
741
742         glUniform2f(loc, x, y);
743 }
744
745 void immUniform2fv(const char* name, const float data[2])
746 {
747         int loc = glGetUniformLocation(imm.bound_program, name);
748
749 #if TRUST_NO_ONE
750         assert(loc != -1);
751 #endif
752
753         glUniform2fv(loc, 1, data);
754 }
755
756 void immUniform3f(const char* name, float x, float y, float z)
757         {
758         int loc = glGetUniformLocation(imm.bound_program, name);
759
760 #if TRUST_NO_ONE
761         assert(loc != -1);
762 #endif
763
764         glUniform3f(loc, x, y, z);
765         }
766
767 void immUniform3fv(const char* name, const float data[3])
768         {
769         int loc = glGetUniformLocation(imm.bound_program, name);
770
771 #if TRUST_NO_ONE
772         assert(loc != -1);
773 #endif
774
775         glUniform3fv(loc, 1, data);
776         }
777
778 void immUniformArray3fv(const char* name, const float *data, int count)
779         {
780         int loc = glGetUniformLocation(imm.bound_program, name);
781
782 #if TRUST_NO_ONE
783         assert(loc != -1);
784         assert(count > 0);
785 #endif
786
787         glUniform3fv(loc, count, data);
788         }
789
790 void immUniform4f(const char* name, float x, float y, float z, float w)
791         {
792         int loc = glGetUniformLocation(imm.bound_program, name);
793
794 #if TRUST_NO_ONE
795         assert(loc != -1);
796 #endif
797
798         glUniform4f(loc, x, y, z, w);
799         }
800
801 void immUniform4fv(const char* name, const float data[4])
802         {
803         int loc = glGetUniformLocation(imm.bound_program, name);
804
805 #if TRUST_NO_ONE
806         assert(loc != -1);
807 #endif
808
809         glUniform4fv(loc, 1, data);
810         }
811
812 void immUniformMatrix4fv(const char* name, const float data[4][4])
813         {
814         int loc = glGetUniformLocation(imm.bound_program, name);
815
816 #if TRUST_NO_ONE
817         assert(loc != -1);
818 #endif
819
820         glUniformMatrix4fv(loc, 1, GL_FALSE, (float *)data);
821         }
822
823 void immUniform1i(const char* name, int x)
824         {
825         int loc = glGetUniformLocation(imm.bound_program, name);
826
827 #if TRUST_NO_ONE
828         assert(loc != -1);
829 #endif
830
831         glUniform1i(loc, x);
832         }
833
834
835 // --- convenience functions for setting "uniform vec4 color" ---
836
837 void immUniformColor4f(float r, float g, float b, float a)
838         {
839         immUniform4f("color", r, g, b, a);
840         }
841
842 void immUniformColor4fv(const float rgba[4])
843         {
844         immUniform4fv("color", rgba);
845         }
846
847 void immUniformColor3f(float r, float g, float b)
848         {
849         immUniform4f("color", r, g, b, 1.0f);
850         }
851
852 void immUniformColor3fv(const float rgb[3])
853         {
854         immUniform4f("color", rgb[0], rgb[1], rgb[2], 1.0f);
855         }
856
857 void immUniformColor3fvAlpha(const float rgb[3], float a)
858         {
859         immUniform4f("color", rgb[0], rgb[1], rgb[2], a);
860         }
861
862 // TODO: v-- treat as sRGB? --v
863
864 void immUniformColor3ub(unsigned char r, unsigned char g, unsigned char b)
865         {
866         const float scale = 1.0f / 255.0f;
867         immUniform4f("color", scale * r, scale * g, scale * b, 1.0f);
868         }
869
870 void immUniformColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
871         {
872         const float scale = 1.0f / 255.0f;
873         immUniform4f("color", scale * r, scale * g, scale * b, scale * a);
874         }
875
876 void immUniformColor3ubv(const unsigned char rgb[3])
877         {
878         immUniformColor3ub(rgb[0], rgb[1], rgb[2]);
879         }
880
881 void immUniformColor4ubv(const unsigned char rgba[4])
882         {
883         immUniformColor4ub(rgba[0], rgba[1], rgba[2], rgba[3]);
884         }