Files
blender/intern/gawain/src/vertex_format.c
Mike Erwin c1dc078840 Gawain: remove GL enum from vertex format API
Callers now have to use Gawain's COMP enum to specify vertex attributes.

This makes the API more bullet-proof (at least less vulnerable) since GLenum covers waaay more than component types.

Also prepares us for Vulkan.
2017-04-07 16:00:03 -04:00

269 lines
6.4 KiB
C

// Gawain vertex format
//
// This code is part of the Gawain library, with modifications
// specific to integration with Blender.
//
// Copyright 2016 Mike Erwin
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
#include "vertex_format.h"
#include <stdlib.h>
#include <string.h>
#define PACK_DEBUG 0
#if PACK_DEBUG
#include <stdio.h>
#endif
void VertexFormat_clear(VertexFormat* format)
{
#if TRUST_NO_ONE
memset(format, 0, sizeof(VertexFormat));
#else
format->attrib_ct = 0;
format->packed = false;
format->name_offset = 0;
#endif
}
void VertexFormat_copy(VertexFormat* dest, const VertexFormat* src)
{
// copy regular struct fields
memcpy(dest, src, sizeof(VertexFormat));
}
static GLenum convert_comp_type_to_gl(VertexCompType type)
{
static const GLenum table[] = {
[COMP_I8] = GL_BYTE,
[COMP_U8] = GL_UNSIGNED_BYTE,
[COMP_I16] = GL_SHORT,
[COMP_U16] = GL_UNSIGNED_SHORT,
[COMP_I32] = GL_INT,
[COMP_U32] = GL_UNSIGNED_INT,
[COMP_F32] = GL_FLOAT,
#if USE_10_10_10
[COMP_I10] = GL_INT_2_10_10_10_REV
#endif
};
return table[type];
}
static unsigned comp_sz(VertexCompType type)
{
#if TRUST_NO_ONE
assert(type <= COMP_F32); // other types have irregular sizes (not bytes)
#endif
const GLubyte sizes[] = {1,1,2,2,4,4,4};
return sizes[type];
}
static unsigned attrib_sz(const Attrib *a)
{
#if USE_10_10_10
if (a->comp_type == COMP_I10)
return 4; // always packed as 10_10_10_2
#endif
return a->comp_ct * comp_sz(a->comp_type);
}
static unsigned attrib_align(const Attrib *a)
{
#if USE_10_10_10
if (a->comp_type == COMP_I10)
return 4; // always packed as 10_10_10_2
#endif
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
}
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;
}
static const char* copy_attrib_name(VertexFormat* format, const char* name)
{
// strncpy does 110% of what we need; let's do exactly 100%
char* name_copy = format->names + format->name_offset;
unsigned available = VERTEX_ATTRIB_NAMES_BUFFER_LEN - format->name_offset;
bool terminated = false;
for (unsigned i = 0; i < available; ++i)
{
const char c = name[i];
name_copy[i] = c;
if (c == '\0')
{
terminated = true;
format->name_offset += (i + 1);
break;
}
}
#if TRUST_NO_ONE
assert(terminated);
assert(format->name_offset <= VERTEX_ATTRIB_NAMES_BUFFER_LEN);
#endif
return name_copy;
}
unsigned VertexFormat_add_attrib(VertexFormat* format, const char* name, VertexCompType 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
assert(comp_ct >= 1 && comp_ct <= 4);
switch (comp_type)
{
case COMP_F32:
// float type can only kept as float
assert(fetch_mode == KEEP_FLOAT);
break;
#if USE_10_10_10
case COMP_I10:
// 10_10_10 format intended for normals (xyz) or colors (rgb)
// extra component packed.w can be manually set to { -2, -1, 0, 1 }
assert(comp_ct == 3 || comp_ct == 4);
assert(fetch_mode == NORMALIZE_INT_TO_FLOAT); // not strictly required, may relax later
break;
#endif
default:
// integer types can be kept as int or converted/normalized to float
assert(fetch_mode != KEEP_FLOAT);
}
#endif
const unsigned attrib_id = format->attrib_ct++;
Attrib* attrib = format->attribs + attrib_id;
attrib->name = copy_attrib_name(format, name);
attrib->comp_type = comp_type;
attrib->gl_comp_type = convert_comp_type_to_gl(comp_type);
#if USE_10_10_10
attrib->comp_ct = (comp_type == COMP_I10) ? 4 : comp_ct; // system needs 10_10_10_2 to be 4 or BGRA
#else
attrib->comp_ct = comp_ct;
#endif
attrib->sz = attrib_sz(attrib);
attrib->offset = 0; // offsets & stride are calculated later (during pack)
attrib->fetch_mode = fetch_mode;
return attrib_id;
}
unsigned padding(unsigned offset, unsigned alignment)
{
const unsigned mod = offset % alignment;
return (mod == 0) ? 0 : (alignment - mod);
}
#if PACK_DEBUG
static void show_pack(unsigned a_idx, unsigned sz, unsigned 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 VertexFormat_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
// (keep attrib ID order, adjust their offsets to reorder in buffer)
// TODO:
// realloc just enough to hold the final combo string. And just enough to
// hold used attribs, not all 16.
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;
}
#if USE_10_10_10
// OpenGL ES packs in a different order as desktop GL but component conversion is the same.
// Of the code here, only struct PackedNormal needs to change.
#define SIGNED_INT_10_MAX 511
#define SIGNED_INT_10_MIN -512
static int clampi(int x, int min_allowed, int max_allowed)
{
#if TRUST_NO_ONE
assert(min_allowed <= max_allowed);
#endif
if (x < min_allowed)
return min_allowed;
else if (x > max_allowed)
return max_allowed;
else
return x;
}
static int quantize(float x)
{
int qx = x * 511.0f;
return clampi(qx, SIGNED_INT_10_MIN, SIGNED_INT_10_MAX);
}
PackedNormal convert_i10_v3(const float data[3])
{
PackedNormal n = { .x = quantize(data[0]), .y = quantize(data[1]), .z = quantize(data[2]) };
return n;
}
#endif // USE_10_10_10