Compare commits

...

1 Commits

Author SHA1 Message Date
ReinUsesLisp
661a7f4d3c vulkan: Initial implementation 2018-11-28 23:53:15 -03:00
30 changed files with 1685 additions and 120 deletions

View File

@@ -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
# ======================================

View File

@@ -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) \

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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()

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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, &current_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, &current_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

View 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
View 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

View File

@@ -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

View File

@@ -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),

View File

@@ -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();

View File

@@ -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)

View File

@@ -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);

View File

@@ -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 =

View File

@@ -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) {

View File

@@ -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;
};

View 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
}

View 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;
};

View 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;
}

View 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;
};

View File

@@ -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