#include "api.h" #include "graphics/graphics.h" #include "data/blob.h" #include "data/image.h" #include "data/modelData.h" #include "data/rasterizer.h" #include "util.h" #include #include StringEntry lovrBlendAlphaMode[] = { [BLEND_ALPHA_MULTIPLY] = ENTRY("alphamultiply"), [BLEND_PREMULTIPLIED] = ENTRY("premultiplied"), { 0 } }; StringEntry lovrBlendMode[] = { [BLEND_ALPHA] = ENTRY("alpha"), [BLEND_ADD] = ENTRY("add"), [BLEND_SUBTRACT] = ENTRY("subtract"), [BLEND_MULTIPLY] = ENTRY("multiply"), [BLEND_LIGHTEN] = ENTRY("lighten"), [BLEND_DARKEN] = ENTRY("darken"), [BLEND_SCREEN] = ENTRY("screen"), [BLEND_NONE] = ENTRY("none"), { 0 } }; StringEntry lovrBufferLayout[] = { [LAYOUT_PACKED] = ENTRY("packed"), [LAYOUT_STD140] = ENTRY("std140"), [LAYOUT_STD430] = ENTRY("std430"), { 0 } }; StringEntry lovrCompareMode[] = { [COMPARE_NONE] = ENTRY("none"), [COMPARE_EQUAL] = ENTRY("equal"), [COMPARE_NEQUAL] = ENTRY("notequal"), [COMPARE_LESS] = ENTRY("less"), [COMPARE_LEQUAL] = ENTRY("lequal"), [COMPARE_GREATER] = ENTRY("greater"), [COMPARE_GEQUAL] = ENTRY("gequal"), { 0 } }; StringEntry lovrCullMode[] = { [CULL_NONE] = ENTRY("none"), [CULL_FRONT] = ENTRY("front"), [CULL_BACK] = ENTRY("back"), { 0 } }; StringEntry lovrDefaultShader[] = { [SHADER_UNLIT] = ENTRY("unlit"), [SHADER_NORMAL] = ENTRY("normal"), [SHADER_FONT] = ENTRY("font"), [SHADER_CUBEMAP] = ENTRY("cubemap"), [SHADER_EQUIRECT] = ENTRY("equirect"), [SHADER_FILL_2D] = ENTRY("fill"), [SHADER_FILL_ARRAY] = ENTRY("fillarray"), { 0 } }; StringEntry lovrDrawMode[] = { [DRAW_POINTS] = ENTRY("points"), [DRAW_LINES] = ENTRY("lines"), [DRAW_TRIANGLES] = ENTRY("triangles"), { 0 } }; StringEntry lovrDrawStyle[] = { [STYLE_FILL] = ENTRY("fill"), [STYLE_LINE] = ENTRY("line"), { 0 } }; StringEntry lovrDataType[] = { [TYPE_I8x4] = ENTRY("i8x4"), [TYPE_U8x4] = ENTRY("u8x4"), [TYPE_SN8x4] = ENTRY("sn8x4"), [TYPE_UN8x4] = ENTRY("un8x4"), [TYPE_SN10x3] = ENTRY("sn10x3"), [TYPE_UN10x3] = ENTRY("un10x3"), [TYPE_I16] = ENTRY("i16"), [TYPE_I16x2] = ENTRY("i16x2"), [TYPE_I16x4] = ENTRY("i16x4"), [TYPE_U16] = ENTRY("u16"), [TYPE_U16x2] = ENTRY("u16x2"), [TYPE_U16x4] = ENTRY("u16x4"), [TYPE_SN16x2] = ENTRY("sn16x2"), [TYPE_SN16x4] = ENTRY("sn16x4"), [TYPE_UN16x2] = ENTRY("un16x2"), [TYPE_UN16x4] = ENTRY("un16x4"), [TYPE_I32] = ENTRY("i32"), [TYPE_I32x2] = ENTRY("i32x2"), [TYPE_I32x3] = ENTRY("i32x3"), [TYPE_I32x4] = ENTRY("i32x4"), [TYPE_U32] = ENTRY("u32"), [TYPE_U32x2] = ENTRY("u32x2"), [TYPE_U32x3] = ENTRY("u32x3"), [TYPE_U32x4] = ENTRY("u32x4"), [TYPE_F16x2] = ENTRY("f16x2"), [TYPE_F16x4] = ENTRY("f16x4"), [TYPE_F32] = ENTRY("f32"), [TYPE_F32x2] = ENTRY("f32x2"), [TYPE_F32x3] = ENTRY("f32x3"), [TYPE_F32x4] = ENTRY("f32x4"), [TYPE_MAT2] = ENTRY("mat2"), [TYPE_MAT3] = ENTRY("mat3"), [TYPE_MAT4] = ENTRY("mat4"), [TYPE_INDEX16] = ENTRY("index16"), [TYPE_INDEX32] = ENTRY("index32"), { 0 } }; StringEntry lovrFilterMode[] = { [FILTER_NEAREST] = ENTRY("nearest"), [FILTER_LINEAR] = ENTRY("linear"), { 0 } }; StringEntry lovrHorizontalAlign[] = { [ALIGN_LEFT] = ENTRY("left"), [ALIGN_CENTER] = ENTRY("center"), [ALIGN_RIGHT] = ENTRY("right"), { 0 } }; StringEntry lovrMeshStorage[] = { [MESH_CPU] = ENTRY("cpu"), [MESH_GPU] = ENTRY("gpu"), { 0 } }; StringEntry lovrOriginType[] = { [ORIGIN_ROOT] = ENTRY("root"), [ORIGIN_PARENT] = ENTRY("parent"), { 0 } }; StringEntry lovrShaderStage[] = { [STAGE_VERTEX] = ENTRY("vertex"), [STAGE_FRAGMENT] = ENTRY("pixel"), [STAGE_COMPUTE] = ENTRY("compute"), { 0 } }; StringEntry lovrShaderType[] = { [SHADER_GRAPHICS] = ENTRY("graphics"), [SHADER_COMPUTE] = ENTRY("compute"), { 0 } }; StringEntry lovrStackType[] = { [STACK_TRANSFORM] = ENTRY("transform"), [STACK_STATE] = ENTRY("state"), { 0 } }; StringEntry lovrStencilAction[] = { [STENCIL_KEEP] = ENTRY("keep"), [STENCIL_ZERO] = ENTRY("zero"), [STENCIL_REPLACE] = ENTRY("replace"), [STENCIL_INCREMENT] = ENTRY("increment"), [STENCIL_DECREMENT] = ENTRY("decrement"), [STENCIL_INCREMENT_WRAP] = ENTRY("incrementwrap"), [STENCIL_DECREMENT_WRAP] = ENTRY("decrementwrap"), [STENCIL_INVERT] = ENTRY("invert"), { 0 } }; StringEntry lovrTextureFeature[] = { [0] = ENTRY("sample"), [1] = ENTRY("render"), [2] = ENTRY("storage"), [3] = ENTRY("blit"), { 0 } }; StringEntry lovrTextureType[] = { [TEXTURE_2D] = ENTRY("2d"), [TEXTURE_3D] = ENTRY("3d"), [TEXTURE_CUBE] = ENTRY("cube"), [TEXTURE_ARRAY] = ENTRY("array"), { 0 } }; StringEntry lovrTextureUsage[] = { [0] = ENTRY("sample"), [1] = ENTRY("render"), [2] = ENTRY("storage"), [3] = ENTRY("transfer"), { 0 } }; StringEntry lovrVerticalAlign[] = { [ALIGN_TOP] = ENTRY("top"), [ALIGN_MIDDLE] = ENTRY("middle"), [ALIGN_BOTTOM] = ENTRY("bottom"), { 0 } }; StringEntry lovrWinding[] = { [WINDING_COUNTERCLOCKWISE] = ENTRY("counterclockwise"), [WINDING_CLOCKWISE] = ENTRY("clockwise"), { 0 } }; StringEntry lovrWrapMode[] = { [WRAP_CLAMP] = ENTRY("clamp"), [WRAP_REPEAT] = ENTRY("repeat"), { 0 } }; static uint32_t luax_checkdatatype(lua_State* L, int index) { size_t length; const char* string = luaL_checklstring(L, index, &length); if (length < 3) { return luaL_error(L, "invalid DataType '%s'", string), 0; } // Deprecated: plurals are allowed and ignored if (string[length - 1] == 's') { length--; } // x1 is allowed and ignored if (string[length - 2] == 'x' && string[length - 1] == '1') { length -= 2; } // vec[234] if (length == 4 && string[0] == 'v' && string[1] == 'e' && string[2] == 'c' && string[3] >= '2' && string[3] <= '4') { return TYPE_F32x2 + string[3] - '2'; } if (length == 3 && !memcmp(string, "int", length)) { return TYPE_I32; } if (length == 4 && !memcmp(string, "uint", length)) { return TYPE_U32; } if (length == 5 && !memcmp(string, "float", length)) { return TYPE_F32; } if (length == 5 && !memcmp(string, "color", length)) { return TYPE_UN8x4; } if (length == 5 && !memcmp(string, "index", length)) { return TYPE_INDEX32; } for (int i = 0; lovrDataType[i].length; i++) { if (length == lovrDataType[i].length && !memcmp(string, lovrDataType[i].string, length)) { return i; } } return luaL_error(L, "invalid DataType '%s'", string), 0; } uint32_t luax_checkcomparemode(lua_State* L, int index) { size_t length; const char* string = lua_tolstring(L, index, &length); if (string && length <= 2) { if (string[0] == '=' && (length == 1 || string[1] == '=')) { return COMPARE_EQUAL; } if ((string[0] == '~' || string[0] == '!') && string[1] == '=') { return COMPARE_NEQUAL; } if (string[0] == '<' && length == 1) { return COMPARE_LESS; } if (string[0] == '<' && string[1] == '=') { return COMPARE_LEQUAL; } if (string[0] == '>' && length == 1) { return COMPARE_GREATER; } if (string[0] == '>' && string[1] == '=') { return COMPARE_GEQUAL; } } return luax_checkenum(L, index, CompareMode, "none"); } static void luax_writeshadercache(void) { size_t size; lovrGraphicsGetShaderCache(NULL, &size); if (size == 0) { return; } void* data = lovrMalloc(size); lovrGraphicsGetShaderCache(data, &size); if (size > 0) { luax_writefile(".lovrshadercache", data, size); } lovrFree(data); } static int l_lovrGraphicsInitialize(lua_State* L) { GraphicsConfig config = { .debug = false, .vsync = false, .stencil = false, .antialias = true }; bool shaderCache = true; luax_pushconf(L); lua_getfield(L, -1, "graphics"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "debug"); config.debug = lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "vsync"); config.vsync = lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "stencil"); config.stencil = lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "antialias"); config.antialias = lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "shadercache"); shaderCache = lua_toboolean(L, -1); lua_pop(L, 1); } lua_pop(L, 2); uint32_t defer = lovrDeferPush(); if (shaderCache) { config.cacheData = luax_readfile(".lovrshadercache", &config.cacheSize); lovrDefer(lovrFree, config.cacheData); } lovrGraphicsInit(&config); luax_atexit(L, lovrGraphicsDestroy); if (shaderCache) { // Finalizers run in the opposite order they were added, so this has to go last luax_atexit(L, luax_writeshadercache); } lovrDeferPop(defer); return 0; } static int l_lovrGraphicsIsInitialized(lua_State* L) { bool initialized = lovrGraphicsIsInitialized(); lua_pushboolean(L, initialized); return 1; } static int l_lovrGraphicsIsTimingEnabled(lua_State* L) { bool enabled = lovrGraphicsIsTimingEnabled(); lua_pushboolean(L, enabled); return 1; } static int l_lovrGraphicsSetTimingEnabled(lua_State* L) { bool enable = lua_toboolean(L, 1); lovrGraphicsSetTimingEnabled(enable); return 0; } static int l_lovrGraphicsSubmit(lua_State* L) { bool table = lua_istable(L, 1); int length = table ? luax_len(L, 1) : lua_gettop(L); uint32_t count = 0; Pass* stack[8]; Pass** passes = stack; uint32_t defer = lovrDeferPush(); if ((size_t) length > COUNTOF(stack)) { passes = lovrMalloc(length * sizeof(Pass*)); lovrDefer(lovrFree, passes); } if (table) { for (int i = 0; i < length; i++) { lua_rawgeti(L, 1, i + 1); if (lua_toboolean(L, -1)) { passes[count++] = luax_checktype(L, -1, Pass); } lua_pop(L, 1); } } else { for (int i = 0; i < length; i++) { if (lua_toboolean(L, i + 1)) { passes[count++] = luax_checktype(L, i + 1, Pass); } } } lovrGraphicsSubmit(passes, count); lua_pushboolean(L, true); lovrDeferPop(defer); return 1; } static int l_lovrGraphicsPresent(lua_State* L) { lovrGraphicsPresent(); return 0; } static int l_lovrGraphicsWait(lua_State* L) { lovrGraphicsWait(); return 0; } static int l_lovrGraphicsGetDevice(lua_State* L) { GraphicsDevice device; lovrGraphicsGetDevice(&device); lua_newtable(L); lua_pushinteger(L, device.deviceId), lua_setfield(L, -2, "id"); lua_pushinteger(L, device.vendorId), lua_setfield(L, -2, "vendor"); lua_pushstring(L, device.name), lua_setfield(L, -2, "name"); lua_pushstring(L, device.renderer), lua_setfield(L, -2, "renderer"); lua_pushinteger(L, device.subgroupSize), lua_setfield(L, -2, "subgroupSize"); lua_pushboolean(L, device.discrete), lua_setfield(L, -2, "discrete"); return 1; } static int l_lovrGraphicsGetFeatures(lua_State* L) { GraphicsFeatures features; lovrGraphicsGetFeatures(&features); lua_newtable(L); lua_pushboolean(L, features.textureBC), lua_setfield(L, -2, "textureBC"); lua_pushboolean(L, features.textureASTC), lua_setfield(L, -2, "textureASTC"); lua_pushboolean(L, features.wireframe), lua_setfield(L, -2, "wireframe"); lua_pushboolean(L, features.depthClamp), lua_setfield(L, -2, "depthClamp"); lua_pushboolean(L, features.depthResolve), lua_setfield(L, -2, "depthResolve"); lua_pushboolean(L, features.indirectDrawFirstInstance), lua_setfield(L, -2, "indirectDrawFirstInstance"); lua_pushboolean(L, features.float64), lua_setfield(L, -2, "float64"); lua_pushboolean(L, features.int64), lua_setfield(L, -2, "int64"); lua_pushboolean(L, features.int16), lua_setfield(L, -2, "int16"); return 1; } static int l_lovrGraphicsGetLimits(lua_State* L) { GraphicsLimits limits; lovrGraphicsGetLimits(&limits); lua_newtable(L); lua_pushinteger(L, limits.textureSize2D), lua_setfield(L, -2, "textureSize2D"); lua_pushinteger(L, limits.textureSize3D), lua_setfield(L, -2, "textureSize3D"); lua_pushinteger(L, limits.textureSizeCube), lua_setfield(L, -2, "textureSizeCube"); lua_pushinteger(L, limits.textureLayers), lua_setfield(L, -2, "textureLayers"); lua_createtable(L, 3, 0); lua_pushinteger(L, limits.renderSize[0]), lua_rawseti(L, -2, 1); lua_pushinteger(L, limits.renderSize[1]), lua_rawseti(L, -2, 2); lua_pushinteger(L, limits.renderSize[2]), lua_rawseti(L, -2, 3); lua_setfield(L, -2, "renderSize"); lua_pushinteger(L, limits.uniformBuffersPerStage), lua_setfield(L, -2, "uniformBuffersPerStage"); lua_pushinteger(L, limits.storageBuffersPerStage), lua_setfield(L, -2, "storageBuffersPerStage"); lua_pushinteger(L, limits.sampledTexturesPerStage), lua_setfield(L, -2, "sampledTexturesPerStage"); lua_pushinteger(L, limits.storageTexturesPerStage), lua_setfield(L, -2, "storageTexturesPerStage"); lua_pushinteger(L, limits.samplersPerStage), lua_setfield(L, -2, "samplersPerStage"); lua_pushinteger(L, limits.resourcesPerShader), lua_setfield(L, -2, "resourcesPerShader"); lua_pushinteger(L, limits.uniformBufferRange), lua_setfield(L, -2, "uniformBufferRange"); lua_pushinteger(L, limits.storageBufferRange), lua_setfield(L, -2, "storageBufferRange"); lua_pushinteger(L, limits.uniformBufferAlign), lua_setfield(L, -2, "uniformBufferAlign"); lua_pushinteger(L, limits.storageBufferAlign), lua_setfield(L, -2, "storageBufferAlign"); lua_pushinteger(L, limits.vertexAttributes), lua_setfield(L, -2, "vertexAttributes"); lua_pushinteger(L, limits.vertexBufferStride), lua_setfield(L, -2, "vertexBufferStride"); lua_pushinteger(L, limits.vertexShaderOutputs), lua_setfield(L, -2, "vertexShaderOutputs"); lua_pushinteger(L, limits.clipDistances), lua_setfield(L, -2, "clipDistances"); lua_pushinteger(L, limits.cullDistances), lua_setfield(L, -2, "cullDistances"); lua_pushinteger(L, limits.clipAndCullDistances), lua_setfield(L, -2, "clipAndCullDistances"); lua_createtable(L, 3, 0); lua_pushinteger(L, limits.workgroupCount[0]), lua_rawseti(L, -2, 1); lua_pushinteger(L, limits.workgroupCount[1]), lua_rawseti(L, -2, 2); lua_pushinteger(L, limits.workgroupCount[2]), lua_rawseti(L, -2, 3); lua_setfield(L, -2, "workgroupCount"); lua_createtable(L, 3, 0); lua_pushinteger(L, limits.workgroupSize[0]), lua_rawseti(L, -2, 1); lua_pushinteger(L, limits.workgroupSize[1]), lua_rawseti(L, -2, 2); lua_pushinteger(L, limits.workgroupSize[2]), lua_rawseti(L, -2, 3); lua_setfield(L, -2, "workgroupSize"); lua_pushinteger(L, limits.totalWorkgroupSize), lua_setfield(L, -2, "totalWorkgroupSize"); lua_pushinteger(L, limits.computeSharedMemory), lua_setfield(L, -2, "computeSharedMemory"); lua_pushinteger(L, limits.shaderConstantSize), lua_setfield(L, -2, "shaderConstantSize"); lua_pushinteger(L, limits.indirectDrawCount), lua_setfield(L, -2, "indirectDrawCount"); lua_pushinteger(L, limits.instances), lua_setfield(L, -2, "instances"); lua_pushnumber(L, limits.anisotropy), lua_setfield(L, -2, "anisotropy"); lua_pushnumber(L, limits.pointSize), lua_setfield(L, -2, "pointSize"); return 1; } static int l_lovrGraphicsIsFormatSupported(lua_State* L) { TextureFormat format = luax_checkenum(L, 1, TextureFormat, NULL); uint32_t features = 0; int top = lua_gettop(L); for (int i = 2; i <= top; i++) { features |= 1 << luax_checkenum(L, i, TextureFeature, NULL); } uint32_t support = lovrGraphicsGetFormatSupport(format, features); lua_pushboolean(L, support & (1 << 0)); // linear lua_pushboolean(L, support & (1 << 1)); // srgb return 2; } static int l_lovrGraphicsGetBackgroundColor(lua_State* L) { float color[4]; lovrGraphicsGetBackgroundColor(color); lua_pushnumber(L, color[0]); lua_pushnumber(L, color[1]); lua_pushnumber(L, color[2]); lua_pushnumber(L, color[3]); return 4; } static int l_lovrGraphicsSetBackgroundColor(lua_State* L) { float color[4]; luax_readcolor(L, 1, color); lovrGraphicsSetBackgroundColor(color); return 0; } static int l_lovrGraphicsGetWindowPass(lua_State* L) { Pass* pass = lovrGraphicsGetWindowPass(); luax_pushtype(L, Pass, pass); return 1; } static int l_lovrGraphicsGetDefaultFont(lua_State* L) { Font* font = lovrGraphicsGetDefaultFont(); luax_pushtype(L, Font, font); return 1; } static uint32_t luax_checkbufferformat(lua_State* L, int index, DataField* fields, uint32_t* count, uint32_t max) { lovrCheck(lua_istable(L, index), "Expected a table for field list"); uint32_t length = luax_len(L, index); lovrCheck(length > 0, "At least one field must be provided"); lovrCheck(*count + length <= max, "Too many buffer fields (maybe format contains a cycle?)"); memset(fields + *count, 0, length * sizeof(DataField)); DataField* field = fields + *count; *count += length; for (uint32_t i = 0; i < length; i++, field++) { lua_rawgeti(L, index, i + 1); lovrCheck(lua_istable(L, -1), "Expected table for type info"); lua_getfield(L, -1, "type"); if (lua_isnil(L, -1)) lua_pop(L, 1), lua_rawgeti(L, -1, 2); if (lua_istable(L, -1)) { field->fields = fields + *count; field->fieldCount = luax_checkbufferformat(L, -1, fields, count, max); } else if (lua_type(L, -1) == LUA_TSTRING) { field->type = luax_checkdatatype(L, -1); } else { lovrThrow("Buffer field type must be a string or a table"); } lua_pop(L, 1); lua_getfield(L, -1, "name"); if (lua_isnil(L, -1)) lua_pop(L, 1), lua_rawgeti(L, -1, 1); lovrCheck(lua_type(L, -1) == LUA_TSTRING, "Buffer fields must have a 'name' key"); field->name = lua_tostring(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "length"); if (lua_isnil(L, -1)) lua_pop(L, 1), lua_rawgeti(L, -1, 3); field->length = luax_optu32(L, -1, 0); lua_pop(L, 1); lua_getfield(L, -1, "offset"); field->offset = lua_isnil(L, -1) ? 0 : luax_checku32(L, -1); lua_pop(L, 1); lua_pop(L, 1); } return length; } static int l_lovrGraphicsNewBuffer(lua_State* L) { DataField format[64]; BufferInfo info = { 0 }; DataLayout layout = LAYOUT_PACKED; bool hasData = false; Blob* blob = NULL; int type = lua_type(L, 1); // Format if (type == LUA_TNUMBER) { info.size = luax_checku32(L, 1); } else if ((blob = luax_totype(L, 1, Blob)) != NULL) { 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 = 1; info.format = format; format[0] = (DataField) { .type = luax_checkdatatype(L, 1) }; } else if (type == LUA_TTABLE) { info.format = format; format[0] = (DataField) { .fieldCount = luax_len(L, 1), .fields = format + 1 }; lua_rawgeti(L, 1, 1); bool anonymous = lua_type(L, -1) == LUA_TSTRING; lua_pop(L, 1); if (anonymous) { lovrCheck(format->fieldCount < COUNTOF(format), "Too many buffer fields"); info.fieldCount = format->fieldCount + 1; for (uint32_t i = 1; i <= format->fieldCount; i++) { lua_rawgeti(L, 1, i); 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)); } lua_getfield(L, 1, "layout"); layout = luax_checkenum(L, -1, BufferLayout, "packed"); lua_pop(L, 1); lua_getfield(L, 1, "stride"); format->stride = luax_optu32(L, -1, 0); lua_pop(L, 1); } else { return luax_typeerror(L, 1, "number, Blob, table, or string"); } // Length/size if (info.format) { lovrGraphicsAlignFields(format, layout); switch (lua_type(L, 2)) { 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_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); } lua_pop(L, 1); hasData = true; break; default: if ((blob = luax_totype(L, 2, Blob)) != NULL) { lovrCheck(blob->size < UINT32_MAX, "Blob is too big to create a Buffer (max size is 1GB)"); info.size = (uint32_t) blob->size; format->length = info.size / format->stride; break; } else if (luax_tovector(L, 2, NULL)) { format->length = 0; hasData = true; break; } return luax_typeerror(L, 2, "nil, number, vector, table, or Blob"); } } void* data; Buffer* buffer = lovrBufferCreate(&info, (blob || hasData) ? &data : NULL); // Write data if (blob) { memcpy(data, blob->data, info.size); } else if (hasData) { luax_checkbufferdata(L, 2, format, data); } luax_pushtype(L, Buffer, buffer); lovrRelease(buffer, lovrBufferDestroy); return 1; } static void freeImages(void* arg) { TextureInfo* info = arg; for (uint32_t i = 0; i < info->imageCount; i++) { lovrRelease(info->images[i], lovrImageDestroy); } } static int l_lovrGraphicsNewTexture(lua_State* L) { TextureInfo info = { .type = TEXTURE_2D, .format = FORMAT_RGBA8, .layers = 1, .mipmaps = ~0u, .usage = TEXTURE_SAMPLE, .srgb = true }; int index = 1; Image* stack[6]; Image** images = stack; uint32_t defer = lovrDeferPush(); if (lua_isnumber(L, 1)) { info.width = luax_checku32(L, index++); info.height = luax_checku32(L, index++); if (lua_isnumber(L, index)) { info.layers = luax_checku32(L, index++); info.type = TEXTURE_ARRAY; } info.usage |= TEXTURE_RENDER; info.mipmaps = 1; } else if (lua_istable(L, 1)) { int tableLength = luax_len(L, index++); if ((size_t) tableLength > COUNTOF(stack)) { images = lovrMalloc(tableLength * sizeof(Image*)); lovrDefer(lovrFree, images); } info.images = images; lovrDefer(freeImages, &info); if (tableLength == 0) { info.layers = 6; info.type = TEXTURE_CUBE; const char* faces[6] = { "right", "left", "top", "bottom", "back", "front" }; const char* altFaces[6] = { "px", "nx", "py", "ny", "pz", "nz" }; for (int i = 0; i < 6; i++) { lua_pushstring(L, faces[i]); lua_rawget(L, 1); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_pushstring(L, altFaces[i]); lua_rawget(L, 1); } lovrCheck(!lua_isnil(L, -1), "No array texture layers given and cubemap face '%s' missing", faces[i]); images[info.imageCount++] = luax_checkimage(L, -1); } } else { for (int i = 0; i < tableLength; i++) { lua_rawgeti(L, 1, i + 1); images[info.imageCount++] = luax_checkimage(L, -1); lua_pop(L, 1); } info.type = info.imageCount == 6 ? TEXTURE_CUBE : TEXTURE_ARRAY; info.layers = info.imageCount == 1 ? lovrImageGetLayerCount(images[0]) : info.imageCount; } } else { info.imageCount = 1; info.images = images; images[0] = luax_checkimage(L, index++); info.layers = lovrImageGetLayerCount(images[0]); if (lovrImageIsCube(images[0])) { info.type = TEXTURE_CUBE; } else if (info.layers > 1) { info.type = TEXTURE_ARRAY; } } if (info.imageCount > 0) { Image* image = images[0]; uint32_t levels = lovrImageGetLevelCount(image); info.format = lovrImageGetFormat(image); info.srgb = lovrImageIsSRGB(image); info.width = lovrImageGetWidth(image, 0); info.height = lovrImageGetHeight(image, 0); bool mipmappable = lovrGraphicsGetFormatSupport(info.format, TEXTURE_FEATURE_BLIT) & (1 << info.srgb); info.mipmaps = (levels == 1 && mipmappable) ? ~0u : levels; for (uint32_t i = 1; i < info.imageCount; i++) { lovrCheck(lovrImageGetWidth(images[0], 0) == lovrImageGetWidth(images[i], 0), "Image widths must match"); lovrCheck(lovrImageGetHeight(images[0], 0) == lovrImageGetHeight(images[i], 0), "Image heights must match"); lovrCheck(lovrImageGetFormat(images[0]) == lovrImageGetFormat(images[i]), "Image formats must match"); lovrCheck(lovrImageGetLevelCount(images[0]) == lovrImageGetLevelCount(images[i]), "Image mipmap counts must match"); lovrCheck(lovrImageGetLayerCount(images[i]) == 1, "When a list of images are provided, each must have a single layer"); } } if (lua_istable(L, index)) { lua_getfield(L, index, "type"); info.type = lua_isnil(L, -1) ? info.type : (uint32_t) luax_checkenum(L, -1, TextureType, NULL); lua_pop(L, 1); if (info.imageCount == 0) { lua_getfield(L, index, "format"); info.format = lua_isnil(L, -1) ? info.format : (uint32_t) luax_checkenum(L, -1, TextureFormat, NULL); lua_pop(L, 1); } lua_getfield(L, index, "linear"); info.srgb = lua_isnil(L, -1) ? info.srgb : !lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, index, "mipmaps"); bool mipmappable = lovrGraphicsGetFormatSupport(info.format, TEXTURE_FEATURE_BLIT) & (1 << info.srgb); if (lua_type(L, -1) == LUA_TNUMBER) { info.mipmaps = lua_tonumber(L, -1); } else if (!lua_isnil(L, -1)) { info.mipmaps = lua_toboolean(L, -1) ? ~0u : 1; } else { info.mipmaps = (info.imageCount == 0 || !mipmappable) ? 1 : ~0u; } lovrCheck(info.imageCount == 0 || info.mipmaps == 1 || mipmappable, "This texture format does not support blitting, which is required for mipmap generation"); lua_pop(L, 1); lua_getfield(L, index, "usage"); switch (lua_type(L, -1)) { case LUA_TSTRING: info.usage = 1 << luax_checkenum(L, -1, TextureUsage, NULL); break; case LUA_TTABLE: info.usage = 0; int length = luax_len(L, -1); for (int i = 0; i < length; i++) { lua_rawgeti(L, -1, i + 1); info.usage |= 1 << luax_checkenum(L, -1, TextureUsage, NULL); lua_pop(L, 1); } break; case LUA_TNIL: break; default: return luaL_error(L, "Expected Texture usage to be a string, table, or nil"); } lua_pop(L, 1); lua_getfield(L, index, "label"); info.label = lua_tostring(L, -1); lua_pop(L, 1); } if (lua_type(L, 1) == LUA_TNUMBER && lua_type(L, 3) != LUA_TNUMBER && info.type == TEXTURE_CUBE) { info.layers = 6; } Texture* texture = lovrTextureCreate(&info); luax_pushtype(L, Texture, texture); lovrRelease(texture, lovrTextureDestroy); lovrDeferPop(defer); return 1; } static int l_lovrGraphicsNewTextureView(lua_State* L) { Texture* texture = luax_checktype(L, 1, Texture); const TextureInfo* base = lovrTextureGetInfo(texture); luaL_checktype(L, 2, LUA_TTABLE); TextureViewInfo info = { 0 }; lua_getfield(L, 2, "type"); info.type = lua_isnil(L, -1) ? base->type : luax_checkenum(L, -1, TextureType, NULL); lua_pop(L, 1); lua_getfield(L, 2, "layer"); info.layerIndex = lua_isnil(L, -1) ? 0 : luax_checku32(L, -1) - 1; lua_pop(L, 1); lua_getfield(L, 2, "layercount"); info.layerCount = lua_isnil(L, -1) ? ~0u : luax_checku32(L, -1); lua_pop(L, 1); lua_getfield(L, 2, "mipmap"); info.levelIndex = lua_isnil(L, -1) ? 0 : luax_checku32(L, -1) - 1; lua_pop(L, 1); lua_getfield(L, 2, "mipmapcount"); info.levelCount = lua_isnil(L, -1) ? ~0u : luax_checku32(L, -1); lua_pop(L, 1); lua_getfield(L, 2, "label"); info.label = lua_tostring(L, -1); lua_pop(L, 1); Texture* view = lovrTextureCreateView(texture, &info); luax_pushtype(L, Texture, view); lovrRelease(view, lovrTextureDestroy); return 1; } static int l_lovrGraphicsNewSampler(lua_State* L) { SamplerInfo info = { .min = FILTER_LINEAR, .mag = FILTER_LINEAR, .mip = FILTER_LINEAR, .wrap = { WRAP_REPEAT, WRAP_REPEAT, WRAP_REPEAT }, .range = { 0.f, -1.f } }; luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, 1, "filter"); if (lua_isstring(L, -1)) { info.min = info.mag = info.mip = luax_checkenum(L, -1, FilterMode, NULL); } else if (lua_istable(L, -1)) { lua_rawgeti(L, -1, 1); lua_rawgeti(L, -2, 2); lua_rawgeti(L, -3, 3); info.min = luax_checkenum(L, -3, FilterMode, NULL); info.mag = luax_checkenum(L, -2, FilterMode, NULL); info.mip = luax_checkenum(L, -1, FilterMode, NULL); lua_pop(L, 3); } else if (!lua_isnil(L, -1)) { lovrThrow("Expected string or table for Sampler filter"); } lua_pop(L, 1); lua_getfield(L, 1, "wrap"); if (lua_isstring(L, -1)) { info.wrap[0] = info.wrap[1] = info.wrap[2] = luax_checkenum(L, -1, WrapMode, NULL); } else if (lua_istable(L, -1)) { lua_rawgeti(L, -1, 1); lua_rawgeti(L, -2, 2); lua_rawgeti(L, -3, 3); info.wrap[0] = luax_checkenum(L, -3, WrapMode, NULL); info.wrap[1] = luax_checkenum(L, -2, WrapMode, NULL); info.wrap[2] = luax_checkenum(L, -1, WrapMode, NULL); lua_pop(L, 3); } else if (!lua_isnil(L, -1)) { lovrThrow("Expected string or table for Sampler wrap"); } lua_pop(L, 1); lua_getfield(L, 1, "compare"); info.compare = luax_checkcomparemode(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "anisotropy"); info.anisotropy = luax_optfloat(L, -1, 0.f); lua_pop(L, 1); lua_getfield(L, 1, "mipmaprange"); if (!lua_isnil(L, -1)) { lovrCheck(lua_istable(L, -1), "Sampler mipmap range must be nil or a table"); lua_rawgeti(L, -1, 1); lua_rawgeti(L, -2, 2); info.range[0] = luax_checkfloat(L, -2); info.range[1] = luax_checkfloat(L, -1); lua_pop(L, 2); } lua_pop(L, 1); Sampler* sampler = lovrSamplerCreate(&info); luax_pushtype(L, Sampler, sampler); lovrRelease(sampler, lovrSamplerDestroy); return 1; } static ShaderSource luax_checkshadersource(lua_State* L, int index, ShaderStage stage, bool* shouldFree) { *shouldFree = false; if (lua_isstring(L, index)) { size_t length; const char* string = lua_tolstring(L, index, &length); if (memchr(string, '\n', MIN(256, length))) { return (ShaderSource) { stage, string, length }; } else { for (int i = 0; lovrDefaultShader[i].length; i++) { if (lovrDefaultShader[i].length == length && !memcmp(lovrDefaultShader[i].string, string, length)) { return lovrGraphicsGetDefaultShaderSource(i, stage); } } size_t size; void* code = luax_readfile(string, &size); if (code) { *shouldFree = true; return (ShaderSource) { stage, code, size }; } else { luaL_argerror(L, index, "single-line string was not filename or DefaultShader"); } } } else if (lua_isuserdata(L, index)) { Blob* blob = luax_checktype(L, index, Blob); return (ShaderSource) { stage, blob->data, blob->size }; } else { luax_typeerror(L, index, "string, Blob, or DefaultShader"); } return (ShaderSource) { 0 }; } static int l_lovrGraphicsCompileShader(lua_State* L) { ShaderSource inputs[2], outputs[2]; bool shouldFree[2]; uint32_t count; if (lua_gettop(L) == 1) { inputs[0] = luax_checkshadersource(L, 1, STAGE_COMPUTE, &shouldFree[0]); count = 1; } else { inputs[0] = luax_checkshadersource(L, 1, STAGE_VERTEX, &shouldFree[0]); inputs[1] = luax_checkshadersource(L, 2, STAGE_FRAGMENT, &shouldFree[1]); count = 2; } lovrGraphicsCompileShader(inputs, outputs, count, luax_readfile); for (uint32_t i = 0; i < count; i++) { if (shouldFree[i] && outputs[i].code != inputs[i].code) lovrFree((void*) inputs[i].code); Blob* blob = lovrBlobCreate((void*) outputs[i].code, outputs[i].size, "Shader code"); luax_pushtype(L, Blob, blob); lovrRelease(blob, lovrBlobDestroy); } return count; } static int l_lovrGraphicsNewShader(lua_State* L) { ShaderSource source[2], compiled[2]; ShaderInfo info = { .stages = compiled }; bool shouldFree[2] = { 0 }; int index; if (lua_gettop(L) == 1 || lua_istable(L, 2)) { info.type = SHADER_COMPUTE; source[0] = luax_checkshadersource(L, 1, STAGE_COMPUTE, &shouldFree[0]); info.stageCount = 1; index = 2; } else { info.type = SHADER_GRAPHICS; source[0] = luax_checkshadersource(L, 1, STAGE_VERTEX, &shouldFree[0]); source[1] = luax_checkshadersource(L, 2, STAGE_FRAGMENT, &shouldFree[1]); info.stageCount = 2; index = 3; } lovrGraphicsCompileShader(source, compiled, info.stageCount, luax_readfile); arr_t(ShaderFlag) flags; arr_init(&flags); if (lua_istable(L, index)) { lua_getfield(L, index, "flags"); if (!lua_isnil(L, -1)) { luaL_checktype(L, -1, LUA_TTABLE); lua_pushnil(L); while (lua_next(L, -2) != 0) { ShaderFlag flag = { 0 }; flag.value = lua_isboolean(L, -1) ? (double) lua_toboolean(L, -1) : lua_tonumber(L, -1); switch (lua_type(L, -2)) { case LUA_TSTRING: flag.name = lua_tostring(L, -2); break; case LUA_TNUMBER: flag.id = lua_tointeger(L, -2); break; default: lovrThrow("Unexpected ShaderFlag key type (%s)", lua_typename(L, lua_type(L, -2))); } arr_push(&flags, flag); lua_pop(L, 1); } } lua_pop(L, 1); lua_getfield(L, index, "type"); info.type = lua_isnil(L, -1) ? info.type : luax_checkenum(L, -1, ShaderType, NULL); lua_pop(L, 1); lua_getfield(L, index, "label"); info.label = lua_tostring(L, -1); lua_pop(L, 1); } lovrCheck(flags.length < 1000, "Too many Shader flags"); info.flags = flags.data; info.flagCount = (uint32_t) flags.length; Shader* shader = lovrShaderCreate(&info); luax_pushtype(L, Shader, shader); lovrRelease(shader, lovrShaderDestroy); for (uint32_t i = 0; i < info.stageCount; i++) { if (shouldFree[i]) lovrFree((void*) source[i].code); if (source[i].code != compiled[i].code) lovrFree((void*) compiled[i].code); } arr_free(&flags); return 1; } static Texture* luax_opttexture(lua_State* L, int index) { if (lua_isnil(L, index)) { return NULL; } Texture* texture = luax_totype(L, index, Texture); if (texture) return texture; Image* image = luax_checkimage(L, index); TextureInfo info = { .type = TEXTURE_2D, .format = lovrImageGetFormat(image), .width = lovrImageGetWidth(image, 0), .height = lovrImageGetHeight(image, 0), .layers = 1, .mipmaps = ~0u, .usage = TEXTURE_SAMPLE, .srgb = lovrImageIsSRGB(image), .imageCount = 1, .images = &image }; uint32_t defer = lovrDeferPush(); lovrDeferRelease(image, lovrImageDestroy); texture = lovrTextureCreate(&info); lovrDeferPop(defer); return texture; } static int l_lovrGraphicsNewMaterial(lua_State* L) { MaterialInfo info; memset(&info, 0, sizeof(info)); luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, 1, "color"); luax_optcolor(L, -1, info.data.color); lua_pop(L, 1); lua_getfield(L, 1, "glow"); if (lua_isnil(L, -1)) { memset(info.data.glow, 0, sizeof(info.data.glow)); } else { luax_optcolor(L, -1, info.data.glow); } lua_pop(L, 1); lua_getfield(L, 1, "uvShift"); if (lua_type(L, -1) == LUA_TNUMBER) { float shift = lua_tonumber(L, -1); info.data.uvShift[0] = shift; info.data.uvShift[1] = shift; } else if (lua_type(L, -1) == LUA_TTABLE) { lua_rawgeti(L, -1, 1); lua_rawgeti(L, -2, 2); info.data.uvShift[0] = luax_optfloat(L, -2, 0.f); info.data.uvShift[1] = luax_optfloat(L, -1, 0.f); lua_pop(L, 2); } else if (!lua_isnil(L, -1)) { float* v = luax_checkvector(L, -1, V_VEC2, "vec2, table, or nil"); info.data.uvShift[0] = v[0]; info.data.uvShift[1] = v[1]; } lua_pop(L, 1); lua_getfield(L, 1, "uvScale"); if (lua_isnil(L, -1)) { info.data.uvScale[0] = 1.f; info.data.uvScale[1] = 1.f; } else if (lua_isnumber(L, -1)) { float scale = lua_tonumber(L, -1); info.data.uvScale[0] = scale; info.data.uvScale[1] = scale; } else if (lua_type(L, -1) == LUA_TTABLE) { lua_rawgeti(L, -1, 1); lua_rawgeti(L, -2, 2); info.data.uvScale[0] = luax_optfloat(L, -2, 1.f); info.data.uvScale[1] = luax_optfloat(L, -1, 1.f); lua_pop(L, 2); } else { float* v = luax_checkvector(L, -1, V_VEC2, "vec2, table, or nil"); info.data.uvScale[0] = v[0]; info.data.uvScale[1] = v[1]; } lua_pop(L, 1); lua_getfield(L, 1, "metalness"); info.data.metalness = luax_optfloat(L, -1, 1.f); lua_pop(L, 1); lua_getfield(L, 1, "roughness"); info.data.roughness = luax_optfloat(L, -1, 1.f); lua_pop(L, 1); lua_getfield(L, 1, "clearcoat"); info.data.clearcoat = luax_optfloat(L, -1, 0.f); lua_pop(L, 1); lua_getfield(L, 1, "clearcoatRoughness"); info.data.clearcoatRoughness = luax_optfloat(L, -1, 0.f); lua_pop(L, 1); lua_getfield(L, 1, "occlusionStrength"); info.data.occlusionStrength = luax_optfloat(L, -1, 1.f); lua_pop(L, 1); lua_getfield(L, 1, "normalScale"); info.data.normalScale = luax_optfloat(L, -1, 1.f); lua_pop(L, 1); lua_getfield(L, 1, "alphaCutoff"); info.data.alphaCutoff = luax_optfloat(L, -1, 0.f); lua_pop(L, 1); lua_getfield(L, 1, "texture"); info.texture = luax_opttexture(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "glowTexture"); info.glowTexture = luax_opttexture(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "metalnessTexture"); info.metalnessTexture = luax_opttexture(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "roughnessTexture"); info.roughnessTexture = luax_opttexture(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "clearcoatTexture"); info.clearcoatTexture = luax_opttexture(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "occlusionTexture"); info.occlusionTexture = luax_opttexture(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "normalTexture"); info.normalTexture = luax_opttexture(L, -1); lua_pop(L, 1); Material* material = lovrMaterialCreate(&info); luax_pushtype(L, Material, material); lovrRelease(material, lovrMaterialDestroy); return 1; } static int l_lovrGraphicsNewFont(lua_State* L) { FontInfo info = { 0 }; info.rasterizer = luax_totype(L, 1, Rasterizer); info.spread = 4.; uint32_t defer = lovrDeferPush(); if (!info.rasterizer) { Blob* blob = NULL; float size; if (lua_type(L, 1) == LUA_TNUMBER || lua_isnoneornil(L, 1)) { size = luax_optfloat(L, 1, 32.); info.spread = luaL_optnumber(L, 2, info.spread); } else { blob = luax_readblob(L, 1, "Font"); size = luax_optfloat(L, 2, 32.); info.spread = luaL_optnumber(L, 3, info.spread); lovrDeferRelease(blob, lovrBlobDestroy); } info.rasterizer = lovrRasterizerCreate(blob, size); lovrDeferRelease(info.rasterizer, lovrRasterizerDestroy); } else { info.spread = luaL_optnumber(L, 2, info.spread); } Font* font = lovrFontCreate(&info); luax_pushtype(L, Font, font); lovrRelease(font, lovrFontDestroy); lovrDeferPop(defer); return 1; } static int l_lovrGraphicsNewMesh(lua_State* L) { MeshInfo info = { 0 }; DataField stack[17]; DataField* format = stack; bool customFormat = false; if (lua_istable(L, 1)) { lua_rawgeti(L, 1, 1); if (lua_istable(L, -1)) { lua_getfield(L, -1, "type"); lua_rawgeti(L, -2, 1); if (lua_type(L, -2) == LUA_TSTRING || lua_type(L, -1) == LUA_TSTRING) { info.vertexFormat = format; format[0] = (DataField) { .fields = format + 1, .fieldCount = luax_len(L, 1) }; lovrCheck(format->fieldCount + 1 <= COUNTOF(stack), "Mesh has too many vertex attributes (max is %d)", COUNTOF(stack) - 1); for (uint32_t i = 0; i < format->fieldCount; i++) { lua_rawgeti(L, 1, i + 1); lovrCheck(lua_istable(L, -1), "Expected table of tables"); DataField* attribute = &format->fields[i]; memset(attribute, 0, sizeof(*attribute)); lua_getfield(L, -1, "name"); if (lua_isnil(L, -1)) lua_pop(L, 1), lua_rawgeti(L, -1, 1); lovrCheck(lua_type(L, -1) == LUA_TSTRING, "Mesh attribute must have 'name' key"); attribute->name = lua_tostring(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "type"); if (lua_isnil(L, -1)) lua_pop(L, 1), lua_rawgeti(L, -1, 2); lovrCheck(lua_type(L, -1) == LUA_TSTRING, "Mesh attribute must have 'type' key"); attribute->type = luax_checkdatatype(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "offset"); attribute->offset = luax_optu32(L, -1, 0); lua_pop(L, 2); } lua_getfield(L, 1, "stride"); format->stride = luax_optu32(L, -1, 0); lua_pop(L, 1); lovrGraphicsAlignFields(format, LAYOUT_PACKED); customFormat = true; } lua_pop(L, 2); } lua_pop(L, 1); } DataField defaultFormat[] = { { .stride = 32 }, { .name = "VertexPosition", .type = TYPE_F32x3, .offset = 0 }, { .name = "VertexNormal", .type = TYPE_F32x3, .offset = 12 }, { .name = "VertexUV", .type = TYPE_F32x2, .offset = 24 } }; if (!customFormat) { format = defaultFormat; info.vertexFormat = defaultFormat; info.vertexFormat->fields = defaultFormat + 1; info.vertexFormat->fieldCount = COUNTOF(defaultFormat) - 1; } Blob* blob = NULL; bool hasData = false; int index = 1 + customFormat; switch (lua_type(L, index)) { case LUA_TNUMBER: format->length = luax_checku32(L, index); break; case LUA_TTABLE: format->length = luax_len(L, index); hasData = true; break; case LUA_TUSERDATA: if ((info.vertexBuffer = luax_totype(L, index, Buffer)) != NULL) break; if ((blob = luax_totype(L, index, Blob)) != NULL) { lovrCheck(blob->size % format->stride == 0, "Blob size must be a multiple of vertex size"); lovrCheck(blob->size < UINT32_MAX, "Max Blob size is 4GB"); format->length = (uint32_t) (blob->size / format->stride); hasData = true; break; } default: return luax_typeerror(L, index, "number, table, Blob, or Buffer"); } if (info.vertexBuffer) { info.storage = MESH_GPU; } else { info.storage = luax_checkenum(L, index + 1, MeshStorage, "cpu"); } void* vertices = NULL; Mesh* mesh = lovrMeshCreate(&info, hasData ? &vertices : NULL); if (blob) { memcpy(vertices, blob->data, blob->size); } else if (hasData) { luax_checkbufferdata(L, index, lovrMeshGetVertexFormat(mesh), vertices); } luax_pushtype(L, Mesh, mesh); lovrRelease(mesh, lovrMeshDestroy); return 1; } static int l_lovrGraphicsNewModel(lua_State* L) { ModelInfo info = { 0 }; info.data = luax_totype(L, 1, ModelData); info.materials = true; info.mipmaps = true; uint32_t defer = lovrDeferPush(); if (!info.data) { Blob* blob = luax_readblob(L, 1, "Model"); lovrDeferRelease(blob, lovrBlobDestroy); info.data = lovrModelDataCreate(blob, luax_readfile); lovrDeferRelease(info.data, lovrModelDataDestroy); } if (lua_istable(L, 2)) { lua_getfield(L, 2, "mipmaps"); info.mipmaps = lua_isnil(L, -1) || lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, 2, "materials"); info.materials = lua_isnil(L, -1) || lua_toboolean(L, -1); lua_pop(L, 1); } Model* model = lovrModelCreate(&info); luax_pushtype(L, Model, model); lovrRelease(model, lovrModelDestroy); lovrDeferPop(defer); return 1; } int l_lovrPassSetCanvas(lua_State* L); static int l_lovrGraphicsNewPass(lua_State* L) { Pass* pass = lovrPassCreate(); luax_pushtype(L, Pass, pass); lua_insert(L, 1); l_lovrPassSetCanvas(L); lua_settop(L, 1); lovrRelease(pass, lovrPassDestroy); return 1; } static const luaL_Reg lovrGraphics[] = { { "initialize", l_lovrGraphicsInitialize }, { "isInitialized", l_lovrGraphicsIsInitialized }, { "isTimingEnabled", l_lovrGraphicsIsTimingEnabled }, { "setTimingEnabled", l_lovrGraphicsSetTimingEnabled }, { "submit", l_lovrGraphicsSubmit }, { "present", l_lovrGraphicsPresent }, { "wait", l_lovrGraphicsWait }, { "getDevice", l_lovrGraphicsGetDevice }, { "getFeatures", l_lovrGraphicsGetFeatures }, { "getLimits", l_lovrGraphicsGetLimits }, { "isFormatSupported", l_lovrGraphicsIsFormatSupported }, { "getBackgroundColor", l_lovrGraphicsGetBackgroundColor }, { "setBackgroundColor", l_lovrGraphicsSetBackgroundColor }, { "getWindowPass", l_lovrGraphicsGetWindowPass }, { "getDefaultFont", l_lovrGraphicsGetDefaultFont }, { "newBuffer", l_lovrGraphicsNewBuffer }, { "newTexture", l_lovrGraphicsNewTexture }, { "newTextureView", l_lovrGraphicsNewTextureView }, { "newSampler", l_lovrGraphicsNewSampler }, { "compileShader", l_lovrGraphicsCompileShader }, { "newShader", l_lovrGraphicsNewShader }, { "newMaterial", l_lovrGraphicsNewMaterial }, { "newFont", l_lovrGraphicsNewFont }, { "newMesh", l_lovrGraphicsNewMesh }, { "newModel", l_lovrGraphicsNewModel }, { "newPass", l_lovrGraphicsNewPass }, { NULL, NULL } }; extern const luaL_Reg lovrBuffer[]; extern const luaL_Reg lovrTexture[]; extern const luaL_Reg lovrSampler[]; extern const luaL_Reg lovrShader[]; extern const luaL_Reg lovrMaterial[]; extern const luaL_Reg lovrFont[]; extern const luaL_Reg lovrMesh[]; extern const luaL_Reg lovrModel[]; extern const luaL_Reg lovrReadback[]; extern const luaL_Reg lovrPass[]; int luaopen_lovr_graphics(lua_State* L) { lua_newtable(L); luax_register(L, lovrGraphics); luax_registertype(L, Buffer); luax_registertype(L, Texture); luax_registertype(L, Sampler); luax_registertype(L, Shader); luax_registertype(L, Material); luax_registertype(L, Font); luax_registertype(L, Mesh); luax_registertype(L, Model); luax_registertype(L, Readback); luax_registertype(L, Pass); return 1; }