Engine: support dynamic input resolution for presets K, L and M
This commit is contained in:
1 parent
653460f7b2
commit
37d1055228
11 files changed
+190
-47
No files matched your search
+35
-3
@@ -32,7 +32,7 @@ frame color/depth/motion ──► Engine input stage ──► 11 fused Swin la
|
||||
- Input images are borrowed Vulkan views. The caller provides the device, synchronization, and command buffer,
|
||||
so a future game adapter can schedule inference with its own rendering work.
|
||||
- Per-frame parameters include exposure, jitter, motion conventions, reset, and each layer's window shifts.
|
||||
Window shifts can change without rebuilding shaders or buffers. Resolution changes require a new `Engine`.
|
||||
Window shifts and input resolution can change without rebuilding shaders or buffers. Output resolution changes require a new `Engine`.
|
||||
- Frame parameters and window shifts are written by commands inside the frame's own command buffer, so a frame can
|
||||
be recorded while earlier ones are still queued on the GPU. Each input/output view and layout combination gets
|
||||
immutable descriptors, cached and reused; first use allocates descriptors, steady recording does not. Borrowed
|
||||
@@ -620,6 +620,38 @@ but reduced it by 0.83 dB on the independent capture. All-explicit gains improve
|
||||
consistently, so no accuracy variant became the default. `--unfold-gains` remains available;
|
||||
the retained enc0 change preserves numerical behavior on this device.
|
||||
|
||||
## Dynamic input resolution (K, L and M)
|
||||
|
||||
The engine accepts changing render subrect dimensions with a fixed output size. K keeps its output-sized
|
||||
network and temporal history, updates the frame's motion conversion and reconstruction table, and reserves
|
||||
conversion inputs up to the output size. Use a K package with all sixteen reconstruction tables for changing
|
||||
upscaling ratios; older single-table packages cannot match NGX at every ratio.
|
||||
|
||||
L and M update the Vulkan network's active token grid, exposure grid and reconstruction tier per frame.
|
||||
Their input and history planes reserve the constructor's render capacity; the shim reserves output-sized
|
||||
capacity for the Vulkan path. Bilinear reads clamp to the active rectangle. A render-size change resets
|
||||
L/M temporal history. Input dimensions must be at least 64 on each axis and cannot exceed the output.
|
||||
Standalone callers set `GameFrameM::renderWidth/renderHeight` or `MFrameSettings::renderWidth/renderHeight`;
|
||||
zero keeps the constructor size.
|
||||
|
||||
L/M's HIP network retains its fixed constructor grid. When a feature renders at another size, the shim uses
|
||||
one Vulkan engine for the changing sizes and retains the original HIP engine and shared buffers until queued
|
||||
command lists retire. Returning to the original size resumes HIP with reset history. This uses additional
|
||||
VRAM once the Vulkan engine is allocated. K's HIP network remains usable across all input-size changes.
|
||||
|
||||
Rebuild L and M packages with the current `compile_m.py` (`D4RL0002` / `D4RM0004`). Earlier packages retain fixed-size support but lack
|
||||
active-rectangle sampling, so a size change uses the CUDA fallback. The dynamic regression can be run independently:
|
||||
|
||||
```sh
|
||||
build/native-engine/test-game --dynamic-ml PATH_TO_M_OR_L_MODEL
|
||||
```
|
||||
|
||||
On the RX 9070 XT, six-frame HDR sequences for both L and M pass shrinking/growing input sizes,
|
||||
Quality/Performance tier changes and queued direct/copy comparisons. Direct and copied inputs are byte-identical,
|
||||
and implicit resize resets match explicit resets. Increasing allocation capacity changes a few recurrent pixels
|
||||
through normalized sampling and FP8 rounding: RMSE is 7.29e-6 (M) and 8.12e-6 (L), within the test's
|
||||
5e-5 RMSE and 1% per-channel error limits. This is an adapter regression, not an in-game or NGX parity claim.
|
||||
|
||||
## Preset M (runtime and shim path exist; experimental)
|
||||
|
||||
Preset M (DLSS 4.5, `rrlite_*`) is a different pipeline: an input stage that embeds 2x2-pixel tokens, a network of
|
||||
@@ -687,7 +719,7 @@ each), and the engine output is byte-identical for each too. The shim records
|
||||
preset M through `engine_evaluate_m` when `[Engine] Enabled` is set and the model is in `engine\m` next to the DLL
|
||||
(or `D4R_ENGINE_MODEL_M_DIR`).
|
||||
|
||||
M packages now use `D4RM0003`. Rebuild with the current `compile_m.py`: the blocked FP8 operands,
|
||||
M packages now use `D4RM0004` (L: `D4RL0002`). Rebuild with the current `compile_m.py`: the blocked FP8 operands,
|
||||
RGBA32F half-history bindings and 8×64 downsample dispatch must match the runtime. `D4RM0001` and
|
||||
`D4RM0002` packages are rejected rather than silently loading incompatible shaders. Native `hipnet.bin`
|
||||
remains `D4RMHIP1`; its tensor and weight layout is unchanged.
|
||||
@@ -859,7 +891,7 @@ per-pixel head (expansion), where M has single folded products, and a reconstruc
|
||||
history blended in linear light. `L_NOTES.md` has the algorithms. The network is M's ten layers with L's weights.
|
||||
|
||||
The runtime is M's (`MModel`, `MEngine`, `GameUpscalerM`, `d4r-m`): `compile_m.py` recognises a capture of preset L by
|
||||
its unfolded kernels and writes an L model directory (`offsets.bin` `D4RL0001`: the input and expansion stages as
|
||||
its unfolded kernels and writes an L model directory (`offsets.bin` `D4RL0002`: the input and expansion stages as
|
||||
layer blocks of `layer_m.comp` KIND 3 and 4, and L's five constants), which `MModel` loads as preset L
|
||||
(`isPresetL()`). The shim runs preset 12 with the model in `engine\l` / `engine\l-ldr`; the log says `engine backend
|
||||
(L): feature N runs preset L in the game's command list`.
|
||||
|
||||
+7
-6
@@ -5,14 +5,15 @@ usage: compile_m.py CAPTURE_DIR OUT_DIR [--frame N]
|
||||
CAPTURE_DIR/replay must hold the frame's rrlite_enc0 and rrlite_dec0 launches and its ten Swin layer launches,
|
||||
with their weight allocations (D4R_CUDA_REPLAY_DUMP_DIR, filter `rrlite_`). A capture of preset M has the `_folded`
|
||||
input and expansion kernels; one of preset L (DLSS preset 12) has the unfolded ones, and gets an L model directory
|
||||
(offsets.bin `D4RL0001`, L_NOTES.md): the same network, layer_m.comp's L input and expansion stages, post_m.comp's
|
||||
(offsets.bin `D4RL0002`, L_NOTES.md): the same network, layer_m.comp's L input and expansion stages, post_m.comp's
|
||||
L reconstruction.
|
||||
Writes weights8.bin (the layers' FP8 matrices in the blocked fixed-16 tile layout of test_m.pack8, with the front-end
|
||||
enc0/dec0 matrices plain and NGX-ordered as their own stages read them), weights16.bin (f16 vectors and the dither
|
||||
table), offsets.bin and the compiled shaders. The weights are values read from NVIDIA's DLSS and are not redistributable
|
||||
with d4r; the shaders are d4r's own.
|
||||
"""
|
||||
# D4RM0003 couples the blocked FP8 operands, RGBA32F half-history backing and 8x64 downsample dispatch;
|
||||
# D4RM0004 / D4RL0002 add active-rectangle sampling for dynamic input sizes.
|
||||
# D4RM0003 coupled the blocked FP8 operands, RGBA32F half-history backing and 8x64 downsample dispatch;
|
||||
# the previous package's shader workgroup dimensions are not interchangeable with this runtime.
|
||||
import argparse, glob, hashlib, json, os, struct, subprocess, sys
|
||||
from pathlib import Path
|
||||
@@ -86,7 +87,7 @@ def main():
|
||||
layers.append((add8(b8), add16(b16), o))
|
||||
blob8, blob16 = np.concatenate(w8), np.concatenate(w16)
|
||||
blob8.tofile(a.output / 'weights8.bin'); blob16.tofile(a.output / 'weights16.bin')
|
||||
head = struct.pack('<8sII', b'D4RM0003', blob8.size, blob16.size) + struct.pack('<5I', *enc0, *dec0)
|
||||
head = struct.pack('<8sII', b'D4RM0004', blob8.size, blob16.size) + struct.pack('<5I', *enc0, *dec0)
|
||||
for o8, o16, o in layers: head += struct.pack('<2I72I', o8, o16, *o)
|
||||
(a.output / 'offsets.bin').write_bytes(head)
|
||||
def glsl(src, out, flags=()):
|
||||
@@ -108,7 +109,7 @@ def main():
|
||||
elif marker.exists(): marker.unlink()
|
||||
for p in '01': glsl('exposure_k.comp', f'exposure_m{p}.spv', ['-DMODE=1', '-DPASS=' + p])
|
||||
files = sorted(a.output.glob('*.bin')) + sorted(a.output.glob('*.spv')) + sorted(a.output.glob('hip/*.hsaco'))
|
||||
(a.output / 'model.json').write_text(json.dumps({'format': 'D4RM0003', 'preset': 'M', 'files': {str(f.relative_to(a.output)): hashlib.sha256(f.read_bytes()).hexdigest() for f in files}}, indent=1))
|
||||
(a.output / 'model.json').write_text(json.dumps({'format': 'D4RM0004', 'preset': 'M', 'files': {str(f.relative_to(a.output)): hashlib.sha256(f.read_bytes()).hexdigest() for f in files}}, indent=1))
|
||||
print(f'Compiled native preset M model in {a.output} ({blob8.size} FP8 bytes, {blob16.size} bytes of f16 data)')
|
||||
|
||||
|
||||
@@ -138,7 +139,7 @@ def compile_l(a, one, R):
|
||||
consts = (arg(f'rrlite_enc0_4x4_mvlo_{R}', 268), arg(f'rrlite_enc0_4x4_mvlo_{R}', 272), arg('rrlite_dec0_4x4', 160),
|
||||
arg('rrlite_dec0_4x4', 164), arg(next(k for k in (f'rrlite_post_3_1_mvlo_{R}', f'rrlite_post_3_2_mvlo_{R}')
|
||||
if glob.glob(f'{a.capture}/replay/replay-*-{k}')), 120))
|
||||
head = struct.pack('<8sIII5f', b'D4RL0001', blob8.size, blob16.size, dither, *consts)
|
||||
head = struct.pack('<8sIII5f', b'D4RL0002', blob8.size, blob16.size, dither, *consts)
|
||||
for o8, o16, o in blocks: head += struct.pack('<2I72I', o8, o16, *o)
|
||||
(a.output / 'offsets.bin').write_bytes(head)
|
||||
def glsl(src, out, flags=()):
|
||||
@@ -164,7 +165,7 @@ def compile_l(a, one, R):
|
||||
for stale in ('enc0_m.spv', 'dec0_m.spv', 'post_m.spv', 'post_m_q.spv', 'post_m_c.spv', 'enc0_m_planes.spv', 'dec0_m_planes.spv'):
|
||||
(a.output / stale).unlink(missing_ok=True)
|
||||
files = sorted(a.output.glob('*.bin')) + sorted(a.output.glob('*.spv')) + sorted(a.output.glob('hip/*.hsaco'))
|
||||
(a.output / 'model.json').write_text(json.dumps({'format': 'D4RL0001', 'preset': 'L', 'files': {str(f.relative_to(a.output)): hashlib.sha256(f.read_bytes()).hexdigest() for f in files}}, indent=1))
|
||||
(a.output / 'model.json').write_text(json.dumps({'format': 'D4RL0002', 'preset': 'L', 'files': {str(f.relative_to(a.output)): hashlib.sha256(f.read_bytes()).hexdigest() for f in files}}, indent=1))
|
||||
print(f'Compiled native preset L model in {a.output} ({blob8.size} FP8 bytes, {blob16.size} bytes of f16 data)')
|
||||
|
||||
|
||||
|
||||
@@ -133,6 +133,8 @@ void main()
|
||||
if (p.reset == 0u && all(greaterThanEqual(f, vec2(0.0))) && all(lessThanEqual(f, p.hiSize))) {
|
||||
precise vec2 normalized = fma(f, 1.0 / p.hiSize, p.histOffs * (1.0 / vec2(p.size)));
|
||||
precise vec2 texels = normalized * vec2(p.size);
|
||||
if (any(notEqual(textureSize(featTex, 0), p.size)))
|
||||
texels = clamp(texels, vec2(0.5), vec2(p.size) - 0.5);
|
||||
precise vec2 uv = texels * p.renderInv;
|
||||
hist = f16vec4(roundHalfValue(textureLod(featTex, uv, 0.0)));
|
||||
}
|
||||
|
||||
@@ -65,7 +65,11 @@ void gatherFeatures(uint pi, ivec2 block, float e1, float e2, bool fusedFourth,
|
||||
const vec3 own = texelFetch(colorTex, texel(m), 0).rgb;
|
||||
const vec2 centre = vec2(m) + 0.5, f = centre - EP.jitter, inv = 1.0 / vec2(EP.size);
|
||||
// how much the motion changed since the previous frame
|
||||
const vec2 prev = textureLod(prevMvTex, clamp(((f + EP.prevJitter) + mvA * EP.hi2rnd) * vec2(EP.renderInvX, EP.renderInvY), vec2(0.0), vec2(EP.size) * vec2(EP.renderInvX, EP.renderInvY)), 0.0).xy;
|
||||
vec2 prevPixel = clamp(((f + EP.prevJitter) + mvA * EP.hi2rnd) * vec2(EP.renderInvX, EP.renderInvY), vec2(0.0), vec2(EP.size) * vec2(EP.renderInvX, EP.renderInvY));
|
||||
if (any(notEqual(textureSize(prevMvTex, 0), EP.size)))
|
||||
prevPixel = clamp(prevPixel, 0.5 * vec2(EP.renderInvX, EP.renderInvY),
|
||||
(vec2(EP.size) - 0.5) * vec2(EP.renderInvX, EP.renderInvY));
|
||||
const vec2 prev = textureLod(prevMvTex, prevPixel, 0.0).xy;
|
||||
const vec2 dm = (EP.changeScale * 540.0 / float(EP.size.y)) * (mvA * EP.hi2rnd - EP.hi2rnd * prev);
|
||||
const float d2 = fma(dm.x, dm.x, dm.y * dm.y);
|
||||
const float change = EP.changeGain * (log2(d2 > 0.0 ? sqrt(d2) + 1.0 : 1.0) * 0.6931472);
|
||||
@@ -102,6 +106,8 @@ void gatherFeatures(uint pi, ivec2 block, float e1, float e2, bool fusedFourth,
|
||||
{
|
||||
precise vec2 normalized = fma(q[0], 1.0 / EP.hiSize, EP.prevJitter * inv);
|
||||
precise vec2 texels = normalized * vec2(EP.size);
|
||||
if (any(notEqual(textureSize(featTex, 0), EP.size)))
|
||||
texels = clamp(texels, vec2(0.5), vec2(EP.size) - 0.5);
|
||||
precise vec2 uv = texels * vec2(EP.renderInvX, EP.renderInvY);
|
||||
feat = textureLod(featTex, uv, 0.0);
|
||||
}
|
||||
|
||||
+5
-1
@@ -23,7 +23,10 @@ GameUpscalerM::~GameUpscalerM() = default;
|
||||
// the game's inputs into the engine's images, and the frame's settings
|
||||
void GameUpscalerM::Impl::inputs(VkCommandBuffer cb, const GameFrameM& frame) {
|
||||
auto& g = *this;
|
||||
const uint32_t w = g.width, h = g.height;
|
||||
const uint32_t w = frame.renderWidth ? frame.renderWidth : g.width;
|
||||
const uint32_t h = frame.renderHeight ? frame.renderHeight : g.height;
|
||||
if (w < 64 || h < 64 || w > g.width || h > g.height)
|
||||
throw std::runtime_error("render size exceeds the adapter's inputs or is below 64");
|
||||
if (!gameColorFormat(frame.color.format) || !gameMotionFormat(frame.motion.format) ||
|
||||
!gameDepthFormat(frame.depth.format, frame.depth.aspect) || !gameOutputFormat(frame.output.format))
|
||||
throw std::runtime_error("unsupported game texture format");
|
||||
@@ -40,6 +43,7 @@ void GameUpscalerM::Impl::inputs(VkCommandBuffer cb, const GameFrameM& frame) {
|
||||
}
|
||||
g.fallback.record(cb, frame.color, frame.motion, frame.depth, w, h, w, h, directColor, directMotion, directDepth);
|
||||
MFrameSettings s;
|
||||
s.renderWidth = w; s.renderHeight = h;
|
||||
s.jitter[0] = frame.jitter[0]; s.jitter[1] = frame.jitter[1];
|
||||
s.motionScale[0] = frame.motionToRender[0]; s.motionScale[1] = frame.motionToRender[1];
|
||||
s.motionOffset[0] = frame.motionOffset[0]; s.motionOffset[1] = frame.motionOffset[1];
|
||||
|
||||
+4
-2
@@ -5,16 +5,18 @@
|
||||
|
||||
namespace d4r {
|
||||
struct GameFrameM {
|
||||
GameImage color, depth, motion, output; // as for GameUpscaler; views are not used (inputs are always copied)
|
||||
GameImage color, depth, motion, output; // as for GameUpscaler; compatible views at origin zero may be borrowed
|
||||
float jitter[2] = {0, 0};
|
||||
float motionToRender[2] = {1, 1}; // (motion texel + motionOffset) * this = render pixels
|
||||
float motionOffset[2] = {0, 0}; // jittered motion vectors: jitter - previous jitter, motion texel units
|
||||
bool reset = false;
|
||||
bool depthInverted = true;
|
||||
uint32_t renderWidth = 0, renderHeight = 0; // zero selects the adapter capacity
|
||||
};
|
||||
class GameUpscalerM {
|
||||
public:
|
||||
// The model must outlive this object. The render size is fixed and at most half the output size.
|
||||
// The model must outlive this object. Constructor render dimensions allocate capacity, at most the output size.
|
||||
// Vulkan frames may change active size within that capacity; external networks keep a fixed grid.
|
||||
GameUpscalerM(MModel& model, VkPhysicalDevice physical, VkDevice device, uint32_t outputWidth, uint32_t outputHeight,
|
||||
uint32_t renderWidth, uint32_t renderHeight, const ExternalNetwork* external = nullptr);
|
||||
~GameUpscalerM(); // only once every recorded frame has completed on the GPU
|
||||
|
||||
+4
-1
@@ -197,7 +197,10 @@ void main()
|
||||
const float inv = 1.0 / W;
|
||||
const vec3 cur = inv * vec3(a0.rgb), m = inv * vec3(a0.w, a1.x, a1.y), g = inv * vec3(a1.z, a1.w, a2);
|
||||
// the two most likely motion candidates
|
||||
const vec2 mvA = texelFetch(mvATex, near, 0).xy, mvB = texelFetch(mvBTex, near, 0).xy, mvC = textureLod(mvCTex, pos * p.invRnd, 0.0).xy;
|
||||
const vec2 mvPos = any(notEqual(textureSize(mvCTex, 0), p.rndSize))
|
||||
? clamp(pos, vec2(0.5), vec2(p.rndSize) - 0.5) : pos;
|
||||
const vec2 mvA = texelFetch(mvATex, near, 0).xy, mvB = texelFetch(mvBTex, near, 0).xy,
|
||||
mvC = textureLod(mvCTex, mvPos * p.invRnd, 0.0).xy;
|
||||
vec3 big = vec3(mvA, m.x), small = vec3(mvC, m.y);
|
||||
if (big.z < small.z) { const vec3 t = big; big = small; small = t; }
|
||||
vec3 second = !(small.z < m.z) ? small : vec3(mvB, m.z);
|
||||
|
||||
+5
-1
@@ -192,6 +192,7 @@ struct MFrameSettings {
|
||||
float motionOffset[2] = {0, 0}; // jittered motion vectors: jitter - previous jitter, in motion texel units
|
||||
bool reset = false;
|
||||
bool depthInverted = true;
|
||||
uint32_t renderWidth = 0, renderHeight = 0; // zero uses the constructor size; otherwise within its capacity
|
||||
};
|
||||
class MModel {
|
||||
public:
|
||||
@@ -203,7 +204,8 @@ public:
|
||||
bool uploadRecorded() const;
|
||||
bool hasExternalNetworkStages() const; // the model directory has the plane-layout input and expansion stages
|
||||
bool hasRgb10Output() const;
|
||||
bool isPresetL() const; // a preset L model directory (offsets.bin D4RL0001): MEngine then runs preset L
|
||||
bool hasDynamicResolution() const; // active-rectangle sampling: D4RM0004 / D4RL0002
|
||||
bool isPresetL() const; // a preset L model directory (offsets.bin D4RL0002): MEngine then runs preset L
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
@@ -215,6 +217,8 @@ private:
|
||||
// rounded up to 32, halved).
|
||||
class MEngine {
|
||||
public:
|
||||
// Constructor render dimensions allocate capacity. Vulkan frames may use smaller active dimensions;
|
||||
// external networks retain their constructor grid. A size change resets temporal history.
|
||||
MEngine(MModel& model, uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight, const ExternalNetwork* external = nullptr);
|
||||
static VkDeviceSize networkBufferBytes(uint32_t renderWidth, uint32_t renderHeight);
|
||||
~MEngine();
|
||||
|
||||
+34
-17
@@ -77,9 +77,9 @@ struct MModel::Impl {
|
||||
bool planes = false;
|
||||
std::array<Pipeline, 2> exposure;
|
||||
bool uploaded = false, ldr = false;
|
||||
// preset L (offsets.bin D4RL0001, L_NOTES.md): the input and expansion stages are layer_m.comp's KIND 3 and 4,
|
||||
// preset L (offsets.bin D4RL0002, L_NOTES.md): the input and expansion stages are layer_m.comp's KIND 3 and 4,
|
||||
// with their own layer offset blocks; constants: change scale and gain, sigma, rho, the first gate's gain
|
||||
bool presetL = false;
|
||||
bool presetL = false, dynamicResolution = false;
|
||||
std::array<uint32_t, 74> lEnc0{}, lDec0{};
|
||||
uint32_t dither16 = 0;
|
||||
float lConst[5]{};
|
||||
@@ -90,9 +90,10 @@ MModel::MModel(VkPhysicalDevice physical, VkDevice device, const std::string& di
|
||||
auto& m = *impl;
|
||||
m.ldr = exists(directory + "/ldr"); // the stages were compiled for a game without the HDR flag: no exposure
|
||||
auto head = read(directory + "/offsets.bin");
|
||||
m.presetL = head.size() >= 8 && !std::memcmp(head.data(), "D4RL0001", 8);
|
||||
m.presetL = head.size() >= 8 && (!std::memcmp(head.data(), "D4RL0002", 8) || !std::memcmp(head.data(), "D4RL0001", 8));
|
||||
m.dynamicResolution = head.size() >= 8 && (!std::memcmp(head.data(), "D4RM0004", 8) || !std::memcmp(head.data(), "D4RL0002", 8));
|
||||
const size_t expect = m.presetL ? 40 + (MLayers + 2) * 74 * 4 : 16 + 5 * 4 + MLayers * 74 * 4;
|
||||
if (head.size() != expect || (!m.presetL && std::memcmp(head.data(), "D4RM0003", 8))) throw std::runtime_error("incompatible M/L model metadata");
|
||||
if (head.size() != expect || (!m.presetL && !m.dynamicResolution && std::memcmp(head.data(), "D4RM0003", 8))) throw std::runtime_error("incompatible M/L model metadata");
|
||||
uint32_t n8 = 0, n16 = 0;
|
||||
std::memcpy(&n8, head.data() + 8, 4); std::memcpy(&n16, head.data() + 12, 4);
|
||||
auto b8 = read(directory + "/weights8.bin"), b16 = read(directory + "/weights16.bin");
|
||||
@@ -143,6 +144,7 @@ MModel::~MModel() = default;
|
||||
bool MModel::uploadRecorded() const { return impl->uploaded; }
|
||||
bool MModel::hasExternalNetworkStages() const { return impl->planes; }
|
||||
bool MModel::hasRgb10Output() const { return impl->downRgb10.handle != VK_NULL_HANDLE; }
|
||||
bool MModel::hasDynamicResolution() const { return impl->dynamicResolution; }
|
||||
bool MModel::isPresetL() const { return impl->presetL; }
|
||||
void MModel::recordUpload(VkCommandBuffer cb) {
|
||||
auto& m = *impl;
|
||||
@@ -155,7 +157,7 @@ void MModel::recordUpload(VkCommandBuffer cb) {
|
||||
|
||||
struct MEngine::Impl {
|
||||
MModel::Impl& model;
|
||||
uint32_t rw, rh, ow, oh, hw, hh, gw, gh; // render, output, reconstruction, token pixel grid
|
||||
uint32_t rw, rh, maxRw, maxRh, ow, oh, hw, hh, gw, gh; // render, output, reconstruction, token pixel grid
|
||||
Buffer enc0P, dec0P, postP, downP, expP, expSums, expState, dummy, packed;
|
||||
std::array<Buffer, MLayers> layerP;
|
||||
Buffer tokens, skip1, merged1, skip2, merged2, tubeA, tubeB, dec2Out, dec1Out;
|
||||
@@ -199,7 +201,7 @@ MEngine::MEngine(MModel& model, uint32_t renderWidth, uint32_t renderHeight, uin
|
||||
e.external = external != nullptr;
|
||||
if (renderWidth < 64 || renderHeight < 64 || outputWidth % 2 || outputHeight % 2 || outputWidth > 16384 || outputHeight > 16384)
|
||||
throw std::runtime_error("unsupported preset M dimensions");
|
||||
e.rw = renderWidth; e.rh = renderHeight; e.ow = outputWidth; e.oh = outputHeight;
|
||||
e.rw = e.maxRw = renderWidth; e.rh = e.maxRh = renderHeight; e.ow = outputWidth; e.oh = outputHeight;
|
||||
e.hw = outputWidth / 2 * 3; e.hh = outputHeight / 2 * 3;
|
||||
if (renderWidth > outputWidth || renderHeight > outputHeight)
|
||||
throw std::runtime_error("preset M render dimensions must not exceed the output");
|
||||
@@ -212,7 +214,7 @@ MEngine::MEngine(MModel& model, uint32_t renderWidth, uint32_t renderHeight, uin
|
||||
const VkBufferUsageFlags sb = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, sbt = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
e.enc0P.create(d, sizeof(MEnc0Params), sbt); e.dec0P.create(d, sizeof(MDec0Params), sbt); e.postP.create(d, sizeof(MPostParams), sbt);
|
||||
e.downP.create(d, sizeof(MDownParams), sbt); e.expP.create(d, sizeof(ExposureParams), sbt);
|
||||
e.expSums.create(d, VkDeviceSize((exposureGrid(renderWidth, outputWidth) + 31) / 32) * ((exposureGrid(renderHeight, outputHeight) + 31) / 32) * 4, sb);
|
||||
e.expSums.create(d, VkDeviceSize((std::max(outputWidth / 2, 256u) + 31) / 32) * ((std::max(outputHeight / 2, 256u) + 31) / 32) * 4, sb);
|
||||
e.expState.create(d, 8, sbt); e.dummy.create(d, 256, sb);
|
||||
e.packed.create(d, VkDeviceSize(e.hw) * e.hh * 4, sb);
|
||||
auto tok = [&](Buffer& b, unsigned scale, unsigned channels) { b.create(d, VkDeviceSize(tw / scale) * (th / scale) * channels + 64, sb); };
|
||||
@@ -325,17 +327,30 @@ void MEngine::setFrame(const FrameImages& images, const MFrameSettings& s, Frame
|
||||
if (finalOutput.view && finalOutput.format != VK_FORMAT_R16G16B16A16_SFLOAT &&
|
||||
(finalOutput.format != VK_FORMAT_A2B10G10R10_UNORM_PACK32 || !e.model.downRgb10.handle))
|
||||
throw std::runtime_error("unsupported borrowed output format or missing RGB10A2 shader");
|
||||
const bool reset = s.reset || e.frame == 0;
|
||||
const uint32_t rw = s.renderWidth ? s.renderWidth : e.maxRw;
|
||||
const uint32_t rh = s.renderHeight ? s.renderHeight : e.maxRh;
|
||||
if (rw < 64 || rh < 64 || rw > e.maxRw || rh > e.maxRh)
|
||||
throw std::runtime_error("render size exceeds the engine's capacity or is below 64");
|
||||
if (!e.model.dynamicResolution && (rw != e.maxRw || rh != e.maxRh))
|
||||
throw std::runtime_error("recompile the M/L model for dynamic render sizes");
|
||||
if (e.external && (rw != e.maxRw || rh != e.maxRh))
|
||||
throw std::runtime_error("external M/L network requires its constructor render size");
|
||||
const bool reset = s.reset || e.frame == 0 || rw != e.rw || rh != e.rh;
|
||||
e.rw = rw; e.rh = rh;
|
||||
e.gw = (rw + 31) / 32 * 32; e.gh = (rh + 31) / 32 * 32;
|
||||
e.quality = uint64_t(e.hw) < 3ull * rw || uint64_t(e.hh) < 3ull * rh;
|
||||
e.compact = e.quality && e.model.postCompact.handle &&
|
||||
20ull * rw <= 9ull * e.hw && 20ull * rh <= 9ull * e.hh;
|
||||
const bool L = e.model.presetL;
|
||||
const float fx = float(e.hw) / float(e.rw), fy = float(e.hh) / float(e.rh);
|
||||
e.enc0 = {{int32_t(e.rw), int32_t(e.rh)}, {2, 2}, {int32_t(e.gw / 2), int32_t(e.gh / 2)}, {s.jitter[0], s.jitter[1]}, {e.prevJitter[0], e.prevJitter[1]},
|
||||
{s.motionScale[0] * fx, s.motionScale[1] * fy}, {s.motionOffset[0], s.motionOffset[1]}, {fx, fy}, {1.f / fx, 1.f / fy}, {float(e.hw), float(e.hh)},
|
||||
L ? e.model.lConst[0] : 9.045065879821777f, L ? e.model.lConst[1] : 0.22356846928596497f,
|
||||
s.depthInverted ? 1u : 0u, reset ? 1u : 0u, e.frame & 7u, 1.f, 0.f, {1.f / float(e.hw), 1.f / float(e.hh)}, {1.f / float(e.rw), 1.f / float(e.rh)}};
|
||||
s.depthInverted ? 1u : 0u, reset ? 1u : 0u, e.frame & 7u, 1.f, 0.f, {1.f / float(e.hw), 1.f / float(e.hh)}, {1.f / float(e.maxRw), 1.f / float(e.maxRh)}};
|
||||
e.dec0 = {{int32_t(e.rw), int32_t(e.rh)}, int32_t(e.gw / 2), reset ? 1u : 0u, {s.jitter[0], s.jitter[1]}, {fx, fy}, {float(e.hw), float(e.hh)},
|
||||
{e.prevJitter[0], e.prevJitter[1]}, L ? e.model.lConst[2] : 8.f, L ? e.model.lConst[3] : 0.07863261550664902f, {1.f / float(e.rw), 1.f / float(e.rh)}};
|
||||
{e.prevJitter[0], e.prevJitter[1]}, L ? e.model.lConst[2] : 8.f, L ? e.model.lConst[3] : 0.07863261550664902f, {1.f / float(e.maxRw), 1.f / float(e.maxRh)}};
|
||||
e.post = {{0, 0}, {int32_t(e.hw), int32_t(e.hh)}, {int32_t(e.rw), int32_t(e.rh)}, {1.f / fx, 1.f / fy}, {s.jitter[0], s.jitter[1]},
|
||||
{1.f / float(e.rw), 1.f / float(e.rh)}, L ? e.model.lConst[4] : 2.7530112f, reset ? 1u : 0u, 1.f, 0.f};
|
||||
{1.f / float(e.maxRw), 1.f / float(e.maxRh)}, L ? e.model.lConst[4] : 2.7530112f, reset ? 1u : 0u, 1.f, 0.f};
|
||||
e.down = {{int32_t(e.hw), int32_t(e.hh)}, {0, 0}, {0, 0}, {int32_t(e.rw), int32_t(e.rh)}, {float(e.rw) / float(e.ow), float(e.rh) / float(e.oh)}, {s.jitter[0], s.jitter[1]}, 1.f};
|
||||
const uint32_t sw = exposureGrid(e.rw, e.ow), sh = exposureGrid(e.rh, e.oh);
|
||||
const uint32_t bx = (sw + 31) / 32, by = (sh + 31) / 32;
|
||||
@@ -435,14 +450,16 @@ void MEngine::recordFront(VkCommandBuffer cb, VkQueryPool stamps, uint32_t first
|
||||
vkCmdUpdateBuffer(cb, e.postP.handle, 0, sizeof(e.post), &e.post); vkCmdUpdateBuffer(cb, e.downP.handle, 0, sizeof(e.down), &e.down);
|
||||
vkCmdUpdateBuffer(cb, e.expP.handle, 0, sizeof(e.exposure), &e.exposure);
|
||||
if (!e.external) for (unsigned l = 0; l < MLayers; ++l) {
|
||||
const int32_t shifts[2] = {MShift[l] ^ int32_t((e.frame & 1u) * 2u), MShift[l]};
|
||||
vkCmdUpdateBuffer(cb, e.layerP[l].handle, offsetof(MLayerParams, sx), sizeof(shifts), shifts);
|
||||
const int32_t grid[4] = {int32_t(e.gw / 2 / MScale[l]), int32_t(e.gh / 2 / MScale[l]),
|
||||
MShift[l] ^ int32_t((e.frame & 1u) * 2u), MShift[l]};
|
||||
vkCmdUpdateBuffer(cb, e.layerP[l].handle, 0, sizeof(grid), grid);
|
||||
}
|
||||
if (m.presetL) {
|
||||
// NGX alternates the horizontal window shift every frame: input stage (2, 2) / (0, 2), expansion (0, 0) / (2, 0)
|
||||
const int32_t in[2] = {2 ^ int32_t((e.frame & 1u) * 2u), 2}, ex[2] = {int32_t((e.frame & 1u) * 2u), 0};
|
||||
vkCmdUpdateBuffer(cb, e.enc0LP.handle, offsetof(MLayerParams, sx), sizeof(in), in);
|
||||
vkCmdUpdateBuffer(cb, e.dec0LP.handle, offsetof(MLayerParams, sx), sizeof(ex), ex);
|
||||
const int32_t in[4] = {int32_t(e.gw), int32_t(e.gh), 2 ^ int32_t((e.frame & 1u) * 2u), 2};
|
||||
const int32_t ex[4] = {int32_t(e.gw), int32_t(e.gh), int32_t((e.frame & 1u) * 2u), 0};
|
||||
vkCmdUpdateBuffer(cb, e.enc0LP.handle, 0, sizeof(in), in);
|
||||
vkCmdUpdateBuffer(cb, e.dec0LP.handle, 0, sizeof(ex), ex);
|
||||
}
|
||||
memoryBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, compute, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
uint32_t query = first;
|
||||
@@ -479,7 +496,7 @@ void MEngine::recordBack(VkCommandBuffer cb, VkQueryPool stamps, uint32_t first)
|
||||
dispatch(cb, compact ? m.postCompact : (e.quality ? m.postQuality : m.post),
|
||||
e.postSets[i], (e.hw + 15) / 16, (e.hh + 15) / 16); nextStage(cb);
|
||||
stamp();
|
||||
// D4RM0003's downsample workgroup covers 8x64 output pixels.
|
||||
// D4RM0004's downsample workgroup covers 8x64 output pixels.
|
||||
dispatch(cb, e.rgb10Output ? m.downRgb10 : m.down, e.downSet, (e.ow + 7) / 8, (e.oh + 63) / 64);
|
||||
stamp();
|
||||
closeStages(cb);
|
||||
|
||||
+47
-3
@@ -72,6 +72,7 @@ struct Case {
|
||||
bool rgb10Output = false, rotateOutputFormats = false;
|
||||
bool regularDepth = false, swapDepth = false; // K: depth convention; near and far values exchanged
|
||||
float exposureScale = 1, preExposure = 1; // K: the game's factors on the measured exposure
|
||||
bool dynamicM = false, largerCapacityM = false, explicitSizeResetM = false;
|
||||
bool displayMotion = false; // K: motion vectors at output resolution, in output pixels
|
||||
bool gameExposure = false; float exposureValue = 1; // K: the game's 1x1 exposure texture instead of measurement
|
||||
};
|
||||
@@ -201,7 +202,7 @@ std::vector<char> evaluateM(Context& c, d4r::MModel& model, const Case& test, un
|
||||
}
|
||||
std::memset(output[slot]->upload.map,0,output[slot]->upload.size);
|
||||
}
|
||||
d4r::GameUpscalerM adapter(model,c.physical,c.device,ow,oh,rw,rh);
|
||||
d4r::GameUpscalerM adapter(model,c.physical,c.device,ow,oh,test.largerCapacityM ? ow : rw,test.largerCapacityM ? oh : rh);
|
||||
const auto bytes = std::max({output[0]->upload.size, output[1]->upload.size, output[2]->upload.size});
|
||||
Buffer results(c,bytes*count);
|
||||
std::memset(results.map,0,results.size);
|
||||
@@ -219,7 +220,12 @@ std::vector<char> evaluateM(Context& c, d4r::MModel& model, const Case& test, un
|
||||
const unsigned frameMask = rotateInputs ? rotatingMasks[frame] : mask;
|
||||
f.color=color[slot]->game(0,0,frameMask&1,layout); f.motion=motion[slot]->game(0,0,frameMask&2,layout);
|
||||
f.depth=depth[slot]->game(0,0,frameMask&4,layout); f.output=output[slot]->game(0,0,frameMask&8,VK_IMAGE_LAYOUT_GENERAL);
|
||||
f.reset=frame==0||frame==4;
|
||||
if (test.dynamicM) {
|
||||
const uint32_t widths[count] = {640, 608, 448, 448, 640, 640};
|
||||
const uint32_t heights[count] = {360, 344, 256, 256, 360, 360};
|
||||
f.renderWidth = widths[frame]; f.renderHeight = heights[frame];
|
||||
}
|
||||
f.reset=frame==0||frame==4||(test.explicitSizeResetM && (frame==1||frame==2));
|
||||
f.jitter[0]=frame%2 ? .25f : -.125f; f.jitter[1]=frame%3 ? .375f : -.25f;
|
||||
adapter.record(c.command,f);
|
||||
auto r=region(ow,oh); r.bufferOffset=frame*bytes;
|
||||
@@ -241,9 +247,46 @@ void expectEqual(const std::vector<char>& expected,const std::vector<char>& actu
|
||||
std::cout<<name<<": byte-identical (six temporal frames)\n";
|
||||
}
|
||||
} // namespace
|
||||
// Normalized bilinear sampling can round differently when the allocation extent changes.
|
||||
// FP8 quantization can amplify a rounding difference in a few recurrent pixels.
|
||||
// Require less than 1% per-channel error and at least 86 dB PSNR at peak 1.
|
||||
void expectCapacityEquivalent(const std::vector<char>& expected, const std::vector<char>& actual, const std::string& name) {
|
||||
if (expected.size() != actual.size()) throw std::runtime_error(name + ": size mismatch");
|
||||
double squared = 0; float relative = 0;
|
||||
for (size_t i = 0; i < expected.size()/2; ++i) {
|
||||
const float a = float(reinterpret_cast<const _Float16*>(expected.data())[i]);
|
||||
const float b = float(reinterpret_cast<const _Float16*>(actual.data())[i]);
|
||||
const float error = std::abs(a-b);
|
||||
if (!std::isfinite(error)) throw std::runtime_error(name + ": nonfinite output");
|
||||
squared += double(error)*error;
|
||||
relative = std::max(relative,error/std::max(1.f,std::abs(a)));
|
||||
}
|
||||
const double rmse = std::sqrt(squared/(expected.size()/2));
|
||||
if (relative > .01f || rmse > 5e-5)
|
||||
throw std::runtime_error(name + ": capacity changed the result (relative " + std::to_string(relative) + ", RMSE " + std::to_string(rmse) + ")");
|
||||
std::cout<<name<<": RMSE "<<rmse<<", max relative error "<<relative<<"\n";
|
||||
}
|
||||
void checkDynamicM(Context& c, d4r::MModel& m) {
|
||||
Case dynamic; dynamic.dynamicM = true;
|
||||
const auto reference = evaluateM(c,m,dynamic,0,false);
|
||||
expectEqual(reference,evaluateM(c,m,dynamic,15,true),"M/L dynamic sizes with queued direct inputs");
|
||||
dynamic.explicitSizeResetM = true;
|
||||
expectEqual(reference,evaluateM(c,m,dynamic,0,true),"M/L size changes reset history automatically");
|
||||
dynamic.explicitSizeResetM = false;
|
||||
dynamic.largerCapacityM = true;
|
||||
expectCapacityEquivalent(reference,evaluateM(c,m,dynamic,0,true),"M/L dynamic sizes within output-sized capacity");
|
||||
expectCapacityEquivalent(reference,evaluateM(c,m,dynamic,15,true),"M/L dynamic sizes with larger capacity and direct inputs");
|
||||
}
|
||||
int main(int argc,char** argv) {
|
||||
try {
|
||||
if(argc!=2 && argc!=3) throw std::runtime_error("usage: test-game K_MODEL_DIR [M_MODEL_DIR]");
|
||||
if (argc == 3 && !std::strcmp(argv[1], "--dynamic-ml")) {
|
||||
Context c; c.create(2,true);
|
||||
d4r::MModel m(c.physical,c.device,argv[2],c.cache);
|
||||
c.begin(); m.recordUpload(c.command); c.submit();
|
||||
checkDynamicM(c,m);
|
||||
return 0;
|
||||
}
|
||||
if(argc!=2 && argc!=3) throw std::runtime_error("usage: test-game K_MODEL_DIR [M_MODEL_DIR] | --dynamic-ml M_OR_L_MODEL_DIR");
|
||||
// The reconstruction table NGX itself selected at these sizes (measured through its launch arguments),
|
||||
// on both sides of every threshold and for unequal axis ratios: {render w, h, output w, h, table}.
|
||||
{
|
||||
@@ -332,6 +375,7 @@ int main(int argc,char** argv) {
|
||||
if (argc==3) {
|
||||
d4r::MModel m(c.physical,c.device,argv[2],c.cache);
|
||||
c.begin();m.recordUpload(c.command);c.submit();
|
||||
checkDynamicM(c, m);
|
||||
for (bool depthAspect:{false,true}) {
|
||||
Case test;test.depthAspect=depthAspect;
|
||||
auto reference=evaluateM(c,m,test,0,false);
|
||||
|
||||
+40
-12
@@ -1816,7 +1816,9 @@ struct Feature
|
||||
bool engineViewBudgetReported = false;
|
||||
std::vector<std::unique_ptr<EngineBorrowedView>> engineViews; // destroyed after engine descriptor pools
|
||||
std::unique_ptr<d4r::GameUpscaler> engine;
|
||||
std::unique_ptr<d4r::GameUpscalerM> engineM; // preset M
|
||||
std::unique_ptr<d4r::GameUpscalerM> engineM; // preset M/L, original network
|
||||
std::unique_ptr<d4r::GameUpscalerM> engineMDynamic; // Vulkan at changing sizes; original HIP resources stay alive
|
||||
unsigned int engineMLastWidth = 0, engineMLastHeight = 0;
|
||||
unsigned int engineMRenderWidth = 0, engineMRenderHeight = 0; // first evaluation's effective extent
|
||||
// Preset M and preset K with the network on the native HIP layers ([Engine] Network): the buffers shared
|
||||
// with HIP and the bridge's network. The game's list is split around the network, as for the CUDA backend's
|
||||
@@ -5771,11 +5773,11 @@ static bool engine_evaluate_m(Feature& feature, ID3D12GraphicsCommandList* list,
|
||||
}
|
||||
if ((feature.flags & 0x2) == 0)
|
||||
return refuse("needs render-resolution motion vectors");
|
||||
if (!model->hasDynamicResolution() && feature.engineM != nullptr &&
|
||||
(p.renderWidth != feature.engineMRenderWidth || p.renderHeight != feature.engineMRenderHeight))
|
||||
return refuse("dynamic render sizes need a model rebuilt by the current compile_m.py");
|
||||
// NGX measures the exposure for this preset, so the AutoExposure flag, the game's exposure texture, the
|
||||
// pre-exposure and the exposure scale are all ignored here (CUDA output is identical for each of them).
|
||||
if (feature.engineM != nullptr &&
|
||||
(p.renderWidth != feature.engineMRenderWidth || p.renderHeight != feature.engineMRenderHeight))
|
||||
return refuse("changing the effective render resolution after initialization is not implemented");
|
||||
if (color == output || depth == output || motion == output)
|
||||
return refuse("input/output texture aliasing is not supported");
|
||||
d4r::GameFrameM game;
|
||||
@@ -5824,7 +5826,10 @@ static bool engine_evaluate_m(Feature& feature, ID3D12GraphicsCommandList* list,
|
||||
game.motionOffset[0] = (p.jitterX - feature.engineJitter[0]) / p.mvScaleX;
|
||||
game.motionOffset[1] = (p.jitterY - feature.engineJitter[1]) / p.mvScaleY;
|
||||
}
|
||||
game.reset = p.reset != 0 || feature.engineM == nullptr;
|
||||
game.renderWidth = p.renderWidth;
|
||||
game.renderHeight = p.renderHeight;
|
||||
game.reset = p.reset != 0 || feature.engineM == nullptr ||
|
||||
p.renderWidth != feature.engineMLastWidth || p.renderHeight != feature.engineMLastHeight;
|
||||
game.depthInverted = (feature.flags & 0x8) != 0;
|
||||
try
|
||||
{
|
||||
@@ -5836,7 +5841,8 @@ static bool engine_evaluate_m(Feature& feature, ID3D12GraphicsCommandList* list,
|
||||
bytes, bytes);
|
||||
const d4r::ExternalNetwork external = {feature.engineTokens.buffer, feature.engineHead.buffer};
|
||||
feature.engineM = std::make_unique<d4r::GameUpscalerM>(*model, g_vk.physical, g_vk.device, feature.outWidth,
|
||||
feature.outHeight, p.renderWidth, p.renderHeight,
|
||||
feature.outHeight, (hip || !model->hasDynamicResolution()) ? p.renderWidth : feature.outWidth,
|
||||
(hip || !model->hasDynamicResolution()) ? p.renderHeight : feature.outHeight,
|
||||
hip ? &external : nullptr);
|
||||
feature.engineMRenderWidth = p.renderWidth;
|
||||
feature.engineMRenderHeight = p.renderHeight;
|
||||
@@ -5855,7 +5861,30 @@ static bool engine_evaluate_m(Feature& feature, ID3D12GraphicsCommandList* list,
|
||||
release_engine_network(feature);
|
||||
return refuse("the engine could not be created");
|
||||
}
|
||||
if (feature.engineNet != nullptr)
|
||||
d4r::GameUpscalerM* adapter = feature.engineM.get();
|
||||
const bool resizedHip = feature.engineNet != nullptr &&
|
||||
(p.renderWidth != feature.engineMRenderWidth || p.renderHeight != feature.engineMRenderHeight);
|
||||
if (resizedHip)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!feature.engineMDynamic)
|
||||
{
|
||||
feature.engineMDynamic = std::make_unique<d4r::GameUpscalerM>(*model, g_vk.physical, g_vk.device,
|
||||
feature.outWidth, feature.outHeight, feature.outWidth, feature.outHeight);
|
||||
logf("engine backend (%s): dynamic render sizes use the Vulkan network; original HIP resources retained", name);
|
||||
}
|
||||
adapter = feature.engineMDynamic.get();
|
||||
}
|
||||
catch (const std::exception& error)
|
||||
{
|
||||
logf("engine backend (%s): %s", name, error.what());
|
||||
return refuse("the dynamic engine could not be created");
|
||||
}
|
||||
}
|
||||
feature.engineMLastWidth = p.renderWidth;
|
||||
feature.engineMLastHeight = p.renderHeight;
|
||||
if (feature.engineNet != nullptr && !resizedHip)
|
||||
{
|
||||
// The native HIP network: the game's list is split around it. Its native completion callback releases
|
||||
// the second part, which records expansion, reconstruction and the final store (or the conversion blit
|
||||
@@ -5869,14 +5898,14 @@ static bool engine_evaluate_m(Feature& feature, ID3D12GraphicsCommandList* list,
|
||||
{
|
||||
if (front)
|
||||
{
|
||||
feature.engineM->recordFront(command, game);
|
||||
adapter->recordFront(command, game);
|
||||
// The HIP network's stream waits for this frame's number (record_engine_input_ready): the
|
||||
// fill is part of the first half, so the network starts the moment the front's GPU work ends.
|
||||
if (feature.engineGpuWait.load())
|
||||
record_engine_input_ready(feature, command, frame);
|
||||
}
|
||||
else
|
||||
feature.engineM->recordBack(command, game);
|
||||
adapter->recordBack(command, game);
|
||||
}
|
||||
catch (const std::exception& error)
|
||||
{
|
||||
@@ -5906,7 +5935,7 @@ static bool engine_evaluate_m(Feature& feature, ID3D12GraphicsCommandList* list,
|
||||
{
|
||||
try
|
||||
{
|
||||
feature.engineM->record(command, game);
|
||||
adapter->record(command, game);
|
||||
}
|
||||
catch (const std::exception& error)
|
||||
{
|
||||
@@ -6079,8 +6108,7 @@ static bool engine_evaluate(Feature& feature, ID3D12GraphicsCommandList* list, I
|
||||
const d4r::ExternalNetwork external = {feature.engineTokens.buffer, feature.engineHead.buffer};
|
||||
feature.engine = std::make_unique<d4r::GameUpscaler>(*model, g_vk.physical, g_vk.device,
|
||||
feature.outWidth, feature.outHeight,
|
||||
std::max(feature.width, p.renderWidth),
|
||||
std::max(feature.height, p.renderHeight),
|
||||
feature.outWidth, feature.outHeight,
|
||||
hip ? &external : nullptr);
|
||||
logf("engine backend: feature %u runs preset K in the game's command list (%ux%u -> %ux%u), network on %s",
|
||||
feature.handle.Id, p.renderWidth, p.renderHeight, feature.outWidth, feature.outHeight,
|
||||
|
||||
Reference in new issue
Block a user