mirror of https://github.com/snes9xgit/snes9x.git
Vulkan: Separate context, surface, and swapchain.
This commit is contained in:
parent
72e4946410
commit
febcf27482
|
@ -1,6 +1,7 @@
|
|||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include "vulkan_context.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
|
@ -93,45 +94,61 @@ std::vector<std::string> Vulkan::Context::get_device_list()
|
|||
}
|
||||
|
||||
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
||||
bool Context::init_win32(HINSTANCE hinstance, HWND hwnd, int preferred_device)
|
||||
bool Context::init_win32()
|
||||
{
|
||||
instance = create_instance_preamble(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
||||
if (!instance)
|
||||
return false;
|
||||
|
||||
return init(preferred_device);
|
||||
}
|
||||
|
||||
bool Context::create_win32_surface(HINSTANCE hinstance, HWND hwnd)
|
||||
{
|
||||
auto win32_surface_create_info = vk::Win32SurfaceCreateInfoKHR{}
|
||||
.setHinstance(hinstance)
|
||||
.setHwnd(hwnd);
|
||||
surface = instance->createWin32SurfaceKHRUnique(win32_surface_create_info).value;
|
||||
if (!surface)
|
||||
auto retval = instance->createWin32SurfaceKHRUnique(win32_surface_create_info);
|
||||
if (retval.result != vk::Result::eSuccess)
|
||||
return false;
|
||||
return init(preferred_device);
|
||||
surface = std::move(retval.value);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VK_USE_PLATFORM_XLIB_KHR
|
||||
bool Context::init_Xlib(Display *dpy, Window xid, int preferred_device)
|
||||
bool Context::init_Xlib()
|
||||
{
|
||||
instance = create_instance_preamble(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
||||
if (!instance)
|
||||
return false;
|
||||
|
||||
return init();
|
||||
}
|
||||
|
||||
bool Context::create_Xlib_surface(Display *dpy, Window xid)
|
||||
{
|
||||
auto retval = instance->createXlibSurfaceKHRUnique({ {}, dpy, xid });
|
||||
if (retval.result != vk::Result::eSuccess)
|
||||
return false;
|
||||
surface = std::move(retval.value);
|
||||
|
||||
return init(preferred_device);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
|
||||
bool Context::init_wayland(wl_display *dpy, wl_surface *parent, int initial_width, int initial_height, int preferred_device)
|
||||
bool Context::init_wayland()
|
||||
{
|
||||
instance = create_instance_preamble(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
||||
if (!instance)
|
||||
return false;
|
||||
|
||||
return init();
|
||||
}
|
||||
|
||||
bool Context::create_wayland_surface(wl_display *dpy, wl_surface *parent)
|
||||
{
|
||||
auto wayland_surface_create_info = vk::WaylandSurfaceCreateInfoKHR{}
|
||||
.setSurface(parent)
|
||||
.setDisplay(dpy);
|
||||
|
@ -141,18 +158,29 @@ bool Context::init_wayland(wl_display *dpy, wl_surface *parent, int initial_widt
|
|||
return false;
|
||||
surface = std::move(new_surface);
|
||||
|
||||
return init(preferred_device, initial_width, initial_height);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Context::init(int preferred_device, int initial_width, int initial_height)
|
||||
bool Context::destroy_surface()
|
||||
{
|
||||
init_device(preferred_device);
|
||||
wait_idle();
|
||||
if (swapchain)
|
||||
swapchain->uncreate();
|
||||
|
||||
surface.reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Context::init()
|
||||
{
|
||||
init_device();
|
||||
init_vma();
|
||||
init_command_pool();
|
||||
init_descriptor_pool();
|
||||
|
||||
create_swapchain(initial_width, initial_height);
|
||||
swapchain = std::make_unique<Swapchain>(*this);
|
||||
wait_idle();
|
||||
return true;
|
||||
}
|
||||
|
@ -192,7 +220,7 @@ static bool find_extension(std::vector<vk::ExtensionProperties> &props, const ch
|
|||
}) != props.end();
|
||||
};
|
||||
|
||||
static uint32_t find_graphics_queue(vk::PhysicalDevice &device)
|
||||
static std::optional<uint32_t> find_graphics_queue(vk::PhysicalDevice &device)
|
||||
{
|
||||
auto queue_props = device.getQueueFamilyProperties();
|
||||
for (size_t i = 0; i < queue_props.size(); i++)
|
||||
|
@ -203,7 +231,7 @@ static uint32_t find_graphics_queue(vk::PhysicalDevice &device)
|
|||
}
|
||||
}
|
||||
|
||||
return UINT32_MAX;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static bool check_extensions(std::vector<const char *> &required_extensions, vk::PhysicalDevice &device)
|
||||
|
@ -217,7 +245,7 @@ static bool check_extensions(std::vector<const char *> &required_extensions, vk:
|
|||
return true;
|
||||
};
|
||||
|
||||
bool Context::init_device(int preferred_device)
|
||||
bool Context::init_device()
|
||||
{
|
||||
std::vector<const char *> required_extensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
|
@ -251,8 +279,9 @@ bool Context::init_device(int preferred_device)
|
|||
if (!device_chosen)
|
||||
return false;
|
||||
|
||||
graphics_queue_family_index = find_graphics_queue(physical_device);
|
||||
if (graphics_queue_family_index == UINT32_MAX)
|
||||
if (auto index = find_graphics_queue(physical_device))
|
||||
graphics_queue_family_index = *index;
|
||||
else
|
||||
return false;
|
||||
|
||||
std::vector<float> priorities = { 1.0f };
|
||||
|
@ -262,14 +291,6 @@ bool Context::init_device(int preferred_device)
|
|||
device = physical_device.createDevice(dci).value;
|
||||
queue = device.getQueue(graphics_queue_family_index, 0);
|
||||
|
||||
auto surface_formats = physical_device.getSurfaceFormatsKHR(surface.get()).value;
|
||||
if (std::find_if(surface_formats.begin(),
|
||||
surface_formats.end(),
|
||||
[](vk::SurfaceFormatKHR &f) {
|
||||
return (f.format == vk::Format::eB8G8R8A8Unorm);
|
||||
}) == surface_formats.end())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -289,16 +310,15 @@ bool Context::init_vma()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Context::create_swapchain(int width, int height)
|
||||
bool Context::create_swapchain()
|
||||
{
|
||||
wait_idle();
|
||||
swapchain = std::make_unique<Swapchain>(*this);
|
||||
return swapchain->create(2, width, height);
|
||||
return swapchain->create();
|
||||
}
|
||||
|
||||
bool Context::recreate_swapchain(int width, int height)
|
||||
bool Context::recreate_swapchain()
|
||||
{
|
||||
return swapchain->recreate(width, height);
|
||||
return swapchain->recreate();
|
||||
}
|
||||
|
||||
void Context::wait_idle()
|
||||
|
|
|
@ -22,22 +22,28 @@ class Context
|
|||
Context();
|
||||
~Context();
|
||||
#ifdef VK_USE_PLATFORM_XLIB_KHR
|
||||
bool init_Xlib(Display *dpy, Window xid, int preferred_device = -1);
|
||||
bool init_Xlib();
|
||||
bool create_Xlib_surface(Display *dpy, Window xid);
|
||||
#endif
|
||||
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
|
||||
bool init_wayland(wl_display *dpy, wl_surface *parent, int width, int height, int preferred_device = -1);
|
||||
bool init_wayland();
|
||||
bool create_wayland_surface(wl_display *dpy, wl_surface *parent);
|
||||
#endif
|
||||
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
||||
bool init_win32(HINSTANCE hinstance, HWND hwnd, int preferred_device = -1);
|
||||
bool init_win32();
|
||||
bool create_win32_surface(HINSTANCE hinstance, HWND hwnd);
|
||||
#endif
|
||||
bool init(int preferred_device = -1, int initial_width = -1, int initial_height = -1);
|
||||
bool create_swapchain(int width = -1, int height = -1);
|
||||
bool recreate_swapchain(int width = -1, int height = -1);
|
||||
bool init();
|
||||
bool create_swapchain();
|
||||
bool recreate_swapchain();
|
||||
bool destroy_surface();
|
||||
void wait_idle();
|
||||
vk::CommandBuffer begin_cmd_buffer();
|
||||
void end_cmd_buffer();
|
||||
void hard_barrier(vk::CommandBuffer cmd);
|
||||
static std::vector<std::string> get_device_list();
|
||||
void set_preferred_device(int device) { preferred_device = device; };
|
||||
void unset_preferred_device() { preferred_device = -1; };
|
||||
|
||||
vma::Allocator allocator;
|
||||
vk::Device device;
|
||||
|
@ -53,9 +59,10 @@ class Context
|
|||
|
||||
private:
|
||||
bool init_vma();
|
||||
bool init_device(int preferred_device = 0);
|
||||
bool init_device();
|
||||
bool init_command_pool();
|
||||
bool init_descriptor_pool();
|
||||
int preferred_device;
|
||||
|
||||
#ifdef VK_USE_PLATFORM_XLIB_KHR
|
||||
Display *xlib_display;
|
||||
|
|
|
@ -9,7 +9,6 @@ Swapchain::Swapchain(Context &context_)
|
|||
{
|
||||
device = context.device;
|
||||
queue = context.queue;
|
||||
surface = context.surface.get();
|
||||
physical_device = context.physical_device;
|
||||
command_pool = context.command_pool.get();
|
||||
create_render_pass();
|
||||
|
@ -77,17 +76,17 @@ void Swapchain::create_render_pass()
|
|||
.setDependencies(subpass_dependency)
|
||||
.setAttachments(attachment_description);
|
||||
|
||||
render_pass = device.createRenderPassUnique(render_pass_create_info).value;
|
||||
render_pass = context.device.createRenderPassUnique(render_pass_create_info).value;
|
||||
}
|
||||
|
||||
bool Swapchain::recreate(int new_width, int new_height)
|
||||
bool Swapchain::recreate()
|
||||
{
|
||||
if (swapchain_object)
|
||||
{
|
||||
device.waitIdle();
|
||||
}
|
||||
|
||||
return create(num_swapchain_images, new_width, new_height);
|
||||
return create();
|
||||
}
|
||||
|
||||
vk::Image Swapchain::get_image()
|
||||
|
@ -123,7 +122,7 @@ bool Swapchain::check_and_resize(int width, int height)
|
|||
|
||||
if (width == -1 && height == -1)
|
||||
{
|
||||
surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(surface).value;
|
||||
surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(context.surface.get()).value;
|
||||
width = surface_capabilities.currentExtent.width;
|
||||
height = surface_capabilities.currentExtent.height;
|
||||
}
|
||||
|
@ -133,24 +132,34 @@ bool Swapchain::check_and_resize(int width, int height)
|
|||
|
||||
if (extents.width != (uint32_t)width || extents.height != (uint32_t)height)
|
||||
{
|
||||
recreate(width, height);
|
||||
set_desired_size(width, height);
|
||||
recreate();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width, int new_height)
|
||||
bool Swapchain::uncreate()
|
||||
{
|
||||
frames.clear();
|
||||
image_data.clear();
|
||||
swapchain_object.reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Swapchain::create()
|
||||
{
|
||||
frames.clear();
|
||||
image_data.clear();
|
||||
|
||||
auto surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(surface).value;
|
||||
auto surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(context.surface.get()).value;
|
||||
|
||||
if (surface_capabilities.minImageCount > desired_num_swapchain_images)
|
||||
if (desired_latency == - 1 || (int)surface_capabilities.minImageCount > desired_latency)
|
||||
num_swapchain_images = surface_capabilities.minImageCount;
|
||||
else
|
||||
num_swapchain_images = desired_num_swapchain_images;
|
||||
num_swapchain_images = desired_latency;
|
||||
|
||||
// If extents aren't reported (Wayland), we have to rely on Wayland to report
|
||||
// the size, so keep current extent.
|
||||
|
@ -168,11 +177,11 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
|
|||
}
|
||||
}
|
||||
|
||||
if (new_width > 0 && new_height > 0)
|
||||
if (desired_width > 0 && desired_height > 0)
|
||||
{
|
||||
// No buffer is allocated for surface yet
|
||||
extents.width = new_width;
|
||||
extents.height = new_height;
|
||||
extents.width = desired_width;
|
||||
extents.height = desired_height;
|
||||
}
|
||||
else if (extents.width < 1 || extents.height < 1)
|
||||
{
|
||||
|
@ -191,14 +200,6 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
|
|||
if (extents.height < surface_capabilities.minImageExtent.height)
|
||||
extents.height = surface_capabilities.minImageExtent.height;
|
||||
|
||||
auto present_modes = physical_device.getSurfacePresentModesKHR(surface).value;
|
||||
supports_mailbox = vector_find(present_modes, vk::PresentModeKHR::eMailbox);
|
||||
supports_immediate = vector_find(present_modes, vk::PresentModeKHR::eImmediate);
|
||||
supports_relaxed = vector_find(present_modes, vk::PresentModeKHR::eFifoRelaxed);
|
||||
|
||||
auto swapchain_maintenance_info = vk::SwapchainPresentModesCreateInfoEXT{}
|
||||
.setPresentModes(present_modes);
|
||||
|
||||
auto swapchain_create_info = vk::SwapchainCreateInfoKHR{}
|
||||
.setMinImageCount(num_swapchain_images)
|
||||
.setImageFormat(vk::Format::eB8G8R8A8Unorm)
|
||||
|
@ -209,11 +210,10 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
|
|||
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
|
||||
.setClipped(true)
|
||||
.setPresentMode(get_present_mode())
|
||||
.setSurface(surface)
|
||||
.setSurface(context.surface.get())
|
||||
.setPreTransform(vk::SurfaceTransformFlagBitsKHR::eIdentity)
|
||||
.setImageArrayLayers(1)
|
||||
.setQueueFamilyIndices(graphics_queue_index)
|
||||
.setPNext(&swapchain_maintenance_info);
|
||||
.setQueueFamilyIndices(graphics_queue_index);
|
||||
|
||||
swapchain_object.reset();
|
||||
auto resval = device.createSwapchainKHRUnique(swapchain_create_info);
|
||||
|
@ -299,7 +299,7 @@ bool Swapchain::begin_frame()
|
|||
}
|
||||
|
||||
vk::ResultValue<uint32_t> result_value(vk::Result::eSuccess, 0);
|
||||
result_value = device.acquireNextImageKHR(swapchain_object.get(), UINT64_MAX, frame.acquire.get());
|
||||
result_value = device.acquireNextImageKHR(swapchain_object.get(), 33333333, frame.acquire.get());
|
||||
|
||||
if (result_value.result == vk::Result::eErrorOutOfDateKHR ||
|
||||
result_value.result == vk::Result::eSuboptimalKHR)
|
||||
|
|
|
@ -13,10 +13,16 @@ class Swapchain
|
|||
public:
|
||||
Swapchain(Context &);
|
||||
~Swapchain();
|
||||
bool create(unsigned int num_frames, int width = -1, int height = -1);
|
||||
bool recreate(int width = -1, int height = -1);
|
||||
bool create();
|
||||
bool uncreate();
|
||||
bool recreate();
|
||||
bool create_resources();
|
||||
bool check_and_resize(int width = -1, int height = -1);
|
||||
Swapchain &set_desired_size(int width, int height) { desired_width = width; desired_height = height; return *this; }
|
||||
void unset_desired_size() { desired_width = -1; desired_height = -1; }
|
||||
Swapchain &set_desired_latency(int latency) { desired_latency = latency; return *this; }
|
||||
void unset_desired_latency() { desired_latency = -1; }
|
||||
|
||||
bool begin_frame();
|
||||
void begin_render_pass();
|
||||
void end_render_pass();
|
||||
|
@ -61,6 +67,9 @@ class Swapchain
|
|||
unsigned int current_frame = 0;
|
||||
unsigned int current_swapchain_image = 0;
|
||||
unsigned int num_swapchain_images = 0;
|
||||
int desired_width = -1;
|
||||
int desired_height = -1;
|
||||
int desired_latency = -1;
|
||||
uint64_t presentation_id = 0;
|
||||
bool vsync = true;
|
||||
bool supports_immediate = false;
|
||||
|
@ -71,7 +80,6 @@ class Swapchain
|
|||
|
||||
Vulkan::Context &context;
|
||||
vk::Device device;
|
||||
vk::SurfaceKHR surface;
|
||||
vk::CommandPool command_pool;
|
||||
vk::PhysicalDevice physical_device;
|
||||
vk::Queue queue;
|
||||
|
|
|
@ -91,7 +91,10 @@ void S9xVulkanDisplayDriver::refresh()
|
|||
|
||||
#ifdef GDK_WINDOWING_WAYLAND
|
||||
if (GDK_IS_WAYLAND_WINDOW(drawing_area->get_window()->gobj()))
|
||||
{
|
||||
std::tie(new_width, new_height) = wayland_surface->get_size_for_metrics(get_metrics(*drawing_area));
|
||||
context->swapchain->set_desired_size(new_width, new_height);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
|
@ -101,7 +104,7 @@ void S9xVulkanDisplayDriver::refresh()
|
|||
|
||||
if (new_width != current_width || new_height != current_height)
|
||||
{
|
||||
context->recreate_swapchain(new_width, new_height);
|
||||
context->recreate_swapchain();
|
||||
context->wait_idle();
|
||||
current_width = new_width;
|
||||
current_height = new_height;
|
||||
|
@ -128,10 +131,14 @@ int S9xVulkanDisplayDriver::init()
|
|||
wl_display *display = gdk_wayland_display_get_wl_display(drawing_area->get_display()->gobj());
|
||||
|
||||
if (!wayland_surface->attach(display, surface, get_metrics(*drawing_area)))
|
||||
return -1;
|
||||
|
||||
if (!context->init_wayland(wayland_surface->display, wayland_surface->child, current_width, current_height))
|
||||
return -1;
|
||||
goto abort;
|
||||
if (!context->init_wayland())
|
||||
goto abort;
|
||||
if (!context->create_wayland_surface(wayland_surface->display, wayland_surface->child))
|
||||
goto abort;
|
||||
context->swapchain->set_desired_size(current_width, current_height);
|
||||
if (!context->create_swapchain())
|
||||
goto abort;
|
||||
}
|
||||
#endif
|
||||
if (GDK_IS_X11_WINDOW(drawing_area->get_window()->gobj()))
|
||||
|
@ -139,8 +146,12 @@ int S9xVulkanDisplayDriver::init()
|
|||
display = gdk_x11_display_get_xdisplay(drawing_area->get_display()->gobj());
|
||||
xid = gdk_x11_window_get_xid(drawing_area->get_window()->gobj());
|
||||
|
||||
if (!context->init_Xlib(display, xid))
|
||||
return -1;
|
||||
if (!context->init_Xlib())
|
||||
goto abort;
|
||||
if (!context->create_Xlib_surface(display, xid))
|
||||
goto abort;
|
||||
if (!context->create_swapchain())
|
||||
goto abort;
|
||||
}
|
||||
|
||||
device = context->device;
|
||||
|
@ -168,6 +179,10 @@ int S9xVulkanDisplayDriver::init()
|
|||
simple_output = std::make_unique<Vulkan::SimpleOutput>(context.get(), vk::Format::eR5G6B5UnormPack16);
|
||||
|
||||
return 0;
|
||||
|
||||
abort:
|
||||
context.reset();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void S9xVulkanDisplayDriver::deinit()
|
||||
|
|
|
@ -28,7 +28,7 @@ EmuCanvasVulkan::EmuCanvasVulkan(EmuConfig *config, QWidget *parent, QWidget *ma
|
|||
|
||||
EmuCanvasVulkan::~EmuCanvasVulkan()
|
||||
{
|
||||
deinit();
|
||||
assert(!context);
|
||||
}
|
||||
|
||||
bool EmuCanvasVulkan::initImGui()
|
||||
|
@ -85,14 +85,16 @@ bool EmuCanvasVulkan::createContext()
|
|||
QGuiApplication::sync();
|
||||
|
||||
context = std::make_unique<Vulkan::Context>();
|
||||
context->set_preferred_device(config->display_device_index);
|
||||
|
||||
#ifdef _WIN32
|
||||
auto hwnd = (HWND)winId();
|
||||
if (!context->init_win32(nullptr, hwnd, config->display_device_index))
|
||||
{
|
||||
context.reset();
|
||||
return false;
|
||||
}
|
||||
goto fail;
|
||||
if (!context->create_win32_surface(nullptr, hwnd))
|
||||
goto fail;
|
||||
if (!context->swapchain->create())
|
||||
goto fail;
|
||||
#else
|
||||
if (platform == "wayland")
|
||||
{
|
||||
|
@ -101,21 +103,31 @@ bool EmuCanvasVulkan::createContext()
|
|||
auto surface = (wl_surface *)pni->nativeResourceForWindow("surface", main_window->windowHandle());
|
||||
wayland_surface->attach(display, surface, { parent->x() - main_window->x(), parent->y() - main_window->y(), width(), height(), static_cast<int>(devicePixelRatio()) });
|
||||
auto [scaled_width, scaled_height] = wayland_surface->get_size();
|
||||
if (!context->init_wayland(display, wayland_surface->child, scaled_width, scaled_height, config->display_device_index))
|
||||
{
|
||||
context.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!context->init_wayland())
|
||||
goto fail;
|
||||
|
||||
if (!context->create_wayland_surface(display, wayland_surface->child))
|
||||
goto fail;
|
||||
|
||||
context->swapchain->set_desired_size(scaled_width, scaled_height);
|
||||
if (!context->swapchain->create())
|
||||
goto fail;
|
||||
}
|
||||
else if (platform == "xcb")
|
||||
{
|
||||
auto display = (Display *)pni->nativeResourceForWindow("display", window);
|
||||
auto xid = (Window)winId();
|
||||
if (!context->init_Xlib(display, xid, config->display_device_index))
|
||||
{
|
||||
context.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!context->init_Xlib())
|
||||
goto fail;
|
||||
|
||||
if (!context->create_Xlib_surface(display, xid))
|
||||
goto fail;
|
||||
|
||||
context->wait_idle();
|
||||
if (!context->swapchain->create())
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -130,6 +142,10 @@ bool EmuCanvasVulkan::createContext()
|
|||
paintEvent(nullptr);
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
context.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
void EmuCanvasVulkan::tryLoadShader()
|
||||
|
|
Loading…
Reference in New Issue