- engine/rr: d4r's own shaders for the denoiser frame (exposure, six encoders,
five decoders, hkpn output stage) at NGX layouts, plus PTX->GLSL/HIP packing
scripts, reference implementations and weight extraction
- runtime.h/runtime_rr.inc, game_rr.{h,cpp}: GameDenoiser wired into the engine
build (d4r-rr replay binary) and the D3D12 shim's feature-13 path
- shim: answer GetFeatureRequirements from d4r's own state only (the old
cross-binary CUDA handoff crashed OptiScaler's scan); drop the core's
CUDA GetFeatureRequirements import
- shim/harness: exact guide capture/replay of a frame's RR inputs
- kernels/rr: expanded enc0/engine prototypes; kernels/tools: ptxsim rewrite
plus rrsim and an engine test; vulkan: ptx2glsl FP8/cooperative-matrix
support, kvk inout buffers and KVK_DISABLE_OPTIMIZATION
- bridge: report the surface array in cuSurfObjectCreate traces
- packaging: RR settings and the ICU launch overrides in D4R_README
128 lines
7.9 KiB
C++
128 lines
7.9 KiB
C++
#include "game.h"
|
|
#include "game_internal.h"
|
|
|
|
namespace d4r {
|
|
namespace {
|
|
bool depthAspect(VkFormat format) { return format == VK_FORMAT_D32_SFLOAT || format == VK_FORMAT_D32_SFLOAT_S8_UINT; }
|
|
} // namespace
|
|
|
|
bool gameColorFormat(VkFormat format) {
|
|
switch (format) {
|
|
case VK_FORMAT_R16G16B16A16_SFLOAT: case VK_FORMAT_R32G32B32A32_SFLOAT: case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
|
|
case VK_FORMAT_A2B10G10R10_UNORM_PACK32: case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_B8G8R8A8_UNORM:
|
|
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: // R9G9B9E5_SHAREDEXP (RE Requiem's colour and output): sampled and blitted
|
|
return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
bool gameMotionFormat(VkFormat format) {
|
|
return format == VK_FORMAT_R16G16_SFLOAT || format == VK_FORMAT_R32G32_SFLOAT ||
|
|
format == VK_FORMAT_R16G16B16A16_SFLOAT || format == VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
}
|
|
bool gameDepthFormat(VkFormat format, VkImageAspectFlags aspect) {
|
|
if (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) return depthAspect(format);
|
|
return format == VK_FORMAT_R32_SFLOAT || format == VK_FORMAT_R16_SFLOAT || format == VK_FORMAT_R16_UNORM;
|
|
}
|
|
bool gameOutputFormat(VkFormat format) { return gameColorFormat(format); }
|
|
|
|
struct GameUpscaler::Impl {
|
|
Model& model;
|
|
Shape shape;
|
|
Engine engine;
|
|
uint32_t maxWidth, maxHeight;
|
|
game_detail::CanonicalInputs fallback;
|
|
bool started = false;
|
|
uint32_t frames = 0;
|
|
Impl(Model& m, VkPhysicalDevice p, VkDevice d, uint32_t ow, uint32_t oh, uint32_t rw, uint32_t rh, const ExternalNetwork* external)
|
|
: model(m), shape(makeKShape(ow, oh)), engine(m, shape, external), maxWidth(rw), maxHeight(rh),
|
|
fallback(p, d, rw, rh, ow, oh) {}
|
|
void inputs(VkCommandBuffer cb, const GameFrame& frame);
|
|
void result(VkCommandBuffer cb, const GameFrame& frame);
|
|
};
|
|
GameUpscaler::GameUpscaler(Model& model, VkPhysicalDevice physical, VkDevice device, uint32_t outputWidth, uint32_t outputHeight,
|
|
uint32_t maxRenderWidth, uint32_t maxRenderHeight, const ExternalNetwork* external)
|
|
: impl(std::make_unique<Impl>(model, physical, device, outputWidth, outputHeight, maxRenderWidth, maxRenderHeight, external)) {
|
|
if (!maxRenderWidth || !maxRenderHeight || maxRenderWidth > outputWidth || maxRenderHeight > outputHeight)
|
|
throw std::runtime_error("render dimensions must be positive and no larger than the output");
|
|
}
|
|
GameUpscaler::~GameUpscaler() = default;
|
|
|
|
// Everything before the network: input conversion, exposure setup and the input stage (and a reset's clears).
|
|
void GameUpscaler::Impl::inputs(VkCommandBuffer cb, const GameFrame& frame) {
|
|
auto& g = *this;
|
|
const uint32_t w = frame.settings.renderWidth, h = frame.settings.renderHeight;
|
|
if (!w || !h || w > g.maxWidth || h > g.maxHeight) throw std::runtime_error("render size exceeds the adapter's inputs");
|
|
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");
|
|
const bool gameExposure = frame.exposure.view != VK_NULL_HANDLE;
|
|
if (gameExposure && frame.settings.autoExposure) throw std::runtime_error("game exposure and automatic exposure are exclusive");
|
|
// Validates the settings before anything is recorded.
|
|
FrameParams parameters = makeKFrameParams(g.shape, frame.settings);
|
|
const bool directColor = frame.color.view != VK_NULL_HANDLE;
|
|
const bool directMotion = frame.motion.view != VK_NULL_HANDLE;
|
|
const bool directDepth = frame.depth.view != VK_NULL_HANDLE;
|
|
const bool directOutput = game_detail::directOutput(frame.output, frame.color, frame.motion, frame.depth, g.model.hasRgb10Output());
|
|
auto origin = [&](const GameImage& image, bool direct, int32_t* inOrigin, int32_t* outOrigin, int32_t* outMax) {
|
|
if (!direct) return;
|
|
if ((image.x || image.y) && !g.model.hasDirectOrigins())
|
|
throw std::runtime_error("recompile the model for direct texture rectangles");
|
|
if (uint64_t(image.x) + w > INT32_MAX || uint64_t(image.y) + h > INT32_MAX)
|
|
throw std::runtime_error("texture rectangle exceeds shader coordinate range");
|
|
inOrigin[0] = outOrigin[0] = int32_t(image.x); inOrigin[1] = outOrigin[1] = int32_t(image.y);
|
|
outMax[0] += int32_t(image.x); outMax[1] += int32_t(image.y);
|
|
};
|
|
origin(frame.color, directColor, parameters.input.colOrigin, parameters.output.colOrigin, parameters.output.colMax);
|
|
origin(frame.motion, directMotion, parameters.input.mvOrigin, parameters.output.mvOrigin, parameters.output.mvMax);
|
|
origin(frame.depth, directDepth, parameters.input.depOrigin, parameters.output.depOrigin, parameters.output.depMax);
|
|
if (directOutput) {
|
|
if (uint64_t(frame.output.x) + g.shape.outputWidth > INT32_MAX || uint64_t(frame.output.y) + g.shape.outputHeight > INT32_MAX)
|
|
throw std::runtime_error("output rectangle exceeds shader coordinate range");
|
|
parameters.output.outOrigin[0] = int32_t(frame.output.x); parameters.output.outOrigin[1] = int32_t(frame.output.y);
|
|
}
|
|
if (!g.started) {
|
|
if (!g.model.uploadRecorded()) g.model.recordUpload(cb);
|
|
g.engine.recordInitialize(cb);
|
|
g.started = true;
|
|
}
|
|
const uint32_t mw = frame.settings.displayMotion ? g.shape.outputWidth : w, mh = frame.settings.displayMotion ? g.shape.outputHeight : h;
|
|
g.fallback.record(cb, frame.color, frame.motion, frame.depth, w, h, mw, mh, directColor, directMotion, directDepth);
|
|
ExposureControl exposure;
|
|
exposure.size[0] = int32_t(w); exposure.size[1] = int32_t(h);
|
|
if (directColor) { exposure.origin[0] = int32_t(frame.color.x); exposure.origin[1] = int32_t(frame.color.y); }
|
|
exposure.game = gameExposure;
|
|
exposure.measure = !gameExposure && (g.frames % 2 == 0 || frame.settings.reset);
|
|
exposure.reset = frame.settings.reset;
|
|
exposure.exposureScale = frame.settings.exposureScale; exposure.preExposure = frame.settings.preExposure;
|
|
// The window schedule follows the evaluation counter, which advances every frame and is not restarted by a
|
|
// reset; the external network's phase counter must advance in lockstep, so this is the only place it moves.
|
|
auto windows = makeKWindows(g.shape, g.frames++);
|
|
FrameImages images{directColor ? frame.color.view : g.fallback.color.view, directMotion ? frame.motion.view : g.fallback.motion.view,
|
|
directDepth ? frame.depth.view : g.fallback.depth.view, directColor ? frame.color.layout : VK_IMAGE_LAYOUT_GENERAL,
|
|
directMotion ? frame.motion.layout : VK_IMAGE_LAYOUT_GENERAL, directDepth ? frame.depth.layout : VK_IMAGE_LAYOUT_GENERAL,
|
|
gameExposure ? frame.exposure.view : VK_NULL_HANDLE, frame.exposure.layout};
|
|
g.engine.setFrame(images, parameters.input, parameters.output, &windows,
|
|
(frame.settings.autoExposure || gameExposure) ? &exposure : nullptr,
|
|
directOutput ? FrameOutput{frame.output.view, frame.output.format} : FrameOutput{});
|
|
}
|
|
// Everything after the network: the copy into the game's output texture when the store was not direct.
|
|
void GameUpscaler::Impl::result(VkCommandBuffer cb, const GameFrame& frame) {
|
|
auto& g = *this;
|
|
if (game_detail::directOutput(frame.output, frame.color, frame.motion, frame.depth, g.model.hasRgb10Output())) return;
|
|
game_detail::copyOutput(cb, g.engine.outputImage(), frame.output, g.shape.outputWidth, g.shape.outputHeight);
|
|
}
|
|
void GameUpscaler::record(VkCommandBuffer cb, const GameFrame& frame) {
|
|
impl->inputs(cb, frame);
|
|
impl->engine.recordFrame(cb); // begins and ends with barriers against all other work
|
|
impl->result(cb, frame);
|
|
}
|
|
void GameUpscaler::recordFront(VkCommandBuffer cb, const GameFrame& frame) {
|
|
impl->inputs(cb, frame);
|
|
impl->engine.recordFront(cb);
|
|
}
|
|
void GameUpscaler::recordBack(VkCommandBuffer cb, const GameFrame& frame) {
|
|
impl->engine.recordBack(cb);
|
|
impl->result(cb, frame);
|
|
}
|
|
} // namespace d4r
|