A standalone Vulkan inference engine (engine/) for DLSS presets K and M that runs from weights extracted from the user's own nvngx_dlss.dll, with d4r's own input, network and reconstruction shaders or the native HIP network layers, recorded into the game's command list by the shim. Opt-in ([Engine] Enabled / D4R_ENGINE=1); anything uncovered falls back to the CUDA/ZLUDA path with the reason logged. Byte-identical to NGX's CUDA backend under the phase-capable reference (1706x960 -> 2560x1440, native HIP network, eleven frames, RX 9070 XT): - K render- and display-resolution motion vectors, HDR and LDR - K game-supplied exposure and automatic exposure with any pre-exposure and exposure scale (one residual: effective exposure exactly 2 or 4) - K and M with the DLSS indicator's inverted-axis parameters - M with any exposure settings (NGX ignores them for M) K packages need the current compile_k.py (marker D4RO0003). Also: HIP shared buffers are pooled across feature recreation (ROCm leaks destroyed imports), hipnet.bin must match the preset, M-only games do not load the K model, install_d4r_runtime.sh stages k, k-ldr, m, m-ldr with hip/, the activation table has a generator (gen_activation_m.py), and package_release.sh can bundle the engine's shaders (never its weights). Signed-off-by: woutermans <[email protected]>
140 lines
8.9 KiB
C++
140 lines
8.9 KiB
C++
#pragma once
|
|
#include "game.h"
|
|
#include "vk_dispatch.h"
|
|
#include <stdexcept>
|
|
|
|
namespace d4r::game_detail {
|
|
inline void check(VkResult result, const char* operation) {
|
|
if (result != VK_SUCCESS) throw std::runtime_error(std::string(operation) + ": VkResult " + std::to_string(result));
|
|
}
|
|
inline uint32_t memoryType(VkPhysicalDevice physical, uint32_t bits) {
|
|
VkPhysicalDeviceMemoryProperties memory;
|
|
vkGetPhysicalDeviceMemoryProperties(physical, &memory);
|
|
for (uint32_t i = 0; i < memory.memoryTypeCount; ++i)
|
|
if ((bits & (1u << i)) && (memory.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) return i;
|
|
throw std::runtime_error("no device-local Vulkan memory type");
|
|
}
|
|
// Canonical fallback plane; direct sampled inputs do not use it.
|
|
struct Plane {
|
|
VkDevice device = VK_NULL_HANDLE;
|
|
VkImage image = VK_NULL_HANDLE;
|
|
VkDeviceMemory memory = VK_NULL_HANDLE;
|
|
VkImageView view = VK_NULL_HANDLE;
|
|
~Plane() {
|
|
if (view) vkDestroyImageView(device, view, nullptr);
|
|
if (image) vkDestroyImage(device, image, nullptr);
|
|
if (memory) vkFreeMemory(device, memory, nullptr);
|
|
}
|
|
void create(VkPhysicalDevice physical, VkDevice dev, VkFormat format, uint32_t width, uint32_t height) {
|
|
device = dev;
|
|
VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
|
info.imageType = VK_IMAGE_TYPE_2D; info.format = format; info.extent = {width, height, 1};
|
|
info.mipLevels = info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
check(vkCreateImage(dev, &info, nullptr, &image), "vkCreateImage");
|
|
VkMemoryRequirements requirements; vkGetImageMemoryRequirements(dev, image, &requirements);
|
|
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = memoryType(physical, requirements.memoryTypeBits);
|
|
check(vkAllocateMemory(dev, &allocation, nullptr, &memory), "vkAllocateMemory");
|
|
check(vkBindImageMemory(dev, image, memory, 0), "vkBindImageMemory");
|
|
VkImageViewCreateInfo vi{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
vi.image = image; vi.viewType = VK_IMAGE_VIEW_TYPE_2D; vi.format = format;
|
|
vi.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
check(vkCreateImageView(dev, &vi, nullptr, &view), "vkCreateImageView");
|
|
}
|
|
};
|
|
inline void barrier(VkCommandBuffer cb, VkPipelineStageFlags source, VkPipelineStageFlags destination, VkAccessFlags src, VkAccessFlags dst) {
|
|
VkMemoryBarrier b{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
|
b.srcAccessMask = src; b.dstAccessMask = dst;
|
|
vkCmdPipelineBarrier(cb, source, destination, 0, 1, &b, 0, nullptr, 0, nullptr);
|
|
}
|
|
inline bool directOutput(const GameImage& output, const GameImage& color, const GameImage& motion, const GameImage& depth, bool rgb10) {
|
|
return output.view != VK_NULL_HANDLE && (output.format == VK_FORMAT_R16G16B16A16_SFLOAT ||
|
|
(rgb10 && output.format == VK_FORMAT_A2B10G10R10_UNORM_PACK32)) &&
|
|
output.layout == VK_IMAGE_LAYOUT_GENERAL && output.image != color.image &&
|
|
output.image != motion.image && output.image != depth.image;
|
|
}
|
|
inline void copyInput(VkCommandBuffer cb, const GameImage& source, VkImage destination, uint32_t width, uint32_t height,
|
|
VkBuffer depthStaging = VK_NULL_HANDLE) {
|
|
if (source.aspect & VK_IMAGE_ASPECT_DEPTH_BIT) {
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1};
|
|
region.imageOffset = {int32_t(source.x), int32_t(source.y), 0}; region.imageExtent = {width, height, 1};
|
|
vkCmdCopyImageToBuffer(cb, source.image, source.layout, depthStaging, 1, ®ion);
|
|
barrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageOffset = {0, 0, 0};
|
|
vkCmdCopyBufferToImage(cb, depthStaging, destination, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
|
} else {
|
|
VkImageBlit region{};
|
|
region.srcSubresource = region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
region.srcOffsets[0] = {int32_t(source.x), int32_t(source.y), 0};
|
|
region.srcOffsets[1] = {int32_t(source.x + width), int32_t(source.y + height), 1};
|
|
region.dstOffsets[1] = {int32_t(width), int32_t(height), 1};
|
|
vkCmdBlitImage(cb, source.image, source.layout, destination, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion, VK_FILTER_NEAREST);
|
|
}
|
|
}
|
|
// Both adapters allocate conversion resources only when an input cannot be borrowed.
|
|
struct CanonicalInputs {
|
|
VkPhysicalDevice physical;
|
|
VkDevice device;
|
|
uint32_t width, height, motionWidth, motionHeight;
|
|
Plane color, motion, depth;
|
|
VkBuffer depthStaging = VK_NULL_HANDLE;
|
|
VkDeviceMemory depthMemory = VK_NULL_HANDLE;
|
|
CanonicalInputs(VkPhysicalDevice p, VkDevice d, uint32_t w, uint32_t h, uint32_t mw, uint32_t mh)
|
|
: physical(p), device(d), width(w), height(h), motionWidth(mw), motionHeight(mh) {}
|
|
~CanonicalInputs() {
|
|
if (depthStaging) vkDestroyBuffer(device, depthStaging, nullptr);
|
|
if (depthMemory) vkFreeMemory(device, depthMemory, nullptr);
|
|
}
|
|
void ensure(VkCommandBuffer cb, Plane& plane, VkFormat format, uint32_t w, uint32_t h) {
|
|
if (plane.image) return;
|
|
plane.create(physical, device, format, w, h);
|
|
VkImageMemoryBarrier layout{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
|
layout.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
layout.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; layout.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
layout.srcQueueFamilyIndex = layout.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
layout.image = plane.image; layout.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
0, 0, nullptr, 0, nullptr, 1, &layout);
|
|
}
|
|
void record(VkCommandBuffer cb, const GameImage& col, const GameImage& mv, const GameImage& dep,
|
|
uint32_t w, uint32_t h, uint32_t mw, uint32_t mh, bool directColor, bool directMotion, bool directDepth) {
|
|
if (directColor && directMotion && directDepth) return;
|
|
if (!directColor) ensure(cb, color, VK_FORMAT_R16G16B16A16_SFLOAT, width, height);
|
|
if (!directMotion) ensure(cb, motion, VK_FORMAT_R16G16_SFLOAT, motionWidth, motionHeight);
|
|
if (!directDepth) {
|
|
ensure(cb, depth, VK_FORMAT_R32_SFLOAT, width, height);
|
|
if ((dep.aspect & VK_IMAGE_ASPECT_DEPTH_BIT) && !depthStaging) {
|
|
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
|
info.size = VkDeviceSize(width) * height * 4;
|
|
info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
check(vkCreateBuffer(device, &info, nullptr, &depthStaging), "vkCreateBuffer");
|
|
VkMemoryRequirements requirements; vkGetBufferMemoryRequirements(device, depthStaging, &requirements);
|
|
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
allocation.allocationSize = requirements.size;
|
|
allocation.memoryTypeIndex = memoryType(physical, requirements.memoryTypeBits);
|
|
check(vkAllocateMemory(device, &allocation, nullptr, &depthMemory), "vkAllocateMemory");
|
|
check(vkBindBufferMemory(device, depthStaging, depthMemory, 0), "vkBindBufferMemory");
|
|
}
|
|
}
|
|
barrier(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT,
|
|
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
|
if (!directColor) copyInput(cb, col, color.image, w, h);
|
|
if (!directMotion) copyInput(cb, mv, motion.image, mw, mh);
|
|
if (!directDepth) copyInput(cb, dep, depth.image, w, h, depthStaging);
|
|
}
|
|
};
|
|
inline void copyOutput(VkCommandBuffer cb, VkImage source, const GameImage& destination, uint32_t width, uint32_t height) {
|
|
VkImageBlit region{};
|
|
region.srcSubresource = region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
region.srcOffsets[1] = {int32_t(width), int32_t(height), 1};
|
|
region.dstOffsets[0] = {int32_t(destination.x), int32_t(destination.y), 0};
|
|
region.dstOffsets[1] = {int32_t(destination.x + width), int32_t(destination.y + height), 1};
|
|
vkCmdBlitImage(cb, source, VK_IMAGE_LAYOUT_GENERAL, destination.image, destination.layout, 1, ®ion, VK_FILTER_NEAREST);
|
|
barrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT);
|
|
}
|
|
} // namespace d4r::game_detail
|