Compare commits

...

34 commits

Author SHA1 Message Date
PancakeTAS
729c7ca088
more wiki changes 2025-08-13 22:59:05 +02:00
strokesws
4b69c71b10
refactor: replace the build guide link 2025-08-13 22:52:52 +02:00
strokesws
27a2cc761e
refactor: review readme file explanation 2025-08-13 22:52:52 +02:00
PancakeTAS
60baec1e26 feat(exe): fixing processes with space in their name 2025-08-13 20:49:42 +02:00
PancakeTAS
e09d590f2d feat(exe): present exe files in ui 2025-08-13 20:49:42 +02:00
PancakeTAS
7ddf1bdbde feat(exe): update c++ implementation to get exe 2025-08-13 20:49:42 +02:00
PancakeTAS
f5b55c7f83 feat(exe): remove pointless last file 2025-08-13 20:49:42 +02:00
PancakeTAS
938fe930b0 feat(exe): print process name rather than command name 2025-08-13 20:49:42 +02:00
PancakeTAS
62ce71e4d4 feat(exe): update default configuration 2025-08-13 20:49:42 +02:00
Rerence1016
26e4dc5827 feat(exe): fix comments, remove regex dependency 2025-08-13 20:49:42 +02:00
Rerence1016
ec59c072b5 feat(exe): use /proc/self/maps to get exe for ui and core lsfg-vk 2025-08-13 20:49:42 +02:00
Rerence1016
b899324b8c feat(exe): allow specifying games by their exe file 2025-08-13 20:49:42 +02:00
Rerence1016
bee751e16b feat(exe): add regex, find and use exe name for proton/wine processes in ui 2025-08-13 20:49:42 +02:00
PancakeTAS
da16437210
fix(test): ensure building works without renderdoc 2025-08-13 01:49:25 +02:00
PancakeTAS
a71b994d74 feat(fp16): fixup test 2025-08-12 20:30:34 +02:00
PancakeTAS
f5690d741c feat(fp16): add no_fp16 to default configuration 2025-08-12 20:30:34 +02:00
PancakeTAS
7bac21f793 feat(fp16): remove SPIR-V Tools dependency 2025-08-12 20:30:34 +02:00
PancakeTAS
3a86e5ade4 feat(fp16): add no fp16 flag to ui 2025-08-12 20:30:34 +02:00
PancakeTAS
012b18b97c feat(fp16): add flag for overriding fp16 2025-08-12 20:30:34 +02:00
PancakeTAS
73e09afcf4 feat(fp16): adding all shaders 2025-08-12 20:30:34 +02:00
PancakeTAS
28d293d531 feat(fp16): update to use correct index 2025-08-12 20:30:34 +02:00
PancakeTAS
77d1b68b8b feat(fp16): account for new shader bindings 2025-08-12 20:30:34 +02:00
PancakeTAS
6c3571e672 feat(fp16): remove translation remnants 2025-08-12 20:30:34 +02:00
PancakeTAS
cb234bde74 feat(fp16): translate shaders to fp16
this will not work with the steam version, nor is it deployable
2025-08-12 20:30:34 +02:00
PancakeTAS
3fcde7c126 feat(fp16): passthrough fp16 to shader pool 2025-08-12 20:30:34 +02:00
PancakeTAS
b93a4eeaf2 feat(fp16): fetch for fp16 support 2025-08-12 20:30:34 +02:00
PancakeTAS
b34dd6ddaa feat(test): make tests work 2025-08-11 19:33:04 +02:00
PancakeTAS
fd6f611dd0 feat(test): fixup test 2025-08-11 19:33:04 +02:00
PancakeTAS
405733d50e feat(test): write main test 2025-08-11 19:33:04 +02:00
PancakeTAS
a90593fdc1 feat(test): move util back to framegen 2025-08-11 19:33:04 +02:00
PancakeTAS
883e8cf875 feat(test): create test boilerplate 2025-08-11 19:33:04 +02:00
PancakeTAS
5b2e9d81e4 feat(test): move image uploading util 2025-08-11 19:33:04 +02:00
PancakeTAS
219083dcd5 feat(test): create file structure for tests 2025-08-11 19:33:04 +02:00
PancakeTAS
2f488a42ef
workflows: allow manual dispatch 2025-08-07 19:07:09 +02:00
42 changed files with 779 additions and 258 deletions

View file

@ -3,6 +3,7 @@ name: Build lsfg-vk
on:
push:
branches: ["release"]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always

View file

@ -5,7 +5,6 @@ on:
workflows: ["Build lsfg-vk"]
types:
- completed
branches: ["release"]
jobs:
package:

3
.gitmodules vendored
View file

@ -1,9 +1,6 @@
[submodule "thirdparty/pe-parse"]
path = thirdparty/pe-parse
url = https://github.com/trailofbits/pe-parse
[submodule "thirdparty/dxbc"]
path = thirdparty/dxbc
url = https://github.com/PancakeTAS/dxbc.git
[submodule "thirdparty/toml11"]
path = thirdparty/toml11
url = https://github.com/ToruNiina/toml11

View file

@ -1,20 +1,26 @@
cmake_minimum_required(VERSION 3.10)
set(CMAKE_SKIP_RPATH ON)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
if(NOT LSFGVK_EXCESS_DEBUG)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
endif()
# subprojects
add_compile_options(-fPIC
-Wno-deprecated-declarations
-Wno-unused-template)
add_subdirectory(thirdparty/dxbc EXCLUDE_FROM_ALL)
add_subdirectory(thirdparty/pe-parse/pe-parser-library EXCLUDE_FROM_ALL)
add_subdirectory(thirdparty/toml11 EXCLUDE_FROM_ALL)
add_subdirectory(thirdparty/volk EXCLUDE_FROM_ALL)
add_subdirectory(framegen)
if(LSFGVK_EXCESS_DEBUG)
add_subdirectory(test)
endif()
# main project
project(lsfg-vk
VERSION 1.0.0
@ -36,9 +42,9 @@ set_target_properties(lsfg-vk PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON)
target_include_directories(lsfg-vk
PRIVATE include)
target_link_libraries(lsfg-vk PRIVATE
pe-parse dxbc toml11
PUBLIC include)
target_link_libraries(lsfg-vk PUBLIC
pe-parse toml11
lsfg-vk-framegen)
get_target_property(TOML11_INCLUDE_DIRS toml11 INTERFACE_INCLUDE_DIRECTORIES)

View file

@ -1,23 +1,46 @@
# lsfg-vk
Lossless Scaling is a Windows-exclusive app with the goal of bringing frame generation (among other features) to every single game or app.
lsfg-vk brings this frame generation to Linux users by acting as a Vulkan layer inbetween your game and your graphics card.
Lossless Scaling is a Windows-exclusive app bringing frame generation (among other features) to every single game or app.
## Installation
**lsfg-vk** brings this frame generation to Linux users by acting as a Vulkan layer inbetween your game and your graphics card.
lsfg-vk has been prebuilt to run on a variety of Linux distributions:
- Click [here](https://github.com/PancakeTAS/lsfg-vk/releases) to download lsfg-vk for your distribution
- Follow [this guide](https://github.com/PancakeTAS/lsfg-vk/wiki/Installation-Guide) if you need any more help.
## Getting Started
Once installed, open up the lsfg-vk Configuration Window from your application menu or by typing `lsfg-vk-ui` into the console.
For comprehensive instructions on setting up, configuring, and using **lsfg-vk**, please refer to the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki).
Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for more information and join the [Discord](https://discord.gg/losslessscaling) for help (needs Steam verification).
### Installation
**lsfg-vk** is available as pre-built packages for various Linux distributions.
1. Visit the [Releases page](https://github.com/PancakeTAS/lsfg-vk/releases) to download the package for your Linux distribution.
2. Follow the step-by-step instructions in the [Installation Guide](https://github.com/PancakeTAS/lsfg-vk/wiki/Installation-Guide) for your specific distro.
### Configuration and Usage
After installation, you can open the graphical configuration editor **lsfg-vk-ui** from your application menu or by typing `lsfg-vk-ui` in your console.
* For detailed instructions on setting up your preferences, visit the [Configuring lsfg-vk](https://github.com/PancakeTAS/lsfg-vk/wiki/Configuring-lsfg%E2%80%90vk) page.
* Learn how to use **lsfg-vk**'s integrated benchmark on the [Using lsfg-vk's integrated benchmark](https://github.com/PancakeTAS/lsfg-vk/wiki/Using-lsfg%E2%80%90vk's-integrated-benchmark) page.
## Building from Source
If you prefer to build **lsfg-vk** yourself for development, debugging, or to use the latest and greatest features, a detailed build guide is available in the [Build Guide](https://github.com/PancakeTAS/lsfg-vk/wiki/Building-from-source) page.
## Support and Troubleshooting
If you encounter any issues or need assistance, please consult the following resources:
* **Quirks:** Before reporting any issues refer to the [Quirks](https://github.com/PancakeTAS/lsfg-vk/wiki/How-to-ask-for-help) page.
* **How to Ask for Help:** When creating a report, refer to the [How to ask for help](https://github.com/PancakeTAS/lsfg-vk/wiki/How-to-ask-for-help) page.
* **Discord:** Join the [Lossless Scaling Discord server](https://discord.gg/losslessscaling) for help (Steam verification required).
## Credits
Most of the project has still only been written by me, PancakeTAS, but I couldn't have done it without the help of these people:
- [0xNULLderef](https://github.com/0xNULLderef): Teaching me how to reverse engineer software.
- [Caliel666](https://github.com/Caliel666): Writing the initial draft of the user interface.
- [Samueru-sama](https://github.com/Samueru-sama): Helping with various things XDG as well as app images and testing.
- Other contributors: Thank you for your contribution!
* **0xNULLderef:** Teaching me how to reverse engineer software.
* **Caliel666:** Writing the initial draft of the user interface.
* **Samueru-sama:** Helping with various things XDG as well as app images and testing.
* Other contributors: Thank you for your contributions!
I'd also like to thank every single person sponsoring this project. Thanks to you I'll be able to invest more time into this and hopefully bring some cool new features to everyone.

View file

@ -1,7 +1,13 @@
cmake_minimum_required(VERSION 3.10)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
if(NOT LSFGVK_EXCESS_DEBUG)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
endif()
if(LSFGVK_EXCESS_DEBUG)
add_compile_definitions(LSFGVK_EXCESS_DEBUG)
endif()
project(lsfg-vk-framegen
DESCRIPTION "Lossless Scaling Frame Generation Backend"

View file

@ -119,6 +119,10 @@ namespace LSFG::Core {
: descriptorSet(&descriptorSet), device(&device) {}
std::vector<VkWriteDescriptorSet> entries;
size_t bufferIdx{0};
size_t samplerIdx{16};
size_t inputIdx{32};
size_t outputIdx{48};
};
}

View file

@ -21,10 +21,11 @@ namespace LSFG::Core {
///
/// @param instance Vulkan instance
/// @param deviceUUID The UUID of the Vulkan device to use.
/// @param forceDisableFp16 Force-disable FP16 shaders.
///
/// @throws LSFG::vulkan_error if object creation fails.
///
Device(const Instance& instance, uint64_t deviceUUID);
Device(const Instance& instance, uint64_t deviceUUID, bool forceDisableFp16);
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->device; }
@ -34,6 +35,8 @@ namespace LSFG::Core {
[[nodiscard]] uint32_t getComputeFamilyIdx() const { return this->computeFamilyIdx; }
/// Get the compute queue.
[[nodiscard]] VkQueue getComputeQueue() const { return this->computeQueue; }
/// Check if the device supports FP16.
[[nodiscard]] bool getFP16Support() const { return this->supportsFP16; }
// Trivially copyable, moveable and destructible
Device(const Core::Device&) noexcept = default;
@ -46,6 +49,7 @@ namespace LSFG::Core {
VkPhysicalDevice physicalDevice{};
uint32_t computeFamilyIdx{0};
bool supportsFP16{false};
VkQueue computeQueue{};
};

View file

@ -48,6 +48,21 @@ namespace LSFG::Core {
Image(const Core::Device& device, VkExtent2D extent, VkFormat format,
VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int fd);
///
/// Create the image and export the backing fd
///
/// @param device Vulkan device
/// @param extent Extent of the image in pixels.
/// @param format Vulkan format of the image
/// @param usage Usage flags for the image
/// @param aspectFlags Aspect flags for the image view
/// @param fd Pointer to an integer where the file descriptor will be stored.
///
/// @throws LSFG::vulkan_error if object creation fails.
///
Image(const Core::Device& device, VkExtent2D extent, VkFormat format,
VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd);
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->image; }
/// Get the Vulkan device memory handle.

View file

@ -27,11 +27,14 @@ namespace LSFG::Pool {
/// Create the shader pool.
///
/// @param source Function to retrieve shader source code by name.
/// @param fp16 If true, use the FP16 variant of shaders.
///
/// @throws std::runtime_error if the shader pool cannot be created.
///
ShaderPool(const std::function<std::vector<uint8_t>(const std::string&)>& source)
: source(source) {}
ShaderPool(
const std::function<std::vector<uint8_t>(const std::string&, bool)>& source,
bool fp16)
: source(source), fp16(fp16) {}
///
/// Retrieve a shader module by name or create it.
@ -57,7 +60,9 @@ namespace LSFG::Pool {
Core::Pipeline getPipeline(
const Core::Device& device, const std::string& name);
private:
std::function<std::vector<uint8_t>(const std::string&)> source;
std::function<std::vector<uint8_t>(const std::string&, bool)> source;
bool fp16{false};
std::unordered_map<std::string, Core::ShaderModule> shaders;
std::unordered_map<std::string, Core::Pipeline> pipelines;
};

View file

@ -16,6 +16,7 @@ namespace LSFG_3_1 {
/// @param isHdr Whether the images are in HDR format.
/// @param flowScale Internal flow scale factor.
/// @param generationCount Number of frames to generate.
/// @param forceDisableFp16 Whether to force-disable FP16 optimizations.
/// @param loader Function to load shader source code by name.
///
/// @throws LSFG::vulkan_error if Vulkan objects fail to initialize.
@ -23,7 +24,18 @@ namespace LSFG_3_1 {
[[gnu::visibility("default")]]
void initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(const std::string&)>& loader);
bool forceDisableFp16,
const std::function<std::vector<uint8_t>(const std::string&, bool)>& loader);
#ifdef LSFGVK_EXCESS_DEBUG
///
/// Initialize the renderdoc API.
///
/// @throws LSFG::vulkan_error if the renderdoc API cannot be initialized.
///
[[gnu::visibility("default")]]
void initializeRenderDoc();
#endif // LSFGVK_EXCESS_DEBUG
///
/// Create a new LSFG context on a swapchain.

View file

@ -16,6 +16,7 @@ namespace LSFG_3_1P {
/// @param isHdr Whether the images are in HDR format.
/// @param flowScale Internal flow scale factor.
/// @param generationCount Number of frames to generate.
/// @param forceDisableFp16 Whether to force-disable FP16 optimizations.
/// @param loader Function to load shader source code by name.
///
/// @throws LSFG::vulkan_error if Vulkan objects fail to initialize.
@ -23,7 +24,18 @@ namespace LSFG_3_1P {
[[gnu::visibility("default")]]
void initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(const std::string&)>& loader);
bool forceDisableFp16,
const std::function<std::vector<uint8_t>(const std::string&, bool)>& loader);
#ifdef LSFGVK_EXCESS_DEBUG
///
/// Initialize the renderdoc API.
///
/// @throws LSFG::vulkan_error if the renderdoc API cannot be initialized.
///
[[gnu::visibility("default")]]
void initializeRenderDoc();
#endif // LSFGVK_EXCESS_DEBUG
///
/// Create a new LSFG context on a swapchain.

View file

@ -12,8 +12,9 @@
#include "core/buffer.hpp"
#include "common/exception.hpp"
#include <memory>
#include <cstddef>
#include <cstdint>
#include <memory>
using namespace LSFG::Core;
@ -55,10 +56,11 @@ void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pip
// updater class
DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Image& image) {
size_t* idx{type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ? &this->outputIdx : &this->inputIdx};
this->entries.push_back({
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = this->descriptorSet->handle(),
.dstBinding = static_cast<uint32_t>(this->entries.size()),
.dstBinding = static_cast<uint32_t>(*idx),
.descriptorCount = 1,
.descriptorType = type,
.pImageInfo = new VkDescriptorImageInfo {
@ -67,6 +69,7 @@ DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType typ
},
.pBufferInfo = nullptr
});
(*idx)++;
return *this;
}
@ -74,7 +77,7 @@ DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType typ
this->entries.push_back({
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = this->descriptorSet->handle(),
.dstBinding = static_cast<uint32_t>(this->entries.size()),
.dstBinding = static_cast<uint32_t>(this->samplerIdx++),
.descriptorCount = 1,
.descriptorType = type,
.pImageInfo = new VkDescriptorImageInfo {
@ -89,7 +92,7 @@ DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType typ
this->entries.push_back({
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = this->descriptorSet->handle(),
.dstBinding = static_cast<uint32_t>(this->entries.size()),
.dstBinding = static_cast<uint32_t>(this->bufferIdx++),
.descriptorCount = 1,
.descriptorType = type,
.pImageInfo = nullptr,
@ -102,16 +105,34 @@ DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType typ
}
DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type) {
size_t* idx{};
switch (type) {
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
idx = &this->inputIdx;
break;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
idx = &this->outputIdx;
break;
case VK_DESCRIPTOR_TYPE_SAMPLER:
idx = &this->samplerIdx;
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
idx = &this->bufferIdx;
break;
default:
throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unsupported descriptor type");
}
this->entries.push_back({
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = this->descriptorSet->handle(),
.dstBinding = static_cast<uint32_t>(this->entries.size()),
.dstBinding = static_cast<uint32_t>(*idx),
.descriptorCount = 1,
.descriptorType = type,
.pImageInfo = new VkDescriptorImageInfo {
},
.pBufferInfo = nullptr
});
(*idx)++;
return *this;
}

View file

@ -1,3 +1,4 @@
#include <iostream>
#include <volk.h>
#include <vulkan/vulkan_core.h>
@ -15,10 +16,10 @@ using namespace LSFG::Core;
const std::vector<const char*> requiredExtensions = {
"VK_KHR_external_memory_fd",
"VK_KHR_external_semaphore_fd",
"VK_EXT_robustness2",
"VK_EXT_robustness2"
};
Device::Device(const Instance& instance, uint64_t deviceUUID) {
Device::Device(const Instance& instance, uint64_t deviceUUID, bool forceDisableFp16) {
// get all physical devices
uint32_t deviceCount{};
auto res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, nullptr);
@ -62,11 +63,26 @@ Device::Device(const Instance& instance, uint64_t deviceUUID) {
if (!computeFamilyIdx)
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No compute queue family found");
// check if physical device supports float16
VkPhysicalDeviceVulkan12Features supported12Features{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES
};
VkPhysicalDeviceFeatures2 supportedFeatures{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &supported12Features
};
vkGetPhysicalDeviceFeatures2(*physicalDevice, &supportedFeatures);
this->supportsFP16 = !forceDisableFp16 && supported12Features.shaderFloat16;
if (this->supportsFP16)
std::cerr << "lsfg-vk: Using FP16 acceleration" << '\n';
else if (!forceDisableFp16)
std::cerr << "lsfg-vk: FP16 acceleration not supported, using FP32" << '\n';
// create logical device
const float queuePriority{1.0F}; // highest priority
VkPhysicalDeviceRobustness2FeaturesEXT robustness2{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT,
.nullDescriptor = VK_TRUE,
.nullDescriptor = VK_TRUE
};
VkPhysicalDeviceVulkan13Features features13{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
@ -76,6 +92,7 @@ Device::Device(const Instance& instance, uint64_t deviceUUID) {
const VkPhysicalDeviceVulkan12Features features12{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
.pNext = &features13,
.shaderFloat16 = this->supportsFP16,
.timelineSemaphore = VK_TRUE,
.vulkanMemoryModel = VK_TRUE
};

View file

@ -240,3 +240,136 @@ Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format,
}
);
}
// second shared memory constructors
Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format,
VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd)
: extent(extent), format(format), aspectFlags(aspectFlags) {
// create image
const VkExternalMemoryImageCreateInfo externalInfo{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
};
const VkImageCreateInfo desc{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = &externalInfo,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {
.width = extent.width,
.height = extent.height,
.depth = 1
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
};
VkImage imageHandle{};
auto res = vkCreateImage(device.handle(), &desc, nullptr, &imageHandle);
if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Failed to create Vulkan image");
// find memory type
VkPhysicalDeviceMemoryProperties memProps;
vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps);
VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(device.handle(), imageHandle, &memReqs);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
std::optional<uint32_t> memType{};
for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN
(memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
memType.emplace(i);
break;
} // NOLINTEND
}
if (!memType.has_value())
throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image");
#pragma clang diagnostic pop
// allocate and bind memory
const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR,
.image = imageHandle,
};
const VkExportMemoryAllocateInfo exportInfo{
.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
.pNext = &dedicatedInfo,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
};
const VkMemoryAllocateInfo allocInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &exportInfo,
.allocationSize = memReqs.size,
.memoryTypeIndex = memType.value()
};
VkDeviceMemory memoryHandle{};
res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle);
if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image");
res = vkBindImageMemory(device.handle(), imageHandle, memoryHandle, 0);
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image");
// obtain the sharing fd
const VkMemoryGetFdInfoKHR fdInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
.memory = memoryHandle,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR,
};
res = vkGetMemoryFdKHR(device.handle(), &fdInfo, fd);
if (res != VK_SUCCESS || *fd < 0)
throw LSFG::vulkan_error(res, "Failed to obtain sharing fd for Vulkan image");
// create image view
const VkImageViewCreateInfo viewDesc{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = imageHandle,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = format,
.components = {
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
.b = VK_COMPONENT_SWIZZLE_IDENTITY,
.a = VK_COMPONENT_SWIZZLE_IDENTITY
},
.subresourceRange = {
.aspectMask = aspectFlags,
.levelCount = 1,
.layerCount = 1
}
};
VkImageView viewHandle{};
res = vkCreateImageView(device.handle(), &viewDesc, nullptr, &viewHandle);
if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Failed to create image view");
// store objects in shared ptr
this->layout = std::make_shared<VkImageLayout>(VK_IMAGE_LAYOUT_UNDEFINED);
this->image = std::shared_ptr<VkImage>(
new VkImage(imageHandle),
[dev = device.handle()](VkImage* img) {
vkDestroyImage(dev, *img, nullptr);
}
);
this->memory = std::shared_ptr<VkDeviceMemory>(
new VkDeviceMemory(memoryHandle),
[dev = device.handle()](VkDeviceMemory* mem) {
vkFreeMemory(dev, *mem, nullptr);
}
);
this->view = std::shared_ptr<VkImageView>(
new VkImageView(viewHandle),
[dev = device.handle()](VkImageView* imgView) {
vkDestroyImageView(dev, *imgView, nullptr);
}
);
}

View file

@ -29,16 +29,40 @@ ShaderModule::ShaderModule(const Core::Device& device, const std::vector<uint8_t
// create descriptor set layout
std::vector<VkDescriptorSetLayoutBinding> layoutBindings;
size_t bindIdx = 0;
size_t bufferIdx{0};
size_t samplerIdx{16};
size_t inputIdx{32};
size_t outputIdx{48};
for (const auto &[count, type] : descriptorTypes)
for (size_t i = 0; i < count; i++, bindIdx++)
for (size_t i = 0; i < count; i++) {
size_t* bindIdx{};
switch (type) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
bindIdx = &bufferIdx;
break;
case VK_DESCRIPTOR_TYPE_SAMPLER:
bindIdx = &samplerIdx;
break;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
bindIdx = &inputIdx;
break;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
bindIdx = &outputIdx;
break;
default:
throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unsupported descriptor type");
}
layoutBindings.emplace_back(VkDescriptorSetLayoutBinding {
.binding = static_cast<uint32_t>(bindIdx),
.binding = static_cast<uint32_t>(*bindIdx),
.descriptorType = type,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT
});
(*bindIdx)++;
}
const VkDescriptorSetLayoutCreateInfo layoutDesc{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = static_cast<uint32_t>(layoutBindings.size()),

View file

@ -22,7 +22,7 @@ Core::ShaderModule ShaderPool::getShader(
return it->second;
// grab the shader
auto bytecode = this->source(name);
auto bytecode = this->source(name, this->fp16);
if (bytecode.empty())
throw std::runtime_error("Shader code is empty: " + name);

View file

@ -10,6 +10,11 @@
#include "common/exception.hpp"
#include "common/utils.hpp"
#ifdef LSFGVK_EXCESS_DEBUG
#include <renderdoc_app.h>
#include <dlfcn.h>
#endif // LSFGVK_EXCESS_DEBUG
#include <cstdint>
#include <optional>
#include <cstdlib>
@ -26,17 +31,22 @@ namespace {
std::optional<Core::Instance> instance;
std::optional<Vulkan> device;
std::unordered_map<int32_t, Context> contexts;
#ifdef LSFGVK_EXCESS_DEBUG
std::optional<RENDERDOC_API_1_6_0*> renderdoc;
#endif // LSFGVK_EXCESS_DEBUG
}
void LSFG_3_1::initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(const std::string&)>& loader) {
bool forceDisableFp16,
const std::function<std::vector<uint8_t>(const std::string&, bool)>& loader) {
if (instance.has_value() || device.has_value())
return;
instance.emplace();
device.emplace(Vulkan {
.device{*instance, deviceUUID},
.device{*instance, deviceUUID, forceDisableFp16},
.generationCount = generationCount,
.flowScale = flowScale,
.isHdr = isHdr
@ -47,11 +57,30 @@ void LSFG_3_1::initialize(uint64_t deviceUUID,
device->descriptorPool = Core::DescriptorPool(device->device);
device->resources = Pool::ResourcePool(device->isHdr, device->flowScale);
device->shaders = Pool::ShaderPool(loader);
device->shaders = Pool::ShaderPool(loader, device->device.getFP16Support());
std::srand(static_cast<uint32_t>(std::time(nullptr)));
}
#ifdef LSFGVK_EXCESS_DEBUG
void LSFG_3_1::initializeRenderDoc() {
if (renderdoc.has_value())
return;
if (void* mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD)) {
auto rdocGetAPI = reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
RENDERDOC_API_1_6_0* rdoc{};
rdocGetAPI(eRENDERDOC_API_Version_1_6_0, reinterpret_cast<void**>(&rdoc));
renderdoc.emplace(rdoc);
}
if (!renderdoc.has_value()) {
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "RenderDoc API not found");
}
}
#endif // LSFGVK_EXCESS_DEBUG
int32_t LSFG_3_1::createContext(
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format) {
@ -71,7 +100,19 @@ void LSFG_3_1::presentContext(int32_t id, int inSem, const std::vector<int>& out
if (it == contexts.end())
throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found");
#ifdef LSFGVK_EXCESS_DEBUG
if (renderdoc.has_value())
(*renderdoc)->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->handle()), nullptr);
#endif // LSFGVK_EXCESS_DEBUG
it->second.present(*device, inSem, outSem);
#ifdef LSFGVK_EXCESS_DEBUG
if (renderdoc.has_value()) {
vkDeviceWaitIdle(device->device.handle());
(*renderdoc)->EndFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->handle()), nullptr);
}
#endif // LSFGVK_EXCESS_DEBUG
}
void LSFG_3_1::deleteContext(int32_t id) {

View file

@ -10,6 +10,11 @@
#include "common/exception.hpp"
#include "common/utils.hpp"
#ifdef LSFGVK_EXCESS_DEBUG
#include <renderdoc_app.h>
#include <dlfcn.h>
#endif // LSFGVK_EXCESS_DEBUG
#include <cstdint>
#include <optional>
#include <cstdlib>
@ -26,17 +31,22 @@ namespace {
std::optional<Core::Instance> instance;
std::optional<Vulkan> device;
std::unordered_map<int32_t, Context> contexts;
#ifdef LSFGVK_EXCESS_DEBUG
std::optional<RENDERDOC_API_1_6_0*> renderdoc;
#endif // LSFGVK_EXCESS_DEBUG
}
void LSFG_3_1P::initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(const std::string&)>& loader) {
bool forceDisableFp16,
const std::function<std::vector<uint8_t>(const std::string&, bool)>& loader) {
if (instance.has_value() || device.has_value())
return;
instance.emplace();
device.emplace(Vulkan {
.device{*instance, deviceUUID},
.device{*instance, deviceUUID, forceDisableFp16},
.generationCount = generationCount,
.flowScale = flowScale,
.isHdr = isHdr
@ -47,11 +57,30 @@ void LSFG_3_1P::initialize(uint64_t deviceUUID,
device->descriptorPool = Core::DescriptorPool(device->device);
device->resources = Pool::ResourcePool(device->isHdr, device->flowScale);
device->shaders = Pool::ShaderPool(loader);
device->shaders = Pool::ShaderPool(loader, device->device.getFP16Support());
std::srand(static_cast<uint32_t>(std::time(nullptr)));
}
#ifdef LSFGVK_EXCESS_DEBUG
void LSFG_3_1P::initializeRenderDoc() {
if (renderdoc.has_value())
return;
if (void* mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD)) {
auto rdocGetAPI = reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
RENDERDOC_API_1_6_0* rdoc{};
rdocGetAPI(eRENDERDOC_API_Version_1_6_0, reinterpret_cast<void**>(&rdoc));
renderdoc.emplace(rdoc);
}
if (!renderdoc.has_value()) {
throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "RenderDoc API not found");
}
}
#endif // LSFGVK_EXCESS_DEBUG
int32_t LSFG_3_1P::createContext(
int in0, int in1, const std::vector<int>& outN,
VkExtent2D extent, VkFormat format) {
@ -71,7 +100,19 @@ void LSFG_3_1P::presentContext(int32_t id, int inSem, const std::vector<int>& ou
if (it == contexts.end())
throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found");
#ifdef LSFGVK_EXCESS_DEBUG
if (renderdoc.has_value())
(*renderdoc)->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->handle()), nullptr);
#endif // LSFGVK_EXCESS_DEBUG
it->second.present(*device, inSem, outSem);
#ifdef LSFGVK_EXCESS_DEBUG
if (renderdoc.has_value()) {
vkDeviceWaitIdle(device->device.handle());
(*renderdoc)->EndFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance->handle()), nullptr);
}
#endif // LSFGVK_EXCESS_DEBUG
}
void LSFG_3_1P::deleteContext(int32_t id) {

View file

@ -21,12 +21,12 @@ Generate::Generate(Vulkan& vk,
inImg3(std::move(inImg3)), inImg4(std::move(inImg4)),
inImg5(std::move(inImg5)) {
// create resources
this->shaderModule = vk.shaders.getShader(vk.device, "p_generate",
this->shaderModule = vk.shaders.getShader(vk.device, "generate",
{ { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
{ 2, VK_DESCRIPTOR_TYPE_SAMPLER },
{ 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
{ 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } });
this->pipeline = vk.shaders.getPipeline(vk.device, "p_generate");
this->pipeline = vk.shaders.getPipeline(vk.device, "generate");
this->samplers.at(0) = vk.resources.getSampler(vk.device);
this->samplers.at(1) = vk.resources.getSampler(vk.device,
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS);

View file

@ -16,12 +16,12 @@ Mipmaps::Mipmaps(Vulkan& vk,
Core::Image inImg_0, Core::Image inImg_1)
: inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) {
// create resources
this->shaderModule = vk.shaders.getShader(vk.device, "p_mipmaps",
this->shaderModule = vk.shaders.getShader(vk.device, "mipmaps",
{ { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER },
{ 1, VK_DESCRIPTOR_TYPE_SAMPLER },
{ 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
{ 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } });
this->pipeline = vk.shaders.getPipeline(vk.device, "p_mipmaps");
this->pipeline = vk.shaders.getPipeline(vk.device, "mipmaps");
this->buffer = vk.resources.getBuffer(vk.device);
this->sampler = vk.resources.getSampler(vk.device);
for (size_t i = 0; i < 2; i++)

View file

@ -15,6 +15,8 @@ namespace Config {
bool enable{false};
/// Path to Lossless.dll.
std::string dll;
/// Whether FP16 is force-disabled
bool no_fp16{false};
/// The frame generation muliplier
size_t multiplier{2};

View file

@ -6,6 +6,8 @@ const std::string DEFAULT_CONFIG = R"(version = 1
[global]
# override the location of Lossless Scaling
# dll = "/games/Lossless Scaling/Lossless.dll"
# force-disable fp16 (use on older nvidia cards)
# no_fp16 = true
# [[game]] # example entry
# exe = "Game.exe"
@ -30,7 +32,7 @@ multiplier = 4
performance_mode = false
[[game]] # override Genshin Impact
exe = "Genshin"
exe = "GenshinImpact.exe"
multiplier = 3
)";

View file

@ -17,10 +17,11 @@ namespace Extract {
/// Get a shader by name.
///
/// @param name The name of the shader to get.
/// @param fp16 If true, use the FP16 variant of shaders.
/// @return The shader bytecode.
///
/// @throws std::runtime_error if the shader is not found.
///
std::vector<uint8_t> getShader(const std::string& name);
std::vector<uint8_t> getShader(const std::string& name, bool fp16);
}

View file

@ -1,16 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
namespace Extract {
///
/// Translate DXBC bytecode to SPIR-V bytecode.
///
/// @param bytecode The DXBC bytecode to translate.
/// @return The translated SPIR-V bytecode.
///
std::vector<uint8_t> translateShader(std::vector<uint8_t> bytecode);
}

View file

@ -73,7 +73,8 @@ void Config::updateConfig(const std::string& file) {
// parse global configuration
const toml::value globalTable = toml::find_or_default<toml::table>(toml, "global");
const Configuration global{
.dll = toml::find_or(globalTable, "dll", std::string()),
.dll = toml::find_or(globalTable, "dll", std::string()),
.no_fp16 = toml::find_or(globalTable, "no_fp16", false),
.config_file = file,
.timestamp = std::filesystem::last_write_time(file)
};
@ -97,6 +98,7 @@ void Config::updateConfig(const std::string& file) {
Configuration game{
.enable = true,
.dll = global.dll,
.no_fp16 = global.no_fp16,
.multiplier = toml::find_or(gameTable, "multiplier", 2U),
.flowScale = toml::find_or(gameTable, "flow_scale", 1.0F),
.performance = toml::find_or(gameTable, "performance_mode", false),

View file

@ -2,7 +2,6 @@
#include "config/config.hpp"
#include "common/exception.hpp"
#include "extract/extract.hpp"
#include "extract/trans.hpp"
#include "utils/utils.hpp"
#include "hooks.hpp"
#include "layer.hpp"
@ -52,8 +51,9 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain,
LSFG_3_1::finalize();
// print config
std::cerr << "lsfg-vk: Reloaded configuration for " << name.second << ":\n";
std::cerr << "lsfg-vk: Reloaded configuration for " << name.first << ":\n";
if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n';
if (conf.no_fp16) std::cerr << " FP16 Acceleration: Force-disabled\n";
std::cerr << " Multiplier: " << conf.multiplier << '\n';
std::cerr << " Flow Scale: " << conf.flowScale << '\n';
std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n';
@ -99,11 +99,8 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain,
lsfgInitialize(
Utils::getDeviceUUID(info.physicalDevice),
conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1,
[](const std::string& name) {
auto dxbc = Extract::getShader(name);
auto spirv = Extract::translateShader(dxbc);
return spirv;
}
conf.no_fp16,
Extract::getShader
);
this->lsfgCtxId = std::shared_ptr<int32_t>(

View file

@ -3,84 +3,89 @@
#include <pe-parse/parse.h>
#include <cstdlib>
#include <unordered_map>
#include <filesystem>
#include <algorithm>
#include <cstdint>
#include <stdexcept>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <array>
using namespace Extract;
const uint32_t NO = 49; // native offset
const uint32_t PO = NO + 23; // performance+native offset
const uint32_t FP = 49; // fp32 offset
const std::unordered_map<std::string, uint32_t> nameIdxTable = {{
{ "mipmaps", 255 },
{ "alpha[0]", 267 },
{ "alpha[1]", 268 },
{ "alpha[2]", 269 },
{ "alpha[3]", 270 },
{ "beta[0]", 275 },
{ "beta[1]", 276 },
{ "beta[2]", 277 },
{ "beta[3]", 278 },
{ "beta[4]", 279 },
{ "gamma[0]", 257 },
{ "gamma[1]", 259 },
{ "gamma[2]", 260 },
{ "gamma[3]", 261 },
{ "gamma[4]", 262 },
{ "delta[0]", 257 },
{ "delta[1]", 263 },
{ "delta[2]", 264 },
{ "delta[3]", 265 },
{ "delta[4]", 266 },
{ "delta[5]", 258 },
{ "delta[6]", 271 },
{ "delta[7]", 272 },
{ "delta[8]", 273 },
{ "delta[9]", 274 },
{ "generate", 256 },
{ "p_mipmaps", 255 },
{ "p_alpha[0]", 290 },
{ "p_alpha[1]", 291 },
{ "p_alpha[2]", 292 },
{ "p_alpha[3]", 293 },
{ "p_beta[0]", 298 },
{ "p_beta[1]", 299 },
{ "p_beta[2]", 300 },
{ "p_beta[3]", 301 },
{ "p_beta[4]", 302 },
{ "p_gamma[0]", 280 },
{ "p_gamma[1]", 282 },
{ "p_gamma[2]", 283 },
{ "p_gamma[3]", 284 },
{ "p_gamma[4]", 285 },
{ "p_delta[0]", 280 },
{ "p_delta[1]", 286 },
{ "p_delta[2]", 287 },
{ "p_delta[3]", 288 },
{ "p_delta[4]", 289 },
{ "p_delta[5]", 281 },
{ "p_delta[6]", 294 },
{ "p_delta[7]", 295 },
{ "p_delta[8]", 296 },
{ "p_delta[9]", 297 },
{ "p_generate", 256 },
{ "mipmaps", 255 + NO },
{ "alpha[0]", 267 + NO },
{ "alpha[1]", 268 + NO },
{ "alpha[2]", 269 + NO },
{ "alpha[3]", 270 + NO },
{ "beta[0]", 275 + NO },
{ "beta[1]", 276 + NO },
{ "beta[2]", 277 + NO },
{ "beta[3]", 278 + NO },
{ "beta[4]", 279 + NO },
{ "gamma[0]", 257 + NO },
{ "gamma[1]", 259 + NO },
{ "gamma[2]", 260 + NO },
{ "gamma[3]", 261 + NO },
{ "gamma[4]", 262 + NO },
{ "delta[0]", 257 + NO },
{ "delta[1]", 263 + NO },
{ "delta[2]", 264 + NO },
{ "delta[3]", 265 + NO },
{ "delta[4]", 266 + NO },
{ "delta[5]", 258 + NO },
{ "delta[6]", 271 + NO },
{ "delta[7]", 272 + NO },
{ "delta[8]", 273 + NO },
{ "delta[9]", 274 + NO },
{ "generate", 256 + NO },
{ "p_alpha[0]", 267 + PO },
{ "p_alpha[1]", 268 + PO },
{ "p_alpha[2]", 269 + PO },
{ "p_alpha[3]", 270 + PO },
{ "p_beta[0]", 275 + PO },
{ "p_beta[1]", 276 + PO },
{ "p_beta[2]", 277 + PO },
{ "p_beta[3]", 278 + PO },
{ "p_beta[4]", 279 + PO },
{ "p_gamma[0]", 257 + PO },
{ "p_gamma[1]", 259 + PO },
{ "p_gamma[2]", 260 + PO },
{ "p_gamma[3]", 261 + PO },
{ "p_gamma[4]", 262 + PO },
{ "p_delta[0]", 257 + PO },
{ "p_delta[1]", 263 + PO },
{ "p_delta[2]", 264 + PO },
{ "p_delta[3]", 265 + PO },
{ "p_delta[4]", 266 + PO },
{ "p_delta[5]", 258 + PO },
{ "p_delta[6]", 271 + PO },
{ "p_delta[7]", 272 + PO },
{ "p_delta[8]", 273 + PO },
{ "p_delta[9]", 274 + PO },
}};
namespace {
auto& shaders() {
static std::unordered_map<uint32_t, std::vector<uint8_t>> shaderData;
auto& pshaders() {
static std::unordered_map<uint32_t, std::array<std::vector<uint8_t>, 2>> shaderData;
return shaderData;
}
int on_resource(void*, const peparse::resource& res) {
int on_resource(void* ptr, const peparse::resource& res) {
if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0)
return 0;
std::vector<uint8_t> resource_data(res.buf->bufLen);
std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data());
shaders()[res.name] = resource_data;
auto* shaders = reinterpret_cast<std::unordered_map<uint32_t, std::vector<uint8_t>>*>(ptr);
shaders->emplace(res.name, std::move(resource_data));
return 0;
}
@ -116,33 +121,44 @@ namespace {
}
void Extract::extractShaders() {
if (!shaders().empty())
if (!pshaders().empty())
return;
std::unordered_map<uint32_t, std::vector<uint8_t>> shaders{};
// parse the dll
peparse::parsed_pe* dll = peparse::ParsePEFromFile(getDllPath().c_str());
if (!dll)
throw std::runtime_error("Unable to read Lossless.dll, is it installed?");
peparse::IterRsrc(dll, on_resource, nullptr);
peparse::IterRsrc(dll, on_resource, reinterpret_cast<void*>(&shaders));
peparse::DestructParsedPE(dll);
// ensure all shaders are present
for (const auto& [name, idx] : nameIdxTable)
if (shaders().find(idx) == shaders().end())
throw std::runtime_error("Shader not found: " + name + ".\n- Is Lossless Scaling up to date?");
for (const auto& [name, idx] : nameIdxTable) {
auto fp16 = shaders.find(idx);
if (fp16 == shaders.end())
throw std::runtime_error("Shader not found: " + name + " (FP16).\n- Is Lossless Scaling up to date?");
auto fp32 = shaders.find(idx + FP);
if (fp32 == shaders.end())
throw std::runtime_error("Shader not found: " + name + " (FP32).\n- Is Lossless Scaling up to date?");
pshaders().emplace(idx, std::array<std::vector<uint8_t>, 2>{
std::move(fp32->second),
std::move(fp16->second)
});
}
}
std::vector<uint8_t> Extract::getShader(const std::string& name) {
if (shaders().empty())
std::vector<uint8_t> Extract::getShader(const std::string& name, bool fp16) {
if (pshaders().empty())
throw std::runtime_error("Shaders are not loaded.");
auto hit = nameIdxTable.find(name);
if (hit == nameIdxTable.end())
throw std::runtime_error("Shader hash not found: " + name);
auto sit = shaders().find(hit->second);
if (sit == shaders().end())
auto sit = pshaders().find(hit->second);
if (sit == pshaders().end())
throw std::runtime_error("Shader not found: " + name);
return sit->second;
return fp16 ? sit->second.at(1) : sit->second.at(0);
}

View file

@ -1,76 +0,0 @@
#include "extract/trans.hpp"
#include <thirdparty/spirv.hpp>
#include <dxbc_modinfo.h>
#include <dxbc_module.h>
#include <dxbc_reader.h>
#include <cstdint>
#include <cstddef>
#include <algorithm>
#include <vector>
using namespace Extract;
struct BindingOffsets {
uint32_t bindingIndex{};
uint32_t bindingOffset{};
uint32_t setIndex{};
uint32_t setOffset{};
};
std::vector<uint8_t> Extract::translateShader(std::vector<uint8_t> bytecode) {
// compile the shader
dxvk::DxbcReader reader(reinterpret_cast<const char*>(bytecode.data()), bytecode.size());
dxvk::DxbcModule module(reader);
const dxvk::DxbcModuleInfo info{};
auto code = module.compile(info, "CS");
// find all bindings
std::vector<BindingOffsets> bindingOffsets;
std::vector<uint32_t> varIds;
for (auto ins : code) {
if (ins.opCode() == spv::OpDecorate) {
if (ins.arg(2) == spv::DecorationBinding) {
const uint32_t varId = ins.arg(1);
bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
bindingOffsets[varId].bindingIndex = ins.arg(3);
bindingOffsets[varId].bindingOffset = ins.offset() + 3;
varIds.push_back(varId);
}
if (ins.arg(2) == spv::DecorationDescriptorSet) {
const uint32_t varId = ins.arg(1);
bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
bindingOffsets[varId].setIndex = ins.arg(3);
bindingOffsets[varId].setOffset = ins.offset() + 3;
}
}
if (ins.opCode() == spv::OpFunction)
break;
}
std::vector<BindingOffsets> validBindings;
for (const auto varId : varIds) {
auto info = bindingOffsets[varId];
if (info.bindingOffset)
validBindings.push_back(info);
}
// patch binding offset
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
for (size_t i = 0; i < validBindings.size(); i++)
code.data()[validBindings.at(i).bindingOffset] // NOLINT
= static_cast<uint8_t>(i);
#pragma clang diagnostic pop
// return the new bytecode
std::vector<uint8_t> spirvBytecode(code.size());
std::copy_n(reinterpret_cast<uint8_t*>(code.data()),
code.size(), spirvBytecode.data());
return spirvBytecode;
}

View file

@ -3,10 +3,7 @@
#include "utils/benchmark.hpp"
#include "utils/utils.hpp"
#include <unistd.h>
#include <exception>
#include <fstream>
#include <stdexcept>
#include <iostream>
#include <cstdint>
@ -34,7 +31,7 @@ namespace {
try {
Config::activeConf = Config::getConfig(name);
} catch (const std::exception& e) {
std::cerr << "lsfg-vk: The configuration for " << name.second << " is invalid, IGNORING:\n";
std::cerr << "lsfg-vk: The configuration for " << name.first << " is invalid, IGNORING:\n";
std::cerr << e.what() << '\n';
return; // default configuration will unload
}
@ -45,8 +42,9 @@ namespace {
return; // default configuration will unload
// print config
std::cerr << "lsfg-vk: Loaded configuration for " << name.second << ":\n";
std::cerr << "lsfg-vk: Loaded configuration for " << name.first << ":\n";
if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n';
if (conf.no_fp16) std::cerr << " FP16 Acceleration: Force-disabled\n";
std::cerr << " Multiplier: " << conf.multiplier << '\n';
std::cerr << " Flow Scale: " << conf.flowScale << '\n';
std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n';
@ -56,22 +54,6 @@ namespace {
// remove mesa var in favor of config
unsetenv("MESA_VK_WSI_PRESENT_MODE"); // NOLINT
// write latest file
try {
std::ofstream latest("/tmp/lsfg-vk_last", std::ios::trunc);
if (!latest.is_open())
throw std::runtime_error("Failed to open /tmp/lsfg-vk_last for writing");
latest << "exe: " << name.first << '\n';
latest << "comm: " << name.second << '\n';
latest << "pid: " << getpid() << '\n';
if (!latest.good())
throw std::runtime_error("Failed to write to /tmp/lsfg-vk_last");
} catch (const std::exception& e) {
std::cerr << "lsfg-vk: An error occurred while trying to write the latest file, exiting:\n";
std::cerr << "- " << e.what() << '\n';
exit(EXIT_FAILURE);
}
// load shaders
try {
Extract::extractShaders();

View file

@ -1,7 +1,6 @@
#include "utils/benchmark.hpp"
#include "config/config.hpp"
#include "extract/extract.hpp"
#include "extract/trans.hpp"
#include <vulkan/vulkan_core.h>
#include <lsfg_3_1.hpp>
@ -42,11 +41,8 @@ void Benchmark::run(uint32_t width, uint32_t height) {
lsfgInitialize(
deviceUUID, // some magic number if not given
conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1,
[](const std::string& name) -> std::vector<uint8_t> {
auto dxbc = Extract::getShader(name);
auto spirv = Extract::translateShader(dxbc);
return spirv;
}
conf.no_fp16,
Extract::getShader
);
const int32_t ctx = lsfgCreateContext(-1, -1, {},
{ .width = width, .height = height },

View file

@ -209,20 +209,25 @@ void Utils::resetLimitN(const std::string& id) noexcept {
}
std::pair<std::string, std::string> Utils::getProcessName() {
// check override first
const char* process_name = std::getenv("LSFG_PROCESS");
if (process_name && *process_name != '\0')
return { process_name, process_name };
// then check benchmark flag
const char* benchmark_flag = std::getenv("LSFG_BENCHMARK");
if (benchmark_flag)
return { "benchmark", "benchmark" };
std::array<char, 4096> exe{};
// find executed binary
const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1);
if (exe_len <= 0)
return { "Unknown Process", "unknown" };
exe.at(static_cast<size_t>(exe_len)) = '\0';
std::string exe_str(exe.data());
// find command name as well
std::ifstream comm_file("/proc/self/comm");
if (!comm_file.is_open())
return { std::string(exe.data()), "unknown" };
@ -233,7 +238,37 @@ std::pair<std::string, std::string> Utils::getProcessName() {
if (comm_str.back() == '\n')
comm_str.pop_back();
return{ std::string(exe.data()), comm_str };
// replace binary with exe for wine apps
if (exe_str.find("wine") != std::string::npos
|| exe_str.find("proton") != std::string::npos) {
std::ifstream proc_maps("/proc/self/maps");
if (!proc_maps.is_open())
return{ exe_str, comm_str };
std::string line;
while (std::getline(proc_maps, line)) {
if (!line.ends_with(".exe"))
continue;
size_t pos = line.find_first_of('/');
if (pos == std::string::npos) {
pos = line.find_last_of(' ');
if (pos == std::string::npos)
continue;
pos += 1; // skip space
}
const std::string exe_name = line.substr(pos);
if (exe_name.empty())
continue;
exe_str = exe_name;
break;
}
}
return{ exe_str, comm_str };
}
std::string Utils::getConfigFile() {

53
test/CMakeLists.txt Normal file
View file

@ -0,0 +1,53 @@
cmake_minimum_required(VERSION 3.10)
if(NOT LSFGVK_EXCESS_DEBUG)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
endif()
project(lsfg-vk-test
DESCRIPTION "Test: lsfg-vk"
LANGUAGES CXX)
file(GLOB SOURCES
"src/*.cpp"
)
add_executable(lsfg-vk-test ${SOURCES})
# target
set_target_properties(lsfg-vk-test PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON)
target_link_libraries(lsfg-vk-test PUBLIC
lsfg-vk lsfg-vk-framegen
vulkan)
# diagnostics
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set_target_properties(lsfg-vk-test PROPERTIES
EXPORT_COMPILE_COMMANDS ON)
endif()
if(LSFGVK_EXCESS_DEBUG)
target_compile_options(lsfg-vk-test PRIVATE
-Weverything
# disable compat c++ flags
-Wno-pre-c++20-compat-pedantic
-Wno-pre-c++17-compat
-Wno-c++98-compat-pedantic
-Wno-c++98-compat
# disable other flags
-Wno-missing-designated-field-initializers
-Wno-shadow # allow shadowing
-Wno-switch-enum # ignore missing cases
-Wno-switch-default # ignore missing default
-Wno-padded # ignore automatic padding
-Wno-exit-time-destructors # allow globals
-Wno-global-constructors # allow globals
-Wno-cast-function-type-strict # for vulkan
)
set_target_properties(lsfg-vk-test PROPERTIES
CXX_CLANG_TIDY clang-tidy)
endif()

130
test/src/main.cpp Normal file
View file

@ -0,0 +1,130 @@
#include "common/utils.hpp"
#include "core/commandpool.hpp"
#include "core/device.hpp"
#include "core/image.hpp"
#include "core/instance.hpp"
#include "extract/extract.hpp"
#include <vulkan/vulkan_core.h>
#include <cstdlib>
#include <utility>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include <array>
using namespace LSFG;
// test configuration
const VkExtent2D SRC_EXTENT = { 2560 , 1440 };
const VkFormat SRC_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
const std::array<std::string, 3> SRC_FILES = {
"test/f0.dds",
"test/f1.dds",
"test/f2.dds"
};
const size_t MULTIPLIER = 3;
const bool IS_HDR = false;
const float FLOW_SCALE = 0.7F;
#define PERFORMANCE_MODE false
// test configuration end
#if PERFORMANCE_MODE
#include "lsfg_3_1p.hpp"
using namespace LSFG_3_1P;
#else
#include "lsfg_3_1.hpp"
using namespace LSFG_3_1;
#endif
namespace {
/// Create images for frame generation
std::pair<Core::Image, Core::Image> create_images(const Core::Device& device,
std::array<int, 2>& fds,
std::vector<int>& outFds, std::vector<Core::Image>& out_n) {
const Core::Image frame_0{device,
SRC_EXTENT, SRC_FORMAT,
VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
&fds.at(0)
};
const Core::Image frame_1{device,
SRC_EXTENT, SRC_FORMAT,
VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
&fds.at(1)
};
for (size_t i = 0; i < (MULTIPLIER - 1); i++)
out_n.at(i) = Core::Image{device,
SRC_EXTENT, SRC_FORMAT,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
&outFds.at(i)
};
return { frame_0, frame_1 };
}
/// Create the LSFG context.
int32_t create_lsfg(const std::array<int, 2>& fds, const std::vector<int>& outFds) {
Extract::extractShaders();
initialize(
0x1463ABAC,
IS_HDR, 1.0F / FLOW_SCALE, MULTIPLIER - 1,
false,
Extract::getShader
);
initializeRenderDoc();
return createContext(
fds.at(0), fds.at(1), outFds,
SRC_EXTENT, SRC_FORMAT
);
}
/// Destroy the LSFG context.
void delete_lsfg(int32_t id) {
deleteContext(id);
finalize();
}
}
namespace {
std::array<int, 2> fds{};
std::vector<int> outFds(MULTIPLIER - 1);
std::pair<Core::Image, Core::Image> frames{};
std::vector<Core::Image> out_n(MULTIPLIER - 1);
int32_t lsfg_id{};
}
int main() {
// initialize host Vulkan
const Core::Instance instance{};
const Core::Device device{instance, 0x1463ABAC, false};
const Core::CommandPool commandPool{device};
// setup test
frames = create_images(device, fds, outFds, out_n);
lsfg_id = create_lsfg(fds, outFds);
Utils::clearImage(device, frames.first);
Utils::clearImage(device, frames.second);
// run
for (size_t fc = 0; fc < SRC_FILES.size(); fc++) {
if (fc % 2 == 0)
Utils::uploadImage(device, commandPool, frames.first, SRC_FILES.at(fc));
else
Utils::uploadImage(device, commandPool, frames.second, SRC_FILES.at(fc));
// run the present
presentContext(lsfg_id, -1, {});
}
// destroy test
delete_lsfg(lsfg_id);
return 0;
}

1
thirdparty/dxbc vendored

@ -1 +0,0 @@
Subproject commit 78ab59a8aaeb43cd1b0a5e91ba86722433a10b78

View file

@ -39,6 +39,14 @@
<property name="icon-name">folder-symbolic</property>
</object>
</child>
<!--General Properties: FP16 Override -->
<child>
<object class="LSPrefSwitch" id="no_fp16">
<property name="opt-name">Force-disable FP16</property>
<property name="opt-subtitle">(Global Option) Force-disable FP16 acceleration (use on older NVIDIA GPUs)</property>
<property name="default-state">false</property>
</object>
</child>
<!--General Properties: Profile name -->
<child>
<object class="LSPrefEntry" id="profile_name">

View file

@ -30,6 +30,7 @@ pub fn default_config() -> TomlConfig {
version: 1,
global: TomlGlobal {
dll: None,
no_fp16: false
},
game: vec![
TomlGame {
@ -49,7 +50,7 @@ pub fn default_config() -> TomlConfig {
experimental_present_mode: PresentMode::Vsync,
},
TomlGame {
exe: String::from("Genshin"),
exe: String::from("GenshinImpact.exe"),
multiplier: Multiplier::from(3),
flow_scale: FlowScale::from(1.0),
performance_mode: false,

View file

@ -62,7 +62,9 @@ impl Into<u32> for PresentMode {
/// Global configuration for the application
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct TomlGlobal {
pub dll: Option<String>
pub dll: Option<String>,
#[serde(default)]
pub no_fp16: bool
}
/// Game-specific configuration

View file

@ -25,6 +25,7 @@ pub fn build(app: &adw::Application) {
if let Some(dll_path) = config.global.dll {
imp.main.imp().dll.imp().entry.set_text(&dll_path);
}
imp.main.imp().no_fp16.imp().switch.set_active(config.global.no_fp16);
// register handlers on sidebar pane.
sidebar_handler::register_signals(&imp.sidebar, imp.main.clone());

View file

@ -94,6 +94,12 @@ pub fn register_signals(sidebar_: pane::PaneSidebar, main: &pane::PaneMain) {
}
});
});
let no_fp16 = main.no_fp16.imp();
no_fp16.switch.connect_state_notify(|switch| {
let _ = config::edit_config(|config| {
config.global.no_fp16 = switch.state();
});
});
// utility buttons
let entry = dll.entry.clone();

View file

@ -18,11 +18,28 @@ pub fn find_vulkan_processes() -> ProcResult<Vec<(String, String)>> {
continue;
}
// find executed binary
let mut exe = prc.exe()?.to_string_lossy().to_string();
// replace binary with exe for wine apps
if exe.contains("wine") || exe.contains("proton") {
let result = maps.iter()
.filter_map(|map| map.filename())
.map(|filename| filename.to_string_lossy().to_string())
.find(|filename| filename.ends_with(".exe"));
if let Some(exe_name) = result {
exe = exe_name;
}
}
// split off last part of the path
exe = exe.split('/').last().unwrap_or(&exe).to_string();
// format process information
let pid = prc.pid();
let name = prc.stat()?.comm;
let process_info = format!("PID {}: {}", pid, name);
processes.push((process_info, name));
let process_info = format!("PID {}: {}", pid, exe);
processes.push((process_info, exe));
}
Ok(processes)

View file

@ -9,6 +9,8 @@ pub struct PaneMain {
#[template_child]
pub dll: TemplateChild<PrefEntry>,
#[template_child]
pub no_fp16: TemplateChild<PrefSwitch>,
#[template_child]
pub profile_name: TemplateChild<PrefEntry>,
#[template_child]
pub multiplier: TemplateChild<PrefNumber>,