mirror of
https://github.com/Toastconcern/FrameStudio.git
synced 2026-10-28 23:03:21 +01:00
Frame Studio now builds for Windows (x64, x86, ARM64), Linux (x64, x86, ARM64)
and macOS (Apple Silicon, Intel):
- studio/platform.h with Win32 and POSIX implementations (dialogs through
tinyfiledialogs on Linux/macOS), a GLFW window beside the Win32 one, and a
platform-neutral frame pacer
- MoltenVK support in the renderer (portability enumeration) and bundled in
the macOS app
- 32-bit builds: large-address-aware, LZMA packing within a 32-bit address space
- unaligned file reads in the loaders go through loadUnaligned (no UB)
- scripts/build-studio.sh, studio/build_windows.bat [x64|x86|arm64],
cmake/llvm-mingw.cmake, and a GitHub Actions workflow building every target
Frame Home installs without Studio: frame/install-frame-home.{sh,ps1} and
"Install Frame Home.bat" (packaged as FrameHome-installer.zip).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
187 lines
9.7 KiB
C++
187 lines
9.7 KiB
C++
// The compositor layer's import of Frame Home's subsampled frames, without SteamVR: one device exports a subsampled
|
|
// 2-layer image the way HslRenderer::createExportImages makes it, a second device (VK_EXT_fragment_density_map on,
|
|
// its fragmentDensityMap feature off, as the layer makes vrcompositor's) imports it the way frame_home_layer.cpp does
|
|
// and writes it into the descriptor sets the layer draws with: each slot's plain and subsampled set from one pool.
|
|
// subsample_import_test [combined-image-sampler pool count = 3 * slots] exit 0: every step worked
|
|
// Build (on the Frame): g++ -O2 -std=c++17 subsample_import_test.cpp -o subsample_import_test -lvulkan
|
|
#include <vulkan/vulkan.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
constexpr uint32_t kSlots = 4, kSize = 2416, kLayers = 2;
|
|
constexpr VkFormat kFormat = VK_FORMAT_R8G8B8A8_SRGB;
|
|
constexpr VkImageUsageFlags kUsage = 0x17; // as Frame Home's exports in direct mode
|
|
|
|
bool check(VkResult r, const char* what) {
|
|
std::printf("%-58s %s (%d)\n", what, r == VK_SUCCESS ? "ok" : "FAILED", static_cast<int>(r));
|
|
return r == VK_SUCCESS;
|
|
}
|
|
|
|
VkDevice makeDevice(VkPhysicalDevice pd, uint32_t family) {
|
|
const float priority = 1;
|
|
VkDeviceQueueCreateInfo q = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
|
q.queueFamilyIndex = family;
|
|
q.queueCount = 1;
|
|
q.pQueuePriorities = &priority;
|
|
const char* exts[] = {VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
|
|
VK_EXT_FRAGMENT_DENSITY_MAP_EXTENSION_NAME};
|
|
VkDeviceCreateInfo ci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
|
ci.queueCreateInfoCount = 1;
|
|
ci.pQueueCreateInfos = &q;
|
|
ci.enabledExtensionCount = 3;
|
|
ci.ppEnabledExtensionNames = exts;
|
|
VkDevice d = VK_NULL_HANDLE;
|
|
return check(vkCreateDevice(pd, &ci, nullptr, &d), "create device (FDM extension, feature off)") ? d : VK_NULL_HANDLE;
|
|
}
|
|
|
|
VkImageCreateInfo exportImageInfo(const void* next) {
|
|
VkImageCreateInfo ci = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
|
ci.pNext = next;
|
|
ci.flags = VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT;
|
|
ci.imageType = VK_IMAGE_TYPE_2D;
|
|
ci.format = kFormat;
|
|
ci.extent = {kSize, kSize, 1};
|
|
ci.mipLevels = 1;
|
|
ci.arrayLayers = kLayers;
|
|
ci.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
ci.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
ci.usage = kUsage;
|
|
ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
return ci;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
const uint32_t samplerCount = argc > 1 ? static_cast<uint32_t>(std::atoi(argv[1])) : 3 * kSlots;
|
|
VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
|
|
app.apiVersion = VK_API_VERSION_1_1;
|
|
VkInstanceCreateInfo ici = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
|
ici.pApplicationInfo = &app;
|
|
VkInstance inst;
|
|
if (!check(vkCreateInstance(&ici, nullptr, &inst), "create instance")) return 1;
|
|
uint32_t n = 1;
|
|
VkPhysicalDevice pd;
|
|
vkEnumeratePhysicalDevices(inst, &n, &pd);
|
|
const VkDevice exporter = makeDevice(pd, 0), importer = makeDevice(pd, 0);
|
|
if (!exporter || !importer) return 1;
|
|
auto getFd = reinterpret_cast<PFN_vkGetMemoryFdKHR>(vkGetDeviceProcAddr(exporter, "vkGetMemoryFdKHR"));
|
|
|
|
// Frame Home's side: the exported images.
|
|
VkExternalMemoryImageCreateInfo ext = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO};
|
|
ext.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
|
|
VkImageCreateInfo eci = exportImageInfo(&ext);
|
|
int fds[kSlots];
|
|
VkDeviceSize sizes[kSlots];
|
|
uint32_t memoryType = 0;
|
|
for (uint32_t i = 0; i < kSlots; ++i) {
|
|
VkImage img;
|
|
if (!check(vkCreateImage(exporter, &eci, nullptr, &img), "export: create subsampled image")) return 1;
|
|
VkMemoryRequirements req;
|
|
vkGetImageMemoryRequirements(exporter, img, &req);
|
|
VkPhysicalDeviceMemoryProperties mp;
|
|
vkGetPhysicalDeviceMemoryProperties(pd, &mp);
|
|
for (memoryType = 0; memoryType < mp.memoryTypeCount; ++memoryType)
|
|
if ((req.memoryTypeBits & (1u << memoryType)) && (mp.memoryTypes[memoryType].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) break;
|
|
VkMemoryDedicatedAllocateInfo ded = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO};
|
|
ded.image = img;
|
|
VkExportMemoryAllocateInfo exp = {VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO};
|
|
exp.pNext = &ded;
|
|
exp.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
|
|
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
ai.pNext = &exp;
|
|
ai.allocationSize = sizes[i] = req.size;
|
|
ai.memoryTypeIndex = memoryType;
|
|
VkDeviceMemory mem;
|
|
if (!check(vkAllocateMemory(exporter, &ai, nullptr, &mem), "export: allocate") ||
|
|
!check(vkBindImageMemory(exporter, img, mem, 0), "export: bind")) return 1;
|
|
VkMemoryGetFdInfoKHR gi = {VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR};
|
|
gi.memory = mem;
|
|
gi.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
|
|
if (!check(getFd(exporter, &gi, &fds[i]), "export: fd")) return 1;
|
|
}
|
|
|
|
// The layer's side (ensureBase): both kinds of set layout, one pool, a set of each kind per slot.
|
|
VkSamplerCreateInfo si = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
|
si.flags = VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT;
|
|
si.magFilter = si.minFilter = VK_FILTER_LINEAR;
|
|
si.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
si.addressModeU = si.addressModeV = si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
VkSampler subsampledSampler;
|
|
if (!check(vkCreateSampler(importer, &si, nullptr, &subsampledSampler), "layer: subsampled sampler")) return 1;
|
|
VkDescriptorSetLayoutBinding b[2] = {};
|
|
b[0].binding = 0; b[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; b[0].descriptorCount = 1; b[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
b[1].binding = 1; b[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; b[1].descriptorCount = 1; b[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
VkDescriptorSetLayoutCreateInfo li = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
|
li.bindingCount = 2;
|
|
li.pBindings = b;
|
|
VkDescriptorSetLayout plainLayout, subsampledLayout;
|
|
if (!check(vkCreateDescriptorSetLayout(importer, &li, nullptr, &plainLayout), "layer: plain set layout")) return 1;
|
|
b[0].pImmutableSamplers = &subsampledSampler;
|
|
if (!check(vkCreateDescriptorSetLayout(importer, &li, nullptr, &subsampledLayout), "layer: subsampled set layout")) return 1;
|
|
VkDescriptorPoolSize ps[2] = {{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, samplerCount}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2 * kSlots}};
|
|
VkDescriptorPoolCreateInfo pi = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
|
pi.maxSets = 2 * kSlots;
|
|
pi.poolSizeCount = 2;
|
|
pi.pPoolSizes = ps;
|
|
VkDescriptorPool pool;
|
|
std::printf("pool: %u combined image samplers\n", samplerCount);
|
|
if (!check(vkCreateDescriptorPool(importer, &pi, nullptr, &pool), "layer: pool")) return 1;
|
|
VkDescriptorSet plainSets[kSlots], subsampledSets[kSlots];
|
|
for (uint32_t i = 0; i < kSlots; ++i) {
|
|
VkDescriptorSetAllocateInfo dai = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
|
dai.descriptorPool = pool;
|
|
dai.descriptorSetCount = 1;
|
|
dai.pSetLayouts = &plainLayout;
|
|
if (!check(vkAllocateDescriptorSets(importer, &dai, &plainSets[i]), "layer: plain set")) return 2;
|
|
dai.pSetLayouts = &subsampledLayout;
|
|
if (!check(vkAllocateDescriptorSets(importer, &dai, &subsampledSets[i]), "layer: subsampled set")) return 2;
|
|
}
|
|
|
|
// The layer's import: the same image, the memory from the fd, a view, the subsampled set written.
|
|
VkImageCreateInfo ici2 = exportImageInfo(&ext);
|
|
for (uint32_t i = 0; i < kSlots; ++i) {
|
|
VkImage img;
|
|
if (!check(vkCreateImage(importer, &ici2, nullptr, &img), "import: create image with the Hello's flags")) return 1;
|
|
VkMemoryRequirements req;
|
|
vkGetImageMemoryRequirements(importer, img, &req);
|
|
if (req.size > sizes[i]) { std::printf("import: needs %llu bytes, exported %llu: FAILED\n", (unsigned long long)req.size, (unsigned long long)sizes[i]); return 1; }
|
|
VkImportMemoryFdInfoKHR imp = {VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR};
|
|
imp.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
|
|
imp.fd = fds[i];
|
|
VkMemoryDedicatedAllocateInfo ded = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO};
|
|
ded.image = img;
|
|
imp.pNext = &ded;
|
|
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
ai.pNext = &imp;
|
|
ai.allocationSize = sizes[i];
|
|
ai.memoryTypeIndex = memoryType;
|
|
VkDeviceMemory mem;
|
|
if (!check(vkAllocateMemory(importer, &ai, nullptr, &mem), "import: allocate from fd") ||
|
|
!check(vkBindImageMemory(importer, img, mem, 0), "import: bind")) return 1;
|
|
VkImageViewCreateInfo vi = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
vi.image = img;
|
|
vi.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
vi.format = kFormat;
|
|
vi.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, kLayers};
|
|
VkImageView view;
|
|
if (!check(vkCreateImageView(importer, &vi, nullptr, &view), "import: view")) return 1;
|
|
VkDescriptorImageInfo ii = {VK_NULL_HANDLE, view, VK_IMAGE_LAYOUT_GENERAL};
|
|
VkWriteDescriptorSet w = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
|
w.dstSet = subsampledSets[i];
|
|
w.dstBinding = 0;
|
|
w.descriptorCount = 1;
|
|
w.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
w.pImageInfo = ⅈ
|
|
vkUpdateDescriptorSets(importer, 1, &w, 0, nullptr);
|
|
std::printf("%-58s ok\n", "import: subsampled set written");
|
|
}
|
|
std::printf("ALL OK\n");
|
|
return 0;
|
|
}
|