| |
| /* |
| * Mesa 3-D graphics library |
| * Version: 6.5 |
| * |
| * Copyright (C) 1999-2006 Brian Paul All Rights Reserved. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included |
| * in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN |
| * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| * |
| * Authors: |
| * Keith Whitwell <keith@tungstengraphics.com> |
| */ |
| |
| /* Split indexed primitives with per-vertex copying. |
| */ |
| |
| #include "main/glheader.h" |
| #include "main/imports.h" |
| #include "main/macros.h" |
| #include "main/enums.h" |
| #include "main/mtypes.h" |
| |
| #include "vbo_split.h" |
| #include "vbo.h" |
| |
| |
| #define ELT_TABLE_SIZE 16 |
| |
| /* Used for vertex-level splitting of indexed buffers. Note that |
| * non-indexed primitives may be converted to indexed in some cases |
| * (eg loops, fans) in order to use this splitting path. |
| */ |
| struct copy_context { |
| |
| GLcontext *ctx; |
| const struct gl_client_array **array; |
| const struct _mesa_prim *prim; |
| GLuint nr_prims; |
| const struct _mesa_index_buffer *ib; |
| vbo_draw_func draw; |
| |
| const struct split_limits *limits; |
| |
| struct { |
| GLuint attr; |
| GLuint size; |
| const struct gl_client_array *array; |
| const GLubyte *src_ptr; |
| |
| struct gl_client_array dstarray; |
| |
| } varying[VERT_ATTRIB_MAX]; |
| GLuint nr_varying; |
| |
| const struct gl_client_array *dstarray_ptr[VERT_ATTRIB_MAX]; |
| struct _mesa_index_buffer dstib; |
| |
| GLuint *translated_elt_buf; |
| const GLuint *srcelt; |
| |
| /* A baby hash table to avoid re-emitting (some) duplicate |
| * vertices when splitting indexed primitives. |
| */ |
| struct { |
| GLuint in; |
| GLuint out; |
| } vert_cache[ELT_TABLE_SIZE]; |
| |
| |
| GLuint vertex_size; |
| GLubyte *dstbuf; |
| GLubyte *dstptr; /* dstptr == dstbuf + dstelt_max * vertsize */ |
| GLuint dstbuf_size; /* in vertices */ |
| GLuint dstbuf_nr; /* count of emitted vertices, also the |
| * largest value in dstelt. Our |
| * MaxIndex. |
| */ |
| |
| GLuint *dstelt; |
| GLuint dstelt_nr; |
| GLuint dstelt_size; |
| |
| #define MAX_PRIM 32 |
| struct _mesa_prim dstprim[MAX_PRIM]; |
| GLuint dstprim_nr; |
| |
| }; |
| |
| |
| static GLuint type_size( GLenum type ) |
| { |
| switch(type) { |
| case GL_BYTE: return sizeof(GLbyte); |
| case GL_UNSIGNED_BYTE: return sizeof(GLubyte); |
| case GL_SHORT: return sizeof(GLshort); |
| case GL_UNSIGNED_SHORT: return sizeof(GLushort); |
| case GL_INT: return sizeof(GLint); |
| case GL_UNSIGNED_INT: return sizeof(GLuint); |
| case GL_FLOAT: return sizeof(GLfloat); |
| case GL_DOUBLE: return sizeof(GLdouble); |
| default: return 0; |
| } |
| } |
| |
| static GLuint attr_size( const struct gl_client_array *array ) |
| { |
| return array->Size * type_size(array->Type); |
| } |
| |
| |
| /* Starts returning true slightly before the buffer fills, to ensure |
| * that there is sufficient room for any remaining vertices to finish |
| * off the prim: |
| */ |
| static GLboolean check_flush( struct copy_context *copy ) |
| { |
| GLenum mode = copy->dstprim[copy->dstprim_nr].mode; |
| |
| if (GL_TRIANGLE_STRIP == mode && |
| copy->dstelt_nr & 1) { /* see bug9962 */ |
| return GL_FALSE; |
| } |
| |
| if (copy->dstbuf_nr + 4 > copy->dstbuf_size) |
| return GL_TRUE; |
| |
| if (copy->dstelt_nr + 4 > copy->dstelt_size) |
| return GL_TRUE; |
| |
| return GL_FALSE; |
| } |
| |
| static void flush( struct copy_context *copy ) |
| { |
| GLuint i; |
| |
| /* Set some counters: |
| */ |
| copy->dstib.count = copy->dstelt_nr; |
| |
| copy->draw( copy->ctx, |
| copy->dstarray_ptr, |
| copy->dstprim, |
| copy->dstprim_nr, |
| ©->dstib, |
| 0, |
| copy->dstbuf_nr ); |
| |
| /* Reset all pointers: |
| */ |
| copy->dstprim_nr = 0; |
| copy->dstelt_nr = 0; |
| copy->dstbuf_nr = 0; |
| copy->dstptr = copy->dstbuf; |
| |
| /* Clear the vertex cache: |
| */ |
| for (i = 0; i < ELT_TABLE_SIZE; i++) |
| copy->vert_cache[i].in = ~0; |
| } |
| |
| |
| |
| static void begin( struct copy_context *copy, GLenum mode, GLboolean begin_flag ) |
| { |
| struct _mesa_prim *prim = ©->dstprim[copy->dstprim_nr]; |
| |
| /* _mesa_printf("begin %s (%d)\n", _mesa_lookup_enum_by_nr(mode), begin_flag); */ |
| |
| prim->mode = mode; |
| prim->begin = begin_flag; |
| } |
| |
| |
| /* Use a hashtable to attempt to identify recently-emitted vertices |
| * and avoid re-emitting them. |
| */ |
| static GLuint elt(struct copy_context *copy, GLuint elt_idx) |
| { |
| GLuint elt = copy->srcelt[elt_idx]; |
| GLuint slot = elt & (ELT_TABLE_SIZE-1); |
| |
| /* _mesa_printf("elt %d\n", elt); */ |
| |
| /* Look up the incoming element in the vertex cache. Re-emit if |
| * necessary. |
| */ |
| if (copy->vert_cache[slot].in != elt) { |
| GLubyte *csr = copy->dstptr; |
| GLuint i; |
| |
| /* _mesa_printf(" --> emit to dstelt %d\n", copy->dstbuf_nr); */ |
| |
| for (i = 0; i < copy->nr_varying; i++) { |
| const struct gl_client_array *srcarray = copy->varying[i].array; |
| const GLubyte *srcptr = copy->varying[i].src_ptr + elt * srcarray->StrideB; |
| |
| memcpy(csr, srcptr, copy->varying[i].size); |
| csr += copy->varying[i].size; |
| |
| if (0) |
| { |
| const GLuint *f = (const GLuint *)srcptr; |
| GLuint j; |
| _mesa_printf(" varying %d: ", i); |
| for(j = 0; j < copy->varying[i].size / 4; j++) |
| _mesa_printf("%x ", f[j]); |
| _mesa_printf("\n"); |
| } |
| |
| } |
| |
| copy->vert_cache[slot].in = elt; |
| copy->vert_cache[slot].out = copy->dstbuf_nr++; |
| copy->dstptr += copy->vertex_size; |
| |
| assert(csr == copy->dstptr); |
| assert(copy->dstptr == (copy->dstbuf + |
| copy->dstbuf_nr * |
| copy->vertex_size)); |
| } |
| /* else */ |
| /* _mesa_printf(" --> reuse vertex\n"); */ |
| |
| /* _mesa_printf(" --> emit %d\n", copy->vert_cache[slot].out); */ |
| copy->dstelt[copy->dstelt_nr++] = copy->vert_cache[slot].out; |
| return check_flush(copy); |
| } |
| |
| static void end( struct copy_context *copy, GLboolean end_flag ) |
| { |
| struct _mesa_prim *prim = ©->dstprim[copy->dstprim_nr]; |
| |
| /* _mesa_printf("end (%d)\n", end_flag); */ |
| |
| prim->end = end_flag; |
| prim->count = copy->dstelt_nr - prim->start; |
| |
| if (++copy->dstprim_nr == MAX_PRIM || |
| check_flush(copy)) |
| flush(copy); |
| } |
| |
| |
| |
| static void replay_elts( struct copy_context *copy ) |
| { |
| GLuint i, j, k; |
| GLboolean split; |
| |
| for (i = 0; i < copy->nr_prims; i++) { |
| const struct _mesa_prim *prim = ©->prim[i]; |
| const GLuint start = prim->start; |
| GLuint first, incr; |
| |
| switch (prim->mode) { |
| |
| case GL_LINE_LOOP: |
| /* Convert to linestrip and emit the final vertex explicitly, |
| * but only in the resultant strip that requires it. |
| */ |
| j = 0; |
| while (j != prim->count) { |
| begin(copy, GL_LINE_STRIP, prim->begin && j == 0); |
| |
| for (split = GL_FALSE; j != prim->count && !split; j++) |
| split = elt(copy, start + j); |
| |
| if (j == prim->count) { |
| /* Done, emit final line. Split doesn't matter as |
| * it is always raised a bit early so we can emit |
| * the last verts if necessary! |
| */ |
| if (prim->end) |
| (void)elt(copy, start + 0); |
| |
| end(copy, prim->end); |
| } |
| else { |
| /* Wrap |
| */ |
| assert(split); |
| end(copy, 0); |
| j--; |
| } |
| } |
| break; |
| |
| case GL_TRIANGLE_FAN: |
| case GL_POLYGON: |
| j = 2; |
| while (j != prim->count) { |
| begin(copy, prim->mode, prim->begin && j == 0); |
| |
| split = elt(copy, start+0); |
| assert(!split); |
| |
| split = elt(copy, start+j-1); |
| assert(!split); |
| |
| for (; j != prim->count && !split; j++) |
| split = elt(copy, start+j); |
| |
| end(copy, prim->end && j == prim->count); |
| |
| if (j != prim->count) { |
| /* Wrapped the primitive, need to repeat some vertices: |
| */ |
| j -= 1; |
| } |
| } |
| break; |
| |
| default: |
| (void)split_prim_inplace(prim->mode, &first, &incr); |
| |
| j = 0; |
| while (j != prim->count) { |
| |
| begin(copy, prim->mode, prim->begin && j == 0); |
| |
| split = 0; |
| for (k = 0; k < first; k++, j++) |
| split |= elt(copy, start+j); |
| |
| assert(!split); |
| |
| for (; j != prim->count && !split; ) |
| for (k = 0; k < incr; k++, j++) |
| split |= elt(copy, start+j); |
| |
| end(copy, prim->end && j == prim->count); |
| |
| if (j != prim->count) { |
| /* Wrapped the primitive, need to repeat some vertices: |
| */ |
| assert(j > first - incr); |
| j -= (first - incr); |
| } |
| } |
| break; |
| } |
| } |
| |
| if (copy->dstprim_nr) |
| flush(copy); |
| } |
| |
| |
| static void replay_init( struct copy_context *copy ) |
| { |
| GLcontext *ctx = copy->ctx; |
| GLuint i; |
| GLuint offset; |
| const GLvoid *srcptr; |
| |
| /* Make a list of varying attributes and their vbo's. Also |
| * calculate vertex size. |
| */ |
| copy->vertex_size = 0; |
| for (i = 0; i < VERT_ATTRIB_MAX; i++) { |
| struct gl_buffer_object *vbo = copy->array[i]->BufferObj; |
| |
| if (copy->array[i]->StrideB == 0) { |
| copy->dstarray_ptr[i] = copy->array[i]; |
| } |
| else { |
| GLuint j = copy->nr_varying++; |
| |
| copy->varying[j].attr = i; |
| copy->varying[j].array = copy->array[i]; |
| copy->varying[j].size = attr_size(copy->array[i]); |
| copy->vertex_size += attr_size(copy->array[i]); |
| |
| if (vbo->Name && !vbo->Pointer) |
| ctx->Driver.MapBuffer(ctx, |
| GL_ARRAY_BUFFER_ARB, |
| GL_WRITE_ONLY, /* XXX */ |
| vbo); |
| |
| copy->varying[j].src_ptr = ADD_POINTERS(vbo->Pointer, |
| copy->array[i]->Ptr); |
| |
| copy->dstarray_ptr[i] = ©->varying[j].dstarray; |
| } |
| } |
| |
| /* There must always be an index buffer. Currently require the |
| * caller convert non-indexed prims to indexed. Could alternately |
| * do it internally. |
| */ |
| if (copy->ib->obj->Name && !copy->ib->obj->Pointer) |
| ctx->Driver.MapBuffer(ctx, |
| GL_ARRAY_BUFFER_ARB, /* XXX */ |
| GL_WRITE_ONLY, /* XXX */ |
| copy->ib->obj); |
| |
| srcptr = (const GLubyte *)ADD_POINTERS(copy->ib->obj->Pointer, copy->ib->ptr); |
| |
| switch (copy->ib->type) { |
| case GL_UNSIGNED_BYTE: |
| copy->translated_elt_buf = _mesa_malloc(sizeof(GLuint) * copy->ib->count); |
| copy->srcelt = copy->translated_elt_buf; |
| |
| for (i = 0; i < copy->ib->count; i++) |
| copy->translated_elt_buf[i] = ((const GLubyte *)srcptr)[i]; |
| break; |
| |
| case GL_UNSIGNED_SHORT: |
| copy->translated_elt_buf = _mesa_malloc(sizeof(GLuint) * copy->ib->count); |
| copy->srcelt = copy->translated_elt_buf; |
| |
| for (i = 0; i < copy->ib->count; i++) |
| copy->translated_elt_buf[i] = ((const GLushort *)srcptr)[i]; |
| break; |
| |
| case GL_UNSIGNED_INT: |
| copy->translated_elt_buf = NULL; |
| copy->srcelt = (const GLuint *)srcptr; |
| break; |
| } |
| |
| |
| /* Figure out the maximum allowed vertex buffer size: |
| */ |
| if (copy->vertex_size * copy->limits->max_verts <= copy->limits->max_vb_size) { |
| copy->dstbuf_size = copy->limits->max_verts; |
| } |
| else { |
| copy->dstbuf_size = copy->limits->max_vb_size / copy->vertex_size; |
| } |
| |
| /* Allocate an output vertex buffer: |
| * |
| * XXX: This should be a VBO! |
| */ |
| copy->dstbuf = _mesa_malloc(copy->dstbuf_size * |
| copy->vertex_size); |
| copy->dstptr = copy->dstbuf; |
| |
| /* Setup new vertex arrays to point into the output buffer: |
| */ |
| for (offset = 0, i = 0; i < copy->nr_varying; i++) { |
| const struct gl_client_array *src = copy->varying[i].array; |
| struct gl_client_array *dst = ©->varying[i].dstarray; |
| |
| dst->Size = src->Size; |
| dst->Type = src->Type; |
| dst->Stride = copy->vertex_size; |
| dst->StrideB = copy->vertex_size; |
| dst->Ptr = copy->dstbuf + offset; |
| dst->Enabled = GL_TRUE; |
| dst->Normalized = src->Normalized; |
| dst->BufferObj = ctx->Array.NullBufferObj; |
| dst->_MaxElement = copy->dstbuf_size; /* may be less! */ |
| |
| offset += copy->varying[i].size; |
| } |
| |
| /* Allocate an output element list: |
| */ |
| copy->dstelt_size = MIN2(65536, |
| copy->ib->count * 2 + 3); |
| copy->dstelt_size = MIN2(copy->dstelt_size, |
| copy->limits->max_indices); |
| copy->dstelt = _mesa_malloc(sizeof(GLuint) * copy->dstelt_size); |
| copy->dstelt_nr = 0; |
| |
| /* Setup the new index buffer to point to the allocated element |
| * list: |
| */ |
| copy->dstib.count = 0; /* duplicates dstelt_nr */ |
| copy->dstib.type = GL_UNSIGNED_INT; |
| copy->dstib.obj = ctx->Array.NullBufferObj; |
| copy->dstib.ptr = copy->dstelt; |
| } |
| |
| |
| static void replay_finish( struct copy_context *copy ) |
| { |
| GLcontext *ctx = copy->ctx; |
| GLuint i; |
| |
| /* Free our vertex and index buffers: |
| */ |
| _mesa_free(copy->translated_elt_buf); |
| _mesa_free(copy->dstbuf); |
| _mesa_free(copy->dstelt); |
| |
| /* Unmap VBO's |
| */ |
| for (i = 0; i < copy->nr_varying; i++) { |
| struct gl_buffer_object *vbo = copy->varying[i].array->BufferObj; |
| |
| if (vbo->Name && vbo->Pointer) |
| ctx->Driver.UnmapBuffer(ctx, GL_ARRAY_BUFFER_ARB, vbo); |
| } |
| |
| /* Unmap index buffer: |
| */ |
| if (copy->ib->obj->Name && copy->ib->obj->Pointer) { |
| ctx->Driver.UnmapBuffer(ctx, |
| GL_ARRAY_BUFFER_ARB, /* XXX */ |
| copy->ib->obj); |
| } |
| } |
| |
| void vbo_split_copy( GLcontext *ctx, |
| const struct gl_client_array *arrays[], |
| const struct _mesa_prim *prim, |
| GLuint nr_prims, |
| const struct _mesa_index_buffer *ib, |
| vbo_draw_func draw, |
| const struct split_limits *limits ) |
| { |
| struct copy_context copy; |
| GLuint i; |
| |
| memset(©, 0, sizeof(copy)); |
| |
| /* Require indexed primitives: |
| */ |
| assert(ib); |
| |
| copy.ctx = ctx; |
| copy.array = arrays; |
| copy.prim = prim; |
| copy.nr_prims = nr_prims; |
| copy.ib = ib; |
| copy.draw = draw; |
| copy.limits = limits; |
| |
| |
| /* Clear the vertex cache: |
| */ |
| for (i = 0; i < ELT_TABLE_SIZE; i++) |
| copy.vert_cache[i].in = ~0; |
| |
| |
| replay_init(©); |
| replay_elts(©); |
| replay_finish(©); |
| } |