Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@
/.cache
/.ccls

build-android/
/build-android-*
build-android*

# IntelliJ idea
.idea
31 changes: 29 additions & 2 deletions framegen/v3.1_src/lsfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ namespace {
std::optional<Vulkan> device;
std::unordered_map<int32_t, Context> contexts;
bool externalMode = false;

// Bounded drain of the framegen queue: a fence-only submit completes when
// all previously submitted work does. Used instead of vkQueueWaitIdle /
// vkDeviceWaitIdle in external mode, where an unbounded wait on the game's
// queue can freeze the present thread forever if the queue is wedged.
void drainQueueBounded(VkDevice dev, VkQueue queue) {
const VkFenceCreateInfo fenceInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
VkFence fence{};
if (vkCreateFence(dev, &fenceInfo, nullptr, &fence) != VK_SUCCESS)
return;
if (vkQueueSubmit(queue, 0, nullptr, fence) == VK_SUCCESS)
vkWaitForFences(dev, 1, &fence, VK_TRUE, 2'000'000'000ULL);
vkDestroyFence(dev, fence, nullptr);
}
}

void LSFG_3_1::initialize(uint64_t deviceUUID,
Expand Down Expand Up @@ -110,18 +125,30 @@ void LSFG_3_1::deleteContext(int32_t id) {
if (it == contexts.end())
throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context");

vkDeviceWaitIdle(device->device.handle());
// external mode: the device belongs to the game — idling it from a layer
// hook races the game's own queue access and can deadlock the driver.
// All framegen work is submitted on this one queue, so drain just it,
// with a bounded wait so a wedged queue can't freeze the present thread.
if (externalMode)
drainQueueBounded(device->device.handle(), device->device.getComputeQueue());
else
vkDeviceWaitIdle(device->device.handle());
contexts.erase(it);
}

void LSFG_3_1::finalize() {
if (!instance.has_value() || !device.has_value())
return;

vkDeviceWaitIdle(device->device.handle());
// see deleteContext: never idle the game's whole device in external mode
if (externalMode)
drainQueueBounded(device->device.handle(), device->device.getComputeQueue());
else
vkDeviceWaitIdle(device->device.handle());
contexts.clear();
device.reset();
instance.reset();
externalMode = false;
}

#ifdef __ANDROID__
Expand Down
31 changes: 29 additions & 2 deletions framegen/v3.1p_src/lsfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ namespace {
std::optional<Vulkan> device;
std::unordered_map<int32_t, Context> contexts;
bool externalMode = false;

// Bounded drain of the framegen queue: a fence-only submit completes when
// all previously submitted work does. Used instead of vkQueueWaitIdle /
// vkDeviceWaitIdle in external mode, where an unbounded wait on the game's
// queue can freeze the present thread forever if the queue is wedged.
void drainQueueBounded(VkDevice dev, VkQueue queue) {
const VkFenceCreateInfo fenceInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
VkFence fence{};
if (vkCreateFence(dev, &fenceInfo, nullptr, &fence) != VK_SUCCESS)
return;
if (vkQueueSubmit(queue, 0, nullptr, fence) == VK_SUCCESS)
vkWaitForFences(dev, 1, &fence, VK_TRUE, 2'000'000'000ULL);
vkDestroyFence(dev, fence, nullptr);
}
}

void LSFG_3_1P::initialize(uint64_t deviceUUID,
Expand Down Expand Up @@ -110,18 +125,30 @@ void LSFG_3_1P::deleteContext(int32_t id) {
if (it == contexts.end())
throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context");

vkDeviceWaitIdle(device->device.handle());
// external mode: the device belongs to the game — idling it from a layer
// hook races the game's own queue access and can deadlock the driver.
// All framegen work is submitted on this one queue, so drain just it,
// with a bounded wait so a wedged queue can't freeze the present thread.
if (externalMode)
drainQueueBounded(device->device.handle(), device->device.getComputeQueue());
else
vkDeviceWaitIdle(device->device.handle());
contexts.erase(it);
}

void LSFG_3_1P::finalize() {
if (!instance.has_value() || !device.has_value())
return;

vkDeviceWaitIdle(device->device.handle());
// see deleteContext: never idle the game's whole device in external mode
if (externalMode)
drainQueueBounded(device->device.handle(), device->device.getComputeQueue());
else
vkDeviceWaitIdle(device->device.handle());
contexts.clear();
device.reset();
instance.reset();
externalMode = false;
}

#ifdef __ANDROID__
Expand Down
10 changes: 10 additions & 0 deletions include/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ class LsContext {
/// failures. The present hook passes frames through untouched when set.
[[nodiscard]] bool isDisabled() const { return this->forceDisabled; }

/// Whether a config reload finalized the framegen module after this
/// context was created. Its framegen context id is dead; the present hook
/// must force the game to recreate the swapchain instead of presenting.
[[nodiscard]] bool isStale() const;

///
/// Pace and forward a present without frame generation (multiplier <= 1).
/// Applies the same vsync-locked fps limiter as the framegen path so the
Expand All @@ -76,6 +81,7 @@ class LsContext {
VkExtent2D extent;

std::shared_ptr<int32_t> lsfgCtxId; // lsfg context id
uint64_t lsfgEpoch{0}; // framegen module epoch this context was built against
Mini::Image frame_0, frame_1; // frames shared with lsfg. write to frame_0 when fc % 2 == 0
std::vector<Mini::Image> out_n; // output images shared with lsfg, indexed by framegen id

Expand All @@ -89,6 +95,10 @@ class LsContext {
uint32_t copyFenceTimeouts{0};
bool forceDisabled{false};

// acquire semaphores handed to a timed-out vkAcquireNextImageKHR are
// parked here instead of destroyed (wrapper ICDs may signal them late)
std::vector<Mini::Semaphore> retiredSemaphores;

int64_t pacerAnchorNs{0}; // schedule anchor of the current real frame
int64_t pacerNextDueNs{0}; // next vsync-grid slot for the real frame
int64_t lastRealPresentNs{0}; // previous real-frame entry, for the EWMA
Expand Down
13 changes: 5 additions & 8 deletions include/layer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,6 @@ namespace Layer {
const VkSemaphoreGetFdInfoKHR* pGetFdInfo,
int* pFd);

#ifdef __ANDROID__
/// Call to the original vkGetAndroidHardwareBufferPropertiesANDROID function.
VkResult ovkGetAndroidHardwareBufferPropertiesANDROID(
VkDevice device,
const AHardwareBuffer* hardwareBuffer,
VkAndroidHardwareBufferPropertiesANDROID* pProperties);
#endif

/// Call to the original vkGetDeviceQueue function.
void ovkGetDeviceQueue(
VkDevice device,
Expand All @@ -199,6 +191,11 @@ namespace Layer {
bool ovkGetPhysicalDeviceFeatures2(
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceFeatures2* pFeatures);
/// Enumerate device extensions (returns false when unavailable or failed).
bool ovkEnumerateDeviceExtensionProperties(
VkPhysicalDevice physicalDevice,
uint32_t* pPropertyCount,
VkExtensionProperties* pProperties);
/// The instance the layer initialized on.
VkInstance ovkInstance();

Expand Down
31 changes: 0 additions & 31 deletions include/mini/image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

#include <vulkan/vulkan_core.h>

#ifdef __ANDROID__
#include <android/hardware_buffer.h>
#endif

#include <memory>

namespace Mini {
Expand Down Expand Up @@ -35,28 +31,6 @@ namespace Mini {
Image(VkDevice device, VkPhysicalDevice physicalDevice, VkExtent2D extent, VkFormat format,
VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd);

#ifdef __ANDROID__
///
/// Create the image backed by an AHardwareBuffer (Android path).
/// Allocates an AHB, wraps it in a VkImage, and exposes the AHB pointer
/// for sharing with framegen via createContextFromAHB.
///
/// @param device Vulkan device
/// @param physicalDevice Vulkan physical 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
///
/// @throws LSFG::vulkan_error if object creation fails.
///
Image(VkDevice device, VkPhysicalDevice physicalDevice, VkExtent2D extent, VkFormat format,
VkImageUsageFlags usage, VkImageAspectFlags aspectFlags);

/// Get the AHardwareBuffer handle (Android only).
[[nodiscard]] AHardwareBuffer* getAhb() const { return this->ahb; }
#endif

///
/// Create a plain device-local image with no external memory
/// (single-device framegen path: the image is shared with the framegen
Expand Down Expand Up @@ -89,11 +63,6 @@ namespace Mini {
std::shared_ptr<VkImage> image;
std::shared_ptr<VkDeviceMemory> memory;

#ifdef __ANDROID__
AHardwareBuffer* ahb{}; // owned, released via custom deleter
std::shared_ptr<AHardwareBuffer> ahbRef; // shared ownership for copy/move
#endif

VkExtent2D extent{};
VkFormat format{};
VkImageAspectFlags aspectFlags{};
Expand Down
5 changes: 3 additions & 2 deletions scripts/build/android.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ cmake -S "${REPO_ROOT}" -B "${BUILD_DIR}" \
-DLSFGVK_ANDROID_WINE=ON \
-DVOLK_STATIC_DEFINES=VK_USE_PLATFORM_ANDROID_KHR \
-DCMAKE_CXX_FLAGS="-DVK_USE_PLATFORM_ANDROID_KHR" \
-DCMAKE_C_FLAGS="-DVK_USE_PLATFORM_ANDROID_KHR"
-DCMAKE_C_FLAGS="-DVK_USE_PLATFORM_ANDROID_KHR" \
-DCMAKE_SHARED_LINKER_FLAGS="-Wl,-z,max-page-size=16384"

cmake --build "${BUILD_DIR}" --parallel

Expand All @@ -71,4 +72,4 @@ echo " ${DIST_DIR}/liblsfg-vk-${ABI}.so"
echo " ${DIST_DIR}/VkLayer_LS_frame_generation.json"
echo ""
echo "For GameNative Android app updates, copy the arm64-v8a shared library to:"
echo " app/src/main/assets/lsfg_vk/android_arm64_v8a/liblsfg-vk-layer.so"
echo " app/src/main/jniLibs/arm64-v8a/liblsfg-vk-layer.so"
Loading
Loading