Compare commits
1 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
661a7f4d3c |
@@ -21,6 +21,8 @@ option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(ENABLE_VULKAN "Enables Vulkan backend" OFF)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
@@ -321,6 +323,10 @@ if (ENABLE_QT)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
|
||||
endif()
|
||||
|
||||
if (ENABLE_VULKAN)
|
||||
find_package(Vulkan REQUIRED)
|
||||
endif()
|
||||
|
||||
# Platform-specific library requirements
|
||||
# ======================================
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
||||
CLS(Render) \
|
||||
SUB(Render, Software) \
|
||||
SUB(Render, OpenGL) \
|
||||
SUB(Render, Vulkan) \
|
||||
CLS(Audio) \
|
||||
SUB(Audio, DSP) \
|
||||
SUB(Audio, Sink) \
|
||||
|
||||
@@ -112,6 +112,7 @@ enum class Class : ClassType {
|
||||
Render, ///< Emulator video output and hardware acceleration
|
||||
Render_Software, ///< Software renderer backend
|
||||
Render_OpenGL, ///< OpenGL backend
|
||||
Render_Vulkan, ///< Vulkan backend
|
||||
Audio, ///< Audio emulation
|
||||
Audio_DSP, ///< The HLE implementation of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
|
||||
@@ -52,6 +52,12 @@ public:
|
||||
/// Releases (dunno if this is the "right" word) the GLFW context from the caller thread
|
||||
virtual void DoneCurrent() = 0;
|
||||
|
||||
/// Returns if window is shown (not minimized)
|
||||
virtual bool IsShown() const = 0;
|
||||
|
||||
/// Retrieves Vulkan specific handlers from the window
|
||||
virtual void RetrieveVulkanHandlers(void** instance, void** surface) const = 0;
|
||||
|
||||
/**
|
||||
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
|
||||
* @param framebuffer_x Framebuffer x-coordinate that was pressed
|
||||
|
||||
@@ -343,6 +343,11 @@ struct TouchscreenInput {
|
||||
u32 rotation_angle;
|
||||
};
|
||||
|
||||
enum class RendererBackend {
|
||||
OpenGL = 0,
|
||||
Vulkan = 1,
|
||||
};
|
||||
|
||||
struct Values {
|
||||
// System
|
||||
bool use_docked_mode;
|
||||
@@ -380,6 +385,9 @@ struct Values {
|
||||
std::string sdmc_dir;
|
||||
|
||||
// Renderer
|
||||
RendererBackend renderer_backend;
|
||||
int vulkan_device;
|
||||
|
||||
float resolution_factor;
|
||||
bool use_frame_limit;
|
||||
u16 frame_limit;
|
||||
|
||||
@@ -68,7 +68,27 @@ add_library(video_core STATIC
|
||||
video_core.h
|
||||
)
|
||||
|
||||
if (Vulkan_FOUND)
|
||||
target_sources(video_core PRIVATE
|
||||
renderer_vulkan/renderer_vulkan.cpp
|
||||
renderer_vulkan/renderer_vulkan.h
|
||||
renderer_vulkan/vk_rasterizer.cpp
|
||||
renderer_vulkan/vk_rasterizer.h
|
||||
renderer_vulkan/vk_swapchain.cpp
|
||||
renderer_vulkan/vk_swapchain.h
|
||||
renderer_vulkan/vk_sync.cpp
|
||||
renderer_vulkan/vk_sync.h
|
||||
renderer_vulkan/vk_helper.cpp
|
||||
renderer_vulkan/vk_helper.h)
|
||||
|
||||
target_include_directories(video_core PRIVATE Vulkan::Vulkan)
|
||||
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(video_core)
|
||||
|
||||
target_link_libraries(video_core PUBLIC common core)
|
||||
target_link_libraries(video_core PRIVATE glad)
|
||||
if (Vulkan_FOUND)
|
||||
target_link_libraries(video_core PRIVATE Vulkan::Vulkan)
|
||||
endif()
|
||||
|
||||
295
src/video_core/renderer_vulkan/renderer_vulkan.cpp
Normal file
295
src/video_core/renderer_vulkan/renderer_vulkan.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/renderer_vulkan/vk_helper.h"
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||
#include "video_core/renderer_vulkan/vk_sync.h"
|
||||
#include "video_core/utils.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& window) : RendererBase(window) {}
|
||||
|
||||
RendererVulkan::~RendererVulkan() {
|
||||
ShutDown();
|
||||
}
|
||||
|
||||
void RendererVulkan::SwapBuffers(
|
||||
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) {
|
||||
|
||||
Core::System::GetInstance().GetPerfStats().EndSystemFrame();
|
||||
|
||||
const auto& layout = render_window.GetFramebufferLayout();
|
||||
if (framebuffer && layout.width > 0 && layout.height > 0 && render_window.IsShown()) {
|
||||
if (swapchain->HasFramebufferChanged(layout)) {
|
||||
sync->DestroyCommandBuffers();
|
||||
swapchain->Create(layout.width, layout.height);
|
||||
}
|
||||
|
||||
const u32 image_index = swapchain->AcquireNextImage(present_semaphore);
|
||||
DrawScreen(*framebuffer, image_index);
|
||||
|
||||
vk::Semaphore render_semaphore = sync->QuerySemaphore();
|
||||
swapchain->QueuePresent(present_queue, image_index, present_semaphore, render_semaphore);
|
||||
|
||||
render_window.SwapBuffers();
|
||||
}
|
||||
|
||||
render_window.PollEvents();
|
||||
|
||||
Core::System::GetInstance().FrameLimiter().DoFrameLimiting(CoreTiming::GetGlobalTimeUs());
|
||||
Core::System::GetInstance().GetPerfStats().BeginSystemFrame();
|
||||
}
|
||||
|
||||
bool RendererVulkan::Init() {
|
||||
CreateRasterizer();
|
||||
return InitVulkanObjects();
|
||||
}
|
||||
|
||||
void RendererVulkan::ShutDown() {
|
||||
device.waitIdle();
|
||||
|
||||
// Always destroy sync before swapchain because sync will countain fences used by swapchain.
|
||||
sync.reset();
|
||||
swapchain.reset();
|
||||
|
||||
device.destroy(screen_info.staging_image);
|
||||
device.free(screen_info.staging_memory);
|
||||
|
||||
device.destroy(present_semaphore);
|
||||
device.destroy();
|
||||
}
|
||||
|
||||
void RendererVulkan::CreateRasterizer() {
|
||||
if (rasterizer) {
|
||||
return;
|
||||
}
|
||||
rasterizer = std::make_unique<RasterizerVulkan>(render_window, screen_info);
|
||||
}
|
||||
|
||||
bool RendererVulkan::InitVulkanObjects() {
|
||||
render_window.RetrieveVulkanHandlers(reinterpret_cast<void**>(&instance),
|
||||
reinterpret_cast<void**>(&surface));
|
||||
if (!PickPhysicalDevice()) {
|
||||
return false;
|
||||
}
|
||||
if (!CreateLogicalDevice()) {
|
||||
return false;
|
||||
}
|
||||
const auto& framebuffer = render_window.GetFramebufferLayout();
|
||||
swapchain = std::make_unique<VulkanSwapchain>(surface, physical_device, device,
|
||||
graphics_family_index, present_family_index);
|
||||
swapchain->Create(framebuffer.width, framebuffer.height);
|
||||
|
||||
sync = std::make_unique<VulkanSync>(device, graphics_queue, graphics_family_index);
|
||||
|
||||
if (device.createSemaphore(&vk::SemaphoreCreateInfo(), nullptr, &present_semaphore) !=
|
||||
vk::Result::eSuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RendererVulkan::PickPhysicalDevice() {
|
||||
u32 device_count{};
|
||||
if (instance.enumeratePhysicalDevices(&device_count, nullptr) != vk::Result::eSuccess ||
|
||||
device_count == 0) {
|
||||
LOG_ERROR(Render_Vulkan, "No Vulkan devices found!");
|
||||
return false;
|
||||
}
|
||||
std::vector<vk::PhysicalDevice> devices(device_count);
|
||||
instance.enumeratePhysicalDevices(&device_count, devices.data());
|
||||
|
||||
s32 device_index = Settings::values.vulkan_device;
|
||||
if (device_index < 0 || device_index >= static_cast<s32>(device_count)) {
|
||||
LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
|
||||
return false;
|
||||
}
|
||||
physical_device = devices[device_index];
|
||||
|
||||
if (!IsDeviceSuitable(physical_device)) {
|
||||
LOG_ERROR(Render_Vulkan, "Device {} is not suitable!", device_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<vk::QueueFamilyProperties> queue_families{
|
||||
physical_device.getQueueFamilyProperties()};
|
||||
int i{};
|
||||
for (const auto& queue_family : queue_families) {
|
||||
if (queue_family.queueCount > 0) {
|
||||
if (queue_family.queueFlags & vk::QueueFlagBits::eGraphics) {
|
||||
graphics_family_index = i;
|
||||
}
|
||||
if (physical_device.getSurfaceSupportKHR(i, surface)) {
|
||||
present_family_index = i;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (graphics_family_index == UndefinedFamily || present_family_index == UndefinedFamily) {
|
||||
LOG_ERROR(Render_Vulkan, "Device has not enough queues!");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO(Render_Vulkan, "{}", physical_device.getProperties().deviceName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RendererVulkan::CreateLogicalDevice() {
|
||||
const float queue_priorities{1.f};
|
||||
std::vector<vk::DeviceQueueCreateInfo> queue_cis{GetDeviceQueueCreateInfos(&queue_priorities)};
|
||||
|
||||
vk::PhysicalDeviceFeatures device_features{};
|
||||
|
||||
std::vector<const char*> extensions{};
|
||||
extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
|
||||
vk::DeviceCreateInfo device_ci({}, static_cast<u32>(queue_cis.size()), queue_cis.data(), 0,
|
||||
nullptr, static_cast<u32>(extensions.size()), extensions.data(),
|
||||
&device_features);
|
||||
if (physical_device.createDevice(&device_ci, nullptr, &device) != vk::Result::eSuccess) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Device failed to be created!");
|
||||
return false;
|
||||
}
|
||||
|
||||
graphics_queue = device.getQueue(graphics_family_index, 0);
|
||||
present_queue = device.getQueue(present_family_index, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RendererVulkan::IsDeviceSuitable(vk::PhysicalDevice physical_device) const {
|
||||
// TODO(Rodrigo): Query suitability before creating logical device
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<vk::DeviceQueueCreateInfo> RendererVulkan::GetDeviceQueueCreateInfos(
|
||||
const float* queue_priority) const {
|
||||
std::vector<vk::DeviceQueueCreateInfo> queue_cis;
|
||||
std::set<u32> unique_queue_families = {graphics_family_index, present_family_index};
|
||||
|
||||
for (u32 queue_family : unique_queue_families) {
|
||||
VkDeviceQueueCreateInfo queue_ci{};
|
||||
queue_ci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queue_ci.queueFamilyIndex = queue_family;
|
||||
queue_ci.queueCount = 1;
|
||||
queue_ci.pQueuePriorities = queue_priority;
|
||||
queue_cis.push_back(queue_ci);
|
||||
}
|
||||
return queue_cis;
|
||||
}
|
||||
|
||||
void RendererVulkan::DrawScreen(const Tegra::FramebufferConfig& framebuffer, u32 image_index) {
|
||||
const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)};
|
||||
const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
|
||||
const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
|
||||
const vk::Extent2D& framebuffer_size{swapchain->GetSize()};
|
||||
|
||||
if (rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool recreate{!screen_info.staging_image || framebuffer.width != screen_info.width ||
|
||||
framebuffer.height != screen_info.height};
|
||||
if (recreate) {
|
||||
if (screen_info.staging_image || screen_info.staging_memory) {
|
||||
ASSERT(screen_info.staging_image && screen_info.staging_memory);
|
||||
|
||||
// Wait to avoid using staging memory while it's being transfered
|
||||
device.waitIdle();
|
||||
device.destroy(screen_info.staging_image);
|
||||
device.free(screen_info.staging_memory);
|
||||
}
|
||||
|
||||
const vk::ImageCreateInfo image_ci(
|
||||
{}, vk::ImageType::e2D, vk::Format::eR8G8B8A8Unorm,
|
||||
{framebuffer.width, framebuffer.height, 1}, 1, 1, vk::SampleCountFlagBits::e1,
|
||||
vk::ImageTiling::eLinear, vk::ImageUsageFlagBits::eTransferSrc,
|
||||
vk::SharingMode::eExclusive, 0, nullptr, vk::ImageLayout::ePreinitialized);
|
||||
screen_info.staging_image = device.createImage(image_ci);
|
||||
|
||||
vk::MemoryRequirements mem_reqs{
|
||||
device.getImageMemoryRequirements(screen_info.staging_image)};
|
||||
|
||||
const auto memory_type = FindMemoryType(physical_device, mem_reqs.memoryTypeBits,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||
if (!memory_type) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Couldn't find a suitable memory type!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
// TODO(Rodrigo): Use a memory allocator
|
||||
screen_info.staging_memory = device.allocateMemory({mem_reqs.size, *memory_type});
|
||||
|
||||
device.bindImageMemory(screen_info.staging_image, screen_info.staging_memory, 0);
|
||||
|
||||
screen_info.width = framebuffer.width;
|
||||
screen_info.height = framebuffer.height;
|
||||
screen_info.size_in_bytes = mem_reqs.size;
|
||||
}
|
||||
|
||||
Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes, Memory::FlushMode::Flush);
|
||||
|
||||
void* data = device.mapMemory(screen_info.staging_memory, 0, screen_info.size_in_bytes, {});
|
||||
|
||||
VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4,
|
||||
Memory::GetPointer(framebuffer_addr), static_cast<u8*>(data),
|
||||
true);
|
||||
device.unmapMemory(screen_info.staging_memory);
|
||||
|
||||
// Record blitting
|
||||
vk::CommandBuffer cmdbuf{sync->BeginRecord()};
|
||||
|
||||
if (recreate) {
|
||||
SetImageLayout(cmdbuf, screen_info.staging_image, vk::ImageAspectFlagBits::eColor,
|
||||
vk::ImageLayout::ePreinitialized, vk::ImageLayout::eGeneral,
|
||||
vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer);
|
||||
}
|
||||
SetImageLayout(cmdbuf, swapchain->GetImage(image_index), vk::ImageAspectFlagBits::eColor,
|
||||
vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer);
|
||||
|
||||
// TODO(Rodrigo): Use clip values
|
||||
const bool flip_y =
|
||||
framebuffer.transform_flags == Tegra::FramebufferConfig::TransformFlags::FlipV;
|
||||
const s32 y0 = flip_y ? static_cast<s32>(framebuffer_size.height) : 0;
|
||||
const s32 y1 = flip_y ? 0 : static_cast<s32>(framebuffer_size.height);
|
||||
vk::ImageSubresourceLayers subresource(vk::ImageAspectFlagBits::eColor, 0, 0, 1);
|
||||
std::array<vk::Offset3D, 2> src_offsets, dst_offsets;
|
||||
src_offsets[0] = {0, 0, 0};
|
||||
src_offsets[1] = {static_cast<s32>(screen_info.width), static_cast<s32>(screen_info.height), 1};
|
||||
dst_offsets[0] = {0, y0, 0};
|
||||
dst_offsets[1] = {static_cast<s32>(framebuffer_size.width), y1, 1};
|
||||
const vk::ImageBlit blit(subresource, src_offsets, subresource, dst_offsets);
|
||||
|
||||
cmdbuf.blitImage(screen_info.staging_image, vk::ImageLayout::eGeneral,
|
||||
swapchain->GetImage(image_index), vk::ImageLayout::eTransferDstOptimal, {blit},
|
||||
vk::Filter::eLinear);
|
||||
|
||||
SetImageLayout(cmdbuf, swapchain->GetImage(image_index), vk::ImageAspectFlagBits::eColor,
|
||||
vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::ePresentSrcKHR,
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer);
|
||||
|
||||
sync->EndRecord(cmdbuf);
|
||||
sync->Execute(swapchain->GetFence(image_index));
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
80
src/video_core/renderer_vulkan/renderer_vulkan.h
Normal file
80
src/video_core/renderer_vulkan/renderer_vulkan.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
constexpr auto UndefinedSize = std::numeric_limits<u32>::max();
|
||||
constexpr auto UndefinedFamily = std::numeric_limits<u32>::max();
|
||||
|
||||
constexpr auto WaitTimeout = std::numeric_limits<u64>::max();
|
||||
|
||||
class VulkanSwapchain;
|
||||
class VulkanSync;
|
||||
|
||||
struct VulkanScreenInfo {
|
||||
u32 width{};
|
||||
u32 height{};
|
||||
u64 size_in_bytes{};
|
||||
vk::Image staging_image;
|
||||
vk::DeviceMemory staging_memory;
|
||||
};
|
||||
|
||||
class RendererVulkan : public VideoCore::RendererBase {
|
||||
public:
|
||||
explicit RendererVulkan(Core::Frontend::EmuWindow& window);
|
||||
~RendererVulkan() override;
|
||||
|
||||
/// Swap buffers (render frame)
|
||||
void SwapBuffers(
|
||||
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
|
||||
|
||||
/// Initialize the renderer
|
||||
bool Init() override;
|
||||
|
||||
/// Shutdown the renderer
|
||||
void ShutDown() override;
|
||||
|
||||
private:
|
||||
void CreateRasterizer();
|
||||
bool InitVulkanObjects();
|
||||
bool PickPhysicalDevice();
|
||||
bool CreateLogicalDevice();
|
||||
|
||||
bool IsDeviceSuitable(vk::PhysicalDevice physical_device) const;
|
||||
|
||||
std::vector<vk::DeviceQueueCreateInfo> GetDeviceQueueCreateInfos(
|
||||
const float* queue_priority) const;
|
||||
|
||||
void DrawScreen(const Tegra::FramebufferConfig& framebuffer, u32 image_index);
|
||||
|
||||
vk::Instance instance;
|
||||
vk::SurfaceKHR surface;
|
||||
|
||||
vk::PhysicalDevice physical_device;
|
||||
vk::Device device;
|
||||
|
||||
u32 graphics_family_index = UndefinedFamily;
|
||||
u32 present_family_index = UndefinedFamily;
|
||||
|
||||
vk::Queue graphics_queue{};
|
||||
vk::Queue present_queue{};
|
||||
|
||||
VulkanScreenInfo screen_info{};
|
||||
|
||||
std::unique_ptr<VulkanSwapchain> swapchain;
|
||||
|
||||
std::unique_ptr<VulkanSync> sync;
|
||||
|
||||
vk::Semaphore present_semaphore;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
93
src/video_core/renderer_vulkan/vk_helper.cpp
Normal file
93
src/video_core/renderer_vulkan/vk_helper.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <optional>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "video_core/renderer_vulkan/vk_helper.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
std::optional<u32> FindMemoryType(vk::PhysicalDevice device, u32 type_filter,
|
||||
vk::MemoryPropertyFlags properties) {
|
||||
vk::PhysicalDeviceMemoryProperties props{device.getMemoryProperties()};
|
||||
for (u32 i = 0; i < props.memoryTypeCount; i++) {
|
||||
if ((type_filter & (1 << i)) &&
|
||||
(props.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// Couldn't find a suitable memory type
|
||||
return {};
|
||||
}
|
||||
|
||||
void SetImageLayout(vk::CommandBuffer cmdbuf, vk::Image image, vk::ImageLayout old_image_layout,
|
||||
vk::ImageLayout new_image_layout, vk::ImageSubresourceRange subresource_range,
|
||||
vk::PipelineStageFlags src_stage_mask, vk::PipelineStageFlags dst_stage_mask,
|
||||
u32 src_family, u32 dst_family) {
|
||||
vk::ImageMemoryBarrier barrier({}, {}, old_image_layout, new_image_layout, src_family,
|
||||
dst_family, image, subresource_range);
|
||||
|
||||
switch (old_image_layout) {
|
||||
case vk::ImageLayout::eUndefined:
|
||||
barrier.srcAccessMask = {};
|
||||
break;
|
||||
case vk::ImageLayout::ePreinitialized:
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eHostWrite;
|
||||
break;
|
||||
case vk::ImageLayout::eColorAttachmentOptimal:
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
break;
|
||||
case vk::ImageLayout::eDepthStencilAttachmentOptimal:
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite;
|
||||
break;
|
||||
case vk::ImageLayout::eTransferSrcOptimal:
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
|
||||
break;
|
||||
case vk::ImageLayout::eTransferDstOptimal:
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
break;
|
||||
case vk::ImageLayout::eShaderReadOnlyOptimal:
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (new_image_layout) {
|
||||
case vk::ImageLayout::eTransferDstOptimal:
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
break;
|
||||
case vk::ImageLayout::eTransferSrcOptimal:
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
|
||||
break;
|
||||
case vk::ImageLayout::eColorAttachmentOptimal:
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
break;
|
||||
case vk::ImageLayout::eDepthStencilAttachmentOptimal:
|
||||
barrier.dstAccessMask |= vk::AccessFlagBits::eDepthStencilAttachmentWrite;
|
||||
break;
|
||||
case vk::ImageLayout::eShaderReadOnlyOptimal:
|
||||
if (barrier.srcAccessMask == static_cast<vk::AccessFlagBits>(0)) {
|
||||
barrier.srcAccessMask =
|
||||
vk::AccessFlagBits::eHostWrite | vk::AccessFlagBits::eTransferWrite;
|
||||
}
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cmdbuf.pipelineBarrier(src_stage_mask, dst_stage_mask, {}, {}, {}, {barrier});
|
||||
}
|
||||
|
||||
void SetImageLayout(vk::CommandBuffer cmdbuf, vk::Image image, vk::ImageAspectFlags aspect_mask,
|
||||
vk::ImageLayout old_image_layout, vk::ImageLayout new_image_layout,
|
||||
vk::PipelineStageFlags src_stage_mask, vk::PipelineStageFlags dst_stage_mask,
|
||||
u32 src_family, u32 dst_family) {
|
||||
vk::ImageSubresourceRange subresource_range(aspect_mask, 0, 1, 0, 1);
|
||||
SetImageLayout(cmdbuf, image, old_image_layout, new_image_layout, subresource_range,
|
||||
src_stage_mask, dst_stage_mask, src_family, dst_family);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
28
src/video_core/renderer_vulkan/vk_helper.h
Normal file
28
src/video_core/renderer_vulkan/vk_helper.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
std::optional<u32> FindMemoryType(vk::PhysicalDevice device, u32 type_filter,
|
||||
vk::MemoryPropertyFlags properties);
|
||||
|
||||
void SetImageLayout(vk::CommandBuffer cmdbuf, vk::Image image, vk::ImageLayout old_image_layout,
|
||||
vk::ImageLayout new_image_layout, vk::ImageSubresourceRange subresource_range,
|
||||
vk::PipelineStageFlags src_stage_mask, vk::PipelineStageFlags dst_stage_mask,
|
||||
u32 src_family = VK_QUEUE_FAMILY_IGNORED,
|
||||
u32 dst_family = VK_QUEUE_FAMILY_IGNORED);
|
||||
|
||||
void SetImageLayout(vk::CommandBuffer cmdbuf, vk::Image image, vk::ImageAspectFlags aspect_mask,
|
||||
vk::ImageLayout old_image_layout, vk::ImageLayout new_image_layout,
|
||||
vk::PipelineStageFlags src_stage_mask, vk::PipelineStageFlags dst_stage_mask,
|
||||
u32 src_family = VK_QUEUE_FAMILY_IGNORED,
|
||||
u32 dst_family = VK_QUEUE_FAMILY_IGNORED);
|
||||
|
||||
} // namespace Vulkan
|
||||
26
src/video_core/renderer_vulkan/vk_rasterizer.cpp
Normal file
26
src/video_core/renderer_vulkan/vk_rasterizer.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& renderer, VulkanScreenInfo& info)
|
||||
: VideoCore::RasterizerInterface() {}
|
||||
|
||||
RasterizerVulkan::~RasterizerVulkan() = default;
|
||||
|
||||
void RasterizerVulkan::DrawArrays() {}
|
||||
|
||||
void RasterizerVulkan::Clear() {}
|
||||
|
||||
void RasterizerVulkan::FlushAll() {}
|
||||
|
||||
void RasterizerVulkan::FlushRegion(Tegra::GPUVAddr addr, u64 size) {}
|
||||
|
||||
void RasterizerVulkan::InvalidateRegion(Tegra::GPUVAddr addr, u64 size) {}
|
||||
|
||||
void RasterizerVulkan::FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) {}
|
||||
|
||||
} // namespace Vulkan
|
||||
30
src/video_core/renderer_vulkan/vk_rasterizer.h
Normal file
30
src/video_core/renderer_vulkan/vk_rasterizer.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
struct VulkanScreenInfo;
|
||||
|
||||
class RasterizerVulkan : public VideoCore::RasterizerInterface {
|
||||
public:
|
||||
explicit RasterizerVulkan(Core::Frontend::EmuWindow& renderer, VulkanScreenInfo& info);
|
||||
~RasterizerVulkan() override;
|
||||
|
||||
void DrawArrays() override;
|
||||
void Clear() override;
|
||||
void FlushAll() override;
|
||||
void FlushRegion(Tegra::GPUVAddr addr, u64 size) override;
|
||||
void InvalidateRegion(Tegra::GPUVAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) override;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
238
src/video_core/renderer_vulkan/vk_swapchain.cpp
Normal file
238
src/video_core/renderer_vulkan/vk_swapchain.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
static vk::SurfaceFormatKHR ChooseSwapSurfaceFormat(
|
||||
const std::vector<vk::SurfaceFormatKHR>& formats) {
|
||||
if (formats.size() == 1 && formats[0].format == vk::Format::eUndefined) {
|
||||
return {vk::Format::eB8G8R8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||
}
|
||||
|
||||
const auto& found = std::find_if(formats.begin(), formats.end(), [](const auto& format) {
|
||||
return format.format == vk::Format::eB8G8R8A8Unorm &&
|
||||
format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear;
|
||||
});
|
||||
return found != formats.end() ? *found : formats[0];
|
||||
}
|
||||
|
||||
static vk::PresentModeKHR ChooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& modes) {
|
||||
// Mailbox doesn't lock the application like fifo (vsync), prefer it
|
||||
const auto& found = std::find_if(modes.begin(), modes.end(), [](const auto& mode) {
|
||||
return mode == vk::PresentModeKHR::eMailbox;
|
||||
});
|
||||
return found != modes.end() ? *found : vk::PresentModeKHR::eFifo;
|
||||
}
|
||||
|
||||
static vk::Extent2D ChooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width,
|
||||
u32 height) {
|
||||
if (capabilities.currentExtent.width != UndefinedSize) {
|
||||
return capabilities.currentExtent;
|
||||
}
|
||||
vk::Extent2D extent = {width, height};
|
||||
extent.width = std::max(capabilities.minImageExtent.width,
|
||||
std::min(capabilities.maxImageExtent.width, extent.width));
|
||||
extent.height = std::max(capabilities.minImageExtent.height,
|
||||
std::min(capabilities.maxImageExtent.height, extent.height));
|
||||
return extent;
|
||||
}
|
||||
|
||||
VulkanSwapchain::VulkanSwapchain(vk::SurfaceKHR& surface, vk::PhysicalDevice& physical_device,
|
||||
vk::Device& device, const u32& graphics_family,
|
||||
const u32& present_family)
|
||||
: surface(surface), physical_device(physical_device), device(device),
|
||||
graphics_family(graphics_family), present_family(present_family) {}
|
||||
|
||||
VulkanSwapchain::~VulkanSwapchain() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void VulkanSwapchain::Create(u32 width, u32 height) {
|
||||
const vk::SurfaceCapabilitiesKHR capabilities{
|
||||
physical_device.getSurfaceCapabilitiesKHR(surface)};
|
||||
if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
device.waitIdle();
|
||||
Destroy();
|
||||
|
||||
CreateSwapchain(width, height, capabilities);
|
||||
CreateImageViews();
|
||||
CreateRenderPass();
|
||||
CreateFramebuffers();
|
||||
CreateFences();
|
||||
}
|
||||
|
||||
u32 VulkanSwapchain::AcquireNextImage(vk::Semaphore present_complete) {
|
||||
u32 image_index{};
|
||||
vk::Result result{
|
||||
device.acquireNextImageKHR(*handle, WaitTimeout, present_complete, {}, &image_index)};
|
||||
if (result == vk::Result::eErrorOutOfDateKHR) {
|
||||
if (current_width > 0 && current_height > 0) {
|
||||
Create(current_width, current_height);
|
||||
}
|
||||
} else if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Failed to acquire swapchain image!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
device.waitForFences(1, &fences[image_index].get(), false, WaitTimeout);
|
||||
device.resetFences(1, &fences[image_index].get());
|
||||
|
||||
return image_index;
|
||||
}
|
||||
|
||||
void VulkanSwapchain::QueuePresent(vk::Queue queue, u32 image_index,
|
||||
vk::Semaphore present_semaphore,
|
||||
vk::Semaphore render_semaphore) {
|
||||
std::array<vk::Semaphore, 2> semaphores{present_semaphore, render_semaphore};
|
||||
const u32 wait_semaphore_count{render_semaphore ? 2u : 1u};
|
||||
|
||||
const vk::PresentInfoKHR present_info(wait_semaphore_count, semaphores.data(), 1, &handle.get(),
|
||||
&image_index, {});
|
||||
|
||||
switch (queue.presentKHR(&present_info)) {
|
||||
case vk::Result::eErrorOutOfDateKHR:
|
||||
if (current_width > 0 && current_height > 0) {
|
||||
Create(current_width, current_height);
|
||||
}
|
||||
break;
|
||||
case vk::Result::eSuccess:
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_Vulkan, "Vulkan failed to present swapchain!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanSwapchain::HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const {
|
||||
// TODO(Rodrigo): Handle framebuffer pixel format changes
|
||||
return framebuffer.width != current_width || framebuffer.height != current_height;
|
||||
}
|
||||
|
||||
const vk::Extent2D& VulkanSwapchain::GetSize() const {
|
||||
return extent;
|
||||
}
|
||||
|
||||
const vk::Image& VulkanSwapchain::GetImage(std::size_t image_index) const {
|
||||
return images[image_index];
|
||||
}
|
||||
|
||||
const vk::Fence& VulkanSwapchain::GetFence(std::size_t image_index) const {
|
||||
return *fences[image_index];
|
||||
}
|
||||
|
||||
const vk::RenderPass& VulkanSwapchain::GetRenderPass() const {
|
||||
return *renderpass;
|
||||
}
|
||||
|
||||
void VulkanSwapchain::CreateSwapchain(u32 width, u32 height,
|
||||
const vk::SurfaceCapabilitiesKHR& capabilities) {
|
||||
std::vector<vk::SurfaceFormatKHR> formats{physical_device.getSurfaceFormatsKHR(surface)};
|
||||
|
||||
std::vector<vk::PresentModeKHR> present_modes{
|
||||
physical_device.getSurfacePresentModesKHR(surface)};
|
||||
|
||||
vk::SurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)};
|
||||
vk::PresentModeKHR present_mode{ChooseSwapPresentMode(present_modes)};
|
||||
extent = ChooseSwapExtent(capabilities, width, height);
|
||||
|
||||
current_width = extent.width;
|
||||
current_height = extent.height;
|
||||
|
||||
u32 requested_image_count = capabilities.minImageCount + 1;
|
||||
if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) {
|
||||
requested_image_count = capabilities.maxImageCount;
|
||||
}
|
||||
|
||||
vk::SwapchainCreateInfoKHR swapchain_ci(
|
||||
{}, surface, requested_image_count, surface_format.format, surface_format.colorSpace,
|
||||
extent, 1, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst,
|
||||
{}, {}, {}, capabilities.currentTransform, vk::CompositeAlphaFlagBitsKHR::eOpaque,
|
||||
present_mode, false, {});
|
||||
|
||||
std::array<u32, 2> queue_indices{graphics_family, present_family};
|
||||
if (graphics_family != present_family) {
|
||||
swapchain_ci.imageSharingMode = vk::SharingMode::eConcurrent;
|
||||
swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size());
|
||||
swapchain_ci.pQueueFamilyIndices = queue_indices.data();
|
||||
} else {
|
||||
swapchain_ci.imageSharingMode = vk::SharingMode::eExclusive;
|
||||
}
|
||||
handle = device.createSwapchainKHRUnique(swapchain_ci);
|
||||
|
||||
images = device.getSwapchainImagesKHR(*handle);
|
||||
image_count = static_cast<u32>(images.size());
|
||||
image_format = surface_format.format;
|
||||
}
|
||||
|
||||
void VulkanSwapchain::CreateImageViews() {
|
||||
image_views.resize(image_count);
|
||||
for (u32 i = 0; i < image_count; i++) {
|
||||
vk::ImageViewCreateInfo image_view_ci({}, images[i], vk::ImageViewType::e2D, image_format,
|
||||
{}, {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
|
||||
image_views[i] = device.createImageViewUnique(image_view_ci);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanSwapchain::CreateRenderPass() {
|
||||
vk::AttachmentDescription color_attachment(
|
||||
{}, image_format, vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eClear,
|
||||
vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare,
|
||||
vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined,
|
||||
vk::ImageLayout::ePresentSrcKHR);
|
||||
|
||||
vk::AttachmentReference color_attachment_ref(0, vk::ImageLayout::eColorAttachmentOptimal);
|
||||
|
||||
vk::SubpassDescription subpass_description;
|
||||
subpass_description.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
|
||||
subpass_description.colorAttachmentCount = 1;
|
||||
subpass_description.pColorAttachments = &color_attachment_ref;
|
||||
|
||||
vk::SubpassDependency dependency(
|
||||
VK_SUBPASS_EXTERNAL, 0, vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput, {},
|
||||
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, {});
|
||||
|
||||
vk::RenderPassCreateInfo renderpass_ci({}, 1, &color_attachment, 1, &subpass_description, 1,
|
||||
&dependency);
|
||||
renderpass = device.createRenderPassUnique(renderpass_ci);
|
||||
}
|
||||
|
||||
void VulkanSwapchain::CreateFramebuffers() {
|
||||
framebuffers.resize(image_count);
|
||||
for (u32 i = 0; i < image_count; i++) {
|
||||
vk::ImageView image_view{*image_views[i]};
|
||||
const vk::FramebufferCreateInfo framebuffer_ci({}, *renderpass, 1, &image_view,
|
||||
extent.width, extent.height, 1);
|
||||
framebuffers[i] = device.createFramebufferUnique(framebuffer_ci);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanSwapchain::CreateFences() {
|
||||
fences.resize(image_count);
|
||||
for (u32 i = 0; i < image_count; i++) {
|
||||
const vk::FenceCreateInfo fence_ci(vk::FenceCreateFlagBits::eSignaled);
|
||||
fences[i] = device.createFenceUnique(fence_ci);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanSwapchain::Destroy() {
|
||||
fences.clear();
|
||||
framebuffers.clear();
|
||||
renderpass.reset();
|
||||
image_views.clear();
|
||||
handle.reset();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
71
src/video_core/renderer_vulkan/vk_swapchain.h
Normal file
71
src/video_core/renderer_vulkan/vk_swapchain.h
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Layout {
|
||||
struct FramebufferLayout;
|
||||
}
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class VulkanSwapchain {
|
||||
public:
|
||||
explicit VulkanSwapchain(vk::SurfaceKHR& surface, vk::PhysicalDevice& physical_device,
|
||||
vk::Device& device, const u32& graphics_family,
|
||||
const u32& present_family);
|
||||
~VulkanSwapchain();
|
||||
|
||||
void Create(u32 width, u32 height);
|
||||
|
||||
u32 AcquireNextImage(vk::Semaphore present_complete);
|
||||
|
||||
void QueuePresent(vk::Queue queue, u32 image_index, vk::Semaphore present_semaphore,
|
||||
vk::Semaphore render_semaphore);
|
||||
|
||||
bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const;
|
||||
|
||||
const vk::Extent2D& GetSize() const;
|
||||
const vk::Image& GetImage(std::size_t image_index) const;
|
||||
const vk::Fence& GetFence(std::size_t image_index) const;
|
||||
const vk::RenderPass& GetRenderPass() const;
|
||||
|
||||
private:
|
||||
void CreateSwapchain(u32 width, u32 height, const vk::SurfaceCapabilitiesKHR& capabilities);
|
||||
void CreateImageViews();
|
||||
void CreateRenderPass();
|
||||
void CreateFramebuffers();
|
||||
void CreateFences();
|
||||
|
||||
void Destroy();
|
||||
|
||||
vk::SurfaceKHR& surface;
|
||||
vk::PhysicalDevice& physical_device;
|
||||
vk::Device& device;
|
||||
const u32& graphics_family;
|
||||
const u32& present_family;
|
||||
|
||||
vk::UniqueSwapchainKHR handle;
|
||||
|
||||
u32 current_width{};
|
||||
u32 current_height{};
|
||||
|
||||
vk::Format image_format{};
|
||||
vk::Extent2D extent{};
|
||||
|
||||
u32 image_count{};
|
||||
std::vector<vk::Image> images;
|
||||
std::vector<vk::UniqueImageView> image_views;
|
||||
std::vector<vk::UniqueFramebuffer> framebuffers;
|
||||
std::vector<vk::UniqueFence> fences;
|
||||
|
||||
vk::UniqueRenderPass renderpass;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
130
src/video_core/renderer_vulkan/vk_sync.cpp
Normal file
130
src/video_core/renderer_vulkan/vk_sync.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/renderer_vulkan/vk_sync.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
VulkanSync::VulkanSync(vk::Device& device, vk::Queue& queue, const u32& queue_family_index)
|
||||
: device(device), queue(queue) {
|
||||
|
||||
calls.reserve(1024);
|
||||
CreateFreshCall();
|
||||
|
||||
const vk::CommandPoolCreateInfo command_pool_ci(vk::CommandPoolCreateFlagBits::eTransient,
|
||||
queue_family_index);
|
||||
one_shot_pool = device.createCommandPoolUnique(command_pool_ci);
|
||||
}
|
||||
|
||||
VulkanSync::~VulkanSync() {
|
||||
FreeUnusedMemory();
|
||||
}
|
||||
|
||||
void VulkanSync::AddCommand(vk::CommandBuffer cmdbuf, vk::CommandPool pool) {
|
||||
current_call->commands.push_back(cmdbuf);
|
||||
current_call->pools.push_back(pool);
|
||||
}
|
||||
|
||||
vk::CommandBuffer VulkanSync::BeginRecord() {
|
||||
vk::CommandBufferAllocateInfo cmdbuf_ai(*one_shot_pool, vk::CommandBufferLevel::ePrimary, 1);
|
||||
vk::CommandBuffer cmdbuf = device.allocateCommandBuffers(cmdbuf_ai)[0];
|
||||
|
||||
cmdbuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
|
||||
return cmdbuf;
|
||||
}
|
||||
|
||||
void VulkanSync::EndRecord(vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.end();
|
||||
AddCommand(cmdbuf, *one_shot_pool);
|
||||
}
|
||||
|
||||
void VulkanSync::Execute(vk::Fence fence) {
|
||||
if (device.createSemaphore(&vk::SemaphoreCreateInfo(), nullptr, ¤t_call->semaphore) !=
|
||||
vk::Result::eSuccess) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Vulkan failed to create a semaphore!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (fence) {
|
||||
current_call->fence = fence;
|
||||
current_call->fence_owned = false;
|
||||
} else {
|
||||
current_call->fence = device.createFence({vk::FenceCreateFlags{}});
|
||||
current_call->fence_owned = true;
|
||||
}
|
||||
|
||||
vk::SubmitInfo submit_info(0, nullptr, nullptr, static_cast<u32>(current_call->commands.size()),
|
||||
current_call->commands.data(), 1, ¤t_call->semaphore);
|
||||
if (wait_semaphore) {
|
||||
// TODO(Rodrigo): This could be optimized with an extra argument.
|
||||
vk::PipelineStageFlags stage_flags = vk::PipelineStageFlagBits::eAllCommands;
|
||||
|
||||
submit_info.waitSemaphoreCount = 1;
|
||||
submit_info.pWaitSemaphores = &wait_semaphore;
|
||||
submit_info.pWaitDstStageMask = &stage_flags;
|
||||
}
|
||||
queue.submit({submit_info}, current_call->fence);
|
||||
|
||||
wait_semaphore = current_call->semaphore;
|
||||
calls.push_back(std::move(current_call));
|
||||
CreateFreshCall();
|
||||
}
|
||||
|
||||
vk::Semaphore VulkanSync::QuerySemaphore() {
|
||||
vk::Semaphore semaphore = wait_semaphore;
|
||||
wait_semaphore = vk::Semaphore(nullptr);
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
void VulkanSync::DestroyCommandBuffers() {
|
||||
constexpr u32 retries = 4;
|
||||
for (u32 i = 0; i < retries; ++i) {
|
||||
FreeUnusedMemory();
|
||||
if (calls.empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// After some retries, wait for device idle and free all buffers
|
||||
device.waitIdle();
|
||||
FreeUnusedMemory();
|
||||
ASSERT(calls.empty());
|
||||
}
|
||||
|
||||
void VulkanSync::FreeUnusedMemory() {
|
||||
auto it = calls.begin();
|
||||
while (it != calls.end()) {
|
||||
const Call* call = it->get();
|
||||
|
||||
switch (device.getFenceStatus(call->fence)) {
|
||||
case vk::Result::eSuccess:
|
||||
device.destroy(call->semaphore);
|
||||
if (call->fence_owned) {
|
||||
device.destroy(call->fence);
|
||||
}
|
||||
for (std::size_t i = 0; i < call->commands.size(); i++) {
|
||||
if (vk::CommandPool pool = call->pools[i]; pool) {
|
||||
device.freeCommandBuffers(pool, {call->commands[i]});
|
||||
}
|
||||
}
|
||||
it = calls.erase(it);
|
||||
break;
|
||||
case vk::Result::eNotReady:
|
||||
it++;
|
||||
break;
|
||||
default:
|
||||
it++;
|
||||
LOG_CRITICAL(Render_Vulkan, "Failed to get a fence status!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanSync::CreateFreshCall() {
|
||||
current_call = std::make_unique<Call>();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
59
src/video_core/renderer_vulkan/vk_sync.h
Normal file
59
src/video_core/renderer_vulkan/vk_sync.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class VulkanSync {
|
||||
public:
|
||||
explicit VulkanSync(vk::Device& device, vk::Queue& queue, const u32& queue_family_index);
|
||||
~VulkanSync();
|
||||
|
||||
/**
|
||||
* Adds a command to execution queue.
|
||||
* Passing a null handle for pool means that the command buffer has to be externally freed.
|
||||
*/
|
||||
void AddCommand(vk::CommandBuffer cmdbuf, vk::CommandPool pool = vk::CommandPool(nullptr));
|
||||
|
||||
vk::CommandBuffer BeginRecord();
|
||||
|
||||
void EndRecord(vk::CommandBuffer cmdbuf);
|
||||
|
||||
void Execute(vk::Fence fence = vk::Fence(nullptr));
|
||||
|
||||
vk::Semaphore QuerySemaphore();
|
||||
|
||||
void DestroyCommandBuffers();
|
||||
|
||||
void FreeUnusedMemory();
|
||||
|
||||
private:
|
||||
struct Call {
|
||||
std::vector<vk::CommandBuffer> commands;
|
||||
std::vector<vk::CommandPool> pools;
|
||||
vk::Fence fence;
|
||||
bool fence_owned;
|
||||
vk::Semaphore semaphore;
|
||||
};
|
||||
|
||||
void CreateFreshCall();
|
||||
|
||||
vk::Device& device;
|
||||
vk::Queue& queue;
|
||||
|
||||
std::vector<std::unique_ptr<Call>> calls;
|
||||
std::unique_ptr<Call> current_call;
|
||||
|
||||
vk::UniqueCommandPool one_shot_pool;
|
||||
|
||||
vk::Semaphore wait_semaphore{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
165
src/video_core/utils.h
Normal file
165
src/video_core/utils.h
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
// 8x8 Z-Order coordinate from 2D coordinates
|
||||
static inline u32 MortonInterleave(u32 x, u32 y) {
|
||||
static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15};
|
||||
static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a};
|
||||
return xlut[x % 8] + ylut[y % 8];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the offset of the position of the pixel in Morton order
|
||||
*/
|
||||
static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) {
|
||||
// Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each
|
||||
// of which is composed of four 2x2 subtiles each of which is composed of four texels.
|
||||
// Each structure is embedded into the next-bigger one in a diagonal pattern, e.g.
|
||||
// texels are laid out in a 2x2 subtile like this:
|
||||
// 2 3
|
||||
// 0 1
|
||||
//
|
||||
// The full 8x8 tile has the texels arranged like this:
|
||||
//
|
||||
// 42 43 46 47 58 59 62 63
|
||||
// 40 41 44 45 56 57 60 61
|
||||
// 34 35 38 39 50 51 54 55
|
||||
// 32 33 36 37 48 49 52 53
|
||||
// 10 11 14 15 26 27 30 31
|
||||
// 08 09 12 13 24 25 28 29
|
||||
// 02 03 06 07 18 19 22 23
|
||||
// 00 01 04 05 16 17 20 21
|
||||
//
|
||||
// This pattern is what's called Z-order curve, or Morton order.
|
||||
|
||||
const unsigned int block_height = 8;
|
||||
const unsigned int coarse_x = x & ~7;
|
||||
|
||||
u32 i = VideoCore::MortonInterleave(x, y);
|
||||
|
||||
const unsigned int offset = coarse_x * block_height;
|
||||
|
||||
return (i + offset) * bytes_per_pixel;
|
||||
}
|
||||
|
||||
static inline u32 MortonInterleave128(u32 x, u32 y) {
|
||||
// 128x128 Z-Order coordinate from 2D coordinates
|
||||
static constexpr u32 xlut[] = {
|
||||
0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042,
|
||||
0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809,
|
||||
0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000,
|
||||
0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043,
|
||||
0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a,
|
||||
0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001,
|
||||
0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048,
|
||||
0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b,
|
||||
0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002,
|
||||
0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049,
|
||||
0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840,
|
||||
0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003,
|
||||
0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a,
|
||||
0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841,
|
||||
0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008,
|
||||
0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b,
|
||||
0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842,
|
||||
0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009,
|
||||
0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800,
|
||||
0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843,
|
||||
0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a,
|
||||
0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801,
|
||||
0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848,
|
||||
0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b,
|
||||
0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802,
|
||||
0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849,
|
||||
0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040,
|
||||
0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803,
|
||||
0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a,
|
||||
0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041,
|
||||
0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808,
|
||||
0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b,
|
||||
0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042,
|
||||
0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809,
|
||||
0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b,
|
||||
};
|
||||
static constexpr u32 ylut[] = {
|
||||
0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090,
|
||||
0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124,
|
||||
0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200,
|
||||
0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294,
|
||||
0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330,
|
||||
0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404,
|
||||
0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0,
|
||||
0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534,
|
||||
0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610,
|
||||
0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4,
|
||||
0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780,
|
||||
0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014,
|
||||
0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0,
|
||||
0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184,
|
||||
0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220,
|
||||
0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4,
|
||||
0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390,
|
||||
0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424,
|
||||
0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500,
|
||||
0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594,
|
||||
0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630,
|
||||
0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704,
|
||||
0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0,
|
||||
0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034,
|
||||
0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110,
|
||||
0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4,
|
||||
0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280,
|
||||
0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314,
|
||||
0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0,
|
||||
0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484,
|
||||
0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520,
|
||||
0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4,
|
||||
0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690,
|
||||
0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724,
|
||||
0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4,
|
||||
};
|
||||
return xlut[x % 128] + ylut[y % 128];
|
||||
}
|
||||
|
||||
static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
|
||||
// Calculates the offset of the position of the pixel in Morton order
|
||||
// Framebuffer images are split into 128x128 tiles.
|
||||
|
||||
const unsigned int block_height = 128;
|
||||
const unsigned int coarse_x = x & ~127;
|
||||
|
||||
u32 i = MortonInterleave128(x, y);
|
||||
|
||||
const unsigned int offset = coarse_x * block_height;
|
||||
|
||||
return (i + offset) * bytes_per_pixel;
|
||||
}
|
||||
|
||||
static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel,
|
||||
u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data,
|
||||
bool morton_to_gl) {
|
||||
u8* data_ptrs[2];
|
||||
for (unsigned y = 0; y < height; ++y) {
|
||||
for (unsigned x = 0; x < width; ++x) {
|
||||
const u32 coarse_y = y & ~127;
|
||||
u32 morton_offset =
|
||||
GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
|
||||
u32 gl_pixel_index = (x + y * width) * gl_bytes_per_pixel;
|
||||
|
||||
data_ptrs[morton_to_gl] = morton_data + morton_offset;
|
||||
data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
|
||||
|
||||
memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
@@ -3,14 +3,28 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#ifdef HAS_VULKAN
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#endif
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window) {
|
||||
return std::make_unique<OpenGL::RendererOpenGL>(emu_window);
|
||||
switch (Settings::values.renderer_backend) {
|
||||
case Settings::RendererBackend::OpenGL:
|
||||
return std::make_unique<OpenGL::RendererOpenGL>(emu_window);
|
||||
#ifdef HAS_VULKAN
|
||||
case Settings::RendererBackend::Vulkan:
|
||||
return std::make_unique<Vulkan::RendererVulkan>(emu_window);
|
||||
#endif
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
||||
@@ -151,6 +151,12 @@ void GRenderWindow::DoneCurrent() {
|
||||
|
||||
void GRenderWindow::PollEvents() {}
|
||||
|
||||
bool GRenderWindow::IsShown() const {
|
||||
return !isMinimized();
|
||||
}
|
||||
|
||||
void GRenderWindow::RetrieveVulkanHandlers(void** instance, void** surface) const {}
|
||||
|
||||
// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
|
||||
//
|
||||
// Older versions get the window size (density independent pixels),
|
||||
|
||||
@@ -114,6 +114,8 @@ public:
|
||||
void MakeCurrent() override;
|
||||
void DoneCurrent() override;
|
||||
void PollEvents() override;
|
||||
bool IsShown() const override;
|
||||
void RetrieveVulkanHandlers(void** instance, void** surface) const override;
|
||||
|
||||
void BackupGeometry();
|
||||
void RestoreGeometry();
|
||||
|
||||
@@ -6,11 +6,22 @@ add_executable(yuzu-cmd
|
||||
default_ini.h
|
||||
emu_window/emu_window_sdl2.cpp
|
||||
emu_window/emu_window_sdl2.h
|
||||
emu_window/emu_window_sdl2_gl.cpp
|
||||
emu_window/emu_window_sdl2_gl.h
|
||||
resource.h
|
||||
yuzu.cpp
|
||||
yuzu.rc
|
||||
)
|
||||
|
||||
if (Vulkan_FOUND)
|
||||
target_sources(yuzu-cmd PRIVATE
|
||||
emu_window/emu_window_sdl2_vk.cpp
|
||||
emu_window/emu_window_sdl2_vk.h)
|
||||
|
||||
target_include_directories(yuzu-cmd PRIVATE Vulkan::Vulkan)
|
||||
target_compile_definitions(yuzu-cmd PRIVATE HAS_VULKAN)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(yuzu-cmd)
|
||||
|
||||
target_link_libraries(yuzu-cmd PRIVATE common core input_common)
|
||||
@@ -18,6 +29,9 @@ target_link_libraries(yuzu-cmd PRIVATE inih glad)
|
||||
if (MSVC)
|
||||
target_link_libraries(yuzu-cmd PRIVATE getopt)
|
||||
endif()
|
||||
if (Vulkan_FOUND)
|
||||
target_link_libraries(yuzu-cmd PRIVATE Vulkan::Vulkan)
|
||||
endif()
|
||||
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
|
||||
@@ -336,6 +336,11 @@ void Config::ReadValues() {
|
||||
Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
|
||||
|
||||
// Renderer
|
||||
const int renderer_backend = sdl2_config->GetInteger(
|
||||
"Renderer", "backend", static_cast<int>(Settings::RendererBackend::OpenGL));
|
||||
Settings::values.renderer_backend = static_cast<Settings::RendererBackend>(renderer_backend);
|
||||
Settings::values.vulkan_device = sdl2_config->GetInteger("Renderer", "vulkan_device", 0);
|
||||
|
||||
Settings::values.resolution_factor =
|
||||
(float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0);
|
||||
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
|
||||
|
||||
@@ -85,6 +85,13 @@ use_cpu_jit =
|
||||
use_multi_core=
|
||||
|
||||
[Renderer]
|
||||
# Which backend API to use.
|
||||
# 0: OpenGL (default), 1: Vulkan
|
||||
backend =
|
||||
|
||||
# Which Vulkan physical device to use (defaults to 0)
|
||||
vulkan_device =
|
||||
|
||||
# Whether to use software or hardware rendering.
|
||||
# 0: Software, 1 (default): Hardware
|
||||
use_hw_renderer =
|
||||
|
||||
@@ -2,17 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include <fmt/format.h>
|
||||
#include <glad/glad.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/settings.h"
|
||||
#include "emu_window_sdl2.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/motion_emu.h"
|
||||
@@ -81,6 +72,10 @@ bool EmuWindow_SDL2::IsOpen() const {
|
||||
return is_open;
|
||||
}
|
||||
|
||||
bool EmuWindow_SDL2::IsShown() const {
|
||||
return is_shown;
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::OnResize() {
|
||||
int width, height;
|
||||
SDL_GetWindowSize(render_window, &width, &height);
|
||||
@@ -108,30 +103,6 @@ void EmuWindow_SDL2::Fullscreen() {
|
||||
SDL_MaximizeWindow(render_window);
|
||||
}
|
||||
|
||||
bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
|
||||
std::vector<std::string> unsupported_ext;
|
||||
|
||||
if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
|
||||
unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
|
||||
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
||||
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
|
||||
if (!GLAD_GL_ARB_multi_bind)
|
||||
unsupported_ext.push_back("ARB_multi_bind");
|
||||
|
||||
// Extensions required to support some texture formats.
|
||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
||||
unsupported_ext.push_back("EXT_texture_compression_s3tc");
|
||||
if (!GLAD_GL_ARB_texture_compression_rgtc)
|
||||
unsupported_ext.push_back("ARB_texture_compression_rgtc");
|
||||
if (!GLAD_GL_ARB_depth_buffer_float)
|
||||
unsupported_ext.push_back("ARB_depth_buffer_float");
|
||||
|
||||
for (const std::string& ext : unsupported_ext)
|
||||
LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext);
|
||||
|
||||
return unsupported_ext.empty();
|
||||
}
|
||||
|
||||
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
InputCommon::Init();
|
||||
|
||||
@@ -142,73 +113,15 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
||||
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
}
|
||||
|
||||
gl_context = SDL_GL_CreateContext(render_window);
|
||||
|
||||
if (gl_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!SupportsRequiredGLExtensions()) {
|
||||
LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting...");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
SDL_GL_SetSwapInterval(false);
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
|
||||
DoneCurrent();
|
||||
}
|
||||
|
||||
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
||||
InputCommon::SDL::CloseSDLJoysticks();
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_Quit();
|
||||
|
||||
InputCommon::Shutdown();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::SwapBuffers() {
|
||||
SDL_GL_SwapWindow(render_window);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::PollEvents() {
|
||||
SDL_Event event;
|
||||
|
||||
@@ -221,7 +134,11 @@ void EmuWindow_SDL2::PollEvents() {
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
OnResize();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED;
|
||||
OnResize();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
@@ -265,14 +182,6 @@ void EmuWindow_SDL2::PollEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::MakeCurrent() {
|
||||
SDL_GL_MakeCurrent(render_window, gl_context);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::DoneCurrent() {
|
||||
SDL_GL_MakeCurrent(render_window, nullptr);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||
|
||||
|
||||
@@ -15,22 +15,16 @@ public:
|
||||
explicit EmuWindow_SDL2(bool fullscreen);
|
||||
~EmuWindow_SDL2();
|
||||
|
||||
/// Swap buffers to display the next frame
|
||||
void SwapBuffers() override;
|
||||
|
||||
/// Polls window events
|
||||
void PollEvents() override;
|
||||
|
||||
/// Makes the graphics context current for the caller thread
|
||||
void MakeCurrent() override;
|
||||
|
||||
/// Releases the GL context from the caller thread
|
||||
void DoneCurrent() override;
|
||||
|
||||
/// Whether the window is still open, and a close request hasn't yet been sent
|
||||
bool IsOpen() const;
|
||||
|
||||
private:
|
||||
/// Returns if window is shown (not minimized)
|
||||
bool IsShown() const override;
|
||||
|
||||
protected:
|
||||
/// Called by PollEvents when a key is pressed or released.
|
||||
void OnKeyEvent(int key, u8 state);
|
||||
|
||||
@@ -58,9 +52,6 @@ private:
|
||||
/// Called when user passes the fullscreen parameter flag
|
||||
void Fullscreen();
|
||||
|
||||
/// Whether the GPU and driver supports the OpenGL extension required
|
||||
bool SupportsRequiredGLExtensions();
|
||||
|
||||
/// Called when a configuration change affects the minimal size of the window
|
||||
void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||
@@ -68,10 +59,9 @@ private:
|
||||
/// Is the window still open?
|
||||
bool is_open = true;
|
||||
|
||||
/// Is the window being shown?
|
||||
bool is_shown = true;
|
||||
|
||||
/// Internal SDL2 render window
|
||||
SDL_Window* render_window;
|
||||
|
||||
using SDL_GLContext = void*;
|
||||
/// The OpenGL context associated with the window
|
||||
SDL_GLContext gl_context;
|
||||
};
|
||||
|
||||
121
src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
Normal file
121
src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include <fmt/format.h>
|
||||
#include <glad/glad.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/settings.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/motion_emu.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
|
||||
|
||||
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
|
||||
std::vector<std::string> unsupported_ext;
|
||||
|
||||
if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
|
||||
unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
|
||||
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
||||
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
|
||||
if (!GLAD_GL_ARB_multi_bind)
|
||||
unsupported_ext.push_back("ARB_multi_bind");
|
||||
|
||||
// Extensions required to support some texture formats.
|
||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
||||
unsupported_ext.push_back("EXT_texture_compression_s3tc");
|
||||
if (!GLAD_GL_ARB_texture_compression_rgtc)
|
||||
unsupported_ext.push_back("ARB_texture_compression_rgtc");
|
||||
if (!GLAD_GL_ARB_depth_buffer_float)
|
||||
unsupported_ext.push_back("ARB_depth_buffer_float");
|
||||
|
||||
for (const std::string& ext : unsupported_ext)
|
||||
LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext);
|
||||
|
||||
return unsupported_ext.empty();
|
||||
}
|
||||
|
||||
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
||||
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
}
|
||||
|
||||
gl_context = SDL_GL_CreateContext(render_window);
|
||||
|
||||
if (gl_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!SupportsRequiredGLExtensions()) {
|
||||
LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting...");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
SDL_GL_SetSwapInterval(false);
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
|
||||
DoneCurrent();
|
||||
}
|
||||
|
||||
EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_Quit();
|
||||
|
||||
InputCommon::Shutdown();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::SwapBuffers() {
|
||||
SDL_GL_SwapWindow(render_window);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::MakeCurrent() {
|
||||
SDL_GL_MakeCurrent(render_window, gl_context);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::DoneCurrent() {
|
||||
SDL_GL_MakeCurrent(render_window, nullptr);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void** instance, void** surface) const {
|
||||
// Should not have been called from OpenGL
|
||||
}
|
||||
36
src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
Normal file
36
src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
|
||||
|
||||
class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
|
||||
public:
|
||||
explicit EmuWindow_SDL2_GL(bool fullscreen);
|
||||
~EmuWindow_SDL2_GL();
|
||||
|
||||
/// Swap buffers to display the next frame
|
||||
void SwapBuffers() override;
|
||||
|
||||
/// Makes the graphics context current for the caller thread
|
||||
void MakeCurrent() override;
|
||||
|
||||
/// Releases the GL context from the caller thread
|
||||
void DoneCurrent() override;
|
||||
|
||||
/// Ignored in OpenGL
|
||||
void RetrieveVulkanHandlers(void** instance, void** surface) const override;
|
||||
|
||||
private:
|
||||
/// Whether the GPU and driver supports the OpenGL extension required
|
||||
bool SupportsRequiredGLExtensions();
|
||||
|
||||
using SDL_GLContext = void*;
|
||||
/// The OpenGL context associated with the window
|
||||
SDL_GLContext gl_context;
|
||||
};
|
||||
144
src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
Normal file
144
src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <SDL.h>
|
||||
#include <SDL_vulkan.h>
|
||||
#include <fmt/format.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
|
||||
|
||||
static VkBool32 DebugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT object_type,
|
||||
u64 object, size_t location, s32 message_code,
|
||||
const char* layer_prefix, const char* message, void* user_data) {
|
||||
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
|
||||
LOG_ERROR(Render_Vulkan, message);
|
||||
UNREACHABLE();
|
||||
} else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
|
||||
LOG_WARNING(Render_Vulkan, message);
|
||||
} else if (flags &
|
||||
(VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)) {
|
||||
LOG_DEBUG(Render_Vulkan, message);
|
||||
} else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
|
||||
LOG_TRACE(Render_Vulkan, message);
|
||||
}
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_VULKAN);
|
||||
|
||||
unsigned extra_ext_count{};
|
||||
if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, NULL)) {
|
||||
LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions count from SDL! {}",
|
||||
SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
auto extra_ext_names = std::make_unique<const char* []>(extra_ext_count);
|
||||
if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, extra_ext_names.get())) {
|
||||
LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions from SDL! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
std::vector<const char*> enabled_extensions;
|
||||
enabled_extensions.insert(enabled_extensions.begin(), extra_ext_names.get(),
|
||||
extra_ext_names.get() + extra_ext_count);
|
||||
|
||||
std::vector<const char*> enabled_layers;
|
||||
if (enable_layers) {
|
||||
enabled_extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
|
||||
enabled_layers.push_back("VK_LAYER_LUNARG_standard_validation");
|
||||
}
|
||||
|
||||
VkApplicationInfo app_info{};
|
||||
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
app_info.apiVersion = VK_API_VERSION_1_0;
|
||||
app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
|
||||
app_info.pApplicationName = "yuzu-emu";
|
||||
app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
|
||||
app_info.pEngineName = "yuzu-emu";
|
||||
|
||||
VkInstanceCreateInfo instance_ci{};
|
||||
instance_ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
instance_ci.pApplicationInfo = &app_info;
|
||||
instance_ci.enabledExtensionCount = static_cast<u32>(enabled_extensions.size());
|
||||
instance_ci.ppEnabledExtensionNames = enabled_extensions.data();
|
||||
if (enable_layers) {
|
||||
instance_ci.enabledLayerCount = static_cast<u32>(enabled_layers.size());
|
||||
instance_ci.ppEnabledLayerNames = enabled_layers.data();
|
||||
}
|
||||
|
||||
if (vkCreateInstance(&instance_ci, nullptr, &instance) != VK_SUCCESS) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create Vulkan instance!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (enable_layers) {
|
||||
VkDebugReportCallbackCreateInfoEXT callback_ci{};
|
||||
callback_ci.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
|
||||
callback_ci.pfnCallback = DebugCallback;
|
||||
callback_ci.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT |
|
||||
VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
|
||||
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
|
||||
|
||||
vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr();
|
||||
|
||||
vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(
|
||||
instance, "vkCreateDebugReportCallbackEXT");
|
||||
|
||||
vkDestroyDebugReportCallbackEXT =
|
||||
(PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(
|
||||
instance, "vkDestroyDebugReportCallbackEXT");
|
||||
|
||||
if (vkCreateDebugReportCallbackEXT(instance, &callback_ci, nullptr, &debug_report) !=
|
||||
VK_SUCCESS) {
|
||||
LOG_CRITICAL(Frontend, "Failed to setup Vulkan debug callback!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!SDL_Vulkan_CreateSurface(render_window, instance, &surface)) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create Vulkan surface! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{} (Vulkan)", Common::g_build_name,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
}
|
||||
|
||||
EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() {
|
||||
if (enable_layers) {
|
||||
vkDestroyDebugReportCallbackEXT(instance, debug_report, nullptr);
|
||||
}
|
||||
vkDestroyInstance(instance, nullptr);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_VK::SwapBuffers() {}
|
||||
|
||||
void EmuWindow_SDL2_VK::MakeCurrent() {
|
||||
// Unused on Vulkan
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_VK::DoneCurrent() {
|
||||
// Unused on Vulkan
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_VK::RetrieveVulkanHandlers(void** instance, void** surface) const {
|
||||
*instance = this->instance;
|
||||
*surface = this->surface;
|
||||
}
|
||||
44
src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
Normal file
44
src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
|
||||
|
||||
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
|
||||
public:
|
||||
explicit EmuWindow_SDL2_VK(bool fullscreen);
|
||||
~EmuWindow_SDL2_VK();
|
||||
|
||||
/// Swap buffers to display the next frame
|
||||
void SwapBuffers() override;
|
||||
|
||||
/// Makes the graphics context current for the caller thread
|
||||
void MakeCurrent() override;
|
||||
|
||||
/// Releases the GL context from the caller thread
|
||||
void DoneCurrent() override;
|
||||
|
||||
/// Retrieves Vulkan specific handlers from the window
|
||||
void RetrieveVulkanHandlers(void** instance, void** surface) const override;
|
||||
|
||||
private:
|
||||
/// Vulkan instance
|
||||
VkInstance instance{};
|
||||
|
||||
/// Vulkan surface
|
||||
VkSurfaceKHR surface{};
|
||||
|
||||
/// Vulkan debug callback
|
||||
VkDebugReportCallbackEXT debug_report{};
|
||||
|
||||
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
|
||||
PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT{};
|
||||
PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT{};
|
||||
|
||||
/// Enable Vulkan validations layers
|
||||
static constexpr bool enable_layers = true;
|
||||
};
|
||||
@@ -29,7 +29,10 @@
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "yuzu_cmd/config.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
|
||||
#ifdef HAS_VULKAN
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
|
||||
#endif
|
||||
|
||||
#include <getopt.h>
|
||||
#ifndef _MSC_VER
|
||||
@@ -169,7 +172,20 @@ int main(int argc, char** argv) {
|
||||
Settings::values.use_gdbstub = use_gdbstub;
|
||||
Settings::Apply();
|
||||
|
||||
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
|
||||
std::unique_ptr<EmuWindow_SDL2> emu_window;
|
||||
switch (Settings::values.renderer_backend) {
|
||||
case Settings::RendererBackend::OpenGL:
|
||||
emu_window = std::make_unique<EmuWindow_SDL2_GL>(fullscreen);
|
||||
break;
|
||||
case Settings::RendererBackend::Vulkan:
|
||||
#ifdef HAS_VULKAN
|
||||
emu_window = std::make_unique<EmuWindow_SDL2_VK>(fullscreen);
|
||||
break;
|
||||
#else
|
||||
LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!Settings::values.use_multi_core) {
|
||||
// Single core mode must acquire OpenGL context for entire emulation session
|
||||
|
||||
Reference in New Issue
Block a user