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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,7 @@ list(APPEND uevr_SOURCES
"src/mods/pluginloader/UScriptStructFunctions.hpp"
"src/mods/uobjecthook/SDKDumper.hpp"
"src/mods/vr/CVarManager.hpp"
"src/mods/vr/ChromaVoid.hpp"
"src/mods/vr/D3D11Component.hpp"
"src/mods/vr/D3D12Component.hpp"
"src/mods/vr/FFakeStereoRenderingHook.hpp"
Expand Down
54 changes: 54 additions & 0 deletions src/mods/VR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <dbt.h>

#include <imgui.h>
#include <glm/gtc/color_space.hpp>
#include <utility/Module.hpp>
#include <utility/Registry.hpp>
#include <utility/ScopeGuard.hpp>
Expand Down Expand Up @@ -2407,6 +2408,29 @@ void VR::on_draw_sidebar_entry(std::string_view name) {

m_overlay_component.on_draw_ui();

if (ImGui::TreeNode("Passthrough")) {
m_void_passthrough->draw("Chroma Key Void");

if (m_void_passthrough->value()) {
const auto key = get_void_key_color_srgb();
float col[3]{key.x, key.y, key.z};

if (ImGui::ColorEdit3("Key Color", col)) {
// Clamp: CTRL+click typed input is not range-limited by ImGui.
const auto to_byte = [](float v) { return (int32_t)(std::clamp(v, 0.0f, 1.0f) * 255.0f + 0.5f); };
m_void_color->value() = (to_byte(col[0]) << 16) | (to_byte(col[1]) << 8) | to_byte(col[2]);
}

ImGui::TextWrapped("Fills the void around the 2D screen with a chroma key. Set the same color in Virtual Desktop's passthrough settings.");

if (get_runtime()->is_openvr()) {
ImGui::TextWrapped("Requires an OpenXR runtime.");
}
}

ImGui::TreePop();
}

ImGui::TreePop();
}

Expand Down Expand Up @@ -3221,6 +3245,36 @@ void VR::set_standing_origin(const Vector4f& origin) {
m_standing_origin = origin;
}

glm::vec4 VR::get_void_key_color_srgb() const {
const auto packed = (uint32_t)m_void_color->value();
return {
((packed >> 16) & 0xFF) / 255.0f,
((packed >> 8) & 0xFF) / 255.0f,
(packed & 0xFF) / 255.0f,
1.0f};
}

glm::vec4 VR::get_void_key_color_linear() const {
// sRGB-view render targets take linear clear values; convert so the displayed bytes match the
// picked key exactly (what the runtime's chroma filter sees).
const auto srgb = get_void_key_color_srgb();
return {glm::convertSRGBToLinear(glm::vec3{srgb}), 1.0f};
}

glm::vec4 VR::get_screen_capture_clear_color() const {
if (!is_chroma_void_active()) {
return {0.0f, 0.0f, 0.0f, 1.0f};
}

// Dashboard/system overlays dim the scene; a dimmed key no longer matches the runtime's chroma
// filter and shows as a solid glow -- fall back to a black void until focus returns.
if (m_openxr->session_state != XR_SESSION_STATE_FOCUSED) {
return {0.0f, 0.0f, 0.0f, 1.0f};
}

return get_void_key_color_linear();
}

glm::quat VR::get_rotation_offset() {
ZoneScopedN(__FUNCTION__);

Expand Down
19 changes: 19 additions & 0 deletions src/mods/VR.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,20 @@ class VR : public Mod {
return m_2d_screen_mode->value();
}

// Chroma-key void is live: the void around the 2D screen clears to the user's key so runtimes
// like Virtual Desktop can composite camera passthrough in its place. OpenXR only -- the OpenVR
// 2D path has no void surface to color.
bool is_chroma_void_active() const {
return m_void_passthrough->value() && is_using_2d_screen() && get_runtime()->is_openxr();
}

// The configured chroma key, ungated. sRGB variant for UNORM-view surfaces (screen textures --
// also the space the runtime's chroma filter sees); linear for sRGB-view clears.
glm::vec4 get_void_key_color_srgb() const;
glm::vec4 get_void_key_color_linear() const;
// Void clear color for this frame: the key, or black while inactive/unfocused.
glm::vec4 get_screen_capture_clear_color() const;

bool is_roomscale_enabled() const {
return m_roomscale_movement->value() && !m_aim_temp_disabled;
}
Expand Down Expand Up @@ -895,6 +909,9 @@ class VR : public Mod {
const ModToggle::Ptr m_decoupled_pitch_ui_adjust{ ModToggle::create(generate_name("DecoupledPitchUIAdjust"), true) };
const ModToggle::Ptr m_load_blueprint_code{ ModToggle::create(generate_name("LoadBlueprintCode"), false, true) };
const ModToggle::Ptr m_2d_screen_mode{ ModToggle::create(generate_name("2DScreenMode"), false) };
// Chroma-key void for runtime passthrough (e.g. Virtual Desktop). Color packed 0xRRGGBB.
const ModToggle::Ptr m_void_passthrough{ ModToggle::create(generate_name("PassthroughVoid"), false) };
const ModInt32::Ptr m_void_color{ ModInt32::create(generate_name("PassthroughVoidColor"), 0xFF00FF) };
const ModToggle::Ptr m_roomscale_movement{ ModToggle::create(generate_name("RoomscaleMovement"), false) };
const ModToggle::Ptr m_roomscale_sweep{ ModToggle::create(generate_name("RoomscaleMovementSweep"), true) };
const ModToggle::Ptr m_swap_controllers{ ModToggle::create(generate_name("SwapControllerInputs"), false) };
Expand Down Expand Up @@ -1041,6 +1058,8 @@ class VR : public Mod {
*m_decoupled_pitch_ui_adjust,
*m_load_blueprint_code,
*m_2d_screen_mode,
*m_void_passthrough,
*m_void_color,
*m_roomscale_movement,
*m_roomscale_sweep,
*m_swap_controllers,
Expand Down
45 changes: 45 additions & 0 deletions src/mods/vr/ChromaVoid.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

#include <algorithm>
#include <array>

#include <windows.h>

// Shared chroma-void geometry for the passthrough key feature. D3D11_RECT and D3D12_RECT are both
// RECT-layout, so one set of helpers serves both backends and their math cannot drift.
namespace vrmod::chroma {
constexpr LONG PAD_PX = 4;
// Black ring at the projection layer's outer edge so a cropped-FOV boundary blends black-on-black
// instead of flashing the key during reprojection.
constexpr LONG EDGE_GUARD_PX = 16;

// Border pad clamped so tiny render targets can never produce inverted rects.
// NB: (std::min) parenthesized -- windows.h min/max macros may be live in including TUs.
inline LONG clamped_pad(LONG w, LONG h) {
const LONG limit = (std::min)(w / 4, h / 4);
return (std::max)(0L, (std::min)(PAD_PX, limit));
}

// The four edge strips of a w*h surface, `pad` px thick.
inline std::array<RECT, 4> border_ring(LONG w, LONG h, LONG pad) {
return {{
{0, 0, w, pad},
{0, h - pad, w, h},
{0, pad, pad, h - pad},
{w - pad, pad, w, h - pad},
}};
}

// Interior key rect for one eye's void, inset `guard` px inside the SUBMITTED region (the
// view_bounds crop of the texture), not the texture edge -- otherwise a projection-override crop
// larger than the guard would discard the whole black ring. Guard is clamped against the region.
inline RECT guard_interior(const float bounds[4], LONG eye_w, LONG h, LONG guard, LONG x_off) {
const LONG l = x_off + (LONG)(bounds[0] * (float)eye_w);
const LONG r = x_off + (LONG)(bounds[1] * (float)eye_w);
const LONG t = (LONG)(bounds[2] * (float)h);
const LONG b = (LONG)(bounds[3] * (float)h);
const LONG limit = (std::min)((r - l) / 4, (b - t) / 4);
const LONG g = (std::max)(0L, (std::min)(guard, limit));
return {l + g, t + g, r - g, b - g};
}
} // namespace vrmod::chroma
Loading
Loading