From 22c8daa5c01fade41ff9d70c1ada9a7be0810d5c Mon Sep 17 00:00:00 2001 From: bjorn Date: Fri, 31 Jul 2026 11:24:24 -0700 Subject: [PATCH 1/8] Mutable materials; --- CHANGES.md | 1 + etc/shaders/lovr.glsl | 13 +- src/api/api.h | 3 + src/api/l_graphics.c | 206 +++++++------ src/api/l_graphics_material.c | 150 +++++++-- src/api/l_graphics_model.c | 13 +- src/core/spv.c | 2 +- src/modules/graphics/graphics.c | 529 ++++++++++++++++++++------------ src/modules/graphics/graphics.h | 66 ++-- 9 files changed, 620 insertions(+), 363 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6c80c02b9a..bc403c6361 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +20,7 @@ dev - Add `sleep` callback to `World:setCallbacks`. - Add `linearDamping` and `angularDamping` settings to `lovr.physics.newWorld`. - Add `lovr.headset.getHandPosition/Orientation/Pose`. +- Add `Material:get/setNumber`, `Material:get/setColor`, `Material:get/setTexture`, and `Material:get/setQuad`. ### Change diff --git a/etc/shaders/lovr.glsl b/etc/shaders/lovr.glsl index bd4200f756..485bd505d3 100644 --- a/etc/shaders/lovr.glsl +++ b/etc/shaders/lovr.glsl @@ -38,11 +38,6 @@ layout(set = 0, binding = 2) uniform DrawBuffer { layout(row_major) Draw Draws[2 layout(set = 0, binding = 3) uniform sampler Sampler; struct MaterialData { - vec4 color; - vec4 glow; - vec2 uvShift; - vec2 uvScale; - vec2 sdfRange; float metalness; float roughness; float clearcoat; @@ -50,6 +45,10 @@ struct MaterialData { float occlusionStrength; float normalScale; float alphaCutoff; + vec4 color; + vec4 glow; + vec4 quad; + vec2 sdfRange; }; layout(set = 1, binding = 0) uniform MaterialBuffer { @@ -558,8 +557,8 @@ void main() { #endif if (flag_uvTransform) { - UV *= Material.uvScale; - UV += Material.uvShift; + UV *= Material.quad.zw; + UV += Material.quad.xy; } } #endif diff --git a/src/api/api.h b/src/api/api.h index 92b123d0d2..3a355cc8a6 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -51,6 +51,9 @@ extern StringEntry lovrHorizontalAlign[]; extern StringEntry lovrJointType[]; extern StringEntry lovrKeyboardKey[]; extern StringEntry lovrLayerType[]; +extern StringEntry lovrMaterialColor[]; +extern StringEntry lovrMaterialNumber[]; +extern StringEntry lovrMaterialTexture[]; extern StringEntry lovrMeshStorage[]; extern StringEntry lovrModelDrawMode[]; extern StringEntry lovrMotorMode[]; diff --git a/src/api/l_graphics.c b/src/api/l_graphics.c index 698241ab95..60594103cb 100644 --- a/src/api/l_graphics.c +++ b/src/api/l_graphics.c @@ -163,6 +163,34 @@ StringEntry lovrHorizontalAlign[] = { { 0 } }; +StringEntry lovrMaterialColor[] = { + [COLOR_BASE] = ENTRY("base"), + [COLOR_GLOW] = ENTRY("glow"), + { 0 } +}; + +StringEntry lovrMaterialNumber[] = { + [NUMBER_METALNESS] = ENTRY("metalness"), + [NUMBER_ROUGHNESS] = ENTRY("roughness"), + [NUMBER_CLEARCOAT] = ENTRY("clearcoat"), + [NUMBER_CLEARCOAT_ROUGHNESS] = ENTRY("clearcoatroughness"), + [NUMBER_OCCLUSION_STRENGTH] = ENTRY("occlusionstrength"), + [NUMBER_NORMAL_SCALE] = ENTRY("normalscale"), + [NUMBER_ALPHA_CUTOFF] = ENTRY("alphacutoff"), + { 0 } +}; + +StringEntry lovrMaterialTexture[] = { + [TEXTURE_COLOR] = ENTRY("color"), + [TEXTURE_GLOW] = ENTRY("glow"), + [TEXTURE_METALNESS] = ENTRY("metalness"), + [TEXTURE_ROUGHNESS] = ENTRY("roughness"), + [TEXTURE_CLEARCOAT] = ENTRY("clearcoat"), + [TEXTURE_OCCLUSION] = ENTRY("occlusion"), + [TEXTURE_NORMAL] = ENTRY("normal"), + { 0 } +}; + StringEntry lovrMeshStorage[] = { [MESH_CPU] = ENTRY("cpu"), [MESH_GPU] = ENTRY("gpu"), @@ -1459,112 +1487,98 @@ static Texture* luax_opttexture(lua_State* L, int index) { } 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); - } - 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); - } - 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); + Material* material = lovrMaterialCreate(luax_totype(L, 1, Texture)); + luax_assert(L, material); - lua_getfield(L, 1, "normalScale"); - info.data.normalScale = luax_optfloat(L, -1, 1.f); - lua_pop(L, 1); + if (lua_istable(L, 1)) { + float value, color[4]; - lua_getfield(L, 1, "alphaCutoff"); - info.data.alphaCutoff = luax_optfloat(L, -1, 0.f); - lua_pop(L, 1); + for (uint32_t i = 0; i < NUMBER_COUNT; i++) { + lua_pushlstring(L, lovrMaterialNumber[i].string, lovrMaterialNumber[i].length); + lua_gettable(L, 1); + if (!lua_isnil(L, -1)) lovrMaterialSetNumber(material, i, luax_checkfloat(L, -1)); + lua_pop(L, 1); + } - lua_getfield(L, 1, "texture"); - info.texture = luax_opttexture(L, -1); - lua_pop(L, 1); + for (uint32_t i = 0; i < COLOR_COUNT; i++) { + if (i == 0) { + lua_pushliteral(L, "color"); + } else { + lua_pushlstring(L, lovrMaterialColor[i].string, lovrMaterialColor[i].length); + } - lua_getfield(L, 1, "glowTexture"); - info.glowTexture = luax_opttexture(L, -1); - lua_pop(L, 1); + lua_gettable(L, 1); + if (!lua_isnil(L, -1)) { + luax_optcolor(L, -1, color); + lovrMaterialSetColor(material, i, color); + } + lua_pop(L, 1); + } - lua_getfield(L, 1, "metalnessTexture"); - info.metalnessTexture = luax_opttexture(L, -1); - lua_pop(L, 1); + for (uint32_t i = 0; i < TEXTURE_COUNT; i++) { + if (i == 0) { + lua_pushliteral(L, "texture"); + } else { + char key[64]; + size_t length = lovrMaterialTexture[i].length; + memcpy(key, lovrMaterialTexture[i].string, length); + memcpy(key + length, "Texture", strlen("Texture")); + lua_pushlstring(L, key, length + strlen("Texture")); + } + lua_gettable(L, 1); + if (!lua_isnil(L, -1)) { + Texture* texture = luax_checktype(L, -1, Texture); + if (!lovrMaterialSetTexture(material, i, texture)) { + lovrRelease(material, lovrMaterialDestroy); + luax_throw(L); + } + } + lua_pop(L, 1); + } - lua_getfield(L, 1, "roughnessTexture"); - info.roughnessTexture = luax_opttexture(L, -1); - lua_pop(L, 1); + float quad[4]; - lua_getfield(L, 1, "clearcoatTexture"); - info.clearcoatTexture = luax_opttexture(L, -1); - lua_pop(L, 1); + // Deprecated + lua_getfield(L, 1, "uvShift"); + if (lua_istable(L, -1)) { + lua_rawgeti(L, -1, 1); + lua_rawgeti(L, -2, 2); + quad[0] = luax_optfloat(L, -2, 0.f); + quad[1] = luax_optfloat(L, -1, 0.f); + lua_pop(L, 2); + } else if (lua_type(L, -1) == LUA_TNUMBER) { + quad[0] = quad[1] = lua_tonumber(L, -1); + } + lua_pop(L, 1); - lua_getfield(L, 1, "occlusionTexture"); - info.occlusionTexture = luax_opttexture(L, -1); - lua_pop(L, 1); + // Deprecated + lua_getfield(L, 1, "uvScale"); + if (lua_istable(L, -1)) { + lua_rawgeti(L, -1, 1); + lua_rawgeti(L, -2, 2); + quad[2] = luax_optfloat(L, -2, 1.f); + quad[3] = luax_optfloat(L, -1, 1.f); + lua_pop(L, 2); + } else if (lua_type(L, -1) == LUA_TNUMBER) { + quad[2] = quad[3] = lua_tonumber(L, -1); + } + lua_pop(L, 1); - lua_getfield(L, 1, "normalTexture"); - info.normalTexture = luax_opttexture(L, -1); - lua_pop(L, 1); + lua_getfield(L, 1, "quad"); + if (lua_istable(L, -1)) { + lua_rawgeti(L, -1, 1); + lua_rawgeti(L, -2, 2); + lua_rawgeti(L, -3, 3); + lua_rawgeti(L, -4, 4); + quad[0] = luax_optfloat(L, -4, 0.f); + quad[1] = luax_optfloat(L, -3, 0.f); + quad[2] = luax_optfloat(L, -2, 1.f); + quad[3] = luax_optfloat(L, -1, 1.f); + lua_pop(L, 4); + } + lua_pop(L, 1); + } - Material* material = lovrMaterialCreate(&info); - luax_assert(L, material); luax_pushtype(L, Material, material); lovrRelease(material, lovrMaterialDestroy); return 1; diff --git a/src/api/l_graphics_material.c b/src/api/l_graphics_material.c index a3d2a4d6f1..be0bea126e 100644 --- a/src/api/l_graphics_material.c +++ b/src/api/l_graphics_material.c @@ -17,66 +17,162 @@ Material* luax_optmaterial(lua_State* L, int index) { } } +static int l_lovrMaterialGetNumber(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + MaterialNumber key = luax_checkenum(L, 2, MaterialNumber, NULL); + float number = lovrMaterialGetNumber(material, key); + lua_pushnumber(L, number); + return 1; +} + +static int l_lovrMaterialSetNumber(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + MaterialNumber key = luax_checkenum(L, 2, MaterialNumber, NULL); + float number = luax_checkfloat(L, 3); + lovrMaterialSetNumber(material, key, number); + return 0; +} + +static int l_lovrMaterialGetColor(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + MaterialColor key = luax_checkenum(L, 2, MaterialColor, "base"); + const float* color = lovrMaterialGetColor(material, key); + 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_lovrMaterialSetColor(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + int index = 2; + float color[4]; + MaterialColor key = lua_type(L, index) == LUA_TSTRING ? luax_checkenum(L, index++, MaterialColor, NULL) : COLOR_BASE; + luax_readcolor(L, index, color); + lovrMaterialSetColor(material, key, color); + return 0; +} + +static int l_lovrMaterialGetTexture(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + MaterialTexture key = luax_checkenum(L, 2, MaterialTexture, "color"); + Texture* texture = lovrMaterialGetTexture(material, key); + luax_pushtype(L, Texture, texture); + return 1; +} + +static int l_lovrMaterialSetTexture(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + int index = 2; + MaterialTexture key = lua_type(L, index) == LUA_TSTRING ? luax_checkenum(L, index++, MaterialTexture, NULL) : TEXTURE_COLOR; + Texture* texture = luax_checktype(L, index, Texture); + lovrMaterialSetTexture(material, key, texture); + return 0; +} + +static int l_lovrMaterialGetQuad(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + float ox, oy, sx, sy; + lovrMaterialGetQuad(material, &ox, &oy, &sx, &sy); + lua_pushnumber(L, ox); + lua_pushnumber(L, oy); + lua_pushnumber(L, sx); + lua_pushnumber(L, sy); + return 4; +} + +static int l_lovrMaterialSetQuad(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + float ox = luax_checkfloat(L, 2); + float oy = luax_checkfloat(L, 3); + float sx = luax_checkfloat(L, 4); + float sy = luax_checkfloat(L, 5); + lovrMaterialSetQuad(material, ox, oy, sx, sy); + return 0; +} + +// Deprecated static int l_lovrMaterialGetProperties(lua_State* L) { Material* material = luax_checktype(L, 1, Material); - const MaterialInfo* info = lovrMaterialGetInfo(material); + lua_newtable(L); + lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_METALNESS)), lua_setfield(L, -2, "metalness"); + lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_ROUGHNESS)), lua_setfield(L, -2, "roughness"); + lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_CLEARCOAT)), lua_setfield(L, -2, "clearcoat"); + lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_CLEARCOAT_ROUGHNESS)), lua_setfield(L, -2, "clearcoatRoughness"); + lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_OCCLUSION_STRENGTH)), lua_setfield(L, -2, "occlusionStrength"); + lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_NORMAL_SCALE)), lua_setfield(L, -2, "normalScale"); + lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_ALPHA_CUTOFF)), lua_setfield(L, -2, "alphaCutoff"); + + const float* color; + color = lovrMaterialGetColor(material, COLOR_BASE); + lua_createtable(L, 4, 0); - lua_pushnumber(L, info->data.color[0]); + lua_pushnumber(L, color[0]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, info->data.color[1]); + lua_pushnumber(L, color[1]); lua_rawseti(L, -2, 2); - lua_pushnumber(L, info->data.color[2]); + lua_pushnumber(L, color[2]); lua_rawseti(L, -2, 3); - lua_pushnumber(L, info->data.color[3]); + lua_pushnumber(L, color[3]); lua_rawseti(L, -2, 4); lua_setfield(L, -2, "color"); + color = lovrMaterialGetColor(material, COLOR_GLOW); + lua_createtable(L, 4, 0); - lua_pushnumber(L, info->data.glow[0]); + lua_pushnumber(L, color[0]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, info->data.glow[1]); + lua_pushnumber(L, color[1]); lua_rawseti(L, -2, 2); - lua_pushnumber(L, info->data.glow[2]); + lua_pushnumber(L, color[2]); lua_rawseti(L, -2, 3); - lua_pushnumber(L, info->data.glow[3]); + lua_pushnumber(L, color[3]); lua_rawseti(L, -2, 4); lua_setfield(L, -2, "glow"); + float ox, oy, sx, sy; + lovrMaterialGetQuad(material, &ox, &oy, &sx, &sy); + lua_createtable(L, 2, 0); - lua_pushnumber(L, info->data.uvShift[0]); + lua_pushnumber(L, ox); lua_rawseti(L, -2, 1); - lua_pushnumber(L, info->data.uvShift[1]); + lua_pushnumber(L, oy); lua_rawseti(L, -2, 2); lua_setfield(L, -2, "uvShift"); lua_createtable(L, 2, 0); - lua_pushnumber(L, info->data.uvScale[0]); + lua_pushnumber(L, sx); lua_rawseti(L, -2, 1); - lua_pushnumber(L, info->data.uvScale[1]); + lua_pushnumber(L, sy); lua_rawseti(L, -2, 2); lua_setfield(L, -2, "uvScale"); - lua_pushnumber(L, info->data.metalness), lua_setfield(L, -2, "metalness"); - lua_pushnumber(L, info->data.roughness), lua_setfield(L, -2, "roughness"); - lua_pushnumber(L, info->data.clearcoat), lua_setfield(L, -2, "clearcoat"); - lua_pushnumber(L, info->data.clearcoatRoughness), lua_setfield(L, -2, "clearcoatRoughness"); - lua_pushnumber(L, info->data.occlusionStrength), lua_setfield(L, -2, "occlusionStrength"); - lua_pushnumber(L, info->data.normalScale), lua_setfield(L, -2, "normalScale"); - lua_pushnumber(L, info->data.alphaCutoff), lua_setfield(L, -2, "alphaCutoff"); - luax_pushtype(L, Texture, info->texture), lua_setfield(L, -2, "texture"); - luax_pushtype(L, Texture, info->glowTexture), lua_setfield(L, -2, "glowTexture"); - luax_pushtype(L, Texture, info->metalnessTexture), lua_setfield(L, -2, "metalnessTexture"); - luax_pushtype(L, Texture, info->roughnessTexture), lua_setfield(L, -2, "roughnessTexture"); - luax_pushtype(L, Texture, info->clearcoatTexture), lua_setfield(L, -2, "clearcoatTexture"); - luax_pushtype(L, Texture, info->occlusionTexture), lua_setfield(L, -2, "occlusionTexture"); - luax_pushtype(L, Texture, info->normalTexture), lua_setfield(L, -2, "normalTexture"); + luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_COLOR)), lua_setfield(L, -2, "texture"); + luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_GLOW)), lua_setfield(L, -2, "glowTexture"); + luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_METALNESS)), lua_setfield(L, -2, "metalnessTexture"); + luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_ROUGHNESS)), lua_setfield(L, -2, "roughnessTexture"); + luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_CLEARCOAT)), lua_setfield(L, -2, "clearcoatTexture"); + luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_OCCLUSION)), lua_setfield(L, -2, "occlusionTexture"); + luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_NORMAL)), lua_setfield(L, -2, "normalTexture"); return 1; } const luaL_Reg lovrMaterial[] = { + { "getNumber", l_lovrMaterialGetNumber }, + { "setNumber", l_lovrMaterialSetNumber }, + { "getColor", l_lovrMaterialGetColor }, + { "setColor", l_lovrMaterialSetColor }, + { "getTexture", l_lovrMaterialGetTexture }, + { "setTexture", l_lovrMaterialSetTexture }, + { "getQuad", l_lovrMaterialGetQuad }, + { "setQuad", l_lovrMaterialSetQuad }, + + // Deprecated { "getProperties", l_lovrMaterialGetProperties }, + { NULL, NULL } }; diff --git a/src/api/l_graphics_model.c b/src/api/l_graphics_model.c index 0a118cd726..63e73d3d0f 100644 --- a/src/api/l_graphics_model.c +++ b/src/api/l_graphics_model.c @@ -247,12 +247,20 @@ static int l_lovrModelGetTexture(lua_State* L) { static int l_lovrModelGetMaterial(lua_State* L) { Model* model = luax_checktype(L, 1, Model); uint32_t index = luax_checkmaterialindex(L, 2, lovrModelGetMetadata(model)); - Material* material = lovrModelGetMaterial(model, index); - luax_assert(L, material); + Material* material; + luax_assert(L, lovrModelGetMaterial(model, index, &material)); luax_pushtype(L, Material, material); return 1; } +static int l_lovrModelSetMaterial(lua_State* L) { + Model* model = luax_checktype(L, 1, Model); + uint32_t index = luax_checkmaterialindex(L, 2, lovrModelGetMetadata(model)); + Material* material = luax_optmaterial(L, 3); + luax_assert(L, lovrModelSetMaterial(model, index, material)); + return 0; +} + static int l_lovrModelBuildRaytracer(lua_State* L) { Model* model = luax_checktype(L, 1, Model); luax_assert(L, lovrModelBuildRaytracer(model)); @@ -402,6 +410,7 @@ const luaL_Reg lovrModel[] = { { "getMaterialCount", l_lovrModelMetaGetMaterialCount }, { "getMaterialName", l_lovrModelMetaGetMaterialName }, { "getMaterial", l_lovrModelGetMaterial }, + { "setMaterial", l_lovrModelSetMaterial }, { "buildRaytracer", l_lovrModelBuildRaytracer }, diff --git a/src/core/spv.c b/src/core/spv.c index c8bc55d9b9..9e820bd7dd 100644 --- a/src/core/spv.c +++ b/src/core/spv.c @@ -173,7 +173,7 @@ const char* spv_result_to_string(spv_result result) { case SPV_INVALID: return "Invalid SPIR-V"; case SPV_TOO_BIG: return "SPIR-V contains too many types/variables (max ID is 65534)"; case SPV_UNSUPPORTED_SPEC_CONSTANT_TYPE: return "This type of specialization constant is not supported"; - case SPV_UNSUPPORTED_DATA_TYPE: return "Struct fields must be square float matrices, float/int/uint vectors, 32 bit numbers, or bools"; + case SPV_UNSUPPORTED_DATA_TYPE: return "Struct fields must be square float matrices, float/int/uint vectors, 8, 16, or 32 bit numbers, or bools"; default: return NULL; } } diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 899c79caa5..ba7075425b 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -29,10 +29,12 @@ #define MAX_PIPELINES 8192 #define MAX_TALLIES 255 +#define MATERIAL_BLOCK_SIZE 256 #define TRANSFORM_STACK_SIZE 16 #define PIPELINE_STACK_SIZE 8 #define MAX_SHADER_RESOURCES 32 #define MAX_CUSTOM_ATTRIBUTES 10 +#define MATERIAL_STRIDE ALIGN(sizeof(MaterialData), state.limits.uniformBufferAlign) #define FLOAT_BITS(f) ((union { float f; uint32_t u; }) { f }).u typedef struct { @@ -176,26 +178,41 @@ struct Shader { char* names; }; +typedef struct { + float numbers[NUMBER_COUNT]; + float padding; + float colors[COLOR_COUNT][4]; + float quad[4]; + float sdfRange[2]; +} MaterialData; + +typedef struct { + uint32_t next; + uint32_t tick; +} MaterialNode; + typedef struct { void* next; - void* pointer; - Material* materials; + uint32_t head; + uint32_t tail; + MaterialNode nodes[MATERIAL_BLOCK_SIZE]; gpu_buffer* buffer; + void* bufferPointer; gpu_bundle_pool* bundlePool; gpu_bundle* bundles; - uint32_t head; - uint32_t tail; } MaterialBlock; struct Material { atomic_uint ref; - uint32_t next; - uint32_t tick; uint32_t index; - MaterialInfo info; - gpu_bundle* bundle; MaterialBlock* block; - bool hasWritableTexture; + MaterialData data; + Texture* textures[TEXTURE_COUNT]; + gpu_binding bindings[1 + TEXTURE_COUNT]; + gpu_bundle* bundle; + MaterialData* pointer; + uint32_t bundleTick; + uint32_t bufferTick; }; typedef struct { @@ -792,15 +809,7 @@ bool lovrGraphicsInit(GraphicsConfig* config) { // Default Material - state.defaultMaterial = lovrMaterialCreate(&(MaterialInfo) { - .data.color = { 1.f, 1.f, 1.f, 1.f }, - .data.uvScale = { 1.f, 1.f }, - .data.metalness = 0.f, - .data.roughness = 1.f, - .data.normalScale = 1.f, - .texture = state.defaultTexture - }); - + state.defaultMaterial = lovrMaterialCreate(NULL); if (!state.defaultMaterial) goto fail; // Default Samplers @@ -918,7 +927,6 @@ void lovrGraphicsDestroy(void) { MaterialBlock* next = block->next; gpu_bundle_pool_destroy(block->bundlePool); gpu_buffer_destroy(block->buffer); - lovrFree(block->materials); lovrFree(block->bundlePool); lovrFree(block->bundles); lovrFree(block->buffer); @@ -3100,11 +3108,7 @@ void lovrTextureSetSampler(Texture* texture, Sampler* sampler) { Material* lovrTextureToMaterial(Texture* texture) { if (!texture->material) { - texture->material = lovrMaterialCreate(&(MaterialInfo) { - .data.color = { 1.f, 1.f, 1.f, 1.f }, - .data.uvScale = { 1.f, 1.f }, - .texture = texture - }); + texture->material = lovrMaterialCreate(texture); if (!texture->material) { return NULL; @@ -3113,7 +3117,7 @@ Material* lovrTextureToMaterial(Texture* texture) { // Since the Material refcounts the Texture, this creates a cycle. Release the texture to make // sure this is a weak relationship (the automaterial does not keep the texture refcounted). lovrRelease(texture, lovrTextureDestroy); - texture->material->info.texture = NULL; + texture->material->textures[TEXTURE_COLOR] = NULL; } return texture->material; @@ -4116,164 +4120,272 @@ const DataField* lovrShaderGetBufferFormat(Shader* shader, const char* name, uin // Material -Material* lovrMaterialCreate(const MaterialInfo* info) { - Texture* textures[] = { - info->texture, - info->glowTexture, - info->metalnessTexture, - info->roughnessTexture, - info->clearcoatTexture, - info->occlusionTexture, - info->normalTexture - }; - - for (uint32_t i = 0; i < COUNTOF(textures); i++) { - if (!textures[i]) continue; - lovrCheck(i == 0 || textures[i]->info.type == TEXTURE_2D, "Material textures must be 2D"); - lovrCheck(textures[i]->info.samples == 1, "Material textures can not be multisampled"); - lovrCheck(textures[i]->info.usage & TEXTURE_SAMPLE, "Textures must be created with the 'sample' usage to use them in Materials"); - } - - // TODO this should be more fine-grained +static bool lovrMaterialAllocate(Material* material) { mtx_lock(&state.lock); MaterialBlock* block = NULL; - - for (MaterialBlock* node = state.materials; node != NULL; node = node->next) { - if (node->head != ~0u && gpu_is_complete(node->materials[node->head].tick)) { - block = node; + for (MaterialBlock* b = state.materials; b != NULL; b = b->next) { + if (b->head != ~0u && gpu_is_complete(b->nodes[b->head].tick)) { + block = b; break; } } if (!block) { - const uint32_t count = 256; - block = lovrMalloc(sizeof(*block)); - block->materials = lovrMalloc(count * sizeof(Material)); + block = lovrMalloc(sizeof(MaterialBlock)); block->buffer = lovrMalloc(gpu_sizeof_buffer()); block->bundlePool = lovrMalloc(gpu_sizeof_bundle_pool()); - block->bundles = lovrMalloc(count * gpu_sizeof_bundle()); + block->bundles = lovrMalloc(MATERIAL_BLOCK_SIZE * gpu_sizeof_bundle()); - for (uint32_t i = 0; i < count; i++) { - block->materials[i] = (Material) { - .index = i, - .next = i + 1, - .block = block, - .bundle = (gpu_bundle*) ((char*) block->bundles + i * gpu_sizeof_bundle()) - }; + for (uint32_t i = 0; i < MATERIAL_BLOCK_SIZE; i++) { + block->nodes[i].next = i + 1; + block->nodes[i].tick = 0; } - block->materials[count - 1].next = ~0u; - block->tail = count - 1; block->head = 0; + block->tail = MATERIAL_BLOCK_SIZE - 1; + block->nodes[block->tail].next = ~0u; gpu_bundle_pool_info poolInfo = { .bundles = block->bundles, .layout = state.materialLayout->gpu, - .count = count + .count = MATERIAL_BLOCK_SIZE }; if (!gpu_bundle_pool_init(block->bundlePool, &poolInfo)) { - lovrFree(block->materials); lovrFree(block->buffer); lovrFree(block->bundlePool); lovrFree(block->bundles); mtx_unlock(&state.lock); - return NULL; + return false; } gpu_buffer_info bufferInfo = { .type = GPU_BUFFER_STATIC, - .size = count * (uint32_t) ALIGN(sizeof(MaterialData), state.limits.uniformBufferAlign), - .pointer = &block->pointer + .size = MATERIAL_BLOCK_SIZE * MATERIAL_STRIDE, + .pointer = &block->bufferPointer }; if (!gpu_buffer_init(block->buffer, &bufferInfo)) { - lovrFree(block->materials); lovrFree(block->buffer); lovrFree(block->bundlePool); lovrFree(block->bundles); gpu_bundle_pool_destroy(block->bundlePool); mtx_unlock(&state.lock); - return NULL; + return false; } block->next = state.materials; state.materials = block; } - Material* material = &block->materials[block->head]; - material->ref = 1; - material->info = *info; + material->block = block; + material->index = block->head; + material->bundle = (gpu_bundle*) ((char*) block->bundles + block->head * gpu_sizeof_bundle()); + material->bundleTick = state.tick; - MaterialData* data; - uint32_t stride = ALIGN(sizeof(MaterialData), state.limits.uniformBufferAlign); + material->bindings[0] = (gpu_binding) { + .number = 0, + .type = GPU_SLOT_UNIFORM_BUFFER, + .buffer.object = block->buffer, + .buffer.offset = material->index * MATERIAL_STRIDE, + .buffer.extent = MATERIAL_STRIDE + }; - if (block->pointer) { - data = (MaterialData*) ((char*) block->pointer + material->index * stride); - } else { - BufferView staging = getBuffer(GPU_BUFFER_UPLOAD, sizeof(MaterialData), 4); - if (!staging.buffer) return mtx_unlock(&state.lock), NULL; + if (block->bufferPointer) { + material->pointer = (MaterialData*) ((char*) block->bufferPointer + material->index * MATERIAL_STRIDE); + material->bufferTick = state.tick; + } - gpu_copy_buffers(state.stream, staging.buffer, block->buffer, staging.offset, stride * material->index, sizeof(MaterialData)); - state.barrier.prev |= GPU_PHASE_COPY; - state.barrier.next |= GPU_PHASE_SHADER_VERTEX | GPU_PHASE_SHADER_FRAGMENT; - state.barrier.flush |= GPU_CACHE_TRANSFER_WRITE; - state.barrier.clear |= GPU_CACHE_UNIFORM; - data = staging.pointer; + block->head = block->nodes[block->head].next; + block->nodes[material->index].next = ~0u; + + mtx_unlock(&state.lock); + + return true; +} + +static void lovrMaterialRecycle(Material* material) { + if (!material->block) return; + mtx_lock(&state.lock); + material->block->nodes[material->index].tick = state.tick; + material->block->tail = material->index; + if (material->block->head == ~0u) material->block->head = material->block->tail; + material->block = NULL; + material->index = ~0u; + material->bundle = NULL; + mtx_unlock(&state.lock); +} + +static bool lovrMaterialUpload(Material* material) { + if (material->bufferTick == state.tick) { + return true; } - memcpy(data, info, sizeof(MaterialData)); + mtx_lock(&state.lock); + BufferView staging = getBuffer(GPU_BUFFER_STREAM, sizeof(MaterialData), 4); + if (!staging.buffer) return mtx_unlock(&state.lock), NULL; + gpu_copy_buffers(state.stream, staging.buffer, material->block->buffer, staging.offset, MATERIAL_STRIDE * material->index, sizeof(MaterialData)); + state.barrier.prev |= GPU_PHASE_COPY; + state.barrier.next |= GPU_PHASE_SHADER_VERTEX | GPU_PHASE_SHADER_FRAGMENT; + state.barrier.flush |= GPU_CACHE_TRANSFER_WRITE; + state.barrier.clear |= GPU_CACHE_UNIFORM; + material->bufferTick = state.tick; + material->pointer = staging.pointer; + memcpy(material->pointer, &material->data, sizeof(MaterialData)); + mtx_unlock(&state.lock); - gpu_buffer_binding buffer = { - .object = block->buffer, - .offset = material->index * stride, - .extent = stride - }; + return true; +} - gpu_binding bindings[8] = { - { 0, GPU_SLOT_UNIFORM_BUFFER, .buffer = buffer } +Material* lovrMaterialCreate(Texture* texture) { + Material* material = lovrCalloc(sizeof(Material)); + material->ref = 1; + + material->data = (MaterialData) { + .colors[COLOR_BASE] = { 1.f, 1.f, 1.f, 1.f }, + .quad = { 0.f, 0.f, 1.f, 1.f } }; - for (uint32_t i = 0; i < COUNTOF(textures); i++) { - Texture* texture = textures[i] ? textures[i] : state.defaultTexture; - bindings[i + 1] = (gpu_binding) { i + 1, GPU_SLOT_SAMPLED_TEXTURE, .texture.object = texture->sampleViewFloat }; - material->hasWritableTexture |= texture->info.usage != TEXTURE_SAMPLE; - lovrRetain(textures[i]); + for (uint32_t i = 0; i < TEXTURE_COUNT; i++) { + Texture* t = state.defaultTexture; + material->textures[i] = t; + material->bindings[i + 1] = (gpu_binding) { + .number = i + 1, + .type = GPU_SLOT_SAMPLED_TEXTURE, + .texture.object = t->sampleViewFloat + }; + lovrRetain(t); } - gpu_bundle_info bundleInfo = { - .layout = state.materialLayout->gpu, - .bindings = bindings, - .count = COUNTOF(bindings) - }; + if (texture) { + // This will take care of allocating the material and writing all the bindings + if (!lovrMaterialSetTexture(material, TEXTURE_COLOR, texture)) { + lovrFree(material); + return NULL; + } + } else { + if (!lovrMaterialAllocate(material)) { + lovrFree(material); + return NULL; + } - gpu_bundle_write(&material->bundle, &bundleInfo, 1); + gpu_bundle_info bundleInfo = { + .layout = state.materialLayout->gpu, + .bindings = material->bindings, + .count = COUNTOF(material->bindings) + }; + + gpu_bundle_write(&material->bundle, &bundleInfo, 1); + } + + if (material->pointer) { + memcpy(material->pointer, &material->data, sizeof(MaterialData)); + } else { + lovrMaterialUpload(material); + } - block->head = material->next; - material->next = ~0u; - mtx_unlock(&state.lock); return material; } void lovrMaterialDestroy(void* ref) { Material* material = ref; - mtx_lock(&state.lock); - material->tick = state.tick; - material->block->tail = material->index; - if (material->block->head == ~0u) material->block->head = material->block->tail; - mtx_unlock(&state.lock); - lovrRelease(material->info.texture, lovrTextureDestroy); - lovrRelease(material->info.glowTexture, lovrTextureDestroy); - lovrRelease(material->info.metalnessTexture, lovrTextureDestroy); - lovrRelease(material->info.roughnessTexture, lovrTextureDestroy); - lovrRelease(material->info.clearcoatTexture, lovrTextureDestroy); - lovrRelease(material->info.occlusionTexture, lovrTextureDestroy); - lovrRelease(material->info.normalTexture, lovrTextureDestroy); + lovrMaterialRecycle(material); + for (uint32_t i = 0; i < TEXTURE_COUNT; i++) { + lovrRelease(material->textures[i], lovrTextureDestroy); + } + lovrFree(material); +} + +float lovrMaterialGetNumber(Material* material, MaterialNumber key) { + return material->data.numbers[key]; +} + +bool lovrMaterialSetNumber(Material* material, MaterialNumber key, float number) { + if (!lovrMaterialUpload(material)) return false; + material->data.numbers[key] = number; + material->pointer->numbers[key] = number; + return true; +} + +const float* lovrMaterialGetColor(Material* material, MaterialColor key) { + return material->data.colors[key]; +} + +bool lovrMaterialSetColor(Material* material, MaterialColor key, float color[4]) { + if (!lovrMaterialUpload(material)) return false; + memcpy(material->data.colors[key], color, 4 * sizeof(float)); + material->pointer->colors[key][0] = lovrMathGammaToLinear(color[0]); + material->pointer->colors[key][1] = lovrMathGammaToLinear(color[1]); + material->pointer->colors[key][2] = lovrMathGammaToLinear(color[2]); + material->pointer->colors[key][3] = color[3]; + return true; +} + +Texture* lovrMaterialGetTexture(Material* material, MaterialTexture key) { + return material->textures[key]; +} + +bool lovrMaterialSetTexture(Material* material, MaterialTexture key, Texture* texture) { + if (texture == material->textures[key]) return true; + + lovrCheck(key == TEXTURE_COLOR || texture->info.type == TEXTURE_2D, "Material textures must be 2D"); + lovrCheck(texture->info.samples == 1, "Material textures can not be multisampled"); + lovrCheck(texture->info.usage & TEXTURE_SAMPLE, "Textures must be created with the 'sample' usage to use them in Materials"); + + lovrRelease(material->textures[key], lovrTextureDestroy); + material->textures[key] = texture; + material->bindings[key + 1].texture.object = texture->sampleViewFloat; + lovrRetain(texture); + + gpu_bundle_info bundleInfo = { .layout = state.materialLayout->gpu }; + + if (material->bundleTick == state.tick) { + bundleInfo.bindings = &material->bindings[key + 1]; + bundleInfo.count = 1; + } else { + lovrMaterialRecycle(material); + + if (!lovrMaterialAllocate(material)) { + return false; + } + + bundleInfo.bindings = material->bindings; + bundleInfo.count = COUNTOF(material->bindings); + } + + gpu_bundle_write(&material->bundle, &bundleInfo, 1); + + return true; } -const MaterialInfo* lovrMaterialGetInfo(Material* material) { - return &material->info; +void lovrMaterialGetQuad(Material* material, float* ox, float* oy, float* sx, float* sy) { + *ox = material->data.quad[0]; + *oy = material->data.quad[1]; + *sx = material->data.quad[2]; + *sy = material->data.quad[3]; +} + +bool lovrMaterialSetQuad(Material* material, float ox, float oy, float sx, float sy) { + if (!lovrMaterialUpload(material)) return false; + material->data.quad[0] = ox; + material->data.quad[1] = oy; + material->data.quad[2] = sx; + material->data.quad[3] = sy; + material->pointer->quad[0] = ox; + material->pointer->quad[1] = oy; + material->pointer->quad[2] = sx; + material->pointer->quad[3] = sy; + return true; +} + +static bool lovrMaterialSetSDFRange(Material* material, float x, float y) { + if (!lovrMaterialUpload(material)) return false; + material->data.sdfRange[0] = x; + material->data.sdfRange[1] = y; + material->pointer->sdfRange[0] = x; + material->pointer->sdfRange[1] = y; + return true; } // Font @@ -4344,11 +4456,7 @@ Font* lovrFontCreate(const FontInfo* info) { return NULL; } - font->material = lovrMaterialCreate(&(MaterialInfo) { - .data.color = { 1.f, 1.f, 1.f, 1.f }, - .data.uvScale = { 1.f, 1.f }, - .texture = font->atlas - }); + font->material = lovrMaterialCreate(font->atlas); if (!font->material) { lovrFontDestroy(font); @@ -4514,12 +4622,8 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint, bool* resized) { return NULL; } - Material* material = lovrMaterialCreate(&(MaterialInfo) { - .data.color = { 1.f, 1.f, 1.f, 1.f }, - .data.uvScale = { 1.f, 1.f }, - .data.sdfRange = { font->info.spread / newWidth, font->info.spread / newHeight }, - .texture = atlas - }); + Material* material = lovrMaterialCreate(atlas); + lovrMaterialSetSDFRange(material, font->info.spread / newWidth, font->info.spread / newHeight); if (!material) { lovrTextureDestroy(atlas); @@ -5330,58 +5434,65 @@ Model* lovrModelCreate(const ModelInfo* info) { ModelData* data = info->data; ModelMetadata* meta = &model->meta; - // Materials and Textures + // Materials + + model->textures = lovrCalloc(meta->imageCount * sizeof(Texture*)); + model->materials = lovrCalloc(meta->materialCount * sizeof(Material*)); + if (info->materials) { - model->textures = lovrCalloc(meta->imageCount * sizeof(Texture*)); - model->materials = lovrMalloc(meta->materialCount * sizeof(Material*)); for (uint32_t i = 0; i < meta->materialCount; i++) { - MaterialInfo material; + Material* material = lovrMaterialCreate(NULL); + lovrAssertGoto(fail, material, "Failed to create model material: %s", lovrGetError()); + model->materials[i] = material; + ModelMaterial* properties = &meta->materials[i]; - memcpy(&material.data, properties, sizeof(MaterialData)); - - struct { uint32_t index; Texture** texture; } textures[] = { - { properties->texture, &material.texture }, - { properties->glowTexture, &material.glowTexture }, - { properties->metalnessTexture, &material.metalnessTexture }, - { properties->roughnessTexture, &material.roughnessTexture }, - { properties->clearcoatTexture, &material.clearcoatTexture }, - { properties->occlusionTexture, &material.occlusionTexture }, - { properties->normalTexture, &material.normalTexture } + lovrMaterialSetNumber(material, NUMBER_METALNESS, properties->metalness); + lovrMaterialSetNumber(material, NUMBER_ROUGHNESS, properties->roughness); + lovrMaterialSetNumber(material, NUMBER_CLEARCOAT, properties->clearcoat); + lovrMaterialSetNumber(material, NUMBER_CLEARCOAT_ROUGHNESS, properties->clearcoatRoughness); + lovrMaterialSetNumber(material, NUMBER_OCCLUSION_STRENGTH, properties->occlusionStrength); + lovrMaterialSetNumber(material, NUMBER_NORMAL_SCALE, properties->normalScale); + lovrMaterialSetNumber(material, NUMBER_ALPHA_CUTOFF, properties->alphaCutoff); + lovrMaterialSetColor(material, COLOR_BASE, properties->color); + lovrMaterialSetColor(material, COLOR_GLOW, properties->glow); + lovrMaterialSetQuad(material, properties->uvShift[0], properties->uvShift[1], properties->uvScale[0], properties->uvScale[1]); + + uint32_t textures[] = { + [TEXTURE_COLOR] = properties->texture, + [TEXTURE_GLOW] = properties->glowTexture, + [TEXTURE_METALNESS] = properties->metalnessTexture, + [TEXTURE_ROUGHNESS] = properties->roughnessTexture, + [TEXTURE_CLEARCOAT] = properties->clearcoatTexture, + [TEXTURE_OCCLUSION] = properties->occlusionTexture, + [TEXTURE_NORMAL] = properties->normalTexture }; for (uint32_t t = 0; t < COUNTOF(textures); t++) { - uint32_t index = textures[t].index; - Texture** texture = textures[t].texture; - - if (index == ~0u) { - *texture = NULL; - } else { - if (!model->textures[index]) { - Image* image = data->images[index]; - - model->textures[index] = lovrTextureCreate(&(TextureInfo) { - .type = TEXTURE_2D, - .usage = TEXTURE_SAMPLE, - .format = lovrImageGetFormat(image), - .width = lovrImageGetWidth(image, 0), - .height = lovrImageGetHeight(image, 0), - .layers = 1, - .mipmaps = info->mipmaps || lovrImageGetLevelCount(image) > 1 ? ~0u : 1, - .samples = 1, - .srgb = texture == &material.texture || texture == &material.glowTexture, - .images = &image, - .imageCount = 1 - }); - - if (!model->textures[index]) goto fail; - } - - *texture = model->textures[index]; + uint32_t index = textures[t]; + if (index == ~0u) continue; + + if (!model->textures[index]) { + Image* image = data->images[index]; + + model->textures[index] = lovrTextureCreate(&(TextureInfo) { + .type = TEXTURE_2D, + .usage = TEXTURE_SAMPLE, + .format = lovrImageGetFormat(image), + .width = lovrImageGetWidth(image, 0), + .height = lovrImageGetHeight(image, 0), + .layers = 1, + .mipmaps = info->mipmaps || lovrImageGetLevelCount(image) > 1 ? ~0u : 1, + .samples = 1, + .srgb = t == TEXTURE_COLOR || t == TEXTURE_GLOW, + .images = &image, + .imageCount = 1 + }); + + if (!model->textures[index]) goto fail; } - } - model->materials[i] = lovrMaterialCreate(&material); - lovrAssertGoto(fail, model->materials[i], "Failed to create model material: %s", lovrGetError()); + lovrMaterialSetTexture(material, t, model->textures[index]); + } } } @@ -5504,8 +5615,18 @@ Model* lovrModelClone(Model* parent) { ModelMetadata* meta = &model->meta; - model->textures = parent->textures; - model->materials = parent->materials; + model->textures = lovrMalloc(meta->imageCount * sizeof(Texture*)); + model->materials = lovrMalloc(meta->materialCount * sizeof(Material*)); + memcpy(model->textures, parent->textures, meta->imageCount * sizeof(Texture*)); + memcpy(model->materials, parent->materials, meta->materialCount * sizeof(Material*)); + + for (uint32_t i = 0; i < meta->imageCount; i++) { + lovrRetain(model->textures[i]); + } + + for (uint32_t i = 0; i < meta->materialCount; i++) { + lovrRetain(model->materials[i]); + } model->rawVertexBuffer = parent->rawVertexBuffer; model->indexBuffer = parent->indexBuffer; @@ -5571,16 +5692,14 @@ void lovrModelDestroy(void* ref) { return; } ModelMetadata* meta = &model->meta; - if (model->materials) { - for (uint32_t i = 0; i < meta->materialCount; i++) { - lovrRelease(model->materials[i], lovrMaterialDestroy); - } - for (uint32_t i = 0; i < meta->imageCount; i++) { - lovrRelease(model->textures[i], lovrTextureDestroy); - } - lovrFree(model->materials); - lovrFree(model->textures); + for (uint32_t i = 0; i < meta->materialCount; i++) { + lovrRelease(model->materials[i], lovrMaterialDestroy); + } + for (uint32_t i = 0; i < meta->imageCount; i++) { + lovrRelease(model->textures[i], lovrTextureDestroy); } + lovrFree(model->materials); + lovrFree(model->textures); if (model->meshes) { for (uint32_t i = 0; i < meta->meshCount; i++) { lovrRelease(model->meshes[i], lovrMeshDestroy); @@ -5833,7 +5952,7 @@ Mesh* lovrModelGetMesh(Model* model, uint32_t index) { default: lovrUnreachable(); } - if (model->materials && part->material != ~0u) { + if (part->material != ~0u) { lovrMeshSetMaterial(mesh, model->materials[part->material]); } @@ -5853,10 +5972,20 @@ Texture* lovrModelGetTexture(Model* model, uint32_t index) { return model->textures[index]; } -Material* lovrModelGetMaterial(Model* model, uint32_t index) { +bool lovrModelGetMaterial(Model* model, uint32_t index, Material** material) { uint32_t count = model->meta.materialCount; lovrCheck(index < count, "Invalid material index '%d' (Model has %d material%s)", index + 1, count, count == 1 ? "" : "s"); - return model->materials[index]; + *material = model->materials[index]; + return true; +} + +bool lovrModelSetMaterial(Model* model, uint32_t index, Material* material) { + uint32_t count = model->meta.materialCount; + lovrCheck(index < count, "Invalid material index '%d' (Model has %d material%s)", index + 1, count, count == 1 ? "" : "s"); + lovrRelease(model->materials[index], lovrMaterialDestroy); + model->materials[index] = material; + lovrRetain(material); + return true; } static bool lovrModelAnimateVertices(Model* model) { @@ -8965,7 +9094,7 @@ static bool drawNode(Pass* pass, Model* model, uint32_t index, uint32_t instance DrawInfo draw = { .mode = part->mode == DRAW_POINT_LIST ? DRAW_POINTS : part->mode == DRAW_LINE_LIST ? DRAW_LINES : DRAW_TRIANGLES, - .material = model->materials && part->material != ~0u ? model->materials[part->material] : NULL, + .material = part->material != ~0u ? model->materials[part->material] : NULL, .transform = node->skin == ~0u ? globalTransform : NULL, .bounds = bounds, .vertex.buffer = model->vertexBuffer, @@ -9042,7 +9171,7 @@ bool lovrPassDrawPart(Pass* pass, Model* model, uint32_t meshIndex, uint32_t par DrawInfo draw = { .mode = part->mode == DRAW_POINT_LIST ? DRAW_POINTS : part->mode == DRAW_LINE_LIST ? DRAW_LINES : DRAW_TRIANGLES, - .material = model->materials && part->material != ~0u ? model->materials[part->material] : NULL, + .material = part->material != ~0u ? model->materials[part->material] : NULL, .transform = transform, // TODO fix skinned mesh transforms? .bounds = part->bounds, .vertex.buffer = model->vertexBuffer, @@ -9771,20 +9900,16 @@ static void trackTexture(Pass* pass, Texture* texture, gpu_phase phase, gpu_cach } static void trackMaterial(Pass* pass, Material* material) { - if (!material->hasWritableTexture) { - return; - } - gpu_phase phase = GPU_PHASE_SHADER_VERTEX | GPU_PHASE_SHADER_FRAGMENT; gpu_cache cache = GPU_CACHE_TEXTURE; - trackTexture(pass, material->info.texture, phase, cache); - trackTexture(pass, material->info.glowTexture, phase, cache); - trackTexture(pass, material->info.metalnessTexture, phase, cache); - trackTexture(pass, material->info.roughnessTexture, phase, cache); - trackTexture(pass, material->info.clearcoatTexture, phase, cache); - trackTexture(pass, material->info.occlusionTexture, phase, cache); - trackTexture(pass, material->info.normalTexture, phase, cache); + trackTexture(pass, material->textures[TEXTURE_COLOR], phase, cache); + trackTexture(pass, material->textures[TEXTURE_GLOW], phase, cache); + trackTexture(pass, material->textures[TEXTURE_METALNESS], phase, cache); + trackTexture(pass, material->textures[TEXTURE_ROUGHNESS], phase, cache); + trackTexture(pass, material->textures[TEXTURE_CLEARCOAT], phase, cache); + trackTexture(pass, material->textures[TEXTURE_OCCLUSION], phase, cache); + trackTexture(pass, material->textures[TEXTURE_NORMAL], phase, cache); } static void trackRaytracer(Pass* pass, Raytracer* raytracer, gpu_phase phase, gpu_cache cache) { diff --git a/src/modules/graphics/graphics.h b/src/modules/graphics/graphics.h index b6172bd5f5..2d3507480b 100644 --- a/src/modules/graphics/graphics.h +++ b/src/modules/graphics/graphics.h @@ -379,35 +379,44 @@ const DataField* lovrShaderGetBufferFormat(Shader* shader, const char* name, uin // Material -typedef struct { - float color[4]; - float glow[4]; - float uvShift[2]; - float uvScale[2]; - float sdfRange[2]; - float metalness; - float roughness; - float clearcoat; - float clearcoatRoughness; - float occlusionStrength; - float normalScale; - float alphaCutoff; -} MaterialData; +typedef enum { + NUMBER_METALNESS, + NUMBER_ROUGHNESS, + NUMBER_CLEARCOAT, + NUMBER_CLEARCOAT_ROUGHNESS, + NUMBER_OCCLUSION_STRENGTH, + NUMBER_NORMAL_SCALE, + NUMBER_ALPHA_CUTOFF, + NUMBER_COUNT +} MaterialNumber; -typedef struct { - MaterialData data; - Texture* texture; - Texture* glowTexture; - Texture* metalnessTexture; - Texture* roughnessTexture; - Texture* clearcoatTexture; - Texture* occlusionTexture; - Texture* normalTexture; -} MaterialInfo; - -Material* lovrMaterialCreate(const MaterialInfo* info); +typedef enum { + COLOR_BASE, + COLOR_GLOW, + COLOR_COUNT +} MaterialColor; + +typedef enum { + TEXTURE_COLOR, + TEXTURE_GLOW, + TEXTURE_METALNESS, + TEXTURE_ROUGHNESS, + TEXTURE_CLEARCOAT, + TEXTURE_OCCLUSION, + TEXTURE_NORMAL, + TEXTURE_COUNT +} MaterialTexture; + +Material* lovrMaterialCreate(Texture* texture); void lovrMaterialDestroy(void* ref); -const MaterialInfo* lovrMaterialGetInfo(Material* material); +float lovrMaterialGetNumber(Material* material, MaterialNumber key); +bool lovrMaterialSetNumber(Material* material, MaterialNumber key, float number); +const float* lovrMaterialGetColor(Material* material, MaterialColor key); +bool lovrMaterialSetColor(Material* material, MaterialColor key, float color[4]); +Texture* lovrMaterialGetTexture(Material* material, MaterialTexture key); +bool lovrMaterialSetTexture(Material* material, MaterialTexture key, Texture* texture); +void lovrMaterialGetQuad(Material* material, float* ox, float* oy, float* sx, float* sy); +bool lovrMaterialSetQuad(Material* material, float ox, float oy, float sx, float sy); // Font @@ -536,7 +545,8 @@ Buffer* lovrModelGetVertexBuffer(Model* model); Buffer* lovrModelGetIndexBuffer(Model* model); Mesh* lovrModelGetMesh(Model* model, uint32_t index); Texture* lovrModelGetTexture(Model* model, uint32_t index); -Material* lovrModelGetMaterial(Model* model, uint32_t index); +bool lovrModelGetMaterial(Model* model, uint32_t index, Material** material); +bool lovrModelSetMaterial(Model* model, uint32_t index, Material* material); bool lovrModelBuildRaytracer(Model* model); // Raytracer From 976fc0b332b36cc8e9a3ea82eb0c96421be28222 Mon Sep 17 00:00:00 2001 From: bjorn Date: Mon, 10 Aug 2026 12:37:40 -0700 Subject: [PATCH 2/8] Adjust material defaults; --- src/modules/graphics/graphics.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index ba7075425b..1404a6cdab 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -4243,6 +4243,8 @@ Material* lovrMaterialCreate(Texture* texture) { material->ref = 1; material->data = (MaterialData) { + .numbers[NUMBER_OCCLUSION_STRENGTH] = 1.f, + .numbers[NUMBER_NORMAL_SCALE] = 1.f, .colors[COLOR_BASE] = { 1.f, 1.f, 1.f, 1.f }, .quad = { 0.f, 0.f, 1.f, 1.f } }; From 3f8eb0875dd5b032cba6a9927a114328e614cbc8 Mon Sep 17 00:00:00 2001 From: bjorn Date: Mon, 10 Aug 2026 13:46:45 -0700 Subject: [PATCH 3/8] Fixes; --- src/api/l_graphics.c | 7 ++++--- src/api/l_graphics_material.c | 6 +++--- src/modules/graphics/graphics.c | 20 +++++++++++++++----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/api/l_graphics.c b/src/api/l_graphics.c index 60594103cb..aee43b56a8 100644 --- a/src/api/l_graphics.c +++ b/src/api/l_graphics.c @@ -1491,8 +1491,6 @@ static int l_lovrGraphicsNewMaterial(lua_State* L) { luax_assert(L, material); if (lua_istable(L, 1)) { - float value, color[4]; - for (uint32_t i = 0; i < NUMBER_COUNT; i++) { lua_pushlstring(L, lovrMaterialNumber[i].string, lovrMaterialNumber[i].length); lua_gettable(L, 1); @@ -1509,6 +1507,7 @@ static int l_lovrGraphicsNewMaterial(lua_State* L) { lua_gettable(L, 1); if (!lua_isnil(L, -1)) { + float color[4]; luax_optcolor(L, -1, color); lovrMaterialSetColor(material, i, color); } @@ -1536,7 +1535,7 @@ static int l_lovrGraphicsNewMaterial(lua_State* L) { lua_pop(L, 1); } - float quad[4]; + float quad[4] = { 0.f, 0.f, 1.f, 1.f }; // Deprecated lua_getfield(L, 1, "uvShift"); @@ -1577,6 +1576,8 @@ static int l_lovrGraphicsNewMaterial(lua_State* L) { lua_pop(L, 4); } lua_pop(L, 1); + + lovrMaterialSetQuad(material, quad[0], quad[1], quad[2], quad[3]); } luax_pushtype(L, Material, material); diff --git a/src/api/l_graphics_material.c b/src/api/l_graphics_material.c index be0bea126e..11acac4b9a 100644 --- a/src/api/l_graphics_material.c +++ b/src/api/l_graphics_material.c @@ -29,7 +29,7 @@ static int l_lovrMaterialSetNumber(lua_State* L) { Material* material = luax_checktype(L, 1, Material); MaterialNumber key = luax_checkenum(L, 2, MaterialNumber, NULL); float number = luax_checkfloat(L, 3); - lovrMaterialSetNumber(material, key, number); + luax_assert(L, lovrMaterialSetNumber(material, key, number)); return 0; } @@ -50,7 +50,7 @@ static int l_lovrMaterialSetColor(lua_State* L) { float color[4]; MaterialColor key = lua_type(L, index) == LUA_TSTRING ? luax_checkenum(L, index++, MaterialColor, NULL) : COLOR_BASE; luax_readcolor(L, index, color); - lovrMaterialSetColor(material, key, color); + luax_assert(L, lovrMaterialSetColor(material, key, color)); return 0; } @@ -67,7 +67,7 @@ static int l_lovrMaterialSetTexture(lua_State* L) { int index = 2; MaterialTexture key = lua_type(L, index) == LUA_TSTRING ? luax_checkenum(L, index++, MaterialTexture, NULL) : TEXTURE_COLOR; Texture* texture = luax_checktype(L, index, Texture); - lovrMaterialSetTexture(material, key, texture); + luax_assert(L, lovrMaterialSetTexture(material, key, texture)); return 0; } diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 1404a6cdab..cc098d772a 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -4208,9 +4208,11 @@ static bool lovrMaterialAllocate(Material* material) { static void lovrMaterialRecycle(Material* material) { if (!material->block) return; mtx_lock(&state.lock); - material->block->nodes[material->index].tick = state.tick; - material->block->tail = material->index; - if (material->block->head == ~0u) material->block->head = material->block->tail; + MaterialBlock* block = material->block; + block->nodes[material->index].tick = state.tick; + if (block->head == ~0u) block->head = material->index; + else block->nodes[block->tail].next = material->index; + block->tail = material->index; material->block = NULL; material->index = ~0u; material->bundle = NULL; @@ -4224,7 +4226,7 @@ static bool lovrMaterialUpload(Material* material) { mtx_lock(&state.lock); BufferView staging = getBuffer(GPU_BUFFER_STREAM, sizeof(MaterialData), 4); - if (!staging.buffer) return mtx_unlock(&state.lock), NULL; + if (!staging.buffer) return mtx_unlock(&state.lock), false; gpu_copy_buffers(state.stream, staging.buffer, material->block->buffer, staging.offset, MATERIAL_STRIDE * material->index, sizeof(MaterialData)); state.barrier.prev |= GPU_PHASE_COPY; state.barrier.next |= GPU_PHASE_SHADER_VERTEX | GPU_PHASE_SHADER_FRAGMENT; @@ -4232,7 +4234,15 @@ static bool lovrMaterialUpload(Material* material) { state.barrier.clear |= GPU_CACHE_UNIFORM; material->bufferTick = state.tick; material->pointer = staging.pointer; - memcpy(material->pointer, &material->data, sizeof(MaterialData)); + memcpy(material->pointer->numbers, &material->data.numbers, sizeof(material->data.numbers)); + for (uint32_t i = 0; i < COLOR_COUNT; i++) { + material->pointer->colors[i][0] = lovrMathGammaToLinear(material->data.colors[i][0]); + material->pointer->colors[i][1] = lovrMathGammaToLinear(material->data.colors[i][1]); + material->pointer->colors[i][2] = lovrMathGammaToLinear(material->data.colors[i][2]); + material->pointer->colors[i][3] = material->data.colors[i][3]; + } + memcpy(material->pointer->quad, material->data.quad, sizeof(material->data.quad)); + memcpy(material->pointer->sdfRange, material->data.sdfRange, sizeof(material->data.sdfRange)); mtx_unlock(&state.lock); return true; From b8464b4d27e7bf07e05a29b847cbb08cb86b198c Mon Sep 17 00:00:00 2001 From: bjorn Date: Sat, 15 Aug 2026 12:48:18 -0700 Subject: [PATCH 4/8] ModelData uses new quad field; --- src/api/l_data_modelData.c | 19 ++++++++----------- src/modules/data/modelData.c | 7 +++---- src/modules/data/modelData.h | 9 ++++----- src/modules/data/modelData_gltf.c | 8 ++++---- src/modules/data/modelData_obj.c | 7 +++---- src/modules/graphics/graphics.c | 2 +- 6 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/api/l_data_modelData.c b/src/api/l_data_modelData.c index 6c830fbc74..cb04603445 100644 --- a/src/api/l_data_modelData.c +++ b/src/api/l_data_modelData.c @@ -602,19 +602,16 @@ static int l_lovrModelDataGetMaterial(lua_State* L) { lua_rawseti(L, -2, 4); lua_setfield(L, -2, "glow"); - lua_createtable(L, 2, 0); - lua_pushnumber(L, material->uvShift[0]); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, material->uvShift[1]); - lua_rawseti(L, -2, 2); - lua_setfield(L, -2, "uvShift"); - - lua_createtable(L, 2, 0); - lua_pushnumber(L, material->uvScale[0]); + lua_createtable(L, 4, 0); + lua_pushnumber(L, material->quad[0]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, material->uvScale[1]); + lua_pushnumber(L, material->quad[1]); lua_rawseti(L, -2, 2); - lua_setfield(L, -2, "uvScale"); + lua_pushnumber(L, material->quad[2]); + lua_rawseti(L, -2, 3); + lua_pushnumber(L, material->quad[3]); + lua_rawseti(L, -2, 4); + lua_setfield(L, -2, "quad"); lua_pushnumber(L, material->metalness), lua_setfield(L, -2, "metalness"); lua_pushnumber(L, material->roughness), lua_setfield(L, -2, "roughness"); diff --git a/src/modules/data/modelData.c b/src/modules/data/modelData.c index 53a4fdf4b5..f2003e3dea 100644 --- a/src/modules/data/modelData.c +++ b/src/modules/data/modelData.c @@ -109,10 +109,6 @@ void lovrModelDataAllocate(ModelData* model) { for (uint32_t i = 0; i < meta->materialCount; i++) { meta->materials[i] = (ModelMaterial) { - .color = { 1.f, 1.f, 1.f, 1.f }, - .glow = { 0.f, 0.f, 0.f, 1.f }, - .uvShift = { 0.f, 0.f }, - .uvScale = { 1.f, 1.f }, .metalness = 1.f, .roughness = 1.f, .clearcoat = 0.f, @@ -120,6 +116,9 @@ void lovrModelDataAllocate(ModelData* model) { .occlusionStrength = 1.f, .normalScale = 1.f, .alphaCutoff = 0.f, + .color = { 1.f, 1.f, 1.f, 1.f }, + .glow = { 0.f, 0.f, 0.f, 1.f }, + .quad = { 0.f, 0.f, 1.f, 1.f }, .texture = ~0u, .glowTexture = ~0u, .metalnessTexture = ~0u, diff --git a/src/modules/data/modelData.h b/src/modules/data/modelData.h index 6843151e0b..7702cb03ac 100644 --- a/src/modules/data/modelData.h +++ b/src/modules/data/modelData.h @@ -74,11 +74,6 @@ typedef struct { } ModelMesh; typedef struct { - float color[4]; - float glow[4]; - float uvShift[2]; - float uvScale[2]; - float sdfRange[2]; float metalness; float roughness; float clearcoat; @@ -86,6 +81,10 @@ typedef struct { float occlusionStrength; float normalScale; float alphaCutoff; + float color[4]; + float glow[4]; + float quad[4]; + float sdfRange[2]; uint32_t texture; uint32_t glowTexture; uint32_t metalnessTexture; diff --git a/src/modules/data/modelData_gltf.c b/src/modules/data/modelData_gltf.c index 8786522fe0..d2ff85c6c1 100644 --- a/src/modules/data/modelData_gltf.c +++ b/src/modules/data/modelData_gltf.c @@ -179,12 +179,12 @@ static jsmntok_t* nomTexture(const char* json, jsmntok_t* token, ModelMaterial* gltfString key = NOM_STR(json, token); if (STR_EQ(key, "offset")) { token++; // Enter array - material->uvShift[0] = NOM_FLOAT(json, token); - material->uvShift[1] = NOM_FLOAT(json, token); + material->quad[0] = NOM_FLOAT(json, token); + material->quad[1] = NOM_FLOAT(json, token); } else if (STR_EQ(key, "scale")) { token++; // Enter array - material->uvScale[0] = NOM_FLOAT(json, token); - material->uvScale[1] = NOM_FLOAT(json, token); + material->quad[2] = NOM_FLOAT(json, token); + material->quad[3] = NOM_FLOAT(json, token); } else { token = NOM(token); } diff --git a/src/modules/data/modelData_obj.c b/src/modules/data/modelData_obj.c index c1ce151aec..0a7babf9ca 100644 --- a/src/modules/data/modelData_obj.c +++ b/src/modules/data/modelData_obj.c @@ -58,10 +58,6 @@ static bool parseMtl(char* path, char* base, ModelDataIO* io, arr_image_t* image if (STARTS_WITH(line, "newmtl ")) { map_set(names, hash64(line + 7, length - 7), materials->length); arr_push(materials, ((ModelMaterial) { - .color = { 1.f, 1.f, 1.f, 1.f }, - .glow = { 0.f, 0.f, 0.f, 1.f }, - .uvShift = { 0.f, 1.f }, - .uvScale = { 1.f, 1.f }, .metalness = 1.f, .roughness = 1.f, .clearcoat = 0.f, @@ -69,6 +65,9 @@ static bool parseMtl(char* path, char* base, ModelDataIO* io, arr_image_t* image .occlusionStrength = 1.f, .normalScale = 1.f, .alphaCutoff = 0.f, + .color = { 1.f, 1.f, 1.f, 1.f }, + .glow = { 0.f, 0.f, 0.f, 1.f }, + .quad = { 0.f, 1.f, 1.f, 1.f }, .texture = ~0u, .glowTexture = ~0u, .metalnessTexture = ~0u, diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index cc098d772a..012496593f 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -5467,7 +5467,7 @@ Model* lovrModelCreate(const ModelInfo* info) { lovrMaterialSetNumber(material, NUMBER_ALPHA_CUTOFF, properties->alphaCutoff); lovrMaterialSetColor(material, COLOR_BASE, properties->color); lovrMaterialSetColor(material, COLOR_GLOW, properties->glow); - lovrMaterialSetQuad(material, properties->uvShift[0], properties->uvShift[1], properties->uvScale[0], properties->uvScale[1]); + lovrMaterialSetQuad(material, properties->quad[0], properties->quad[1], properties->quad[0], properties->quad[1]); uint32_t textures[] = { [TEXTURE_COLOR] = properties->texture, From 9ac7b4fe64dc2cd7e451bfbde3a3a030f69ec1b9 Mon Sep 17 00:00:00 2001 From: bjorn Date: Sat, 15 Aug 2026 15:20:44 -0700 Subject: [PATCH 5/8] Material:is/setDoubleSided and auto cull mode; --- src/api/l_data_modelData.c | 2 ++ src/api/l_graphics_material.c | 16 ++++++++++++++++ src/api/l_graphics_pass.c | 6 ++++-- src/modules/data/modelData.h | 1 + src/modules/data/modelData_gltf.c | 2 ++ src/modules/graphics/graphics.c | 27 ++++++++++++++++++++++++--- src/modules/graphics/graphics.h | 5 ++++- 7 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/api/l_data_modelData.c b/src/api/l_data_modelData.c index cb04603445..b1a3051c69 100644 --- a/src/api/l_data_modelData.c +++ b/src/api/l_data_modelData.c @@ -630,6 +630,8 @@ static int l_lovrModelDataGetMaterial(lua_State* L) { PUSH_IMAGE(occlusionTexture); PUSH_IMAGE(normalTexture); + lua_pushboolean(L, material->doubleSided), lua_setfield(L, -2, "doubleSided"); + return 1; } diff --git a/src/api/l_graphics_material.c b/src/api/l_graphics_material.c index 11acac4b9a..6fe009e6c3 100644 --- a/src/api/l_graphics_material.c +++ b/src/api/l_graphics_material.c @@ -92,6 +92,20 @@ static int l_lovrMaterialSetQuad(lua_State* L) { return 0; } +static int l_lovrMaterialIsDoubleSided(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + bool doubleSided = lovrMaterialIsDoubleSided(material); + lua_pushboolean(L, doubleSided); + return 1; +} + +static int l_lovrMaterialSetDoubleSided(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + bool doubleSided = lua_toboolean(L, 2); + lovrMaterialSetDoubleSided(material, doubleSided); + return 0; +} + // Deprecated static int l_lovrMaterialGetProperties(lua_State* L) { Material* material = luax_checktype(L, 1, Material); @@ -170,6 +184,8 @@ const luaL_Reg lovrMaterial[] = { { "setTexture", l_lovrMaterialSetTexture }, { "getQuad", l_lovrMaterialGetQuad }, { "setQuad", l_lovrMaterialSetQuad }, + { "isDoubleSided", l_lovrMaterialIsDoubleSided }, + { "setDoubleSided", l_lovrMaterialSetDoubleSided }, // Deprecated { "getProperties", l_lovrMaterialGetProperties }, diff --git a/src/api/l_graphics_pass.c b/src/api/l_graphics_pass.c index b45a548e7e..7aa93d9976 100644 --- a/src/api/l_graphics_pass.c +++ b/src/api/l_graphics_pass.c @@ -617,10 +617,12 @@ static int l_lovrPassSetDepthClamp(lua_State* L) { static int l_lovrPassSetFaceCull(lua_State* L) { Pass* pass = luax_checktype(L, 1, Pass); CullMode mode; - if (lua_type(L, 2) == LUA_TBOOLEAN) { + if (lua_isnoneornil(L, 2)) { + mode = CULL_AUTO; + } else if (lua_type(L, 2) == LUA_TBOOLEAN) { mode = lua_toboolean(L, 2) ? CULL_BACK : CULL_NONE; } else { - mode = luax_checkenum(L, 2, CullMode, "none"); + mode = luax_checkenum(L, 2, CullMode, NULL); } lovrPassSetFaceCull(pass, mode); return 0; diff --git a/src/modules/data/modelData.h b/src/modules/data/modelData.h index 7702cb03ac..8666b6f84a 100644 --- a/src/modules/data/modelData.h +++ b/src/modules/data/modelData.h @@ -92,6 +92,7 @@ typedef struct { uint32_t clearcoatTexture; uint32_t occlusionTexture; uint32_t normalTexture; + bool doubleSided; const char* name; } ModelMaterial; diff --git a/src/modules/data/modelData_gltf.c b/src/modules/data/modelData_gltf.c index d2ff85c6c1..b9a2c661d8 100644 --- a/src/modules/data/modelData_gltf.c +++ b/src/modules/data/modelData_gltf.c @@ -1049,6 +1049,8 @@ bool lovrModelDataInitGltf(ModelData** result, Blob* source, ModelDataIO* io) { material->glow[2] = NOM_FLOAT(json, token); } else if (STR_EQ(key, "alphaCutoff")) { material->alphaCutoff = NOM_FLOAT(json, token); + } else if (STR_EQ(key, "doubleSided")) { + material->doubleSided = NOM_BOOL(json, token); } else if (STR_EQ(key, "name")) { gltfString name = NOM_STR(json, token); meta->materialLookup[material - meta->materials] = (uint32_t) hash64(name.data, name.length); diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 012496593f..1543f484ad 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -207,6 +207,7 @@ struct Material { uint32_t index; MaterialBlock* block; MaterialData data; + bool doubleSided; Texture* textures[TEXTURE_COUNT]; gpu_binding bindings[1 + TEXTURE_COUNT]; gpu_bundle* bundle; @@ -453,6 +454,7 @@ typedef struct { typedef struct { bool dirty; bool viewCull; + CullMode faceCull; DrawMode mode; float color[4]; Buffer* lastVertexBuffer; @@ -4391,6 +4393,14 @@ bool lovrMaterialSetQuad(Material* material, float ox, float oy, float sx, float return true; } +bool lovrMaterialIsDoubleSided(Material* material) { + return material->doubleSided; +} + +void lovrMaterialSetDoubleSided(Material* material, bool doubleSided) { + material->doubleSided = doubleSided; +} + static bool lovrMaterialSetSDFRange(Material* material, float x, float y) { if (!lovrMaterialUpload(material)) return false; material->data.sdfRange[0] = x; @@ -5467,7 +5477,8 @@ Model* lovrModelCreate(const ModelInfo* info) { lovrMaterialSetNumber(material, NUMBER_ALPHA_CUTOFF, properties->alphaCutoff); lovrMaterialSetColor(material, COLOR_BASE, properties->color); lovrMaterialSetColor(material, COLOR_GLOW, properties->glow); - lovrMaterialSetQuad(material, properties->quad[0], properties->quad[1], properties->quad[0], properties->quad[1]); + lovrMaterialSetQuad(material, properties->quad[0], properties->quad[1], properties->quad[2], properties->quad[3]); + lovrMaterialSetDoubleSided(material, properties->doubleSided); uint32_t textures[] = { [TEXTURE_COLOR] = properties->texture, @@ -6827,6 +6838,7 @@ void lovrPassReset(Pass* pass) { pass->pipelineIndex = 0; memset(pass->pipeline, 0, sizeof(Pipeline)); + pass->pipeline->faceCull = CULL_AUTO; pass->pipeline->mode = DRAW_TRIANGLES; pass->pipeline->lastVertexFormat = ~0u; pass->pipeline->color[0] = 1.f; @@ -7430,8 +7442,11 @@ void lovrPassSetDepthClamp(Pass* pass, bool clamp) { } void lovrPassSetFaceCull(Pass* pass, CullMode mode) { - pass->pipeline->dirty |= pass->pipeline->info.rasterizer.cullMode != (gpu_cull_mode) mode; - pass->pipeline->info.rasterizer.cullMode = (gpu_cull_mode) mode; + if (mode != CULL_AUTO) { + pass->pipeline->dirty |= pass->pipeline->info.rasterizer.cullMode != (gpu_cull_mode) mode; + pass->pipeline->info.rasterizer.cullMode = (gpu_cull_mode) mode; + } + pass->pipeline->faceCull = mode; } void lovrPassSetFont(Pass* pass, Font* font) { @@ -7845,6 +7860,12 @@ static void lovrPassResolvePipeline(Pass* pass, DrawInfo* info, Draw* draw, Draw pipeline->dirty = true; } + if (pipeline->faceCull == CULL_AUTO) { + gpu_cull_mode cullMode = draw->material->doubleSided ? GPU_CULL_NONE : GPU_CULL_BACK; + pipeline->dirty |= cullMode != pipeline->info.rasterizer.cullMode; + pipeline->info.rasterizer.cullMode = cullMode; + } + // Vertex formats if (info->vertex.buffer && pipeline->lastVertexBuffer != info->vertex.buffer) { pipeline->lastVertexFormat = ~0u; diff --git a/src/modules/graphics/graphics.h b/src/modules/graphics/graphics.h index 2d3507480b..2ef05cc005 100644 --- a/src/modules/graphics/graphics.h +++ b/src/modules/graphics/graphics.h @@ -417,6 +417,8 @@ Texture* lovrMaterialGetTexture(Material* material, MaterialTexture key); bool lovrMaterialSetTexture(Material* material, MaterialTexture key, Texture* texture); void lovrMaterialGetQuad(Material* material, float* ox, float* oy, float* sx, float* sy); bool lovrMaterialSetQuad(Material* material, float ox, float oy, float sx, float sy); +bool lovrMaterialIsDoubleSided(Material* material); +void lovrMaterialSetDoubleSided(Material* material, bool doubleSided); // Font @@ -675,7 +677,8 @@ typedef struct { typedef enum { CULL_NONE, CULL_FRONT, - CULL_BACK + CULL_BACK, + CULL_AUTO } CullMode; typedef enum { From aaa7c786c79d92a247fd04869ecceb7eab59344d Mon Sep 17 00:00:00 2001 From: bjorn Date: Sat, 15 Aug 2026 17:52:11 -0700 Subject: [PATCH 6/8] Update changelog; --- CHANGES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index bc403c6361..27558d9054 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,7 +20,9 @@ dev - Add `sleep` callback to `World:setCallbacks`. - Add `linearDamping` and `angularDamping` settings to `lovr.physics.newWorld`. - Add `lovr.headset.getHandPosition/Orientation/Pose`. -- Add `Material:get/setNumber`, `Material:get/setColor`, `Material:get/setTexture`, and `Material:get/setQuad`. +- Add `Material:get/setNumber`, `Material:get/setColor`, and `Material:get/setTexture`. +- Add `Material:get/setQuad`. +- Add `Material:is/setDoubleSided`. ### Change From 88bdd24245355e4a58affc4dd3fcaa78ca7bd934 Mon Sep 17 00:00:00 2001 From: bjorn Date: Fri, 21 Aug 2026 13:19:57 -0700 Subject: [PATCH 7/8] Disable culling by default; --- src/modules/graphics/graphics.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 1543f484ad..4b73a6608c 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -6838,7 +6838,6 @@ void lovrPassReset(Pass* pass) { pass->pipelineIndex = 0; memset(pass->pipeline, 0, sizeof(Pipeline)); - pass->pipeline->faceCull = CULL_AUTO; pass->pipeline->mode = DRAW_TRIANGLES; pass->pipeline->lastVertexFormat = ~0u; pass->pipeline->color[0] = 1.f; From 12cb8a95190b1b741e6282e60d6b75e962461ad9 Mon Sep 17 00:00:00 2001 From: bjorn Date: Fri, 28 Aug 2026 14:23:55 -0700 Subject: [PATCH 8/8] Refactoring; --- CHANGES.md | 8 +- etc/shaders/lovr.glsl | 8 +- src/api/api.h | 2 - src/api/l_data_modelData.c | 16 ++ src/api/l_graphics.c | 204 +++++++++--------- src/api/l_graphics_material.c | 265 ++++++++++++++++------- src/api/l_graphics_pass.c | 6 +- src/modules/data/modelData.c | 6 +- src/modules/data/modelData.h | 10 +- src/modules/data/modelData_obj.c | 6 +- src/modules/graphics/graphics.c | 354 +++++++++++++++++-------------- src/modules/graphics/graphics.h | 54 +++-- 12 files changed, 550 insertions(+), 389 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 27558d9054..9a341c8685 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,9 +20,9 @@ dev - Add `sleep` callback to `World:setCallbacks`. - Add `linearDamping` and `angularDamping` settings to `lovr.physics.newWorld`. - Add `lovr.headset.getHandPosition/Orientation/Pose`. -- Add `Material:get/setNumber`, `Material:get/setColor`, and `Material:get/setTexture`. -- Add `Material:get/setQuad`. -- Add `Material:is/setDoubleSided`. +- Add getters and setters for `Material` properties. +- Add `doubleSided` property to `Material`. +- Add `Model:setMaterial`. ### Change @@ -42,6 +42,8 @@ dev - Change `lovr.graphics.newBuffer` to accept fields without names. - Change `Font:getLines` wrap parameter to default to no wrap limit. - Change `lovr.headset.isVisible` to also return whether the main session is visible, for overlays. +- Change `lovr.graphics.newMaterial` to also take a `Texture` or an `Image`. +- Change `uvShift` and `uvScale` material properties to be named `quad`. ### Fix diff --git a/etc/shaders/lovr.glsl b/etc/shaders/lovr.glsl index 485bd505d3..6e240484c3 100644 --- a/etc/shaders/lovr.glsl +++ b/etc/shaders/lovr.glsl @@ -38,6 +38,10 @@ layout(set = 0, binding = 2) uniform DrawBuffer { layout(row_major) Draw Draws[2 layout(set = 0, binding = 3) uniform sampler Sampler; struct MaterialData { + vec4 color; + vec4 glow; + vec4 quad; + vec2 sdfRange; float metalness; float roughness; float clearcoat; @@ -45,10 +49,6 @@ struct MaterialData { float occlusionStrength; float normalScale; float alphaCutoff; - vec4 color; - vec4 glow; - vec4 quad; - vec2 sdfRange; }; layout(set = 1, binding = 0) uniform MaterialBuffer { diff --git a/src/api/api.h b/src/api/api.h index 3a355cc8a6..5eda209758 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -51,8 +51,6 @@ extern StringEntry lovrHorizontalAlign[]; extern StringEntry lovrJointType[]; extern StringEntry lovrKeyboardKey[]; extern StringEntry lovrLayerType[]; -extern StringEntry lovrMaterialColor[]; -extern StringEntry lovrMaterialNumber[]; extern StringEntry lovrMaterialTexture[]; extern StringEntry lovrMeshStorage[]; extern StringEntry lovrModelDrawMode[]; diff --git a/src/api/l_data_modelData.c b/src/api/l_data_modelData.c index b1a3051c69..b1cc38c51b 100644 --- a/src/api/l_data_modelData.c +++ b/src/api/l_data_modelData.c @@ -602,6 +602,22 @@ static int l_lovrModelDataGetMaterial(lua_State* L) { lua_rawseti(L, -2, 4); lua_setfield(L, -2, "glow"); + // Deprecated + lua_createtable(L, 2, 0); + lua_pushnumber(L, material->quad[0]); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, material->quad[1]); + lua_rawseti(L, -2, 2); + lua_setfield(L, -2, "uvShift"); + + // Deprecated + lua_createtable(L, 2, 0); + lua_pushnumber(L, material->quad[2]); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, material->quad[3]); + lua_rawseti(L, -2, 2); + lua_setfield(L, -2, "uvScale"); + lua_createtable(L, 4, 0); lua_pushnumber(L, material->quad[0]); lua_rawseti(L, -2, 1); diff --git a/src/api/l_graphics.c b/src/api/l_graphics.c index aee43b56a8..b12a2b6c8f 100644 --- a/src/api/l_graphics.c +++ b/src/api/l_graphics.c @@ -163,23 +163,6 @@ StringEntry lovrHorizontalAlign[] = { { 0 } }; -StringEntry lovrMaterialColor[] = { - [COLOR_BASE] = ENTRY("base"), - [COLOR_GLOW] = ENTRY("glow"), - { 0 } -}; - -StringEntry lovrMaterialNumber[] = { - [NUMBER_METALNESS] = ENTRY("metalness"), - [NUMBER_ROUGHNESS] = ENTRY("roughness"), - [NUMBER_CLEARCOAT] = ENTRY("clearcoat"), - [NUMBER_CLEARCOAT_ROUGHNESS] = ENTRY("clearcoatroughness"), - [NUMBER_OCCLUSION_STRENGTH] = ENTRY("occlusionstrength"), - [NUMBER_NORMAL_SCALE] = ENTRY("normalscale"), - [NUMBER_ALPHA_CUTOFF] = ENTRY("alphacutoff"), - { 0 } -}; - StringEntry lovrMaterialTexture[] = { [TEXTURE_COLOR] = ENTRY("color"), [TEXTURE_GLOW] = ENTRY("glow"), @@ -1487,97 +1470,124 @@ static Texture* luax_opttexture(lua_State* L, int index) { } static int l_lovrGraphicsNewMaterial(lua_State* L) { - Material* material = lovrMaterialCreate(luax_totype(L, 1, Texture)); + if (lua_isuserdata(L, 1)) { + Material* material = lovrMaterialCreate(luax_opttexture(L, 1)); + luax_assert(L, material); + luax_pushtype(L, Material, material); + lovrRelease(material, lovrMaterialDestroy); + return 1; + } else if (!lua_istable(L, 1)) { + return luax_typeerror(L, 1, "Image, Texture, or table"); + } + + Material* material = lovrMaterialCreate(NULL); luax_assert(L, material); - if (lua_istable(L, 1)) { - for (uint32_t i = 0; i < NUMBER_COUNT; i++) { - lua_pushlstring(L, lovrMaterialNumber[i].string, lovrMaterialNumber[i].length); - lua_gettable(L, 1); - if (!lua_isnil(L, -1)) lovrMaterialSetNumber(material, i, luax_checkfloat(L, -1)); - lua_pop(L, 1); - } + lua_getfield(L, 1, "color"); + if (!lua_isnil(L, -1)) { + float color[4]; + luax_optcolor(L, -1, color); + lovrMaterialSetColor(material, color); + } + lua_pop(L, 1); - for (uint32_t i = 0; i < COLOR_COUNT; i++) { - if (i == 0) { - lua_pushliteral(L, "color"); - } else { - lua_pushlstring(L, lovrMaterialColor[i].string, lovrMaterialColor[i].length); - } + lua_getfield(L, 1, "glow"); + if (!lua_isnil(L, -1)) { + float glow[4]; + luax_optcolor(L, -1, glow); + lovrMaterialSetGlow(material, glow); + } + lua_pop(L, 1); - lua_gettable(L, 1); - if (!lua_isnil(L, -1)) { - float color[4]; - luax_optcolor(L, -1, color); - lovrMaterialSetColor(material, i, color); - } - lua_pop(L, 1); - } + float quad[4] = { 0.f, 0.f, 1.f, 1.f }; - for (uint32_t i = 0; i < TEXTURE_COUNT; i++) { - if (i == 0) { - lua_pushliteral(L, "texture"); - } else { - char key[64]; - size_t length = lovrMaterialTexture[i].length; - memcpy(key, lovrMaterialTexture[i].string, length); - memcpy(key + length, "Texture", strlen("Texture")); - lua_pushlstring(L, key, length + strlen("Texture")); - } - lua_gettable(L, 1); - if (!lua_isnil(L, -1)) { - Texture* texture = luax_checktype(L, -1, Texture); - if (!lovrMaterialSetTexture(material, i, texture)) { - lovrRelease(material, lovrMaterialDestroy); - luax_throw(L); - } - } - lua_pop(L, 1); - } + // Deprecated + lua_getfield(L, 1, "uvShift"); + if (lua_type(L, -1) == LUA_TNUMBER) { + float shift = lua_tonumber(L, -1); + quad[0] = shift; + quad[1] = shift; + } else if (lua_type(L, -1) == LUA_TTABLE) { + lua_rawgeti(L, -1, 1); + lua_rawgeti(L, -2, 2); + quad[0] = luax_optfloat(L, -2, 0.f); + quad[1] = luax_optfloat(L, -1, 0.f); + lua_pop(L, 2); + } + lua_pop(L, 1); + + // Deprecated + lua_getfield(L, 1, "uvScale"); + if (lua_isnumber(L, -1)) { + float scale = lua_tonumber(L, -1); + quad[2] = scale; + quad[3] = scale; + } else if (lua_type(L, -1) == LUA_TTABLE) { + lua_rawgeti(L, -1, 1); + lua_rawgeti(L, -2, 2); + quad[2] = luax_optfloat(L, -2, 1.f); + quad[3] = luax_optfloat(L, -1, 1.f); + lua_pop(L, 2); + } + lua_pop(L, 1); - float quad[4] = { 0.f, 0.f, 1.f, 1.f }; + lua_getfield(L, 1, "quad"); + if (lua_istable(L, -1)) { + lua_rawgeti(L, -1, 1); + lua_rawgeti(L, -2, 2); + lua_rawgeti(L, -3, 3); + lua_rawgeti(L, -4, 4); + quad[0] = luax_optfloat(L, -4, 0.f); + quad[1] = luax_optfloat(L, -3, 0.f); + quad[2] = luax_optfloat(L, -2, 1.f); + quad[3] = luax_optfloat(L, -1, 1.f); + lua_pop(L, 4); + } + lua_pop(L, 1); - // Deprecated - lua_getfield(L, 1, "uvShift"); - if (lua_istable(L, -1)) { - lua_rawgeti(L, -1, 1); - lua_rawgeti(L, -2, 2); - quad[0] = luax_optfloat(L, -2, 0.f); - quad[1] = luax_optfloat(L, -1, 0.f); - lua_pop(L, 2); - } else if (lua_type(L, -1) == LUA_TNUMBER) { - quad[0] = quad[1] = lua_tonumber(L, -1); - } - lua_pop(L, 1); + lovrMaterialSetQuad(material, quad); - // Deprecated - lua_getfield(L, 1, "uvScale"); - if (lua_istable(L, -1)) { - lua_rawgeti(L, -1, 1); - lua_rawgeti(L, -2, 2); - quad[2] = luax_optfloat(L, -2, 1.f); - quad[3] = luax_optfloat(L, -1, 1.f); - lua_pop(L, 2); - } else if (lua_type(L, -1) == LUA_TNUMBER) { - quad[2] = quad[3] = lua_tonumber(L, -1); - } - lua_pop(L, 1); + lua_getfield(L, 1, "metalness"); + if (!lua_isnil(L, -1)) lovrMaterialSetMetalness(material, luax_tofloat(L, -1)); + lua_pop(L, 1); - lua_getfield(L, 1, "quad"); - if (lua_istable(L, -1)) { - lua_rawgeti(L, -1, 1); - lua_rawgeti(L, -2, 2); - lua_rawgeti(L, -3, 3); - lua_rawgeti(L, -4, 4); - quad[0] = luax_optfloat(L, -4, 0.f); - quad[1] = luax_optfloat(L, -3, 0.f); - quad[2] = luax_optfloat(L, -2, 1.f); - quad[3] = luax_optfloat(L, -1, 1.f); - lua_pop(L, 4); + lua_getfield(L, 1, "roughness"); + if (!lua_isnil(L, -1)) lovrMaterialSetRoughness(material, luax_tofloat(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, 1, "clearcoat"); + if (!lua_isnil(L, -1)) lovrMaterialSetClearcoat(material, luax_tofloat(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, 1, "clearcoatRoughness"); + if (!lua_isnil(L, -1)) lovrMaterialSetClearcoatRoughness(material, luax_tofloat(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, 1, "occlusionStrength"); + if (!lua_isnil(L, -1)) lovrMaterialSetOcclusionStrength(material, luax_tofloat(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, 1, "normalScale"); + if (!lua_isnil(L, -1)) lovrMaterialSetNormalScale(material, luax_tofloat(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, 1, "alphaCutoff"); + if (!lua_isnil(L, -1)) lovrMaterialSetAlphaCutoff(material, luax_tofloat(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, 1, "doubleSided"); + lovrMaterialSetDoubleSided(material, lua_toboolean(L, -1)); + lua_pop(L, 1); + + for (uint32_t i = 0; i < MAX_MATERIAL_TEXTURES; i++) { + if (i == 0) { + lua_pushliteral(L, "texture"); + } else { + lua_pushfstring(L, "%sTexture", lovrMaterialTexture[i].string); } + lua_gettable(L, 1); + if (!lua_isnil(L, -1)) lovrMaterialSetTexture(material, i, luax_opttexture(L, -1)); lua_pop(L, 1); - - lovrMaterialSetQuad(material, quad[0], quad[1], quad[2], quad[3]); } luax_pushtype(L, Material, material); diff --git a/src/api/l_graphics_material.c b/src/api/l_graphics_material.c index 6fe009e6c3..77878d12dd 100644 --- a/src/api/l_graphics_material.c +++ b/src/api/l_graphics_material.c @@ -17,26 +17,29 @@ Material* luax_optmaterial(lua_State* L, int index) { } } -static int l_lovrMaterialGetNumber(lua_State* L) { +static int l_lovrMaterialGetColor(lua_State* L) { Material* material = luax_checktype(L, 1, Material); - MaterialNumber key = luax_checkenum(L, 2, MaterialNumber, NULL); - float number = lovrMaterialGetNumber(material, key); - lua_pushnumber(L, number); - return 1; + float color[4]; + lovrMaterialGetColor(material, 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_lovrMaterialSetNumber(lua_State* L) { +static int l_lovrMaterialSetColor(lua_State* L) { Material* material = luax_checktype(L, 1, Material); - MaterialNumber key = luax_checkenum(L, 2, MaterialNumber, NULL); - float number = luax_checkfloat(L, 3); - luax_assert(L, lovrMaterialSetNumber(material, key, number)); + float color[4]; + luax_readcolor(L, 2, color); + luax_assert(L, lovrMaterialSetColor(material, color)); return 0; } -static int l_lovrMaterialGetColor(lua_State* L) { +static int l_lovrMaterialGetGlow(lua_State* L) { Material* material = luax_checktype(L, 1, Material); - MaterialColor key = luax_checkenum(L, 2, MaterialColor, "base"); - const float* color = lovrMaterialGetColor(material, key); + float color[4]; + lovrMaterialGetGlow(material, color); lua_pushnumber(L, color[0]); lua_pushnumber(L, color[1]); lua_pushnumber(L, color[2]); @@ -44,58 +47,123 @@ static int l_lovrMaterialGetColor(lua_State* L) { return 4; } -static int l_lovrMaterialSetColor(lua_State* L) { +static int l_lovrMaterialSetGlow(lua_State* L) { Material* material = luax_checktype(L, 1, Material); - int index = 2; - float color[4]; - MaterialColor key = lua_type(L, index) == LUA_TSTRING ? luax_checkenum(L, index++, MaterialColor, NULL) : COLOR_BASE; - luax_readcolor(L, index, color); - luax_assert(L, lovrMaterialSetColor(material, key, color)); + float glow[4]; + luax_readcolor(L, 2, glow); + luax_assert(L, lovrMaterialSetGlow(material, glow)); return 0; } -static int l_lovrMaterialGetTexture(lua_State* L) { +static int l_lovrMaterialGetQuad(lua_State* L) { Material* material = luax_checktype(L, 1, Material); - MaterialTexture key = luax_checkenum(L, 2, MaterialTexture, "color"); - Texture* texture = lovrMaterialGetTexture(material, key); - luax_pushtype(L, Texture, texture); + float quad[4]; + lovrMaterialGetQuad(material, quad); + lua_pushnumber(L, quad[0]); + lua_pushnumber(L, quad[1]); + lua_pushnumber(L, quad[2]); + lua_pushnumber(L, quad[3]); + return 4; +} + +static int l_lovrMaterialSetQuad(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + float quad[4]; + quad[0] = luax_checkfloat(L, 2); + quad[1] = luax_checkfloat(L, 3); + quad[2] = luax_checkfloat(L, 4); + quad[3] = luax_checkfloat(L, 5); + lovrMaterialSetQuad(material, quad); + return 0; +} + +static int l_lovrMaterialGetMetalness(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + lua_pushnumber(L, lovrMaterialGetMetalness(material)); return 1; } -static int l_lovrMaterialSetTexture(lua_State* L) { +static int l_lovrMaterialSetMetalness(lua_State * L) { Material* material = luax_checktype(L, 1, Material); - int index = 2; - MaterialTexture key = lua_type(L, index) == LUA_TSTRING ? luax_checkenum(L, index++, MaterialTexture, NULL) : TEXTURE_COLOR; - Texture* texture = luax_checktype(L, index, Texture); - luax_assert(L, lovrMaterialSetTexture(material, key, texture)); + luax_assert(L, lovrMaterialSetMetalness(material, luax_checkfloat(L, 2))); return 0; } -static int l_lovrMaterialGetQuad(lua_State* L) { +static int l_lovrMaterialGetRoughness(lua_State * L) { Material* material = luax_checktype(L, 1, Material); - float ox, oy, sx, sy; - lovrMaterialGetQuad(material, &ox, &oy, &sx, &sy); - lua_pushnumber(L, ox); - lua_pushnumber(L, oy); - lua_pushnumber(L, sx); - lua_pushnumber(L, sy); - return 4; + lua_pushnumber(L, lovrMaterialGetRoughness(material)); + return 1; } -static int l_lovrMaterialSetQuad(lua_State* L) { +static int l_lovrMaterialSetRoughness(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + luax_assert(L, lovrMaterialSetRoughness(material, luax_checkfloat(L, 2))); + return 0; +} + +static int l_lovrMaterialGetClearcoat(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + lua_pushnumber(L, lovrMaterialGetClearcoat(material)); + return 1; +} + +static int l_lovrMaterialSetClearcoat(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + luax_assert(L, lovrMaterialSetClearcoat(material, luax_checkfloat(L, 2))); + return 0; +} + +static int l_lovrMaterialGetClearcoatRoughness(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + lua_pushnumber(L, lovrMaterialGetClearcoatRoughness(material)); + return 1; +} + +static int l_lovrMaterialSetClearcoatRoughness(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + luax_assert(L, lovrMaterialSetClearcoatRoughness(material, luax_checkfloat(L, 2))); + return 0; +} + +static int l_lovrMaterialGetOcclusionStrength(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + lua_pushnumber(L, lovrMaterialGetOcclusionStrength(material)); + return 1; +} + +static int l_lovrMaterialSetOcclusionStrength(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + luax_assert(L, lovrMaterialSetOcclusionStrength(material, luax_checkfloat(L, 2))); + return 0; +} + +static int l_lovrMaterialGetNormalScale(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + lua_pushnumber(L, lovrMaterialGetNormalScale(material)); + return 1; +} + +static int l_lovrMaterialSetNormalScale(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + luax_assert(L, lovrMaterialSetNormalScale(material, luax_checkfloat(L, 2))); + return 0; +} + +static int l_lovrMaterialGetAlphaCutoff(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + lua_pushnumber(L, lovrMaterialGetAlphaCutoff(material)); + return 1; +} + +static int l_lovrMaterialSetAlphaCutoff(lua_State * L) { Material* material = luax_checktype(L, 1, Material); - float ox = luax_checkfloat(L, 2); - float oy = luax_checkfloat(L, 3); - float sx = luax_checkfloat(L, 4); - float sy = luax_checkfloat(L, 5); - lovrMaterialSetQuad(material, ox, oy, sx, sy); + luax_assert(L, lovrMaterialSetAlphaCutoff(material, luax_checkfloat(L, 2))); return 0; } static int l_lovrMaterialIsDoubleSided(lua_State* L) { Material* material = luax_checktype(L, 1, Material); - bool doubleSided = lovrMaterialIsDoubleSided(material); - lua_pushboolean(L, doubleSided); + lua_pushboolean(L, lovrMaterialIsDoubleSided(material)); return 1; } @@ -106,23 +174,24 @@ static int l_lovrMaterialSetDoubleSided(lua_State* L) { return 0; } -// Deprecated -static int l_lovrMaterialGetProperties(lua_State* L) { +static int l_lovrMaterialGetTexture(lua_State* L) { Material* material = luax_checktype(L, 1, Material); + MaterialTexture type = luax_checkenum(L, 2, MaterialTexture, "color"); + Texture* texture = lovrMaterialGetTexture(material, type); + luax_pushtype(L, Texture, texture); + return 1; +} - lua_newtable(L); - - lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_METALNESS)), lua_setfield(L, -2, "metalness"); - lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_ROUGHNESS)), lua_setfield(L, -2, "roughness"); - lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_CLEARCOAT)), lua_setfield(L, -2, "clearcoat"); - lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_CLEARCOAT_ROUGHNESS)), lua_setfield(L, -2, "clearcoatRoughness"); - lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_OCCLUSION_STRENGTH)), lua_setfield(L, -2, "occlusionStrength"); - lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_NORMAL_SCALE)), lua_setfield(L, -2, "normalScale"); - lua_pushnumber(L, lovrMaterialGetNumber(material, NUMBER_ALPHA_CUTOFF)), lua_setfield(L, -2, "alphaCutoff"); - - const float* color; - color = lovrMaterialGetColor(material, COLOR_BASE); +static int l_lovrMaterialSetTexture(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + int index = 2; + MaterialTexture type = lua_type(L, index) == LUA_TSTRING ? luax_checkenum(L, index++, MaterialTexture, NULL) : TEXTURE_COLOR; + Texture* texture = luax_checktype(L, index, Texture); + luax_assert(L, lovrMaterialSetTexture(material, type, texture)); + return 0; +} +static void luax_pushcolor(lua_State* L, const float color[4]) { lua_createtable(L, 4, 0); lua_pushnumber(L, color[0]); lua_rawseti(L, -2, 1); @@ -132,38 +201,61 @@ static int l_lovrMaterialGetProperties(lua_State* L) { lua_rawseti(L, -2, 3); lua_pushnumber(L, color[3]); lua_rawseti(L, -2, 4); - lua_setfield(L, -2, "color"); +} - color = lovrMaterialGetColor(material, COLOR_GLOW); +static int l_lovrMaterialGetProperties(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); - lua_createtable(L, 4, 0); - lua_pushnumber(L, color[0]); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, color[1]); - lua_rawseti(L, -2, 2); - lua_pushnumber(L, color[2]); - lua_rawseti(L, -2, 3); - lua_pushnumber(L, color[3]); - lua_rawseti(L, -2, 4); + lua_newtable(L); + + float color[4]; + lovrMaterialGetColor(material, color); + luax_pushcolor(L, color); + lua_setfield(L, -2, "color"); + + lovrMaterialGetGlow(material, color); + luax_pushcolor(L, color); lua_setfield(L, -2, "glow"); - float ox, oy, sx, sy; - lovrMaterialGetQuad(material, &ox, &oy, &sx, &sy); + float quad[4]; + lovrMaterialGetQuad(material, quad); + // Deprecated lua_createtable(L, 2, 0); - lua_pushnumber(L, ox); + lua_pushnumber(L, quad[0]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, oy); + lua_pushnumber(L, quad[1]); lua_rawseti(L, -2, 2); lua_setfield(L, -2, "uvShift"); + // Deprecated lua_createtable(L, 2, 0); - lua_pushnumber(L, sx); + lua_pushnumber(L, quad[2]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, sy); + lua_pushnumber(L, quad[3]); lua_rawseti(L, -2, 2); lua_setfield(L, -2, "uvScale"); + lua_createtable(L, 4, 0); + lua_pushnumber(L, quad[0]); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, quad[1]); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, quad[2]); + lua_rawseti(L, -2, 3); + lua_pushnumber(L, quad[3]); + lua_rawseti(L, -2, 4); + lua_setfield(L, -2, "quad"); + + lua_pushnumber(L, lovrMaterialGetMetalness(material)), lua_setfield(L, -2, "metalness"); + lua_pushnumber(L, lovrMaterialGetRoughness(material)), lua_setfield(L, -2, "roughness"); + lua_pushnumber(L, lovrMaterialGetClearcoat(material)), lua_setfield(L, -2, "clearcoat"); + lua_pushnumber(L, lovrMaterialGetClearcoatRoughness(material)), lua_setfield(L, -2, "clearcoatRoughness"); + lua_pushnumber(L, lovrMaterialGetOcclusionStrength(material)), lua_setfield(L, -2, "occlusionStrength"); + lua_pushnumber(L, lovrMaterialGetNormalScale(material)), lua_setfield(L, -2, "normalScale"); + lua_pushnumber(L, lovrMaterialGetAlphaCutoff(material)), lua_setfield(L, -2, "alphaCutoff"); + lua_pushboolean(L, lovrMaterialIsDoubleSided(material)), lua_setfield(L, -2, "doubleSided"); + luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_COLOR)), lua_setfield(L, -2, "texture"); luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_GLOW)), lua_setfield(L, -2, "glowTexture"); luax_pushtype(L, Texture, lovrMaterialGetTexture(material, TEXTURE_METALNESS)), lua_setfield(L, -2, "metalnessTexture"); @@ -176,19 +268,30 @@ static int l_lovrMaterialGetProperties(lua_State* L) { } const luaL_Reg lovrMaterial[] = { - { "getNumber", l_lovrMaterialGetNumber }, - { "setNumber", l_lovrMaterialSetNumber }, { "getColor", l_lovrMaterialGetColor }, { "setColor", l_lovrMaterialSetColor }, - { "getTexture", l_lovrMaterialGetTexture }, - { "setTexture", l_lovrMaterialSetTexture }, + { "getGlow", l_lovrMaterialGetGlow }, + { "setGlow", l_lovrMaterialSetGlow }, { "getQuad", l_lovrMaterialGetQuad }, { "setQuad", l_lovrMaterialSetQuad }, + { "getMetalness", l_lovrMaterialGetMetalness }, + { "setMetalness", l_lovrMaterialSetMetalness }, + { "getRoughness", l_lovrMaterialGetRoughness }, + { "setRoughness", l_lovrMaterialSetRoughness }, + { "getClearcoat", l_lovrMaterialGetClearcoat }, + { "setClearcoat", l_lovrMaterialSetClearcoat }, + { "getClearcoatRoughness", l_lovrMaterialGetClearcoatRoughness }, + { "setClearcoatRoughness", l_lovrMaterialSetClearcoatRoughness }, + { "getOcclusionStrength", l_lovrMaterialGetOcclusionStrength }, + { "setOcclusionStrength", l_lovrMaterialSetOcclusionStrength }, + { "getNormalScale", l_lovrMaterialGetNormalScale }, + { "setNormalScale", l_lovrMaterialSetNormalScale }, + { "getAlphaCutoff", l_lovrMaterialGetAlphaCutoff }, + { "setAlphaCutoff", l_lovrMaterialSetAlphaCutoff }, { "isDoubleSided", l_lovrMaterialIsDoubleSided }, { "setDoubleSided", l_lovrMaterialSetDoubleSided }, - - // Deprecated + { "getTexture", l_lovrMaterialGetTexture }, + { "setTexture", l_lovrMaterialSetTexture }, { "getProperties", l_lovrMaterialGetProperties }, - { NULL, NULL } }; diff --git a/src/api/l_graphics_pass.c b/src/api/l_graphics_pass.c index 7aa93d9976..b45a548e7e 100644 --- a/src/api/l_graphics_pass.c +++ b/src/api/l_graphics_pass.c @@ -617,12 +617,10 @@ static int l_lovrPassSetDepthClamp(lua_State* L) { static int l_lovrPassSetFaceCull(lua_State* L) { Pass* pass = luax_checktype(L, 1, Pass); CullMode mode; - if (lua_isnoneornil(L, 2)) { - mode = CULL_AUTO; - } else if (lua_type(L, 2) == LUA_TBOOLEAN) { + if (lua_type(L, 2) == LUA_TBOOLEAN) { mode = lua_toboolean(L, 2) ? CULL_BACK : CULL_NONE; } else { - mode = luax_checkenum(L, 2, CullMode, NULL); + mode = luax_checkenum(L, 2, CullMode, "none"); } lovrPassSetFaceCull(pass, mode); return 0; diff --git a/src/modules/data/modelData.c b/src/modules/data/modelData.c index f2003e3dea..72377e7ef6 100644 --- a/src/modules/data/modelData.c +++ b/src/modules/data/modelData.c @@ -109,6 +109,9 @@ void lovrModelDataAllocate(ModelData* model) { for (uint32_t i = 0; i < meta->materialCount; i++) { meta->materials[i] = (ModelMaterial) { + .color = { 1.f, 1.f, 1.f, 1.f }, + .glow = { 0.f, 0.f, 0.f, 1.f }, + .quad = { 0.f, 0.f, 1.f, 1.f }, .metalness = 1.f, .roughness = 1.f, .clearcoat = 0.f, @@ -116,9 +119,6 @@ void lovrModelDataAllocate(ModelData* model) { .occlusionStrength = 1.f, .normalScale = 1.f, .alphaCutoff = 0.f, - .color = { 1.f, 1.f, 1.f, 1.f }, - .glow = { 0.f, 0.f, 0.f, 1.f }, - .quad = { 0.f, 0.f, 1.f, 1.f }, .texture = ~0u, .glowTexture = ~0u, .metalnessTexture = ~0u, diff --git a/src/modules/data/modelData.h b/src/modules/data/modelData.h index 8666b6f84a..3a7e8e689b 100644 --- a/src/modules/data/modelData.h +++ b/src/modules/data/modelData.h @@ -74,6 +74,10 @@ typedef struct { } ModelMesh; typedef struct { + float color[4]; + float glow[4]; + float quad[4]; + float sdfRange[2]; float metalness; float roughness; float clearcoat; @@ -81,10 +85,7 @@ typedef struct { float occlusionStrength; float normalScale; float alphaCutoff; - float color[4]; - float glow[4]; - float quad[4]; - float sdfRange[2]; + bool doubleSided; uint32_t texture; uint32_t glowTexture; uint32_t metalnessTexture; @@ -92,7 +93,6 @@ typedef struct { uint32_t clearcoatTexture; uint32_t occlusionTexture; uint32_t normalTexture; - bool doubleSided; const char* name; } ModelMaterial; diff --git a/src/modules/data/modelData_obj.c b/src/modules/data/modelData_obj.c index 0a7babf9ca..86472d57ee 100644 --- a/src/modules/data/modelData_obj.c +++ b/src/modules/data/modelData_obj.c @@ -58,6 +58,9 @@ static bool parseMtl(char* path, char* base, ModelDataIO* io, arr_image_t* image if (STARTS_WITH(line, "newmtl ")) { map_set(names, hash64(line + 7, length - 7), materials->length); arr_push(materials, ((ModelMaterial) { + .color = { 1.f, 1.f, 1.f, 1.f }, + .glow = { 0.f, 0.f, 0.f, 1.f }, + .quad = { 0.f, 1.f, 1.f, 1.f }, .metalness = 1.f, .roughness = 1.f, .clearcoat = 0.f, @@ -65,9 +68,6 @@ static bool parseMtl(char* path, char* base, ModelDataIO* io, arr_image_t* image .occlusionStrength = 1.f, .normalScale = 1.f, .alphaCutoff = 0.f, - .color = { 1.f, 1.f, 1.f, 1.f }, - .glow = { 0.f, 0.f, 0.f, 1.f }, - .quad = { 0.f, 1.f, 1.f, 1.f }, .texture = ~0u, .glowTexture = ~0u, .metalnessTexture = ~0u, diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 4b73a6608c..3e4b53542e 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -178,14 +179,6 @@ struct Shader { char* names; }; -typedef struct { - float numbers[NUMBER_COUNT]; - float padding; - float colors[COLOR_COUNT][4]; - float quad[4]; - float sdfRange[2]; -} MaterialData; - typedef struct { uint32_t next; uint32_t tick; @@ -202,18 +195,34 @@ typedef struct { gpu_bundle* bundles; } MaterialBlock; +typedef struct { + float color[4]; + float glow[4]; + float quad[4]; + float sdfRange[2]; + float metalness; + float roughness; + float clearcoat; + float clearcoatRoughness; + float occlusionStrength; + float normalScale; + float alphaCutoff; + bool doubleSided; // Doesn't need to go here, but can hide in padding bytes for now +} MaterialData; + +static_assert(offsetof(MaterialData, alphaCutoff) == offsetof(ModelMaterial, alphaCutoff), "MaterialData != ModelMaterial"); + struct Material { atomic_uint ref; uint32_t index; MaterialBlock* block; + uint32_t bufferTick; + uint32_t bundleTick; MaterialData data; - bool doubleSided; - Texture* textures[TEXTURE_COUNT]; - gpu_binding bindings[1 + TEXTURE_COUNT]; - gpu_bundle* bundle; MaterialData* pointer; - uint32_t bundleTick; - uint32_t bufferTick; + gpu_bundle* bundle; + gpu_binding bindings[1 + MAX_MATERIAL_TEXTURES]; + Texture* textures[MAX_MATERIAL_TEXTURES]; }; typedef struct { @@ -454,7 +463,6 @@ typedef struct { typedef struct { bool dirty; bool viewCull; - CullMode faceCull; DrawMode mode; float color[4]; Buffer* lastVertexBuffer; @@ -3111,10 +3119,7 @@ void lovrTextureSetSampler(Texture* texture, Sampler* sampler) { Material* lovrTextureToMaterial(Texture* texture) { if (!texture->material) { texture->material = lovrMaterialCreate(texture); - - if (!texture->material) { - return NULL; - } + if (!texture->material) return NULL; // Since the Material refcounts the Texture, this creates a cycle. Release the texture to make // sure this is a weak relationship (the automaterial does not keep the texture refcounted). @@ -4221,32 +4226,22 @@ static void lovrMaterialRecycle(Material* material) { mtx_unlock(&state.lock); } -static bool lovrMaterialUpload(Material* material) { - if (material->bufferTick == state.tick) { - return true; +static bool lovrMaterialUpload(Material* material, size_t offset, size_t size) { + if (material->bufferTick != state.tick) { + mtx_lock(&state.lock); + BufferView staging = getBuffer(GPU_BUFFER_STREAM, sizeof(MaterialData), 4); + if (!staging.buffer) return mtx_unlock(&state.lock), false; + gpu_copy_buffers(state.stream, staging.buffer, material->block->buffer, staging.offset, MATERIAL_STRIDE * material->index, sizeof(MaterialData)); + state.barrier.prev |= GPU_PHASE_COPY; + state.barrier.next |= GPU_PHASE_SHADER_VERTEX | GPU_PHASE_SHADER_FRAGMENT; + state.barrier.flush |= GPU_CACHE_TRANSFER_WRITE; + state.barrier.clear |= GPU_CACHE_UNIFORM; + material->bufferTick = state.tick; + material->pointer = staging.pointer; + mtx_unlock(&state.lock); } - mtx_lock(&state.lock); - BufferView staging = getBuffer(GPU_BUFFER_STREAM, sizeof(MaterialData), 4); - if (!staging.buffer) return mtx_unlock(&state.lock), false; - gpu_copy_buffers(state.stream, staging.buffer, material->block->buffer, staging.offset, MATERIAL_STRIDE * material->index, sizeof(MaterialData)); - state.barrier.prev |= GPU_PHASE_COPY; - state.barrier.next |= GPU_PHASE_SHADER_VERTEX | GPU_PHASE_SHADER_FRAGMENT; - state.barrier.flush |= GPU_CACHE_TRANSFER_WRITE; - state.barrier.clear |= GPU_CACHE_UNIFORM; - material->bufferTick = state.tick; - material->pointer = staging.pointer; - memcpy(material->pointer->numbers, &material->data.numbers, sizeof(material->data.numbers)); - for (uint32_t i = 0; i < COLOR_COUNT; i++) { - material->pointer->colors[i][0] = lovrMathGammaToLinear(material->data.colors[i][0]); - material->pointer->colors[i][1] = lovrMathGammaToLinear(material->data.colors[i][1]); - material->pointer->colors[i][2] = lovrMathGammaToLinear(material->data.colors[i][2]); - material->pointer->colors[i][3] = material->data.colors[i][3]; - } - memcpy(material->pointer->quad, material->data.quad, sizeof(material->data.quad)); - memcpy(material->pointer->sdfRange, material->data.sdfRange, sizeof(material->data.sdfRange)); - mtx_unlock(&state.lock); - + memcpy((char*) material->pointer + offset, (char*) &material->data + offset, size); return true; } @@ -4255,48 +4250,45 @@ Material* lovrMaterialCreate(Texture* texture) { material->ref = 1; material->data = (MaterialData) { - .numbers[NUMBER_OCCLUSION_STRENGTH] = 1.f, - .numbers[NUMBER_NORMAL_SCALE] = 1.f, - .colors[COLOR_BASE] = { 1.f, 1.f, 1.f, 1.f }, - .quad = { 0.f, 0.f, 1.f, 1.f } + .color = { 1.f, 1.f, 1.f, 1.f }, + .quad = { 0.f, 0.f, 1.f, 1.f }, + .metalness = 1.f, + .roughness = 1.f, + .occlusionStrength = 1.f, + .normalScale = 1.f }; - for (uint32_t i = 0; i < TEXTURE_COUNT; i++) { - Texture* t = state.defaultTexture; - material->textures[i] = t; + for (uint32_t i = 0; i < MAX_MATERIAL_TEXTURES; i++) { material->bindings[i + 1] = (gpu_binding) { .number = i + 1, .type = GPU_SLOT_SAMPLED_TEXTURE, - .texture.object = t->sampleViewFloat + .texture.object = state.defaultTexture->sampleViewFloat }; - lovrRetain(t); } + // Setting the texture takes care of allocating the material and writing the bindings, and avoids + // writing bindings twice. But it only works if the texture is non-NULL if (texture) { - // This will take care of allocating the material and writing all the bindings if (!lovrMaterialSetTexture(material, TEXTURE_COLOR, texture)) { - lovrFree(material); - return NULL; + lovrMaterialDestroy(material); + return false; } } else { if (!lovrMaterialAllocate(material)) { - lovrFree(material); - return NULL; + return false; } - gpu_bundle_info bundleInfo = { + gpu_bundle_write(&material->bundle, &(gpu_bundle_info) { .layout = state.materialLayout->gpu, .bindings = material->bindings, .count = COUNTOF(material->bindings) - }; - - gpu_bundle_write(&material->bundle, &bundleInfo, 1); + }, 1); } if (material->pointer) { memcpy(material->pointer, &material->data, sizeof(MaterialData)); } else { - lovrMaterialUpload(material); + lovrMaterialUpload(material, 0, sizeof(MaterialData)); } return material; @@ -4305,57 +4297,155 @@ Material* lovrMaterialCreate(Texture* texture) { void lovrMaterialDestroy(void* ref) { Material* material = ref; lovrMaterialRecycle(material); - for (uint32_t i = 0; i < TEXTURE_COUNT; i++) { + for (uint32_t i = 0; i < MAX_MATERIAL_TEXTURES; i++) { lovrRelease(material->textures[i], lovrTextureDestroy); } lovrFree(material); } -float lovrMaterialGetNumber(Material* material, MaterialNumber key) { - return material->data.numbers[key]; +void lovrMaterialGetColor(Material* material, float color[4]) { + color[0] = lovrMathLinearToGamma(material->data.color[0]); + color[1] = lovrMathLinearToGamma(material->data.color[1]); + color[2] = lovrMathLinearToGamma(material->data.color[2]); + color[3] = material->data.color[3]; } -bool lovrMaterialSetNumber(Material* material, MaterialNumber key, float number) { - if (!lovrMaterialUpload(material)) return false; - material->data.numbers[key] = number; - material->pointer->numbers[key] = number; - return true; +bool lovrMaterialSetColor(Material* material, float color[4]) { + material->data.color[0] = lovrMathGammaToLinear(color[0]); + material->data.color[1] = lovrMathGammaToLinear(color[1]); + material->data.color[2] = lovrMathGammaToLinear(color[2]); + material->data.color[3] = color[3]; + return lovrMaterialUpload(material, offsetof(MaterialData, color), sizeof(material->data.color)); } -const float* lovrMaterialGetColor(Material* material, MaterialColor key) { - return material->data.colors[key]; +void lovrMaterialGetGlow(Material* material, float glow[4]) { + glow[0] = lovrMathLinearToGamma(material->data.glow[0]); + glow[1] = lovrMathLinearToGamma(material->data.glow[1]); + glow[2] = lovrMathLinearToGamma(material->data.glow[2]); + glow[3] = material->data.glow[3]; } -bool lovrMaterialSetColor(Material* material, MaterialColor key, float color[4]) { - if (!lovrMaterialUpload(material)) return false; - memcpy(material->data.colors[key], color, 4 * sizeof(float)); - material->pointer->colors[key][0] = lovrMathGammaToLinear(color[0]); - material->pointer->colors[key][1] = lovrMathGammaToLinear(color[1]); - material->pointer->colors[key][2] = lovrMathGammaToLinear(color[2]); - material->pointer->colors[key][3] = color[3]; - return true; +bool lovrMaterialSetGlow(Material* material, float glow[4]) { + material->data.glow[0] = lovrMathGammaToLinear(glow[0]); + material->data.glow[1] = lovrMathGammaToLinear(glow[1]); + material->data.glow[2] = lovrMathGammaToLinear(glow[2]); + material->data.glow[3] = glow[3]; + return lovrMaterialUpload(material, offsetof(MaterialData, glow), sizeof(material->data.glow)); +} + +void lovrMaterialGetQuad(Material* material, float quad[4]) { + memcpy(quad, material->data.quad, 4 * sizeof(float)); +} + +bool lovrMaterialSetQuad(Material* material, float quad[4]) { + memcpy(quad, material->data.quad, 4 * sizeof(float)); + return lovrMaterialUpload(material, offsetof(MaterialData, quad), sizeof(material->data.quad)); +} + +void lovrMaterialGetSdfRange(Material* material, float* x, float* y) { + *x = material->data.sdfRange[0]; + *y = material->data.sdfRange[1]; +} + +bool lovrMaterialSetSdfRange(Material* material, float x, float y) { + material->data.sdfRange[0] = x; + material->data.sdfRange[1] = y; + return lovrMaterialUpload(material, offsetof(MaterialData, sdfRange), sizeof(material->data.sdfRange)); +} + +float lovrMaterialGetMetalness(Material* material) { + return material->data.metalness; } -Texture* lovrMaterialGetTexture(Material* material, MaterialTexture key) { - return material->textures[key]; +bool lovrMaterialSetMetalness(Material* material, float metalness) { + material->data.metalness = metalness; + return lovrMaterialUpload(material, offsetof(MaterialData, metalness), sizeof(material->data.metalness)); } -bool lovrMaterialSetTexture(Material* material, MaterialTexture key, Texture* texture) { - if (texture == material->textures[key]) return true; +float lovrMaterialGetRoughness(Material* material) { + return material->data.roughness; +} + +bool lovrMaterialSetRoughness(Material* material, float roughness) { + material->data.roughness = roughness; + return lovrMaterialUpload(material, offsetof(MaterialData, roughness), sizeof(material->data.roughness)); +} + +float lovrMaterialGetClearcoat(Material* material) { + return material->data.clearcoat; +} + +bool lovrMaterialSetClearcoat(Material* material, float clearcoat) { + material->data.clearcoat = clearcoat; + return lovrMaterialUpload(material, offsetof(MaterialData, clearcoat), sizeof(material->data.clearcoat)); +} + +float lovrMaterialGetClearcoatRoughness(Material* material) { + return material->data.clearcoatRoughness; +} + +bool lovrMaterialSetClearcoatRoughness(Material* material, float clearcoatRoughness) { + material->data.clearcoatRoughness = clearcoatRoughness; + return lovrMaterialUpload(material, offsetof(MaterialData, clearcoatRoughness), sizeof(material->data.clearcoatRoughness)); +} + +float lovrMaterialGetOcclusionStrength(Material* material) { + return material->data.occlusionStrength; +} + +bool lovrMaterialSetOcclusionStrength(Material* material, float occlusionStrength) { + material->data.occlusionStrength = occlusionStrength; + return lovrMaterialUpload(material, offsetof(MaterialData, occlusionStrength), sizeof(material->data.occlusionStrength)); +} + +float lovrMaterialGetNormalScale(Material* material) { + return material->data.normalScale; +} + +bool lovrMaterialSetNormalScale(Material* material, float normalScale) { + material->data.normalScale = normalScale; + return lovrMaterialUpload(material, offsetof(MaterialData, normalScale), sizeof(material->data.normalScale)); +} + +float lovrMaterialGetAlphaCutoff(Material* material) { + return material->data.alphaCutoff; +} + +bool lovrMaterialSetAlphaCutoff(Material* material, float alphaCutoff) { + material->data.alphaCutoff = alphaCutoff; + return lovrMaterialUpload(material, offsetof(MaterialData, alphaCutoff), sizeof(material->data.alphaCutoff)); +} + +bool lovrMaterialIsDoubleSided(Material* material) { + return material->data.doubleSided; +} - lovrCheck(key == TEXTURE_COLOR || texture->info.type == TEXTURE_2D, "Material textures must be 2D"); - lovrCheck(texture->info.samples == 1, "Material textures can not be multisampled"); - lovrCheck(texture->info.usage & TEXTURE_SAMPLE, "Textures must be created with the 'sample' usage to use them in Materials"); +void lovrMaterialSetDoubleSided(Material* material, bool doubleSided) { + material->data.doubleSided = doubleSided; +} + +Texture* lovrMaterialGetTexture(Material* material, MaterialTexture type) { + return material->textures[type]; +} - lovrRelease(material->textures[key], lovrTextureDestroy); - material->textures[key] = texture; - material->bindings[key + 1].texture.object = texture->sampleViewFloat; +bool lovrMaterialSetTexture(Material* material, MaterialTexture type, Texture* texture) { + if (texture == material->textures[type]) return true; + + if (texture) { + lovrCheck(type == TEXTURE_COLOR || texture->info.type == TEXTURE_2D, "Material textures must be 2D"); + lovrCheck(texture->info.samples == 1, "Material textures can not be multisampled"); + lovrCheck(texture->info.usage & TEXTURE_SAMPLE, "Textures must be created with the 'sample' usage to use them in Materials"); + } + + lovrRelease(material->textures[type], lovrTextureDestroy); + material->textures[type] = texture; + material->bindings[type + 1].texture.object = (texture ? texture : state.defaultTexture)->sampleViewFloat; lovrRetain(texture); gpu_bundle_info bundleInfo = { .layout = state.materialLayout->gpu }; if (material->bundleTick == state.tick) { - bundleInfo.bindings = &material->bindings[key + 1]; + bundleInfo.bindings = &material->bindings[type + 1]; bundleInfo.count = 1; } else { lovrMaterialRecycle(material); @@ -4373,43 +4463,6 @@ bool lovrMaterialSetTexture(Material* material, MaterialTexture key, Texture* te return true; } -void lovrMaterialGetQuad(Material* material, float* ox, float* oy, float* sx, float* sy) { - *ox = material->data.quad[0]; - *oy = material->data.quad[1]; - *sx = material->data.quad[2]; - *sy = material->data.quad[3]; -} - -bool lovrMaterialSetQuad(Material* material, float ox, float oy, float sx, float sy) { - if (!lovrMaterialUpload(material)) return false; - material->data.quad[0] = ox; - material->data.quad[1] = oy; - material->data.quad[2] = sx; - material->data.quad[3] = sy; - material->pointer->quad[0] = ox; - material->pointer->quad[1] = oy; - material->pointer->quad[2] = sx; - material->pointer->quad[3] = sy; - return true; -} - -bool lovrMaterialIsDoubleSided(Material* material) { - return material->doubleSided; -} - -void lovrMaterialSetDoubleSided(Material* material, bool doubleSided) { - material->doubleSided = doubleSided; -} - -static bool lovrMaterialSetSDFRange(Material* material, float x, float y) { - if (!lovrMaterialUpload(material)) return false; - material->data.sdfRange[0] = x; - material->data.sdfRange[1] = y; - material->pointer->sdfRange[0] = x; - material->pointer->sdfRange[1] = y; - return true; -} - // Font Font* lovrGraphicsGetDefaultFont(void) { @@ -4645,7 +4698,7 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint, bool* resized) { } Material* material = lovrMaterialCreate(atlas); - lovrMaterialSetSDFRange(material, font->info.spread / newWidth, font->info.spread / newHeight); + lovrMaterialSetSdfRange(material, font->info.spread / newWidth, font->info.spread / newHeight); if (!material) { lovrTextureDestroy(atlas); @@ -5468,17 +5521,9 @@ Model* lovrModelCreate(const ModelInfo* info) { model->materials[i] = material; ModelMaterial* properties = &meta->materials[i]; - lovrMaterialSetNumber(material, NUMBER_METALNESS, properties->metalness); - lovrMaterialSetNumber(material, NUMBER_ROUGHNESS, properties->roughness); - lovrMaterialSetNumber(material, NUMBER_CLEARCOAT, properties->clearcoat); - lovrMaterialSetNumber(material, NUMBER_CLEARCOAT_ROUGHNESS, properties->clearcoatRoughness); - lovrMaterialSetNumber(material, NUMBER_OCCLUSION_STRENGTH, properties->occlusionStrength); - lovrMaterialSetNumber(material, NUMBER_NORMAL_SCALE, properties->normalScale); - lovrMaterialSetNumber(material, NUMBER_ALPHA_CUTOFF, properties->alphaCutoff); - lovrMaterialSetColor(material, COLOR_BASE, properties->color); - lovrMaterialSetColor(material, COLOR_GLOW, properties->glow); - lovrMaterialSetQuad(material, properties->quad[0], properties->quad[1], properties->quad[2], properties->quad[3]); - lovrMaterialSetDoubleSided(material, properties->doubleSided); + memcpy(&material->data, properties, sizeof(MaterialData)); + material->data.doubleSided = properties->doubleSided; + lovrMaterialUpload(material, 0, sizeof(MaterialData)); uint32_t textures[] = { [TEXTURE_COLOR] = properties->texture, @@ -7441,11 +7486,8 @@ void lovrPassSetDepthClamp(Pass* pass, bool clamp) { } void lovrPassSetFaceCull(Pass* pass, CullMode mode) { - if (mode != CULL_AUTO) { - pass->pipeline->dirty |= pass->pipeline->info.rasterizer.cullMode != (gpu_cull_mode) mode; - pass->pipeline->info.rasterizer.cullMode = (gpu_cull_mode) mode; - } - pass->pipeline->faceCull = mode; + pass->pipeline->dirty |= pass->pipeline->info.rasterizer.cullMode != (gpu_cull_mode) mode; + pass->pipeline->info.rasterizer.cullMode = (gpu_cull_mode) mode; } void lovrPassSetFont(Pass* pass, Font* font) { @@ -7846,6 +7888,7 @@ bool lovrPassSendData(Pass* pass, const char* name, size_t length, void** data, static void lovrPassResolvePipeline(Pass* pass, DrawInfo* info, Draw* draw, Draw* prev) { Pipeline* pipeline = pass->pipeline; Shader* shader = draw->shader; + bool disableCulling = false; if (pipeline->info.drawMode != (gpu_draw_mode) info->mode) { pipeline->info.drawMode = (gpu_draw_mode) info->mode; @@ -7859,10 +7902,9 @@ static void lovrPassResolvePipeline(Pass* pass, DrawInfo* info, Draw* draw, Draw pipeline->dirty = true; } - if (pipeline->faceCull == CULL_AUTO) { - gpu_cull_mode cullMode = draw->material->doubleSided ? GPU_CULL_NONE : GPU_CULL_BACK; - pipeline->dirty |= cullMode != pipeline->info.rasterizer.cullMode; - pipeline->info.rasterizer.cullMode = cullMode; + if (pipeline->info.rasterizer.cullMode != GPU_CULL_NONE && draw->material->data.doubleSided) { + if (prev) pipeline->dirty |= !prev->material->data.doubleSided; + disableCulling = true; } // Vertex formats @@ -7930,6 +7972,7 @@ static void lovrPassResolvePipeline(Pass* pass, DrawInfo* info, Draw* draw, Draw pipeline->dirty = false; draw->pipelineInfo = lovrPassAllocate(pass, sizeof(gpu_pipeline_info)); memcpy(draw->pipelineInfo, &pipeline->info, sizeof(pipeline->info)); + if (disableCulling) draw->pipelineInfo->rasterizer.cullMode = GPU_CULL_NONE; draw->pipeline = NULL; } else { draw->pipelineInfo = prev->pipelineInfo; @@ -9932,16 +9975,9 @@ static void trackTexture(Pass* pass, Texture* texture, gpu_phase phase, gpu_cach } static void trackMaterial(Pass* pass, Material* material) { - gpu_phase phase = GPU_PHASE_SHADER_VERTEX | GPU_PHASE_SHADER_FRAGMENT; - gpu_cache cache = GPU_CACHE_TEXTURE; - - trackTexture(pass, material->textures[TEXTURE_COLOR], phase, cache); - trackTexture(pass, material->textures[TEXTURE_GLOW], phase, cache); - trackTexture(pass, material->textures[TEXTURE_METALNESS], phase, cache); - trackTexture(pass, material->textures[TEXTURE_ROUGHNESS], phase, cache); - trackTexture(pass, material->textures[TEXTURE_CLEARCOAT], phase, cache); - trackTexture(pass, material->textures[TEXTURE_OCCLUSION], phase, cache); - trackTexture(pass, material->textures[TEXTURE_NORMAL], phase, cache); + for (uint32_t i = 0; i < MAX_MATERIAL_TEXTURES; i++) { + trackTexture(pass, material->textures[i], GPU_PHASE_SHADER_VERTEX | GPU_PHASE_SHADER_FRAGMENT, GPU_CACHE_TEXTURE); + } } static void trackRaytracer(Pass* pass, Raytracer* raytracer, gpu_phase phase, gpu_cache cache) { diff --git a/src/modules/graphics/graphics.h b/src/modules/graphics/graphics.h index 2ef05cc005..6f2c6395c1 100644 --- a/src/modules/graphics/graphics.h +++ b/src/modules/graphics/graphics.h @@ -379,23 +379,6 @@ const DataField* lovrShaderGetBufferFormat(Shader* shader, const char* name, uin // Material -typedef enum { - NUMBER_METALNESS, - NUMBER_ROUGHNESS, - NUMBER_CLEARCOAT, - NUMBER_CLEARCOAT_ROUGHNESS, - NUMBER_OCCLUSION_STRENGTH, - NUMBER_NORMAL_SCALE, - NUMBER_ALPHA_CUTOFF, - NUMBER_COUNT -} MaterialNumber; - -typedef enum { - COLOR_BASE, - COLOR_GLOW, - COLOR_COUNT -} MaterialColor; - typedef enum { TEXTURE_COLOR, TEXTURE_GLOW, @@ -404,21 +387,37 @@ typedef enum { TEXTURE_CLEARCOAT, TEXTURE_OCCLUSION, TEXTURE_NORMAL, - TEXTURE_COUNT + MAX_MATERIAL_TEXTURES } MaterialTexture; Material* lovrMaterialCreate(Texture* texture); void lovrMaterialDestroy(void* ref); -float lovrMaterialGetNumber(Material* material, MaterialNumber key); -bool lovrMaterialSetNumber(Material* material, MaterialNumber key, float number); -const float* lovrMaterialGetColor(Material* material, MaterialColor key); -bool lovrMaterialSetColor(Material* material, MaterialColor key, float color[4]); -Texture* lovrMaterialGetTexture(Material* material, MaterialTexture key); -bool lovrMaterialSetTexture(Material* material, MaterialTexture key, Texture* texture); -void lovrMaterialGetQuad(Material* material, float* ox, float* oy, float* sx, float* sy); -bool lovrMaterialSetQuad(Material* material, float ox, float oy, float sx, float sy); +void lovrMaterialGetColor(Material* material, float color[4]); +bool lovrMaterialSetColor(Material* material, float color[4]); +void lovrMaterialGetGlow(Material* material, float glow[4]); +bool lovrMaterialSetGlow(Material* material, float glow[4]); +void lovrMaterialGetQuad(Material* material, float quad[4]); +bool lovrMaterialSetQuad(Material* material, float quad[4]); +void lovrMaterialGetSdfRange(Material* material, float* x, float* y); +bool lovrMaterialSetSdfRange(Material* material, float x, float y); +float lovrMaterialGetMetalness(Material* material); +bool lovrMaterialSetMetalness(Material* material, float metalness); +float lovrMaterialGetRoughness(Material* material); +bool lovrMaterialSetRoughness(Material* material, float roughness); +float lovrMaterialGetClearcoat(Material* material); +bool lovrMaterialSetClearcoat(Material* material, float clearcoat); +float lovrMaterialGetClearcoatRoughness(Material* material); +bool lovrMaterialSetClearcoatRoughness(Material* material, float clearcoatRoughness); +float lovrMaterialGetOcclusionStrength(Material* material); +bool lovrMaterialSetOcclusionStrength(Material* material, float occlusionStrength); +float lovrMaterialGetNormalScale(Material* material); +bool lovrMaterialSetNormalScale(Material* material, float normalScale); +float lovrMaterialGetAlphaCutoff(Material* material); +bool lovrMaterialSetAlphaCutoff(Material* material, float alphaCutoff); bool lovrMaterialIsDoubleSided(Material* material); void lovrMaterialSetDoubleSided(Material* material, bool doubleSided); +Texture* lovrMaterialGetTexture(Material* material, MaterialTexture type); +bool lovrMaterialSetTexture(Material* material, MaterialTexture type, Texture* texture); // Font @@ -677,8 +676,7 @@ typedef struct { typedef enum { CULL_NONE, CULL_FRONT, - CULL_BACK, - CULL_AUTO + CULL_BACK } CullMode; typedef enum {