Vulkan renderer

This commit is contained in:
legatt 2026-04-01 15:51:37 +03:00
parent a127e95741
commit 55258615c5
29 changed files with 2284 additions and 55 deletions

12
.idea/editor.xml generated
View File

@ -245,17 +245,29 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppCodeStyle/UseAutoCommonCase/@EntryValue" value="Never" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue" value="true" type="bool" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="NEXT_LINE" type="string" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="NEXT_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/EMPTY_BLOCK_STYLE/@EntryValue" value="TOGETHER_SAME_LINE" type="string" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/EMPTY_BLOCK_STYLE/@EntryValue" value="TOGETHER_SAME_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/REQUIRES_EXPRESSION_BRACES/@EntryValue" value="END_OF_LINE" type="string" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/REQUIRES_EXPRESSION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" /> <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_DECLARATION_LPAR/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_INVOCATION_LPAR/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_ARGUMENTS_STYLE/@EntryValue" value="CHOP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_CTOR_INITIALIZER_STYLE/@EntryValue" value="CHOP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_PARAMETERS_STYLE/@EntryValue" value="CHOP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/CppPreferForwardDeclaration/PreferForwardDeclarations/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;7&quot; Title=&quot;Local variables&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;local variable&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0AFB7787612DF743B09AD9412E48D4CC/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;7&quot; Title=&quot;Local variables&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;local variable&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;1&quot; Title=&quot;Classes and structs&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;__interface&quot; /&gt;&lt;type Name=&quot;class&quot; /&gt;&lt;type Name=&quot;struct&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=0B82708A1BA7774EB13D27F245698A56/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;1&quot; Title=&quot;Classes and structs&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;__interface&quot; /&gt;&lt;type Name=&quot;class&quot; /&gt;&lt;type Name=&quot;struct&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;AaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />
<option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=1350D079A82E0740947E85445B5AF47C/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;6&quot; Title=&quot;Parameters&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;function parameter&quot; /&gt;&lt;type Name=&quot;lambda parameter&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" /> <option name="/Default/CodeStyle/Naming/CppNamingOptions/Rules/=1350D079A82E0740947E85445B5AF47C/@EntryIndexedValue" value="&lt;NamingElement Priority=&quot;6&quot; Title=&quot;Parameters&quot;&gt;&lt;Descriptor Static=&quot;Indeterminate&quot; Constexpr=&quot;Indeterminate&quot; Const=&quot;Indeterminate&quot; Volatile=&quot;Indeterminate&quot; Accessibility=&quot;NOT_APPLICABLE&quot;&gt;&lt;type Name=&quot;function parameter&quot; /&gt;&lt;type Name=&quot;lambda parameter&quot; /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=&quot;True&quot; WarnAboutPrefixesAndSuffixes=&quot;False&quot; Prefix=&quot;&quot; Suffix=&quot;&quot; Style=&quot;aaBb&quot; /&gt;&lt;/NamingElement&gt;" type="string" />

View File

@ -19,9 +19,14 @@ project (EtrediaEmpires LANGUAGES CXX)
add_subdirectory(sdk/map) add_subdirectory(sdk/map)
add_subdirectory(sdk/assets) add_subdirectory(sdk/assets)
add_subdirectory(p7lib) add_subdirectory(p7lib)
add_subdirectory(utils) add_subdirectory(utils)
add_subdirectory(server/lib) add_subdirectory(server/lib)
add_subdirectory(engine-api) add_subdirectory(engine-api)
add_subdirectory(renderers/magnum-renderer) add_subdirectory(renderers/magnum-renderer)
add_subdirectory(renderers/vulkan)
add_subdirectory(engine) add_subdirectory(engine)

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,12 @@
function(add_library_target TARGET_NAME) function(add_library_target TARGET_NAME)
cmake_parse_arguments(
ADDITIONAL
""
""
"MODULES;MODULE_BASEDIRS"
""
${ARGN}
)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -22,23 +30,14 @@ function(add_library_target TARGET_NAME)
modules/*.ixx modules/*.ixx
) )
target_sources(${TARGET_NAME} PUBLIC FILE_SET modules TYPE CXX_MODULES BASE_DIRS ${PROJECT_SOURCE_DIR} FILES ${MODULES}) list(APPEND MODULES ${ADDITIONAL_MODULES})
set (MODULE_BASEDIRS ${PROJECT_SOURCE_DIR})
list(APPEND MODULE_BASEDIRS ${ADDITIONAL_MODULE_BASEDIRS})
target_sources(${TARGET_NAME} PUBLIC FILE_SET modules TYPE CXX_MODULES BASE_DIRS ${MODULE_BASEDIRS} FILES ${MODULES})
target_sources(${TARGET_NAME} PUBLIC FILE_SET headers TYPE HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES ${HEADERS}) target_sources(${TARGET_NAME} PUBLIC FILE_SET headers TYPE HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES ${HEADERS})
target_sources(${TARGET_NAME} PRIVATE ${SOURCES}) target_sources(${TARGET_NAME} PRIVATE ${SOURCES})
# MSVC STD module linking
# target_compile_options(${TARGET_NAME} PRIVATE
# /EHsc
# "SHELL:/reference \"std=$ENV{STD_MODULE_DIR}/std.ifc\""
# "SHELL:/reference \"std.compat=$ENV{STD_MODULE_DIR}/std.compat.ifc\""
#)
#target_link_libraries(${TARGET_NAME} PRIVATE "$ENV{STD_MODULE_DIR}/std.obj")
# target_link_libraries(${TARGET_NAME} PRIVATE "$ENV{STD_MODULE_DIR}/std.compat.obj")
#target_compile_options(${TARGET_NAME} PRIVATE
# /wd5050
#)
#target_compile_features(${TARGET_NAME} PRIVATE cxx_std_23 INTERFACE cxx_std_23)
# Clang modules # Clang modules
# target_compile_options(${TARGET_NAME} PRIVATE # target_compile_options(${TARGET_NAME} PRIVATE
# -fmodules # -fmodules

View File

@ -62,8 +62,8 @@ export namespace ee_engine {
void SetCamera(const CameraTransform& camera); void SetCamera(const CameraTransform& camera);
constexpr CameraTransform getCamera() const noexcept { return camera; }; constexpr CameraTransform getCamera() const noexcept { return camera; };
glm::vec2 ScreenToWorld(const float screenx, const float screenY); glm::vec2 ScreenToWorld(float screenX, float screenY) const;
glm::vec2 ScreenToWorld(const glm::vec2& screenPos); glm::vec2 ScreenToWorld(const glm::vec2& screenPos) const;
protected: protected:
bool _isReady; bool _isReady;
@ -77,12 +77,12 @@ export namespace ee_engine {
MapMesh *mapMesh; MapMesh *mapMesh;
ShaderRefs shaders; ShaderRefs shaders;
struct RegionIndecies { struct RegionRanges {
std::size_t startIndex; std::size_t offset;
std::size_t provincesCount; std::size_t provincesCount;
}; };
std::unordered_map<std::uint32_t /*id*/, RegionIndecies> regionsMapping; std::unordered_map<std::uint32_t /*id*/, RegionRanges> regionsMapping;
explicit IRenderer(GLFWwindow *window); explicit IRenderer(GLFWwindow *window);
@ -92,7 +92,7 @@ export namespace ee_engine {
virtual void ApplyCamera() = 0; virtual void ApplyCamera() = 0;
virtual void UpdateProvinces(const std::size_t index, const std::vector<ee_engine::ProvinceSSBO>& data) = 0; virtual void UpdateProvinces(std::size_t offset, const std::vector<ProvinceSSBO>& data) = 0;
private: private:
void UpdateRegionCmd(RendererCommand& command); void UpdateRegionCmd(RendererCommand& command);

View File

@ -31,7 +31,7 @@ void ee_engine::IRenderer::Draw() {
DrawUI(); DrawUI();
} }
void ee_engine::IRenderer::ResizeWindow(int width, int height) { void ee_engine::IRenderer::ResizeWindow(const int width, const int height) {
screenWidth = width; screenWidth = width;
screenHeight = height; screenHeight = height;
screenAspect = static_cast<float>(screenWidth) / static_cast<float>(screenHeight); screenAspect = static_cast<float>(screenWidth) / static_cast<float>(screenHeight);
@ -45,15 +45,15 @@ void ee_engine::IRenderer::SetCamera(const CameraTransform& camera) {
this->camera = camera; this->camera = camera;
float halfHeight = camera.zoom; const float halfHeight = camera.zoom;
float halfWidth = camera.zoom * screenAspect; const float halfWidth = camera.zoom * screenAspect;
glm::mat4 P = glm::ortho( const glm::mat4 P = glm::ortho(
-halfWidth, +halfWidth, -halfWidth, +halfWidth,
-halfHeight, +halfHeight -halfHeight, +halfHeight
); );
glm::mat4 V = glm::translate( const glm::mat4 V = glm::translate(
glm::mat4(1.0f), glm::mat4(1.0f),
glm::vec3(-camera.position, 0.0f) glm::vec3(-camera.position, 0.0f)
); );
@ -64,11 +64,11 @@ void ee_engine::IRenderer::SetCamera(const CameraTransform& camera) {
ApplyCamera(); ApplyCamera();
} }
glm::vec2 ee_engine::IRenderer::ScreenToWorld(const float screenX, const float screenY) { glm::vec2 ee_engine::IRenderer::ScreenToWorld(const float screenX, const float screenY) const {
return ScreenToWorld(glm::vec2(screenX, screenY)); return ScreenToWorld(glm::vec2(screenX, screenY));
} }
glm::vec2 ee_engine::IRenderer::ScreenToWorld(const glm::vec2& screenPos) { glm::vec2 ee_engine::IRenderer::ScreenToWorld(const glm::vec2& screenPos) const {
float x = 2.0f * screenPos.x / screenWidth - 1.0f; float x = 2.0f * screenPos.x / screenWidth - 1.0f;
float y = -2.0f * screenPos.y / screenHeight + 1.0f; float y = -2.0f * screenPos.y / screenHeight + 1.0f;
@ -79,7 +79,7 @@ glm::vec2 ee_engine::IRenderer::ScreenToWorld(const glm::vec2& screenPos) {
return glm::vec2(world) / world.w; return glm::vec2(world) / world.w;
} }
void ee_engine::IRenderer::UpdateRegionCmd(ee_engine::RendererCommand& command) { void ee_engine::IRenderer::UpdateRegionCmd(RendererCommand& command) {
if (!regionsMapping.contains(command.region.id)) { if (!regionsMapping.contains(command.region.id)) {
LOG(WARNING, "Updating unregistered region with id: %d", command.region.id); LOG(WARNING, "Updating unregistered region with id: %d", command.region.id);
return; return;
@ -90,14 +90,14 @@ void ee_engine::IRenderer::UpdateRegionCmd(ee_engine::RendererCommand& command)
command.region.id, command.region.data.regionId); command.region.id, command.region.data.regionId);
} }
RegionIndecies mapping = regionsMapping[command.region.id]; RegionRanges mapping = regionsMapping[command.region.id];
std::vector<ProvinceSSBO> buffer; std::vector<ProvinceSSBO> buffer;
buffer.reserve(mapping.provincesCount); buffer.reserve(mapping.provincesCount);
for (std::size_t i = 0; i < mapping.provincesCount; i++) { for (std::size_t i = 0; i < mapping.provincesCount; i++) {
buffer.push_back(command.region.data); buffer.push_back(command.region.data);
} }
UpdateProvinces(mapping.startIndex, buffer); UpdateProvinces(mapping.offset, buffer);
command.type = ee_engine::RendererCommand::COMPLETED; command.type = RendererCommand::COMPLETED;
} }

View File

@ -16,6 +16,7 @@ add_executable_target(${EXE})
target_link_libraries(${EXE} PUBLIC utils) target_link_libraries(${EXE} PUBLIC utils)
target_link_libraries(${EXE} PUBLIC engine-api) target_link_libraries(${EXE} PUBLIC engine-api)
target_link_libraries(${EXE} PUBLIC magnum-renderer) target_link_libraries(${EXE} PUBLIC magnum-renderer)
target_link_libraries(${EXE} PUBLIC vulkan-renderer)
target_link_libraries(${EXE} PRIVATE Boost::lockfree) target_link_libraries(${EXE} PRIVATE Boost::lockfree)
target_link_libraries(${EXE} PRIVATE xxHash::xxhash) target_link_libraries(${EXE} PRIVATE xxHash::xxhash)

View File

@ -10,17 +10,17 @@ ee_engine::InputHandlers Actions::mappings{
.mapScroll = MapScroll .mapScroll = MapScroll
}; };
void Actions::Zoom(float dx, float dy) { void Actions::Zoom(const float dx, const float dy) {
ee_engine::CameraTransform cam = Engine::renderer->getCamera(); ee_engine::CameraTransform cam = Engine::renderer->getCamera();
cam.zoom *= pow(1.1f, -dy); cam.zoom *= pow(1.1f, -dy);
Engine::renderer->SetCamera(cam); Engine::renderer->SetCamera(cam);
} }
void Actions::MapScroll(float x, float y, float lastX, float lastY) { void Actions::MapScroll(const float x, const float y, const float lastX, const float lastY) {
ee_engine::CameraTransform cam = Engine::renderer->getCamera(); ee_engine::CameraTransform cam = Engine::renderer->getCamera();
glm::vec2 pos = Engine::renderer->ScreenToWorld(x, y); const glm::vec2 pos = Engine::renderer->ScreenToWorld(x, y);
glm::vec2 lastPos = Engine::renderer->ScreenToWorld(lastX, lastY); const glm::vec2 lastPos = Engine::renderer->ScreenToWorld(lastX, lastY);
cam.position -= pos - lastPos; cam.position -= pos - lastPos;
Engine::renderer->SetCamera(cam); Engine::renderer->SetCamera(cam);

View File

@ -5,7 +5,7 @@ import utils;
import Engine; import Engine;
import Actions; import Actions;
import MagnumRender; import VulkanRender;
import GLFWInput; import GLFWInput;
import std; import std;
@ -20,7 +20,7 @@ int main(int argc, char **argv) {
glfwInit(); glfwInit();
// Vulkan only // Vulkan only
// glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow *window = glfwCreateWindow(800, 600, "Etredia Empires", nullptr, nullptr); GLFWwindow *window = glfwCreateWindow(800, 600, "Etredia Empires", nullptr, nullptr);
@ -38,16 +38,17 @@ int main(int argc, char **argv) {
Engine::assetManager->LoadDir(ee_assets::MATERIAL, "assets/materials"); Engine::assetManager->LoadDir(ee_assets::MATERIAL, "assets/materials");
// Load assets // Load assets
Engine::assetManager->LoadAsync(ee_assets::MAP_MESH, "assets/map/ET.map"); Engine::assetManager->LoadAsync(ee_assets::MAP_MESH, "assets/map/regions.map");
Engine::assetManager->LoadAsync(ee_assets::SAVE, "assets/scenarios/empires.save"); Engine::assetManager->LoadAsync(ee_assets::SAVE, "assets/scenarios/empires.save");
// Setup input manager TODO: builder // Setup input manager TODO: builder
Engine::input = Allocate<GLFWInput>(Actions::mappings); Engine::input = Allocate<GLFWInput>(Actions::mappings);
// Setup renderer TODO: builder // Setup renderer TODO: builder
Engine::renderer = reinterpret_cast<ee_engine::IRenderer*>(Allocate<ee_magnum::MagnumRenderer>(window)); //Engine::renderer = reinterpret_cast<ee_engine::IRenderer*>(Allocate<ee_magnum::MagnumRenderer>(window));
Engine::renderer = reinterpret_cast<ee_engine::IRenderer*>(Allocate<ee_vulkan::VulkanRenderer>(window));
ee_assets::AssetMapMesh& regions = Engine::assetManager->FindOnce<ee_assets::AssetMapMesh>("assets/map/ET.map"); ee_assets::AssetMapMesh& regions = Engine::assetManager->FindOnce<ee_assets::AssetMapMesh>("assets/map/regions.map");
Engine::renderer->RegisterMapMesh(&regions.content); Engine::renderer->RegisterMapMesh(&regions.content);
//TODO: ShaderRefs builder from set of AssetMaterial //TODO: ShaderRefs builder from set of AssetMaterial
@ -64,19 +65,20 @@ int main(int argc, char **argv) {
Engine::renderer->Init(); Engine::renderer->Init();
Engine::renderer->SetCamera({ Engine::renderer->SetCamera({
//.position = {1000.0f, 1000.0f}, .position = {1000.0f, 1000.0f},
.zoom = 2000.0f .zoom = 2000.0f
//.zoom = 1.0f
}); });
glfwSetFramebufferSizeCallback(window, [](GLFWwindow *window, int width, int height) { glfwSetFramebufferSizeCallback(window, [](GLFWwindow*, const int width, const int height) {
Engine::renderer->ResizeWindow(width, height); Engine::renderer->ResizeWindow(width, height);
}); });
// Setup Client TODO: builder // Setup Client TODO: builder
Engine::client = Allocate<Client>(); Engine::client = Allocate<Client>();
// AssetHandle save = Engine::assetManager->Find("assets/scenarios/empires.save"); const AssetHandle save = Engine::assetManager->Find("assets/scenarios/empires.save");
// Engine::client->LoadSave(save); Engine::client->LoadSave(save);
// Engine::client->Init(); Engine::client->Init();
Engine::client->Start(); Engine::client->Start();
// Game loop // Game loop
@ -99,5 +101,4 @@ int main(int argc, char **argv) {
return 0; return 0;
} }
// TODO: Use CRTP where possible in classes with virtual/override
// TODO: Leaks detection // TODO: Leaks detection

View File

@ -18,8 +18,8 @@ using namespace Magnum;
export namespace ee_magnum { export namespace ee_magnum {
class MagnumRenderer final : public ee_engine::IRenderer { class MagnumRenderer final : public ee_engine::IRenderer {
public: public:
MagnumRenderer(GLFWwindow *window); explicit MagnumRenderer(GLFWwindow *window);
~MagnumRenderer(); ~MagnumRenderer() override;
void Init() override; void Init() override;
void Shutdown() override; void Shutdown() override;
@ -31,8 +31,7 @@ export namespace ee_magnum {
void ApplyCamera() override; void ApplyCamera() override;
virtual void void UpdateProvinces(std::size_t offset, const std::vector<ee_engine::ProvinceSSBO>& data) override;
UpdateProvinces(const std::size_t index, const std::vector<ee_engine::ProvinceSSBO>& data) override;
private: private:
Platform::GLContext context; Platform::GLContext context;
@ -44,7 +43,5 @@ export namespace ee_magnum {
void ValidateSupport(); void ValidateSupport();
void LoadShaders(); void LoadShaders();
void LoadProvinces(); void LoadProvinces();
void ApplyMapMVP();
}; };
} }

View File

@ -95,7 +95,7 @@ void ee_magnum::MagnumRenderer::LoadProvinces() {
provincesData.push_back(data); provincesData.push_back(data);
} }
regionsMapping[region] = {.startIndex = regionIndex, .provincesCount = provs.size()}; regionsMapping[region] = {.offset = regionIndex, .provincesCount = provs.size()};
regionIndex += provs.size() * sizeof(ee_engine::ProvinceSSBO); regionIndex += provs.size() * sizeof(ee_engine::ProvinceSSBO);
} }
@ -107,10 +107,10 @@ void ee_magnum::MagnumRenderer::LoadProvinces() {
provinceSSBO.bind(GL::Buffer::Target::ShaderStorage, ProvinceMaterial::PROVINCES_DATA_BINDING); provinceSSBO.bind(GL::Buffer::Target::ShaderStorage, ProvinceMaterial::PROVINCES_DATA_BINDING);
} }
void ee_magnum::MagnumRenderer::UpdateProvinces(const std::size_t index, void ee_magnum::MagnumRenderer::UpdateProvinces(const std::size_t offset,
const std::vector<ee_engine::ProvinceSSBO>& data) { const std::vector<ee_engine::ProvinceSSBO>& data) {
Containers::ArrayView provincesData(data.data(), data.size()); Containers::ArrayView provincesData(data.data(), data.size());
provinceSSBO.setSubData(index, provincesData); provinceSSBO.setSubData(offset, provincesData);
} }
void ee_magnum::MagnumRenderer::Shutdown() { void ee_magnum::MagnumRenderer::Shutdown() {

View File

@ -0,0 +1,29 @@
include(${CMAKE_SOURCE_DIR}/cmake/library.cmake)
set(LIB vulkan-renderer)
# Find dependencies
find_package(Vulkan REQUIRED)
find_package(glm CONFIG REQUIRED)
# Link library
add_library_target(${LIB}
MODULES ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include/vulkan/vulkan.cppm
MODULE_BASEDIRS ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include/
)
target_compile_definitions(${LIB}
PUBLIC
VULKAN_HPP_NO_CONSTRUCTORS
VULKAN_HPP_ENABLE_STD_MODULE
VULKAN_HPP_STD_MODULE=std
)
target_link_libraries(${LIB} PUBLIC engine-api)
target_link_libraries(${LIB} PUBLIC utils)
target_link_libraries(${LIB} PUBLIC p7)
target_link_libraries(${LIB} PRIVATE glm::glm)
target_link_libraries(${LIB} PRIVATE Vulkan::Vulkan)

View File

@ -0,0 +1,89 @@
module;
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
export module VulkanRender;
import Render;
import views;
import vulkan_render;
import std;
namespace ee_vulkan {
template <typename T> using lazy = std::optional<T>;
constexpr std::uint64_t TIMEOUT = std::numeric_limits<std::uint64_t>::max();
struct DrawConfig {
static constexpr vk::ClearValue CLEAR_COLOR = {.color = {std::array{1.0f, 1.0f, 1.0f, 1.0f}}};
static constexpr vk::ClearValue CLEAR_DEPTH = {.depthStencil = {1.0f, 0}};
};
struct Mesh {
lazy<vulkan_render::Buffer<
glm::vec2,
vk::BufferUsageFlagBits::eVertexBuffer,
vulkan_render::BufferMode::STAGING_ACCESS
>> vertices;
lazy<vulkan_render::Buffer<
std::uint32_t,
vk::BufferUsageFlagBits::eIndexBuffer,
vulkan_render::BufferMode::STAGING_ACCESS
>> indices;
lazy<vulkan_render::Buffer<
ee_engine::ProvinceSSBO,
vk::BufferUsageFlagBits::eStorageBuffer,
vulkan_render::BufferMode::STAGING_ACCESS
>> storage;
lazy<vulkan_render::Buffer<
vk::DrawIndexedIndirectCommand,
vk::BufferUsageFlagBits::eIndirectBuffer,
vulkan_render::BufferMode::STAGING_ACCESS
>> indirectCommands;
};
}
export namespace ee_vulkan {
class VulkanRenderer final : public ee_engine::IRenderer {
public:
explicit VulkanRenderer(GLFWwindow *window);
~VulkanRenderer() override;
void Init() override;
void Shutdown() override;
protected:
void Clear() override;
void DrawMap() override;
void DrawUI() override;
void ResizeWindow(int width, int height) override;
void ApplyCamera() override;
void UpdateProvinces(std::size_t offset, const std::vector<ee_engine::ProvinceSSBO>& data) override;
private:
const std::uint32_t MAX_FRAMES_IN_FLIGHT = 3;
bool isDrawing = false;
lazy<vulkan_render::Context> context;
vulkan_render::Surface surface;
lazy<vulkan_render::Swapchain> swapchain;
lazy<vulkan_render::RenderCommands> renderCommands;
lazy<vulkan_render::Pipeline> provincesPipeline;
lazy<vulkan_render::SyncManager> syncManager;
lazy<vulkan_render::Texture<vulkan_render::TextureMode::DEPTH>> depth;
lazy<vulkan_render::Uniform<glm::mat4>> viewProjectionUniform;
Mesh provinces;
std::size_t currentFrame = 0;
void RecreateSwapchain();
void LoadProvincesMaterial();
void LoadProvinces();
void UpdateData(std::uint32_t frame);
};
}

View File

@ -0,0 +1,80 @@
/**
* Control partition module is responsible for configuration and usage of Vulkan operation objects
* including command buffers, dynamic rendering, synchronization objects and so on.
*/
export module vulkan_render:control;
import :hardware;
import :resources;
import :data;
import std;
import vulkan_hpp;
export namespace vulkan_render {
class Command {
public:
const Context &context;
vk::raii::CommandBuffer handle;
Command(const Context &context, vk::raii::CommandBuffer&& buffer);
Command(const Context &context, const vk::raii::CommandPool &pool);
Command(Command&&) noexcept = default;
Command(const Command&) = delete;
Command& operator=(const Command&) = delete;
void Begin() const;
void Submit(const vk::raii::Queue &queue) const;
};
/**
* Set of Vulkan commands buffers for each frame in flight.
*/
class RenderCommands {
public:
vk::raii::CommandPool graphicsPool;
vk::raii::CommandPool transferPool;
std::vector<Command> graphicsBuffers;
std::vector<Command> transferBuffers;
/**
* Create pools and initialize all command buffers.
* @param context Vulkan context with ready device.
* @param maxFramesInFlight amount of created buffers for each purpose.
*/
RenderCommands(const Context &context, std::uint32_t maxFramesInFlight);
void SubmitStagingBuffer(const BaseBuffer<BufferMode::STAGING_ACCESS> &buffer) const;
private:
static vk::raii::CommandPool MakePool(const Context &context, std::uint32_t queueFamily);
static std::vector<Command> MakeBuffers(
const Context &context,
const vk::raii::CommandPool &pool,
std::uint32_t size
);
};
//TODO: sync2 with timeline-semaphore(s)
class SyncManager {
public:
const Context &context;
const std::uint32_t TIMEOUT;
std::vector<vk::raii::Semaphore> imageAvailable;
std::vector<vk::raii::Semaphore> renderFinished;
std::vector<vk::raii::Fence> inFlightFences;
std::vector<vk::Fence> imagesInFlight;
SyncManager(const Context &context, std::uint32_t maxFramesInFlight, std::uint64_t timeout);
void WaitInFlightFence(std::size_t currentFrame);
void WaitImageInFlight(std::size_t currentFrame, std::uint32_t imageIndex);
vk::Result AcquireNextImage(
const Swapchain &swapchain,
std::size_t currentFrame,
std::uint32_t &imageIndex
) const;
};
}

View File

@ -0,0 +1,276 @@
/**
* Data partition module is responsible for configuration user-specific Vulkan objects
* like buffers, textures, transforms and so on.
*/
module;
#if defined(_MSC_VER)
#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#else
#define NO_UNIQUE_ADDRESS [[no_unique_address]]
#endif
export module vulkan_render:data;
import :hardware;
import :resources;
import std;
import vulkan_hpp;
import utils;
namespace vulkan_render {
struct NoMember {
constexpr NoMember() = default;
constexpr NoMember(std::nullptr_t) {}
};
template <typename T, bool condition>
using optional = std::conditional_t<condition, T, NoMember>;
template <bool hasStaging>
using StagingBuffer = optional<vk::raii::Buffer, hasStaging>;
template <bool hasStaging>
using StagingDeviceMemory = optional<vk::raii::DeviceMemory, hasStaging>;
template <typename T, bool isMapped>
using MappedMemory = optional<std::span<T>, isMapped>;
}
export namespace vulkan_render {
enum class BufferMode {
STATIC,
CPU_ACCESS,
STAGING_ACCESS
};
template <BufferMode mode>
class BaseBuffer {
public:
const Context &context;
vk::raii::Buffer handle = nullptr;
vk::raii::DeviceMemory memory = nullptr;
NO_UNIQUE_ADDRESS StagingBuffer<mode == BufferMode::STAGING_ACCESS> stagingHandle = nullptr;
NO_UNIQUE_ADDRESS StagingDeviceMemory<mode == BufferMode::STAGING_ACCESS> stagingMemory = nullptr;
std::size_t memorySize;
explicit BaseBuffer(const Context &context, const std::size_t memorySize) :
context(context),
memorySize(memorySize) {}
};
template <typename T, vk::BufferUsageFlagBits usage, BufferMode mode>
class Buffer : public BaseBuffer<mode> {
public:
NO_UNIQUE_ADDRESS MappedMemory<T, mode != BufferMode::STATIC> content;
static constexpr std::size_t elementSize = sizeof(T);
Buffer(const Context &context, const std::span<T> data) requires (mode == BufferMode::STATIC)
: BaseBuffer<mode>(context, data.size() * elementSize) {
MakeInstance(
usage,
vk::Flags(vk::MemoryPropertyFlagBits::eHostVisible) | vk::MemoryPropertyFlagBits::eHostCoherent,
this->handle,
this->memory
);
void *mappedMemory = this->memory.mapMemory(0, this->memorySize);
std::ranges::copy(data, static_cast<T*>(mappedMemory));
this->memory.unmapMemory();
}
Buffer(const Context &context, const std::size_t size) requires (mode == BufferMode::CPU_ACCESS)
: BaseBuffer<mode>(context, size * elementSize) {
MakeInstance(
usage,
vk::Flags(vk::MemoryPropertyFlagBits::eHostVisible) | vk::MemoryPropertyFlagBits::eHostCoherent,
this->handle,
this->memory
);
void *mappedMemory = this->memory.mapMemory(0, this->memorySize);
content = std::span<T>(static_cast<T*>(mappedMemory), size);
std::ranges::fill(content, T{});
}
Buffer(const Context &context, const std::size_t size) requires (mode == BufferMode::STAGING_ACCESS)
: BaseBuffer<mode>(context, size * elementSize) {
MakeInstance(
vk::Flags(vk::BufferUsageFlagBits::eTransferDst) | usage,
vk::MemoryPropertyFlagBits::eDeviceLocal,
this->handle,
this->memory
);
MakeInstance(
vk::BufferUsageFlagBits::eTransferSrc,
vk::Flags(vk::MemoryPropertyFlagBits::eHostVisible) | vk::MemoryPropertyFlagBits::eHostCoherent,
this->stagingHandle,
this->stagingMemory
);
void *mappedMemory = this->stagingMemory.mapMemory(0, this->memorySize);
content = std::span<T>(static_cast<T*>(mappedMemory), size);
std::ranges::fill(content, T{});
}
private:
void MakeInstance(
const vk::BufferUsageFlags &usageFlags,
const vk::MemoryPropertyFlags &memoryFlags,
vk::raii::Buffer &buffer,
vk::raii::DeviceMemory &memory
) {
buffer = this->context.device.handle.createBuffer({
.size = this->memorySize,
.usage = usageFlags,
.sharingMode = vk::SharingMode::eExclusive
});
if (
std::optional<vk::raii::DeviceMemory> allocated = this->context.AllocateBufferMemory(
buffer,
memoryFlags
);
allocated.has_value()
) {
memory = std::move(allocated).value();
buffer.bindMemory(memory, 0);
}
}
};
enum class TextureMode {
RENDER,
DEPTH
};
template <TextureMode mode>
class Texture {
public:
const Context &context;
const std::uint32_t width;
const std::uint32_t height;
NO_UNIQUE_ADDRESS MappedMemory<std::byte, mode == TextureMode::RENDER> data;
vk::raii::Image handle = nullptr;
vk::raii::ImageView view = nullptr;
vk::raii::DeviceMemory memory = nullptr;
const vk::Format format;
Texture(
const Context &context,
const std::uint32_t width, const std::uint32_t height,
const vk::Format &format,
const std::span<std::byte> textureData
) requires (mode == TextureMode::RENDER)
: context(context), width(width), height(height), data(textureData), format(format) {
Buffer<
std::byte,
vk::BufferUsageFlagBits::eTransferSrc,
BufferMode::STATIC
> stagingBuffer(context, data);
MakeInstance(
vk::Flags(vk::ImageUsageFlagBits::eTransferDst) | vk::ImageUsageFlagBits::eSampled,
vk::MemoryPropertyFlagBits::eDeviceLocal,
handle,
memory
);
/* TODO: Transition image layout
* Copy buffer to image
* Transition image layout
* Create view
*/
}
Texture(
const Context &context,
const Surface &surface
) requires (mode == TextureMode::DEPTH)
: context(context), width(surface.extent.width), height(surface.extent.height), format(vk::Format::eD32SfloatS8Uint) {
MakeInstance(
vk::ImageUsageFlagBits::eDepthStencilAttachment,
vk::MemoryPropertyFlagBits::eDeviceLocal,
handle,
memory
);
MakeView(vk::ImageAspectFlagBits::eDepth);
}
Texture(Texture&&) noexcept = default;
Texture(const Texture&) = delete;
Texture& operator=(const Texture&) = delete;
private:
void MakeInstance(
const vk::ImageUsageFlags &usageFlags,
const vk::MemoryPropertyFlags &memoryflags,
vk::raii::Image &image,
vk::raii::DeviceMemory &memory
) const {
image = context.device.handle.createImage({
.imageType = vk::ImageType::e2D,
.format = format,
.extent = {.width = width, .height = height, .depth = 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = usageFlags,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
});
if (
std::optional<vk::raii::DeviceMemory> allocated = context.AllocateImageMemory(
handle,
memoryflags
);
allocated.has_value()
) {
memory = std::move(allocated).value();
handle.bindMemory(memory, 0);
}
}
void MakeView(const vk::ImageAspectFlags &flags) {
view = context.device.handle.createImageView({
.image = handle,
.viewType = vk::ImageViewType::e2D,
.format = format,
.subresourceRange = {
.aspectMask = flags,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1
}
});
}
};
template <typename T>
class Uniform {
public:
std::vector<Buffer<T, vk::BufferUsageFlagBits::eUniformBuffer, BufferMode::CPU_ACCESS>> buffers;
explicit Uniform(const Context &context, std::uint32_t maxFramesInFlight) {
buffers.reserve(maxFramesInFlight);
for (std::uint32_t i = 0; i < maxFramesInFlight; i++) {
buffers.push_back(Buffer<T, vk::BufferUsageFlagBits::eUniformBuffer, BufferMode::CPU_ACCESS>(context, 1));
}
}
};
}

View File

@ -0,0 +1,30 @@
/**
* Factory partition module is responsible for construction of render objects
*/
export module vulkan_render:factory;
import :hardware;
import :resources;
export namespace vulkan_render {
class ResourcesFactory {
public:
/**
* Initialize frames swapchain
* @param context Vulkan context with initialized device and surface capabilities.
* @param surface surface origin. Extent will be clamped with frames size.
*/
static Swapchain CreateSwapchain(const Context &context,Surface &surface);
private:
/**
* Initialize images and views for swapchain frames
* @param context Vulkan context with ready device.
* @param swapchain frames target with initialized swapchain origin.
* @param surface surface origin.
*/
static void SetupFrames(const Context &context, Swapchain &swapchain, const Surface &surface);
};
}

View File

@ -0,0 +1,195 @@
/**
* Hardware partition module is responsible for any Vulkan operations dependent on
* hardware capabilities including devices and extensions initialization, memory allocations and so on.
*/
export module vulkan_render:hardware;
import :resources;
import std;
import vulkan_hpp;
export namespace vulkan_render {
struct PhysicalDeviceCapabilities {
vk::PhysicalDeviceProperties properties;
using Features1 = vk::PhysicalDeviceFeatures2;
using Features12 = vk::PhysicalDeviceVulkan12Features;
using Features13 = vk::PhysicalDeviceVulkan13Features;
using Features = vk::StructureChain<Features1, Features12, Features13>;
Features features;
Features enabledFeatures;
Features1& GetFeatures1() { return features.get<Features1>(); }
Features12& GetFeatures12() { return features.get<Features12>(); }
Features13& GetFeatures13() { return features.get<Features13>(); }
Features1& GetEnabledFeatures1() { return enabledFeatures.get<Features1>(); }
Features12& GetEnabledFeatures12() { return enabledFeatures.get<Features12>(); }
Features13& GetEnabledFeatures13() { return enabledFeatures.get<Features13>(); }
};
/**
* Priority scoring rules to select one physical device among available.
*/
struct PhysicalDevicePriorities {
struct GPU {
std::uint32_t DISCRETE = 1000;
std::uint32_t INTEGRATED = 1;
std::uint32_t VIRTUAL = 1;
std::uint32_t CPU = 0;
std::uint32_t OTHER = 0;
} gpu;
struct QueueFamilies {
std::uint32_t PRESENT = 500;
} queueFamilies;
struct SurfaceFormats {
std::uint32_t B8G8R8_SRGB = 10;
} surfaceFormats;
struct PresentModes {
std::uint32_t FIFO_RELAXED = 10;
} presentModes;
};
struct SurfaceCapabilities {
vk::SurfaceCapabilitiesKHR capabilities;
std::vector<vk::SurfaceFormatKHR> availableFormats;
std::vector<vk::PresentModeKHR> availablePresentModes;
};
}
export namespace vulkan_render {
/**
* Vulkan objects associated with physical device.
*/
struct PhysicalDevice {
/**
* Ids of queue families by their usage.
*/
struct QueueFamilies {
static constexpr std::uint32_t NO_VALUE = std::numeric_limits<std::uint32_t>::max();
std::uint32_t transfer = NO_VALUE;
std::uint32_t graphics = NO_VALUE;
std::uint32_t present = NO_VALUE;
} queueFamilies;
struct Memory {
vk::PhysicalDeviceMemoryProperties properties;
} memory;
vk::raii::PhysicalDevice handle = nullptr;
};
/**
* Vulkan objects associated with logical device.
*/
struct LogicalDevice {
vk::raii::Device handle = nullptr;
static constexpr float DEFAULT_QUEUE_PRIORITY = 1.0f;
vk::raii::Queue transfer = nullptr;
vk::raii::Queue graphics = nullptr;
vk::raii::Queue present = nullptr;
};
}
export namespace vulkan_render {
/**
* Vulkan device and hardware context.
* @warning Physical and logical devices need to be defined either directly
* or using vulkan_render::Context::SetupPhysicalDevice and vulkan_render::Context::SetupLogicalDevice
* before context usage.
*/
class Context {
public:
vk::raii::Context context;
vk::raii::Instance vulkan;
LogicalDevice device {};
PhysicalDevice physicalDevice {};
/// Loaded on physical device selection in vulkan_render::Context::SetupPhysicalDevice.
PhysicalDeviceCapabilities physicalDeviceCapabilities;
/// Loaded on physical device selection in vulkan_render::Context::SetupPhysicalDevice.
SurfaceCapabilities surfaceCapabilities;
/**
* Initialize VkInstance.
* @param appName application name to register in Vulkan.
*/
explicit Context(const char* appName);
/**
* Initialize VkInstance.
* @param instanceInfo custom Vulkan configuration.
*/
explicit Context(const vk::InstanceCreateInfo &instanceInfo);
/**
* Initialize VkInstance.
* @param appName application name to register in Vulkan.
* @param extensions list of vulkan enabled extensions labels.
* @param layers list of vulkan enabled layers labels.
*/
Context(const char* appName, const std::vector<const char*> &extensions, const std::vector<const char*> &layers);
/**
* Select available physical device by priority.
*
* @note Updates vulkan_render::Context::physicalDevice and vulkan_render::Context::physicalDeviceCapabilities
* if at least one device is available, otherwise no effect.
*
* @param surface instance to be capable with.
* @param priorities configuration of physical device selection rules.
* @param[out] selectedFormat surface format of selected physical device. Undefined if no device was selected.
* @param[out] selectedPresentMode present mode of selected physical device. Undefined if no device was selected.
*/
void SetupPhysicalDevice(
const Surface &surface,
const PhysicalDevicePriorities &priorities,
vk::SurfaceFormatKHR &selectedFormat,
vk::PresentModeKHR &selectedPresentMode
);
/**
* Initialize logical device with its available queues.
* @param extensions list of enabled device extensions.
*/
void SetupLogicalDevice(const std::vector<const char*> &extensions);
/**
* Find memory type available on device for specified requirements.
* @return id of memory type or std::nullopt if no suitable memory found.
*/
std::optional<std::uint32_t> FindMemoryType(
const vk::MemoryRequirements &requirements,
const vk::MemoryPropertyFlags &flags
);
std::optional<vk::raii::DeviceMemory> AllocateMemory(
const vk::MemoryRequirements2 &memoryRequirements,
const vk::MemoryPropertyFlags &memoryFlags
) const;
std::optional<vk::raii::DeviceMemory> AllocateBufferMemory(
const vk::raii::Buffer &buffer,
const vk::MemoryPropertyFlags &memoryFlags
) const;
std::optional<vk::raii::DeviceMemory> AllocateImageMemory(
const vk::raii::Image &image,
const vk::MemoryPropertyFlags &memoryFlags
) const;
private:
vk::raii::Instance MakeInstance(
const char *appName,
const std::vector<const char*> &extensions,
const std::vector<const char*> &layers
) const;
};
}

View File

@ -0,0 +1,38 @@
/**
* Resources partition module is responsible for definition of
* Vulkan resource objects including images, surface and so on.
*/
export module vulkan_render:resources;
import std;
import vulkan_hpp;
export namespace vulkan_render {
/**
* Vulkan objects associated with surface.
* @note handle needs to be set manually according your window context.
*/
struct Surface {
vk::raii::SurfaceKHR handle = nullptr;
vk::SurfaceFormatKHR format;
vk::PresentModeKHR presentMode;
vk::Extent2D extent;
};
/**
* Vulkan objects associated with frames swapchain.
* @note Can be created with vulkan_render::ResourcesFactory::CreateSwapchain.
*/
struct Swapchain {
vk::raii::SwapchainKHR handle;
/**
* Images used as targets to render frames.
*/
std::vector<vk::Image> frames;
/**
* Views describing how the swapchain frames are accessed during rendering.
*/
std::vector<vk::raii::ImageView> frameViews;
};
}

View File

@ -0,0 +1,8 @@
export module vulkan_render;
export import vulkan_hpp;
export import :hardware;
export import :workflow;
export import :control;
export import :resources;
export import :data;
export import :factory;

View File

@ -0,0 +1,157 @@
/**
* Workflow partition module is responsible for configuration of rendering stages
* including pipelines, render passes and so on.
*/
export module vulkan_render:workflow;
import :hardware;
import :resources;
import :data;
import std;
import vulkan_hpp;
export namespace vulkan_render {
using DescriptorBindings = std::vector<vk::DescriptorSetLayoutBinding>;
using DescriptorCounts = std::unordered_map<vk::DescriptorType /*type*/, std::uint8_t /*count*/>;
/**
* Content of SPIR-V shaders binary data for a single pipeline.
*/
struct ShaderProgram {
std::span<std::byte> vertex;
std::span<std::byte> fragment;
/**
* Name of vertex shader entry point.
*/
const char* vertexMainFun = "main";
/**
* Name of fragment shader entry point.
*/
const char* fragmentMainFun = "main";
};
struct ShaderAttributeFormat {
static constexpr vk::Format FLOAT = vk::Format::eR32Sfloat;
static constexpr vk::Format VEC2 = vk::Format::eR32G32Sfloat;
static constexpr vk::Format VEC3 = vk::Format::eR32G32B32Sfloat;
static constexpr vk::Format VEC4 = vk::Format::eR32G32B32A32Sfloat;
ShaderAttributeFormat() = delete;
};
/**
* Shaders input attributes binding.
*/
struct ShaderBindings {
vk::VertexInputBindingDescription vertexBinding;
std::uint32_t vertexAttributesCount;
vk::VertexInputAttributeDescription vertexAttributes;
};
/**
* Set of descriptions needed for Vulkan pipeline initialization.
*/
struct PipelineConfig {
vk::PipelineVertexInputStateCreateInfo vertexInputInfo;
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
vk::PipelineViewportStateCreateInfo viewportStateInfo;
vk::PipelineRasterizationStateCreateInfo rasterizerInfo;
vk::PipelineMultisampleStateCreateInfo multisamplingInfo;
vk::PipelineDepthStencilStateCreateInfo depthStencilInfo;
vk::PipelineColorBlendStateCreateInfo colorBlendInfo;
vk::PipelineDynamicStateCreateInfo dynamicStateInfo;
vk::DescriptorSetLayoutCreateInfo descriptorsInfo;
// SRC - new color, DST - old color
std::vector<vk::PipelineColorBlendAttachmentState> colorBlendAttachmentStates;
std::vector<vk::DynamicState> dynamicStates;
std::vector<vk::DescriptorSetLayoutBinding> descriptors;
vk::Viewport defaultViewport;
vk::Rect2D defaultScissors;
vk::PushConstantRange pushConstantRange;
};
struct DescriptorInfo {
std::variant<
vk::DescriptorBufferInfo,
vk::DescriptorImageInfo
> info;
std::uint32_t bindingPosition;
vk::DescriptorType type;
};
}
export namespace vulkan_render {
/**
* Vulkan pipeline constructed on context, shaders and descriptors.
*/
class Pipeline {
public:
const Context &context;
const Surface &surface;
const Swapchain &swapchain;
const std::uint32_t maxFramesInFlight;
DescriptorCounts descriptorCounts;
vk::raii::DescriptorSetLayout descriptorsLayout;
vk::raii::RenderPass renderPass;
vk::raii::PipelineLayout layout;
vk::raii::Pipeline handle;
vk::Viewport viewport;
vk::Rect2D scissors;
vk::raii::DescriptorPool descriptorPool;
vk::raii::DescriptorSets descriptorSets;
std::vector<vk::raii::Framebuffer> framebuffers;
/**
* Initialize Pipeline on shaders and their input.
* @param context Vulkan context with ready device.
* @param surface
* @param swapchain swapchain frame images used to allocate descriptors.
* @param config pipeline configuration.
* @param shaderProgram SPIR-V shaders binaries.
* @param maxFramesInFlight max number of frames in flight used to allocate descriptors .
*/
Pipeline(
const Context &context,
const Surface &surface,
const Swapchain &swapchain,
const Texture<TextureMode::DEPTH> &depth,
const PipelineConfig &config,
const ShaderProgram &shaderProgram,
std::uint32_t maxFramesInFlight
);
void UpdateDescriptors(const std::vector<DescriptorInfo> &descriptors, std::uint32_t frame);
/**
* Construct default pipeline configuration for given shaders input layout.
* @param surface surface origin.
* @param bindings shaders input attributes binding definition.
* @param descriptors descriptors binding definition (e.g. uniforms).
* @param pushConstants definition for small performant data set.
* @return default pipeline configuration.
*/
static PipelineConfig MakeConfig(
const Surface &surface,
const ShaderBindings &bindings,
const DescriptorBindings &descriptors,
const vk::PushConstantRange& pushConstants
);
private:
vk::raii::DescriptorSetLayout MakeDescriptorsLayout(const PipelineConfig &config) const;
vk::raii::PipelineLayout MakeLayout(const PipelineConfig &config) const;
vk::raii::Pipeline MakeInstance(const PipelineConfig &config, const ShaderProgram &shaderProgram) const;
vk::raii::DescriptorPool MakeDescriptorPool(const DescriptorBindings &descriptors);
vk::raii::DescriptorSets MakeDescriptorSets() const;
vk::raii::RenderPass MakerRenderPass() const;
void MakeFramebuffers(const Texture<TextureMode::DEPTH> &depth);
};
}

View File

@ -0,0 +1,356 @@
module;
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
module VulkanRender;
import utils;
ee_vulkan::VulkanRenderer::VulkanRenderer(GLFWwindow *window) : IRenderer(window) {
std::uint32_t glfwExtensionsCount;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionsCount);
context.emplace(
"EtrediaEmpires",
std::vector(glfwExtensions, glfwExtensions + glfwExtensionsCount),
std::vector{"VK_LAYER_KHRONOS_validation"}
);
VkSurfaceKHR vulkanSurface;
glfwCreateWindowSurface(*context->vulkan, window, nullptr, &vulkanSurface);
surface.handle = vk::raii::SurfaceKHR(context->vulkan, vulkanSurface);
surface.extent = {
.width = static_cast<std::uint32_t>(screenWidth),
.height = static_cast<std::uint32_t>(screenHeight)
};
context->SetupPhysicalDevice(surface, {}, surface.format, surface.presentMode);
context->SetupLogicalDevice({vk::KHRSwapchainExtensionName});
swapchain.emplace(
vulkan_render::ResourcesFactory::CreateSwapchain(*context, surface)
);
renderCommands.emplace(*context, MAX_FRAMES_IN_FLIGHT);
syncManager.emplace(*context, MAX_FRAMES_IN_FLIGHT, TIMEOUT);
LOG(INFO, "Renderer created");
}
ee_vulkan::VulkanRenderer::~VulkanRenderer() {}
void ee_vulkan::VulkanRenderer::Init() {
depth.emplace(*context, surface);
LoadProvincesMaterial();
viewProjectionUniform.emplace(*context, MAX_FRAMES_IN_FLIGHT);
LoadProvinces();
isDrawing = true;
}
void ee_vulkan::VulkanRenderer::Shutdown() {
context->device.handle.waitIdle();
}
void ee_vulkan::VulkanRenderer::Clear() {}
void ee_vulkan::VulkanRenderer::DrawMap() {
if (!isDrawing) return;
syncManager->WaitInFlightFence(currentFrame);
std::uint32_t imageIndex = 0;
switch (vk::Result result = syncManager->AcquireNextImage(*swapchain, currentFrame, imageIndex)) {
case vk::Result::eSuccess:
break;
case vk::Result::eErrorOutOfDateKHR:
case vk::Result::eSuboptimalKHR:
LOG(WARNING, "Swapchain needs to be recreated");
RecreateSwapchain();
return;
default:
LOG(ERROR, "Failed to get next image index. Code: %d", result);
break;
}
UpdateData(currentFrame);
syncManager->WaitImageInFlight(currentFrame, imageIndex);
const vulkan_render::Command &renderCommand = renderCommands->graphicsBuffers[currentFrame];
renderCommand.handle.reset();
renderCommand.Begin();
std::array clears = {DrawConfig::CLEAR_COLOR, DrawConfig::CLEAR_DEPTH};
renderCommand.handle.beginRenderPass({
.renderPass = provincesPipeline->renderPass,
.framebuffer = provincesPipeline->framebuffers[imageIndex],
.renderArea = {
.offset = {.x = 0, .y = 0},
.extent = surface.extent
},
.clearValueCount = static_cast<std::uint32_t>(clears.size()),
.pClearValues = clears.data()
}, vk::SubpassContents::eInline);
renderCommand.handle.bindPipeline(vk::PipelineBindPoint::eGraphics, provincesPipeline->handle);
renderCommand.handle.setViewport(0, {provincesPipeline->viewport});
renderCommand.handle.setScissor(0, {provincesPipeline->scissors});
renderCommand.handle.bindVertexBuffers(
0,
{provinces.vertices->handle},
{0}
);
renderCommand.handle.bindIndexBuffer(
provinces.indices->handle,
0,
vk::IndexType::eUint32
);
renderCommand.handle.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics,
provincesPipeline->layout,
0,
{provincesPipeline->descriptorSets[currentFrame]},
{}
);
renderCommand.handle.drawIndexedIndirect(
provinces.indirectCommands->handle,
0,
provinces.indirectCommands->content.size(),
provinces.indirectCommands->elementSize
);
renderCommand.handle.endRenderPass();
renderCommand.handle.end();
context->device.handle.resetFences({syncManager->inFlightFences[currentFrame]});
vk::CommandBufferSubmitInfo cmdSubmitInfo {
.commandBuffer = renderCommand.handle
};
vk::SemaphoreSubmitInfo waitSemaphoreInfo {
.semaphore = syncManager->imageAvailable[currentFrame],
.stageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput
};
vk::SemaphoreSubmitInfo signalSemaphoreInfo {
.semaphore = syncManager->renderFinished[currentFrame],
.stageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput
};
const vk::SubmitInfo2 submitInfo {
.waitSemaphoreInfoCount = 1,
.pWaitSemaphoreInfos = &waitSemaphoreInfo,
.commandBufferInfoCount = 1,
.pCommandBufferInfos = &cmdSubmitInfo,
.signalSemaphoreInfoCount = 1,
.pSignalSemaphoreInfos = &signalSemaphoreInfo,
};
context->device.graphics.submit2({submitInfo}, syncManager->inFlightFences[currentFrame]);
vk::SwapchainKHR swapchains[] = {swapchain->handle};
switch (
vk::Result result = context->device.present.presentKHR({
.waitSemaphoreCount = 1,
.pWaitSemaphores = &signalSemaphoreInfo.semaphore,
.swapchainCount = 1,
.pSwapchains = swapchains,
.pImageIndices = &imageIndex
})
) {
case vk::Result::eSuccess:
break;
case vk::Result::eErrorOutOfDateKHR:
case vk::Result::eSuboptimalKHR:
LOG(WARNING, "Swapchain needs to be recreated");
RecreateSwapchain();
return;
default:
LOG(ERROR, "Failed to get next image index. Code: %d", result);
break;
}
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
void ee_vulkan::VulkanRenderer::DrawUI() {}
void ee_vulkan::VulkanRenderer::ResizeWindow(const int width, const int height) {
IRenderer::ResizeWindow(width, height);
surface.extent = {
.width = static_cast<std::uint32_t>(screenWidth),
.height = static_cast<std::uint32_t>(screenHeight)
};
}
void ee_vulkan::VulkanRenderer::ApplyCamera() {
viewProjection[1][1] *= -1;
viewProjectionInversed[1][1] *= -1;
}
void ee_vulkan::VulkanRenderer::UpdateProvinces(const std::size_t offset, const std::vector<ee_engine::ProvinceSSBO> &data) {
const std::size_t elementOffset = offset / provinces.storage->elementSize;
std::ranges::copy(data, provinces.storage->content.data() + elementOffset);
renderCommands->SubmitStagingBuffer(*provinces.storage);
}
void ee_vulkan::VulkanRenderer::RecreateSwapchain() {
// We should not render with minimized window
if (screenWidth == 0 || screenHeight == 0)
{
isDrawing = false;
return;
}
isDrawing = true;
context->device.handle.waitIdle();
swapchain = vulkan_render::ResourcesFactory::CreateSwapchain(*context, surface);
depth.emplace(*context, surface);
LoadProvincesMaterial();
}
void ee_vulkan::VulkanRenderer::LoadProvincesMaterial() {
constexpr vulkan_render::ShaderBindings shaderBindings {
.vertexBinding = {
.binding = 0,
.stride = sizeof(glm::vec2),
.inputRate = vk::VertexInputRate::eVertex
},
.vertexAttributesCount = 1,
.vertexAttributes = {
.location = 0,
.binding = 0,
.format = vulkan_render::ShaderAttributeFormat::VEC2,
.offset = 0
}
};
const vk::DescriptorSetLayoutBinding viewProjection {
.binding = 0,
.descriptorType = vk::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eVertex,
.pImmutableSamplers = nullptr
};
const vk::DescriptorSetLayoutBinding provincesSSBO {
.binding = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = 1,
.stageFlags = vk::Flags(vk::ShaderStageFlagBits::eVertex) | vk::ShaderStageFlagBits::eFragment,
.pImmutableSamplers = nullptr
};
const vk::PushConstantRange model {
.stageFlags = vk::ShaderStageFlagBits::eVertex,
.offset = 0,
.size = sizeof(glm::mat4)
};
vulkan_render::PipelineConfig provincesMaterial = vulkan_render::Pipeline::MakeConfig(
surface,
shaderBindings,
{viewProjection, provincesSSBO},
model
);
vulkan_render::ShaderProgram provincesShader {
.vertex = {reinterpret_cast<std::byte*>(shaders.vertProvinces->content), shaders.vertProvinces->size},
.fragment = {reinterpret_cast<std::byte*>(shaders.fragProvinces->content), shaders.fragProvinces->size}
};
provincesPipeline.emplace(
*context,
surface,
*swapchain,
*depth,
provincesMaterial,
provincesShader,
MAX_FRAMES_IN_FLIGHT
);
}
void ee_vulkan::VulkanRenderer::LoadProvinces() {
std::vector<ee_engine::ProvinceSSBO> provincesData;
std::vector<glm::vec2> verticesData;
std::vector<std::uint32_t> indicesData;
std::vector<vk::DrawIndexedIndirectCommand> indirectCommandsData;
std::size_t regionOffset = 0;
std::uint32_t indicesOffset = 0;
std::int32_t verticesOffset = 0;
for (auto& [region, provs] : mapMesh->regions) {
for (auto&[vertices, indices] : provs) {
indirectCommandsData.push_back({
.indexCount = static_cast<std::uint32_t>(indices.size()),
.instanceCount = 1,
.firstIndex = indicesOffset,
.vertexOffset = verticesOffset,
.firstInstance = static_cast<std::uint32_t>(provincesData.size())
});
verticesData.append_range(vertices);
indicesData.append_range(indices);
ee_engine::ProvinceSSBO data{
.regionId = region,
.color = 0xFF0000
};
provincesData.push_back(data);
indicesOffset += indices.size();
verticesOffset += vertices.size();
}
regionsMapping[region] = {.offset = regionOffset, .provincesCount = provs.size()};
regionOffset += provs.size() * sizeof(ee_engine::ProvinceSSBO);
}
provinces.vertices.emplace(*context, verticesData.size());
provinces.indices.emplace(*context, indicesData.size());
provinces.storage.emplace(*context, provincesData.size());
provinces.indirectCommands.emplace(*context, indirectCommandsData.size());
std::ranges::copy(verticesData, provinces.vertices->content.data());
std::ranges::copy(indicesData, provinces.indices->content.data());
std::ranges::copy(provincesData, provinces.storage->content.data());
std::ranges::copy(indirectCommandsData, provinces.indirectCommands->content.data());
renderCommands->SubmitStagingBuffer(*provinces.vertices);
renderCommands->SubmitStagingBuffer(*provinces.indices);
renderCommands->SubmitStagingBuffer(*provinces.storage);
renderCommands->SubmitStagingBuffer(*provinces.indirectCommands);
}
void ee_vulkan::VulkanRenderer::UpdateData(const std::uint32_t frame) {
std::span<glm::mat4> &vpBuffer = viewProjectionUniform->buffers[frame].content;
vpBuffer[0] = viewProjection;
provincesPipeline->UpdateDescriptors({
{
.info = vk::DescriptorBufferInfo{
.buffer = viewProjectionUniform->buffers[frame].handle,
.offset = 0,
.range = viewProjectionUniform->buffers[frame].memorySize
},
.bindingPosition = 0,
.type = vk::DescriptorType::eUniformBuffer
},
{
.info = vk::DescriptorBufferInfo{
.buffer = provinces.storage->handle,
.offset = 0,
.range = provinces.storage->memorySize
},
.bindingPosition = 1,
.type = vk::DescriptorType::eStorageBuffer
}
}, frame);
}

View File

@ -0,0 +1,155 @@
module vulkan_render;
import utils;
vulkan_render::Command::Command(const Context &context, vk::raii::CommandBuffer &&buffer) :
context(context),
handle(std::move(buffer)) {}
vulkan_render::Command::Command(const Context &context, const vk::raii::CommandPool &pool) :
context(context),
handle(std::move(
context.device.handle.allocateCommandBuffers({
.commandPool = pool,
.level = vk::CommandBufferLevel::ePrimary, //TODO: reuse some commands using SECONDARY
.commandBufferCount = 1
}).front()
)) {}
void vulkan_render::Command::Begin() const {
handle.begin({
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit
});
}
void vulkan_render::Command::Submit(const vk::raii::Queue &queue) const {
handle.end();
vk::CommandBufferSubmitInfo cmdSubmitInfo {
.commandBuffer = handle
};
const vk::SubmitInfo2 submitInfo {
.commandBufferInfoCount = 1,
.pCommandBufferInfos = &cmdSubmitInfo,
};
const vk::raii::Fence fence = context.device.handle.createFence({});
queue.submit2({submitInfo}, fence);
if (
vk::Result result = context.device.handle.waitForFences({fence}, true, std::numeric_limits<std::uint64_t>::max());
result != vk::Result::eSuccess
) {
LOG(WARNING, "Command buffer submit timeout. Code: %d", result);
}
}
vulkan_render::RenderCommands::RenderCommands(const Context &context, const std::uint32_t maxFramesInFlight) :
graphicsPool(MakePool(context, context.physicalDevice.queueFamilies.graphics)),
transferPool(MakePool(context, context.physicalDevice.queueFamilies.transfer)) {
graphicsBuffers = MakeBuffers(context, graphicsPool, maxFramesInFlight);
transferBuffers = MakeBuffers(context, transferPool, maxFramesInFlight);
LOG(INFO, "Command buffers initialized.");
}
void vulkan_render::RenderCommands::SubmitStagingBuffer(const BaseBuffer<BufferMode::STAGING_ACCESS> &buffer) const {
const Command cmd(buffer.context, transferPool);
cmd.Begin();
vk::BufferCopy2 copyRegion {
.srcOffset = 0,
.dstOffset = 0,
.size = buffer.memorySize
};
cmd.handle.copyBuffer2({
.srcBuffer = buffer.stagingHandle,
.dstBuffer = buffer.handle,
.regionCount = 1,
.pRegions = &copyRegion
});
cmd.Submit(buffer.context.device.transfer);
}
vk::raii::CommandPool vulkan_render::RenderCommands::MakePool(const Context &context, const std::uint32_t queueFamily) {
return context.device.handle.createCommandPool({
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = queueFamily,
});
}
std::vector<vulkan_render::Command> vulkan_render::RenderCommands::MakeBuffers(
const Context &context,
const vk::raii::CommandPool &pool,
const std::uint32_t size
) {
std::vector<vk::raii::CommandBuffer> buffers = context.device.handle.allocateCommandBuffers({
.commandPool = pool,
.level = vk::CommandBufferLevel::ePrimary, //TODO: reuse some commands using SECONDARY
.commandBufferCount = size
});
std::vector<Command> result;
result.reserve(buffers.size());
for (vk::raii::CommandBuffer &buffer : buffers) {
result.emplace_back(context, std::move(buffer));
}
return result;
}
vulkan_render::SyncManager::SyncManager(
const Context &context,
const std::uint32_t maxFramesInFlight,
const std::uint64_t timeout
) :
context(context), TIMEOUT(timeout) {
imageAvailable.reserve(maxFramesInFlight);
renderFinished.reserve(maxFramesInFlight);
inFlightFences.reserve(maxFramesInFlight);
imagesInFlight.resize(maxFramesInFlight, nullptr);
for (std::uint32_t i = 0; i < maxFramesInFlight; i++) {
imageAvailable.push_back(context.device.handle.createSemaphore({}));
renderFinished.push_back(context.device.handle.createSemaphore({}));
inFlightFences.push_back(context.device.handle.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}));
}
LOG(INFO, "Sync objects initialized.");
}
void vulkan_render::SyncManager::WaitInFlightFence(const std::size_t currentFrame) {
if (
vk::Result result = context.device.handle.waitForFences({inFlightFences[currentFrame]}, vk::True, TIMEOUT);
result != vk::Result::eSuccess
)
LOG(ERROR, "Waiting semaphore timeout. Code: %d", result);
}
void vulkan_render::SyncManager::WaitImageInFlight(const std::size_t currentFrame, const std::uint32_t imageIndex) {
if (imagesInFlight[imageIndex] == nullptr) return;
if (
vk::Result result = context.device.handle.waitForFences({imagesInFlight[imageIndex]}, vk::True, TIMEOUT);
result != vk::Result::eSuccess
)
LOG(ERROR, "Waiting semaphore timeout. Code: %d", result);
imagesInFlight[imageIndex] = inFlightFences[currentFrame];
}
vk::Result vulkan_render::SyncManager::AcquireNextImage(
const Swapchain &swapchain,
const std::size_t currentFrame,
std::uint32_t &imageIndex
) const {
auto [result, newImageIndex] = context.device.handle.acquireNextImage2KHR({
.swapchain = swapchain.handle,
.timeout = TIMEOUT,
.semaphore = imageAvailable[currentFrame],
.deviceMask = 1
});
imageIndex = newImageIndex;
return result;
}

View File

@ -0,0 +1,87 @@
module vulkan_render;
import utils;
vulkan_render::Swapchain vulkan_render::ResourcesFactory::CreateSwapchain(const Context &context, Surface &surface) {
// Clamp extent
surface.extent.width = std::max(
context.surfaceCapabilities.capabilities.minImageExtent.width,
(std::min)(context.surfaceCapabilities.capabilities.maxImageExtent.width, surface.extent.width)
);
surface.extent.height = std::max(
context.surfaceCapabilities.capabilities.minImageExtent.height,
(std::min)(context.surfaceCapabilities.capabilities.maxImageExtent.height, surface.extent.height)
);
std::uint32_t imagesCount = context.surfaceCapabilities.capabilities.minImageCount + 1;
// Clamp min images count
if (context.surfaceCapabilities.capabilities.maxImageCount > 0 && imagesCount > context.surfaceCapabilities.capabilities.maxImageCount)
imagesCount = context.surfaceCapabilities.capabilities.maxImageCount;
vk::SwapchainCreateInfoKHR swapchainCreateInfo {
.surface = surface.handle,
.minImageCount = imagesCount,
.imageFormat = surface.format.format,
.imageColorSpace = surface.format.colorSpace,
.imageExtent = surface.extent,
.imageArrayLayers = 1,
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment,
.preTransform = context.surfaceCapabilities.capabilities.currentTransform,
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
.presentMode = surface.presentMode,
.clipped = vk::True,
.oldSwapchain = nullptr
};
const std::vector queueFamiliesShare {context.physicalDevice.queueFamilies.graphics, context.physicalDevice.queueFamilies.present};
if (context.physicalDevice.queueFamilies.graphics != context.physicalDevice.queueFamilies.present)
{
swapchainCreateInfo.imageSharingMode = vk::SharingMode::eConcurrent;
swapchainCreateInfo.queueFamilyIndexCount = queueFamiliesShare.size();
swapchainCreateInfo.pQueueFamilyIndices = queueFamiliesShare.data();
}
else
swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive;
Swapchain swapchain {
.handle = context.device.handle.createSwapchainKHR(swapchainCreateInfo)
};
LOG(INFO, "Swapchain is created");
SetupFrames(context, swapchain, surface);
return swapchain;
}
void vulkan_render::ResourcesFactory::SetupFrames(const Context &context, Swapchain &swapchain, const Surface &surface) {
swapchain.frames.clear();
swapchain.frameViews.clear();
swapchain.frames = swapchain.handle.getImages();
LOG(INFO, "Frames are ready");
swapchain.frameViews.reserve(swapchain.frames.size());
for (size_t i = 0; i < swapchain.frames.size(); i++)
{
vk::ImageViewCreateInfo frameViewInfo {
.image = swapchain.frames[i],
.viewType = vk::ImageViewType::e2D,
.format = surface.format.format,
.components {
.r = vk::ComponentSwizzle::eIdentity,
.g = vk::ComponentSwizzle::eIdentity,
.b = vk::ComponentSwizzle::eIdentity,
.a = vk::ComponentSwizzle::eIdentity
},
.subresourceRange {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1
}
};
swapchain.frameViews.push_back(context.device.handle.createImageView(frameViewInfo));
}
LOG(INFO, "Frame views are ready.");
}

View File

@ -0,0 +1,304 @@
module vulkan_render;
import utils;
template <typename T> using iterator = std::ranges::borrowed_iterator_t<std::vector<T>&>;
vulkan_render::Context::Context(const char *appName)
: vulkan(MakeInstance(appName, {}, {})) {}
vulkan_render::Context::Context(const vk::InstanceCreateInfo &instanceInfo)
: vulkan(vk::raii::Instance(context, instanceInfo)) {}
vulkan_render::Context::Context(
const char *appName,
const std::vector<const char*> &extensions,
const std::vector<const char*> &layers
)
: vulkan(MakeInstance(appName, extensions, layers)) {}
void vulkan_render::Context::SetupPhysicalDevice(
const Surface &surface,
const PhysicalDevicePriorities &priorities,
vk::SurfaceFormatKHR& selectedFormat,
vk::PresentModeKHR& selectedPresentMode
) {
// Find physical devices
const std::vector<vk::raii::PhysicalDevice> devices = vulkan.enumeratePhysicalDevices();
if (devices.size() == 0) {
LOG(CRITICAL, "No devices with Vulkan support found!");
return;
}
LOG(INFO, "Found %zu Vulkan compatible physical devices.", devices.size());
if (devices.empty())
{
LOG(CRITICAL, "No compatible physical devices found.");
}
// Filter and select device
std::uint32_t bestPriority = 0;
for (const vk::raii::PhysicalDevice &physicalDeviceVariant : devices)
{
std::uint32_t priority = 1;
// Check device features and properties
physicalDeviceCapabilities.features = physicalDeviceVariant.getFeatures2<
PhysicalDeviceCapabilities::Features1,
PhysicalDeviceCapabilities::Features12,
PhysicalDeviceCapabilities::Features13
>();
//TODO: provide features list to check and enable
const PhysicalDeviceCapabilities::Features1 &features1 = physicalDeviceCapabilities.GetFeatures1();
const PhysicalDeviceCapabilities::Features13 &features13 = physicalDeviceCapabilities.GetFeatures13();
if (
features1.features.wideLines != vk::True ||
features1.features.independentBlend != vk::True ||
features1.features.multiDrawIndirect != vk::True ||
features13.dynamicRendering != vk::True ||
features13.synchronization2 != vk::True ||
features13.maintenance4 != vk::True
) {
continue;
}
PhysicalDeviceCapabilities::Features1 &enabledFeatures1 = physicalDeviceCapabilities.GetEnabledFeatures1();
PhysicalDeviceCapabilities::Features13 &enabledFeatures13 = physicalDeviceCapabilities.GetEnabledFeatures13();
enabledFeatures1.features.wideLines = vk::True;
enabledFeatures1.features.independentBlend = vk::True;
enabledFeatures1.features.multiDrawIndirect = vk::True;
enabledFeatures13.dynamicRendering = vk::True;
enabledFeatures13.synchronization2 = vk::True;
enabledFeatures13.maintenance4 = vk::True;
physicalDeviceCapabilities.properties = physicalDeviceVariant.getProperties();
if (physicalDeviceCapabilities.properties.apiVersion < vk::makeApiVersion(0, 1, 3, 0))
continue;
// Check queue families
std::vector<vk::QueueFamilyProperties> queueFamiliesProperties = physicalDeviceVariant.getQueueFamilyProperties();
std::uint32_t i = 0;
PhysicalDevice::QueueFamilies queueFamiliesBuffer;
for (const vk::QueueFamilyProperties &familyProps : queueFamiliesProperties)
{
if (familyProps.queueFlags & vk::QueueFlagBits::eGraphics)
queueFamiliesBuffer.graphics = i;
if (
queueFamiliesBuffer.graphics != queueFamiliesBuffer.present ||
queueFamiliesBuffer.graphics == PhysicalDevice::QueueFamilies::NO_VALUE
)
{
if (physicalDeviceVariant.getSurfaceSupportKHR(i, surface.handle))
queueFamiliesBuffer.present = i;
}
i++;
}
if (
queueFamiliesBuffer.graphics == PhysicalDevice::QueueFamilies::NO_VALUE ||
queueFamiliesBuffer.present == PhysicalDevice::QueueFamilies::NO_VALUE
)
continue;
queueFamiliesBuffer.transfer = queueFamiliesBuffer.graphics;
// Check surface info
SurfaceCapabilities surfaceCapabilitiesBuffer {};
surfaceCapabilitiesBuffer.availableFormats = physicalDeviceVariant.getSurfaceFormatsKHR(surface.handle);
surfaceCapabilitiesBuffer.availablePresentModes = physicalDeviceVariant.getSurfacePresentModesKHR(surface.handle);
if (
surfaceCapabilitiesBuffer.availableFormats.empty() ||
surfaceCapabilitiesBuffer.availablePresentModes.empty()
)
continue;
// Rate and select physical device
if (queueFamiliesBuffer.graphics == queueFamiliesBuffer.present)
priority += priorities.queueFamilies.PRESENT;
switch (physicalDeviceCapabilities.properties.deviceType) {
case vk::PhysicalDeviceType::eDiscreteGpu:
priority += priorities.gpu.DISCRETE;
break;
case vk::PhysicalDeviceType::eIntegratedGpu:
priority += priorities.gpu.INTEGRATED;
break;
case vk::PhysicalDeviceType::eVirtualGpu:
priority += priorities.gpu.VIRTUAL;
break;
case vk::PhysicalDeviceType::eCpu:
priority += priorities.gpu.CPU;
break;
case vk::PhysicalDeviceType::eOther:
priority += priorities.gpu.OTHER;
break;
}
vk::SurfaceFormatKHR formatBuffer {
.format = vk::Format::eB8G8R8A8Unorm,
.colorSpace = vk::ColorSpaceKHR::eSrgbNonlinear
};
for (const vk::SurfaceFormatKHR &format : surfaceCapabilitiesBuffer.availableFormats)
{
if (format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
{
formatBuffer = format;
priority += priorities.surfaceFormats.B8G8R8_SRGB;
break;
}
}
vk::PresentModeKHR presentModeBuffer = vk::PresentModeKHR::eFifo;
for (const vk::PresentModeKHR &presentMode : surfaceCapabilitiesBuffer.availablePresentModes)
{
if (presentMode == vk::PresentModeKHR::eFifoRelaxed)
{
presentModeBuffer = presentMode;
priority += priorities.presentModes.FIFO_RELAXED;
break;
}
}
if (priority > bestPriority)
{
bestPriority = priority;
physicalDevice.handle = physicalDeviceVariant;
physicalDevice.queueFamilies = queueFamiliesBuffer;
surfaceCapabilities = surfaceCapabilitiesBuffer;
selectedFormat = formatBuffer;
selectedPresentMode = presentModeBuffer;
}
}
surfaceCapabilities.capabilities = physicalDevice.handle.getSurfaceCapabilitiesKHR(surface.handle);
LOG(INFO, "%hs is selected for render with %d priority score", physicalDeviceCapabilities.properties.deviceName, bestPriority);
physicalDevice.memory.properties = physicalDevice.handle.getMemoryProperties();
}
void vulkan_render::Context::SetupLogicalDevice(const std::vector<const char*> &extensions) {
// Define queues
std::vector<vk::DeviceQueueCreateInfo> queuesCreateInfos;
const std::set uniqueQueueFamilies {
physicalDevice.queueFamilies.transfer,
physicalDevice.queueFamilies.graphics,
physicalDevice.queueFamilies.present
};
for (const std::uint32_t queueFamily : uniqueQueueFamilies)
{
vk::DeviceQueueCreateInfo queueCreateInfo {
.queueFamilyIndex = queueFamily,
.queueCount = 1,
.pQueuePriorities = &LogicalDevice::DEFAULT_QUEUE_PRIORITY
};
queuesCreateInfos.push_back(queueCreateInfo);
}
// Create logical device
const vk::DeviceCreateInfo deviceInfo {
.pNext = &physicalDeviceCapabilities.enabledFeatures.get(),
.queueCreateInfoCount = static_cast<std::uint32_t>(queuesCreateInfos.size()),
.pQueueCreateInfos = queuesCreateInfos.data(),
.enabledExtensionCount = static_cast<std::uint32_t>(extensions.size()),
.ppEnabledExtensionNames = extensions.data(),
};
device.handle = physicalDevice.handle.createDevice(deviceInfo);
LOG(INFO, "Logical device is created.");
// Save queues descriptor
device.transfer = device.handle.getQueue(physicalDevice.queueFamilies.transfer, 0);
device.graphics = device.handle.getQueue(physicalDevice.queueFamilies.graphics, 0);
device.present = device.handle.getQueue(physicalDevice.queueFamilies.present, 0);
LOG(INFO, "Queues registered.");
}
std::optional<std::uint32_t> vulkan_render::Context::FindMemoryType(const vk::MemoryRequirements &requirements, const vk::MemoryPropertyFlags &flags) {
std::optional<std::uint32_t> memoryType;
for (std::uint32_t i = 0; i < physicalDevice.memory.properties.memoryTypeCount; i++)
{
if (
requirements.memoryTypeBits & (1 << i) &&
(physicalDevice.memory.properties.memoryTypes[i].propertyFlags & flags) == flags
) {
memoryType = i;
break;
}
}
if (!memoryType.has_value()) {
LOG(CRITICAL, "Failed to find GPU memory with requirements %d and flags %d", requirements.memoryTypeBits, flags);
}
return memoryType;
}
std::optional<vk::raii::DeviceMemory> vulkan_render::Context::AllocateMemory(
const vk::MemoryRequirements2 &memoryRequirements,
const vk::MemoryPropertyFlags &memoryFlags
) const {
std::optional<std::uint32_t> memoryTypeId;
const vk::PhysicalDeviceMemoryProperties &memoryProps = physicalDevice.memory.properties;
for (std::uint32_t i = 0; i < memoryProps.memoryTypeCount; i++) {
if (
memoryRequirements.memoryRequirements.memoryTypeBits & (1 << i) &&
(memoryProps.memoryTypes[i].propertyFlags & memoryFlags) == memoryFlags
) {
memoryTypeId = i;
break;
}
}
if (!memoryTypeId.has_value()) {
LOG(CRITICAL, "Failed to find suitable GPU memory. Flags: %d", memoryFlags);
return std::nullopt;
}
return device.handle.allocateMemory({
.allocationSize = memoryRequirements.memoryRequirements.size,
.memoryTypeIndex = memoryTypeId.value()
});
}
std::optional<vk::raii::DeviceMemory> vulkan_render::Context::AllocateBufferMemory(
const vk::raii::Buffer &buffer,
const vk::MemoryPropertyFlags &memoryFlags
) const {
return AllocateMemory(device.handle.getBufferMemoryRequirements2({.buffer = buffer}), memoryFlags);
}
std::optional<vk::raii::DeviceMemory> vulkan_render::Context::AllocateImageMemory(
const vk::raii::Image &image,
const vk::MemoryPropertyFlags &memoryFlags
) const {
return AllocateMemory(device.handle.getImageMemoryRequirements2({.image = image}), memoryFlags);
}
vk::raii::Instance vulkan_render::Context::MakeInstance(
const char *appName,
const std::vector<const char*> &extensions,
const std::vector<const char*> &layers
) const {
const vk::ApplicationInfo appInfo {
.pApplicationName = appName,
.applicationVersion = vk::makeApiVersion(0, 1, 0, 0),
.pEngineName = "No Engine",
.engineVersion = vk::makeApiVersion(0, 1, 0, 0),
.apiVersion = vk::makeApiVersion(0, 1, 3, 0)
};
const vk::InstanceCreateInfo createInfo {
.pApplicationInfo = &appInfo,
.enabledLayerCount = static_cast<std::uint32_t>(layers.size()),
.ppEnabledLayerNames = layers.data(),
.enabledExtensionCount = static_cast<std::uint32_t>(extensions.size()),
.ppEnabledExtensionNames = extensions.data(),
};
return vk::raii::Instance(context, createInfo);
}

View File

@ -0,0 +1,361 @@
module vulkan_render;
import utils;
vulkan_render::Pipeline::Pipeline(
const Context &context,
const Surface &surface,
const Swapchain &swapchain,
const Texture<TextureMode::DEPTH> &depth,
const PipelineConfig &config,
const ShaderProgram &shaderProgram,
const std::uint32_t maxFramesInFlight
) : context(context),
surface(surface),
swapchain(swapchain),
maxFramesInFlight(maxFramesInFlight),
descriptorsLayout(MakeDescriptorsLayout(config)),
layout(MakeLayout(config)),
renderPass(MakerRenderPass()),
handle(MakeInstance(config, shaderProgram)),
viewport(config.defaultViewport),
scissors(config.defaultScissors),
descriptorPool(MakeDescriptorPool(config.descriptors)),
descriptorSets(MakeDescriptorSets()) {
MakeFramebuffers(depth);
LOG(INFO, "Pipeline created");
}
void vulkan_render::Pipeline::UpdateDescriptors(
const std::vector<DescriptorInfo> &descriptors,
const std::uint32_t frame
) {
std::vector<vk::WriteDescriptorSet> writes;
writes.reserve(descriptors.size());
for (const auto &[info, bindingPosition, type] : descriptors) {
vk::WriteDescriptorSet write {
.dstSet = descriptorSets[frame],
.dstBinding = bindingPosition,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = type,
};
switch (type) {
case vk::DescriptorType::eStorageBuffer:
case vk::DescriptorType::eUniformBuffer:
write.pBufferInfo = &std::get<vk::DescriptorBufferInfo>(info);
break;
case vk::DescriptorType::eCombinedImageSampler:
write.pImageInfo = &std::get<vk::DescriptorImageInfo>(info);
break;
default:
break;
}
writes.push_back(write);
}
context.device.handle.updateDescriptorSets(writes, 0);
}
vulkan_render::PipelineConfig vulkan_render::Pipeline::MakeConfig(
const Surface &surface,
const ShaderBindings &bindings,
const DescriptorBindings &descriptors,
const vk::PushConstantRange &pushConstants
) {
PipelineConfig config;
config.dynamicStates.push_back(vk::DynamicState::eViewport);
config.dynamicStates.push_back(vk::DynamicState::eScissor);
config.colorBlendAttachmentStates.push_back({
.blendEnable = vk::True,
.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha,
.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
.colorBlendOp = vk::BlendOp::eAdd,
.srcAlphaBlendFactor = vk::BlendFactor::eOne,
.dstAlphaBlendFactor = vk::BlendFactor::eZero,
.alphaBlendOp = vk::BlendOp::eAdd,
.colorWriteMask = vk::Flags(vk::ColorComponentFlagBits::eR) | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB
});
config.descriptors = descriptors;
config.vertexInputInfo = {
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &bindings.vertexBinding,
.vertexAttributeDescriptionCount = bindings.vertexAttributesCount,
.pVertexAttributeDescriptions = &bindings.vertexAttributes
};
config.inputAssemblyInfo = {
.topology = vk::PrimitiveTopology::eTriangleList,
.primitiveRestartEnable = vk::False
};
config.rasterizerInfo = {
.depthClampEnable = vk::False,
.rasterizerDiscardEnable = vk::False,
.polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eNone, //TODO: BACK_BIT
.frontFace = vk::FrontFace::eCounterClockwise,
.depthBiasEnable = vk::False,
.lineWidth = 1.0f,
};
config.multisamplingInfo = {
.rasterizationSamples = vk::SampleCountFlagBits::e1,
.sampleShadingEnable = vk::False,
};
config.depthStencilInfo = {
.depthTestEnable = vk::False,//TODO
.depthWriteEnable = vk::False,//TODO
.depthCompareOp = vk::CompareOp::eLess,
.depthBoundsTestEnable = vk::False,
.stencilTestEnable = vk::False,
.front = {},
.back = {},
.minDepthBounds = 0.0f,
.maxDepthBounds = 1.0f,
};
config.dynamicStateInfo = {
.dynamicStateCount = static_cast<std::uint32_t>(config.dynamicStates.size()),
.pDynamicStates = config.dynamicStates.data()
};
config.descriptorsInfo = {
.bindingCount = static_cast<std::uint32_t>(config.descriptors.size()),
.pBindings = config.descriptors.data()
};
config.pushConstantRange = pushConstants;
config.defaultViewport = {
.x = 0.0f,
.y = 0.0f,
.width = static_cast<float>(surface.extent.width),
.height = static_cast<float>(surface.extent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f
};
config.defaultScissors = {
.offset = {.x = 0, .y = 0},
.extent = surface.extent
};
config.viewportStateInfo = {
.viewportCount = 1,
.pViewports = &config.defaultViewport,
.scissorCount = 1,
.pScissors = &config.defaultScissors
};
config.colorBlendInfo = {
.logicOpEnable = vk::False,
.attachmentCount = static_cast<std::uint32_t>(config.colorBlendAttachmentStates.size()),
.pAttachments = config.colorBlendAttachmentStates.data(),
};
return config;
}
vk::raii::DescriptorSetLayout vulkan_render::Pipeline::MakeDescriptorsLayout(const PipelineConfig &config) const {
return context.device.handle.createDescriptorSetLayout(config.descriptorsInfo);
}
vk::raii::PipelineLayout vulkan_render::Pipeline::MakeLayout(const PipelineConfig &config) const {
const vk::PipelineLayoutCreateInfo layoutInfo {
.setLayoutCount = 1,
.pSetLayouts = &*descriptorsLayout,
.pushConstantRangeCount = 1,
.pPushConstantRanges = &config.pushConstantRange
};
return context.device.handle.createPipelineLayout(layoutInfo);
}
vk::raii::Pipeline vulkan_render::Pipeline::MakeInstance(
const PipelineConfig &config,
const ShaderProgram &shaderProgram
) const {
// Configure shaders
std::vector<vk::PipelineShaderStageCreateInfo> stagesInfo;
stagesInfo.reserve(3);
// Vertex
vk::ShaderModuleCreateInfo shaderModuleCreateInfo = {
.codeSize = shaderProgram.vertex.size(),
.pCode = reinterpret_cast<const std::uint32_t*>(shaderProgram.vertex.data())
};
const vk::raii::ShaderModule vertexModule = context.device.handle.createShaderModule(shaderModuleCreateInfo);
stagesInfo.push_back({
.stage = vk::ShaderStageFlagBits::eVertex,
.module = vertexModule,
.pName = shaderProgram.vertexMainFun,
.pSpecializationInfo = nullptr
});
// Fragment
shaderModuleCreateInfo = {
.codeSize = shaderProgram.fragment.size(),
.pCode = reinterpret_cast<const std::uint32_t*>(shaderProgram.fragment.data())
};
const vk::raii::ShaderModule fragmentModule = context.device.handle.createShaderModule(shaderModuleCreateInfo);
stagesInfo.push_back({
.stage = vk::ShaderStageFlagBits::eFragment,
.module = fragmentModule,
.pName = shaderProgram.fragmentMainFun,
.pSpecializationInfo = nullptr
});
//
vk::StructureChain<vk::GraphicsPipelineCreateInfo, vk::PipelineRenderingCreateInfo> pipelineInfo(
{
.stageCount = static_cast<std::uint32_t>(stagesInfo.size()),
.pStages = stagesInfo.data(),
.pVertexInputState = &config.vertexInputInfo,
.pInputAssemblyState = &config.inputAssemblyInfo,
.pViewportState = &config.viewportStateInfo,
.pRasterizationState = &config.rasterizerInfo,
.pMultisampleState = &config.multisamplingInfo,
.pDepthStencilState = &config.depthStencilInfo,
.pColorBlendState = &config.colorBlendInfo,
.pDynamicState = &config.dynamicStateInfo,
.layout = layout,
.renderPass = renderPass,
.basePipelineHandle = nullptr,
.basePipelineIndex = -1,
},
{
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &surface.format.format,
.depthAttachmentFormat = vk::Format::eD32SfloatS8Uint
}
);
return context.device.handle.createGraphicsPipeline(nullptr, pipelineInfo.get());
}
vk::raii::DescriptorPool vulkan_render::Pipeline::MakeDescriptorPool(const DescriptorBindings &descriptors) {
descriptorCounts.clear();
for (const vk::DescriptorSetLayoutBinding &binding : descriptors) {
descriptorCounts[binding.descriptorType] += binding.descriptorCount;
}
std::vector<vk::DescriptorPoolSize> poolSizes;
poolSizes.reserve(descriptorCounts.size());
for (const auto &[type, count] : descriptorCounts) {
poolSizes.push_back({
.type = type,
.descriptorCount = count * maxFramesInFlight
});
}
return context.device.handle.createDescriptorPool({
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = static_cast<std::uint32_t>(maxFramesInFlight),
.poolSizeCount = static_cast<std::uint32_t>(poolSizes.size()),
.pPoolSizes = poolSizes.data()
});
}
vk::raii::DescriptorSets vulkan_render::Pipeline::MakeDescriptorSets() const {
std::vector layouts(maxFramesInFlight, *descriptorsLayout);
return vk::raii::DescriptorSets(
context.device.handle,
{
.descriptorPool = descriptorPool,
.descriptorSetCount = maxFramesInFlight,
.pSetLayouts = layouts.data()
}
);
}
vk::raii::RenderPass vulkan_render::Pipeline::MakerRenderPass() const {
const vk::AttachmentDescription colorAttachmentDescription {
.format = surface.format.format,
.samples = vk::SampleCountFlagBits::e1, // No multisampling
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
.initialLayout = vk::ImageLayout::eUndefined,
.finalLayout = vk::ImageLayout::ePresentSrcKHR
};
vk::AttachmentReference colorAttachmentRef {
.attachment = 0,
.layout = vk::ImageLayout::eColorAttachmentOptimal
};
const vk::AttachmentDescription depthAttachmentDescription {
.format = vk::Format::eD32SfloatS8Uint,
.samples = vk::SampleCountFlagBits::e1,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eDontCare,
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare ,
.initialLayout = vk::ImageLayout::eUndefined,
.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal,
};
vk::AttachmentReference depthAttachmentRef {
.attachment = 1,
.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal
};
vk::SubpassDescription subpass {
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
.colorAttachmentCount = 1,
.pColorAttachments = &colorAttachmentRef,
.pDepthStencilAttachment = &depthAttachmentRef
};
vk::SubpassDependency dependency {
.srcSubpass = vk::SubpassExternal,
.dstSubpass = 0,
.srcStageMask = vk::Flags(vk::PipelineStageFlagBits::eColorAttachmentOutput) | vk::PipelineStageFlagBits::eLateFragmentTests,
.dstStageMask = vk::Flags(vk::PipelineStageFlagBits::eColorAttachmentOutput) | vk::PipelineStageFlagBits::eEarlyFragmentTests,
.srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite,
.dstAccessMask = vk::Flags(vk::AccessFlagBits::eColorAttachmentWrite) | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
};
std::array attachments = {colorAttachmentDescription, depthAttachmentDescription};
const vk::RenderPassCreateInfo baseRenderPassInfo {
.attachmentCount = static_cast<std::uint32_t>(attachments.size()),
.pAttachments = attachments.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &dependency
};
return context.device.handle.createRenderPass(baseRenderPassInfo);
}
void vulkan_render::Pipeline::MakeFramebuffers(const Texture<TextureMode::DEPTH> &depth) {
framebuffers.reserve(swapchain.frameViews.size());
for (const vk::raii::ImageView &frameView : swapchain.frameViews)
{
std::array attachments = {*frameView, *depth.view};
vk::FramebufferCreateInfo frameBufferInfo {
.renderPass = renderPass,
.attachmentCount = static_cast<std::uint32_t>(attachments.size()),
.pAttachments = attachments.data(),
.width = surface.extent.width,
.height = surface.extent.height,
.layers = 1
};
framebuffers.emplace_back(context.device.handle.createFramebuffer(frameBufferInfo));
}
}

View File

@ -0,0 +1,4 @@
@echo off
glslc provinces/provinces.frag -O -o ../../../assets/shaders/provinces.frag
glslc provinces/provinces.vert -O -o ../../../assets/shaders/provinces.vert

View File

@ -0,0 +1,10 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}

View File

@ -0,0 +1,35 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 position;
layout(binding = 0) uniform Camera {
mat4 viewProjection;
} camera;
layout(push_constant) uniform Model {
mat4 model;
} model;
struct Province {
uint regionId;
uint color;
};
layout(std430, binding = 1) readonly buffer Data {
Province provinces[];
};
layout(location = 0) out vec3 fragColor;
vec3 unpackColor(uint c) {
return vec3(
c & 0xFFu,
(c >> 8) & 0xFFu,
(c >> 16) & 0xFFu
) / 255.0;
}
void main() {
gl_Position = camera.viewProjection * vec4(position, 0.0, 1.0);
fragColor = unpackColor(provinces[gl_InstanceIndex].color);
}