-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxeunlock.lua
More file actions
321 lines (273 loc) · 12.6 KB
/
Copy pathxeunlock.lua
File metadata and controls
321 lines (273 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
--[[
xeunlock.lua - XeUnlock
Unlocks the Intel Xe features that ship inside Resident Evil Requiem but
were never wired up: XeSS Super Resolution quality selection and native
XeSS Frame Generation. Xe Low Latency is already active out of the box.
INSTALL
<game>/reframework/autorun/xeunlock.lua
REQUIRES
REFramework as <game>/dinput8.dll (tested: nightly 01398)
Special K as <game>/reframework/plugins/dxgi.dll (tested: SK_26_8_12)
Special K is NOT optional. Without it, enabling frame generation makes
REFramework re-hook D3D12 every 11.007 s, which stutters badly. Do not
install Special K in the game root - that prevents the game from starting
(0xC0000142). See the README.
REQUIRED GAME SETTINGS
Motion Blur -> Off
Lens Distortion -> Off (also controls chromatic aberration)
With these on, ghosting is severe at higher quality levels. Not an XeSS
defect: temporal upscalers reconstruct from motion vectors, and these
effects break the correspondence between the frame and its motion data.
----------------------------------------------------------------------
NOTES FOR ANYONE EXTENDING THIS
----------------------------------------------------------------------
1. via.render.FrameGenerationInterface and via.render.UpscalingInterface
are STATIC FACADES. They have no instance. Every method is Static, so
call them with this = nil. sdk.hook installs on them fine and never
fires - measured over 11,041 frames with an empty call counter.
via.render.XeSSFrameGenerationInterface IS instance-based, and you get
that instance from the STATIC getter get_XeSSInterface().
2. Most state getters lie. Measured:
isUpscalingAvailable / isFrameGenerationAvailable -> real
get_EnableUpscaling -> real
getUpscalingQualityDisplayName -> real*
getUpscalingAlgorithm -> always 0
get_UpscalingQuality -> always 0
get_UpscalingQualityCount -> always 0
getFrameGenerationAlgorithm -> always 0
get_UsingFrameGenerationAlgorithm -> always 0
getUpscalingQualityDisplayValue -> always 0
getUpscalingAlgorithm() returned 0/None while XeSS was demonstrably
running at 1506x848. Verify by image and framerate, never by getter.
* only after set_EnableUpscaling(true). Before that it returns nil,
garbage strings and mojibake.
3. Quality indices are PER ALGORITHM. XeSS index 0 is not FSR3 index 0.
Never hardcode a table - ask the engine. An earlier version of this
script hardcoded one and had the order exactly backwards.
4. Do not measure frame generation with re.on_frame. It counts ENGINE
frames and is blind to generated ones - it read a flat ~75 while an
external counter showed 125. Use an external overlay.
--]]
local TAG = "[XeUnlock]"
local function L(fmt, ...)
local ok, s = pcall(string.format, fmt, ...)
log.info(TAG .. " " .. (ok and s or tostring(fmt)))
end
local function i64(v) local ok, r = pcall(sdk.to_int64, v); return ok and r or nil end
local UPS = "via.render.UpscalingInterface"
local FGI = "via.render.FrameGenerationInterface"
local XFGI = "via.render.XeSSFrameGenerationInterface"
-- via.render.UpscalingInterface.UpscalingAlgorithm. Max=15 is a sentinel.
local ALGS = {
{ v = 0, name = "None" },
{ v = 1, name = "DLSS" },
{ v = 2, name = "DLAA" },
{ v = 3, name = "FSR1" },
{ v = 4, name = "FSR2" },
{ v = 5, name = "FSR3" },
{ v = 6, name = "XESS" },
{ v = 7, name = "NIS" },
{ v = 8, name = "MetalFXSpatial" },
{ v = 9, name = "MetalFXTemporal" },
{ v = 10, name = "PSSR" },
{ v = 11, name = "NDSR" },
{ v = 12, name = "DirectSR" },
{ v = 13, name = "AASR" },
{ v = 14, name = "GSR2" },
}
local NOTE = "index 0 = best quality, higher = more aggressive (read from engine)"
-- Valid indices stop at 6 for XeSS. Past the valid range the engine returns
-- adjacent memory, so the probe stops at the first index with no name.
local PROBE_MAX = 8
local T0 = os.time()
local function elapsed() return os.time() - T0 end
local ST = { lines = {}, fg_on = false, levels = nil, alg = nil }
local function P(s)
ST.lines[#ST.lines+1] = "[" .. tostring(elapsed()) .. "s] " .. s
if #ST.lines > 14 then table.remove(ST.lines, 1) end
end
----------------------------------------------------------------------
-- calls
----------------------------------------------------------------------
-- static method: this = nil
local function st(type_name, method, ...)
local td = sdk.find_type_definition(type_name)
if not td then return nil, "no typedef" end
local m = td:get_method(method)
if not m then return nil, "no method " .. method end
local a = { ... }
local unpk = table.unpack or unpack
local ok, r = pcall(function() return m:call(nil, unpk(a)) end)
if ok then return r, "ok" end
return nil, tostring(r)
end
-- instance method on a raw pointer
local function inst(type_name, ptr, method, ...)
if not ptr then return nil, "no instance" end
local td = sdk.find_type_definition(type_name)
if not td then return nil, "no typedef" end
local m = td:get_method(method)
if not m then return nil, "no method " .. method end
local a = { ... }
local unpk = table.unpack or unpack
local ok, r = pcall(function() return m:call(ptr, unpk(a)) end)
if ok then return r, "ok" end
return nil, tostring(r)
end
----------------------------------------------------------------------
-- upscaler
----------------------------------------------------------------------
local function check_availability()
L("--- isUpscalingAvailable per algorithm ---")
for _, a in ipairs(ALGS) do
local d = st(UPS, "isUpscalingAvailable", a.v)
a.avail = tostring(d)
L(" %2d %-16s -> %s", a.v, a.name, tostring(d))
end
P("availability checked")
end
-- Probes the quality levels of the CURRENT algorithm.
-- Does not use get_UpscalingQualityCount - that always returns 0.
local function probe_levels()
-- REQUIRED ORDER: without upscaling enabled the probe returns garbage.
st(UPS, "set_EnableUpscaling", true)
L("--- probing quality levels (0..%d) ---", PROBE_MAX)
local t = {}
for i = 0, PROBE_MAX do
local nm = st(UPS, "getUpscalingQualityDisplayName", i)
local s = nm and tostring(nm) or nil
if s == "" then s = nil end
if not s then
L(" index %2d : no name - end of valid range", i)
break
end
t[#t+1] = { i = i, name = s }
L(" index %2d : %s", i, s)
end
ST.levels = t
if #t == 0 then
L(" nothing responded - falling back to raw indices")
P("level probe: nothing. using raw indices")
else
P("level probe: " .. tostring(#t) .. " levels")
end
end
local function pick(alg, name)
local _, via = st(UPS, "setUpscalingAlgorithm", alg)
L("setUpscalingAlgorithm(%d = %s) -> %s", alg, name, tostring(via))
P("upscaler -> " .. name .. ": " .. tostring(via))
ST.alg = name
ST.levels = nil -- indices mean different things per algorithm
pcall(probe_levels)
end
local function enable(v)
local _, via = st(UPS, "set_EnableUpscaling", v)
L("set_EnableUpscaling(%s) -> %s", tostring(v), tostring(via))
P("upscaling " .. (v and "ENABLED" or "DISABLED") .. ": " .. tostring(via))
end
local function apply(index, label)
local _, via = st(UPS, "set_UpscalingQuality", index)
L("set_UpscalingQuality(%d) [%s] -> %s", index, tostring(label), tostring(via))
P(string.format("quality %d (%s): %s", index, tostring(label), tostring(via)))
end
local function diagnostics()
local en = st(UPS, "get_EnableUpscaling")
local alg = i64((st(UPS, "getUpscalingAlgorithm")))
local q = i64((st(UPS, "get_UpscalingQuality")))
local cnt = i64((st(UPS, "get_UpscalingQualityCount")))
L("DIAG enabled=%s | alg=%s quality=%s count=%s (last three always lie)",
tostring(en), tostring(alg), tostring(q), tostring(cnt))
P(string.format("en=%s | alg=%s q=%s cnt=%s",
tostring(en), tostring(alg), tostring(q), tostring(cnt)))
end
----------------------------------------------------------------------
-- frame generation
----------------------------------------------------------------------
local function fg_enable()
local d = st(FGI, "isFrameGenerationAvailable", 3)
local _, via = st(FGI, "setFrameGenerationAlgorithm", 3) -- 3 = XeSS
L("XeFG ENABLE: available=%s set=%s", tostring(d), tostring(via))
P("XeFG enabled (available=" .. tostring(d) .. ")")
ST.fg_on = true
end
local function fg_disable()
local _, via = st(FGI, "setFrameGenerationAlgorithm", 0)
L("XeFG DISABLE -> %s", tostring(via))
P("FG disabled")
ST.fg_on = false
end
-- Native MFG. Known not to stick: returns ok, reads back 0. Kept because it
-- may behave differently on the DLSS interface / NVIDIA hardware.
local function fg_mfg(n)
local xi = st(FGI, "get_XeSSInterface")
if not xi then P("MFG: no XeSSInterface"); return end
local before = i64((inst(XFGI, xi, "get_GenerateFramesCount")))
local _, via = inst(XFGI, xi, "set_GenerateFramesCount", n)
-- read back on a FRESH handle: get_XeSSInterface returns a new wrapper
-- on every call, so reusing the old one would not prove persistence
local after = i64((inst(XFGI, st(FGI, "get_XeSSInterface"), "get_GenerateFramesCount")))
L("MFG(%d): %s -> %s [%s]", n, tostring(before), tostring(after), tostring(via))
P(string.format("MFG %d: %s -> %s", n, tostring(before), tostring(after)))
end
----------------------------------------------------------------------
-- panel
----------------------------------------------------------------------
local ui_dead = false
re.on_draw_ui(function()
if ui_dead then return end
local ok, err = pcall(function()
if not imgui.tree_node("XeUnlock") then return end
------------------------------------------------------------- upscaler
imgui.text("STEP 1 - pick the upscaler")
imgui.text(" selected by us: " .. tostring(ST.alg or "none yet"))
if imgui.button("Verify which are available") then pcall(check_availability) end
for _, a in ipairs(ALGS) do
local r = string.format("%2d %-16s%s##a%d", a.v, a.name,
a.avail and (" available=" .. a.avail) or "", a.v)
if imgui.button(r) then pcall(pick, a.v, a.name) end
end
imgui.text("")
if imgui.button("Enable upscaling") then pcall(enable, true) end
if imgui.button("DISABLE upscaling") then pcall(enable, false) end
if imgui.button("Diagnostics (log)") then pcall(diagnostics) end
-------------------------------------------------------------- quality
imgui.text("")
imgui.text("STEP 2 - quality")
imgui.text(" NOTE: indices mean different things per upscaler.")
imgui.text(" " .. NOTE)
if imgui.button("Probe level names (current algorithm)") then pcall(probe_levels) end
if ST.levels and #ST.levels > 0 then
for _, n in ipairs(ST.levels) do
local r = string.format("%d - %s##q%d", n.i, tostring(n.name), n.i)
if imgui.button(r) then pcall(apply, n.i, n.name) end
end
else
imgui.text(" (engine returned no names - raw indices)")
for i = 0, 6 do
if imgui.button(string.format("index %d##r%d", i, i)) then
pcall(apply, i, "raw")
end
end
end
----------------------------------------------------- frame generation
imgui.text("")
imgui.text("STEP 3 - frame generation")
imgui.text(ST.fg_on and " XeFG: ON" or " XeFG: off")
if imgui.button("ENABLE XeSS FG") then pcall(fg_enable) end
if imgui.button("Disable FG") then pcall(fg_disable) end
imgui.text(" native MFG below does not stick - see README")
if imgui.button("MFG = 2") then pcall(fg_mfg, 2) end
if imgui.button("MFG = 3") then pcall(fg_mfg, 3) end
if imgui.button("MFG = 4") then pcall(fg_mfg, 4) end
------------------------------------------------------------------ log
imgui.text("")
imgui.text("--- log (t=" .. tostring(elapsed()) .. "s) ---")
for _, s in ipairs(ST.lines) do imgui.text(s) end
imgui.tree_pop()
end)
if not ok then
ui_dead = true
L("!! panel disabled after imgui error: %s", tostring(err))
end
end)
L("XeUnlock loaded - press Insert to open the panel")