diff --git a/CHANGES.md b/CHANGES.md index 6c80c02b9..9a341c868 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +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 getters and setters for `Material` properties. +- Add `doubleSided` property to `Material`. +- Add `Model:setMaterial`. ### Change @@ -39,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 bd4200f75..6e240484c 100644 --- a/etc/shaders/lovr.glsl +++ b/etc/shaders/lovr.glsl @@ -40,8 +40,7 @@ layout(set = 0, binding = 3) uniform sampler Sampler; struct MaterialData { vec4 color; vec4 glow; - vec2 uvShift; - vec2 uvScale; + vec4 quad; vec2 sdfRange; float metalness; float roughness; @@ -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 92b123d0d..5eda20975 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -51,6 +51,7 @@ extern StringEntry lovrHorizontalAlign[]; extern StringEntry lovrJointType[]; extern StringEntry lovrKeyboardKey[]; extern StringEntry lovrLayerType[]; +extern StringEntry lovrMaterialTexture[]; extern StringEntry lovrMeshStorage[]; extern StringEntry lovrModelDrawMode[]; extern StringEntry lovrMotorMode[]; diff --git a/src/api/l_data_modelData.c b/src/api/l_data_modelData.c index 6c830fbc7..b1cc38c51 100644 --- a/src/api/l_data_modelData.c +++ b/src/api/l_data_modelData.c @@ -602,20 +602,33 @@ 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->uvShift[0]); + lua_pushnumber(L, material->quad[0]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, material->uvShift[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->uvScale[0]); + lua_pushnumber(L, material->quad[2]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, material->uvScale[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); + lua_pushnumber(L, material->quad[1]); + lua_rawseti(L, -2, 2); + 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"); lua_pushnumber(L, material->clearcoat), lua_setfield(L, -2, "clearcoat"); @@ -633,6 +646,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.c b/src/api/l_graphics.c index 698241ab9..b12a2b6c8 100644 --- a/src/api/l_graphics.c +++ b/src/api/l_graphics.c @@ -163,6 +163,17 @@ StringEntry lovrHorizontalAlign[] = { { 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 +1470,126 @@ static Texture* luax_opttexture(lua_State* L, int index) { } static int l_lovrGraphicsNewMaterial(lua_State* L) { - MaterialInfo info; - memset(&info, 0, sizeof(info)); + 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"); + } - luaL_checktype(L, 1, LUA_TTABLE); + Material* material = lovrMaterialCreate(NULL); + luax_assert(L, material); lua_getfield(L, 1, "color"); - luax_optcolor(L, -1, info.data.color); + if (!lua_isnil(L, -1)) { + float color[4]; + luax_optcolor(L, -1, color); + lovrMaterialSetColor(material, 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); + if (!lua_isnil(L, -1)) { + float glow[4]; + luax_optcolor(L, -1, glow); + lovrMaterialSetGlow(material, glow); } lua_pop(L, 1); + float quad[4] = { 0.f, 0.f, 1.f, 1.f }; + + // Deprecated 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; + quad[0] = shift; + quad[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); + 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_isnil(L, -1)) { - info.data.uvScale[0] = 1.f; - info.data.uvScale[1] = 1.f; - } else if (lua_isnumber(L, -1)) { + if (lua_isnumber(L, -1)) { float scale = lua_tonumber(L, -1); - info.data.uvScale[0] = scale; - info.data.uvScale[1] = scale; + quad[2] = scale; + quad[3] = 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); + quad[2] = luax_optfloat(L, -2, 1.f); + quad[3] = luax_optfloat(L, -1, 1.f); lua_pop(L, 2); } 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); + + lovrMaterialSetQuad(material, quad); + lua_getfield(L, 1, "metalness"); - info.data.metalness = luax_optfloat(L, -1, 1.f); + if (!lua_isnil(L, -1)) lovrMaterialSetMetalness(material, luax_tofloat(L, -1)); lua_pop(L, 1); lua_getfield(L, 1, "roughness"); - info.data.roughness = luax_optfloat(L, -1, 1.f); + if (!lua_isnil(L, -1)) lovrMaterialSetRoughness(material, luax_tofloat(L, -1)); lua_pop(L, 1); lua_getfield(L, 1, "clearcoat"); - info.data.clearcoat = luax_optfloat(L, -1, 0.f); + if (!lua_isnil(L, -1)) lovrMaterialSetClearcoat(material, luax_tofloat(L, -1)); lua_pop(L, 1); lua_getfield(L, 1, "clearcoatRoughness"); - info.data.clearcoatRoughness = luax_optfloat(L, -1, 0.f); + if (!lua_isnil(L, -1)) lovrMaterialSetClearcoatRoughness(material, luax_tofloat(L, -1)); lua_pop(L, 1); lua_getfield(L, 1, "occlusionStrength"); - info.data.occlusionStrength = luax_optfloat(L, -1, 1.f); + if (!lua_isnil(L, -1)) lovrMaterialSetOcclusionStrength(material, luax_tofloat(L, -1)); lua_pop(L, 1); lua_getfield(L, 1, "normalScale"); - info.data.normalScale = luax_optfloat(L, -1, 1.f); + if (!lua_isnil(L, -1)) lovrMaterialSetNormalScale(material, luax_tofloat(L, -1)); lua_pop(L, 1); lua_getfield(L, 1, "alphaCutoff"); - info.data.alphaCutoff = luax_optfloat(L, -1, 0.f); - lua_pop(L, 1); - - lua_getfield(L, 1, "texture"); - info.texture = luax_opttexture(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "glowTexture"); - info.glowTexture = luax_opttexture(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "metalnessTexture"); - info.metalnessTexture = luax_opttexture(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "roughnessTexture"); - info.roughnessTexture = luax_opttexture(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "clearcoatTexture"); - info.clearcoatTexture = luax_opttexture(L, -1); + if (!lua_isnil(L, -1)) lovrMaterialSetAlphaCutoff(material, luax_tofloat(L, -1)); lua_pop(L, 1); - lua_getfield(L, 1, "occlusionTexture"); - info.occlusionTexture = luax_opttexture(L, -1); + lua_getfield(L, 1, "doubleSided"); + lovrMaterialSetDoubleSided(material, lua_toboolean(L, -1)); lua_pop(L, 1); - lua_getfield(L, 1, "normalTexture"); - info.normalTexture = luax_opttexture(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); + } - 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 a3d2a4d6f..77878d12d 100644 --- a/src/api/l_graphics_material.c +++ b/src/api/l_graphics_material.c @@ -17,66 +17,281 @@ Material* luax_optmaterial(lua_State* L, int index) { } } -static int l_lovrMaterialGetProperties(lua_State* L) { +static int l_lovrMaterialGetColor(lua_State* L) { Material* material = luax_checktype(L, 1, Material); - const MaterialInfo* info = lovrMaterialGetInfo(material); - lua_newtable(L); + 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_lovrMaterialSetColor(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + float color[4]; + luax_readcolor(L, 2, color); + luax_assert(L, lovrMaterialSetColor(material, color)); + return 0; +} + +static int l_lovrMaterialGetGlow(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + float color[4]; + lovrMaterialGetGlow(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_lovrMaterialSetGlow(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + float glow[4]; + luax_readcolor(L, 2, glow); + luax_assert(L, lovrMaterialSetGlow(material, glow)); + return 0; +} + +static int l_lovrMaterialGetQuad(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + 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_lovrMaterialSetMetalness(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + luax_assert(L, lovrMaterialSetMetalness(material, luax_checkfloat(L, 2))); + return 0; +} + +static int l_lovrMaterialGetRoughness(lua_State * L) { + Material* material = luax_checktype(L, 1, Material); + lua_pushnumber(L, lovrMaterialGetRoughness(material)); + return 1; +} + +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); + 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); + lua_pushboolean(L, lovrMaterialIsDoubleSided(material)); + 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; +} + +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; +} + +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, 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); +} + +static int l_lovrMaterialGetProperties(lua_State* L) { + Material* material = luax_checktype(L, 1, Material); + + lua_newtable(L); + + float color[4]; + lovrMaterialGetColor(material, color); + luax_pushcolor(L, color); lua_setfield(L, -2, "color"); - lua_createtable(L, 4, 0); - lua_pushnumber(L, info->data.glow[0]); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, info->data.glow[1]); - lua_rawseti(L, -2, 2); - lua_pushnumber(L, info->data.glow[2]); - lua_rawseti(L, -2, 3); - lua_pushnumber(L, info->data.glow[3]); - lua_rawseti(L, -2, 4); + lovrMaterialGetGlow(material, color); + luax_pushcolor(L, color); lua_setfield(L, -2, "glow"); + float quad[4]; + lovrMaterialGetQuad(material, quad); + + // Deprecated lua_createtable(L, 2, 0); - lua_pushnumber(L, info->data.uvShift[0]); + lua_pushnumber(L, quad[0]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, info->data.uvShift[1]); + lua_pushnumber(L, quad[1]); lua_rawseti(L, -2, 2); lua_setfield(L, -2, "uvShift"); + // Deprecated lua_createtable(L, 2, 0); - lua_pushnumber(L, info->data.uvScale[0]); + lua_pushnumber(L, quad[2]); lua_rawseti(L, -2, 1); - lua_pushnumber(L, info->data.uvScale[1]); + lua_pushnumber(L, quad[3]); 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"); + 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"); + 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[] = { + { "getColor", l_lovrMaterialGetColor }, + { "setColor", l_lovrMaterialSetColor }, + { "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 }, + { "getTexture", l_lovrMaterialGetTexture }, + { "setTexture", l_lovrMaterialSetTexture }, { "getProperties", l_lovrMaterialGetProperties }, { NULL, NULL } }; diff --git a/src/api/l_graphics_model.c b/src/api/l_graphics_model.c index 0a118cd72..63e73d3d0 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 c8bc55d9b..9e820bd7d 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/data/modelData.c b/src/modules/data/modelData.c index 53a4fdf4b..72377e7ef 100644 --- a/src/modules/data/modelData.c +++ b/src/modules/data/modelData.c @@ -111,8 +111,7 @@ void lovrModelDataAllocate(ModelData* model) { 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 }, + .quad = { 0.f, 0.f, 1.f, 1.f }, .metalness = 1.f, .roughness = 1.f, .clearcoat = 0.f, diff --git a/src/modules/data/modelData.h b/src/modules/data/modelData.h index 6843151e0..3a7e8e689 100644 --- a/src/modules/data/modelData.h +++ b/src/modules/data/modelData.h @@ -76,8 +76,7 @@ typedef struct { typedef struct { float color[4]; float glow[4]; - float uvShift[2]; - float uvScale[2]; + float quad[4]; float sdfRange[2]; float metalness; float roughness; @@ -86,6 +85,7 @@ typedef struct { float occlusionStrength; float normalScale; float alphaCutoff; + bool doubleSided; 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 8786522fe..b9a2c661d 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); } @@ -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/data/modelData_obj.c b/src/modules/data/modelData_obj.c index c1ce151ae..86472d57e 100644 --- a/src/modules/data/modelData_obj.c +++ b/src/modules/data/modelData_obj.c @@ -60,8 +60,7 @@ static bool parseMtl(char* path, char* base, ModelDataIO* io, arr_image_t* image 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 }, + .quad = { 0.f, 1.f, 1.f, 1.f }, .metalness = 1.f, .roughness = 1.f, .clearcoat = 0.f, diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 899c79caa..3e4b53542 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -29,10 +30,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 +179,50 @@ struct Shader { char* names; }; +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; +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 next; - uint32_t tick; uint32_t index; - MaterialInfo info; - gpu_bundle* bundle; MaterialBlock* block; - bool hasWritableTexture; + uint32_t bufferTick; + uint32_t bundleTick; + MaterialData data; + MaterialData* pointer; + gpu_bundle* bundle; + gpu_binding bindings[1 + MAX_MATERIAL_TEXTURES]; + Texture* textures[MAX_MATERIAL_TEXTURES]; }; typedef struct { @@ -792,15 +819,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 +937,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,20 +3118,13 @@ 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 - }); - - if (!texture->material) { - return NULL; - } + texture->material = lovrMaterialCreate(texture); + 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). lovrRelease(texture, lovrTextureDestroy); - texture->material->info.texture = NULL; + texture->material->textures[TEXTURE_COLOR] = NULL; } return texture->material; @@ -4116,164 +4127,340 @@ 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; + } + + block->head = block->nodes[block->head].next; + block->nodes[material->index].next = ~0u; + + mtx_unlock(&state.lock); + + return true; +} - gpu_copy_buffers(state.stream, staging.buffer, block->buffer, staging.offset, stride * material->index, sizeof(MaterialData)); +static void lovrMaterialRecycle(Material* material) { + if (!material->block) return; + mtx_lock(&state.lock); + 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; + mtx_unlock(&state.lock); +} + +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; - data = staging.pointer; + material->bufferTick = state.tick; + material->pointer = staging.pointer; + mtx_unlock(&state.lock); } - memcpy(data, info, sizeof(MaterialData)); + memcpy((char*) material->pointer + offset, (char*) &material->data + offset, size); + return true; +} - gpu_buffer_binding buffer = { - .object = block->buffer, - .offset = material->index * stride, - .extent = stride - }; +Material* lovrMaterialCreate(Texture* texture) { + Material* material = lovrCalloc(sizeof(Material)); + material->ref = 1; - gpu_binding bindings[8] = { - { 0, GPU_SLOT_UNIFORM_BUFFER, .buffer = buffer } + material->data = (MaterialData) { + .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 < 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 < MAX_MATERIAL_TEXTURES; i++) { + material->bindings[i + 1] = (gpu_binding) { + .number = i + 1, + .type = GPU_SLOT_SAMPLED_TEXTURE, + .texture.object = state.defaultTexture->sampleViewFloat + }; } - gpu_bundle_info bundleInfo = { - .layout = state.materialLayout->gpu, - .bindings = bindings, - .count = COUNTOF(bindings) - }; + // 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) { + if (!lovrMaterialSetTexture(material, TEXTURE_COLOR, texture)) { + lovrMaterialDestroy(material); + return false; + } + } else { + if (!lovrMaterialAllocate(material)) { + return false; + } - gpu_bundle_write(&material->bundle, &bundleInfo, 1); + gpu_bundle_write(&material->bundle, &(gpu_bundle_info) { + .layout = state.materialLayout->gpu, + .bindings = material->bindings, + .count = COUNTOF(material->bindings) + }, 1); + } + + if (material->pointer) { + memcpy(material->pointer, &material->data, sizeof(MaterialData)); + } else { + lovrMaterialUpload(material, 0, sizeof(MaterialData)); + } - 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 < MAX_MATERIAL_TEXTURES; i++) { + lovrRelease(material->textures[i], lovrTextureDestroy); + } + lovrFree(material); } -const MaterialInfo* lovrMaterialGetInfo(Material* material) { - return &material->info; +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 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)); +} + +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 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; +} + +bool lovrMaterialSetMetalness(Material* material, float metalness) { + material->data.metalness = metalness; + return lovrMaterialUpload(material, offsetof(MaterialData, metalness), sizeof(material->data.metalness)); +} + +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; +} + +void lovrMaterialSetDoubleSided(Material* material, bool doubleSided) { + material->data.doubleSided = doubleSided; +} + +Texture* lovrMaterialGetTexture(Material* material, MaterialTexture type) { + return material->textures[type]; +} + +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[type + 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; } // Font @@ -4344,11 +4531,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 +4697,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 +5509,58 @@ 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 } + memcpy(&material->data, properties, sizeof(MaterialData)); + material->data.doubleSided = properties->doubleSided; + lovrMaterialUpload(material, 0, sizeof(MaterialData)); + + 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 +5683,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 +5760,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 +6020,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 +6040,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) { @@ -7691,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; @@ -7704,6 +7902,11 @@ static void lovrPassResolvePipeline(Pass* pass, DrawInfo* info, Draw* draw, Draw pipeline->dirty = true; } + if (pipeline->info.rasterizer.cullMode != GPU_CULL_NONE && draw->material->data.doubleSided) { + if (prev) pipeline->dirty |= !prev->material->data.doubleSided; + disableCulling = true; + } + // Vertex formats if (info->vertex.buffer && pipeline->lastVertexBuffer != info->vertex.buffer) { pipeline->lastVertexFormat = ~0u; @@ -7769,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; @@ -8965,7 +9169,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 +9246,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 +9975,9 @@ static void trackTexture(Pass* pass, Texture* texture, gpu_phase phase, gpu_cach } static void trackMaterial(Pass* pass, Material* material) { - if (!material->hasWritableTexture) { - return; + 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); } - - 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); } 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 b6172bd5f..6f2c6395c 100644 --- a/src/modules/graphics/graphics.h +++ b/src/modules/graphics/graphics.h @@ -379,35 +379,45 @@ 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 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 { + TEXTURE_COLOR, + TEXTURE_GLOW, + TEXTURE_METALNESS, + TEXTURE_ROUGHNESS, + TEXTURE_CLEARCOAT, + TEXTURE_OCCLUSION, + TEXTURE_NORMAL, + MAX_MATERIAL_TEXTURES +} MaterialTexture; + +Material* lovrMaterialCreate(Texture* texture); void lovrMaterialDestroy(void* ref); -const MaterialInfo* lovrMaterialGetInfo(Material* material); +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 @@ -536,7 +546,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