Skip to content

Commit f93f311

Browse files
committed
refactor: split TH things into its own class
fix: switches to shared_mutex for thread safety
1 parent c93d824 commit f93f311

File tree

4 files changed

+183
-142
lines changed

4 files changed

+183
-142
lines changed

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.21)
22

33
# Set project name and version
44
set(PLUGIN_NAME TerrainHelper)
5-
set(PLUGIN_VERSION 0.3.5)
5+
set(PLUGIN_VERSION 0.3.6)
66
project(${PLUGIN_NAME} VERSION ${PLUGIN_VERSION} LANGUAGES CXX)
77

88
# Set compile defitions
@@ -25,10 +25,12 @@ include_directories("extern/CLibUtil/include")
2525
include_directories("extern/detours")
2626

2727
set(headers
28+
"include/TerrainHelper.h"
2829
"include/PCH.h"
2930
)
3031

3132
set(sources
33+
"src/TerrainHelper.cpp"
3234
"src/main.cpp"
3335
)
3436

include/TerrainHelper.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <shared_mutex>
5+
#include <string>
6+
#include <unordered_map>
7+
#include <unordered_set>
8+
9+
class TerrainHelper {
10+
private:
11+
struct ExtendedSlots {
12+
std::array<RE::NiSourceTexturePtr, 6> parallax;
13+
};
14+
15+
static std::shared_mutex extendedSlotsMutex;
16+
static std::unordered_map<uint32_t, ExtendedSlots> extendedSlots;
17+
18+
static std::unordered_set<std::string> texturesErrorLogged;
19+
20+
public:
21+
static void TESObjectLAND_SetupMaterial(RE::TESObjectLAND* land);
22+
static void BSLightingShader_SetupMaterial(RE::BSLightingShader* shader, RE::BSLightingShaderMaterialBase const* material);
23+
24+
static void onDataLoaded();
25+
26+
// Helpers
27+
static RE::TESLandTexture* GetDefaultLandTexture();
28+
};

src/TerrainHelper.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#include "TerrainHelper.h"
2+
3+
using namespace std;
4+
5+
// statics
6+
std::shared_mutex TerrainHelper::extendedSlotsMutex;
7+
std::unordered_map<uint32_t, TerrainHelper::ExtendedSlots> TerrainHelper::extendedSlots;
8+
std::unordered_set<std::string> TerrainHelper::texturesErrorLogged;
9+
10+
void TerrainHelper::BSLightingShader_SetupMaterial(RE::BSLightingShader* shader, RE::BSLightingShaderMaterialBase const* material) {
11+
if (material == nullptr) {
12+
// material is null
13+
return;
14+
}
15+
16+
ExtendedSlots materialBase;
17+
{
18+
const shared_lock lock(extendedSlotsMutex);
19+
if (!extendedSlots.contains(material->hashKey)) {
20+
// hash does not exists
21+
return;
22+
}
23+
24+
materialBase = extendedSlots[material->hashKey];
25+
}
26+
27+
// get renderer and shadow state
28+
auto shadowState = RE::BSGraphics::RendererShadowState::GetSingleton();
29+
30+
const auto& stateData = RE::BSGraphics::State::GetSingleton()->GetRuntimeData();
31+
32+
static constexpr size_t ParallaxStartIndex = 16; // 16-21
33+
34+
// Populate extended slots
35+
for (uint32_t textureI = 0; textureI < 6; ++textureI) {
36+
const uint32_t heightIndex = ParallaxStartIndex + textureI;
37+
if (materialBase.parallax[textureI] != nullptr && materialBase.parallax[textureI] != stateData.defaultTextureNormalMap) {
38+
shadowState->SetPSTexture(heightIndex, materialBase.parallax[textureI]->rendererTexture);
39+
}
40+
else {
41+
// set default texture, which is empty
42+
shadowState->SetPSTexture(heightIndex, nullptr);
43+
}
44+
}
45+
}
46+
47+
void TerrainHelper::TESObjectLAND_SetupMaterial(RE::TESObjectLAND* land) {
48+
for (uint32_t quadI = 0; quadI < 4; ++quadI) {
49+
// Get hash key of vanilla material
50+
uint32_t hashKey = 0;
51+
52+
if (land->loadedData->mesh[quadI] == nullptr) {
53+
// continue if cannot find mesh
54+
continue;
55+
}
56+
57+
const auto& children = land->loadedData->mesh[quadI]->GetChildren();
58+
auto geometry = children.empty() ? nullptr : static_cast<RE::BSGeometry*>(children[0].get());
59+
if (geometry != nullptr) {
60+
const auto shaderProp = static_cast<RE::BSLightingShaderProperty*>(geometry->GetGeometryRuntimeData().properties[1].get());
61+
if (shaderProp != nullptr) {
62+
hashKey = shaderProp->GetBaseMaterial()->hashKey;
63+
}
64+
}
65+
66+
if (hashKey == 0) {
67+
// continue if cannot find hash key
68+
continue;
69+
}
70+
71+
// Create array of texture sets (6 tiles)
72+
std::array<RE::BGSTextureSet*, 6> textureSets;
73+
auto defTexture = land->loadedData->defQuadTextures[quadI];
74+
if (defTexture != nullptr && defTexture->formID != 0) {
75+
textureSets[0] = defTexture->textureSet;
76+
}
77+
else {
78+
// this is a default texture
79+
textureSets[0] = GetDefaultLandTexture()->textureSet;
80+
}
81+
for (uint32_t textureI = 0; textureI < 5; ++textureI) {
82+
auto curTexture = land->loadedData->quadTextures[quadI][textureI];
83+
if (curTexture == nullptr) {
84+
textureSets[textureI + 1] = nullptr;
85+
continue;
86+
}
87+
88+
if (curTexture->formID == 0) {
89+
// this is a default texture
90+
textureSets[textureI + 1] = GetDefaultLandTexture()->textureSet;
91+
}
92+
else {
93+
textureSets[textureI + 1] = land->loadedData->quadTextures[quadI][textureI]->textureSet;
94+
}
95+
}
96+
97+
// Assign textures to material
98+
{
99+
const unique_lock lock(extendedSlotsMutex);
100+
if (!extendedSlots.contains(hashKey)) {
101+
extendedSlots[hashKey] = {};
102+
}
103+
104+
for (uint32_t textureI = 0; textureI < 6; ++textureI) {
105+
if (textureSets[textureI] == nullptr) {
106+
continue;
107+
}
108+
109+
auto txSet = textureSets[textureI];
110+
if (txSet->GetTexturePath(static_cast<RE::BSTextureSet::Texture>(3)) != nullptr) {
111+
txSet->SetTexture(static_cast<RE::BSTextureSet::Texture>(3), extendedSlots[hashKey].parallax[textureI]);
112+
113+
if (extendedSlots[hashKey].parallax[textureI] == RE::BSGraphics::State::GetSingleton()->GetRuntimeData().defaultTextureNormalMap) {
114+
// this file was not found
115+
if (get<1>(texturesErrorLogged.insert(txSet->GetTexturePath(static_cast<RE::BSTextureSet::Texture>(3))))) {
116+
spdlog::error("Unable to find parallax map {} while setting up material", txSet->GetTexturePath(static_cast<RE::BSTextureSet::Texture>(3)));
117+
}
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}
124+
125+
void TerrainHelper::onDataLoaded() {
126+
// Get the default landscape texture set for terrain helper
127+
const auto thDefaultLandTexSet = RE::TESForm::LookupByEditorID<RE::BGSTextureSet>("LandscapeDefault");
128+
129+
// Get game default texture set
130+
const auto skyrimDefaultLand = GetDefaultLandTexture();
131+
132+
RE::BGSTextureSet* skyrimDefaultLandTexSet = nullptr;
133+
if (thDefaultLandTexSet != nullptr) {
134+
spdlog::info("LandscapeDefault EDID texture set found");
135+
skyrimDefaultLand->textureSet = thDefaultLandTexSet;
136+
}
137+
else {
138+
spdlog::info("LandscapeDefault EDID texture set not found, using default");
139+
}
140+
}
141+
142+
RE::TESLandTexture* TerrainHelper::GetDefaultLandTexture() {
143+
static const auto defaultLandTextureAddress = REL::Relocation<RE::TESLandTexture**>(RELOCATION_ID(514783, 400936));
144+
return *defaultLandTextureAddress;
145+
}

src/main.cpp

Lines changed: 7 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
#include "PCH.h"
1919

20+
#include "TerrainHelper.h"
21+
2022
#define DLLEXPORT __declspec(dllexport)
2123

2224
using namespace std;
2325

24-
unordered_set<string> texturesErrorLogged;
26+
2527
void SetupLog() {
2628
auto logsFolder = SKSE::log::log_directory();
2729
if (!logsFolder) {
@@ -35,63 +37,18 @@ void SetupLog() {
3537
spdlog::flush_on(spdlog::level::trace);
3638
}
3739

38-
struct ExtendedSlots {
39-
array<RE::NiSourceTexturePtr, 6> parallax;
40-
};
41-
mutex extendedSlotsMutex;
42-
unordered_map<uint32_t, ExtendedSlots> extendedSlots;
43-
4440
struct BSLightingShader_SetupMaterial
4541
{
4642
static void thunk(RE::BSLightingShader* shader, RE::BSLightingShaderMaterialBase const* material)
4743
{
4844
func(shader, material);
4945

50-
if (material == nullptr) {
51-
// material is null
52-
return;
53-
}
54-
55-
ExtendedSlots materialBase;
56-
{
57-
const lock_guard<mutex> lock(extendedSlotsMutex);
58-
if (!extendedSlots.contains(material->hashKey)) {
59-
// hash does not exists
60-
return;
61-
}
62-
63-
materialBase = extendedSlots[material->hashKey];
64-
}
65-
66-
// get renderer and shadow state
67-
auto shadowState = RE::BSGraphics::RendererShadowState::GetSingleton();
68-
69-
const auto& stateData = RE::BSGraphics::State::GetSingleton()->GetRuntimeData();
70-
71-
static constexpr size_t ParallaxStartIndex = 16; // 16-21
72-
73-
// Populate extended slots
74-
for (uint32_t textureI = 0; textureI < 6; ++textureI) {
75-
const uint32_t heightIndex = ParallaxStartIndex + textureI;
76-
if (materialBase.parallax[textureI] != nullptr && materialBase.parallax[textureI] != stateData.defaultTextureNormalMap) {
77-
shadowState->SetPSTexture(heightIndex, materialBase.parallax[textureI]->rendererTexture);
78-
}
79-
else {
80-
// set default texture, which is empty
81-
shadowState->SetPSTexture(heightIndex, nullptr);
82-
}
83-
}
46+
TerrainHelper::BSLightingShader_SetupMaterial(shader, material);
8447
};
8548

8649
static inline REL::Relocation<decltype(thunk)> func;
8750
};
8851

89-
RE::TESLandTexture* GetDefaultLandTexture()
90-
{
91-
static const auto defaultLandTextureAddress = REL::Relocation<RE::TESLandTexture**>(RELOCATION_ID(514783, 400936));
92-
return *defaultLandTextureAddress;
93-
}
94-
9552
struct TESObjectLAND_SetupMaterial
9653
{
9754
static bool thunk(RE::TESObjectLAND* land)
@@ -103,107 +60,17 @@ struct TESObjectLAND_SetupMaterial
10360
return vanResult;
10461
}
10562

106-
for (uint32_t quadI = 0; quadI < 4; ++quadI) {
107-
// Get hash key of vanilla material
108-
uint32_t hashKey = 0;
109-
110-
if (land->loadedData->mesh[quadI] == nullptr) {
111-
// continue if cannot find mesh
112-
continue;
113-
}
114-
115-
const auto& children = land->loadedData->mesh[quadI]->GetChildren();
116-
auto geometry = children.empty() ? nullptr : static_cast<RE::BSGeometry*>(children[0].get());
117-
if (geometry != nullptr) {
118-
const auto shaderProp = static_cast<RE::BSLightingShaderProperty*>(geometry->GetGeometryRuntimeData().properties[1].get());
119-
if (shaderProp != nullptr) {
120-
hashKey = shaderProp->GetBaseMaterial()->hashKey;
121-
}
122-
}
123-
124-
if (hashKey == 0) {
125-
// continue if cannot find hash key
126-
continue;
127-
}
128-
129-
{
130-
const lock_guard<mutex> lock(extendedSlotsMutex);
131-
if (!extendedSlots.contains(hashKey)) {
132-
extendedSlots[hashKey] = {};
133-
}
134-
}
135-
136-
// Create array of texture sets (6 tiles)
137-
std::array<RE::BGSTextureSet*, 6> textureSets;
138-
auto defTexture = land->loadedData->defQuadTextures[quadI];
139-
if (defTexture != nullptr && defTexture->formID != 0) {
140-
textureSets[0] = defTexture->textureSet;
141-
}
142-
else {
143-
// this is a default texture
144-
textureSets[0] = GetDefaultLandTexture()->textureSet;
145-
}
146-
for (uint32_t textureI = 0; textureI < 5; ++textureI) {
147-
auto curTexture = land->loadedData->quadTextures[quadI][textureI];
148-
if (curTexture == nullptr) {
149-
textureSets[textureI + 1] = nullptr;
150-
continue;
151-
}
152-
153-
if (curTexture->formID == 0) {
154-
// this is a default texture
155-
textureSets[textureI + 1] = GetDefaultLandTexture()->textureSet;
156-
}
157-
else {
158-
textureSets[textureI + 1] = land->loadedData->quadTextures[quadI][textureI]->textureSet;
159-
}
160-
}
161-
162-
// Assign textures to material
163-
for (uint32_t textureI = 0; textureI < 6; ++textureI) {
164-
if (textureSets[textureI] == nullptr) {
165-
continue;
166-
}
167-
168-
auto txSet = textureSets[textureI];
169-
if (txSet->GetTexturePath(static_cast<RE::BSTextureSet::Texture>(3)) != nullptr) {
170-
const lock_guard<mutex> lock(extendedSlotsMutex);
171-
txSet->SetTexture(static_cast<RE::BSTextureSet::Texture>(3), extendedSlots[hashKey].parallax[textureI]);
172-
173-
if (extendedSlots[hashKey].parallax[textureI] == RE::BSGraphics::State::GetSingleton()->GetRuntimeData().defaultTextureNormalMap) {
174-
// this file was not found
175-
if (get<1>(texturesErrorLogged.insert(txSet->GetTexturePath(static_cast<RE::BSTextureSet::Texture>(3))))) {
176-
spdlog::error("Unable to find parallax map {} while setting up material", txSet->GetTexturePath(static_cast<RE::BSTextureSet::Texture>(3)));
177-
}
178-
}
179-
}
180-
}
181-
}
182-
63+
TerrainHelper::TESObjectLAND_SetupMaterial(land);
18364
return true;
18465
}
18566

18667
static inline REL::Relocation<decltype(thunk)> func;
18768
};
18869

189-
void onDataLoaded() {
190-
// Get the default landscape texture set for terrain helper
191-
const auto defaultLandTextureSet = RE::TESForm::LookupByEditorID<RE::BGSTextureSet>("LandscapeDefault");
192-
193-
if (defaultLandTextureSet != nullptr) {
194-
spdlog::info("LandscapeDefault EDID texture set found");
195-
// set default texture set to this one
196-
GetDefaultLandTexture()->textureSet = defaultLandTextureSet;
197-
}
198-
else {
199-
spdlog::info("LandscapeDefault EDID texture set not found, using default");
200-
}
201-
}
202-
20370
void MessageHandler(SKSE::MessagingInterface::Message* a_msg) {
20471
switch (a_msg->type) {
20572
case SKSE::MessagingInterface::kDataLoaded:
206-
onDataLoaded();
73+
TerrainHelper::onDataLoaded();
20774
break;
20875
}
20976
}
@@ -235,8 +102,7 @@ extern "C" DLLEXPORT constinit auto SKSEPlugin_Version = []() noexcept {
235102
return v;
236103
}();
237104

238-
extern "C" DLLEXPORT bool SKSEAPI SKSEPlugin_Query(const SKSE::QueryInterface*, SKSE::PluginInfo* pluginInfo)
239-
{
105+
extern "C" DLLEXPORT bool SKSEAPI SKSEPlugin_Query(const SKSE::QueryInterface*, SKSE::PluginInfo* pluginInfo) {
240106
pluginInfo->name = SKSEPlugin_Version.pluginName;
241107
pluginInfo->infoVersion = SKSE::PluginInfo::kVersion;
242108
pluginInfo->version = SKSEPlugin_Version.pluginVersion;

0 commit comments

Comments
 (0)