1/************************************************************************** 2 3Copyright 2002-2008 VMware, Inc. 4 5All Rights Reserved. 6 7Permission is hereby granted, free of charge, to any person obtaining a 8copy of this software and associated documentation files (the "Software"), 9to deal in the Software without restriction, including without limitation 10on the rights to use, copy, modify, merge, publish, distribute, sub 11license, and/or sell copies of the Software, and to permit persons to whom 12the Software is furnished to do so, subject to the following conditions: 13 14The above copyright notice and this permission notice (including the next 15paragraph) shall be included in all copies or substantial portions of the 16Software. 17 18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 21VMWARE AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, 22DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 23OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 24USE OR OTHER DEALINGS IN THE SOFTWARE. 25 26**************************************************************************/ 27 28/* 29 * Authors: 30 * Keith Whitwell <keithw@vmware.com> 31 */ 32 33#include "main/glheader.h" 34#include "main/bufferobj.h" 35#include "main/context.h" 36#include "main/macros.h" 37#include "main/vtxfmt.h" 38#include "main/dlist.h" 39#include "main/eval.h" 40#include "main/state.h" 41#include "main/light.h" 42#include "main/api_arrayelt.h" 43#include "main/draw_validate.h" 44#include "main/dispatch.h" 45#include "util/bitscan.h" 46#include "util/u_memory.h" 47 48#include "vbo_noop.h" 49#include "vbo_private.h" 50 51 52/** ID/name for immediate-mode VBO */ 53#define IMM_BUFFER_NAME 0xaabbccdd 54 55 56static void GLAPIENTRY 57vbo_exec_Materialfv(GLenum face, GLenum pname, const GLfloat *params); 58 59static void GLAPIENTRY 60vbo_exec_EvalCoord1f(GLfloat u); 61 62static void GLAPIENTRY 63vbo_exec_EvalCoord2f(GLfloat u, GLfloat v); 64 65 66static void 67vbo_reset_all_attr(struct vbo_exec_context *exec); 68 69 70/** 71 * Close off the last primitive, execute the buffer, restart the 72 * primitive. This is called when we fill a vertex buffer before 73 * hitting glEnd. 74 */ 75static void 76vbo_exec_wrap_buffers(struct vbo_exec_context *exec) 77{ 78 if (exec->vtx.prim_count == 0) { 79 exec->vtx.copied.nr = 0; 80 exec->vtx.vert_count = 0; 81 exec->vtx.buffer_ptr = exec->vtx.buffer_map; 82 } 83 else { 84 struct gl_context *ctx = gl_context_from_vbo_exec(exec); 85 unsigned last = exec->vtx.prim_count - 1; 86 struct pipe_draw_start_count_bias *last_draw = &exec->vtx.draw[last]; 87 const bool last_begin = exec->vtx.markers[last].begin; 88 GLuint last_count = 0; 89 90 if (_mesa_inside_begin_end(ctx)) { 91 last_draw->count = exec->vtx.vert_count - last_draw->start; 92 last_count = last_draw->count; 93 exec->vtx.markers[last].end = 0; 94 } 95 96 /* Special handling for wrapping GL_LINE_LOOP */ 97 if (exec->vtx.mode[last] == GL_LINE_LOOP && 98 last_count > 0 && 99 !exec->vtx.markers[last].end) { 100 /* draw this section of the incomplete line loop as a line strip */ 101 exec->vtx.mode[last] = GL_LINE_STRIP; 102 if (!last_begin) { 103 /* This is not the first section of the line loop, so don't 104 * draw the 0th vertex. We're saving it until we draw the 105 * very last section of the loop. 106 */ 107 last_draw->start++; 108 last_draw->count--; 109 } 110 } 111 112 /* Execute the buffer and save copied vertices. 113 */ 114 if (exec->vtx.vert_count) 115 vbo_exec_vtx_flush(exec); 116 else { 117 exec->vtx.prim_count = 0; 118 exec->vtx.copied.nr = 0; 119 } 120 121 /* Emit a glBegin to start the new list. 122 */ 123 assert(exec->vtx.prim_count == 0); 124 125 if (_mesa_inside_begin_end(ctx)) { 126 exec->vtx.mode[0] = ctx->Driver.CurrentExecPrimitive; 127 exec->vtx.draw[0].start = 0; 128 exec->vtx.markers[0].begin = 0; 129 exec->vtx.prim_count++; 130 131 if (exec->vtx.copied.nr == last_count) 132 exec->vtx.markers[0].begin = last_begin; 133 } 134 } 135} 136 137 138/** 139 * Deal with buffer wrapping where provoked by the vertex buffer 140 * filling up, as opposed to upgrade_vertex(). 141 */ 142static void 143vbo_exec_vtx_wrap(struct vbo_exec_context *exec) 144{ 145 unsigned numComponents; 146 147 /* Run pipeline on current vertices, copy wrapped vertices 148 * to exec->vtx.copied. 149 */ 150 vbo_exec_wrap_buffers(exec); 151 152 if (!exec->vtx.buffer_ptr) { 153 /* probably ran out of memory earlier when allocating the VBO */ 154 return; 155 } 156 157 /* Copy stored stored vertices to start of new list. 158 */ 159 assert(exec->vtx.max_vert - exec->vtx.vert_count > exec->vtx.copied.nr); 160 161 numComponents = exec->vtx.copied.nr * exec->vtx.vertex_size; 162 memcpy(exec->vtx.buffer_ptr, 163 exec->vtx.copied.buffer, 164 numComponents * sizeof(fi_type)); 165 exec->vtx.buffer_ptr += numComponents; 166 exec->vtx.vert_count += exec->vtx.copied.nr; 167 168 exec->vtx.copied.nr = 0; 169} 170 171 172/** 173 * Copy the active vertex's values to the ctx->Current fields. 174 */ 175static void 176vbo_exec_copy_to_current(struct vbo_exec_context *exec) 177{ 178 struct gl_context *ctx = gl_context_from_vbo_exec(exec); 179 struct vbo_context *vbo = vbo_context(ctx); 180 GLbitfield64 enabled = exec->vtx.enabled & (~BITFIELD64_BIT(VBO_ATTRIB_POS)); 181 bool color0_changed = false; 182 183 while (enabled) { 184 const int i = u_bit_scan64(&enabled); 185 186 /* Note: the exec->vtx.current[i] pointers point into the 187 * ctx->Current.Attrib and ctx->Light.Material.Attrib arrays. 188 */ 189 GLfloat *current = (GLfloat *)vbo->current[i].Ptr; 190 fi_type tmp[8]; /* space for doubles */ 191 int dmul_shift = 0; 192 193 assert(exec->vtx.attr[i].size); 194 195 if (exec->vtx.attr[i].type == GL_DOUBLE || 196 exec->vtx.attr[i].type == GL_UNSIGNED_INT64_ARB) { 197 memset(tmp, 0, sizeof(tmp)); 198 memcpy(tmp, exec->vtx.attrptr[i], exec->vtx.attr[i].size * sizeof(GLfloat)); 199 dmul_shift = 1; 200 } else { 201 COPY_CLEAN_4V_TYPE_AS_UNION(tmp, 202 exec->vtx.attr[i].size, 203 exec->vtx.attrptr[i], 204 exec->vtx.attr[i].type); 205 } 206 207 if (memcmp(current, tmp, 4 * sizeof(GLfloat) << dmul_shift) != 0) { 208 memcpy(current, tmp, 4 * sizeof(GLfloat) << dmul_shift); 209 210 if (i == VBO_ATTRIB_COLOR0) 211 color0_changed = true; 212 213 if (i >= VBO_ATTRIB_MAT_FRONT_AMBIENT) { 214 ctx->NewState |= _NEW_MATERIAL; 215 ctx->PopAttribState |= GL_LIGHTING_BIT; 216 217 /* The fixed-func vertex program uses this. */ 218 if (i == VBO_ATTRIB_MAT_FRONT_SHININESS || 219 i == VBO_ATTRIB_MAT_BACK_SHININESS) 220 ctx->NewState |= _NEW_FF_VERT_PROGRAM; 221 } else { 222 ctx->NewState |= _NEW_CURRENT_ATTRIB; 223 ctx->PopAttribState |= GL_CURRENT_BIT; 224 } 225 } 226 227 /* Given that we explicitly state size here, there is no need 228 * for the COPY_CLEAN above, could just copy 16 bytes and be 229 * done. The only problem is when Mesa accesses ctx->Current 230 * directly. 231 */ 232 /* Size here is in components - not bytes */ 233 if (exec->vtx.attr[i].type != vbo->current[i].Format.Type || 234 (exec->vtx.attr[i].size >> dmul_shift) != vbo->current[i].Format.Size) { 235 vbo_set_vertex_format(&vbo->current[i].Format, 236 exec->vtx.attr[i].size >> dmul_shift, 237 exec->vtx.attr[i].type); 238 } 239 } 240 241 if (color0_changed && ctx->Light.ColorMaterialEnabled) { 242 _mesa_update_color_material(ctx, 243 ctx->Current.Attrib[VBO_ATTRIB_COLOR0]); 244 } 245} 246 247 248/** 249 * Flush existing data, set new attrib size, replay copied vertices. 250 * This is called when we transition from a small vertex attribute size 251 * to a larger one. Ex: glTexCoord2f -> glTexCoord4f. 252 * We need to go back over the previous 2-component texcoords and insert 253 * zero and one values. 254 * \param attr VBO_ATTRIB_x vertex attribute value 255 */ 256static void 257vbo_exec_wrap_upgrade_vertex(struct vbo_exec_context *exec, 258 GLuint attr, GLuint newSize, GLenum newType) 259{ 260 struct gl_context *ctx = gl_context_from_vbo_exec(exec); 261 struct vbo_context *vbo = vbo_context(ctx); 262 const GLint lastcount = exec->vtx.vert_count; 263 fi_type *old_attrptr[VBO_ATTRIB_MAX]; 264 const GLuint old_vtx_size_no_pos = exec->vtx.vertex_size_no_pos; 265 const GLuint old_vtx_size = exec->vtx.vertex_size; /* floats per vertex */ 266 const GLuint oldSize = exec->vtx.attr[attr].size; 267 GLuint i; 268 269 assert(attr < VBO_ATTRIB_MAX); 270 271 if (unlikely(!exec->vtx.buffer_ptr)) { 272 /* We should only hit this when use_buffer_objects=true */ 273 assert(exec->vtx.bufferobj); 274 vbo_exec_vtx_map(exec); 275 assert(exec->vtx.buffer_ptr); 276 } 277 278 /* Run pipeline on current vertices, copy wrapped vertices 279 * to exec->vtx.copied. 280 */ 281 vbo_exec_wrap_buffers(exec); 282 283 if (unlikely(exec->vtx.copied.nr)) { 284 /* We're in the middle of a primitive, keep the old vertex 285 * format around to be able to translate the copied vertices to 286 * the new format. 287 */ 288 memcpy(old_attrptr, exec->vtx.attrptr, sizeof(old_attrptr)); 289 } 290 291 /* Heuristic: Attempt to isolate attributes received outside 292 * begin/end so that they don't bloat the vertices. 293 */ 294 if (!_mesa_inside_begin_end(ctx) && 295 !oldSize && lastcount > 8 && exec->vtx.vertex_size) { 296 vbo_exec_copy_to_current(exec); 297 vbo_reset_all_attr(exec); 298 } 299 300 /* Fix up sizes: 301 */ 302 exec->vtx.attr[attr].size = newSize; 303 exec->vtx.attr[attr].active_size = newSize; 304 exec->vtx.attr[attr].type = newType; 305 exec->vtx.vertex_size += newSize - oldSize; 306 exec->vtx.vertex_size_no_pos = exec->vtx.vertex_size - exec->vtx.attr[0].size; 307 exec->vtx.max_vert = vbo_compute_max_verts(exec); 308 exec->vtx.vert_count = 0; 309 exec->vtx.buffer_ptr = exec->vtx.buffer_map; 310 exec->vtx.enabled |= BITFIELD64_BIT(attr); 311 312 if (attr != 0) { 313 if (unlikely(oldSize)) { 314 unsigned offset = exec->vtx.attrptr[attr] - exec->vtx.vertex; 315 316 /* If there are attribs after the resized attrib... */ 317 if (offset + oldSize < old_vtx_size_no_pos) { 318 int size_diff = newSize - oldSize; 319 fi_type *old_first = exec->vtx.attrptr[attr] + oldSize; 320 fi_type *new_first = exec->vtx.attrptr[attr] + newSize; 321 fi_type *old_last = exec->vtx.vertex + old_vtx_size_no_pos - 1; 322 fi_type *new_last = exec->vtx.vertex + exec->vtx.vertex_size_no_pos - 1; 323 324 if (size_diff < 0) { 325 /* Decreasing the size: Copy from first to last to move 326 * elements to the left. 327 */ 328 fi_type *old_end = old_last + 1; 329 fi_type *old = old_first; 330 fi_type *new = new_first; 331 332 do { 333 *new++ = *old++; 334 } while (old != old_end); 335 } else { 336 /* Increasing the size: Copy from last to first to move 337 * elements to the right. 338 */ 339 fi_type *old_end = old_first - 1; 340 fi_type *old = old_last; 341 fi_type *new = new_last; 342 343 do { 344 *new-- = *old--; 345 } while (old != old_end); 346 } 347 348 /* Update pointers to attribs, because we moved them. */ 349 GLbitfield64 enabled = exec->vtx.enabled & 350 ~BITFIELD64_BIT(VBO_ATTRIB_POS) & 351 ~BITFIELD64_BIT(attr); 352 while (enabled) { 353 unsigned i = u_bit_scan64(&enabled); 354 355 if (exec->vtx.attrptr[i] > exec->vtx.attrptr[attr]) 356 exec->vtx.attrptr[i] += size_diff; 357 } 358 } 359 } else { 360 /* Just have to append the new attribute at the end */ 361 exec->vtx.attrptr[attr] = exec->vtx.vertex + 362 exec->vtx.vertex_size_no_pos - newSize; 363 } 364 } 365 366 /* The position is always last. */ 367 exec->vtx.attrptr[0] = exec->vtx.vertex + exec->vtx.vertex_size_no_pos; 368 369 /* Replay stored vertices to translate them 370 * to new format here. 371 * 372 * -- No need to replay - just copy piecewise 373 */ 374 if (unlikely(exec->vtx.copied.nr)) { 375 fi_type *data = exec->vtx.copied.buffer; 376 fi_type *dest = exec->vtx.buffer_ptr; 377 378 assert(exec->vtx.buffer_ptr == exec->vtx.buffer_map); 379 380 for (i = 0 ; i < exec->vtx.copied.nr ; i++) { 381 GLbitfield64 enabled = exec->vtx.enabled; 382 while (enabled) { 383 const int j = u_bit_scan64(&enabled); 384 GLuint sz = exec->vtx.attr[j].size; 385 GLint old_offset = old_attrptr[j] - exec->vtx.vertex; 386 GLint new_offset = exec->vtx.attrptr[j] - exec->vtx.vertex; 387 388 assert(sz); 389 390 if (j == attr) { 391 if (oldSize) { 392 fi_type tmp[4]; 393 COPY_CLEAN_4V_TYPE_AS_UNION(tmp, oldSize, 394 data + old_offset, 395 exec->vtx.attr[j].type); 396 COPY_SZ_4V(dest + new_offset, newSize, tmp); 397 } else { 398 fi_type *current = (fi_type *)vbo->current[j].Ptr; 399 COPY_SZ_4V(dest + new_offset, sz, current); 400 } 401 } 402 else { 403 COPY_SZ_4V(dest + new_offset, sz, data + old_offset); 404 } 405 } 406 407 data += old_vtx_size; 408 dest += exec->vtx.vertex_size; 409 } 410 411 exec->vtx.buffer_ptr = dest; 412 exec->vtx.vert_count += exec->vtx.copied.nr; 413 exec->vtx.copied.nr = 0; 414 } 415} 416 417 418/** 419 * This is when a vertex attribute transitions to a different size. 420 * For example, we saw a bunch of glTexCoord2f() calls and now we got a 421 * glTexCoord4f() call. We promote the array from size=2 to size=4. 422 * \param newSize size of new vertex (number of 32-bit words). 423 * \param attr VBO_ATTRIB_x vertex attribute value 424 */ 425static void 426vbo_exec_fixup_vertex(struct gl_context *ctx, GLuint attr, 427 GLuint newSize, GLenum newType) 428{ 429 struct vbo_exec_context *exec = &vbo_context(ctx)->exec; 430 431 assert(attr < VBO_ATTRIB_MAX); 432 433 if (newSize > exec->vtx.attr[attr].size || 434 newType != exec->vtx.attr[attr].type) { 435 /* New size is larger. Need to flush existing vertices and get 436 * an enlarged vertex format. 437 */ 438 vbo_exec_wrap_upgrade_vertex(exec, attr, newSize, newType); 439 } 440 else if (newSize < exec->vtx.attr[attr].active_size) { 441 GLuint i; 442 const fi_type *id = 443 vbo_get_default_vals_as_union(exec->vtx.attr[attr].type); 444 445 /* New size is smaller - just need to fill in some 446 * zeros. Don't need to flush or wrap. 447 */ 448 for (i = newSize; i <= exec->vtx.attr[attr].size; i++) 449 exec->vtx.attrptr[attr][i-1] = id[i-1]; 450 451 exec->vtx.attr[attr].active_size = newSize; 452 } 453} 454 455 456/** 457 * If index=0, does glVertexAttrib*() alias glVertex() to emit a vertex? 458 * It depends on a few things, including whether we're inside or outside 459 * of glBegin/glEnd. 460 */ 461static inline bool 462is_vertex_position(const struct gl_context *ctx, GLuint index) 463{ 464 return (index == 0 && 465 _mesa_attr_zero_aliases_vertex(ctx) && 466 _mesa_inside_begin_end(ctx)); 467} 468 469/* Write a 64-bit value into a 32-bit pointer by preserving endianness. */ 470#if UTIL_ARCH_LITTLE_ENDIAN 471 #define SET_64BIT(dst32, u64) do { \ 472 *(dst32)++ = (u64); \ 473 *(dst32)++ = (uint64_t)(u64) >> 32; \ 474 } while (0) 475#else 476 #define SET_64BIT(dst32, u64) do { \ 477 *(dst32)++ = (uint64_t)(u64) >> 32; \ 478 *(dst32)++ = (u64); \ 479 } while (0) 480#endif 481 482 483/** 484 * This macro is used to implement all the glVertex, glColor, glTexCoord, 485 * glVertexAttrib, etc functions. 486 * \param A VBO_ATTRIB_x attribute index 487 * \param N attribute size (1..4) 488 * \param T type (GL_FLOAT, GL_DOUBLE, GL_INT, GL_UNSIGNED_INT) 489 * \param C cast type (uint32_t or uint64_t) 490 * \param V0, V1, v2, V3 attribute value 491 */ 492#define ATTR_UNION(A, N, T, C, V0, V1, V2, V3) \ 493do { \ 494 struct vbo_exec_context *exec = &vbo_context(ctx)->exec; \ 495 int sz = (sizeof(C) / sizeof(GLfloat)); \ 496 \ 497 assert(sz == 1 || sz == 2); \ 498 /* store a copy of the attribute in exec except for glVertex */ \ 499 if ((A) != 0) { \ 500 /* Check if attribute size or type is changing. */ \ 501 if (unlikely(exec->vtx.attr[A].active_size != N * sz || \ 502 exec->vtx.attr[A].type != T)) { \ 503 vbo_exec_fixup_vertex(ctx, A, N * sz, T); \ 504 } \ 505 \ 506 C *dest = (C *)exec->vtx.attrptr[A]; \ 507 if (N>0) dest[0] = V0; \ 508 if (N>1) dest[1] = V1; \ 509 if (N>2) dest[2] = V2; \ 510 if (N>3) dest[3] = V3; \ 511 assert(exec->vtx.attr[A].type == T); \ 512 \ 513 /* we now have accumulated a per-vertex attribute */ \ 514 ctx->Driver.NeedFlush |= FLUSH_UPDATE_CURRENT; \ 515 } else { \ 516 /* This is a glVertex call */ \ 517 int size = exec->vtx.attr[0].size; \ 518 \ 519 /* Check if attribute size or type is changing. */ \ 520 if (unlikely(size < N * sz || \ 521 exec->vtx.attr[0].type != T)) { \ 522 vbo_exec_wrap_upgrade_vertex(exec, 0, N * sz, T); \ 523 } \ 524 \ 525 uint32_t *dst = (uint32_t *)exec->vtx.buffer_ptr; \ 526 uint32_t *src = (uint32_t *)exec->vtx.vertex; \ 527 unsigned vertex_size_no_pos = exec->vtx.vertex_size_no_pos; \ 528 \ 529 /* Copy over attributes from exec. */ \ 530 for (unsigned i = 0; i < vertex_size_no_pos; i++) \ 531 *dst++ = *src++; \ 532 \ 533 /* Store the position, which is always last and can have 32 or */ \ 534 /* 64 bits per channel. */ \ 535 if (sizeof(C) == 4) { \ 536 if (N > 0) *dst++ = V0; \ 537 if (N > 1) *dst++ = V1; \ 538 if (N > 2) *dst++ = V2; \ 539 if (N > 3) *dst++ = V3; \ 540 \ 541 if (unlikely(N < size)) { \ 542 if (N < 2 && size >= 2) *dst++ = V1; \ 543 if (N < 3 && size >= 3) *dst++ = V2; \ 544 if (N < 4 && size >= 4) *dst++ = V3; \ 545 } \ 546 } else { \ 547 /* 64 bits: dst can be unaligned, so copy each 4-byte word */ \ 548 /* separately */ \ 549 if (N > 0) SET_64BIT(dst, V0); \ 550 if (N > 1) SET_64BIT(dst, V1); \ 551 if (N > 2) SET_64BIT(dst, V2); \ 552 if (N > 3) SET_64BIT(dst, V3); \ 553 \ 554 if (unlikely(N * 2 < size)) { \ 555 if (N < 2 && size >= 4) SET_64BIT(dst, V1); \ 556 if (N < 3 && size >= 6) SET_64BIT(dst, V2); \ 557 if (N < 4 && size >= 8) SET_64BIT(dst, V3); \ 558 } \ 559 } \ 560 \ 561 /* dst now points at the beginning of the next vertex */ \ 562 exec->vtx.buffer_ptr = (fi_type*)dst; \ 563 \ 564 /* Don't set FLUSH_UPDATE_CURRENT because */ \ 565 /* Current.Attrib[VBO_ATTRIB_POS] is never used. */ \ 566 \ 567 if (unlikely(++exec->vtx.vert_count >= exec->vtx.max_vert)) \ 568 vbo_exec_vtx_wrap(exec); \ 569 } \ 570} while (0) 571 572 573#undef ERROR 574#define ERROR(err) _mesa_error(ctx, err, __func__) 575#define TAG(x) vbo_exec_##x 576 577#include "vbo_attrib_tmp.h" 578 579 580 581/** 582 * Execute a glMaterial call. Note that if GL_COLOR_MATERIAL is enabled, 583 * this may be a (partial) no-op. 584 */ 585static void GLAPIENTRY 586vbo_exec_Materialfv(GLenum face, GLenum pname, const GLfloat *params) 587{ 588 GLbitfield updateMats; 589 GET_CURRENT_CONTEXT(ctx); 590 591 /* This function should be a no-op when it tries to update material 592 * attributes which are currently tracking glColor via glColorMaterial. 593 * The updateMats var will be a mask of the MAT_BIT_FRONT/BACK_x bits 594 * indicating which material attributes can actually be updated below. 595 */ 596 if (ctx->Light.ColorMaterialEnabled) { 597 updateMats = ~ctx->Light._ColorMaterialBitmask; 598 } 599 else { 600 /* GL_COLOR_MATERIAL is disabled so don't skip any material updates */ 601 updateMats = ALL_MATERIAL_BITS; 602 } 603 604 if (ctx->API == API_OPENGL_COMPAT && face == GL_FRONT) { 605 updateMats &= FRONT_MATERIAL_BITS; 606 } 607 else if (ctx->API == API_OPENGL_COMPAT && face == GL_BACK) { 608 updateMats &= BACK_MATERIAL_BITS; 609 } 610 else if (face != GL_FRONT_AND_BACK) { 611 _mesa_error(ctx, GL_INVALID_ENUM, "glMaterial(invalid face)"); 612 return; 613 } 614 615 switch (pname) { 616 case GL_EMISSION: 617 if (updateMats & MAT_BIT_FRONT_EMISSION) 618 MAT_ATTR(VBO_ATTRIB_MAT_FRONT_EMISSION, 4, params); 619 if (updateMats & MAT_BIT_BACK_EMISSION) 620 MAT_ATTR(VBO_ATTRIB_MAT_BACK_EMISSION, 4, params); 621 break; 622 case GL_AMBIENT: 623 if (updateMats & MAT_BIT_FRONT_AMBIENT) 624 MAT_ATTR(VBO_ATTRIB_MAT_FRONT_AMBIENT, 4, params); 625 if (updateMats & MAT_BIT_BACK_AMBIENT) 626 MAT_ATTR(VBO_ATTRIB_MAT_BACK_AMBIENT, 4, params); 627 break; 628 case GL_DIFFUSE: 629 if (updateMats & MAT_BIT_FRONT_DIFFUSE) 630 MAT_ATTR(VBO_ATTRIB_MAT_FRONT_DIFFUSE, 4, params); 631 if (updateMats & MAT_BIT_BACK_DIFFUSE) 632 MAT_ATTR(VBO_ATTRIB_MAT_BACK_DIFFUSE, 4, params); 633 break; 634 case GL_SPECULAR: 635 if (updateMats & MAT_BIT_FRONT_SPECULAR) 636 MAT_ATTR(VBO_ATTRIB_MAT_FRONT_SPECULAR, 4, params); 637 if (updateMats & MAT_BIT_BACK_SPECULAR) 638 MAT_ATTR(VBO_ATTRIB_MAT_BACK_SPECULAR, 4, params); 639 break; 640 case GL_SHININESS: 641 if (*params < 0 || *params > ctx->Const.MaxShininess) { 642 _mesa_error(ctx, GL_INVALID_VALUE, 643 "glMaterial(invalid shininess: %f out range [0, %f])", 644 *params, ctx->Const.MaxShininess); 645 return; 646 } 647 if (updateMats & MAT_BIT_FRONT_SHININESS) 648 MAT_ATTR(VBO_ATTRIB_MAT_FRONT_SHININESS, 1, params); 649 if (updateMats & MAT_BIT_BACK_SHININESS) 650 MAT_ATTR(VBO_ATTRIB_MAT_BACK_SHININESS, 1, params); 651 break; 652 case GL_COLOR_INDEXES: 653 if (ctx->API != API_OPENGL_COMPAT) { 654 _mesa_error(ctx, GL_INVALID_ENUM, "glMaterialfv(pname)"); 655 return; 656 } 657 if (updateMats & MAT_BIT_FRONT_INDEXES) 658 MAT_ATTR(VBO_ATTRIB_MAT_FRONT_INDEXES, 3, params); 659 if (updateMats & MAT_BIT_BACK_INDEXES) 660 MAT_ATTR(VBO_ATTRIB_MAT_BACK_INDEXES, 3, params); 661 break; 662 case GL_AMBIENT_AND_DIFFUSE: 663 if (updateMats & MAT_BIT_FRONT_AMBIENT) 664 MAT_ATTR(VBO_ATTRIB_MAT_FRONT_AMBIENT, 4, params); 665 if (updateMats & MAT_BIT_FRONT_DIFFUSE) 666 MAT_ATTR(VBO_ATTRIB_MAT_FRONT_DIFFUSE, 4, params); 667 if (updateMats & MAT_BIT_BACK_AMBIENT) 668 MAT_ATTR(VBO_ATTRIB_MAT_BACK_AMBIENT, 4, params); 669 if (updateMats & MAT_BIT_BACK_DIFFUSE) 670 MAT_ATTR(VBO_ATTRIB_MAT_BACK_DIFFUSE, 4, params); 671 break; 672 default: 673 _mesa_error(ctx, GL_INVALID_ENUM, "glMaterialfv(pname)"); 674 return; 675 } 676} 677 678 679/** 680 * Flush (draw) vertices. 681 * 682 * \param flags bitmask of FLUSH_STORED_VERTICES, FLUSH_UPDATE_CURRENT 683 */ 684static void 685vbo_exec_FlushVertices_internal(struct vbo_exec_context *exec, unsigned flags) 686{ 687 struct gl_context *ctx = gl_context_from_vbo_exec(exec); 688 689 if (flags & FLUSH_STORED_VERTICES) { 690 if (exec->vtx.vert_count) { 691 vbo_exec_vtx_flush(exec); 692 } 693 694 if (exec->vtx.vertex_size) { 695 vbo_exec_copy_to_current(exec); 696 vbo_reset_all_attr(exec); 697 } 698 699 /* All done. */ 700 ctx->Driver.NeedFlush = 0; 701 } else { 702 assert(flags == FLUSH_UPDATE_CURRENT); 703 704 /* Note that the vertex size is unchanged. 705 * (vbo_reset_all_attr isn't called) 706 */ 707 vbo_exec_copy_to_current(exec); 708 709 /* Only FLUSH_UPDATE_CURRENT is done. */ 710 ctx->Driver.NeedFlush = ~FLUSH_UPDATE_CURRENT; 711 } 712} 713 714 715static void GLAPIENTRY 716vbo_exec_EvalCoord1f(GLfloat u) 717{ 718 GET_CURRENT_CONTEXT(ctx); 719 struct vbo_exec_context *exec = &vbo_context(ctx)->exec; 720 721 { 722 GLint i; 723 if (exec->eval.recalculate_maps) 724 vbo_exec_eval_update(exec); 725 726 for (i = 0; i <= VBO_ATTRIB_TEX7; i++) { 727 if (exec->eval.map1[i].map) 728 if (exec->vtx.attr[i].active_size != exec->eval.map1[i].sz) 729 vbo_exec_fixup_vertex(ctx, i, exec->eval.map1[i].sz, GL_FLOAT); 730 } 731 } 732 733 memcpy(exec->vtx.copied.buffer, exec->vtx.vertex, 734 exec->vtx.vertex_size * sizeof(GLfloat)); 735 736 vbo_exec_do_EvalCoord1f(exec, u); 737 738 memcpy(exec->vtx.vertex, exec->vtx.copied.buffer, 739 exec->vtx.vertex_size * sizeof(GLfloat)); 740} 741 742 743static void GLAPIENTRY 744vbo_exec_EvalCoord2f(GLfloat u, GLfloat v) 745{ 746 GET_CURRENT_CONTEXT(ctx); 747 struct vbo_exec_context *exec = &vbo_context(ctx)->exec; 748 749 { 750 GLint i; 751 if (exec->eval.recalculate_maps) 752 vbo_exec_eval_update(exec); 753 754 for (i = 0; i <= VBO_ATTRIB_TEX7; i++) { 755 if (exec->eval.map2[i].map) 756 if (exec->vtx.attr[i].active_size != exec->eval.map2[i].sz) 757 vbo_exec_fixup_vertex(ctx, i, exec->eval.map2[i].sz, GL_FLOAT); 758 } 759 760 if (ctx->Eval.AutoNormal) 761 if (exec->vtx.attr[VBO_ATTRIB_NORMAL].active_size != 3) 762 vbo_exec_fixup_vertex(ctx, VBO_ATTRIB_NORMAL, 3, GL_FLOAT); 763 } 764 765 memcpy(exec->vtx.copied.buffer, exec->vtx.vertex, 766 exec->vtx.vertex_size * sizeof(GLfloat)); 767 768 vbo_exec_do_EvalCoord2f(exec, u, v); 769 770 memcpy(exec->vtx.vertex, exec->vtx.copied.buffer, 771 exec->vtx.vertex_size * sizeof(GLfloat)); 772} 773 774 775static void GLAPIENTRY 776vbo_exec_EvalCoord1fv(const GLfloat *u) 777{ 778 vbo_exec_EvalCoord1f(u[0]); 779} 780 781 782static void GLAPIENTRY 783vbo_exec_EvalCoord2fv(const GLfloat *u) 784{ 785 vbo_exec_EvalCoord2f(u[0], u[1]); 786} 787 788 789static void GLAPIENTRY 790vbo_exec_EvalPoint1(GLint i) 791{ 792 GET_CURRENT_CONTEXT(ctx); 793 GLfloat du = ((ctx->Eval.MapGrid1u2 - ctx->Eval.MapGrid1u1) / 794 (GLfloat) ctx->Eval.MapGrid1un); 795 GLfloat u = i * du + ctx->Eval.MapGrid1u1; 796 797 vbo_exec_EvalCoord1f(u); 798} 799 800 801static void GLAPIENTRY 802vbo_exec_EvalPoint2(GLint i, GLint j) 803{ 804 GET_CURRENT_CONTEXT(ctx); 805 GLfloat du = ((ctx->Eval.MapGrid2u2 - ctx->Eval.MapGrid2u1) / 806 (GLfloat) ctx->Eval.MapGrid2un); 807 GLfloat dv = ((ctx->Eval.MapGrid2v2 - ctx->Eval.MapGrid2v1) / 808 (GLfloat) ctx->Eval.MapGrid2vn); 809 GLfloat u = i * du + ctx->Eval.MapGrid2u1; 810 GLfloat v = j * dv + ctx->Eval.MapGrid2v1; 811 812 vbo_exec_EvalCoord2f(u, v); 813} 814 815 816/** 817 * Called via glBegin. 818 */ 819static void GLAPIENTRY 820vbo_exec_Begin(GLenum mode) 821{ 822 GET_CURRENT_CONTEXT(ctx); 823 struct vbo_context *vbo = vbo_context(ctx); 824 struct vbo_exec_context *exec = &vbo->exec; 825 int i; 826 827 if (_mesa_inside_begin_end(ctx)) { 828 _mesa_error(ctx, GL_INVALID_OPERATION, "glBegin"); 829 return; 830 } 831 832 if (ctx->NewState) 833 _mesa_update_state(ctx); 834 835 GLenum error = _mesa_valid_prim_mode(ctx, mode); 836 if (error != GL_NO_ERROR) { 837 _mesa_error(ctx, error, "glBegin"); 838 return; 839 } 840 841 /* Heuristic: attempt to isolate attributes occurring outside 842 * begin/end pairs. 843 * 844 * Use FLUSH_STORED_VERTICES, because it updates current attribs and 845 * sets vertex_size to 0. (FLUSH_UPDATE_CURRENT doesn't change vertex_size) 846 */ 847 if (exec->vtx.vertex_size && !exec->vtx.attr[VBO_ATTRIB_POS].size) 848 vbo_exec_FlushVertices_internal(exec, FLUSH_STORED_VERTICES); 849 850 i = exec->vtx.prim_count++; 851 exec->vtx.mode[i] = mode; 852 exec->vtx.draw[i].start = exec->vtx.vert_count; 853 exec->vtx.markers[i].begin = 1; 854 855 ctx->Driver.CurrentExecPrimitive = mode; 856 857 ctx->Exec = ctx->BeginEnd; 858 859 /* We may have been called from a display list, in which case we should 860 * leave dlist.c's dispatch table in place. 861 */ 862 if (ctx->CurrentClientDispatch == ctx->MarshalExec) { 863 ctx->CurrentServerDispatch = ctx->Exec; 864 } else if (ctx->CurrentClientDispatch == ctx->OutsideBeginEnd) { 865 ctx->CurrentClientDispatch = ctx->Exec; 866 _glapi_set_dispatch(ctx->CurrentClientDispatch); 867 } else { 868 assert(ctx->CurrentClientDispatch == ctx->Save); 869 } 870} 871 872 873/** 874 * Try to merge / concatenate the two most recent VBO primitives. 875 */ 876static void 877try_vbo_merge(struct vbo_exec_context *exec) 878{ 879 unsigned cur = exec->vtx.prim_count - 1; 880 881 assert(exec->vtx.prim_count >= 1); 882 883 vbo_try_prim_conversion(&exec->vtx.mode[cur], &exec->vtx.draw[cur].count); 884 885 if (exec->vtx.prim_count >= 2) { 886 struct gl_context *ctx = gl_context_from_vbo_exec(exec); 887 unsigned prev = cur - 1; 888 889 if (vbo_merge_draws(ctx, false, 890 exec->vtx.mode[prev], 891 exec->vtx.mode[cur], 892 exec->vtx.draw[prev].start, 893 exec->vtx.draw[cur].start, 894 &exec->vtx.draw[prev].count, 895 exec->vtx.draw[cur].count, 896 0, 0, 897 &exec->vtx.markers[prev].end, 898 exec->vtx.markers[cur].begin, 899 exec->vtx.markers[cur].end)) 900 exec->vtx.prim_count--; /* drop the last primitive */ 901 } 902} 903 904 905/** 906 * Called via glEnd. 907 */ 908static void GLAPIENTRY 909vbo_exec_End(void) 910{ 911 GET_CURRENT_CONTEXT(ctx); 912 struct vbo_exec_context *exec = &vbo_context(ctx)->exec; 913 914 if (!_mesa_inside_begin_end(ctx)) { 915 _mesa_error(ctx, GL_INVALID_OPERATION, "glEnd"); 916 return; 917 } 918 919 ctx->Exec = ctx->OutsideBeginEnd; 920 921 if (ctx->CurrentClientDispatch == ctx->MarshalExec) { 922 ctx->CurrentServerDispatch = ctx->Exec; 923 } else if (ctx->CurrentClientDispatch == ctx->BeginEnd) { 924 ctx->CurrentClientDispatch = ctx->Exec; 925 _glapi_set_dispatch(ctx->CurrentClientDispatch); 926 } 927 928 if (exec->vtx.prim_count > 0) { 929 /* close off current primitive */ 930 unsigned last = exec->vtx.prim_count - 1; 931 struct pipe_draw_start_count_bias *last_draw = &exec->vtx.draw[last]; 932 unsigned count = exec->vtx.vert_count - last_draw->start; 933 934 last_draw->count = count; 935 exec->vtx.markers[last].end = 1; 936 937 if (count) 938 ctx->Driver.NeedFlush |= FLUSH_STORED_VERTICES; 939 940 /* Special handling for GL_LINE_LOOP */ 941 if (exec->vtx.mode[last] == GL_LINE_LOOP && 942 exec->vtx.markers[last].begin == 0) { 943 /* We're finishing drawing a line loop. Append 0th vertex onto 944 * end of vertex buffer so we can draw it as a line strip. 945 */ 946 const fi_type *src = exec->vtx.buffer_map + 947 last_draw->start * exec->vtx.vertex_size; 948 fi_type *dst = exec->vtx.buffer_map + 949 exec->vtx.vert_count * exec->vtx.vertex_size; 950 951 /* copy 0th vertex to end of buffer */ 952 memcpy(dst, src, exec->vtx.vertex_size * sizeof(fi_type)); 953 954 last_draw->start++; /* skip vertex0 */ 955 /* note that the count stays unchanged */ 956 exec->vtx.mode[last] = GL_LINE_STRIP; 957 958 /* Increment the vertex count so the next primitive doesn't 959 * overwrite the last vertex which we just added. 960 */ 961 exec->vtx.vert_count++; 962 exec->vtx.buffer_ptr += exec->vtx.vertex_size; 963 } 964 965 try_vbo_merge(exec); 966 } 967 968 ctx->Driver.CurrentExecPrimitive = PRIM_OUTSIDE_BEGIN_END; 969 970 if (exec->vtx.prim_count == VBO_MAX_PRIM) 971 vbo_exec_vtx_flush(exec); 972 973 if (MESA_DEBUG_FLAGS & DEBUG_ALWAYS_FLUSH) { 974 _mesa_flush(ctx); 975 } 976} 977 978 979/** 980 * Called via glPrimitiveRestartNV() 981 */ 982static void GLAPIENTRY 983vbo_exec_PrimitiveRestartNV(void) 984{ 985 GLenum curPrim; 986 GET_CURRENT_CONTEXT(ctx); 987 988 curPrim = ctx->Driver.CurrentExecPrimitive; 989 990 if (curPrim == PRIM_OUTSIDE_BEGIN_END) { 991 _mesa_error(ctx, GL_INVALID_OPERATION, "glPrimitiveRestartNV"); 992 } 993 else { 994 vbo_exec_End(); 995 vbo_exec_Begin(curPrim); 996 } 997} 998 999 1000static void 1001vbo_exec_vtxfmt_init(struct vbo_exec_context *exec) 1002{ 1003 struct gl_context *ctx = gl_context_from_vbo_exec(exec); 1004 GLvertexformat *vfmt = &exec->vtxfmt; 1005 1006#define NAME_AE(x) _ae_##x 1007#define NAME_CALLLIST(x) _mesa_##x 1008#define NAME(x) vbo_exec_##x 1009#define NAME_ES(x) _es_##x 1010 1011#include "vbo_init_tmp.h" 1012} 1013 1014 1015static void 1016vbo_reset_all_attr(struct vbo_exec_context *exec) 1017{ 1018 while (exec->vtx.enabled) { 1019 const int i = u_bit_scan64(&exec->vtx.enabled); 1020 1021 /* Reset the vertex attribute by setting its size to zero. */ 1022 exec->vtx.attr[i].size = 0; 1023 exec->vtx.attr[i].type = GL_FLOAT; 1024 exec->vtx.attr[i].active_size = 0; 1025 exec->vtx.attrptr[i] = NULL; 1026 } 1027 1028 exec->vtx.vertex_size = 0; 1029} 1030 1031 1032void 1033vbo_exec_vtx_init(struct vbo_exec_context *exec, bool use_buffer_objects) 1034{ 1035 struct gl_context *ctx = gl_context_from_vbo_exec(exec); 1036 1037 if (use_buffer_objects) { 1038 /* Use buffer objects for immediate mode. */ 1039 struct vbo_exec_context *exec = &vbo_context(ctx)->exec; 1040 exec->vtx.bufferobj = ctx->Driver.NewBufferObject(ctx, IMM_BUFFER_NAME); 1041 } else { 1042 /* Use allocated memory for immediate mode. */ 1043 exec->vtx.bufferobj = NULL; 1044 exec->vtx.buffer_map = 1045 align_malloc(ctx->Const.glBeginEndBufferSize, 64); 1046 exec->vtx.buffer_ptr = exec->vtx.buffer_map; 1047 } 1048 1049 vbo_exec_vtxfmt_init(exec); 1050 _mesa_noop_vtxfmt_init(ctx, &exec->vtxfmt_noop); 1051 1052 exec->vtx.enabled = u_bit_consecutive64(0, VBO_ATTRIB_MAX); /* reset all */ 1053 vbo_reset_all_attr(exec); 1054 1055 exec->vtx.info.instance_count = 1; 1056 exec->vtx.info.max_index = ~0; 1057} 1058 1059 1060void 1061vbo_exec_vtx_destroy(struct vbo_exec_context *exec) 1062{ 1063 /* using a real VBO for vertex data */ 1064 struct gl_context *ctx = gl_context_from_vbo_exec(exec); 1065 1066 /* True VBOs should already be unmapped 1067 */ 1068 if (exec->vtx.buffer_map) { 1069 assert(!exec->vtx.bufferobj || 1070 exec->vtx.bufferobj->Name == IMM_BUFFER_NAME); 1071 if (!exec->vtx.bufferobj) { 1072 align_free(exec->vtx.buffer_map); 1073 exec->vtx.buffer_map = NULL; 1074 exec->vtx.buffer_ptr = NULL; 1075 } 1076 } 1077 1078 /* Free the vertex buffer. Unmap first if needed. 1079 */ 1080 if (exec->vtx.bufferobj && 1081 _mesa_bufferobj_mapped(exec->vtx.bufferobj, MAP_INTERNAL)) { 1082 ctx->Driver.UnmapBuffer(ctx, exec->vtx.bufferobj, MAP_INTERNAL); 1083 } 1084 _mesa_reference_buffer_object(ctx, &exec->vtx.bufferobj, NULL); 1085} 1086 1087 1088/** 1089 * If inside glBegin()/glEnd(), it should assert(0). Otherwise, if 1090 * FLUSH_STORED_VERTICES bit in \p flags is set flushes any buffered 1091 * vertices, if FLUSH_UPDATE_CURRENT bit is set updates 1092 * __struct gl_contextRec::Current and gl_light_attrib::Material 1093 * 1094 * Note that the default T&L engine never clears the 1095 * FLUSH_UPDATE_CURRENT bit, even after performing the update. 1096 * 1097 * \param flags bitmask of FLUSH_STORED_VERTICES, FLUSH_UPDATE_CURRENT 1098 */ 1099void 1100vbo_exec_FlushVertices(struct gl_context *ctx, GLuint flags) 1101{ 1102 struct vbo_exec_context *exec = &vbo_context(ctx)->exec; 1103 1104#ifndef NDEBUG 1105 /* debug check: make sure we don't get called recursively */ 1106 exec->flush_call_depth++; 1107 assert(exec->flush_call_depth == 1); 1108#endif 1109 1110 if (_mesa_inside_begin_end(ctx)) { 1111 /* We've had glBegin but not glEnd! */ 1112#ifndef NDEBUG 1113 exec->flush_call_depth--; 1114 assert(exec->flush_call_depth == 0); 1115#endif 1116 return; 1117 } 1118 1119 /* Flush (draw). */ 1120 vbo_exec_FlushVertices_internal(exec, flags); 1121 1122#ifndef NDEBUG 1123 exec->flush_call_depth--; 1124 assert(exec->flush_call_depth == 0); 1125#endif 1126} 1127 1128 1129void GLAPIENTRY 1130_es_Color4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a) 1131{ 1132 vbo_exec_Color4f(r, g, b, a); 1133} 1134 1135 1136void GLAPIENTRY 1137_es_Normal3f(GLfloat x, GLfloat y, GLfloat z) 1138{ 1139 vbo_exec_Normal3f(x, y, z); 1140} 1141 1142 1143void GLAPIENTRY 1144_es_MultiTexCoord4f(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q) 1145{ 1146 vbo_exec_MultiTexCoord4f(target, s, t, r, q); 1147} 1148 1149 1150void GLAPIENTRY 1151_es_Materialfv(GLenum face, GLenum pname, const GLfloat *params) 1152{ 1153 vbo_exec_Materialfv(face, pname, params); 1154} 1155 1156 1157void GLAPIENTRY 1158_es_Materialf(GLenum face, GLenum pname, GLfloat param) 1159{ 1160 GLfloat p[4]; 1161 p[0] = param; 1162 p[1] = p[2] = p[3] = 0.0F; 1163 vbo_exec_Materialfv(face, pname, p); 1164} 1165 1166 1167/** 1168 * A special version of glVertexAttrib4f that does not treat index 0 as 1169 * VBO_ATTRIB_POS. 1170 */ 1171static void 1172VertexAttrib4f_nopos(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w) 1173{ 1174 GET_CURRENT_CONTEXT(ctx); 1175 if (index < MAX_VERTEX_GENERIC_ATTRIBS) 1176 ATTRF(VBO_ATTRIB_GENERIC0 + index, 4, x, y, z, w); 1177 else 1178 ERROR(GL_INVALID_VALUE); 1179} 1180 1181void GLAPIENTRY 1182_es_VertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w) 1183{ 1184 VertexAttrib4f_nopos(index, x, y, z, w); 1185} 1186 1187 1188void GLAPIENTRY 1189_es_VertexAttrib1f(GLuint indx, GLfloat x) 1190{ 1191 VertexAttrib4f_nopos(indx, x, 0.0f, 0.0f, 1.0f); 1192} 1193 1194 1195void GLAPIENTRY 1196_es_VertexAttrib1fv(GLuint indx, const GLfloat* values) 1197{ 1198 VertexAttrib4f_nopos(indx, values[0], 0.0f, 0.0f, 1.0f); 1199} 1200 1201 1202void GLAPIENTRY 1203_es_VertexAttrib2f(GLuint indx, GLfloat x, GLfloat y) 1204{ 1205 VertexAttrib4f_nopos(indx, x, y, 0.0f, 1.0f); 1206} 1207 1208 1209void GLAPIENTRY 1210_es_VertexAttrib2fv(GLuint indx, const GLfloat* values) 1211{ 1212 VertexAttrib4f_nopos(indx, values[0], values[1], 0.0f, 1.0f); 1213} 1214 1215 1216void GLAPIENTRY 1217_es_VertexAttrib3f(GLuint indx, GLfloat x, GLfloat y, GLfloat z) 1218{ 1219 VertexAttrib4f_nopos(indx, x, y, z, 1.0f); 1220} 1221 1222 1223void GLAPIENTRY 1224_es_VertexAttrib3fv(GLuint indx, const GLfloat* values) 1225{ 1226 VertexAttrib4f_nopos(indx, values[0], values[1], values[2], 1.0f); 1227} 1228 1229 1230void GLAPIENTRY 1231_es_VertexAttrib4fv(GLuint indx, const GLfloat* values) 1232{ 1233 VertexAttrib4f_nopos(indx, values[0], values[1], values[2], values[3]); 1234} 1235