From 542ef348eea684b9dc829185ff8342d03c8e7fb9 Mon Sep 17 00:00:00 2001 From: GeorgH93 Date: Mon, 14 Oct 2019 23:02:51 +0200 Subject: [PATCH] first release --- .gitignore | 334 ++++++++++++++++ .gitmodules | 15 + CMakeLists.txt | 56 +++ LICENSE | 373 +++++++++++++++++ README.md | 17 + external/assimp | 1 + external/fmt | 1 + external/glfw | 1 + external/glm | 1 + external/spdlog | 1 + openVulkanoCpp.sln | 25 ++ openVulkanoCpp.sln.DotSettings | 14 + openVulkanoCpp/Base/EngineConfiguration.hpp | 32 ++ openVulkanoCpp/Base/EngineConstants.hpp | 31 ++ openVulkanoCpp/Base/ICloseable.hpp | 12 + openVulkanoCpp/Base/IGraphicsApp.hpp | 25 ++ openVulkanoCpp/Base/IGraphicsAppManager.hpp | 30 ++ openVulkanoCpp/Base/IInitable.hpp | 12 + openVulkanoCpp/Base/ITickable.hpp | 12 + openVulkanoCpp/Base/Logger.cpp | 13 + openVulkanoCpp/Base/Logger.hpp | 95 +++++ openVulkanoCpp/Base/PlatformEnums.hpp | 45 +++ openVulkanoCpp/Base/Render/IRenderer.hpp | 25 ++ openVulkanoCpp/Base/Timer.hpp | 98 +++++ openVulkanoCpp/Base/UI/BaseWindow.hpp | 99 +++++ openVulkanoCpp/Base/UI/IWindow.hpp | 113 ++++++ openVulkanoCpp/Base/Utils.hpp | 56 +++ .../Data/ReadOnlyAtomicArrayQueue.hpp | 44 +++ openVulkanoCpp/Host/GraphicsAppManager.hpp | 227 +++++++++++ openVulkanoCpp/Host/PlatformProducer.hpp | 51 +++ openVulkanoCpp/Host/WindowGLFW.hpp | 374 ++++++++++++++++++ openVulkanoCpp/Scene/AABB.hpp | 98 +++++ openVulkanoCpp/Scene/Camera.hpp | 169 ++++++++ openVulkanoCpp/Scene/Drawable.cpp | 26 ++ openVulkanoCpp/Scene/Drawable.hpp | 82 ++++ openVulkanoCpp/Scene/Geometry.hpp | 206 ++++++++++ openVulkanoCpp/Scene/Material.hpp | 13 + openVulkanoCpp/Scene/Node.cpp | 9 + openVulkanoCpp/Scene/Node.hpp | 212 ++++++++++ openVulkanoCpp/Scene/Scene.hpp | 89 +++++ openVulkanoCpp/Scene/Shader.hpp | 38 ++ openVulkanoCpp/Scene/Vertex.hpp | 178 +++++++++ openVulkanoCpp/Shader/CompileShaders.bat | 7 + openVulkanoCpp/Shader/basic.frag | 9 + openVulkanoCpp/Shader/basic.frag.spv | Bin 0 -> 376 bytes openVulkanoCpp/Shader/basic.vert | 29 ++ openVulkanoCpp/Shader/basic.vert.spv | Bin 0 -> 2824 bytes openVulkanoCpp/Vulkan/Buffer.hpp | 109 +++++ openVulkanoCpp/Vulkan/CommandHelper.hpp | 45 +++ openVulkanoCpp/Vulkan/Context.hpp | 119 ++++++ .../Vulkan/Debuging/ValidationLayer.hpp | 97 +++++ openVulkanoCpp/Vulkan/Device.hpp | 279 +++++++++++++ openVulkanoCpp/Vulkan/DeviceManager.hpp | 44 +++ openVulkanoCpp/Vulkan/FrameBuffer.cpp | 76 ++++ openVulkanoCpp/Vulkan/FrameBuffer.hpp | 107 +++++ openVulkanoCpp/Vulkan/Image.hpp | 98 +++++ openVulkanoCpp/Vulkan/Pipeline.hpp | 42 ++ openVulkanoCpp/Vulkan/RenderPass.hpp | 146 +++++++ openVulkanoCpp/Vulkan/Renderer.hpp | 220 +++++++++++ .../Vulkan/Resources/IShaderOwner.hpp | 16 + .../Vulkan/Resources/ManagedResource.hpp | 93 +++++ .../Vulkan/Resources/ResourceManager.hpp | 258 ++++++++++++ .../Vulkan/Resources/UniformBuffer.hpp | 54 +++ openVulkanoCpp/Vulkan/Scene/IRecordable.hpp | 15 + .../Vulkan/Scene/VulkanGeometry.hpp | 41 ++ openVulkanoCpp/Vulkan/Scene/VulkanNode.hpp | 53 +++ openVulkanoCpp/Vulkan/Scene/VulkanShader.hpp | 97 +++++ openVulkanoCpp/Vulkan/SwapChain.hpp | 261 ++++++++++++ openVulkanoCpp/Vulkan/VulkanUtils.hpp | 41 ++ openVulkanoCpp/main.cpp | 108 +++++ openVulkanoCpp/openVulkanoCpp.vcxproj | 168 ++++++++ openVulkanoCpp/packages.config | 5 + 72 files changed, 5990 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 160000 external/assimp create mode 160000 external/fmt create mode 160000 external/glfw create mode 160000 external/glm create mode 160000 external/spdlog create mode 100644 openVulkanoCpp.sln create mode 100644 openVulkanoCpp.sln.DotSettings create mode 100644 openVulkanoCpp/Base/EngineConfiguration.hpp create mode 100644 openVulkanoCpp/Base/EngineConstants.hpp create mode 100644 openVulkanoCpp/Base/ICloseable.hpp create mode 100644 openVulkanoCpp/Base/IGraphicsApp.hpp create mode 100644 openVulkanoCpp/Base/IGraphicsAppManager.hpp create mode 100644 openVulkanoCpp/Base/IInitable.hpp create mode 100644 openVulkanoCpp/Base/ITickable.hpp create mode 100644 openVulkanoCpp/Base/Logger.cpp create mode 100644 openVulkanoCpp/Base/Logger.hpp create mode 100644 openVulkanoCpp/Base/PlatformEnums.hpp create mode 100644 openVulkanoCpp/Base/Render/IRenderer.hpp create mode 100644 openVulkanoCpp/Base/Timer.hpp create mode 100644 openVulkanoCpp/Base/UI/BaseWindow.hpp create mode 100644 openVulkanoCpp/Base/UI/IWindow.hpp create mode 100644 openVulkanoCpp/Base/Utils.hpp create mode 100644 openVulkanoCpp/Data/ReadOnlyAtomicArrayQueue.hpp create mode 100644 openVulkanoCpp/Host/GraphicsAppManager.hpp create mode 100644 openVulkanoCpp/Host/PlatformProducer.hpp create mode 100644 openVulkanoCpp/Host/WindowGLFW.hpp create mode 100644 openVulkanoCpp/Scene/AABB.hpp create mode 100644 openVulkanoCpp/Scene/Camera.hpp create mode 100644 openVulkanoCpp/Scene/Drawable.cpp create mode 100644 openVulkanoCpp/Scene/Drawable.hpp create mode 100644 openVulkanoCpp/Scene/Geometry.hpp create mode 100644 openVulkanoCpp/Scene/Material.hpp create mode 100644 openVulkanoCpp/Scene/Node.cpp create mode 100644 openVulkanoCpp/Scene/Node.hpp create mode 100644 openVulkanoCpp/Scene/Scene.hpp create mode 100644 openVulkanoCpp/Scene/Shader.hpp create mode 100644 openVulkanoCpp/Scene/Vertex.hpp create mode 100644 openVulkanoCpp/Shader/CompileShaders.bat create mode 100644 openVulkanoCpp/Shader/basic.frag create mode 100644 openVulkanoCpp/Shader/basic.frag.spv create mode 100644 openVulkanoCpp/Shader/basic.vert create mode 100644 openVulkanoCpp/Shader/basic.vert.spv create mode 100644 openVulkanoCpp/Vulkan/Buffer.hpp create mode 100644 openVulkanoCpp/Vulkan/CommandHelper.hpp create mode 100644 openVulkanoCpp/Vulkan/Context.hpp create mode 100644 openVulkanoCpp/Vulkan/Debuging/ValidationLayer.hpp create mode 100644 openVulkanoCpp/Vulkan/Device.hpp create mode 100644 openVulkanoCpp/Vulkan/DeviceManager.hpp create mode 100644 openVulkanoCpp/Vulkan/FrameBuffer.cpp create mode 100644 openVulkanoCpp/Vulkan/FrameBuffer.hpp create mode 100644 openVulkanoCpp/Vulkan/Image.hpp create mode 100644 openVulkanoCpp/Vulkan/Pipeline.hpp create mode 100644 openVulkanoCpp/Vulkan/RenderPass.hpp create mode 100644 openVulkanoCpp/Vulkan/Renderer.hpp create mode 100644 openVulkanoCpp/Vulkan/Resources/IShaderOwner.hpp create mode 100644 openVulkanoCpp/Vulkan/Resources/ManagedResource.hpp create mode 100644 openVulkanoCpp/Vulkan/Resources/ResourceManager.hpp create mode 100644 openVulkanoCpp/Vulkan/Resources/UniformBuffer.hpp create mode 100644 openVulkanoCpp/Vulkan/Scene/IRecordable.hpp create mode 100644 openVulkanoCpp/Vulkan/Scene/VulkanGeometry.hpp create mode 100644 openVulkanoCpp/Vulkan/Scene/VulkanNode.hpp create mode 100644 openVulkanoCpp/Vulkan/Scene/VulkanShader.hpp create mode 100644 openVulkanoCpp/Vulkan/SwapChain.hpp create mode 100644 openVulkanoCpp/Vulkan/VulkanUtils.hpp create mode 100644 openVulkanoCpp/main.cpp create mode 100644 openVulkanoCpp/openVulkanoCpp.vcxproj create mode 100644 openVulkanoCpp/packages.config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9432859 --- /dev/null +++ b/.gitignore @@ -0,0 +1,334 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ +/cmake-build-debug/ +/bin/ + +perf.csv \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cf82e7e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,15 @@ +[submodule "external/spdlog"] + path = external/spdlog + url = https://github.com/gabime/spdlog +[submodule "external/glm"] + path = external/glm + url = https://github.com/g-truc/glm +[submodule "external/glfw"] + path = external/glfw + url = https://github.com/glfw/glfw +[submodule "external/fmt"] + path = external/fmt + url = https://github.com/fmtlib/fmt +[submodule "external/assimp"] + path = external/assimp + url = https://github.com/assimp/assimp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2e0084c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.7 FATAL_ERROR) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_SOURCE_DIR}/bin/release") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_SOURCE_DIR}/bin/debug") +set(CMAKE_DEBUG_POSTFIX "d") + +project (openVulkanoCpp) +add_executable(openVulkanoCpp openVulkanoCpp/main.cpp) + +#Setup Vulkan +find_package(Vulkan REQUIRED) +set(Vulkan_LIBRARIES Vulkan::Vulkan) +target_link_libraries(openVulkanoCpp PRIVATE ${Vulkan_LIBRARIES}) +target_include_directories(openVulkanoCpp PUBLIC ${Vulkan_INCLUDE_DIR}) + +if (ANDROID) + add_definitions(-DVK_USE_PLATFORM_ANDROID_KHR) +elseif (WIN32) + add_definitions(-DVK_USE_PLATFORM_WIN32_KHR) +else() + add_definitions(-DVK_USE_PLATFORM_XCB_KHR) + find_package(XCB REQUIRED) + link_libraries(${XCB_LIBRARIES}) +endif() + +set_property(TARGET openVulkanoCpp PROPERTY CXX_STANDARD 14) +target_compile_options(openVulkanoCpp PRIVATE -Wall) + +# glfw +if (NOT ANDROID) +add_subdirectory(external/glfw EXCLUDE_FROM_ALL) +add_dependencies(openVulkanoCpp glfw) +target_include_directories(openVulkanoCpp PUBLIC external/glfw/include/GLFW) +target_link_libraries(openVulkanoCpp PRIVATE glfw) +endif() + +# glm +add_subdirectory(external/glm EXCLUDE_FROM_ALL) +target_link_libraries(openVulkanoCpp PRIVATE glm) + +# fmt +#add_subdirectory(external/fmt EXCLUDE_FROM_ALL) +#target_link_libraries(openVulkanoCpp PRIVATE fmt) + +# spdlog +add_subdirectory(external/spdlog EXCLUDE_FROM_ALL) +target_link_libraries(openVulkanoCpp PRIVATE spdlog) +#add_definitions(-SPDLOG_FMT_EXTERNAL) + +# assimp +add_subdirectory(external/assimp EXCLUDE_FROM_ALL) +target_link_libraries(openVulkanoCpp PRIVATE assimp) + +target_sources(openVulkanoCpp PRIVATE openVulkanoCpp/Vulkan/FrameBuffer.cpp) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + 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/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5f4708 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# openVulkano +openVulkano is a simple Vulkan rendering engine which is capable of recording command buffers from multiple threads. + +## Building +### Visual Studio +#### Requierements +* Visual Studio 2019, 2017 might work but not tested +* [Vulkan SDK v1.1.x](https://vulkan.lunarg.com/sdk/home#windows) +* [Assimp SDK v4.1](https://github.com/assimp/assimp/releases/download/v4.1.0/assimp-sdk-4.1.0-setup.exe) +* git + +#### Build it +1. Clone it `git clone --recursive https://github.com/GeorgH93/openVulkano` +2. Open the `openVulkanoCpp.sln` with Visual Studio +3. If you are not using the default path of Assimp change it in the project properties +4. Use Visual Studio to build or run it + diff --git a/external/assimp b/external/assimp new file mode 160000 index 0000000..799fd74 --- /dev/null +++ b/external/assimp @@ -0,0 +1 @@ +Subproject commit 799fd74714f9ffac29004c6b5a674b3402524094 diff --git a/external/fmt b/external/fmt new file mode 160000 index 0000000..91f7619 --- /dev/null +++ b/external/fmt @@ -0,0 +1 @@ +Subproject commit 91f7619cc9cfd38ca3b73494793570ab93f69e08 diff --git a/external/glfw b/external/glfw new file mode 160000 index 0000000..d252483 --- /dev/null +++ b/external/glfw @@ -0,0 +1 @@ +Subproject commit d25248343e248337284dfbe5ecd1eddbd37ae66d diff --git a/external/glm b/external/glm new file mode 160000 index 0000000..ea678fa --- /dev/null +++ b/external/glm @@ -0,0 +1 @@ +Subproject commit ea678faff9340ae4a79f50f2edd947141405e128 diff --git a/external/spdlog b/external/spdlog new file mode 160000 index 0000000..aa65dd8 --- /dev/null +++ b/external/spdlog @@ -0,0 +1 @@ +Subproject commit aa65dd89053bea0b7e0ffc8d9be4f152d5c01f44 diff --git a/openVulkanoCpp.sln b/openVulkanoCpp.sln new file mode 100644 index 0000000..f9dd75f --- /dev/null +++ b/openVulkanoCpp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openVulkanoCpp", "openVulkanoCpp\openVulkanoCpp.vcxproj", "{D546A70B-536A-487A-91E1-1CD4563A0104}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D546A70B-536A-487A-91E1-1CD4563A0104}.Debug|x64.ActiveCfg = Debug|x64 + {D546A70B-536A-487A-91E1-1CD4563A0104}.Debug|x64.Build.0 = Debug|x64 + {D546A70B-536A-487A-91E1-1CD4563A0104}.Release|x64.ActiveCfg = Release|x64 + {D546A70B-536A-487A-91E1-1CD4563A0104}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F4F4FB70-77B5-4739-A960-EE5528AB1A4A} + EndGlobalSection +EndGlobal diff --git a/openVulkanoCpp.sln.DotSettings b/openVulkanoCpp.sln.DotSettings new file mode 100644 index 0000000..9ad4899 --- /dev/null +++ b/openVulkanoCpp.sln.DotSettings @@ -0,0 +1,14 @@ + + <NamingElement Priority="1"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="10"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></NamingElement> + <NamingElement Priority="9"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="11"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="PUBLIC"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></NamingElement> + <NamingElement Priority="13"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="scoped enumerator" /><type Name="unscoped enumerator" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></NamingElement> + <NamingElement Priority="16"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="namespace" /><type Name="namespace alias" /></Descriptor><Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="5"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="function parameter" /><type Name="lambda parameter" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></NamingElement> + True + True + True + True + True + True \ No newline at end of file diff --git a/openVulkanoCpp/Base/EngineConfiguration.hpp b/openVulkanoCpp/Base/EngineConfiguration.hpp new file mode 100644 index 0000000..bb26429 --- /dev/null +++ b/openVulkanoCpp/Base/EngineConfiguration.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include + +namespace openVulkanoCpp +{ + class EngineConfiguration + { + private: + EngineConfiguration() = default; + ~EngineConfiguration() = default; + + uint32_t numThreads = 1; + + public: + static EngineConfiguration* GetEngineConfiguration() + { + static EngineConfiguration* config = new EngineConfiguration(); + return config; + } + + void SetNumThreads(uint32_t numThreads) + { + this->numThreads = numThreads; + } + + uint32_t GetNumThreads() const + { + return std::max(static_cast(1), numThreads); + } + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Base/EngineConstants.hpp b/openVulkanoCpp/Base/EngineConstants.hpp new file mode 100644 index 0000000..0fd6c6e --- /dev/null +++ b/openVulkanoCpp/Base/EngineConstants.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include //TODO replace with external fmt + +namespace openVulkanoCpp +{ +#define MAKE_VERSION(major, minor, patch) (((major) << 22) | ((minor) << 12) | (patch)) + + const char* ENGINE_NAME = "openVulkanoCpp"; + + struct EngineVersion + { + int major, minor, patch; + int intVersion; + std::string stringVersion; + + EngineVersion(int major, int minor, int patch, int build = 0) : major(major), minor(minor), patch(patch) + { + intVersion = ((major) << 24) | ((minor) << 16) | (patch); + std::string buildConfig = ""; +#ifdef _DEBUG + buildConfig += "-MSVC_DEBUG"; +#elif DEBUG + buildConfig += "-DEBUG"; +#endif + stringVersion = fmt::format("v{0}.{1}.{2}.{3}{4}", major, minor, patch, build, buildConfig); + } + }; + + const EngineVersion ENGINE_VERSION(0, 0, 1); +} diff --git a/openVulkanoCpp/Base/ICloseable.hpp b/openVulkanoCpp/Base/ICloseable.hpp new file mode 100644 index 0000000..27fb326 --- /dev/null +++ b/openVulkanoCpp/Base/ICloseable.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace openVulkanoCpp +{ + class ICloseable + { + public: + virtual ~ICloseable() = default; + + virtual void Close() = 0; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Base/IGraphicsApp.hpp b/openVulkanoCpp/Base/IGraphicsApp.hpp new file mode 100644 index 0000000..767acfa --- /dev/null +++ b/openVulkanoCpp/Base/IGraphicsApp.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include "IInitable.hpp" +#include "ITickable.hpp" +#include "ICloseable.hpp" + +namespace openVulkanoCpp +{ + class IGraphicsAppManager; + + class IGraphicsApp : public IInitable, public ITickable, public ICloseable + { + private: + IGraphicsAppManager* manager = nullptr; + + public: + virtual ~IGraphicsApp() = default; + + IGraphicsAppManager* GetGraphicsAppManager() const { return manager; } + void SetGraphicsAppManager(IGraphicsAppManager* manager) { this->manager = manager; } + virtual std::string GetAppName() = 0; + virtual std::string GetAppVersion() = 0; + virtual int GetAppVersionAsInt() = 0; + }; +} diff --git a/openVulkanoCpp/Base/IGraphicsAppManager.hpp b/openVulkanoCpp/Base/IGraphicsAppManager.hpp new file mode 100644 index 0000000..635a00e --- /dev/null +++ b/openVulkanoCpp/Base/IGraphicsAppManager.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include "PlatformEnums.hpp" + +namespace openVulkanoCpp +{ + class IWindow; + class IGraphicsApp; + class IRenderer; + + class IGraphicsAppManager + { + public: + virtual ~IGraphicsAppManager() = default; + + virtual RenderAPI::RenderApi GetRenderApi() const = 0; + virtual IGraphicsApp* GetGraphicsApp() const = 0; + virtual IRenderer* GetRenderer() const = 0; + virtual bool IsRunning() const = 0; + virtual bool IsPaused() const = 0; + virtual void Stop() = 0; + virtual void Run() = 0; + virtual void Pause() = 0; + virtual void Resume() = 0; + + virtual float GetAvgFrameTime() const = 0; + virtual float GetAvgFps() const = 0; + virtual uint64_t GetFrameCount() const = 0; + }; +} diff --git a/openVulkanoCpp/Base/IInitable.hpp b/openVulkanoCpp/Base/IInitable.hpp new file mode 100644 index 0000000..f3cfa9f --- /dev/null +++ b/openVulkanoCpp/Base/IInitable.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace openVulkanoCpp +{ + class IInitable + { + public: + virtual ~IInitable() = default; + + virtual void Init() = 0; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Base/ITickable.hpp b/openVulkanoCpp/Base/ITickable.hpp new file mode 100644 index 0000000..e35de47 --- /dev/null +++ b/openVulkanoCpp/Base/ITickable.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace openVulkanoCpp +{ + class ITickable + { + public: + virtual ~ITickable() = default; + + virtual void Tick() = 0; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Base/Logger.cpp b/openVulkanoCpp/Base/Logger.cpp new file mode 100644 index 0000000..c362e0b --- /dev/null +++ b/openVulkanoCpp/Base/Logger.cpp @@ -0,0 +1,13 @@ +#include "Logger.hpp" + +namespace openVulkanoCpp +{ + std::vector Logger::sinks; + std::shared_ptr Logger::WINDOW = nullptr; + std::shared_ptr Logger::MANAGER = nullptr; + std::shared_ptr Logger::RENDER = nullptr; + std::shared_ptr Logger::PHYSIC = nullptr; + std::shared_ptr Logger::AUDIO = nullptr; + std::shared_ptr Logger::DATA = nullptr; + std::shared_ptr Logger::SCENE = nullptr; +} \ No newline at end of file diff --git a/openVulkanoCpp/Base/Logger.hpp b/openVulkanoCpp/Base/Logger.hpp new file mode 100644 index 0000000..26dbe31 --- /dev/null +++ b/openVulkanoCpp/Base/Logger.hpp @@ -0,0 +1,95 @@ +#pragma once + +#define SPDLOG_DEBUG_ON +#define SPDLOG_TRACE_ON + +#include +#include +#include +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/null_sink.h" +#ifndef NO_CONSOLE_LOG +#include +#endif +#ifdef _MSC_VER +#include "spdlog/sinks/msvc_sink.h" +#endif + +namespace openVulkanoCpp +{ + class Logger + { //TODO add custom sink for in game/engine console + static std::vector sinks; + public: + static std::shared_ptr WINDOW; + static std::shared_ptr MANAGER; + static std::shared_ptr RENDER; + static std::shared_ptr PHYSIC; + static std::shared_ptr AUDIO; + static std::shared_ptr DATA; + static std::shared_ptr SCENE; + + static void SetupLogger(std::string logFolder = "logs", std::string logFile = "openVulkano.log") + { + static bool initialized = false; + if (initialized) return; + try + { + try + { //TODO allow log files in folders + sinks.push_back(std::make_shared(logFile, 1024 * 1024 * 512, 3, true)); + } + catch (const spdlog::spdlog_ex& e) + { + std::cerr << "Log create file log sink: " << e.what() << std::endl; + } +#ifndef NO_CONSOLE_LOG + sinks.push_back(std::make_shared()); +#endif +#ifdef _MSC_VER // If it was build with msvc in debug we can use the msvc sink + sinks.push_back(std::make_shared()); +#endif + // Make sure that there is always a sink for the loggers + if (sinks.empty()) sinks.push_back(std::make_shared()); + + MANAGER = CreateLogger("manager"); + WINDOW = CreateLogger("window"); + RENDER = CreateLogger("render"); + PHYSIC = CreateLogger("physic"); + AUDIO = CreateLogger("audio"); + DATA = CreateLogger("data"); + SCENE = CreateLogger("scene"); + + spdlog::flush_every(std::chrono::seconds(5)); + + MANAGER->info("Logger initialized"); + initialized = true; + } + catch (const spdlog::spdlog_ex& e) + { + std::cerr << "Log initialization failed: " << e.what() << std::endl; + } + } + + /** + * \brief Creates a new custom logger that writes to the main log file. + * \param name The name of the logger + * \param reg If set to true the logger can be accessed again with spdlog::get(name) + * \return The created logger + */ + static std::shared_ptr CreateLogger(const std::string& name, const bool reg = true) + { + auto logger = std::make_shared(name, sinks.begin(), sinks.end()); + if (reg) spdlog::register_logger(logger); +#ifdef LOG_DATE + logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [T%t] [%^%l%$] [%n]: %v"); +#else + logger->set_pattern("[%H:%M:%S.%e] [T%t] [%^%l%$] [%n]: %v"); +#endif +#ifdef DEBUG + logger->set_level(spdlog::level::debug); +#endif + return logger; + } + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Base/PlatformEnums.hpp b/openVulkanoCpp/Base/PlatformEnums.hpp new file mode 100644 index 0000000..a0588af --- /dev/null +++ b/openVulkanoCpp/Base/PlatformEnums.hpp @@ -0,0 +1,45 @@ +#pragma once + +namespace openVulkanoCpp +{ + namespace RenderAPI + { + enum RenderApi + { + VULKAN = 0, + //OpenGL, + //DirectX11, + //DirectX12, + MAX_VALUE + }; + + inline std::string ToString(RenderApi api) + { + switch (api) + { + case VULKAN: return "Vulkan"; + } + return "Invalid"; + } + } + + namespace Platform + { + enum Platform + { + Windows = 0, MacOS, Linux, Android, MAX_VALUE + }; + + inline std::string ToString(Platform os) + { + switch (os) + { + case Windows: return "Windows"; + case MacOS: return "Windows"; + case Linux: return "Windows"; + case Android: return "Windows"; + } + return "Invalid"; + } + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Base/Render/IRenderer.hpp b/openVulkanoCpp/Base/Render/IRenderer.hpp new file mode 100644 index 0000000..4512dfb --- /dev/null +++ b/openVulkanoCpp/Base/Render/IRenderer.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include "../ITickable.hpp" +#include "../ICloseable.hpp" +#include "../../Scene/Scene.hpp" + +namespace openVulkanoCpp +{ + class IWindow; + class IGraphicsAppManager; + + class IRenderer : virtual public ITickable, virtual public ICloseable + { + public: + virtual ~IRenderer() = default; + + virtual void Init(IGraphicsAppManager* graphicsAppManager, IWindow* window) = 0; + + virtual std::string GetMainRenderDeviceName() = 0; + virtual void Resize(uint32_t newWidth, uint32_t newHeight) = 0; + + virtual void SetScene(Scene::Scene* scene) = 0; + virtual Scene::Scene* GetScene() = 0; + }; +} diff --git a/openVulkanoCpp/Base/Timer.hpp b/openVulkanoCpp/Base/Timer.hpp new file mode 100644 index 0000000..24c4cb0 --- /dev/null +++ b/openVulkanoCpp/Base/Timer.hpp @@ -0,0 +1,98 @@ +#pragma once +#include +#include + +namespace openVulkanoCpp +{ + /** + * \brief High-res timer + */ + class Timer + { //TODO maybe add a Windows option that uses QPC https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps + std::chrono::high_resolution_clock::time_point tPrev, tStop; + int64_t tickNanoseconds, tickMilliseconds; + uint64_t totalNanoseconds; + double tickSeconds, totalSeconds; + bool stopped; + + public: + Timer() + { + Reset(); + } + + ~Timer() = default; + + void Reset() + { + tickNanoseconds = 0; + tickMilliseconds = 0; + tickSeconds = 0; + totalNanoseconds = 0; + totalSeconds = 0; + tPrev = std::chrono::high_resolution_clock::now(); + stopped = false; + } + + void Start() + { + if (stopped) + { + tPrev += std::chrono::high_resolution_clock::now() - tStop; + stopped = false; + } + } + + void Stop() + { + tStop = std::chrono::high_resolution_clock::now(); + stopped = true; + } + + /** + * \brief Will update the timer + */ + void Tick() + { + if (stopped) + { + tickNanoseconds = 0; + } + else + { + const auto now = std::chrono::high_resolution_clock::now(); + tickNanoseconds = std::chrono::duration(now - tPrev).count(); + tPrev = now; + if (tickNanoseconds < 0) tickNanoseconds = 0; + } + totalNanoseconds += tickNanoseconds; + tickMilliseconds = tickNanoseconds / 1000000; + tickSeconds = tickNanoseconds / 1000000000.0; + totalSeconds += tickSeconds; + } + + int64_t GetTickNanoseconds() const { return tickNanoseconds; } + + int64_t GetTickMilliseconds() const { return tickMilliseconds; } + + double GetTickSeconds() const { return tickSeconds; } + + uint64_t GetTotalNanoseconds() const { return totalNanoseconds; } + + /** + * \brief Gets the total amount of seconds past since the timer has been started. This will drift over time! + * \return The summed total runtime of the timer. + */ + double GetTotalSeconds() const { return totalSeconds; } + + /** + * \brief Will recalculate the past time from the total nanoseconds and return it. This is more precise but also slower. + * \return The calculated total runtime of the timer. + */ + double GetTotalSecondsPrecise() + { + totalSeconds = totalNanoseconds / 1000000000.0; + return totalSeconds; + } + }; +} diff --git a/openVulkanoCpp/Base/UI/BaseWindow.hpp b/openVulkanoCpp/Base/UI/BaseWindow.hpp new file mode 100644 index 0000000..b6291f0 --- /dev/null +++ b/openVulkanoCpp/Base/UI/BaseWindow.hpp @@ -0,0 +1,99 @@ +#pragma once +#include "IWindow.hpp" + +namespace openVulkanoCpp +{ + class BaseWindow : virtual public IWindow + { + const int windowId; + public: + BaseWindow() : windowId(CreateWindowId()) {} + virtual ~BaseWindow() = default; + + void GetSize(int* width, int* height) override = 0; + + void GetSize(uint32_t* width, uint32_t* height) override + { + int w, h; + GetSize(&w, &h); + *width = w; + *height = h; + } + + uint32_t GetWidth() override + { + uint32_t width, height; + GetSize(&width, &height); + return width; + } + + uint32_t GetHeight() override + { + uint32_t width, height; + GetSize(&width, &height); + return height; + } + + glm::ivec2 GetSize() override + { + glm::ivec2 size; + this->GetSize(&size.x, &size.y); + return size; + } + + void SetSize(uint32_t width, uint32_t height) override = 0; + + void SetSize(glm::ivec2 size) override + { + SetSize(size.x, size.y); + } + + void GetPosition(int* x, int* y) override = 0; + + int GetPositionX() override + { + int x, y; + GetPosition(&x, &y); + return x; + } + + int GetPositionY() override + { + int x, y; + GetPosition(&x, &y); + return y; + } + + + glm::ivec2 GetPosition() override + { + glm::ivec2 position; + GetPosition(&position.x, &position.y); + return position; + } + + void SetPosition(int posX, int posY) override = 0; + + void SetPosition(glm::ivec2 pos) override { SetPosition(pos.x, pos.y); } + + void Show() override = 0; + void Hide() override = 0; + + void Show(const bool show) override { if (show) Show(); else Hide(); } + + IVulkanWindow* GetVulkanWindow() override + { + return nullptr; + } + + IOpenGlWindow* GetOpenGlWindow() override + { + return nullptr; + } + + int GetWindowId() const override + { + return windowId; + } + }; +} diff --git a/openVulkanoCpp/Base/UI/IWindow.hpp b/openVulkanoCpp/Base/UI/IWindow.hpp new file mode 100644 index 0000000..d7e0472 --- /dev/null +++ b/openVulkanoCpp/Base/UI/IWindow.hpp @@ -0,0 +1,113 @@ +#pragma once +#include +#include +#include +#include +#include "../PlatformEnums.hpp" +#include "../ITickable.hpp" +#include "../ICloseable.hpp" + +namespace openVulkanoCpp +{ + enum WindowMode + { + WINDOWED, BORDERLESS, FULLSCREEN, BORDERLESS_FULLSCREEN + }; + + class IWindowHandler; + class IVulkanWindow; + class IOpenGlWindow; + + class IWindow : public ITickable, public ICloseable + { + public: + virtual ~IWindow() = default; + + virtual void Init(RenderAPI::RenderApi renderApi) = 0; + + virtual const std::string& GetTitle() = 0; + virtual void SetTitle(const std::string& title) = 0; + + virtual WindowMode GetWindowMode() = 0; + virtual void SetWindowMode(WindowMode) = 0; + virtual void SetFullscreen() { SetWindowMode(FULLSCREEN); } + virtual void SetWindowed() { SetWindowMode(WINDOWED); } + + virtual uint32_t GetWidth() = 0; + virtual uint32_t GetHeight() = 0; + virtual void GetSize(int* width, int* height) = 0; + virtual void GetSize(uint32_t* width, uint32_t* height) = 0; + virtual glm::ivec2 GetSize() = 0; + virtual void SetSize(uint32_t width, uint32_t height) = 0; + virtual void SetSize(glm::ivec2 size) { SetSize(size.x, size.y); } + virtual void SetSizeLimits(int minWidth, int minHeight, int maxWidth, int maxHeight) = 0; + + virtual int GetPositionX() = 0; + virtual int GetPositionY() = 0; + virtual void GetPosition(int* x, int* y) = 0; + virtual glm::ivec2 GetPosition() = 0; + virtual void SetPosition(int posX, int posY) = 0; + virtual void SetPosition(glm::ivec2 pos) = 0; + + virtual void Show() = 0; + virtual void Hide() = 0; + virtual void Show(bool show) = 0; + + virtual IWindowHandler* GetWindowHandler() = 0; + virtual void SetWindowHandler(IWindowHandler* handler) = 0; + + /** + * \brief Gets the vulkan window implementation of the window. + * \return The IVulkanWindow reference of the window. nullptr if the current Window dose not implement IVulkanWindow + */ + virtual IVulkanWindow* GetVulkanWindow() = 0; + virtual IOpenGlWindow* GetOpenGlWindow() = 0; + + virtual int GetWindowId() const = 0; + + protected: + static int CreateWindowId() + { + static int id = 0; + return id++; + } + }; + + class IVulkanWindow : virtual public IWindow + { + public: + virtual ~IVulkanWindow() = default; + + virtual vk::SurfaceKHR CreateSurface(const vk::Instance& instance, const vk::AllocationCallbacks* pAllocator = nullptr) = 0; + virtual std::vector GetRequiredInstanceExtensions() = 0; + }; + + class IOpenGlWindow : virtual public IWindow + { + public: + virtual ~IOpenGlWindow() = default; + + virtual void MakeCurrentThread() = 0; + virtual void Present() const = 0; + }; + + class IWindowHandler + { + public: + virtual ~IWindowHandler() = default; + + virtual void OnWindowMinimize(IWindow* window) = 0; + virtual void OnWindowRestore(IWindow* window) = 0; + virtual void OnWindowFocusLost(IWindow* window) = 0; + virtual void OnWindowFocusGained(IWindow* window) = 0; + virtual void OnWindowMove(IWindow* window, int posX, int posY) = 0; + virtual void OnWindowResize(IWindow* window, uint32_t newWidth, uint32_t newHeight) = 0; + virtual void OnWindowClose(IWindow* window) = 0; + }; + + class WindowInitFailedException : public std::runtime_error + { + public: + WindowInitFailedException(char const* const message) : runtime_error(message) {} + }; +} diff --git a/openVulkanoCpp/Base/Utils.hpp b/openVulkanoCpp/Base/Utils.hpp new file mode 100644 index 0000000..4f89534 --- /dev/null +++ b/openVulkanoCpp/Base/Utils.hpp @@ -0,0 +1,56 @@ +#pragma once +#include +#include +#include +#include + +namespace openVulkanoCpp +{ + class Utils + { + public: + static std::vector toCString(const std::vector& values) + { + std::vector result; + result.reserve(values.size()); + for (const auto& string : values) { + result.push_back(string.c_str()); + } + return result; + } + + static std::vector toCString(const std::set& values) + { + std::vector result; + result.reserve(values.size()); + for (const auto& string : values) { + result.push_back(string.c_str()); + } + return result; + } + + template + static bool Contains(std::vector& vec, const T& element) + { + return (std::find(vec.begin(), vec.end(), element) != vec.end()); + } + + template + static void Remove(std::vector& vec, const T& element) + { + vec.erase(std::remove(vec.begin(), vec.end(), element), vec.end()); + } + + template + static auto EnumAsInt(Enumeration const value) + -> typename std::underlying_type::type + { + return static_cast::type>(value); + } + + static bool MatchesAnyElementWise(const glm::vec3& a, const glm::vec3& b) + { + return a.x == b.x || a.y == b.y || a.z == b.z; + } + }; +} diff --git a/openVulkanoCpp/Data/ReadOnlyAtomicArrayQueue.hpp b/openVulkanoCpp/Data/ReadOnlyAtomicArrayQueue.hpp new file mode 100644 index 0000000..d0cdceb --- /dev/null +++ b/openVulkanoCpp/Data/ReadOnlyAtomicArrayQueue.hpp @@ -0,0 +1,44 @@ +#pragma once +#include +#include + +namespace openVulkanoCpp +{ + namespace Data + { + template + class ReadOnlyAtomicArrayQueue final + { + T* data; + std::atomic size; + + public: + ReadOnlyAtomicArrayQueue(std::vector& data) + { + this->data = data.data(); + size.store(data.size()); + } + + ReadOnlyAtomicArrayQueue(T* data, size_t size) + { + this->data = data; + this->size.store(size); + } + + ~ReadOnlyAtomicArrayQueue() = default; + + size_t GetSize() const + { + return size.load(std::memory_order_relaxed); + } + + T* Pop() + { + size_t s = size.load(std::memory_order_relaxed); + while (size > 0 && !size.compare_exchange_weak(s, s - 1)); + if (s > 0) return &data[s - 1]; + return nullptr; + } + }; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Host/GraphicsAppManager.hpp b/openVulkanoCpp/Host/GraphicsAppManager.hpp new file mode 100644 index 0000000..ad149e0 --- /dev/null +++ b/openVulkanoCpp/Host/GraphicsAppManager.hpp @@ -0,0 +1,227 @@ +#pragma once +#include +#include +#include +#include +#include "../Base/IGraphicsAppManager.hpp" +#include "../Base/UI/IWindow.hpp" +#include "../Base/IGraphicsApp.hpp" +#include "../Base/PlatformEnums.hpp" +#include "../Base/Logger.hpp" +#include "../Base/Timer.hpp" +#include "../Base/Render/IRenderer.hpp" +#include "PlatformProducer.hpp" + +namespace openVulkanoCpp +{ + /** + * \brief A simple GraphicsAppManager. It can only handle on window. + */ + class GraphicsAppManager : virtual public IGraphicsAppManager, virtual public IWindowHandler + { + private: + IWindow* window; + IGraphicsApp* app; + IRenderer* renderer; + RenderAPI::RenderApi renderApi; + bool paused = false, running = false; + float fpsTimer = 0, avgFps = 0, avgFrameTime = 0; + uint64_t frameCount = 0, lastFrameCount = 0; + Timer* frameTimer; + std::string windowTitleFormat; + + public: + explicit GraphicsAppManager(IGraphicsApp* app, RenderAPI::RenderApi renderApi = RenderAPI::VULKAN) : app(app), renderApi(renderApi) + { + if (renderApi >= RenderAPI::MAX_VALUE) throw std::runtime_error("Invalid RenderAPI"); + Logger::SetupLogger(); + if (!app) + { + const auto msg = "The app must not be null!"; + Logger::MANAGER->error(msg); + throw std::runtime_error(msg); + } + window = PlatformProducer::CreateBestWindow(renderApi); + renderer = PlatformProducer::CreateRenderManager(renderApi); + app->SetGraphicsAppManager(this); + window->SetWindowHandler(this); + frameTimer = new Timer(); + } + + ~GraphicsAppManager() override + { + delete renderer; + delete window; + delete frameTimer; + } + + public: // Getter + RenderAPI::RenderApi GetRenderApi() const override + { + return renderApi; + } + + IGraphicsApp* GetGraphicsApp() const override + { + return app; + } + + IRenderer* GetRenderer() const override + { + return renderer; + } + + bool IsRunning() const override + { + return running; + } + + bool IsPaused() const override + { + return paused; + } + + public: // Setter + void Stop() override + { + running = false; + Logger::MANAGER->info("Graphics application stopped"); + } + + void Pause() override + { + paused = true; + frameTimer->Stop(); + Logger::MANAGER->info("Graphics application paused"); + } + + void Resume() override + { + paused = false; + frameTimer->Start(); + Logger::MANAGER->info("Graphics application resumed"); + } + + public: + void Run() override + { + running = true; + StartUp(); + frameTimer->Reset(); + Loop(); // Runs the rendering loop + ShutDown(); + } + + private: + void StartUp() + { + try + { + Logger::MANAGER->info("Initializing ..."); + app->Init(); + window->Init(renderApi); + //TODO restore window settings if there are any set + renderer->Init((IGraphicsAppManager*)this, window); + windowTitleFormat = app->GetAppName() + " " + app->GetAppVersion() + " - " + renderer->GetMainRenderDeviceName() + " - {:.1f} fps ({:.1f} ms)"; + Logger::MANAGER->info("Initialized"); + } + catch (std::exception& e) + { + Logger::MANAGER->error("Failed to initiate: {0}", e.what()); + running = false; +#ifdef DEBUG + throw e; +#endif + } + } + + void Loop() + { + while (running) + { + window->Tick(); + if (paused) + { // The rendering is paused + // No need to burn cpu time if the app is paused + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + else + { + app->Tick(); + renderer->Tick(); + frameTimer->Tick(); + UpdateFps(); + } + } + } + + void ShutDown() const + { + Logger::MANAGER->info("Shutting down ..."); + renderer->Close(); + window->Close(); + app->Close(); + Logger::MANAGER->info("Shutdown complete"); + } + + void UpdateFps() + { + frameCount++; + fpsTimer += frameTimer->GetTickSeconds(); + + if(fpsTimer > 1.0f) + { + avgFps = static_cast(frameCount - lastFrameCount) / fpsTimer; + avgFrameTime = (1 / avgFps) * 1000.0f; + lastFrameCount = frameCount; + fpsTimer = 0; + window->SetTitle(fmt::format(windowTitleFormat, avgFps, avgFrameTime)); + } + } + + public: //FPS stuff + uint64_t GetFrameCount() const override + { + return frameCount; + } + + float GetAvgFrameTime() const override + { + return avgFrameTime; + } + + float GetAvgFps() const override + { + return avgFps; + } + + public: // Window Manager + void OnWindowMinimize(IWindow* window) override + { + if (window != this->window) return; + Pause(); + } + + void OnWindowRestore(IWindow* window) override + { + if (window != this->window) return; + Resume(); + } + + void OnWindowFocusLost(IWindow* window) override {} + void OnWindowFocusGained(IWindow* window) override {} + void OnWindowMove(IWindow* window, int posX, int posY) override {} //TODO save window pos + + void OnWindowResize(IWindow* window, const uint32_t newWidth, const uint32_t newHeight) override + { + if(window != this->window) return; + renderer->Resize(newWidth, newHeight); + } + + void OnWindowClose(IWindow* window) override + { + if (window != this->window) return; + Stop(); + } + }; +} diff --git a/openVulkanoCpp/Host/PlatformProducer.hpp b/openVulkanoCpp/Host/PlatformProducer.hpp new file mode 100644 index 0000000..539d6e8 --- /dev/null +++ b/openVulkanoCpp/Host/PlatformProducer.hpp @@ -0,0 +1,51 @@ +#pragma once +#include +#include "../Base/Logger.hpp" +#include "../Vulkan/Renderer.hpp" +#include "../Base/PlatformEnums.hpp" +#include "WindowGLFW.hpp" + +namespace openVulkanoCpp +{ + /** + * \brief Helper class the produces all the platform depending classes + */ + class PlatformProducer + { + public: + + /** + * \brief Creates the renderer for the given render api + * \param renderApi The render api that should be used + * \return The created Renderer. + * \throws std::runtime_error if the render api is not supported + */ + static IRenderer* CreateRenderManager(RenderAPI::RenderApi renderApi) + { + switch (renderApi) + { + case RenderAPI::VULKAN: return new Vulkan::Renderer(); + default: + Logger::RENDER->error("Unsupported render api requested! Requested %d", static_cast(renderApi)); + throw std::runtime_error("Unsupported render api requested!"); + } + } + + /** + * \brief Creates a window that fits best for the current environment + * \param renderApi The render api that should be used when searching for the best suited window + * \return The created window. nullptr if no window is supported on the current platform + * \throws std::runtime_error if the render api is not supported + */ + static IWindow* CreateBestWindow(RenderAPI::RenderApi renderApi) + { //TODO add more windows to chose from + switch(renderApi) + { + case RenderAPI::VULKAN: return new WindowGLFW(); + default: + Logger::RENDER->error("Unsupported render api requested! Requested %d", static_cast(renderApi)); + throw std::runtime_error("Unsupported render api requested!"); + } + } + }; +} diff --git a/openVulkanoCpp/Host/WindowGLFW.hpp b/openVulkanoCpp/Host/WindowGLFW.hpp new file mode 100644 index 0000000..8fc7c40 --- /dev/null +++ b/openVulkanoCpp/Host/WindowGLFW.hpp @@ -0,0 +1,374 @@ +#pragma once +#include +#include "../Base/UI/BaseWindow.hpp" +#include "../Base/Logger.hpp" + +namespace openVulkanoCpp +{ + class WindowGLFW : public BaseWindow, virtual public IVulkanWindow, virtual public IOpenGlWindow + { + private: + GLFWwindow* window = nullptr; + uint32_t width = 1280, height = 720; + std::string title = "Window Title"; + WindowMode windowMode = WINDOWED; + IWindowHandler* handler = nullptr; + + public: + WindowGLFW() = default; + virtual ~WindowGLFW() { if (window != nullptr) Close(); } + + protected: + void Create() + { + window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + if(!window) return; + glfwSetWindowUserPointer(window, this); + RegisterCallbacks(); + } + + void RegisterCallbacks() const + { + glfwSetErrorCallback(ErrorCallback); + + glfwSetDropCallback(window, DropCallback); + glfwSetFramebufferSizeCallback(window, ResizeCallback); + glfwSetWindowFocusCallback(window, FocusCallback); + glfwSetWindowRefreshCallback(window, RefreshCallback); + glfwSetWindowIconifyCallback(window, MinimizeCallback); + glfwSetWindowPosCallback(window, WindowMoveCallback); + glfwSetWindowCloseCallback(window, CloseCallback); + + // Input Callbacks + glfwSetKeyCallback(window, KeyboardCallback); + glfwSetMouseButtonCallback(window, MouseButtonCallback); + glfwSetCursorPosCallback(window, MouseMoveCallback); + glfwSetScrollCallback(window, MouseScrollCallback); + } + + static GLFWmonitor* GetPrimaryMonitor() + { + return glfwGetPrimaryMonitor(); + } + + static std::vector GetMonitors() + { + int count; + GLFWmonitor** monitorsArray = glfwGetMonitors(&count); + std::vector monitors; + monitors.reserve(count); + for (int i = 0; i < count; i++) + { + monitors[i] = monitorsArray[i]; + } + return monitors; + } + + public: // IWindow implementation + void Init(RenderAPI::RenderApi renderApi) override + { + if (!glfwInit()) throw WindowInitFailedException("Failed to initialize glfw"); + if(renderApi == RenderAPI::VULKAN) glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + Create(); + if(!window) + { + glfwTerminate(); + throw WindowInitFailedException("Failed to initialize window"); + } + if (renderApi != RenderAPI::VULKAN) MakeCurrentThread(); + Logger::WINDOW->info("GLFW Window created (id: {0})", GetWindowId()); + } + + void Close() override + { + glfwDestroyWindow(window); + window = nullptr; + glfwTerminate(); + Logger::WINDOW->info("GLFW Window destroyed (id: {0})", GetWindowId()); + } + + void Present() const override + { + glfwSwapBuffers(window); + } + + void Show() override + { + glfwShowWindow(window); + } + + void Hide() override + { + glfwHideWindow(window); + } + + void Tick() override + { + glfwPollEvents(); + } + + void SetTitle(const std::string& title) override + { + this->title = title; + glfwSetWindowTitle(window, title.c_str()); + } + + const std::string& GetTitle() override + { + return title; + } + + void SetSize(uint32_t width, uint32_t height) override + { + if (!window) + { + this->width = width; + this->height = height; + } + else + { + glfwSetWindowSize(window, width, height); + } + } + + void SetPosition(int posX, int posY) override + { + glfwSetWindowPos(window, posX, posY); + } + + void SetSizeLimits(int minWidth, int minHeight, int maxWidth, int maxHeight) override + { + minWidth = (minWidth < 0) ? GLFW_DONT_CARE : minWidth; + minHeight = (minHeight < 0) ? GLFW_DONT_CARE : minHeight; + maxWidth = (maxWidth < 0) ? GLFW_DONT_CARE : maxWidth; + maxHeight = (maxHeight < 0) ? GLFW_DONT_CARE : maxHeight; + glfwSetWindowSizeLimits(window, minWidth, minHeight, maxWidth, maxHeight); + } + + void MakeCurrentThread() override + { + glfwMakeContextCurrent(window); + } + + void SetWindowMode(WindowMode windowMode) override + { + if(windowMode == this->windowMode) return; //Nothing change here + //TODO + this->windowMode = windowMode; + } + + void SetWindowHandler(IWindowHandler* handler) override + { + this->handler = handler; + } + + IVulkanWindow* GetVulkanWindow() override + { + return this; + } + + IOpenGlWindow* GetOpenGlWindow() override + { + return this; + } + + // Status getter + WindowMode GetWindowMode() override + { + return windowMode; + } + + void GetSize(int* width, int* height) override + { + glfwGetWindowSize(window, width, height); + } + + void GetPosition(int* x, int* y) override + { + glfwGetWindowPos(window, x, y); + } + + IWindowHandler* GetWindowHandler() override + { + return handler; + } + + //IVulkanWindow stuff + vk::SurfaceKHR CreateSurface(const vk::Instance& instance, const vk::AllocationCallbacks* pAllocator) override + { + VkSurfaceKHR rawSurface; + const auto result = static_cast(glfwCreateWindowSurface(static_cast(instance), window, reinterpret_cast(pAllocator), &rawSurface)); + return createResultValue(result, rawSurface, "vk::CommandBuffer::begin"); + } + + std::vector GetRequiredInstanceExtensions() override + { + return GetVulkanRequiredInstanceExtensions(); + } + + public: // Window events + void OnResize(const uint32_t newWidth, const uint32_t newHeight) + { + Logger::WINDOW->debug("Window (id: {0}) resized (width: {1}, height: {2})", GetWindowId(), newWidth, newHeight); + handler->OnWindowResize(this, newWidth, newHeight); + } + + void OnMinimize() + { + Logger::WINDOW->debug("Window (id: {0}) minimized", GetWindowId()); + handler->OnWindowMinimize(this); + } + + void OnRestore() + { + Logger::WINDOW->debug("Window (id: {0}) restored", GetWindowId()); + handler->OnWindowRestore(this); + } + + void OnFocusLost() + { + Logger::WINDOW->debug("Window (id: {0}) focus lost", GetWindowId()); + handler->OnWindowFocusLost(this); + } + + void OnFocusGained() + { + Logger::WINDOW->debug("Window (id: {0}) focus gained", GetWindowId()); + handler->OnWindowFocusGained(this); + } + + void OnMove(const int posX, const int posY) + { + Logger::WINDOW->debug("Window (id: {0}) moved (x: {1}, y: {2})", GetWindowId(), posX, posY); + handler->OnWindowMove(this, posX, posY); + } + + void OnClose() + { + Logger::WINDOW->debug("Window (id: {0}) closed", GetWindowId()); + handler->OnWindowClose(this); + } + + public: // Input events TODO + virtual void OnKeyPressed(int key, int mods) {} + virtual void OnKeyReleased(int key, int mods) {} + virtual void OnMousePressed(int button, int mods) {} + virtual void OnMouseReleased(int button, int mods) {} + virtual void OnMouseMoved(double posX, double posY) {} + virtual void OnMouseScrolled(double delta) {} + + protected: + virtual void OnKeyEvent(int key, int scanCode, int action, int mods) + { + switch (action) + { + case GLFW_PRESS: OnKeyPressed(key, mods); break; + case GLFW_RELEASE: OnKeyReleased(key, mods); break; + default: break; + } + } + + virtual void OnMouseButtonEvent(int button, int action, int mods) + { + switch (action) + { + case GLFW_PRESS: OnMousePressed(button, mods); break; + case GLFW_RELEASE: OnMouseReleased(button, mods); break; + default: break; + } + } + + private: // Callbacks + static WindowGLFW* GetWindow(GLFWwindow* window) + { + return static_cast(glfwGetWindowUserPointer(window)); + } + + static void KeyboardCallback(GLFWwindow* window, int key, int scanCode, int action, int mods) + { + GetWindow(window)->OnKeyEvent(key, scanCode, action, mods); + } + + static void MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) + { + GetWindow(window)->OnMouseButtonEvent(button, action, mods); + } + + static void MouseMoveCallback(GLFWwindow* window, double posX, double posY) + { + GetWindow(window)->OnMouseMoved(posX, posY); + } + + static void MouseScrollCallback(GLFWwindow* window, double xOffset, double yOffset) + { + GetWindow(window)->OnMouseScrolled(yOffset); + } + + static void ResizeCallback(GLFWwindow* window, int width, int height) + { + GetWindow(window)->OnResize(width, height); + } + + static void FocusCallback(GLFWwindow* window, const int focused) + { + if (focused == GLFW_TRUE) + GetWindow(window)->OnFocusGained(); + else + GetWindow(window)->OnFocusLost(); + } + + static void MinimizeCallback(GLFWwindow* window, const int minimized) + { + if(minimized == GLFW_TRUE) + GetWindow(window)->OnMinimize(); + else + GetWindow(window)->OnRestore(); + } + + static void RefreshCallback(GLFWwindow* window) + { + //TODO is there really anything to do? or is it ok if the window is only redrawn on the next frame? + } + + static void WindowMoveCallback(GLFWwindow* window, const int posX, const int posY) + { + GetWindow(window)->OnMove(posX, posY); + } + + static void CloseCallback(GLFWwindow* window) + { + GetWindow(window)->OnClose(); + } + + static void DropCallback(GLFWwindow* window, const int count, const char** paths) + { + //TODO something useful + } + + static void ErrorCallback(const int error, const char* description) + { + Logger::WINDOW->error("GLFW error (e{0}): {1}", error, description); + } + + + + + public: + static std::vector GetVulkanRequiredInstanceExtensions() + { + std::vector result; + uint32_t count = 0; + const char** names = glfwGetRequiredInstanceExtensions(&count); + if (names && count) + { + for (uint32_t i = 0; i < count; ++i) + { + result.emplace_back(names[i]); + } + } + return result; + } + + + }; +} diff --git a/openVulkanoCpp/Scene/AABB.hpp b/openVulkanoCpp/Scene/AABB.hpp new file mode 100644 index 0000000..d6a8135 --- /dev/null +++ b/openVulkanoCpp/Scene/AABB.hpp @@ -0,0 +1,98 @@ +#pragma once +#include +#include "../Base/IInitable.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + /** + * \brief A class that represents an axis aligned bounding box + */ + class AABB final : public virtual IInitable + { + glm::vec3 min, max; + + public: + AABB() : min(INFINITY), max(-INFINITY) {} + ~AABB() = default; + + /** + * \brief Initiates the AABB to min=Inf, max=-Inf + */ + void Init() override + { + min = glm::vec3(INFINITY); + max = glm::vec3(-INFINITY); + } + + /** + * \brief Initiates the AABB to a single point (min=max=point) + * \param point The point that should be used as min and max of the AABB + */ + void Init(const glm::vec3& point) + { + min = max = point; + } + + /** + * \brief Initiates the AABB from some other AABB + * \param other The other AABB that should be copied + */ + void Init(const AABB& other) + { + min = other.GetMin(); + max = other.GetMax(); + } + + const glm::vec3& GetMin() const { return min; } + + const glm::vec3& GetMax() const { return max; } + + void Grow(const glm::vec3& point) + { + min = glm::min(min, point); + max = glm::max(max, point); + } + + void Grow(const AABB& otherAABB) + { + min = glm::min(min, otherAABB.GetMin()); + max = glm::max(max, otherAABB.GetMax()); + } + + void Grow(const AABB& otherAABB, glm::mat4x4 transformation) + { + //TODO + } + + glm::vec3 GetDiagonal() const + { + return max - min; + } + + glm::vec3 GetCenter() const + { + return min + (GetDiagonal() * 0.5f); + } + + /** + * \brief Checks if the AABB overlaps with an other AABB + * \param other The other AABB that should be checked + * \return true if the AABB overlaps with the other, false if not + */ + bool IsOverlapping(const AABB& other) const + { + return !(other.min.x > max.x || other.max.x < min.x || other.min.y > max.y || other.max.y < min.y || other.min.z > max.z || other.max.z < min.z); + } + + /** + * \brief Resets the AABB to min=Inf, max=-Inf, same as Init() + */ + void Reset() + { + Init(); + } + }; + } +} diff --git a/openVulkanoCpp/Scene/Camera.hpp b/openVulkanoCpp/Scene/Camera.hpp new file mode 100644 index 0000000..e695f1c --- /dev/null +++ b/openVulkanoCpp/Scene/Camera.hpp @@ -0,0 +1,169 @@ +#pragma once +#define _USE_MATH_DEFINES +#include +#include +#include "Node.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + class Camera : public Node + { + protected: + float nearPlane, farPlane; + float width, height; + public: + glm::mat4x4 projection, view, viewProjection; + + Camera() = default; + virtual ~Camera() = default; + + public: + void Init(float width, float height, float nearPlane, float farPlane) + { + this->width = width; + this->height = height; + this->nearPlane = nearPlane; + this->farPlane = farPlane; + Node::Init(); + UpdateProjectionMatrix(); + } + + virtual void SetSize(const float& width, const float& height) + { + this->width = width; + this->height = height; + UpdateProjectionMatrix(); + } + + void SetNearPlane(float nearPlane) + { + this->nearPlane = nearPlane; + } + + void SetFarPlane(float farPlane) + { + this->farPlane = farPlane; + } + + + float NearPlane() const + { + return nearPlane; + } + + float FarPlane() const + { + return farPlane; + } + + virtual void UpdateProjectionMatrix() = 0; + + void UpdateViewProjectionMatrix() + { // In vulkan the screen space is defined as y=0=top and y=1=bottom and thus the coordinate have to be flipped + viewProjection = projection * glm::mat4x4(1,0,0,0,0,-1,0,0,0,0,1,0,0,0,0,1) * view; + } + + void UpdateWorldMatrix(const glm::mat4x4& parentWorldMat) override + { + Node::UpdateWorldMatrix(parentWorldMat); + view = glm::inverse(GetWorldMatrix()); + UpdateViewProjectionMatrix(); + } + + const glm::mat4x4& GetViewProjectionMatrix() const + { + return viewProjection; + } + + const glm::mat4x4* GetViewProjectionMatrixPointer() const + { + return &viewProjection; + } + }; + + class PerspectiveCamera : public Camera + { + protected: + float fov, aspect; + + public: + void Init(float fovDegrees, float width, float height, float nearPlane, float farPlane) + { + this->fov = glm::radians(fovDegrees); + aspect = width / height; + Camera::Init(width, height, nearPlane, farPlane); + } + + void SetSize(const float& width, const float& height) override + { + aspect = width / height; + Camera::SetSize(width, height); + } + + void SetAspect(const float& aspect) + { + this->aspect = aspect; + Camera::SetSize(aspect, 1); + } + + void SetFovX(const float& fov) + { + SetFov(2 * atan(tan(fov * 0.5f) * aspect)); + } + + void SetFovXRad(const float& fov) + { + SetFovRad(2 * atan(tan(fov * 0.5f) * aspect)); + } + + void SetFov(const float& fov) + { + SetFovRad(glm::radians(fov)); + } + + void SetFovRad(const float& fov) + { + this->fov = fov; + } + + float GetFov() const + { + return glm::degrees(fov); + } + + float GetFovX() const + { + return 2 * atan(tan(GetFov() * 0.5f) * aspect); + } + + float GetFovRad() const + { + return fov; + } + + float GetFovXRad() const + { + return 2 * atan(tan(fov * 0.5f) * aspect); + } + + void UpdateProjectionMatrix() override + { + projection = glm::perspectiveLH_ZO(fov, aspect, nearPlane, farPlane); + UpdateViewProjectionMatrix(); + } + }; + + class OrthographicCamera : public Camera + { + public: + void UpdateProjectionMatrix() override + { + const float widthHalf = width * 0.5f, heightHalf = height * 0.5f; + projection = glm::orthoLH_ZO(-widthHalf, widthHalf, -heightHalf, heightHalf, nearPlane, farPlane); + UpdateViewProjectionMatrix(); + } + }; + } +} diff --git a/openVulkanoCpp/Scene/Drawable.cpp b/openVulkanoCpp/Scene/Drawable.cpp new file mode 100644 index 0000000..214d945 --- /dev/null +++ b/openVulkanoCpp/Scene/Drawable.cpp @@ -0,0 +1,26 @@ +#include "Drawable.hpp" +#include "Scene.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + void Drawable::SetScene(Scene* scene) + { + if (this->scene == scene) return; + if (scene && this->scene) throw std::runtime_error("Drawable has been associated with a scene already!"); + this->scene = scene; + if(scene) scene->RegisterDrawable(this); + } + + void Drawable::RemoveNode(Node* node) + { + Utils::Remove(nodes, node); + if (nodes.empty()) + { + scene = nullptr; + scene->RemoveDrawable(this); + } + } + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/Drawable.hpp b/openVulkanoCpp/Scene/Drawable.hpp new file mode 100644 index 0000000..cff58bf --- /dev/null +++ b/openVulkanoCpp/Scene/Drawable.hpp @@ -0,0 +1,82 @@ +#pragma once +#include +#include "../Base/ICloseable.hpp" +#include "Geometry.hpp" +#include "Material.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + class Node; + class Scene; + + struct Drawable : virtual public ICloseable + { + std::vector nodes; + Scene* scene = nullptr; + Geometry* mesh = nullptr; + Material* material = nullptr; + + public: + Drawable() = default; + + explicit Drawable(const Drawable* toCopy) + { + mesh = toCopy->mesh; + material = toCopy->material; + } + + virtual ~Drawable() + { + if(mesh) Drawable::Close(); + } + + Drawable* Copy() const + { + return new Drawable(this); + } + + void Init(Geometry* mesh, Material* material) + { + if (this->mesh || this->material) throw std::runtime_error("Drawable is already initialized."); + this->mesh = mesh; + this->material = material; + } + + void Init(Drawable* drawable) + { + if (mesh || material) throw std::runtime_error("Drawable is already initialized."); + this->mesh = drawable->mesh; + this->material = drawable->material; + } + + void Close() override + { + if (!nodes.empty()) throw std::runtime_error("Drawable is still being used!!!"); + mesh = nullptr; + material = nullptr; + } + + Scene* GetScene() const + { + return scene; + } + + private: + friend class Node; + friend class Scene; + + void AddNode(Node* node) + { + if (!mesh) throw std::runtime_error("Drawable is not initialized."); + if (Utils::Contains(nodes, node)) throw std::runtime_error("A drawable must not use the same node more than once."); + nodes.push_back(node); + } + + void SetScene(Scene* scene); + + void RemoveNode(Node* node); + }; + } +} diff --git a/openVulkanoCpp/Scene/Geometry.hpp b/openVulkanoCpp/Scene/Geometry.hpp new file mode 100644 index 0000000..9020f2f --- /dev/null +++ b/openVulkanoCpp/Scene/Geometry.hpp @@ -0,0 +1,206 @@ +#pragma once +#include +#include +#include +#include +#include +#include "Vertex.hpp" +#include "../Base/Logger.hpp" +#include "../Base/Utils.hpp" +#include "../Base/ICloseable.hpp" +#include "AABB.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + enum class VertexIndexType + { + UINT16 = sizeof(uint16_t), UINT32 = sizeof(uint32_t) + }; + + struct Geometry : public virtual ICloseable + { + uint32_t vertexCount = 0, indexCount = 0; + Vertex* vertices; + void* indices; + VertexIndexType indexType; + AABB aabb; + ICloseable* renderGeo = nullptr; + + Vertex* GetVertices() const { return vertices; } + void* GetIndices() const { return indices; } + uint16_t* GetIndices16() const { return static_cast(indices); } + uint32_t* GetIndices32() const { return static_cast(indices); } + uint32_t GetIndexCount() const { return indexCount; } + uint32_t GetVertexCount() const { return vertexCount; } + + static Geometry* LoadFromFile(const std::string file) + { + Geometry* mesh = new Geometry(); + mesh->InitFromFile(file); + return mesh; + } + + Geometry() : vertexCount(0), indexCount(0), vertices(nullptr), indices(nullptr), indexType(VertexIndexType::UINT16) {} + + ~Geometry() + { + if (vertices) Geometry::Close(); + } + + void InitFromFile(const std::string file) + { + Assimp::Importer importer; + + const uint32_t flags = aiProcess_CalcTangentSpace | aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_GenNormals | + aiProcess_ImproveCacheLocality | aiProcess_RemoveRedundantMaterials | aiProcess_GenUVCoords | aiProcess_TransformUVCoords | + aiProcess_ConvertToLeftHanded | aiProcess_PreTransformVertices | aiProcess_OptimizeGraph; + + const aiScene* scene = importer.ReadFile(file, flags); + if (!scene) throw std::runtime_error("Failed to load file \"" + file + "\" Error: " + importer.GetErrorString()); + if (!scene->HasMeshes()) throw std::runtime_error("File \"" + file + "\" does not have any meshes"); + if (scene->mNumMeshes > 1) Logger::DATA->warn("File {0} contains more than one mesh. Only first one will be loaded", file); + Init(scene->mMeshes[0]); + importer.FreeScene(); + } + + /** + * \brief Creates the arrays for the vertices and indices. They will not be filled! + * \param vertexCount The amount of vertices that will be used + * \param indexCount The amount of indices that will be used + */ + void Init(uint32_t vertexCount, uint32_t indexCount) + { + if (this->vertexCount || this->indexCount) throw std::runtime_error("Geometry is already initialized."); + this->vertexCount = vertexCount; + this->indexCount = indexCount; + indexType = (vertexCount > UINT16_MAX) ? VertexIndexType::UINT32 : VertexIndexType::UINT16; + vertices = new Vertex[vertexCount]; + indices = malloc(static_cast(Utils::EnumAsInt(indexType)) * indexCount); + renderGeo = nullptr; + } + + void Init(aiMesh* mesh) + { + aabb.Init(); + Init(mesh->mNumVertices, mesh->mNumFaces * 3); // Reserve the space for the data + for (unsigned int i = 0; i < mesh->mNumVertices; i++) + { + vertices[i].Set(mesh->mVertices[i]); + if (mesh->HasNormals()) vertices[i].SetNormal(mesh->mNormals[i]); + if (mesh->HasTangentsAndBitangents()) + { + vertices[i].SetTangentAndBiTangent(mesh->mTangents[i], mesh->mBitangents[i]); + } + if (mesh->HasTextureCoords(0)) vertices[i].SetTextureCoordinates(mesh->mTextureCoords[0][i]); + if (mesh->HasVertexColors(0)) vertices[i].SetColor(mesh->mColors[0][i]); + aabb.Grow(vertices[i].position); + } + + for (unsigned int i = 0; i < mesh->mNumFaces; i++) + { + const aiFace face = mesh->mFaces[i]; + if (face.mNumIndices != 3) throw std::runtime_error("Mesh is not a triangle mesh!"); + for (unsigned int j = 0; j < face.mNumIndices; j++) + { + if (indexType == VertexIndexType::UINT16) + { + static_cast(indices)[i * face.mNumIndices + j] = static_cast(face.mIndices[j]); + } + else + { + static_cast(indices)[i * face.mNumIndices + j] = face.mIndices[j]; + } + } + } + + //TODO load bones + //TODO load materials + } + + void InitCube(float x = 1, float y = 1, float z = 1, glm::vec4 color = glm::vec4(1)) + { + Init(24, 36); + SetIndices(new uint32_t[indexCount]{ + 0, 1, 2, 0, 2, 3, // front face index data + 4, 5, 6, 4, 6, 7, // back face index data + 8, 9, 10, 8, 10, 11, // top face index data + 12, 13, 14, 12, 14, 15, // bottom face index data + 16, 17, 18, 16, 18, 19, // left face index data + 20, 21, 22, 20, 22, 23 // right face index data + }, indexCount); + x *= 0.5f; y *= 0.5f; z *= 0.5f; + int i = 0; + // front face vertex data + vertices[i++].Set(-x, +y, -z, +0, +0, -1, +0, +0); + vertices[i++].Set(-x, -y, -z, +0, +0, -1, +0, +1); + vertices[i++].Set(+x, -y, -z, +0, +0, -1, +1, +1); + vertices[i++].Set(+x, +y, -z, +0, +0, -1, +1, +0); + // back face vertex data + vertices[i++].Set(-x, +y, +z, +0, +0, +1, +1, +0); + vertices[i++].Set(+x, +y, +z, +0, +0, +1, +0, +0); + vertices[i++].Set(+x, -y, +z, +0, +0, +1, +0, +1); + vertices[i++].Set(-x, -y, +z, +0, +0, +1, +1, +1); + // top face vertex data + vertices[i++].Set(-x, -y, -z, +0, +1, +0, +0, +0); + vertices[i++].Set(-x, -y, +z, +0, +1, +0, +0, +1); + vertices[i++].Set(+x, -y, +z, +0, +1, +0, +1, +1); + vertices[i++].Set(+x, -y, -z, +0, +1, +0, +1, +0); + // bottom face vertex data + vertices[i++].Set(-x, +y, -z, +0, -1, +0, +1, +0); + vertices[i++].Set(+x, +y, -z, +0, -1, +0, +0, +0); + vertices[i++].Set(+x, +y, +z, +0, -1, +0, +0, +1); + vertices[i++].Set(-x, +y, +z, +0, -1, +0, +1, +1); + // Fill in the left face vertex data + vertices[i++].Set(-x, +y, +z, -1, +0, +0, +0, +0); + vertices[i++].Set(-x, -y, +z, -1, +0, +0, +0, +1); + vertices[i++].Set(-x, -y, -z, -1, +0, +0, +1, +1); + vertices[i++].Set(-x, +y, -z, -1, +0, +0, +1, +0); + // Fill in the right face vertex data + vertices[i++].Set(+x, +y, -z, +1, +0, +0, +0, +0); + vertices[i++].Set(+x, -y, -z, +1, +0, +0, +0, +1); + vertices[i++].Set(+x, -y, +z, +1, +0, +0, +1, +1); + vertices[i].Set(+x, +y, +z, +1, +0, +0, +1, +0); + + for(i = 0; i < vertexCount; i++) + { + vertices[i].color = color; + } + } + + void SetIndices(const uint32_t* data, uint32_t size, uint32_t offset = 0) const + { + size += offset; + for(; offset < size; offset++) + { + if (indexType == VertexIndexType::UINT16) + { + static_cast(indices)[offset] = static_cast(data[offset]); + } + else + { + static_cast(indices)[offset] = data[offset]; + } + } + } + + void Close() override + { + vertexCount = 0; + indexCount = 0; + Free(); + renderGeo->Close(); + renderGeo = nullptr; + } + + void Free() + { + if(vertices) delete[] vertices; + free(indices); + vertices = nullptr; + indices = nullptr; + } + }; + } +} diff --git a/openVulkanoCpp/Scene/Material.hpp b/openVulkanoCpp/Scene/Material.hpp new file mode 100644 index 0000000..eef38f6 --- /dev/null +++ b/openVulkanoCpp/Scene/Material.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "Shader.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + struct Material + { + Shader* shader; + }; + } +} diff --git a/openVulkanoCpp/Scene/Node.cpp b/openVulkanoCpp/Scene/Node.cpp new file mode 100644 index 0000000..96c7158 --- /dev/null +++ b/openVulkanoCpp/Scene/Node.cpp @@ -0,0 +1,9 @@ +#include "Node.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + const glm::mat4x4 Node::IDENTITY = glm::mat4(1); + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/Node.hpp b/openVulkanoCpp/Scene/Node.hpp new file mode 100644 index 0000000..fe1dd42 --- /dev/null +++ b/openVulkanoCpp/Scene/Node.hpp @@ -0,0 +1,212 @@ +#pragma once +#include +#include +#include +#include "../Base/Utils.hpp" +#include "../Base/IInitable.hpp" +#include "../Base/ICloseable.hpp" +#include "Drawable.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + class Scene; + + enum class UpdateFrequency + { + Always, Sometimes, Never + }; + + struct Node : virtual IInitable, virtual ICloseable + { + friend Scene; + protected: + static const glm::mat4x4 IDENTITY; + public: + glm::mat4x4 localMat, worldMat; + bool enabled = true; + Node* parent = nullptr; + Scene* scene = nullptr; + std::vector children; + std::vector drawables; + UpdateFrequency matrixUpdateFrequency = UpdateFrequency::Never; + ICloseable* renderNode = nullptr; + + public: + Node() = default; + virtual ~Node() = default; + + void Init() override + { + if (parent || scene || !children.empty() || !drawables.empty()) throw std::runtime_error("Node already initialized"); + localMat = worldMat = IDENTITY; + enabled = true; + parent = nullptr; + children = std::vector(); + drawables = std::vector(); + } + + void Close() override + { + children.clear(); + if (renderNode) renderNode->Close(); + parent = nullptr; + scene = nullptr; + enabled = false; + if (!children.empty()) Logger::SCENE->warn("Closing Node that has children!"); + for (Node* child : children) + { + child->SetParent(nullptr); + } + children.clear(); + for(size_t i = drawables.size(); i > 0; i--) + { + RemoveDrawable(drawables[i]); + } + } + + void AddChild(Node* node) + { + node->SetParent(this); + children.push_back(node); + node->UpdateWorldMatrix(worldMat); + } + + void AddChild(Drawable* drawable) + { + AddDrawable(drawable); + } + + void RemoveChild(Node* node) + { + if (node->parent == this) + { + Utils::Remove(children, node); + node->SetParent(nullptr); + } + } + + void RemoveChild(Drawable* drawable) + { + RemoveDrawable(drawable); + } + + void AddDrawable(Drawable* drawable) + { + if (scene) drawable->SetScene(scene); + else if (drawable->GetScene()) Logger::SCENE->warn("Drawable is already associated with a scene, but the node it was added to is not!"); + drawable->AddNode(this); + drawables.push_back(drawable); + } + + void RemoveDrawable(Drawable* drawable) + { + drawable->RemoveNode(this); + Utils::Remove(drawables, drawable); + } + + void SetMatrix(glm::mat4x4 mat) + { + localMat = mat; + UpdateWorldMatrix(parent ? parent->GetWorldMatrix() : IDENTITY); + } + + const glm::mat4x4& GetMatrix() const + { + return localMat; + } + + const glm::mat4x4& GetWorldMatrix() const + { + return worldMat; + } + + bool IsEnabled() const + { + return enabled; + } + + void Enable() + { + enabled = true; + } + + void Disable() + { + enabled = false; + } + + Node* GetParent() const + { + return parent; + } + + Scene* GetScene() const + { + return scene; + } + + bool IsRoot() const + { + return scene && parent == this; + } + + UpdateFrequency GetUpdateFrequency() + { + return matrixUpdateFrequency; + } + + void SetUpdateFrequency(UpdateFrequency frequency) + { + if (!children.empty()) throw std::runtime_error("The update must not be changed for nodes with children."); + this->matrixUpdateFrequency = frequency; + } + + protected: + virtual void UpdateWorldMatrix(const glm::mat4x4& parentWorldMat) + { + worldMat = parentWorldMat * localMat; + for (const auto& node : children) + { + node->UpdateWorldMatrix(worldMat); + } + } + + private: + void SetParent(Node* parent) + { + if (this->parent && parent) throw std::runtime_error("Node already has a parent! Nodes must not be used multiple times!"); + this->parent = parent; + if(parent && parent != this) this->scene = parent->scene; + if (!parent) SetScene(nullptr); + } + + void SetScene(Scene* scene) + { + if (this->scene && scene) throw std::runtime_error("Node already has a scene!"); + this->scene = scene; + for (const auto& node : children) + { + node->SetScene(scene); + } + if (scene) + { + for (size_t i = 0; i < drawables.size(); i++) + { + Scene* drawableScene = drawables[i]->GetScene(); + if(drawableScene) + { + if(drawableScene != scene) + { + Logger::SCENE->warn("Drawable is already associated with a scene! Creating copy."); + drawables[i] = drawables[i]->Copy(); + } + } + drawables[i]->SetScene(scene); + } + } + } + }; + } +} diff --git a/openVulkanoCpp/Scene/Scene.hpp b/openVulkanoCpp/Scene/Scene.hpp new file mode 100644 index 0000000..7674345 --- /dev/null +++ b/openVulkanoCpp/Scene/Scene.hpp @@ -0,0 +1,89 @@ +#pragma once +#include "Node.hpp" +#include "Camera.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + struct Scene : virtual public IInitable, virtual public ICloseable + { + Node* root; + std::vector shapeList; + Shader* shader; + Camera* camera; + + public: + Scene() : root(nullptr) {} + + virtual ~Scene() + { + if (root) Scene::Close(); + } + + void Init() override + { + Node* newRoot = new Node(); + newRoot->Init(); + Init(newRoot); + } + + void Init(Node* root) + { + if (root->GetParent()) throw std::runtime_error("Node has a parent! Only nodes without a parent may be a root node!"); + root->SetScene(this); + root->SetParent(root); + this->root = root; + } + + void Close() override + { + //TODO + } + + Node* GetRoot() const + { + return root; + } + + void RegisterDrawable(Drawable* drawable) + { + if (drawable->GetScene() != this) drawable->SetScene(this); + if (Utils::Contains(shapeList, drawable)) return; // Prevent duplicate entries + shapeList.push_back(drawable); + } + + void RemoveDrawable(Drawable* drawable) + { + Utils::Remove(shapeList, drawable); + drawable->SetScene(nullptr); + } + + void SetCamera(Camera* camera) + { + this->camera = camera; + } + + Camera* GetCamera() const + { + return camera; + } + + /** + * \brief Checks if the scene is valid and attempts to fix problems. + */ + void Validate() + { + for (Drawable* drawable : shapeList) + { + if(drawable->GetScene() != this) + { + if (!drawable->GetScene()) drawable->SetScene(this); + else Logger::SCENE->error("Scene is linked with drawable from different scene!!!"); //TODO handle + } + } + //TODO check node tree + } + }; + } +} diff --git a/openVulkanoCpp/Scene/Shader.hpp b/openVulkanoCpp/Scene/Shader.hpp new file mode 100644 index 0000000..e3e57e3 --- /dev/null +++ b/openVulkanoCpp/Scene/Shader.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include "../Base/ICloseable.hpp" + +namespace openVulkanoCpp +{ + namespace Scene + { + enum class Topology + { + PointList, LineList, LineStripe, TriangleList, TriangleStripe + }; + + struct Shader : public virtual ICloseable + { + std::string vertexShaderName, fragmentShaderName; + Topology topology = Topology::TriangleList; + ICloseable* renderShader = nullptr; + + Shader() = default; + ~Shader() { if (renderShader) Shader::Close(); } + + void Init(const std::string& vertexShaderName, const std::string& fragmentShaderName) + { + if (renderShader) throw std::runtime_error("Shader already initialized!"); + this->vertexShaderName = vertexShaderName; + this->fragmentShaderName = fragmentShaderName; + } + + void Close() override + { + renderShader->Close(); + renderShader = nullptr; + } + }; + } +} diff --git a/openVulkanoCpp/Scene/Vertex.hpp b/openVulkanoCpp/Scene/Vertex.hpp new file mode 100644 index 0000000..1c7c038 --- /dev/null +++ b/openVulkanoCpp/Scene/Vertex.hpp @@ -0,0 +1,178 @@ +#pragma once +#include +#include +#include + +namespace openVulkanoCpp +{ + struct Vertex + { + glm::vec3 position, normal, tangent, biTangent, textureCoordinates; + glm::vec4 color; + + Vertex() = default; + + Vertex(const aiVector3D& pos) : position(pos.x, pos.y, pos.z), normal(), tangent(), biTangent(), textureCoordinates(), color() + {} + + Vertex(const float& x, const float& y, const float& z, const float& nx, const float& ny, const float& nz, const float& u, const float& v) + : position({ x, y, z }), normal({ nx, ny, nz }), tangent(), biTangent(), textureCoordinates({ u, v, 0 }), color() + {} + + Vertex(const glm::vec3& position, const glm::vec3& normal, const glm::vec3& tangent, const glm::vec3& biTangent, const glm::vec2& textureCoordinates) + : position(position), normal(normal), tangent(tangent), biTangent(biTangent), textureCoordinates(textureCoordinates, 0), color() + {} + + Vertex(const glm::vec3 & position, const glm::vec3 & normal, const glm::vec3 & tangent, const glm::vec3 & biTangent, const glm::vec3 & textureCoordinates) + : position(position), normal(normal), tangent(tangent), biTangent(biTangent), textureCoordinates(textureCoordinates), color() + {} + + ~Vertex() = default; + + void Set(const float& x, const float& y, const float& z) + { + position = { x, y, z }; + } + + void Set(const glm::vec3& position) + { + this->position = position; + } + + void Set(const aiVector3D& position) + { + this->position = { position.x, position.y, position.z }; + } + + void Set(const float& x, const float& y, const float& z, const float& nx, const float& ny, const float& nz, const float& u, const float& v) + { + this->position = { x, y, z }; + this->normal = { nx, ny, nz }; + this->textureCoordinates = { u, v, 0 }; + } + + void Set(const glm::vec3& position, const glm::vec3& normal, const glm::vec2& textureCoordinates) + { + this->position = position; + this->normal = normal; + this->textureCoordinates = { textureCoordinates, 0 }; + } + + void Set(const glm::vec3& position, const glm::vec3& normal, const glm::vec3& tangent, const glm::vec3& biTangent, const glm::vec2& textureCoordinates) + { + this->position = position; + this->normal = normal; + this->tangent = tangent; + this->biTangent = biTangent; + this->textureCoordinates = { textureCoordinates,0 }; + } + + void SetNormal(const float& nx, const float& ny, const float& nz) + { + this->normal = { nx, ny, nz }; + } + + void SetNormal(const glm::vec3& normal) + { + this->normal = normal; + } + + void SetNormal(const aiVector3D& normal) + { + this->normal = { normal.x, normal.y, normal.z }; + } + + void SetTangent(const float& tx, const float& ty, const float& tz) + { + this->tangent = { tx, ty, tz }; + } + + void SetTangent(const glm::vec3& tangent) + { + this->tangent = tangent; + } + + void SetTangent(const aiVector3D& tangent) + { + this->tangent = { tangent.x, tangent.y, tangent.z }; + } + + void SetTangentAndBiTangent(const float& tx, const float& ty, const float& tz, const float& bx, const float& by, const float& bz) + { + this->biTangent = { bx, by, bz }; + this->tangent = { tx, ty, tz }; + } + + void SetTangentAndBiTangent(const glm::vec3& tangent, const glm::vec3& biTangent) + { + this->biTangent = biTangent; + this->tangent = tangent; + } + + void SetTangentAndBiTangent(const aiVector3D& tangent, const aiVector3D& biTangent) + { + this->tangent = { tangent.x, tangent.y, tangent.z }; + this->biTangent = { biTangent.x, biTangent.y, biTangent.z }; + } + + void SetBiTangent(const float& bx, const float& by, const float& bz) + { + this->biTangent = { bx, by, bz }; + } + + void SetBiTangent(const glm::vec3& biTangent) + { + this->biTangent = biTangent; + } + + void SetBiTangent(const aiVector3D& biTangent) + { + this->biTangent = { biTangent.x, biTangent.y, biTangent.z }; + } + + void SetTextureCoordinates(const float& u, const float& v) + { + this->textureCoordinates = { u, v, 0 }; + } + + void SetTextureCoordinates(const float& u, const float& v, const float& w) + { + this->textureCoordinates = { u, v, w }; + } + + void SetTextureCoordinates(const glm::vec2& textureCoordinates) + { + this->textureCoordinates = { textureCoordinates, 0 }; + } + + void SetTextureCoordinates(const glm::vec3& textureCoordinates) + { + this->textureCoordinates = textureCoordinates; + } + + void SetTextureCoordinates(const aiVector2D& textureCoordinates) + { + this->textureCoordinates = { textureCoordinates.x, textureCoordinates.y, 0 }; + } + + void SetTextureCoordinates(const aiVector3D& textureCoordinates) + { + this->textureCoordinates = { textureCoordinates.x, textureCoordinates.y, textureCoordinates.z }; + } + + void SetColor(const float& r, const float& g, const float& b, const float& a = 1) + { + color = { r,g,b,a }; + } + + void SetColor(const glm::vec4& color) + { + this->color = color; + } + + void SetColor(const aiColor4D& color) + { + this->color = { color.r, color.g, color.b, color.a }; + } + }; +} diff --git a/openVulkanoCpp/Shader/CompileShaders.bat b/openVulkanoCpp/Shader/CompileShaders.bat new file mode 100644 index 0000000..4313117 --- /dev/null +++ b/openVulkanoCpp/Shader/CompileShaders.bat @@ -0,0 +1,7 @@ +REM Make the directory batch file resides in as the working directory. +pushd %~dp0 + +glslangvalidator -V basic.vert -o basic.vert.spv +glslangvalidator -V basic.frag -o basic.frag.spv + +popd \ No newline at end of file diff --git a/openVulkanoCpp/Shader/basic.frag b/openVulkanoCpp/Shader/basic.frag new file mode 100644 index 0000000..1a0e2cd --- /dev/null +++ b/openVulkanoCpp/Shader/basic.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec4 color; +layout(location = 0) out vec4 outColor; + +void main() +{ + outColor = color; +} \ No newline at end of file diff --git a/openVulkanoCpp/Shader/basic.frag.spv b/openVulkanoCpp/Shader/basic.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..21bd1c09b07c4c935bcba795272d7090bc4c5b66 GIT binary patch literal 376 zcmYk1%MQU{5QRspE$SKxJ0bRBfk=dnMK|4f0%0RT6Y2#$m5s!AewX-@>CBvS=FHUh zhLKspA}d+V-mhgIh*@l*%SpOPN7sBmnoP%1)XjH7X&Oux$%@nVa28P}VxoyoI18wJ zT+Qwb1)%@>07Ns{EnjEH>_X`@^4NWVgb8VSB6;e?fBQD>@*lWO=IRc02-YXM=yivB z?e6m}k@Y9S7oT1`go&?^Irqn_*G^$GF4R7G>1pNx5|Edm@~jfluVCe?{>k*5cmd4= B6WRa( literal 0 HcmV?d00001 diff --git a/openVulkanoCpp/Shader/basic.vert b/openVulkanoCpp/Shader/basic.vert new file mode 100644 index 0000000..cefb660 --- /dev/null +++ b/openVulkanoCpp/Shader/basic.vert @@ -0,0 +1,29 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec3 tangent; +layout(location = 3) in vec3 biTangent; +layout(location = 4) in vec3 textureCoordinates; +layout(location = 5) in vec4 color; +layout(location = 0) out vec4 outColor; + +layout(binding = 0) uniform NodeData +{ + mat4 world; +} node; + +layout(std140, push_constant) uniform CameraData { + mat4 viewProjection; +} cam; + +void main() +{ + vec3 light = normalize(vec3(1)); + vec4 worldPos = node.world * vec4(position, 1.0); + vec3 worldNormal = normalize(transpose(inverse(mat3(node.world))) * normal); + float brightness = max(0.0, dot(worldNormal, light)); + outColor = vec4(clamp(color.rgb * (0.5 + brightness / 2), 0, 1), 1); + gl_Position = normalize(cam.viewProjection * worldPos); +} \ No newline at end of file diff --git a/openVulkanoCpp/Shader/basic.vert.spv b/openVulkanoCpp/Shader/basic.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..7b48e7c86bf4390f822979818a1e72492999010a GIT binary patch literal 2824 zcmZ9MTT>KQ5XX<;E*cP(i<$(LPTyvGdnWswJLLM@5!Z0VIq7aR!lI|VrG zj=I`vWAVXKqZ98(S(L|(&TiCt9&n^;4-+p+^E7Wwa|og`0EUk})XFz>ULW_wY~`%z=N z5WU|O$y*sowd0P@yX1S29^}=um1a_VKYFqqfReJhCwZsUsK?o6oaOO9*p&kgkIvF5 zYf{+LdygmWd_8&Yb2lBmm9E-K_Lq}R9<{e)MJxQ69J+&6>w~VOc`zdmwLZ{YjrQU! zDmsTR80YUKepb&?CgZzP+|GS0X1)=e(STnWpa1@hIQ8uX7w`z@->+m zAoDKuJqa@J56cR>32D*8pAuKrT@js@J?|i+uShqd{C_2jB}4*HwA@ugP$k=Zlr z>8yBSnI4(F1G9MZ;&tXtUM%DV>R%OKDP(Vtd{>13nDlG9$qj5=96QHbVCW$7tm`Hx z^yG=mZvz-JVb83G>*Dzx2E!Ja-vTgV{T6uat!L~eIho9EcFXK0@^yVb%!^&%@cw^7 z_%85e2YJWl$1V+a$>=A#Wae`v$hQ>@U%tUbMPxTliH1b%%$(wr^Jx({GVgpQVn^=F zhrW#kOxFkOY{00&?D+QZrJlMX(f2Pz_+yWrU3;Ur=qe)K#)e#}t0J;o&x!vy*GXaE zyh)6&L|~kI>K6H*7lA_@awI47`C2?a;O6sl`YRAzodL|k&``^JXY>=rs=(f>IS->>!LcEG?n#Gq%`6L&#G zj9209rGPE!ei^W;czy%Wy$-c6izhZZ`b+K9Om0So|169>Ga3kcQ4>Z!^qhG3W6NHU z+lmMqj$wDio1bC71dO_D&wmw>JBk_C- n%$*)T7GVR<+`)bqQ42MoCqBQme~9=UBR=mVWBXp#H$?vfD0alk literal 0 HcmV?d00001 diff --git a/openVulkanoCpp/Vulkan/Buffer.hpp b/openVulkanoCpp/Vulkan/Buffer.hpp new file mode 100644 index 0000000..0a90248 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Buffer.hpp @@ -0,0 +1,109 @@ +#pragma once +#include "../Base/ICloseable.hpp" +#include "Device.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + /** + * \brief A not managed buffer. This should be used rarely. + */ + struct Buffer : virtual public ICloseable + { + vk::Device device; + vk::DeviceMemory memory; + vk::DeviceSize size = 0, alignment = 0, allocSize = 0; + vk::MemoryPropertyFlags memoryPropertyFlags; + void* mapped = nullptr; + + /** + * \brief Maps the buffer into the memory of the host. + * \tparam T The type of the buffers data. + * \param offset The offset from where to map the buffer. + * \param size The size to be mapped. VK_WHOLE_SIZE to map the whole buffer. + * \return The pointer to the mapped buffer. + */ + template + T * Map(size_t offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) + { + mapped = device.mapMemory(memory, offset, size, vk::MemoryMapFlags()); + return static_cast(mapped); + } + + /** + * \brief Un-maps the buffer from the host. + */ + void UnMap() + { + device.unmapMemory(memory); + mapped = nullptr; + } + + /** + * \brief Copies data into the mapped buffer. Will not do anything if the buffer is not mapped! + * \param size The size of the data to copy. + * \param data The data to copy + * \param offset The offset for where to copy the data to in the buffer. + */ + void Copy(size_t size, const void* data, VkDeviceSize offset = 0) const + { + if (!mapped) return; + memcpy(static_cast(mapped) + offset, data, size); + } + + /** + * \brief Copies data into the mapped buffer. Will not do anything if the buffer is not mapped! + * \param data The data to copy. + * \param offset The offset for where to copy the data to in the buffer. + */ + template + void Copy(const T& data, VkDeviceSize offset = 0) const + { + Copy(sizeof(T), &data, offset); + } + + /** + * \brief Copies data into the mapped buffer. Will not do anything if the buffer is not mapped! + * \param data The data to copy. + * \param offset The offset for where to copy the data to in the buffer. + */ + template + void Copy(const std::vector& data, VkDeviceSize offset = 0) const + { + copy(sizeof(T) * data.size(), data.data(), offset); + } + + /** + * \brief Flushes the memory region of the buffer to the device. This should be only necessary for non coherent memory. + * \param size The amount to flush. VK_WHOLE_SIZE flushes the entire buffer. + * \param offset The offset from where to start the flush. + */ + void Flush(vk::DeviceSize size = VK_WHOLE_SIZE, vk::DeviceSize offset = 0) const + { + device.flushMappedMemoryRanges(vk::MappedMemoryRange(memory, offset, size)); + } + + /** + * \brief Invalidates the memory region of the buffer to allow access from the host. This should be only necessary for non coherent memory. + * \param size The amount to make available. VK_WHOLE_SIZE invalidate the entire buffer. + * \param offset The offset from where to make the memory available. + */ + void Invalidate(vk::DeviceSize size = VK_WHOLE_SIZE, vk::DeviceSize offset = 0) const + { + device.invalidateMappedMemoryRanges(vk::MappedMemoryRange(memory, offset, size)); + } + + void Close() override + { + if (mapped) UnMap(); + if(memory) + { + device.free(memory); + memory = vk::DeviceMemory(); + } + } + + }; + } +} diff --git a/openVulkanoCpp/Vulkan/CommandHelper.hpp b/openVulkanoCpp/Vulkan/CommandHelper.hpp new file mode 100644 index 0000000..eafda84 --- /dev/null +++ b/openVulkanoCpp/Vulkan/CommandHelper.hpp @@ -0,0 +1,45 @@ +#pragma once +#include +#include "../Base/ICloseable.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + struct CommandHelper : virtual ICloseable + { + vk::Device device; + vk::CommandPool cmdPool; + vk::CommandBuffer cmdBuffer; + vk::CommandBufferLevel level; + + CommandHelper() = default; + ~CommandHelper() { if (cmdPool) CommandHelper::Close(); } + + void Init(vk::Device device, uint32_t queueIndex, vk::CommandBufferLevel level = vk::CommandBufferLevel::eSecondary) + { + this->level = level; + this->device = device; + cmdPool = device.createCommandPool(vk::CommandPoolCreateInfo({}, queueIndex)); + vk::CommandBufferAllocateInfo bufferAllocInfo = { cmdPool, level, 1 }; + cmdBuffer = device.allocateCommandBuffers(bufferAllocInfo)[0]; + } + + void Reset() const + { + device.resetCommandPool(cmdPool, {}); + } + + vk::CommandBufferLevel GetLevel() const + { + return level; + } + + void Close() override + { + device.freeCommandBuffers(cmdPool, 1, &cmdBuffer); + device.destroyCommandPool(cmdPool); + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Context.hpp b/openVulkanoCpp/Vulkan/Context.hpp new file mode 100644 index 0000000..1621be2 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Context.hpp @@ -0,0 +1,119 @@ +#pragma once +#include +#include "../Base/IGraphicsApp.hpp" +#include "../Base/IGraphicsAppManager.hpp" +#include "../Base/EngineConstants.hpp" +#include "../Base/Utils.hpp" +#include "Debuging/ValidationLayer.hpp" +#include "DeviceManager.hpp" +#include "SwapChain.hpp" +#include "RenderPass.hpp" +#include "Pipeline.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class Context : virtual public ICloseable + { + bool enableValidationLayer, initialized; + std::set requiredExtensions; + public: + DeviceManager deviceManager; + vk::Instance instance; // Vulkan instance + vk::DispatchLoaderDynamic dynamicDispatch; // for access to features not available in statically linked Vulkan lib + vk::SurfaceKHR surface; // Vulkan surface to display framebuffer on + Device* device = nullptr; + SwapChain swapChain; + RenderPass swapChainRenderPass; + IVulkanWindow* window = nullptr; + IGraphicsAppManager* graphicsAppManager = nullptr; + Pipeline pipeline; + + Context() : initialized(false) + { +#ifdef DEBUG + enableValidationLayer = true; +#else + enableValidationLayer = false; +#endif + } + virtual ~Context() + { + if (initialized) Close(); + } + + void Init(IGraphicsAppManager* graphicsAppManager, IVulkanWindow* window) + { + if (initialized) throw std::runtime_error("The context is already initialized"); + this->graphicsAppManager = graphicsAppManager; + this->window = window; + + // Get the extensions required to display on the window + for (const auto& requiredExtension : window->GetRequiredInstanceExtensions()) { RequireExtension(requiredExtension.c_str()); } + + CreateInstance(); // Create the vulkan instance + surface = window->CreateSurface(instance); // Create the surface from the window + CreateDevice(); + + + swapChain.Init(device, surface, window); + swapChainRenderPass.Init(device, &swapChain); + + pipeline.Init(device->device); + + initialized = true; + } + + void Close() override + { + if (!initialized) return; + device->WaitIdle(); + + pipeline.Close(); + swapChainRenderPass.Close(); + swapChain.Close(); + deviceManager.Close(); + //TODO + + if (enableValidationLayer) Debug::CloseValidationLayers(instance); + initialized = false; + } + + void Resize(const uint32_t newWidth, const uint32_t newHeight) + { + device->WaitIdle(); + swapChain.Resize(newWidth, newHeight); + } + + void RequireExtension(const char* extension) + { + requiredExtensions.emplace(extension); + } + private: + void CreateInstance() + { + if (enableValidationLayer) RequireExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + vk::ApplicationInfo appInfo(graphicsAppManager->GetGraphicsApp()->GetAppName().c_str(), graphicsAppManager->GetGraphicsApp()->GetAppVersionAsInt(), ENGINE_NAME, ENGINE_VERSION.intVersion, VK_MAKE_VERSION(1, 1, 0)); + std::vector extensions = Utils::toCString(requiredExtensions), layers = Debug::GetValidationLayers(); + + const vk::InstanceCreateInfo createInfo(vk::InstanceCreateFlags(), &appInfo, enableValidationLayer ? layers.size() : 0, + layers.data(), extensions.size(), extensions.data()); + + instance = vk::createInstance(createInfo); + + if (enableValidationLayer) Debug::SetupValidationLayers(instance, vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::ePerformanceWarning /*| vk::DebugReportFlagBitsEXT::eInformation | vk::DebugReportFlagBitsEXT::eDebug*/); + dynamicDispatch.init(instance, &vkGetInstanceProcAddr); + } + + void CreateDevice() + { + deviceManager.Init(instance); + device = deviceManager.GetCompatibleDevice({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }); + device->PrepareDevice({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }, surface); + dynamicDispatch.init(instance, &vkGetInstanceProcAddr, device->device, &vkGetDeviceProcAddr); + Logger::RENDER->info("Found device: {0}", device->GetDeviceName());; + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Debuging/ValidationLayer.hpp b/openVulkanoCpp/Vulkan/Debuging/ValidationLayer.hpp new file mode 100644 index 0000000..8865538 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Debuging/ValidationLayer.hpp @@ -0,0 +1,97 @@ +#pragma once +#include +#include + +#define RENDER_DOC + +namespace openVulkanoCpp +{ + namespace Vulkan + { + namespace Debug + { + std::list activeValidationLayerNames = { + "VK_LAYER_LUNARG_assistant_layer", + "VK_LAYER_LUNARG_standard_validation", + //"VK_EXT_debug_marker", +#ifdef RENDER_DOC + "VK_LAYER_RENDERDOC_Capture", // RenderDoc must be open for this layer to work! +#endif + }; + + static std::set GetAvailableValidationLayers() + { + auto layers = vk::enumerateInstanceLayerProperties(); + std::set layersVector; + std::string layerList = ""; + for(const auto& layer : layers) + { + std::string name = layer.layerName; + layersVector.insert(name); + if (layerList.length() > 0) layerList += ", "; + layerList += name; + } + Logger::RENDER->debug("Available Vulkan Validation Layers: {0}", layerList); + return layersVector; + } + + static std::vector GetValidationLayers() + { + std::set availableLayers = GetAvailableValidationLayers(); + std::vector layers; + std::string layerList = ""; + for (const auto& name : activeValidationLayerNames) + { + if (availableLayers.count(name) != 0) + { + layers.push_back(name.c_str()); + if (layerList.length() > 0) layerList += ", "; + layerList += name; + } + } + Logger::RENDER->debug("Active Vulkan Validation Layers: {0}", layerList); + return layers; + } + + static std::once_flag dispatcherInitFlag; + vk::DispatchLoaderDynamic dispatcher; + vk::DebugReportCallbackEXT msgCallback; + + inline VkBool32 ValidationLayerCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, + uint64_t srcObject, size_t location, int32_t msgCode, const char* layerPrefix, + const char* msg, void* pUserData) + { + std::string prefix = "VK_DEBUG:"; + spdlog::level::level_enum level = spdlog::level::info; + if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) level = spdlog::level::err; + else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) level = spdlog::level::warn; + else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) + { + level = spdlog::level::warn; + prefix = "[PERF] " + prefix; + } + else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) level = spdlog::level::debug; + + Logger::RENDER->log(level, "{0} [{1}] Code {2}: {3}", prefix, layerPrefix, msgCode, msg); + + return false; + } + + static void SetupValidationLayers(const vk::Instance& instance, const vk::DebugReportFlagsEXT& flags) + { + Logger::RENDER->info("Setting up Vulkan Validation Layer"); + std::call_once(dispatcherInitFlag, [&] { dispatcher.init(instance, &vkGetInstanceProcAddr); }); + vk::DebugReportCallbackCreateInfoEXT dbgCreateInfo = {}; + dbgCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)ValidationLayerCallback; + dbgCreateInfo.flags = flags; + msgCallback = instance.createDebugReportCallbackEXT(dbgCreateInfo, nullptr, dispatcher); + Logger::RENDER->info("Vulkan Validation Layer setup"); + } + + static void CloseValidationLayers(const vk::Instance& instance) { + std::call_once(dispatcherInitFlag, [&] { dispatcher.init(instance, &vkGetInstanceProcAddr); }); + instance.destroyDebugReportCallbackEXT(msgCallback, nullptr, dispatcher); + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Device.hpp b/openVulkanoCpp/Vulkan/Device.hpp new file mode 100644 index 0000000..32495cd --- /dev/null +++ b/openVulkanoCpp/Vulkan/Device.hpp @@ -0,0 +1,279 @@ +#pragma once +#include +#include +#include +#include + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class DeviceQueueCreateInfoBuilder + { + std::vector createInfos; + std::vector> prioritiesVector; + public: + DeviceQueueCreateInfoBuilder() = default; + ~DeviceQueueCreateInfoBuilder() = default; + + void AddQueueFamily(const uint32_t queueFamilyIndex, const std::vector& priorities) + { + prioritiesVector.push_back(priorities); + createInfos.emplace_back(vk::DeviceQueueCreateFlags(), queueFamilyIndex, priorities.size(), prioritiesVector[prioritiesVector.size()-1].data()); + } + + void AddQueueFamily(uint32_t queueFamilyIndex, uint32_t count = 1) + { + std::vector priorities; + priorities.resize(count); + std::fill(priorities.begin(), priorities.end(), 0.0f); + AddQueueFamily(queueFamilyIndex, priorities); + } + + std::vector& GetDeviceQueueCreateInfos() + { + return createInfos; + } + }; + + class Device : virtual public ICloseable + { + public: + vk::PhysicalDevice physicalDevice; + std::vector queueFamilyProperties; // Queue family properties + vk::PhysicalDeviceProperties properties; // Physical device properties (for e.g. checking device limits) + vk::PhysicalDeviceFeatures features; // Physical device features (for e.g. checking if a feature is available) + vk::PhysicalDeviceMemoryProperties memoryProperties; // available memory properties + vk::Device device; // Logical device, application's view of the physical device (GPU) + vk::PipelineCache pipelineCache; + vk::CommandPool graphicsCommandPool; + std::set supportedExtensions; + vk::Queue graphicsQueue; + + struct QueueIndices { + uint32_t graphics = VK_QUEUE_FAMILY_IGNORED; + uint32_t compute = VK_QUEUE_FAMILY_IGNORED; + uint32_t transfer = VK_QUEUE_FAMILY_IGNORED; + + uint32_t GetGraphics() const { return graphics; } + uint32_t GetCompute() const { return compute != graphics ? compute : VK_QUEUE_FAMILY_IGNORED; } + uint32_t GetTransfer() const { return (transfer != graphics && transfer != compute) ? transfer : VK_QUEUE_FAMILY_IGNORED; } + } queueIndices; + + bool useDebugMarkers; + + public: + Device(vk::PhysicalDevice& physicalDevice) + { + this->physicalDevice = physicalDevice; + useDebugMarkers = false; + QueryDevice(); + } + + std::string GetDeviceName() const + { + return properties.deviceName; + } + + void PrepareDevice(const vk::ArrayProxy& requestedExtensions, const vk::SurfaceKHR& surface) + { + queueIndices.graphics = FindBestQueue(vk::QueueFlagBits::eGraphics, surface); // Make sure that the graphics queue supports the surface + BuildDevice(requestedExtensions); + //TODO setup debug marker + pipelineCache = device.createPipelineCache(vk::PipelineCacheCreateInfo()); + + graphicsQueue = device.getQueue(queueIndices.graphics, 0); + graphicsCommandPool = device.createCommandPool({ vk::CommandPoolCreateFlagBits::eResetCommandBuffer, queueIndices.graphics, }); + } + + std::set GetExtensions() const + { + return supportedExtensions; + } + + bool IsExtensionAvailable(const vk::ArrayProxy& extensions) const + { + for(const auto& extension : extensions) + { + if (supportedExtensions.count(extension) == 0) return false; + } + return true; + } + + void WaitIdle() const + { //TODO wait all queues idle + graphicsQueue.waitIdle(); + device.waitIdle(); + } + + + vk::CommandBuffer CreateCommandBuffer(vk::CommandBufferLevel level = vk::CommandBufferLevel::ePrimary) const + { + const vk::CommandBufferAllocateInfo cmdBufferAllocInfo(graphicsCommandPool, level, 1); + return device.allocateCommandBuffers(cmdBufferAllocInfo)[0]; + } + + void FlushCommandBuffer(vk::CommandBuffer& cmdBuffer) const + { + graphicsQueue.submit(vk::SubmitInfo{ 0, nullptr, nullptr, 1, &cmdBuffer }, vk::Fence()); + WaitIdle(); + } + + void ExecuteNow(const std::function& function) const + { + vk::CommandBuffer commandBuffer = CreateCommandBuffer(); + commandBuffer.begin(vk::CommandBufferBeginInfo{ vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); + function(commandBuffer); + commandBuffer.end(); + FlushCommandBuffer(commandBuffer); + device.freeCommandBuffers(graphicsCommandPool, commandBuffer); + } + + vk::ShaderModule CreateShaderModule(const std::string filename) + { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) throw std::runtime_error("Failed to open shader file!"); + const size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); + vk::ShaderModuleCreateInfo smci = { {}, buffer.size(), reinterpret_cast(buffer.data()) }; + return CreateShaderModule(smci); + } + + vk::ShaderModule CreateShaderModule(vk::ShaderModuleCreateInfo& createInfo) const + { + return device.createShaderModule(createInfo); + } + private: + void QueryDevice() + { + // Query device features + queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + properties = physicalDevice.getProperties(); + features = physicalDevice.getFeatures(); + for (auto& ext : physicalDevice.enumerateDeviceExtensionProperties()) { supportedExtensions.insert(ext.extensionName); } + + // Query device memory properties + memoryProperties = physicalDevice.getMemoryProperties(); + queueIndices.graphics = FindBestQueue(vk::QueueFlagBits::eGraphics); + queueIndices.compute = FindBestQueue(vk::QueueFlagBits::eCompute); + queueIndices.transfer = FindBestQueue(vk::QueueFlagBits::eTransfer); + } + + void BuildDevice(const vk::ArrayProxy& requestedExtensions) + { + vk::DeviceCreateInfo deviceCreateInfo; + deviceCreateInfo.pEnabledFeatures = &features; //TODO add option to disable not needed features + + + DeviceQueueCreateInfoBuilder deviceQueueCreateInfoBuilder; + deviceQueueCreateInfoBuilder.AddQueueFamily(queueIndices.GetGraphics(), queueFamilyProperties[queueIndices.GetGraphics()].queueCount); + if (queueIndices.GetCompute() != VK_QUEUE_FAMILY_IGNORED) + deviceQueueCreateInfoBuilder.AddQueueFamily(queueIndices.GetCompute(), queueFamilyProperties[queueIndices.GetCompute()].queueCount); + if (queueIndices.GetTransfer() != VK_QUEUE_FAMILY_IGNORED) + deviceQueueCreateInfoBuilder.AddQueueFamily(queueIndices.GetTransfer(), queueFamilyProperties[queueIndices.GetTransfer()].queueCount); + const std::vector deviceQueueCreateInfos = deviceQueueCreateInfoBuilder.GetDeviceQueueCreateInfos(); + + deviceCreateInfo.queueCreateInfoCount = static_cast(deviceQueueCreateInfos.size()); + deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfos.data(); + + std::vector enabledExtensions; + for (const auto& extension : requestedExtensions) + { + enabledExtensions.push_back(extension.c_str()); + } +#ifdef DEBUG + if (IsExtensionAvailable({ VK_EXT_DEBUG_MARKER_EXTENSION_NAME })) + { // Enable debug marker extension if available + enabledExtensions.push_back(VK_EXT_DEBUG_MARKER_EXTENSION_NAME); + useDebugMarkers = true; + } +#endif + deviceCreateInfo.enabledExtensionCount = static_cast(enabledExtensions.size()); + deviceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data(); + + device = physicalDevice.createDevice(deviceCreateInfo); + } + + uint32_t FindBestQueue(const vk::QueueFlags& desiredFlags, const vk::SurfaceKHR& surface = nullptr) const + { + uint32_t best = VK_QUEUE_FAMILY_IGNORED; + VkQueueFlags bestExtraFlagsCount = VK_QUEUE_FLAG_BITS_MAX_ENUM; + for (size_t i = 0; i < queueFamilyProperties.size(); i++) + { + vk::QueueFlags flags = queueFamilyProperties[i].queueFlags; + if (!(flags & desiredFlags)) continue; // Skip queue without desired flags + if (surface && VK_FALSE == physicalDevice.getSurfaceSupportKHR(i, surface)) continue; + const VkQueueFlags currentExtraFlags = (flags & ~desiredFlags).operator VkQueueFlags(); + + if (0 == currentExtraFlags) return i; // return exact match + + if (best == VK_QUEUE_FAMILY_IGNORED || currentExtraFlags < bestExtraFlagsCount) + { + best = i; + bestExtraFlagsCount = currentExtraFlags; + } + } + return best; + } + + public: + /** + * \brief Vulkan does not require does not define a depth buffer format that must be supported. This method checks for the first supported format from an given array of formats. + * \param depthFormats Array of depth formats + * \return The first format supported as a depth buffer + * \throws If no depth buffer format is supported + */ + vk::Format GetSupportedDepthFormat(const std::vector& depthFormats = { vk::Format::eD32SfloatS8Uint, vk::Format::eD32Sfloat, vk::Format::eD24UnormS8Uint, vk::Format::eD16UnormS8Uint, vk::Format::eD16Unorm }) const + { + for (auto& format : depthFormats) + { + vk::FormatProperties formatProps; + formatProps = physicalDevice.getFormatProperties(format); + if (formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment) + { + return format; + } + } + + throw std::runtime_error("No supported depth format"); + } + + vk::Bool32 GetMemoryType(uint32_t typeBits, const vk::MemoryPropertyFlags& properties, uint32_t* typeIndex) const + { + for (uint32_t i = 0; i < 32; i++) + { + if ((typeBits & 1) == 1) + { + if ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + *typeIndex = i; + return VK_TRUE; + } + } + typeBits >>= 1; + } + return VK_FALSE; + } + + uint32_t GetMemoryType(uint32_t typeBits, const vk::MemoryPropertyFlags& properties) const + { + uint32_t result = 0; + if (VK_FALSE == GetMemoryType(typeBits, properties, &result)) + { + throw std::runtime_error("Unable to find memory type " + to_string(properties)); + } + return result; + } + + + void Close() override + { + device.destroyCommandPool(graphicsCommandPool); + //TODO fill + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/DeviceManager.hpp b/openVulkanoCpp/Vulkan/DeviceManager.hpp new file mode 100644 index 0000000..65a2125 --- /dev/null +++ b/openVulkanoCpp/Vulkan/DeviceManager.hpp @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include "../Base/ICloseable.hpp" +#include "Device.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + + class DeviceManager : virtual public ICloseable + { + std::vector devices; + public: + void Init(const vk::Instance& instance) + { + devices = std::vector(); + for (auto& physicalDevice : instance.enumeratePhysicalDevices()) + { + devices.emplace_back(physicalDevice); + } + } + + Device* GetCompatibleDevice(const std::vector& deviceExtensions) + { + for (auto& device : devices) + { + if (device.IsExtensionAvailable(deviceExtensions)) return &device; + } + throw std::runtime_error("No device with required extensions found!"); + } + + void Close() override + { + for (auto& device : devices) + { + device.Close(); + } + devices.clear(); + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/FrameBuffer.cpp b/openVulkanoCpp/Vulkan/FrameBuffer.cpp new file mode 100644 index 0000000..e8ee4d9 --- /dev/null +++ b/openVulkanoCpp/Vulkan/FrameBuffer.cpp @@ -0,0 +1,76 @@ +#include "FrameBuffer.hpp" +#include "RenderPass.hpp" + +void openVulkanoCpp::Vulkan::FrameBuffer::Init(Device* device, vk::Extent3D size, bool useDepthBuffer) +{ + this->size = size; + this->device = device; + this->useDepthBuffer = useDepthBuffer; + colorFormat = FindColorFormat(); + if (useDepthBuffer) + { + depthBufferFormat = FindDepthFormat(); + CreateDepthStencil(); + } +} + +void openVulkanoCpp::Vulkan::FrameBuffer::InitRenderPass(RenderPass* renderPass) +{ + if (!device) throw std:: + runtime_error("The frame buffer needs to be initialized before binding it to a render pass"); + this->renderPass = renderPass; + CreateFrameBuffer(); +} + +void openVulkanoCpp::Vulkan::FrameBuffer::Resize(vk::Extent3D size) +{ + this->size = size; + DestroyFrameBuffer(); + if (depthBuffer) depthBuffer.Close(); + if (useDepthBuffer) CreateDepthStencil(); + CreateFrameBuffer(); + renderPass->UpdateBeginInfo(); +} + +void openVulkanoCpp::Vulkan::FrameBuffer::CreateDepthStencil() +{ + vk::ImageCreateInfo depthStencilCreateInfo({}, vk::ImageType::e2D, depthBufferFormat, + size, 1, 1); + depthStencilCreateInfo.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits:: + eTransferSrc; + + const vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; + const vk::ImageViewCreateInfo depthStencilViewCreateInfo({}, {}, vk::ImageViewType::e2D, depthBufferFormat, + {}, vk::ImageSubresourceRange(aspectMask, 0, 1, 0, 1)); + depthBuffer.Init(device, depthStencilCreateInfo, depthStencilViewCreateInfo); + + device->ExecuteNow([&](auto commandBuffer) + { + depthBuffer.SetLayout(commandBuffer, aspectMask, vk::ImageLayout::eDepthStencilAttachmentOptimal); + }); +} + +void openVulkanoCpp::Vulkan::FrameBuffer::CreateFrameBuffer() +{ + vk::ImageView attachments[2]; // First attachment is the color buffer, second (optional) the depth buffer + if (useDepthBuffer) attachments[1] = depthBuffer.view; //Depth buffer is the same for all frame buffers + const vk::FramebufferCreateInfo fbCreateInfo({}, renderPass->renderPass, useDepthBuffer ? 2 : 1, attachments, size.width, + size.height, 1); + + auto images = GetImages(); + frameBuffers.resize(images.size()); + for (uint32_t i = 0; i < frameBuffers.size(); i++) + { + attachments[0] = images[i]->GetView(); + frameBuffers[i] = device->device.createFramebuffer(fbCreateInfo); + } +} + +void openVulkanoCpp::Vulkan::FrameBuffer::DestroyFrameBuffer() +{ + for (const auto frameBuffer : frameBuffers) + { + device->device.destroyFramebuffer(frameBuffer); + } + frameBuffers.clear(); +} diff --git a/openVulkanoCpp/Vulkan/FrameBuffer.hpp b/openVulkanoCpp/Vulkan/FrameBuffer.hpp new file mode 100644 index 0000000..ac31323 --- /dev/null +++ b/openVulkanoCpp/Vulkan/FrameBuffer.hpp @@ -0,0 +1,107 @@ +#pragma once +#include +#include +#include "../Base/ICloseable.hpp" +#include "Image.hpp" +#include "Device.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class RenderPass; + + class FrameBuffer : ICloseable + { + Image depthBuffer; + std::vector frameBuffers; + vk::Format depthBufferFormat = vk::Format::eUndefined, colorFormat = vk::Format::eUndefined; + vk::Extent3D size; + RenderPass* renderPass; + bool useDepthBuffer; + Device* device = nullptr; + protected: + uint32_t currentFrameBufferId = 0; + + FrameBuffer() = default; + + virtual ~FrameBuffer() + { + if (device) FrameBuffer::Close(); + } + + void Init(Device* device, vk::Extent3D size, bool useDepthBuffer = true); + + void SetCurrentFrameId(uint32_t id) + { + currentFrameBufferId = id; + } + + uint32_t GetCurrentFrameId() const + { + return currentFrameBufferId; + } + + public: + void InitRenderPass(RenderPass* renderPass); + + protected: + void Resize(vk::Extent3D size); + + void Close() override + { + DestroyFrameBuffer(); + if(depthBuffer) depthBuffer.Close(); + device = nullptr; + } + + protected: + + virtual void CreateDepthStencil(); + + virtual void CreateFrameBuffer(); + + void DestroyFrameBuffer(); + + virtual vk::Format FindColorFormat() = 0; + + virtual vk::Format FindDepthFormat() + { + return device->GetSupportedDepthFormat(); + } + + public: + virtual vk::Format GetColorFormat() + { + return colorFormat; + } + + virtual vk::Format GetDepthFormat() + { + return depthBufferFormat; + } + + virtual std::vector GetImages() = 0; + + bool UseDepthBuffer() const + { + return useDepthBuffer; + } + + vk::Extent3D GetSize3D() const + { + return size; + } + + vk::Extent2D GetSize2D() const + { + return { size.width, size.height }; + } + + vk::Framebuffer& GetCurrentFrameBuffer() + { + return frameBuffers[currentFrameBufferId]; + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Image.hpp b/openVulkanoCpp/Vulkan/Image.hpp new file mode 100644 index 0000000..5ca2d79 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Image.hpp @@ -0,0 +1,98 @@ +#pragma once +#include +#include "Buffer.hpp" +#include "VulkanUtils.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class IImage + { + public: + virtual ~IImage() = default; + + virtual vk::Image GetImage() = 0; + virtual vk::ImageView GetView() = 0; + }; + + struct Image : public Buffer, virtual public IImage + { + vk::Image image; + vk::Extent3D extent; + vk::ImageView view; + vk::Sampler sampler; + vk::Format format = vk::Format::eUndefined; + + /** + * \brief + * \param device + * \param imageCreateInfo + * \param imageViewCreateInfo The image will be set automatically after it's creation + * \param memoryPropertyFlags + */ + void Init(const Device* device, const vk::ImageCreateInfo& imageCreateInfo, vk::ImageViewCreateInfo imageViewCreateInfo, const vk::MemoryPropertyFlags& memoryPropertyFlags = vk::MemoryPropertyFlagBits::eDeviceLocal) + { + this->device = device->device; + image = device->device.createImage(imageCreateInfo); + format = imageCreateInfo.format; + extent = imageCreateInfo.extent; + + const vk::MemoryRequirements memRequirements = device->device.getImageMemoryRequirements(image); + size = allocSize = memRequirements.size; + const vk::MemoryAllocateInfo memAllocInfo = { allocSize, device->GetMemoryType(memRequirements.memoryTypeBits, memoryPropertyFlags) }; + memory = device->device.allocateMemory(memAllocInfo); + device->device.bindImageMemory(image, memory, 0); + + imageViewCreateInfo.image = image; + view = device->device.createImageView(imageViewCreateInfo); + } + + void SetLayout(vk::CommandBuffer& cmdBuffer, vk::ImageSubresourceRange subResourceRange, vk::ImageLayout newLayout, vk::ImageLayout oldLayout = vk::ImageLayout::eUndefined) const + { + const vk::ImageMemoryBarrier imgMemBarrier(VulkanUtils::GetAccessFlagsForLayout(oldLayout), VulkanUtils::GetAccessFlagsForLayout(newLayout), oldLayout, + newLayout, 0, 0, image, subResourceRange); + cmdBuffer.pipelineBarrier(VulkanUtils::GetPipelineStageForLayout(oldLayout), VulkanUtils::GetPipelineStageForLayout(newLayout), + {}, nullptr, nullptr, imgMemBarrier); + } + + void SetLayout(vk::CommandBuffer& cmdBuffer, vk::ImageAspectFlags aspectMask, vk::ImageLayout newLayout, vk::ImageLayout oldLayout = vk::ImageLayout::eUndefined) const + { + SetLayout(cmdBuffer, vk::ImageSubresourceRange(aspectMask, 0, 1, 0, 1), newLayout, oldLayout); + } + + void Close() override + { + if(sampler) + { + device.destroySampler(sampler); + sampler = vk::Sampler(); + } + if(view) + { + device.destroyImageView(view); + view = vk::ImageView(); + } + if(image) + { + device.destroyImage(image); + image = vk::Image(); + } + Buffer::Close(); + } + + operator bool() const { return image.operator bool(); } + + + vk::Image GetImage() override + { + return image; + } + + vk::ImageView GetView() override + { + return view; + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Pipeline.hpp b/openVulkanoCpp/Vulkan/Pipeline.hpp new file mode 100644 index 0000000..94933f2 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Pipeline.hpp @@ -0,0 +1,42 @@ +#pragma once +#include +#include "../Base/ICloseable.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + struct Pipeline : virtual ICloseable + { + vk::Device device; + vk::DescriptorSetLayout descriptorSetLayout; + vk::PipelineLayout pipelineLayout; + vk::DescriptorPool descriptorPool; + + void Init(vk::Device& device) + { + this->device = device; + + CreatePipelineLayout(); + } + + void Close() override + { + device.destroyPipelineLayout(pipelineLayout); + device.destroyDescriptorSetLayout(descriptorSetLayout); + } + + private: + void CreatePipelineLayout() + { + vk::PushConstantRange camPushConstantDesc = { vk::ShaderStageFlagBits::eVertex, 0, 64 }; + vk::DescriptorSetLayoutBinding nodeLayoutBinding = { 0, vk::DescriptorType::eUniformBufferDynamic, 1, vk::ShaderStageFlagBits::eVertex }; + std::array layoutBindings = { nodeLayoutBinding }; + vk::DescriptorSetLayoutCreateInfo dslci = { {}, layoutBindings.size(), layoutBindings.data() }; + descriptorSetLayout = device.createDescriptorSetLayout(dslci); + vk::PipelineLayoutCreateInfo plci = { {}, 1, &descriptorSetLayout, 1, &camPushConstantDesc }; + pipelineLayout = this->device.createPipelineLayout(plci); + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/RenderPass.hpp b/openVulkanoCpp/Vulkan/RenderPass.hpp new file mode 100644 index 0000000..7003f71 --- /dev/null +++ b/openVulkanoCpp/Vulkan/RenderPass.hpp @@ -0,0 +1,146 @@ +#pragma once +#include +#include "Device.hpp" +#include "FrameBuffer.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class RenderPass : virtual public ICloseable + { + protected: + vk::Device device; + vk::RenderPassBeginInfo beginInfo; + vk::ClearColorValue clearColor; + vk::ClearDepthStencilValue clearDepth; + std::vector clearValues; + FrameBuffer* frameBuffer = nullptr; + bool useClearColor = false, useClearDepth = true; + + public: + vk::RenderPass renderPass; + + RenderPass() = default; + + ~RenderPass() + { + if (frameBuffer) RenderPass::Close(); + } + + void Init(Device* device, FrameBuffer* frameBuffer) + { + this->device = device->device; + this->frameBuffer = frameBuffer; + CreateRenderPass(); + frameBuffer->InitRenderPass(this); + SetClearColor(); + } + + void Close() override + { + device.destroy(renderPass); + frameBuffer = nullptr; + clearValues.clear(); + } + + void SetClearColor(vk::ClearColorValue clearColor = vk::ClearColorValue(std::array{ 0.39f, 0.58f, 0.93f, 1.0f })) + { + this->clearColor = clearColor; + useClearColor = true; + UpdateBeginInfo(); + } + + void DisableClearColor() + { + useClearColor = false; + UpdateBeginInfo(); + } + + void SetClearDepth(vk::ClearDepthStencilValue clearDepth = vk::ClearDepthStencilValue(1, 0)) + { + this->clearDepth = clearDepth; + useClearDepth = true; + UpdateBeginInfo(); + } + + void DisableClearDepth() + { + useClearDepth = false; + } + + virtual void UpdateBeginInfo() + { //TODO allow to control the render rect size + clearValues.clear(); + if (useClearColor) clearValues.emplace_back(clearColor); + if(frameBuffer->UseDepthBuffer() && useClearDepth) clearValues.emplace_back(clearDepth); + + beginInfo = vk::RenderPassBeginInfo(renderPass, vk::Framebuffer(), + vk::Rect2D(vk::Offset2D(), frameBuffer->GetSize2D()), + clearValues.size(), clearValues.data()); + } + + virtual void Begin(vk::CommandBuffer& commandBuffer) + { + beginInfo.framebuffer = frameBuffer->GetCurrentFrameBuffer(); + commandBuffer.beginRenderPass(beginInfo, vk::SubpassContents::eSecondaryCommandBuffers); + } + + virtual void End(vk::CommandBuffer& commandBuffer) + { + commandBuffer.endRenderPass(); + } + + FrameBuffer* GetFrameBuffer() const + { + return frameBuffer; + } + + protected: + virtual void CreateRenderPass() + { + std::vector attachments; + attachments.reserve(frameBuffer->UseDepthBuffer() ? 2 : 1); + + // Color attachment + attachments.emplace_back(vk::AttachmentDescriptionFlags(), frameBuffer->GetColorFormat(), vk::SampleCountFlagBits::e1, + vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eLoad, + vk::AttachmentStoreOp::eStore, vk::ImageLayout::eUndefined, vk::ImageLayout::ePresentSrcKHR); + + vk::AttachmentReference* depthReference = nullptr;; + if (frameBuffer->UseDepthBuffer()) + { // Depth attachment + attachments.emplace_back(vk::AttachmentDescriptionFlags(), frameBuffer->GetDepthFormat(), vk::SampleCountFlagBits::e1, + vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eDontCare, vk::AttachmentLoadOp::eClear, + vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal); + depthReference = new vk::AttachmentReference(1, vk::ImageLayout::eDepthStencilAttachmentOptimal); + } + + std::vector colorAttachmentReferences; + colorAttachmentReferences.emplace_back(0, vk::ImageLayout::eColorAttachmentOptimal); + + std::vector subPasses; + subPasses.emplace_back(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics, 0, nullptr, + colorAttachmentReferences.size(), colorAttachmentReferences.data(), nullptr, depthReference, 0, nullptr); + std::vector subPassDependencies; + subPassDependencies.emplace_back(0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eBottomOfPipe, vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, + vk::AccessFlagBits::eMemoryRead, vk::DependencyFlagBits::eByRegion); + subPassDependencies.emplace_back(VK_SUBPASS_EXTERNAL, 0, vk::PipelineStageFlagBits::eBottomOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::AccessFlagBits::eMemoryRead, + vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, + vk::DependencyFlagBits::eByRegion); + + const vk::RenderPassCreateInfo createInfo(vk::RenderPassCreateFlags(), attachments.size(), attachments.data(), + subPasses.size(), subPasses.data(), subPassDependencies.size(), subPassDependencies.data()); + CreateRenderPass(createInfo); + delete depthReference; + } + + void CreateRenderPass(const vk::RenderPassCreateInfo& renderPassCreateInfo) + { + renderPass = device.createRenderPass(renderPassCreateInfo); + } + }; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Vulkan/Renderer.hpp b/openVulkanoCpp/Vulkan/Renderer.hpp new file mode 100644 index 0000000..5844573 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Renderer.hpp @@ -0,0 +1,220 @@ +#pragma once +#include +#include +#include +#include "../Base/Render/IRenderer.hpp" +#include "../Base/UI/IWindow.hpp" +#include "../Base/Logger.hpp" +#include "Context.hpp" +#include "Resources/ResourceManager.hpp" +#include "../Data/ReadOnlyAtomicArrayQueue.hpp" +#include "CommandHelper.hpp" +#include "../Base/EngineConfiguration.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + struct WaitSemaphores + { + std::vector renderReady, renderComplete; + }; + + class Renderer : public IRenderer + { + Context context; + std::shared_ptr logger; + std::vector waitSemaphores; + Scene::Scene* scene = nullptr; + std::ofstream perfFile; + ResourceManager resourceManager; + uint32_t currentImageId = -1; + std::vector threadPool; + std::vector> commands; + std::vector> submitBuffers; + VulkanShader* shader; + + public: + Renderer() = default; + virtual ~Renderer() = default; + + void Init(IGraphicsAppManager* graphicsAppManager, IWindow* window) override + { + logger = Logger::RENDER; + logger->info("Initializing Vulkan renderer ..."); + IVulkanWindow* vulkanWindow = window->GetVulkanWindow(); + if (!vulkanWindow) + { + logger->error("The provided window is not compatible with Vulkan."); + throw std::runtime_error("The provided window is not compatible with Vulkan."); + } + context.Init(graphicsAppManager, vulkanWindow); + for (int i = 0; i < context.swapChain.GetImageCount(); i++) + { + waitSemaphores.emplace_back(); + waitSemaphores[i].renderComplete.push_back(context.device->device.createSemaphore({})); + waitSemaphores[i].renderReady.resize(2); + } + resourceManager.Init(&context, context.swapChain.GetImageCount()); + threadPool.resize(EngineConfiguration::GetEngineConfiguration()->GetNumThreads() - 1); + + //Setup cmd pools and buffers + commands.resize(threadPool.size() + 2); // One extra cmd object for the primary buffer and one for the main thread + for(uint32_t i = 0; i < commands.size(); i++) + { + commands[i] = std::vector(context.swapChain.GetImageCount()); + for(size_t j = 0; j < commands[i].size(); j++) + { + commands[i][j].Init(context.device->device, context.device->queueIndices.GetGraphics(), + (i == commands.size() - 1) ? vk::CommandBufferLevel::ePrimary : vk::CommandBufferLevel::eSecondary); + } + } + submitBuffers.resize(context.swapChain.GetImageCount()); + for(uint32_t i = 0; i < submitBuffers.size(); i++) + { + submitBuffers[i].resize(commands.size() - 1); + for (size_t j = 0; j < submitBuffers[i].size(); j++) + { + submitBuffers[i][j] = commands[j][i].cmdBuffer; + } + } + + shader = resourceManager.CreateShader(scene->shader); + + perfFile.open("perf.csv"); + perfFile << "sep=,\ntotal,fps\n"; + + logger->info("Vulkan renderer initialized"); + } + + void Tick() override + { + currentImageId = context.swapChain.AcquireNextImage(); + auto tickStart= std::chrono::high_resolution_clock::now(); + + Render(); + + // Perf logging + auto tickDone = std::chrono::high_resolution_clock::now(); + auto time = std::chrono::duration_cast(tickDone - tickStart).count(); + perfFile << time << ',' << 1000000000.0 / time << '\n'; + } + + void Close() override + { + perfFile.close(); + //context.Close(); + } + + std::string GetMainRenderDeviceName() override + { + return (context.device) ? context.device->GetDeviceName() : "Unknown"; + } + + void Resize(const uint32_t newWidth, const uint32_t newHeight) override + { + context.Resize(newWidth, newHeight); + resourceManager.Resize(); + } + + void SetScene(Scene::Scene* scene) override + { + this->scene = scene; + } + + Scene::Scene* GetScene() override + { + return scene; + } + + CommandHelper* GetCommandData(uint32_t poolId) + { + return &commands[poolId][currentImageId]; + } + + static void RunThread(Renderer* renderer, Data::ReadOnlyAtomicArrayQueue* jobQueue, uint32_t id) + { + renderer->RecordSecondaryBuffer(jobQueue, id); + } + + void StartThreads(Data::ReadOnlyAtomicArrayQueue* jobQueue) + { + for(uint32_t i = 0; i < threadPool.size(); i++) + { + threadPool[i] = std::thread(RunThread, this, jobQueue, i); + } + } + + void RecordPrimaryBuffer() + { + CommandHelper* cmdHelper = GetCommandData(commands.size() - 1); + cmdHelper->Reset(); + cmdHelper->cmdBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); + context.swapChainRenderPass.Begin(cmdHelper->cmdBuffer); + } + + void Submit() + { + for (auto& thread : threadPool) { thread.join(); } // Wait till everything is recorded + CommandHelper* cmdHelper = GetCommandData(commands.size() - 1); + cmdHelper->cmdBuffer.executeCommands(submitBuffers[currentImageId].size(), submitBuffers[currentImageId].data()); + context.swapChainRenderPass.End(cmdHelper->cmdBuffer); + cmdHelper->cmdBuffer.end(); + std::array stateFlags = { vk::PipelineStageFlags(vk::PipelineStageFlagBits::eColorAttachmentOutput), vk::PipelineStageFlags(vk::PipelineStageFlagBits::eColorAttachmentOutput) }; + waitSemaphores[currentImageId].renderReady[0] = resourceManager.EndFrame(); + waitSemaphores[currentImageId].renderReady[1] = context.swapChain.imageAvailableSemaphore; + vk::SubmitInfo si = vk::SubmitInfo( + waitSemaphores[currentImageId].renderReady.size(), waitSemaphores[currentImageId].renderReady.data(), stateFlags.data(), + 1, &cmdHelper->cmdBuffer, + waitSemaphores[currentImageId].renderComplete.size(), waitSemaphores[currentImageId].renderComplete.data()); + context.device->graphicsQueue.submit(1, &si, context.swapChain.GetCurrentSubmitFence()); + context.swapChain.Present(context.device->graphicsQueue, waitSemaphores[currentImageId].renderComplete); + } + + void Render() + { + resourceManager.StartFrame(currentImageId); + Data::ReadOnlyAtomicArrayQueue jobQueue(scene->shapeList); + StartThreads(&jobQueue); + RecordPrimaryBuffer(); + RecordSecondaryBuffer(&jobQueue, threadPool.size()); + Submit(); + } + + void RecordSecondaryBuffer(Data::ReadOnlyAtomicArrayQueue* jobQueue, uint32_t poolId) + { + Scene::Geometry* lastGeo = nullptr; + Scene::Node* lastNode = nullptr; + CommandHelper* cmdHelper = GetCommandData(poolId); + cmdHelper->Reset(); + vk::CommandBufferInheritanceInfo inheritance = { context.swapChainRenderPass.renderPass, 0, context.swapChainRenderPass.GetFrameBuffer()->GetCurrentFrameBuffer() }; + cmdHelper->cmdBuffer.begin(vk::CommandBufferBeginInfo{ vk::CommandBufferUsageFlagBits::eOneTimeSubmit | vk::CommandBufferUsageFlagBits::eRenderPassContinue, &inheritance }); + shader->Record(cmdHelper->cmdBuffer, currentImageId); + cmdHelper->cmdBuffer.pushConstants(context.pipeline.pipelineLayout, vk::ShaderStageFlagBits::eVertex, 0, 64, scene->GetCamera()->GetViewProjectionMatrixPointer()); + Scene::Drawable** drawablePointer; + while((drawablePointer = jobQueue->Pop()) != nullptr) + { + Scene::Drawable* drawable = *drawablePointer; + Scene::Geometry* mesh = drawable->mesh; + if (mesh != lastGeo) + { + if (!mesh->renderGeo) resourceManager.PrepareGeometry(mesh); + dynamic_cast(mesh->renderGeo)->Record(cmdHelper->cmdBuffer, currentImageId); + lastGeo = mesh; + } + for(Scene::Node* node : drawable->nodes) + { + if (node != lastNode) + { + if (!node->renderNode) resourceManager.PrepareNode(node); + dynamic_cast(node->renderNode)->Record(cmdHelper->cmdBuffer, currentImageId); + lastNode = node; + } + cmdHelper->cmdBuffer.drawIndexed(mesh->GetIndexCount(), 1, 0, 0, 0); + } + } + cmdHelper->cmdBuffer.end(); + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Resources/IShaderOwner.hpp b/openVulkanoCpp/Vulkan/Resources/IShaderOwner.hpp new file mode 100644 index 0000000..9af850f --- /dev/null +++ b/openVulkanoCpp/Vulkan/Resources/IShaderOwner.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "../Scene/VulkanShader.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + struct VulkanShader; + + class IShaderOwner + { + public: + virtual void RemoveShader(VulkanShader* shader) = 0; + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Resources/ManagedResource.hpp b/openVulkanoCpp/Vulkan/Resources/ManagedResource.hpp new file mode 100644 index 0000000..0b0c8da --- /dev/null +++ b/openVulkanoCpp/Vulkan/Resources/ManagedResource.hpp @@ -0,0 +1,93 @@ +#pragma once +#include + +namespace openVulkanoCpp +{ + namespace Vulkan + { + struct MemoryAllocation + { + vk::DeviceMemory memory; + size_t used, size; + uint32_t type; + + MemoryAllocation(size_t size, uint32_t type) + { + memory = nullptr; + this->size = size; + used = 0; + this->type = type; + } + + size_t FreeSpace() const + { + return size - used; + } + }; + + struct ManagedBuffer + { + MemoryAllocation* allocation; + vk::DeviceSize offset, size; + vk::Buffer buffer; + vk::BufferUsageFlags usage; + vk::MemoryPropertyFlags properties; + vk::Device device; + void* mapped = nullptr; + + bool IsLast() + { + return (offset + size == allocation->used); + } + + /** + * \brief Maps the buffer into the memory of the host. + * \tparam T The type of the buffers data. + * \param offset The offset from where to map the buffer. + * \param size The size to be mapped. VK_WHOLE_SIZE to map the whole buffer. + * \return The pointer to the mapped buffer. + */ + template + T* Map(size_t offset = 0, vk::DeviceSize size = VK_WHOLE_SIZE) + { + if (size == VK_WHOLE_SIZE) size = this->size; + mapped = device.mapMemory(allocation->memory, this->offset + offset, size, vk::MemoryMapFlags()); + return static_cast(mapped); + } + + /** + * \brief Un-maps the buffer from the host. + */ + void UnMap() + { + device.unmapMemory(allocation->memory); + mapped = nullptr; + } + + void Copy(void* data) const + { + if(mapped) + { + memcpy(mapped, data, size); + } + else + { + void* dataMapped = device.mapMemory(allocation->memory, offset, size); + memcpy(dataMapped, data, size); + device.unmapMemory(allocation->memory); + } + } + + void Copy(void* data, uint32_t size, uint32_t offset) const + { + if(mapped) memcpy(static_cast(mapped) + offset, data, size); + else + { + void* dataMapped = device.mapMemory(allocation->memory, this->offset + offset, size); + memcpy(dataMapped, data, size); + device.unmapMemory(allocation->memory); + } + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Resources/ResourceManager.hpp b/openVulkanoCpp/Vulkan/Resources/ResourceManager.hpp new file mode 100644 index 0000000..47ba50a --- /dev/null +++ b/openVulkanoCpp/Vulkan/Resources/ResourceManager.hpp @@ -0,0 +1,258 @@ +#pragma once +#include "vulkan/vulkan.hpp" +#include "../Device.hpp" +#include "../../Base/ICloseable.hpp" +#include "../Scene/VulkanShader.hpp" +#include "IShaderOwner.hpp" +#include "../Scene/VulkanGeometry.hpp" +#include "ManagedResource.hpp" +#include "../Scene/VulkanNode.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class ResourceManager : virtual public ICloseable, virtual public IShaderOwner + { + Context* context; + vk::Device device = nullptr; + vk::Queue transferQueue = nullptr; + vk::CommandPool* cmdPools = nullptr; + vk::CommandBuffer* cmdBuffers = nullptr; + vk::Semaphore* semaphores = nullptr; + std::vector allocations; + std::vector shaders; + MemoryAllocation* lastAllocation = nullptr; + std::mutex mutex; + vk::DeviceSize uniformBufferAlignment; + std::vector> toFree; + std::vector recycleBuffers; + + int buffers = -1, currentBuffer = -1; + + public: + ResourceManager() = default; + virtual ~ResourceManager() { if (device) ResourceManager::Close(); } + + void Init(Context* context, int buffers = 2) + { + this->context = context; + this->device = context->device->device; + this->buffers = buffers; + + uniformBufferAlignment = context->device->properties.limits.minUniformBufferOffsetAlignment; + + cmdPools = new vk::CommandPool[buffers]; + cmdBuffers = new vk::CommandBuffer[buffers]; + semaphores = new vk::Semaphore[buffers]; + for (int i = 0; i < buffers; i++) + { + cmdPools[i] = this->device.createCommandPool({ {}, context->device->queueIndices.transfer }); + cmdBuffers[i] = this->device.allocateCommandBuffers({ cmdPools[i], vk::CommandBufferLevel::ePrimary, 1 })[0]; + semaphores[i] = this->device.createSemaphore({}); + } + toFree.resize(buffers); + + transferQueue = this->device.getQueue(context->device->queueIndices.transfer, 0); + } + + void Close() override + { + transferQueue.waitIdle(); + for (int i = 0; i < buffers; i++) + { + device.freeCommandBuffers(cmdPools[i], 1, &cmdBuffers[i]); + device.destroyCommandPool(cmdPools[0]); + } + for (auto shader : shaders) + { + shader->Close(); + } + cmdBuffers = nullptr; + cmdPools = nullptr; + device = nullptr; + } + + void StartFrame(uint64_t frameId) + { + currentBuffer = frameId; + FreeBuffers(); + device.resetCommandPool(cmdPools[currentBuffer], {}); + cmdBuffers[currentBuffer].begin({ vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); + } + + vk::Semaphore EndFrame() + { + cmdBuffers[currentBuffer].end(); + vk::SubmitInfo si = { 0, nullptr, nullptr, 1, &cmdBuffers[currentBuffer], 1, &semaphores[currentBuffer] }; + transferQueue.submit(1, &si, vk::Fence()); + return semaphores[currentBuffer]; + } + + void Resize() + { + for (auto shader : shaders) + { + Scene::Shader* s = shader->shader; + shader->Close(); + shader->Init(context, s, this); + } + } + + void PrepareGeometry(Scene::Geometry* geometry) + { + mutex.lock(); + if(!geometry->renderGeo) + { + VulkanGeometry* vkGeometry = new VulkanGeometry(); + ManagedBuffer* vertexBuffer = CreateDeviceOnlyBufferWithData(sizeof(Vertex) * geometry->GetVertexCount(), vk::BufferUsageFlagBits::eVertexBuffer, geometry->GetVertices()); + ManagedBuffer* indexBuffer = CreateDeviceOnlyBufferWithData(Utils::EnumAsInt(geometry->indexType) * geometry->GetIndexCount(), vk::BufferUsageFlagBits::eIndexBuffer, geometry->GetIndices()); + vkGeometry->Init(geometry, vertexBuffer->buffer, indexBuffer->buffer); + geometry->renderGeo = vkGeometry; + } + mutex.unlock(); + } + + void PrepareMaterial(Scene::Material* material) + { + mutex.lock(); + if(!material->shader->renderShader) + { + material->shader->renderShader = CreateShader(material->shader); + } + mutex.unlock(); + } + + void PrepareNode(Scene::Node* node) + { + mutex.lock(); + if (!node->renderNode) + { + UniformBuffer* uBuffer = new UniformBuffer(); + ManagedBuffer* buffer; + VulkanNode* vkNode; + const vk::DeviceSize allocSize = aligned(sizeof(glm::mat4x4), uniformBufferAlignment); + if (node->GetUpdateFrequency() != Scene::UpdateFrequency::Never) + { + vkNode = new VulkanNodeDynamic(); + uint32_t imgs = context->swapChain.GetImageCount(); + buffer = CreateBuffer(imgs * allocSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible); + buffer->Map(); + } + else + { + vkNode = new VulkanNode(); + buffer = CreateDeviceOnlyBufferWithData(sizeof(glm::mat4), vk::BufferUsageFlagBits::eUniformBuffer, &node->worldMat); + } + uBuffer->Init(buffer, allocSize, &context->pipeline.descriptorSetLayout, context->pipeline.pipelineLayout); + vkNode->Init(node, uBuffer); + node->renderNode = vkNode; + } + mutex.unlock(); + } + + void RemoveShader(VulkanShader* shader) override + { + Utils::Remove(shaders, shader); + } + + protected: // Allocation management + static vk::DeviceSize aligned(vk::DeviceSize size, vk::DeviceSize byteAlignment) + { + return (size + byteAlignment - 1) & ~(byteAlignment - 1); + } + + void FreeBuffer(ManagedBuffer* buffer) + { + toFree[currentBuffer].push_back(buffer); + } + + void DoFreeBuffer(ManagedBuffer* buffer) + { + if (buffer->IsLast()) + { + device.destroyBuffer(buffer->buffer); + buffer->allocation->used -= buffer->size; + } + else + { + recycleBuffers.push_back(buffer); + } + } + + void FreeBuffers() + { + for (auto& i : toFree[currentBuffer]) + { + DoFreeBuffer(i); + } + toFree[currentBuffer].clear(); + } + + ManagedBuffer* CreateDeviceOnlyBufferWithData(vk::DeviceSize size, vk::BufferUsageFlagBits usage, void* data) + { + ManagedBuffer* target = CreateBuffer(size, usage | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); + ManagedBuffer* uploadBuffer = CreateBuffer(size, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible); + uploadBuffer->Copy(data, size, 0); + RecordCopy(uploadBuffer->buffer, target->buffer, size); + FreeBuffer(uploadBuffer); + return target; + } + + void RecordCopy(vk::Buffer src, vk::Buffer dest, vk::DeviceSize size) const + { + vk::BufferCopy copyRegion = { 0, 0, size }; + cmdBuffers[currentBuffer].copyBuffer(src, dest, 1, ©Region); + } + + ManagedBuffer* CreateBuffer(vk::DeviceSize size, const vk::BufferUsageFlags& usage, const vk::MemoryPropertyFlags& properties) + { + size = aligned(size, uniformBufferAlignment); + const vk::BufferCreateInfo bufferCreateInfo = { {}, size, usage, vk::SharingMode::eExclusive }; + vk::Buffer buffer = device.createBuffer(bufferCreateInfo); + const vk::MemoryRequirements memoryRequirements = device.getBufferMemoryRequirements(buffer); + uint32_t memtype = context->device->GetMemoryType(memoryRequirements.memoryTypeBits, properties); + if (memoryRequirements.size != size) Logger::DATA->warn("Memory Requirement Size ({0}) != Size ({1})", memoryRequirements.size, size); + MemoryAllocation* allocation = GetFreeMemoryAllocation(memoryRequirements.size, memtype); + uint32_t offset = allocation->used; + device.bindBufferMemory(buffer, allocation->memory, offset); + allocation->used += memoryRequirements.size; + return new ManagedBuffer{ allocation, offset, size, buffer, usage, properties, device, nullptr }; + } + + MemoryAllocation* CreateMemoryAllocation(size_t size, uint32_t type, bool addToCache = true) + { + MemoryAllocation* alloc = new MemoryAllocation(size, type); + const vk::MemoryAllocateInfo allocInfo = { size, type }; + alloc->memory = device.allocateMemory(allocInfo); + if (addToCache) allocations.push_back(alloc); + return alloc; + } + + MemoryAllocation* GetFreeMemoryAllocation(size_t size, uint32_t type, bool createIfAllFull = true) + { + MemoryAllocation* alloc = nullptr; + for (MemoryAllocation* allocation : allocations) + { + if (allocation->type == type && allocation->FreeSpace() >= size) + { + alloc = allocation; + break; + } + } + if(!alloc && createIfAllFull) alloc = CreateMemoryAllocation(256 * 1024 * 1024, type, true); + if(alloc) lastAllocation = alloc; + return alloc; + } + + public: + VulkanShader* CreateShader(Scene::Shader* shader) + { + VulkanShader* vkShader = new VulkanShader(); + vkShader->Init(context, shader, this); + shaders.push_back(vkShader); + return vkShader; + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Resources/UniformBuffer.hpp b/openVulkanoCpp/Vulkan/Resources/UniformBuffer.hpp new file mode 100644 index 0000000..1a91157 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Resources/UniformBuffer.hpp @@ -0,0 +1,54 @@ +#pragma once +#include "../../Base/ICloseable.hpp" +#include "../Scene/IRecordable.hpp" +#include "ManagedResource.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + struct UniformBuffer : virtual ICloseable, virtual IRecordable + { + ManagedBuffer* buffer; + vk::DescriptorPool descPool; + vk::DescriptorSet descSet; + vk::PipelineLayout layout; + uint32_t allocSizeFrame; + + void Init(ManagedBuffer* buffer, uint32_t allocSizeFrame, vk::DescriptorSetLayout* descriptorSetLayout, vk::PipelineLayout layout) + { + this->buffer = buffer; + this->layout = layout; + this->allocSizeFrame = allocSizeFrame; + vk::DescriptorPoolSize poolSize = { vk::DescriptorType::eUniformBufferDynamic, 1 }; + const vk::DescriptorPoolCreateInfo poolCreateInfo = { {}, 1, 1, &poolSize }; + descPool = buffer->device.createDescriptorPool(poolCreateInfo); + const vk::DescriptorSetAllocateInfo descSetAllocInfo = { descPool, 1, descriptorSetLayout }; + descSet = buffer->device.allocateDescriptorSets(descSetAllocInfo)[0]; + vk::DescriptorBufferInfo bufferInfo = { buffer->buffer, 0, allocSizeFrame }; + vk::WriteDescriptorSet writeDescriptorSet = { descSet }; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.descriptorType = vk::DescriptorType::eUniformBufferDynamic; + writeDescriptorSet.pBufferInfo = &bufferInfo; + buffer->device.updateDescriptorSets(1, &writeDescriptorSet, 0, nullptr); + } + + void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override + { + uint32_t frameOffset = allocSizeFrame * bufferId; + cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, 1, + &descSet, 1, &frameOffset); + } + + void Update(void* data, uint32_t size, uint32_t bufferId) const + { + buffer->Copy(data, size, allocSizeFrame * bufferId); + } + + void Close() override + { + buffer->device.destroyDescriptorPool(descPool); + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Scene/IRecordable.hpp b/openVulkanoCpp/Vulkan/Scene/IRecordable.hpp new file mode 100644 index 0000000..b9752c0 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Scene/IRecordable.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "vulkan/vulkan.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class IRecordable + { + public: + virtual ~IRecordable() = default; + virtual void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) = 0; + }; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Vulkan/Scene/VulkanGeometry.hpp b/openVulkanoCpp/Vulkan/Scene/VulkanGeometry.hpp new file mode 100644 index 0000000..7e13b41 --- /dev/null +++ b/openVulkanoCpp/Vulkan/Scene/VulkanGeometry.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "IRecordable.hpp" +#include "../../Scene/Scene.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class VulkanGeometry : virtual public IRecordable, virtual public ICloseable + { + Scene::Geometry* geometry = nullptr; + vk::Buffer vertexBuffer, indexBuffer; + vk::IndexType indexType; + vk::DeviceSize* offsets = new vk::DeviceSize(); + + public: + VulkanGeometry() = default; + virtual ~VulkanGeometry() { if (vertexBuffer) VulkanGeometry::Close(); }; + + void Init(Scene::Geometry* geo, vk::Buffer vertexBuffer, vk::Buffer indexBuffer) + { + this->geometry = geo; + offsets[0] = 0; + indexType = (geo->indexType == Scene::VertexIndexType::UINT16) ? vk::IndexType::eUint16 : vk::IndexType::eUint32; + this->vertexBuffer = vertexBuffer; + this->indexBuffer = indexBuffer; + } + + void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override + { + cmdBuffer.bindVertexBuffers(0, 1, &vertexBuffer, offsets); + cmdBuffer.bindIndexBuffer(indexBuffer, 0, indexType); + } + + void Close() override + { + + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Scene/VulkanNode.hpp b/openVulkanoCpp/Vulkan/Scene/VulkanNode.hpp new file mode 100644 index 0000000..92200bb --- /dev/null +++ b/openVulkanoCpp/Vulkan/Scene/VulkanNode.hpp @@ -0,0 +1,53 @@ +#pragma once +#include "../../Base/ICloseable.hpp" +#include "IRecordable.hpp" +#include "../../Scene/Camera.hpp" +#include "../Resources/UniformBuffer.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + struct VulkanNode : virtual IRecordable, virtual ICloseable + { + Scene::Node* node = nullptr; + UniformBuffer* buffer = nullptr; + + virtual void Init(Scene::Node* node, UniformBuffer* uniformBuffer) + { + this->node = node; + this->buffer = uniformBuffer; + } + + void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override + { + buffer->Record(cmdBuffer, 0); + } + + void Close() override {} + }; + + struct VulkanNodeDynamic : VulkanNode + { + uint32_t lastUpdate = -1; + + void Init(Scene::Node* node, UniformBuffer* uniformBuffer) override + { + VulkanNode::Init(node, uniformBuffer); + lastUpdate = -1; + } + + void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override + { + if(bufferId != lastUpdate) + { + lastUpdate = bufferId; + buffer->Update(&node->worldMat, sizeof(glm::mat4x4), bufferId); + } + buffer->Record(cmdBuffer, bufferId); + } + + void Close() override{} + }; + } +} diff --git a/openVulkanoCpp/Vulkan/Scene/VulkanShader.hpp b/openVulkanoCpp/Vulkan/Scene/VulkanShader.hpp new file mode 100644 index 0000000..dc153bf --- /dev/null +++ b/openVulkanoCpp/Vulkan/Scene/VulkanShader.hpp @@ -0,0 +1,97 @@ +#pragma once +#include +#include "../Device.hpp" +#include "../../Scene/Shader.hpp" +#include "../../Scene/Vertex.hpp" +#include "../../Base/ICloseable.hpp" +#include "../Resources/IShaderOwner.hpp" +#include "IRecordable.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + vk::PrimitiveTopology ToVkTopology(Scene::Topology topology) + { + switch (topology) { + case Scene::Topology::PointList: return vk::PrimitiveTopology::ePointList; + case Scene::Topology::LineList: return vk::PrimitiveTopology::eLineList; + case Scene::Topology::LineStripe: return vk::PrimitiveTopology::eLineStrip; + case Scene::Topology::TriangleList: return vk::PrimitiveTopology::eTriangleList; + case Scene::Topology::TriangleStripe: return vk::PrimitiveTopology::eTriangleStrip; + default: throw std::runtime_error("Unknown topology!"); + } + } + + struct VulkanShader : virtual public ICloseable, virtual public IRecordable + { + Scene::Shader* shader = nullptr; + vk::Device device; + vk::ShaderModule shaderModuleVertex, shaderModuleFragment; + vk::Pipeline pipeline; + IShaderOwner* owner; + + VulkanShader() = default; + virtual ~VulkanShader() { if (shader) VulkanShader::Close(); } + + void Init(Context* context, Scene::Shader* shader, IShaderOwner* owner) + { + this->device = context->device->device; + this->shader = shader; + this->owner = owner; + shaderModuleVertex = context->device->CreateShaderModule(shader->vertexShaderName + ".vert.spv"); + shaderModuleFragment = context->device->CreateShaderModule(shader->fragmentShaderName + ".frag.spv"); + std::vector shaderStageCreateInfos(2); + shaderStageCreateInfos[0] = { {}, vk::ShaderStageFlagBits::eVertex, shaderModuleVertex, "main" }; + shaderStageCreateInfos[1] = { {}, vk::ShaderStageFlagBits::eFragment, shaderModuleFragment, "main" }; + + auto vertexBindDesc = vk::VertexInputBindingDescription(0, sizeof(Vertex), vk::VertexInputRate::eVertex); + std::vector attributeDescriptions; + attributeDescriptions.emplace_back(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, position)); + attributeDescriptions.emplace_back(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal)); + attributeDescriptions.emplace_back(2, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, tangent)); + attributeDescriptions.emplace_back(3, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, biTangent)); + attributeDescriptions.emplace_back(4, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, textureCoordinates)); + attributeDescriptions.emplace_back(5, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Vertex, color)); + + auto viewport = context->swapChain.GetFullscreenViewport(); + auto scissor = context->swapChain.GetFullscreenScissor(); + vk::PipelineViewportStateCreateInfo viewportStateCreateInfo = { {}, 1, &viewport, 1, &scissor }; + vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = { {}, 1, &vertexBindDesc, + static_cast(attributeDescriptions.size()), attributeDescriptions.data() }; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly = { {}, ToVkTopology(shader->topology), 0 }; + vk::PipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.cullMode = vk::CullModeFlagBits::eBack; + vk::PipelineMultisampleStateCreateInfo msaa = {}; + vk::PipelineDepthStencilStateCreateInfo depth = { {}, 1, 1, vk::CompareOp::eGreater }; + vk::PipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eA | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eR; + vk::PipelineColorBlendStateCreateInfo colorInfo = {}; + colorInfo.attachmentCount = 1; + colorInfo.pAttachments = &colorBlendAttachment; + + + + vk::GraphicsPipelineCreateInfo pipelineCreateInfo = { {}, static_cast(shaderStageCreateInfos.size()), shaderStageCreateInfos.data(), &pipelineVertexInputStateCreateInfo, &inputAssembly, + nullptr, &viewportStateCreateInfo, &rasterizer, &msaa, &depth, &colorInfo, nullptr, context->pipeline.pipelineLayout, context->swapChainRenderPass.renderPass }; + pipeline = this->device.createGraphicsPipeline({}, pipelineCreateInfo); + + } + + void Record(vk::CommandBuffer& cmdBuffer, uint32_t bufferId) override + { + cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + } + + void Close() override + { + owner->RemoveShader(this); + shader = nullptr; + device.destroyPipeline(pipeline); + device.destroyShaderModule(shaderModuleVertex); + device.destroyShaderModule(shaderModuleFragment); + } + }; + + } +} diff --git a/openVulkanoCpp/Vulkan/SwapChain.hpp b/openVulkanoCpp/Vulkan/SwapChain.hpp new file mode 100644 index 0000000..b775d41 --- /dev/null +++ b/openVulkanoCpp/Vulkan/SwapChain.hpp @@ -0,0 +1,261 @@ +#pragma once +#include +#include +#include "Device.hpp" +#include "Image.hpp" +#include "FrameBuffer.hpp" + +namespace openVulkanoCpp +{ + namespace Vulkan + { + struct SwapChainImage : virtual public IImage + { + vk::Image image; + vk::ImageView view; + vk::Fence fence; + + + vk::Image GetImage() override + { + return image; + } + + vk::ImageView GetView() override + { + return view; + } + }; + + class SwapChain : public FrameBuffer + { + vk::SurfaceKHR surface; + std::vector images; + Device* device = nullptr; + IVulkanWindow* window = nullptr; + vk::SurfaceFormatKHR surfaceFormat; + vk::PresentModeKHR presentMode; + vk::Viewport fullscreenViewport; + bool useVsync = false; + + uint32_t preferredImageCount = 2; //TODO add option + vk::Extent2D size{0,0}; + + public: + vk::SwapchainKHR swapChain; + vk::Semaphore imageAvailableSemaphore; + + SwapChain() = default; + ~SwapChain() { if (device) SwapChain::Close(); } + + void Init(Device* device, vk::SurfaceKHR surface, IVulkanWindow* window) + { + if (!device) throw std::runtime_error("The device must not be null"); + if (!window) throw std::runtime_error("The window must not be null"); + this->device = device; + this->surface = surface; + this->window = window; + + imageAvailableSemaphore = device->device.createSemaphore({}); + CreateSwapChain({window->GetWidth(), window->GetHeight() }); + + FrameBuffer::Init(device, vk::Extent3D(size, 1)); + } + + void Close() override + { + DestroySwapChain(); + device->device.destroySemaphore(imageAvailableSemaphore); + device = nullptr; + FrameBuffer::Close(); + } + + void Resize(const uint32_t newWidth, const uint32_t newHeight) + { + if(newWidth == 0 || newHeight == 0) return; // Swap chain size of 0 pixel is not allowed + + CreateSwapChain({ newWidth, newHeight }); + FrameBuffer::Resize(vk::Extent3D(size, 1)); + } + + vk::Extent2D GetSize() const + { + return size; + } + + vk::Viewport GetFullscreenViewport() const + { + return fullscreenViewport; + } + + vk::Rect2D GetFullscreenScissor() const + { + return { {0,0}, GetSize() }; + } + + uint32_t AcquireNextImage(const vk::Fence fence = vk::Fence()) + { + const auto resultValue = device->device.acquireNextImageKHR(swapChain, UINT64_MAX, imageAvailableSemaphore, fence); + const vk::Result result = resultValue.result; + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) throw std::error_code(result); + SetCurrentFrameId(resultValue.value); + + vk::Fence submitFence = GetCurrentSubmitFence(); + device->device.waitForFences(1, &submitFence, true, -1); + device->device.resetFences(1, &submitFence); + + return currentFrameBufferId; + } + + vk::Fence& GetCurrentSubmitFence() + { + return images[currentFrameBufferId].fence; + } + + void Present(vk::Queue& queue ,std::vector& semaphores) const + { + queue.presentKHR(vk::PresentInfoKHR(semaphores.size(), semaphores.data(), + 1, &swapChain, ¤tFrameBufferId)); + } + + bool UseVsync() { return useVsync; } + + void SetVsync(bool useVsync) + { //TODO change swap chain + this->useVsync = useVsync; + } + + uint32_t GetImageCount() const + { + return images.size(); + } + + private: + void CreateSwapChain(vk::Extent2D size) + { + Logger::RENDER->debug("Creating swap chain for window {0} ...", window->GetWindowId()); + surfaceFormat = ChoseSurfaceFormat(); + presentMode = ChosePresentMode(); + const vk::SurfaceCapabilitiesKHR surfaceCapabilities = device->physicalDevice.getSurfaceCapabilitiesKHR(surface); + if(surfaceCapabilities.currentExtent.width != ~static_cast(0)) + { // The surface does provide it's size to the vulkan driver + size = surfaceCapabilities.currentExtent; + } + + vk::SurfaceTransformFlagBitsKHR preTransform; //TODO add option to allow rotation and other modifications + if (surfaceCapabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) + { preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity; } + else { preTransform = surfaceCapabilities.currentTransform; } + + uint32_t usingImages = std::max(preferredImageCount, surfaceCapabilities.minImageCount); + if (surfaceCapabilities.maxImageCount > 0) //GPU has limit of swap chain images + usingImages = std::min(usingImages, surfaceCapabilities.maxImageCount); + Logger::RENDER->debug("GPU supports {0} to {1} swap chain images. Preferred: {2}, Using: {3}", surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount, preferredImageCount, usingImages); + + const vk::SwapchainCreateInfoKHR createInfo({}, surface, usingImages, surfaceFormat.format, + surfaceFormat.colorSpace, size, 1, + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst, + vk::SharingMode::eExclusive, 0, nullptr, preTransform, + vk::CompositeAlphaFlagBitsKHR::eOpaque, presentMode, VK_TRUE, swapChain); + const vk::SwapchainKHR newSwapChain = device->device.createSwapchainKHR(createInfo); + + DestroySwapChain(); + swapChain = newSwapChain; + this->size = size; + + CreateSwapChainImages(); + + fullscreenViewport = vk::Viewport{ 0, 0, (float)size.width, (float)size.height, 0, 1 }; + Logger::RENDER->debug("Swap chain for window {0} created", window->GetWindowId()); + } + + void CreateSwapChainImages() + { + vk::ImageViewCreateInfo imgViewCreateInfo; + imgViewCreateInfo.format = surfaceFormat.format; + imgViewCreateInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + imgViewCreateInfo.subresourceRange.levelCount = 1; + imgViewCreateInfo.subresourceRange.layerCount = 1; + imgViewCreateInfo.viewType = vk::ImageViewType::e2D; + + auto swapChainImages = device->device.getSwapchainImagesKHR(swapChain); + + images.resize(swapChainImages.size()); + for (uint32_t i = 0; i < swapChainImages.size(); i++) + { + images[i].image = swapChainImages[i]; + imgViewCreateInfo.image = swapChainImages[i]; + images[i].view = device->device.createImageView(imgViewCreateInfo); + images[i].fence = device->device.createFence({ vk::FenceCreateFlags(vk::FenceCreateFlagBits::eSignaled)}); + } + } + + void DestroySwapChain() const + { + for(auto& image : images) + { + device->device.destroyImageView(image.view); + device->device.destroyFence(image.fence); + } + device->device.destroySwapchainKHR(swapChain); + } + + protected: + vk::Format FindColorFormat() override + { + return surfaceFormat.format; + } + + virtual vk::PresentModeKHR ChosePresentMode() + { + std::vector presentModes = device->physicalDevice.getSurfacePresentModesKHR(surface); +#ifdef DEBUG + std::string availableModes = ""; + for (const auto& presentMode : presentModes) + { + if (availableModes.length() > 0) availableModes += ", "; + availableModes += vk::to_string(presentMode); + } + Logger::RENDER->debug("Available swap chain present modes {0}. Searching best mode for: vsync={1}", availableModes, useVsync); +#endif + vk::PresentModeKHR mode = vk::PresentModeKHR::eFifo; + if (useVsync) + { + if (Utils::Contains(presentModes, vk::PresentModeKHR::eMailbox)) mode = vk::PresentModeKHR::eMailbox; + } + else + { + if (Utils::Contains(presentModes, vk::PresentModeKHR::eImmediate)) mode = vk::PresentModeKHR::eImmediate; + else if (Utils::Contains(presentModes, vk::PresentModeKHR::eFifoRelaxed)) mode = vk::PresentModeKHR::eFifoRelaxed; + } + Logger::RENDER->debug("Using swap chain present mode {0}", vk::to_string(mode)); + return mode; + } + + virtual vk::SurfaceFormatKHR ChoseSurfaceFormat() + { //TODO allow to chose output format + std::vector surfaceFormats = device->physicalDevice.getSurfaceFormatsKHR(surface); + if(surfaceFormats.size() == 1 && surfaceFormats[0].format == vk::Format::eUndefined) + { // GPU does not have a preferred surface format + return vk::SurfaceFormatKHR{ vk::Format::eB8G8R8A8Unorm, surfaceFormats[0].colorSpace }; + } + else + { //TODO chose best fitting + return surfaceFormats[0]; + } + } + + + public: + std::vector GetImages() override + { + std::vector imgs; + for (auto& image : images) + { + imgs.push_back(&image); + } + return imgs; + } + }; + } +} diff --git a/openVulkanoCpp/Vulkan/VulkanUtils.hpp b/openVulkanoCpp/Vulkan/VulkanUtils.hpp new file mode 100644 index 0000000..9b7ed10 --- /dev/null +++ b/openVulkanoCpp/Vulkan/VulkanUtils.hpp @@ -0,0 +1,41 @@ +#pragma once +#include + +namespace openVulkanoCpp +{ + namespace Vulkan + { + class VulkanUtils + { + public: + static vk::AccessFlags GetAccessFlagsForLayout(vk::ImageLayout layout) + { + switch (layout) + { + case vk::ImageLayout::ePreinitialized: return vk::AccessFlagBits::eHostWrite; + case vk::ImageLayout::eTransferSrcOptimal: return vk::AccessFlagBits::eTransferRead; + case vk::ImageLayout::eTransferDstOptimal: return vk::AccessFlagBits::eTransferWrite; + case vk::ImageLayout::eShaderReadOnlyOptimal: return vk::AccessFlagBits::eShaderRead; + case vk::ImageLayout::eColorAttachmentOptimal: return vk::AccessFlagBits::eColorAttachmentWrite; + case vk::ImageLayout::eDepthStencilAttachmentOptimal: return vk::AccessFlagBits::eDepthStencilAttachmentWrite; + default: return vk::AccessFlags(); + } + } + + static vk::PipelineStageFlags GetPipelineStageForLayout(vk::ImageLayout layout) + { + switch (layout) + { + case vk::ImageLayout::ePreinitialized: return vk::PipelineStageFlagBits::eHost; + case vk::ImageLayout::eTransferDstOptimal: + case vk::ImageLayout::eTransferSrcOptimal: return vk::PipelineStageFlagBits::eTransfer; + case vk::ImageLayout::eShaderReadOnlyOptimal: return vk::PipelineStageFlagBits::eFragmentShader; + case vk::ImageLayout::eColorAttachmentOptimal: return vk::PipelineStageFlagBits::eColorAttachmentOutput; + case vk::ImageLayout::eDepthStencilAttachmentOptimal: return vk::PipelineStageFlagBits::eEarlyFragmentTests; + case vk::ImageLayout::eUndefined: return vk::PipelineStageFlagBits::eTopOfPipe; + default: return vk::PipelineStageFlagBits::eBottomOfPipe; + } + } + }; + } +} diff --git a/openVulkanoCpp/main.cpp b/openVulkanoCpp/main.cpp new file mode 100644 index 0000000..f729ad1 --- /dev/null +++ b/openVulkanoCpp/main.cpp @@ -0,0 +1,108 @@ +#include "Host/GraphicsAppManager.hpp" +#include "Scene/Scene.hpp" +#include "Scene/Shader.hpp" +#include "Base/EngineConfiguration.hpp" + +using namespace openVulkanoCpp::Scene; + +uint32_t GEOS = 3000, OBJECTS = 10000, DYNAMIC = 1000; + +class ExampleApp : public openVulkanoCpp::IGraphicsApp +{ + Scene scene; + PerspectiveCamera cam; + Material mat; + Shader shader; + std::vector drawablesPool; + std::vector nodesPool; + +public: + std::string GetAppName() override { return "ExampleApp"; } + std::string GetAppVersion() override { return "v1.0"; } + int GetAppVersionAsInt() override { return 1; } + + void Init() override + { + std::srand(1); + scene.Init(); + cam.Init(70, 16, 9, 0.1f, 100); + scene.SetCamera(&cam); + cam.SetMatrix(glm::translate(glm::mat4(1), glm::vec3(0,0,-10))); + shader.Init("Shader\\basic", "Shader\\basic"); + drawablesPool.resize(GEOS); + for(int i = 0; i < GEOS; i++) + { + Geometry* geo = new Geometry(); + geo->InitCube(std::rand() % 1000 / 1000.0f + 0.01f, std::rand() % 1000 / 1000.0f + 0.01f, std::rand() % 1000 / 1000.0f + 0.01f, glm::vec4((std::rand() % 255) / 255.0f, (std::rand() % 255) / 255.0f, (std::rand() % 255) / 255.0f, 1)); + drawablesPool[i] = new Drawable(); + drawablesPool[i]->Init(geo, &mat); + } + nodesPool.resize(OBJECTS); + for(int i = 0; i < OBJECTS; i++) + { + nodesPool[i] = new Node(); + nodesPool[i]->Init(); + scene.GetRoot()->AddChild(nodesPool[i]); + if (i < DYNAMIC) nodesPool[i]->SetUpdateFrequency(UpdateFrequency::Always); + nodesPool[i]->AddDrawable(drawablesPool[std::rand() % GEOS]); + nodesPool[i]->SetMatrix(glm::translate(glm::mat4x4(1), glm::vec3((std::rand() % 10000) / 1000.0f - 5, (std::rand() % 10000) / 1000.0f - 5, (std::rand() % 10000) / 1000.0f - 5))); + } + + scene.shader = &shader; + + GetGraphicsAppManager()->GetRenderer()->SetScene(&scene); + } + + void Tick() override + { + for(int i = 0; i < DYNAMIC; i++) + { + nodesPool[i]->SetMatrix(glm::translate(glm::mat4x4(1), glm::vec3((std::rand() % 10000) / 1000.0f - 5, (std::rand() % 10000) / 1000.0f - 5, (std::rand() % 10000) / 1000.0f - 5))); + } + } + + void Close() override{} +}; + +#include +#include +#include + +int main(int argc, char** argv) +{ + std::cout << "Amount of Threads to use [2]: "; + int threads = 2; + std::string input; + std::getline(std::cin, input); + if (!input.empty()) + { + std::istringstream stream(input); + stream >> threads; + } + std::cout << "Amount of geometries to produce [" << GEOS << "]: "; + std::getline(std::cin, input); + if (!input.empty()) + { + std::istringstream stream(input); + stream >> GEOS; + } + std::cout << "Amount of objects to render [" << OBJECTS << "]: "; + std::getline(std::cin, input); + if (!input.empty()) + { + std::istringstream stream(input); + stream >> OBJECTS; + } + std::cout << "Amount of moving objects [" << DYNAMIC << "]: "; + std::getline(std::cin, input); + if (!input.empty()) + { + std::istringstream stream(input); + stream >> DYNAMIC; + } + DYNAMIC = std::min(DYNAMIC, OBJECTS); + openVulkanoCpp::EngineConfiguration::GetEngineConfiguration()->SetNumThreads(threads); + openVulkanoCpp::IGraphicsAppManager* manager = new openVulkanoCpp::GraphicsAppManager(new ExampleApp()); + manager->Run(); + return 0; +} diff --git a/openVulkanoCpp/openVulkanoCpp.vcxproj b/openVulkanoCpp/openVulkanoCpp.vcxproj new file mode 100644 index 0000000..efda132 --- /dev/null +++ b/openVulkanoCpp/openVulkanoCpp.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {D546A70B-536A-487A-91E1-1CD4563A0104} + openVulkanoCpp + 10.0 + + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(VULKAN_SDK)\Include;$(SolutionDir)\external\spdlog\include;C:\Program Files\Assimp\include + true + _MBCS;%(PreprocessorDefinitions);DEBUG + + + Console + true + vulkan-1.lib;assimp-vc140-mt.lib;%(AdditionalDependencies) + $(VULKAN_SDK)\Lib;C:\Program Files\Assimp\lib\x64;%(AdditionalLibraryDirectories) + mainCRTStartup + + + call "$(ProjectDir)Shader\CompileShaders.bat" +xcopy /y "$(ProjectDir)Shader\*.spv" "$(OutDir)\Shader\" + + + + + Level3 + MaxSpeed + true + true + true + true + $(VULKAN_SDK)\Include;$(SolutionDir)\external\spdlog\include;C:\Program Files\Assimp\include + Speed + + + Console + true + true + true + vulkan-1.lib;assimp-vc140-mt.lib;%(AdditionalDependencies) + $(VULKAN_SDK)\Lib;C:\Program Files\Assimp\lib\x64;%(AdditionalLibraryDirectories) + mainCRTStartup + + + call "$(ProjectDir)Shader\CompileShaders.bat" +xcopy /y "$(ProjectDir)Shader\*.spv" "$(OutDir)\Shader\" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/openVulkanoCpp/packages.config b/openVulkanoCpp/packages.config new file mode 100644 index 0000000..8322362 --- /dev/null +++ b/openVulkanoCpp/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file