renderer_vulkan: Include pipeline builder class and finish VulanState

This commit is contained in:
GPUCode
2022-05-20 11:02:25 +03:00
parent 0d0712f7d7
commit 06cecba63f
17 changed files with 700 additions and 1185 deletions

View File

@ -261,6 +261,9 @@ create_target_directory_groups(citra-qt)
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Widgets Qt5::Multimedia)
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
target_compile_definitions(citra-qt PRIVATE
# Use QStringBuilder for string concatenation to reduce

View File

@ -27,6 +27,10 @@
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#if !defined(WIN32)
#include <qpa/qplatformnativeinterface.h>
#endif
EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
EmuThread::~EmuThread() = default;
@ -191,6 +195,40 @@ void OpenGLWindow::exposeEvent(QExposeEvent* event) {
QWindow::exposeEvent(event);
}
static Frontend::WindowSystemType GetWindowSystemType() {
// Determine WSI type based on Qt platform.
QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("windows"))
return Frontend::WindowSystemType::Windows;
else if (platform_name == QStringLiteral("xcb"))
return Frontend::WindowSystemType::X11;
else if (platform_name == QStringLiteral("wayland"))
return Frontend::WindowSystemType::Wayland;
LOG_CRITICAL(Frontend, "Unknown Qt platform!");
return Frontend::WindowSystemType::Windows;
}
static Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
Frontend::EmuWindow::WindowSystemInfo wsi;
wsi.type = GetWindowSystemType();
// Our Win32 Qt external doesn't have the private API.
#if defined(WIN32) || defined(__APPLE__)
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
if (wsi.type == Frontend::WindowSystemType::Wayland)
wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
else
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#endif
wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
return wsi;
}
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
: QWidget(parent_), emu_thread(emu_thread) {
@ -396,6 +434,9 @@ void GRenderWindow::InitRenderTarget() {
child_widget = createWindowContainer(child_window, this);
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
// Update the Window System information with the new render target
window_info = GetWindowSystemInfo(child_widget->windowHandle());
layout()->addWidget(child_widget);
core_context = CreateSharedContext();

View File

@ -12,6 +12,15 @@
namespace Frontend {
/// Information for the Graphics Backends signifying what type of screen pointer is in
/// WindowInformation
enum class WindowSystemType {
Headless,
Windows,
X11,
Wayland,
};
struct Frame;
/**
* For smooth Vsync rendering, we want to always present the latest frame that the core generates,
@ -95,6 +104,23 @@ public:
std::pair<unsigned, unsigned> min_client_area_size;
};
/// Data describing host window system information
struct WindowSystemInfo {
// Window system type. Determines which GL context or Vulkan WSI is used.
WindowSystemType type = WindowSystemType::Headless;
// Connection to a display server. This is used on X11 and Wayland platforms.
void* display_connection = nullptr;
// Render surface. This is a pointer to the native window handle, which depends
// on the platform. e.g. HWND for Windows, Window for X11. If the surface is
// set to nullptr, the video backend will run in headless mode.
void* render_surface = nullptr;
// Scale of the render surface. For hidpi systems, this will be >1.
float render_surface_scale = 1.0f;
};
/// Polls window events
virtual void PollEvents() = 0;
@ -148,6 +174,13 @@ public:
config = val;
}
/**
* Returns system information about the drawing area.
*/
const WindowSystemInfo& GetWindowInfo() const {
return window_info;
}
/**
* Gets the framebuffer layout (width, height, and screen regions)
* @note This method is thread-safe
@ -194,6 +227,8 @@ protected:
framebuffer_layout = layout;
}
WindowSystemInfo window_info;
private:
/**
* Handler called when the minimal client area was requested to be changed via SetConfig.

View File

@ -75,8 +75,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_buffer.h
renderer_vulkan/vk_instance.cpp
renderer_vulkan/vk_instance.h
renderer_vulkan/vk_resource_cache.cpp
renderer_vulkan/vk_resource_cache.h
renderer_vulkan/vk_pipeline_builder.cpp
renderer_vulkan/vk_pipeline_builder.h
renderer_vulkan/vk_rasterizer_cache.cpp
renderer_vulkan/vk_rasterizer_cache.h
renderer_vulkan/vk_rasterizer.cpp

View File

@ -31,194 +31,103 @@
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/vk_state.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
#include "video_core/video_core.h"
// Include these late to avoid polluting previous headers
#ifdef _WIN32
#include <windows.h>
// ensure include order
#include <vulkan/vulkan_win32.h>
#endif
#if !defined(_WIN32) && !defined(__APPLE__)
#include <X11/Xlib.h>
#include <vulkan/vulkan_wayland.h>
#include <vulkan/vulkan_xlib.h>
#endif
namespace Vulkan {
class OGLTextureMailboxException : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
vk::SurfaceKHR CreateSurface(const VkInstance& instance,
const Frontend::EmuWindow& emu_window) {
const auto& window_info = emu_window.GetWindowInfo();
VkSurfaceKHR unsafe_surface = nullptr;
class OGLTextureMailbox : public Frontend::TextureMailbox {
public:
std::mutex swap_chain_lock;
std::condition_variable free_cv;
std::condition_variable present_cv;
std::array<Frontend::Frame, SWAP_CHAIN_SIZE> swap_chain{};
std::queue<Frontend::Frame*> free_queue{};
std::deque<Frontend::Frame*> present_queue{};
Frontend::Frame* previous_frame = nullptr;
OGLTextureMailbox() {
for (auto& frame : swap_chain) {
free_queue.push(&frame);
#ifdef _WIN32
if (window_info.type == Core::Frontend::WindowSystemType::Windows) {
const HWND hWnd = static_cast<HWND>(window_info.render_surface);
const VkWin32SurfaceCreateInfoKHR win32_ci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
nullptr, 0, nullptr, hWnd};
if (vkCreateWin32SurfaceKHR(instance, &win32_ci, nullptr, &unsafe_surface) != VK_SUCCESS) {
LOG_ERROR(Render_Vulkan, "Failed to initialize Win32 surface");
UNREACHABLE();
}
}
#endif
#if !defined(_WIN32) && !defined(__APPLE__)
if (window_info.type == Frontend::WindowSystemType::X11) {
const VkXlibSurfaceCreateInfoKHR xlib_ci{
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0,
static_cast<Display*>(window_info.display_connection),
reinterpret_cast<Window>(window_info.render_surface)};
if (vkCreateXlibSurfaceKHR(instance, &xlib_ci, nullptr, &unsafe_surface) != VK_SUCCESS) {
LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface");
UNREACHABLE();
}
}
~OGLTextureMailbox() override {
// lock the mutex and clear out the present and free_queues and notify any people who are
// blocked to prevent deadlock on shutdown
std::scoped_lock lock(swap_chain_lock);
std::queue<Frontend::Frame*>().swap(free_queue);
present_queue.clear();
present_cv.notify_all();
free_cv.notify_all();
}
void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override {
frame->present.Release();
frame->present.Create();
GLint previous_draw_fbo{};
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
frame->color.handle);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
frame->color_reloaded = false;
}
void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) override {
OpenGLState prev_state = OpenGLState::GetCurState();
OpenGLState state = OpenGLState::GetCurState();
// Recreate the color texture attachment
frame->color.Release();
frame->color.Create();
state.renderbuffer = frame->color.handle;
state.Apply();
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
// Recreate the FBO for the render target
frame->render.Release();
frame->render.Create();
state.draw.read_framebuffer = frame->render.handle;
state.draw.draw_framebuffer = frame->render.handle;
state.Apply();
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
frame->color.handle);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
}
prev_state.Apply();
frame->width = width;
frame->height = height;
frame->color_reloaded = true;
}
Frontend::Frame* GetRenderFrame() override {
std::unique_lock<std::mutex> lock(swap_chain_lock);
// If theres no free frames, we will reuse the oldest render frame
if (free_queue.empty()) {
auto frame = present_queue.back();
present_queue.pop_back();
return frame;
}
Frontend::Frame* frame = free_queue.front();
free_queue.pop();
return frame;
}
void ReleaseRenderFrame(Frontend::Frame* frame) override {
std::unique_lock<std::mutex> lock(swap_chain_lock);
present_queue.push_front(frame);
present_cv.notify_one();
}
// This is virtual as it is to be overriden in OGLVideoDumpingMailbox below.
virtual void LoadPresentFrame() {
// free the previous frame and add it back to the free queue
if (previous_frame) {
free_queue.push(previous_frame);
free_cv.notify_one();
}
// the newest entries are pushed to the front of the queue
Frontend::Frame* frame = present_queue.front();
present_queue.pop_front();
// remove all old entries from the present queue and move them back to the free_queue
for (auto f : present_queue) {
free_queue.push(f);
}
present_queue.clear();
previous_frame = frame;
}
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
std::unique_lock<std::mutex> lock(swap_chain_lock);
// wait for new entries in the present_queue
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
[&] { return !present_queue.empty(); });
if (present_queue.empty()) {
// timed out waiting for a frame to draw so return the previous frame
return previous_frame;
}
LoadPresentFrame();
return previous_frame;
}
};
/// This mailbox is different in that it will never discard rendered frames
class OGLVideoDumpingMailbox : public OGLTextureMailbox {
public:
bool quit = false;
Frontend::Frame* GetRenderFrame() override {
std::unique_lock<std::mutex> lock(swap_chain_lock);
// If theres no free frames, we will wait until one shows up
if (free_queue.empty()) {
free_cv.wait(lock, [&] { return (!free_queue.empty() || quit); });
if (quit) {
throw OGLTextureMailboxException("VideoDumpingMailbox quitting");
}
if (free_queue.empty()) {
LOG_CRITICAL(Render_OpenGL, "Could not get free frame");
return nullptr;
if (window_info.type == Frontend::WindowSystemType::Wayland) {
const VkWaylandSurfaceCreateInfoKHR wayland_ci{
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
static_cast<wl_display*>(window_info.display_connection),
static_cast<wl_surface*>(window_info.render_surface)};
if (vkCreateWaylandSurfaceKHR(instance, &wayland_ci, nullptr, &unsafe_surface) != VK_SUCCESS) {
LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface");
UNREACHABLE();
}
}
Frontend::Frame* frame = free_queue.front();
free_queue.pop();
return frame;
#endif
if (!unsafe_surface) {
LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
UNREACHABLE();
}
void LoadPresentFrame() override {
// free the previous frame and add it back to the free queue
if (previous_frame) {
free_queue.push(previous_frame);
free_cv.notify_one();
}
return vk::SurfaceKHR(unsafe_surface);
}
Frontend::Frame* frame = present_queue.back();
present_queue.pop_back();
previous_frame = frame;
// Do not remove entries from the present_queue, as video dumping would require
// that we preserve all frames
std::vector<const char*> RequiredExtensions(Frontend::WindowSystemType window_type, bool enable_debug_utils) {
std::vector<const char*> extensions;
extensions.reserve(6);
switch (window_type) {
case Frontend::WindowSystemType::Headless:
break;
#ifdef _WIN32
case Frontend::WindowSystemType::Windows:
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
break;
#endif
#if !defined(_WIN32) && !defined(__APPLE__)
case Frontend::WindowSystemType::X11:
extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
break;
case Frontend::WindowSystemType::Wayland:
extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
break;
#endif
default:
LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
break;
}
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
std::unique_lock<std::mutex> lock(swap_chain_lock);
// wait for new entries in the present_queue
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
[&] { return !present_queue.empty(); });
if (present_queue.empty()) {
// timed out waiting for a frame
return nullptr;
if (window_type != Frontend::WindowSystemType::Headless) {
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
}
LoadPresentFrame();
return previous_frame;
if (enable_debug_utils) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
};
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
return extensions;
}
static const char vertex_shader[] = R"(
in vec2 vert_position;
@ -353,14 +262,12 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons
}
RendererVulkan::RendererVulkan(Frontend::EmuWindow& window)
: RendererBase{window}, frame_dumper(Core::System::GetInstance().VideoDumper(), window) {
: RendererBase{window} {
window.mailbox = std::make_unique<OGLTextureMailbox>();
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
window.mailbox = nullptr;
swapchain = std::make_unique<VKSwapChain>(CreateSurface(nullptr, window));
}
RendererVulkan::~RendererVulkan() = default;
MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
@ -372,19 +279,9 @@ void RendererVulkan::SwapBuffers() {
PrepareRendertarget();
RenderScreenshot();
const auto& layout = render_window.GetFramebufferLayout();
RenderToMailbox(layout, render_window.mailbox, false);
if (frame_dumper.IsDumping()) {
try {
RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true);
} catch (const OGLTextureMailboxException& exception) {
LOG_DEBUG(Render_OpenGL, "Frame dumper exception caught: {}", exception.what());
}
}
m_current_frame++;
Core::System::GetInstance().perf_stats->EndSystemFrame();
@ -403,42 +300,8 @@ void RendererVulkan::SwapBuffers() {
}
}
void RendererVulkan::RenderScreenshot() {
if (VideoCore::g_renderer_screenshot_requested) {
// Draw this frame to the screenshot framebuffer
screenshot_framebuffer.Create();
GLuint old_read_fb = state.draw.read_framebuffer;
GLuint old_draw_fb = state.draw.draw_framebuffer;
state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle;
state.Apply();
Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout};
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
renderbuffer);
DrawScreens(layout, false);
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
VideoCore::g_screenshot_bits);
screenshot_framebuffer.Release();
state.draw.read_framebuffer = old_read_fb;
state.draw.draw_framebuffer = old_draw_fb;
state.Apply();
glDeleteRenderbuffers(1, &renderbuffer);
VideoCore::g_screenshot_complete_callback();
VideoCore::g_renderer_screenshot_requested = false;
}
}
void RendererVulkan::PrepareRendertarget() {
for (int i : {0, 1, 2}) {
for (int i = 0; i < 3; i++) {
int fb_id = i == 2 ? 1 : 0;
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
@ -450,21 +313,18 @@ void RendererVulkan::PrepareRendertarget() {
LCD::Read(color_fill.raw, lcd_color_addr);
if (color_fill.is_enabled) {
LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b,
screen_infos[i].texture);
// Resize the texture in case the framebuffer size has changed
screen_infos[i].texture.width = 1;
screen_infos[i].texture.height = 1;
LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, screen_infos[i]);
} else {
if (screen_infos[i].texture.width != (GLsizei)framebuffer.width ||
screen_infos[i].texture.height != (GLsizei)framebuffer.height ||
screen_infos[i].texture.format != framebuffer.color_format) {
auto rect = screen_infos[i].texture->GetExtent();
auto format = screen_infos[i].format;
if (rect.width != framebuffer.width || rect.height != framebuffer.height ||
format != framebuffer.color_format) {
// Reallocate texture if the framebuffer size has changed.
// This is expected to not happen very often and hence should not be a
// performance problem.
ConfigureFramebufferTexture(screen_infos[i].texture, framebuffer);
ConfigureFramebufferTexture(screen_infos[i], framebuffer);
}
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
// Resize the texture in case the framebuffer size has changed
@ -483,27 +343,6 @@ void RendererVulkan::RenderToMailbox(const Layout::FramebufferLayout& layout,
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
frame = mailbox->GetRenderFrame();
// Clean up sync objects before drawing
// INTEL driver workaround. We can't delete the previous render sync object until we are
// sure that the presentation is done
if (frame->present_fence) {
glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
}
// delete the draw fence if the frame wasn't presented
if (frame->render_fence) {
glDeleteSync(frame->render_fence);
frame->render_fence = nullptr;
}
// wait for the presentation to be done
if (frame->present_fence) {
glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(frame->present_fence);
frame->present_fence = nullptr;
}
}
{
@ -519,8 +358,6 @@ void RendererVulkan::RenderToMailbox(const Layout::FramebufferLayout& layout,
state.Apply();
DrawScreens(layout, flipped);
// Create a fence for the frontend to wait on and swap this frame to OffTex
frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
mailbox->ReleaseRenderFrame(frame);
}
}
@ -539,7 +376,7 @@ void RendererVulkan::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram
? (!right_eye ? framebuffer.address_left1 : framebuffer.address_right1)
: (!right_eye ? framebuffer.address_left2 : framebuffer.address_right2);
LOG_TRACE(Render_OpenGL, "0x{:08x} bytes from 0x{:08x}({}x{}), fmt {:x}",
LOG_TRACE(Render_Vulkan, "0x{:08x} bytes from 0x{:08x}({}x{}), fmt {:x}",
framebuffer.stride * framebuffer.height, framebuffer_addr, framebuffer.width.Value(),
framebuffer.height.Value(), framebuffer.format);
@ -553,35 +390,18 @@ void RendererVulkan::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram
// only allows rows to have a memory alignement of 4.
ASSERT(pixel_stride % 4 == 0);
if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr,
static_cast<u32>(pixel_stride), screen_info)) {
if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, static_cast<u32>(pixel_stride), screen_info)) {
// Reset the screen info's display texture to its own permanent texture
screen_info.display_texture = screen_info.texture.resource.handle;
screen_info.display_texture = screen_info.texture;
screen_info.display_texcoords = Common::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
const u8* framebuffer_data = VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr);
vk::Rect2D region{{0, 0}, {framebuffer.width, framebuffer.height}};
std::span<u8> framebuffer_data(VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr),
screen_info.texture->GetSize());
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
state.Apply();
glActiveTexture(GL_TEXTURE0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride);
// Update existing texture
// TODO: Test what happens on hardware when you change the framebuffer dimensions so that
// they differ from the LCD resolution.
// TODO: Applications could theoretically crash Citra here by specifying too large
// framebuffer sizes. We should make sure that this cannot happen.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height,
screen_info.texture.gl_format, screen_info.texture.gl_type,
framebuffer_data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
state.texture_units[0].texture_2d = 0;
state.Apply();
screen_info.texture->Upload(0, 1, pixel_stride, region, framebuffer_data);
}
}
@ -590,7 +410,7 @@ void RendererVulkan::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram
* be 1x1 but will stretch across whatever it's rendered on.
*/
void RendererVulkan::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b,
const TextureInfo& texture) {
const ScreenInfo& screen) {
state.texture_units[0].texture_2d = texture.resource.handle;
state.Apply();
@ -671,8 +491,8 @@ void RendererVulkan::ReloadSampler() {
void RendererVulkan::ReloadShader() {
// Link shaders and get variable locations
std::string shader_data;
if (GLES) {
std::string shader_data = fragment_shader;
/*if (GLES) {
shader_data += fragment_shader_precision_OES;
}
if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) {
@ -715,7 +535,7 @@ void RendererVulkan::ReloadShader() {
shader_data += shader_text;
}
}
}
}*/
shader.Create(vertex_shader, shader_data.c_str());
state.draw.shader_program = shader.handle;
state.Apply();
@ -948,8 +768,7 @@ void RendererVulkan::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
void RendererVulkan::DrawScreens(const Layout::FramebufferLayout& layout, bool flipped) {
if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
// Update background color before drawing
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
0.0f);
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f);
}
if (VideoCore::g_renderer_sampler_update_requested.exchange(false)) {
@ -1112,10 +931,13 @@ void RendererVulkan::DrawScreens(const Layout::FramebufferLayout& layout, bool f
}
void RendererVulkan::TryPresent(int timeout_ms) {
g_vk_task_scheduler->Submit(true);
const auto& layout = render_window.GetFramebufferLayout();
auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms);
if (!frame) {
LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
LOG_DEBUG(Render_Vulkan, "TryGetPresentFrame returned no frame to present");
return;
}
@ -1125,9 +947,10 @@ void RendererVulkan::TryPresent(int timeout_ms) {
// Recreate the presentation FBO if the color attachment was changed
if (frame->color_reloaded) {
LOG_DEBUG(Render_OpenGL, "Reloading present frame");
LOG_DEBUG(Render_Vulkan, "Reloading present frame");
render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
}
glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
// INTEL workaround.
// Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
@ -1154,25 +977,6 @@ void RendererVulkan::TryPresent(int timeout_ms) {
/// Updates the framerate
void RendererVulkan::UpdateFramerate() {}
void RendererVulkan::PrepareVideoDumping() {
auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get());
{
std::unique_lock lock(mailbox->swap_chain_lock);
mailbox->quit = false;
}
frame_dumper.StartDumping();
}
void RendererVulkan::CleanupVideoDumping() {
frame_dumper.StopDumping();
auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get());
{
std::unique_lock lock(mailbox->swap_chain_lock);
mailbox->quit = true;
}
mailbox->free_cv.notify_one();
}
static const char* GetSource(GLenum source) {
#define RET(s) \
case GL_DEBUG_SOURCE_##s: \
@ -1229,40 +1033,28 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum
/// Initialize the renderer
VideoCore::ResultStatus RendererVulkan::Init() {
#ifndef ANDROID
if (!gladLoadGL()) {
return VideoCore::ResultStatus::ErrorBelowGL33;
}
// Create vulkan instance
vk::ApplicationInfo app_info("PS2 Emulator", 1, nullptr, 0, VK_API_VERSION_1_3);
// Qualcomm has some spammy info messages that are marked as errors but not important
// https://developer.qualcomm.com/comment/11845
if (GLAD_GL_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(DebugHandler, nullptr);
}
#endif
// Get required extensions
auto extensions = RequiredExtensions(render_window.GetWindowInfo().type, true);
const char* gl_version{reinterpret_cast<char const*>(glGetString(GL_VERSION))};
const char* gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
const char* gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
const char* layers = "VK_LAYER_KHRONOS_validation";
vk::InstanceCreateInfo instance_info{{}, &app_info, layers, extensions};
LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
auto instance = vk::createInstance(instance_info);
auto surface = swapchain->GetSurface();
auto physical_device = instance.enumeratePhysicalDevices()[0];
// Create global instance
g_vk_instace = std::make_unique<VKInstance>();
g_vk_instace->Create(instance, physical_device, surface, true);
auto& telemetry_session = Core::System::GetInstance().TelemetrySession();
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
telemetry_session.AddField(user_system, "GPU_Vendor", std::string(gpu_vendor));
telemetry_session.AddField(user_system, "GPU_Model", std::string(gpu_model));
telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
if (!strcmp(gpu_vendor, "GDI Generic")) {
return VideoCore::ResultStatus::ErrorGenericDrivers;
}
if (!(GLAD_GL_VERSION_3_3 || GLAD_GL_ES_VERSION_3_1)) {
return VideoCore::ResultStatus::ErrorBelowGL33;
}
telemetry_session.AddField(user_system, "GPU_Vendor", "NVIDIA");
telemetry_session.AddField(user_system, "GPU_Model", "GTX 1650");
telemetry_session.AddField(user_system, "GPU_Vulkan_Version", "Vulkan 1.3");
InitOpenGLObjects();

View File

@ -10,6 +10,7 @@
#include "core/hw/gpu.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_vulkan/vk_resource_cache.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/renderer_vulkan/vk_state.h"
namespace Layout {
@ -38,8 +39,8 @@ struct ScreenInfo {
class RendererVulkan : public RendererBase {
public:
explicit RendererVulkan(Frontend::EmuWindow& window);
~RendererVulkan() override;
RendererVulkan(Frontend::EmuWindow& window);
~RendererVulkan() override = default;
/// Initialize the renderer
VideoCore::ResultStatus Init() override;
@ -59,7 +60,6 @@ private:
void ReloadSampler();
void ReloadShader();
void PrepareRendertarget();
void RenderScreenshot();
void RenderToMailbox(const Layout::FramebufferLayout& layout,
std::unique_ptr<Frontend::TextureMailbox>& mailbox, bool flipped);
void ConfigureFramebufferTexture(ScreenInfo& screen, const GPU::Regs::FramebufferConfig& framebuffer);
@ -83,11 +83,12 @@ private:
// OpenGL object IDs
VKBuffer vertex_buffer;
OGLProgram shader;
OGLSampler filter_sampler;
//OGLProgram shader;
//OGLSampler filter_sampler;
/// Display information for top and bottom screens respectively
std::array<ScreenInfo, 3> screen_infos;
std::unique_ptr<VKSwapChain> swapchain;
};
} // namespace OpenGL

View File

@ -72,7 +72,7 @@ bool VKInstance::CreateDevice(vk::SurfaceKHR surface, bool validation_enabled) {
static constexpr float queue_priorities[] = { 1.0f };
vk::DeviceCreateInfo device_info;
device_info.setPEnabledExtensionNames(device_extensions);
device_info.setPEnabledExtensionNames(extensions);
// Create queue create info structs
if (graphics_queue_family_index != present_queue_family_index) {
@ -92,8 +92,7 @@ bool VKInstance::CreateDevice(vk::SurfaceKHR surface, bool validation_enabled) {
}
// Set device features
device_info.setPEnabledFeatures(&device_features);
device_info.setPNext(&new_features);
device_info.setPEnabledFeatures(&vk_features);
// Enable debug layer on debug builds
if (validation_enabled) {

View File

@ -0,0 +1,144 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_vulkan/vk_pipeline_builder.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_shader_state.h"
#include <algorithm>
#include <array>
#include <type_traits>
namespace Vulkan {
PipelineBuilder::PipelineBuilder() {
vertex_input_state = vk::PipelineVertexInputStateCreateInfo{
{}, HardwareVertex::binding_desc, HardwareVertex::attribute_desc
};
/* Include all required pointers to the pipeline info structure */
vk::GraphicsPipelineCreateInfo pipeline_info{
{}, 0, shader_stages.data(), &vertex_input_state, &input_assembly, nullptr,
&viewport_state, &rasterization_state, &multisample_info, &depth_state,
&blend_state, &dynamic_info, nullptr, nullptr };
}
vk::Pipeline PipelineBuilder::Build() {
auto& device = g_vk_instace->GetDevice();
auto result = device.createGraphicsPipeline({}, pipeline_info);
if (result.result != vk::Result::eSuccess) {
LOG_CRITICAL(Render_Vulkan, "Failed to build vulkan pipeline!");
UNREACHABLE();
}
return result.value;
}
void PipelineBuilder::SetPipelineLayout(vk::PipelineLayout layout) {
pipeline_info.layout = layout;
}
void PipelineBuilder::SetShaderStage(vk::ShaderStageFlagBits stage, vk::ShaderModule module) {
auto result = std::ranges::find_if(shader_stages.begin(), shader_stages.end(), [stage](const auto& info) {
return info.stage == stage;
});
/* If the stage already exists, just replace the module */
if (result != shader_stages.end()) {
result->module = module;
}
else {
shader_stages.emplace_back(vk::PipelineShaderStageCreateFlags(), stage, module, "main");
pipeline_info.stageCount++;
}
}
void PipelineBuilder::SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart) {
input_assembly.topology = topology;
input_assembly.primitiveRestartEnable = enable_primitive_restart;
pipeline_info.pInputAssemblyState = &input_assembly;
}
void PipelineBuilder::SetRasterizationState(vk::PolygonMode polygon_mode, vk::CullModeFlags cull_mode,
vk::FrontFace front_face) {
rasterization_state.polygonMode = polygon_mode;
rasterization_state.cullMode = cull_mode;
rasterization_state.frontFace = front_face;
}
void PipelineBuilder::SetLineWidth(float width) {
rasterization_state.lineWidth = width;
}
void PipelineBuilder::SetMultisamples(u32 multisamples, bool per_sample_shading) {
multisample_info.rasterizationSamples = static_cast<vk::SampleCountFlagBits>(multisamples);
multisample_info.sampleShadingEnable = per_sample_shading;
multisample_info.minSampleShading = (multisamples > 1) ? 1.0f : 0.0f;
}
void PipelineBuilder::SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op) {
depth_state.depthTestEnable = depth_test;
depth_state.depthWriteEnable = depth_write;
depth_state.depthCompareOp = compare_op;
}
void PipelineBuilder::SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back) {
depth_state.stencilTestEnable = stencil_test;
depth_state.front = front;
depth_state.back = back;
}
void PipelineBuilder::SetBlendConstants(float r, float g, float b, float a) {
blend_state.blendConstants = std::array<float, 4>{r, g, b, a};
}
void PipelineBuilder::SetBlendAttachment(bool blend_enable, vk::BlendFactor src_factor, vk::BlendFactor dst_factor,
vk::BlendOp op, vk::BlendFactor alpha_src_factor,
vk::BlendFactor alpha_dst_factor, vk::BlendOp alpha_op,
vk::ColorComponentFlags write_mask) {
blend_attachment.blendEnable = blend_enable;
blend_attachment.srcColorBlendFactor = src_factor;
blend_attachment.dstColorBlendFactor = dst_factor;
blend_attachment.colorBlendOp = op;
blend_attachment.srcAlphaBlendFactor = alpha_src_factor;
blend_attachment.dstAlphaBlendFactor = alpha_dst_factor;
blend_attachment.alphaBlendOp = alpha_op;
blend_attachment.colorWriteMask = write_mask;
blend_state.attachmentCount = 1;
blend_state.pAttachments = &blend_attachment;
}
void PipelineBuilder::SetNoBlendingState() {
SetBlendAttachment(false, vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd, vk::BlendFactor::eOne,
vk::BlendFactor::eZero, vk::BlendOp::eAdd, vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
}
void PipelineBuilder::AddDynamicState(vk::DynamicState state) {
if (dynamic_info.dynamicStateCount < MAX_DYNAMIC_STATES) {
dynamic_states[dynamic_info.dynamicStateCount] = state;
dynamic_info.dynamicStateCount++;
dynamic_info.pDynamicStates = dynamic_states.data();
return;
}
LOG_ERROR(Render_Vulkan, "Cannot include more dynamic states!");
UNREACHABLE();
}
void PipelineBuilder::SetViewport(float x, float y, float width, float height, float min_depth, float max_depth) {
viewport = vk::Viewport{ x, y, width, height, min_depth, max_depth };
viewport_state.pViewports = &viewport;
viewport_state.viewportCount = 1;
}
void PipelineBuilder::SetScissorRect(s32 x, s32 y, u32 width, u32 height) {
scissor = vk::Rect2D{{x, y}, {width, height}};
viewport_state.pScissors = &scissor;
viewport_state.scissorCount = 1u;
}
} // namespace Vulkan

View File

@ -0,0 +1,76 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include "video_core/renderer_vulkan/vk_texture.h"
namespace Vulkan {
constexpr u32 MAX_DYNAMIC_STATES = 14;
constexpr u32 MAX_SHADER_STAGES = 3;
class PipelineBuilder {
public:
PipelineBuilder();
~PipelineBuilder() = default;
vk::Pipeline Build();
void SetPipelineLayout(vk::PipelineLayout layout);
void SetShaderStage(vk::ShaderStageFlagBits stage, vk::ShaderModule module);
void SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart = false);
void SetLineWidth(float width);
void SetMultisamples(u32 multisamples, bool per_sample_shading);
void SetRasterizationState(vk::PolygonMode polygon_mode, vk::CullModeFlags cull_mode,
vk::FrontFace front_face);
void SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op);
void SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back);
void SetNoDepthTestState();
void SetNoStencilState();
void SetBlendConstants(float r, float g, float b, float a);
void SetNoBlendingState();
void SetBlendAttachment(bool blend_enable, vk::BlendFactor src_factor, vk::BlendFactor dst_factor,
vk::BlendOp op, vk::BlendFactor alpha_src_factor, vk::BlendFactor alpha_dst_factor,
vk::BlendOp alpha_op,vk::ColorComponentFlags write_mask);
void SetViewport(float x, float y, float width, float height, float min_depth, float max_depth);
void SetScissorRect(s32 x, s32 y, u32 width, u32 height);
void AddDynamicState(vk::DynamicState state);
void SetMultisamples(vk::SampleCountFlagBits samples);
private:
vk::GraphicsPipelineCreateInfo pipeline_info;
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages;
vk::PipelineVertexInputStateCreateInfo vertex_input_state;
vk::PipelineInputAssemblyStateCreateInfo input_assembly;
vk::PipelineRasterizationStateCreateInfo rasterization_state;
vk::PipelineDepthStencilStateCreateInfo depth_state;
// Blending
vk::PipelineColorBlendStateCreateInfo blend_state;
vk::PipelineColorBlendAttachmentState blend_attachment;
vk::PipelineDynamicStateCreateInfo dynamic_info;
std::array<vk::DynamicState, MAX_DYNAMIC_STATES> dynamic_states;
vk::PipelineViewportStateCreateInfo viewport_state;
vk::Viewport viewport{0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
vk::Rect2D scissor;
// Multisampling
vk::PipelineMultisampleStateCreateInfo multisample_info;
};
} // namespace Vulkan

View File

@ -111,65 +111,6 @@ static_assert(sizeof(VSUniformData) < 16384,
struct ScreenInfo;
struct VertexBase {
VertexBase() = default;
VertexBase(const Pica::Shader::OutputVertex& v, bool flip_quaternion) {
position[0] = v.pos.x.ToFloat32();
position[1] = v.pos.y.ToFloat32();
position[2] = v.pos.z.ToFloat32();
position[3] = v.pos.w.ToFloat32();
color[0] = v.color.x.ToFloat32();
color[1] = v.color.y.ToFloat32();
color[2] = v.color.z.ToFloat32();
color[3] = v.color.w.ToFloat32();
tex_coord0[0] = v.tc0.x.ToFloat32();
tex_coord0[1] = v.tc0.y.ToFloat32();
tex_coord1[0] = v.tc1.x.ToFloat32();
tex_coord1[1] = v.tc1.y.ToFloat32();
tex_coord2[0] = v.tc2.x.ToFloat32();
tex_coord2[1] = v.tc2.y.ToFloat32();
tex_coord0_w = v.tc0_w.ToFloat32();
normquat[0] = v.quat.x.ToFloat32();
normquat[1] = v.quat.y.ToFloat32();
normquat[2] = v.quat.z.ToFloat32();
normquat[3] = v.quat.w.ToFloat32();
view[0] = v.view.x.ToFloat32();
view[1] = v.view.y.ToFloat32();
view[2] = v.view.z.ToFloat32();
if (flip_quaternion) {
normquat = -normquat;
}
}
glm::vec4 position;
glm::vec4 color;
glm::vec2 tex_coord0;
glm::vec2 tex_coord1;
glm::vec2 tex_coord2;
float tex_coord0_w;
glm::vec4 normquat;
glm::vec3 view;
};
/// Structure that the hardware rendered vertices are composed of
struct HardwareVertex : public VertexBase {
HardwareVertex() = default;
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion) : VertexBase(v, flip_quaternion) {};
static constexpr auto binding_desc = vk::VertexInputBindingDescription(0, sizeof(VertexBase));
static constexpr std::array<vk::VertexInputAttributeDescription, 8> attribute_desc =
{
vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, position)),
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, color)),
vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord0)),
vk::VertexInputAttributeDescription(3, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord1)),
vk::VertexInputAttributeDescription(4, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord2)),
vk::VertexInputAttributeDescription(5, 0, vk::Format::eR32Sfloat, offsetof(VertexBase, tex_coord0_w)),
vk::VertexInputAttributeDescription(6, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, normquat)),
vk::VertexInputAttributeDescription(7, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexBase, view)),
};
};
class RasterizerVulkan : public VideoCore::RasterizerInterface {
public:
explicit RasterizerVulkan(Frontend::EmuWindow& emu_window);

View File

@ -1,500 +0,0 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_vulkan/vk_resource_cache.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include <algorithm>
#include <array>
#include <type_traits>
namespace Vulkan {
VKResourceCache::~VKResourceCache() {
for (int i = 0; i < DESCRIPTOR_SET_LAYOUT_COUNT; i++) {
g_vk_instace->GetDevice().destroyDescriptorSetLayout(descriptor_layouts[i]);
}
}
bool VKResourceCache::Initialize() {
// Define the descriptor sets we will be using
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set = {{
{ 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eGeometry | vk::ShaderStageFlagBits::eFragment }, // shader_data
{ 1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex } // pica_uniforms
}};
std::array<vk::DescriptorSetLayoutBinding, 4> texture_set = {{
{ 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex0
{ 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex1
{ 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex2
{ 3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex_cube
}};
std::array<vk::DescriptorSetLayoutBinding, 3> lut_set = {{
{ 0, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_lf
{ 1, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_rg
{ 2, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment } // texture_buffer_lut_rgba
}};
// Create and store descriptor set layouts
std::array<vk::DescriptorSetLayoutCreateInfo, DESCRIPTOR_SET_LAYOUT_COUNT> create_infos = {{
{ vk::DescriptorSetLayoutCreateFlags(), ubo_set },
{ vk::DescriptorSetLayoutCreateFlags(), texture_set },
{ vk::DescriptorSetLayoutCreateFlags(), lut_set }
}};
for (int i = 0; i < DESCRIPTOR_SET_LAYOUT_COUNT; i++) {
descriptor_layouts[i] = g_vk_instace->GetDevice().createDescriptorSetLayout(create_infos[i]);
}
// Create the standard descriptor set layout
vk::PipelineLayoutCreateInfo layout_info({}, descriptor_layouts);
pipeline_layout = g_vk_instace->GetDevice().createPipelineLayoutUnique(layout_info);
return true;
}
vk::RenderPass VKResourceCache::GetRenderPass(vk::Format color_format, vk::Format depth_format,
vk::SampleCountFlagBits multisamples,
vk::AttachmentLoadOp load_op) {
// Search the cache if we can reuse an already created renderpass
RenderPassCacheKey key = {
.color = color_format,
.depth = depth_format,
.samples = multisamples
};
auto it = renderpass_cache.find(key);
if (it != renderpass_cache.end()) {
return it->second.get();
}
// Otherwise create a new one with the parameters provided
vk::SubpassDescription subpass({}, vk::PipelineBindPoint::eGraphics);
std::array<vk::AttachmentDescription, 2> attachments;
std::array<vk::AttachmentReference, 2> references;
u32 index = 0;
if (color_format != vk::Format::eUndefined) {
references[index] = vk::AttachmentReference{index, vk::ImageLayout::eColorAttachmentOptimal};
attachments[index] =
{
{},
color_format,
multisamples,
load_op,
vk::AttachmentStoreOp::eStore,
vk::AttachmentLoadOp::eDontCare,
vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eColorAttachmentOptimal,
vk::ImageLayout::eColorAttachmentOptimal
};
subpass.setColorAttachmentCount(1);
subpass.setPColorAttachments(&references[index++]);
}
if (depth_format != vk::Format::eUndefined) {
references[index] = vk::AttachmentReference{index, vk::ImageLayout::eDepthStencilAttachmentOptimal};
attachments[index] =
{
{},
depth_format,
static_cast<vk::SampleCountFlagBits>(multisamples),
load_op,
vk::AttachmentStoreOp::eStore,
vk::AttachmentLoadOp::eDontCare,
vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eDepthStencilAttachmentOptimal,
vk::ImageLayout::eDepthStencilAttachmentOptimal
};
subpass.setPDepthStencilAttachment(&references[index++]);
}
std::array<vk::SubpassDescription, 1> subpasses = { subpass };
vk::RenderPassCreateInfo renderpass_info({}, attachments, subpasses);
auto renderpass = g_vk_instace->GetDevice().createRenderPassUnique(renderpass_info);
vk::RenderPass handle = renderpass.get();
renderpass_cache.emplace(key, std::move(renderpass));
return handle;
}
Pipeline::Pipeline() { Clear(); }
void Pipeline::Clear()
{
m_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
m_ci.pNext = nullptr;
m_ci.flags = 0;
m_ci.pSetLayouts = nullptr;
m_ci.setLayoutCount = 0;
m_ci.pPushConstantRanges = nullptr;
m_ci.pushConstantRangeCount = 0;
}
void Pipeline::Build() {
VkPipelineLayout layout;
VkResult res = vkCreatePipelineLayout(device, &m_ci, nullptr, &layout);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreatePipelineLayout() failed: ");
return VK_NULL_HANDLE;
}
Clear();
return layout;
}
void Pipeline::AddDescriptorSet(VkDescriptorSetLayout layout)
{
pxAssert(m_ci.setLayoutCount < MAX_SETS);
m_sets[m_ci.setLayoutCount] = layout;
m_ci.setLayoutCount++;
m_ci.pSetLayouts = m_sets.data();
}
void Pipeline::AddPushConstants(VkShaderStageFlags stages, u32 offset, u32 size)
{
pxAssert(m_ci.pushConstantRangeCount < MAX_PUSH_CONSTANTS);
VkPushConstantRange& r = m_push_constants[m_ci.pushConstantRangeCount];
r.stageFlags = stages;
r.offset = offset;
r.size = size;
m_ci.pushConstantRangeCount++;
m_ci.pPushConstantRanges = m_push_constants.data();
}
GraphicsPipelineBuilder::GraphicsPipelineBuilder() { Clear(); }
void GraphicsPipelineBuilder::Clear()
{
m_ci = {};
m_ci.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
m_shader_stages = {};
m_vertex_input_state = {};
m_vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
m_ci.pVertexInputState = &m_vertex_input_state;
m_vertex_attributes = {};
m_vertex_buffers = {};
m_input_assembly = {};
m_input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
m_rasterization_state = {};
m_rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
m_rasterization_state.lineWidth = 1.0f;
m_depth_state = {};
m_depth_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
m_blend_state = {};
m_blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
m_blend_attachments = {};
m_viewport_state = {};
m_viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
m_viewport = {};
m_scissor = {};
m_dynamic_state = {};
m_dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
m_dynamic_state_values = {};
m_multisample_state = {};
m_multisample_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
m_provoking_vertex = {};
m_provoking_vertex.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT;
// set defaults
SetNoCullRasterizationState();
SetNoDepthTestState();
SetNoBlendingState();
SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
// have to be specified even if dynamic
SetViewport(0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f);
SetScissorRect(0, 0, 1, 1);
SetMultisamples(VK_SAMPLE_COUNT_1_BIT);
}
VkPipeline GraphicsPipelineBuilder::Create(VkDevice device, VkPipelineCache pipeline_cache, bool clear /* = true */)
{
VkPipeline pipeline;
VkResult res = vkCreateGraphicsPipelines(device, pipeline_cache, 1, &m_ci, nullptr, &pipeline);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines() failed: ");
return VK_NULL_HANDLE;
}
if (clear)
Clear();
return pipeline;
}
void GraphicsPipelineBuilder::SetShaderStage(
VkShaderStageFlagBits stage, VkShaderModule module, const char* entry_point)
{
pxAssert(m_ci.stageCount < MAX_SHADER_STAGES);
u32 index = 0;
for (; index < m_ci.stageCount; index++)
{
if (m_shader_stages[index].stage == stage)
break;
}
if (index == m_ci.stageCount)
{
m_ci.stageCount++;
m_ci.pStages = m_shader_stages.data();
}
VkPipelineShaderStageCreateInfo& s = m_shader_stages[index];
s.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
s.stage = stage;
s.module = module;
s.pName = entry_point;
}
void GraphicsPipelineBuilder::AddVertexBuffer(
u32 binding, u32 stride, VkVertexInputRate input_rate /*= VK_VERTEX_INPUT_RATE_VERTEX*/)
{
pxAssert(m_vertex_input_state.vertexAttributeDescriptionCount < MAX_VERTEX_BUFFERS);
VkVertexInputBindingDescription& b = m_vertex_buffers[m_vertex_input_state.vertexBindingDescriptionCount];
b.binding = binding;
b.stride = stride;
b.inputRate = input_rate;
m_vertex_input_state.vertexBindingDescriptionCount++;
m_vertex_input_state.pVertexBindingDescriptions = m_vertex_buffers.data();
m_ci.pVertexInputState = &m_vertex_input_state;
}
void GraphicsPipelineBuilder::AddVertexAttribute(u32 location, u32 binding, VkFormat format, u32 offset)
{
pxAssert(m_vertex_input_state.vertexAttributeDescriptionCount < MAX_VERTEX_BUFFERS);
VkVertexInputAttributeDescription& a =
m_vertex_attributes[m_vertex_input_state.vertexAttributeDescriptionCount];
a.location = location;
a.binding = binding;
a.format = format;
a.offset = offset;
m_vertex_input_state.vertexAttributeDescriptionCount++;
m_vertex_input_state.pVertexAttributeDescriptions = m_vertex_attributes.data();
m_ci.pVertexInputState = &m_vertex_input_state;
}
void GraphicsPipelineBuilder::SetPrimitiveTopology(
VkPrimitiveTopology topology, bool enable_primitive_restart /*= false*/)
{
m_input_assembly.topology = topology;
m_input_assembly.primitiveRestartEnable = enable_primitive_restart;
m_ci.pInputAssemblyState = &m_input_assembly;
}
void GraphicsPipelineBuilder::SetRasterizationState(
VkPolygonMode polygon_mode, VkCullModeFlags cull_mode, VkFrontFace front_face)
{
m_rasterization_state.polygonMode = polygon_mode;
m_rasterization_state.cullMode = cull_mode;
m_rasterization_state.frontFace = front_face;
m_ci.pRasterizationState = &m_rasterization_state;
}
void GraphicsPipelineBuilder::SetLineWidth(float width) { m_rasterization_state.lineWidth = width; }
void GraphicsPipelineBuilder::SetMultisamples(u32 multisamples, bool per_sample_shading)
{
m_multisample_state.rasterizationSamples = static_cast<VkSampleCountFlagBits>(multisamples);
m_multisample_state.sampleShadingEnable = per_sample_shading;
m_multisample_state.minSampleShading = (multisamples > 1) ? 1.0f : 0.0f;
}
void GraphicsPipelineBuilder::SetNoCullRasterizationState()
{
SetRasterizationState(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
}
void GraphicsPipelineBuilder::SetDepthState(bool depth_test, bool depth_write, VkCompareOp compare_op)
{
m_depth_state.depthTestEnable = depth_test;
m_depth_state.depthWriteEnable = depth_write;
m_depth_state.depthCompareOp = compare_op;
m_ci.pDepthStencilState = &m_depth_state;
}
void GraphicsPipelineBuilder::SetStencilState(
bool stencil_test, const VkStencilOpState& front, const VkStencilOpState& back)
{
m_depth_state.stencilTestEnable = stencil_test;
m_depth_state.front = front;
m_depth_state.back = back;
}
void GraphicsPipelineBuilder::SetNoStencilState()
{
m_depth_state.stencilTestEnable = VK_FALSE;
m_depth_state.front = {};
m_depth_state.back = {};
}
void GraphicsPipelineBuilder::SetNoDepthTestState() { SetDepthState(false, false, VK_COMPARE_OP_ALWAYS); }
void GraphicsPipelineBuilder::SetBlendConstants(float r, float g, float b, float a)
{
m_blend_state.blendConstants[0] = r;
m_blend_state.blendConstants[1] = g;
m_blend_state.blendConstants[2] = b;
m_blend_state.blendConstants[3] = a;
m_ci.pColorBlendState = &m_blend_state;
}
void GraphicsPipelineBuilder::AddBlendAttachment(bool blend_enable, VkBlendFactor src_factor,
VkBlendFactor dst_factor, VkBlendOp op, VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor,
VkBlendOp alpha_op,
VkColorComponentFlags
write_mask /* = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT */)
{
pxAssert(m_blend_state.attachmentCount < MAX_ATTACHMENTS);
VkPipelineColorBlendAttachmentState& bs = m_blend_attachments[m_blend_state.attachmentCount];
bs.blendEnable = blend_enable;
bs.srcColorBlendFactor = src_factor;
bs.dstColorBlendFactor = dst_factor;
bs.colorBlendOp = op;
bs.srcAlphaBlendFactor = alpha_src_factor;
bs.dstAlphaBlendFactor = alpha_dst_factor;
bs.alphaBlendOp = alpha_op;
bs.colorWriteMask = write_mask;
m_blend_state.attachmentCount++;
m_blend_state.pAttachments = m_blend_attachments.data();
m_ci.pColorBlendState = &m_blend_state;
}
void GraphicsPipelineBuilder::SetBlendAttachment(u32 attachment, bool blend_enable, VkBlendFactor src_factor,
VkBlendFactor dst_factor, VkBlendOp op, VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor,
VkBlendOp alpha_op,
VkColorComponentFlags
write_mask /*= VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT*/)
{
pxAssert(attachment < MAX_ATTACHMENTS);
VkPipelineColorBlendAttachmentState& bs = m_blend_attachments[attachment];
bs.blendEnable = blend_enable;
bs.srcColorBlendFactor = src_factor;
bs.dstColorBlendFactor = dst_factor;
bs.colorBlendOp = op;
bs.srcAlphaBlendFactor = alpha_src_factor;
bs.dstAlphaBlendFactor = alpha_dst_factor;
bs.alphaBlendOp = alpha_op;
bs.colorWriteMask = write_mask;
if (attachment >= m_blend_state.attachmentCount)
{
m_blend_state.attachmentCount = attachment + 1u;
m_blend_state.pAttachments = m_blend_attachments.data();
m_ci.pColorBlendState = &m_blend_state;
}
}
void GraphicsPipelineBuilder::AddBlendFlags(u32 flags)
{
m_blend_state.flags |= flags;
}
void GraphicsPipelineBuilder::ClearBlendAttachments()
{
m_blend_attachments = {};
m_blend_state.attachmentCount = 0;
}
void GraphicsPipelineBuilder::SetNoBlendingState()
{
ClearBlendAttachments();
SetBlendAttachment(0, false, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD,
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT);
}
void GraphicsPipelineBuilder::AddDynamicState(VkDynamicState state)
{
pxAssert(m_dynamic_state.dynamicStateCount < MAX_DYNAMIC_STATE);
m_dynamic_state_values[m_dynamic_state.dynamicStateCount] = state;
m_dynamic_state.dynamicStateCount++;
m_dynamic_state.pDynamicStates = m_dynamic_state_values.data();
m_ci.pDynamicState = &m_dynamic_state;
}
void GraphicsPipelineBuilder::SetDynamicViewportAndScissorState()
{
AddDynamicState(VK_DYNAMIC_STATE_VIEWPORT);
AddDynamicState(VK_DYNAMIC_STATE_SCISSOR);
}
void GraphicsPipelineBuilder::SetViewport(
float x, float y, float width, float height, float min_depth, float max_depth)
{
m_viewport.x = x;
m_viewport.y = y;
m_viewport.width = width;
m_viewport.height = height;
m_viewport.minDepth = min_depth;
m_viewport.maxDepth = max_depth;
m_viewport_state.pViewports = &m_viewport;
m_viewport_state.viewportCount = 1u;
m_ci.pViewportState = &m_viewport_state;
}
void GraphicsPipelineBuilder::SetScissorRect(s32 x, s32 y, u32 width, u32 height)
{
m_scissor.offset.x = x;
m_scissor.offset.y = y;
m_scissor.extent.width = width;
m_scissor.extent.height = height;
m_viewport_state.pScissors = &m_scissor;
m_viewport_state.scissorCount = 1u;
m_ci.pViewportState = &m_viewport_state;
}
void GraphicsPipelineBuilder::SetMultisamples(VkSampleCountFlagBits samples)
{
m_multisample_state.rasterizationSamples = samples;
m_ci.pMultisampleState = &m_multisample_state;
}
void GraphicsPipelineBuilder::SetPipelineLayout(VkPipelineLayout layout) { m_ci.layout = layout; }
void GraphicsPipelineBuilder::SetRenderPass(VkRenderPass render_pass, u32 subpass)
{
m_ci.renderPass = render_pass;
m_ci.subpass = subpass;
}
void GraphicsPipelineBuilder::SetProvokingVertex(VkProvokingVertexModeEXT mode)
{
Util::AddPointerToChain(&m_rasterization_state, &m_provoking_vertex);
m_provoking_vertex.provokingVertexMode = mode;
}
} // namespace Vulkan

View File

@ -1,108 +0,0 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include "video_core/renderer_vulkan/vk_texture.h"
namespace Vulkan {
struct RenderPassCacheKey {
vk::Format color, depth;
vk::SampleCountFlagBits samples;
};
constexpr u32 DESCRIPTOR_SET_LAYOUT_COUNT = 3;
/// Wrapper class that manages resource caching and storage.
/// It stores pipelines and renderpasses
class VKResourceCache {
public:
VKResourceCache() = default;
~VKResourceCache();
// Perform at startup, create descriptor layouts, compiles all static shaders.
bool Initialize();
void Shutdown();
// Public interface.
vk::PipelineCache GetPipelineCache() const { return pipeline_cache.get(); }
vk::RenderPass GetRenderPass(vk::Format color_format, vk::Format depth_format,
vk::SampleCountFlagBits multisamples,
vk::AttachmentLoadOp load_op);
auto& GetDescriptorLayouts() const { return descriptor_layouts; }
private:
// Descriptor sets
std::array<vk::DescriptorSetLayout, DESCRIPTOR_SET_LAYOUT_COUNT> descriptor_layouts;
vk::UniquePipelineLayout pipeline_layout;
// Render pass cache
std::unordered_map<RenderPassCacheKey, vk::UniqueRenderPass> renderpass_cache;
vk::UniquePipelineCache pipeline_cache;
std::string pipeline_cache_filename;
};
constexpr u32 MAX_DYNAMIC_STATES = 8;
constexpr u32 MAX_ATTACHMENTS = 2;
constexpr u32 MAX_VERTEX_BUFFERS = 3;
class Pipeline {
public:
Pipeline();
~Pipeline() = default;
void Build();
void SetShaderStage(vk::ShaderStageFlagBits stage, vk::ShaderModule module);
void AddVertexBuffer(u32 binding, u32 stride, vk::VertexInputRate input_rate);
void AddVertexAttribute(u32 location, u32 binding, VkFormat format, u32 offset);
void SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart = false);
void SetRasterizationState(vk::PolygonMode polygon_mode, vk::CullModeFlags cull_mode,
vk::FrontFace front_face);
void SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op);
void SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back);
void SetNoDepthTestState();
void SetNoStencilState();
void AddDynamicState(vk::DynamicState state);
void SetMultisamples(VkSampleCountFlagBits samples);
private:
vk::GraphicsPipelineCreateInfo pipeline_info;
std::array<vk::PipelineShaderStageCreateInfo, 3> shader_stages;
vk::PipelineVertexInputStateCreateInfo vertex_input_state;
vk::PipelineInputAssemblyStateCreateInfo input_assembly;
vk::PipelineRasterizationStateCreateInfo rasterization_state;
vk::PipelineDepthStencilStateCreateInfo depth_state;
// Blending
vk::PipelineColorBlendStateCreateInfo blend_state;
std::array<vk::PipelineColorBlendAttachmentState, MAX_ATTACHMENTS> blend_attachments;
std::array<vk::DynamicState, MAX_DYNAMIC_STATES> dynamic_state_values;
VkPipelineViewportStateCreateInfo m_viewport_state;
VkViewport m_viewport;
VkRect2D m_scissor;
VkPipelineDynamicStateCreateInfo m_dynamic_state;
vk::PipelineMultisampleStateCreateInfo multisample_info;
};
extern std::unique_ptr<VKResourceCache> g_vk_res_cache;
} // namespace Vulkan

View File

@ -11,12 +11,73 @@
#include <string>
#include <type_traits>
#include <vulkan/vulkan.hpp>
#include <glm/glm.hpp>
#include "common/hash.h"
#include "video_core/regs.h"
#include "video_core/shader/shader.h"
namespace Vulkan {
/* Vertex attributes */
struct VertexBase {
VertexBase() = default;
VertexBase(const Pica::Shader::OutputVertex& v, bool flip_quaternion) {
position[0] = v.pos.x.ToFloat32();
position[1] = v.pos.y.ToFloat32();
position[2] = v.pos.z.ToFloat32();
position[3] = v.pos.w.ToFloat32();
color[0] = v.color.x.ToFloat32();
color[1] = v.color.y.ToFloat32();
color[2] = v.color.z.ToFloat32();
color[3] = v.color.w.ToFloat32();
tex_coord0[0] = v.tc0.x.ToFloat32();
tex_coord0[1] = v.tc0.y.ToFloat32();
tex_coord1[0] = v.tc1.x.ToFloat32();
tex_coord1[1] = v.tc1.y.ToFloat32();
tex_coord2[0] = v.tc2.x.ToFloat32();
tex_coord2[1] = v.tc2.y.ToFloat32();
tex_coord0_w = v.tc0_w.ToFloat32();
normquat[0] = v.quat.x.ToFloat32();
normquat[1] = v.quat.y.ToFloat32();
normquat[2] = v.quat.z.ToFloat32();
normquat[3] = v.quat.w.ToFloat32();
view[0] = v.view.x.ToFloat32();
view[1] = v.view.y.ToFloat32();
view[2] = v.view.z.ToFloat32();
if (flip_quaternion) {
normquat = -normquat;
}
}
glm::vec4 position;
glm::vec4 color;
glm::vec2 tex_coord0;
glm::vec2 tex_coord1;
glm::vec2 tex_coord2;
float tex_coord0_w;
glm::vec4 normquat;
glm::vec3 view;
};
/// Structure that the hardware rendered vertices are composed of
struct HardwareVertex : public VertexBase {
HardwareVertex() = default;
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion) : VertexBase(v, flip_quaternion) {};
static constexpr auto binding_desc = vk::VertexInputBindingDescription(0, sizeof(VertexBase));
static constexpr std::array<vk::VertexInputAttributeDescription, 8> attribute_desc =
{
vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, position)),
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, color)),
vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord0)),
vk::VertexInputAttributeDescription(3, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord1)),
vk::VertexInputAttributeDescription(4, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord2)),
vk::VertexInputAttributeDescription(5, 0, vk::Format::eR32Sfloat, offsetof(VertexBase, tex_coord0_w)),
vk::VertexInputAttributeDescription(6, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, normquat)),
vk::VertexInputAttributeDescription(7, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexBase, view)),
};
};
enum class ProgramType : u32 { VS, GS, FS };
enum Attributes {

View File

@ -6,7 +6,6 @@
#include <shaderc/shaderc.hpp>
#include "video_core/renderer_vulkan/vk_state.h"
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
#include "video_core/renderer_vulkan/vk_resource_cache.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_shader_gen.h"
@ -60,11 +59,6 @@ void VulkanState::Create() {
vk::DescriptorPoolCreateInfo pool_create_info({}, 1024, pool_sizes);
desc_pool = device.createDescriptorPoolUnique(pool_create_info);
// Create descriptor sets
auto& layouts = g_vk_res_cache->GetDescriptorLayouts();
vk::DescriptorSetAllocateInfo alloc_info(desc_pool.get(), layouts);
descriptor_sets = device.allocateDescriptorSetsUnique(alloc_info);
// Create texture sampler
auto props = g_vk_instace->GetPhysicalDevice().getProperties();
vk::SamplerCreateInfo sampler_info{
@ -75,47 +69,15 @@ void VulkanState::Create() {
false, vk::CompareOp::eAlways, {}, {},
vk::BorderColor::eIntOpaqueBlack, false
};
sampler = g_vk_instace->GetDevice().createSamplerUnique(sampler_info);
// Define the descriptor sets we will be using
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set = {{
{ 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eGeometry | vk::ShaderStageFlagBits::eFragment }, // shader_data
{ 1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex } // pica_uniforms
}};
std::array<vk::DescriptorSetLayoutBinding, 4> texture_set = {{
{ 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex0
{ 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex1
{ 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex2
{ 3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex_cube
}};
std::array<vk::DescriptorSetLayoutBinding, 3> lut_set = {{
{ 0, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_lf
{ 1, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_rg
{ 2, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment } // texture_buffer_lut_rgba
}};
// Create and store descriptor set layouts
std::array<vk::DescriptorSetLayoutCreateInfo, DESCRIPTOR_SET_LAYOUT_COUNT> create_infos = {{
{ vk::DescriptorSetLayoutCreateFlags(), ubo_set },
{ vk::DescriptorSetLayoutCreateFlags(), texture_set },
{ vk::DescriptorSetLayoutCreateFlags(), lut_set }
}};
for (int i = 0; i < DESCRIPTOR_SET_LAYOUT_COUNT; i++) {
descriptor_layouts[i] = g_vk_instace->GetDevice().createDescriptorSetLayout(create_infos[i]);
}
// Create the standard descriptor set layout
vk::PipelineLayoutCreateInfo layout_info({}, descriptor_layouts);
pipeline_layout = g_vk_instace->GetDevice().createPipelineLayoutUnique(layout_info);
sampler = device.createSamplerUnique(sampler_info);
// Compile trivial vertex shader
auto source = GenerateTrivialVertexShader(true);
MakeShader(source.code, vk::ShaderStageFlagBits::eVertex);
trivial_vertex_shader = vk::UniqueShaderModule{CompileShader(source.code, vk::ShaderStageFlagBits::eVertex)};
// Configure descriptor sets and pipeline builder
ConfigureDescriptorSets();
ConfigurePipeline();
dirty_flags |= DirtyFlags::All;
}
@ -186,13 +148,14 @@ void VulkanState::UnbindTexture(u32 index) {
dirty_flags |= DirtyFlags::Texture;
}
void VulkanState::PushRenderTargets(VKTexture* color, VKTexture* depth_stencil) {
color_attachment = color;
depth_attachment = depth_stencil;
void VulkanState::PushAttachment(Attachment attachment) {
targets.push_back(attachment);
}
void VulkanState::SetRenderArea(vk::Rect2D new_render_area) {
render_area = new_render_area;
void VulkanState::PopAttachment() {
if (!targets.empty()) {
targets.pop_back();
}
}
void VulkanState::BeginRendering() {
@ -201,21 +164,27 @@ void VulkanState::BeginRendering() {
}
// Make sure attachments are in optimal layout
color_attachment->Transition(vk::ImageLayout::eColorAttachmentOptimal);
depth_attachment->Transition(vk::ImageLayout::eDepthStencilAttachmentOptimal);
auto& attachment = targets.back();
vk::RenderingInfo render_info{{}, attachment.render_area, 1, {}};
std::array<vk::RenderingAttachmentInfo, 2> infos{};
if (attachment.color) {
attachment.color->Transition(vk::ImageLayout::eColorAttachmentOptimal);
infos[0] = {attachment.color->GetView(), attachment.color->GetLayout()};
render_info.colorAttachmentCount = 1;
render_info.pColorAttachments = &infos[0];
}
if (attachment.depth_stencil) {
attachment.depth_stencil->Transition(vk::ImageLayout::eDepthStencilAttachmentOptimal);
infos[1] = {attachment.depth_stencil->GetView(), attachment.depth_stencil->GetLayout()};
render_info.pDepthAttachment = &infos[1];
render_info.pStencilAttachment = &infos[1];
}
// Begin rendering
vk::RenderingAttachmentInfoKHR color_info(color_attachment->GetView(), color_attachment->GetLayout());
vk::RenderingAttachmentInfoKHR depth_stencil_info(depth_attachment->GetView(), depth_attachment->GetLayout());
vk::RenderingInfo render_info
(
{}, render_area, 1, {},
color_info,
&depth_stencil_info,
&depth_stencil_info
);
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.beginRendering(render_info);
rendering = true;
@ -304,7 +273,7 @@ void VulkanState::SetStencilTest(bool enable, vk::StencilOp fail, vk::StencilOp
fail_op = fail;
pass_op = pass;
depth_fail_op = depth_fail;
compare_op = compare;
stencil_op = compare;
dirty_flags |= DirtyFlags::Stencil;
}
@ -317,7 +286,7 @@ void VulkanState::SetDepthWrite(bool enable) {
void VulkanState::SetDepthTest(bool enable, vk::CompareOp compare) {
depth_enabled = enable;
test_func = compare;
depth_op = compare;
dirty_flags |= DirtyFlags::DepthTest;
}
@ -333,36 +302,41 @@ void VulkanState::SetBlendOp(vk::BlendOp rgb_op, vk::BlendOp alpha_op, vk::Blend
}
void VulkanState::SetFragmentShader(const Pica::Regs& regs) {
vk::Pipeline pipeline;
pipeline_key.fragment_config = PicaFSConfig::BuildFromRegs(regs);
auto it1 = pipelines.find(pipeline_key);
do {
// Try to use an already complete pipeline
vk::Pipeline pipeline;
if (it1 != pipelines.end()) {
pipeline = it1->second.get();
break;
}
else {
// Maybe the shader has been compiled but the pipeline state changed?
auto shader = fragment_shaders.find(pipeline_key.fragment_config);
if (shader != fragment_shaders.end()) {
pipeline = MakePipeline(shader->second.get());
break;
builder.SetShaderStage(vk::ShaderStageFlagBits::eFragment, shader->second.get());
pipeline = builder.Build();
}
else {
// Re-compile shader module and create new pipeline
auto result = GenerateFragmentShader(pipeline_key.fragment_config);
auto module = MakeShader(result.code, vk::ShaderStageFlagBits::eFragment);
pipeline = MakePipeline(module);
} while (false);
auto module = CompileShader(result.code, vk::ShaderStageFlagBits::eFragment);
fragment_shaders.emplace(pipeline_key.fragment_config, vk::UniqueShaderModule{module});
builder.SetShaderStage(vk::ShaderStageFlagBits::eFragment, shader->second.get());
pipeline = builder.Build();
}
// Cache the resulted pipeline
pipelines.emplace(pipeline_key, vk::UniquePipeline{pipeline});
}
// Bind the pipeline
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
}
vk::ShaderModule VulkanState::MakeShader(const std::string& source, vk::ShaderStageFlagBits stage) {
vk::ShaderModule VulkanState::CompileShader(const std::string& source, vk::ShaderStageFlagBits stage) {
shaderc::Compiler compiler;
shaderc::CompileOptions options;
options.SetOptimizationLevel(shaderc_optimization_level_performance);
@ -393,73 +367,8 @@ vk::ShaderModule VulkanState::MakeShader(const std::string& source, vk::ShaderSt
vk::ShaderModuleCreateInfo shader_info{{}, shader_code};
auto& device = g_vk_instace->GetDevice();
auto shader = device.createShaderModuleUnique(shader_info);
if (stage == vk::ShaderStageFlagBits::eFragment) {
auto handle = shader.get();
fragment_shaders[pipeline_key.fragment_config] = std::move(shader);
return handle;
}
else if (stage == vk::ShaderStageFlagBits::eVertex) {
trivial_vertex_shader = std::move(shader);
return trivial_vertex_shader.get();
}
UNREACHABLE();
}
vk::Pipeline VulkanState::MakePipeline(vk::ShaderModule fragment) {
std::array<vk::PipelineShaderStageCreateInfo, 2> shader_stages {{
{ {}, vk::ShaderStageFlagBits::eVertex, trivial_vertex_shader.get(), "main" },
{ {}, vk::ShaderStageFlagBits::eFragment, fragment, "main" }
}};
vk::PipelineVertexInputStateCreateInfo vertex_input_info{
{}, HardwareVertex::binding_desc, HardwareVertex::attribute_desc
};
vk::PipelineInputAssemblyStateCreateInfo input_assembly{{}, vk::PrimitiveTopology::eTriangleList, false};
vk::PipelineRasterizationStateCreateInfo rasterizer{
{}, false, false, vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone,
vk::FrontFace::eClockwise, false
};
vk::PipelineMultisampleStateCreateInfo multisampling{{}, vk::SampleCountFlagBits::e1};
vk::PipelineColorBlendStateCreateInfo color_blending{{}, false, vk::LogicOp::eCopy, pipeline_key.blend_config};
// Enable every required dynamic state
std::array<vk::DynamicState, 14> dynamic_states{
vk::DynamicState::eDepthCompareOp, vk::DynamicState::eLineWidth,
vk::DynamicState::eDepthTestEnable, vk::DynamicState::eColorWriteEnableEXT,
vk::DynamicState::eStencilTestEnable, vk::DynamicState::eStencilOp,
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask,
vk::DynamicState::eCullMode, vk::DynamicState::eBlendConstants,
vk::DynamicState::eViewport, vk::DynamicState::eScissor,
vk::DynamicState::eLogicOpEXT, vk::DynamicState::eFrontFace
};
vk::PipelineDynamicStateCreateInfo dynamic_info{{}, dynamic_states};
vk::PipelineDepthStencilStateCreateInfo depth_info{
{}, true, true, vk::CompareOp::eGreaterOrEqual, false, true
};
vk::GraphicsPipelineCreateInfo pipeline_info{
{}, shader_stages, &vertex_input_info, &input_assembly, nullptr, nullptr,
&rasterizer, &multisampling, &depth_info, &color_blending, &dynamic_info,
pipeline_layout.get(), nullptr
};
auto& device = g_vk_instace->GetDevice();
auto result = device.createGraphicsPipelineUnique(nullptr, pipeline_info);
if (result.result == vk::Result::eSuccess) {
auto handle = result.value.get();
pipelines[pipeline_key] = std::move(result.value);
return handle;
}
return VK_NULL_HANDLE;
auto shader = device.createShaderModule(shader_info);
return shader;
}
void VulkanState::Apply() {
@ -487,9 +396,126 @@ void VulkanState::Apply() {
command_buffer.setScissor(0, scissor);
}
if (dirty_flags & DirtyFlags::DepthTest) {
command_buffer.setDepthTestEnable(depth_enabled);
command_buffer.setDepthCompareOp(depth_op);
}
if (dirty_flags & DirtyFlags::Stencil) {
command_buffer.setStencilTestEnable(stencil_enabled);
command_buffer.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, stencil_ref);
command_buffer.setStencilOp(vk::StencilFaceFlagBits::eFrontAndBack, fail_op, pass_op,
depth_fail_op, stencil_op);
}
if (dirty_flags & DirtyFlags::LogicOp) {
command_buffer.setLogicOpEXT(logic_op);
}
if (dirty_flags & DirtyFlags::CullMode) {
command_buffer.setCullMode(cull_mode);
}
if (dirty_flags & DirtyFlags::FrontFace) {
command_buffer.setFrontFace(front_face);
}
if (dirty_flags & DirtyFlags::BlendConsts) {
command_buffer.setBlendConstants(blend_constants.data());
}
if (dirty_flags & DirtyFlags::StencilMask) {
command_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, stencil_write_mask);
command_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, stencil_input_mask);
}
if (dirty_flags & DirtyFlags::DepthWrite) {
command_buffer.setDepthWriteEnable(depth_writes);
}
dirty_flags = DirtyFlags::None;
}
void VulkanState::ConfigureDescriptorSets() {
// Define the descriptor sets we will be using
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set = {{
{ 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eGeometry | vk::ShaderStageFlagBits::eFragment }, // shader_data
{ 1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex } // pica_uniforms
}};
std::array<vk::DescriptorSetLayoutBinding, 4> texture_set = {{
{ 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex0
{ 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex1
{ 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex2
{ 3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex_cube
}};
std::array<vk::DescriptorSetLayoutBinding, 3> lut_set = {{
{ 0, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_lf
{ 1, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_rg
{ 2, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment } // texture_buffer_lut_rgba
}};
// Create and store descriptor set layouts
std::array<vk::DescriptorSetLayoutCreateInfo, DESCRIPTOR_SET_LAYOUT_COUNT> create_infos{{
{ vk::DescriptorSetLayoutCreateFlags(), ubo_set },
{ vk::DescriptorSetLayoutCreateFlags(), texture_set },
{ vk::DescriptorSetLayoutCreateFlags(), lut_set }
}};
auto& device = g_vk_instace->GetDevice();
for (int i = 0; i < DESCRIPTOR_SET_LAYOUT_COUNT; i++) {
descriptor_layouts[i] = device.createDescriptorSetLayout(create_infos[i]);
}
vk::DescriptorSetAllocateInfo alloc_info(desc_pool.get(), descriptor_layouts);
descriptor_sets = device.allocateDescriptorSetsUnique(alloc_info);
// Create the standard descriptor set layout
vk::PipelineLayoutCreateInfo layout_info({}, descriptor_layouts);
pipeline_layout = device.createPipelineLayoutUnique(layout_info);
}
void VulkanState::ConfigurePipeline() {
builder.SetPipelineLayout(pipeline_layout.get());
// Set rasterization state
builder.SetPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
builder.SetLineWidth(1.0f);
builder.SetRasterizationState(vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone,
vk::FrontFace::eClockwise);
// Set depth, stencil tests and blending
builder.SetNoDepthTestState();
builder.SetNoStencilState();
builder.SetBlendConstants(1.f, 1.f, 1.f, 1.f);
builder.SetBlendAttachment(true, vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
// Enable every required dynamic state
std::array<vk::DynamicState, MAX_DYNAMIC_STATES> dynamic_states{
vk::DynamicState::eDepthCompareOp, vk::DynamicState::eLineWidth,
vk::DynamicState::eDepthTestEnable, vk::DynamicState::eColorWriteEnableEXT,
vk::DynamicState::eStencilTestEnable, vk::DynamicState::eStencilOp,
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask,
vk::DynamicState::eCullMode, vk::DynamicState::eBlendConstants,
vk::DynamicState::eViewport, vk::DynamicState::eScissor,
vk::DynamicState::eLogicOpEXT, vk::DynamicState::eFrontFace
};
for (auto& state : dynamic_states) {
builder.AddDynamicState(state);
}
// Add trivial vertex shader
builder.SetShaderStage(vk::ShaderStageFlagBits::eVertex, trivial_vertex_shader.get());
}
void VulkanState::UpdateDescriptorSet() {
std::vector<vk::WriteDescriptorSet> writes;
std::vector<vk::DescriptorBufferInfo> buffer_infos;

View File

@ -9,6 +9,7 @@
#include <xxhash.h>
#include "video_core/regs.h"
#include "video_core/renderer_vulkan/vk_shader_state.h"
#include "video_core/renderer_vulkan/vk_pipeline_builder.h"
#include "video_core/renderer_vulkan/vk_texture.h"
namespace Vulkan {
@ -54,6 +55,15 @@ BindingID operator + (BindingID lhs, u32 rhs) {
return static_cast<BindingID>(static_cast<u32>(lhs) + rhs);
}
struct Attachment {
VKTexture* color{}, *depth_stencil{};
vk::ClearColorValue clear_color;
vk::ClearDepthStencilValue depth_color;
vk::Rect2D render_area{-1};
};
constexpr u32 DESCRIPTOR_SET_LAYOUT_COUNT = 3;
/// Tracks global Vulkan state
class VulkanState {
public:
@ -87,9 +97,8 @@ public:
vk::BlendFactor src_alpha, vk::BlendFactor dst_alpha);
/// Rendering
void PushRenderTargets(VKTexture* color, VKTexture* depth_stencil);
void PopRenderTargets();
void SetRenderArea(vk::Rect2D render_area);
void PushAttachment(Attachment attachment);
void PopAttachment();
void SetFragmentShader(const Pica::Regs& config);
void BeginRendering();
void EndRendering();
@ -105,9 +114,10 @@ public:
void Apply();
private:
void ConfigureDescriptorSets();
void ConfigurePipeline();
void UpdateDescriptorSet();
vk::Pipeline MakePipeline(vk::ShaderModule fragment);
vk::ShaderModule MakeShader(const std::string& source, vk::ShaderStageFlagBits stage);
vk::ShaderModule CompileShader(const std::string& source, vk::ShaderStageFlagBits stage);
private:
struct Binding {
@ -116,40 +126,32 @@ private:
vk::UniqueBufferView buffer_view{};
};
struct Attachment {
VKTexture* color{};
VKTexture* depth_stencil{};
};
DirtyFlags dirty_flags;
bool rendering = false;
VKTexture dummy_texture;
vk::UniqueSampler sampler;
std::vector<Attachment> targets;
VKBuffer* vertex_buffer{}, * index_buffer{};
vk::DeviceSize vertex_offset{}, index_offset{};
std::array<Binding, 9> bindings{};
std::vector<vk::UniqueDescriptorSet> descriptor_sets{};
vk::DeviceSize vertex_offset, index_offset;
std::array<Binding, 9> bindings;
std::vector<vk::UniqueDescriptorSet> descriptor_sets;
vk::UniqueDescriptorPool desc_pool;
vk::Viewport viewport{ 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
vk::Viewport viewport{0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
vk::CullModeFlags cull_mode{};
vk::FrontFace front_face{};
vk::Rect2D scissor{};
vk::LogicOp logic_op{};
std::array<float, 4> blend_constants{};
VKTexture* color_attachment{}, * depth_attachment{};
vk::Rect2D render_area{};
bool depth_enabled, depth_writes;
vk::CompareOp test_func;
u32 stencil_write_mask{}, stencil_input_mask{}, stencil_ref{};
bool stencil_enabled{}, stencil_writes{};
bool depth_enabled{}, depth_writes{}, stencil_enabled{}, stencil_writes{};
vk::StencilOp fail_op, pass_op, depth_fail_op;
vk::CompareOp compare_op;
vk::CompareOp depth_op, stencil_op;
// Pipeline cache
PipelineBuilder builder;
vk::UniqueShaderModule trivial_vertex_shader;
vk::UniquePipelineLayout pipeline_layout;
std::vector<vk::DescriptorSetLayout> descriptor_layouts;

View File

@ -164,18 +164,18 @@ void VKTexture::Transition(vk::ImageLayout new_layout) {
}
void VKTexture::Upload(u32 level, u32 layer, u32 row_length, vk::Rect2D region, std::span<u8> pixels) {
u8* staging = g_vk_task_scheduler->RequestStaging(pixels.size());
if (!staging) {
auto [buffer, offset] = g_vk_task_scheduler->RequestStaging(pixels.size());
if (!buffer) {
LOG_ERROR(Render_Vulkan, "Cannot copy pixels without staging buffer!");
}
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
// Copy pixels to staging buffer
std::memcpy(staging, pixels.data(), pixels.size());
std::memcpy(buffer, pixels.data(), pixels.size());
vk::BufferImageCopy copy_region {
0, row_length, region.extent.height,
vk::BufferImageCopy copy_region{
offset, row_length, region.extent.height,
{info.aspect, level, layer, 1},
{ region.offset.x, region.offset.y, 0 },
{ region.extent.width, region.extent.height, 1 }

View File

@ -49,6 +49,8 @@ public:
vk::Format GetFormat() const { return info.format; }
vk::ImageLayout GetLayout() const { return layout; }
u32 GetSamples() const { return info.multisamples; }
u32 GetSize() const { return image_size; }
vk::Extent2D GetExtent() const { return {info.width, info.height}; }
/// Copies CPU side pixel data to the GPU texture buffer
void Upload(u32 level, u32 layer, u32 row_length, vk::Rect2D region, std::span<u8> pixels);