skyrim-vr-upscale / bench /openvr_inject_harness.cpp
phanerozoic's picture
source: kernel, HLSL in-game path, OpenVR integration, benches
5506acb verified
Raw
History Blame
8.11 kB
// svr_vrtest: minimal OpenVR D3D11 harness for the skyrim-vr-upscale
// injection path. Renders a procedural diagnostic pattern (fine checker,
// concentric rings, radial spokes) to both eyes at the runtime's
// recommended size and submits through IVRCompositor, which is where the
// vrperfkit fork intercepts and runs the SVR kernel. After a settle
// period it drives vrperfkit's own hotkeys via SendInput to capture a
// still, cycle the upscale method, and capture again, so one headless run
// leaves SVR/FSR/NIS/CAS captures of the identical frame on disk.
//
// Not Skyrim: any OpenVR D3D11 process + the same two DLLs is the
// generalization claim, and this is that process.
#include <d3d11.h>
#include <d3dcompiler.h>
#include <openvr.h>
#include <cstdio>
#include <cstring>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
static const char* kShader = R"(
Texture2D dummy : register(t0);
struct VSOut { float4 pos : SV_Position; float2 uv : TEXCOORD0; };
VSOut vs(uint id : SV_VertexID) {
VSOut o;
float2 xy = float2((id << 1) & 2, id & 2);
o.pos = float4(xy * 2.0 - 1.0, 0.0, 1.0);
o.uv = xy;
return o;
}
cbuffer CB : register(b0) { float4 misc; } // x = eye, y = time, zw = size
float4 ps(VSOut i) : SV_Target {
float2 px = i.uv * misc.zw;
float2 c = misc.zw * 0.5;
float2 d = px - c;
float r = length(d);
// fine checker whose pitch grows with eccentricity bands
float pitch = 2.0 + 6.0 * step(0.25 * misc.w, r) + 8.0 * step(0.55 * misc.w, r);
float2 q = floor(px / pitch);
float checker = fmod(q.x + q.y, 2.0);
// concentric rings every 64 px and radial spokes every 15 degrees
float ring = step(fmod(r, 64.0), 2.5);
float ang = atan2(d.y, d.x);
float spoke = step(fmod(abs(ang) * 57.29578, 15.0), 0.35);
float3 base = lerp(float3(0.12, 0.12, 0.14), float3(0.88, 0.88, 0.9), checker);
base = lerp(base, float3(1.0, 0.55, 0.1), ring);
base = lerp(base, float3(0.1, 0.8, 1.0), spoke);
// eye tint corner marker so L/R are distinguishable in captures
if (px.x < 40 && px.y < 40) base = misc.x < 0.5 ? float3(1, 0, 0) : float3(0, 1, 0);
return float4(base, 1.0);
}
)";
static void keytap(WORD vk) {
// vrperfkit polls the down-state per frame (GetAsyncKeyState high bit), so
// the key must stay down across several frames to be seen.
INPUT in = {};
in.type = INPUT_KEYBOARD;
in.ki.wVk = vk;
SendInput(1, &in, sizeof(INPUT));
Sleep(80);
in.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &in, sizeof(INPUT));
Sleep(40);
}
static void dump_staging(ID3D11Device* dev, ID3D11DeviceContext* ctx,
ID3D11Texture2D* tex, uint32_t w, uint32_t h,
const char* path) {
D3D11_TEXTURE2D_DESC sd = {};
sd.Width = w;
sd.Height = h;
sd.MipLevels = 1;
sd.ArraySize = 1;
sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.SampleDesc.Count = 1;
sd.Usage = D3D11_USAGE_STAGING;
sd.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
ID3D11Texture2D* stg = nullptr;
if (FAILED(dev->CreateTexture2D(&sd, nullptr, &stg))) return;
ctx->CopyResource(stg, tex);
D3D11_MAPPED_SUBRESOURCE m;
if (SUCCEEDED(ctx->Map(stg, 0, D3D11_MAP_READ, 0, &m))) {
FILE* f = fopen(path, "wb");
if (f) {
fprintf(f, "P6\n%u %u\n255\n", w, h);
for (uint32_t y = 0; y < h; ++y) {
const unsigned char* row = (const unsigned char*)m.pData + y * m.RowPitch;
for (uint32_t x = 0; x < w; ++x) fwrite(row + x * 4, 1, 3, f);
}
fclose(f);
}
ctx->Unmap(stg, 0);
}
stg->Release();
}
int main(int argc, char** argv) {
int frames = 2000;
if (argc > 1) frames = atoi(argv[1]);
bool selftest = (argc > 2 && strcmp(argv[2], "--selftest") == 0);
vr::IVRSystem* sys = nullptr;
uint32_t w = 1098, h = 1220;
if (!selftest) {
vr::EVRInitError err = vr::VRInitError_None;
sys = vr::VR_Init(&err, vr::VRApplication_Scene);
if (err != vr::VRInitError_None) {
printf("VR_Init failed: %s\n",
vr::VR_GetVRInitErrorAsEnglishDescription(err));
return 1;
}
sys->GetRecommendedRenderTargetSize(&w, &h);
printf("recommended per-eye: %ux%u\n", w, h);
}
ID3D11Device* dev = nullptr;
ID3D11DeviceContext* ctx = nullptr;
D3D_FEATURE_LEVEL fl;
if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
nullptr, 0, D3D11_SDK_VERSION, &dev, &fl,
&ctx))) {
printf("D3D11CreateDevice failed\n");
return 1;
}
D3D11_TEXTURE2D_DESC td = {};
td.Width = w;
td.Height = h;
td.MipLevels = 1;
td.ArraySize = 1;
td.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
td.SampleDesc.Count = 1;
td.Usage = D3D11_USAGE_DEFAULT;
td.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
ID3D11Texture2D* eye[2] = {};
ID3D11RenderTargetView* rtv[2] = {};
for (int e = 0; e < 2; ++e) {
if (FAILED(dev->CreateTexture2D(&td, nullptr, &eye[e]))) return 1;
if (FAILED(dev->CreateRenderTargetView(eye[e], nullptr, &rtv[e]))) return 1;
}
ID3DBlob *vsb = nullptr, *psb = nullptr, *errb = nullptr;
if (FAILED(D3DCompile(kShader, strlen(kShader), nullptr, nullptr, nullptr,
"vs", "vs_5_0", 0, 0, &vsb, &errb))) {
printf("vs: %s\n", errb ? (char*)errb->GetBufferPointer() : "?");
return 1;
}
if (FAILED(D3DCompile(kShader, strlen(kShader), nullptr, nullptr, nullptr,
"ps", "ps_5_0", 0, 0, &psb, &errb))) {
printf("ps: %s\n", errb ? (char*)errb->GetBufferPointer() : "?");
return 1;
}
ID3D11VertexShader* vs = nullptr;
ID3D11PixelShader* ps = nullptr;
HRESULT hrv = dev->CreateVertexShader(vsb->GetBufferPointer(),
vsb->GetBufferSize(), nullptr, &vs);
HRESULT hrp = dev->CreatePixelShader(psb->GetBufferPointer(),
psb->GetBufferSize(), nullptr, &ps);
if (FAILED(hrv) || FAILED(hrp) || !vs || !ps) {
printf("shader create failed: vs=0x%08lx ps=0x%08lx\n", hrv, hrp);
return 1;
}
D3D11_BUFFER_DESC bd = {};
bd.ByteWidth = 16;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
ID3D11Buffer* cb = nullptr;
if (FAILED(dev->CreateBuffer(&bd, nullptr, &cb))) {
printf("cbuffer create failed\n");
return 1;
}
// No culling: the fullscreen triangle is CCW in NDC, which the default
// rasterizer (cull back) would discard entirely.
D3D11_RASTERIZER_DESC rd = {};
rd.FillMode = D3D11_FILL_SOLID;
rd.CullMode = D3D11_CULL_NONE;
ID3D11RasterizerState* rs = nullptr;
if (FAILED(dev->CreateRasterizerState(&rd, &rs))) {
printf("rasterizer create failed\n");
return 1;
}
ctx->RSSetState(rs);
auto render_eye = [&](int e, int f) {
float misc[4] = {(float)e, f * 0.011f, (float)w, (float)h};
ctx->UpdateSubresource(cb, 0, nullptr, misc, 0, 0);
ctx->OMSetRenderTargets(1, &rtv[e], nullptr);
D3D11_VIEWPORT vp = {0, 0, (float)w, (float)h, 0, 1};
ctx->RSSetViewports(1, &vp);
ctx->RSSetState(rs);
ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
ctx->IASetInputLayout(nullptr);
ctx->VSSetShader(vs, nullptr, 0);
ctx->VSSetConstantBuffers(0, 1, &cb);
ctx->PSSetShader(ps, nullptr, 0);
ctx->PSSetConstantBuffers(0, 1, &cb);
ctx->Draw(3, 0);
};
if (selftest) {
render_eye(0, 100);
ctx->Flush();
dump_staging(dev, ctx, eye[0], w, h, "D:\\svr_vrtest\\dump\\selftest.ppm");
printf("selftest: wrote dump/selftest.ppm (%ux%u)\n", w, h);
return 0;
}
vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];
for (int f = 0; f < frames; ++f) {
vr::VRCompositor()->WaitGetPoses(poses, vr::k_unMaxTrackedDeviceCount,
nullptr, 0);
for (int e = 0; e < 2; ++e) {
render_eye(e, f);
vr::Texture_t t = {eye[e], vr::TextureType_DirectX,
vr::ColorSpace_Gamma};
vr::VRCompositor()->Submit(e == 0 ? vr::Eye_Left : vr::Eye_Right, &t);
}
}
printf("done\n");
vr::VR_Shutdown();
return 0;
}