/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2016 by Mike Erwin. * All rights reserved. */ /** \file * \ingroup gpu * * GPU vertex buffer */ #include "MEM_guardedalloc.h" #include "GPU_vertex_buffer.h" #include "gpu_context_private.h" #include "gpu_vertex_format_private.h" #include #include #define KEEP_SINGLE_COPY 1 static uint vbo_memory_usage; static GLenum convert_usage_type_to_gl(GPUUsageType type) { static const GLenum table[] = { [GPU_USAGE_STREAM] = GL_STREAM_DRAW, [GPU_USAGE_STATIC] = GL_STATIC_DRAW, [GPU_USAGE_DYNAMIC] = GL_DYNAMIC_DRAW, }; return table[type]; } GPUVertBuf *GPU_vertbuf_create(GPUUsageType usage) { GPUVertBuf *verts = MEM_mallocN(sizeof(GPUVertBuf), "GPUVertBuf"); GPU_vertbuf_init(verts, usage); return verts; } GPUVertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat *format, GPUUsageType usage) { GPUVertBuf *verts = GPU_vertbuf_create(usage); GPU_vertformat_copy(&verts->format, format); if (!format->packed) { VertexFormat_pack(&verts->format); } return verts; /* this function might seem redundant, but there is potential for memory savings here... */ /* TODO: implement those memory savings */ } void GPU_vertbuf_init(GPUVertBuf *verts, GPUUsageType usage) { memset(verts, 0, sizeof(GPUVertBuf)); verts->usage = usage; verts->dirty = true; } void GPU_vertbuf_init_with_format_ex(GPUVertBuf *verts, const GPUVertFormat *format, GPUUsageType usage) { GPU_vertbuf_init(verts, usage); GPU_vertformat_copy(&verts->format, format); if (!format->packed) { VertexFormat_pack(&verts->format); } } /** Same as discard but does not free. */ void GPU_vertbuf_clear(GPUVertBuf *verts) { if (verts->vbo_id) { GPU_buf_free(verts->vbo_id); verts->vbo_id = 0; #if VRAM_USAGE vbo_memory_usage -= GPU_vertbuf_size_get(verts); #endif } if (verts->data) { MEM_SAFE_FREE(verts->data); } } void GPU_vertbuf_discard(GPUVertBuf *verts) { GPU_vertbuf_clear(verts); MEM_freeN(verts); } uint GPU_vertbuf_size_get(const GPUVertBuf *verts) { return vertex_buffer_size(&verts->format, verts->vertex_len); } /* create a new allocation, discarding any existing data */ void GPU_vertbuf_data_alloc(GPUVertBuf *verts, uint v_len) { GPUVertFormat *format = &verts->format; if (!format->packed) { VertexFormat_pack(format); } #if TRUST_NO_ONE /* catch any unnecessary use */ assert(verts->vertex_alloc != v_len || verts->data == NULL); #endif /* discard previous data if any */ if (verts->data) { MEM_freeN(verts->data); } #if VRAM_USAGE uint new_size = vertex_buffer_size(&verts->format, v_len); vbo_memory_usage += new_size - GPU_vertbuf_size_get(verts); #endif verts->dirty = true; verts->vertex_len = verts->vertex_alloc = v_len; verts->data = MEM_mallocN(sizeof(GLubyte) * GPU_vertbuf_size_get(verts), "GPUVertBuf data"); } /* resize buffer keeping existing data */ void GPU_vertbuf_data_resize(GPUVertBuf *verts, uint v_len) { #if TRUST_NO_ONE assert(verts->data != NULL); assert(verts->vertex_alloc != v_len); #endif #if VRAM_USAGE uint new_size = vertex_buffer_size(&verts->format, v_len); vbo_memory_usage += new_size - GPU_vertbuf_size_get(verts); #endif verts->dirty = true; verts->vertex_len = verts->vertex_alloc = v_len; verts->data = MEM_reallocN(verts->data, sizeof(GLubyte) * GPU_vertbuf_size_get(verts)); } /* Set vertex count but does not change allocation. * Only this many verts will be uploaded to the GPU and rendered. * This is useful for streaming data. */ void GPU_vertbuf_data_len_set(GPUVertBuf *verts, uint v_len) { #if TRUST_NO_ONE assert(verts->data != NULL); /* only for dynamic data */ assert(v_len <= verts->vertex_alloc); #endif #if VRAM_USAGE uint new_size = vertex_buffer_size(&verts->format, v_len); vbo_memory_usage += new_size - GPU_vertbuf_size_get(verts); #endif verts->vertex_len = v_len; } void GPU_vertbuf_attr_set(GPUVertBuf *verts, uint a_idx, uint v_idx, const void *data) { const GPUVertFormat *format = &verts->format; const GPUVertAttr *a = &format->attrs[a_idx]; #if TRUST_NO_ONE assert(a_idx < format->attr_len); assert(v_idx < verts->vertex_alloc); assert(verts->data != NULL); #endif verts->dirty = true; memcpy((GLubyte *)verts->data + a->offset + v_idx * format->stride, data, a->sz); } void GPU_vertbuf_attr_fill(GPUVertBuf *verts, uint a_idx, const void *data) { const GPUVertFormat *format = &verts->format; const GPUVertAttr *a = &format->attrs[a_idx]; #if TRUST_NO_ONE assert(a_idx < format->attr_len); #endif const uint stride = a->sz; /* tightly packed input data */ GPU_vertbuf_attr_fill_stride(verts, a_idx, stride, data); } /** Fills a whole vertex (all attributes). Data must match packed layout. */ void GPU_vertbuf_vert_set(GPUVertBuf *verts, uint v_idx, const void *data) { const GPUVertFormat *format = &verts->format; #if TRUST_NO_ONE assert(v_idx < verts->vertex_alloc); assert(verts->data != NULL); #endif verts->dirty = true; memcpy((GLubyte *)verts->data + v_idx * format->stride, data, format->stride); } void GPU_vertbuf_attr_fill_stride(GPUVertBuf *verts, uint a_idx, uint stride, const void *data) { const GPUVertFormat *format = &verts->format; const GPUVertAttr *a = &format->attrs[a_idx]; #if TRUST_NO_ONE assert(a_idx < format->attr_len); assert(verts->data != NULL); #endif verts->dirty = true; const uint vertex_len = verts->vertex_len; if (format->attr_len == 1 && stride == format->stride) { /* we can copy it all at once */ memcpy(verts->data, data, vertex_len * a->sz); } else { /* we must copy it per vertex */ for (uint v = 0; v < vertex_len; v++) { memcpy((GLubyte *)verts->data + a->offset + v * format->stride, (const GLubyte *)data + v * stride, a->sz); } } } void GPU_vertbuf_attr_get_raw_data(GPUVertBuf *verts, uint a_idx, GPUVertBufRaw *access) { const GPUVertFormat *format = &verts->format; const GPUVertAttr *a = &format->attrs[a_idx]; #if TRUST_NO_ONE assert(a_idx < format->attr_len); assert(verts->data != NULL); #endif verts->dirty = true; access->size = a->sz; access->stride = format->stride; access->data = (GLubyte *)verts->data + a->offset; access->data_init = access->data; #if TRUST_NO_ONE access->_data_end = access->data_init + (size_t)(verts->vertex_alloc * format->stride); #endif } static void VertBuffer_upload_data(GPUVertBuf *verts) { uint buffer_sz = GPU_vertbuf_size_get(verts); /* orphan the vbo to avoid sync */ glBufferData(GL_ARRAY_BUFFER, buffer_sz, NULL, convert_usage_type_to_gl(verts->usage)); /* upload data */ glBufferSubData(GL_ARRAY_BUFFER, 0, buffer_sz, verts->data); if (verts->usage == GPU_USAGE_STATIC) { MEM_freeN(verts->data); verts->data = NULL; } verts->dirty = false; } void GPU_vertbuf_use(GPUVertBuf *verts) { /* only create the buffer the 1st time */ if (verts->vbo_id == 0) { verts->vbo_id = GPU_buf_alloc(); } glBindBuffer(GL_ARRAY_BUFFER, verts->vbo_id); if (verts->dirty) { VertBuffer_upload_data(verts); } } uint GPU_vertbuf_get_memory_usage(void) { return vbo_memory_usage; }