Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/Mod.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class ModValue : public IModValue {
return m_value;
}

T& default_value() {
const T& default_value() const {
return m_default_value;
}

Expand Down
142 changes: 141 additions & 1 deletion src/Mods.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <optional>
#include <string>
#include <string_view>

#include <spdlog/spdlog.h>

#include "Framework.hpp"
Expand All @@ -9,6 +16,128 @@
#include "mods/UObjectHook.hpp"
#include "Mods.hpp"

namespace {

constexpr const char* UI_INVERT_ALPHA_KEY = "UI_InvertAlpha";
const std::string UI_INVERT_ALPHA_KEY_STRING{UI_INVERT_ALPHA_KEY};
constexpr float UI_INVERT_ALPHA_SLIDER_MIN = 0.01f;
constexpr float UI_INVERT_ALPHA_SLIDER_MAX = 0.99f;

std::string_view trim(std::string_view value) {
while (!value.empty() && std::isspace(static_cast<unsigned char>(value.front()))) {
value.remove_prefix(1);
}

while (!value.empty() && std::isspace(static_cast<unsigned char>(value.back()))) {
value.remove_suffix(1);
}

return value;
}

std::optional<float> parse_float(std::string_view value) {
const auto trimmed = trim(value);

if (trimmed.empty()) {
return std::nullopt;
}

std::string buffer{trimmed};
char* end_ptr{};
const auto parsed_value = std::strtof(buffer.c_str(), &end_ptr);

if (end_ptr == buffer.c_str()) {
return std::nullopt;
}

while (*end_ptr != '\0') {
if (!std::isspace(static_cast<unsigned char>(*end_ptr))) {
return std::nullopt;
}

++end_ptr;
}

return parsed_value;
}

bool migrate_ui_invert_alpha(utility::Config& cfg) {
auto apply_value = [&](float new_value) {
const auto clamped = std::clamp(new_value, UI_INVERT_ALPHA_SLIDER_MIN, UI_INVERT_ALPHA_SLIDER_MAX);
cfg.set<float>(UI_INVERT_ALPHA_KEY_STRING, clamped);
return true;
};

auto safe_get_bool = [&]() -> std::optional<bool> {
try {
if (auto value = cfg.get<bool>(UI_INVERT_ALPHA_KEY_STRING)) {
return *value;
}
} catch (...) {
}

return std::nullopt;
};

if (auto legacy_bool = safe_get_bool()) {
return apply_value(*legacy_bool ? UI_INVERT_ALPHA_SLIDER_MAX : UI_INVERT_ALPHA_SLIDER_MIN);
}

auto safe_get_float = [&]() -> std::optional<float> {
try {
if (auto value = cfg.get<float>(UI_INVERT_ALPHA_KEY_STRING)) {
return *value;
}
} catch (...) {
}

return std::nullopt;
};

if (auto legacy_float = safe_get_float()) {
if (*legacy_float < UI_INVERT_ALPHA_SLIDER_MIN || *legacy_float > UI_INVERT_ALPHA_SLIDER_MAX) {
return apply_value(*legacy_float);
}

return false;
}

auto safe_get_string = [&]() -> std::optional<std::string> {
try {
return cfg.get<std::string>(UI_INVERT_ALPHA_KEY_STRING);
} catch (...) {
return std::nullopt;
}
};

if (auto legacy_string = safe_get_string()) {
const auto trimmed = trim(*legacy_string);

if (!trimmed.empty()) {
std::string lowered{trimmed};
std::transform(lowered.begin(), lowered.end(), lowered.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});

if (lowered == "true" || lowered == "false") {
return apply_value(lowered == "true" ? UI_INVERT_ALPHA_SLIDER_MAX : UI_INVERT_ALPHA_SLIDER_MIN);
}

if (lowered == "1" || lowered == "0") {
return apply_value(lowered == "1" ? UI_INVERT_ALPHA_SLIDER_MAX : UI_INVERT_ALPHA_SLIDER_MIN);
}

if (auto parsed = parse_float(trimmed)) {
return apply_value(*parsed);
}
}
}

return false;
}

} // namespace

Mods::Mods() {
m_mods.emplace_back(FrameworkConfig::get());
m_mods.emplace_back(VR::get());
Expand Down Expand Up @@ -54,12 +183,23 @@ std::optional<std::string> Mods::on_initialize_d3d_thread() const {
}

void Mods::reload_config(bool set_defaults) const {
utility::Config cfg{ Framework::get_persistent_dir("config.txt").string() };
const auto config_path = Framework::get_persistent_dir("config.txt");
utility::Config cfg{ config_path.string() };

const auto migrated_invert_alpha = !set_defaults && migrate_ui_invert_alpha(cfg);

for (auto& mod : m_mods) {
spdlog::info("{:s}::on_config_load()", mod->get_name().data());
mod->on_config_load(cfg, set_defaults);
}

if (migrated_invert_alpha) {
if (!cfg.save(config_path.string())) {
spdlog::warn("Failed to persist migrated UI_InvertAlpha value");
} else {
spdlog::info("Persisted migrated UI_InvertAlpha value");
}
}
}

void Mods::on_pre_imgui_frame() const {
Expand Down
176 changes: 174 additions & 2 deletions src/mods/vr/D3D11Component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <imgui_internal.h>
#include <openvr.h>
#include <d3dcompiler.h>
#include <cstring>

namespace vertex_shader1 {
#include "shaders/vs.hpp"
Expand Down Expand Up @@ -31,6 +32,22 @@ namespace pixel_shader1 {
//#define AFR_DEPTH_TEMP_DISABLED

namespace vrmod {
namespace {
constexpr const char* k_ui_invert_ps_hlsl = R"(
Texture2D Texture : register(t0);
SamplerState TextureSampler : register(s0);

float4 SpritePixelShader(float4 color : COLOR0, float2 texCoord : TEXCOORD0) : SV_Target
{
float4 tex = Texture.Sample(TextureSampler, texCoord);
float invertAmount = saturate(color.a);
float blendedAlpha = lerp(tex.a, 1.0 - tex.a, invertAmount);
float3 blendedColor = tex.rgb * color.rgb;
return float4(blendedColor, blendedAlpha);
}
)";
} // namespace

class DX11StateBackup {
public:
DX11StateBackup(ID3D11DeviceContext* context) {
Expand Down Expand Up @@ -390,6 +407,7 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) {
}

const auto is_2d_screen = vr->is_using_2d_screen();
const auto ui_invert_alpha = vr->get_overlay_component().get_ui_invert_alpha();

auto draw_2d_view = [&]() {
if (!is_2d_screen || !m_engine_tex_ref.has_texture() || !m_engine_tex_ref.has_srv()) {
Expand Down Expand Up @@ -481,8 +499,18 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) {
m_openxr.copy((uint32_t)runtimes::OpenXR::SwapchainIndex::UI_RIGHT, m_2d_screen_tex[1]);
}
} else {
if (m_engine_ui_ref.has_texture()) {
m_openxr.copy((uint32_t)runtimes::OpenXR::SwapchainIndex::UI, m_engine_ui_ref);
if (m_engine_ui_ref.has_texture() && m_engine_ui_ref.has_srv()) {
if (ui_invert_alpha > 0.0f && ensure_ui_invert_resources()) {
m_openxr.copy(
(uint32_t)runtimes::OpenXR::SwapchainIndex::UI,
nullptr,
nullptr,
[&](ID3D11Texture2D* render_target) {
render_ui_invert_to_rt(render_target, m_engine_ui_ref, ui_invert_alpha);
});
} else {
m_openxr.copy((uint32_t)runtimes::OpenXR::SwapchainIndex::UI, m_engine_ui_ref);
}
}
}

Expand Down Expand Up @@ -1022,6 +1050,9 @@ void D3D11Component::on_reset(VR* vr) {
m_constant_buffer.Reset();
m_backbuffer_batch.reset();
m_game_batch.reset();
m_ui_invert_ps.Reset();
m_ui_invert_blend.Reset();
m_ui_invert_ready = false;
m_is_shader_setup = false;

for (auto& tex : m_2d_screen_tex) {
Expand Down Expand Up @@ -1212,6 +1243,147 @@ void D3D11Component::render_srv_to_rtv(DirectX::DX11::SpriteBatch* batch, Textur
batch->End();
}

bool D3D11Component::ensure_ui_invert_resources() {
if (m_ui_invert_ready) {
return m_ui_invert_ps != nullptr && m_ui_invert_blend != nullptr;
}

m_ui_invert_ready = true;

auto& hook = g_framework->get_d3d11_hook();
auto device = hook->get_device();

if (device == nullptr) {
spdlog::error("[VR] D3D11 UI invert: device is null");
return false;
}

// Create an opaque blend state (write shader output directly).
D3D11_BLEND_DESC blend_desc{};
blend_desc.RenderTarget[0].BlendEnable = FALSE;
blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;

if (FAILED(device->CreateBlendState(&blend_desc, &m_ui_invert_blend))) {
spdlog::error("[VR] D3D11 UI invert: failed to create blend state");
m_ui_invert_blend.Reset();
return false;
}

ComPtr<ID3DBlob> ps_blob{};
ComPtr<ID3DBlob> error_blob{};

const UINT flags =
#if defined(_DEBUG)
D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_ENABLE_STRICTNESS;
#else
D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_ENABLE_STRICTNESS;
#endif

const auto hr = D3DCompile(
k_ui_invert_ps_hlsl,
strlen(k_ui_invert_ps_hlsl),
"UEVR_UIInvert",
nullptr,
nullptr,
"SpritePixelShader",
"ps_4_0",
flags,
0,
&ps_blob,
&error_blob);

if (FAILED(hr)) {
if (error_blob != nullptr) {
spdlog::error("[VR] D3D11 UI invert: shader compile failed: {}", (const char*)error_blob->GetBufferPointer());
} else {
spdlog::error("[VR] D3D11 UI invert: shader compile failed (hr=0x{:08x})", (uint32_t)hr);
}
return false;
}

if (FAILED(device->CreatePixelShader(ps_blob->GetBufferPointer(), ps_blob->GetBufferSize(), nullptr, &m_ui_invert_ps))) {
spdlog::error("[VR] D3D11 UI invert: failed to create pixel shader");
m_ui_invert_ps.Reset();
return false;
}

spdlog::info("[VR] D3D11 UI invert shader ready");
return true;
}

void D3D11Component::render_ui_invert_to_rt(ID3D11Texture2D* render_target, TextureContext& srv, float invert_amount) {
if (render_target == nullptr || !srv.has_srv()) {
return;
}

if (!ensure_ui_invert_resources()) {
return;
}

auto& hook = g_framework->get_d3d11_hook();
auto device = hook->get_device();

ComPtr<ID3D11DeviceContext> context{};
device->GetImmediateContext(&context);

DX11StateBackup backup{context.Get()};

TextureContext rtv{render_target};
if (!rtv.has_rtv()) {
return;
}

D3D11_TEXTURE2D_DESC dest_desc{};
render_target->GetDesc(&dest_desc);

float clear_color[4]{0.0f, 0.0f, 0.0f, 0.0f};
context->ClearRenderTargetView(rtv, clear_color);

ID3D11RenderTargetView* views[] = { rtv };
context->OMSetRenderTargets(1, views, nullptr);

D3D11_VIEWPORT viewport{};
viewport.Width = (float)dest_desc.Width;
viewport.Height = (float)dest_desc.Height;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;

m_game_batch->SetViewport(viewport);
context->RSSetViewports(1, &viewport);

D3D11_RECT scissor_rect{};
scissor_rect.left = 0;
scissor_rect.top = 0;
scissor_rect.right = (LONG)dest_desc.Width;
scissor_rect.bottom = (LONG)dest_desc.Height;
context->RSSetScissorRects(1, &scissor_rect);

RECT dest_rect{};
dest_rect.left = 0;
dest_rect.top = 0;
dest_rect.right = (LONG)dest_desc.Width;
dest_rect.bottom = (LONG)dest_desc.Height;

const auto tint = DirectX::XMVectorSet(1.0f, 1.0f, 1.0f, invert_amount);

auto set_custom_shaders = [&]() {
context->PSSetShader(m_ui_invert_ps.Get(), nullptr, 0);
};

m_game_batch->Begin(
DirectX::DX11::SpriteSortMode_Immediate,
m_ui_invert_blend.Get(),
nullptr,
nullptr,
nullptr,
set_custom_shaders);

m_game_batch->Draw(srv, dest_rect, tint);
m_game_batch->End();
}

bool D3D11Component::setup() {
SPDLOG_INFO_EVERY_N_SEC(1, "[VR] Setting up D3D11 textures...");

Expand Down
Loading
Loading