Added Qt vulcan renderer support to Qt/SDL version.

This commit is contained in:
harry
2026-10-10 08:25:38 -04:00
parent 584cf9ddd0
commit 127be2f0bf
8 changed files with 1006 additions and 2 deletions
+10 -1
View File
@@ -12,7 +12,16 @@
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"environment": [
{
"name": "QT_QPA_PLATFORM",
"value": "xcb"
},
{
"name": "QT_QPA_PLATFORMTHEME",
"value": "gtk2"
}
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
+18
View File
@@ -37,6 +37,8 @@ endif()
option(FCEU_PROFILER_ENABLE "Enable the built-in FCEU performance profiler" OFF)
find_package(Vulkan QUIET)
if ( ${FCEU_PROFILER_ENABLE} )
message( STATUS "FCEU Profiler Enabled")
add_definitions( -D__FCEU_PROFILER_ENABLE__ )
@@ -711,6 +713,17 @@ set(SRC_DRIVERS_SDL
set(SOURCES ${SRC_CORE} ${SRC_DRIVERS_COMMON} ${SRC_DRIVERS_SDL})
include(CheckCXXSourceCompiles)
if (Vulkan_FOUND)
set(CMAKE_REQUIRED_LIBRARIES ${Qt}::Gui Vulkan::Vulkan)
check_cxx_source_compiles("#include <QVulkanInstance>\nint main() { QVulkanInstance instance; return instance.isValid(); }" FCEUX_QT_HAS_VULKAN)
unset(CMAKE_REQUIRED_LIBRARIES)
endif()
if (Vulkan_FOUND AND FCEUX_QT_HAS_VULKAN)
list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/ConsoleViewerVulkan.cpp)
endif()
# Put build timestamp into BUILD_TS environment variable and from there into
# the FCEUX_BUILD_TIMESTAMP preprocessor definition.
# Note: with CMake >= 3.8.0, this will respect SOURCE_DATE_EPOCH. For more info,
@@ -766,6 +779,11 @@ target_link_libraries( ${APP_NAME}
${SYS_LIBS}
)
if (Vulkan_FOUND AND FCEUX_QT_HAS_VULKAN)
target_compile_definitions(${APP_NAME} PRIVATE FCEUX_HAS_QT_VULKAN)
target_link_libraries(${APP_NAME} Vulkan::Vulkan)
endif()
if(NOT MSVC)
# Add minizip subdirectory so #include<unzip.h> works without the "minizip" subdirectory in the directive
target_include_directories(${APP_NAME} PRIVATE ${MINIZIP_INCLUDE_DIRS}/minizip)
+3
View File
@@ -94,6 +94,9 @@ ConsoleVideoConfDialog_t::ConsoleVideoConfDialog_t(QWidget *parent)
driverSelect->addItem( tr("OpenGL"), ConsoleViewerBase::VIDEO_DRIVER_OPENGL );
driverSelect->addItem( tr("SDL"), ConsoleViewerBase::VIDEO_DRIVER_SDL );
driverSelect->addItem( tr("QPainter"), ConsoleViewerBase::VIDEO_DRIVER_QPAINTER );
#ifdef FCEUX_HAS_QT_VULKAN
driverSelect->addItem( tr("Vulkan"), ConsoleViewerBase::VIDEO_DRIVER_VULKAN );
#endif
hbox1 = new QHBoxLayout();
+2 -1
View File
@@ -14,7 +14,8 @@ class ConsoleViewerBase
{
VIDEO_DRIVER_OPENGL = 0,
VIDEO_DRIVER_SDL,
VIDEO_DRIVER_QPAINTER
VIDEO_DRIVER_QPAINTER,
VIDEO_DRIVER_VULKAN
};
virtual int init(void) = 0;
virtual void reset(void) = 0;
+844
View File
@@ -0,0 +1,844 @@
#include <algorithm>
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <vector>
#include <QExposeEvent>
#include <QGuiApplication>
#include <QMouseEvent>
#include <QResizeEvent>
#include <QTimer>
#include <QVBoxLayout>
#include <QWindow>
#include <vulkan/vulkan.h>
#include <QVulkanInstance>
#include "Qt/ConsoleViewerVulkan.h"
#include "Qt/ConsoleUtilities.h"
#include "Qt/ConsoleWindow.h"
#include "Qt/fceuWrapper.h"
#include "Qt/nes_shm.h"
#include "Qt/throttle.h"
extern unsigned int gui_draw_area_width;
extern unsigned int gui_draw_area_height;
class ConsoleVulkanWindow_t : public QWindow
{
public:
ConsoleVulkanWindow_t()
{
setSurfaceType(QSurface::VulkanSurface);
resize(256, 224);
instanceReady = instance.create();
if (instanceReady)
setVulkanInstance(&instance);
}
~ConsoleVulkanWindow_t() override
{
cleanup();
}
void shutdown()
{
exposedCallback = {};
cleanup();
}
bool initialize(QString &error)
{
if (!instanceReady)
{
error = QStringLiteral("Could not create a Vulkan instance");
return false;
}
create();
surface = instance.surfaceForWindow(this);
if (surface == VK_NULL_HANDLE || !selectDevice(error) || !createDevice(error) ||
!createCommandPool(error) || !createSync(error) || !recreateSwapchain(error))
{
cleanup();
return false;
}
return true;
}
void setVsync(bool enabled)
{
if (vsyncEnabled != enabled)
{
vsyncEnabled = enabled;
swapchainDirty = true;
}
}
void setExposedCallback(std::function<void()> callback)
{
exposedCallback = std::move(callback);
}
void render(const uint32_t *pixels, int width, int height, const QColor &background,
bool forceAspect, double aspectX, double aspectY, bool linearFilter)
{
if (!isExposed() || !device || !pixels || width <= 0 || height <= 0)
return;
if (swapchainDirty && !recreateSwapchain(lastError))
return;
if (!swapchain || !ensureTexture(width, height) || !ensureStaging(width, height))
return;
uint32_t *mapped = nullptr;
if (vkMapMemory(device, stagingMemory, 0, stagingSize, 0, (void **)&mapped) != VK_SUCCESS)
return;
memcpy(mapped, pixels, (size_t)width * height * sizeof(uint32_t));
vkUnmapMemory(device, stagingMemory);
uint32_t imageIndex = 0;
VkResult result = vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
imageAvailable, VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR)
{
swapchainDirty = true;
return;
}
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
return;
vkResetCommandBuffer(commandBuffer, 0);
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS)
return;
VkImageMemoryBarrier textureToTransfer{};
textureToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
textureToTransfer.srcAccessMask = textureInitialized ? VK_ACCESS_TRANSFER_READ_BIT : 0;
textureToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
textureToTransfer.oldLayout = textureInitialized ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
textureToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
textureToTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
textureToTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
textureToTransfer.image = textureImage;
textureToTransfer.subresourceRange = colorRange();
vkCmdPipelineBarrier(commandBuffer,
textureInitialized ? VK_PIPELINE_STAGE_TRANSFER_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &textureToTransfer);
VkBufferImageCopy copy{};
copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy.imageSubresource.layerCount = 1;
copy.imageExtent = { (uint32_t)width, (uint32_t)height, 1 };
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, textureImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);
VkImageMemoryBarrier textureToSource = textureToTransfer;
textureToSource.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
textureToSource.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
textureToSource.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
textureToSource.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &textureToSource);
textureInitialized = true;
VkImage image = swapchainImages[imageIndex];
VkImageMemoryBarrier targetToTransfer{};
targetToTransfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
targetToTransfer.srcAccessMask = 0;
targetToTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
targetToTransfer.oldLayout = swapchainLayouts[imageIndex];
targetToTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
targetToTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
targetToTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
targetToTransfer.image = image;
targetToTransfer.subresourceRange = colorRange();
vkCmdPipelineBarrier(commandBuffer,
targetToTransfer.oldLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT : VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &targetToTransfer);
VkClearColorValue clearColor{};
clearColor.float32[0] = background.redF();
clearColor.float32[1] = background.greenF();
clearColor.float32[2] = background.blueF();
clearColor.float32[3] = 1.0f;
VkImageSubresourceRange range = colorRange();
vkCmdClearColorImage(commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
&clearColor, 1, &range);
int x = 0, y = 0, drawWidth = (int)swapchainExtent.width, drawHeight = (int)swapchainExtent.height;
if (forceAspect && aspectX > 0.0 && aspectY > 0.0)
{
double ratio = aspectX / aspectY;
if ((double)drawWidth / drawHeight > ratio)
drawWidth = (int)(drawHeight * ratio);
else
drawHeight = (int)(drawWidth / ratio);
x = ((int)swapchainExtent.width - drawWidth) / 2;
y = ((int)swapchainExtent.height - drawHeight) / 2;
}
VkImageBlit blit{};
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.srcSubresource.layerCount = 1;
blit.srcOffsets[1] = { width, height, 1 };
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.dstSubresource.layerCount = 1;
blit.dstOffsets[0] = { x, y, 0 };
blit.dstOffsets[1] = { x + drawWidth, y + drawHeight, 1 };
vkCmdBlitImage(commandBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit,
linearFilter ? VK_FILTER_LINEAR : VK_FILTER_NEAREST);
VkImageMemoryBarrier targetToPresent = targetToTransfer;
targetToPresent.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
targetToPresent.dstAccessMask = 0;
targetToPresent.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
targetToPresent.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &targetToPresent);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
return;
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkSubmitInfo submit{};
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit.waitSemaphoreCount = 1;
submit.pWaitSemaphores = &imageAvailable;
submit.pWaitDstStageMask = &waitStage;
submit.commandBufferCount = 1;
submit.pCommandBuffers = &commandBuffer;
submit.signalSemaphoreCount = 1;
submit.pSignalSemaphores = &renderFinished;
if (vkQueueSubmit(queue, 1, &submit, VK_NULL_HANDLE) != VK_SUCCESS)
return;
VkPresentInfoKHR present{};
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.waitSemaphoreCount = 1;
present.pWaitSemaphores = &renderFinished;
present.swapchainCount = 1;
present.pSwapchains = &swapchain;
present.pImageIndices = &imageIndex;
result = vkQueuePresentKHR(queue, &present);
vkQueueWaitIdle(queue);
swapchainLayouts[imageIndex] = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
{
videoBufferSwapMark();
if (nes_shm)
nes_shm->render_count++;
}
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
swapchainDirty = true;
}
void setLinearFilter(bool enabled) { linearFilter = enabled; }
void setBackground(const QColor &color) { background = color; }
unsigned int mouseButtons() const { return mouseButtonMask; }
const QString &errorString() const { return lastError; }
protected:
void exposeEvent(QExposeEvent *event) override
{
QWindow::exposeEvent(event);
if (isExposed())
{
swapchainDirty = true;
if (exposedCallback)
exposedCallback();
}
}
void resizeEvent(QResizeEvent *event) override
{
QWindow::resizeEvent(event);
swapchainDirty = true;
}
void mousePressEvent(QMouseEvent *event) override
{
mouseButtonMask = event->buttons();
event->accept();
}
void mouseReleaseEvent(QMouseEvent *event) override
{
mouseButtonMask = event->buttons();
event->accept();
}
private:
VkImageSubresourceRange colorRange() const
{
VkImageSubresourceRange range{};
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
range.levelCount = 1;
range.layerCount = 1;
return range;
}
bool selectDevice(QString &error)
{
uint32_t count = 0;
vkEnumeratePhysicalDevices(instance.vkInstance(), &count, nullptr);
std::vector<VkPhysicalDevice> devices(count);
if (!count || vkEnumeratePhysicalDevices(instance.vkInstance(), &count, devices.data()) != VK_SUCCESS)
{
error = QStringLiteral("No Vulkan-capable device was found");
return false;
}
for (VkPhysicalDevice candidate : devices)
{
uint32_t familyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(candidate, &familyCount, nullptr);
std::vector<VkQueueFamilyProperties> families(familyCount);
vkGetPhysicalDeviceQueueFamilyProperties(candidate, &familyCount, families.data());
for (uint32_t i = 0; i < familyCount; ++i)
{
VkBool32 presentSupport = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(candidate, i, surface, &presentSupport);
if ((families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && presentSupport)
{
physicalDevice = candidate;
queueFamily = i;
return true;
}
}
}
error = QStringLiteral("No Vulkan queue supports graphics and presentation");
return false;
}
bool createDevice(QString &error)
{
float priority = 1.0f;
VkDeviceQueueCreateInfo queueInfo{};
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueInfo.queueFamilyIndex = queueFamily;
queueInfo.queueCount = 1;
queueInfo.pQueuePriorities = &priority;
const char *extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
VkDeviceCreateInfo deviceInfo{};
deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceInfo.queueCreateInfoCount = 1;
deviceInfo.pQueueCreateInfos = &queueInfo;
deviceInfo.enabledExtensionCount = 1;
deviceInfo.ppEnabledExtensionNames = extensions;
if (vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device) != VK_SUCCESS)
{
error = QStringLiteral("Could not create a Vulkan device with swapchain support");
return false;
}
vkGetDeviceQueue(device, queueFamily, 0, &queue);
return true;
}
bool createCommandPool(QString &error)
{
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = queueFamily;
if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS)
{
error = QStringLiteral("Could not create Vulkan command pool");
return false;
}
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;
return vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) == VK_SUCCESS;
}
bool createSync(QString &error)
{
VkSemaphoreCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
if (vkCreateSemaphore(device, &info, nullptr, &imageAvailable) != VK_SUCCESS ||
vkCreateSemaphore(device, &info, nullptr, &renderFinished) != VK_SUCCESS)
{
error = QStringLiteral("Could not create Vulkan synchronization objects");
return false;
}
return true;
}
bool memoryType(uint32_t typeBits, VkMemoryPropertyFlags flags, uint32_t &index)
{
VkPhysicalDeviceMemoryProperties properties{};
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &properties);
for (uint32_t i = 0; i < properties.memoryTypeCount; ++i)
{
if ((typeBits & (1u << i)) &&
(properties.memoryTypes[i].propertyFlags & flags) == flags)
{
index = i;
return true;
}
}
return false;
}
bool recreateSwapchain(QString &error)
{
if (!device || size().width() <= 0 || size().height() <= 0)
return false;
vkDeviceWaitIdle(device);
VkSurfaceCapabilitiesKHR caps{};
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &caps) != VK_SUCCESS ||
!(caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT))
{
error = QStringLiteral("Vulkan surface does not support transfer destination images");
return false;
}
uint32_t formatCount = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, nullptr);
std::vector<VkSurfaceFormatKHR> formats(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, formats.data());
if (formats.empty())
{
error = QStringLiteral("Vulkan surface has no supported formats");
return false;
}
surfaceFormat = formats.front();
for (const auto &candidate : formats)
{
if (candidate.format == VK_FORMAT_B8G8R8A8_UNORM || candidate.format == VK_FORMAT_B8G8R8A8_SRGB)
{
surfaceFormat = candidate;
break;
}
}
if (textureImage && textureFormat != surfaceFormat.format)
destroyTexture();
textureFormat = surfaceFormat.format;
if (caps.currentExtent.width != UINT32_MAX)
swapchainExtent = caps.currentExtent;
else
{
swapchainExtent.width = std::clamp((uint32_t)size().width(), caps.minImageExtent.width, caps.maxImageExtent.width);
swapchainExtent.height = std::clamp((uint32_t)size().height(), caps.minImageExtent.height, caps.maxImageExtent.height);
}
uint32_t imageCount = caps.minImageCount + 1;
if (caps.maxImageCount && imageCount > caps.maxImageCount)
imageCount = caps.maxImageCount;
uint32_t modeCount = 0;
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &modeCount, nullptr);
std::vector<VkPresentModeKHR> modes(modeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &modeCount, modes.data());
VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
if (!vsyncEnabled && std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR) != modes.end())
mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
VkSwapchainCreateInfoKHR info{};
info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
info.surface = surface;
info.minImageCount = imageCount;
info.imageFormat = surfaceFormat.format;
info.imageColorSpace = surfaceFormat.colorSpace;
info.imageExtent = swapchainExtent;
info.imageArrayLayers = 1;
info.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.preTransform = caps.currentTransform;
VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
if (!(caps.supportedCompositeAlpha & compositeAlpha))
{
const VkCompositeAlphaFlagBitsKHR options[] = {
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
};
for (VkCompositeAlphaFlagBitsKHR option : options)
{
if (caps.supportedCompositeAlpha & option)
{
compositeAlpha = option;
break;
}
}
}
info.compositeAlpha = compositeAlpha;
info.presentMode = mode;
info.clipped = VK_TRUE;
info.oldSwapchain = swapchain;
VkSwapchainKHR newSwapchain = VK_NULL_HANDLE;
if (vkCreateSwapchainKHR(device, &info, nullptr, &newSwapchain) != VK_SUCCESS)
{
error = QStringLiteral("Could not create a Vulkan swapchain");
return false;
}
if (swapchain)
vkDestroySwapchainKHR(device, swapchain, nullptr);
swapchain = newSwapchain;
vkGetSwapchainImagesKHR(device, swapchain, &imageCount, nullptr);
swapchainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapchain, &imageCount, swapchainImages.data());
swapchainLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED);
swapchainDirty = false;
return true;
}
bool ensureTexture(int imageWidth, int imageHeight)
{
if (textureImage && textureWidth == imageWidth && textureHeight == imageHeight)
return true;
vkDeviceWaitIdle(device);
destroyTexture();
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = textureFormat;
imageInfo.extent = { (uint32_t)imageWidth, (uint32_t)imageHeight, 1 };
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS)
return false;
VkMemoryRequirements requirements{};
vkGetImageMemoryRequirements(device, textureImage, &requirements);
uint32_t index = 0;
if (!memoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, index))
return false;
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = requirements.size;
allocInfo.memoryTypeIndex = index;
if (vkAllocateMemory(device, &allocInfo, nullptr, &textureMemory) != VK_SUCCESS ||
vkBindImageMemory(device, textureImage, textureMemory, 0) != VK_SUCCESS)
return false;
textureWidth = imageWidth;
textureHeight = imageHeight;
textureInitialized = false;
return true;
}
bool ensureStaging(int imageWidth, int imageHeight)
{
VkDeviceSize needed = (VkDeviceSize)imageWidth * imageHeight * sizeof(uint32_t);
if (stagingBuffer && stagingSize >= needed)
return true;
destroyStaging();
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = needed;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(device, &bufferInfo, nullptr, &stagingBuffer) != VK_SUCCESS)
return false;
VkMemoryRequirements requirements{};
vkGetBufferMemoryRequirements(device, stagingBuffer, &requirements);
uint32_t index = 0;
if (!memoryType(requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, index))
return false;
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = requirements.size;
allocInfo.memoryTypeIndex = index;
if (vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory) != VK_SUCCESS ||
vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0) != VK_SUCCESS)
return false;
stagingSize = needed;
return true;
}
void destroyTexture()
{
if (textureImage) vkDestroyImage(device, textureImage, nullptr);
if (textureMemory) vkFreeMemory(device, textureMemory, nullptr);
textureImage = VK_NULL_HANDLE;
textureMemory = VK_NULL_HANDLE;
textureWidth = textureHeight = 0;
textureInitialized = false;
}
void destroyStaging()
{
if (stagingBuffer) vkDestroyBuffer(device, stagingBuffer, nullptr);
if (stagingMemory) vkFreeMemory(device, stagingMemory, nullptr);
stagingBuffer = VK_NULL_HANDLE;
stagingMemory = VK_NULL_HANDLE;
stagingSize = 0;
}
void cleanup()
{
if (device)
{
vkDeviceWaitIdle(device);
destroyTexture();
destroyStaging();
if (swapchain) vkDestroySwapchainKHR(device, swapchain, nullptr);
if (imageAvailable) vkDestroySemaphore(device, imageAvailable, nullptr);
if (renderFinished) vkDestroySemaphore(device, renderFinished, nullptr);
if (commandPool) vkDestroyCommandPool(device, commandPool, nullptr);
vkDestroyDevice(device, nullptr);
}
device = VK_NULL_HANDLE;
if (instance.isValid())
{
QWindow::destroy();
surface = VK_NULL_HANDLE;
instance.destroy();
}
surface = VK_NULL_HANDLE;
}
QVulkanInstance instance;
bool instanceReady = false;
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
VkQueue queue = VK_NULL_HANDLE;
uint32_t queueFamily = 0;
VkCommandPool commandPool = VK_NULL_HANDLE;
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
VkSemaphore imageAvailable = VK_NULL_HANDLE;
VkSemaphore renderFinished = VK_NULL_HANDLE;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
VkSurfaceFormatKHR surfaceFormat{};
VkExtent2D swapchainExtent{};
std::vector<VkImage> swapchainImages;
std::vector<VkImageLayout> swapchainLayouts;
VkFormat textureFormat = VK_FORMAT_UNDEFINED;
VkImage textureImage = VK_NULL_HANDLE;
VkDeviceMemory textureMemory = VK_NULL_HANDLE;
int textureWidth = 0;
int textureHeight = 0;
bool textureInitialized = false;
VkBuffer stagingBuffer = VK_NULL_HANDLE;
VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
VkDeviceSize stagingSize = 0;
bool swapchainDirty = true;
bool vsyncEnabled = true;
bool linearFilter = false;
unsigned int mouseButtonMask = 0;
QColor background = Qt::black;
QString lastError;
std::function<void()> exposedCallback;
};
ConsoleViewVulkan_t::ConsoleViewVulkan_t(QWidget *parent)
: QWidget(parent), vulkanWindow(new ConsoleVulkanWindow_t), windowContainer(nullptr),
bgColor(nullptr), localBuf(nullptr), localBufSize(0), aspectRatio(1.0),
aspectX(1.0), aspectY(1.0), xscale(2.0), yscale(2.0), sx(0), sy(0), rw(256), rh(224),
forceAspect(true), autoScaleEna(true), linearFilter(false), vsyncEnabled(true), mouseButtonMask(0), drawQueued(false)
{
consoleWin_t *win = qobject_cast<consoleWin_t *>(parent);
if (win)
{
bgColor = win->getVideoBgColorPtr();
bgColor->setRgb(0, 0, 0);
}
QWidget::setMinimumSize(256, 224);
setFocusPolicy(Qt::StrongFocus);
localBufSize = (4 * GL_NES_WIDTH) * (4 * GL_NES_HEIGHT) * sizeof(uint32_t);
localBuf = (uint32_t *)malloc(localBufSize);
if (localBuf)
memset32(localBuf, alphaMask, localBufSize);
if (g_config)
{
int option = 0;
g_config->getOption("SDL.OpenGLip", &option);
linearFilter = option != 0;
g_config->getOption("SDL.AutoScale", &option);
autoScaleEna = option != 0;
g_config->getOption("SDL.XScale", &xscale);
g_config->getOption("SDL.YScale", &yscale);
g_config->getOption("SDL.ForceAspect", &forceAspect);
g_config->getOption("SDL.VideoVsync", &vsyncEnabled);
if (bgColor)
fceuLoadConfigColor("SDL.VideoBgColor", bgColor);
}
windowContainer = QWidget::createWindowContainer(vulkanWindow, this);
vulkanWindow->setExposedCallback([this]() { queueRedraw(); });
windowContainer->setFocusPolicy(Qt::StrongFocus);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(windowContainer);
}
ConsoleViewVulkan_t::~ConsoleViewVulkan_t()
{
shutdown();
if (localBuf)
free(localBuf);
}
void ConsoleViewVulkan_t::shutdown(void)
{
if (vulkanWindow)
vulkanWindow->shutdown();
}
int ConsoleViewVulkan_t::init(void)
{
QString error;
if (!vulkanWindow->initialize(error))
{
fprintf(stderr, "FCEUX: Vulkan renderer initialization failed: %s\n",
error.toLocal8Bit().constData());
return -1;
}
return 0;
}
void ConsoleViewVulkan_t::reset(void)
{
queueRedraw();
}
void ConsoleViewVulkan_t::queueRedraw(void)
{
if (drawQueued.exchange(true))
return;
QTimer::singleShot(0, this, [this]() {
drawQueued.store(false);
if (!vulkanWindow || !localBuf)
return;
int frameWidth = GL_NES_WIDTH;
int frameHeight = GL_NES_HEIGHT;
if (nes_shm && nes_shm->video.ncol > 0 && nes_shm->video.nrow > 0)
{
frameWidth = nes_shm->video.ncol;
frameHeight = nes_shm->video.nrow;
}
if (bgColor)
vulkanWindow->setBackground(*bgColor);
vulkanWindow->setLinearFilter(linearFilter);
vulkanWindow->render(localBuf, frameWidth, frameHeight,
bgColor ? *bgColor : QColor(Qt::black), forceAspect, aspectX, aspectY, linearFilter);
});
}
void ConsoleViewVulkan_t::transfer2LocalBuffer(void)
{
if (!localBuf || !nes_shm)
return;
int bufferIndex = nes_shm->pixBufIdx - 1;
if (bufferIndex < 0)
bufferIndex = NES_VIDEO_BUFLEN - 1;
unsigned int copySize = nes_shm->video.ncol * nes_shm->video.nrow * 4;
copySize = std::min(copySize, localBufSize);
uint8_t *source = (uint8_t *)nes_shm->pixbuf[bufferIndex];
if ((nes_shm->video.preScaler == 1) || (nes_shm->video.preScaler == 4))
{
for (unsigned int i = 0; i < copySize / 4; ++i)
{
localBuf[i] = (source[0]) | (source[1] << 8) | (source[2] << 16) | 0xff000000;
source += 4;
}
}
else
copyPixels32(localBuf, source, copySize, alphaMask);
}
void ConsoleViewVulkan_t::setVsyncEnable(bool enabled)
{
vsyncEnabled = enabled;
if (vulkanWindow)
vulkanWindow->setVsync(enabled);
}
void ConsoleViewVulkan_t::setLinearFilterEnable(bool enabled)
{
linearFilter = enabled;
if (vulkanWindow)
vulkanWindow->setLinearFilter(enabled);
}
void ConsoleViewVulkan_t::setScaleXY(double xs, double ys)
{
xscale = xs;
yscale = ys;
if (forceAspect)
xscale = yscale = std::min(xscale, yscale);
}
void ConsoleViewVulkan_t::setAspectXY(double x, double y)
{
aspectX = x;
aspectY = y;
aspectRatio = y / x;
}
void ConsoleViewVulkan_t::getAspectXY(double &x, double &y)
{
x = aspectX;
y = aspectY;
}
void ConsoleViewVulkan_t::setCursor(const QCursor &cursor)
{
QWidget::setCursor(cursor);
if (windowContainer)
windowContainer->setCursor(cursor);
}
void ConsoleViewVulkan_t::setCursor(Qt::CursorShape shape)
{
QWidget::setCursor(shape);
if (windowContainer)
windowContainer->setCursor(shape);
}
void ConsoleViewVulkan_t::setBgColor(QColor &color)
{
if (bgColor)
*bgColor = color;
if (vulkanWindow)
vulkanWindow->setBackground(color);
}
void ConsoleViewVulkan_t::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
gui_draw_area_width = event->size().width();
gui_draw_area_height = event->size().height();
}
void ConsoleViewVulkan_t::calculateViewport(int frameWidth, int frameHeight, int &x, int &y, int &w, int &h)
{
w = width();
h = height();
if (frameWidth <= 0 || frameHeight <= 0)
return;
if (forceAspect && aspectX > 0.0 && aspectY > 0.0)
{
double ratio = aspectX / aspectY;
if ((double)w / h > ratio)
w = (int)(h * ratio);
else
h = (int)(w / ratio);
}
x = (width() - w) / 2;
y = (height() - h) / 2;
sx = x;
sy = y;
rw = w;
rh = h;
}
void ConsoleViewVulkan_t::getNormalizedCursorPos(double &x, double &y)
{
QPoint pos = mapFromGlobal(QCursor::pos());
int frameWidth = nes_shm ? nes_shm->video.ncol : 256;
int frameHeight = nes_shm ? nes_shm->video.nrow : 224;
int viewX, viewY, viewWidth, viewHeight;
calculateViewport(frameWidth, frameHeight, viewX, viewY, viewWidth, viewHeight);
x = std::clamp((double)(pos.x() - viewX) / std::max(1, viewWidth), 0.0, 1.0);
y = std::clamp((double)(pos.y() - viewY) / std::max(1, viewHeight), 0.0, 1.0);
}
bool ConsoleViewVulkan_t::getMouseButtonState(unsigned int button)
{
mouseButtonMask = vulkanWindow ? vulkanWindow->mouseButtons() : mouseButtonMask;
return (mouseButtonMask & button) != 0;
}
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include <QColor>
#include <QWidget>
#include <atomic>
#include "Qt/ConsoleViewerInterface.h"
class ConsoleVulkanWindow_t;
class ConsoleViewVulkan_t : public QWidget, public ConsoleViewerBase
{
Q_OBJECT
public:
ConsoleViewVulkan_t(QWidget *parent = nullptr);
~ConsoleViewVulkan_t() override;
void shutdown(void);
int init(void) override;
void reset(void) override;
void queueRedraw(void) override;
int driver(void) override { return VIDEO_DRIVER_VULKAN; }
void transfer2LocalBuffer(void) override;
void setVsyncEnable(bool ena) override;
void setLinearFilterEnable(bool ena) override;
bool getForceAspectOpt(void) override { return forceAspect; }
void setForceAspectOpt(bool val) override { forceAspect = val; }
bool getAutoScaleOpt(void) override { return autoScaleEna; }
void setAutoScaleOpt(bool val) override { autoScaleEna = val; }
double getScaleX(void) override { return xscale; }
double getScaleY(void) override { return yscale; }
void setScaleXY(double xs, double ys) override;
void getNormalizedCursorPos(double &x, double &y) override;
bool getMouseButtonState(unsigned int btn) override;
void setAspectXY(double x, double y) override;
void getAspectXY(double &x, double &y) override;
double getAspectRatio(void) override { return aspectRatio; }
void setCursor(const QCursor &c) override;
void setCursor(Qt::CursorShape s) override;
void setBgColor(QColor &c) override;
QSize size(void) override { return QWidget::size(); }
QCursor cursor(void) override { return QWidget::cursor(); }
void setMinimumSize(const QSize &s) override { QWidget::setMinimumSize(s); }
void setMaximumSize(const QSize &s) override { QWidget::setMaximumSize(s); }
protected:
void resizeEvent(QResizeEvent *event) override;
private:
void calculateViewport(int frameWidth, int frameHeight, int &x, int &y, int &w, int &h);
ConsoleVulkanWindow_t *vulkanWindow;
QWidget *windowContainer;
QColor *bgColor;
uint32_t *localBuf;
uint32_t localBufSize;
double aspectRatio;
double aspectX;
double aspectY;
double xscale;
double yscale;
int sx;
int sy;
int rw;
int rh;
bool forceAspect;
bool autoScaleEna;
bool linearFilter;
bool vsyncEnabled;
unsigned int mouseButtonMask;
std::atomic_bool drawQueued;
};
+50
View File
@@ -157,6 +157,9 @@ consoleWin_t::consoleWin_t(QWidget *parent)
viewport_GL = NULL;
viewport_SDL = NULL;
viewport_QWidget = NULL;
#ifdef FCEUX_HAS_QT_VULKAN
viewport_Vulkan = NULL;
#endif
viewport_Interface = NULL;
contextMenuEnable = false;
@@ -2145,6 +2148,22 @@ int consoleWin_t::unloadVideoDriver(void)
viewport_QWidget = NULL;
}
#ifdef FCEUX_HAS_QT_VULKAN
if (viewport_Vulkan != NULL)
{
if (viewport_Vulkan == centralWidget())
{
takeCentralWidget();
}
else
{
printf("Error: Central Widget Failed!\n");
}
viewport_Vulkan->shutdown();
viewport_Vulkan->deleteLater();
viewport_Vulkan = NULL;
}
#endif
return 0;
}
//---------------------------------------------------------------------------
@@ -2182,6 +2201,16 @@ void consoleWin_t::videoDriverDestroyed(QObject* obj)
}
viewport_QWidget = NULL;
}
#ifdef FCEUX_HAS_QT_VULKAN
if (viewport_Vulkan == obj)
{
if (viewport_Interface == static_cast<ConsoleViewerBase*>(viewport_Vulkan))
{
viewport_Interface = NULL;
}
viewport_Vulkan = NULL;
}
#endif
printf("Video Driver Destroyed: %p\n", obj);
//printf("viewport_GL: %p\n", viewport_GL);
//printf("viewport_SDL: %p\n", viewport_SDL);
@@ -2258,6 +2287,27 @@ int consoleWin_t::loadVideoDriver( int driverId, bool force )
connect( viewport_GL, SIGNAL(destroyed(QObject*)), this, SLOT(videoDriverDestroyed(QObject*)) );
}
break;
#ifdef FCEUX_HAS_QT_VULKAN
case ConsoleViewerBase::VIDEO_DRIVER_VULKAN:
{
viewport_Vulkan = new ConsoleViewVulkan_t(this);
viewport_Interface = static_cast<ConsoleViewerBase*>(viewport_Vulkan);
setCentralWidget(viewport_Vulkan);
setViewportAspect();
connect(viewport_Vulkan, SIGNAL(destroyed(QObject*)), this, SLOT(videoDriverDestroyed(QObject*)));
if (viewport_Vulkan->init() != 0)
{
unloadVideoDriver();
if (g_config)
{
g_config->setOption("SDL.VideoDriver", (int)ConsoleViewerBase::VIDEO_DRIVER_QPAINTER);
g_config->save();
}
return loadVideoDriver(ConsoleViewerBase::VIDEO_DRIVER_QPAINTER);
}
}
break;
#endif
default:
case ConsoleViewerBase::VIDEO_DRIVER_QPAINTER:
{
+6
View File
@@ -33,6 +33,9 @@
#include "Qt/ConsoleViewerGL.h"
#include "Qt/ConsoleViewerSDL.h"
#include "Qt/ConsoleViewerQWidget.h"
#ifdef FCEUX_HAS_QT_VULKAN
#include "Qt/ConsoleViewerVulkan.h"
#endif
#include "Qt/GamePadConf.h"
#include "Qt/AviRecord.h"
@@ -132,6 +135,9 @@ class consoleWin_t : public QMainWindow
ConsoleViewGL_t *viewport_GL;
ConsoleViewSDL_t *viewport_SDL;
ConsoleViewQWidget_t *viewport_QWidget;
#ifdef FCEUX_HAS_QT_VULKAN
ConsoleViewVulkan_t *viewport_Vulkan;
#endif
ConsoleViewerBase *viewport_Interface;
void setCyclePeriodms( int ms );