17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 00:01:15 +01:00

feat(vulkan): gfxrenderer picks the opengl 4.6 or the vulkan 1.4 renderer (B1-B3)

This commit is contained in:
2026-10-24 03:06:33 +02:00
parent 6bcdb7dd05
commit 68fa5dc2da
25 changed files with 3056 additions and 83 deletions

View File

@@ -50,6 +50,7 @@ file(GLOB HEADERS "*.h"
"scene/*.h"
"rendering/*.h"
"rendering/opengl46/*.h"
"rendering/vulkan14/*.h"
"audio/*.h"
"environment/*.h"
"application/*.h"
@@ -69,6 +70,7 @@ if (APPLE)
endif()
option(WITH_OPENGL_MODERN "Compile with OpenGL modern renderer" ON)
option(WITH_VULKAN "Compile with the Vulkan 1.4 renderer (gfxrenderer vulkan); needs the submodules ref/volk and ref/Vulkan-Headers" OFF)
option(WITH_PYTHON "Python 3.14 embedded scripting" ON)
option(WITH_UART "Compile with libserialport" ON)
option(WITH_LUA "Compile with lua scripting support" ON)
@@ -400,10 +402,29 @@ if (WITH_OPENGL_MODERN)
"rendering/opengl46/opengl46gpuparticles.cpp"
"rendering/opengl46/opengl46terrain.cpp"
"rendering/opengl46/opengl46grass.cpp"
"rendering/opengl46/opengl46backend.cpp"
"rendering/opengl46/opengl46renderer.cpp")
set(DEFINITIONS ${DEFINITIONS} "WITH_OPENGL_MODERN")
endif()
if (WITH_VULKAN)
# the vulkan loader is loaded by volk when the program runs, so the program starts without it (and runs with opengl)
set(VULKAN_HEADERS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ref/Vulkan-Headers/include")
set(VOLK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ref/volk")
if (NOT EXISTS "${VOLK_DIR}/volk.h" OR NOT EXISTS "${VULKAN_HEADERS_DIR}/vulkan/vulkan_core.h")
message(FATAL_ERROR "WITH_VULKAN=ON needs the submodules ref/volk and ref/Vulkan-Headers: git submodule update --init ref/volk ref/Vulkan-Headers")
endif()
set(SOURCES ${SOURCES}
"rendering/vulkan14/vulkan14common.cpp"
"rendering/vulkan14/vulkan14capabilities.cpp"
"rendering/vulkan14/vulkan14instance.cpp"
"rendering/vulkan14/vulkan14device.cpp"
"rendering/vulkan14/vulkan14swapchain.cpp"
"rendering/vulkan14/vulkan14renderer.cpp"
"ref/volk/volk.c")
set(DEFINITIONS ${DEFINITIONS} "WITH_VULKAN")
endif()
add_subdirectory("${DEPS_DIR}/glfw/" EXCLUDE_FROM_ALL)
set(SOURCES ${SOURCES} "extras/piped_proc.cpp" "widgets/cameraview_extcam.cpp")
@@ -529,6 +550,11 @@ set_target_properties(eu07_core PROPERTIES
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_link_libraries(${PROJECT_NAME} eu07_core)
if (WITH_VULKAN)
# the vulkan headers are seen by the files of rendering/vulkan14 only, they include them
target_include_directories(${PROJECT_NAME} PRIVATE "${VULKAN_HEADERS_DIR}" "${VOLK_DIR}")
target_link_libraries(${PROJECT_NAME} ${CMAKE_DL_LIBS})
endif()
target_compile_definitions(eu07_settings INTERFACE ${DEFINITIONS})

View File

@@ -381,11 +381,6 @@ int eu07_application::init(int Argc, char *Argv[])
ErrorLog("Failed to initialize glfw!");
return result;
}
if ((result = init_ogl()) != 0)
{
ErrorLog("Failed to initialize ogl!");
return result;
}
if (crashreport_is_pending())
{ // run crashgui as early as possible
@@ -966,6 +961,13 @@ GLFWwindow *eu07_application::window(int const Windowindex, bool visible, int wi
glfwWindowHint(GLFW_VISIBLE, visible);
if (share_ctx && (m_gfxbackend != nullptr) && (false == m_gfxbackend->gl_context))
{
// the windows of the renderer have no opengl context to share
WriteLog("window with an opengl context shared with the main window isn't available with the " + m_gfxbackend->name + " renderer");
return nullptr;
}
auto *childwindow = glfwCreateWindow(width, height, "eu07window", monitor, share_ctx ? m_windows.front() : nullptr);
if (!childwindow)
return nullptr;
@@ -1221,33 +1223,6 @@ int eu07_application::init_locale()
return 0;
}
// the context of the required version couldn't be created. a context with default settings tells which version the
// driver does provide, so the user can be told what is missing
static std::string describe_gl_driver()
{
glfwDefaultWindowHints();
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
auto *probe = glfwCreateWindow(64, 64, "eu07 opengl probe", nullptr, nullptr);
if (probe == nullptr)
return "no OpenGL context";
glfwMakeContextCurrent(probe);
std::string description;
auto const getstring = reinterpret_cast<PFNGLGETSTRINGPROC>(glfwGetProcAddress("glGetString"));
if (getstring != nullptr)
{
auto const text = [&](GLenum const Name) {
auto const *value = reinterpret_cast<char const *>(getstring(Name));
return std::string(value != nullptr ? value : "?");
};
description = "OpenGL " + text(GL_VERSION) + ", " + text(GL_RENDERER) + " (" + text(GL_VENDOR) + ")";
}
glfwMakeContextCurrent(nullptr);
glfwDestroyWindow(probe);
return description.empty() ? "unknown OpenGL version" : description;
}
int eu07_application::init_glfw()
{
if (glfwInit() == GLFW_FALSE)
@@ -1267,29 +1242,64 @@ int eu07_application::init_glfw()
}
}
select_gfx_backend();
return create_main_window();
}
void eu07_application::select_gfx_backend()
{
auto *factory{gfx_renderer_factory::get_instance()};
m_gfxbackend = nullptr;
if (Global.GfxRenderer == "vulkan")
{
// a renderer which can't run here falls back to opengl, so a setting in eu07.ini doesn't leave the user with a black screen
std::string reason;
auto const *vulkan{factory->backend("vulkan")};
if (vulkan == nullptr)
{
reason = "this build of the simulator has no vulkan renderer";
}
else if ((vulkan->probe == nullptr) || vulkan->probe(reason))
{
m_gfxbackend = vulkan;
}
if (m_gfxbackend == nullptr)
{
WriteLog("gfxrenderer vulkan: not available (" + reason + "), using opengl");
crashreport_add_info("gfxrenderer_fallback", reason);
Global.GfxRenderer = "default";
}
}
if (m_gfxbackend == nullptr)
{
m_gfxbackend = factory->backend("modern");
}
crashreport_add_info("gfxrenderer", Global.GfxRenderer);
WriteLog("renderer: " + (m_gfxbackend != nullptr ? m_gfxbackend->name : std::string("none")));
}
int eu07_application::create_main_window()
{
if (m_gfxbackend == nullptr)
{
ErrorLog("Bad init: no renderer found");
return -1;
}
auto *monitor{find_monitor(Global.fullscreen_monitor)};
if (!monitor)
monitor = glfwGetPrimaryMonitor();
// the window may be made a second time, after another backend was tried
glfwDefaultWindowHints();
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
if (Global.gfx_skippipeline && Global.iMultisampling > 0)
{
glfwWindowHint(GLFW_SAMPLES, 1 << Global.iMultisampling);
}
crashreport_add_info("gfxrenderer", Global.GfxRenderer);
// activate core profile for opengl 4.6 renderer
// NOTE: the hints stay in effect for the additional windows and shared contexts created later through window()
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
if (Global.gfx_gldebug)
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
glfwWindowHint(GLFW_SRGB_CAPABLE, !Global.gfx_shadergamma);
if (m_gfxbackend->window_hints != nullptr)
m_gfxbackend->window_hints();
if (Global.fullscreen_windowed)
{
@@ -1306,21 +1316,16 @@ int eu07_application::init_glfw()
char const *glfwerror = nullptr;
glfwGetError(&glfwerror);
std::string const reason = (glfwerror != nullptr ? glfwerror : "unknown error");
auto const driver = describe_gl_driver();
ErrorLog("Bad init: failed to create OpenGL 4.6 core profile context (" + reason + "); the graphics card/driver provides: " + driver);
#ifdef _WIN32
auto const message = "MaSzyna requires OpenGL 4.6 (core profile), but the OpenGL 4.6 context could not be created.\n\n"
"Your graphics card/driver provides:\n" + driver + "\n\n"
"Details: " + reason + "\n\n"
"Update the graphics driver or use a graphics card with OpenGL 4.6 support.";
MessageBoxA(nullptr, message.c_str(), "MaSzyna", MB_ICONERROR | MB_OK);
#endif
if (m_gfxbackend->window_failed != nullptr)
m_gfxbackend->window_failed(reason);
ErrorLog("Bad init: failed to create glfw window");
return -1;
}
glfwMakeContextCurrent(mainwindow);
glfwSwapInterval(Global.VSync ? 1 : 0); // vsync
if ((m_gfxbackend->after_window != nullptr) && (false == m_gfxbackend->after_window(mainwindow)))
{
return -1;
}
{
int width, height;
@@ -1343,6 +1348,23 @@ int eu07_application::init_glfw()
return 0;
}
int eu07_application::fallback_to_opengl(std::string const &Reason)
{
WriteLog("gfxrenderer vulkan: " + Reason + ", using opengl");
crashreport_add_info("gfxrenderer_fallback", Reason);
Global.GfxRenderer = "default";
m_gfxbackend = gfx_renderer_factory::get_instance()->backend("modern");
crashreport_add_info("gfxrenderer", Global.GfxRenderer);
WriteLog("renderer: " + (m_gfxbackend != nullptr ? m_gfxbackend->name : std::string("none")));
// nothing hangs on the window yet (the ui is set up later), so it can be made anew with the hints of opengl
for (auto *window : m_windows)
glfwDestroyWindow(window);
m_windows.clear();
return create_main_window();
}
void eu07_application::init_callbacks()
{
@@ -1358,17 +1380,6 @@ void eu07_application::init_callbacks()
glfwSetCursorEnterCallback(window, cursor_enter_callback);
}
int eu07_application::init_ogl()
{
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
ErrorLog("Bad init: failed to initialize glad");
return -1;
}
return 0;
}
int eu07_application::init_ui()
{
if (false == ui_layer::init(m_windows.front()))
@@ -1383,7 +1394,7 @@ int eu07_application::init_gfx()
{
// NOTE: global_settings maps unsupported gfxrenderer values (e.g. the removed "legacy") to "default"
GfxRenderer = gfx_renderer_factory::get_instance()->create("modern");
GfxRenderer = (m_gfxbackend != nullptr && m_gfxbackend->create != nullptr) ? m_gfxbackend->create() : nullptr;
if (!GfxRenderer)
{
@@ -1393,7 +1404,22 @@ int eu07_application::init_gfx()
if (false == GfxRenderer->Init(m_windows.front()))
{
return -1;
if (Global.GfxRenderer != "vulkan")
{
return -1;
}
// the reason is in the log already
GfxRenderer->Shutdown();
GfxRenderer.reset();
if (fallback_to_opengl("the renderer failed to start") != 0)
{
return -1;
}
GfxRenderer = m_gfxbackend->create();
if (!GfxRenderer || (false == GfxRenderer->Init(m_windows.front())))
{
return -1;
}
}
for (const global_settings::extraviewport_config &conf : Global.extra_viewports)

View File

@@ -17,6 +17,8 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/uart.h"
#endif
struct gfx_backend_desc;
class eu07_application {
const int MAX_NETWORK_PER_FRAME = 1000;
@@ -105,8 +107,12 @@ private:
int init_settings( int Argc, char *Argv[] );
int init_locale();
int init_glfw();
// the backend of the renderer picked by gfxrenderer, opengl if it can't run here
void select_gfx_backend();
int create_main_window();
// the backend picked can't run after all: the main window is made anew for opengl
int fallback_to_opengl( std::string const &Reason );
void init_callbacks();
int init_ogl();
int init_ui();
int init_gfx();
int init_audio();
@@ -122,6 +128,7 @@ private:
mode_stack m_modestack; // current behaviour mode
python_taskqueue m_taskqueue;
std::vector<GLFWwindow *> m_windows;
gfx_backend_desc const *m_gfxbackend { nullptr }; // owned by gfx_renderer_factory
std::optional<network::manager> m_network;
std::optional<headtrack> m_headtrack;

View File

@@ -1460,6 +1460,10 @@ debug_panel::update_section_renderer( std::vector<text_line> &Output ) {
{
textline += "Shaders";
}
else if (Global.GfxRenderer == "vulkan")
{
textline += "Vulkan";
}
textline += " ";

View File

@@ -389,7 +389,11 @@ bool ui_layer::init(GLFWwindow *Window)
imgui_style();
ImGui_ImplGlfw_InitForOpenGL(m_window, false);
// a window without an opengl context belongs to the vulkan renderer
if (glfwGetWindowAttrib(m_window, GLFW_CLIENT_API) == GLFW_NO_API)
ImGui_ImplGlfw_InitForVulkan(m_window, false);
else
ImGui_ImplGlfw_InitForOpenGL(m_window, false);
if (!GfxRenderer->GetImguiRenderer() || !GfxRenderer->GetImguiRenderer()->Init())
{

View File

@@ -0,0 +1,107 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// how the application makes the windows of the opengl 4.6 renderer: a core profile context of the main window, which the
// windows made later share, and the gl functions loaded through it
#include "stdafx.h"
#include "rendering/opengl46/opengl46renderer.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
namespace
{
// the context of the required version couldn't be created. a context with default settings tells which version the
// driver does provide, so the user can be told what is missing
std::string describe_gl_driver()
{
glfwDefaultWindowHints();
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
auto *probe = glfwCreateWindow(64, 64, "eu07 opengl probe", nullptr, nullptr);
if (probe == nullptr)
return "no OpenGL context";
glfwMakeContextCurrent(probe);
std::string description;
auto const getstring = reinterpret_cast<PFNGLGETSTRINGPROC>(glfwGetProcAddress("glGetString"));
if (getstring != nullptr)
{
auto const text = [&](GLenum const Name) {
auto const *value = reinterpret_cast<char const *>(getstring(Name));
return std::string(value != nullptr ? value : "?");
};
description = "OpenGL " + text(GL_VERSION) + ", " + text(GL_RENDERER) + " (" + text(GL_VENDOR) + ")";
}
glfwMakeContextCurrent(nullptr);
glfwDestroyWindow(probe);
return description.empty() ? "unknown OpenGL version" : description;
}
void window_hints()
{
if (Global.gfx_skippipeline && Global.iMultisampling > 0)
{
glfwWindowHint(GLFW_SAMPLES, 1 << Global.iMultisampling);
}
// activate core profile for opengl 4.6 renderer
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
if (Global.gfx_gldebug)
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
glfwWindowHint(GLFW_SRGB_CAPABLE, !Global.gfx_shadergamma);
}
bool after_window(GLFWwindow *Window)
{
glfwMakeContextCurrent(Window);
glfwSwapInterval(Global.VSync ? 1 : 0); // vsync
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
ErrorLog("Bad init: failed to initialize glad");
return false;
}
return true;
}
void window_failed(std::string const &Reason)
{
auto const driver = describe_gl_driver();
ErrorLog("Bad init: failed to create OpenGL 4.6 core profile context (" + Reason + "); the graphics card/driver provides: " + driver);
#ifdef _WIN32
auto const message = "MaSzyna requires OpenGL 4.6 (core profile), but the OpenGL 4.6 context could not be created.\n\n"
"Your graphics card/driver provides:\n" + driver + "\n\n"
"Details: " + Reason + "\n\n"
"Update the graphics driver or use a graphics card with OpenGL 4.6 support.";
MessageBoxA(nullptr, message.c_str(), "MaSzyna", MB_ICONERROR | MB_OK);
#endif
}
} // namespace
gfx_backend_desc opengl46_renderer::backend_desc()
{
gfx_backend_desc desc;
desc.name = "opengl 4.6";
desc.create = &opengl46_renderer::create_func;
desc.window_hints = &window_hints;
desc.after_window = &after_window;
desc.window_failed = &window_failed;
desc.gl_context = true;
return desc;
}

View File

@@ -10215,7 +10215,7 @@ std::unique_ptr<gfx_renderer> opengl46_renderer::create_func()
return std::unique_ptr<gfx_renderer>(new opengl46_renderer());
}
bool opengl46_renderer::renderer_register = gfx_renderer_factory::get_instance()->register_backend("modern", opengl46_renderer::create_func);
bool opengl46_renderer::renderer_register = gfx_renderer_factory::get_instance()->register_backend("modern", opengl46_renderer::backend_desc());
bool opengl46_renderer::opengl46_imgui_renderer::Init()
{

View File

@@ -185,6 +185,8 @@ class opengl46_renderer : public gfx_renderer {
GLenum static const sunlight{0};
static std::unique_ptr<gfx_renderer> create_func();
// how the windows of the renderer are made (opengl46backend.cpp)
static gfx_backend_desc backend_desc();
private:
// types

View File

@@ -15,15 +15,29 @@ std::unique_ptr<gfx_renderer> GfxRenderer;
bool gfx_renderer_factory::register_backend(const std::string &backend, gfx_renderer_factory::create_method func)
{
backends[backend] = func;
gfx_backend_desc desc;
desc.name = backend;
desc.create = func;
return register_backend(backend, desc);
}
bool gfx_renderer_factory::register_backend(const std::string &backend, gfx_backend_desc const &desc)
{
backends[backend] = desc;
return true;
}
gfx_backend_desc const *gfx_renderer_factory::backend(const std::string &name) const
{
auto it = backends.find(name);
return (it != backends.end() ? &it->second : nullptr);
}
std::unique_ptr<gfx_renderer> gfx_renderer_factory::create(const std::string &backend)
{
auto it = backends.find(backend);
if (it != backends.end())
return it->second();
if ((it != backends.end()) && (it->second.create != nullptr))
return it->second.create();
ErrorLog("renderer \"" + backend + "\" not found!");
return nullptr;

View File

@@ -184,18 +184,44 @@ public:
virtual auto Render_Preview( TAnimModel *Instance, int const Size, int const Margin, bool const Shadows, std::vector<std::uint8_t> &Image ) -> bool { return false; }
};
// what the application has to know about a backend before its renderer exists: whether it can run here at all, and how its
// windows are made. a window made for opengl can't be drawn to with vulkan and the other way round, so the application asks
// the backend picked by gfxrenderer before the main window is made, and falls back to opengl when it can't run
struct gfx_backend_desc
{
using create_method = std::unique_ptr<gfx_renderer>(*)();
std::string name; // for the log
create_method create { nullptr };
// whether the backend can run on this machine. false: Reason tells why. null: it always can
bool (*probe)( std::string &Reason ) { nullptr };
// glfw hints of the windows of the backend, set over the defaults before the main window is made. they stay in effect for
// the windows made after it
void (*window_hints)() { nullptr };
// after the main window was made, before the renderer is. false: the backend can't use the window
bool (*after_window)( GLFWwindow *Window ) { nullptr };
// the main window couldn't be made; Reason is the error of glfw. tells the user what is missing
void (*window_failed)( std::string const &Reason ) { nullptr };
// the windows have an opengl context, which the windows made later can share
bool gl_context { true };
};
class gfx_renderer_factory
{
public:
using create_method = std::unique_ptr<gfx_renderer>(*)();
using create_method = gfx_backend_desc::create_method;
// a backend with no window of its own to care about (e.g. "null")
bool register_backend(const std::string &backend, create_method func);
bool register_backend(const std::string &backend, gfx_backend_desc const &desc);
// the description of the backend registered by the name, null if there's none (e.g. not built in)
gfx_backend_desc const *backend(const std::string &name) const;
std::unique_ptr<gfx_renderer> create(const std::string &name);
static gfx_renderer_factory* get_instance();
private:
std::unordered_map<std::string, create_method> backends;
std::unordered_map<std::string, gfx_backend_desc> backends;
static gfx_renderer_factory *instance;
};

View File

@@ -0,0 +1,288 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14capabilities.h"
#include <cstring>
namespace vk14
{
namespace
{
// one VkBool32 in the chain; without captures, so it turns into a plain function pointer and the table stays constant
#define VK14_FIELD(member, field) [](feature_chain &chain) -> VkBool32 & { return chain.member.field; }
#define VK14_CORE(field) VK14_FIELD(features2.features, field)
// columns: structure, name, required, guaranteed by vulkan 1.4, purpose.
//
// beyond what vulkan 1.4 requires of every device the renderer needs multiDrawIndirect, shaderDrawParameters,
// textureCompressionBC and geometryShader, so a gpu without one of them is turned down by its name rather than failing later.
// "guaranteed" follows the minimum requirements of vulkan 1.2-1.4 (VP_LUNARG_minimum_requirements_1_4 of the vulkan sdk)
std::vector<feature_entry> const g_feature_table = {
// VkPhysicalDeviceFeatures (1.0)
{"VkPhysicalDeviceFeatures", "geometryShader", true, false, "map icons and cube shadow maps of the lamps until multiview takes them over", VK14_CORE(geometryShader)},
{"VkPhysicalDeviceFeatures", "multiDrawIndirect", true, false, "multi draw indirect of the gpu culling", VK14_CORE(multiDrawIndirect)},
{"VkPhysicalDeviceFeatures", "textureCompressionBC", true, false, "bc1-bc7 compressed dds textures", VK14_CORE(textureCompressionBC)},
{"VkPhysicalDeviceFeatures", "drawIndirectFirstInstance", true, true, "instanced indirect draws", VK14_CORE(drawIndirectFirstInstance)},
{"VkPhysicalDeviceFeatures", "fullDrawIndexUint32", true, true, "32 bit index buffers", VK14_CORE(fullDrawIndexUint32)},
{"VkPhysicalDeviceFeatures", "imageCubeArray", true, true, "environment map and cube shadow maps", VK14_CORE(imageCubeArray)},
{"VkPhysicalDeviceFeatures", "independentBlend", true, true, "blending per attachment", VK14_CORE(independentBlend)},
{"VkPhysicalDeviceFeatures", "sampleRateShading", true, true, "multisampling", VK14_CORE(sampleRateShading)},
{"VkPhysicalDeviceFeatures", "depthClamp", true, true, "shadow maps", VK14_CORE(depthClamp)},
{"VkPhysicalDeviceFeatures", "depthBiasClamp", true, true, "shadow maps", VK14_CORE(depthBiasClamp)},
{"VkPhysicalDeviceFeatures", "samplerAnisotropy", true, true, "anisotropic texture filtering", VK14_CORE(samplerAnisotropy)},
{"VkPhysicalDeviceFeatures", "largePoints", true, true, "points larger than a pixel", VK14_CORE(largePoints)},
{"VkPhysicalDeviceFeatures", "fragmentStoresAndAtomics", true, true, "clustered lights and post-processing", VK14_CORE(fragmentStoresAndAtomics)},
{"VkPhysicalDeviceFeatures", "shaderImageGatherExtended", true, true, "shadow filtering", VK14_CORE(shaderImageGatherExtended)},
{"VkPhysicalDeviceFeatures", "shaderStorageImageExtendedFormats", true, true, "compute passes", VK14_CORE(shaderStorageImageExtendedFormats)},
{"VkPhysicalDeviceFeatures", "fillModeNonSolid", false, false, "wireframe", VK14_CORE(fillModeNonSolid)},
{"VkPhysicalDeviceFeatures", "pipelineStatisticsQuery", false, false, "draw statistics", VK14_CORE(pipelineStatisticsQuery)},
// VkPhysicalDeviceVulkan11Features
{"VkPhysicalDeviceVulkan11Features", "shaderDrawParameters", true, false, "gl_DrawID and gl_BaseInstance of multi draws", VK14_FIELD(v11, shaderDrawParameters)},
{"VkPhysicalDeviceVulkan11Features", "multiview", true, true, "cube shadow maps of the lamps in one pass", VK14_FIELD(v11, multiview)},
// VkPhysicalDeviceVulkan12Features
{"VkPhysicalDeviceVulkan12Features", "timelineSemaphore", true, true, "synchronization of the frames", VK14_FIELD(v12, timelineSemaphore)},
{"VkPhysicalDeviceVulkan12Features", "scalarBlockLayout", true, true, "buffer layouts shared with the c++ code", VK14_FIELD(v12, scalarBlockLayout)},
{"VkPhysicalDeviceVulkan12Features", "uniformBufferStandardLayout", true, true, "std430 uniform buffers", VK14_FIELD(v12, uniformBufferStandardLayout)},
{"VkPhysicalDeviceVulkan12Features", "hostQueryReset", true, true, "gpu times of the passes", VK14_FIELD(v12, hostQueryReset)},
{"VkPhysicalDeviceVulkan12Features", "separateDepthStencilLayouts", true, true, "depth read while the stencil is written", VK14_FIELD(v12, separateDepthStencilLayouts)},
{"VkPhysicalDeviceVulkan12Features", "drawIndirectCount", false, false, "draw counts made on the gpu", VK14_FIELD(v12, drawIndirectCount)},
{"VkPhysicalDeviceVulkan12Features", "samplerFilterMinmax", false, false, "min/max reduction of the depth pyramid", VK14_FIELD(v12, samplerFilterMinmax)},
{"VkPhysicalDeviceVulkan12Features", "shaderOutputLayer", false, false, "layered rendering without a geometry shader", VK14_FIELD(v12, shaderOutputLayer)},
// VkPhysicalDeviceVulkan13Features
{"VkPhysicalDeviceVulkan13Features", "dynamicRendering", true, true, "passes without VkRenderPass and VkFramebuffer", VK14_FIELD(v13, dynamicRendering)},
{"VkPhysicalDeviceVulkan13Features", "synchronization2", true, true, "barriers and submits", VK14_FIELD(v13, synchronization2)},
{"VkPhysicalDeviceVulkan13Features", "maintenance4", true, true, "pipeline layouts and buffer sizes", VK14_FIELD(v13, maintenance4)},
{"VkPhysicalDeviceVulkan13Features", "shaderDemoteToHelperInvocation", true, true, "alpha tested geometry", VK14_FIELD(v13, shaderDemoteToHelperInvocation)},
{"VkPhysicalDeviceVulkan13Features", "pipelineCreationCacheControl", true, true, "pipelines made on worker threads", VK14_FIELD(v13, pipelineCreationCacheControl)},
// VkPhysicalDeviceVulkan14Features
{"VkPhysicalDeviceVulkan14Features", "pushDescriptor", true, true, "descriptors of materials and compute passes without pools", VK14_FIELD(v14, pushDescriptor)},
{"VkPhysicalDeviceVulkan14Features", "maintenance5", true, true, "pipelines without shader modules", VK14_FIELD(v14, maintenance5)},
{"VkPhysicalDeviceVulkan14Features", "maintenance6", true, true, "descriptor and push constant calls of 1.4", VK14_FIELD(v14, maintenance6)},
{"VkPhysicalDeviceVulkan14Features", "hostImageCopy", false, false, "texture uploads without command buffers", VK14_FIELD(v14, hostImageCopy)},
};
#undef VK14_CORE
#undef VK14_FIELD
} // namespace
char const *device_type_string(VkPhysicalDeviceType Type)
{
switch (Type)
{
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
return "integrated gpu";
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
return "discrete gpu";
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
return "virtual gpu";
case VK_PHYSICAL_DEVICE_TYPE_CPU:
return "cpu";
default:
break;
}
return "other";
}
std::string vendor_string(std::uint32_t VendorId)
{
switch (VendorId)
{
case 0x1002:
return "AMD";
case 0x106B:
return "Apple";
case 0x10DE:
return "NVIDIA";
case 0x13B5:
return "ARM";
case 0x1414:
return "Microsoft";
case 0x5143:
return "Qualcomm";
case 0x8086:
return "Intel";
case 0x10005:
return "Mesa";
default:
break;
}
char buffer[16] = {};
std::snprintf(buffer, sizeof(buffer), "0x%04X", VendorId);
return std::string(buffer);
}
feature_chain::feature_chain()
{
reset();
}
void feature_chain::reset()
{
features2 = {};
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
v11 = {};
v11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
v12 = {};
v12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
v13 = {};
v13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
v14 = {};
v14.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES;
features2.pNext = &v11;
v11.pNext = &v12;
v12.pNext = &v13;
v13.pNext = &v14;
v14.pNext = nullptr;
}
std::vector<feature_entry> const &feature_table()
{
return g_feature_table;
}
void query_capabilities(VkPhysicalDevice Physical, feature_chain &Queried, capabilities &Caps)
{
Caps = capabilities();
// extensions
std::uint32_t extensioncount{0};
std::vector<VkExtensionProperties> extensions;
if (VK14_VERIFY(vkEnumerateDeviceExtensionProperties(Physical, nullptr, &extensioncount, nullptr)) && (extensioncount > 0))
{
extensions.resize(extensioncount);
if (!VK14_VERIFY(vkEnumerateDeviceExtensionProperties(Physical, nullptr, &extensioncount, extensions.data())))
{
extensions.clear();
}
}
auto const hasextension = [&extensions](char const *Name) {
return std::any_of(extensions.begin(), extensions.end(), [Name](VkExtensionProperties const &Entry) { return std::strcmp(Entry.extensionName, Name) == 0; });
};
Caps.ext_swapchain = hasextension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
Caps.ext_memory_budget = hasextension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
Caps.ext_swapchain_maintenance1 = hasextension(VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME) || hasextension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME);
Caps.ext_extended_dynamic_state3 = hasextension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
// properties
VkPhysicalDeviceVulkan12Properties properties12{};
properties12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES;
VkPhysicalDeviceProperties2 properties{};
properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
properties.pNext = &properties12;
vkGetPhysicalDeviceProperties2(Physical, &properties);
Caps.device_name = properties.properties.deviceName;
Caps.device_type = properties.properties.deviceType;
Caps.api_version = properties.properties.apiVersion;
Caps.driver_version = properties.properties.driverVersion;
Caps.vendor_id = properties.properties.vendorID;
Caps.device_id = properties.properties.deviceID;
Caps.driver_name = properties12.driverName;
Caps.driver_info = properties12.driverInfo;
Caps.timestamp_period = properties.properties.limits.timestampPeriod;
VkPhysicalDeviceMemoryProperties memory{};
vkGetPhysicalDeviceMemoryProperties(Physical, &memory);
for (std::uint32_t i = 0; i < memory.memoryHeapCount; ++i)
{
if ((memory.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0)
{
Caps.device_local_memory += memory.memoryHeaps[i].size;
}
}
// the version first: the structures of 1.4 mean nothing to an older driver
if (Caps.api_version < VK_API_VERSION_1_4)
{
Caps.missing.push_back("the driver gives vulkan " + version_string(Caps.api_version) + ", the renderer needs 1.4");
return;
}
// features
Queried.reset();
vkGetPhysicalDeviceFeatures2(Physical, Queried.head());
Caps.draw_indirect_count = (Queried.v12.drawIndirectCount == VK_TRUE);
Caps.host_image_copy = (Queried.v14.hostImageCopy == VK_TRUE);
Caps.fill_mode_non_solid = (Queried.features2.features.fillModeNonSolid == VK_TRUE);
Caps.pipeline_statistics_query = (Queried.features2.features.pipelineStatisticsQuery == VK_TRUE);
Caps.sampler_filter_minmax = (Queried.v12.samplerFilterMinmax == VK_TRUE);
Caps.shader_output_layer = (Queried.v12.shaderOutputLayer == VK_TRUE);
// can the renderer run on it
if (!Caps.ext_swapchain)
{
Caps.missing.push_back(std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME) + " isn't supported, the device can't show images in a window");
}
if (properties.properties.limits.timestampComputeAndGraphics == VK_FALSE)
{
Caps.missing.push_back("timestampComputeAndGraphics isn't supported (needed for the gpu times of the passes)");
}
for (auto const &entry : g_feature_table)
{
if ((false == entry.required) || (entry.field(Queried) == VK_TRUE))
{
continue;
}
auto message{std::string(entry.structure) + "::" + entry.name + " isn't supported (needed for " + entry.purpose + ")"};
if (entry.guaranteed)
{
message += "; vulkan 1.4 requires it, the driver doesn't conform";
}
Caps.missing.push_back(message);
}
}
void build_enabled_features(feature_chain &Queried, feature_chain &Enabled)
{
Enabled.reset();
for (auto const &entry : g_feature_table)
{
if (entry.field(Queried) == VK_TRUE)
{
entry.field(Enabled) = VK_TRUE;
}
}
}
std::string capability_report(capabilities const &Caps)
{
std::string report{"gpu capabilities\n"};
auto const line = [&report](std::string const &Text) { report += " " + Text + "\n"; };
auto const flag = [](bool const Value) { return Value ? "yes" : "no"; };
line("device " + Caps.device_name + " (" + vendor_string(Caps.vendor_id) + ", " + device_type_string(Caps.device_type) + ")");
line("api " + version_string(Caps.api_version));
line("driver " + Caps.driver_name + " " + Caps.driver_info);
line("device memory " + memory_size_string(Caps.device_local_memory));
line(std::string("draw indirect count ") + flag(Caps.draw_indirect_count));
line(std::string("extended dynamic state3 ") + flag(Caps.ext_extended_dynamic_state3));
line(std::string("memory budget ") + flag(Caps.ext_memory_budget));
line(std::string("swapchain maintenance1 ") + flag(Caps.ext_swapchain_maintenance1));
line(std::string("host image copy ") + flag(Caps.host_image_copy));
line(std::string("sampler filter minmax ") + flag(Caps.sampler_filter_minmax));
line(std::string("shader output layer ") + flag(Caps.shader_output_layer));
line(std::string("fill mode non solid ") + flag(Caps.fill_mode_non_solid));
line(std::string("pipeline statistics ") + flag(Caps.pipeline_statistics_query));
return report;
}
} // namespace vk14

View File

@@ -0,0 +1,108 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// what a gpu can do, and whether the renderer can run on it.
//
// one table of features drives three things: what is asked of the driver, what is switched on when the device is made, and
// what a gpu is turned down for (by the name of the missing feature). the code of the renderer goes by the flags of
// vk14::capabilities, each optional one with a way without it
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
namespace vk14
{
// the structures of the features the renderer cares about, in one object so a single pNext chain can go over them.
// not copyable: the chain points into the object itself
struct feature_chain
{
VkPhysicalDeviceFeatures2 features2{};
VkPhysicalDeviceVulkan11Features v11{};
VkPhysicalDeviceVulkan12Features v12{};
VkPhysicalDeviceVulkan13Features v13{};
VkPhysicalDeviceVulkan14Features v14{};
feature_chain();
feature_chain(feature_chain const &) = delete;
feature_chain &operator=(feature_chain const &) = delete;
// every structure with its sType and no features, linked into the chain
void reset();
// head of the chain, for vkGetPhysicalDeviceFeatures2 or VkDeviceCreateInfo::pNext
VkPhysicalDeviceFeatures2 *head()
{
return &features2;
}
};
struct feature_entry
{
char const *structure;
char const *name;
// the renderer doesn't run on a gpu without it
bool required;
// vulkan 1.4 requires it of every device, so a 1.4 device without it is a driver which doesn't conform
bool guaranteed;
// what it is for, for the log
char const *purpose;
VkBool32 &(*field)(feature_chain &);
};
std::vector<feature_entry> const &feature_table();
struct capabilities
{
// identification
std::string device_name;
std::string driver_name;
std::string driver_info;
VkPhysicalDeviceType device_type{VK_PHYSICAL_DEVICE_TYPE_OTHER};
std::uint32_t api_version{0};
std::uint32_t driver_version{0};
std::uint32_t vendor_id{0};
std::uint32_t device_id{0};
VkDeviceSize device_local_memory{0};
float timestamp_period{0.f}; // nanoseconds per tick
// device extensions
bool ext_swapchain{false};
bool ext_memory_budget{false};
bool ext_swapchain_maintenance1{false}; // the khr one, or the ext one it came from
bool ext_extended_dynamic_state3{false};
// optional features, each with a way without it
bool draw_indirect_count{false};
bool host_image_copy{false};
bool fill_mode_non_solid{false};
bool pipeline_statistics_query{false};
bool sampler_filter_minmax{false};
bool shader_output_layer{false};
// empty when the renderer can run on the device, otherwise one line per missing thing
std::vector<std::string> missing;
};
// the features, properties and extensions of a gpu. Queried gets what the device reports; the device is made with the
// features switched on from it
void query_capabilities(VkPhysicalDevice Physical, feature_chain &Queried, capabilities &Caps);
// the required features and the optional ones the device has, switched on
void build_enabled_features(feature_chain &Queried, feature_chain &Enabled);
// the report for the log, several lines
std::string capability_report(capabilities const &Caps);
// the kind of the gpu and its maker, for the log
char const *device_type_string(VkPhysicalDeviceType Type);
std::string vendor_string(std::uint32_t VendorId);
} // namespace vk14

View File

@@ -0,0 +1,145 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "utilities/Logs.h"
namespace vk14
{
char const *result_string(VkResult Result)
{
switch (Result)
{
case VK_SUCCESS:
return "VK_SUCCESS";
case VK_NOT_READY:
return "VK_NOT_READY";
case VK_TIMEOUT:
return "VK_TIMEOUT";
case VK_EVENT_SET:
return "VK_EVENT_SET";
case VK_EVENT_RESET:
return "VK_EVENT_RESET";
case VK_INCOMPLETE:
return "VK_INCOMPLETE";
case VK_ERROR_OUT_OF_HOST_MEMORY:
return "VK_ERROR_OUT_OF_HOST_MEMORY";
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
case VK_ERROR_INITIALIZATION_FAILED:
return "VK_ERROR_INITIALIZATION_FAILED";
case VK_ERROR_DEVICE_LOST:
return "VK_ERROR_DEVICE_LOST";
case VK_ERROR_MEMORY_MAP_FAILED:
return "VK_ERROR_MEMORY_MAP_FAILED";
case VK_ERROR_LAYER_NOT_PRESENT:
return "VK_ERROR_LAYER_NOT_PRESENT";
case VK_ERROR_EXTENSION_NOT_PRESENT:
return "VK_ERROR_EXTENSION_NOT_PRESENT";
case VK_ERROR_FEATURE_NOT_PRESENT:
return "VK_ERROR_FEATURE_NOT_PRESENT";
case VK_ERROR_INCOMPATIBLE_DRIVER:
return "VK_ERROR_INCOMPATIBLE_DRIVER";
case VK_ERROR_TOO_MANY_OBJECTS:
return "VK_ERROR_TOO_MANY_OBJECTS";
case VK_ERROR_FORMAT_NOT_SUPPORTED:
return "VK_ERROR_FORMAT_NOT_SUPPORTED";
case VK_ERROR_FRAGMENTED_POOL:
return "VK_ERROR_FRAGMENTED_POOL";
case VK_ERROR_UNKNOWN:
return "VK_ERROR_UNKNOWN";
case VK_ERROR_OUT_OF_POOL_MEMORY:
return "VK_ERROR_OUT_OF_POOL_MEMORY";
case VK_ERROR_INVALID_EXTERNAL_HANDLE:
return "VK_ERROR_INVALID_EXTERNAL_HANDLE";
case VK_ERROR_FRAGMENTATION:
return "VK_ERROR_FRAGMENTATION";
case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS:
return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS";
case VK_ERROR_SURFACE_LOST_KHR:
return "VK_ERROR_SURFACE_LOST_KHR";
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
case VK_SUBOPTIMAL_KHR:
return "VK_SUBOPTIMAL_KHR";
case VK_ERROR_OUT_OF_DATE_KHR:
return "VK_ERROR_OUT_OF_DATE_KHR";
case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
case VK_ERROR_VALIDATION_FAILED_EXT:
return "VK_ERROR_VALIDATION_FAILED_EXT";
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
default:
break;
}
return "VK_ERROR_<unrecognized>";
}
std::string version_string(std::uint32_t Version)
{
return std::to_string(VK_API_VERSION_MAJOR(Version)) + "." + std::to_string(VK_API_VERSION_MINOR(Version)) + "." + std::to_string(VK_API_VERSION_PATCH(Version));
}
std::string memory_size_string(VkDeviceSize Bytes)
{
auto const mib{static_cast<double>(Bytes) / (1024.0 * 1024.0)};
char buffer[64] = {};
if (mib >= 1024.0)
{
std::snprintf(buffer, sizeof(buffer), "%.2f GiB", mib / 1024.0);
}
else
{
std::snprintf(buffer, sizeof(buffer), "%.0f MiB", mib);
}
return std::string(buffer);
}
bool verify_result(VkResult Result, char const *Call, char const *File, int Line)
{
if (Result == VK_SUCCESS)
{
return true;
}
// only the name of the file, the path of the build is noise in the log
char const *name{File};
for (char const *c = File; *c != '\0'; ++c)
{
if ((*c == '/') || (*c == '\\'))
{
name = c + 1;
}
}
log_error(std::string(Call) + " failed with " + result_string(Result) + " (" + name + ":" + std::to_string(Line) + ")");
return false;
}
void log_info(std::string const &Message)
{
WriteLog("vulkan: " + Message);
}
void log_warning(std::string const &Message)
{
WriteLog("vulkan: warning: " + Message);
}
void log_error(std::string const &Message)
{
ErrorLog("vulkan: " + Message);
}
} // namespace vk14

View File

@@ -0,0 +1,42 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the vulkan api of the vulkan 1.4 renderer, for the files of rendering/vulkan14 only: the rest of the program doesn't see
// the vulkan headers. the functions come from volk, which loads the vulkan loader when the program runs, so the program starts
// on a machine without vulkan too (and runs with opengl there)
#pragma once
#include <volk.h>
// glfw declares its vulkan functions only when the vulkan headers come before its own, and stdafx.h brings glfw in first
extern "C" GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow *window, VkAllocationCallbacks const *allocator, VkSurfaceKHR *surface);
extern "C" GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, std::uint32_t queuefamily);
namespace vk14
{
// human readable VkResult, for the log
char const *result_string(VkResult Result);
// "1.4.309"
std::string version_string(std::uint32_t Version);
// bytes as MiB or GiB
std::string memory_size_string(VkDeviceSize Bytes);
// logs a failed call and gives back false; true for VK_SUCCESS
bool verify_result(VkResult Result, char const *Call, char const *File, int Line);
// the log of the renderer, with the "vulkan: " prefix
void log_info(std::string const &Message);
void log_warning(std::string const &Message);
void log_error(std::string const &Message);
} // namespace vk14
// true when the call succeeded; a failure is logged with the call and the place
#define VK14_VERIFY(expr) (::vk14::verify_result((expr), #expr, __FILE__, __LINE__))

View File

@@ -0,0 +1,328 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include <cstring>
namespace vk14
{
namespace
{
// "1" -> 1; anything which isn't a plain number -> -1
int index_of(std::string const &Text)
{
if (Text.empty() || (Text.size() > 3) || (false == std::all_of(Text.begin(), Text.end(), [](char const Char) { return std::isdigit(static_cast<unsigned char>(Char)) != 0; })))
{
return -1;
}
return std::stoi(Text);
}
std::string lowercase(std::string Text)
{
std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char const Char) { return static_cast<char>(std::tolower(Char)); });
return Text;
}
} // namespace
device::~device()
{
destroy();
}
queue_families device::find_queue_families(VkInstance Instance, VkPhysicalDevice Physical, VkSurfaceKHR Surface, std::uint32_t &TimestampBits)
{
queue_families families;
TimestampBits = 0;
std::uint32_t count{0};
vkGetPhysicalDeviceQueueFamilyProperties(Physical, &count, nullptr);
std::vector<VkQueueFamilyProperties> properties(count);
vkGetPhysicalDeviceQueueFamilyProperties(Physical, &count, properties.data());
for (std::uint32_t i = 0; i < count; ++i)
{
if ((properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)
{
continue;
}
VkBool32 present{VK_FALSE};
if (Surface != VK_NULL_HANDLE)
{
if (!VK14_VERIFY(vkGetPhysicalDeviceSurfaceSupportKHR(Physical, i, Surface, &present)))
{
present = VK_FALSE;
}
}
else
{
present = (glfwGetPhysicalDevicePresentationSupport(Instance, Physical, i) == GLFW_TRUE ? VK_TRUE : VK_FALSE);
}
if (present == VK_TRUE)
{
families.graphics = i;
TimestampBits = properties[i].timestampValidBits;
break;
}
}
if (families.graphics == queue_families::invalid)
{
return families;
}
families.compute = families.graphics;
families.transfer = families.graphics;
// a compute family without graphics can work alongside the frame
for (std::uint32_t i = 0; i < count; ++i)
{
auto const flags{properties[i].queueFlags};
if (((flags & VK_QUEUE_COMPUTE_BIT) != 0) && ((flags & VK_QUEUE_GRAPHICS_BIT) == 0))
{
families.compute = i;
families.dedicated_compute = true;
break;
}
}
// a family of transfers only is usually the copy engine
for (std::uint32_t i = 0; i < count; ++i)
{
auto const flags{properties[i].queueFlags};
if (((flags & VK_QUEUE_TRANSFER_BIT) != 0) && ((flags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT)) == 0))
{
families.transfer = i;
families.dedicated_transfer = true;
break;
}
}
return families;
}
std::uint64_t device::score(capabilities const &Caps)
{
std::uint64_t score{0};
switch (Caps.device_type)
{
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
score = 3;
break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
score = 2;
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
score = 1;
break;
default:
break;
}
// then the memory, in mebibytes
return (score << 40) + (Caps.device_local_memory >> 20);
}
bool device::select(VkInstance Instance, VkSurfaceKHR Surface, std::string const &Wanted, bool const Verbose, std::string &Reason)
{
m_physical = VK_NULL_HANDLE;
std::uint32_t count{0};
if (!VK14_VERIFY(vkEnumeratePhysicalDevices(Instance, &count, nullptr)) || (count == 0))
{
Reason = "no vulkan device";
return false;
}
std::vector<VkPhysicalDevice> devices(count);
if (!VK14_VERIFY(vkEnumeratePhysicalDevices(Instance, &count, devices.data())))
{
Reason = "the vulkan devices can't be listed";
return false;
}
auto const wantedindex{index_of(Wanted)};
auto const wantedname{lowercase(Wanted)};
if (Verbose)
{
log_info(std::to_string(count) + " device(s)");
}
VkPhysicalDevice best{VK_NULL_HANDLE};
VkPhysicalDevice chosen{VK_NULL_HANDLE};
std::uint64_t bestscore{0};
bool wantedfound{false};
std::string firstmissing;
for (std::uint32_t i = 0; i < count; ++i)
{
feature_chain queried;
capabilities caps;
query_capabilities(devices[i], queried, caps);
std::uint32_t timestampbits{0};
auto const families{find_queue_families(Instance, devices[i], Surface, timestampbits)};
if (families.graphics == queue_families::invalid)
{
caps.missing.push_back("no queue family both draws and shows images in a window");
}
else if (timestampbits == 0)
{
caps.missing.push_back("the graphics queue has no timestamps (needed for the gpu times of the passes)");
}
auto const label{"device " + std::to_string(i) + " \"" + caps.device_name + "\" (" + device_type_string(caps.device_type) + ", vulkan " + version_string(caps.api_version) + ")"};
auto const iswanted{(false == Wanted.empty()) && ((wantedindex >= 0) ? (wantedindex == static_cast<int>(i)) : (lowercase(caps.device_name).find(wantedname) != std::string::npos))};
wantedfound |= iswanted;
if (false == caps.missing.empty())
{
if (Verbose || iswanted)
{
log_warning(label + " can't be used:");
for (auto const &reason : caps.missing)
{
log_warning(" " + reason);
}
}
if (firstmissing.empty())
{
firstmissing = caps.device_name + ": " + caps.missing.front();
}
continue;
}
if (Verbose)
{
log_info(label + " can be used");
}
if (iswanted && (chosen == VK_NULL_HANDLE))
{
chosen = devices[i];
}
auto const devicescore{score(caps)};
if ((best == VK_NULL_HANDLE) || (devicescore > bestscore))
{
best = devices[i];
bestscore = devicescore;
}
}
if (Verbose && (false == Wanted.empty()) && (chosen == VK_NULL_HANDLE))
{
log_warning("gfx.vulkan.device " + Wanted + (wantedfound ? " can't be used" : " matches no device") + ", picking the device by itself");
}
if (chosen == VK_NULL_HANDLE)
{
chosen = best;
}
if (chosen == VK_NULL_HANDLE)
{
Reason = "no gpu has what the vulkan renderer needs (" + firstmissing + ")";
return false;
}
m_physical = chosen;
query_capabilities(m_physical, m_queried, m_capabilities);
std::uint32_t timestampbits{0};
m_families = find_queue_families(Instance, m_physical, Surface, timestampbits);
return true;
}
bool device::create(instance const &Instance, std::string &Reason)
{
if (m_device != VK_NULL_HANDLE)
{
return true;
}
if (m_physical == VK_NULL_HANDLE)
{
Reason = "no gpu was picked";
return false;
}
feature_chain enabled;
build_enabled_features(m_queried, enabled);
std::vector<std::uint32_t> families{m_families.graphics};
for (auto const family : {m_families.compute, m_families.transfer})
{
if (std::find(families.begin(), families.end(), family) == families.end())
{
families.push_back(family);
}
}
float const priority{1.f};
std::vector<VkDeviceQueueCreateInfo> queues;
for (auto const family : families)
{
VkDeviceQueueCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
info.queueFamilyIndex = family;
info.queueCount = 1;
info.pQueuePriorities = &priority;
queues.push_back(info);
}
// everything else the renderer uses is in the core of vulkan 1.4
std::vector<char const *> extensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME};
{
// a device of the portability subset (vulkan on metal) has to be told the program knows it
std::uint32_t count{0};
vkEnumerateDeviceExtensionProperties(m_physical, nullptr, &count, nullptr);
std::vector<VkExtensionProperties> available(count);
vkEnumerateDeviceExtensionProperties(m_physical, nullptr, &count, available.data());
m_portability_subset = std::any_of(available.begin(), available.end(), [](VkExtensionProperties const &Entry) { return std::strcmp(Entry.extensionName, "VK_KHR_portability_subset") == 0; });
if (m_portability_subset)
{
extensions.push_back("VK_KHR_portability_subset");
}
}
VkDeviceCreateInfo create{};
create.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
create.pNext = enabled.head();
create.queueCreateInfoCount = static_cast<std::uint32_t>(queues.size());
create.pQueueCreateInfos = queues.data();
create.enabledExtensionCount = static_cast<std::uint32_t>(extensions.size());
create.ppEnabledExtensionNames = extensions.data();
auto const result{vkCreateDevice(m_physical, &create, nullptr, &m_device)};
if (result != VK_SUCCESS)
{
m_device = VK_NULL_HANDLE;
Reason = std::string("vkCreateDevice failed with ") + result_string(result);
return false;
}
// the functions of the device straight from the driver, without the dispatch of the loader
volkLoadDevice(m_device);
vkGetDeviceQueue(m_device, m_families.graphics, 0, &m_graphics_queue);
vkGetDeviceQueue(m_device, m_families.compute, 0, &m_compute_queue);
vkGetDeviceQueue(m_device, m_families.transfer, 0, &m_transfer_queue);
Instance.set_object_name(m_device, VK_OBJECT_TYPE_DEVICE, reinterpret_cast<std::uint64_t>(m_device), "eu07 device");
Instance.set_object_name(m_device, VK_OBJECT_TYPE_QUEUE, reinterpret_cast<std::uint64_t>(m_graphics_queue), "graphics queue");
log_info("using " + m_capabilities.device_name + ", queue families: graphics " + std::to_string(m_families.graphics) + ", compute " + std::to_string(m_families.compute) + (m_families.dedicated_compute ? " (own)" : " (shared)") + ", transfer " +
std::to_string(m_families.transfer) + (m_families.dedicated_transfer ? " (own)" : " (shared)"));
return true;
}
void device::destroy()
{
if (m_device != VK_NULL_HANDLE)
{
vkDestroyDevice(m_device, nullptr);
m_device = VK_NULL_HANDLE;
}
m_graphics_queue = VK_NULL_HANDLE;
m_compute_queue = VK_NULL_HANDLE;
m_transfer_queue = VK_NULL_HANDLE;
}
} // namespace vk14

View File

@@ -0,0 +1,91 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the gpu the renderer runs on, and the VkDevice with its queues.
//
// the gpu is the one named by gfx.vulkan.device (its index in the list of the log, or a part of its name), or else the best
// usable one: discrete before integrated, then the one with more memory. a gpu missing something the renderer needs is
// turned down with the name of what it lacks
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14capabilities.h"
namespace vk14
{
class instance;
struct queue_families
{
static constexpr std::uint32_t invalid{VK_QUEUE_FAMILY_IGNORED};
// graphics and presentation, from one family (every desktop driver has one)
std::uint32_t graphics{invalid};
// asynchronous compute and transfers; the graphics family where there's no family of their own
std::uint32_t compute{invalid};
std::uint32_t transfer{invalid};
bool dedicated_compute{false};
bool dedicated_transfer{false};
};
class device
{
public:
device() = default;
device(device const &) = delete;
device &operator=(device const &) = delete;
~device();
// picks the gpu. Surface: the window to show images in, null before there's one (the presentation is then asked of
// glfw). Verbose: every gpu and why it can't be used goes to the log. false: no usable gpu, Reason tells why
bool select(VkInstance Instance, VkSurfaceKHR Surface, std::string const &Wanted, bool const Verbose, std::string &Reason);
// makes the VkDevice of the gpu picked by select()
bool create(instance const &Instance, std::string &Reason);
void destroy();
VkPhysicalDevice physical() const
{
return m_physical;
}
VkDevice handle() const
{
return m_device;
}
capabilities const &caps() const
{
return m_capabilities;
}
queue_families const &families() const
{
return m_families;
}
VkQueue graphics_queue() const
{
return m_graphics_queue;
}
private:
static queue_families find_queue_families(VkInstance Instance, VkPhysicalDevice Physical, VkSurfaceKHR Surface, std::uint32_t &TimestampBits);
static std::uint64_t score(capabilities const &Caps);
VkPhysicalDevice m_physical{VK_NULL_HANDLE};
VkDevice m_device{VK_NULL_HANDLE};
capabilities m_capabilities;
queue_families m_families;
feature_chain m_queried;
bool m_portability_subset{false};
VkQueue m_graphics_queue{VK_NULL_HANDLE};
VkQueue m_compute_queue{VK_NULL_HANDLE};
VkQueue m_transfer_queue{VK_NULL_HANDLE};
};
} // namespace vk14

View File

@@ -0,0 +1,342 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include <cstring>
namespace vk14
{
namespace
{
char const *const validation_layer_name = "VK_LAYER_KHRONOS_validation";
bool contains(std::vector<char const *> const &List, char const *Name)
{
return std::any_of(List.begin(), List.end(), [Name](char const *Entry) { return std::strcmp(Entry, Name) == 0; });
}
std::vector<VkExtensionProperties> instance_extensions(char const *Layer)
{
std::uint32_t count{0};
std::vector<VkExtensionProperties> extensions;
if (VK14_VERIFY(vkEnumerateInstanceExtensionProperties(Layer, &count, nullptr)) && (count > 0))
{
extensions.resize(count);
if (!VK14_VERIFY(vkEnumerateInstanceExtensionProperties(Layer, &count, extensions.data())))
{
extensions.clear();
}
}
return extensions;
}
bool has_extension(std::vector<VkExtensionProperties> const &Extensions, char const *Name)
{
return std::any_of(Extensions.begin(), Extensions.end(), [Name](VkExtensionProperties const &Entry) { return std::strcmp(Entry.extensionName, Name) == 0; });
}
VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT Severity, VkDebugUtilsMessageTypeFlagsEXT Types, VkDebugUtilsMessengerCallbackDataEXT const *Data, void * /*UserData*/)
{
std::string message{(Types & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) != 0 ? "validation" : (Types & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) != 0 ? "performance" : "general"};
if ((Data != nullptr) && (Data->pMessageIdName != nullptr))
{
message += " [";
message += Data->pMessageIdName;
message += "]";
}
message += ": ";
message += ((Data != nullptr) && (Data->pMessage != nullptr)) ? Data->pMessage : "<no message>";
if ((Severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0)
{
log_error(message);
}
else if ((Severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0)
{
log_warning(message);
}
else
{
log_info(message);
}
// anything else is for the development of layers
return VK_FALSE;
}
void fill_messenger_info(VkDebugUtilsMessengerCreateInfoEXT &Info)
{
Info = {};
Info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
Info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
Info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
Info.pfnUserCallback = &debug_callback;
}
} // namespace
bool load_vulkan(std::string &Reason)
{
// the loader is loaded once for the run; volk keeps it
static VkResult const loaded{volkInitialize()};
if (loaded != VK_SUCCESS)
{
Reason = "no vulkan loader (vulkan-1.dll / libvulkan.so.1) on the machine; the graphics driver has no vulkan";
return false;
}
if (glfwVulkanSupported() == GLFW_FALSE)
{
Reason = "glfw finds no vulkan loader with the extensions to show images in a window";
return false;
}
auto const loaderversion{volkGetInstanceVersion()};
// an instance of vulkan 1.4 needs a loader of 1.1 or newer; the device has to give 1.4 itself
if (loaderversion < VK_API_VERSION_1_1)
{
Reason = "the vulkan loader is of version " + version_string(loaderversion) + ", update the graphics driver";
return false;
}
return true;
}
instance::~instance()
{
destroy();
}
bool instance::create(instance_config const &Config, std::string &Reason)
{
if (m_instance != VK_NULL_HANDLE)
{
return true;
}
if (false == load_vulkan(Reason))
{
return false;
}
std::vector<char const *> layers;
std::vector<char const *> extensions;
if (false == collect_layers(layers, Config))
{
Reason = "the validation layer can't be listed";
return false;
}
if (false == collect_extensions(extensions, Config, Reason))
{
return false;
}
VkApplicationInfo application{};
application.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application.pApplicationName = "MaSzyna";
application.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
application.pEngineName = "MaSzyna vulkan14";
application.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
application.apiVersion = VK_API_VERSION_1_4;
VkInstanceCreateInfo create{};
create.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create.pApplicationInfo = &application;
#ifdef __APPLE__
// vulkan on metal (moltenvk, kosmickrisp) is listed only to the programs which ask for the devices of the portability subset
if (contains(extensions, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME))
{
create.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
}
#endif
create.enabledLayerCount = static_cast<std::uint32_t>(layers.size());
create.ppEnabledLayerNames = layers.empty() ? nullptr : layers.data();
create.enabledExtensionCount = static_cast<std::uint32_t>(extensions.size());
create.ppEnabledExtensionNames = extensions.empty() ? nullptr : extensions.data();
void const **tail{&create.pNext};
// in the chain the messenger sees the creation and the destruction of the instance too
VkDebugUtilsMessengerCreateInfoEXT messenger{};
if (m_debug_utils_enabled)
{
fill_messenger_info(messenger);
*tail = &messenger;
tail = &messenger.pNext;
}
// synchronization validation checks the barriers; the gpu-assisted one the accesses of the shaders
std::vector<VkValidationFeatureEnableEXT> validationfeatures;
VkValidationFeaturesEXT validation{};
if (m_validation_enabled && contains(extensions, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME))
{
validationfeatures.push_back(VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT);
if (Config.validation >= 2)
{
validationfeatures.push_back(VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT);
}
validation.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT;
validation.enabledValidationFeatureCount = static_cast<std::uint32_t>(validationfeatures.size());
validation.pEnabledValidationFeatures = validationfeatures.data();
*tail = &validation;
tail = &validation.pNext;
}
auto const result{vkCreateInstance(&create, nullptr, &m_instance)};
if (result != VK_SUCCESS)
{
m_instance = VK_NULL_HANDLE;
Reason = (result == VK_ERROR_INCOMPATIBLE_DRIVER ? std::string("no installed driver gives vulkan 1.4 (vkCreateInstance: VK_ERROR_INCOMPATIBLE_DRIVER)") : std::string("vkCreateInstance failed with ") + result_string(result));
return false;
}
// the functions of the instance; those of the device come from it directly, once it's made
volkLoadInstanceOnly(m_instance);
if (m_debug_utils_enabled)
{
VkDebugUtilsMessengerCreateInfoEXT info{};
fill_messenger_info(info);
if ((vkCreateDebugUtilsMessengerEXT == nullptr) || !VK14_VERIFY(vkCreateDebugUtilsMessengerEXT(m_instance, &info, nullptr, &m_messenger)))
{
// the renderer works, only the messages of the validation layer don't reach the log
m_messenger = VK_NULL_HANDLE;
log_warning("the messages of the validation layer won't reach the log");
}
}
return true;
}
bool instance::collect_layers(std::vector<char const *> &Layers, instance_config const &Config)
{
Layers.clear();
m_validation_enabled = false;
if (Config.validation <= 0)
{
return true;
}
std::uint32_t count{0};
if (!VK14_VERIFY(vkEnumerateInstanceLayerProperties(&count, nullptr)))
{
return false;
}
std::vector<VkLayerProperties> available(count);
if ((count > 0) && !VK14_VERIFY(vkEnumerateInstanceLayerProperties(&count, available.data())))
{
return false;
}
if (std::any_of(available.begin(), available.end(), [](VkLayerProperties const &Layer) { return std::strcmp(Layer.layerName, validation_layer_name) == 0; }))
{
Layers.push_back(validation_layer_name);
m_validation_enabled = true;
log_info(std::string("validation layer on") + (Config.validation >= 2 ? " (with gpu-assisted validation)" : ""));
}
else
{
// gfx.vulkan.validation doesn't stop the renderer on a machine without the vulkan sdk
log_warning(std::string(validation_layer_name) + " isn't installed (vulkan sdk), running without validation");
}
return true;
}
bool instance::collect_extensions(std::vector<char const *> &Extensions, instance_config const &Config, std::string &Reason)
{
Extensions.clear();
m_debug_utils_enabled = false;
std::uint32_t glfwcount{0};
auto const **glfwextensions{glfwGetRequiredInstanceExtensions(&glfwcount)};
if ((glfwextensions == nullptr) || (glfwcount == 0))
{
Reason = "no vulkan driver gives the extensions to show images in a window";
return false;
}
for (std::uint32_t i = 0; i < glfwcount; ++i)
{
Extensions.push_back(glfwextensions[i]);
}
auto const available{instance_extensions(nullptr)};
for (auto const *name : Extensions)
{
if (false == has_extension(available, name))
{
Reason = std::string("the instance extension ") + name + " to show images in a window isn't available";
return false;
}
}
if (Config.debug_utils || m_validation_enabled)
{
if (has_extension(available, VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
{
if (false == contains(Extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
{
Extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
m_debug_utils_enabled = true;
}
else
{
log_warning(std::string(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) + " isn't available, no names of objects and no messages of the validation layer");
}
}
#ifdef __APPLE__
if (has_extension(available, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME))
{
Extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
}
#endif
if (m_validation_enabled && has_extension(instance_extensions(validation_layer_name), VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME))
{
Extensions.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);
}
std::string list;
for (auto const *name : Extensions)
{
list += (list.empty() ? "" : ", ");
list += name;
}
log_info("instance extensions: " + list);
return true;
}
void instance::set_object_name(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name) const
{
if ((false == m_debug_utils_enabled) || (vkSetDebugUtilsObjectNameEXT == nullptr) || (Device == VK_NULL_HANDLE) || (Handle == 0))
{
return;
}
VkDebugUtilsObjectNameInfoEXT info{};
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
info.objectType = Type;
info.objectHandle = Handle;
info.pObjectName = Name.c_str();
vkSetDebugUtilsObjectNameEXT(Device, &info);
}
void instance::destroy()
{
if ((m_messenger != VK_NULL_HANDLE) && (vkDestroyDebugUtilsMessengerEXT != nullptr))
{
vkDestroyDebugUtilsMessengerEXT(m_instance, m_messenger, nullptr);
}
m_messenger = VK_NULL_HANDLE;
if (m_instance != VK_NULL_HANDLE)
{
vkDestroyInstance(m_instance, nullptr);
m_instance = VK_NULL_HANDLE;
}
m_validation_enabled = false;
m_debug_utils_enabled = false;
}
} // namespace vk14

View File

@@ -0,0 +1,64 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the vulkan loader, loaded when the program runs, and the VkInstance: vulkan 1.4, the extensions glfw needs to show images
// in a window and, with gfx.vulkan.validation, the khronos validation layer with its messages passed to the log
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
namespace vk14
{
// loads the vulkan loader (once). false: there's no vulkan on the machine, Reason tells what is missing
bool load_vulkan(std::string &Reason);
struct instance_config
{
// 0: none, 1: VK_LAYER_KHRONOS_validation, 2: also its gpu-assisted validation
int validation{0};
// VK_EXT_debug_utils: the messages of the validation layer in the log, names of objects
bool debug_utils{false};
};
class instance
{
public:
instance() = default;
instance(instance const &) = delete;
instance &operator=(instance const &) = delete;
~instance();
// false: Reason tells why
bool create(instance_config const &Config, std::string &Reason);
void destroy();
VkInstance handle() const
{
return m_instance;
}
bool validation_enabled() const
{
return m_validation_enabled;
}
// a name of a vulkan object, for the messages of the validation layer and for captures. nothing without debug utils
void set_object_name(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name) const;
private:
bool collect_layers(std::vector<char const *> &Layers, instance_config const &Config);
bool collect_extensions(std::vector<char const *> &Extensions, instance_config const &Config, std::string &Reason);
VkInstance m_instance{VK_NULL_HANDLE};
VkDebugUtilsMessengerEXT m_messenger{VK_NULL_HANDLE};
bool m_validation_enabled{false};
bool m_debug_utils_enabled{false};
};
} // namespace vk14

View File

@@ -0,0 +1,628 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14renderer.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/nullrenderer.h"
#include "application/application.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/Timer.h"
#include "utilities/utilities.h"
#include "imgui/imgui.h"
namespace
{
// the colour the windows are cleared to, the initial background of the opengl renderer (srgb)
glm::vec3 const clear_colour{51.f / 255.f, 102.f / 255.f, 85.f / 255.f};
float srgb_to_linear(float const Value)
{
return (Value <= 0.04045f ? Value / 12.92f : std::pow((Value + 0.055f) / 1.055f, 2.4f));
}
} // namespace
// the objects of the vulkan api. destroyed in the reverse order: the swapchains and the frames, then the device, then the instance
struct vulkan14_renderer::context
{
struct frame
{
VkCommandPool pool{VK_NULL_HANDLE};
VkCommandBuffer commands{VK_NULL_HANDLE};
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
std::uint64_t done{0};
};
vk14::instance instance;
vk14::device device;
// [0]: the main window, then the windows of the extra viewports
std::vector<std::unique_ptr<vk14::swapchain>> windows;
std::vector<frame> frames;
// one semaphore counts the frames done by the gpu; the binary ones are only between the swapchains and the queue
VkSemaphore timeline{VK_NULL_HANDLE};
std::uint64_t timelinevalue{0};
// frames recorded since the start
std::uint64_t framecount{0};
// the windows with an image acquired in the frame being made
std::vector<vk14::swapchain *> acquired;
bool devicelost{false};
~context()
{
destroy();
}
bool create_frames(std::uint32_t const Count, std::string &Reason)
{
auto const handle{device.handle()};
VkSemaphoreTypeCreateInfo timelineinfo{};
timelineinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO;
timelineinfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
timelineinfo.initialValue = 0;
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreinfo.pNext = &timelineinfo;
if (!VK14_VERIFY(vkCreateSemaphore(handle, &semaphoreinfo, nullptr, &timeline)))
{
Reason = "no timeline semaphore";
return false;
}
instance.set_object_name(handle, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(timeline), "frame timeline");
frames.resize(Count);
for (std::uint32_t i = 0; i < Count; ++i)
{
VkCommandPoolCreateInfo poolinfo{};
poolinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolinfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
poolinfo.queueFamilyIndex = device.families().graphics;
if (!VK14_VERIFY(vkCreateCommandPool(handle, &poolinfo, nullptr, &frames[i].pool)))
{
Reason = "no command pool";
return false;
}
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = frames[i].pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 1;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, &frames[i].commands)))
{
Reason = "no command buffer";
return false;
}
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
}
return true;
}
void destroy()
{
auto const handle{device.handle()};
if (handle != VK_NULL_HANDLE)
{
// the end of the renderer, not a frame: everything on the gpu has to be done
vkDeviceWaitIdle(handle);
}
acquired.clear();
windows.clear();
if (handle != VK_NULL_HANDLE)
{
for (auto &frame : frames)
{
if (frame.pool != VK_NULL_HANDLE)
{
vkDestroyCommandPool(handle, frame.pool, nullptr);
}
}
if (timeline != VK_NULL_HANDLE)
{
vkDestroySemaphore(handle, timeline, nullptr);
}
}
frames.clear();
timeline = VK_NULL_HANDLE;
device.destroy();
instance.destroy();
}
};
namespace
{
// whether the renderer can run on the machine, before there is a window: the loader, an instance of vulkan 1.4 and a gpu
// with everything the renderer needs
bool probe(std::string &Reason)
{
vk14::instance instance;
if (false == instance.create(vk14::instance_config{}, Reason))
{
return false;
}
vk14::device device;
if (false == device.select(instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, true, Reason))
{
return false;
}
WriteLog(vk14::capability_report(device.caps()));
return true;
}
void window_hints()
{
// the swapchain shows the images; a window with an opengl context couldn't be given to vulkan
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
}
void window_failed(std::string const &Reason)
{
vk14::log_error("the window couldn't be made (" + Reason + ")");
}
} // namespace
vulkan14_renderer::vulkan14_renderer() = default;
vulkan14_renderer::~vulkan14_renderer()
{
Shutdown();
}
std::unique_ptr<gfx_renderer> vulkan14_renderer::create_func()
{
return std::make_unique<vulkan14_renderer>();
}
gfx_backend_desc vulkan14_renderer::backend_desc()
{
gfx_backend_desc desc;
desc.name = "vulkan 1.4";
desc.create = &vulkan14_renderer::create_func;
desc.probe = &probe;
desc.window_hints = &window_hints;
desc.window_failed = &window_failed;
desc.gl_context = false;
return desc;
}
bool vulkan14_renderer::renderer_register = gfx_renderer_factory::get_instance()->register_backend("vulkan", vulkan14_renderer::backend_desc());
bool vulkan14_renderer::Init(GLFWwindow *Window)
{
m_context = std::make_unique<context>();
auto &ctx{*m_context};
std::string reason;
vk14::instance_config config;
config.validation = Global.gfx_vulkan_validation;
config.debug_utils = (Global.gfx_vulkan_validation > 0);
auto const framesinflight{static_cast<std::uint32_t>(std::clamp(Global.gfx_vulkan_framesinflight, 2, 3))};
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason) && ctx.device.create(ctx.instance, reason) && ctx.create_frames(framesinflight, reason)};
if (ok)
{
auto mainwindow{std::make_unique<vk14::swapchain>()};
ok = mainwindow->create(ctx.instance, ctx.device, Window, "main window", framesinflight, reason);
ctx.windows.emplace_back(std::move(mainwindow));
}
if (false == ok)
{
vk14::log_error("the renderer can't start: " + reason);
m_context.reset();
return false;
}
vk14::log_info(std::to_string(framesinflight) + " frames in flight");
// until the stages of the plan bring them, these parts of the simulator aren't there with vulkan
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), the user interface (R5), picking, mouse position in the scene and screenshots (D6), "
"python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
WriteLog("Gfx Renderer: setup complete");
update_info();
return true;
}
bool vulkan14_renderer::AddViewport(global_settings::extraviewport_config const &conf)
{
if (!m_context)
{
return false;
}
if (conf.monitor == "MAIN")
{
// the projection of the main window is changed only, which the drawing of the scenery takes care of (D6)
vk14::log_info("extra viewport on the main window: drawn from the stage D6 of the plan");
return true;
}
// a window of its own, without an opengl context
auto *window{Application.window(-1, true, conf.width, conf.height, Application.find_monitor(conf.monitor), true, false)};
if (window == nullptr)
{
vk14::log_error("the window of the extra viewport on " + conf.monitor + " couldn't be made");
return false;
}
auto &ctx{*m_context};
auto viewport{std::make_unique<vk14::swapchain>()};
std::string reason;
if (false == viewport->create(ctx.instance, ctx.device, window, "viewport " + conf.monitor, static_cast<std::uint32_t>(ctx.frames.size()), reason))
{
vk14::log_error(reason);
return false;
}
ctx.windows.emplace_back(std::move(viewport));
return true;
}
void vulkan14_renderer::Shutdown()
{
m_context.reset();
}
bool vulkan14_renderer::Render()
{
if (!m_context || m_context->devicelost)
{
return false;
}
auto &ctx{*m_context};
auto const device{ctx.device.handle()};
auto const slot{static_cast<std::uint32_t>(ctx.framecount % ctx.frames.size())};
auto &frame{ctx.frames[slot]};
Timer::subsystem.gfx_total.start();
// the gpu has to be done with the frame which used the slot before
if (frame.done > 0)
{
VkSemaphoreWaitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO;
wait.semaphoreCount = 1;
wait.pSemaphores = &ctx.timeline;
wait.pValues = &frame.done;
if (!VK14_VERIFY(vkWaitSemaphores(device, &wait, UINT64_MAX)))
{
ctx.devicelost = true;
return false;
}
}
VK14_VERIFY(vkResetCommandPool(device, frame.pool, 0));
ctx.acquired.clear();
for (auto &window : ctx.windows)
{
switch (window->acquire(slot))
{
case vk14::swapchain::acquire_result::ready:
ctx.acquired.push_back(window.get());
break;
case vk14::swapchain::acquire_result::skipped:
break;
default:
ctx.devicelost = true;
return false;
}
}
// the frames of the user interface go on (its input works too), though nothing of it is drawn yet
Timer::subsystem.gfx_gui.start();
Application.render_ui();
Timer::subsystem.gfx_gui.stop();
VkCommandBufferBeginInfo begin{};
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
for (auto *window : ctx.acquired)
{
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(clear_colour.r), srgb_to_linear(clear_colour.g), srgb_to_linear(clear_colour.b)} : clear_colour};
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
}
VK14_VERIFY(vkEndCommandBuffer(frame.commands));
std::vector<VkSemaphoreSubmitInfo> waits;
std::vector<VkSemaphoreSubmitInfo> signals;
for (auto *window : ctx.acquired)
{
VkSemaphoreSubmitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
wait.semaphore = window->acquire_semaphore(slot);
wait.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
waits.push_back(wait);
VkSemaphoreSubmitInfo signal{};
signal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
signal.semaphore = window->present_semaphore();
signal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
signals.push_back(signal);
}
VkSemaphoreSubmitInfo timelinesignal{};
timelinesignal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
timelinesignal.semaphore = ctx.timeline;
timelinesignal.value = ctx.timelinevalue + 1;
timelinesignal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
signals.push_back(timelinesignal);
VkCommandBufferSubmitInfo commands{};
commands.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
commands.commandBuffer = frame.commands;
VkSubmitInfo2 submit{};
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(waits.size());
submit.pWaitSemaphoreInfos = waits.data();
submit.commandBufferInfoCount = 1;
submit.pCommandBufferInfos = &commands;
submit.signalSemaphoreInfoCount = static_cast<std::uint32_t>(signals.size());
submit.pSignalSemaphoreInfos = signals.data();
if (!VK14_VERIFY(vkQueueSubmit2(ctx.device.graphics_queue(), 1, &submit, VK_NULL_HANDLE)))
{
ctx.devicelost = true;
return false;
}
frame.done = ++ctx.timelinevalue;
Timer::subsystem.gfx_total.stop();
return true;
}
void vulkan14_renderer::SwapBuffers()
{
if (!m_context)
{
return;
}
auto &ctx{*m_context};
Timer::subsystem.gfx_swap.start();
if (false == ctx.acquired.empty())
{
std::vector<VkSemaphore> waits;
std::vector<VkSwapchainKHR> swapchains;
std::vector<std::uint32_t> images;
std::vector<VkResult> results(ctx.acquired.size(), VK_SUCCESS);
for (auto *window : ctx.acquired)
{
waits.push_back(window->present_semaphore());
swapchains.push_back(window->handle());
images.push_back(window->image_index());
}
VkPresentInfoKHR present{};
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.waitSemaphoreCount = static_cast<std::uint32_t>(waits.size());
present.pWaitSemaphores = waits.data();
present.swapchainCount = static_cast<std::uint32_t>(swapchains.size());
present.pSwapchains = swapchains.data();
present.pImageIndices = images.data();
present.pResults = results.data();
auto const result{vkQueuePresentKHR(ctx.device.graphics_queue(), &present)};
for (std::size_t i = 0; i < ctx.acquired.size(); ++i)
{
ctx.acquired[i]->presented(results[i]);
}
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
{
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
ctx.devicelost = true;
}
ctx.acquired.clear();
}
++ctx.framecount;
Timer::subsystem.gfx_swap.stop();
}
float vulkan14_renderer::Framerate()
{
return m_framerate;
}
// geometry: kept in memory for the simulation, nothing on the gpu until D1
gfx::geometrybank_handle vulkan14_renderer::Create_Bank()
{
return m_geometry.register_bank(std::make_unique<null_geometrybank>());
}
gfx::geometry_handle vulkan14_renderer::Insert(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type)
{
// NOTE: we expect indexed geometry to come with calculated tangents
return m_geometry.create_chunk(Indices, Vertices, Userdata, Geometry, Type);
}
gfx::geometry_handle vulkan14_renderer::Insert(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.create_chunk(Vertices, Userdata, Geometry, Type);
}
bool vulkan14_renderer::Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.replace(Vertices, Userdata, Geometry, Offset);
}
bool vulkan14_renderer::Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.append(Vertices, Userdata, Geometry);
}
gfx::index_array const &vulkan14_renderer::Indices(gfx::geometry_handle const &Geometry) const
{
return m_geometry.indices(Geometry);
}
gfx::vertex_array const &vulkan14_renderer::Vertices(gfx::geometry_handle const &Geometry) const
{
return m_geometry.vertices(Geometry);
}
gfx::userdata_array const &vulkan14_renderer::UserData(gfx::geometry_handle const &Geometry) const
{
return m_geometry.userdata(Geometry);
}
// materials, shaders, textures: none until D1 and D2
material_handle vulkan14_renderer::Fetch_Material(std::string const &Filename, bool const Loadnow)
{
return null_handle;
}
void vulkan14_renderer::Bind_Material(material_handle const Material, TSubModel const *sm, lighting_data const *lighting) {}
IMaterial const *vulkan14_renderer::Material(material_handle const Material) const
{
return IMaterial::null_material();
}
std::shared_ptr<gl::program> vulkan14_renderer::Fetch_Shader(std::string const &name)
{
return nullptr;
}
texture_handle vulkan14_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint)
{
return null_handle;
}
void vulkan14_renderer::Bind_Texture(texture_handle const Texture) {}
void vulkan14_renderer::Bind_Texture(std::size_t const Unit, texture_handle const Texture) {}
ITexture &vulkan14_renderer::Texture(texture_handle const Texture)
{
return *ITexture::null_texture();
}
ITexture const &vulkan14_renderer::Texture(texture_handle const Texture) const
{
return *ITexture::null_texture();
}
// picking: D6
void vulkan14_renderer::Pick_Control_Callback(std::function<void(TSubModel const *, glm::vec2 const)> Callback) {}
void vulkan14_renderer::Pick_Node_Callback(std::function<void(scene::basic_node *)> Callback) {}
TSubModel const *vulkan14_renderer::Pick_Control() const
{
return nullptr;
}
scene::basic_node const *vulkan14_renderer::Pick_Node() const
{
return nullptr;
}
glm::dvec3 vulkan14_renderer::Mouse_Position() const
{
return glm::dvec3(0.0);
}
// maintenance
void vulkan14_renderer::Update(double const Deltatime)
{
m_updateaccumulator += Deltatime;
if (m_updateaccumulator < 1.0)
{
return;
}
m_updateaccumulator = 0.0;
m_framerate = 1000.f / Timer::subsystem.mainloop_total.average();
update_info();
}
void vulkan14_renderer::update_info()
{
m_infotimes.clear();
m_infostats.clear();
if (!m_context)
{
return;
}
auto const &ctx{*m_context};
auto const &caps{ctx.device.caps()};
m_infotimes = "vulkan " + vk14::version_string(caps.api_version) + ": " + caps.device_name + " (" + caps.driver_name + ")\n" + "frames: " + std::to_string(ctx.framecount) + ", in flight " + std::to_string(ctx.frames.size()) + "\n" +
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n";
for (auto const &window : ctx.windows)
{
auto const extent{window->extent()};
m_infostats += "window: " + std::to_string(extent.width) + "x" + std::to_string(extent.height) + (window->srgb() ? ", srgb" : ", unorm") + (window->present_mode() == VK_PRESENT_MODE_FIFO_KHR ? ", vsync" : ", no vsync") + "\n";
}
}
void vulkan14_renderer::Update_Pick_Control() {}
void vulkan14_renderer::Update_Pick_Node() {}
glm::dvec3 vulkan14_renderer::Update_Mouse_Position()
{
return glm::dvec3(0.0);
}
bool vulkan14_renderer::Debug_Ui_State(std::optional<bool> State)
{
if (State)
{
m_debug_ui_active = *State;
}
return m_debug_ui_active;
}
std::string const &vulkan14_renderer::info_times() const
{
return m_infotimes;
}
std::string const &vulkan14_renderer::info_stats() const
{
return m_infostats;
}
imgui_renderer *vulkan14_renderer::GetImguiRenderer()
{
return &m_imgui;
}
void vulkan14_renderer::MakeScreenshot()
{
vk14::log_warning("screenshots aren't made by the vulkan renderer yet (D6)");
}
// imgui: the font atlas is built so its frames go on; nothing is drawn until R5
bool vulkan14_renderer::imgui_stub::Init()
{
auto &io{ImGui::GetIO()};
io.BackendRendererName = "vulkan14 (no drawing yet)";
unsigned char *pixels{nullptr};
int width{0}, height{0};
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
io.Fonts->SetTexID(static_cast<ImTextureID>(0));
return true;
}
void vulkan14_renderer::imgui_stub::Shutdown()
{
ImGui::GetIO().BackendRendererName = nullptr;
}
void vulkan14_renderer::imgui_stub::BeginFrame() {}
void vulkan14_renderer::imgui_stub::Render() {}

View File

@@ -0,0 +1,106 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the frame of the renderer only: the gpu, the windows with
// their swapchains, the frames in flight; each window is cleared to a colour. the scenery, the materials and the textures
// come in the later stages of the plan (claude/vulkan14-renderer-plan.md); until then those methods keep the data the
// simulation reads back and draw nothing.
//
// the vulkan headers stay in the files of rendering/vulkan14: the objects of the api live in vulkan14_renderer::context
#pragma once
#include "rendering/renderer.h"
#include "rendering/geometrybank.h"
class vulkan14_renderer : public gfx_renderer
{
public:
vulkan14_renderer();
~vulkan14_renderer() override;
// lifecycle
bool Init(GLFWwindow *Window) override;
bool AddViewport(global_settings::extraviewport_config const &conf) override;
void Shutdown() override;
bool Render() override;
void SwapBuffers() override;
float Framerate() override;
// geometry
gfx::geometrybank_handle Create_Bank() override;
gfx::geometry_handle Insert(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type) override;
gfx::geometry_handle Insert(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type) override;
bool Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset = 0) override;
bool Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type) override;
gfx::index_array const &Indices(gfx::geometry_handle const &Geometry) const override;
gfx::vertex_array const &Vertices(gfx::geometry_handle const &Geometry) const override;
gfx::userdata_array const &UserData(gfx::geometry_handle const &Geometry) const override;
// materials
material_handle Fetch_Material(std::string const &Filename, bool const Loadnow = true) override;
void Bind_Material(material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr) override;
IMaterial const *Material(material_handle const Material) const override;
// shaders
std::shared_ptr<gl::program> Fetch_Shader(std::string const &name) override;
// textures
texture_handle Fetch_Texture(std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA) override;
void Bind_Texture(texture_handle const Texture) override;
void Bind_Texture(std::size_t const Unit, texture_handle const Texture) override;
ITexture &Texture(texture_handle const Texture) override;
ITexture const &Texture(texture_handle const Texture) const override;
// picking
void Pick_Control_Callback(std::function<void(TSubModel const *, glm::vec2 const)> Callback) override;
void Pick_Node_Callback(std::function<void(scene::basic_node *)> Callback) override;
TSubModel const *Pick_Control() const override;
scene::basic_node const *Pick_Node() const override;
glm::dvec3 Mouse_Position() const override;
// maintenance
void Update(double const Deltatime) override;
void Update_Pick_Control() override;
void Update_Pick_Node() override;
glm::dvec3 Update_Mouse_Position() override;
bool Debug_Ui_State(std::optional<bool> State) override;
// debug
std::string const &info_times() const override;
std::string const &info_stats() const override;
// ui
imgui_renderer *GetImguiRenderer() override;
void MakeScreenshot() override;
static std::unique_ptr<gfx_renderer> create_func();
// how the application makes the windows of the renderer, and whether it can run on the machine
static gfx_backend_desc backend_desc();
private:
// the objects of the vulkan api (vulkan14renderer.cpp)
struct context;
// imgui without drawing: the frames of the ui go on (its input works), nothing is drawn until the stage R5 of the plan
class imgui_stub : public imgui_renderer
{
public:
bool Init() override;
void Shutdown() override;
void BeginFrame() override;
void Render() override;
};
void update_info();
std::unique_ptr<context> m_context;
// the data of the geometry, which the simulation reads back; nothing on the gpu yet
gfx::geometrybank_manager m_geometry;
imgui_stub m_imgui;
float m_framerate{0.f};
double m_updateaccumulator{0.0};
bool m_debug_ui_active{false};
std::string m_infotimes;
std::string m_infostats;
static bool renderer_register;
};

View File

@@ -0,0 +1,455 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Globals.h"
namespace vk14
{
namespace
{
char const *present_mode_string(VkPresentModeKHR const Mode)
{
switch (Mode)
{
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return "immediate";
case VK_PRESENT_MODE_MAILBOX_KHR:
return "mailbox";
case VK_PRESENT_MODE_FIFO_KHR:
return "fifo";
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return "fifo relaxed";
default:
break;
}
return "other";
}
bool is_srgb(VkFormat const Format)
{
switch (Format)
{
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
return true;
default:
break;
}
return false;
}
// the settings the swapchain follows: vsync, and whether the shaders write gamma themselves (as GLFW_SRGB_CAPABLE in opengl)
bool wanted_vsync()
{
return Global.VSync;
}
bool wanted_srgb()
{
return (false == Global.gfx_shadergamma);
}
} // namespace
swapchain::~swapchain()
{
destroy();
}
bool swapchain::create(instance const &Instance, device const &Device, GLFWwindow *Window, std::string const &Name, std::uint32_t const Frames, std::string &Reason)
{
m_instance = &Instance;
m_device = &Device;
m_window = Window;
m_name = Name;
if (!VK14_VERIFY(glfwCreateWindowSurface(Instance.handle(), Window, nullptr, &m_surface)))
{
m_surface = VK_NULL_HANDLE;
Reason = "the window of " + Name + " gives no vulkan surface";
return false;
}
Instance.set_object_name(Device.handle(), VK_OBJECT_TYPE_SURFACE_KHR, reinterpret_cast<std::uint64_t>(m_surface), Name + " surface");
VkBool32 present{VK_FALSE};
if (!VK14_VERIFY(vkGetPhysicalDeviceSurfaceSupportKHR(Device.physical(), Device.families().graphics, m_surface, &present)) || (present == VK_FALSE))
{
Reason = "the gpu can't show images in the window of " + Name;
return false;
}
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
m_acquiresemaphores.assign(Frames, VK_NULL_HANDLE);
for (std::uint32_t i = 0; i < Frames; ++i)
{
if (!VK14_VERIFY(vkCreateSemaphore(Device.handle(), &semaphoreinfo, nullptr, &m_acquiresemaphores[i])))
{
Reason = "no semaphores for " + Name;
return false;
}
Instance.set_object_name(Device.handle(), VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(m_acquiresemaphores[i]), Name + " acquire " + std::to_string(i));
}
return build(Reason);
}
bool swapchain::outdated() const
{
if (m_rebuild || (m_swapchain == VK_NULL_HANDLE) || (m_vsync != wanted_vsync()) || (m_srgb != wanted_srgb()))
{
return true;
}
int width{0}, height{0};
glfwGetFramebufferSize(m_window, &width, &height);
return (width != m_framebuffer.x) || (height != m_framebuffer.y);
}
bool swapchain::build(std::string &Reason)
{
auto const physical{m_device->physical()};
auto const device{m_device->handle()};
int width{0}, height{0};
glfwGetFramebufferSize(m_window, &width, &height);
if ((width <= 0) || (height <= 0))
{
// minimized: no swapchain of 0x0, the window is skipped until it comes back
m_rebuild = true;
return true;
}
VkSurfaceCapabilitiesKHR capabilities{};
auto const result{vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical, m_surface, &capabilities)};
if (result != VK_SUCCESS)
{
Reason = std::string("the surface of ") + m_name + " can't be queried (" + result_string(result) + ")";
return false;
}
VkExtent2D extent{static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height)};
if (capabilities.currentExtent.width != 0xFFFFFFFFu)
{
extent = capabilities.currentExtent;
}
extent.width = std::clamp(extent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
extent.height = std::clamp(extent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
if ((extent.width == 0) || (extent.height == 0))
{
m_rebuild = true;
return true;
}
// the format: srgb when the shaders write linear colours (gfx.shadergamma no), as GLFW_SRGB_CAPABLE in opengl
auto const srgb{wanted_srgb()};
std::uint32_t formatcount{0};
vkGetPhysicalDeviceSurfaceFormatsKHR(physical, m_surface, &formatcount, nullptr);
std::vector<VkSurfaceFormatKHR> formats(formatcount);
vkGetPhysicalDeviceSurfaceFormatsKHR(physical, m_surface, &formatcount, formats.data());
if (formats.empty())
{
Reason = "the surface of " + m_name + " has no formats";
return false;
}
std::vector<VkFormat> const wanted{srgb ? std::vector<VkFormat>{VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_A8B8G8R8_SRGB_PACK32} : std::vector<VkFormat>{VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_A8B8G8R8_UNORM_PACK32}};
VkSurfaceFormatKHR chosen{formats.front()};
if ((formats.size() == 1) && (formats.front().format == VK_FORMAT_UNDEFINED))
{
// any format will do
chosen = {wanted.front(), VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}
else
{
auto found{false};
for (auto const format : wanted)
{
auto const lookup{std::find_if(formats.begin(), formats.end(), [format](VkSurfaceFormatKHR const &Entry) { return (Entry.format == format) && (Entry.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR); })};
if (lookup != formats.end())
{
chosen = *lookup;
found = true;
break;
}
}
if (false == found)
{
log_warning(m_name + ": no " + (srgb ? "srgb" : "unorm") + " 8 bit format of the surface, using format " + std::to_string(chosen.format));
}
}
// the presentation: vsync waits for the screen (fifo, always there), without vsync mailbox, or else immediate
auto const vsync{wanted_vsync()};
std::uint32_t modecount{0};
vkGetPhysicalDeviceSurfacePresentModesKHR(physical, m_surface, &modecount, nullptr);
std::vector<VkPresentModeKHR> modes(modecount);
vkGetPhysicalDeviceSurfacePresentModesKHR(physical, m_surface, &modecount, modes.data());
auto presentmode{VK_PRESENT_MODE_FIFO_KHR};
if (false == vsync)
{
for (auto const mode : {VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR})
{
if (std::find(modes.begin(), modes.end(), mode) != modes.end())
{
presentmode = mode;
break;
}
}
}
auto imagecount{capabilities.minImageCount + 1};
if (capabilities.maxImageCount > 0)
{
imagecount = std::min(imagecount, capabilities.maxImageCount);
}
auto compositealpha{VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR};
if ((capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) == 0)
{
for (auto const alpha : {VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR})
{
if ((capabilities.supportedCompositeAlpha & alpha) != 0)
{
compositealpha = alpha;
break;
}
}
}
// the images still in use by the frames in flight and the presentation have to be done with before they go
auto const oldswapchain{m_swapchain};
if (oldswapchain != VK_NULL_HANDLE)
{
vkQueueWaitIdle(m_device->graphics_queue());
release_images();
}
VkSwapchainCreateInfoKHR create{};
create.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
create.surface = m_surface;
create.minImageCount = imagecount;
create.imageFormat = chosen.format;
create.imageColorSpace = chosen.colorSpace;
create.imageExtent = extent;
create.imageArrayLayers = 1;
// drawn into as a colour attachment; the frame is copied into it at the end (flipped, see K8 of the plan)
create.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
create.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
create.preTransform = capabilities.currentTransform;
create.compositeAlpha = compositealpha;
create.presentMode = presentmode;
create.clipped = VK_TRUE;
create.oldSwapchain = oldswapchain;
VkSwapchainKHR swapchain{VK_NULL_HANDLE};
auto const created{vkCreateSwapchainKHR(device, &create, nullptr, &swapchain)};
if (oldswapchain != VK_NULL_HANDLE)
{
vkDestroySwapchainKHR(device, oldswapchain, nullptr);
}
m_swapchain = VK_NULL_HANDLE;
if (created != VK_SUCCESS)
{
Reason = "the swapchain of " + m_name + " can't be made (" + result_string(created) + ")";
return false;
}
m_swapchain = swapchain;
m_format = chosen.format;
m_colorspace = chosen.colorSpace;
m_presentmode = presentmode;
m_extent = extent;
m_framebuffer = {width, height};
m_vsync = vsync;
m_srgb = srgb;
m_rebuild = false;
m_instance->set_object_name(device, VK_OBJECT_TYPE_SWAPCHAIN_KHR, reinterpret_cast<std::uint64_t>(m_swapchain), m_name + " swapchain");
std::uint32_t count{0};
vkGetSwapchainImagesKHR(device, m_swapchain, &count, nullptr);
m_images.resize(count);
vkGetSwapchainImagesKHR(device, m_swapchain, &count, m_images.data());
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
m_views.assign(count, VK_NULL_HANDLE);
m_presentsemaphores.assign(count, VK_NULL_HANDLE);
for (std::uint32_t i = 0; i < count; ++i)
{
VkImageViewCreateInfo view{};
view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view.image = m_images[i];
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
view.format = m_format;
view.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
if (!VK14_VERIFY(vkCreateImageView(device, &view, nullptr, &m_views[i])) || !VK14_VERIFY(vkCreateSemaphore(device, &semaphoreinfo, nullptr, &m_presentsemaphores[i])))
{
Reason = "the images of the swapchain of " + m_name + " can't be used";
return false;
}
m_instance->set_object_name(device, VK_OBJECT_TYPE_IMAGE, reinterpret_cast<std::uint64_t>(m_images[i]), m_name + " image " + std::to_string(i));
m_instance->set_object_name(device, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(m_presentsemaphores[i]), m_name + " present " + std::to_string(i));
}
log_info("swapchain of " + m_name + ": " + std::to_string(m_extent.width) + "x" + std::to_string(m_extent.height) + ", " + std::to_string(count) + " images, " + (is_srgb(m_format) ? "srgb" : "unorm") + " format " + std::to_string(m_format) + ", present mode " +
present_mode_string(m_presentmode) + (m_vsync ? " (vsync)" : ""));
return true;
}
swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame)
{
if (outdated())
{
std::string reason;
if (false == build(reason))
{
log_error(reason);
return acquire_result::failed;
}
}
if ((m_swapchain == VK_NULL_HANDLE) || m_rebuild)
{
return acquire_result::skipped;
}
auto const result{vkAcquireNextImageKHR(m_device->handle(), m_swapchain, UINT64_MAX, m_acquiresemaphores[Frame], VK_NULL_HANDLE, &m_image)};
switch (result)
{
case VK_SUCCESS:
return acquire_result::ready;
case VK_SUBOPTIMAL_KHR:
// the image is acquired and its semaphore will be signalled, so it's drawn and shown; the swapchain is made anew after
m_rebuild = true;
return acquire_result::ready;
case VK_ERROR_OUT_OF_DATE_KHR:
m_rebuild = true;
return acquire_result::skipped;
default:
break;
}
log_error(std::string("vkAcquireNextImageKHR of ") + m_name + " failed with " + result_string(result));
return acquire_result::failed;
}
void swapchain::presented(VkResult const Result)
{
if ((Result == VK_SUBOPTIMAL_KHR) || (Result == VK_ERROR_OUT_OF_DATE_KHR))
{
m_rebuild = true;
}
}
void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const
{
VkImageMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
// the acquire semaphore is waited for at the colour output, so the change of layout waits for it too
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.srcAccessMask = VK_ACCESS_2_NONE;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = m_images[m_image];
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(Commands, &dependency);
VkRenderingAttachmentInfo colour{};
colour.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
colour.imageView = m_views[m_image];
colour.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colour.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colour.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colour.clearValue.color = {{Colour.r, Colour.g, Colour.b, Colour.a}};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, m_extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &colour;
vkCmdBeginRendering(Commands, &rendering);
vkCmdEndRendering(Commands);
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_NONE;
barrier.dstAccessMask = VK_ACCESS_2_NONE;
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
vkCmdPipelineBarrier2(Commands, &dependency);
}
void swapchain::release_images()
{
auto const device{m_device != nullptr ? m_device->handle() : VK_NULL_HANDLE};
if (device == VK_NULL_HANDLE)
{
return;
}
for (auto view : m_views)
{
if (view != VK_NULL_HANDLE)
{
vkDestroyImageView(device, view, nullptr);
}
}
for (auto semaphore : m_presentsemaphores)
{
if (semaphore != VK_NULL_HANDLE)
{
vkDestroySemaphore(device, semaphore, nullptr);
}
}
m_views.clear();
m_presentsemaphores.clear();
m_images.clear();
}
void swapchain::destroy()
{
auto const device{m_device != nullptr ? m_device->handle() : VK_NULL_HANDLE};
if (device != VK_NULL_HANDLE)
{
release_images();
if (m_swapchain != VK_NULL_HANDLE)
{
vkDestroySwapchainKHR(device, m_swapchain, nullptr);
}
for (auto semaphore : m_acquiresemaphores)
{
if (semaphore != VK_NULL_HANDLE)
{
vkDestroySemaphore(device, semaphore, nullptr);
}
}
}
m_swapchain = VK_NULL_HANDLE;
m_acquiresemaphores.clear();
if ((m_surface != VK_NULL_HANDLE) && (m_instance != nullptr) && (m_instance->handle() != VK_NULL_HANDLE))
{
vkDestroySurfaceKHR(m_instance->handle(), m_surface, nullptr);
}
m_surface = VK_NULL_HANDLE;
m_window = nullptr;
}
} // namespace vk14

View File

@@ -0,0 +1,122 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// a window the renderer shows images in: its surface and swapchain, made anew when the window changes size, when the driver
// says the swapchain is out of date, and when vsync or gfx.shadergamma change. a minimized window (0x0) is skipped until
// it comes back
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
namespace vk14
{
class instance;
class device;
class swapchain
{
public:
enum class acquire_result
{
ready, // an image to draw into, its acquire semaphore will be signalled
skipped, // nothing to draw this frame (minimized, or made anew)
failed // the device is gone
};
swapchain() = default;
swapchain(swapchain const &) = delete;
swapchain &operator=(swapchain const &) = delete;
~swapchain();
// the surface of the window and, when the window isn't minimized, the swapchain. Frames: frames in flight
bool create(instance const &Instance, device const &Device, GLFWwindow *Window, std::string const &Name, std::uint32_t const Frames, std::string &Reason);
void destroy();
// the image of the frame Frame (index of the frame in flight)
acquire_result acquire(std::uint32_t const Frame);
// the image acquired, from undefined to cleared with Colour (linear) and ready to be shown
void record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const;
// the semaphore signalled when the image of the frame Frame is acquired
VkSemaphore acquire_semaphore(std::uint32_t const Frame) const
{
return m_acquiresemaphores[Frame];
}
// the semaphore the presentation of the acquired image waits for
VkSemaphore present_semaphore() const
{
return m_presentsemaphores[m_image];
}
VkSwapchainKHR handle() const
{
return m_swapchain;
}
std::uint32_t image_index() const
{
return m_image;
}
// the result of the presentation of the image: out of date or suboptimal makes the swapchain anew before the next image
void presented(VkResult const Result);
GLFWwindow *window() const
{
return m_window;
}
VkExtent2D extent() const
{
return m_extent;
}
VkFormat format() const
{
return m_format;
}
VkPresentModeKHR present_mode() const
{
return m_presentmode;
}
bool srgb() const
{
return m_srgb;
}
private:
// false only when the device is gone or the surface is lost; a minimized window gives true with no swapchain
bool build(std::string &Reason);
void release_images();
// whether the swapchain has to be made anew before the next image
bool outdated() const;
instance const *m_instance{nullptr};
device const *m_device{nullptr};
GLFWwindow *m_window{nullptr};
std::string m_name;
VkSurfaceKHR m_surface{VK_NULL_HANDLE};
VkSwapchainKHR m_swapchain{VK_NULL_HANDLE};
VkFormat m_format{VK_FORMAT_UNDEFINED};
VkColorSpaceKHR m_colorspace{VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
VkPresentModeKHR m_presentmode{VK_PRESENT_MODE_FIFO_KHR};
VkExtent2D m_extent{0, 0};
// the size of the window the swapchain was made for; the swapchain itself may be clamped to the limits of the surface
glm::ivec2 m_framebuffer{0, 0};
// the settings the swapchain was made for
bool m_vsync{false};
bool m_srgb{false};
bool m_rebuild{false};
std::vector<VkImage> m_images;
std::vector<VkImageView> m_views;
// one per image: a semaphore given to the presentation can be reused only once the image comes back
std::vector<VkSemaphore> m_presentsemaphores;
// one per frame in flight: free again once the frame which waited for it is done
std::vector<VkSemaphore> m_acquiresemaphores;
std::uint32_t m_image{0};
};
} // namespace vk14

View File

@@ -46,6 +46,12 @@ python_screen_viewer::python_screen_viewer(std::shared_ptr<python_rt> rt, std::s
conf->window = Application.window(-1, true, conf->window_size.x, conf->window_size.y,
monitor, false, Global.python_sharectx);
if (conf->window == nullptr)
{
// e.g. the renderer has no opengl context to share
ErrorLog("python screen \"" + surfacename + "\": window couldn't be made");
continue;
}
{
int w;

View File

@@ -317,10 +317,13 @@ bool global_settings::ConfigParseGraphics(cParser& Parser, const std::string& to
{
ParseOne(Parser, GfxRenderer);
if (GfxRenderer == "full")
// "default": the opengl 4.6 renderer, "vulkan": the vulkan 1.4 one (opengl when it can't run on the machine)
if ((GfxRenderer == "full") || (GfxRenderer == "opengl") || (GfxRenderer == "gl46"))
GfxRenderer = "default";
else if (GfxRenderer == "vulkan14")
GfxRenderer = "vulkan";
if (GfxRenderer != "default")
if ((GfxRenderer != "default") && (GfxRenderer != "vulkan"))
{
// the legacy OpenGL 2.1 renderer ("legacy", "simple" and other values) and the NVRHI renderer ("experimental") were removed
WriteLog("eu07.ini: gfxrenderer \"" + GfxRenderer + "\" is no longer supported, using \"default\"");
@@ -1741,6 +1744,26 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
Parser >> gfx_shadow_sdsm_split;
gfx_shadow_sdsm_split = std::clamp(gfx_shadow_sdsm_split, 0.f, 1.f);
}
else if (Token == "gfx.vulkan.device")
{
// index of the gpu in the list of the log, or a part of its name
Parser.getTokens(1, false);
Parser >> gfx_vulkan_device;
}
else if (Token == "gfx.vulkan.validation")
{
// no/yes, 2: also gpu-assisted
std::string value;
Parser.getTokens(1);
Parser >> value;
gfx_vulkan_validation = (value == "2" ? 2 : (value == "yes" || value == "true" || value == "1") ? 1 : 0);
}
else if (Token == "gfx.vulkan.framesinflight")
{
Parser.getTokens(1);
Parser >> gfx_vulkan_framesinflight;
gfx_vulkan_framesinflight = std::clamp(gfx_vulkan_framesinflight, 2, 3);
}
else if (ConfigParse_gfx_sky(Parser, Token))
{
// parsed
@@ -2158,6 +2181,9 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.skippipeline", gfx_skippipeline );
export_as_text( Output, "gfx.extraeffects", gfx_extraeffects );
export_as_text( Output, "gfx.shadergamma", gfx_shadergamma );
export_as_text( Output, "gfx.vulkan.device", gfx_vulkan_device );
export_as_text( Output, "gfx.vulkan.validation", gfx_vulkan_validation );
export_as_text( Output, "gfx.vulkan.framesinflight", gfx_vulkan_framesinflight );
export_as_text( Output, "gfx.shadow.angle.min", gfx_shadow_angle_min );
export_as_text( Output, "gfx.shadow.rank.cutoff", gfx_shadow_rank_cutoff );
export_as_text( Output, "gfx.shadow.sdsm", gfx_shadow_sdsm );

View File

@@ -367,6 +367,12 @@ struct global_settings {
bool gfx_extraeffects = true;
bool gfx_shadergamma = false;
bool gfx_gldebug = false;
// (vulkan) the gpu to run on: its index in the list of the log or a part of its name; empty: the best one
std::string gfx_vulkan_device;
// (vulkan) 0: no validation layer, 1: validation layer, 2: also gpu-assisted validation
int gfx_vulkan_validation { 0 };
// (vulkan) frames recorded while the gpu works on the earlier ones, 2 or 3
int gfx_vulkan_framesinflight { 2 };
bool vr = false;
std::string vr_backend;