Files
d4r/engine/game.cpp
T
ayo 7c5d3c9157 feat: add d4r inference engines and Windows parity tooling
Add native K/M/RR inference engines, HIP kernels, replay and validation
harnesses, and Windows DLSS setup and runtime support. Include GPU
profiling tools, build and packaging scripts, platform integration
patches, and implementation documentation.
2026-10-16 08:54:47 -04:00

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