Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 69 additions & 12 deletions src/TruePBR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1340,22 +1340,79 @@ struct TESBoundObject_Clone3D
auto* result = func(object, ref, arg3);
if (result != nullptr && ref != nullptr && ref->data.objectReference != nullptr && ref->data.objectReference->formType == RE::FormType::Static) {
auto* stat = static_cast<RE::TESObjectSTAT*>(ref->data.objectReference);
if (stat->data.materialObj != nullptr && stat->data.materialObj->directionalData.singlePass) {
if (auto* pbrData = truePBR->GetPBRMaterialObjectData(stat->data.materialObj)) {
RE::BSVisit::TraverseScenegraphGeometries(result, [pbrData](RE::BSGeometry* geometry) {
if (auto* shaderProperty = static_cast<RE::BSShaderProperty*>(geometry->GetGeometryRuntimeData().shaderProperty.get())) {
if (shaderProperty->GetMaterialType() == RE::BSShaderMaterial::Type::kLighting &&
shaderProperty->flags.any(RE::BSShaderProperty::EShaderPropertyFlag::kVertexLighting)) {
if (auto* material = static_cast<BSLightingShaderMaterialPBR*>(shaderProperty->material)) {
material->ApplyMaterialObjectData(*pbrData);
BSLightingShaderMaterialPBR::All[material].materialObjectData = pbrData;
RE::BGSMaterialObject* currentMato = stat->data.materialObj;

// Resolve PBR MATO data for the three-way condition as a single pointer:
// non-null -> apply MATO to geometries
// null -> clear any previously applied MATO from geometries
// This covers all negative cases (currentMato == nullptr, singlePass ==
// false, or no matching PBR entry) with one branch so the clear path is
// never silently skipped.
auto* pbrData = (currentMato != nullptr && currentMato->directionalData.singlePass) ? truePBR->GetPBRMaterialObjectData(currentMato) : nullptr;

if (pbrData != nullptr) {
RE::BSVisit::TraverseScenegraphGeometries(result, [pbrData, ref](RE::BSGeometry* geometry) {
if (auto* shaderProperty = static_cast<RE::BSShaderProperty*>(geometry->GetGeometryRuntimeData().shaderProperty.get())) {
if (shaderProperty->GetMaterialType() == RE::BSShaderMaterial::Type::kLighting &&
shaderProperty->flags.any(RE::BSShaderProperty::EShaderPropertyFlag::kVertexLighting)) {
if (auto* material = static_cast<BSLightingShaderMaterialPBR*>(shaderProperty->material)) {
auto& ext = BSLightingShaderMaterialPBR::All[material];
const auto prevOwnerRefID = ext.lastOwnerRefFormID;

// Fork-before-write: if this material instance is already owned
// by a different ref whose MATO payload differs from the incoming
// one, clone it so we don't contaminate the previous owner's
// geometry. Use pointer identity: GetPBRMaterialObjectData
// returns stable addresses into pbrMaterialObjects, so two
// different MATOs always produce different pointers regardless of
// whether their individual fields (baseColorScale, roughness,
// specularLevel, glint) happen to match.
const bool wouldContaminate =
(prevOwnerRefID != 0) &&
(prevOwnerRefID != ref->GetFormID()) &&
(ext.materialObjectData != pbrData);

BSLightingShaderMaterialPBR* targetMat = material;

if (wouldContaminate) {
auto* freshMat = BSLightingShaderMaterialPBR::Make();
if (freshMat) {
freshMat->CopyMembers(material);
shaderProperty->material = freshMat;
targetMat = freshMat;
} else {
logger::warn("[TruePBR] failed to clone PBR material for ref {:08X}; skipping to avoid contamination", ref->GetFormID());
return RE::BSVisit::BSVisitControl::kContinue;
}
}

targetMat->ApplyMaterialObjectData(*pbrData);
auto& targetExt = BSLightingShaderMaterialPBR::All[targetMat];
targetExt.materialObjectData = pbrData;
targetExt.lastOwnerRefFormID = ref->GetFormID();
}
}
}

return RE::BSVisit::BSVisitControl::kContinue;
});
}
return RE::BSVisit::BSVisitControl::kContinue;
});
} else {
RE::BSVisit::TraverseScenegraphGeometries(result, [](RE::BSGeometry* geometry) {
if (auto* shaderProperty = static_cast<RE::BSShaderProperty*>(geometry->GetGeometryRuntimeData().shaderProperty.get())) {
if (shaderProperty->GetMaterialType() == RE::BSShaderMaterial::Type::kLighting &&
shaderProperty->flags.any(RE::BSShaderProperty::EShaderPropertyFlag::kVertexLighting)) {
if (auto* material = static_cast<BSLightingShaderMaterialPBR*>(shaderProperty->material)) {
auto& ext = BSLightingShaderMaterialPBR::All[material];
if (ext.materialObjectData != nullptr) {
material->ClearMaterialObjectData();
ext.materialObjectData = nullptr;
ext.lastOwnerRefFormID = 0;
}
}
}
}
return RE::BSVisit::BSVisitControl::kContinue;
});
}
}
return result;
Expand Down
8 changes: 8 additions & 0 deletions src/TruePBR/BSLightingShaderMaterialPBR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ void BSLightingShaderMaterialPBR::ApplyMaterialObjectData(const TruePBR::PBRMate
projectedMaterialGlintParameters = materialObjectData.glintParameters;
}

void BSLightingShaderMaterialPBR::ClearMaterialObjectData()
{
projectedMaterialBaseColorScale = { 1.f, 1.f, 1.f };
projectedMaterialRoughness = 1.f;
projectedMaterialSpecularLevel = 0.04f;
projectedMaterialGlintParameters = GlintParameters{};
}

void BSLightingShaderMaterialPBR::OnLoadTextureSet(std::uint64_t arg1, RE::BSTextureSet* inTextureSet)
{
const auto& stateData = globals::game::graphicsState->GetRuntimeData();
Expand Down
8 changes: 8 additions & 0 deletions src/TruePBR/BSLightingShaderMaterialPBR.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class BSLightingShaderMaterialPBR : public RE::BSLightingShaderMaterialBase
{
TruePBR::PBRTextureSetData* textureSetData = nullptr;
TruePBR::PBRMaterialObjectData* materialObjectData = nullptr;
/// FormID of the TESObjectREFR whose Clone3D call last wrote MATO data to this
/// material. Used by the fork-before-write check to detect when a pooled material
/// instance would be overwritten by a different ref, triggering a clone instead.
RE::FormID lastOwnerRefFormID = 0;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

inline static constexpr auto FEATURE = static_cast<RE::BSShaderMaterial::Feature>(32);
Expand Down Expand Up @@ -64,6 +68,10 @@ class BSLightingShaderMaterialPBR : public RE::BSLightingShaderMaterialBase

void ApplyTextureSetData(const TruePBR::PBRTextureSetData& textureSetData);
void ApplyMaterialObjectData(const TruePBR::PBRMaterialObjectData& materialObjectData);
/// Resets all projected-material fields to their default values.
/// Called on references that carry no MATO (or no PBR config for their MATO) to
/// prevent stale data copied in by CopyMembers from persisting on the material.
void ClearMaterialObjectData();
Comment thread
coderabbitai[bot] marked this conversation as resolved.

float GetRoughnessScale() const;
float GetSpecularLevel() const;
Expand Down
Loading