#include "api.h" #include "graphics/graphics.h" #include "graphics/buffer.h" #include "graphics/canvas.h" #include "graphics/material.h" #include "graphics/mesh.h" #include "graphics/model.h" #include "graphics/shader.h" #include "data/blob.h" #include "data/modelData.h" #include "data/rasterizer.h" #include "data/image.h" #include "core/os.h" #include "core/util.h" #include #include #include #include #include StringEntry lovrArcMode[] = { [ARC_MODE_PIE] = ENTRY("pie"), [ARC_MODE_OPEN] = ENTRY("open"), [ARC_MODE_CLOSED] = ENTRY("closed"), { 0 } }; StringEntry lovrAttributeType[] = { [I8] = ENTRY("byte"), [U8] = ENTRY("ubyte"), [I16] = ENTRY("short"), [U16] = ENTRY("ushort"), [I32] = ENTRY("int"), [U32] = ENTRY("uint"), [F32] = ENTRY("float"), { 0 } }; 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"), { 0 } }; StringEntry lovrBlockType[] = { [BLOCK_UNIFORM] = ENTRY("uniform"), [BLOCK_COMPUTE] = ENTRY("compute"), { 0 } }; StringEntry lovrBufferUsage[] = { [USAGE_STATIC] = ENTRY("static"), [USAGE_DYNAMIC] = ENTRY("dynamic"), [USAGE_STREAM] = ENTRY("stream"), { 0 } }; StringEntry lovrCompareMode[] = { [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 lovrCoordinateSpace[] = { [SPACE_LOCAL] = ENTRY("local"), [SPACE_GLOBAL] = ENTRY("global"), { 0 } }; StringEntry lovrDefaultShader[] = { [SHADER_UNLIT] = ENTRY("unlit"), [SHADER_STANDARD] = ENTRY("standard"), [SHADER_CUBE] = ENTRY("cube"), [SHADER_PANO] = ENTRY("pano"), [SHADER_FONT] = ENTRY("font"), [SHADER_FILL] = ENTRY("screenspace"), { 0 } }; StringEntry lovrDrawMode[] = { [DRAW_POINTS] = ENTRY("points"), [DRAW_LINES] = ENTRY("lines"), [DRAW_LINE_STRIP] = ENTRY("linestrip"), [DRAW_LINE_LOOP] = ENTRY("lineloop"), [DRAW_TRIANGLE_STRIP] = ENTRY("strip"), [DRAW_TRIANGLES] = ENTRY("triangles"), [DRAW_TRIANGLE_FAN] = ENTRY("fan"), { 0 } }; StringEntry lovrDrawStyle[] = { [STYLE_FILL] = ENTRY("fill"), [STYLE_LINE] = ENTRY("line"), { 0 } }; StringEntry lovrFilterMode[] = { [FILTER_NEAREST] = ENTRY("nearest"), [FILTER_BILINEAR] = ENTRY("bilinear"), [FILTER_TRILINEAR] = ENTRY("trilinear"), { 0 } }; StringEntry lovrHorizontalAlign[] = { [ALIGN_LEFT] = ENTRY("left"), [ALIGN_CENTER] = ENTRY("center"), [ALIGN_RIGHT] = ENTRY("right"), { 0 } }; StringEntry lovrMaterialColor[] = { [COLOR_DIFFUSE] = ENTRY("diffuse"), [COLOR_EMISSIVE] = ENTRY("emissive"), { 0 } }; StringEntry lovrMaterialScalar[] = { [SCALAR_METALNESS] = ENTRY("metalness"), [SCALAR_ROUGHNESS] = ENTRY("roughness"), [SCALAR_ALPHA_CUTOFF] = ENTRY("alphacutoff"), { 0 } }; StringEntry lovrMaterialTexture[] = { [TEXTURE_DIFFUSE] = ENTRY("diffuse"), [TEXTURE_EMISSIVE] = ENTRY("emissive"), [TEXTURE_METALNESS] = ENTRY("metalness"), [TEXTURE_ROUGHNESS] = ENTRY("roughness"), [TEXTURE_OCCLUSION] = ENTRY("occlusion"), [TEXTURE_NORMAL] = ENTRY("normal"), { 0 } }; StringEntry lovrShaderType[] = { [SHADER_GRAPHICS] = ENTRY("graphics"), [SHADER_COMPUTE] = ENTRY("compute"), { 0 } }; StringEntry lovrStencilAction[] = { [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 lovrTextureFormat[] = { [FORMAT_RGB] = ENTRY("rgb"), [FORMAT_RGBA] = ENTRY("rgba"), [FORMAT_RGBA4] = ENTRY("rgba4"), [FORMAT_R16] = ENTRY("r16"), [FORMAT_RG16] = ENTRY("rg16"), [FORMAT_RGBA16] = ENTRY("rgba16"), [FORMAT_RGBA16F] = ENTRY("rgba16f"), [FORMAT_RGBA32F] = ENTRY("rgba32f"), [FORMAT_R16F] = ENTRY("r16f"), [FORMAT_R32F] = ENTRY("r32f"), [FORMAT_RG16F] = ENTRY("rg16f"), [FORMAT_RG32F] = ENTRY("rg32f"), [FORMAT_RGB5A1] = ENTRY("rgb5a1"), [FORMAT_RGB10A2] = ENTRY("rgb10a2"), [FORMAT_RG11B10F] = ENTRY("rg11b10f"), [FORMAT_D16] = ENTRY("d16"), [FORMAT_D32F] = ENTRY("d32f"), [FORMAT_D24S8] = ENTRY("d24s8"), [FORMAT_DXT1] = ENTRY("dxt1"), [FORMAT_DXT3] = ENTRY("dxt3"), [FORMAT_DXT5] = ENTRY("dxt5"), [FORMAT_ASTC_4x4] = ENTRY("astc4x4"), [FORMAT_ASTC_5x4] = ENTRY("astc5x4"), [FORMAT_ASTC_5x5] = ENTRY("astc5x5"), [FORMAT_ASTC_6x5] = ENTRY("astc6x5"), [FORMAT_ASTC_6x6] = ENTRY("astc6x6"), [FORMAT_ASTC_8x5] = ENTRY("astc8x5"), [FORMAT_ASTC_8x6] = ENTRY("astc8x6"), [FORMAT_ASTC_8x8] = ENTRY("astc8x8"), [FORMAT_ASTC_10x5] = ENTRY("astc10x5"), [FORMAT_ASTC_10x6] = ENTRY("astc10x6"), [FORMAT_ASTC_10x8] = ENTRY("astc10x8"), [FORMAT_ASTC_10x10] = ENTRY("astc10x10"), [FORMAT_ASTC_12x10] = ENTRY("astc12x10"), [FORMAT_ASTC_12x12] = ENTRY("astc12x12"), { 0 } }; StringEntry lovrTextureType[] = { [TEXTURE_2D] = ENTRY("2d"), [TEXTURE_ARRAY] = ENTRY("array"), [TEXTURE_CUBE] = ENTRY("cube"), [TEXTURE_VOLUME] = ENTRY("volume"), { 0 } }; StringEntry lovrUniformAccess[] = { [ACCESS_READ] = ENTRY("read"), [ACCESS_WRITE] = ENTRY("write"), [ACCESS_READ_WRITE] = ENTRY("readwrite"), { 0 } }; StringEntry lovrVerticalAlign[] = { [ALIGN_TOP] = ENTRY("top"), [ALIGN_MIDDLE] = ENTRY("middle"), [ALIGN_BOTTOM] = ENTRY("bottom"), { 0 } }; StringEntry lovrWinding[] = { [WINDING_CLOCKWISE] = ENTRY("clockwise"), [WINDING_COUNTERCLOCKWISE] = ENTRY("counterclockwise"), { 0 } }; StringEntry lovrWrapMode[] = { [WRAP_CLAMP] = ENTRY("clamp"), [WRAP_REPEAT] = ENTRY("repeat"), [WRAP_MIRRORED_REPEAT] = ENTRY("mirroredrepeat"), { 0 } }; static uint32_t luax_getvertexcount(lua_State* L, int index) { int type = lua_type(L, index); if (type == LUA_TTABLE) { int count = luax_len(L, index); lua_rawgeti(L, index, 1); int tableType = lua_type(L, -1); lua_pop(L, 1); return tableType == LUA_TNUMBER ? count / 3 : count; } else if (type == LUA_TNUMBER) { return (lua_gettop(L) - index + 1) / 3; } else if (type == LUA_TNONE || type == LUA_TNIL) { return 0; } else { // vec3 return lua_gettop(L) - index + 1; } } static void luax_readvertices(lua_State* L, int index, float* v, uint32_t count) { switch (lua_type(L, index)) { case LUA_TTABLE: lua_rawgeti(L, index, 1); if (lua_type(L, -1) == LUA_TNUMBER) { lua_pop(L, 1); for (uint32_t i = 0; i < count; i++) { for (int j = 0; j < 3; j++) { lua_rawgeti(L, index, 3 * i + j + 1); v[j] = lua_tonumber(L, -1); lua_pop(L, 1); } v[3] = v[4] = v[5] = v[6] = v[7] = 0.f; v += 8; } } else { lua_pop(L, 1); for (uint32_t i = 0; i < count; i++) { lua_rawgeti(L, index, i + 1); vec3_init(v, luax_checkvector(L, -1, V_VEC3, NULL)); lua_pop(L, 1); v[3] = v[4] = v[5] = v[6] = v[7] = 0.f; v += 8; } } break; case LUA_TNUMBER: for (uint32_t i = 0; i < count; i++) { for (int j = 0; j < 3; j++) { v[j] = lua_tonumber(L, index + 3 * i + j); } v[3] = v[4] = v[5] = v[6] = v[7] = 0.f; v += 8; } break; default: for (uint32_t i = 0; i < count; i++) { vec3_init(v, luax_checkvector(L, index + i, V_VEC3, NULL)); v[3] = v[4] = v[5] = v[6] = v[7] = 0.f; v += 8; } break; } } static void stencilCallback(void* userdata) { lua_State* L = userdata; luaL_checktype(L, -1, LUA_TFUNCTION); lua_call(L, 0, 0); } // Must be released when done static Image* luax_checkimage(lua_State* L, int index, bool flip) { Image* image = luax_totype(L, index, Image); if (image) { lovrRetain(image); } else { Blob* blob = luax_readblob(L, index, "Texture"); image = lovrImageCreateFromBlob(blob, flip); lovrRelease(blob, lovrBlobDestroy); } return image; } // Base static int l_lovrGraphicsPresent(lua_State* L) { lovrGraphicsPresent(); return 0; } static int l_lovrGraphicsCreateWindow(lua_State* L) { WindowFlags flags; memset(&flags, 0, sizeof(flags)); if (!lua_toboolean(L, 1)) { return 0; } luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, 1, "width"); flags.width = luaL_optinteger(L, -1, 1080); lua_pop(L, 1); lua_getfield(L, 1, "height"); flags.height = luaL_optinteger(L, -1, 600); lua_pop(L, 1); lua_getfield(L, 1, "fullscreen"); flags.fullscreen = lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "resizable"); flags.resizable = lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "msaa"); flags.msaa = lua_tointeger(L, -1); lua_pop(L, 1); lua_getfield(L, 1, "title"); flags.title = luaL_optstring(L, -1, "LÖVR"); lua_pop(L, 1); lua_getfield(L, 1, "icon"); Image* image = NULL; if (!lua_isnil(L, -1)) { image = luax_checkimage(L, -1, false); flags.icon.data = image->blob->data; flags.icon.width = image->width; flags.icon.height = image->height; } lua_pop(L, 1); lua_getfield(L, 1, "vsync"); flags.vsync = lua_tointeger(L, -1); lua_pop(L, 1); lovrGraphicsCreateWindow(&flags); luax_atexit(L, lovrGraphicsDestroy); // The lua_State that creates the window shall be the one to destroy it lovrRelease(image, lovrImageDestroy); return 0; } static int l_lovrGraphicsGetWidth(lua_State* L) { lua_pushnumber(L, lovrGraphicsGetWidth()); return 1; } static int l_lovrGraphicsGetHeight(lua_State* L) { lua_pushnumber(L, lovrGraphicsGetHeight()); return 1; } static int l_lovrGraphicsGetDimensions(lua_State* L) { lua_pushnumber(L, lovrGraphicsGetWidth()); lua_pushnumber(L, lovrGraphicsGetHeight()); return 2; } static int l_lovrGraphicsGetPixelDensity(lua_State* L) { lua_pushnumber(L, lovrGraphicsGetPixelDensity()); return 1; } static int l_lovrGraphicsHasWindow(lua_State *L) { bool window = os_window_is_open(); lua_pushboolean(L, window); return 1; } static int l_lovrGraphicsGetViewPose(lua_State* L) { uint32_t view = luaL_checkinteger(L, 1) - 1; lovrAssert(view < 2, "Invalid view index %d", view + 1); if (lua_gettop(L) > 1) { float* matrix = luax_checkvector(L, 2, V_MAT4, NULL); bool invert = lua_toboolean(L, 3); lovrGraphicsGetViewMatrix(view, matrix); if (!invert) mat4_invert(matrix); lua_settop(L, 2); return 1; } else { float matrix[16], angle, ax, ay, az; lovrGraphicsGetViewMatrix(view, matrix); mat4_invert(matrix); mat4_getAngleAxis(matrix, &angle, &ax, &ay, &az); lua_pushnumber(L, matrix[12]); lua_pushnumber(L, matrix[13]); lua_pushnumber(L, matrix[14]); lua_pushnumber(L, angle); lua_pushnumber(L, ax); lua_pushnumber(L, ay); lua_pushnumber(L, az); return 7; } } static int l_lovrGraphicsSetViewPose(lua_State* L) { uint32_t view = luaL_checkinteger(L, 1) - 1; lovrAssert(view < 2, "Invalid view index %d", view + 1); VectorType t; float* m = luax_tovector(L, 2, &t); if (m && t == V_MAT4) { float matrix[16]; mat4_init(matrix, m); bool inverted = lua_toboolean(L, 3); if (!inverted) mat4_invert(matrix); lovrGraphicsSetViewMatrix(view, matrix); } else { int index = 2; float position[4], orientation[4], matrix[16]; index = luax_readvec3(L, index, position, "vec3, number, or mat4"); index = luax_readquat(L, index, orientation, NULL); mat4_fromQuat(matrix, orientation); memcpy(matrix + 12, position, 3 * sizeof(float)); mat4_invert(matrix); lovrGraphicsSetViewMatrix(view, matrix); } return 0; } static int l_lovrGraphicsGetProjection(lua_State* L) { uint32_t view = luaL_checkinteger(L, 1) - 1; lovrAssert(view < 2, "Invalid view index %d", view + 1); if (lua_gettop(L) > 1) { float* matrix = luax_checkvector(L, 2, V_MAT4, NULL); lovrGraphicsGetProjection(view, matrix); lua_settop(L, 2); return 1; } else { float matrix[16], left, right, up, down; lovrGraphicsGetProjection(view, matrix); mat4_getFov(matrix, &left, &right, &up, &down); lua_pushnumber(L, left); lua_pushnumber(L, right); lua_pushnumber(L, up); lua_pushnumber(L, down); return 4; } } static int l_lovrGraphicsSetProjection(lua_State* L) { uint32_t view = luaL_checkinteger(L, 1) - 1; lovrAssert(view < 2, "Invalid view index %d", view + 1); if (lua_type(L, 2) == LUA_TNUMBER) { float left = luax_checkfloat(L, 2); float right = luax_checkfloat(L, 3); float up = luax_checkfloat(L, 4); float down = luax_checkfloat(L, 5); float clipNear = luax_optfloat(L, 6, .1f); float clipFar = luax_optfloat(L, 7, 100.f); float matrix[16]; mat4_fov(matrix, left, right, up, down, clipNear, clipFar); lovrGraphicsSetProjection(view, matrix); } else { float* m = luax_checkvector(L, 2, V_MAT4, "mat4 or number"); lovrGraphicsSetProjection(view, m); } return 0; } static int l_lovrGraphicsTick(lua_State* L) { const char* label = luaL_checkstring(L, 1); lovrGraphicsTick(label); return 0; } static int l_lovrGraphicsTock(lua_State* L) { lovrGraphicsFlush(); const char* label = luaL_checkstring(L, 1); lua_pushnumber(L, lovrGraphicsTock(label)); return 1; } static int l_lovrGraphicsGetFeatures(lua_State* L) { const GpuFeatures* features = lovrGraphicsGetFeatures(); lua_newtable(L); lua_pushboolean(L, features->astc); lua_setfield(L, -2, "astc"); lua_pushboolean(L, features->compute); lua_setfield(L, -2, "compute"); lua_pushboolean(L, features->dxt); lua_setfield(L, -2, "dxt"); lua_pushboolean(L, features->instancedStereo); lua_setfield(L, -2, "instancedstereo"); lua_pushboolean(L, features->multiview); lua_setfield(L, -2, "multiview"); lua_pushboolean(L, features->timers); lua_setfield(L, -2, "timers"); return 1; } static int l_lovrGraphicsGetLimits(lua_State* L) { const GpuLimits* limits = lovrGraphicsGetLimits(); lua_newtable(L); lua_pushnumber(L, limits->pointSizes[1]); lua_setfield(L, -2, "pointsize"); lua_pushinteger(L, limits->textureSize); lua_setfield(L, -2, "texturesize"); lua_pushinteger(L, limits->textureMSAA); lua_setfield(L, -2, "texturemsaa"); lua_pushinteger(L, limits->textureAnisotropy); lua_setfield(L, -2, "anisotropy"); lua_pushinteger(L, limits->blockSize); lua_setfield(L, -2, "blocksize"); lua_createtable(L, 3, 0); lua_pushinteger(L, limits->compute[0]); lua_rawseti(L, -2, 1); lua_pushinteger(L, limits->compute[1]); lua_rawseti(L, -2, 2); lua_pushinteger(L, limits->compute[2]); lua_rawseti(L, -2, 3); lua_setfield(L, -2, "compute"); return 1; } static int l_lovrGraphicsGetStats(lua_State* L) { if (lua_gettop(L) > 0) { luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); } else { lua_createtable(L, 0, 7); } lovrGraphicsFlush(); const GpuStats* stats = lovrGraphicsGetStats(); lua_pushinteger(L, stats->shaderSwitches); lua_setfield(L, 1, "shaderswitches"); lua_pushinteger(L, stats->renderPasses); lua_setfield(L, 1, "renderpasses"); lua_pushinteger(L, stats->drawCalls); lua_setfield(L, 1, "drawcalls"); lua_pushinteger(L, stats->bufferCount); lua_setfield(L, 1, "buffers"); lua_pushinteger(L, stats->textureCount); lua_setfield(L, 1, "textures"); lua_pushinteger(L, stats->bufferMemory); lua_setfield(L, 1, "buffermemory"); lua_pushinteger(L, stats->textureMemory); lua_setfield(L, 1, "texturememory"); return 1; } // State static int l_lovrGraphicsReset(lua_State* L) { lovrGraphicsReset(); return 0; } static int l_lovrGraphicsGetAlphaSampling(lua_State* L) { lua_pushboolean(L, lovrGraphicsGetAlphaSampling()); return 1; } static int l_lovrGraphicsSetAlphaSampling(lua_State* L) { lovrGraphicsSetAlphaSampling(lua_toboolean(L, 1)); return 0; } static int l_lovrGraphicsGetBackgroundColor(lua_State* L) { Color color = lovrGraphicsGetBackgroundColor(); lua_pushnumber(L, color.r); lua_pushnumber(L, color.g); lua_pushnumber(L, color.b); lua_pushnumber(L, color.a); return 4; } static int l_lovrGraphicsSetBackgroundColor(lua_State* L) { Color color; luax_readcolor(L, 1, &color); lovrGraphicsSetBackgroundColor(color); return 0; } static int l_lovrGraphicsGetBlendMode(lua_State* L) { BlendMode mode; BlendAlphaMode alphaMode; lovrGraphicsGetBlendMode(&mode, &alphaMode); if (mode == BLEND_NONE) { lua_pushnil(L); return 1; } luax_pushenum(L, BlendMode, mode); luax_pushenum(L, BlendAlphaMode, alphaMode); return 2; } static int l_lovrGraphicsSetBlendMode(lua_State* L) { BlendMode mode = lua_isnoneornil(L, 1) ? BLEND_NONE : luax_checkenum(L, 1, BlendMode, NULL); BlendAlphaMode alphaMode = luax_checkenum(L, 2, BlendAlphaMode, "alphamultiply"); lovrGraphicsSetBlendMode(mode, alphaMode); return 0; } static int l_lovrGraphicsGetCanvas(lua_State* L) { Canvas* canvas = lovrGraphicsGetCanvas(); luax_pushtype(L, Canvas, canvas); return 1; } static int l_lovrGraphicsSetCanvas(lua_State* L) { Canvas* canvas = lua_isnoneornil(L, 1) ? NULL : luax_checktype(L, 1, Canvas); lovrGraphicsSetCanvas(canvas); return 0; } static int l_lovrGraphicsGetColor(lua_State* L) { Color color = lovrGraphicsGetColor(); lua_pushnumber(L, color.r); lua_pushnumber(L, color.g); lua_pushnumber(L, color.b); lua_pushnumber(L, color.a); return 4; } static int l_lovrGraphicsSetColor(lua_State* L) { Color color; luax_readcolor(L, 1, &color); lovrGraphicsSetColor(color); return 0; } static int l_lovrGraphicsGetColorMask(lua_State* L) { bool r, b, g, a; lovrGraphicsGetColorMask(&r, &g, &b, &a); lua_pushboolean(L, r); lua_pushboolean(L, g); lua_pushboolean(L, b); lua_pushboolean(L, a); return 4; } static int l_lovrGraphicsSetColorMask(lua_State* L) { bool r = lua_toboolean(L, 1); bool g = lua_toboolean(L, 2); bool b = lua_toboolean(L, 3); bool a = lua_toboolean(L, 4); lovrGraphicsSetColorMask(r, g, b, a); return 0; } static int l_lovrGraphicsIsCullingEnabled(lua_State* L) { lua_pushboolean(L, lovrGraphicsIsCullingEnabled()); return 1; } static int l_lovrGraphicsSetCullingEnabled(lua_State* L) { lovrGraphicsSetCullingEnabled(lua_toboolean(L, 1)); return 0; } static int l_lovrGraphicsGetDefaultFilter(lua_State* L) { TextureFilter filter = lovrGraphicsGetDefaultFilter(); luax_pushenum(L, FilterMode, filter.mode); lua_pushnumber(L, filter.anisotropy); return 2; } static int l_lovrGraphicsSetDefaultFilter(lua_State* L) { FilterMode mode = luax_checkenum(L, 1, FilterMode, NULL); float anisotropy = luax_optfloat(L, 2, 1.f); lovrGraphicsSetDefaultFilter((TextureFilter) { .mode = mode, .anisotropy = anisotropy }); return 0; } static int l_lovrGraphicsGetDepthTest(lua_State* L) { CompareMode mode; bool write; lovrGraphicsGetDepthTest(&mode, &write); luax_pushenum(L, CompareMode, mode); lua_pushboolean(L, write); return 2; } static int l_lovrGraphicsSetDepthTest(lua_State* L) { CompareMode mode = lua_isnoneornil(L, 1) ? COMPARE_NONE : luax_checkenum(L, 1, CompareMode, NULL); bool write = lua_isnoneornil(L, 2) ? true : lua_toboolean(L, 2); lovrGraphicsSetDepthTest(mode, write); return 0; } static int l_lovrGraphicsGetFont(lua_State* L) { Font* font = lovrGraphicsGetFont(); luax_pushtype(L, Font, font); return 1; } static int l_lovrGraphicsSetFont(lua_State* L) { Font* font = lua_isnoneornil(L, 1) ? NULL : luax_checktype(L, 1, Font); lovrGraphicsSetFont(font); return 0; } static int l_lovrGraphicsGetLineWidth(lua_State* L) { lua_pushnumber(L, lovrGraphicsGetLineWidth()); return 1; } static int l_lovrGraphicsSetLineWidth(lua_State* L) { uint8_t width = (uint8_t) luaL_optinteger(L, 1, 1); lovrGraphicsSetLineWidth(width); return 0; } static int l_lovrGraphicsGetPointSize(lua_State* L) { lua_pushnumber(L, lovrGraphicsGetPointSize()); return 1; } static int l_lovrGraphicsSetPointSize(lua_State* L) { float size = luax_optfloat(L, 1, 1.f); lovrGraphicsSetPointSize(size); return 0; } static int l_lovrGraphicsGetShader(lua_State* L) { Shader* shader = lovrGraphicsGetShader(); luax_pushtype(L, Shader, shader); return 1; } static int l_lovrGraphicsSetShader(lua_State* L) { Shader* shader = lua_isnoneornil(L, 1) ? NULL : luax_checktype(L, 1, Shader); lovrGraphicsSetShader(shader); return 0; } static int l_lovrGraphicsGetStencilTest(lua_State* L) { CompareMode mode; int value; lovrGraphicsGetStencilTest(&mode, &value); luax_pushenum(L, CompareMode, mode); lua_pushinteger(L, value); return 2; } static int l_lovrGraphicsSetStencilTest(lua_State* L) { if (lua_isnoneornil(L, 1)) { lovrGraphicsSetStencilTest(COMPARE_NONE, 0); } else { CompareMode mode = luax_checkenum(L, 1, CompareMode, NULL); int value = luaL_checkinteger(L, 2); lovrGraphicsSetStencilTest(mode, value); } return 0; } static int l_lovrGraphicsGetWinding(lua_State* L) { luax_pushenum(L, Winding, lovrGraphicsGetWinding()); return 1; } static int l_lovrGraphicsSetWinding(lua_State* L) { Winding winding = luax_checkenum(L, 1, Winding, NULL); lovrGraphicsSetWinding(winding); return 0; } static int l_lovrGraphicsIsWireframe(lua_State* L) { lua_pushboolean(L, lovrGraphicsIsWireframe()); return 1; } static int l_lovrGraphicsSetWireframe(lua_State* L) { lovrGraphicsSetWireframe(lua_toboolean(L, 1)); return 0; } // Transforms static int l_lovrGraphicsPush(lua_State* L) { lovrGraphicsPush(); return 0; } static int l_lovrGraphicsPop(lua_State* L) { lovrGraphicsPop(); return 0; } static int l_lovrGraphicsOrigin(lua_State* L) { lovrGraphicsOrigin(); return 0; } static int l_lovrGraphicsTranslate(lua_State* L) { float translation[4]; luax_readvec3(L, 1, translation, NULL); lovrGraphicsTranslate(translation); return 0; } static int l_lovrGraphicsRotate(lua_State* L) { float rotation[4]; luax_readquat(L, 1, rotation, NULL); lovrGraphicsRotate(rotation); return 0; } static int l_lovrGraphicsScale(lua_State* L) { float scale[4]; luax_readscale(L, 1, scale, 3, NULL); lovrGraphicsScale(scale); return 0; } static int l_lovrGraphicsTransform(lua_State* L) { float transform[16]; luax_readmat4(L, 1, transform, 3); lovrGraphicsMatrixTransform(transform); return 0; } // Rendering static int l_lovrGraphicsClear(lua_State* L) { int index = 1; int top = lua_gettop(L); bool clearColor = true; bool clearDepth = true; bool clearStencil = true; Color color = lovrGraphicsGetBackgroundColor(); float depth = 1.f; int stencil = 0; if (top == 1) { luax_readcolor(L, index, &color); lovrGraphicsClear(&color, NULL, NULL); return 0; } if (top >= index) { if (lua_type(L, index) == LUA_TNUMBER) { color.r = luax_checkfloat(L, index++); color.g = luax_checkfloat(L, index++); color.b = luax_checkfloat(L, index++); color.a = luax_optfloat(L, index++, 1.f); } else { clearColor = lua_toboolean(L, index++); } } if (top >= index) { if (lua_type(L, index) == LUA_TNUMBER) { depth = luax_checkfloat(L, index++); } else { clearDepth = lua_toboolean(L, index++); } } if (top >= index) { if (lua_type(L, index) == LUA_TNUMBER) { stencil = luaL_checkinteger(L, index++); } else { clearStencil = lua_toboolean(L, index++); } } lovrGraphicsClear(clearColor ? &color : NULL, clearDepth ? &depth : NULL, clearStencil ? &stencil : NULL); return 0; } static int l_lovrGraphicsDiscard(lua_State* L) { int top = lua_gettop(L); bool color = top >= 1 ? lua_toboolean(L, 1) : true; bool depth = top >= 2 ? lua_toboolean(L, 2) : true; bool stencil = top >= 3 ? lua_toboolean(L, 3) : true; lovrGraphicsDiscard(color, depth, stencil); return 0; } static int l_lovrGraphicsFlush(lua_State* L) { lovrGraphicsFlush(); return 0; } static int l_lovrGraphicsPoints(lua_State* L) { float* vertices; uint32_t count = luax_getvertexcount(L, 1); lovrGraphicsPoints(count, &vertices); luax_readvertices(L, 1, vertices, count); return 0; } static int l_lovrGraphicsLine(lua_State* L) { float* vertices; uint32_t count = luax_getvertexcount(L, 1); lovrAssert(count >= 2, "Need at least 2 points to draw a line"); lovrGraphicsLine(count, &vertices); luax_readvertices(L, 1, vertices, count); return 0; } static int l_lovrGraphicsPlane(lua_State* L) { DrawStyle style = STYLE_FILL; Material* material = NULL; if (lua_isuserdata(L, 1)) { material = luax_checktype(L, 1, Material); } else { style = luax_checkenum(L, 1, DrawStyle, NULL); } float transform[16]; int index = luax_readmat4(L, 2, transform, 2); float u = luax_optfloat(L, index++, 0.f); float v = luax_optfloat(L, index++, 0.f); float w = luax_optfloat(L, index++, 1.f - u); float h = luax_optfloat(L, index++, 1.f - v); lovrGraphicsPlane(style, material, transform, u, v, w, h); return 0; } static int luax_rectangularprism(lua_State* L, int scaleComponents) { DrawStyle style = STYLE_FILL; Material* material = NULL; if (lua_isuserdata(L, 1)) { material = luax_checktype(L, 1, Material); } else { style = luax_checkenum(L, 1, DrawStyle, NULL); } float transform[16]; luax_readmat4(L, 2, transform, scaleComponents); lovrGraphicsBox(style, material, transform); return 0; } static int l_lovrGraphicsCube(lua_State* L) { return luax_rectangularprism(L, 1); } static int l_lovrGraphicsBox(lua_State* L) { return luax_rectangularprism(L, 3); } static int l_lovrGraphicsArc(lua_State* L) { DrawStyle style = STYLE_FILL; Material* material = NULL; if (lua_isuserdata(L, 1)) { material = luax_checktype(L, 1, Material); } else { style = luax_checkenum(L, 1, DrawStyle, NULL); } ArcMode mode = ARC_MODE_PIE; int index = 2; if (lua_type(L, index) == LUA_TSTRING) { mode = luax_checkenum(L, index++, ArcMode, NULL); } float transform[16]; index = luax_readmat4(L, index, transform, 1); float r1 = luax_optfloat(L, index++, 0.f); float r2 = luax_optfloat(L, index++, 2.f * (float) M_PI); int segments = luaL_optinteger(L, index, 64) * (MIN(fabsf(r2 - r1), 2.f * (float) M_PI) / (2.f * (float) M_PI)); lovrGraphicsArc(style, mode, material, transform, r1, r2, segments); return 0; } static int l_lovrGraphicsCircle(lua_State* L) { DrawStyle style = STYLE_FILL; Material* material = NULL; if (lua_isuserdata(L, 1)) { material = luax_checktype(L, 1, Material); } else { style = luax_checkenum(L, 1, DrawStyle, NULL); } float transform[16]; int index = luax_readmat4(L, 2, transform, 1); int segments = luaL_optinteger(L, index, 32); lovrGraphicsCircle(style, material, transform, segments); return 0; } static int l_lovrGraphicsCylinder(lua_State* L) { float transform[16]; Material* material = luax_totype(L, 1, Material); int index = material ? 2 : 1; index = luax_readmat4(L, index, transform, 1); float r1 = luax_optfloat(L, index++, 1.f); float r2 = luax_optfloat(L, index++, 1.f); bool capped = lua_isnoneornil(L, index) ? true : lua_toboolean(L, index++); int segments = luaL_optinteger(L, index, (lua_Integer) floorf(16 + 16 * MAX(r1, r2))); lovrGraphicsCylinder(material, transform, r1, r2, capped, segments); return 0; } static int l_lovrGraphicsSphere(lua_State* L) { float transform[16]; Material* material = luax_totype(L, 1, Material); int index = material ? 2 : 1; index = luax_readmat4(L, index, transform, 1); int segments = luaL_optinteger(L, index, 30); lovrGraphicsSphere(material, transform, segments); return 0; } static int l_lovrGraphicsSkybox(lua_State* L) { Texture* texture = luax_checktype(L, 1, Texture); lovrGraphicsSkybox(texture); return 0; } static int l_lovrGraphicsPrint(lua_State* L) { size_t length; const char* str = luaL_checklstring(L, 1, &length); float transform[16]; int index = luax_readmat4(L, 2, transform, 1); float wrap = luax_optfloat(L, index++, 0.f); HorizontalAlign halign = luax_checkenum(L, index++, HorizontalAlign, "center"); VerticalAlign valign = luax_checkenum(L, index++, VerticalAlign, "middle"); lovrGraphicsPrint(str, length, transform, wrap, halign, valign); return 0; } static int l_lovrGraphicsStencil(lua_State* L) { luaL_checktype(L, 1, LUA_TFUNCTION); StencilAction action = luax_checkenum(L, 2, StencilAction, "replace"); int replaceValue = luaL_optinteger(L, 3, 1); bool keepValues = lua_toboolean(L, 4); if (!keepValues) { int clearTo = lua_isnumber(L, 4) ? lua_tonumber(L, 4) : 0; lovrGraphicsClear(NULL, NULL, &clearTo); } lua_settop(L, 1); bool r, g, b, a; lovrGraphicsGetColorMask(&r, &g, &b, &a); lovrGraphicsSetColorMask(false, false, false, false); lovrGraphicsStencil(action, replaceValue, stencilCallback, L); lovrGraphicsSetColorMask(r, g, b, a); return 0; } static int l_lovrGraphicsFill(lua_State* L) { Texture* texture = lua_isnoneornil(L, 1) ? NULL : luax_checktype(L, 1, Texture); float u = luax_optfloat(L, 2, 0.f); float v = luax_optfloat(L, 3, 0.f); float w = luax_optfloat(L, 4, 1.f - u); float h = luax_optfloat(L, 5, 1.f - v); lovrGraphicsFill(texture, u, v, w, h); return 0; } static int l_lovrGraphicsCompute(lua_State* L) { Shader* shader = luax_checktype(L, 1, Shader); int x = luaL_optinteger(L, 2, 1); int y = luaL_optinteger(L, 3, 1); int z = luaL_optinteger(L, 4, 1); lovrGraphicsCompute(shader, x, y, z); return 0; } // Types static void luax_checkuniformtype(lua_State* L, int index, UniformType* baseType, int* components) { size_t length; lovrAssert(lua_type(L, index) == LUA_TSTRING, "Uniform types must be strings, got %s", lua_typename(L, index)); const char* type = lua_tolstring(L, index, &length); if (!strcmp(type, "float")) { *baseType = UNIFORM_FLOAT; *components = 1; } else if (!strcmp(type, "int")) { *baseType = UNIFORM_INT; *components = 1; } else { int n = type[length - 1] - '0'; lovrAssert(n >= 2 && n <= 4, "Unknown uniform type '%s'", type); if (type[0] == 'v' && type[1] == 'e' && type[2] == 'c' && length == 4) { *baseType = UNIFORM_FLOAT; *components = n; } else if (type[0] == 'i' && type[1] == 'v' && type[2] == 'e' && type[3] == 'c' && length == 5) { *baseType = UNIFORM_INT; *components = n; } else if (type[0] == 'm' && type[1] == 'a' && type[2] == 't' && length == 4) { *baseType = UNIFORM_MATRIX; *components = n; } else { lovrThrow("Unknown uniform type '%s'", type); } } } static int l_lovrGraphicsNewCanvas(lua_State* L) { Attachment attachments[MAX_CANVAS_ATTACHMENTS]; int attachmentCount = 0; int width = 0; int height = 0; int index; if (luax_totype(L, 1, Texture)) { for (index = 1; index <= MAX_CANVAS_ATTACHMENTS; index++) { Texture* texture = luax_totype(L, index, Texture); if (!texture) break; attachments[attachmentCount++] = (Attachment) { texture, 0, 0 }; } } else if (lua_istable(L, 1)) { luax_readattachments(L, 1, attachments, &attachmentCount); index = 2; } else { width = luaL_checkinteger(L, 1); height = luaL_checkinteger(L, 2); index = 3; } TextureFormat format = FORMAT_RGBA; bool anonymous = attachmentCount == 0; CanvasFlags flags = { .depth = { .enabled = true, .readable = false, .format = FORMAT_D16 }, .stereo = anonymous, .msaa = 0, .mipmaps = true }; if (lua_istable(L, index)) { lua_getfield(L, index, "depth"); switch (lua_type(L, -1)) { case LUA_TNIL: break; case LUA_TBOOLEAN: flags.depth.enabled = lua_toboolean(L, -1); break; case LUA_TSTRING: flags.depth.format = luax_checkenum(L, -1, TextureFormat, NULL); break; case LUA_TTABLE: lua_getfield(L, -1, "readable"); flags.depth.readable = lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "format"); flags.depth.format = luax_checkenum(L, -1, TextureFormat, NULL); lua_pop(L, 1); break; default: lovrThrow("Expected boolean, string, or table for Canvas depth flag"); } lua_pop(L, 1); lua_getfield(L, index, "stereo"); flags.stereo = lua_isnil(L, -1) ? flags.stereo : lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, index, "msaa"); flags.msaa = lua_isnil(L, -1) ? flags.msaa : luaL_checkinteger(L, -1); lua_pop(L, 1); lua_getfield(L, index, "mipmaps"); flags.mipmaps = lua_isnil(L, -1) ? flags.mipmaps : lua_toboolean(L, -1); lua_pop(L, 1); if (attachmentCount == 0) { lua_getfield(L, index, "format"); format = luax_checkenum(L, -1, TextureFormat, "rgba"); anonymous = lua_isnil(L, -1) || lua_toboolean(L, -1); lua_pop(L, 1); } } if (width == 0 && height == 0 && attachmentCount > 0) { width = lovrTextureGetWidth(attachments[0].texture, attachments[0].level); height = lovrTextureGetHeight(attachments[0].texture, attachments[0].level); } Canvas* canvas = lovrCanvasCreate(width, height, flags); if (anonymous) { bool multiview = flags.stereo && lovrGraphicsGetFeatures()->multiview; TextureType textureType = multiview ? TEXTURE_ARRAY : TEXTURE_2D; uint32_t depth = multiview ? 2 : 1; Texture* texture = lovrTextureCreate(textureType, NULL, 0, true, flags.mipmaps, flags.msaa); lovrTextureAllocate(texture, lovrCanvasGetWidth(canvas), lovrCanvasGetHeight(canvas), depth, format); lovrTextureSetWrap(texture, (TextureWrap) { .s = WRAP_CLAMP, .t = WRAP_CLAMP, .r = WRAP_CLAMP }); attachments[0] = (Attachment) { texture, 0, 0 }; attachmentCount++; } if (attachmentCount > 0) { lovrCanvasSetAttachments(canvas, attachments, attachmentCount); if (anonymous) { lovrRelease(attachments[0].texture, lovrTextureDestroy); } } luax_pushtype(L, Canvas, canvas); lovrRelease(canvas, lovrCanvasDestroy); return 1; } static int l_lovrGraphicsNewFont(lua_State* L) { Rasterizer* rasterizer = luax_totype(L, 1, Rasterizer); uint32_t padding = 2; double spread = 4.; if (!rasterizer) { Blob* blob = NULL; float size; if (lua_type(L, 1) == LUA_TNUMBER || lua_isnoneornil(L, 1)) { size = luaL_optinteger(L, 1, 32); padding = luaL_optinteger(L, 2, padding); spread = luaL_optnumber(L, 3, spread); } else { blob = luax_readblob(L, 1, "Font"); size = luaL_optinteger(L, 2, 32); padding = luaL_optinteger(L, 3, padding); spread = luaL_optnumber(L, 4, spread); } rasterizer = lovrRasterizerCreate(blob, size); lovrRelease(blob, lovrBlobDestroy); } else { padding = luaL_optinteger(L, 2, padding); spread = luaL_optnumber(L, 3, spread); } Font* font = lovrFontCreate(rasterizer, padding, spread); luax_pushtype(L, Font, font); lovrRelease(rasterizer, lovrRasterizerDestroy); lovrRelease(font, lovrFontDestroy); return 1; } static int l_lovrGraphicsNewMaterial(lua_State* L) { Material* material = lovrMaterialCreate(); int index = 1; if (lua_type(L, index) == LUA_TSTRING) { Blob* blob = luax_readblob(L, index++, "Texture"); Image* image = lovrImageCreateFromBlob(blob, true); Texture* texture = lovrTextureCreate(TEXTURE_2D, &image, 1, true, true, 0); lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, texture); lovrRelease(blob, lovrBlobDestroy); lovrRelease(image, lovrImageDestroy); lovrRelease(texture, lovrTextureDestroy); } else if (lua_isuserdata(L, index)) { Texture* texture = luax_checktype(L, index, Texture); lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, texture); index++; } if (lua_isnumber(L, index)) { Color color; luax_readcolor(L, index, &color); lovrMaterialSetColor(material, COLOR_DIFFUSE, color); } luax_pushtype(L, Material, material); lovrRelease(material, lovrMaterialDestroy); return 1; } static int l_lovrGraphicsNewMesh(lua_State* L) { uint32_t count; int dataIndex = 0; int formatIndex = 0; int drawModeIndex = 2; Blob* blob = NULL; if (lua_isnumber(L, 1)) { count = lua_tointeger(L, 1); } else if (lua_istable(L, 1)) { if (lua_isnumber(L, 2)) { drawModeIndex++; formatIndex = 1; count = lua_tointeger(L, 2); dataIndex = 0; } else if (lua_istable(L, 2)) { drawModeIndex++; formatIndex = 1; count = luax_len(L, 2); dataIndex = 2; } else if ((blob = luax_totype(L, 2, Blob)) != NULL) { drawModeIndex++; formatIndex = 1; dataIndex = 2; } else { count = luax_len(L, 1); dataIndex = 1; } } else { luaL_argerror(L, 1, "table or number expected"); return 0; } MeshAttribute attributes[MAX_ATTRIBUTES]; const char* attributeNames[MAX_ATTRIBUTES]; int attributeCount = 0; size_t stride = 0; if (formatIndex == 0) { stride = 32; attributeCount = 3; attributes[0] = (MeshAttribute) { .offset = 0, .stride = stride, .type = F32, .components = 3 }; attributes[1] = (MeshAttribute) { .offset = 12, .stride = stride, .type = F32, .components = 3 }; attributes[2] = (MeshAttribute) { .offset = 24, .stride = stride, .type = F32, .components = 2 }; attributeNames[0] = "lovrPosition"; attributeNames[1] = "lovrNormal"; attributeNames[2] = "lovrTexCoord"; } else { attributeCount = luax_len(L, formatIndex); lovrAssert(attributeCount >= 0 && attributeCount <= MAX_ATTRIBUTES, "Attribute count must be between 0 and %d", MAX_ATTRIBUTES); for (int i = 0; i < attributeCount; i++) { lua_rawgeti(L, formatIndex, i + 1); lovrAssert(lua_type(L, -1) == LUA_TTABLE, "Attribute definitions must be tables containing name, type, and component count"); lua_rawgeti(L, -1, 3); lua_rawgeti(L, -2, 2); lua_rawgeti(L, -3, 1); attributeNames[i] = lua_tostring(L, -1); attributes[i].offset = (uint32_t) stride; attributes[i].type = luax_checkenum(L, -2, AttributeType, "float"); attributes[i].components = luaL_optinteger(L, -3, 1); switch (attributes[i].type) { case I8: case U8: stride += 1 * attributes[i].components; break; case I16: case U16: stride += 2 * attributes[i].components; break; case I32: case U32: case F32: stride += 4 * attributes[i].components; break; } lua_pop(L, 4); } } if (blob) { lovrAssert(blob->size / stride < UINT32_MAX, "Too many vertices in Blob"); count = (uint32_t) (blob->size / stride); } DrawMode mode = luax_checkenum(L, drawModeIndex, DrawMode, "fan"); BufferUsage usage = luax_checkenum(L, drawModeIndex + 1, BufferUsage, "dynamic"); bool readable = lua_toboolean(L, drawModeIndex + 2); Buffer* vertexBuffer = lovrBufferCreate(count * stride, NULL, BUFFER_VERTEX, usage, readable); Mesh* mesh = lovrMeshCreate(mode, vertexBuffer, count); for (int i = 0; i < attributeCount; i++) { lovrMeshAttachAttribute(mesh, attributeNames[i], &(MeshAttribute) { .buffer = vertexBuffer, .offset = attributes[i].offset, .stride = stride, .type = attributes[i].type, .components = attributes[i].components, .normalized = attributes[i].type == I8 || attributes[i].type == U8 }); } lovrMeshAttachAttribute(mesh, "lovrDrawID", &(MeshAttribute) { .buffer = lovrGraphicsGetIdentityBuffer(), .type = U8, .components = 1, .divisor = 1 }); if (dataIndex) { AttributeData data = { .raw = lovrBufferMap(vertexBuffer, 0, false) }; if (blob) { memcpy(data.raw, blob->data, count * stride); } else { for (uint32_t i = 0; i < count; i++) { lua_rawgeti(L, dataIndex, i + 1); lovrAssert(lua_istable(L, -1), "Vertices should be specified as a table of tables"); int component = 0; for (int j = 0; j < attributeCount; j++) { MeshAttribute* attribute = &attributes[j]; for (unsigned k = 0; k < attribute->components; k++) { lua_rawgeti(L, -1, ++component); switch (attribute->type) { case I8: *data.i8++ = luaL_optinteger(L, -1, 0); break; case U8: *data.u8++ = luaL_optinteger(L, -1, 0); break; case I16: *data.i16++ = luaL_optinteger(L, -1, 0); break; case U16: *data.u16++ = luaL_optinteger(L, -1, 0); break; case I32: *data.i32++ = luaL_optinteger(L, -1, 0); break; case U32: *data.u32++ = luaL_optinteger(L, -1, 0); break; case F32: *data.f32++ = luaL_optnumber(L, -1, 0.); break; } lua_pop(L, 1); } } lua_pop(L, 1); } } lovrBufferFlush(vertexBuffer, 0, count * stride); lovrBufferUnmap(vertexBuffer); } lovrRelease(vertexBuffer, lovrBufferDestroy); luax_pushtype(L, Mesh, mesh); lovrRelease(mesh, lovrMeshDestroy); return 1; } static int l_lovrGraphicsNewModel(lua_State* L) { ModelData* modelData = luax_totype(L, 1, ModelData); if (!modelData) { Blob* blob = luax_readblob(L, 1, "Model"); modelData = lovrModelDataCreate(blob, luax_readfile); lovrRelease(blob, lovrBlobDestroy); } else { lovrRetain(modelData); } Model* model = lovrModelCreate(modelData); luax_pushtype(L, Model, model); lovrRelease(modelData, lovrModelDataDestroy); lovrRelease(model, lovrModelDestroy); return 1; } static const char* luax_readshadersource(lua_State* L, int index, int *outLength) { if (lua_isnoneornil(L, index)) { return NULL; } Blob* blob = luax_totype(L, index, Blob); if (blob) { *outLength = (int) blob->size; return blob->data; } size_t length; const char* source = luaL_checklstring(L, index, &length); if (memchr(source, '\n', MIN(1024, length))) { *outLength = (int) length; return source; } else { void* contents = luax_readfile(source, &length); lovrAssert(contents, "Could not read shader from file '%s'", source); *outLength = (int) length; return contents; } } #define MAX_SHADER_FLAGS 32 static void luax_parseshaderflags(lua_State* L, int index, ShaderFlag flags[MAX_SHADER_FLAGS], uint32_t* count) { if (lua_isnil(L, -1)) { *count = 0; return; } lovrAssert(lua_istable(L, -1), "Shader flags must be a table"); lua_pushnil(L); while (lua_next(L, -2) != 0) { ShaderFlag* flag = &flags[(*count)++]; lovrAssert(*count <= MAX_SHADER_FLAGS, "Too many shader flags (max is %d)", MAX_SHADER_FLAGS); if (lua_type(L, -2) == LUA_TSTRING) { flag->name = lua_tostring(L, -2); } else if (lua_isnumber(L, -2)) { flag->index = lua_tointeger(L, -2); } else { lovrThrow("Shader flag names must be strings or numbers"); } switch (lua_type(L, -1)) { case LUA_TBOOLEAN: flag->type = FLAG_BOOL; flag->value.b32 = lua_toboolean(L, -1); break; case LUA_TNUMBER: flag->type = FLAG_INT; flag->value.i32 = lua_tointeger(L, -1); break; default: lovrThrow("Shader flag values must be booleans or integers"); } lua_pop(L, 1); } } static int l_lovrGraphicsNewShader(lua_State* L) { ShaderFlag flags[MAX_SHADER_FLAGS]; uint32_t flagCount = 0; bool multiview = true; Shader* shader; if (lua_isstring(L, 1) && (lua_istable(L, 2) || lua_gettop(L) == 1)) { DefaultShader shaderType = luax_checkenum(L, 1, DefaultShader, NULL); if (lua_istable(L, 2)) { lua_getfield(L, 2, "flags"); luax_parseshaderflags(L, -1, flags, &flagCount); lua_pop(L, 1); lua_getfield(L, 2, "stereo"); multiview = lua_isnil(L, -1) ? multiview : lua_toboolean(L, -1); lua_pop(L, 1); } shader = lovrShaderCreateDefault(shaderType, flags, flagCount, multiview); // Builtin uniforms if (shaderType == SHADER_STANDARD) { lovrShaderSetFloats(shader, "lovrExposure", (float[1]) { 1.f }, 0, 1); lovrShaderSetFloats(shader, "lovrLightDirection", (float[3]) { -1.f, -1.f, -1.f }, 0, 3); lovrShaderSetFloats(shader, "lovrLightColor", (float[4]) { 1.f, 1.f, 1.f, 1.f }, 0, 4); } } else { int vertexSourceLength; const char* vertexSource = luax_readshadersource(L, 1, &vertexSourceLength); int fragmentSourceLength; const char* fragmentSource = luax_readshadersource(L, 2, &fragmentSourceLength); if (lua_istable(L, 3)) { lua_getfield(L, 3, "flags"); luax_parseshaderflags(L, -1, flags, &flagCount); lua_pop(L, 1); lua_getfield(L, 3, "stereo"); multiview = lua_isnil(L, -1) ? multiview : lua_toboolean(L, -1); lua_pop(L, 1); } shader = lovrShaderCreateGraphics(vertexSource, vertexSourceLength, fragmentSource, fragmentSourceLength, flags, flagCount, multiview); } luax_pushtype(L, Shader, shader); lovrRelease(shader, lovrShaderDestroy); return 1; } static int l_lovrGraphicsNewComputeShader(lua_State* L) { int sourceLength; const char* source = luax_readshadersource(L, 1, &sourceLength); luaL_argcheck(L, source, 1, "string or Blob expected"); ShaderFlag flags[MAX_SHADER_FLAGS]; uint32_t flagCount = 0; if (lua_istable(L, 2)) { lua_getfield(L, 2, "flags"); luax_parseshaderflags(L, -1, flags, &flagCount); lua_pop(L, 1); } Shader* shader = lovrShaderCreateCompute(source, sourceLength, flags, flagCount); luax_pushtype(L, Shader, shader); lovrRelease(shader, lovrShaderDestroy); return 1; } static int l_lovrGraphicsNewShaderBlock(lua_State* L) { arr_uniform_t uniforms; arr_init(&uniforms, realloc); BlockType type = luax_checkenum(L, 1, BlockType, NULL); luaL_checktype(L, 2, LUA_TTABLE); lua_pushnil(L); while (lua_next(L, 2) != 0) { Uniform uniform; // Name strncpy(uniform.name, luaL_checkstring(L, -2), LOVR_MAX_UNIFORM_LENGTH - 1); if (lua_type(L, -1) == LUA_TSTRING) { uniform.count = 1; luax_checkuniformtype(L, -1, &uniform.type, &uniform.components); } else { luaL_checktype(L, -1, LUA_TTABLE); lua_rawgeti(L, -1, 1); luax_checkuniformtype(L, -1, &uniform.type, &uniform.components); lua_pop(L, 1); lua_rawgeti(L, -1, 2); uniform.count = luaL_optinteger(L, -1, 1); lua_pop(L, 1); } lovrAssert(uniform.count >= 1, "Uniform count must be positive, got %d for '%s'", uniform.count, uniform.name); arr_push(&uniforms, uniform); // Pop the table, leaving the key for lua_next to nom lua_pop(L, 1); } BufferUsage usage = USAGE_DYNAMIC; bool readable = false; if (lua_istable(L, 3)) { lua_getfield(L, 3, "usage"); usage = luax_checkenum(L, -1, BufferUsage, "dynamic"); lua_pop(L, 1); lua_getfield(L, 3, "readable"); readable = lua_toboolean(L, -1); lua_pop(L, 1); } lovrAssert(type == BLOCK_UNIFORM || lovrGraphicsGetFeatures()->compute, "Compute blocks are not supported on this system"); size_t size = lovrShaderComputeUniformLayout(&uniforms); Buffer* buffer = lovrBufferCreate(size, NULL, type == BLOCK_COMPUTE ? BUFFER_SHADER_STORAGE : BUFFER_UNIFORM, usage, readable); ShaderBlock* block = lovrShaderBlockCreate(type, buffer, &uniforms); luax_pushtype(L, ShaderBlock, block); arr_free(&uniforms); lovrRelease(buffer, lovrBufferDestroy); lovrRelease(block, lovrShaderBlockDestroy); return 1; } static int l_lovrGraphicsNewTexture(lua_State* L) { int index = 1; int width, height, depth; int argType = lua_type(L, index); bool blank = argType == LUA_TNUMBER; TextureType type = TEXTURE_2D; if (blank) { width = lua_tointeger(L, index++); height = luaL_checkinteger(L, index++); depth = lua_type(L, index) == LUA_TNUMBER ? lua_tonumber(L, index++) : 0; lovrAssert(width > 0 && height > 0, "A Texture must have a positive width, height, and depth"); } else if (argType != LUA_TTABLE) { luaL_checkany(L, 1); lua_createtable(L, 1, 0); lua_pushvalue(L, 1); lua_rawseti(L, -2, 1); lua_replace(L, 1); depth = 1; index++; } else { depth = luax_len(L, index++); type = depth > 0 ? TEXTURE_ARRAY : TEXTURE_CUBE; } bool hasFlags = lua_istable(L, index); bool srgb = !blank; bool mipmaps = true; TextureFormat format = FORMAT_RGBA; int msaa = 0; if (hasFlags) { lua_getfield(L, index, "linear"); srgb = lua_isnil(L, -1) ? srgb : !lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, index, "mipmaps"); mipmaps = lua_isnil(L, -1) ? mipmaps : lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, index, "type"); type = lua_isnil(L, -1) ? type : (TextureType) luax_checkenum(L, -1, TextureType, NULL); lua_pop(L, 1); lua_getfield(L, index, "format"); format = lua_isnil(L, -1) ? format : (TextureFormat) luax_checkenum(L, -1, TextureFormat, NULL); lua_pop(L, 1); lua_getfield(L, index, "msaa"); msaa = lua_isnil(L, -1) ? msaa : luaL_checkinteger(L, -1); lua_pop(L, 1); } Texture* texture = lovrTextureCreate(type, NULL, 0, srgb, mipmaps, msaa); lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter()); if (blank) { depth = depth ? depth : (type == TEXTURE_CUBE ? 6 : 1); lovrTextureAllocate(texture, width, height, depth, format); } else { if (type == TEXTURE_CUBE && depth == 0) { depth = 6; const char* faces[6] = { "right", "left", "top", "bottom", "back", "front" }; for (int i = 0; i < 6; i++) { lua_pushstring(L, faces[i]); lua_rawget(L, 1); lovrAssert(!lua_isnil(L, -1), "Could not load cubemap texture: missing '%s' face", faces[i]); lua_rawseti(L, 1, i + 1); } } for (int i = 0; i < depth; i++) { lua_rawgeti(L, 1, i + 1); Image* image = luax_checkimage(L, -1, type != TEXTURE_CUBE); if (i == 0) { lovrTextureAllocate(texture, image->width, image->height, depth, image->format); } lovrTextureReplacePixels(texture, image, 0, 0, i, 0); lovrRelease(image, lovrImageDestroy); lua_pop(L, 1); } } luax_pushtype(L, Texture, texture); lovrRelease(texture, lovrTextureDestroy); return 1; } static const luaL_Reg lovrGraphics[] = { // Base { "present", l_lovrGraphicsPresent }, { "createWindow", l_lovrGraphicsCreateWindow }, { "getWidth", l_lovrGraphicsGetWidth }, { "getHeight", l_lovrGraphicsGetHeight }, { "getDimensions", l_lovrGraphicsGetDimensions }, { "getPixelDensity", l_lovrGraphicsGetPixelDensity }, { "hasWindow", l_lovrGraphicsHasWindow }, { "getViewPose", l_lovrGraphicsGetViewPose }, { "setViewPose", l_lovrGraphicsSetViewPose }, { "getProjection", l_lovrGraphicsGetProjection }, { "setProjection", l_lovrGraphicsSetProjection }, { "tick", l_lovrGraphicsTick }, { "tock", l_lovrGraphicsTock }, { "getFeatures", l_lovrGraphicsGetFeatures }, { "getLimits", l_lovrGraphicsGetLimits }, { "getStats", l_lovrGraphicsGetStats }, // State { "reset", l_lovrGraphicsReset }, { "getAlphaSampling", l_lovrGraphicsGetAlphaSampling }, { "setAlphaSampling", l_lovrGraphicsSetAlphaSampling }, { "getBackgroundColor", l_lovrGraphicsGetBackgroundColor }, { "setBackgroundColor", l_lovrGraphicsSetBackgroundColor }, { "getBlendMode", l_lovrGraphicsGetBlendMode }, { "setBlendMode", l_lovrGraphicsSetBlendMode }, { "getCanvas", l_lovrGraphicsGetCanvas }, { "setCanvas", l_lovrGraphicsSetCanvas }, { "getColor", l_lovrGraphicsGetColor }, { "setColor", l_lovrGraphicsSetColor }, { "getColorMask", l_lovrGraphicsGetColorMask }, { "setColorMask", l_lovrGraphicsSetColorMask }, { "isCullingEnabled", l_lovrGraphicsIsCullingEnabled }, { "setCullingEnabled", l_lovrGraphicsSetCullingEnabled }, { "getDefaultFilter", l_lovrGraphicsGetDefaultFilter }, { "setDefaultFilter", l_lovrGraphicsSetDefaultFilter }, { "getDepthTest", l_lovrGraphicsGetDepthTest }, { "setDepthTest", l_lovrGraphicsSetDepthTest }, { "getFont", l_lovrGraphicsGetFont }, { "setFont", l_lovrGraphicsSetFont }, { "getLineWidth", l_lovrGraphicsGetLineWidth }, { "setLineWidth", l_lovrGraphicsSetLineWidth }, { "getPointSize", l_lovrGraphicsGetPointSize }, { "setPointSize", l_lovrGraphicsSetPointSize }, { "getShader", l_lovrGraphicsGetShader }, { "setShader", l_lovrGraphicsSetShader }, { "getStencilTest", l_lovrGraphicsGetStencilTest }, { "setStencilTest", l_lovrGraphicsSetStencilTest }, { "getWinding", l_lovrGraphicsGetWinding }, { "setWinding", l_lovrGraphicsSetWinding }, { "isWireframe", l_lovrGraphicsIsWireframe }, { "setWireframe", l_lovrGraphicsSetWireframe }, // Transforms { "push", l_lovrGraphicsPush }, { "pop", l_lovrGraphicsPop }, { "origin", l_lovrGraphicsOrigin }, { "translate", l_lovrGraphicsTranslate }, { "rotate", l_lovrGraphicsRotate }, { "scale", l_lovrGraphicsScale }, { "transform", l_lovrGraphicsTransform }, // Rendering { "clear", l_lovrGraphicsClear }, { "discard", l_lovrGraphicsDiscard }, { "flush", l_lovrGraphicsFlush }, { "points", l_lovrGraphicsPoints }, { "line", l_lovrGraphicsLine }, { "plane", l_lovrGraphicsPlane }, { "cube", l_lovrGraphicsCube }, { "box", l_lovrGraphicsBox }, { "arc", l_lovrGraphicsArc }, { "circle", l_lovrGraphicsCircle }, { "cylinder", l_lovrGraphicsCylinder }, { "sphere", l_lovrGraphicsSphere }, { "skybox", l_lovrGraphicsSkybox }, { "print", l_lovrGraphicsPrint }, { "stencil", l_lovrGraphicsStencil }, { "fill", l_lovrGraphicsFill }, { "compute", l_lovrGraphicsCompute }, // Types { "newCanvas", l_lovrGraphicsNewCanvas }, { "newFont", l_lovrGraphicsNewFont }, { "newMaterial", l_lovrGraphicsNewMaterial }, { "newMesh", l_lovrGraphicsNewMesh }, { "newModel", l_lovrGraphicsNewModel }, { "newShader", l_lovrGraphicsNewShader }, { "newComputeShader", l_lovrGraphicsNewComputeShader }, { "newShaderBlock", l_lovrGraphicsNewShaderBlock }, { "newTexture", l_lovrGraphicsNewTexture }, { NULL, NULL } }; extern const luaL_Reg lovrCanvas[]; extern const luaL_Reg lovrFont[]; extern const luaL_Reg lovrMaterial[]; extern const luaL_Reg lovrMesh[]; extern const luaL_Reg lovrModel[]; extern const luaL_Reg lovrShader[]; extern const luaL_Reg lovrShaderBlock[]; extern const luaL_Reg lovrTexture[]; int luaopen_lovr_graphics(lua_State* L) { lua_newtable(L); luax_register(L, lovrGraphics); luax_registertype(L, Canvas); luax_registertype(L, Font); luax_registertype(L, Material); luax_registertype(L, Mesh); luax_registertype(L, Model); luax_registertype(L, Shader); luax_registertype(L, ShaderBlock); luax_registertype(L, Texture); luax_pushconf(L); bool debug = false; lua_getfield(L, -1, "graphics"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "debug"); debug = lua_toboolean(L, -1); lua_pop(L, 1); } lua_pop(L, 1); lovrGraphicsInit(debug); lua_pushcfunction(L, l_lovrGraphicsCreateWindow); lua_getfield(L, -2, "window"); lua_call(L, 1, 0); lua_pop(L, 1); return 1; }