Skip to content
78 changes: 37 additions & 41 deletions src/Deferred.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "Features/SubsurfaceScattering.h"
#include "Features/TerrainBlending.h"

#include "Hooks.h"
#include "Streamline.h"

struct DepthStates
Expand Down Expand Up @@ -520,7 +521,7 @@ void Deferred::DeferredPasses()

void Deferred::EndDeferred()
{
if (!inWorld)
if (!globals::state->inWorld)
return;

auto shaderCache = globals::shaderCache;
Expand Down Expand Up @@ -755,33 +756,61 @@ void Deferred::Hooks::Main_RenderShadowMaps::thunk()

void Deferred::Hooks::Main_RenderWorld::thunk(bool a1)
{
auto deferred = globals::deferred;
deferred->inWorld = true;
globals::state->inWorld = true;
func(a1);
deferred->inWorld = false;
globals::state->inWorld = false;
};

void Deferred::Hooks::Main_RenderWorld_Start::thunk(RE::BSBatchRenderer* This, uint32_t StartRange, uint32_t EndRanges, uint32_t RenderFlags, int GeometryGroup)
{
auto deferred = globals::deferred;
auto shaderCache = globals::shaderCache;

if (shaderCache->IsEnabled() && deferred->inWorld) {
if (shaderCache->IsEnabled() && globals::state->inWorld) {
// Here is where the first opaque objects start rendering
deferred->StartDeferred();
func(This, StartRange, EndRanges, RenderFlags, GeometryGroup); // RenderBatches // RenderBatches
func(This, StartRange, EndRanges, RenderFlags, GeometryGroup); // RenderBatches
} else {
func(This, StartRange, EndRanges, RenderFlags, GeometryGroup); // RenderBatches
}
};

void Deferred::RenderBlendedDecals()
{
if (!globals::state->blendedDecalRenderPasses.empty()) {
if (globals::game::isVR) {
auto& runtimeData = globals::game::shadowState->GetVRRuntimeData();
auto runtimeDataCopy = runtimeData;
runtimeData.rasterStateDepthBiasMode = 10;

for (auto& renderPass : globals::state->blendedDecalRenderPasses)
::Hooks::BSBatchRenderer_RenderPassImmediately1::func(renderPass.a_pass, renderPass.a_technique, renderPass.a_alphaTest, renderPass.a_renderFlags);

globals::state->blendedDecalRenderPasses.clear();

runtimeData = runtimeDataCopy;
} else {
auto& runtimeData = globals::game::shadowState->GetRuntimeData();
auto runtimeDataCopy = runtimeData;
runtimeData.rasterStateDepthBiasMode = 10;

for (auto& renderPass : globals::state->blendedDecalRenderPasses)
::Hooks::BSBatchRenderer_RenderPassImmediately1::func(renderPass.a_pass, renderPass.a_technique, renderPass.a_alphaTest, renderPass.a_renderFlags);

globals::state->blendedDecalRenderPasses.clear();

runtimeData = runtimeDataCopy;
}
}
}

void Deferred::Hooks::Main_RenderWorld_BlendedDecals::thunk(RE::BSShaderAccumulator* This, uint32_t RenderFlags)
{
auto deferred = globals::deferred;
auto terrainBlending = globals::features::terrainBlending;
auto shaderCache = globals::shaderCache;

if (shaderCache->IsEnabled() && deferred->inWorld) {
if (shaderCache->IsEnabled() && globals::state->inWorld) {
// Defer terrain rendering until after everything else
if (terrainBlending->loaded)
terrainBlending->RenderTerrainBlendingPasses();
Expand All @@ -796,45 +825,12 @@ void Deferred::Hooks::Main_RenderWorld_BlendedDecals::thunk(RE::BSShaderAccumula

// Blended decals
deferred->inDecals = true;
func(This, RenderFlags);
deferred->RenderBlendedDecals();
deferred->inDecals = false;

// After this point, water starts rendering
};

void Deferred::Hooks::BSShaderAccumulator_BlendedDecals_RenderGeometryGroup::thunk(RE::BSBatchRenderer* This, uint32_t StartRange, uint32_t EndRanges, uint32_t RenderFlags, int GeometryGroup)
{
auto deferred = globals::deferred;

if (deferred->inBlendedDecals) {
func(This, StartRange, EndRanges, RenderFlags, 12);
} else {
func(This, StartRange, EndRanges, RenderFlags, GeometryGroup);
}
};

void Deferred::Hooks::BSShaderAccumulator_FirstPerson_BlendedDecals::thunk(RE::BSShaderAccumulator* This, uint32_t RenderFlags)
{
auto deferred = globals::deferred;

deferred->inBlendedDecals = true;
func(This, RenderFlags);
deferred->inBlendedDecals = false;
func(This, RenderFlags);
deferred->inDecals = false;
};

void Deferred::Hooks::BSShaderAccumulator_ShadowMapOrMask_BlendedDecals::thunk(RE::BSShaderAccumulator* This, uint32_t RenderFlags)
{
auto deferred = globals::deferred;

deferred->inBlendedDecals = true;
func(This, RenderFlags);
deferred->inBlendedDecals = false;
func(This, RenderFlags);
deferred->inDecals = false;
};

void Deferred::Hooks::BSCubeMapCamera_RenderCubemap::thunk(RE::NiAVObject* camera, int a2, bool a3, bool a4, bool a5)
{
auto deferred = globals::deferred;
Expand Down
26 changes: 2 additions & 24 deletions src/Deferred.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ class Deferred
ID3D11ComputeShader* mainCompositeCS = nullptr;
ID3D11ComputeShader* mainCompositeInteriorCS = nullptr;

bool inWorld = false;
bool inBlendedDecals = false;
bool inDecals = false;
bool inReflections = false;
Expand Down Expand Up @@ -80,6 +79,8 @@ class Deferred
winrt::com_ptr<ID3D11ShaderResourceView> lutTexture = nullptr;
void BindLUT();

void RenderBlendedDecals();

struct Hooks
{
struct Main_RenderShadowMaps
Expand All @@ -106,24 +107,6 @@ class Deferred
static inline REL::Relocation<decltype(thunk)> func;
};

struct BSShaderAccumulator_BlendedDecals_RenderGeometryGroup
{
static void thunk(RE::BSBatchRenderer* This, uint32_t StartRange, uint32_t EndRanges, uint32_t RenderFlags, int GeometryGroup);
static inline REL::Relocation<decltype(thunk)> func;
};

struct BSShaderAccumulator_FirstPerson_BlendedDecals
{
static void thunk(RE::BSShaderAccumulator* This, uint32_t RenderFlags);
static inline REL::Relocation<decltype(thunk)> func;
};

struct BSShaderAccumulator_ShadowMapOrMask_BlendedDecals
{
static void thunk(RE::BSShaderAccumulator* This, uint32_t RenderFlags);
static inline REL::Relocation<decltype(thunk)> func;
};

struct BSCubeMapCamera_RenderCubemap
{
static void thunk(RE::NiAVObject* camera, int a2, bool a3, bool a4, bool a5);
Expand Down Expand Up @@ -160,11 +143,6 @@ class Deferred
stl::write_thunk_call<Main_RenderWorld_Start>(REL::RelocationID(99938, 106583).address() + REL::Relocate(0x8E, 0x84));
stl::write_thunk_call<Main_RenderWorld_BlendedDecals>(REL::RelocationID(99938, 106583).address() + REL::Relocate(0x319, 0x308, 0x321));

stl::write_thunk_call<BSShaderAccumulator_BlendedDecals_RenderGeometryGroup>(REL::RelocationID(99942, 106587).address() + REL::Relocate(0x111, 0x112));

stl::write_thunk_call<BSShaderAccumulator_FirstPerson_BlendedDecals>(REL::RelocationID(99943, 106588).address() + REL::Relocate(0xFE, 0xF4));
stl::write_thunk_call<BSShaderAccumulator_ShadowMapOrMask_BlendedDecals>(REL::RelocationID(99947, 106592).address() + 0x107);

stl::write_vfunc<0x2, BSImagespaceShaderHDRTonemapBlendCinematic_SetupTechnique>(RE::VTABLE_BSImagespaceShaderHDRTonemapBlendCinematic[0]);
stl::write_vfunc<0x2, BSImagespaceShaderHDRTonemapBlendCinematicFade_SetupTechnique>(RE::VTABLE_BSImagespaceShaderHDRTonemapBlendCinematicFade[0]);

Expand Down
3 changes: 1 addition & 2 deletions src/Features/TerrainBlending.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ void TerrainBlending::Hooks::BSBatchRenderer__RenderPassImmediately::thunk(RE::B
{
auto singleton = globals::features::terrainBlending;
auto shaderCache = globals::shaderCache;
auto deferred = globals::deferred;

if (shaderCache->IsEnabled()) {
if (singleton->renderDepth) {
Expand All @@ -261,7 +260,7 @@ void TerrainBlending::Hooks::BSBatchRenderer__RenderPassImmediately::thunk(RE::B

if (inTerrain)
func(a_pass, a_technique, a_alphaTest, a_renderFlags); // Run terrain twice
} else if (deferred->inWorld) {
} else if (globals::state->inWorld) {
if (auto shaderProperty = a_pass->shaderProperty) {
if (a_pass->shader->shaderType.get() == RE::BSShader::Type::Lighting) {
if (shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kMultiTextureLandscape)) {
Expand Down
47 changes: 32 additions & 15 deletions src/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -849,41 +849,58 @@ namespace Hooks
static inline REL::Relocation<decltype(thunk)> func;
};

struct BSBatchRenderer_RenderPassImmediately1
void BSBatchRenderer_RenderPassImmediately1::thunk(RE::BSRenderPass* a_pass, uint32_t a_technique, bool a_alphaTest, uint32_t a_renderFlags)
{
static void thunk(RE::BSRenderPass* pass, uint32_t technique, bool alphaTest, uint32_t renderFlags)
{
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(pass, technique))
return;
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(a_pass, a_technique))
return;

func(pass, technique, alphaTest, renderFlags);
// Separate deferred and forward blended decals
if (globals::state->inWorld && a_pass->accumulationHint == 3 && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
RenderPass call{ a_pass, a_technique, a_alphaTest, a_renderFlags };
globals::state->blendedDecalRenderPasses.push_back(call);
return;
}
static inline REL::Relocation<decltype(thunk)> func;
};

func(a_pass, a_technique, a_alphaTest, a_renderFlags);
}
Comment on lines +852 to +865

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null pointer check for shader property access.

The code accesses a_pass->shaderProperty->flags without checking if shaderProperty is null, which could cause crashes.

Apply this diff to add safety:

 // Separate deferred and forward blended decals
-if (globals::state->inWorld && a_pass->accumulationHint == 3 && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
+if (globals::state->inWorld && a_pass->accumulationHint == 3 && a_pass->shaderProperty && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void BSBatchRenderer_RenderPassImmediately1::thunk(RE::BSRenderPass* a_pass, uint32_t a_technique, bool a_alphaTest, uint32_t a_renderFlags)
{
static void thunk(RE::BSRenderPass* pass, uint32_t technique, bool alphaTest, uint32_t renderFlags)
{
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(pass, technique))
return;
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(a_pass, a_technique))
return;
func(pass, technique, alphaTest, renderFlags);
// Separate deferred and forward blended decals
if (globals::state->inWorld && a_pass->accumulationHint == 3 && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
RenderPass call{ a_pass, a_technique, a_alphaTest, a_renderFlags };
globals::state->blendedDecalRenderPasses.push_back(call);
return;
}
static inline REL::Relocation<decltype(thunk)> func;
};
func(a_pass, a_technique, a_alphaTest, a_renderFlags);
}
void BSBatchRenderer_RenderPassImmediately1::thunk(RE::BSRenderPass* a_pass, uint32_t a_technique, bool a_alphaTest, uint32_t a_renderFlags)
{
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(a_pass, a_technique))
return;
// Separate deferred and forward blended decals
if (globals::state->inWorld
&& a_pass->accumulationHint == 3
&& a_pass->shaderProperty
&& !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite))
{
RenderPass call{ a_pass, a_technique, a_alphaTest, a_renderFlags };
globals::state->blendedDecalRenderPasses.push_back(call);
return;
}
func(a_pass, a_technique, a_alphaTest, a_renderFlags);
}
🤖 Prompt for AI Agents
In src/Hooks.cpp around lines 852 to 865, the code accesses
a_pass->shaderProperty->flags without verifying if shaderProperty is null,
risking a crash. Add a null pointer check for a_pass->shaderProperty before
accessing its flags to ensure safety. Modify the condition to first confirm
a_pass->shaderProperty is not null, then proceed with the existing flag checks.


struct BSBatchRenderer_RenderPassImmediately2
{
static void thunk(RE::BSRenderPass* pass, uint32_t technique, bool alphaTest, uint32_t renderFlags)
static void thunk(RE::BSRenderPass* a_pass, uint32_t a_technique, bool a_alphaTest, uint32_t a_renderFlags)
{
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(pass, technique))
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(a_pass, a_technique))
return;

if (globals::features::interiorSunShadows->loaded)
globals::features::interiorSunShadows->UpdateRasterStateCullMode(pass, technique);
globals::features::interiorSunShadows->UpdateRasterStateCullMode(a_pass, a_technique);

// Separate deferred and forward blended decals
if (globals::state->inWorld && a_pass->accumulationHint == 3 && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
RenderPass call{ a_pass, a_technique, a_alphaTest, a_renderFlags };
globals::state->blendedDecalRenderPasses.push_back(call);
return;
}
Comment on lines +877 to +882

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null pointer check for shader property access.

Same issue as in the first hook - missing null check for shaderProperty.

Apply this diff to add safety:

 // Separate deferred and forward blended decals
-if (globals::state->inWorld && a_pass->accumulationHint == 3 && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
+if (globals::state->inWorld && a_pass->accumulationHint == 3 && a_pass->shaderProperty && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
🤖 Prompt for AI Agents
In src/Hooks.cpp around lines 877 to 882, the code accesses
a_pass->shaderProperty without checking if shaderProperty is null, which can
cause a crash. Add a null pointer check for a_pass->shaderProperty before
accessing its flags to ensure safety. Only proceed with the condition if
shaderProperty is not null.


func(pass, technique, alphaTest, renderFlags);
func(a_pass, a_technique, a_alphaTest, a_renderFlags);
}
static inline REL::Relocation<decltype(thunk)> func;
};

struct BSBatchRenderer_RenderPassImmediately3
{
static void thunk(RE::BSRenderPass* pass, uint32_t technique, bool alphaTest, uint32_t renderFlags)
static void thunk(RE::BSRenderPass* a_pass, uint32_t a_technique, bool a_alphaTest, uint32_t a_renderFlags)
{
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(pass, technique))
if (globals::features::lightLimitFix->loaded && !globals::features::lightLimitFix->CheckParticleLights(a_pass, a_technique))
return;

func(pass, technique, alphaTest, renderFlags);
// Separate deferred and forward blended decals
if (globals::state->inWorld && a_pass->accumulationHint == 3 && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
RenderPass call{ a_pass, a_technique, a_alphaTest, a_renderFlags };
globals::state->blendedDecalRenderPasses.push_back(call);
return;
}
Comment on lines +896 to +901

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null pointer check for shader property access.

Same issue as in the other hooks - missing null check for shaderProperty.

Apply this diff to add safety:

 // Separate deferred and forward blended decals
-if (globals::state->inWorld && a_pass->accumulationHint == 3 && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
+if (globals::state->inWorld && a_pass->accumulationHint == 3 && a_pass->shaderProperty && !a_pass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
🤖 Prompt for AI Agents
In src/Hooks.cpp around lines 896 to 901, the code accesses
a_pass->shaderProperty without checking if shaderProperty is null, which can
cause a crash. Add a null pointer check for a_pass->shaderProperty before
accessing its flags to ensure safety. Only proceed with the condition if
shaderProperty is not null.


func(a_pass, a_technique, a_alphaTest, a_renderFlags);
}
static inline REL::Relocation<decltype(thunk)> func;
};
Expand Down
14 changes: 14 additions & 0 deletions src/Hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ namespace Hooks
static void thunk(bool isCompute);
static inline REL::Relocation<decltype(thunk)> func;
};

struct BSBatchRenderer_RenderPassImmediately1
{
static void thunk(RE::BSRenderPass* pass, uint32_t technique, bool alphaTest, uint32_t renderFlags);
static inline REL::Relocation<decltype(thunk)> func;
};

struct RenderPass
{
RE::BSRenderPass* a_pass;
uint32_t a_technique;
bool a_alphaTest;
uint32_t a_renderFlags;
};
void Install();
void InstallD3DHooks();
}
4 changes: 4 additions & 0 deletions src/State.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using json = nlohmann::json;
#include <FeatureBuffer.h>

#include "reshade/reshade_api.hpp"
#include <Hooks.h>
#include <reshade/reshade.hpp>

class State
Expand Down Expand Up @@ -147,6 +148,9 @@ class State
ETMaterialModel = 0b111 << 6,
};

std::vector<::Hooks::RenderPass> blendedDecalRenderPasses;
bool inWorld = false;

void UpdateSharedData(bool a_inWorld, bool a_prepass);

struct alignas(16) PermutationCB
Expand Down
5 changes: 0 additions & 5 deletions src/TruePBR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -704,11 +704,6 @@ struct BSLightingShaderProperty_GetRenderPasses

lightingTechnique = (static_cast<uint32_t>(lightingType) << 24) | lightingFlags;
currentPass->passEnum = lightingTechnique + LightingTechniqueStart;

// Separate deferred and forward blended decals
if (currentPass->accumulationHint == 3 && currentPass->shaderProperty->flags.all(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) {
currentPass->accumulationHint = 16;
}
}
currentPass = currentPass->next;
}
Expand Down