This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/gpu/intern/gpu_vertex_format.cc

389 lines
12 KiB
C++

/*
* 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 format
*/
#include "GPU_vertex_format.h"
#include "gpu_shader_private.hh"
#include "gpu_vertex_format_private.h"
#include <cstddef>
#include <cstring>
#include "BLI_ghash.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#define PACK_DEBUG 0
#if PACK_DEBUG
# include <stdio.h>
#endif
using namespace blender::gpu;
void GPU_vertformat_clear(GPUVertFormat *format)
{
#if TRUST_NO_ONE
memset(format, 0, sizeof(GPUVertFormat));
#else
format->attr_len = 0;
format->packed = false;
format->name_offset = 0;
format->name_len = 0;
format->deinterleaved = false;
for (uint i = 0; i < GPU_VERT_ATTR_MAX_LEN; i++) {
format->attrs[i].name_len = 0;
}
#endif
}
void GPU_vertformat_copy(GPUVertFormat *dest, const GPUVertFormat *src)
{
/* copy regular struct fields */
memcpy(dest, src, sizeof(GPUVertFormat));
}
static uint comp_sz(GPUVertCompType type)
{
#if TRUST_NO_ONE
assert(type <= GPU_COMP_F32); /* other types have irregular sizes (not bytes) */
#endif
const uint sizes[] = {1, 1, 2, 2, 4, 4, 4};
return sizes[type];
}
static uint attr_sz(const GPUVertAttr *a)
{
if (a->comp_type == GPU_COMP_I10) {
return 4; /* always packed as 10_10_10_2 */
}
return a->comp_len * comp_sz(static_cast<GPUVertCompType>(a->comp_type));
}
static uint attr_align(const GPUVertAttr *a)
{
if (a->comp_type == GPU_COMP_I10) {
return 4; /* always packed as 10_10_10_2 */
}
uint c = comp_sz(static_cast<GPUVertCompType>(a->comp_type));
if (a->comp_len == 3 && c <= 2) {
return 4 * c; /* AMD HW can't fetch these well, so pad it out (other vendors too?) */
}
return c; /* most fetches are ok if components are naturally aligned */
}
uint vertex_buffer_size(const GPUVertFormat *format, uint vertex_len)
{
#if TRUST_NO_ONE
assert(format->packed && format->stride > 0);
#endif
return format->stride * vertex_len;
}
static uchar copy_attr_name(GPUVertFormat *format, const char *name)
{
/* `strncpy` does 110% of what we need; let's do exactly 100% */
uchar name_offset = format->name_offset;
char *name_copy = format->names + name_offset;
uint available = GPU_VERT_ATTR_NAMES_BUF_LEN - name_offset;
bool terminated = false;
for (uint 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 <= GPU_VERT_ATTR_NAMES_BUF_LEN);
#else
(void)terminated;
#endif
return name_offset;
}
uint GPU_vertformat_attr_add(GPUVertFormat *format,
const char *name,
GPUVertCompType comp_type,
uint comp_len,
GPUVertFetchMode fetch_mode)
{
#if TRUST_NO_ONE
assert(format->name_len < GPU_VERT_FORMAT_MAX_NAMES); /* there's room for more */
assert(format->attr_len < GPU_VERT_ATTR_MAX_LEN); /* there's room for more */
assert(!format->packed); /* packed means frozen/locked */
assert((comp_len >= 1 && comp_len <= 4) || comp_len == 8 || comp_len == 12 || comp_len == 16);
switch (comp_type) {
case GPU_COMP_F32:
/* float type can only kept as float */
assert(fetch_mode == GPU_FETCH_FLOAT);
break;
case GPU_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_len == 3 || comp_len == 4);
/* Not strictly required, may relax later. */
assert(fetch_mode == GPU_FETCH_INT_TO_FLOAT_UNIT);
break;
default:
/* integer types can be kept as int or converted/normalized to float */
assert(fetch_mode != GPU_FETCH_FLOAT);
/* only support float matrices (see Batch_update_program_bindings) */
assert(comp_len != 8 && comp_len != 12 && comp_len != 16);
}
#endif
format->name_len++; /* Multi-name support. */
const uint attr_id = format->attr_len++;
GPUVertAttr *attr = &format->attrs[attr_id];
attr->names[attr->name_len++] = copy_attr_name(format, name);
attr->comp_type = comp_type;
attr->comp_len = (comp_type == GPU_COMP_I10) ?
4 :
comp_len; /* system needs 10_10_10_2 to be 4 or BGRA */
attr->sz = attr_sz(attr);
attr->offset = 0; /* offsets & stride are calculated later (during pack) */
attr->fetch_mode = fetch_mode;
return attr_id;
}
void GPU_vertformat_alias_add(GPUVertFormat *format, const char *alias)
{
GPUVertAttr *attr = &format->attrs[format->attr_len - 1];
#if TRUST_NO_ONE
assert(format->name_len < GPU_VERT_FORMAT_MAX_NAMES); /* there's room for more */
assert(attr->name_len < GPU_VERT_ATTR_MAX_NAMES);
#endif
format->name_len++; /* Multi-name support. */
attr->names[attr->name_len++] = copy_attr_name(format, alias);
}
/**
* Makes vertex attribute from the next vertices to be accessible in the vertex shader.
* For an attribute named "attr" you can access the next nth vertex using "attr{number}".
* Use this function after specifying all the attributes in the format.
*
* NOTE: This does NOT work when using indexed rendering.
* NOTE: Only works for first attribute name. (this limitation can be changed if needed)
*
* WARNING: this function creates a lot of aliases/attributes, make sure to keep the attribute
* name short to avoid overflowing the name-buffer.
*/
void GPU_vertformat_multiload_enable(GPUVertFormat *format, int load_count)
{
/* Sanity check. Maximum can be upgraded if needed. */
BLI_assert(load_count > 1 && load_count < 5);
/* We need a packed format because of format->stride. */
if (!format->packed) {
VertexFormat_pack(format);
}
BLI_assert((format->name_len + 1) * load_count < GPU_VERT_FORMAT_MAX_NAMES);
BLI_assert(format->attr_len * load_count <= GPU_VERT_ATTR_MAX_LEN);
BLI_assert(format->name_offset * load_count < GPU_VERT_ATTR_NAMES_BUF_LEN);
const GPUVertAttr *attr = format->attrs;
int attr_len = format->attr_len;
for (int i = 0; i < attr_len; i++, attr++) {
const char *attr_name = GPU_vertformat_attr_name_get(format, attr, 0);
for (int j = 1; j < load_count; j++) {
char load_name[64];
BLI_snprintf(load_name, sizeof(load_name), "%s%d", attr_name, j);
GPUVertAttr *dst_attr = &format->attrs[format->attr_len++];
*dst_attr = *attr;
dst_attr->names[0] = copy_attr_name(format, load_name);
dst_attr->name_len = 1;
dst_attr->offset += format->stride * j;
}
}
}
int GPU_vertformat_attr_id_get(const GPUVertFormat *format, const char *name)
{
for (int i = 0; i < format->attr_len; i++) {
const GPUVertAttr *attr = &format->attrs[i];
for (int j = 0; j < attr->name_len; j++) {
const char *attr_name = GPU_vertformat_attr_name_get(format, attr, j);
if (STREQ(name, attr_name)) {
return i;
}
}
}
return -1;
}
void GPU_vertformat_attr_rename(GPUVertFormat *format, int attr_id, const char *new_name)
{
BLI_assert(attr_id > -1 && attr_id < format->attr_len);
GPUVertAttr *attr = &format->attrs[attr_id];
char *attr_name = (char *)GPU_vertformat_attr_name_get(format, attr, 0);
BLI_assert(strlen(attr_name) == strlen(new_name));
int i = 0;
while (attr_name[i] != '\0') {
attr_name[i] = new_name[i];
i++;
}
attr->name_len = 1;
}
/* Encode 8 original bytes into 11 safe bytes. */
static void safe_bytes(char out[11], const char data[8])
{
char safe_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
uint64_t in = *(uint64_t *)data;
for (int i = 0; i < 11; i++) {
out[i] = safe_chars[in % 62lu];
in /= 62lu;
}
}
/* Warning: Always add a prefix to the result of this function as
* the generated string can start with a number and not be a valid attribute name. */
void GPU_vertformat_safe_attr_name(const char *attr_name, char *r_safe_name, uint UNUSED(max_len))
{
char data[8] = {0};
uint len = strlen(attr_name);
if (len > 8) {
/* Start with the first 4 chars of the name; */
for (int i = 0; i < 4; i++) {
data[i] = attr_name[i];
}
/* We use a hash to identify each data layer based on its name.
* NOTE: This is still prone to hash collision but the risks are very low.*/
/* Start hashing after the first 2 chars. */
*(uint *)&data[4] = BLI_ghashutil_strhash_p_murmur(attr_name + 4);
}
else {
/* Copy the whole name. Collision is barely possible
* (hash would have to be equal to the last 4 bytes). */
for (int i = 0; i < 8 && attr_name[i] != '\0'; i++) {
data[i] = attr_name[i];
}
}
/* Convert to safe bytes characters. */
safe_bytes(r_safe_name, data);
/* End the string */
r_safe_name[11] = '\0';
BLI_assert(GPU_MAX_SAFE_ATTR_NAME >= 12);
#if 0 /* For debugging */
printf("%s > %lx > %s\n", attr_name, *(uint64_t *)data, r_safe_name);
#endif
}
/**
* Make attribute layout non-interleaved.
* Warning! This does not change data layout!
* Use direct buffer access to fill the data.
* This is for advanced usage.
*
* De-interleaved data means all attribute data for each attribute
* is stored continuously like this:
* 000011112222
* instead of:
* 012012012012
*
* \note This is per attribute de-interleaving, NOT per component.
*/
void GPU_vertformat_deinterleave(GPUVertFormat *format)
{
/* Ideally we should change the stride and offset here. This would allow
* us to use GPU_vertbuf_attr_set / GPU_vertbuf_attr_fill. But since
* we use only 11 bits for attr->offset this limits the size of the
* buffer considerably. So instead we do the conversion when creating
* bindings in create_bindings(). */
format->deinterleaved = true;
}
uint padding(uint offset, uint alignment)
{
const uint mod = offset % alignment;
return (mod == 0) ? 0 : (alignment - mod);
}
#if PACK_DEBUG
static void show_pack(uint a_idx, uint sz, uint pad)
{
const char c = 'A' + a_idx;
for (uint i = 0; i < pad; i++) {
putchar('-');
}
for (uint i = 0; i < sz; i++) {
putchar(c);
}
}
#endif
void VertexFormat_pack(GPUVertFormat *format)
{
GPUVertAttr *a0 = &format->attrs[0];
a0->offset = 0;
uint offset = a0->sz;
#if PACK_DEBUG
show_pack(0, a0->sz, 0);
#endif
for (uint a_idx = 1; a_idx < format->attr_len; a_idx++) {
GPUVertAttr *a = &format->attrs[a_idx];
uint mid_padding = padding(offset, attr_align(a));
offset += mid_padding;
a->offset = offset;
offset += a->sz;
#if PACK_DEBUG
show_pack(a_idx, a->sz, mid_padding);
#endif
}
uint end_padding = padding(offset, attr_align(a0));
#if PACK_DEBUG
show_pack(0, 0, end_padding);
putchar('\n');
#endif
format->stride = offset + end_padding;
format->packed = true;
}
void GPU_vertformat_from_shader(GPUVertFormat *format, const struct GPUShader *gpushader)
{
const Shader *shader = reinterpret_cast<const Shader *>(gpushader);
shader->vertformat_from_shader(format);
}