diff options
author | Mike Erwin <significant.bit@gmail.com> | 2016-08-04 22:36:20 +0300 |
---|---|---|
committer | Mike Erwin <significant.bit@gmail.com> | 2016-08-04 22:36:20 +0300 |
commit | 797f1896fa023c0404965b987a7334448857c665 (patch) | |
tree | e7c0c3f9e49faba388039ec1a51e6a83d29a6144 /source/blender/gpu | |
parent | 42d816a3d9d63acfc8ef609bc16eb95d44cd75d7 (diff) |
OpenGL: immediate mode work-alike
Introducing an immediate mode drawing API that works with modern GL 3.2
core profile. I wrote and tested this using a core context on Mac.
This is part of the Gawain library which is Apache 2 licensed. Be very
careful not to pull other Blender code into these files.
Modifications for the Blender integration:
- prefix filenames to match rest of Blender’s GPU libs
- include GPU_glew.h instead of <OpenGL/gl3.h>
- disable thread-local vars until we figure out how best to do this
Diffstat (limited to 'source/blender/gpu')
-rw-r--r-- | source/blender/gpu/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/gpu/GPU_immediate.h | 67 | ||||
-rw-r--r-- | source/blender/gpu/intern/gpu_immediate.c | 417 |
3 files changed, 486 insertions, 0 deletions
diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index cfa083116f2..5708352c72e 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -54,6 +54,7 @@ set(SRC intern/gpu_draw.c intern/gpu_extensions.c intern/gpu_framebuffer.c + intern/gpu_immediate.c intern/gpu_init_exit.c intern/gpu_material.c intern/gpu_select.c @@ -89,6 +90,7 @@ set(SRC GPU_extensions.h GPU_framebuffer.h GPU_glew.h + GPU_immediate.h GPU_init_exit.h GPU_material.h GPU_select.h diff --git a/source/blender/gpu/GPU_immediate.h b/source/blender/gpu/GPU_immediate.h new file mode 100644 index 00000000000..aeed133645c --- /dev/null +++ b/source/blender/gpu/GPU_immediate.h @@ -0,0 +1,67 @@ + +// Gawain immediate mode work-alike, take 2 +// +// This code is part of the Gawain library, with modifications +// specific to integration with Blender. +// +// Copyright 2016 Mike Erwin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +#pragma once + +#include "GPU_glew.h" +#include <stdbool.h> + +#define PER_THREAD +// #define PER_THREAD __thread +// MSVC uses __declspec(thread) for C code + +#define MAX_VERTEX_ATTRIBS 16 + +#define TRUST_NO_ONE 1 + +typedef enum { + KEEP_FLOAT, + KEEP_INT, + NORMALIZE_INT_TO_FLOAT, // 127 (ubyte) -> 0.5 (and so on for other int types) + CONVERT_INT_TO_FLOAT // 127 (any int type) -> 127.0 +} VertexFetchMode; + +typedef struct { + GLenum comp_type; + unsigned comp_ct; // 1 to 4 + unsigned sz; // size in bytes, 1 to 16 + unsigned offset; // from beginning of vertex, in bytes + VertexFetchMode fetch_mode; + char* name; // TODO: shared allocation of all names within a VertexFormat +} Attrib; + +typedef struct { + unsigned attrib_ct; // 0 to 16 (MAX_VERTEX_ATTRIBS) + unsigned stride; // stride in bytes, 1 to 256 + bool packed; + Attrib attribs[MAX_VERTEX_ATTRIBS]; // TODO: variable-size attribs array +} VertexFormat; + +void clear_VertexFormat(VertexFormat*); +unsigned add_attrib(VertexFormat*, const char* name, GLenum comp_type, unsigned comp_ct, VertexFetchMode); +void pack(VertexFormat*); +// unsigned attrib_idx(const VertexFormat*, const char* name); +void bind_attrib_locations(const VertexFormat*, GLuint program); + +extern PER_THREAD VertexFormat immVertexFormat; // so we don't have to copy or pass around + +void immInit(void); +void immDestroy(void); + +void immBegin(GLenum primitive, unsigned vertex_ct); +void immEnd(void); + +void immAttrib1f(unsigned attrib_id, float x); +void immAttrib3f(unsigned attrib_id, float x, float y, float z); + +void immEndVertex(void); // and move on to the next vertex diff --git a/source/blender/gpu/intern/gpu_immediate.c b/source/blender/gpu/intern/gpu_immediate.c new file mode 100644 index 00000000000..e9d19aef3f8 --- /dev/null +++ b/source/blender/gpu/intern/gpu_immediate.c @@ -0,0 +1,417 @@ + +// Gawain immediate mode work-alike, take 2 +// +// This code is part of the Gawain library, with modifications +// specific to integration with Blender. +// +// Copyright 2016 Mike Erwin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 + +#include "GPU_immediate.h" +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#define PACK_DEBUG 0 + +#if PACK_DEBUG + #include <stdio.h> +#endif + +void clear_VertexFormat(VertexFormat* format) + { + for (unsigned a = 0; a < format->attrib_ct; ++a) + free(format->attribs[a].name); + +#if TRUST_NO_ONE + memset(format, 0, sizeof(VertexFormat)); +#else + format->attrib_ct = 0; + format->packed = false; +#endif + } + +static unsigned comp_sz(GLenum type) + { +#if TRUST_NO_ONE + assert(type >= GL_BYTE && type <= GL_FLOAT); +#endif + + const GLubyte sizes[] = {1,1,2,2,4,4,4}; + return sizes[type - GL_BYTE]; + } + +static unsigned attrib_sz(const Attrib *a) + { + return a->comp_ct * comp_sz(a->comp_type); + } + +static unsigned attrib_align(const Attrib *a) + { + unsigned c = comp_sz(a->comp_type); + if (a->comp_ct == 3 && c <= 2) + return 4 * c; // AMD HW can't fetch these well, so pad it out (other vendors too?) + else + return c; // most fetches are ok if components are naturally aligned + } + +static unsigned vertex_buffer_size(const VertexFormat* format, unsigned vertex_ct) + { +#if TRUST_NO_ONE + assert(format->packed && format->stride > 0); +#endif + + return format->stride * vertex_ct; + } + +unsigned add_attrib(VertexFormat* format, const char* name, GLenum comp_type, unsigned comp_ct, VertexFetchMode fetch_mode) + { +#if TRUST_NO_ONE + assert(format->attrib_ct < MAX_VERTEX_ATTRIBS); // there's room for more + assert(!format->packed); // packed means frozen/locked +#endif + + const unsigned attrib_id = format->attrib_ct++; + Attrib* attrib = format->attribs + attrib_id; + + attrib->name = strdup(name); + attrib->comp_type = comp_type; + attrib->comp_ct = comp_ct; + attrib->sz = attrib_sz(attrib); + attrib->offset = 0; // offsets & stride are calculated later (during pack) + attrib->fetch_mode = fetch_mode; + + return attrib_id; + } + +static unsigned padding(unsigned offset, unsigned alignment) + { + const unsigned mod = offset % alignment; + return (mod == 0) ? 0 : (alignment - mod); + } + +#if PACK_DEBUG +void show_pack(a_idx, sz, pad) + { + const char c = 'A' + a_idx; + for (unsigned i = 0; i < pad; ++i) + putchar('-'); + for (unsigned i = 0; i < sz; ++i) + putchar(c); + } +#endif + +void pack(VertexFormat* format) + { + // for now, attributes are packed in the order they were added, + // making sure each attrib is naturally aligned (add padding where necessary) + + // later we can implement more efficient packing w/ reordering + + Attrib* a0 = format->attribs + 0; + a0->offset = 0; + unsigned offset = a0->sz; + +#if PACK_DEBUG + show_pack(0, a0->sz, 0); +#endif + + for (unsigned a_idx = 1; a_idx < format->attrib_ct; ++a_idx) + { + Attrib* a = format->attribs + a_idx; + unsigned mid_padding = padding(offset, attrib_align(a)); + offset += mid_padding; + a->offset = offset; + offset += a->sz; + +#if PACK_DEBUG + show_pack(a_idx, a->sz, mid_padding); +#endif + } + + unsigned end_padding = padding(offset, attrib_align(a0)); + +#if PACK_DEBUG + show_pack(0, 0, end_padding); + putchar('\n'); +#endif + + format->stride = offset + end_padding; + format->packed = true; + } + +void bind_attrib_locations(const VertexFormat* format, GLuint program) + { +#if TRUST_NO_ONE + assert(glIsProgram(program)); +#endif + + for (unsigned a_idx = 0; a_idx < format->attrib_ct; ++a_idx) + { + const Attrib* a = &format->attribs[a_idx]; + glBindAttribLocation(program, a_idx, a->name); + } + } + +// --- immediate mode work-alike -------------------------------- + +typedef struct { + // current draw call + void* buffer_data; + unsigned buffer_offset; + unsigned buffer_bytes_mapped; + unsigned vertex_ct; + GLenum primitive; + + // current vertex + unsigned vertex_idx; + void* vertex_data; + unsigned short attrib_value_bits; // which attributes of current vertex have been given values? + + GLuint vbo_id; + GLuint vao_id; +} Immediate; + +// size of internal buffer -- make this adjustable? +// #define IMM_BUFFER_SIZE (4 * 1024 * 1024) +#define IMM_BUFFER_SIZE 1024 + +static PER_THREAD bool initialized = false; +static PER_THREAD Immediate imm; +PER_THREAD VertexFormat immVertexFormat; + +void immInit() + { +#if TRUST_NO_ONE + assert(!initialized); +#endif + + clear_VertexFormat(&immVertexFormat); + memset(&imm, 0, sizeof(Immediate)); + + glGenVertexArrays(1, &imm.vao_id); + glBindVertexArray(imm.vao_id); + glGenBuffers(1, &imm.vbo_id); + glBindBuffer(GL_ARRAY_BUFFER, imm.vbo_id); + glBufferData(GL_ARRAY_BUFFER, IMM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW); + + imm.primitive = GL_NONE; + + // glBindBuffer(GL_ARRAY_BUFFER, 0); + initialized = true; + } + +void immDestroy() + { +#if TRUST_NO_ONE + assert(initialized); + assert(imm.primitive == GL_NONE); // make sure we're not between a Begin/End pair +#endif + + clear_VertexFormat(&immVertexFormat); + glDeleteVertexArrays(1, &imm.vao_id); + glDeleteBuffers(1, &imm.vbo_id); + initialized = false; + } + +void immBegin(GLenum primitive, unsigned vertex_ct) + { +#if TRUST_NO_ONE + assert(initialized); + assert(imm.primitive == GL_NONE); // make sure we haven't already begun + + // does vertex_ct make sense for this primitive type? + assert(vertex_ct > 0); + switch (primitive) + { + case GL_POINTS: + break; + case GL_LINES: + assert(vertex_ct % 2 == 0); + break; + case GL_LINE_STRIP: + case GL_LINE_LOOP: + assert(vertex_ct > 2); // otherwise why bother? + break; + case GL_TRIANGLES: + assert(vertex_ct % 3 == 0); + break; + default: + assert(false); + } +#endif + + imm.primitive = primitive; + imm.vertex_ct = vertex_ct; + imm.vertex_idx = 0; + imm.attrib_value_bits = 0; + + // how many bytes do we need for this draw call? + const unsigned bytes_needed = vertex_buffer_size(&immVertexFormat, vertex_ct); + +#if TRUST_NO_ONE + assert(bytes_needed <= IMM_BUFFER_SIZE); +#endif + +// glBindBuffer(GL_ARRAY_BUFFER, imm.vbo_id); + + // does the current buffer have enough room? + const unsigned available_bytes = IMM_BUFFER_SIZE - imm.buffer_offset; + // ensure vertex data is aligned + const unsigned pre_padding = padding(imm.buffer_offset, immVertexFormat.stride); // might waste a little space, but it's safe + if ((bytes_needed + pre_padding) <= available_bytes) + imm.buffer_offset += pre_padding; + else + { + // orphan this buffer & start with a fresh one + glMapBufferRange(GL_ARRAY_BUFFER, 0, IMM_BUFFER_SIZE, GL_MAP_INVALIDATE_BUFFER_BIT); + // glInvalidateBufferData(imm.vbo_id); // VERSION >= 4.3 || ARB_invalidate_subdata + + imm.buffer_offset = 0; + } + +// printf("mapping %u to %u\n", imm.buffer_offset, imm.buffer_offset + bytes_needed - 1); + + imm.buffer_data = glMapBufferRange(GL_ARRAY_BUFFER, imm.buffer_offset, bytes_needed, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + +#if TRUST_NO_ONE + assert(imm.buffer_data != NULL); +#endif + + imm.buffer_bytes_mapped = bytes_needed; + imm.vertex_data = imm.buffer_data; + } + +void immEnd() + { +#if TRUST_NO_ONE + assert(imm.primitive != GL_NONE); // make sure we're between a Begin/End pair + assert(imm.vertex_idx == imm.vertex_ct); // with all vertices defined +#endif + + glUnmapBuffer(GL_ARRAY_BUFFER); + + // set up VAO -- can be done during Begin or End really +// glBindVertexArray(imm.vao_id); + + const unsigned stride = immVertexFormat.stride; + + for (unsigned a_idx = 0; a_idx < immVertexFormat.attrib_ct; ++a_idx) + { + const Attrib* a = immVertexFormat.attribs + a_idx; + + const unsigned offset = imm.buffer_offset + a->offset; + const GLvoid* pointer = (const GLvoid*)0 + offset; + +// printf("enabling attrib %u '%s' at offset %u, stride %u\n", a_idx, a->name, offset, stride); + glEnableVertexAttribArray(a_idx); + + switch (a->fetch_mode) + { + case KEEP_FLOAT: + case CONVERT_INT_TO_FLOAT: + glVertexAttribPointer(a_idx, a->comp_ct, a->comp_type, GL_FALSE, stride, pointer); + break; + case NORMALIZE_INT_TO_FLOAT: + glVertexAttribPointer(a_idx, a->comp_ct, a->comp_type, GL_TRUE, stride, pointer); + break; + case KEEP_INT: + glVertexAttribIPointer(a_idx, a->comp_ct, a->comp_type, stride, pointer); + } + } + + for (unsigned a_idx = immVertexFormat.attrib_ct; a_idx < MAX_VERTEX_ATTRIBS; ++a_idx) + { +// printf("disabling attrib %u\n", a_idx); + glDisableVertexAttribArray(a_idx); + // TODO: compare with previous draw's attrib_ct + // will always need to update pointers, but can reduce Enable/Disable calls + } + + glDrawArrays(imm.primitive, 0, imm.vertex_ct); + +// glBindVertexArray(0); + + // prep for next immBegin + imm.buffer_offset += imm.buffer_bytes_mapped; + imm.primitive = GL_NONE; + + // further optional cleanup + imm.buffer_bytes_mapped = 0; + imm.buffer_data = NULL; + imm.vertex_data = NULL; + } + +static void setAttribValueBit(unsigned attrib_id) + { + unsigned short mask = 1 << attrib_id; + +#if TRUST_NO_ONE + assert((imm.attrib_value_bits & mask) == 0); // not already set +#endif + + imm.attrib_value_bits |= mask; + } + +void immAttrib1f(unsigned attrib_id, float x) + { + Attrib* attrib = immVertexFormat.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < immVertexFormat.attrib_ct); + assert(attrib->comp_type == GL_FLOAT); + assert(attrib->comp_ct == 1); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.primitive != GL_NONE); // make sure we're between a Begin/End pair +#endif + + setAttribValueBit(attrib_id); + + float* data = imm.vertex_data + attrib->offset; +// printf("%s %ld %p\n", __FUNCTION__, (void*)data - imm.buffer_data, data); + + data[0] = x; + } + +void immAttrib3f(unsigned attrib_id, float x, float y, float z) + { + Attrib* attrib = immVertexFormat.attribs + attrib_id; + +#if TRUST_NO_ONE + assert(attrib_id < immVertexFormat.attrib_ct); + assert(attrib->comp_type == GL_FLOAT); + assert(attrib->comp_ct == 3); + assert(imm.vertex_idx < imm.vertex_ct); + assert(imm.primitive != GL_NONE); // make sure we're between a Begin/End pair +#endif + + setAttribValueBit(attrib_id); + + float* data = imm.vertex_data + attrib->offset; +// printf("%s %ld %p\n", __FUNCTION__, (void*)data - imm.buffer_data, data); + + data[0] = x; + data[1] = y; + data[2] = z; + } + +void immEndVertex() + { +#if TRUST_NO_ONE + assert(imm.primitive != GL_NONE); // make sure we're between a Begin/End pair + + // have all attribs been assigned values? + const unsigned short all_bits = ~(0xFFFFU << immVertexFormat.attrib_ct); + assert(imm.attrib_value_bits == all_bits); + + assert(imm.vertex_idx < imm.vertex_ct); +#endif + + imm.vertex_idx++; + imm.vertex_data += immVertexFormat.stride; + imm.attrib_value_bits = 0; + } |