mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 19:56:00 +02:00
Merge pull request #305 from DreadHeadHippy/amd-mp4-video-export
Replay export: MP4 export works on PCs with an AMD iGPU beside another GPU
This commit is contained in:
4 files changed
+88
No files matched your search
@@ -5,10 +5,16 @@
|
||||
#include "Engine/Game/Build/addresses.h"
|
||||
#include "Engine/Game/Build/20260929/replay_export.h"
|
||||
#include "Engine/Game/Build/supported_build.h"
|
||||
#include "Extension/UI/Overlay/gpu_diagnostics.h"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <Windows.h>
|
||||
#include <dxgi1_4.h>
|
||||
#include <wrl/client.h>
|
||||
#include <mfidl.h>
|
||||
#include <mftransform.h>
|
||||
|
||||
namespace dingosdk::replay_export {
|
||||
namespace {
|
||||
@@ -27,10 +33,71 @@ void submit(std::uintptr_t encoder, std::uintptr_t pixels, std::uintptr_t pitch,
|
||||
*reinterpret_cast<std::uint8_t*>(encoder + native::frame_permit) = 1;
|
||||
original.load()(encoder, pixels, pitch, unused);
|
||||
}
|
||||
|
||||
// A hardware H.264 encoder only accepts frames from a device on its own GPU. On
|
||||
// a PC with two GPUs (an AMD processor's integrated graphics beside a graphics
|
||||
// card) the encoder the game gets first can belong to the GPU it does not render
|
||||
// on, and the export fails with "Failed to save video".
|
||||
using EnumTransforms = HRESULT(WINAPI*)(GUID, UINT32, const MFT_REGISTER_TYPE_INFO*, const MFT_REGISTER_TYPE_INFO*,
|
||||
IMFActivate***, UINT32*);
|
||||
std::atomic<EnumTransforms> original_enum{};
|
||||
constexpr GUID video_encoder_category{0xf79eac7d, 0xe545, 0x4387, {0xbd, 0xee, 0xd6, 0x47, 0xd7, 0xbd, 0xe4, 0x2a}};
|
||||
constexpr GUID hardware_vendor_id{0x3aecb0cc, 0x035b, 0x4bcc, {0x81, 0x85, 0x2b, 0x8d, 0x55, 0x1e, 0xf3, 0xaf}};
|
||||
|
||||
// The encoders do not say which adapter they sit on, only their vendor ("VEN_1002").
|
||||
UINT render_vendor() noexcept {
|
||||
LUID render{};
|
||||
if (!overlay::render_adapter_luid(render)) return 0;
|
||||
Microsoft::WRL::ComPtr<IDXGIFactory4> factory;
|
||||
Microsoft::WRL::ComPtr<IDXGIAdapter1> adapter;
|
||||
DXGI_ADAPTER_DESC1 description{};
|
||||
if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory))) ||
|
||||
FAILED(factory->EnumAdapterByLuid(render, IID_PPV_ARGS(&adapter))) || FAILED(adapter->GetDesc1(&description)))
|
||||
return 0;
|
||||
return description.VendorId;
|
||||
}
|
||||
|
||||
HRESULT WINAPI enum_transforms(GUID category, UINT32 flags, const MFT_REGISTER_TYPE_INFO* input,
|
||||
const MFT_REGISTER_TYPE_INFO* output, IMFActivate*** activates, UINT32* count) {
|
||||
const HRESULT result = original_enum.load()(category, flags, input, output, activates, count);
|
||||
if (FAILED(result) || !IsEqualGUID(category, video_encoder_category) || !activates || !*activates || !count)
|
||||
return result;
|
||||
const UINT wanted = render_vendor();
|
||||
if (!wanted) return result;
|
||||
|
||||
std::vector<IMFActivate*> kept, dropped;
|
||||
for (UINT32 i = 0; i < *count; ++i) {
|
||||
IMFActivate* const activate = (*activates)[i];
|
||||
wchar_t vendor[64]{};
|
||||
UINT32 length{};
|
||||
bool foreign{};
|
||||
if (activate && SUCCEEDED(activate->GetString(hardware_vendor_id, vendor, 64, &length))) {
|
||||
const wchar_t* const id = wcsstr(vendor, L"VEN_");
|
||||
foreign = id && static_cast<UINT>(wcstoul(id + 4, nullptr, 16)) != wanted;
|
||||
}
|
||||
(foreign ? dropped : kept).push_back(activate);
|
||||
}
|
||||
if (dropped.empty() || kept.empty()) return result;
|
||||
for (std::size_t i = 0; i < kept.size(); ++i) (*activates)[i] = kept[i];
|
||||
*count = static_cast<UINT32>(kept.size());
|
||||
for (auto* const activate : dropped) activate->Release();
|
||||
logging::write(logging::Level::info, logging::Channel::graphics,
|
||||
"Replay video export skipped hardware encoders that are not on the graphics card the game renders on.");
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool start(std::uintptr_t base) noexcept {
|
||||
try {
|
||||
if (const auto mfplat = LoadLibraryW(L"mfplat.dll")) {
|
||||
void* const target = reinterpret_cast<void*>(GetProcAddress(mfplat, "MFTEnumEx"));
|
||||
void* previous{};
|
||||
if (target && hook_prepare(target, reinterpret_cast<void*>(&enum_transforms), &previous) == HookOk) {
|
||||
original_enum = reinterpret_cast<EnumTransforms>(previous);
|
||||
if (hook_enable(target) != HookOk) hook_remove(target);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<unsigned char, native::submit_frame_prefix.size()> bytes{};
|
||||
if (!base || !memory::read_bytes(base + native::submit_frame, bytes.data(), bytes.size()) ||
|
||||
bytes != native::submit_frame_prefix) {
|
||||
|
||||
@@ -7,5 +7,8 @@ namespace dingosdk::replay_export {
|
||||
// drops the rest. Rendering faster than it updates (an uncapped frame rate),
|
||||
// an export came out with a fraction of its frames and played that much too
|
||||
// fast. Every rendered frame of a replay export is encoded instead.
|
||||
// MP4 export on a PC with GPUs from two vendors (an AMD processor's integrated
|
||||
// graphics beside a graphics card) no longer picks the hardware encoder of the
|
||||
// GPU the game does not render on.
|
||||
bool start(std::uintptr_t base) noexcept;
|
||||
}
|
||||
@@ -1068,9 +1068,25 @@ void watch_draws(ID3D12GraphicsCommandList* list) noexcept {
|
||||
SetLastError(error);
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::atomic<std::uint64_t> render_luid{};
|
||||
}
|
||||
|
||||
bool render_adapter_luid(LUID& luid) noexcept {
|
||||
const auto value = render_luid.load();
|
||||
if (!value) return false;
|
||||
luid.LowPart = static_cast<DWORD>(value);
|
||||
luid.HighPart = static_cast<LONG>(value >> 32);
|
||||
return true;
|
||||
}
|
||||
|
||||
void record_gpu_identity(ID3D12Device* device) noexcept {
|
||||
static std::atomic<bool> recorded{};
|
||||
const auto error = GetLastError();
|
||||
if (device) {
|
||||
const auto luid = device->GetAdapterLuid();
|
||||
render_luid = (static_cast<std::uint64_t>(static_cast<std::uint32_t>(luid.HighPart)) << 32) | luid.LowPart;
|
||||
}
|
||||
try {
|
||||
ComPtr<IDXGIFactory4> factory;
|
||||
ComPtr<IDXGIAdapter1> adapter;
|
||||
|
||||
@@ -42,4 +42,6 @@ void watch_addresses(ID3D12GraphicsCommandList* list) noexcept;
|
||||
// The graphics card the game renders on and its driver's version: said in the log once, and kept
|
||||
// for crash reports' tags (crash_report::set_gpu). Always on.
|
||||
void record_gpu_identity(ID3D12Device* device) noexcept;
|
||||
// The adapter the game renders on, once record_gpu_identity has seen its device.
|
||||
bool render_adapter_luid(LUID& luid) noexcept;
|
||||
}
|
||||
Reference in new issue
Block a user