diff --git a/src/api/api.h b/src/api/api.h index 2c2937c1..61be5669 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -160,17 +160,10 @@ bool luax_writefile(const char* filename, const void* data, size_t size); struct DataField; struct Material; struct ColoredString; -void luax_checkfieldn(lua_State* L, int index, int type, void* data); -void luax_checkfieldv(lua_State* L, int index, int type, void* data); -void luax_checkfieldt(lua_State* L, int index, int type, void* data); -uint32_t luax_checkfieldarray(lua_State* L, int index, const struct DataField* array, char* data); -void luax_checkdataflat(lua_State* L, int index, int subindex, uint32_t count, const struct DataField* format, char* data); -void luax_checkdatatuples(lua_State* L, int index, int start, uint32_t count, const struct DataField* format, char* data); -void luax_checkdatakeys(lua_State* L, int index, int start, uint32_t count, const struct DataField* array, char* data); -void luax_checkstruct(lua_State* L, int index, const struct DataField* fields, uint32_t count, char* data); +void luax_checkbufferdata(lua_State* L, int index, const struct DataField* format, char* data); int luax_pushbufferdata(lua_State* L, const struct DataField* format, uint32_t count, char* data); void luax_pushbufferformat(lua_State* L, const struct DataField* fields, uint32_t count); -uint32_t luax_gettablestride(lua_State* L, int index, int subindex, struct DataField* fields, uint32_t count); +int luax_gettablestride(lua_State* L, int type); uint32_t luax_checkcomparemode(lua_State* L, int index); struct Material* luax_optmaterial(lua_State* L, int index); struct ColoredString* luax_checkcoloredstrings(lua_State* L, int index, uint32_t* count, struct ColoredString* stack); diff --git a/src/api/l_graphics.c b/src/api/l_graphics.c index 01e58f28..058f3c02 100644 --- a/src/api/l_graphics.c +++ b/src/api/l_graphics.c @@ -625,13 +625,12 @@ static int l_lovrGraphicsNewBuffer(lua_State* L) { lovrCheck(blob->size < UINT32_MAX, "Blob is too big to create a Buffer"); info.size = (uint32_t) blob->size; } else if (type == LUA_TSTRING) { - info.fieldCount = 2; + info.fieldCount = 1; info.format = format; - format[0] = (DataField) { .fieldCount = 1 }; - format[1] = (DataField) { .type = luax_checkdatatype(L, 1) }; + format[0] = (DataField) { .type = luax_checkdatatype(L, 1) }; } else if (type == LUA_TTABLE) { info.format = format; - format[0] = (DataField) { .fieldCount = luax_len(L, 1) }; + format[0] = (DataField) { .fieldCount = luax_len(L, 1), .fields = format + 1 }; lua_rawgeti(L, 1, 1); bool anonymous = lua_type(L, -1) == LUA_TSTRING; @@ -646,6 +645,12 @@ static int l_lovrGraphicsNewBuffer(lua_State* L) { format[i] = (DataField) { .type = luax_checkdatatype(L, -1) }; lua_pop(L, 1); } + + // Convert single-field anonymous formats to regular arrays + if (format->fieldCount == 1) { + format->type = format[1].type; + format->fieldCount = 0; + } } else { info.fieldCount = 1; luax_checkbufferformat(L, 1, format, &info.fieldCount, COUNTOF(format)); @@ -664,19 +669,18 @@ static int l_lovrGraphicsNewBuffer(lua_State* L) { // Length/size if (info.format) { - format->fields = format + 1; lovrGraphicsAlignFields(format, layout); switch (lua_type(L, 2)) { - case LUA_TNIL: case LUA_TNONE: format->length = 1; break; + case LUA_TNIL: case LUA_TNONE: format->length = 0; break; case LUA_TNUMBER: format->length = luax_checku32(L, 2); break; case LUA_TTABLE: lua_rawgeti(L, 2, 1); - if (lua_istable(L, -1)) { - format->length = luax_len(L, -2); - } else if (lua_isnil(L, -1) && format->fields[0].name) { - format->length = 1; + if (lua_type(L, -1) == LUA_TNUMBER) { + lovrCheck(format->fieldCount <= 1, "Struct data must be provided as a table of tables"); + DataType type = format->fieldCount == 0 ? format->type : format->fields[0].type; + format->length = luax_len(L, -2) / luax_gettablestride(L, type); } else { - format->length = luax_len(L, -2) / luax_gettablestride(L, 2, 1, format->fields, format->fieldCount); + format->length = luax_len(L, -2); } lua_pop(L, 1); hasData = true; @@ -688,7 +692,7 @@ static int l_lovrGraphicsNewBuffer(lua_State* L) { format->length = info.size / format->stride; break; } else if (luax_tovector(L, 2, NULL)) { - format->length = 1; + format->length = 0; hasData = true; break; } @@ -703,25 +707,7 @@ static int l_lovrGraphicsNewBuffer(lua_State* L) { if (blob) { memcpy(data, blob->data, info.size); } else if (hasData) { - if (luax_tovector(L, 2, NULL)) { - luax_checkfieldv(L, 2, format->fields[0].type, data); - } else if (luax_len(L, 2) == 0 && format->fields[0].name) { - luax_checkstruct(L, 2, format->fields, format->fieldCount, data); - } else { - lua_rawgeti(L, 2, 1); - bool complexFormat = lovrBufferGetInfo(buffer)->complexFormat; - bool tableOfTables = complexFormat || lua_istable(L, -1); - bool tuples = tableOfTables && !complexFormat && (luax_len(L, -1) > 0 || !format->fields[0].name); - lua_pop(L, 1); - - if (tuples) { - luax_checkdatatuples(L, 2, 1, format->length, format, data); - } else if (tableOfTables) { - luax_checkdatakeys(L, 2, 1, format->length, format, data); - } else { - luax_checkdataflat(L, 2, 1, format->length, format, data); - } - } + luax_checkbufferdata(L, 2, format, data); } luax_pushtype(L, Buffer, buffer); @@ -1403,7 +1389,7 @@ static int l_lovrGraphicsNewMesh(lua_State* L) { if (blob) { memcpy(vertices, blob->data, blob->size); } else if (hasData) { - luax_checkdatatuples(L, index, 1, format->length, lovrMeshGetVertexFormat(mesh), vertices); + luax_checkbufferdata(L, index, lovrMeshGetVertexFormat(mesh), vertices); } luax_pushtype(L, Mesh, mesh); diff --git a/src/api/l_graphics_buffer.c b/src/api/l_graphics_buffer.c index 3c75bf28..70d51761 100644 --- a/src/api/l_graphics_buffer.c +++ b/src/api/l_graphics_buffer.c @@ -62,11 +62,67 @@ typedef union { float* f32; } DataPointer; -void luax_checkfieldn(lua_State* L, int index, int type, void* data) { +#ifndef LOVR_UNCHECKED +#define luax_fieldcheck(L, cond, index, field, arr) if (!(cond)) luax_fielderror(L, index, field, arr) +#else +#define luax_fieldcheck(L, cond, index, field, arr) ((void) 0) +#endif + +static void luax_fielderror(lua_State* L, int index, const DataField* field, bool arr) { + if (index < 0) index += lua_gettop(L) + 1; + + if (!field->parent) { + lua_pushliteral(L, "buffer data"); + } else if (!field->name) { + lua_pushliteral(L, ""); + } else { + lua_pushstring(L, field->name); + DataField* parent = field->parent; + while (parent && parent->name) { + if (parent->length > 0) { + lua_pushfstring(L, "%s[]", parent->name); + } else { + lua_pushstring(L, parent->name); + } + lua_insert(L, -2); + lua_pushliteral(L, "."); + lua_insert(L, -2); + lua_concat(L, 3); + parent = parent->parent; + } + lua_pushliteral(L, "'"); + lua_insert(L, -2); + lua_pushliteral(L, "'"); + lua_concat(L, 3); + } + + const char* kind; + const char* expected; + if (arr && field->length > 0) { + kind = "array"; + expected = "table"; + } else if (field->fieldCount > 0) { + kind = "struct"; + expected = "table"; + } else if (typeComponents[field->type] > 1) { + kind = "vector"; + expected = "number, table, or vector"; + } else { + kind = "scalar"; + expected = "number"; + } + + const char* name = lua_tostring(L, -1); + const char* typename = luaL_typename(L, index); + luaL_error(L, "Bad type for %s %s: %s expected, got %s", kind, name, expected, typename); +} + +static void luax_checkfieldn(lua_State* L, int index, const DataField* field, void* data) { DataPointer p = { .raw = data }; - for (uint32_t i = 0; i < typeComponents[type]; i++) { + for (uint32_t i = 0; i < typeComponents[field->type]; i++) { + luax_fieldcheck(L, lua_type(L, index + i) == LUA_TNUMBER, index + i, field, false); double x = lua_tonumber(L, index + i); - switch (type) { + switch (field->type) { case TYPE_I8x4: p.i8[i] = (int8_t) x; break; case TYPE_U8x4: p.u8[i] = (uint8_t) x; break; case TYPE_SN8x4: p.i8[i] = (int8_t) CLAMP(x, -1.f, 1.f) * INT8_MAX; break; @@ -107,19 +163,18 @@ void luax_checkfieldn(lua_State* L, int index, int type, void* data) { } } -void luax_checkfieldv(lua_State* L, int index, int type, void* data) { +static void luax_checkfieldv(lua_State* L, int index, const DataField* field, void* data) { DataPointer p = { .raw = data }; - uint32_t n = typeComponents[type]; - lovrCheck(n > 1, "Expected number for scalar data type, got vector"); VectorType vectorType; float* v = luax_tovector(L, index, &vectorType); - lovrCheck(v, "Expected vector, got non-vector userdata"); - if (n >= TYPE_MAT2 && n <= TYPE_MAT4) { + uint32_t n = typeComponents[field->type]; + luax_fieldcheck(L, v && n > 1, index, field, false); + if (field->type >= TYPE_MAT2 && field->type <= TYPE_MAT4) { lovrCheck(vectorType == V_MAT4, "Tried to send a non-matrix to a matrix type"); } else { lovrCheck(vectorComponents[vectorType] == n, "Expected %d vector components, got %d", n, vectorComponents[vectorType]); } - switch (type) { + switch (field->type) { case TYPE_I8x4: for (int i = 0; i < 4; i++) p.i8[i] = (int8_t) v[i]; break; case TYPE_U8x4: for (int i = 0; i < 4; i++) p.u8[i] = (uint8_t) v[i]; break; case TYPE_SN8x4: for (int i = 0; i < 4; i++) p.i8[i] = (int8_t) CLAMP(v[i], -1.f, 1.f) * INT8_MAX; break; @@ -152,176 +207,121 @@ void luax_checkfieldv(lua_State* L, int index, int type, void* data) { } } -void luax_checkfieldt(lua_State* L, int index, int type, void* data) { +static void luax_checkfieldt(lua_State* L, int index, const DataField* field, void* data) { + luax_fieldcheck(L, lua_istable(L, index), index, field, false); if (index < 0) index += lua_gettop(L) + 1; - int n = typeComponents[type]; - for (int i = 0; i < n; i++) { - lua_rawgeti(L, index, i + 1); + int n = typeComponents[field->type]; + for (int i = 1; i <= n; i++) { + lua_rawgeti(L, index, i); } - luax_checkfieldn(L, -n, type, data); + luax_checkfieldn(L, -n, field, data); lua_pop(L, n); } -uint32_t luax_checkfieldarray(lua_State* L, int index, const DataField* array, char* data) { - int components = typeComponents[array->type]; +static void luax_checkstruct(lua_State* L, int index, const DataField* structure, char* data) { + luax_fieldcheck(L, lua_istable(L, index), index, structure, false); + if (index < 0) index += lua_gettop(L) + 1; uint32_t length = luax_len(L, index); + uint32_t f = 0; - if (components == 1) { - uint32_t count = MIN(length, array->length); - for (uint32_t i = 0; i < count; i++, data += array->stride) { - lua_rawgeti(L, index, i + 1); - luax_checkfieldn(L, -1, array->type, data); + // Number keys + for (uint32_t i = 1; i <= length && f < structure->fieldCount; f++) { + lua_rawgeti(L, index, i); + const DataField* field = &structure->fields[f]; + if (field->length == 0 && field->fieldCount == 0 && lua_type(L, -1) == LUA_TNUMBER) { + int n = typeComponents[field->type]; + for (int c = 1; c < n; c++) lua_rawgeti(L, index, i + c); + luax_checkfieldn(L, -n, field, data + field->offset); + lua_pop(L, n); + i += n; + } else { + luax_checkbufferdata(L, -1, field, data + field->offset); lua_pop(L, 1); - } - return count; - } - - lua_rawgeti(L, index, 1); - int innerType = lua_type(L, -1); - lua_pop(L, 1); - - uint32_t count; - - switch (innerType) { - case LUA_TNUMBER: - if (index < 0) index += lua_gettop(L) + 1; - count = MIN(array->length, length / components); - lovrCheck(length % components == 0, "Table length for key '%s' must be divisible by %d", array->name, components); - for (uint32_t i = 0; i < count; i++, data += array->stride) { - for (int j = 1; j <= components; j++) { - lua_rawgeti(L, index, i * components + j); - } - luax_checkfieldn(L, -components, array->type, data); - lua_pop(L, components); - } - break; - case LUA_TUSERDATA: - case LUA_TLIGHTUSERDATA: - count = MIN(array->length, length); - for (uint32_t i = 0; i < count; i++, data += array->stride) { - lua_rawgeti(L, index, i + 1); - luax_checkfieldv(L, -1, array->type, data); - lua_pop(L, 1); - } - break; - case LUA_TTABLE: - count = MIN(array->length, length); - for (uint32_t i = 0; i < count; i++, data += array->stride) { - lua_rawgeti(L, index, i + 1); - luax_checkfieldt(L, -1, array->type, data); - lua_pop(L, 1); - } - break; - case LUA_TNIL: - count = 0; - break; - } - - return count; -} - -void luax_checkdataflat(lua_State* L, int index, int subindex, uint32_t count, const DataField* format, char* data) { - for (uint32_t i = 0; i < count; i++, data += format->stride) { - for (uint32_t f = 0; f < format->fieldCount; f++) { - int n = 1; - lua_rawgeti(L, index, subindex++); - const DataField* field = &format->fields[f]; - if (lua_isuserdata(L, -1)) { - luax_checkfieldv(L, -1, field->type, data + field->offset); - } else { - n = typeComponents[field->type]; - for (int c = 1; c < n; c++) { - lua_rawgeti(L, index, subindex++); - } - luax_checkfieldn(L, -n, field->type, data + field->offset); - } - lua_pop(L, n); + i++; } } -} -void luax_checkdatatuples(lua_State* L, int index, int start, uint32_t count, const DataField* format, char* data) { - for (uint32_t i = 0; i < count; i++, data += format->stride) { - lua_rawgeti(L, index, start + i); - lovrCheck(lua_type(L, -1) == LUA_TTABLE, "Expected table of tables"); - - for (uint32_t f = 0, subindex = 1; f < format->fieldCount; f++) { - int n = 1; - lua_rawgeti(L, -1, subindex); - const DataField* field = &format->fields[f]; - if (lua_isuserdata(L, -1)) { - luax_checkfieldv(L, -1, field->type, data + field->offset); - } else { - while (n < (int) typeComponents[field->type]) { - lua_rawgeti(L, -n - 1, subindex + n); - n++; - } - luax_checkfieldn(L, -n, field->type, data + field->offset); - } - subindex += n; - lua_pop(L, n); - } - - lua_pop(L, 1); - } -} - -void luax_checkdatakeys(lua_State* L, int index, int start, uint32_t count, const DataField* array, char* data) { - for (uint32_t i = 0; i < count; i++, data += array->stride) { - lua_rawgeti(L, index, start + i); - lovrCheck(lua_istable(L, -1), "Expected table of tables"); - luax_checkstruct(L, -1, array->fields, array->fieldCount, data); - lua_pop(L, 1); - } -} - -void luax_checkstruct(lua_State* L, int index, const DataField* fields, uint32_t fieldCount, char* data) { - for (uint32_t f = 0; f < fieldCount; f++) { - const DataField* field = &fields[f]; - int n = field->fieldCount == 0 ? typeComponents[field->type] : 0; + // String keys + while (f < structure->fieldCount && structure->fields[f].name) { + const DataField* field = &structure->fields[f++]; lua_getfield(L, index, field->name); if (lua_isnil(L, -1)) { memset(data + field->offset, 0, MAX(field->length, 1) * field->stride); - lua_pop(L, 1); - continue; - } - - if (field->length > 0) { - lovrCheck(lua_istable(L, -1), "Expected table for key '%s'", field->name); - uint32_t count; - - if (field->fieldCount > 0) { - uint32_t tableLength = luax_len(L, -1); - count = MIN(field->length, tableLength); - luax_checkdatakeys(L, -1, 1, count, field, data + field->offset); - } else { - count = luax_checkfieldarray(L, -1, field, data + field->offset); - } - - if (count < field->length) { - memset(data + field->offset + count * field->stride, 0, (field->length - count) * field->stride); - } - } else if (field->fieldCount > 0) { - lovrCheck(lua_istable(L, -1), "Expected table for key '%s'", field->name); - luax_checkstruct(L, -1, field->fields, field->fieldCount, data + field->offset); - } else if (n == 1) { - lovrCheck(lua_type(L, -1) == LUA_TNUMBER, "Expected number for key '%s'", field->name); - luax_checkfieldn(L, -1, field->type, data + field->offset); - } else if (lua_isuserdata(L, -1)) { - luax_checkfieldv(L, -1, field->type, data + field->offset); - } else if (lua_istable(L, -1)) { - lovrCheck(luax_len(L, -1) == n, "Table length for key '%s' must be %d", field->name, n); - luax_checkfieldt(L, -1, field->type, data + field->offset); } else { - lovrThrow("Expected table or vector for key '%s'", field->name); + luax_checkbufferdata(L, -1, field, data + field->offset); } lua_pop(L, 1); } } -static int luax_pushcomponents(lua_State* L, DataType type, char* data) { +static void luax_checkarray(lua_State* L, int index, int start, uint32_t count, const DataField* array, char* data) { + luax_fieldcheck(L, lua_istable(L, index), index, array, true); + uint32_t length = luax_len(L, index); + count = MIN(count, (length - start + 1)); + + if (array->fieldCount > 0) { + for (uint32_t i = 0; i < count; i++, data += array->stride) { + lua_rawgeti(L, index, start + i); + luax_checkstruct(L, -1, array, data); + lua_pop(L, 1); + } + } else { + lua_rawgeti(L, index, start); + int type = lua_type(L, -1); + lua_pop(L, 1); + + if (type == LUA_TNUMBER) { + if (index < 0) index += lua_gettop(L) + 1; + uint32_t n = typeComponents[array->type]; + count = MIN(count, (length - start + 1) / n); + for (uint32_t i = 0; i < count; i += n, data += array->stride) { + for (uint32_t j = 0; j < n; j++) { + lua_rawgeti(L, index, start + i + j); + } + luax_checkfieldn(L, -n, array, data); + lua_pop(L, n); + } + } else if (type == LUA_TUSERDATA || type == LUA_TLIGHTUSERDATA) { + for (uint32_t i = 0; i < count; i++, data += array->stride) { + lua_rawgeti(L, index, start + i); + luax_checkfieldv(L, -1, array, data); + lua_pop(L, 1); + } + } else if (type == LUA_TTABLE) { + for (uint32_t i = 0; i < count; i++, data += array->stride) { + lua_rawgeti(L, index, start + i); + luax_checkfieldt(L, -1, array, data); + lua_pop(L, 1); + } + } else { + lua_rawgeti(L, index, start); + luax_fieldcheck(L, type == LUA_TNIL, -1, array, false); + lua_pop(L, 1); + } + } +} + +void luax_checkbufferdata(lua_State* L, int index, const DataField* field, char* data) { + int type = lua_type(L, index); + + if (field->length > 0) { + luax_checkarray(L, index, 1, field->length, field, data); + } else if (field->fieldCount > 0) { + luax_checkstruct(L, index, field, data); + } else if (typeComponents[field->type] == 1) { + luax_checkfieldn(L, index, field, data); + } else if (type == LUA_TUSERDATA || type == LUA_TLIGHTUSERDATA) { + luax_checkfieldv(L, index, field, data); + } else if (type == LUA_TTABLE) { + luax_checkfieldt(L, index, field, data); + } else { + luax_fielderror(L, index, field, false); + } +} + +static int luax_pushfieldn(lua_State* L, DataType type, char* data) { DataPointer p = { .raw = data }; switch (type) { case TYPE_I8x4: for (int i = 0; i < 4; i++) lua_pushinteger(L, p.i8[i]); return 4; @@ -361,104 +361,58 @@ static int luax_pushcomponents(lua_State* L, DataType type, char* data) { } } -static int luax_pushstruct(lua_State* L, const DataField* fields, uint32_t count, char* data) { - lua_createtable(L, 0, count); - for (uint32_t i = 0; i < count; i++) { - const DataField* field = &fields[i]; - if (field->length > 0) { - if (field->fieldCount > 0) { - lua_createtable(L, field->length, 0); - for (uint32_t j = 0; j < field->length; j++) { - luax_pushstruct(L, field->fields, field->fieldCount, data + field->offset + j * field->stride); - lua_rawseti(L, -2, j + 1); - } - } else { - DataType type = field->type; - uint32_t n = typeComponents[field->type]; - lua_createtable(L, (int) (field->length * n), 0); - for (uint32_t j = 0, k = 1; j < field->length; j++, k += n) { - luax_pushcomponents(L, type, data + field->offset + j * field->stride); - for (uint32_t c = 0; c < n; c++) { - lua_rawseti(L, -1 - n + c, k + n - 1 - c); - } - } - } - } else if (field->fieldCount > 0) { - luax_pushstruct(L, field->fields, field->fieldCount, data + field->offset); - } else { - uint32_t n = typeComponents[field->type]; - if (n > 1) { - lua_createtable(L, n, 0); - luax_pushcomponents(L, field->type, data + field->offset); - for (uint32_t c = 0; c < n; c++) { - lua_rawseti(L, -1 - n + c, n - c); - } - } else { - luax_pushcomponents(L, field->type, data + field->offset); - } - } - lua_setfield(L, -2, field->name); - } - return 1; -} - int luax_pushbufferdata(lua_State* L, const DataField* format, uint32_t count, char* data) { - lua_createtable(L, count, 0); - - bool nested = false; - for (uint32_t i = 0; i < format->fieldCount; i++) { - if (format->fields[i].fields || format->fields[i].length > 0) { - nested = true; - break; - } - } - - if (format->fieldCount > 1 || typeComponents[format->fields[0].type] > 1 || nested) { - if (nested) { - for (uint32_t i = 0; i < count; i++) { - luax_pushstruct(L, format->fields, format->fieldCount, data); + if (format->length > 0 && count > 0) { + lua_createtable(L, count, 0); + if (format->fieldCount > 0) { + for (uint32_t i = 0; i < count; i++, data += format->stride) { + luax_pushbufferdata(L, format, 0, data); lua_rawseti(L, -2, i + 1); - data += format->stride; } } else { + int n = typeComponents[format->type]; for (uint32_t i = 0; i < count; i++, data += format->stride) { - lua_newtable(L); - int j = 1; - for (uint32_t f = 0; f < format->fieldCount; f++) { - const DataField* field = &format->fields[f]; - int n = luax_pushcomponents(L, field->type, data + field->offset); - for (int c = 0; c < n; c++) { - lua_rawseti(L, -1 - n + c, j + n - 1 - c); - } - j += n; + luax_pushfieldn(L, format->type, data); + for (int c = 0; c < n; c++) { + lua_rawseti(L, -1 - n + c, i * n + c + 1); } - lua_rawseti(L, -2, i + 1); } } - } else { - for (uint32_t i = 0; i < count; i++, data += format->stride) { - luax_pushcomponents(L, format->fields[0].type, data + format->fields[0].offset); - lua_rawseti(L, -2, i + 1); + return 1; + } else if (format->fieldCount > 0) { + lua_createtable(L, 0, format->fieldCount); + for (uint32_t f = 0; f < format->fieldCount; f++) { + const DataField* field = &format->fields[f]; + if (field->length > 0) { + luax_pushbufferdata(L, field, field->length, data + field->offset); + } else if (field->fieldCount > 0) { + luax_pushbufferdata(L, field, 0, data + field->offset); + } else { + uint32_t n = typeComponents[field->type]; + if (n > 1) { + lua_createtable(L, n, 0); + luax_pushfieldn(L, field->type, data + field->offset); + for (uint32_t c = 0; c < n; c++) { + lua_rawseti(L, -1 - n + c, n - c); + } + } else { + luax_pushfieldn(L, field->type, data + field->offset); + } + } + if (field->name) { + lua_setfield(L, -2, field->name); + } else { + lua_rawseti(L, -2, f + 1); + } } + return 1; + } else { + return luax_pushfieldn(L, format->type, data); } - - return 1; } -uint32_t luax_gettablestride(lua_State* L, int index, int subindex, DataField* fields, uint32_t count) { - int stride = 0; - for (uint32_t i = 0; i < count; i++) { - lovrCheck(!fields[i].fields && fields[i].length == 0, "This Buffer's format requires data to be given as a table of tables"); - lua_rawgeti(L, index, subindex + stride); - switch (lua_type(L, -1)) { - case LUA_TUSERDATA: case LUA_TLIGHTUSERDATA: stride++; break; - case LUA_TNUMBER: stride += typeComponents[fields[i].type]; break; - case LUA_TNIL: lovrThrow("Table does not have enough elements for a single element"); - default: lovrThrow("Expected table of numbers and/or vectors"); - } - lua_pop(L, 1); - } - return (uint32_t) stride; +int luax_gettablestride(lua_State* L, int type) { + return typeComponents[type]; } static int l_lovrBufferGetSize(lua_State* L) { @@ -519,7 +473,11 @@ static int l_lovrBufferGetFormat(lua_State* L) { Buffer* buffer = luax_checktype(L, 1, Buffer); const DataField* format = lovrBufferGetInfo(buffer)->format; if (format) { - luax_pushbufferformat(L, format->fields, format->fieldCount); + if (format->fieldCount > 0) { + luax_pushbufferformat(L, format->fields, format->fieldCount); + } else { + luax_pushbufferformat(L, format, 1); + } } else { lua_pushnil(L); } @@ -540,78 +498,42 @@ static int l_lovrBufferGetData(lua_State* L) { Buffer* buffer = luax_checktype(L, 1, Buffer); const DataField* format = lovrBufferGetInfo(buffer)->format; lovrCheck(format, "Buffer:getData requires the Buffer to have a format"); - uint32_t index = luax_optu32(L, 2, 1) - 1; - lovrCheck(index < format->length, "Buffer:getData index exceeds the Buffer's length"); - uint32_t count = luax_optu32(L, 3, format->length - index); - void* data = lovrBufferGetData(buffer, index * format->stride, count * format->stride); - return luax_pushbufferdata(L, format, count, data); + if (format->length > 0) { + uint32_t index = luax_optu32(L, 2, 1) - 1; + lovrCheck(index < format->length, "Buffer:getData index exceeds the Buffer's length"); + uint32_t count = luax_optu32(L, 3, format->length - index); + void* data = lovrBufferGetData(buffer, index * format->stride, count * format->stride); + return luax_pushbufferdata(L, format, count, data); + } else { + void* data = lovrBufferGetData(buffer, 0, format->stride); + return luax_pushbufferdata(L, format, 0, data); + } } static int l_lovrBufferSetData(lua_State* L) { Buffer* buffer = luax_checktype(L, 1, Buffer); const BufferInfo* info = lovrBufferGetInfo(buffer); const DataField* format = info->format; - bool hasNames = format->fields[0].name; - if (format && format->length == 1) { // When Buffer's length is 1, you can pass a single item - if (lua_istable(L, 2) && luax_len(L, 2) == 0 && hasNames) { - luax_checkstruct(L, 2, format->fields, format->fieldCount, lovrBufferSetData(buffer, 0, ~0u)); - return 0; - } else if (typeComponents[format->fields[0].type] == 1 && lua_type(L, 2) == LUA_TNUMBER) { - luax_checkfieldn(L, 2, format->fields[0].type, lovrBufferSetData(buffer, 0, ~0u)); - return 0; - } else if (typeComponents[format->fields[0].type] > 1 && luax_tovector(L, 2, NULL)) { - luax_checkfieldv(L, 2, format->fields[0].type, lovrBufferSetData(buffer, 0, ~0u)); - return 0; - } - } + if (format) { + if (format->length > 0) { + luax_fieldcheck(L, lua_istable(L, 2), 2, format, -1); + uint32_t length = luax_len(L, 2); + uint32_t dstIndex = luax_optu32(L, 3, 1) - 1; + uint32_t srcIndex = luax_optu32(L, 4, 1) - 1; - if (lua_istable(L, 2)) { - lovrCheck(format, "Buffer must be created with format information to copy a table to it"); + lua_rawgeti(L, 2, srcIndex + 1); + uint32_t tstride = format->fieldCount == 0 && lua_type(L, -1) == LUA_TNUMBER ? typeComponents[format->type] : 1; + lua_pop(L, 1); - uint32_t length = luax_len(L, 2); - uint32_t dstIndex = luax_optu32(L, 3, 1) - 1; - uint32_t srcIndex = luax_optu32(L, 4, 1) - 1; - - // Fast path for scalar formats - if (format->fieldCount == 1 && typeComponents[format->fields[0].type] == 1) { - uint32_t limit = MIN(format->length - dstIndex, length - srcIndex); - uint32_t count = luax_optu32(L, 5, limit); - char* data = lovrBufferSetData(buffer, dstIndex * format->stride, count * format->stride); - for (uint32_t i = 0; i < count; i++, data += format->stride) { - lua_rawgeti(L, 2, srcIndex + i + 1); - luax_checkfieldn(L, -1, format->fields[0].type, data); - lua_pop(L, 1); - } - return 0; - } - - lua_rawgeti(L, 2, 1); - bool tableOfTables = info->complexFormat || lua_istable(L, -1); - bool tuples = tableOfTables && !info->complexFormat && (luax_len(L, -1) > 0 || !hasNames); - lua_pop(L, 1); - - if (tableOfTables) { - uint32_t limit = MIN(format->length - dstIndex, length - srcIndex); + uint32_t limit = MIN(format->length - dstIndex, (length - srcIndex) / tstride); uint32_t count = luax_optu32(L, 5, limit); - lovrCheck(length - srcIndex >= count, "Table does not have enough elements"); char* data = lovrBufferSetData(buffer, dstIndex * format->stride, count * format->stride); - - if (tuples) { - luax_checkdatatuples(L, 2, srcIndex + 1, count, format, data); - } else { - luax_checkdatakeys(L, 2, srcIndex + 1, count, format, data); - } + luax_checkarray(L, 2, srcIndex + 1, count, format, data); } else { - uint32_t tableStride = luax_gettablestride(L, 2, srcIndex + 1, format->fields, format->fieldCount); - lovrCheck(length % tableStride == 0, "Table length is not aligned -- it either uses inconsistent types for each field or is missing some data"); - uint32_t limit = MIN(format->length - dstIndex, (length - srcIndex) / tableStride); - uint32_t count = luax_optu32(L, 5, limit); - - lovrCheck((length - srcIndex) / tableStride >= count, "Table does not have enough elements"); - char* data = lovrBufferSetData(buffer, dstIndex * format->stride, count * format->stride); - luax_checkdataflat(L, 2, srcIndex + 1, count, format, data); + luaL_checkany(L, 2); + luax_checkbufferdata(L, 2, format, lovrBufferSetData(buffer, 0, format->stride)); } return 0; @@ -647,7 +569,7 @@ static int l_lovrBufferSetData(lua_State* L) { return 0; } - return luax_typeerror(L, 2, "table, Blob, or Buffer"); + return luax_typeerror(L, 2, "Blob or Buffer"); } static int l_lovrBufferMapData(lua_State* L) { diff --git a/src/api/l_graphics_mesh.c b/src/api/l_graphics_mesh.c index 0934f936..5456f58b 100644 --- a/src/api/l_graphics_mesh.c +++ b/src/api/l_graphics_mesh.c @@ -83,7 +83,7 @@ static int l_lovrMeshSetVertices(lua_State* L) { uint32_t count = luax_optu32(L, 4, limit); lovrCheck(length <= limit, "Table does not have enough data to set %d items", count); void* data = lovrMeshSetVertices(mesh, index, count); - luax_checkdatatuples(L, 2, 1, count, format, data); + luax_checkbufferdata(L, 2, format, data); } else { return luax_typeerror(L, 2, "table or Blob"); } diff --git a/src/api/l_graphics_pass.c b/src/api/l_graphics_pass.c index a4ae72c5..23554d70 100644 --- a/src/api/l_graphics_pass.c +++ b/src/api/l_graphics_pass.c @@ -686,42 +686,7 @@ static int l_lovrPassSend(lua_State* L) { lua_pushinteger(L, value); } - if (format->length > 0) { - luaL_checktype(L, 3, LUA_TTABLE); - if (format->fieldCount > 1) { - lua_rawgeti(L, 3, 1); - lovrCheck(lua_type(L, -1) == LUA_TTABLE, "Expected table of tables"); - bool dictionary = luax_len(L, -1) == 0; - lua_pop(L, 1); - - // Nested structs/arrays don't support the "tuple" table format - for (uint32_t i = 0; i < format->fieldCount; i++) { - if (format->fields[i].fieldCount > 0 || format->fields[i].length > 0) { - dictionary = true; - break; - } - } - - if (dictionary) { - luax_checkdatakeys(L, 3, 1, format->length, format, data); - } else { - luax_checkdatatuples(L, 3, 1, format->length, format, data); - } - } else { - luax_checkfieldarray(L, 3, format, data); - } - } else if (format->fieldCount > 1) { - luaL_checktype(L, 3, LUA_TTABLE); - luax_checkstruct(L, 3, format->fields, format->fieldCount, data); - } else if (lua_type(L, 3) == LUA_TNUMBER) { - luax_checkfieldn(L, 3, format->type, data); - } else if (lua_isuserdata(L, 3)) { - luax_checkfieldv(L, 3, format->type, data); - } else if (lua_istable(L, 3)) { - luax_checkfieldt(L, 3, format->type, data); - } else { - return luax_typeerror(L, 3, "number, vector, or table"); - } + luax_checkbufferdata(L, 3, format, data); return 0; } diff --git a/src/api/l_graphics_shader.c b/src/api/l_graphics_shader.c index 4f715950..021c10dc 100644 --- a/src/api/l_graphics_shader.c +++ b/src/api/l_graphics_shader.c @@ -96,10 +96,10 @@ static int l_lovrShaderGetBufferFormat(lua_State* L) { lua_pushinteger(L, format->stride); lua_setfield(L, -2, "stride"); - if (format->length == ~0u) { + if (format->length == 0 || format->length == ~0u) { lua_pushnil(L); } else { - lua_pushinteger(L, MAX(format->length, 1)); + lua_pushinteger(L, format->length); } return 2; diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 1b2d775b..6bb129c9 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -74,6 +74,7 @@ struct Buffer { Sync sync; gpu_buffer* gpu; BufferBlock* block; + bool complexFormat; BufferInfo info; }; @@ -1851,6 +1852,11 @@ uint32_t lovrGraphicsAlignFields(DataField* parent, DataLayout layout) { uint32_t extent = 0; uint32_t align = 1; + if (parent->fieldCount == 0) { + align = layout == LAYOUT_PACKED ? table[parent->type].scalarAlign : table[parent->type].baseAlign; + extent = table[parent->type].size; + } + for (uint32_t i = 0; i < parent->fieldCount; i++) { DataField* field = &parent->fields[i]; uint32_t length = MAX(field->length, 1); @@ -1901,12 +1907,11 @@ Buffer* lovrBufferCreate(const BufferInfo* info, void** data) { buffer->info.fieldCount = fieldCount; if (info->format) { - lovrCheck(info->format->length > 0, "Buffer length can not be zero"); char* names = (char*) buffer + sizeof(Buffer); DataField* format = buffer->info.format = (DataField*) (names + charCount); memcpy(format, info->format, fieldCount * sizeof(DataField)); - // Copy names, hash names, fixup children pointers + // Copy names, hash names, fixup children pointers, set parent pointers for (uint32_t i = 0; i < fieldCount; i++) { if (format[i].name) { size_t length = strlen(format[i].name); @@ -1927,6 +1932,15 @@ Buffer* lovrBufferCreate(const BufferInfo* info, void** data) { format->fields = format + 1; } + // Set parent pointers + for (uint32_t i = 0; i < fieldCount; i++) { + if (format[i].fields) { + for (uint32_t j = 0; j < format[i].fieldCount; j++) { + format[i].fields[j].parent = &format[i]; + } + } + } + // Size is optional, and can be computed from format if (buffer->info.size == 0) { buffer->info.size = format->stride * MAX(format->length, 1); @@ -1935,7 +1949,7 @@ Buffer* lovrBufferCreate(const BufferInfo* info, void** data) { // Formats with array/struct fields have extra restrictions, cache it for (uint32_t i = 0; i < format->fieldCount; i++) { if (format->fields[i].fieldCount > 0 || format->fields[i].length > 0) { - buffer->info.complexFormat = true; + buffer->complexFormat = true; break; } } @@ -3035,19 +3049,9 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { if (buffer && resource->bufferFields) { spv_field* field = &resource->bufferFields[0]; - // The following conversions take place, for convenience and to better match Buffer formats: - // - Struct containing either single struct or single array of structs gets unwrapped - // - Struct containing single array of non-structs gets converted to array of single-field structs - if (field->fieldCount == 1 && field->totalFieldCount > 1) { + // Struct containing single item gets unwrapped + if (field->fieldCount == 1) { field = &field->fields[0]; - } else if (field->totalFieldCount == 1 && field->fields[0].arrayLength > 0) { - spv_field* child = &field->fields[0]; - field->arrayLength = child->arrayLength; - field->arrayStride = child->arrayStride; - field->elementSize = child->elementSize; - field->type = child->type; - child->arrayLength = 0; - child->arrayStride = 0; } shader->resources[index].fieldCount = field->totalFieldCount + 1; @@ -3934,7 +3938,7 @@ Mesh* lovrMeshCreate(const MeshInfo* info, void** vertices) { if (buffer) { lovrCheck(buffer->info.format, "Mesh vertex buffer must have format information"); - lovrCheck(!buffer->info.complexFormat, "Mesh vertex buffer must use a format without nested types or arrays"); + lovrCheck(!buffer->complexFormat, "Mesh vertex buffer must use a format without nested types or arrays"); lovrCheck(info->storage == MESH_GPU, "Mesh storage must be 'gpu' when created from a Buffer"); lovrRetain(buffer); } else { @@ -3949,8 +3953,8 @@ Mesh* lovrMeshCreate(const MeshInfo* info, void** vertices) { lovrCheck(format->stride <= state.limits.vertexBufferStride, "Mesh vertex buffer stride exceeds the vertexBufferStride limit of this GPU"); lovrCheck(format->fieldCount <= state.limits.vertexAttributes, "Mesh attribute count exceeds the vertexAttributes limit of this GPU"); - for (uint32_t i = 0; i < format->fieldCount; i++) { - const DataField* attribute = &format->fields[i]; + for (uint32_t i = 0; i < MAX(format->fieldCount, 1); i++) { + const DataField* attribute = format->fieldCount > 0 ? &format->fields[i] : format; lovrCheck(attribute->offset < 256, "Max Mesh attribute offset is 255"); // Limited by u8 gpu_attribute offset lovrCheck(attribute->type < TYPE_MAT2 || attribute->type > TYPE_MAT4, "Currently, Mesh attributes can not use matrix types"); lovrCheck(attribute->type < TYPE_INDEX16 || attribute->type > TYPE_INDEX32, "Mesh attributes can not use index types"); @@ -4011,12 +4015,14 @@ void lovrMeshSetIndexBuffer(Mesh* mesh, Buffer* buffer) { DataField* format = buffer->info.format; lovrCheck(format, "Mesh index buffer must have been created with a format"); - DataType type = format[1].type; - if (format->fieldCount > 1 || (type != TYPE_U16 && type != TYPE_U32 && type != TYPE_INDEX16 && type != TYPE_INDEX32)) { + lovrCheck(format->length > 0, "Mesh index buffer length can not be zero"); + + DataType type = format->type; + if (format->fieldCount > 0 || (type != TYPE_U16 && type != TYPE_U32 && type != TYPE_INDEX16 && type != TYPE_INDEX32)) { lovrThrow("Mesh index buffer must use the u16, u32, index16, or index32 type"); } else { uint32_t stride = (type == TYPE_U16 || type == TYPE_INDEX16) ? 2 : 4; - lovrCheck(format->stride == stride && format[1].offset == 0, "Mesh index buffer must be tightly packed"); + lovrCheck(format->stride == stride && format->offset == 0, "Mesh index buffer must be tightly packed"); } lovrRelease(mesh->indexBuffer, lovrBufferDestroy); @@ -4057,7 +4063,7 @@ void* lovrMeshGetIndices(Mesh* mesh, uint32_t* count, DataType* type) { } *count = mesh->indexCount; - *type = mesh->indexBuffer->info.format[1].type; + *type = mesh->indexBuffer->info.format->type; if (mesh->storage == MESH_CPU) { return mesh->indices; @@ -4071,14 +4077,11 @@ void* lovrMeshSetIndices(Mesh* mesh, uint32_t count, DataType type) { mesh->indexCount = count; mesh->dirtyIndices = true; - if (!mesh->indexBuffer || count > format->length || type != format[1].type) { + if (!mesh->indexBuffer || count > format->length || type != format->type) { lovrRelease(mesh->indexBuffer, lovrBufferDestroy); uint32_t stride = (type == TYPE_U16 || type == TYPE_INDEX16) ? 2 : 4; - DataField format[2] = { - { .length = count, .stride = stride, .fieldCount = 1 }, - { .type = type } - }; - BufferInfo info = { .format = format }; + DataField format = { .length = count, .stride = stride, .type = type }; + BufferInfo info = { .format = &format }; if (mesh->storage == MESH_CPU) { mesh->indexBuffer = lovrBufferCreate(&info, NULL); mesh->indices = realloc(mesh->indices, count * stride); @@ -4100,8 +4103,8 @@ static float* lovrMeshGetPositions(Mesh* mesh) { if (mesh->storage == MESH_GPU) return NULL; const DataField* format = lovrMeshGetVertexFormat(mesh); uint32_t positionHash = (uint32_t) hash64("VertexPosition", strlen("VertexPosition")); - for (uint32_t i = 0; i < format->fieldCount; i++) { - const DataField* attribute = &format->fields[i]; + for (uint32_t i = 0; i < MAX(format->fieldCount, 1); i++) { + const DataField* attribute = format->fieldCount > 0 ? &format->fields[i] : format; if (attribute->type != TYPE_F32x3) continue; if ((attribute->hash == LOCATION_POSITION || attribute->hash == positionHash)) { return (float*) ((char*) mesh->vertices + attribute->offset); @@ -4128,7 +4131,7 @@ void lovrMeshGetTriangles(Mesh* mesh, float** vertices, uint32_t** indices, uint if (mesh->indexCount > 0) { *indexCount = mesh->indexCount; *indices = lovrMalloc(*indexCount * sizeof(uint32_t)); - if (mesh->indexBuffer->info.format[1].type == TYPE_U16 || mesh->indexBuffer->info.format[1].type == TYPE_INDEX16) { + if (mesh->indexBuffer->info.format->type == TYPE_U16 || mesh->indexBuffer->info.format->type == TYPE_INDEX16) { for (uint32_t i = 0; i < mesh->indexCount; i++) { *indices[i] = (uint32_t) ((uint16_t*) mesh->indices)[i]; } @@ -4378,13 +4381,13 @@ Model* lovrModelCreate(const ModelInfo* info) { }, 1); } + DataType indexType = data->indexType == U32 ? TYPE_INDEX32 : TYPE_INDEX16; uint32_t indexSize = data->indexType == U32 ? 4 : 2; if (data->indexCount > 0) { model->indexBuffer = lovrBufferCreate(&(BufferInfo) { .format = (DataField[]) { - { .length = data->indexCount, .stride = indexSize, .fieldCount = 1 }, - { .type = data->indexType == U32 ? TYPE_INDEX32 : TYPE_INDEX16 } + { .length = data->indexCount, .stride = indexSize, .type = indexType } } }, (void**) &indexData); } @@ -5916,8 +5919,8 @@ static void lovrPassResolvePipeline(Pass* pass, DrawInfo* info, Draw* draw, Draw ShaderAttribute* attribute = &shader->attributes[i]; bool found = false; - for (uint32_t j = 0; j < format->fieldCount; j++) { - DataField* field = &format->fields[j]; + for (uint32_t j = 0; j < MAX(format->fieldCount, 1); j++) { + const DataField* field = format->fieldCount > 0 ? &format->fields[j] : format; if (field->hash == attribute->hash || field->hash == attribute->location) { lovrCheck(field->type < TYPE_MAT2, "Currently vertex attributes can not use matrix or index types"); pipeline->info.vertex.attributes[i] = (gpu_attribute) { @@ -7106,7 +7109,7 @@ void lovrPassDrawTexture(Pass* pass, Texture* texture, float* transform) { void lovrPassMesh(Pass* pass, Buffer* vertices, Buffer* indices, float* transform, uint32_t start, uint32_t count, uint32_t instances, uint32_t baseVertex) { lovrCheck(!indices || indices->info.format, "Buffer must have been created with a format to use it as a%s buffer", "n index"); lovrCheck(!vertices || vertices->info.format, "Buffer must have been created with a format to use it as a%s buffer", " vertex"); - lovrCheck(!vertices || !vertices->info.complexFormat, "Vertex buffers must use a simple format without nested types or arrays"); + lovrCheck(!vertices || !vertices->complexFormat, "Vertex buffers must use a simple format without nested types or arrays"); if (count == ~0u) { if (indices || vertices) { diff --git a/src/modules/graphics/graphics.h b/src/modules/graphics/graphics.h index 791b6c0a..72f4913c 100644 --- a/src/modules/graphics/graphics.h +++ b/src/modules/graphics/graphics.h @@ -158,6 +158,7 @@ typedef struct DataField { uint32_t stride; uint32_t fieldCount; struct DataField* fields; + struct DataField* parent; } DataField; typedef enum { @@ -172,7 +173,6 @@ typedef struct { uint32_t size; uint32_t fieldCount; DataField* format; - bool complexFormat; const char* label; uintptr_t handle; } BufferInfo;