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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
/.cache
/.ccls

build-android/
build-android/
/build-android-*
15 changes: 15 additions & 0 deletions framegen/include/core/device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ namespace LSFG::Core {
///
Device(const Instance& instance, uint64_t deviceUUID);

///
/// Adopt an externally owned device. The caller keeps ownership of
/// every handle; the instance must have been adopted with a custom
/// loader entry point first. Barriers use the sync1 compat path since
/// the caller's device features are unknown.
///
/// @param instance Adopted instance (loader already initialized).
/// @param physical Physical device the external device was created on.
/// @param external Device owned by the caller.
/// @param queueFamilyIdx Queue family of the queue below.
/// @param queue Queue all framegen work is submitted to.
///
Device(const Instance& instance, VkPhysicalDevice physical,
VkDevice external, uint32_t queueFamilyIdx, VkQueue queue);

/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->device; }
/// Get the physical device associated with this logical device.
Expand Down
17 changes: 17 additions & 0 deletions framegen/include/core/image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@ namespace LSFG::Core {
AHardwareBuffer* ahb);
#endif

///
/// Wrap a caller-owned VkImage living on the same device (single-device
/// mode). No memory is allocated or imported; only a view is created.
/// The caller must keep the image alive and hand it over in GENERAL
/// layout between presents.
///
/// @param device Vulkan device the image belongs to.
/// @param externalImage Caller-owned image handle.
/// @param extent Extent of the image in pixels.
/// @param format Vulkan format of the image
/// @param aspectFlags Aspect flags for the image view
///
/// @throws LSFG::vulkan_error if view creation fails.
///
Image(const Core::Device& device, VkImage externalImage,
VkExtent2D extent, VkFormat format, VkImageAspectFlags aspectFlags);

/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->image; }
/// Get the Vulkan device memory handle.
Expand Down
10 changes: 10 additions & 0 deletions framegen/include/core/instance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ namespace LSFG::Core {
///
Instance();

///
/// Adopt an externally owned instance. Routes all Vulkan calls through
/// the given loader entry point (e.g. a layer's next-in-chain
/// vkGetInstanceProcAddr) and never destroys the instance.
///
/// @param gipa Loader entry point to resolve Vulkan functions with.
/// @param external Instance owned by the caller.
///
Instance(PFN_vkGetInstanceProcAddr gipa, VkInstance external);

/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return this->instance ? *this->instance : VK_NULL_HANDLE; }

Expand Down
44 changes: 44 additions & 0 deletions framegen/public/lsfg_3_1.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,33 @@ namespace LSFG_3_1 {
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(const std::string&)>& loader);

///
/// Initialize the LSFG library on an externally owned device (single-device
/// mode). All framegen work runs on the caller's device and queue, routed
/// through the given loader entry point; no second VkInstance/VkDevice is
/// created. The caller must ensure the device was created with the features
/// the shader chain needs (storage-image formats, read/write without
/// format, and shaderFloat16 or vulkanMemoryModel depending on the shader
/// path).
///
/// @param gipa Loader entry point (e.g. a layer's next vkGetInstanceProcAddr).
/// @param externalInstance Caller-owned instance.
/// @param physicalDevice Physical device of the external device.
/// @param externalDevice Caller-owned device.
/// @param queueFamilyIdx Queue family of the queue below.
/// @param queue Queue all framegen work is submitted to.
/// @param isHdr Whether the images are in HDR format.
/// @param flowScale Internal flow scale factor.
/// @param generationCount Number of frames to generate.
/// @param loader Function to load shader source code by name.
///
__attribute__((visibility("default")))
void initializeExternal(PFN_vkGetInstanceProcAddr gipa,
VkInstance externalInstance, VkPhysicalDevice physicalDevice,
VkDevice externalDevice, uint32_t queueFamilyIdx, VkQueue queue,
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(const std::string&)>& loader);

///
/// Create a new LSFG context on a swapchain.
///
Expand Down Expand Up @@ -68,6 +95,23 @@ namespace LSFG_3_1 {
VkExtent2D extent, VkFormat format);
#endif

///
/// Single-device variant: wrap caller-owned VkImages living on the adopted
/// device. Only valid after initializeExternal. The caller keeps ownership
/// of the images and must hand them over in GENERAL layout.
///
/// @param in0 First input image.
/// @param in1 Second input image.
/// @param outN Output images, one per generated frame.
/// @param extent Image dimensions.
/// @param format Vulkan format of all images.
/// @return Unique context identifier.
///
__attribute__((visibility("default")))
int32_t createContextFromImages(
VkImage in0, VkImage in1, const std::vector<VkImage>& outN,
VkExtent2D extent, VkFormat format);

///
/// Present a context.
///
Expand Down
36 changes: 36 additions & 0 deletions framegen/public/lsfg_3_1p.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ namespace LSFG_3_1P {
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(const std::string&)>& loader);

///
/// Initialize the LSFG library on an externally owned device (single-device
/// mode). All framegen work runs on the caller's device and queue; no
/// second VkInstance/VkDevice is created. See LSFG_3_1::initializeExternal.
///
__attribute__((visibility("default")))
void initializeExternal(PFN_vkGetInstanceProcAddr gipa,
VkInstance externalInstance, VkPhysicalDevice physicalDevice,
VkDevice externalDevice, uint32_t queueFamilyIdx, VkQueue queue,
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(const std::string&)>& loader);

///
/// Create a new LSFG context on a swapchain.
///
Expand All @@ -55,6 +67,13 @@ namespace LSFG_3_1P {
VkExtent2D extent, VkFormat format);
#endif

/// Single-device variant: wrap caller-owned VkImages living on the adopted
/// device. Only valid after initializeExternal.
__attribute__((visibility("default")))
int32_t createContextFromImages(
VkImage in0, VkImage in1, const std::vector<VkImage>& outN,
VkExtent2D extent, VkFormat format);

///
/// Present a context.
///
Expand Down Expand Up @@ -85,6 +104,23 @@ namespace LSFG_3_1P {
/// Block until framegen's internal Vulkan device is idle. See LSFG_3_1::waitIdle.
__attribute__((visibility("default")))
void waitIdle();

/// Debug handle to an internal image of the framegen graph.
struct DebugImage {
const char* name;
VkImage image;
VkExtent2D extent;
VkFormat format;
};

///
/// Get handles to key internal images (flow chain) for pixel dumping.
/// The images live on the graph's device in GENERAL layout.
///
/// @param id Unique identifier of the context.
///
__attribute__((visibility("default")))
std::vector<DebugImage> debugInternalImages(int32_t id);
#endif

}
23 changes: 23 additions & 0 deletions framegen/src/common/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,29 @@ void Utils::clearImage(const Core::Device& device, Core::Image& image, bool whit
&clearColor,
1, &subresourceRange);

const VkImageMemoryBarrier2 toGeneral{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
.oldLayout = image.getLayout(),
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.image = image.handle(),
.subresourceRange = {
.aspectMask = image.getAspectFlags(),
.levelCount = 1,
.layerCount = 1
}
};
const VkDependencyInfo toGeneralDep = {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &toGeneral
};
image.setLayout(VK_IMAGE_LAYOUT_GENERAL);
Utils::cmdPipelineBarrier2(cmdBuf.handle(), &toGeneralDep);

cmdBuf.end();

cmdBuf.submit(device.getComputeQueue(), fence);
Expand Down
57 changes: 56 additions & 1 deletion framegen/src/core/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "core/image.hpp"
#include "core/instance.hpp"
#include "common/exception.hpp"
#include "common/utils.hpp"

#include <cstdint>
#include <cstring>
Expand Down Expand Up @@ -534,6 +535,60 @@ Device::Device(const Instance& instance, uint64_t deviceUUID) {
if (!this->nullDescriptorSupported) {
this->fallbackDescriptorImage = std::make_shared<Core::Image>(*this,
VkExtent2D{1, 1}, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT);
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT
| VK_IMAGE_USAGE_TRANSFER_DST_BIT);
// nullDescriptor semantics require reads to return zeros; the memory
// is undefined at creation
Utils::clearImage(*this, *this->fallbackDescriptorImage);
}
}

Device::Device(const Instance& instance, VkPhysicalDevice physical,
VkDevice external, uint32_t queueFamilyIdx, VkQueue queue) {
(void)instance; // loader entry points already installed by the adopted Instance
LSFG_FRAMEGEN_LOGI("Entering Device::Device (adopted) — %s", kFramegenBuildStamp);

volkLoadDevice(external);

// The host application owns this device and picked its features; sync2
// entry points can resolve even when the feature was never enabled, and
// calling them would be invalid. Force every barrier through the sync1
// compat path, which is unconditionally legal.
vkCmdPipelineBarrier2 = nullptr;
LSFG_FRAMEGEN_LOGI(
"Adopted external device (queueFamily=%u); barriers via sync1 compat path",
queueFamilyIdx);

if (vkGetPhysicalDeviceFeatures != nullptr && vkGetPhysicalDeviceFormatProperties != nullptr) {
VkPhysicalDeviceFeatures probed{};
vkGetPhysicalDeviceFeatures(physical, &probed);
VkFormatProperties r8{};
vkGetPhysicalDeviceFormatProperties(physical, VK_FORMAT_R8_UNORM, &r8);
VkFormatProperties rgba16f{};
vkGetPhysicalDeviceFormatProperties(physical, VK_FORMAT_R16G16B16A16_SFLOAT, &rgba16f);
LSFG_FRAMEGEN_LOGI(
"Adopted-device caps: extFormats=%d readWoFmt=%d writeWoFmt=%d "
"r8unorm.optimal=0x%x rgba16f.optimal=0x%x (STORAGE_IMAGE=0x%x)",
(int)probed.shaderStorageImageExtendedFormats,
(int)probed.shaderStorageImageReadWithoutFormat,
(int)probed.shaderStorageImageWriteWithoutFormat,
r8.optimalTilingFeatures, rgba16f.optimalTilingFeatures,
VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT);
}

this->computeQueue = queue;
this->computeFamilyIdx = queueFamilyIdx;
this->physicalDevice = physical;
// Unknown whether robustness2/nullDescriptor was enabled — assume not and
// use the fallback image for optional bindings.
this->nullDescriptorSupported = false;
this->device = std::shared_ptr<VkDevice>(
new VkDevice(external),
[](VkDevice* device) { delete device; }
);
this->fallbackDescriptorImage = std::make_shared<Core::Image>(*this,
VkExtent2D{1, 1}, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT
| VK_IMAGE_USAGE_TRANSFER_DST_BIT);
Utils::clearImage(*this, *this->fallbackDescriptorImage);
}
40 changes: 40 additions & 0 deletions framegen/src/core/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,43 @@ Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format,
}

#endif // __ANDROID__

Image::Image(const Core::Device& device, VkImage externalImage,
VkExtent2D extent, VkFormat format, VkImageAspectFlags aspectFlags)
: extent(extent), format(format), aspectFlags(aspectFlags) {
const VkImageViewCreateInfo viewDesc{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = externalImage,
.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{};
auto res = vkCreateImageView(device.handle(), &viewDesc, nullptr, &viewHandle);
if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE)
throw LSFG::vulkan_error(res, "Failed to create view for wrapped image");

// Wrapped images are handed over in GENERAL layout, same convention as
// the AHB path. The image and its memory stay caller-owned.
this->layout = std::make_shared<VkImageLayout>(VK_IMAGE_LAYOUT_GENERAL);
this->image = std::shared_ptr<VkImage>(
new VkImage(externalImage),
[](VkImage* img) { delete img; }
);
this->view = std::shared_ptr<VkImageView>(
new VkImageView(viewHandle),
[dev = device.handle()](VkImageView* imgView) {
vkDestroyImageView(dev, *imgView, nullptr);
}
);
}
11 changes: 11 additions & 0 deletions framegen/src/core/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,14 @@ Instance::Instance() {
}
);
}

Instance::Instance(PFN_vkGetInstanceProcAddr gipa, VkInstance external) {
volkInitializeCustom(gipa);
volkLoadInstance(external);

// non-owning: the caller controls the instance lifetime
this->instance = std::shared_ptr<VkInstance>(
new VkInstance(external),
[](VkInstance* instance) { delete instance; }
);
}
5 changes: 5 additions & 0 deletions framegen/v3.1_include/v3_1/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ namespace LSFG_3_1 {
VkExtent2D extent, VkFormat format);
#endif

/// Single-device variant wrapping caller-owned VkImages.
Context(Vulkan& vk,
VkImage in0, VkImage in1, const std::vector<VkImage>& outN,
VkExtent2D extent, VkFormat format);

///
/// Present on the context.
///
Expand Down
Loading
Loading