Skip to content
Draft
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
410 changes: 410 additions & 0 deletions FOVEATED_ISR_DESIGN.md

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions src/games/ExampleUE/ExampleUECameraModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "ExampleUECameraModule.h"

#include "memory/offsets.h"
#include "aer/ConstantsPool.h"
#include "sdk/ExampleUESDK.hpp"
#include <mods/VR.hpp>

ExampleUECameraModule* ExampleUECameraModule::get() {
static ExampleUECameraModule inst;
return &inst;
}

void ExampleUECameraModule::installHooks() {
auto calcViewFn = memory::calcViewAddr();
if (calcViewFn != 0) {
m_calcViewHook = safetyhook::create_inline(reinterpret_cast<void*>(calcViewFn), reinterpret_cast<void*>(&onCalcView));
}

auto getProjectionFn = memory::getProjectionAddr();
if (getProjectionFn != 0) {
m_getProjectionHook = safetyhook::create_inline(reinterpret_cast<void*>(getProjectionFn), reinterpret_cast<void*>(&onGetProjection));
}
}

void ExampleUECameraModule::onCalcView(sdk::APlayerCameraManager* camMgr, float dt, sdk::FMinimalViewInfo* outView) {
auto inst = get();
inst->m_calcViewHook.call<void>(camMgr, dt, outView);

auto vr = VR::get();
if (vr->is_hmd_active() && outView != nullptr) {
const auto eye = vr->get_current_eye_transform();
const auto hmd = vr->get_transform(0);
const auto offset = vr->get_transform_offset();

// Minimal application of HMD transform to the view info
const auto view = hmd * offset * eye;
outView->Location = glm::vec3{ view[3].x, view[3].y, view[3].z };
}
}

void ExampleUECameraModule::onGetProjection(glm::mat4* outProj, float fov, float aspect, float nearZ, float farZ) {
auto inst = get();
inst->m_getProjectionHook.call<void>(outProj, fov, aspect, nearZ, farZ);

auto vr = VR::get();
if (vr->is_hmd_active() && outProj != nullptr) {
GlobalPool::submit_projection(*outProj, vr->m_render_frame_count);
}
}
19 changes: 19 additions & 0 deletions src/games/ExampleUE/ExampleUECameraModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <safetyhook/inline_hook.hpp>
#include <glm/glm.hpp>

namespace sdk { struct FMinimalViewInfo; struct APlayerCameraManager; }

class ExampleUECameraModule {
public:
static ExampleUECameraModule* get();
void installHooks();

private:
safetyhook::InlineHook m_calcViewHook{};
safetyhook::InlineHook m_getProjectionHook{};

static void onCalcView(sdk::APlayerCameraManager* camMgr, float dt, sdk::FMinimalViewInfo* outView);
static void onGetProjection(glm::mat4* outProj, float fov, float aspect, float nearZ, float farZ);
};
20 changes: 20 additions & 0 deletions src/games/ExampleUE/ExampleUEEntry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "ExampleUEEntry.h"

#include "ExampleUECameraModule.h"
#include "ExampleUERendererModule.h"
#include <imgui.h>

std::optional<std::string> ExampleUEEntry::on_initialize() {
ExampleUERendererModule::get()->installHooks();
ExampleUECameraModule::get()->installHooks();
return std::nullopt;
}

void ExampleUEEntry::on_draw_ui() {
if (!ImGui::CollapsingHeader(get_name().data())) {
return;
}

m_hudScale->draw("HUD Scale");
m_decoupledPitch->draw("Decoupled Pitch");
}
19 changes: 19 additions & 0 deletions src/games/ExampleUE/ExampleUEEntry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "Mod.hpp"

class ExampleUEEntry : public Mod {
public:
static std::shared_ptr<ExampleUEEntry>& get() {
static auto inst = std::make_shared<ExampleUEEntry>();
return inst;
}

std::string_view get_name() const override { return "ExampleUE VR"; }
std::optional<std::string> on_initialize() override;
void on_draw_ui() override;

private:
ModSlider::Ptr m_hudScale = ModSlider::create("HudScale", 0.1f, 1.0f, 0.5f);
ModToggle::Ptr m_decoupledPitch = ModToggle::create("DecoupledPitch", false);
};
47 changes: 47 additions & 0 deletions src/games/ExampleUE/ExampleUERendererModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "ExampleUERendererModule.h"

#include "memory/offsets.h"
#include <Framework.hpp>
#include <mods/VR.hpp>

ExampleUERendererModule* ExampleUERendererModule::get() {
static ExampleUERendererModule inst;
return &inst;
}

void ExampleUERendererModule::installHooks() {
auto beginFrameFn = memory::beginFrameAddr();
if (beginFrameFn != 0) {
m_beginFrameHook = safetyhook::create_inline(reinterpret_cast<void*>(beginFrameFn), reinterpret_cast<void*>(&onBeginFrame));
}

auto beginRenderFn = memory::beginRenderAddr();
if (beginRenderFn != 0) {
m_beginRenderHook = safetyhook::create_inline(reinterpret_cast<void*>(beginRenderFn), reinterpret_cast<void*>(&onBeginRender));
}
}

uintptr_t ExampleUERendererModule::onBeginFrame() {
auto inst = get();
if (g_framework && g_framework->is_ready()) {
auto vr = VR::get();
vr->m_engine_frame_count++;
}
return inst->m_beginFrameHook.call<uintptr_t>();
}

uintptr_t ExampleUERendererModule::onBeginRender(void* ctx) {
auto inst = get();
if (g_framework && g_framework->is_ready()) {
auto vr = VR::get();
g_framework->enable_engine_thread();
g_framework->run_imgui_frame(false);
vr->m_render_frame_count = vr->m_engine_frame_count;
vr->on_begin_rendering(vr->m_render_frame_count);
vr->update_hmd_state(vr->m_render_frame_count);
auto result = inst->m_beginRenderHook.call<uintptr_t>(ctx);
vr->m_presenter_frame_count = vr->m_render_frame_count;
return result;
}
return inst->m_beginRenderHook.call<uintptr_t>(ctx);
}
16 changes: 16 additions & 0 deletions src/games/ExampleUE/ExampleUERendererModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <safetyhook/inline_hook.hpp>

class ExampleUERendererModule {
public:
static ExampleUERendererModule* get();
void installHooks();

private:
safetyhook::InlineHook m_beginFrameHook{};
safetyhook::InlineHook m_beginRenderHook{};

static uintptr_t onBeginFrame();
static uintptr_t onBeginRender(void* context);
};
19 changes: 19 additions & 0 deletions src/games/ExampleUE/memory/offsets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "memory/memory_mul.h"

namespace memory {
// Placeholder ExampleUE signatures; replace with game-specific patterns/offsets when integrating.
inline uintptr_t beginFrameAddr() {
return FuncRelocation("BeginFrame", "48 89 5C 24 ? 57 48 83 EC 20 48 8B D9 E8", 0x0);
}
inline uintptr_t beginRenderAddr() {
return FuncRelocation("BeginRender", "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 30", 0x0);
}
inline uintptr_t calcViewAddr() {
return FuncRelocation("CalcView", "40 53 48 83 EC 40 48 8B DA 48 8B D1", 0x0);
}
inline uintptr_t getProjectionAddr() {
return FuncRelocation("GetProjection", "48 83 EC 48 0F 29 74 24 ?", 0x0);
}
} // namespace memory
16 changes: 16 additions & 0 deletions src/games/ExampleUE/sdk/ExampleUESDK.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>

namespace sdk {

struct FMinimalViewInfo {
glm::vec3 Location{0.0f};
glm::quat Rotation{1.0f, 0.0f, 0.0f, 0.0f};
float FOV{90.0f};
};

struct APlayerCameraManager {};

} // namespace sdk
124 changes: 124 additions & 0 deletions src/mods/foveated/FoveatedAtlas.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include "FoveatedAtlas.hpp"

namespace foveated {

FoveatedAtlas& FoveatedAtlas::get() {
static FoveatedAtlas inst;
return inst;
}

bool FoveatedAtlas::initialize(ID3D12Device* dev, const AtlasConfig& cfg) {
if (dev == nullptr) {
return false;
}

m_cfg = cfg;
const auto totalWidth = getTotalWidth();
const auto totalHeight = getTotalHeight();

D3D12_HEAP_PROPERTIES heapProps{};
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;

D3D12_RESOURCE_DESC texDesc{};
texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texDesc.Alignment = 0;
texDesc.Width = totalWidth;
texDesc.Height = totalHeight;
texDesc.DepthOrArraySize = 1;
texDesc.MipLevels = 1;
texDesc.Format = m_cfg.format;
texDesc.SampleDesc.Count = 1;
texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
texDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;

D3D12_CLEAR_VALUE clear{};
clear.Format = m_cfg.format;
clear.Color[0] = 0.0f;
clear.Color[1] = 0.0f;
clear.Color[2] = 0.0f;
clear.Color[3] = 1.0f;

if (FAILED(dev->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &texDesc,
D3D12_RESOURCE_STATE_RENDER_TARGET, &clear, IID_PPV_ARGS(&m_atlas)))) {
return false;
}

D3D12_DESCRIPTOR_HEAP_DESC rtvDesc{};
rtvDesc.NumDescriptors = 1;
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
if (FAILED(dev->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&m_rtvHeap)))) {
shutdown();
return false;
}

m_rtv = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
dev->CreateRenderTargetView(m_atlas.Get(), nullptr, m_rtv);

// Depth buffer (optional)
D3D12_DESCRIPTOR_HEAP_DESC dsvDesc{};
dsvDesc.NumDescriptors = 1;
dsvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
if (SUCCEEDED(dev->CreateDescriptorHeap(&dsvDesc, IID_PPV_ARGS(&m_dsvHeap)))) {
D3D12_RESOURCE_DESC depthDesc = texDesc;
depthDesc.Format = DXGI_FORMAT_D32_FLOAT;
depthDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

D3D12_CLEAR_VALUE depthClear{};
depthClear.Format = DXGI_FORMAT_D32_FLOAT;
depthClear.DepthStencil.Depth = 1.0f;
depthClear.DepthStencil.Stencil = 0;

if (SUCCEEDED(dev->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &depthDesc,
D3D12_RESOURCE_STATE_DEPTH_WRITE, &depthClear, IID_PPV_ARGS(&m_depth)))) {
m_dsv = m_dsvHeap->GetCPUDescriptorHandleForHeapStart();
dev->CreateDepthStencilView(m_depth.Get(), nullptr, m_dsv);
}
}

m_state = D3D12_RESOURCE_STATE_RENDER_TARGET;
return true;
}

void FoveatedAtlas::shutdown() {
m_atlas.Reset();
m_depth.Reset();
m_rtvHeap.Reset();
m_dsvHeap.Reset();
m_rtv = {};
m_dsv = {};
m_state = D3D12_RESOURCE_STATE_COMMON;
}

void FoveatedAtlas::transitionToRT(ID3D12GraphicsCommandList* cmd) {
if (!m_atlas || cmd == nullptr || m_state == D3D12_RESOURCE_STATE_RENDER_TARGET) {
return;
}

D3D12_RESOURCE_BARRIER barrier{};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = m_atlas.Get();
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = m_state;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;

cmd->ResourceBarrier(1, &barrier);
m_state = D3D12_RESOURCE_STATE_RENDER_TARGET;
}

void FoveatedAtlas::transitionToSRV(ID3D12GraphicsCommandList* cmd) {
if (!m_atlas || cmd == nullptr || m_state == D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) {
return;
}

D3D12_RESOURCE_BARRIER barrier{};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = m_atlas.Get();
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = m_state;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;

cmd->ResourceBarrier(1, &barrier);
m_state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
}

} // namespace foveated
43 changes: 43 additions & 0 deletions src/mods/foveated/FoveatedAtlas.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include <d3d12.h>
#include <wrl/client.h>

namespace foveated {

struct AtlasConfig {
uint32_t fovealWidth = 1440;
uint32_t fovealHeight = 1600;
uint32_t peripheralWidth = 720;
uint32_t peripheralHeight = 800;
DXGI_FORMAT format = DXGI_FORMAT_R10G10B10A2_UNORM;
};

class FoveatedAtlas {
public:
static FoveatedAtlas& get();

bool initialize(ID3D12Device* dev, const AtlasConfig& cfg);
void shutdown();

ID3D12Resource* getTexture() const { return m_atlas.Get(); }
D3D12_CPU_DESCRIPTOR_HANDLE getRTV() const { return m_rtv; }

uint32_t getTotalWidth() const { return m_cfg.fovealWidth * 2; }
uint32_t getTotalHeight() const { return m_cfg.fovealHeight + m_cfg.peripheralHeight; }

void transitionToRT(ID3D12GraphicsCommandList* cmd);
void transitionToSRV(ID3D12GraphicsCommandList* cmd);

private:
AtlasConfig m_cfg{};
Microsoft::WRL::ComPtr<ID3D12Resource> m_atlas;
Microsoft::WRL::ComPtr<ID3D12Resource> m_depth;
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_rtvHeap;
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_dsvHeap;
D3D12_CPU_DESCRIPTOR_HANDLE m_rtv{};
D3D12_CPU_DESCRIPTOR_HANDLE m_dsv{};
D3D12_RESOURCE_STATES m_state{D3D12_RESOURCE_STATE_COMMON};
};

} // namespace foveated
Loading