blob: 1f7d7fec76a06435e56e728bb8d12f32944bbd7f [file] [log] [blame]
/*
* Copyright 2024 Rive
*/
#include "fiddle_context.hpp"
#if !defined(RIVE_VULKAN) || defined(RIVE_TOOLS_NO_GLFW)
std::unique_ptr<FiddleContext> FiddleContext::MakeVulkanPLS(FiddleContextOptions options)
{
return nullptr;
}
#else
#include "rive_vk_bootstrap/rive_vk_bootstrap.hpp"
#include "rive_vk_bootstrap/vulkan_fence_pool.hpp"
#include "rive/pls/pls_renderer.hpp"
#include "rive/pls/vulkan/pls_render_context_vulkan_impl.hpp"
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_beta.h>
#include <vk_mem_alloc.h>
using namespace rive;
using namespace rive::pls;
// +1 because PLS doesn't wait for the previous fence until partway through flush.
// (After we need to acquire a new image from the swapchain.)
static constexpr int kResourcePoolSize = pls::kBufferRingSize + 1;
class FiddleContextVulkanPLS : public FiddleContext
{
public:
FiddleContextVulkanPLS(FiddleContextOptions options) : m_options(options)
{
rive_vkb::load_vulkan();
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
m_instance = VKB_CHECK(vkb::InstanceBuilder()
.set_app_name("path_fiddle")
.set_engine_name("Rive Renderer")
#ifdef DEBUG
.set_debug_callback(rive_vkb::default_debug_callback)
#endif
.enable_validation_layers(m_options.enableVulkanValidationLayers)
.enable_extensions(glfwExtensionCount, glfwExtensions)
.build());
m_instanceDispatch = m_instance.make_table();
VulkanFeatures vulkanFeatures;
std::tie(m_physicalDevice, vulkanFeatures) = rive_vkb::select_physical_device(
vkb::PhysicalDeviceSelector(m_instance).defer_surface_initialization(),
m_options.gpuNameFilter);
m_device = VKB_CHECK(vkb::DeviceBuilder(m_physicalDevice).build());
m_vkDispatch = m_device.make_table();
m_queue = VKB_CHECK(m_device.get_queue(vkb::QueueType::graphics));
VkCommandPoolCreateInfo commandPoolCreateInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VkCommandPoolCreateFlagBits::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = *m_device.get_queue_index(vkb::QueueType::graphics),
};
VK_CHECK(m_vkDispatch.createCommandPool(&commandPoolCreateInfo, nullptr, &m_commandPool));
VkCommandBufferAllocateInfo commandBufferAllocateInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
for (VkCommandBuffer& commandBuffer : m_commandBuffers)
{
VK_CHECK(
m_vkDispatch.allocateCommandBuffers(&commandBufferAllocateInfo, &commandBuffer));
}
m_plsContext = PLSRenderContextVulkanImpl::MakeContext(m_instance,
m_physicalDevice,
m_device,
vulkanFeatures,
m_instance.fp_vkGetInstanceProcAddr,
m_instance.fp_vkGetDeviceProcAddr);
VkSemaphoreCreateInfo semaphoreCreateInfo = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
for (VkSemaphore& semaphore : m_semaphores)
{
VK_CHECK(m_vkDispatch.createSemaphore(&semaphoreCreateInfo, nullptr, &semaphore));
}
m_fencePool = make_rcp<VulkanFencePool>(ref_rcp(vk()));
}
~FiddleContextVulkanPLS()
{
// Destroy these before destroying the VkDevice.
m_plsContext.reset();
m_renderTarget.reset();
m_pixelReadBuffer.reset();
m_swapchainImageViews.clear();
m_fencePool.reset();
m_frameFence.reset();
VK_CHECK(m_vkDispatch.queueWaitIdle(m_queue));
m_vkDispatch.freeCommandBuffers(m_commandPool, kResourcePoolSize, m_commandBuffers);
m_vkDispatch.destroyCommandPool(m_commandPool, nullptr);
for (VkSemaphore semaphore : m_semaphores)
{
m_vkDispatch.destroySemaphore(semaphore, nullptr);
}
if (m_swapchain != VK_NULL_HANDLE)
{
vkb::destroy_swapchain(m_swapchain);
}
if (m_windowSurface != VK_NULL_HANDLE)
{
m_instanceDispatch.destroySurfaceKHR(m_windowSurface, nullptr);
}
vkb::destroy_device(m_device);
vkb::destroy_instance(m_instance);
}
float dpiScale(GLFWwindow* window) const override
{
#ifdef __APPLE__
return 2;
#else
return 1;
#endif
}
Factory* factory() override { return m_plsContext.get(); }
rive::pls::PLSRenderContext* plsContextOrNull() override { return m_plsContext.get(); }
rive::pls::PLSRenderTarget* plsRenderTargetOrNull() override { return m_renderTarget.get(); }
void onSizeChanged(GLFWwindow* window, int width, int height, uint32_t sampleCount) override
{
VK_CHECK(m_vkDispatch.queueWaitIdle(m_queue));
if (m_swapchain != VK_NULL_HANDLE)
{
vkb::destroy_swapchain(m_swapchain);
}
if (m_windowSurface != VK_NULL_HANDLE)
{
m_instanceDispatch.destroySurfaceKHR(m_windowSurface, nullptr);
}
VK_CHECK(glfwCreateWindowSurface(m_instance, window, nullptr, &m_windowSurface));
VkSurfaceCapabilitiesKHR windowCapabilities;
VK_CHECK(
m_instanceDispatch.fp_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice,
m_windowSurface,
&windowCapabilities));
vkb::SwapchainBuilder swapchainBuilder(m_device, m_windowSurface);
swapchainBuilder
.set_desired_format({
.format = VK_FORMAT_B8G8R8A8_UNORM,
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
})
.add_fallback_format({
.format = VK_FORMAT_R8G8B8A8_UNORM,
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
})
.set_desired_present_mode(VK_PRESENT_MODE_IMMEDIATE_KHR)
.add_fallback_present_mode(VK_PRESENT_MODE_MAILBOX_KHR)
.add_fallback_present_mode(VK_PRESENT_MODE_FIFO_RELAXED_KHR)
.add_fallback_present_mode(VK_PRESENT_MODE_FIFO_KHR);
if (windowCapabilities.supportedUsageFlags & VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT)
{
swapchainBuilder.add_image_usage_flags(VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT);
if (m_options.enableReadPixels)
{
swapchainBuilder.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
}
}
else
{
swapchainBuilder.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_SRC_BIT)
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT);
}
m_swapchain = VKB_CHECK(swapchainBuilder.build());
m_swapchainImages = *m_swapchain.get_images();
m_swapchainImageViews.clear();
m_swapchainImageViews.reserve(m_swapchainImages.size());
for (VkImage image : m_swapchainImages)
{
m_swapchainImageViews.push_back(
vk()->makeExternalTextureView(m_swapchain.image_usage_flags,
{
.image = image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = m_swapchain.image_format,
.subresourceRange =
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1,
},
}));
}
m_swapchainImageLayouts = std::vector(m_swapchainImages.size(), VK_IMAGE_LAYOUT_UNDEFINED);
m_swapchainImageIndex = 0;
m_renderTarget = impl()->makeRenderTarget(width, height, m_swapchain.image_format);
m_pixelReadBuffer = nullptr;
}
void toggleZoomWindow() override {}
std::unique_ptr<Renderer> makeRenderer(int width, int height) override
{
return std::make_unique<PLSRenderer>(m_plsContext.get());
}
void begin(const PLSRenderContext::FrameDescriptor& frameDescriptor) override
{
m_vkDispatch.acquireNextImageKHR(m_swapchain,
UINT64_MAX,
m_semaphores[m_resourcePoolIdx],
VK_NULL_HANDLE,
&m_swapchainImageIndex);
m_plsContext->beginFrame(std::move(frameDescriptor));
VkCommandBuffer commandBuffer = m_commandBuffers[m_resourcePoolIdx];
m_vkDispatch.resetCommandBuffer(commandBuffer, {});
VkCommandBufferBeginInfo commandBufferBeginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
m_vkDispatch.beginCommandBuffer(commandBuffer, &commandBufferBeginInfo);
m_renderTarget->setTargetTextureView(m_swapchainImageViews[m_swapchainImageIndex]);
insertSwapchainImageBarrier(VK_IMAGE_LAYOUT_GENERAL);
m_frameFence = m_fencePool->makeFence();
}
void flushPLSContext() final
{
m_plsContext->flush({
.renderTarget = m_renderTarget.get(),
.externalCommandBuffer = m_commandBuffers[m_resourcePoolIdx],
.frameCompletionFence = m_frameFence.get(),
});
}
void end(GLFWwindow* window, std::vector<uint8_t>* pixelData) final
{
flushPLSContext();
VkCommandBuffer commandBuffer = m_commandBuffers[m_resourcePoolIdx];
uint32_t w = m_renderTarget->width();
uint32_t h = m_renderTarget->height();
if (pixelData != nullptr)
{
// Copy the framebuffer out to a buffer.
if (m_pixelReadBuffer == nullptr)
{
m_pixelReadBuffer = vk()->makeBuffer(
{
.size = h * w * 4,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
},
vkutil::Mappability::readWrite);
}
assert(m_pixelReadBuffer->info().size == h * w * 4);
VkBufferImageCopy imageCopyDesc = {
.imageSubresource =
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageExtent = {w, h, 1},
};
insertSwapchainImageBarrier(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
m_vkDispatch.cmdCopyImageToBuffer(commandBuffer,
m_swapchainImages[m_swapchainImageIndex],
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
*m_pixelReadBuffer,
1,
&imageCopyDesc);
vk()->insertBufferMemoryBarrier(commandBuffer,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_HOST_READ_BIT,
*m_pixelReadBuffer);
}
insertSwapchainImageBarrier(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
VK_CHECK(m_vkDispatch.endCommandBuffer(commandBuffer));
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pWaitDstStageMask = &waitDstStageMask,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer,
};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &m_semaphores[m_resourcePoolIdx];
VK_CHECK(m_vkDispatch.queueSubmit(m_queue, 1, &submitInfo, *m_frameFence));
if (pixelData != nullptr)
{
// Copy the buffer containing the framebuffer contents to pixelData.
pixelData->resize(h * w * 4);
// Wait for all rendering to complete before transferring the
// framebuffer data to pixelData.
m_frameFence->wait();
assert(m_pixelReadBuffer->info().size == h * w * 4);
for (uint32_t y = 0; y < h; ++y)
{
auto src = static_cast<const uint8_t*>(m_pixelReadBuffer->contents()) + w * 4 * y;
uint8_t* dst = pixelData->data() + (h - y - 1) * w * 4;
memcpy(dst, src, w * 4);
if (m_swapchain.image_format == VK_FORMAT_B8G8R8A8_UNORM)
{
// Reverse bgr -> rgb.
for (uint32_t x = 0; x < w * 4; x += 4)
{
std::swap(dst[x], dst[x + 2]);
}
}
}
}
VkPresentInfoKHR presentInfo = {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.swapchainCount = 1,
.pSwapchains = &m_swapchain.swapchain,
.pImageIndices = &m_swapchainImageIndex,
};
m_vkDispatch.queuePresentKHR(m_queue, &presentInfo);
m_resourcePoolIdx = (m_resourcePoolIdx + 1) % kResourcePoolSize;
m_frameFence = nullptr;
}
private:
PLSRenderContextVulkanImpl* impl() const
{
return m_plsContext->static_impl_cast<PLSRenderContextVulkanImpl>();
}
VulkanContext* vk() const { return impl()->vulkanContext(); }
void insertSwapchainImageBarrier(VkImageLayout newLayout)
{
vk()->insertImageMemoryBarrier(m_commandBuffers[m_resourcePoolIdx],
m_swapchainImages[m_swapchainImageIndex],
m_swapchainImageLayouts[m_swapchainImageIndex],
newLayout);
m_swapchainImageLayouts[m_swapchainImageIndex] = newLayout;
}
const FiddleContextOptions m_options;
vkb::Instance m_instance;
vkb::InstanceDispatchTable m_instanceDispatch;
vkb::PhysicalDevice m_physicalDevice;
vkb::Device m_device;
vkb::DispatchTable m_vkDispatch;
VkQueue m_queue;
VkSurfaceKHR m_windowSurface = VK_NULL_HANDLE;
vkb::Swapchain m_swapchain;
std::vector<VkImage> m_swapchainImages;
std::vector<rcp<vkutil::TextureView>> m_swapchainImageViews;
std::vector<VkImageLayout> m_swapchainImageLayouts;
uint32_t m_swapchainImageIndex;
VkCommandPool m_commandPool;
VkCommandBuffer m_commandBuffers[kResourcePoolSize];
VkSemaphore m_semaphores[kResourcePoolSize];
rcp<VulkanFencePool> m_fencePool;
rcp<VulkanFence> m_frameFence;
std::unique_ptr<PLSRenderContext> m_plsContext;
rcp<PLSRenderTargetVulkan> m_renderTarget;
rcp<vkutil::Buffer> m_pixelReadBuffer;
int m_resourcePoolIdx = 0;
};
std::unique_ptr<FiddleContext> FiddleContext::MakeVulkanPLS(FiddleContextOptions options)
{
return std::make_unique<FiddleContextVulkanPLS>(options);
}
#endif