engine: presets E/F AutoExposure, the game's exposure fields and NGX's jitter order
NGX's CNN takes this frame's exposure scale and pre-exposure in both translated kernels (input words 244-256, reconstruction 592-604) and the previous frame's exposure as the input kernel's texture 352 (cuda_copy_exposure_kernel's half surface). The runtime left all of it at the model dump's values, so HDR games with a pre-exposure other than 1 or a changing exposure measured 18-39 dB against NGX; now 54-64 dB. AutoExposure (flag 0x40), which the shim refused for E/F: NGX runs K's log-mean measurement on M's grid every second frame. exposure_k.comp MODE 3 writes it to the 1x1 image the kernels sample; sr/exposure_copy_e.comp keeps the previous exposure. 60-66 dB over eleven frames at 960x540 -> 1920x1080, pre-exposure 19-21. The reconstruction's jitter words 544-556 are (y, x); the runtime wrote (-x, -y), which matches only the harness's first three jitters, so every later frame of a game's jitter sequence measured 26-28 dB. Twelve-frame captures now measure 60-66 dB HDR and LDR; on the Steam Deck through the shim 51-67 dB against ZLUDA. The D3D12 harness takes per-frame lists in D4R_HARNESS_PRE_EXPOSURE / _EXPOSURE_SCALE; d4r-e takes the AutoExposure flag, per-frame exposure values and the game's exposure texture. E/F models need rebuilding (the install's model cache key changes); older models keep the old binding and refuse AutoExposure.
This commit is contained in:
1 parent
916b0d1467
commit
3f66081add
12 files changed
+249
-47
No files matched your search
@@ -144,7 +144,7 @@ MarkerPollUs = auto
|
|||||||
; Needs VkD3DDir, a driver with VK_KHR_cooperative_matrix (Mesa RADV on RDNA3/RDNA4) and the model folders
|
; Needs VkD3DDir, a driver with VK_KHR_cooperative_matrix (Mesa RADV on RDNA3/RDNA4) and the model folders
|
||||||
; (RuntimeDir/engine/k, k-ldr, m, m-ldr, l, l-ldr; scripts/install_d4r_runtime.sh). E and F (engine/e, engine/f;
|
; (RuntimeDir/engine/k, k-ldr, m, m-ldr, l, l-ldr; scripts/install_d4r_runtime.sh). E and F (engine/e, engine/f;
|
||||||
; D4R_ENGINE_MODEL_E_DIR, D4R_ENGINE_MODEL_F_DIR) need no cooperative matrices: they run on RDNA2 (Steam Deck)
|
; D4R_ENGINE_MODEL_E_DIR, D4R_ENGINE_MODEL_F_DIR) need no cooperative matrices: they run on RDNA2 (Steam Deck)
|
||||||
; too, from any render size and HDR or LDR, but not with AutoExposure. A game or setting the engine
|
; too, from any render size and HDR or LDR, with AutoExposure too (models built by this version). A game or setting the engine
|
||||||
; does not cover uses the normal path, with the reason in d4r_nvngx.log: other models than E, F, K, L and M,
|
; does not cover uses the normal path, with the reason in d4r_nvngx.log: other models than E, F, K, L and M,
|
||||||
; display-resolution motion vectors with L or M, or a K model built before the current engine/compile_k.py for display-resolution
|
; display-resolution motion vectors with L or M, or a K model built before the current engine/compile_k.py for display-resolution
|
||||||
; motion, non-inverted depth or exposure supplied by the game. Details and measurements: engine/README.md.
|
; motion, non-inverted depth or exposure supplied by the game. Details and measurements: engine/README.md.
|
||||||
|
|||||||
+19
-1
@@ -1147,7 +1147,25 @@ in two constants.
|
|||||||
packed in the DLL; NGX uploads them unpacked), `runtime_e.inc` is `EModel` / `EEngine`, `game_e.cpp` the game
|
packed in the DLL; NGX uploads them unpacked), `runtime_e.inc` is `EModel` / `EEngine`, `game_e.cpp` the game
|
||||||
adapter, `replay_e.cpp` the replay (`d4r-e`). The shim runs presets 5 and 6 with `engine\e` / `engine\f`
|
adapter, `replay_e.cpp` the replay (`d4r-e`). The shim runs presets 5 and 6 with `engine\e` / `engine\f`
|
||||||
(`D4R_ENGINE_MODEL_E_DIR`, `_F_DIR`), LDR or HDR, either motion resolution, any render size (a new size rebuilds
|
(`D4R_ENGINE_MODEL_E_DIR`, `_F_DIR`), LDR or HDR, either motion resolution, any render size (a new size rebuilds
|
||||||
the engine); not with AutoExposure. `build-engine-models.sh` builds both.
|
the engine), the game's exposure texture or AutoExposure. `build-engine-models.sh` builds both.
|
||||||
|
- Exposure (HDR; harness captures with per-frame pre-exposure, exposure scale and exposure texture schedules,
|
||||||
|
2026-10-19). Both translated kernels take this frame's exposure scale (twice), pre-exposure and 1 / pre-exposure
|
||||||
|
(input words 244-256, reconstruction 592-604). They sample the exposure as 1x1 textures: the input kernel's 344 and
|
||||||
|
360 and the reconstruction's 472 and 480 this frame's, the input kernel's 352 the previous frame's, which NGX's
|
||||||
|
`cuda_copy_exposure_kernel` copies into a half surface after each frame (the current one on a reset frame):
|
||||||
|
`sr/exposure_copy_e.comp`. With AutoExposure NGX runs K's measurement (`cuda_luma_convert_kernel`, three
|
||||||
|
`cuda_reduce_sum_kernel`, `cuda_auto_exposure_copy_kernel`: key 0.18 / 0.82 times exp(-mean log luma), the luma
|
||||||
|
scaled by 1 / pre-exposure, truncated to half) on every second frame from the first, on M's grid (half the output,
|
||||||
|
half the render size but at least 256 above 2x), and ignores the game's texture: `exposure_k.comp` MODE 3. The
|
||||||
|
engine used to leave all of this at the model dump's values (exposure 1, the current exposure as the previous one):
|
||||||
|
at 1280x720 -> 1920x1080 with pre-exposure 4/2/8 and scale 0.5/0.75/1.25 it measured 39/18/29 dB against NGX with
|
||||||
|
the game's texture, now 64/55/54 dB; with AutoExposure 65/54/54 dB, through the shim as through `d4r-e` (neutral
|
||||||
|
exposure: 64-66 dB, unchanged). Models built before this keep the old binding and refuse AutoExposure.
|
||||||
|
- The reconstruction's jitter words 544-556 are the previous and this frame's jitter as (y, x). The runtime wrote
|
||||||
|
(-x, -y), which agrees only on the harness's first three jitters (0, -+0.25), so three-frame checks passed while
|
||||||
|
later frames of the jitter sequence measured 26-28 dB (every game's Halton jitter was affected). Twelve-frame
|
||||||
|
captures at 640x360 -> 1280x720 now measure 60-66 dB (HDR), 61-66 dB (LDR), and with AutoExposure at
|
||||||
|
960x540 -> 1920x1080 and pre-exposure 19-21 60-66 dB; on the Steam Deck through the shim 51-67 dB against ZLUDA.
|
||||||
|
|
||||||
With the f32 reconstruction head, accuracy against NGX through ZLUDA (three temporal frames): 63-68 dB at 640x360 -> 1280x720
|
With the f32 reconstruction head, accuracy against NGX through ZLUDA (three temporal frames): 63-68 dB at 640x360 -> 1280x720
|
||||||
(LDR and HDR, both motion resolutions, E and F), 1280x720 -> 1920x1080, 1001x563 -> 1501x845 (generic kernel),
|
(LDR and HDR, both motion resolutions, E and F), 1280x720 -> 1920x1080, 1001x563 -> 1501x845 (generic kernel),
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ variants use aligned word accesses for their packed tensors. Writes:
|
|||||||
<stage>.spv the network stages
|
<stage>.spv the network stages
|
||||||
input-<v>.spv / .globals.bin, final-<v>.spv / .globals.bin the translated kernels
|
input-<v>.spv / .globals.bin, final-<v>.spv / .globals.bin the translated kernels
|
||||||
input.args, final.args their argument blocks from the dump (the runtime rewrites every frame-dependent field)
|
input.args, final.args their argument blocks from the dump (the runtime rewrites every frame-dependent field)
|
||||||
|
exposure_e0.spv / _e1.spv NGX's AutoExposure measurement (engine/exposure_k.comp MODE 3)
|
||||||
|
exposure_copy_e.spv NGX's copy of the frame's exposure that the next frame reads (engine/sr/exposure_copy_e.comp)
|
||||||
Everything but d4r's shaders is derived from the user's DLL: it stays on their machine and is never packaged.
|
Everything but d4r's shaders is derived from the user's DLL: it stays on their machine and is never packaged.
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
@@ -198,7 +200,11 @@ def main():
|
|||||||
for off in l.ptr:
|
for off in l.ptr:
|
||||||
block[off:off + 8] = bytes(8)
|
block[off:off + 8] = bytes(8)
|
||||||
(out / f'{name}.args').write_bytes(bytes(block))
|
(out / f'{name}.args').write_bytes(bytes(block))
|
||||||
lines += ['template input input.args', 'template final final.args']
|
for p in '01':
|
||||||
|
glsl(HERE / 'exposure_k.comp', out / f'exposure_e{p}.spv', ['-DMODE=3', f'-DPASS={p}'])
|
||||||
|
glsl(HERE / 'sr' / 'exposure_copy_e.comp', out / 'exposure_copy_e.spv')
|
||||||
|
lines += ['template input input.args', 'template final final.args', 'exposure exposure_e0.spv exposure_e1.spv',
|
||||||
|
'exposure_copy exposure_copy_e.spv']
|
||||||
(out / 'e.txt').write_text('\n'.join(lines) + '\n')
|
(out / 'e.txt').write_text('\n'.join(lines) + '\n')
|
||||||
print(f'Super Resolution CNN model in {out} ({len(blob)} weight bytes)')
|
print(f'Super Resolution CNN model in {out} ({len(blob)} weight bytes)')
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -5,6 +5,8 @@
|
|||||||
// [0, 10000], 0.005).
|
// [0, 10000], 0.005).
|
||||||
// MODE 2 (Ray Reconstruction, the same cuda_dldn_engine_* kernels): MODE 1's measurement, written to two 1x1
|
// MODE 2 (Ray Reconstruction, the same cuda_dldn_engine_* kernels): MODE 1's measurement, written to two 1x1
|
||||||
// images instead of stage parameter blocks.
|
// images instead of stage parameter blocks.
|
||||||
|
// MODE 3 (presets E and F, NGX's cuda_luma_convert / auto_exposure_copy kernels): MODE 0's measurement, written to
|
||||||
|
// the 1x1 image the input and reconstruction kernels sample as the exposure.
|
||||||
// Both modes measure the same grid: the colour rectangle sampled at half the output size with an
|
// Both modes measure the same grid: the colour rectangle sampled at half the output size with an
|
||||||
// endpoint-aligned nearest-texel lookup (the reference's cuda_luma_convert_kernel; the grid equals the
|
// endpoint-aligned nearest-texel lookup (the reference's cuda_luma_convert_kernel; the grid equals the
|
||||||
// rectangle itself only when the render size is half the output size).
|
// rectangle itself only when the render size is half the output size).
|
||||||
@@ -17,6 +19,7 @@
|
|||||||
#ifndef MODE
|
#ifndef MODE
|
||||||
#define MODE 0
|
#define MODE 0
|
||||||
#endif
|
#endif
|
||||||
|
#define LOG_MEAN (MODE == 0 || MODE == 3) // key * exp(-mean of the log luma)
|
||||||
layout(local_size_x = 64) in;
|
layout(local_size_x = 64) in;
|
||||||
layout(set = 0, binding = 0, std430) readonly buffer Params
|
layout(set = 0, binding = 0, std430) readonly buffer Params
|
||||||
{
|
{
|
||||||
@@ -63,7 +66,7 @@ void main()
|
|||||||
const vec3 c = texelFetch(colorTex, p.origin + sampleAt, 0).rgb;
|
const vec3 c = texelFetch(colorTex, p.origin + sampleAt, 0).rgb;
|
||||||
// the reference accumulates the logarithm of the exposed luma (cuda_luma_convert_kernel). The
|
// the reference accumulates the logarithm of the exposed luma (cuda_luma_convert_kernel). The
|
||||||
// constant is applied to Y = fma(b, 0.25, fma(r, 0.25, g * 0.5)); MODE 1 keeps its own expression.
|
// constant is applied to Y = fma(b, 0.25, fma(r, 0.25, g * 0.5)); MODE 1 keeps its own expression.
|
||||||
#if MODE == 0
|
#if LOG_MEAN
|
||||||
precise const float luma = p.scale * fma(c.b, 0.25, fma(c.r, 0.25, 0.5 * c.g));
|
precise const float luma = p.scale * fma(c.b, 0.25, fma(c.r, 0.25, 0.5 * c.g));
|
||||||
// log(luma + 1e-6) as lg2(x) * ln2, and a non-finite or non-positive luma contributes nothing
|
// log(luma + 1e-6) as lg2(x) * ln2, and a non-finite or non-positive luma contributes nothing
|
||||||
if (luma > 0.0 && !isinf(luma))
|
if (luma > 0.0 && !isinf(luma))
|
||||||
@@ -93,6 +96,8 @@ layout(set = 0, binding = 5) uniform sampler2D gameExposure;
|
|||||||
layout(set = 0, binding = 3, std430) buffer InputParams { float w[]; } inp; // enc0_m: expo, expoPrev at words 25, 26
|
layout(set = 0, binding = 3, std430) buffer InputParams { float w[]; } inp; // enc0_m: expo, expoPrev at words 25, 26
|
||||||
layout(set = 0, binding = 4, std430) buffer PostParams { float w[]; } post; // post_m: words 14, 15
|
layout(set = 0, binding = 4, std430) buffer PostParams { float w[]; } post; // post_m: words 14, 15
|
||||||
layout(set = 0, binding = 5, std430) buffer DownParams { float w[]; } down; // down_m: word 12
|
layout(set = 0, binding = 5, std430) buffer DownParams { float w[]; } down; // down_m: word 12
|
||||||
|
#elif MODE == 3
|
||||||
|
layout(set = 0, binding = 3, r16f) uniform writeonly image2D exposureOut;
|
||||||
#else
|
#else
|
||||||
// MODE 2 (Ray Reconstruction): the denoiser's stages sample the exposure as two 1x1 textures, this frame's and
|
// MODE 2 (Ray Reconstruction): the denoiser's stages sample the exposure as two 1x1 textures, this frame's and
|
||||||
// the previous frame's (zero on the first frame and after a reset)
|
// the previous frame's (zero on the first frame and after a reset)
|
||||||
@@ -124,7 +129,7 @@ void main()
|
|||||||
{
|
{
|
||||||
float mean = 0.0;
|
float mean = 0.0;
|
||||||
for (uint i = 0u; i < 64u; ++i) mean += part[i];
|
for (uint i = 0u; i < 64u; ++i) mean += part[i];
|
||||||
#if MODE == 0
|
#if LOG_MEAN
|
||||||
// auto_exposure_copy_kernel: key * exp2(-log2e * sum), written to a half surface. The formatted
|
// auto_exposure_copy_kernel: key * exp2(-log2e * sum), written to a half surface. The formatted
|
||||||
// store truncates toward zero, as in MODE 1 (measured on the captured K exposure: the store lands
|
// store truncates toward zero, as in MODE 1 (measured on the captured K exposure: the store lands
|
||||||
// one binary16 code below the correctly rounded value).
|
// one binary16 code below the correctly rounded value).
|
||||||
@@ -136,7 +141,7 @@ void main()
|
|||||||
float measured = truncatedHalf(full);
|
float measured = truncatedHalf(full);
|
||||||
#endif
|
#endif
|
||||||
if (!(measured > 0.0) || isinf(measured)) measured = 1.0;
|
if (!(measured > 0.0) || isinf(measured)) measured = 1.0;
|
||||||
#if MODE == 0
|
#if LOG_MEAN
|
||||||
previous = (p.flags & 2u) != 0u ? measured : current;
|
previous = (p.flags & 2u) != 0u ? measured : current;
|
||||||
#else
|
#else
|
||||||
previous = (p.flags & 2u) != 0u ? 0.0 : current; // 0: no earlier exposure (the stages read it as 1)
|
previous = (p.flags & 2u) != 0u ? 0.0 : current; // 0: no earlier exposure (the stages read it as 1)
|
||||||
@@ -154,6 +159,8 @@ void main()
|
|||||||
inp.w[25] = current; inp.w[26] = previous;
|
inp.w[25] = current; inp.w[26] = previous;
|
||||||
post.w[14] = current; post.w[15] = previous;
|
post.w[14] = current; post.w[15] = previous;
|
||||||
down.w[12] = current;
|
down.w[12] = current;
|
||||||
|
#elif MODE == 3
|
||||||
|
imageStore(exposureOut, ivec2(0), vec4(current));
|
||||||
#else
|
#else
|
||||||
imageStore(exposureOut, ivec2(0), vec4(current));
|
imageStore(exposureOut, ivec2(0), vec4(current));
|
||||||
imageStore(previousOut, ivec2(0), vec4(previous));
|
imageStore(previousOut, ivec2(0), vec4(previous));
|
||||||
|
|||||||
+7
-4
@@ -9,13 +9,15 @@ struct GameUpscalerE::Impl {
|
|||||||
uint32_t width, height, outputWidth, outputHeight, motionWidth, motionHeight;
|
uint32_t width, height, outputWidth, outputHeight, motionWidth, motionHeight;
|
||||||
game_detail::CanonicalInputs fallback;
|
game_detail::CanonicalInputs fallback;
|
||||||
bool started = false;
|
bool started = false;
|
||||||
Impl(EModel& m, VkPhysicalDevice p, VkDevice d, uint32_t ow, uint32_t oh, uint32_t rw, uint32_t rh, bool hdr, bool lowRes)
|
Impl(EModel& m, VkPhysicalDevice p, VkDevice d, uint32_t ow, uint32_t oh, uint32_t rw, uint32_t rh, bool hdr, bool lowRes,
|
||||||
: model(m), engine(m, rw, rh, ow, oh, hdr, lowRes), width(rw), height(rh), outputWidth(ow), outputHeight(oh),
|
bool autoExposure)
|
||||||
|
: model(m), engine(m, rw, rh, ow, oh, hdr, lowRes, autoExposure), width(rw), height(rh), outputWidth(ow), outputHeight(oh),
|
||||||
motionWidth(lowRes ? rw : ow), motionHeight(lowRes ? rh : oh), fallback(p, d, rw, rh, motionWidth, motionHeight) {}
|
motionWidth(lowRes ? rw : ow), motionHeight(lowRes ? rh : oh), fallback(p, d, rw, rh, motionWidth, motionHeight) {}
|
||||||
};
|
};
|
||||||
GameUpscalerE::GameUpscalerE(EModel& model, VkPhysicalDevice physical, VkDevice device, uint32_t outputWidth, uint32_t outputHeight,
|
GameUpscalerE::GameUpscalerE(EModel& model, VkPhysicalDevice physical, VkDevice device, uint32_t outputWidth, uint32_t outputHeight,
|
||||||
uint32_t renderWidth, uint32_t renderHeight, bool hdr, bool lowResMotion)
|
uint32_t renderWidth, uint32_t renderHeight, bool hdr, bool lowResMotion, bool autoExposure)
|
||||||
: impl(std::make_unique<Impl>(model, physical, device, outputWidth, outputHeight, renderWidth, renderHeight, hdr, lowResMotion)) {}
|
: impl(std::make_unique<Impl>(model, physical, device, outputWidth, outputHeight, renderWidth, renderHeight, hdr, lowResMotion,
|
||||||
|
autoExposure)) {}
|
||||||
GameUpscalerE::~GameUpscalerE() = default;
|
GameUpscalerE::~GameUpscalerE() = default;
|
||||||
|
|
||||||
void GameUpscalerE::record(VkCommandBuffer cb, const GameFrameE& frame) {
|
void GameUpscalerE::record(VkCommandBuffer cb, const GameFrameE& frame) {
|
||||||
@@ -39,6 +41,7 @@ void GameUpscalerE::record(VkCommandBuffer cb, const GameFrameE& frame) {
|
|||||||
s.jitter[0] = frame.jitter[0]; s.jitter[1] = frame.jitter[1];
|
s.jitter[0] = frame.jitter[0]; s.jitter[1] = frame.jitter[1];
|
||||||
s.motionScale[0] = frame.motionScale[0]; s.motionScale[1] = frame.motionScale[1];
|
s.motionScale[0] = frame.motionScale[0]; s.motionScale[1] = frame.motionScale[1];
|
||||||
s.reset = frame.reset;
|
s.reset = frame.reset;
|
||||||
|
s.preExposure = frame.preExposure; s.exposureScale = frame.exposureScale;
|
||||||
FrameImages images{};
|
FrameImages images{};
|
||||||
images.color = directColor ? frame.color.view : g.fallback.color.view;
|
images.color = directColor ? frame.color.view : g.fallback.color.view;
|
||||||
images.motion = directMotion ? frame.motion.view : g.fallback.motion.view;
|
images.motion = directMotion ? frame.motion.view : g.fallback.motion.view;
|
||||||
|
|||||||
+5
-3
@@ -6,17 +6,19 @@
|
|||||||
namespace d4r {
|
namespace d4r {
|
||||||
struct GameFrameE {
|
struct GameFrameE {
|
||||||
GameImage color, depth, motion, output; // as for GameUpscaler; compatible views at origin zero may be borrowed
|
GameImage color, depth, motion, output; // as for GameUpscaler; compatible views at origin zero may be borrowed
|
||||||
GameImage exposure; // HDR: the game's 1x1 float exposure (a sampled view), else 1
|
GameImage exposure; // HDR without autoExposure: the game's 1x1 float exposure (a sampled view), else 1
|
||||||
float jitter[2] = {0, 0};
|
float jitter[2] = {0, 0};
|
||||||
float motionScale[2] = {1, 1}; // NGX's MV scale
|
float motionScale[2] = {1, 1}; // NGX's MV scale
|
||||||
|
float preExposure = 1, exposureScale = 1; // the game's (DLSS.Pre.Exposure, DLSS.Exposure.Scale)
|
||||||
bool reset = false;
|
bool reset = false;
|
||||||
};
|
};
|
||||||
class GameUpscalerE {
|
class GameUpscalerE {
|
||||||
public:
|
public:
|
||||||
// The model must outlive this object. Render size fixed for its lifetime (a new size needs a new adapter).
|
// The model must outlive this object. Render size fixed for its lifetime (a new size needs a new adapter).
|
||||||
// lowResMotion: motion at the render size (NGX's MVLowRes), otherwise at the output size.
|
// lowResMotion: motion at the render size (NGX's MVLowRes), otherwise at the output size. autoExposure: NGX's
|
||||||
|
// AutoExposure (HDR), the exposure measured from the colour (EModel::hasAutoExposure).
|
||||||
GameUpscalerE(EModel& model, VkPhysicalDevice physical, VkDevice device, uint32_t outputWidth, uint32_t outputHeight,
|
GameUpscalerE(EModel& model, VkPhysicalDevice physical, VkDevice device, uint32_t outputWidth, uint32_t outputHeight,
|
||||||
uint32_t renderWidth, uint32_t renderHeight, bool hdr, bool lowResMotion);
|
uint32_t renderWidth, uint32_t renderHeight, bool hdr, bool lowResMotion, bool autoExposure = false);
|
||||||
~GameUpscalerE(); // only once every recorded frame has completed on the GPU
|
~GameUpscalerE(); // only once every recorded frame has completed on the GPU
|
||||||
GameUpscalerE(const GameUpscalerE&) = delete;
|
GameUpscalerE(const GameUpscalerE&) = delete;
|
||||||
GameUpscalerE& operator=(const GameUpscalerE&) = delete;
|
GameUpscalerE& operator=(const GameUpscalerE&) = delete;
|
||||||
|
|||||||
+19
-7
@@ -2,9 +2,10 @@
|
|||||||
// after frame, and writes the output (and with D4R_E_DUMP_TENSORS=1 the network's tensors) after each frame.
|
// after frame, and writes the output (and with D4R_E_DUMP_TENSORS=1 the network's tensors) after each frame.
|
||||||
//
|
//
|
||||||
// usage: d4r-e MODEL_DIR INPUT_DIR OUT_DIR
|
// usage: d4r-e MODEL_DIR INPUT_DIR OUT_DIR
|
||||||
// INPUT_DIR: frames.txt ("size RW RH OW OH HDR LOWRES MW MH", then per frame "frame F jx jy mvsx mvsy reset") and
|
// INPUT_DIR: frames.txt ("size RW RH OW OH HDR LOWRES MW MH [AUTOEXPOSURE]", then per frame
|
||||||
|
// "frame F jx jy mvsx mvsy reset [preExposure exposureScale]") and
|
||||||
// F-color.bin (RGBA16F, render size), F-motion.bin (RG16F, MW x MH),
|
// F-color.bin (RGBA16F, render size), F-motion.bin (RG16F, MW x MH),
|
||||||
// F-depth.bin (R32F, render size, with LOWRES). D4R_E_TIME_REPEAT=N: best of N GPU timings per frame.
|
// F-depth.bin (R32F, render size, with LOWRES), optionally F-exposure.bin (R32F 1x1: the game's exposure, HDR). D4R_E_TIME_REPEAT=N: best of N GPU timings per frame.
|
||||||
#include "runtime.h"
|
#include "runtime.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -125,11 +126,16 @@ int main(int argc, char** argv) try {
|
|||||||
auto framesText = read(inputDir + "/frames.txt");
|
auto framesText = read(inputDir + "/frames.txt");
|
||||||
std::istringstream frames(std::string(framesText.begin(), framesText.end()));
|
std::istringstream frames(std::string(framesText.begin(), framesText.end()));
|
||||||
std::string key;
|
std::string key;
|
||||||
uint32_t rw, rh, ow, oh, hdr, lowRes, mw, mh;
|
uint32_t rw, rh, ow, oh, hdr, lowRes, mw, mh, autoExposure = 0;
|
||||||
frames >> key >> rw >> rh >> ow >> oh >> hdr >> lowRes >> mw >> mh;
|
std::string sizeLine;
|
||||||
|
std::getline(frames, sizeLine);
|
||||||
|
std::istringstream size(sizeLine);
|
||||||
|
size >> key >> rw >> rh >> ow >> oh >> hdr >> lowRes >> mw >> mh;
|
||||||
|
size >> autoExposure;
|
||||||
d4r::EModel model(c.physical, c.device, modelDir);
|
d4r::EModel model(c.physical, c.device, modelDir);
|
||||||
d4r::EEngine engine(model, rw, rh, ow, oh, hdr != 0, lowRes != 0);
|
d4r::EEngine engine(model, rw, rh, ow, oh, hdr != 0, lowRes != 0, autoExposure != 0);
|
||||||
Input color(c, VK_FORMAT_R16G16B16A16_SFLOAT, rw, rh), motion(c, VK_FORMAT_R16G16_SFLOAT, mw, mh), depth(c, VK_FORMAT_R32_SFLOAT, rw, rh);
|
Input color(c, VK_FORMAT_R16G16B16A16_SFLOAT, rw, rh), motion(c, VK_FORMAT_R16G16_SFLOAT, mw, mh), depth(c, VK_FORMAT_R32_SFLOAT, rw, rh);
|
||||||
|
Input exposure(c, VK_FORMAT_R32_SFLOAT, 1, 1);
|
||||||
HostBuffer staging(c, std::max<VkDeviceSize>(VkDeviceSize(ow) * oh * 8, 64u << 20));
|
HostBuffer staging(c, std::max<VkDeviceSize>(VkDeviceSize(ow) * oh * 8, 64u << 20));
|
||||||
VkQueryPoolCreateInfo qp{VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO};
|
VkQueryPoolCreateInfo qp{VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO};
|
||||||
qp.queryType = VK_QUERY_TYPE_TIMESTAMP; qp.queryCount = 4;
|
qp.queryType = VK_QUERY_TYPE_TIMESTAMP; qp.queryCount = 4;
|
||||||
@@ -148,16 +154,22 @@ int main(int argc, char** argv) try {
|
|||||||
barrier(cb, in.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
|
barrier(cb, in.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
while (frames >> key) {
|
for (std::string line; std::getline(frames, line);) {
|
||||||
|
std::istringstream fields(line);
|
||||||
|
if (!(fields >> key)) continue;
|
||||||
uint32_t f; int reset;
|
uint32_t f; int reset;
|
||||||
d4r::EFrameSettings s;
|
d4r::EFrameSettings s;
|
||||||
frames >> f >> s.jitter[0] >> s.jitter[1] >> s.motionScale[0] >> s.motionScale[1] >> reset;
|
fields >> f >> s.jitter[0] >> s.jitter[1] >> s.motionScale[0] >> s.motionScale[1] >> reset;
|
||||||
|
if (!(fields >> s.preExposure >> s.exposureScale)) s.preExposure = s.exposureScale = 1;
|
||||||
s.reset = reset != 0;
|
s.reset = reset != 0;
|
||||||
upload(color, "color", f, rw, rh, 8);
|
upload(color, "color", f, rw, rh, 8);
|
||||||
upload(motion, "motion", f, mw, mh, 4);
|
upload(motion, "motion", f, mw, mh, 4);
|
||||||
if (lowRes) upload(depth, "depth", f, rw, rh, 4);
|
if (lowRes) upload(depth, "depth", f, rw, rh, 4);
|
||||||
|
const bool gameExposure = std::ifstream(inputDir + "/" + std::to_string(f) + "-exposure.bin").good();
|
||||||
|
if (gameExposure) upload(exposure, "exposure", f, 1, 1, 4);
|
||||||
d4r::FrameImages images{};
|
d4r::FrameImages images{};
|
||||||
images.color = color.view; images.motion = motion.view; images.depth = depth.view;
|
images.color = color.view; images.motion = motion.view; images.depth = depth.view;
|
||||||
|
if (gameExposure) images.exposure = exposure.view;
|
||||||
engine.setFrame(images, s);
|
engine.setFrame(images, s);
|
||||||
if (first) c.submit([&](VkCommandBuffer cb) { model.recordUpload(cb); engine.recordInitialize(cb); first = false; });
|
if (first) c.submit([&](VkCommandBuffer cb) { model.recordUpload(cb); engine.recordInitialize(cb); first = false; });
|
||||||
double best = 1e30, in = 0, net = 0, fin = 0;
|
double best = 1e30, in = 0, net = 0, fin = 0;
|
||||||
|
|||||||
+6
-2
@@ -317,6 +317,7 @@ struct EFrameSettings {
|
|||||||
float jitter[2] = {0, 0}; // render pixels, as the game passes them to NGX
|
float jitter[2] = {0, 0}; // render pixels, as the game passes them to NGX
|
||||||
float motionScale[2] = {1, 1}; // NGX's MV scale: motion texels times this are the motion's pixels
|
float motionScale[2] = {1, 1}; // NGX's MV scale: motion texels times this are the motion's pixels
|
||||||
bool reset = false;
|
bool reset = false;
|
||||||
|
float preExposure = 1, exposureScale = 1; // the game's (DLSS.Pre.Exposure, DLSS.Exposure.Scale)
|
||||||
};
|
};
|
||||||
class EModel {
|
class EModel {
|
||||||
public:
|
public:
|
||||||
@@ -326,6 +327,7 @@ public:
|
|||||||
EModel& operator=(const EModel&) = delete;
|
EModel& operator=(const EModel&) = delete;
|
||||||
void recordUpload(VkCommandBuffer command);
|
void recordUpload(VkCommandBuffer command);
|
||||||
bool uploadRecorded() const;
|
bool uploadRecorded() const;
|
||||||
|
bool hasAutoExposure() const; // the model directory holds the exposure shaders (exposure_e0/1.spv)
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
std::unique_ptr<Impl> impl;
|
std::unique_ptr<Impl> impl;
|
||||||
@@ -335,14 +337,16 @@ class EEngine {
|
|||||||
public:
|
public:
|
||||||
// hdr: NGX's IsHDR (linear colour, the exposure textures); lowResMotion: MVLowRes (render-size motion vectors,
|
// hdr: NGX's IsHDR (linear colour, the exposure textures); lowResMotion: MVLowRes (render-size motion vectors,
|
||||||
// in render pixels; otherwise output-size motion in output pixels). Render size at most the output size.
|
// in render pixels; otherwise output-size motion in output pixels). Render size at most the output size.
|
||||||
|
// autoExposure (HDR): NGX's AutoExposure, the exposure measured from the colour on every second frame instead of
|
||||||
|
// read from images.exposure (needs EModel::hasAutoExposure).
|
||||||
EEngine(EModel& model, uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight,
|
EEngine(EModel& model, uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight,
|
||||||
bool hdr, bool lowResMotion);
|
bool hdr, bool lowResMotion, bool autoExposure = false);
|
||||||
~EEngine();
|
~EEngine();
|
||||||
EEngine(const EEngine&) = delete;
|
EEngine(const EEngine&) = delete;
|
||||||
EEngine& operator=(const EEngine&) = delete;
|
EEngine& operator=(const EEngine&) = delete;
|
||||||
void recordInitialize(VkCommandBuffer command);
|
void recordInitialize(VkCommandBuffer command);
|
||||||
// color (and depth with lowResMotion) at the render size, motion at the render or output size; images.exposure
|
// color (and depth with lowResMotion) at the render size, motion at the render or output size; images.exposure
|
||||||
// the game's 1x1 exposure (HDR; 1 when absent). A borrowed RGBA16F finalOutput (GENERAL) is written directly.
|
// the game's 1x1 exposure (HDR without autoExposure; 1 when absent). A borrowed RGBA16F finalOutput (GENERAL) is written directly.
|
||||||
void setFrame(const FrameImages& images, const EFrameSettings& settings, FrameOutput finalOutput = {});
|
void setFrame(const FrameImages& images, const EFrameSettings& settings, FrameOutput finalOutput = {});
|
||||||
// With a timestamp query pool, four stamps are written from query `first`: start, after the input kernel, after
|
// With a timestamp query pool, four stamps are written from query `first`: start, after the input kernel, after
|
||||||
// the network and after the reconstruction.
|
// the network and after the reconstruction.
|
||||||
|
|||||||
+118
-14
@@ -50,6 +50,10 @@ struct EModel::Impl {
|
|||||||
std::map<std::string, std::unique_ptr<ETranslated>> input, final;
|
std::map<std::string, std::unique_ptr<ETranslated>> input, final;
|
||||||
std::vector<uint8_t> inputTemplate, finalTemplate;
|
std::vector<uint8_t> inputTemplate, finalTemplate;
|
||||||
bool uploaded = false;
|
bool uploaded = false;
|
||||||
|
std::array<Pipeline, 2> exposure; // engine/exposure_k.comp MODE 3 (NGX's AutoExposure), when the model has it
|
||||||
|
bool hasExposure = false;
|
||||||
|
Pipeline exposureCopy; // engine/sr/exposure_copy_e.comp: the previous frame's exposure (HDR)
|
||||||
|
bool hasExposureCopy = false;
|
||||||
uint32_t preset = 5; // 5: E, 6: F
|
uint32_t preset = 5; // 5: E, 6: F
|
||||||
float kspread = 0.035f; // the reconstruction's constant at 584 times rx ry (E 0.035, F 0.1)
|
float kspread = 0.035f; // the reconstruction's constant at 584 times rx ry (E 0.035, F 0.1)
|
||||||
Impl(VkPhysicalDevice physical, VkDevice dev, const std::string& dir, VkPipelineCache c)
|
Impl(VkPhysicalDevice physical, VkDevice dev, const std::string& dir, VkPipelineCache c)
|
||||||
@@ -101,6 +105,18 @@ struct EModel::Impl {
|
|||||||
k.surfaces.emplace_back(static_cast<uint32_t>(std::stoul(item.substr(0, colon))), item.substr(colon + 1));
|
k.surfaces.emplace_back(static_cast<uint32_t>(std::stoul(item.substr(0, colon))), item.substr(colon + 1));
|
||||||
}
|
}
|
||||||
(key == "input" ? input : final)[variant] = std::move(entry);
|
(key == "input" ? input : final)[variant] = std::move(entry);
|
||||||
|
} else if (key == "exposure") {
|
||||||
|
// "exposure PASS0.spv PASS1.spv": the measurement over the colour, then the 1x1 exposure image
|
||||||
|
std::string measure, update; f >> measure >> update;
|
||||||
|
const auto t = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, s = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||||
|
exposure[0].create(device.handle, dir + "/" + measure, {b, t, b}, 32, cache);
|
||||||
|
exposure[1].create(device.handle, dir + "/" + update, {b, b, b, s}, 32, cache);
|
||||||
|
hasExposure = true;
|
||||||
|
} else if (key == "exposure_copy") {
|
||||||
|
std::string shader; f >> shader;
|
||||||
|
exposureCopy.create(device.handle, dir + "/" + shader, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}, 32, cache);
|
||||||
|
hasExposureCopy = true;
|
||||||
} else if (key == "template") {
|
} else if (key == "template") {
|
||||||
std::string which, file; f >> which >> file;
|
std::string which, file; f >> which >> file;
|
||||||
auto bytes = read(dir + "/" + file);
|
auto bytes = read(dir + "/" + file);
|
||||||
@@ -143,12 +159,13 @@ void EModel::recordUpload(VkCommandBuffer cb) {
|
|||||||
m.uploaded = true;
|
m.uploaded = true;
|
||||||
}
|
}
|
||||||
bool EModel::uploadRecorded() const { return impl->uploaded; }
|
bool EModel::uploadRecorded() const { return impl->uploaded; }
|
||||||
|
bool EModel::hasAutoExposure() const { return impl->hasExposure; }
|
||||||
|
|
||||||
struct EEngine::Impl {
|
struct EEngine::Impl {
|
||||||
EModel::Impl& model;
|
EModel::Impl& model;
|
||||||
const Device& d;
|
const Device& d;
|
||||||
uint32_t rw, rh, ow, oh, pw, ph, h0, w0; // render, output, the output padded to 128, the token grid
|
uint32_t rw, rh, ow, oh, pw, ph, h0, w0; // render, output, the output padded to 128, the token grid
|
||||||
bool hdr, lowRes;
|
bool hdr, lowRes, autoExposure;
|
||||||
ETranslated *input = nullptr, *final = nullptr;
|
ETranslated *input = nullptr, *final = nullptr;
|
||||||
std::string finalVariant;
|
std::string finalVariant;
|
||||||
uint32_t finalGrid[2] = {0, 0};
|
uint32_t finalGrid[2] = {0, 0};
|
||||||
@@ -161,6 +178,13 @@ struct EEngine::Impl {
|
|||||||
std::vector<uint8_t> paramStaging, inBlock, finBlock;
|
std::vector<uint8_t> paramStaging, inBlock, finBlock;
|
||||||
std::array<Tex, 2> history, motionHistory;
|
std::array<Tex, 2> history, motionHistory;
|
||||||
Tex warped, discard, zero, exposureOne, out;
|
Tex warped, discard, zero, exposureOne, out;
|
||||||
|
// AutoExposure: NGX's measurement (exposure_k.comp MODE 3) into `measured`, the 1x1 the kernels then sample
|
||||||
|
Buffer expP, expSums, expState;
|
||||||
|
ExposureParams exposure{};
|
||||||
|
Tex measured;
|
||||||
|
VkDescriptorSet exp1Set = VK_NULL_HANDLE;
|
||||||
|
// HDR with a model that has the copy shader: the previous frame's exposure, the input kernel's texture 352
|
||||||
|
Tex previousExposure;
|
||||||
bool outNew = false;
|
bool outNew = false;
|
||||||
VkSampler nearest = VK_NULL_HANDLE, linear = VK_NULL_HANDLE;
|
VkSampler nearest = VK_NULL_HANDLE, linear = VK_NULL_HANDLE;
|
||||||
VkDescriptorPool fixedPool = VK_NULL_HANDLE;
|
VkDescriptorPool fixedPool = VK_NULL_HANDLE;
|
||||||
@@ -170,6 +194,8 @@ struct EEngine::Impl {
|
|||||||
FrameOutput finalOutput{};
|
FrameOutput finalOutput{};
|
||||||
VkDescriptorPool pool = VK_NULL_HANDLE;
|
VkDescriptorPool pool = VK_NULL_HANDLE;
|
||||||
std::array<VkDescriptorSet, 2> input{}, final{};
|
std::array<VkDescriptorSet, 2> input{}, final{};
|
||||||
|
VkDescriptorSet exposure = VK_NULL_HANDLE; // the measurement over this colour view (AutoExposure)
|
||||||
|
VkDescriptorSet copy = VK_NULL_HANDLE; // this frame's exposure into previousExposure
|
||||||
};
|
};
|
||||||
std::vector<Bindings> bindings;
|
std::vector<Bindings> bindings;
|
||||||
const Bindings* bound = nullptr;
|
const Bindings* bound = nullptr;
|
||||||
@@ -182,10 +208,15 @@ struct EEngine::Impl {
|
|||||||
uint32_t lh(uint32_t l) const { return h0 >> l; }
|
uint32_t lh(uint32_t l) const { return h0 >> l; }
|
||||||
uint32_t lw(uint32_t l) const { return w0 >> l; }
|
uint32_t lw(uint32_t l) const { return w0 >> l; }
|
||||||
|
|
||||||
Impl(EModel::Impl& m, uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight, bool isHdr, bool lowResMotion)
|
Impl(EModel::Impl& m, uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight, bool isHdr,
|
||||||
: model(m), d(m.device), rw(renderWidth), rh(renderHeight), ow(outputWidth), oh(outputHeight), hdr(isHdr), lowRes(lowResMotion) {
|
bool lowResMotion, bool isAutoExposure)
|
||||||
|
: model(m), d(m.device), rw(renderWidth), rh(renderHeight), ow(outputWidth), oh(outputHeight), hdr(isHdr), lowRes(lowResMotion),
|
||||||
|
autoExposure(isAutoExposure) {
|
||||||
if (rw < 32 || rh < 32 || rw > ow || rh > oh || ow > 16384 || oh > 16384)
|
if (rw < 32 || rh < 32 || rw > ow || rh > oh || ow > 16384 || oh > 16384)
|
||||||
throw std::runtime_error("unsupported preset E/F dimensions");
|
throw std::runtime_error("unsupported preset E/F dimensions");
|
||||||
|
if (autoExposure && (!hdr || !model.hasExposure))
|
||||||
|
throw std::runtime_error(hdr ? "the preset E/F model has no exposure shaders (rebuild it with compile_sr_e.py)"
|
||||||
|
: "automatic exposure needs HDR colour");
|
||||||
pw = (ow + 127) & ~127u; ph = (oh + 127) & ~127u; w0 = pw / 4; h0 = ph / 4;
|
pw = (ow + 127) & ~127u; ph = (oh + 127) & ~127u; w0 = pw / 4; h0 = ph / 4;
|
||||||
// The mid kernel uses the actual scale/jitter and also covers the integer-rounded 2/3 render sizes.
|
// The mid kernel uses the actual scale/jitter and also covers the integer-rounded 2/3 render sizes.
|
||||||
// Requiring exact 3:2 dimensions sends e.g. 853x533 -> 1280x800 through the much larger generic kernel.
|
// Requiring exact 3:2 dimensions sends e.g. 853x533 -> 1280x800 through the much larger generic kernel.
|
||||||
@@ -234,15 +265,38 @@ struct EEngine::Impl {
|
|||||||
discard.create(d, 1, 1, half4);
|
discard.create(d, 1, 1, half4);
|
||||||
zero.create(d, 1, 1, half4);
|
zero.create(d, 1, 1, half4);
|
||||||
exposureOne.create(d, 1, 1, VK_FORMAT_R32_SFLOAT);
|
exposureOne.create(d, 1, 1, VK_FORMAT_R32_SFLOAT);
|
||||||
|
if (autoExposure) {
|
||||||
|
// NGX's cuda_luma_convert_kernel grid (harness captures 2026-10-19, like M's and L's): half the output,
|
||||||
|
// half the render size but at least 256 where the output is more than twice the render size
|
||||||
|
const uint32_t gx = exposureGrid(rw, ow), gy = exposureGrid(rh, oh);
|
||||||
|
exposure.size[0] = int32_t(rw); exposure.size[1] = int32_t(rh);
|
||||||
|
exposure.sampleSize[0] = int32_t(gx); exposure.sampleSize[1] = int32_t(gy);
|
||||||
|
exposure.invCount = 1.0f / float(gx * gy);
|
||||||
|
exposure.key = 0.18f / 0.82f; // cuda_auto_exposure_copy_kernel's argument 0
|
||||||
|
exposure.blocksX = (gx + 31) / 32;
|
||||||
|
exposure.blocks = exposure.blocksX * ((gy + 31) / 32);
|
||||||
|
expP.create(d, sizeof(ExposureParams), storage);
|
||||||
|
expSums.create(d, VkDeviceSize(exposure.blocks) * 4, storage);
|
||||||
|
expState.create(d, 8, storage);
|
||||||
|
measured.create(d, 1, 1, VK_FORMAT_R16_SFLOAT);
|
||||||
|
}
|
||||||
|
if (hdr && model.hasExposureCopy) previousExposure.create(d, 1, 1, VK_FORMAT_R16_SFLOAT);
|
||||||
VkSamplerCreateInfo si{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
VkSamplerCreateInfo si{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
||||||
si.addressModeU = si.addressModeV = si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; si.maxLod = 0;
|
si.addressModeU = si.addressModeV = si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; si.maxLod = 0;
|
||||||
VKCHECK(vkCreateSampler(d.handle, &si, nullptr, &nearest));
|
VKCHECK(vkCreateSampler(d.handle, &si, nullptr, &nearest));
|
||||||
si.magFilter = si.minFilter = VK_FILTER_LINEAR;
|
si.magFilter = si.minFilter = VK_FILTER_LINEAR;
|
||||||
VKCHECK(vkCreateSampler(d.handle, &si, nullptr, &linear));
|
VKCHECK(vkCreateSampler(d.handle, &si, nullptr, &linear));
|
||||||
VkDescriptorPoolSize sizes[] = {{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3 * ENet}};
|
VkDescriptorPoolSize sizes[] = {{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3 * ENet + 3}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1}};
|
||||||
VkDescriptorPoolCreateInfo pi{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
VkDescriptorPoolCreateInfo pi{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||||
pi.maxSets = ENet; pi.poolSizeCount = 1; pi.pPoolSizes = sizes;
|
pi.maxSets = ENet + 1; pi.poolSizeCount = 2; pi.pPoolSizes = sizes;
|
||||||
VKCHECK(vkCreateDescriptorPool(d.handle, &pi, nullptr, &fixedPool));
|
VKCHECK(vkCreateDescriptorPool(d.handle, &pi, nullptr, &fixedPool));
|
||||||
|
if (autoExposure) {
|
||||||
|
exp1Set = descriptorSet(d.handle, fixedPool, model.exposure[1].descriptors);
|
||||||
|
bindBuffer(d.handle, exp1Set, 0, expP);
|
||||||
|
bindBuffer(d.handle, exp1Set, 1, expSums);
|
||||||
|
bindBuffer(d.handle, exp1Set, 2, expState);
|
||||||
|
bindImage(d.handle, exp1Set, 3, measured.view);
|
||||||
|
}
|
||||||
for (unsigned s = 0; s < ENet; ++s) {
|
for (unsigned s = 0; s < ENet; ++s) {
|
||||||
netSets[s] = descriptorSet(d.handle, fixedPool, model.stages[s].descriptors);
|
netSets[s] = descriptorSet(d.handle, fixedPool, model.stages[s].descriptors);
|
||||||
bindRange(d.handle, netSets[s], 0, params, paramAt[s]);
|
bindRange(d.handle, netSets[s], 0, params, paramAt[s]);
|
||||||
@@ -261,8 +315,8 @@ struct EEngine::Impl {
|
|||||||
// what a translated kernel's texture or surface argument is (NGX's wiring in the captures; null where NGX leaves
|
// what a translated kernel's texture or surface argument is (NGX's wiring in the captures; null where NGX leaves
|
||||||
// it unbound). par: this frame's parity (history written this frame, the other one read)
|
// it unbound). par: this frame's parity (history written this frame, the other one read)
|
||||||
Resource resolve(bool isInput, bool surface, uint32_t offset, uint32_t par, const FrameImages& in, VkImageView output) const {
|
Resource resolve(bool isInput, bool surface, uint32_t offset, uint32_t par, const FrameImages& in, VkImageView output) const {
|
||||||
const VkImageView exposure = hdr ? (in.exposure ? in.exposure : exposureOne.view) : VK_NULL_HANDLE;
|
const VkImageView exposure = !hdr ? VK_NULL_HANDLE : autoExposure ? measured.view : in.exposure ? in.exposure : exposureOne.view;
|
||||||
const VkImageLayout exposureLayout = in.exposure ? in.exposureLayout : VK_IMAGE_LAYOUT_GENERAL;
|
const VkImageLayout exposureLayout = !autoExposure && in.exposure ? in.exposureLayout : VK_IMAGE_LAYOUT_GENERAL;
|
||||||
if (isInput) {
|
if (isInput) {
|
||||||
if (!surface) switch (offset) {
|
if (!surface) switch (offset) {
|
||||||
case 280: return {in.color, true, in.colorLayout};
|
case 280: return {in.color, true, in.colorLayout};
|
||||||
@@ -270,7 +324,10 @@ struct EEngine::Impl {
|
|||||||
case 304: return {in.motion, false, in.motionLayout};
|
case 304: return {in.motion, false, in.motionLayout};
|
||||||
case 312: return {in.depth, false, in.depthLayout};
|
case 312: return {in.depth, false, in.depthLayout};
|
||||||
case 320: return {motionHistory[par ^ 1].view, true};
|
case 320: return {motionHistory[par ^ 1].view, true};
|
||||||
case 344: case 352: case 360: return {exposure, false, exposureLayout};
|
case 344: case 360: return {exposure, false, exposureLayout};
|
||||||
|
// NGX: the previous frame's exposure (its copy kernel's surface; the current one on a reset frame,
|
||||||
|
// where the engine copies first). Models without the copy shader bind the current exposure.
|
||||||
|
case 352: return previousExposure.view ? Resource{previousExposure.view} : Resource{exposure, false, exposureLayout};
|
||||||
} else switch (offset) {
|
} else switch (offset) {
|
||||||
case 328: return {motionHistory[par].view};
|
case 328: return {motionHistory[par].view};
|
||||||
case 368: return {discard.view};
|
case 368: return {discard.view};
|
||||||
@@ -310,6 +367,9 @@ struct EEngine::Impl {
|
|||||||
throw std::runtime_error("preset E/F needs colour and motion (and depth with render-size motion)");
|
throw std::runtime_error("preset E/F needs colour and motion (and depth with render-size motion)");
|
||||||
if (finalOutput.view && finalOutput.format != VK_FORMAT_R16G16B16A16_SFLOAT)
|
if (finalOutput.view && finalOutput.format != VK_FORMAT_R16G16B16A16_SFLOAT)
|
||||||
throw std::runtime_error("preset E/F writes RGBA16F output only");
|
throw std::runtime_error("preset E/F writes RGBA16F output only");
|
||||||
|
if (!(settings.preExposure > 0.0f) || !std::isfinite(settings.preExposure) || !(settings.exposureScale > 0.0f) ||
|
||||||
|
!std::isfinite(settings.exposureScale))
|
||||||
|
throw std::runtime_error("the pre-exposure and exposure scale must be positive numbers");
|
||||||
const bool rst = settings.reset || frame == 0;
|
const bool rst = settings.reset || frame == 0;
|
||||||
const float frw = float(rw), frh = float(rh), fow = float(ow), foh = float(oh);
|
const float frw = float(rw), frh = float(rh), fow = float(ow), foh = float(oh);
|
||||||
const float rx = frw / fow, ry = frh / foh;
|
const float rx = frw / fow, ry = frh / foh;
|
||||||
@@ -328,6 +388,10 @@ struct EEngine::Impl {
|
|||||||
put32(p, 136, rw); put32(p, 140, rh); putF(p, 144, rx); putF(p, 148, ry);
|
put32(p, 136, rw); put32(p, 140, rh); putF(p, 144, rx); putF(p, 148, ry);
|
||||||
putF(p, 152, 1.0f / fow); putF(p, 156, 1.0f / foh); putF(p, 160, model.preset == 5 ? 500.0f / foh : 1.0f);
|
putF(p, 152, 1.0f / fow); putF(p, 156, 1.0f / foh); putF(p, 160, model.preset == 5 ? 500.0f / foh : 1.0f);
|
||||||
put32(p, 184, lowRes ? 1u : 0u); put32(p, 188, lowRes ? 1u : 0u);
|
put32(p, 184, lowRes ? 1u : 0u); put32(p, 188, lowRes ? 1u : 0u);
|
||||||
|
// the game's exposure scale (twice), pre-exposure and its inverse: both kernels take this frame's values, with
|
||||||
|
// or without AutoExposure (captures with per-frame schedules, 2026-10-19); the kept measurement is not rescaled
|
||||||
|
const float exposureFields[4] = {settings.exposureScale, settings.exposureScale, settings.preExposure, 1.0f / settings.preExposure};
|
||||||
|
for (unsigned i = 0; i < 4; ++i) putF(p, 244 + 4 * i, exposureFields[i]);
|
||||||
put64(p, 376, at[0]);
|
put64(p, 376, at[0]);
|
||||||
auto& o = finBlock;
|
auto& o = finBlock;
|
||||||
o = model.finalTemplate;
|
o = model.finalTemplate;
|
||||||
@@ -337,9 +401,12 @@ struct EEngine::Impl {
|
|||||||
put32(o, 312, w0 * 32); put32(o, 316, w0 * 32); put32(o, 320, w0 * 40);
|
put32(o, 312, w0 * 32); put32(o, 316, w0 * 32); put32(o, 320, w0 * 40);
|
||||||
put32(o, 504, oh); put32(o, 508, ow); put32(o, 512, hdr ? 1u : 0u);
|
put32(o, 504, oh); put32(o, 508, ow); put32(o, 512, hdr ? 1u : 0u);
|
||||||
putF(o, 528, ry); putF(o, 532, 1.0f / ry); putF(o, 536, rx); putF(o, 540, 1.0f / rx);
|
putF(o, 528, ry); putF(o, 532, 1.0f / ry); putF(o, 536, rx); putF(o, 540, 1.0f / rx);
|
||||||
putF(o, 544, -pj[0]); putF(o, 548, -pj[1]); putF(o, 552, -settings.jitter[0]); putF(o, 556, -settings.jitter[1]);
|
// the previous and this frame's jitter as (y, x): (-x, -y) agrees with it only on the harness's first three
|
||||||
|
// jitters (0 / -+0.25), so later frames of a Halton sequence measured 26-28 dB (six-frame captures, 2026-10-19)
|
||||||
|
putF(o, 544, pj[1]); putF(o, 548, pj[0]); putF(o, 552, settings.jitter[1]); putF(o, 556, settings.jitter[0]);
|
||||||
put32(o, 564, rh - 1); put32(o, 572, rw - 1);
|
put32(o, 564, rh - 1); put32(o, 572, rw - 1);
|
||||||
putF(o, 584, model.kspread / (rx * ry));
|
putF(o, 584, model.kspread / (rx * ry));
|
||||||
|
for (unsigned i = 0; i < 4; ++i) putF(o, 592 + 4 * i, exposureFields[i]);
|
||||||
put64(o, 0, at[16]); put64(o, 8, at[1]);
|
put64(o, 0, at[16]); put64(o, 8, at[1]);
|
||||||
for (unsigned i = 0; i < 4; ++i) put64(o, model.head[i].arg, headAt[i]);
|
for (unsigned i = 0; i < 4; ++i) put64(o, model.head[i].arg, headAt[i]);
|
||||||
put64(o, 256, scratch);
|
put64(o, 256, scratch);
|
||||||
@@ -361,15 +428,27 @@ struct EEngine::Impl {
|
|||||||
b.images = images; b.finalOutput = finalOutput;
|
b.images = images; b.finalOutput = finalOutput;
|
||||||
const uint32_t tex = 2 * uint32_t(input->textures.size() + final->textures.size());
|
const uint32_t tex = 2 * uint32_t(input->textures.size() + final->textures.size());
|
||||||
const uint32_t surf = 2 * uint32_t(input->surfaces.size() + final->surfaces.size());
|
const uint32_t surf = 2 * uint32_t(input->surfaces.size() + final->surfaces.size());
|
||||||
VkDescriptorPoolSize sizes[] = {{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 12}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, tex},
|
VkDescriptorPoolSize sizes[] = {{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 14}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, tex + 2},
|
||||||
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, surf}};
|
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, surf + 1}};
|
||||||
VkDescriptorPoolCreateInfo pi{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
VkDescriptorPoolCreateInfo pi{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||||
pi.maxSets = 4; pi.poolSizeCount = 3; pi.pPoolSizes = sizes;
|
pi.maxSets = 6; pi.poolSizeCount = 3; pi.pPoolSizes = sizes;
|
||||||
VKCHECK(vkCreateDescriptorPool(d.handle, &pi, nullptr, &b.pool));
|
VKCHECK(vkCreateDescriptorPool(d.handle, &pi, nullptr, &b.pool));
|
||||||
for (uint32_t par = 0; par < 2; ++par) {
|
for (uint32_t par = 0; par < 2; ++par) {
|
||||||
b.input[par] = translatedSet(b, true, par, output);
|
b.input[par] = translatedSet(b, true, par, output);
|
||||||
b.final[par] = translatedSet(b, false, par, output);
|
b.final[par] = translatedSet(b, false, par, output);
|
||||||
}
|
}
|
||||||
|
if (autoExposure) {
|
||||||
|
b.exposure = descriptorSet(d.handle, b.pool, model.exposure[0].descriptors);
|
||||||
|
bindBuffer(d.handle, b.exposure, 0, expP);
|
||||||
|
bindImage(d.handle, b.exposure, 1, images.color, nearest, images.colorLayout);
|
||||||
|
bindBuffer(d.handle, b.exposure, 2, expSums);
|
||||||
|
}
|
||||||
|
if (previousExposure.view) {
|
||||||
|
const auto current = resolve(true, false, 344, 0, images, output);
|
||||||
|
b.copy = descriptorSet(d.handle, b.pool, model.exposureCopy.descriptors);
|
||||||
|
bindImage(d.handle, b.copy, 0, current.view, nearest, current.layout);
|
||||||
|
bindImage(d.handle, b.copy, 1, previousExposure.view);
|
||||||
|
}
|
||||||
bindings.push_back(std::move(b));
|
bindings.push_back(std::move(b));
|
||||||
found = bindings.end() - 1;
|
found = bindings.end() - 1;
|
||||||
}
|
}
|
||||||
@@ -380,6 +459,12 @@ struct EEngine::Impl {
|
|||||||
std::ofstream(std::string(dump) + "/" + std::to_string(frame + 1) + "-final.args", std::ios::binary)
|
std::ofstream(std::string(dump) + "/" + std::to_string(frame + 1) + "-final.args", std::ios::binary)
|
||||||
.write(reinterpret_cast<const char*>(finBlock.data()), static_cast<std::streamsize>(finBlock.size()));
|
.write(reinterpret_cast<const char*>(finBlock.data()), static_cast<std::streamsize>(finBlock.size()));
|
||||||
}
|
}
|
||||||
|
if (autoExposure) {
|
||||||
|
// NGX measures on every second evaluation from the first (and K's rule: on a reset too); the luma is
|
||||||
|
// scaled by 1 / pre-exposure, and the image keeps the last measurement on the frames between
|
||||||
|
exposure.scale = 1.0f / settings.preExposure;
|
||||||
|
exposure.flags = (frame % 2 == 0 || rst ? 1u : 0u) | (rst ? 2u : 0u);
|
||||||
|
}
|
||||||
reset = rst;
|
reset = rst;
|
||||||
prevJitter[0] = settings.jitter[0]; prevJitter[1] = settings.jitter[1];
|
prevJitter[0] = settings.jitter[0]; prevJitter[1] = settings.jitter[1];
|
||||||
}
|
}
|
||||||
@@ -387,6 +472,8 @@ struct EEngine::Impl {
|
|||||||
VkClearColorValue clear{};
|
VkClearColorValue clear{};
|
||||||
VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||||
std::vector<VkImage> images{warped.handle, discard.handle, zero.handle, exposureOne.handle};
|
std::vector<VkImage> images{warped.handle, discard.handle, zero.handle, exposureOne.handle};
|
||||||
|
if (autoExposure) images.push_back(measured.handle);
|
||||||
|
if (previousExposure.handle) images.push_back(previousExposure.handle);
|
||||||
for (int i = 0; i < 2; ++i) { images.push_back(history[i].handle); images.push_back(motionHistory[i].handle); }
|
for (int i = 0; i < 2; ++i) { images.push_back(history[i].handle); images.push_back(motionHistory[i].handle); }
|
||||||
for (VkImage image : images) {
|
for (VkImage image : images) {
|
||||||
imageBarrier(cb, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
imageBarrier(cb, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||||
@@ -396,6 +483,7 @@ struct EEngine::Impl {
|
|||||||
VkClearColorValue one{}; one.float32[0] = 1.0f;
|
VkClearColorValue one{}; one.float32[0] = 1.0f;
|
||||||
vkCmdClearColorImage(cb, exposureOne.handle, VK_IMAGE_LAYOUT_GENERAL, &one, 1, &range);
|
vkCmdClearColorImage(cb, exposureOne.handle, VK_IMAGE_LAYOUT_GENERAL, &one, 1, &range);
|
||||||
vkCmdFillBuffer(cb, arena.handle, 0, VK_WHOLE_SIZE, 0);
|
vkCmdFillBuffer(cb, arena.handle, 0, VK_WHOLE_SIZE, 0);
|
||||||
|
if (autoExposure) vkCmdFillBuffer(cb, expState.handle, 0, VK_WHOLE_SIZE, 0x3F800000u); // 1.0 until the first measurement
|
||||||
memoryBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
memoryBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
VK_ACCESS_TRANSFER_WRITE_BIT);
|
VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||||
// the head weights into the arena (the reconstruction kernel reads them there)
|
// the head weights into the arena (the reconstruction kernel reads them there)
|
||||||
@@ -450,9 +538,22 @@ struct EEngine::Impl {
|
|||||||
std::memcpy(paramStaging.data() + finAt, finBlock.data(), finBlock.size());
|
std::memcpy(paramStaging.data() + finAt, finBlock.data(), finBlock.size());
|
||||||
for (VkDeviceSize a = 0; a < paramBytes; a += 65536)
|
for (VkDeviceSize a = 0; a < paramBytes; a += 65536)
|
||||||
vkCmdUpdateBuffer(cb, params.handle, a, std::min<VkDeviceSize>(65536, paramBytes - a), paramStaging.data() + a);
|
vkCmdUpdateBuffer(cb, params.handle, a, std::min<VkDeviceSize>(65536, paramBytes - a), paramStaging.data() + a);
|
||||||
|
if (autoExposure) vkCmdUpdateBuffer(cb, expP.handle, 0, sizeof(ExposureParams), &exposure);
|
||||||
memoryBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
memoryBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||||
pass();
|
pass();
|
||||||
|
if (autoExposure) { // counted in the input pass's time
|
||||||
|
if (exposure.flags & 1u) {
|
||||||
|
dispatch(cb, model.exposure[0], b.exposure, exposure.blocksX, exposure.blocks / exposure.blocksX);
|
||||||
|
step();
|
||||||
|
}
|
||||||
|
dispatch(cb, model.exposure[1], exp1Set, 1, 1);
|
||||||
|
step();
|
||||||
|
}
|
||||||
|
if (b.copy && reset) { // NGX's reset frame reads the current exposure as the previous one
|
||||||
|
dispatch(cb, model.exposureCopy, b.copy, 1, 1);
|
||||||
|
step();
|
||||||
|
}
|
||||||
dispatch(cb, *input->pipeline, b.input[cur], (ow + 15) / 16, (oh + 15) / 16);
|
dispatch(cb, *input->pipeline, b.input[cur], (ow + 15) / 16, (oh + 15) / 16);
|
||||||
step(); stamp(1); pass();
|
step(); stamp(1); pass();
|
||||||
for (unsigned s = 0; s < ENet; ++s) {
|
for (unsigned s = 0; s < ENet; ++s) {
|
||||||
@@ -467,6 +568,8 @@ struct EEngine::Impl {
|
|||||||
}
|
}
|
||||||
stamp(2);
|
stamp(2);
|
||||||
dispatch(cb, *final->pipeline, b.final[cur], finalGrid[0], finalGrid[1]);
|
dispatch(cb, *final->pipeline, b.final[cur], finalGrid[0], finalGrid[1]);
|
||||||
|
// the next frame's previous exposure (the input kernel read this frame's before the network)
|
||||||
|
if (b.copy) dispatch(cb, model.exposureCopy, b.copy, 1, 1);
|
||||||
memoryBarrier(cb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
|
memoryBarrier(cb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT);
|
VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT);
|
||||||
stamp(3); pass();
|
stamp(3); pass();
|
||||||
@@ -474,8 +577,9 @@ struct EEngine::Impl {
|
|||||||
++frame;
|
++frame;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
EEngine::EEngine(EModel& model, uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight, bool hdr, bool lowResMotion)
|
EEngine::EEngine(EModel& model, uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight, bool hdr,
|
||||||
: impl(std::make_unique<Impl>(*model.impl, renderWidth, renderHeight, outputWidth, outputHeight, hdr, lowResMotion)) {
|
bool lowResMotion, bool autoExposure)
|
||||||
|
: impl(std::make_unique<Impl>(*model.impl, renderWidth, renderHeight, outputWidth, outputHeight, hdr, lowResMotion, autoExposure)) {
|
||||||
if (!model.impl->uploaded) {} // the caller records the model's upload before the first frame
|
if (!model.impl->uploaded) {} // the caller records the model's upload before the first frame
|
||||||
}
|
}
|
||||||
EEngine::~EEngine() = default;
|
EEngine::~EEngine() = default;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#version 460
|
||||||
|
// d4r engine, presets E and F: NGX's cuda_copy_exposure_kernel. After each frame the frame's exposure (the game's
|
||||||
|
// 1x1 texture or the AutoExposure measurement) goes into a 1x1 half surface, which the next frame's input kernel
|
||||||
|
// samples as the previous exposure (its texture 352; on a reset frame NGX binds the current exposure there instead,
|
||||||
|
// so the engine copies before the input kernel too). The formatted half store truncates toward zero, like the
|
||||||
|
// auto-exposure copy (engine/exposure_k.comp).
|
||||||
|
layout(local_size_x = 32) in; // one full wave32 subgroup; invocation 0 stores
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D source;
|
||||||
|
layout(set = 0, binding = 1, r16f) uniform writeonly image2D previous;
|
||||||
|
float truncatedHalf(float v)
|
||||||
|
{
|
||||||
|
uint bits = packHalf2x16(vec2(v, 0.0)) & 65535u;
|
||||||
|
if (unpackHalf2x16(bits).x > v) --bits;
|
||||||
|
return unpackHalf2x16(bits).x;
|
||||||
|
}
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
if (gl_LocalInvocationID.x == 0u)
|
||||||
|
imageStore(previous, ivec2(0), vec4(truncatedHalf(texelFetch(source, ivec2(0), 0).r)));
|
||||||
|
}
|
||||||
@@ -2012,12 +2012,25 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
else if (temporal)
|
else if (temporal)
|
||||||
d4r_ngx_set_int(parameters, "Reset", forceReset || frame == 1 ? 1 : 0);
|
d4r_ngx_set_int(parameters, "Reset", forceReset || frame == 1 ? 1 : 0);
|
||||||
// D4R_HARNESS_PRE_EXPOSURE / D4R_HARNESS_EXPOSURE_SCALE: non-neutral values on every frame, as games
|
// D4R_HARNESS_PRE_EXPOSURE / D4R_HARNESS_EXPOSURE_SCALE: non-neutral values, as games with their own
|
||||||
// with their own exposure pipeline pass them.
|
// exposure pipeline pass them. A comma list gives one value per frame (the last repeats).
|
||||||
|
auto scheduled = [frame](const char* list) {
|
||||||
|
float value = 1.0f;
|
||||||
|
const char* cursor = list;
|
||||||
|
for (int index = 1; index <= frame; ++index)
|
||||||
|
{
|
||||||
|
char* end = nullptr;
|
||||||
|
value = std::strtof(cursor, &end);
|
||||||
|
if (*end != ',')
|
||||||
|
break;
|
||||||
|
cursor = end + 1;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
if (const char* value = std::getenv("D4R_HARNESS_PRE_EXPOSURE"))
|
if (const char* value = std::getenv("D4R_HARNESS_PRE_EXPOSURE"))
|
||||||
d4r_ngx_set_float(parameters, "DLSS.Pre.Exposure", static_cast<float>(std::atof(value)));
|
d4r_ngx_set_float(parameters, "DLSS.Pre.Exposure", scheduled(value));
|
||||||
if (const char* value = std::getenv("D4R_HARNESS_EXPOSURE_SCALE"))
|
if (const char* value = std::getenv("D4R_HARNESS_EXPOSURE_SCALE"))
|
||||||
d4r_ngx_set_float(parameters, "DLSS.Exposure.Scale", static_cast<float>(std::atof(value)));
|
d4r_ngx_set_float(parameters, "DLSS.Exposure.Scale", scheduled(value));
|
||||||
// D4R_HARNESS_NGX_FLOATS="Name=value,Name=value": float evaluation parameters set on every frame after the
|
// D4R_HARNESS_NGX_FLOATS="Name=value,Name=value": float evaluation parameters set on every frame after the
|
||||||
// defaults (e.g. MV.Scale.X=2,FrameTimeDeltaInMsec=8), to see how NGX passes them on.
|
// defaults (e.g. MV.Scale.X=2,FrameTimeDeltaInMsec=8), to see how NGX passes them on.
|
||||||
if (const char* list = std::getenv("D4R_HARNESS_NGX_FLOATS"))
|
if (const char* list = std::getenv("D4R_HARNESS_NGX_FLOATS"))
|
||||||
|
|||||||
@@ -10332,8 +10332,14 @@ static bool engine_evaluate_e(Feature& feature, ID3D12GraphicsCommandList* list,
|
|||||||
return refuse(slot ? "no preset F model (engine\\f)" : "no preset E model (engine\\e)");
|
return refuse(slot ? "no preset F model (engine\\f)" : "no preset E model (engine\\e)");
|
||||||
// NVSDK_NGX_DLSS_Feature_Flags: IsHDR 1, MVLowRes 2, MVJittered 4, DepthInverted 8, AutoExposure 0x40.
|
// NVSDK_NGX_DLSS_Feature_Flags: IsHDR 1, MVLowRes 2, MVJittered 4, DepthInverted 8, AutoExposure 0x40.
|
||||||
const bool hdr = (feature.flags & 0x1) != 0, lowRes = (feature.flags & 0x2) != 0;
|
const bool hdr = (feature.flags & 0x1) != 0, lowRes = (feature.flags & 0x2) != 0;
|
||||||
if ((feature.flags & 0x40) != 0)
|
// AutoExposure: NGX measures the exposure from the colour (HDR only; an LDR feature has no exposure at all)
|
||||||
return refuse("auto exposure is not implemented for preset E/F");
|
const bool autoExposure = hdr && (feature.flags & 0x40) != 0;
|
||||||
|
if (autoExposure && !model->hasAutoExposure())
|
||||||
|
return refuse("auto exposure needs a model built by this d4r version (run d4r/tools/build-engine-models.sh)");
|
||||||
|
if (hdr && !(std::isfinite(p.preExposure) && p.preExposure > 0.0f))
|
||||||
|
return refuse("the pre-exposure is not a positive number");
|
||||||
|
if (hdr && !(std::isfinite(p.exposureScale) && p.exposureScale > 0.0f))
|
||||||
|
return refuse("the exposure scale is not a positive number");
|
||||||
if (color == output || depth == output || motion == output)
|
if (color == output || depth == output || motion == output)
|
||||||
return refuse("input/output texture aliasing is not supported");
|
return refuse("input/output texture aliasing is not supported");
|
||||||
if (p.renderWidth > feature.outWidth || p.renderHeight > feature.outHeight)
|
if (p.renderWidth > feature.outWidth || p.renderHeight > feature.outHeight)
|
||||||
@@ -10344,8 +10350,9 @@ static bool engine_evaluate_e(Feature& feature, ID3D12GraphicsCommandList* list,
|
|||||||
const auto depthState = static_cast<D3D12_RESOURCE_STATES>(env_uint("D4R_SHIM_DEPTH_STATE", 0x40));
|
const auto depthState = static_cast<D3D12_RESOURCE_STATES>(env_uint("D4R_SHIM_DEPTH_STATE", 0x40));
|
||||||
const auto outputState = static_cast<D3D12_RESOURCE_STATES>(env_uint("D4R_SHIM_OUTPUT_STATE", 0x8));
|
const auto outputState = static_cast<D3D12_RESOURCE_STATES>(env_uint("D4R_SHIM_OUTPUT_STATE", 0x8));
|
||||||
const uint32_t mw = lowRes ? p.renderWidth : feature.outWidth, mh = lowRes ? p.renderHeight : feature.outHeight;
|
const uint32_t mw = lowRes ? p.renderWidth : feature.outWidth, mh = lowRes ? p.renderHeight : feature.outHeight;
|
||||||
// HDR: the game's exposure texture is NGX's exposure (its copy kernel moves texel 0 into the 1x1 the kernels read)
|
// HDR: the game's exposure texture is NGX's exposure (its copy kernel moves texel 0 into the 1x1 the kernels read);
|
||||||
const bool gameExposure = hdr && exposure != nullptr;
|
// with AutoExposure NGX measures instead and does not read it
|
||||||
|
const bool gameExposure = hdr && !autoExposure && exposure != nullptr;
|
||||||
Source sources[5] = {
|
Source sources[5] = {
|
||||||
{color, &game.color, p.colorBaseX, p.colorBaseY, p.renderWidth, p.renderHeight, inputState, D3D12_RESOURCE_STATE_COPY_SOURCE},
|
{color, &game.color, p.colorBaseX, p.colorBaseY, p.renderWidth, p.renderHeight, inputState, D3D12_RESOURCE_STATE_COPY_SOURCE},
|
||||||
{depth, &game.depth, p.depthBaseX, p.depthBaseY, p.renderWidth, p.renderHeight, depthState, D3D12_RESOURCE_STATE_COPY_SOURCE},
|
{depth, &game.depth, p.depthBaseX, p.depthBaseY, p.renderWidth, p.renderHeight, depthState, D3D12_RESOURCE_STATE_COPY_SOURCE},
|
||||||
@@ -10390,6 +10397,11 @@ static bool engine_evaluate_e(Feature& feature, ID3D12GraphicsCommandList* list,
|
|||||||
game.jitter[1] = p.jitterY;
|
game.jitter[1] = p.jitterY;
|
||||||
game.motionScale[0] = p.mvScaleX;
|
game.motionScale[0] = p.mvScaleX;
|
||||||
game.motionScale[1] = p.mvScaleY;
|
game.motionScale[1] = p.mvScaleY;
|
||||||
|
if (hdr)
|
||||||
|
{
|
||||||
|
game.preExposure = p.preExposure;
|
||||||
|
game.exposureScale = p.exposureScale;
|
||||||
|
}
|
||||||
const bool resized = feature.engineE != nullptr &&
|
const bool resized = feature.engineE != nullptr &&
|
||||||
(p.renderWidth != feature.engineERenderWidth || p.renderHeight != feature.engineERenderHeight);
|
(p.renderWidth != feature.engineERenderWidth || p.renderHeight != feature.engineERenderHeight);
|
||||||
game.reset = p.reset != 0 || feature.engineE == nullptr || resized;
|
game.reset = p.reset != 0 || feature.engineE == nullptr || resized;
|
||||||
@@ -10399,12 +10411,13 @@ static bool engine_evaluate_e(Feature& feature, ID3D12GraphicsCommandList* list,
|
|||||||
{
|
{
|
||||||
feature.engineE.reset();
|
feature.engineE.reset();
|
||||||
feature.engineE = std::make_unique<d4r::GameUpscalerE>(*model, g_vk.physical, g_vk.device, feature.outWidth,
|
feature.engineE = std::make_unique<d4r::GameUpscalerE>(*model, g_vk.physical, g_vk.device, feature.outWidth,
|
||||||
feature.outHeight, p.renderWidth, p.renderHeight, hdr, lowRes);
|
feature.outHeight, p.renderWidth, p.renderHeight, hdr, lowRes,
|
||||||
|
autoExposure);
|
||||||
feature.engineERenderWidth = p.renderWidth;
|
feature.engineERenderWidth = p.renderWidth;
|
||||||
feature.engineERenderHeight = p.renderHeight;
|
feature.engineERenderHeight = p.renderHeight;
|
||||||
logf("engine backend (%s): feature %u runs preset %s in the game's command list (%ux%u -> %ux%u, %s, %s motion)",
|
logf("engine backend (%s): feature %u runs preset %s in the game's command list (%ux%u -> %ux%u, %s, %s motion%s)",
|
||||||
name, feature.handle.Id, name, p.renderWidth, p.renderHeight, feature.outWidth, feature.outHeight,
|
name, feature.handle.Id, name, p.renderWidth, p.renderHeight, feature.outWidth, feature.outHeight,
|
||||||
hdr ? "HDR" : "LDR", lowRes ? "render-size" : "output-size");
|
hdr ? "HDR" : "LDR", lowRes ? "render-size" : "output-size", autoExposure ? ", auto exposure" : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception& error)
|
catch (const std::exception& error)
|
||||||
|
|||||||
Reference in new issue
Block a user