Compare commits
10 Commits
09de216b0a
...
387bc84985
Author | SHA1 | Date | |
---|---|---|---|
387bc84985 | |||
1297af3db1 | |||
|
a3e89b4e8f | ||
71b95e1ccf | |||
|
6dbb04ea83 | ||
7a712522d6 | |||
|
a370f28f14 | ||
|
3bf44e8985 | ||
66d8618e4e | |||
26ece035a2 |
|
@ -1,17 +0,0 @@
|
||||||
directories:
|
|
||||||
# Add the directories you want added as source here
|
|
||||||
# By default, we've added your entire workspace ('.')
|
|
||||||
.
|
|
||||||
|
|
||||||
# Automatically includes all relevant targets under the 'directories' above
|
|
||||||
derive_targets_from_directories: true
|
|
||||||
|
|
||||||
targets:
|
|
||||||
# If source code isn't resolving, add additional targets that compile it here
|
|
||||||
|
|
||||||
additional_languages:
|
|
||||||
# Uncomment any additional languages you want supported
|
|
||||||
# dart
|
|
||||||
# javascript
|
|
||||||
# python
|
|
||||||
# typescript
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module external.system.id="Blaze" type="BLAZE_CPP_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$/../..">
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/../../.idea" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.." />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module external.system.id="Blaze" type="BLAZE_CPP_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$/../../..">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/../../.." isTestSource="false" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-bin" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-genfiles" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-out" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-testlogs" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-umbra" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/../.." />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
8
.clwb/.idea/.gitignore
vendored
8
.clwb/.idea/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
|
@ -1 +0,0 @@
|
||||||
umbra
|
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.blaze/modules/.project-data-dir.iml" filepath="$PROJECT_DIR$/.blaze/modules/.project-data-dir.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.blaze/modules/.workspace.iml" filepath="$PROJECT_DIR$/.blaze/modules/.workspace.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -4,7 +4,10 @@ obj/
|
||||||
riderModule.iml
|
riderModule.iml
|
||||||
/_ReSharper.Caches/
|
/_ReSharper.Caches/
|
||||||
|
|
||||||
*.user
|
|
||||||
|
|
||||||
bazel-*/
|
|
||||||
test-results
|
test-results
|
||||||
|
cmake-build-vs-debug/
|
||||||
|
cmake-build-debug/
|
||||||
|
cmake-build-debug-msvc/
|
||||||
|
cmake-build-debug-msvc-vs/
|
||||||
|
/.idea/
|
||||||
|
|
13
.idea/.idea.umbra/.idea/.gitignore
vendored
13
.idea/.idea.umbra/.idea/.gitignore
vendored
|
@ -1,13 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Rider ignored files
|
|
||||||
/projectSettingsUpdater.xml
|
|
||||||
/modules.xml
|
|
||||||
/.idea.umbra.iml
|
|
||||||
/contentModel.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
|
||||||
</project>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="UserContentModel">
|
|
||||||
<attachedFolders />
|
|
||||||
<explicitIncludes />
|
|
||||||
<explicitExcludes />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
80
CMakeLists.txt
Normal file
80
CMakeLists.txt
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
cmake_minimum_required(VERSION 3.24)
|
||||||
|
|
||||||
|
Include(FetchContent)
|
||||||
|
|
||||||
|
# Fetch SDL for the runtime
|
||||||
|
FetchContent_Declare(
|
||||||
|
SDL2
|
||||||
|
URL https://www.libsdl.org/release/SDL2-devel-2.24.0-VC.zip
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(SDL2)
|
||||||
|
set(SDL2_DIR ${sdl2_SOURCE_DIR})
|
||||||
|
list(PREPEND CMAKE_PREFIX_PATH "${sdl2_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
# Fetch Catch2 for the file format tests
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v2.13.9 # or a later release
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
|
# Fetch GLM for the renderer
|
||||||
|
FetchContent_Declare(
|
||||||
|
glm
|
||||||
|
GIT_REPOSITORY https://github.com/g-truc/glm.git
|
||||||
|
GIT_TAG 0.9.9.2
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_GetProperties(glm)
|
||||||
|
if(NOT glm_POPULATED)
|
||||||
|
FetchContent_Populate(glm)
|
||||||
|
set(GLM_TEST_ENABLE OFF CACHE BOOL "" FORCE)
|
||||||
|
add_subdirectory(${glm_SOURCE_DIR} ${glm_BINARY_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Fetch SpdLog for.. loggin
|
||||||
|
FetchContent_Declare(
|
||||||
|
spdlog
|
||||||
|
GIT_REPOSITORY https://github.com/gabime/spdlog.git
|
||||||
|
GIT_TAG v1.10.0
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_GetProperties(spdlog)
|
||||||
|
if(NOT spdlog_POPULATED)
|
||||||
|
FetchContent_Populate(spdlog)
|
||||||
|
add_subdirectory(${spdlog_SOURCE_DIR} ${spdlog_BINARY_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
imgui
|
||||||
|
GIT_REPOSITORY https://github.com/ocornut/imgui
|
||||||
|
GIT_TAG 71a0701920dbc83155f718182f01132d1ec2d51e
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(imgui)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
dylib
|
||||||
|
GIT_REPOSITORY "https://github.com/martin-olivier/dylib"
|
||||||
|
GIT_TAG "v2.1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(dylib)
|
||||||
|
|
||||||
|
# Import some find files
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
|
||||||
|
|
||||||
|
project(umbra)
|
||||||
|
|
||||||
|
set(CMAKE_STATIC_LIBRARY_PREFIX "")
|
||||||
|
set(CMAKE_SHARED_LIBRARY_PREFIX "")
|
||||||
|
|
||||||
|
# Core engine
|
||||||
|
add_subdirectory(projs/shadow/shadow-engine)
|
||||||
|
|
||||||
|
# Runtime executable
|
||||||
|
add_subdirectory(projs/shadow/shadow-runtime)
|
||||||
|
|
||||||
|
add_subdirectory(projs/test-game)
|
|
@ -1,7 +0,0 @@
|
||||||
workspace(
|
|
||||||
name = "umbra",
|
|
||||||
)
|
|
||||||
|
|
||||||
load("//vendor:deps.bzl", "deps")
|
|
||||||
|
|
||||||
deps()
|
|
27
cmake/FindImGui.cmake
Normal file
27
cmake/FindImGui.cmake
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
find_package(Vulkan REQUIRED)
|
||||||
|
|
||||||
|
FILE(GLOB_RECURSE SOURCES ${imgui_SOURCE_DIR}/*.cpp)
|
||||||
|
FILE(GLOB_RECURSE HEADERS ${imgui_SOURCE_DIR}/*.h)
|
||||||
|
|
||||||
|
FILE(GLOB_RECURSE HEADERS ${imgui_SOURCE_DIR}/backends/imgui_impl_vulkan.h)
|
||||||
|
FILE(GLOB_RECURSE SOURCES ${imgui_SOURCE_DIR}/backends/imgui_impl_vulkan.cpp)
|
||||||
|
|
||||||
|
add_library(imgui OBJECT
|
||||||
|
${imgui_SOURCE_DIR}/imgui.cpp
|
||||||
|
${imgui_SOURCE_DIR}/imgui_demo.cpp
|
||||||
|
${imgui_SOURCE_DIR}/imgui_draw.cpp
|
||||||
|
${imgui_SOURCE_DIR}/imgui_tables.cpp
|
||||||
|
${imgui_SOURCE_DIR}/imgui_widgets.cpp
|
||||||
|
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl.cpp
|
||||||
|
${imgui_SOURCE_DIR}/backends/imgui_impl_vulkan.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(imgui
|
||||||
|
PUBLIC
|
||||||
|
${SDL2_INCLUDE_DIRS}
|
||||||
|
${imgui_SOURCE_DIR}
|
||||||
|
${imgui_SOURCE_DIR}/backends
|
||||||
|
)
|
||||||
|
target_link_libraries(imgui PRIVATE SDL2::SDL2 Vulkan::Vulkan)
|
41
imgui.ini
Normal file
41
imgui.ini
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
[Window][Debug##Default]
|
||||||
|
Pos=876,8
|
||||||
|
Size=315,195
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000008,0
|
||||||
|
|
||||||
|
[Window][Dear ImGui Demo]
|
||||||
|
Pos=1193,178
|
||||||
|
Size=62,25
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000007,0
|
||||||
|
|
||||||
|
[Window][Game module window]
|
||||||
|
Pos=1193,8
|
||||||
|
Size=62,50
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000004,0
|
||||||
|
|
||||||
|
[Window][Time]
|
||||||
|
Pos=1193,60
|
||||||
|
Size=62,49
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000005,0
|
||||||
|
|
||||||
|
[Window][Active Modules]
|
||||||
|
Pos=1193,111
|
||||||
|
Size=62,65
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000006,0
|
||||||
|
|
||||||
|
[Docking][Data]
|
||||||
|
DockNode ID=0x00000001 Pos=1196,188 Size=379,195 Split=X
|
||||||
|
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=652,419 HiddenTabBar=1 Selected=0x55954704
|
||||||
|
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=129,419 Split=Y
|
||||||
|
DockNode ID=0x00000002 Parent=0x00000009 SizeRef=219,34 Split=Y Selected=0xFC1D20C0
|
||||||
|
DockNode ID=0x00000004 Parent=0x00000002 SizeRef=219,64 Selected=0xFC1D20C0
|
||||||
|
DockNode ID=0x00000005 Parent=0x00000002 SizeRef=219,62 Selected=0xE75A179E
|
||||||
|
DockNode ID=0x00000003 Parent=0x00000009 SizeRef=219,31 Split=Y Selected=0xEE305C78
|
||||||
|
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=219,142 Selected=0xEE305C78
|
||||||
|
DockNode ID=0x00000007 Parent=0x00000003 SizeRef=219,55 Selected=0xE87781F4
|
||||||
|
|
26
projs/docs/diagrams/architecture.plantuml
Normal file
26
projs/docs/diagrams/architecture.plantuml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
@startuml
|
||||||
|
|
||||||
|
class ShadowApplication <<Singleton>> {
|
||||||
|
-ModuleManager module_manager
|
||||||
|
~void packagePrivateMethod()
|
||||||
|
#{abstract} char protectedMethod(int param)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleManager <<Singleton>> {
|
||||||
|
-vector<string, Module> moduels
|
||||||
|
---
|
||||||
|
+void AddModule(Module* mo)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Module {
|
||||||
|
+string domain
|
||||||
|
---
|
||||||
|
+void Init()
|
||||||
|
+void Update()
|
||||||
|
+void ShutDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@enduml
|
|
@ -1,14 +1,29 @@
|
||||||
@startuml
|
@startuml
|
||||||
[shadow-engine] <<static lib>> as engine
|
|
||||||
[shadow-light] <<exe>> as editor
|
[shadow-light] <<exe>> as editor
|
||||||
|
|
||||||
|
[shadow-entity] <<static lib>> as shentity
|
||||||
|
[shadow-file-format] <<static lib>> as shff
|
||||||
|
[shadow-reflection] <<static lib>> as shreflection
|
||||||
|
[shadow-renderer] <<static lib>> as shrenderer
|
||||||
|
[shadow-utilty] <<static lib>> as shutitily
|
||||||
|
|
||||||
|
[shadow-engine] <<static/dynamic lib>> as shengine
|
||||||
|
|
||||||
|
shentity --* shengine
|
||||||
|
shff --* shengine
|
||||||
|
shreflection --* shengine
|
||||||
|
shrenderer --* shengine
|
||||||
|
shutitily --* shengine
|
||||||
|
|
||||||
|
|
||||||
[shadow-runner] <<exe>> as runner
|
[shadow-runner] <<exe>> as runner
|
||||||
|
|
||||||
[test-game] <<dll>> as game
|
[test-game] <<dll>> as game
|
||||||
|
|
||||||
engine <-editor
|
shengine <- editor
|
||||||
runner -> engine
|
runner -> shengine
|
||||||
|
|
||||||
game ..> engine
|
game ..> shengine : uses
|
||||||
|
|
||||||
runner --> game : loads
|
runner --> game : loads
|
||||||
editor --> game : loads
|
editor --> game : loads
|
||||||
|
|
54
projs/docs/diagrams/startup.puml
Normal file
54
projs/docs/diagrams/startup.puml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
@startuml
|
||||||
|
!include <material/file>
|
||||||
|
|
||||||
|
autoactivate on
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
participant main as "int main(args)"
|
||||||
|
|
||||||
|
participant app as "ShadowApplication" <<(O,#ADD1B2) singleton>>
|
||||||
|
|
||||||
|
participant moduleMg as "ModuleManager" <<(O,#ADD1B2) singleton>>
|
||||||
|
|
||||||
|
|
||||||
|
'participant gameDll as "longName" : <$ma_file{scale=0.5}> <<DLL>>
|
||||||
|
|
||||||
|
participant "Game DLL" as dll <<$ma_file{scale=0.5}>> #LightGray <<DLL>>
|
||||||
|
|
||||||
|
-> main
|
||||||
|
main -> app ** : create
|
||||||
|
activate app
|
||||||
|
app -> moduleMg ** : create
|
||||||
|
return app
|
||||||
|
|
||||||
|
main -> app : LoadGame
|
||||||
|
|
||||||
|
app -> app : load
|
||||||
|
app -> dll ** : create
|
||||||
|
return
|
||||||
|
|
||||||
|
app -> dll : sh_main
|
||||||
|
|
||||||
|
loop for each needed module
|
||||||
|
dll -> moduleMg : AddModule()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
main -> app : Init()
|
||||||
|
app -> moduleMg : Init()
|
||||||
|
|
||||||
|
loop module in modules
|
||||||
|
collections module as "Module" <<(O,#ADD1B2)entity>>
|
||||||
|
moduleMg -> module : Init()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1 +1,47 @@
|
||||||
\chapter*{Asd}
|
\chapter{Disclaimer}
|
||||||
|
|
||||||
|
This document only describes the ideas that I plan to explore in my thesis.
|
||||||
|
This is not the final project description for the thesis.
|
||||||
|
|
||||||
|
\chapter{Overview}
|
||||||
|
|
||||||
|
The idea behind the project is to establish the base for a game engine.
|
||||||
|
What this means is that the end product is not something that can compete with the likes of Unity and Unreal engine.
|
||||||
|
The goal is to lay the foundation for something that could compete in the future.
|
||||||
|
This means that the project will need to make crutial decisions on what frameworks libraries to use, and how the core of the engine should be architected.
|
||||||
|
|
||||||
|
The planned engine is the spiritual successor to my old engine called ShadowEngine.
|
||||||
|
This means that some parts of the old codebase will be used but every line will have to be rigorously checked.
|
||||||
|
This project is not a one person project, I'm planning on involving friends of mine. My responsibilities and the code that I write will constitute as the thesis.
|
||||||
|
|
||||||
|
\section{Technical information}
|
||||||
|
The project will be mainly written in C++, but some higher level functionality might be implemented in C# For windowing and interacting with the OS it will use SDL2.
|
||||||
|
The rendering API is going to be Vulkan.
|
||||||
|
Our main focus initially is Windows both for development and for running, as that is what all planned participants have access to.
|
||||||
|
|
||||||
|
\chapter{Main tasks}
|
||||||
|
\section{Build system}
|
||||||
|
The first question that needs decision for the project is what build system to use.
|
||||||
|
There are many different C++ build systems. The most common is Cmake and MSBuild. These are both quite capable.
|
||||||
|
\subsection{Cmake}
|
||||||
|
Cmake is an old powerful build system. it is capable of building almost anything. It is a generator for make files witch are even older.
|
||||||
|
Make files and in turn Cmake is highly organized around the actual compilation commands that get run in the end.
|
||||||
|
This makes them harder to configure.
|
||||||
|
(Magical strings and bad docs)
|
||||||
|
|
||||||
|
\subsection{MSbuild}
|
||||||
|
MSbuild is Microsoft's build system, it is quite powerful and has really good integration with Visual Studio.
|
||||||
|
It is also capable of integrating C# projects to the same workspace.
|
||||||
|
Sadly MSbuild can only build C++ on Windows witch means we can't support other platforms in the future.
|
||||||
|
MSbuild is also not supported by other IDEs like Clion meaning it would be a total vendor lock in.
|
||||||
|
|
||||||
|
\subsection{Bazel}
|
||||||
|
Bazel is a little known build system as it is mostly used by large projects.
|
||||||
|
It was developed for Google's internal repository, as they keep all of their code in a single monolithic repository.
|
||||||
|
This means the Bazel is capable of building basically any language (C/C++, Java C# JS/TS, etc.).
|
||||||
|
Bazel is also capable of managing project dependencies without using Git Submodules.
|
||||||
|
|
||||||
|
\subsection{Decision}
|
||||||
|
After trying both Msbuild and Bazel the decision was to use Bazel.
|
||||||
|
Sadly the Clion Bazel plugin has problems on windows,
|
||||||
|
but theses might be fixable with a few PRs or just shipping our own version of the plugin, as it is available on Github
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
cc_library(
|
|
||||||
name = "shadow-engine",
|
|
||||||
srcs = glob(["src/**/*.cpp", "src/**/*.h"]),
|
|
||||||
hdrs = glob(["src/**/*.h"]),
|
|
||||||
strip_include_prefix = "src/",
|
|
||||||
includes = [],
|
|
||||||
copts = [
|
|
||||||
"/std:c++20"
|
|
||||||
],
|
|
||||||
deps = [
|
|
||||||
"//projs/shadow-utility",
|
|
||||||
"@sdl2"
|
|
||||||
],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
|
@ -1,61 +0,0 @@
|
||||||
#include <iostream>
|
|
||||||
#include <SDL.h>
|
|
||||||
#include <glm.hpp>
|
|
||||||
|
|
||||||
// You must include the command line parameters for your main function to be recognized by SDL
|
|
||||||
int main(int argc, char** args) {
|
|
||||||
|
|
||||||
// Pointers to our window and surface
|
|
||||||
SDL_Surface* winSurface = NULL;
|
|
||||||
SDL_Window* window = NULL;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Initialize SDL. SDL_Init will return -1 if it fails.
|
|
||||||
if ( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) {
|
|
||||||
std::cout << "Error initializing SDL: " << SDL_GetError() << std::endl;
|
|
||||||
system("pause");
|
|
||||||
// End the program
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create our window
|
|
||||||
window = SDL_CreateWindow( "Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_SHOWN );
|
|
||||||
|
|
||||||
// Make sure creating the window succeeded
|
|
||||||
if ( !window ) {
|
|
||||||
std::cout << "Error creating window: " << SDL_GetError() << std::endl;
|
|
||||||
system("pause");
|
|
||||||
// End the program
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the surface from the window
|
|
||||||
winSurface = SDL_GetWindowSurface( window );
|
|
||||||
|
|
||||||
// Make sure getting the surface succeeded
|
|
||||||
if ( !winSurface ) {
|
|
||||||
std::cout << "Error getting surface: " << SDL_GetError() << std::endl;
|
|
||||||
system("pause");
|
|
||||||
// End the program
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the window with a white rectangle
|
|
||||||
SDL_FillRect( winSurface, NULL, SDL_MapRGB( winSurface->format, 255, 255, 255 ) );
|
|
||||||
|
|
||||||
// Update the window display
|
|
||||||
SDL_UpdateWindowSurface( window );
|
|
||||||
|
|
||||||
// Wait
|
|
||||||
system("pause");
|
|
||||||
|
|
||||||
// Destroy the window. This will also destroy the surface
|
|
||||||
SDL_DestroyWindow( window );
|
|
||||||
|
|
||||||
// Quit SDL
|
|
||||||
SDL_Quit();
|
|
||||||
|
|
||||||
// End the program
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
#include "ShadowApplication.h"
|
|
||||||
|
|
||||||
#include "Time.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ShadowEngine {
|
|
||||||
|
|
||||||
ShadowApplication* ShadowApplication::instance = nullptr;
|
|
||||||
|
|
||||||
ShadowApplication::ShadowApplication(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
instance = this;
|
|
||||||
|
|
||||||
if(argc > 1)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < argc; i++)
|
|
||||||
{
|
|
||||||
std::string param(argv[i]);
|
|
||||||
if(param == "-no-gui")
|
|
||||||
{
|
|
||||||
this->no_gui = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//game = _setupFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ShadowApplication::~ShadowApplication()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShadowApplication::Init()
|
|
||||||
{
|
|
||||||
// Initialize SDL. SDL_Init will return -1 if it fails.
|
|
||||||
if ( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) {
|
|
||||||
//std::cout << "Error initializing SDL: " << SDL_GetError() << std::endl;
|
|
||||||
//system("pause");
|
|
||||||
// End the program
|
|
||||||
//return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
window_ = new ShadowWindow(800,450);
|
|
||||||
|
|
||||||
/*
|
|
||||||
moduleManager.PushModule(new Log());
|
|
||||||
moduleManager.PushModule(new EventSystem::ShadowEventManager());
|
|
||||||
moduleManager.PushModule(new SDLPlatform::SDLModule());
|
|
||||||
moduleManager.PushModule(new Rendering::Renderer());
|
|
||||||
|
|
||||||
moduleManager.PushModule(new Assets::AssetManager());
|
|
||||||
|
|
||||||
if(!no_gui)
|
|
||||||
moduleManager.PushModule(new DebugGui::ImGuiModule());
|
|
||||||
|
|
||||||
moduleManager.PushModule(new InputSystem::ShadowActionSystem());
|
|
||||||
//moduleManager.PushModule(new Debug::DebugModule());
|
|
||||||
moduleManager.PushModule(new EntitySystem::EntitySystem());
|
|
||||||
|
|
||||||
|
|
||||||
game->Init();
|
|
||||||
|
|
||||||
moduleManager.Init();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShadowApplication::Start()
|
|
||||||
{
|
|
||||||
while (running)
|
|
||||||
{
|
|
||||||
Time::UpdateTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
#include "ShadowWindow.h"
|
|
||||||
|
|
||||||
|
|
||||||
ShadowWindow::ShadowWindow(int W, int H) : Height(H), Width(W)
|
|
||||||
{
|
|
||||||
// Create our window
|
|
||||||
sdlWindowPtr = SDL_CreateWindow( "Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Width, Height, SDL_WINDOW_SHOWN );
|
|
||||||
|
|
||||||
// Make sure creating the window succeeded
|
|
||||||
if ( !sdlWindowPtr ) {
|
|
||||||
//Raise an error in the log
|
|
||||||
//std::cout << "Error creating window: " << SDL_GetError() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the surface from the window
|
|
||||||
sdlSurface = SDL_GetWindowSurface( sdlWindowPtr );
|
|
||||||
|
|
||||||
// Make sure getting the surface succeeded
|
|
||||||
if ( !sdlSurface ) {
|
|
||||||
//Raise an error in the log
|
|
||||||
//std::cout << "Error getting surface: " << SDL_GetError() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create window
|
|
||||||
/*
|
|
||||||
this->winPtr = SDL_CreateWindow("Hello World!", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W, H,
|
|
||||||
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
|
|
||||||
SH_CORE_ASSERT(winPtr, "Window could not be created! SDL_Error: %s\n", SDL_GetError());
|
|
||||||
|
|
||||||
context = ShadowEngine::Rendering::GraphicsContext::Create(this);
|
|
||||||
context->Init();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
ShadowWindow::~ShadowWindow()
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
|
|
||||||
class ShadowWindow
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
int Height;
|
|
||||||
int Width;
|
|
||||||
|
|
||||||
SDL_Window* sdlWindowPtr;
|
|
||||||
|
|
||||||
SDL_Surface* sdlSurface = NULL;
|
|
||||||
|
|
||||||
|
|
||||||
//ShadowEngine::Ref<ShadowEngine::Rendering::GraphicsContext> context;
|
|
||||||
|
|
||||||
ShadowWindow(int W, int H);
|
|
||||||
|
|
||||||
~ShadowWindow();
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
#include "Time.h"
|
|
||||||
//#include <SDL_hints.h>
|
|
||||||
//#include <SDL.h>
|
|
||||||
|
|
||||||
int Time::NOW = 0;//SDL_GetPerformanceCounter();
|
|
||||||
int Time::LAST = 0;
|
|
||||||
double Time::deltaTime_ms = 0;
|
|
||||||
double Time::deltaTime = 0;
|
|
||||||
|
|
||||||
void Time::UpdateTime()
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
NOW = SDL_GetTicks();
|
|
||||||
deltaTime_ms = LAST > 0 ? (NOW - LAST) *10 : (1.0f / 60.0f);
|
|
||||||
deltaTime_ms = deltaTime_ms == 0 ? (1.0f / 60.0f) : deltaTime_ms;
|
|
||||||
|
|
||||||
LAST = NOW;
|
|
||||||
deltaTime = deltaTime_ms * 0.001;
|
|
||||||
*/
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
class Time
|
|
||||||
{
|
|
||||||
static int NOW;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static int LAST;
|
|
||||||
|
|
||||||
static double deltaTime;
|
|
||||||
static double deltaTime_ms;
|
|
||||||
|
|
||||||
static void UpdateTime();
|
|
||||||
};
|
|
|
@ -1,31 +0,0 @@
|
||||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
|
||||||
load("@rules_cc//cc:defs.bzl", "cc_binary")
|
|
||||||
|
|
||||||
cc_library(
|
|
||||||
name = "shadow-file-format",
|
|
||||||
srcs = glob(["src/**/*.cpp", "src/**/*.h"]),
|
|
||||||
hdrs = glob(["src/**/*.h"]),
|
|
||||||
strip_include_prefix = "src/",
|
|
||||||
includes = [],
|
|
||||||
copts = [
|
|
||||||
"/std:c++17"
|
|
||||||
],
|
|
||||||
deps = [
|
|
||||||
"//projs/shadow-utility"
|
|
||||||
],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
||||||
|
|
||||||
cc_test(
|
|
||||||
name = "test",
|
|
||||||
srcs = glob(["test/**/*.cpp", "test/**/*.h"]),
|
|
||||||
includes = [],
|
|
||||||
defines = [ "CATCH_CONFIG_MAIN" ],
|
|
||||||
copts = [
|
|
||||||
"/std:c++17"
|
|
||||||
],
|
|
||||||
deps = [
|
|
||||||
"//projs/shadow-file-format",
|
|
||||||
"@catch2"
|
|
||||||
],
|
|
||||||
)
|
|
|
@ -1,16 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cc_binary(
|
|
||||||
name = "shadow-runtime",
|
|
||||||
srcs = glob(["src/**/*.cpp", "src/**/*.h"]),
|
|
||||||
includes = [],
|
|
||||||
copts = [
|
|
||||||
"/std:c++20"
|
|
||||||
],
|
|
||||||
deps = [
|
|
||||||
"//projs/shadow-engine",
|
|
||||||
"@sdl2"
|
|
||||||
],
|
|
||||||
)
|
|
|
@ -1,8 +0,0 @@
|
||||||
//
|
|
||||||
// Created by dpete on 2022-06-20.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef UMBRA_MAIN_H
|
|
||||||
#define UMBRA_MAIN_H
|
|
||||||
|
|
||||||
#endif //UMBRA_MAIN_H
|
|
|
@ -1,15 +0,0 @@
|
||||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
|
||||||
|
|
||||||
cc_library(
|
|
||||||
name = "shadow-utility",
|
|
||||||
srcs = glob(["**/*.cpp", "**/*.h"]),
|
|
||||||
hdrs = glob(["**/*.h"]),
|
|
||||||
strip_include_prefix = "src/",
|
|
||||||
includes = [],
|
|
||||||
copts = [
|
|
||||||
"/std:c++20"
|
|
||||||
],
|
|
||||||
deps = [
|
|
||||||
],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
44
projs/shadow/shadow-engine/CMakeLists.txt
Normal file
44
projs/shadow/shadow-engine/CMakeLists.txt
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
find_package(Vulkan REQUIRED)
|
||||||
|
find_package(SDL2 CONFIG REQUIRED)
|
||||||
|
find_package(imgui REQUIRED)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
|
|
||||||
|
FILE(GLOB_RECURSE SOURCES
|
||||||
|
core/src/*.cpp
|
||||||
|
shadow-entity/src/*.cpp
|
||||||
|
shadow-renderer/src/*.cpp
|
||||||
|
shadow-reflection/src/*.cpp
|
||||||
|
shadow-utility/src/*.cpp
|
||||||
|
)
|
||||||
|
FILE(GLOB_RECURSE HEADERS
|
||||||
|
core/inc/*.h
|
||||||
|
shadow-entity/inc/*.h
|
||||||
|
shadow-renderer/inc/*.h
|
||||||
|
shadow-reflection/inc/*.h
|
||||||
|
shadow-utility/inc/*.h
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(shadow-engine SHARED ${SOURCES} $<TARGET_OBJECTS:imgui>)
|
||||||
|
|
||||||
|
target_include_directories(shadow-engine
|
||||||
|
PRIVATE ${SDL2_INCLUDE_DIRS}
|
||||||
|
PUBLIC
|
||||||
|
core/inc
|
||||||
|
shadow-entity/inc
|
||||||
|
shadow-renderer/inc
|
||||||
|
shadow-reflection/inc
|
||||||
|
shadow-utility/inc
|
||||||
|
${glm_SOURCE_DIR}
|
||||||
|
INTERFACE
|
||||||
|
${imgui_SOURCE_DIR}
|
||||||
|
${imgui_SOURCE_DIR}/backends)
|
||||||
|
|
||||||
|
target_link_libraries(shadow-engine
|
||||||
|
PUBLIC Vulkan::Vulkan SDL2::SDL2 spdlog dylib imgui
|
||||||
|
)
|
||||||
|
target_compile_definitions(shadow-engine PRIVATE "EXPORTING_SH_ENGINE")
|
||||||
|
|
||||||
|
target_link_options(shadow-engine PUBLIC -Wl,--export-all-symbols)
|
||||||
|
|
78
projs/shadow/shadow-engine/core/inc/core/Module.h
Normal file
78
projs/shadow/shadow-engine/core/inc/core/Module.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#ifndef UMBRA_MODULE_H
|
||||||
|
#define UMBRA_MODULE_H
|
||||||
|
|
||||||
|
#include "SHObject.h"
|
||||||
|
#include "SDL_events.h"
|
||||||
|
#include <memory>
|
||||||
|
#include "vlkx/vulkan/abstraction/Commands.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ShadowModules are the base of the engine. They add core abilities.
|
||||||
|
/// </summary>
|
||||||
|
class Module : public SHObject
|
||||||
|
{
|
||||||
|
SHObject_Base(Module)
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pre Init is called when the module is added to the engine
|
||||||
|
/// </summary>
|
||||||
|
virtual void PreInit() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Init is called after all the modules are added
|
||||||
|
/// </summary>
|
||||||
|
virtual void Init() = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// update is called each frame
|
||||||
|
/// </summary>
|
||||||
|
virtual void Update(int frame) = 0;
|
||||||
|
|
||||||
|
virtual void Recreate() = 0;
|
||||||
|
|
||||||
|
virtual void PreRender() = 0;
|
||||||
|
|
||||||
|
virtual void Render(VkCommandBuffer& commands, int frame) = 0;
|
||||||
|
|
||||||
|
virtual void LateRender(VkCommandBuffer& commands, int frame) = 0;
|
||||||
|
|
||||||
|
virtual void OverlayRender() = 0;
|
||||||
|
|
||||||
|
virtual void AfterFrameEnd() = 0;
|
||||||
|
|
||||||
|
virtual void Destroy() = 0;
|
||||||
|
|
||||||
|
virtual void Event(SDL_Event* e) = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the name of the module
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
virtual std::string GetName() {
|
||||||
|
return this->GetType();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class especially for modules that are renderers.
|
||||||
|
* Allows the engine to access state from the renderer independent of implementation.
|
||||||
|
*/
|
||||||
|
class RendererModule : public Module {
|
||||||
|
public:
|
||||||
|
// Begin the render pass using the given commands.
|
||||||
|
// Will call out through the regular modules to gather geometry to render.
|
||||||
|
virtual void BeginRenderPass(const std::unique_ptr<vlkx::RenderCommand>& commands) = 0;
|
||||||
|
|
||||||
|
virtual void EnableEditor() = 0;
|
||||||
|
|
||||||
|
virtual VkExtent2D GetRenderExtent() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // ShadowEngine
|
||||||
|
|
||||||
|
#endif //UMBRA_MODULE_H
|
68
projs/shadow/shadow-engine/core/inc/core/ModuleManager.h
Normal file
68
projs/shadow/shadow-engine/core/inc/core/ModuleManager.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#ifndef UMBRA_MODULEMANAGER_H
|
||||||
|
#define UMBRA_MODULEMANAGER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <list>
|
||||||
|
#include "Module.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
struct ModuleRef{
|
||||||
|
public:
|
||||||
|
std::shared_ptr<Module> module;
|
||||||
|
std::string domain;
|
||||||
|
|
||||||
|
// Reinterpret this module as if it were a Renderer Module.
|
||||||
|
// A shortcut for `std::static_pointer_cast<std::shared_ptr<RendererModule>>(ShadowEngine::ModuleManager::instance->GetModule("renderer"))
|
||||||
|
std::shared_ptr<RendererModule> operator->() const { return std::static_pointer_cast<RendererModule>(module); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModuleManager {
|
||||||
|
public:
|
||||||
|
static API ModuleManager *instance;
|
||||||
|
static ModuleManager* getInstance() { return instance; }
|
||||||
|
|
||||||
|
std::list<ModuleRef> modules;
|
||||||
|
ModuleRef renderer;
|
||||||
|
|
||||||
|
ModuleManager();
|
||||||
|
|
||||||
|
~ModuleManager();
|
||||||
|
|
||||||
|
void PushModule(const std::shared_ptr<Module>& module, const std::string& domain);
|
||||||
|
|
||||||
|
Module &GetModule(const std::string& name);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T *GetModuleByType() {
|
||||||
|
for (auto &module: modules) {
|
||||||
|
if (module.module->GetTypeId() == T::TypeId())
|
||||||
|
return dynamic_cast<T *>(module.module.get());
|
||||||
|
}
|
||||||
|
//SH_CORE_ERROR("Can't find the module {0}", T::Type());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
void Update(int frame);
|
||||||
|
|
||||||
|
void LateRender(VkCommandBuffer& commands, int frame);
|
||||||
|
|
||||||
|
void OverlayRender();
|
||||||
|
|
||||||
|
void Recreate();
|
||||||
|
|
||||||
|
void Render(VkCommandBuffer& commands, int frame);
|
||||||
|
|
||||||
|
void PreRender();
|
||||||
|
|
||||||
|
void AfterFrameEnd();
|
||||||
|
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
|
void Event(SDL_Event* evt);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UMBRA_MODULEMANAGER_H
|
49
projs/shadow/shadow-engine/core/inc/core/SDL2Module.h
Normal file
49
projs/shadow/shadow-engine/core/inc/core/SDL2Module.h
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// Created by dpete on 30/08/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UMBRA_SDL2MODULE_H
|
||||||
|
#define UMBRA_SDL2MODULE_H
|
||||||
|
|
||||||
|
#include "Module.h"
|
||||||
|
#include "ShadowWindow.h"
|
||||||
|
|
||||||
|
#include "SDL.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
class SDL2Module : public Module {
|
||||||
|
SHObject_Base(SDL2Module)
|
||||||
|
|
||||||
|
public:
|
||||||
|
ShadowEngine::ShadowWindow* _window;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Init() override;
|
||||||
|
|
||||||
|
void PreInit() override;
|
||||||
|
|
||||||
|
void Update(int frame) override;
|
||||||
|
|
||||||
|
void Recreate() override;
|
||||||
|
|
||||||
|
void Render(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
|
void OverlayRender() override;
|
||||||
|
|
||||||
|
void LateRender(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
|
std::string GetName() override;
|
||||||
|
|
||||||
|
void AfterFrameEnd() override;
|
||||||
|
|
||||||
|
void PreRender() override;
|
||||||
|
|
||||||
|
void Destroy() override;
|
||||||
|
|
||||||
|
void Event(SDL_Event* e) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UMBRA_SDL2MODULE_H
|
|
@ -1,5 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "ShadowWindow.h"
|
#include "ShadowWindow.h"
|
||||||
|
#include "ModuleManager.h"
|
||||||
|
#include "exports.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui_impl_sdl.h"
|
||||||
|
#include "imgui_impl_vulkan.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ShadowEngine {
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
@ -21,7 +28,7 @@ namespace ShadowEngine {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The module manager instance
|
/// The module manager instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
//ShadowEngine::ShadowModuleManager moduleManager;
|
ModuleManager moduleManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the running state.
|
/// Represents the running state.
|
||||||
|
@ -31,6 +38,10 @@ namespace ShadowEngine {
|
||||||
|
|
||||||
bool no_gui = false;
|
bool no_gui = false;
|
||||||
|
|
||||||
|
std::string game = "";
|
||||||
|
|
||||||
|
void loadGame();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default constructor
|
/// Default constructor
|
||||||
|
@ -43,7 +54,7 @@ namespace ShadowEngine {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// Use this for accessing the Application
|
/// Use this for accessing the Application
|
||||||
/// <returns>The current application reference</returns>
|
/// <returns>The current application reference</returns>
|
||||||
static ShadowApplication& Get() { return *instance; };
|
static ShadowApplication& Get();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the active window used for rendering
|
/// Returns the active window used for rendering
|
||||||
|
@ -52,9 +63,11 @@ namespace ShadowEngine {
|
||||||
//ShadowWindow& const GetWindow() const { return window_; };
|
//ShadowWindow& const GetWindow() const { return window_; };
|
||||||
//void SetWindow(ShadowWindow w) { window_ = w; }
|
//void SetWindow(ShadowWindow w) { window_ = w; }
|
||||||
|
|
||||||
//ShadowEngine::ShadowModuleManager& GetModuleManager() { return moduleManager; };
|
ShadowEngine::ModuleManager& GetModuleManager() { return moduleManager; };
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
void Start();
|
void Start();
|
||||||
|
|
||||||
|
void PollEvents();
|
||||||
};
|
};
|
||||||
}
|
}
|
25
projs/shadow/shadow-engine/core/inc/core/ShadowWindow.h
Normal file
25
projs/shadow/shadow-engine/core/inc/core/ShadowWindow.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SDL.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
class ShadowWindow {
|
||||||
|
public:
|
||||||
|
|
||||||
|
int Height;
|
||||||
|
int Width;
|
||||||
|
|
||||||
|
SDL_Window *sdlWindowPtr;
|
||||||
|
|
||||||
|
SDL_Surface *sdlSurface = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
//ShadowEngine::Ref<ShadowEngine::Rendering::GraphicsContext> context;
|
||||||
|
|
||||||
|
ShadowWindow(int W, int H);
|
||||||
|
|
||||||
|
~ShadowWindow();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
20
projs/shadow/shadow-engine/core/inc/core/Time.h
Normal file
20
projs/shadow/shadow-engine/core/inc/core/Time.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "exports.h"
|
||||||
|
|
||||||
|
class Time
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
static API int NOW;
|
||||||
|
static API int LAST;
|
||||||
|
|
||||||
|
static API double deltaTime;
|
||||||
|
static API double deltaTime_ms;
|
||||||
|
|
||||||
|
static API double timeSinceStart;
|
||||||
|
static API double startTime;
|
||||||
|
|
||||||
|
static void UpdateTime();
|
||||||
|
};
|
46
projs/shadow/shadow-engine/core/inc/debug/DebugModule.h
Normal file
46
projs/shadow/shadow-engine/core/inc/debug/DebugModule.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// Created by dpete on 31/08/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UMBRA_DEBUGMODULE_H
|
||||||
|
#define UMBRA_DEBUGMODULE_H
|
||||||
|
|
||||||
|
#include "SDL_events.h"
|
||||||
|
#include "core/Module.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine::Debug {
|
||||||
|
|
||||||
|
class DebugModule : public Module {
|
||||||
|
|
||||||
|
SHObject_Base(DebugModule)
|
||||||
|
|
||||||
|
bool active;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Render(VkCommandBuffer& commands, int frame) override {};
|
||||||
|
|
||||||
|
void PreInit() override { };
|
||||||
|
|
||||||
|
void Init() override { };
|
||||||
|
|
||||||
|
void Recreate() override {};
|
||||||
|
|
||||||
|
void OverlayRender() override;
|
||||||
|
|
||||||
|
void Update(int frame) override { };
|
||||||
|
|
||||||
|
void LateRender(VkCommandBuffer& commands, int frame) override { };
|
||||||
|
|
||||||
|
void AfterFrameEnd() override { };
|
||||||
|
|
||||||
|
void PreRender() override { };
|
||||||
|
|
||||||
|
void Destroy() override {};
|
||||||
|
|
||||||
|
void Event(SDL_Event* e) override {};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //UMBRA_DEBUGMODULE_H
|
16
projs/shadow/shadow-engine/core/inc/exports.h
Normal file
16
projs/shadow/shadow-engine/core/inc/exports.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// Created by dpete on 04/09/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# if defined(EXPORTING_SH_ENGINE)
|
||||||
|
# define API __declspec(dllexport)
|
||||||
|
# else
|
||||||
|
# define API __declspec(dllimport)
|
||||||
|
# endif
|
||||||
|
#else // non windows
|
||||||
|
# define SH_EXPORT
|
||||||
|
#endif
|
||||||
|
|
64
projs/shadow/shadow-engine/core/inc/id/UUID.h
Normal file
64
projs/shadow/shadow-engine/core/inc/id/UUID.h
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// ShadowEntity temporary namespace
|
||||||
|
namespace SE {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universally Unique ID.
|
||||||
|
* 128 Bits.
|
||||||
|
*
|
||||||
|
* Unique per runtime only - the suitability for serialization is undetermined.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class UUID {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data storage; 128 bits.
|
||||||
|
* 2 x 64 bit
|
||||||
|
* 4 x 32 bit
|
||||||
|
* 16 x 8 bit
|
||||||
|
*/
|
||||||
|
|
||||||
|
union Data {
|
||||||
|
uint64_t u64[2];
|
||||||
|
uint32_t u32[4];
|
||||||
|
uint8_t u8[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Create a new, unused, UUID.
|
||||||
|
static UUID Generate();
|
||||||
|
// Check whether the UUID is correctly formed.
|
||||||
|
static bool IsValidStr(char const* str);
|
||||||
|
|
||||||
|
// Create an empty UUID.
|
||||||
|
inline UUID() { std::memset(&data.u8, 0, 16); }
|
||||||
|
// Create a UUID based on the given values.
|
||||||
|
inline UUID(uint64_t i0, uint64_t i1) { data.u64[0] = i0; data.u64[1] = i1; }
|
||||||
|
inline UUID(uint32_t i0, uint32_t i1, uint32_t i2, uint32_t i3) { data.u32[0] = i0; data.u32[1] = i1; data.u32[2] = i2; data.u32[3] = i3; }
|
||||||
|
inline explicit UUID(std::string const& str) : UUID(str.c_str()) {}
|
||||||
|
// Create a UUID from the given format.
|
||||||
|
explicit UUID(char const* str);
|
||||||
|
|
||||||
|
// Check whether the UUID is nonzero.
|
||||||
|
inline bool IsValid() const { return data.u64[0] != 0 && data.u64[1] != 0; }
|
||||||
|
// Set the UUID to zero.
|
||||||
|
inline void Clear() { std::memset(&data.u8, 0, 16); }
|
||||||
|
|
||||||
|
// Get a section of the UUID's data as the given bit width.
|
||||||
|
inline uint8_t GetU8(size_t idx) const { return data.u8[idx]; }
|
||||||
|
inline uint32_t GetU32(size_t idx) const { return data.u32[idx]; }
|
||||||
|
inline uint64_t GetU64(size_t idx) const { return data.u64[idx]; }
|
||||||
|
|
||||||
|
// Check whether this and a given UUID are in/equal.
|
||||||
|
__inline bool operator==(UUID const& other) const { return data.u64[0] == other.data.u64[0] && data.u64[1] == other.data.u64[1]; }
|
||||||
|
__inline bool operator!=(UUID const& other) const { return !(*this == other); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Data data {};
|
||||||
|
};
|
||||||
|
}
|
348
projs/shadow/shadow-engine/core/src/core/App2.txt
Normal file
348
projs/shadow/shadow-engine/core/src/core/App2.txt
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
#include "ShadowApplication.h"
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
|
#include "time.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui_impl_vulkan.h"
|
||||||
|
#include "imgui_impl_sdl.h"
|
||||||
|
#include <vlkx/vulkan/VulkanManager.h>
|
||||||
|
#include "vlkx/render/Camera.h"
|
||||||
|
#include "vlkx/render/render_pass/ScreenRenderPass.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Commands.h"
|
||||||
|
#include "vlkx/render/Geometry.h"
|
||||||
|
#include "temp/model/Builder.h"
|
||||||
|
|
||||||
|
#define CATCH(x) \
|
||||||
|
try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); }
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
struct SkyboxTransform {
|
||||||
|
alignas(sizeof(glm::mat4)) glm::mat4 value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlanetTransform {
|
||||||
|
alignas(sizeof(glm::mat4)) glm::mat4 model;
|
||||||
|
alignas(sizeof(glm::mat4)) glm::mat4 projection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Light {
|
||||||
|
alignas(sizeof(glm::vec4)) glm::vec4 dirTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::ScreenRenderPassManager> passManager;
|
||||||
|
std::unique_ptr<vlkx::RenderCommand> renderCommands;
|
||||||
|
std::unique_ptr<vlkx::UserPerspectiveCamera> camera;
|
||||||
|
std::unique_ptr<vlkx::UniformBuffer> light;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::PushConstant> skyboxConstant;
|
||||||
|
std::unique_ptr<vlkx::PushConstant> planetConstant;
|
||||||
|
std::unique_ptr<vlkxtemp::Model> skyboxModel;
|
||||||
|
std::unique_ptr<vlkxtemp::Model> planetModel;
|
||||||
|
std::unique_ptr<vlkxtemp::Model> asteroidModel;
|
||||||
|
|
||||||
|
float aspectRatio;
|
||||||
|
|
||||||
|
ShadowApplication* ShadowApplication::instance = nullptr;
|
||||||
|
|
||||||
|
ShadowApplication::ShadowApplication(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
if(argc > 1)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
{
|
||||||
|
std::string param(argv[i]);
|
||||||
|
if(param == "-no-gui")
|
||||||
|
{
|
||||||
|
this->no_gui = true;
|
||||||
|
}
|
||||||
|
if(param == "-game")
|
||||||
|
{
|
||||||
|
this->game = argv[i+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//game = _setupFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ShadowApplication::~ShadowApplication()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::Init()
|
||||||
|
{
|
||||||
|
// Initialize SDL. SDL_Init will return -1 if it fails.
|
||||||
|
if ( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) {
|
||||||
|
//std::cout << "Error initializing SDL: " << SDL_GetError() << std::endl;
|
||||||
|
//system("pause");
|
||||||
|
// End the program
|
||||||
|
//return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = new ShadowWindow(800,800);
|
||||||
|
|
||||||
|
CATCH(VulkanManager::getInstance()->initVulkan(window_->sdlWindowPtr);)
|
||||||
|
|
||||||
|
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
|
||||||
|
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
ImGui::CreateContext();
|
||||||
|
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||||
|
|
||||||
|
// Setup Dear ImGui style
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
|
||||||
|
VkDescriptorPool imGuiPool;
|
||||||
|
VulkanManager* vk = VulkanManager::getInstance();
|
||||||
|
VkDescriptorPoolSize pool_sizes[] =
|
||||||
|
{
|
||||||
|
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDescriptorPoolCreateInfo pool_info = {};
|
||||||
|
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||||
|
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
||||||
|
pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes);
|
||||||
|
pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes);
|
||||||
|
pool_info.pPoolSizes = pool_sizes;
|
||||||
|
vkCreateDescriptorPool(vk->getDevice()->logical, &pool_info, VK_NULL_HANDLE, &imGuiPool);
|
||||||
|
|
||||||
|
// Setup Platform/Renderer backends
|
||||||
|
ImGui_ImplSDL2_InitForVulkan(window_->sdlWindowPtr);
|
||||||
|
ImGui_ImplVulkan_InitInfo init_info = {};
|
||||||
|
init_info.Instance = vk->getVulkan();
|
||||||
|
init_info.PhysicalDevice = vk->getDevice()->physical;
|
||||||
|
init_info.Device = vk->getDevice()->logical;
|
||||||
|
init_info.QueueFamily = vk->getDevice()->queueData.graphics;
|
||||||
|
init_info.Queue = vk->getDevice()->graphicsQueue;
|
||||||
|
init_info.PipelineCache = VK_NULL_HANDLE;
|
||||||
|
init_info.DescriptorPool = imGuiPool;
|
||||||
|
init_info.Subpass = 1;
|
||||||
|
init_info.MinImageCount = vk->getSwapchain()->images.size();
|
||||||
|
init_info.ImageCount = vk->getSwapchain()->images.size();
|
||||||
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
init_info.Allocator = VK_NULL_HANDLE;
|
||||||
|
init_info.CheckVkResultFn = nullptr;
|
||||||
|
|
||||||
|
vlkxtemp::ModelBuilder::ShaderPool pool;
|
||||||
|
|
||||||
|
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
|
||||||
|
skyboxConstant = std::make_unique<vlkx::PushConstant>(sizeof(SkyboxTransform), 2);
|
||||||
|
planetConstant = std::make_unique<vlkx::PushConstant>(sizeof(PlanetTransform), 2);
|
||||||
|
light = std::make_unique<vlkx::UniformBuffer>(sizeof(Light), 2);
|
||||||
|
|
||||||
|
aspectRatio = (float) window_->Width / window_->Height;
|
||||||
|
|
||||||
|
vlkx::Camera::Config conf {};
|
||||||
|
camera = vlkx::UserPerspectiveCamera::create( {}, {}, { 110, aspectRatio });
|
||||||
|
|
||||||
|
using vlkxtemp::ModelBuilder;
|
||||||
|
|
||||||
|
|
||||||
|
const vlkx::RefCountedTexture::CubemapLocation skybox {
|
||||||
|
"resources/planets/bg",
|
||||||
|
{
|
||||||
|
"left.png", "right.png",
|
||||||
|
"top.png", "bottom.png",
|
||||||
|
"front.png", "back.png"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
skyboxModel = ModelBuilder {
|
||||||
|
"Skybox", 2, aspectRatio,
|
||||||
|
ModelBuilder::SingleMeshModel { "resources/planets/skybox.obj", 1,
|
||||||
|
{ { ModelBuilder::TextureType::Cubemap, { { skybox } } } }
|
||||||
|
}}
|
||||||
|
.bindTextures(ModelBuilder::TextureType::Cubemap, 1)
|
||||||
|
.pushStage(VK_SHADER_STAGE_VERTEX_BIT)
|
||||||
|
.pushConstant(skyboxConstant.get(), 0)
|
||||||
|
.shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/planets/skybox.vert.spv")
|
||||||
|
.shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/planets/skybox.frag.spv")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
planetModel = ModelBuilder {
|
||||||
|
"Walrus", 2, aspectRatio,
|
||||||
|
ModelBuilder::SingleMeshModel {"resources/walrus/walrus.obj", 1,
|
||||||
|
{{ ModelBuilder::TextureType::Diffuse, { { "resources/walrus/texture.png" } } } }
|
||||||
|
}}
|
||||||
|
.bindTextures(ModelBuilder::TextureType::Diffuse, 2)
|
||||||
|
.uniform(VK_SHADER_STAGE_FRAGMENT_BIT, {{1, 1}})
|
||||||
|
.uniformBuffer(1, *light)
|
||||||
|
.pushStage(VK_SHADER_STAGE_VERTEX_BIT)
|
||||||
|
.pushConstant(planetConstant.get(), 0)
|
||||||
|
.shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/walrus/walrus.vert.spv")
|
||||||
|
.shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/walrus/walrus.frag.spv")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
passManager = std::make_unique<vlkx::ScreenRenderPassManager>(vlkx::RendererConfig { 2 });
|
||||||
|
|
||||||
|
passManager->initializeRenderPass();
|
||||||
|
|
||||||
|
skyboxModel->update(true, VulkanManager::getInstance()->getSwapchain()->extent, VK_SAMPLE_COUNT_1_BIT, *passManager->getPass(), 0);
|
||||||
|
int cursorX, cursorY;
|
||||||
|
SDL_GetMouseState(&cursorX, &cursorY);
|
||||||
|
camera->setPos({ cursorX, cursorY });
|
||||||
|
|
||||||
|
planetModel->update(true, VulkanManager::getInstance()->getSwapchain()->extent, VK_SAMPLE_COUNT_1_BIT, *passManager->getPass(), 0);
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_Init(&init_info, **passManager->getPass());
|
||||||
|
// Upload Fonts
|
||||||
|
VkTools::immediateExecute([](const VkCommandBuffer& commands) { ImGui_ImplVulkan_CreateFontsTexture(commands); }, VulkanManager::getInstance()->getDevice());
|
||||||
|
|
||||||
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printMatrix(glm::mat4 mat) {
|
||||||
|
for (size_t i = 0; i < 4; i++) {
|
||||||
|
for (size_t j = 0; j < 4; j++) {
|
||||||
|
std::cout << mat[i][j] << " ";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateData(int frame) {
|
||||||
|
const float elapsed_time = Time::timeSinceStart;
|
||||||
|
|
||||||
|
const glm::vec3 lightDir{glm::sin(elapsed_time * 0.0006f), -0.3f,
|
||||||
|
glm::cos(elapsed_time * 0.0006f)};
|
||||||
|
*light->getData<Light>(frame) =
|
||||||
|
{glm::vec4{lightDir, elapsed_time}};
|
||||||
|
light->upload(frame);
|
||||||
|
|
||||||
|
glm::mat4 modelMatrix { 1 };
|
||||||
|
modelMatrix = glm::rotate(modelMatrix, elapsed_time * glm::radians(0.0005f), glm::vec3 { 0, 1, 0 });
|
||||||
|
const vlkx::Camera& cam = camera->getCamera();
|
||||||
|
|
||||||
|
const glm::mat4 view = glm::lookAt(glm::vec3{3.0f}, glm::vec3{0.0f},
|
||||||
|
glm::vec3{0.0f, 1.0f, 0.0f});
|
||||||
|
const glm::mat4 proj = glm::perspective(
|
||||||
|
glm::radians(45.0f), aspectRatio,
|
||||||
|
0.1f, 100.0f);
|
||||||
|
|
||||||
|
glm::mat4 planetProjection = cam.getProjMatrix() * cam.getViewMatrix();
|
||||||
|
*planetConstant->getData<PlanetTransform>(frame) = { modelMatrix, planetProjection };
|
||||||
|
glm::mat4 skyboxMat = cam.getProjMatrix() * cam.getSkyboxView();
|
||||||
|
skyboxConstant->getData<SkyboxTransform>(frame)->value = skyboxMat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void imGuiStartDraw() {
|
||||||
|
ImGui_ImplVulkan_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
}
|
||||||
|
void imGuiEndDraw(const VkCommandBuffer& commands) {
|
||||||
|
ImGui::Render();
|
||||||
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showDebugWindow(std::unique_ptr<vlkx::UserPerspectiveCamera>* cam) {
|
||||||
|
#define camPos cam->get()->getCamera().getPosition()
|
||||||
|
#define camFwd cam->get()->getCamera().getForward()
|
||||||
|
ImGui::Begin("Camera Debug");
|
||||||
|
ImGui::Text("Camera position: (x %f, y %f, z %f)", camPos.x, camPos.y, camPos.z );
|
||||||
|
ImGui::Text("Camera target: (x %f, y %f, z %f)", camPos.x + camFwd.x, camPos.y + camFwd.y, camPos.z + camFwd.z);
|
||||||
|
ImGui::Text("Camera FOV: %f", cam->get()->getCamera().getFieldOfView());
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::PollEvents() {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) { // poll until all events are handled!
|
||||||
|
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||||
|
|
||||||
|
switch(event.type) {
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_ESCAPE:
|
||||||
|
camera->reset(); break;
|
||||||
|
case SDLK_w:
|
||||||
|
camera->press(vlkx::Camera::Input::Up, Time::deltaTime); break;
|
||||||
|
case SDLK_s:
|
||||||
|
camera->press(vlkx::Camera::Input::Down, Time::deltaTime); break;
|
||||||
|
case SDLK_a:
|
||||||
|
camera->press(vlkx::Camera::Input::Left, Time::deltaTime); break;
|
||||||
|
case SDLK_d:
|
||||||
|
camera->press(vlkx::Camera::Input::Right, Time::deltaTime); break;
|
||||||
|
} break;
|
||||||
|
case SDL_MOUSEMOTION:
|
||||||
|
camera->move(-event.motion.xrel, -event.motion.yrel); break;
|
||||||
|
|
||||||
|
case SDL_MOUSEWHEEL:
|
||||||
|
camera->scroll(event.wheel.y, 1, 170); break;
|
||||||
|
case SDL_QUIT:
|
||||||
|
running = false; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::Start()
|
||||||
|
{
|
||||||
|
const auto update = [](const int frame) { updateData(frame); };
|
||||||
|
|
||||||
|
while (running)
|
||||||
|
{
|
||||||
|
PollEvents();
|
||||||
|
|
||||||
|
const auto result = renderCommands->execute(renderCommands->getFrame(), VulkanManager::getInstance()->getSwapchain()->swapChain, update,
|
||||||
|
[](const VkCommandBuffer& buffer, uint32_t frame) {
|
||||||
|
passManager->getPass()->execute(buffer, frame, {
|
||||||
|
// Render our model
|
||||||
|
[&frame](const VkCommandBuffer& commands) {
|
||||||
|
planetModel->draw(commands, frame, 1);
|
||||||
|
skyboxModel->draw(commands, frame, 1);
|
||||||
|
},
|
||||||
|
// Render ImGUI
|
||||||
|
[&](const VkCommandBuffer& commands) {
|
||||||
|
imGuiStartDraw();
|
||||||
|
|
||||||
|
bool showDemo = true;
|
||||||
|
//if (showDemo)
|
||||||
|
// ImGui::ShowDemoWindow(&showDemo);
|
||||||
|
|
||||||
|
showDebugWindow(&camera);
|
||||||
|
|
||||||
|
imGuiEndDraw(commands);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.has_value())
|
||||||
|
throw std::runtime_error("Resizing is not implemented");
|
||||||
|
|
||||||
|
renderCommands->nextFrame();
|
||||||
|
|
||||||
|
Time::UpdateTime();
|
||||||
|
|
||||||
|
camera->active(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDeviceWaitIdle(VulkanManager::getInstance()->getDevice()->logical);
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_Shutdown();
|
||||||
|
ImGui_ImplSDL2_Shutdown();
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
|
||||||
|
SDL_DestroyWindow(window_->sdlWindowPtr);
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
}
|
11
projs/shadow/shadow-engine/core/src/core/Module.cpp
Normal file
11
projs/shadow/shadow-engine/core/src/core/Module.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//
|
||||||
|
// Created by dpete on 2022-07-06.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "core/Module.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
SHObject_Base_Impl(Module)
|
||||||
|
|
||||||
|
} // ShadowEngine
|
123
projs/shadow/shadow-engine/core/src/core/ModuleManager.cpp
Normal file
123
projs/shadow/shadow-engine/core/src/core/ModuleManager.cpp
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
//
|
||||||
|
// Created by dpete on 2022-07-06.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "core/ModuleManager.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
ShadowEngine::ModuleManager* ShadowEngine::ModuleManager::instance = nullptr;
|
||||||
|
|
||||||
|
ShadowEngine::ModuleManager::ModuleManager()
|
||||||
|
{
|
||||||
|
if (instance != nullptr)
|
||||||
|
{
|
||||||
|
//ERROR
|
||||||
|
}
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowEngine::ModuleManager::~ModuleManager()
|
||||||
|
= default;
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::PushModule(const std::shared_ptr<Module>& module, const std::string& domain)
|
||||||
|
{
|
||||||
|
ModuleRef r = {module, domain};
|
||||||
|
modules.emplace_back(r);
|
||||||
|
if (domain == "renderer")
|
||||||
|
renderer = r;
|
||||||
|
module->PreInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowEngine::Module& ShadowEngine::ModuleManager::GetModule(const std::string& name)
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
if (module.module->GetName() == name)
|
||||||
|
return *module.module;
|
||||||
|
}
|
||||||
|
//SH_ASSERT(false, "Can't find the module");
|
||||||
|
throw std::runtime_error("Can't find the module");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::Init()
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->Init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::Destroy()
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::PreRender()
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->PreRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::Event(SDL_Event* evt)
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->Event(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::Update(int frame)
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->Update(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::LateRender(VkCommandBuffer& commands, int frame)
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->LateRender(commands, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::Render(VkCommandBuffer& commands, int frame)
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->Render(commands, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::OverlayRender()
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->OverlayRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::Recreate()
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->Recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ShadowEngine::ModuleManager::AfterFrameEnd()
|
||||||
|
{
|
||||||
|
for (auto& module : modules)
|
||||||
|
{
|
||||||
|
module.module->AfterFrameEnd();
|
||||||
|
}
|
||||||
|
}
|
57
projs/shadow/shadow-engine/core/src/core/SDL2Module.cpp
Normal file
57
projs/shadow/shadow-engine/core/src/core/SDL2Module.cpp
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// Created by dpete on 30/08/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "core/SDL2Module.h"
|
||||||
|
#include "core/ShadowWindow.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
#include "imgui_impl_sdl.h"
|
||||||
|
|
||||||
|
SHObject_Base_Impl(ShadowEngine::SDL2Module)
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::Init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::PreInit() {
|
||||||
|
// Initialize SDL. SDL_Init will return -1 if it fails.
|
||||||
|
if ( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) {
|
||||||
|
spdlog::error("Error creating window: " + std::string(SDL_GetError()));
|
||||||
|
//system("pause");
|
||||||
|
// End the program
|
||||||
|
//return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_window = new ShadowWindow(1280,720);
|
||||||
|
SDL_SetWindowResizable(_window->sdlWindowPtr, SDL_TRUE);
|
||||||
|
//SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::Update(int frame) {}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::Recreate() {}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::Render(VkCommandBuffer& commands, int frame) {}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::OverlayRender() {}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::LateRender(VkCommandBuffer& commands, int frame) {}
|
||||||
|
|
||||||
|
std::string ShadowEngine::SDL2Module::GetName() {
|
||||||
|
return this->GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::AfterFrameEnd() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::Event(SDL_Event *e) {
|
||||||
|
ImGui_ImplSDL2_ProcessEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::Destroy() {
|
||||||
|
SDL_DestroyWindow(_window->sdlWindowPtr);
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowEngine::SDL2Module::PreRender() {
|
||||||
|
}
|
112
projs/shadow/shadow-engine/core/src/core/ShadowApplication.cpp
Normal file
112
projs/shadow/shadow-engine/core/src/core/ShadowApplication.cpp
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include "core/ShadowApplication.h"
|
||||||
|
#include "core/Time.h"
|
||||||
|
#include "core/SDL2Module.h"
|
||||||
|
#include "debug/DebugModule.h"
|
||||||
|
#include "dylib.hpp"
|
||||||
|
#include "vlkx/vulkan/abstraction/Commands.h"
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_impl_sdl.h>
|
||||||
|
#include <vlkx/vulkan/VulkanModule.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#define CATCH(x) \
|
||||||
|
try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); }
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
dylib* gameLib;
|
||||||
|
|
||||||
|
ShadowApplication* ShadowApplication::instance = nullptr;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::RenderCommand> renderCommands;
|
||||||
|
|
||||||
|
ShadowApplication::ShadowApplication(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
if(argc > 1)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
{
|
||||||
|
std::string param(argv[i]);
|
||||||
|
if(param == "-no-gui")
|
||||||
|
{
|
||||||
|
this->no_gui = true;
|
||||||
|
}
|
||||||
|
if(param == "-game")
|
||||||
|
{
|
||||||
|
this->game = argv[i+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowApplication::~ShadowApplication()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::loadGame(){
|
||||||
|
if(game.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
void (*gameInti)(ShadowApplication*);
|
||||||
|
|
||||||
|
try {
|
||||||
|
gameLib = new dylib("./", game);
|
||||||
|
|
||||||
|
gameInti = gameLib->get_function<void(ShadowApplication*)>("shadow_main");
|
||||||
|
|
||||||
|
gameInti(this);
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
spdlog::error(e.what());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::Init()
|
||||||
|
{
|
||||||
|
moduleManager.PushModule(std::make_shared<SDL2Module>(),"core");
|
||||||
|
auto renderer = std::make_shared<VulkanModule>();
|
||||||
|
renderer->EnableEditor();
|
||||||
|
moduleManager.PushModule(renderer, "renderer");
|
||||||
|
|
||||||
|
loadGame();
|
||||||
|
|
||||||
|
moduleManager.PushModule(std::make_shared<Debug::DebugModule>(), "core");
|
||||||
|
|
||||||
|
moduleManager.Init();
|
||||||
|
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::Start()
|
||||||
|
{
|
||||||
|
SDL_Event event;
|
||||||
|
while (running)
|
||||||
|
{
|
||||||
|
while (SDL_PollEvent(&event)) { // poll until all events are handled!
|
||||||
|
moduleManager.Event(&event);
|
||||||
|
if (event.type == SDL_QUIT)
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleManager.PreRender();
|
||||||
|
|
||||||
|
moduleManager.renderer->BeginRenderPass(renderCommands);
|
||||||
|
|
||||||
|
moduleManager.AfterFrameEnd();
|
||||||
|
|
||||||
|
renderCommands->nextFrame();
|
||||||
|
Time::UpdateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleManager.Destroy();
|
||||||
|
|
||||||
|
delete gameLib;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowApplication& ShadowApplication::Get() { return *instance; };
|
||||||
|
}
|
21
projs/shadow/shadow-engine/core/src/core/ShadowWindow.cpp
Normal file
21
projs/shadow/shadow-engine/core/src/core/ShadowWindow.cpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#include "core/ShadowWindow.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
ShadowEngine::ShadowWindow::ShadowWindow(int W, int H) : Height(H), Width(W)
|
||||||
|
{
|
||||||
|
// Create our window
|
||||||
|
sdlWindowPtr = SDL_CreateWindow( "Candlefire", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Width, Height, SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN );
|
||||||
|
|
||||||
|
// Make sure creating the window succeeded
|
||||||
|
if ( !sdlWindowPtr ) {
|
||||||
|
//Raise an error in the log
|
||||||
|
spdlog::error("Error creating window: " + std::string(SDL_GetError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowEngine::ShadowWindow::~ShadowWindow()
|
||||||
|
{
|
||||||
|
}
|
27
projs/shadow/shadow-engine/core/src/core/Time.cpp
Normal file
27
projs/shadow/shadow-engine/core/src/core/Time.cpp
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#include "core/Time.h"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
API int Time::NOW = 0;//SDL_GetPerformanceCounter();
|
||||||
|
API int Time::LAST = 0;
|
||||||
|
API double lastFrame = 0;
|
||||||
|
API double Time::deltaTime_ms = 0;
|
||||||
|
API double Time::deltaTime = 0;
|
||||||
|
API double Time::startTime = 0;
|
||||||
|
API double Time::timeSinceStart = 0;
|
||||||
|
|
||||||
|
void Time::UpdateTime()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
auto now = system_clock::now();
|
||||||
|
auto now_ms = time_point_cast<milliseconds>(now);
|
||||||
|
|
||||||
|
auto value = now_ms.time_since_epoch();
|
||||||
|
double duration = value.count();
|
||||||
|
|
||||||
|
deltaTime = duration - lastFrame;
|
||||||
|
if (startTime == 0)
|
||||||
|
startTime = duration;
|
||||||
|
timeSinceStart = duration - startTime;
|
||||||
|
|
||||||
|
lastFrame = duration;
|
||||||
|
}
|
34
projs/shadow/shadow-engine/core/src/debug/DebugModule.cpp
Normal file
34
projs/shadow/shadow-engine/core/src/debug/DebugModule.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// Created by dpete on 31/08/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "debug/DebugModule.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "core/Time.h"
|
||||||
|
#include "core/ModuleManager.h"
|
||||||
|
|
||||||
|
SHObject_Base_Impl(ShadowEngine::Debug::DebugModule)
|
||||||
|
|
||||||
|
void ShadowEngine::Debug::DebugModule::OverlayRender() {
|
||||||
|
|
||||||
|
if (ImGui::Begin("Time", &active, ImGuiWindowFlags_MenuBar)) {
|
||||||
|
ImGui::Text("delta time in ms: %lf", Time::deltaTime_ms);
|
||||||
|
ImGui::Text("delta time in s: %lf", Time::deltaTime);
|
||||||
|
ImGui::Text("LAST time in: %d", Time::LAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (ImGui::Begin("Active Modules", &active, ImGuiWindowFlags_MenuBar)) {
|
||||||
|
|
||||||
|
ShadowEngine::ModuleManager *m = ShadowEngine::ModuleManager::instance;
|
||||||
|
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.4f, 1.0f), "Active Modules:");
|
||||||
|
for (auto &module: m->modules) {
|
||||||
|
ImGui::Text("%s", module.module->GetName().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
}
|
56
projs/shadow/shadow-engine/core/src/id/UUID.cpp
Normal file
56
projs/shadow/shadow-engine/core/src/id/UUID.cpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#include <id/UUID.h>
|
||||||
|
|
||||||
|
namespace SE {
|
||||||
|
static_assert(sizeof(UUID) == 16, "UUID has incorrect size");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that a string has the correct format;
|
||||||
|
* XXXXXXXX-XXXX-XXX-XXXXX-XXXXXXXXXXXX
|
||||||
|
*
|
||||||
|
* The length must be 36.
|
||||||
|
* There must be dashes at index 8, 13, 18 and 23.
|
||||||
|
* @param str the input string
|
||||||
|
* @return whether the UUID string is correctly formed
|
||||||
|
*/
|
||||||
|
bool UUID::IsValidStr(const char *str) {
|
||||||
|
size_t const len = strlen(str);
|
||||||
|
if (len != 36) return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
char c = str[i];
|
||||||
|
if (c == '-') {
|
||||||
|
if (i != 8 && i != 13 && i != 18 && i != 23) return false;
|
||||||
|
} else if (! std::isxdigit(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID::UUID(char const* str ) {
|
||||||
|
// A single byte is two hex characters.
|
||||||
|
// Store them here so that we can use them later.
|
||||||
|
char c0 = '\0', c1;
|
||||||
|
|
||||||
|
size_t const len = strlen( str );
|
||||||
|
uint32_t byteIdx = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++ ) {
|
||||||
|
char const c = str[i];
|
||||||
|
if ( c == '-' )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Scan for pairs of characters.
|
||||||
|
// Only assign a byte if two have been parsed.
|
||||||
|
if (c0 == '\0') {
|
||||||
|
c0 = c;
|
||||||
|
} else {
|
||||||
|
c1 = c;
|
||||||
|
data.u8[byteIdx++] = std::stoi(std::string(c0, c1));
|
||||||
|
// Reset the first char so that we can return to scanning a pair.
|
||||||
|
c0 = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
projs/shadow/shadow-engine/shadow-entity/inc/id/ID.h
Normal file
60
projs/shadow/shadow-engine/shadow-entity/inc/id/ID.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <id/UUID.h> // Shadow-Engine/core/inc/id
|
||||||
|
|
||||||
|
namespace SE {
|
||||||
|
|
||||||
|
// An ID for a section of the scene (which may be unloaded separately of the level; for ie. open world Cells.)
|
||||||
|
using EntitySectionID = UUID;
|
||||||
|
// An ID for a scene (which contains all sections and entities).
|
||||||
|
using EntitySceneID = UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the common data and functions for the Entity System's identifiers.
|
||||||
|
*/
|
||||||
|
struct IDContainer {
|
||||||
|
public:
|
||||||
|
IDContainer() = default;
|
||||||
|
explicit IDContainer(uint64_t v) : id(v) {}
|
||||||
|
|
||||||
|
// Check if the ID is valid (non-zero)
|
||||||
|
__inline bool Valid() const { return id != 0; }
|
||||||
|
// Set this ID to be invalid (zero).
|
||||||
|
__inline void Invalidate() { id = 0; }
|
||||||
|
// Check for in/equality against another ID.
|
||||||
|
__inline bool operator==(IDContainer const& other) const { return id == other.id; }
|
||||||
|
__inline bool operator!=(IDContainer const& other) const { return id != other.id; }
|
||||||
|
|
||||||
|
uint64_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ID used for an Entity.
|
||||||
|
* Is only guaranteed to be unique in the current scene (ie. a level change may also change the IDs of the entities within).
|
||||||
|
*/
|
||||||
|
struct EntityID : public IDContainer {
|
||||||
|
/**
|
||||||
|
* @return a new, unused Entity ID.
|
||||||
|
*/
|
||||||
|
static EntityID Generate();
|
||||||
|
|
||||||
|
EntityID() = default;
|
||||||
|
explicit EntityID(uint64_t v) : IDContainer(v) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ID used for a Component (a segment of data attached to an Entity).
|
||||||
|
* Is only guaranteed to be unique in the current scene (ie. a level change may also change the IDs of the components within).
|
||||||
|
*/
|
||||||
|
struct ComponentID : public IDContainer {
|
||||||
|
/**
|
||||||
|
* @return a new, unused Component ID.
|
||||||
|
*/
|
||||||
|
static ComponentID Generate();
|
||||||
|
|
||||||
|
ComponentID() = default;
|
||||||
|
explicit ComponentID(uint64_t v) : IDContainer(v) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
16
projs/shadow/shadow-engine/shadow-entity/src/id/ID.cpp
Normal file
16
projs/shadow/shadow-engine/shadow-entity/src/id/ID.cpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#include <id/ID.h>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace SE {
|
||||||
|
static std::atomic<uint64_t> entityID = 1;
|
||||||
|
EntityID EntityID::Generate() {
|
||||||
|
EntityID id(entityID++);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::atomic<uint64_t> componentID = 1;
|
||||||
|
ComponentID ComponentID::Generate() {
|
||||||
|
ComponentID id(componentID++);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
20
projs/shadow/shadow-engine/shadow-file-format/CMakeLists.txt
Normal file
20
projs/shadow/shadow-engine/shadow-file-format/CMakeLists.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
# Set up Catch2 testing
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "cmake")
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
# Set up asset sourceset
|
||||||
|
FILE(GLOB_RECURSE SOURCES src/*.cpp src/*.h)
|
||||||
|
FILE(GLOB_RECURSE TESTS test/*.cpp)
|
||||||
|
|
||||||
|
add_library(shadow-asset ${SOURCES})
|
||||||
|
|
||||||
|
# Set up test executable
|
||||||
|
add_executable(shadow-asset-test ${TESTS})
|
||||||
|
target_link_libraries(shadow-asset-test PRIVATE Catch2::Catch2 shadow-utils)
|
||||||
|
|
||||||
|
# Enable testing on the executable
|
||||||
|
include(CTest)
|
||||||
|
include(Catch)
|
||||||
|
catch_discover_tests(shadow-asset-test)
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||||
|
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function(catch_discover_tests TARGET)
|
||||||
|
cmake_parse_arguments(
|
||||||
|
""
|
||||||
|
""
|
||||||
|
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX"
|
||||||
|
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
|
||||||
|
${ARGN}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT _WORKING_DIRECTORY)
|
||||||
|
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
|
endif()
|
||||||
|
if(NOT _TEST_LIST)
|
||||||
|
set(_TEST_LIST ${TARGET}_TESTS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
## Generate a unique name based on the extra arguments
|
||||||
|
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}")
|
||||||
|
string(SUBSTRING ${args_hash} 0 7 args_hash)
|
||||||
|
|
||||||
|
# Define rule to generate test list for aforementioned test executable
|
||||||
|
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
|
||||||
|
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
|
||||||
|
get_property(crosscompiling_emulator
|
||||||
|
TARGET ${TARGET}
|
||||||
|
PROPERTY CROSSCOMPILING_EMULATOR
|
||||||
|
)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET ${TARGET} POST_BUILD
|
||||||
|
BYPRODUCTS "${ctest_tests_file}"
|
||||||
|
COMMAND "${CMAKE_COMMAND}"
|
||||||
|
-D "TEST_TARGET=${TARGET}"
|
||||||
|
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
|
||||||
|
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
|
||||||
|
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
|
||||||
|
-D "TEST_SPEC=${_TEST_SPEC}"
|
||||||
|
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
|
||||||
|
-D "TEST_PROPERTIES=${_PROPERTIES}"
|
||||||
|
-D "TEST_PREFIX=${_TEST_PREFIX}"
|
||||||
|
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
|
||||||
|
-D "TEST_LIST=${_TEST_LIST}"
|
||||||
|
-D "TEST_REPORTER=${_REPORTER}"
|
||||||
|
-D "TEST_OUTPUT_DIR=${_OUTPUT_DIR}"
|
||||||
|
-D "TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}"
|
||||||
|
-D "TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}"
|
||||||
|
-D "CTEST_FILE=${ctest_tests_file}"
|
||||||
|
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
file(WRITE "${ctest_include_file}"
|
||||||
|
"if(EXISTS \"${ctest_tests_file}\")\n"
|
||||||
|
" include(\"${ctest_tests_file}\")\n"
|
||||||
|
"else()\n"
|
||||||
|
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
|
||||||
|
"endif()\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
|
||||||
|
# Add discovered tests to directory TEST_INCLUDE_FILES
|
||||||
|
set_property(DIRECTORY
|
||||||
|
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
|
||||||
|
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
|
||||||
|
if (NOT ${test_include_file_set})
|
||||||
|
set_property(DIRECTORY
|
||||||
|
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Cannot set more than one TEST_INCLUDE_FILE"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
set(_CATCH_DISCOVER_TESTS_SCRIPT
|
||||||
|
CatchAddTests.cmake
|
||||||
|
CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
|
||||||
|
)
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||||
|
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||||
|
|
||||||
|
set(prefix "${TEST_PREFIX}")
|
||||||
|
set(suffix "${TEST_SUFFIX}")
|
||||||
|
set(spec ${TEST_SPEC})
|
||||||
|
set(extra_args ${TEST_EXTRA_ARGS})
|
||||||
|
set(properties ${TEST_PROPERTIES})
|
||||||
|
set(reporter ${TEST_REPORTER})
|
||||||
|
set(output_dir ${TEST_OUTPUT_DIR})
|
||||||
|
set(output_prefix ${TEST_OUTPUT_PREFIX})
|
||||||
|
set(output_suffix ${TEST_OUTPUT_SUFFIX})
|
||||||
|
set(script)
|
||||||
|
set(suite)
|
||||||
|
set(tests)
|
||||||
|
|
||||||
|
function(add_command NAME)
|
||||||
|
set(_args "")
|
||||||
|
# use ARGV* instead of ARGN, because ARGN splits arrays into multiple arguments
|
||||||
|
math(EXPR _last_arg ${ARGC}-1)
|
||||||
|
foreach(_n RANGE 1 ${_last_arg})
|
||||||
|
set(_arg "${ARGV${_n}}")
|
||||||
|
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
|
||||||
|
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
|
||||||
|
else()
|
||||||
|
set(_args "${_args} ${_arg}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Run test executable to get list of available tests
|
||||||
|
if(NOT EXISTS "${TEST_EXECUTABLE}")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
|
||||||
|
OUTPUT_VARIABLE output
|
||||||
|
RESULT_VARIABLE result
|
||||||
|
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||||
|
)
|
||||||
|
# Catch --list-test-names-only reports the number of tests, so 0 is... surprising
|
||||||
|
if(${result} EQUAL 0)
|
||||||
|
message(WARNING
|
||||||
|
"Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
|
||||||
|
)
|
||||||
|
elseif(${result} LESS 0)
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||||
|
" Result: ${result}\n"
|
||||||
|
" Output: ${output}\n"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
string(REPLACE "\n" ";" output "${output}")
|
||||||
|
|
||||||
|
# Run test executable to get list of available reporters
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-reporters
|
||||||
|
OUTPUT_VARIABLE reporters_output
|
||||||
|
RESULT_VARIABLE reporters_result
|
||||||
|
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||||
|
)
|
||||||
|
if(${reporters_result} EQUAL 0)
|
||||||
|
message(WARNING
|
||||||
|
"Test executable '${TEST_EXECUTABLE}' contains no reporters!\n"
|
||||||
|
)
|
||||||
|
elseif(${reporters_result} LESS 0)
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||||
|
" Result: ${reporters_result}\n"
|
||||||
|
" Output: ${reporters_output}\n"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
string(FIND "${reporters_output}" "${reporter}" reporter_is_valid)
|
||||||
|
if(reporter AND ${reporter_is_valid} EQUAL -1)
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"\"${reporter}\" is not a valid reporter!\n"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Prepare reporter
|
||||||
|
if(reporter)
|
||||||
|
set(reporter_arg "--reporter ${reporter}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Prepare output dir
|
||||||
|
if(output_dir AND NOT IS_ABSOLUTE ${output_dir})
|
||||||
|
set(output_dir "${TEST_WORKING_DIR}/${output_dir}")
|
||||||
|
if(NOT EXISTS ${output_dir})
|
||||||
|
file(MAKE_DIRECTORY ${output_dir})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Parse output
|
||||||
|
foreach(line ${output})
|
||||||
|
set(../test ${line})
|
||||||
|
# Escape characters in test case names that would be parsed by Catch2
|
||||||
|
set(test_name ${test})
|
||||||
|
#foreach(char , [ ])
|
||||||
|
#string(REPLACE ${char} "\\${char}" test_name ${test_name})
|
||||||
|
#endforeach(char)
|
||||||
|
# ...add output dir
|
||||||
|
if(output_dir)
|
||||||
|
string(REGEX REPLACE "[^A-Za-z0-9_]" "_" test_name_clean ${test_name})
|
||||||
|
set(output_dir_arg "--out ${output_dir}/${output_prefix}${test_name_clean}${output_suffix}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ...and add to script
|
||||||
|
add_command(add_test
|
||||||
|
"${prefix}${test}${suffix}"
|
||||||
|
${TEST_EXECUTOR}
|
||||||
|
"${TEST_EXECUTABLE}"
|
||||||
|
"${test_name}"
|
||||||
|
${extra_args}
|
||||||
|
"${reporter_arg}"
|
||||||
|
"${output_dir_arg}"
|
||||||
|
)
|
||||||
|
add_command(set_tests_properties
|
||||||
|
"${prefix}${test}${suffix}"
|
||||||
|
PROPERTIES
|
||||||
|
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||||
|
${properties}
|
||||||
|
)
|
||||||
|
list(APPEND tests "${prefix}${test}${suffix}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Create a list of all discovered tests, which users may use to e.g. set
|
||||||
|
# properties on the tests
|
||||||
|
add_command(set ${TEST_LIST} ${tests})
|
||||||
|
|
||||||
|
# Write CTest script
|
||||||
|
file(WRITE "${CTEST_FILE}" "${script}")
|
|
@ -1,9 +1,8 @@
|
||||||
#include "SFFParser.h"
|
#include "SFFParser.h"
|
||||||
|
#include "string-helpers.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include "string-helpers.h"
|
|
||||||
|
|
||||||
namespace Shadow::SFF {
|
namespace Shadow::SFF {
|
||||||
|
|
||||||
SFFElement* SFFParser::ReadFromStream(std::istream& stream)
|
SFFElement* SFFParser::ReadFromStream(std::istream& stream)
|
|
@ -1,4 +1,5 @@
|
||||||
#include <catch2/catch.hpp>
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
|
||||||
TEST_CASE("15 is less than 20", "[numbers]") {
|
TEST_CASE("15 is less than 20", "[numbers]") {
|
||||||
REQUIRE(15 < 20);
|
REQUIRE(15 < 20);
|
17970
projs/shadow/shadow-engine/shadow-file-format/test/catch2/catch.hpp
Normal file
17970
projs/shadow/shadow-engine/shadow-file-format/test/catch2/catch.hpp
Normal file
File diff suppressed because it is too large
Load Diff
61
projs/shadow/shadow-engine/shadow-reflection/inc/SHObject.h
Normal file
61
projs/shadow/shadow-engine/shadow-reflection/inc/SHObject.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <typeinfo>
|
||||||
|
|
||||||
|
#include "exports.h"
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief This is the base class for every class in the Engine that uses runtime reflection.
|
||||||
|
|
||||||
|
* Currently it provides a runtime TypeID and TypeName witch can be accesed as static and as class memebers.
|
||||||
|
* The ID is a int type number witch is generated incramently, on the first call to get a type.
|
||||||
|
|
||||||
|
* Each class that inherits from this or it's parent inheris form it must implement the
|
||||||
|
SHObject::GetType and SHObject::GetTypeId methodes and make it's own static methodes.
|
||||||
|
To make it easier a standard implementation of these can be used with the SHObject_Base() macro
|
||||||
|
witch implements all of these functions. It uses the typeid().name of the class.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class SHObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* \brief Generates a new UID for each call
|
||||||
|
* \return the next Unique ID that was just generated
|
||||||
|
*/
|
||||||
|
API static uint64_t GenerateId() noexcept;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* \brief Returns the top level class type name of the object
|
||||||
|
* \return The class Class name as a string
|
||||||
|
*/
|
||||||
|
virtual const std::string& GetType() const = 0;
|
||||||
|
/**
|
||||||
|
* \brief Gets the top level type ID
|
||||||
|
* \return UID of the class
|
||||||
|
*/
|
||||||
|
virtual const uint64_t GetTypeId() const = 0;
|
||||||
|
|
||||||
|
virtual ~SHObject() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Macro to make the override functions of SHObject. This should be added in each derived class
|
||||||
|
* \param type The type of the class
|
||||||
|
*/
|
||||||
|
#define SHObject_Base(type) \
|
||||||
|
public: \
|
||||||
|
static const std::string& Type(); \
|
||||||
|
static uint64_t TypeId(); \
|
||||||
|
const std::string& GetType() const override { return Type(); } \
|
||||||
|
const uint64_t GetTypeId() const override { return type::TypeId(); } \
|
||||||
|
private:
|
||||||
|
|
||||||
|
#define SHObject_Base_Impl(type) \
|
||||||
|
const std::string& type::Type() { static const std::string t = typeid(type).name(); return t; } \
|
||||||
|
uint64_t type::TypeId() { static const uint64_t id = GenerateId(); return id; }
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "../inc/SHObject.h"
|
||||||
|
|
||||||
|
uint64_t ShadowEngine::SHObject::GenerateId() noexcept {
|
||||||
|
static uint64_t count = 0;
|
||||||
|
return ++count;
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
=====
|
||||||
|
|
||||||
|
This folder exists to store VLKX renderer objects that are TEMPORARY.
|
||||||
|
It currently contains:
|
||||||
|
|
||||||
|
=====
|
||||||
|
|
||||||
|
Model Loader system
|
||||||
|
|
||||||
|
The Model Loader is temporarily implemented as a raw file reader and OBJ parser.
|
||||||
|
It should be removed when the File Format system is able to parse model and texture files.
|
||||||
|
|
||||||
|
====
|
||||||
|
|
||||||
|
Model Abstraction system
|
||||||
|
|
||||||
|
The Model Abstraction allows you to create a model with:
|
||||||
|
- A mesh
|
||||||
|
- An arbitrary number of textures
|
||||||
|
- A push constant
|
||||||
|
- An arbitrarily large uniform buffer
|
||||||
|
- A fragment and vertex shader
|
||||||
|
|
||||||
|
In all, it contains a custom Render Pipeline that will be used to draw the model.
|
||||||
|
This allows for drastic and visually appealing effects.
|
||||||
|
|
||||||
|
It should be maintained and moved into the appropriate Shadow module once ready.
|
||||||
|
|
||||||
|
====
|
||||||
|
|
||||||
|
Model Builder system
|
||||||
|
|
||||||
|
The model Builder allows for simple construction of Models using the Model Abstraction system above.
|
||||||
|
It consumes the Model Loader and returns a Model Abstraction.
|
||||||
|
|
||||||
|
It should be MOVED into the File Format parsing system once it is ready.
|
|
@ -0,0 +1,137 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "vlkx/render/shader/Pipeline.h"
|
||||||
|
#include "Loader.h"
|
||||||
|
#include "vlkx/render/render_pass/GenericRenderPass.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Descriptor.h"
|
||||||
|
|
||||||
|
namespace vlkxtemp {
|
||||||
|
|
||||||
|
class Model;
|
||||||
|
|
||||||
|
class ModelBuilder {
|
||||||
|
public:
|
||||||
|
|
||||||
|
using ShaderPool = vlkx::ShaderModule::ReleasePool;
|
||||||
|
using TextureType = ModelLoader::TextureType;
|
||||||
|
using TexturePerMesh = std::array<std::vector<std::unique_ptr<vlkx::SamplableImage>>, static_cast<int>(TextureType::Count)>;
|
||||||
|
using BindingPoints = std::map<TextureType, uint32_t>;
|
||||||
|
using TextureSource = vlkx::RefCountedTexture::ImageLocation;
|
||||||
|
using TextureSources = std::map<TextureType, std::vector<TextureSource>>;
|
||||||
|
|
||||||
|
class ModelResource {
|
||||||
|
public:
|
||||||
|
virtual ~ModelResource() = default;
|
||||||
|
virtual void load(ModelBuilder* builder) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SingleMeshModel : public ModelResource {
|
||||||
|
public:
|
||||||
|
SingleMeshModel(std::string&& path, int indexBase, TextureSources&& sources)
|
||||||
|
: objFile(std::move(path)), objIndexBase(indexBase), textureSources(std::move(sources)) {}
|
||||||
|
|
||||||
|
void load(ModelBuilder* builder) const override;
|
||||||
|
private:
|
||||||
|
const std::string objFile;
|
||||||
|
const int objIndexBase;
|
||||||
|
const TextureSources textureSources;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MultiMeshModel : public ModelResource {
|
||||||
|
public:
|
||||||
|
MultiMeshModel(std::string&& modelDir, std::string&& textureDir)
|
||||||
|
: models(std::move(modelDir)), textures(std::move(textureDir)) {}
|
||||||
|
|
||||||
|
void load(ModelBuilder* builder) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string models;
|
||||||
|
const std::string textures;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModelPushConstant {
|
||||||
|
struct Meta {
|
||||||
|
const vlkx::PushConstant* constants;
|
||||||
|
uint32_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkShaderStageFlags stage;
|
||||||
|
std::vector<Meta> constants;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Descriptors = std::vector<std::unique_ptr<vlkx::StaticDescriptor>>;
|
||||||
|
|
||||||
|
ModelBuilder(std::string&& name, int frames, float aspect, const ModelResource& resource);
|
||||||
|
|
||||||
|
ModelBuilder(const ModelBuilder&) = delete;
|
||||||
|
ModelBuilder& operator=(const ModelBuilder&) = delete;
|
||||||
|
|
||||||
|
ModelBuilder& texture(TextureType type, const TextureSource& source);
|
||||||
|
ModelBuilder& bindTextures(TextureType type, uint32_t point);
|
||||||
|
ModelBuilder& instanceBuffer(vlkx::PerInstanceVertexBuffer* buffer);
|
||||||
|
ModelBuilder& uniform(VkShaderStageFlags stage, std::vector<vlkx::Descriptor::Meta::Binding>&& bindings);
|
||||||
|
ModelBuilder& uniformBuffer(uint32_t point, const vlkx::UniformBuffer& buffer);
|
||||||
|
ModelBuilder& pushStage(VkShaderStageFlags stage);
|
||||||
|
ModelBuilder& pushConstant(const vlkx::PushConstant* constant, uint32_t offset);
|
||||||
|
ModelBuilder& shader(VkShaderStageFlagBits stage, std::string&& file);
|
||||||
|
|
||||||
|
std::unique_ptr<Model> build();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Descriptors> createDescs() const;
|
||||||
|
|
||||||
|
const int frames;
|
||||||
|
const float aspectRatio;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::StaticPerVertexBuffer> vertexBuffer;
|
||||||
|
std::vector<TexturePerMesh> textures;
|
||||||
|
TexturePerMesh sharedTextures;
|
||||||
|
BindingPoints bindPoints;
|
||||||
|
|
||||||
|
std::vector<vlkx::PerInstanceVertexBuffer*> instanceBuffers;
|
||||||
|
std::vector<vlkx::Descriptor::Meta> uniformMeta;
|
||||||
|
std::vector<vlkx::Descriptor::BufferInfos> uniformBufferMeta;
|
||||||
|
|
||||||
|
std::optional<ModelPushConstant> pushConstants;
|
||||||
|
std::unique_ptr<vlkx::GraphicsPipelineBuilder> pipelineBuilder;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Model {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Model(const Model&) = delete;
|
||||||
|
Model& operator=(const Model&) = delete;
|
||||||
|
|
||||||
|
void update(bool opaque, const VkExtent2D& frame, VkSampleCountFlagBits samples, const vlkx::RenderPass& pass, uint32_t subpass, bool flipY = true);
|
||||||
|
void draw(const VkCommandBuffer& commands, int frame, uint32_t instances) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend std::unique_ptr<Model> ModelBuilder::build();
|
||||||
|
using Descriptors = ModelBuilder::Descriptors;
|
||||||
|
using ModelPushConstant = ModelBuilder::ModelPushConstant;
|
||||||
|
using TexturePerMesh = ModelBuilder::TexturePerMesh;
|
||||||
|
|
||||||
|
Model(float aspectRatio,
|
||||||
|
std::unique_ptr<vlkx::StaticPerVertexBuffer>&& vertexBuffer,
|
||||||
|
std::vector<vlkx::PerInstanceVertexBuffer*>&& perInstanceBuffers,
|
||||||
|
std::optional<ModelPushConstant>&& pushConstants,
|
||||||
|
TexturePerMesh&& sharedTextures,
|
||||||
|
std::vector<TexturePerMesh>&& textures,
|
||||||
|
std::vector<Descriptors>&& descriptors,
|
||||||
|
std::unique_ptr<vlkx::GraphicsPipelineBuilder>&& pipelineBuilder)
|
||||||
|
: aspectRatio(aspectRatio), vertexBuffer(std::move(vertexBuffer)), perInstanceBuffers(std::move(perInstanceBuffers)),
|
||||||
|
pushConstants(std::move(pushConstants)), sharedTextures(std::move(sharedTextures)), textures(std::move(textures)),
|
||||||
|
descriptors(std::move(descriptors)), pipelineBuilder(std::move(pipelineBuilder)) {}
|
||||||
|
|
||||||
|
const float aspectRatio;
|
||||||
|
const std::unique_ptr<vlkx::StaticPerVertexBuffer> vertexBuffer;
|
||||||
|
const std::vector<vlkx::PerInstanceVertexBuffer*> perInstanceBuffers;
|
||||||
|
const std::optional<ModelPushConstant> pushConstants;
|
||||||
|
const TexturePerMesh sharedTextures;
|
||||||
|
const std::vector<TexturePerMesh> textures;
|
||||||
|
const std::vector<Descriptors> descriptors;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::GraphicsPipelineBuilder> pipelineBuilder;
|
||||||
|
std::unique_ptr<vlkx::Pipeline> pipeline;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "vlkx/render/Geometry.h"
|
||||||
|
|
||||||
|
namespace vlkxtemp {
|
||||||
|
|
||||||
|
struct Wavefront {
|
||||||
|
Wavefront(std::string_view path, size_t base);
|
||||||
|
Wavefront(const Wavefront&) = delete;
|
||||||
|
|
||||||
|
Wavefront& operator=(const Wavefront&) = delete;
|
||||||
|
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
std::vector<Geo::VertexAll> vertices;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModelLoader {
|
||||||
|
public:
|
||||||
|
enum class TextureType {
|
||||||
|
Diffuse,
|
||||||
|
Specular,
|
||||||
|
Reflection,
|
||||||
|
Cubemap,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextureData {
|
||||||
|
TextureData(TextureData&&) noexcept = default;
|
||||||
|
TextureData& operator=(TextureData&&) noexcept = default;
|
||||||
|
|
||||||
|
std::string path;
|
||||||
|
TextureType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MeshData {
|
||||||
|
MeshData() = default;
|
||||||
|
MeshData(MeshData&&) noexcept = default;
|
||||||
|
MeshData& operator=(MeshData&&) noexcept = default;
|
||||||
|
|
||||||
|
std::vector<Geo::VertexAll> vertices;
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
std::vector<TextureData> textures;
|
||||||
|
};
|
||||||
|
|
||||||
|
ModelLoader(const std::string& model, const std::string& textures);
|
||||||
|
|
||||||
|
ModelLoader(const ModelLoader&) = delete;
|
||||||
|
ModelLoader& operator=(const ModelLoader&) = delete;
|
||||||
|
|
||||||
|
const std::vector<MeshData>& getMeshes() const { return meshes; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::vector<MeshData> meshes;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#define GLM_FORCE_RADIAN
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
class Camera {
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum class Input {
|
||||||
|
Up, Down, Left, Right
|
||||||
|
};
|
||||||
|
struct Movement {
|
||||||
|
float moveSpeed = 10;
|
||||||
|
float turnSpeed = 0.0005f;
|
||||||
|
std::optional<glm::vec3> center;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Camera configuration; with defaults.
|
||||||
|
* Left and forward vectors are calculated from up, pos and target.
|
||||||
|
*/
|
||||||
|
struct Config {
|
||||||
|
float nearPlane = 0.1f; // The nearest a vertex can be to the camera before being clipped
|
||||||
|
float farPlane = 100; // The furthest a vertex can be from the camera before clipped
|
||||||
|
glm::vec3 upV{0, 1, 0}; // The vector pointing straight up from the camera
|
||||||
|
glm::vec3 pos{0, 0, 0}; // The position of the camera in the world
|
||||||
|
glm::vec3 target{1, 0, 0}; // The point the camera is looking at
|
||||||
|
};
|
||||||
|
|
||||||
|
Camera(const Camera &) = delete;
|
||||||
|
|
||||||
|
Camera &operator=(const Camera &) = delete;
|
||||||
|
|
||||||
|
virtual ~Camera() = default;
|
||||||
|
|
||||||
|
Camera &move(const glm::vec3 &delta);
|
||||||
|
|
||||||
|
Camera &setPos(const glm::vec3 &pos);
|
||||||
|
|
||||||
|
Camera &up(const glm::vec3 &up);
|
||||||
|
|
||||||
|
Camera &forward(const glm::vec3 &forward);
|
||||||
|
|
||||||
|
glm::mat4 getViewMatrix() const;
|
||||||
|
|
||||||
|
glm::mat4 getSkyboxView() const {
|
||||||
|
return glm::mat4{glm::mat3{getViewMatrix()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual glm::mat4 getProjMatrix() const = 0;
|
||||||
|
|
||||||
|
const glm::vec3& getPosition() const { return position; }
|
||||||
|
const glm::vec3& getUp() const { return upVector; }
|
||||||
|
const glm::vec3& getForward() const { return frontVector; }
|
||||||
|
const glm::vec3& getRight() const { return rightVector; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
explicit Camera(const Config &conf) : nearPlane(conf.nearPlane), farPlane(conf.farPlane), position(conf.pos),
|
||||||
|
upVector(glm::normalize(conf.upV)) {
|
||||||
|
forward(conf.target - position);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float nearPlane;
|
||||||
|
const float farPlane;
|
||||||
|
private:
|
||||||
|
|
||||||
|
glm::vec3 position;
|
||||||
|
glm::vec3 upVector;
|
||||||
|
glm::vec3 frontVector;
|
||||||
|
glm::vec3 rightVector;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class PerspectiveCamera : public Camera {
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct Frustum {
|
||||||
|
float fov;
|
||||||
|
float aspect;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RT {
|
||||||
|
glm::vec3 up;
|
||||||
|
glm::vec3 forward;
|
||||||
|
glm::vec3 right;
|
||||||
|
};
|
||||||
|
|
||||||
|
PerspectiveCamera(const Camera::Config &conf, const Frustum &frus) :
|
||||||
|
Camera(conf), aspectRatio(frus.aspect), fov(frus.fov) {}
|
||||||
|
|
||||||
|
PerspectiveCamera(const PerspectiveCamera &) = delete;
|
||||||
|
|
||||||
|
PerspectiveCamera &operator=(const PerspectiveCamera &) = delete;
|
||||||
|
|
||||||
|
PerspectiveCamera &fieldOfView(float newFov);
|
||||||
|
|
||||||
|
RT getRT() const;
|
||||||
|
|
||||||
|
glm::mat4 getProjMatrix() const override;
|
||||||
|
|
||||||
|
float getFieldOfView() const { return fov; }
|
||||||
|
|
||||||
|
float getAspect() const { return aspectRatio; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const float aspectRatio;
|
||||||
|
float fov;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OrthographicCamera : public Camera {
|
||||||
|
public:
|
||||||
|
struct OrthoConfig {
|
||||||
|
float width;
|
||||||
|
float aspect;
|
||||||
|
};
|
||||||
|
|
||||||
|
static OrthoConfig getFullscreenConfig() {
|
||||||
|
return {2, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthographicCamera(const Camera::Config &config, const OrthoConfig &ortho)
|
||||||
|
: Camera(config), aspectRatio(ortho.aspect), width(ortho.width) {}
|
||||||
|
|
||||||
|
OrthographicCamera(const OrthographicCamera &) = delete;
|
||||||
|
|
||||||
|
OrthographicCamera &operator=(const OrthographicCamera &) = delete;
|
||||||
|
|
||||||
|
OrthographicCamera &setWidth(float width);
|
||||||
|
|
||||||
|
glm::mat4 getProjMatrix() const override;
|
||||||
|
|
||||||
|
float getWidth() const { return width; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const float aspectRatio;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
class UserCamera {
|
||||||
|
public:
|
||||||
|
UserCamera(const UserCamera &) = delete;
|
||||||
|
|
||||||
|
UserCamera &operator=(const UserCamera &) = delete;
|
||||||
|
|
||||||
|
virtual ~UserCamera() = default;
|
||||||
|
|
||||||
|
void setInternal(std::function<void(Type *)> op);
|
||||||
|
|
||||||
|
void setPos(const glm::dvec2 &pos) { cursorPos = pos; }
|
||||||
|
|
||||||
|
void move(double x, double y);
|
||||||
|
|
||||||
|
bool scroll(double delta, double min, double max);
|
||||||
|
|
||||||
|
void press(Camera::Input key, float time);
|
||||||
|
|
||||||
|
void active(bool active) { isActive = active; }
|
||||||
|
|
||||||
|
const Type &getCamera() const { return *camera; }
|
||||||
|
|
||||||
|
UserCamera(const Camera::Movement &movement, std::unique_ptr<Type> &&cam)
|
||||||
|
: config(movement), camera(std::move(cam)) {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
|
||||||
|
const Camera::Movement config;
|
||||||
|
bool isActive = false;
|
||||||
|
|
||||||
|
std::unique_ptr<Type> camera;
|
||||||
|
glm::dvec2 cursorPos;
|
||||||
|
glm::vec3 refForward;
|
||||||
|
glm::vec3 refLeft;
|
||||||
|
|
||||||
|
float pitch;
|
||||||
|
float yaw;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UserPerspectiveCamera : public UserCamera<PerspectiveCamera> {
|
||||||
|
public:
|
||||||
|
static std::unique_ptr<UserPerspectiveCamera>
|
||||||
|
create(const Camera::Movement &movement, const Camera::Config &config,
|
||||||
|
const PerspectiveCamera::Frustum &frustum) {
|
||||||
|
return std::make_unique<UserPerspectiveCamera>(movement,
|
||||||
|
std::make_unique<PerspectiveCamera>(config, frustum));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using UserCamera<PerspectiveCamera>::UserCamera;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UserOrthoCamera : public UserCamera<OrthographicCamera> {
|
||||||
|
public:
|
||||||
|
static std::unique_ptr<UserOrthoCamera> create(const Camera::Movement &movement, const Camera::Config &config,
|
||||||
|
const OrthographicCamera::OrthoConfig &ortho) {
|
||||||
|
return std::make_unique<UserOrthoCamera>(movement, std::make_unique<OrthographicCamera>(config, ortho));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using UserCamera<OrthographicCamera>::UserCamera;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define GLM_FORCE_RADIAN
|
||||||
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
// Global namespace for all classes to do with geometry in a level.
|
||||||
|
namespace Geo {
|
||||||
|
|
||||||
|
// The core components of a given mesh.
|
||||||
|
enum MeshType {
|
||||||
|
Triangle, // A construction of triangles
|
||||||
|
Quad, // A construction of quads
|
||||||
|
Cube, // A single Cube.
|
||||||
|
Sphere // A single Sphere.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contains standard uniforms for shader files.
|
||||||
|
struct UniformBufferObject {
|
||||||
|
glm::mat4 model; // Model transform matrix.
|
||||||
|
glm::mat4 view; // View matrix.
|
||||||
|
glm::mat4 proj; // Projection matrix.
|
||||||
|
};
|
||||||
|
|
||||||
|
// All of the metadata involved with a vertex.
|
||||||
|
struct VertexAll {
|
||||||
|
glm::vec3 position; // XYZ coordinates of the vertex's position.
|
||||||
|
glm::vec3 normal; // Unit vector pointing away from the outer surface of the vertex.
|
||||||
|
glm::vec2 texture; // The u/v coordinates of this vertex in the bound texture.
|
||||||
|
|
||||||
|
// How fast should vertex data be read from RAM?
|
||||||
|
static VkVertexInputBindingDescription getBindingDesc() {
|
||||||
|
VkVertexInputBindingDescription desc = {};
|
||||||
|
desc.binding = 0;
|
||||||
|
desc.stride = sizeof(VertexAll);
|
||||||
|
desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How should vertexes be handled?
|
||||||
|
static std::vector<VkVertexInputAttributeDescription> getAttributeDesc() {
|
||||||
|
return {
|
||||||
|
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, position)) },
|
||||||
|
{ 0, 1, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, normal)) },
|
||||||
|
{ 0, 2, VK_FORMAT_R32G32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, texture)) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// All of the metadata involved with a vertex.
|
||||||
|
struct VertexColor {
|
||||||
|
glm::vec3 position; // XYZ coordinates of the vertex's position.
|
||||||
|
glm::vec3 color; // The color of the vertex.
|
||||||
|
|
||||||
|
// How fast should vertex data be read from RAM?
|
||||||
|
static VkVertexInputBindingDescription getBindingDesc() {
|
||||||
|
VkVertexInputBindingDescription desc = {};
|
||||||
|
desc.binding = 0;
|
||||||
|
desc.stride = sizeof(VertexColor);
|
||||||
|
desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How should vertexes be handled?
|
||||||
|
static std::vector<VkVertexInputAttributeDescription> getAttributeDesc() {
|
||||||
|
return {
|
||||||
|
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexColor, position)) },
|
||||||
|
{ 0, 1, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexColor, color)) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contains data about a given Mesh.
|
||||||
|
class Mesh {
|
||||||
|
public:
|
||||||
|
// Pre-load the data for a triangle into the given buffers.
|
||||||
|
static void setTriData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
|
||||||
|
// Pre-load the data for a quad into the given buffers.
|
||||||
|
static void setQuadData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
|
||||||
|
// Pre-load the data for a cube into the given buffers.
|
||||||
|
static void setCubeData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
|
||||||
|
// Pre-load the data for a sphere into the given buffers.
|
||||||
|
static void setSphereData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,242 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include "vlkx/vulkan/abstraction/ImageUsage.h"
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include "GenericRenderPass.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The common base class for rendering and computing passes that run on the GPU.
|
||||||
|
* Provides some utility methods for handling attachment metadata between subpasses.
|
||||||
|
*/
|
||||||
|
class CommonPass {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit CommonPass(int passes) : numPasses(passes) {}
|
||||||
|
|
||||||
|
// Delete the copy and move constructors
|
||||||
|
CommonPass(const CommonPass&) = delete;
|
||||||
|
CommonPass& operator=(const CommonPass&) = delete;
|
||||||
|
virtual ~CommonPass() = default;
|
||||||
|
|
||||||
|
// Get the image layout of the given image at the start of this pass
|
||||||
|
VkImageLayout getInitialLayout(const std::string& name) const;
|
||||||
|
// Get the image layout of the given image at the end of this pass
|
||||||
|
VkImageLayout getFinalLayout(const std::string& name) const;
|
||||||
|
// Get the image layout of the given image before the given subpass starts
|
||||||
|
VkImageLayout getSubpassLayout(const std::string& name, int subpass) const;
|
||||||
|
|
||||||
|
// Update the state of the given image's usage tracker.
|
||||||
|
void update(const std::string& name, MultiImageTracker& tracker) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Some metadata about the usage of an image between subpasses.
|
||||||
|
*/
|
||||||
|
struct Usages {
|
||||||
|
Usages(const int last, const ImageUsage* prev, const ImageUsage* curr) : lastSubpass(last), lastUsage(*prev), currentUsage(*curr) {}
|
||||||
|
const int lastSubpass;
|
||||||
|
const ImageUsage& lastUsage;
|
||||||
|
const ImageUsage& currentUsage;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the usage of an image in the pass to its' tracker.
|
||||||
|
void addUsage(std::string&& name, UsageTracker&& tracker);
|
||||||
|
|
||||||
|
// Get the full history of the image's usages up to this rendering pass.
|
||||||
|
const UsageTracker& getHistory(const std::string& name) const;
|
||||||
|
|
||||||
|
// Get the usage of an image at the start of the given pass.
|
||||||
|
const ImageUsage* getUsage(const std::string& name, int pass) const;
|
||||||
|
|
||||||
|
// Retrieve image usage data, but only if the image is barriered at the given pass.
|
||||||
|
std::optional<Usages> checkForSync(const std::string& name, int pass) const;
|
||||||
|
|
||||||
|
// Validate that the subpass is valid for the given image.
|
||||||
|
// The meaning of includeVirtual is defined by the child implementation.
|
||||||
|
void validate(int pass, const std::string& image, bool includeVirtual) const;
|
||||||
|
|
||||||
|
int getVirtualInitial() const { return -1; }
|
||||||
|
int getVirtualFinal() const { return numPasses; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::map<std::string, UsageTracker> usageHistory;
|
||||||
|
const int numPasses;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Common Pass implementation for Graphics passes; that is, render passes that output to color buffers
|
||||||
|
* for presentation to the screen, or to be used as textures in such.
|
||||||
|
* The instance of the GraphicsPass can be stored and reused to create multiple RenderPassBuilders.
|
||||||
|
* In this way it is essentially a RenderPassBuilderFactory.
|
||||||
|
*/
|
||||||
|
class GraphicsPass : public CommonPass {
|
||||||
|
public:
|
||||||
|
|
||||||
|
using LocationGetter = std::function<int(int pass)>;
|
||||||
|
|
||||||
|
explicit GraphicsPass(int passes) : CommonPass {passes} {}
|
||||||
|
|
||||||
|
GraphicsPass(const GraphicsPass&) = delete;
|
||||||
|
GraphicsPass& operator=(const GraphicsPass&) = delete;
|
||||||
|
|
||||||
|
// Get the default render ops for a color buffer.
|
||||||
|
static RenderPassBuilder::Attachment::OpsType getDefaultOps() {
|
||||||
|
return RenderPassBuilder::Attachment::ColorOps { VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the default render ops for a stencil buffer.
|
||||||
|
static RenderPassBuilder::Attachment::OpsType getStencilOps() {
|
||||||
|
return RenderPassBuilder::Attachment::StencilDepthOps { VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an image reference that is used in this render pass.
|
||||||
|
* @param name the name of the image used
|
||||||
|
* @param history the usage history of the image, for tracking purposes
|
||||||
|
* @param getter a function to get the location of the image, only if the image is used as a render target.
|
||||||
|
* @param ops optional; uses the static defaults if not present.
|
||||||
|
* @return the index into the VkAttachmentDescriptions.
|
||||||
|
*/
|
||||||
|
int add(const std::string& name, UsageTracker&& history, LocationGetter&& getter, const std::optional<RenderPassBuilder::Attachment::OpsType> ops = std::nullopt);
|
||||||
|
|
||||||
|
#define fluent GraphicsPass&
|
||||||
|
|
||||||
|
// Specifies that the source image will be resolved to the single destination at the given pass.
|
||||||
|
fluent addMultisample(const std::string& source, const std::string& dest, int pass);
|
||||||
|
|
||||||
|
// Build a RenderPassBuilder with the information provided so far.
|
||||||
|
std::unique_ptr<RenderPassBuilder> build(int framebuffers);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct AttachmentMeta {
|
||||||
|
int index;
|
||||||
|
LocationGetter getter;
|
||||||
|
vlkx::RenderPassBuilder::Attachment::OpsType ops;
|
||||||
|
std::map<int, std::string> multisample;
|
||||||
|
};
|
||||||
|
|
||||||
|
void setAttachments();
|
||||||
|
void setSubpasses();
|
||||||
|
void setDependencies();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first subpass where the given image is used as a render target.
|
||||||
|
* @param history the usage history of the image; what it was used at at each subpass.
|
||||||
|
* @return nullopt if the image was not used as a render target, the index of the subpass where it was, if not.
|
||||||
|
*/
|
||||||
|
std::optional<int> getFirstRenderTarget(const UsageTracker& history) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the operations that should be used for the given image attachment.
|
||||||
|
* If the user specified ops, it will be checekd against the history.
|
||||||
|
* @param name the name of the image to use as the attachment
|
||||||
|
* @param history the usage history of the attachment, for internal checks
|
||||||
|
* @param userOps operations to use for the image, as an optional override.
|
||||||
|
* @return the ColorOps to use for the given attachment.
|
||||||
|
*/
|
||||||
|
RenderPassBuilder::Attachment::OpsType getOps(const std::string& name, const UsageTracker& history, const std::optional<RenderPassBuilder::Attachment::OpsType>& userOps) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the usage type of the image.
|
||||||
|
* Assumption: an image is only ever used as a color OR depth stencil. Never both.
|
||||||
|
* Assumption: Multisample == RenderTarget
|
||||||
|
* @param name the name of the image to check
|
||||||
|
* @param history the history of the image's usages in the GPU.
|
||||||
|
* @return whether the image is a RenderTarget or a DepthStencil buffer.
|
||||||
|
*/
|
||||||
|
ImageUsage::Type getUsageType(const std::string& name, const UsageTracker& history) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the image is used as type at subpass in its' history.
|
||||||
|
*/
|
||||||
|
bool verifyImageUsage(const UsageTracker& history, int subpass, ImageUsage::Type type) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the subpass is virtual.
|
||||||
|
* For a Render Pass, virtual means it is a preprocessing step.
|
||||||
|
*/
|
||||||
|
bool isVirtual(int subpass) const {
|
||||||
|
return subpass == getVirtualInitial() || subpass == getVirtualFinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the subpass index; for virtual passes, it uses an EXTERNAL subpass.
|
||||||
|
*/
|
||||||
|
uint32_t checkSubpass(int subpass) const {
|
||||||
|
return isVirtual(subpass) ? VK_SUBPASS_EXTERNAL : (uint32_t) subpass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the image's usages are compatible with a render pass.
|
||||||
|
* For example, compute shader linear buffers cannot be used as render targets, etc.
|
||||||
|
*/
|
||||||
|
void verifyHistory(const std::string& image, const UsageTracker& history) const;
|
||||||
|
|
||||||
|
std::map<std::string, AttachmentMeta> metas;
|
||||||
|
std::unique_ptr<vlkx::RenderPassBuilder> builder;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Common Pass implementation for Compute Shaders.
|
||||||
|
* That is, shaders that do not write to color buffers.
|
||||||
|
* A subpass can execute multiple compute shaders unbarriered, which increases efficiency.
|
||||||
|
* We still need to transition images between passes when necessary, hence the wrapper.
|
||||||
|
*/
|
||||||
|
class ComputePass : public CommonPass {
|
||||||
|
public:
|
||||||
|
|
||||||
|
ComputePass(const ComputePass&) = delete;
|
||||||
|
ComputePass& operator=(const ComputePass&) = delete;
|
||||||
|
|
||||||
|
#define fluent ComputePass&
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the given image as an attachment to the compute shader pass.
|
||||||
|
* @param name the name of the image
|
||||||
|
* @param history the usage history of the image
|
||||||
|
* @return the ComputePass instance, for chaining.
|
||||||
|
*/
|
||||||
|
fluent add(std::string&& name, UsageTracker&& history);
|
||||||
|
fluent add(const std::string& name, UsageTracker&& history) {
|
||||||
|
return add(std::string(name), std::move(history));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run computeOps, insert memory barriers to transition used images into the appropriate format.
|
||||||
|
* Images must be a superset of all images that were called with add().
|
||||||
|
* Compute_ops must be equal to the number of subpasses.
|
||||||
|
* Commands must be recording.
|
||||||
|
* @param commands the command buffer to write into.
|
||||||
|
* @param queueFamily the family to use for inserting barriers
|
||||||
|
* @param images the list of images that were used in the compute pass
|
||||||
|
* @param computeOps the compute functions to upload to the GPU
|
||||||
|
*/
|
||||||
|
void execute(const VkCommandBuffer& commands, uint32_t queueFamily, const std::map<std::string, const VkImage*>& images, const std::vector<std::function<void()>>& computeOps) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a memory barrier, to transition the layout of the image from the previous to the curent.
|
||||||
|
* The barrier is performed using the given queue family.
|
||||||
|
* @param commands the command buffer to write into.
|
||||||
|
* @param queueFamily the family to use for inserting barriers.
|
||||||
|
* @param image the list of images that were used in the compute pass
|
||||||
|
* @param prev the previous usage of the image; the state being transitioned from
|
||||||
|
* @param current the new usage of the image; the state being transitioned to.
|
||||||
|
*/
|
||||||
|
void barrier(const VkCommandBuffer& commands, uint32_t queueFamily, const VkImage& image, const ImageUsage& prev, const ImageUsage& current) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify whether the previous usages of the given image in its' history is compatible with a compute shader.
|
||||||
|
* For example, a fragment shader output image is not compatible.
|
||||||
|
* @param name the name of the image being checked
|
||||||
|
* @param history the usage history of the image/
|
||||||
|
*/
|
||||||
|
void verify(const std::string& name, const UsageTracker& history) const;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <variant>
|
||||||
|
#include <optional>
|
||||||
|
#include "vlkx/vulkan/Tools.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gathers the operations that the GPU should perform when rendering to a framebuffer.
|
||||||
|
* Subpasses and dependencies are to be configured automatically using the builder below.
|
||||||
|
* RenderPass objects are disposable, and should be discarded when the framebuffer changes.
|
||||||
|
*/
|
||||||
|
class RenderPass {
|
||||||
|
public:
|
||||||
|
using RenderFunc = std::function<void(const VkCommandBuffer& buffer)>;
|
||||||
|
|
||||||
|
// Delete copy and move constructors to prevent the GPU getting confused with what we're trying to do
|
||||||
|
RenderPass(const RenderPass&) = delete;
|
||||||
|
RenderPass& operator=(const RenderPass&) = delete;
|
||||||
|
~RenderPass();
|
||||||
|
|
||||||
|
RenderPass(int subpasses, VkRenderPass pass, std::vector<VkClearValue> clear, VkExtent2D ext, std::vector<VkFramebuffer> fbs, std::vector<int> attachs)
|
||||||
|
: subpassCount(subpasses), renderPass(pass), clearValues(std::move(clear)), extent(ext), framebuffers(std::move(fbs)), attachments(std::move(attachs)) {}
|
||||||
|
|
||||||
|
const VkRenderPass& operator*() const { return renderPass; }
|
||||||
|
|
||||||
|
int getAttachsInSubpass(int subpass) const {
|
||||||
|
return attachments[subpass];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload all of the subpass render commands to the command buffer.
|
||||||
|
* The size of ops must be equal to the number of subpasses in this render pass.
|
||||||
|
* @param commands the command buffer to execute on; must be recording
|
||||||
|
* @param imageIndex the index of the image on the swapchain that we're rendering to; the target framebuffer.
|
||||||
|
* @param ops the render operations to add onto the command buffer.
|
||||||
|
*/
|
||||||
|
void execute(const VkCommandBuffer& commands, int imageIndex, std::vector<RenderFunc> ops) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The number of sub-render-passes in this pass.
|
||||||
|
const int subpassCount;
|
||||||
|
// The VkRenderPass that this class wraps.
|
||||||
|
VkRenderPass renderPass;
|
||||||
|
// The clear values that will wipe all framebuffers to their empty states.
|
||||||
|
const std::vector<VkClearValue> clearValues;
|
||||||
|
// The size of the framebuffers (all are the same size)
|
||||||
|
const VkExtent2D extent;
|
||||||
|
// The framebuffers that we can render to
|
||||||
|
const std::vector<VkFramebuffer> framebuffers;
|
||||||
|
// The number of color attachments (sampled color images) in each subpass, by subpass index.
|
||||||
|
const std::vector<int> attachments;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stateful, fluent way to create Render Passes.
|
||||||
|
* This object can be stored and reused; when the window size changes, simply set the extent and
|
||||||
|
* export a new RenderPass to be used in the pipeline.
|
||||||
|
*
|
||||||
|
* Allows setting sub-passes, sub-pass dependencies, operations to read and write them, etc.
|
||||||
|
*/
|
||||||
|
class RenderPassBuilder {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information required to define an attachment to be used in a render pass.
|
||||||
|
* Contains information on the layout, the operations to use on read and write, etc.
|
||||||
|
*/
|
||||||
|
struct Attachment {
|
||||||
|
// Operations to use on color attachments.
|
||||||
|
struct ColorOps {
|
||||||
|
VkAttachmentLoadOp LOAD; // Load data in the color attachment
|
||||||
|
VkAttachmentStoreOp STORE; // Store data into the color attachment
|
||||||
|
};
|
||||||
|
|
||||||
|
// Operations to use on depth and stencil buffers.
|
||||||
|
struct StencilDepthOps {
|
||||||
|
VkAttachmentLoadOp DEPTH_LOAD; // Load data in the depth attachment
|
||||||
|
VkAttachmentStoreOp DEPTH_STORE; // Store data in the depth attachment
|
||||||
|
VkAttachmentLoadOp STENCIL_LOAD; // Load data in the stencil attachment
|
||||||
|
VkAttachmentStoreOp STENCIL_STORE; // Store data in the stencil attachment
|
||||||
|
};
|
||||||
|
|
||||||
|
using OpsType = std::variant<ColorOps, StencilDepthOps>;
|
||||||
|
|
||||||
|
// The operations that can be performed on this attachment
|
||||||
|
OpsType ops;
|
||||||
|
// The initial layout of an image in this attachment (pre-GPU upload)
|
||||||
|
VkImageLayout layoutInitial;
|
||||||
|
// The final layout of an image in this attachment (as seen by the shader)
|
||||||
|
VkImageLayout layoutFinal;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes attachments used in each subpass, in terms of Vulkan data.
|
||||||
|
* Attachment here is used in the conceptual sense, not referring to the Attachment struct above.
|
||||||
|
*
|
||||||
|
* If multisampleReferences is non-zero, its' size must be equal to colorReferences' size.
|
||||||
|
* Each index of multisampleReferences refers to the same-index colorReferences entry.
|
||||||
|
*
|
||||||
|
* If stencilDepthReference is non-zero, it is shared between all subpasses.
|
||||||
|
*/
|
||||||
|
struct SubpassAttachments {
|
||||||
|
std::vector<VkAttachmentReference> colorReferences;
|
||||||
|
std::vector<VkAttachmentReference> multisampleReferences;
|
||||||
|
std::optional<VkAttachmentReference> stencilDepthReference;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the dependencies between each sub-pass.
|
||||||
|
* It describes how each subpass will read or modify the data written by the last subpass, if at all.
|
||||||
|
* This dependency information can allow the GPU to run some passes in parallel, and enforce the
|
||||||
|
* strict ordering of those that require it.
|
||||||
|
*/
|
||||||
|
struct SubpassDependency {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines some metadata about the subpass.
|
||||||
|
* Contains the index of the subpass, how it will use data from the last pass, and what exactly it will do.
|
||||||
|
*/
|
||||||
|
struct SubpassMeta {
|
||||||
|
/**
|
||||||
|
* Index of the subpass.
|
||||||
|
*/
|
||||||
|
uint32_t index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how we want to modify the data passed to us from the last subpass.
|
||||||
|
* Will change how the next subpass will wait for the completion of this subpass, if at all.
|
||||||
|
*
|
||||||
|
* VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT:
|
||||||
|
* read/write to the color attachment
|
||||||
|
*
|
||||||
|
* VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT:
|
||||||
|
* VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT:
|
||||||
|
* read/write to the depth or stencil attachment
|
||||||
|
*
|
||||||
|
* VK_PIPELINE_STAGE_VERTEX_SHADER_BIT:
|
||||||
|
* VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT:
|
||||||
|
* read all attachments
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
VkPipelineStageFlags stage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how we want to synchronize with the subpass after this one.
|
||||||
|
*
|
||||||
|
* VK_ACCESS_SHADER_READ_BIT:
|
||||||
|
* VK_ACCESS_SHADER_WRITE_BIT:
|
||||||
|
* read a texture or write to a color buffer
|
||||||
|
*
|
||||||
|
* VK_ACCESS_COLOR_ATTACHMENT_READ_BIT:
|
||||||
|
* VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT:
|
||||||
|
* VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT:
|
||||||
|
* VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT:
|
||||||
|
* read/write to an attachment.
|
||||||
|
*
|
||||||
|
* VK_ACCESS_INPUT_ATTACHMENT_READ_BIT:
|
||||||
|
* for accessing the inputAttachment of a subpass.
|
||||||
|
*
|
||||||
|
* 0:
|
||||||
|
* do not write, but the next subpass will.
|
||||||
|
* Will automatically barrier the render pass.
|
||||||
|
*/
|
||||||
|
VkAccessFlags access;
|
||||||
|
};
|
||||||
|
|
||||||
|
SubpassMeta source; // The source subpass of this dependency (will take effect after this pass completes)
|
||||||
|
SubpassMeta destination; // The destination subpass of this dependency (will take effect before this pass)
|
||||||
|
VkDependencyFlags flags; // Other information that Vulkan needs to know about this dependency; for example, if we use an inputAttachment.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a color attachment.
|
||||||
|
* Effectively RGB images that live entirely on the GPU.
|
||||||
|
*
|
||||||
|
* Describes the resolution mechanics of a multisampled image.
|
||||||
|
*/
|
||||||
|
struct ColorAttachmentMeta {
|
||||||
|
int location; // Where the GPU shaders expect this attachment to be available.
|
||||||
|
int descriptionIdx; // Index of this attachment in the VkAttachmentDescription data.
|
||||||
|
VkImageLayout layout; // Vulkan image layout. Shader optimized or host readable.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a list of VkAttachmentReference that describes the attachments used per subpass.
|
||||||
|
*/
|
||||||
|
static std::vector<VkAttachmentReference> parseColorReferences(std::vector<ColorAttachmentMeta> meta);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a list of VkAttachmentReference that describes the multisampling setup.
|
||||||
|
*/
|
||||||
|
static std::vector<VkAttachmentReference> parseMutisampling(int colorReferencesCount, std::vector<ColorAttachmentMeta> meta);
|
||||||
|
|
||||||
|
RenderPassBuilder(const RenderPassBuilder&) = delete;
|
||||||
|
RenderPassBuilder& operator=(const RenderPassBuilder&) = delete;
|
||||||
|
~RenderPassBuilder() = default;
|
||||||
|
RenderPassBuilder() = default;
|
||||||
|
|
||||||
|
/** Fluent API Features; chain calls to set data on the render pass.*/
|
||||||
|
#define fluent RenderPassBuilder&
|
||||||
|
|
||||||
|
// Set the number of framebuffers in the render pass
|
||||||
|
fluent setFramebufferCount(int count);
|
||||||
|
// Set an attachment description in the render pass
|
||||||
|
fluent setAttachment(int idx, const Attachment& attachment);
|
||||||
|
// Update the image backing an attachment. The function must be executable during execute() later on.
|
||||||
|
fluent updateAttachmentBacking(int idx, std::function<const Image&(int idx)>&& getBacking);
|
||||||
|
// Set a specific subpass. Use the static parse methods to create these vectors.
|
||||||
|
fluent setSubpass(int idx, std::vector<VkAttachmentReference>&& color, std::vector<VkAttachmentReference>&& multisample, VkAttachmentReference& depthStencil);
|
||||||
|
// Add a dependency between two subpasses.
|
||||||
|
fluent addDependency(const SubpassDependency& dep);
|
||||||
|
|
||||||
|
// Build the Render Pass with all the information given.
|
||||||
|
// Can be called multiple times with the same Builder.
|
||||||
|
[[nodiscard]] std::unique_ptr<vlkx::RenderPass> build() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Number of framebuffers in the render pass
|
||||||
|
std::optional<int> framebufferCount;
|
||||||
|
// Descriptions of used attachments
|
||||||
|
std::vector<VkAttachmentDescription> attachmentDescriptors;
|
||||||
|
// Functions to return attachment images.
|
||||||
|
std::vector<std::function<const Image&(int idx)>> attachmentGetters;
|
||||||
|
// Values to clear all attachments
|
||||||
|
std::vector<VkClearValue> clearValues;
|
||||||
|
// Descriptions of subpasses.
|
||||||
|
std::vector<SubpassAttachments> subpassAttachments;
|
||||||
|
// Descriptions of subpass dependencies.
|
||||||
|
std::vector<VkSubpassDependency> subpassDependencies;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <memory>
|
||||||
|
#include "GenericRenderPass.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/ImageUsage.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
// A simple and versatile way to configure render passes.
|
||||||
|
// Intended to be used with the SimpleRenderPass and the ScreenRenderPass.
|
||||||
|
class RendererConfig {
|
||||||
|
public:
|
||||||
|
RendererConfig(std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen = true) : renderImages(destinations) {
|
||||||
|
numOpaquePasses = 1;
|
||||||
|
rendersToScreen = toScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
RendererConfig(int passCount, std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen = true, std::optional<int> firstTransparent = std::nullopt, std::optional<int> firstOverlay = std::nullopt);
|
||||||
|
|
||||||
|
// Get the number of passes that use the depth buffer.
|
||||||
|
int depthPasses() const {
|
||||||
|
return numOpaquePasses + numTransparentPasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the total number of passes.
|
||||||
|
int passes() const {
|
||||||
|
return depthPasses() + numOverlayPasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get whether any passes use the depth buffer.
|
||||||
|
bool usesDepth() const {
|
||||||
|
return depthPasses() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the render pass builder. Can be called multiple times.
|
||||||
|
void build();
|
||||||
|
|
||||||
|
RendererConfig(RendererConfig&) noexcept = default;
|
||||||
|
RendererConfig(const RendererConfig&) = default;
|
||||||
|
|
||||||
|
int numOpaquePasses = 0;
|
||||||
|
int numTransparentPasses = 0;
|
||||||
|
std::vector<std::unique_ptr<vlkx::Image>>& renderImages;
|
||||||
|
bool rendersToScreen;
|
||||||
|
private:
|
||||||
|
int numOverlayPasses = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all of the information required to use an attachment.
|
||||||
|
* This is heavy, so is only used when the image is being finalized.
|
||||||
|
*/
|
||||||
|
struct AttachmentConfig {
|
||||||
|
AttachmentConfig(std::string_view name, std::optional<int>* index)
|
||||||
|
: name(name), index(*index) {}
|
||||||
|
|
||||||
|
AttachmentConfig& setOps(const RenderPassBuilder::Attachment::OpsType& ops) {
|
||||||
|
loadStoreOps = ops;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentConfig& setUsage(const ImageUsage& final) {
|
||||||
|
finalUsage = final;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
std::optional<int>& index;
|
||||||
|
std::optional<RenderPassBuilder::Attachment::OpsType> loadStoreOps;
|
||||||
|
std::optional<ImageUsage> finalUsage;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lighter version of the Attachment, used primarily in the On-Screen Pass Manager.
|
||||||
|
*/
|
||||||
|
struct Attachment {
|
||||||
|
explicit Attachment(std::string_view image) : name(image) {}
|
||||||
|
|
||||||
|
// Adds the image to the tracker and initializes state.
|
||||||
|
void add(MultiImageTracker& tracker, const Image& image) {
|
||||||
|
tracker.track(name, image.getUsage());
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentConfig makeConfig() { return { name, &index }; }
|
||||||
|
|
||||||
|
const std::string name;
|
||||||
|
std::optional<int> index;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Manages Render Passes that will output to the screen.
|
||||||
|
// This is necessarily exclusively a graphical pass.
|
||||||
|
// If necessary, a depth and stencil buffer will be maintained.
|
||||||
|
// The color buffer is automatically assumed to be the swapchain.
|
||||||
|
class ScreenRenderPassManager {
|
||||||
|
public:
|
||||||
|
explicit ScreenRenderPassManager(RendererConfig renderConfig) : config(renderConfig) {}
|
||||||
|
|
||||||
|
ScreenRenderPassManager(const ScreenRenderPassManager&) = delete;
|
||||||
|
ScreenRenderPassManager& operator=(const ScreenRenderPassManager&) = delete;
|
||||||
|
|
||||||
|
// Initialize the render pass we're managing.
|
||||||
|
void initializeRenderPass();
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::RenderPass>& getPass() { return pass; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Prepare the Render Pass builder.
|
||||||
|
void preparePassBuilder();
|
||||||
|
|
||||||
|
const RendererConfig config;
|
||||||
|
|
||||||
|
Attachment destinationInfo { "Destination" };
|
||||||
|
Attachment multisampleInfo { "Multisample" };
|
||||||
|
Attachment depthStencilInfo { "Depth-Stencil" };
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::Image> depthStencilImage;
|
||||||
|
std::unique_ptr<vlkx::RenderPassBuilder> passBuilder;
|
||||||
|
std::unique_ptr<vlkx::RenderPass> pass;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility namespace used to house a constructor for creating a "simple" render pass with all the defaults.
|
||||||
|
*/
|
||||||
|
namespace SimpleRenderPass {
|
||||||
|
static std::unique_ptr<RenderPassBuilder> createBuilder(int framebuffers, const RendererConfig& config, const AttachmentConfig& color, const AttachmentConfig* multisample, const AttachmentConfig* depthStencil, MultiImageTracker& tracker);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <fstream>
|
||||||
|
#include "shadow/util/RefCounter.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
class Pipeline;
|
||||||
|
|
||||||
|
// A simple wrapper for the shader module used by the pipeline.
|
||||||
|
class ShaderModule {
|
||||||
|
public:
|
||||||
|
using CountedShader = shadowutil::RefCounter<ShaderModule>;
|
||||||
|
using ReleasePool = CountedShader::AutoRelease;
|
||||||
|
|
||||||
|
ShaderModule(const std::string& path);
|
||||||
|
|
||||||
|
ShaderModule(const ShaderModule&) = delete;
|
||||||
|
ShaderModule& operator=(const ShaderModule&) = delete;
|
||||||
|
|
||||||
|
~ShaderModule() {
|
||||||
|
vkDestroyShaderModule(VulkanModule::getInstance()->getDevice()->logical, shader, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkShaderModule& operator*() const { return shader; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkShaderModule shader;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PipelineBuilder {
|
||||||
|
public:
|
||||||
|
PipelineBuilder(const PipelineBuilder&) = delete;
|
||||||
|
PipelineBuilder& operator=(const PipelineBuilder&) = delete;
|
||||||
|
|
||||||
|
virtual ~PipelineBuilder() {
|
||||||
|
vkDestroyPipelineCache(VulkanModule::getInstance()->getDevice()->logical, cache, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::unique_ptr<Pipeline> build() const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PipelineBuilder(std::optional<int> maxCache);
|
||||||
|
|
||||||
|
void setName(std::string&& n) { name = std::move(name); }
|
||||||
|
|
||||||
|
void setLayout(std::vector<VkDescriptorSetLayout>&& descLayouts, std::vector<VkPushConstantRange>&& constants);
|
||||||
|
|
||||||
|
const std::string& getName() const { return name; }
|
||||||
|
|
||||||
|
bool hasLayout() const { return layoutInfo.has_value(); }
|
||||||
|
const VkPipelineLayoutCreateInfo& getLayout() const { return layoutInfo.value(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkPipelineCache cache;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
std::optional<VkPipelineLayoutCreateInfo> layoutInfo;
|
||||||
|
std::vector<VkDescriptorSetLayout> descLayouts;
|
||||||
|
std::vector<VkPushConstantRange> constants;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use when creating Graphics pipelines.
|
||||||
|
* Internal state is preserved so that multiple pipelines can be created with one builder.
|
||||||
|
* However, shaders are single-usage. Bind a new shader before claling build again.
|
||||||
|
* See ShaderModule for more information, and how to change this.
|
||||||
|
*/
|
||||||
|
class GraphicsPipelineBuilder : public PipelineBuilder {
|
||||||
|
public:
|
||||||
|
struct Viewport {
|
||||||
|
VkViewport viewport;
|
||||||
|
VkRect2D scissor;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit GraphicsPipelineBuilder(std::optional<int> maxCache = std::nullopt);
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder(const GraphicsPipelineBuilder&) = delete;
|
||||||
|
GraphicsPipelineBuilder& operator=(const GraphicsPipelineBuilder&) = delete;
|
||||||
|
|
||||||
|
#define fluent GraphicsPipelineBuilder&
|
||||||
|
|
||||||
|
fluent name(std::string&& name);
|
||||||
|
fluent depthTest(bool enable, bool write);
|
||||||
|
fluent stencilTest(bool enable);
|
||||||
|
fluent multiSample(VkSampleCountFlagBits samples);
|
||||||
|
fluent topology(VkPrimitiveTopology topology);
|
||||||
|
fluent stencilOp(const VkStencilOpState& state, VkStencilFaceFlags flags);
|
||||||
|
|
||||||
|
fluent addVertex(uint32_t bindPoint, VkVertexInputBindingDescription&& desc, std::vector<VkVertexInputAttributeDescription>&& attrs);
|
||||||
|
fluent layout(std::vector<VkDescriptorSetLayout>&& descLayouts, std::vector<VkPushConstantRange>&& constants);
|
||||||
|
fluent viewport(const Viewport& port, bool flipY = true);
|
||||||
|
fluent renderPass(const VkRenderPass& pass, uint32_t subpass);
|
||||||
|
|
||||||
|
fluent colorBlend(std::vector<VkPipelineColorBlendAttachmentState>&& states);
|
||||||
|
fluent shader(VkShaderStageFlagBits stage, std::string&& file);
|
||||||
|
|
||||||
|
std::unique_ptr<Pipeline> build() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PassInfo {
|
||||||
|
VkRenderPass pass;
|
||||||
|
uint32_t subpass;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipelineInputAssemblyStateCreateInfo assemblyInfo;
|
||||||
|
VkPipelineRasterizationStateCreateInfo rasterizationInfo;
|
||||||
|
VkPipelineMultisampleStateCreateInfo multisampleInfo;
|
||||||
|
VkPipelineDepthStencilStateCreateInfo depthStencilInfo;
|
||||||
|
VkPipelineDynamicStateCreateInfo dynamicStateInfo;
|
||||||
|
|
||||||
|
std::vector<VkVertexInputBindingDescription> bindingDescs;
|
||||||
|
std::vector<VkVertexInputAttributeDescription> attrDescs;
|
||||||
|
|
||||||
|
std::optional<Viewport> viewportMeta;
|
||||||
|
std::optional<PassInfo> passMeta;
|
||||||
|
std::vector<VkPipelineColorBlendAttachmentState> blendStates;
|
||||||
|
std::map<VkShaderStageFlagBits, std::string> shaders;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use when creating Compute Shader pipelines.
|
||||||
|
* Internal state is preserved so that multiple pipelines can be created with one builder.
|
||||||
|
* However, shaders are single-usage. Bind a new shader before claling build again.
|
||||||
|
* See ShaderModule for more information, and how to change this.
|
||||||
|
*/
|
||||||
|
class ComputePipelineBuilder : public PipelineBuilder {
|
||||||
|
public:
|
||||||
|
explicit ComputePipelineBuilder(std::optional<int> maxCache = std::nullopt) : PipelineBuilder(maxCache) {}
|
||||||
|
|
||||||
|
ComputePipelineBuilder(const ComputePipelineBuilder&) = delete;
|
||||||
|
ComputePipelineBuilder& operator=(const ComputePipelineBuilder&) = delete;
|
||||||
|
|
||||||
|
#define fluent ComputePipelineBuilder&
|
||||||
|
|
||||||
|
fluent name(std::string&& name);
|
||||||
|
fluent layout(std::vector<VkDescriptorSetLayout>&& descLayouts, std::vector<VkPushConstantRange>&& pushConstants);
|
||||||
|
fluent shader(std::string&& path);
|
||||||
|
|
||||||
|
std::unique_ptr<Pipeline> build() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<std::string> shaderPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipeline configures:
|
||||||
|
* - Shader Stages
|
||||||
|
* - Fixed Function stages
|
||||||
|
* - Vertex input bindings
|
||||||
|
* - Vertex attributes
|
||||||
|
* - Assembly
|
||||||
|
* - Tesselation
|
||||||
|
* - Viewport and Scissor
|
||||||
|
* - Rasterization
|
||||||
|
* - Multisampling
|
||||||
|
* - Depth testing
|
||||||
|
* - Stencil testing
|
||||||
|
* - Color blending
|
||||||
|
* - Dynamic states
|
||||||
|
* - Pipeline layout
|
||||||
|
* - Descriptor set layout
|
||||||
|
* - Push constant ranges
|
||||||
|
*
|
||||||
|
* Create a Pipeline with one of the builders above.
|
||||||
|
*/
|
||||||
|
class Pipeline {
|
||||||
|
public:
|
||||||
|
Pipeline(const Pipeline&) = delete;
|
||||||
|
Pipeline& operator=(const Pipeline&) = delete;
|
||||||
|
|
||||||
|
~Pipeline();
|
||||||
|
|
||||||
|
void bind(const VkCommandBuffer& buffer) const;
|
||||||
|
|
||||||
|
const VkPipeline& operator*() const { return pipeline; }
|
||||||
|
const VkPipelineLayout& getLayout() const { return layout; }
|
||||||
|
VkPipelineBindPoint getBind() const { return bindPoint; }
|
||||||
|
|
||||||
|
static VkPipelineColorBlendAttachmentState getAlphaBlendState(bool blending) {
|
||||||
|
return {
|
||||||
|
blending, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
|
||||||
|
VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
|
||||||
|
VK_BLEND_OP_ADD, VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend std::unique_ptr<Pipeline> GraphicsPipelineBuilder::build() const;
|
||||||
|
friend std::unique_ptr<Pipeline> ComputePipelineBuilder::build() const;
|
||||||
|
|
||||||
|
Pipeline(std::string name, const VkPipeline& line, const VkPipelineLayout& lay, VkPipelineBindPoint bPoint)
|
||||||
|
: name(std::move(name)), pipeline(line), layout(lay), bindPoint(bPoint) {}
|
||||||
|
|
||||||
|
const std::string name;
|
||||||
|
// The active Pipeline layout.
|
||||||
|
const VkPipelineLayout layout;
|
||||||
|
// The active Pipeline instance.
|
||||||
|
const VkPipeline pipeline;
|
||||||
|
// Whether this is a graphics or compute pipeline
|
||||||
|
const VkPipelineBindPoint bindPoint;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
|
||||||
|
class SwapChain {
|
||||||
|
public:
|
||||||
|
SwapChain();
|
||||||
|
~SwapChain();
|
||||||
|
|
||||||
|
VkSwapchainKHR swapChain;
|
||||||
|
VkFormat format;
|
||||||
|
VkExtent2D extent;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<vlkx::Image>> images;
|
||||||
|
std::unique_ptr<vlkx::Image> multisampleImg;
|
||||||
|
|
||||||
|
VkSurfaceFormatKHR chooseFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
|
||||||
|
VkPresentModeKHR chooseMode(const std::vector<VkPresentModeKHR>& availableModes);
|
||||||
|
VkExtent2D chooseExtent(const VkSurfaceCapabilitiesKHR& capabilities);
|
||||||
|
|
||||||
|
void create(VkSurfaceKHR surface);
|
||||||
|
void destroy();
|
||||||
|
};
|
|
@ -0,0 +1,39 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vulkan/vk_mem_alloc.h>
|
||||||
|
#include "VulkanDevice.h"
|
||||||
|
#include "exports.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace VkTools {
|
||||||
|
struct ManagedImage {
|
||||||
|
VkImage image;
|
||||||
|
VmaAllocation allocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ManagedBuffer {
|
||||||
|
VkBuffer buffer;
|
||||||
|
VmaAllocation allocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern API VmaAllocator allocator;
|
||||||
|
|
||||||
|
ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent);
|
||||||
|
|
||||||
|
VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode, uint32_t mipping, VkDevice device);
|
||||||
|
|
||||||
|
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipping, uint32_t layers,
|
||||||
|
VkDevice device);
|
||||||
|
|
||||||
|
ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
|
||||||
|
VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible = true);
|
||||||
|
|
||||||
|
void immediateExecute(const std::function<void(const VkCommandBuffer &)> &execute, VulkanDevice *dev);
|
||||||
|
|
||||||
|
void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VulkanDevice *dev);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
class ValidationAndExtension {
|
||||||
|
public:
|
||||||
|
ValidationAndExtension();
|
||||||
|
~ValidationAndExtension();
|
||||||
|
|
||||||
|
const std::vector<const char*> requiredValidations = {
|
||||||
|
"VK_LAYER_KHRONOS_validation",
|
||||||
|
//"VK_LAYER_LUNARG_api_dump"
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDebugReportCallbackEXT callback;
|
||||||
|
|
||||||
|
bool checkValidationSupport();
|
||||||
|
|
||||||
|
std::vector<const char*> getRequiredExtensions(SDL_Window* window, bool validationsRequired);
|
||||||
|
void setupDebugCallback(bool validationsRequired, VkInstance vulkan);
|
||||||
|
void destroy(bool validationsRequired, VkInstance vulkan);
|
||||||
|
|
||||||
|
|
||||||
|
VkResult createDebugReportCallbackEXT(
|
||||||
|
VkInstance vulkan,
|
||||||
|
const VkDebugReportCallbackCreateInfoEXT* info,
|
||||||
|
const VkAllocationCallbacks* allocator,
|
||||||
|
VkDebugReportCallbackEXT* callback) {
|
||||||
|
|
||||||
|
auto func = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(vulkan, "vkCreateDebugReportCallbackEXT");
|
||||||
|
|
||||||
|
if (func != nullptr) {
|
||||||
|
return func(vulkan, info, allocator, callback);
|
||||||
|
} else {
|
||||||
|
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyDebugReportCallbackEXT(
|
||||||
|
VkInstance vulkan,
|
||||||
|
const VkDebugReportCallbackEXT callback,
|
||||||
|
const VkAllocationCallbacks* allocator) {
|
||||||
|
|
||||||
|
auto func = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(vulkan, "vkDestroyDebugReportCallbackEXT");
|
||||||
|
|
||||||
|
if (func != nullptr) {
|
||||||
|
func(vulkan, callback, allocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <vlkx/vulkan/ValidationAndExtension.h>
|
||||||
|
|
||||||
|
struct SwapChainMeta {
|
||||||
|
VkSurfaceCapabilitiesKHR capabilities;
|
||||||
|
std::vector<VkSurfaceFormatKHR> formats;
|
||||||
|
std::vector<VkPresentModeKHR> modes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct QueueFamilies {
|
||||||
|
int graphics = -1;
|
||||||
|
int presentation = -1;
|
||||||
|
|
||||||
|
bool present() {
|
||||||
|
return graphics >= 0 && presentation >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class VulkanDevice {
|
||||||
|
public:
|
||||||
|
VulkanDevice();
|
||||||
|
~VulkanDevice();
|
||||||
|
|
||||||
|
/** Physical Devices **/
|
||||||
|
VkPhysicalDevice physical;
|
||||||
|
VkPhysicalDeviceLimits limits;
|
||||||
|
SwapChainMeta swapChain;
|
||||||
|
QueueFamilies queueData;
|
||||||
|
|
||||||
|
std::vector<const char*> deviceExtensions = {
|
||||||
|
VK_KHR_SWAPCHAIN_EXTENSION_NAME
|
||||||
|
};
|
||||||
|
|
||||||
|
void choosePhysicalDevice(VkInstance* vulkan, VkSurfaceKHR surface);
|
||||||
|
bool isSuitable(VkPhysicalDevice device, VkSurfaceKHR surface);
|
||||||
|
bool isSupported(VkPhysicalDevice device);
|
||||||
|
|
||||||
|
SwapChainMeta checkSwapchain(VkPhysicalDevice device, VkSurfaceKHR surface);
|
||||||
|
QueueFamilies checkQueues(VkPhysicalDevice device, VkSurfaceKHR surface);
|
||||||
|
|
||||||
|
QueueFamilies getQueues() { return queueData; }
|
||||||
|
|
||||||
|
/** Logical Devices **/
|
||||||
|
VkDevice logical;
|
||||||
|
VkQueue graphicsQueue;
|
||||||
|
VkQueue presentationQueue;
|
||||||
|
|
||||||
|
void createLogicalDevice(VkSurfaceKHR surface, bool validationRequired, ValidationAndExtension* validators);
|
||||||
|
void destroy();
|
||||||
|
};
|
|
@ -0,0 +1,99 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vlkx/vulkan/ValidationAndExtension.h>
|
||||||
|
#include <vlkx/vulkan/VulkanDevice.h>
|
||||||
|
|
||||||
|
#include <vulkan/vk_mem_alloc.h>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include <SDL_vulkan.h>
|
||||||
|
#include <core/Module.h>
|
||||||
|
#include "SwapChain.h"
|
||||||
|
|
||||||
|
namespace vlkx { class ScreenRenderPassManager; }
|
||||||
|
|
||||||
|
class VulkanModule : public ShadowEngine::RendererModule {
|
||||||
|
SHObject_Base(VulkanModule);
|
||||||
|
public:
|
||||||
|
|
||||||
|
VulkanModule();
|
||||||
|
~VulkanModule() override;
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
static const bool validationRequired = true;
|
||||||
|
#else
|
||||||
|
static const bool validationRequired = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void PreInit() override;
|
||||||
|
|
||||||
|
void Init() override;
|
||||||
|
|
||||||
|
void Recreate() override;
|
||||||
|
|
||||||
|
void Update(int frame) override;
|
||||||
|
|
||||||
|
void PreRender() override;
|
||||||
|
|
||||||
|
void Render(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
|
void OverlayRender() override;
|
||||||
|
|
||||||
|
void LateRender(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
|
void AfterFrameEnd() override;
|
||||||
|
|
||||||
|
void Destroy() override;
|
||||||
|
|
||||||
|
void Event(SDL_Event* e) override;
|
||||||
|
|
||||||
|
void BeginRenderPass(const std::unique_ptr<vlkx::RenderCommand>& commands) override;
|
||||||
|
|
||||||
|
void EnableEditor() override;
|
||||||
|
|
||||||
|
VkExtent2D GetRenderExtent() override;
|
||||||
|
|
||||||
|
// VulkanModule is a singleton class.
|
||||||
|
static VulkanModule* instance;
|
||||||
|
static VulkanModule* getInstance();
|
||||||
|
|
||||||
|
// Initialize all Vulkan context and prepare validations in debug mode.
|
||||||
|
void initVulkan(SDL_Window* window);
|
||||||
|
void createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations);
|
||||||
|
|
||||||
|
// Start and end a frame render.
|
||||||
|
void startDraw();
|
||||||
|
void endDraw();
|
||||||
|
|
||||||
|
// Cleanup after the application has closed.
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
VkInstance getVulkan() { return vulkan; }
|
||||||
|
VulkanDevice* getDevice() { return device; }
|
||||||
|
SwapChain* getSwapchain() { return swapchain; }
|
||||||
|
VmaAllocator getAllocator() { return allocator; }
|
||||||
|
SDL_Window* getWind() { return wnd; }
|
||||||
|
const std::unique_ptr<vlkx::ScreenRenderPassManager>& getRenderPass();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool editorEnabled = false;
|
||||||
|
std::vector<VkDescriptorSet> editorRenderPlanes;
|
||||||
|
std::vector<std::unique_ptr<vlkx::Image>> editorContentFrames;
|
||||||
|
|
||||||
|
// The SDL Window contains the size of the drawable area.
|
||||||
|
SDL_Window* wnd;
|
||||||
|
// To handle the validation of Vulkan API usage
|
||||||
|
ValidationAndExtension* validators{};
|
||||||
|
// To manage interaction with the hardware
|
||||||
|
VulkanDevice* device{};
|
||||||
|
// To handle the framebuffers
|
||||||
|
SwapChain* swapchain{};
|
||||||
|
// To handle automatic management of memory.
|
||||||
|
VmaAllocator allocator{};
|
||||||
|
// To manage the Vulkan context that was passed to us by the API
|
||||||
|
VkInstance vulkan{};
|
||||||
|
// To manage the canvas that was given to us by GLFW
|
||||||
|
VkSurfaceKHR surface{};
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,412 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vlkx/vulkan/VulkanDevice.h>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
#include "vlkx/vulkan/Tools.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
// Root buffer class.
|
||||||
|
// Used to store & move data between the CPU and GPU.
|
||||||
|
// Basically; hotspot!
|
||||||
|
// Utilities and subclasses exist to optimise and speed up transfer and management of data in bulk.
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
// Metadata of CPU->GPU data copying.
|
||||||
|
struct CopyMeta {
|
||||||
|
const void* data; // The start of data in RAM
|
||||||
|
VkDeviceSize length; // The amount of data to move
|
||||||
|
VkDeviceSize start; // The start (destination) in GPU memory
|
||||||
|
};
|
||||||
|
|
||||||
|
// Metadata of bulk CPU->GPU data copying.
|
||||||
|
struct BulkCopyMeta {
|
||||||
|
VkDeviceSize length; // The total data size of all transfers.
|
||||||
|
std::vector<CopyMeta> metas;
|
||||||
|
};
|
||||||
|
|
||||||
|
Buffer(const Buffer&) = delete;
|
||||||
|
Buffer& operator=(const Buffer&) = delete;
|
||||||
|
|
||||||
|
Buffer();
|
||||||
|
|
||||||
|
virtual ~Buffer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** START OF DATA BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// A buffer that stores data on GPU.
|
||||||
|
// Usage of the data is determined by child classes.
|
||||||
|
class DataBuffer : public Buffer {
|
||||||
|
public:
|
||||||
|
DataBuffer(const DataBuffer&) = delete;
|
||||||
|
DataBuffer& operator=(const DataBuffer&) = delete;
|
||||||
|
|
||||||
|
DataBuffer() = default;
|
||||||
|
|
||||||
|
~DataBuffer() override {
|
||||||
|
vmaDestroyBuffer(VkTools::allocator, managed.buffer, managed.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using Buffer::Buffer;
|
||||||
|
|
||||||
|
void setBuffer(const VkTools::ManagedBuffer& buffer) { managed = buffer; }
|
||||||
|
VkTools::ManagedBuffer get() const { return managed; }
|
||||||
|
const VkBuffer& getBuffer() const { return managed.buffer; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkTools::ManagedBuffer managed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A buffer visible to both GPU and CPU.
|
||||||
|
// Useful for uploading data to GPU for format conversions.
|
||||||
|
class StagingBuffer : public DataBuffer {
|
||||||
|
public:
|
||||||
|
StagingBuffer(const BulkCopyMeta& copyMeta);
|
||||||
|
|
||||||
|
StagingBuffer(const StagingBuffer&) = delete;
|
||||||
|
StagingBuffer& operator=(const StagingBuffer&) = delete;
|
||||||
|
|
||||||
|
void copy(const VkBuffer& target) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const VkDeviceSize dataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Root class of vertex buffers.
|
||||||
|
// Provides utilities for subclasses.
|
||||||
|
class VertexBuffer : public DataBuffer {
|
||||||
|
public:
|
||||||
|
VertexBuffer(const VertexBuffer&) = delete;
|
||||||
|
VertexBuffer& operator=(const VertexBuffer&) = delete;
|
||||||
|
|
||||||
|
// Get attributes of vertexes in the buffer
|
||||||
|
// Location will start from "start"
|
||||||
|
// Binding will not be set
|
||||||
|
std::vector<VkVertexInputAttributeDescription> getAttrs(uint32_t start) const;
|
||||||
|
|
||||||
|
// Draw these vertexes without a buffer per vertex.
|
||||||
|
static void draw(const VkCommandBuffer& buffer, uint32_t verts, uint32_t instances);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class DynamicBuffer;
|
||||||
|
|
||||||
|
explicit VertexBuffer(std::vector<VkVertexInputAttributeDescription>&& attrs) : DataBuffer(), attributes(attrs) {}
|
||||||
|
|
||||||
|
// Initialize device memory and the managed buffer.
|
||||||
|
// indices and vertexes are put in the same buffer.
|
||||||
|
// if dynamic, the buffer will be host visible, this allows dynamic text.
|
||||||
|
// otherwise, the buffer is device local.
|
||||||
|
void create(VkDeviceSize totalSize, bool dynamic, bool indexes);
|
||||||
|
|
||||||
|
const std::vector<VkVertexInputAttributeDescription> attributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** END OF DATA BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// A simple plugin to allow the vertex buffer to be widened when reserved with a larger size.
|
||||||
|
class DynamicBuffer {
|
||||||
|
public:
|
||||||
|
DynamicBuffer(const DynamicBuffer&) = delete;
|
||||||
|
DynamicBuffer& operator=(const DynamicBuffer&) = delete;
|
||||||
|
|
||||||
|
~DynamicBuffer() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
DynamicBuffer(size_t size, bool hasIndices, VertexBuffer* buffer);
|
||||||
|
|
||||||
|
// Reallocate the vertex buffer if the given pSize is larger than the available space
|
||||||
|
void resize(size_t pSize);
|
||||||
|
|
||||||
|
VkDeviceSize bufferSize() const { return size; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const bool hasIndices;
|
||||||
|
VertexBuffer* vertexBuffer;
|
||||||
|
VkDeviceSize size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** START OF PER-VERTEX BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// Root class of buffers that store per-vertex data.
|
||||||
|
// eg. shader data
|
||||||
|
class PerVertexBuffer : public VertexBuffer {
|
||||||
|
public:
|
||||||
|
// Interprets the layout of data in containers (vector, etc)
|
||||||
|
struct VertexDataMeta {
|
||||||
|
template <typename C>
|
||||||
|
VertexDataMeta(const C& cont, int unitsPerMesh) : data(cont.data()), unitsPerMesh(unitsPerMesh), sizePerMesh(sizeof(cont[0]) * unitsPerMesh) {}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
VertexDataMeta(const C& cont) : VertexDataMeta(cont, static_cast<int>(cont.size())) {}
|
||||||
|
|
||||||
|
const void* data;
|
||||||
|
int unitsPerMesh;
|
||||||
|
size_t sizePerMesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interface for buffer data.
|
||||||
|
class BufferDataMeta {
|
||||||
|
public:
|
||||||
|
virtual ~BufferDataMeta() = default;
|
||||||
|
// Populate info and return a bulk copy meta for copying the data to device
|
||||||
|
virtual BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const = 0;
|
||||||
|
// Indicate whether the buffer contains index data too
|
||||||
|
virtual bool hasIndices() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Meshes do not share indices, with variable vertices.
|
||||||
|
class NoIndexBufferMeta : public BufferDataMeta {
|
||||||
|
public:
|
||||||
|
explicit NoIndexBufferMeta(std::vector<VertexDataMeta>&& perVertex) : perMeshVertices(perVertex) {}
|
||||||
|
|
||||||
|
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
|
||||||
|
bool hasIndices() const override { return false; };
|
||||||
|
private:
|
||||||
|
const std::vector<VertexDataMeta> perMeshVertices;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Meshes share indices, with static vertices.
|
||||||
|
class SharedIndexMeta : public BufferDataMeta {
|
||||||
|
public:
|
||||||
|
SharedIndexMeta(int meshes, const VertexDataMeta& perVertex, const VertexDataMeta& sharedIndices) : meshes(meshes),
|
||||||
|
perMeshVertex(perVertex),
|
||||||
|
sharedIndices(sharedIndices) {}
|
||||||
|
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
|
||||||
|
bool hasIndices() const override { return true; };
|
||||||
|
private:
|
||||||
|
const int meshes;
|
||||||
|
const VertexDataMeta perMeshVertex;
|
||||||
|
const VertexDataMeta sharedIndices;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Meshes do not share indexes, with variable indices and vertices.
|
||||||
|
class NoShareMeta : public BufferDataMeta {
|
||||||
|
public:
|
||||||
|
struct PerMesh {
|
||||||
|
VertexDataMeta indices;
|
||||||
|
VertexDataMeta vertices;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit NoShareMeta(std::vector<PerMesh>&& perMesh) : perMeshMeta(std::move(perMesh)) {}
|
||||||
|
|
||||||
|
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
|
||||||
|
bool hasIndices() const override { return true; };
|
||||||
|
private:
|
||||||
|
const std::vector<PerMesh> perMeshMeta;
|
||||||
|
};
|
||||||
|
|
||||||
|
PerVertexBuffer(const PerVertexBuffer&) = delete;
|
||||||
|
PerVertexBuffer& operator=(const PerVertexBuffer&) = delete;
|
||||||
|
|
||||||
|
// Render mesh a given number of times, into a recording buffer.
|
||||||
|
void draw(const VkCommandBuffer& buffer, uint32_t bind, int index, uint32_t instances) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using VertexBuffer::VertexBuffer;
|
||||||
|
|
||||||
|
// Stores vertex data for buffers without indices
|
||||||
|
struct MeshDataNoIndex {
|
||||||
|
struct Info {
|
||||||
|
uint32_t vertexCount;
|
||||||
|
VkDeviceSize vertexStart;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Info> info;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores vertex and index data for buffers with both
|
||||||
|
struct MeshDataIndex {
|
||||||
|
struct Info {
|
||||||
|
uint32_t indexCount;
|
||||||
|
VkDeviceSize indexStart;
|
||||||
|
VkDeviceSize vertexStart;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Info> info;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::variant<MeshDataNoIndex, MeshDataIndex>* getInfo() { return &meshDataInfo; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::variant<MeshDataNoIndex, MeshDataIndex> meshDataInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores static data for one-time upload.
|
||||||
|
class StaticPerVertexBuffer : public PerVertexBuffer {
|
||||||
|
public:
|
||||||
|
StaticPerVertexBuffer(const BufferDataMeta& info, std::vector<VkVertexInputAttributeDescription>&& attrs);
|
||||||
|
|
||||||
|
StaticPerVertexBuffer(const StaticPerVertexBuffer&) = delete;
|
||||||
|
StaticPerVertexBuffer& operator=(const StaticPerVertexBuffer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores host-visible data that can be reallocated.
|
||||||
|
class DynamicPerVertexBuffer : public PerVertexBuffer, public DynamicBuffer {
|
||||||
|
public:
|
||||||
|
DynamicPerVertexBuffer(size_t size, std::vector<VkVertexInputAttributeDescription>&& attrs) : PerVertexBuffer(std::move(attrs)), DynamicBuffer(size, true, this) {}
|
||||||
|
|
||||||
|
DynamicPerVertexBuffer(const DynamicPerVertexBuffer&) = delete;
|
||||||
|
DynamicPerVertexBuffer& operator=(const DynamicPerVertexBuffer&) = delete;
|
||||||
|
|
||||||
|
void copyToDevice(const BufferDataMeta& meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** END OF PER-VERTEX BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// Root class of buffers that store vertex data per instance of a mesh.
|
||||||
|
class PerInstanceVertexBuffer : public VertexBuffer {
|
||||||
|
public:
|
||||||
|
PerInstanceVertexBuffer(const PerInstanceVertexBuffer&) = delete;
|
||||||
|
PerInstanceVertexBuffer& operator=(const PerInstanceVertexBuffer&) = delete;
|
||||||
|
|
||||||
|
void bind(const VkCommandBuffer& commands, uint32_t bindPoint, int offset) const;
|
||||||
|
|
||||||
|
uint32_t getSize() const { return sizePerInstance; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PerInstanceVertexBuffer(uint32_t size, std::vector<VkVertexInputAttributeDescription>&& attrs) : VertexBuffer(std::move(attrs)), sizePerInstance(size) {}
|
||||||
|
private:
|
||||||
|
const uint32_t sizePerInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores vertices that are static per instance of the mesh.
|
||||||
|
class StaticPerInstanceBuffer : public PerInstanceVertexBuffer {
|
||||||
|
public:
|
||||||
|
StaticPerInstanceBuffer(uint32_t size, const void* data, uint32_t instances, std::vector<VkVertexInputAttributeDescription>&& attrs);
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
StaticPerInstanceBuffer(const C& cont, std::vector<VkVertexInputAttributeDescription>&& attrs) : StaticPerInstanceBuffer(sizeof(cont[0]), cont.data(), CONTAINER_SIZE(cont), std::move(attrs)) {}
|
||||||
|
|
||||||
|
StaticPerInstanceBuffer(const StaticPerInstanceBuffer&) = delete;
|
||||||
|
StaticPerInstanceBuffer& operator=(const StaticPerInstanceBuffer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores vertices of meshes that are dynamic (ie. text, shifting meshes
|
||||||
|
class DynamicPerInstanceBuffer : public PerInstanceVertexBuffer, public DynamicBuffer {
|
||||||
|
public:
|
||||||
|
DynamicPerInstanceBuffer(uint32_t size, size_t maxInstances, std::vector<VkVertexInputAttributeDescription>&& attrs) : PerInstanceVertexBuffer(size, std::move(attrs)), DynamicBuffer(size * maxInstances, false, this) {}
|
||||||
|
|
||||||
|
DynamicPerInstanceBuffer(const DynamicPerInstanceBuffer&) = delete;
|
||||||
|
DynamicPerInstanceBuffer& operator=(const DynamicPerInstanceBuffer*) = delete;
|
||||||
|
|
||||||
|
void copyToDevice(const void* data, uint32_t instances);
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
void copyToDevice(const C& cont) {
|
||||||
|
copyToDevice(cont.data(), CONTAINER_SIZE(cont));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** END OF PER-INSTANCE BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// Holds uniform data on host and device.
|
||||||
|
// Supports superallocating (allocating more than one "set" of uniforms at once)
|
||||||
|
// Data is stored on host and device simultaneously, so set the host data and flush it to the device.
|
||||||
|
class UniformBuffer : public DataBuffer {
|
||||||
|
public:
|
||||||
|
UniformBuffer(size_t chunkSize, int chunks);
|
||||||
|
|
||||||
|
UniformBuffer(const UniformBuffer&) = delete;
|
||||||
|
UniformBuffer& operator=(const UniformBuffer&) = delete;
|
||||||
|
|
||||||
|
~UniformBuffer() override { delete data; }
|
||||||
|
|
||||||
|
// Whether this buffer holds a single chunk (is not superallocated).
|
||||||
|
// Simplifies certain algorithms significantly if you know this beforehand
|
||||||
|
bool isSingle() const { return numChunks == 1; }
|
||||||
|
|
||||||
|
// Get the data in the buffer, casted to the given type
|
||||||
|
template <typename DataType>
|
||||||
|
DataType* getData(int index) const {
|
||||||
|
checkIndex(index);
|
||||||
|
return reinterpret_cast<DataType*>(data + chunkSize * index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload (flush) the uniform to the GPU
|
||||||
|
void upload(int index) const;
|
||||||
|
void upload(int index, VkDeviceSize dataSize, VkDeviceSize start) const;
|
||||||
|
|
||||||
|
static VkDescriptorType getDescriptorType() { return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; }
|
||||||
|
|
||||||
|
VkDescriptorBufferInfo getDescriptorInfo(int index) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkIndex(int index) const;
|
||||||
|
|
||||||
|
char* data;
|
||||||
|
const size_t chunkSize;
|
||||||
|
const int numChunks;
|
||||||
|
|
||||||
|
size_t chunkLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A small, highly efficient buffer much like the Uniform Buffer.
|
||||||
|
class PushConstant {
|
||||||
|
public:
|
||||||
|
// Size must be < 128.
|
||||||
|
PushConstant(size_t size, int numFrames);
|
||||||
|
|
||||||
|
PushConstant(const PushConstant&) = delete;
|
||||||
|
PushConstant& operator=(const PushConstant&) = delete;
|
||||||
|
|
||||||
|
~PushConstant() { delete[] data; }
|
||||||
|
|
||||||
|
// Whether this buffer holds a single chunk (is not superallocated).
|
||||||
|
// Simplifies certain algorithms significantly if you know this beforehand
|
||||||
|
bool isSingle() const { return numFrames == 1; }
|
||||||
|
|
||||||
|
uint32_t getSize() const { return sizePerFrame; }
|
||||||
|
|
||||||
|
// Get the data in the buffer, casted to the given type
|
||||||
|
template <typename DataType>
|
||||||
|
DataType* getData(int frame) const {
|
||||||
|
checkIndex(frame);
|
||||||
|
return reinterpret_cast<DataType*>(data + (sizePerFrame * frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPushConstantRange makeRange(VkShaderStageFlags stage) {
|
||||||
|
return VkPushConstantRange { stage, 0, sizePerFrame };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload (flush) the uniform to the GPU
|
||||||
|
void upload(const VkCommandBuffer& commands, const VkPipelineLayout& pipelineLayout, int frame, uint32_t offset, VkShaderStageFlags stage) const;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkIndex(int index) const;
|
||||||
|
|
||||||
|
char* data;
|
||||||
|
const uint32_t sizePerFrame;
|
||||||
|
const int numFrames;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include "Queue.h"
|
||||||
|
#include "vlkx/vulkan/VulkanDevice.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
// Root class of VkCommandBuffer wrappers.
|
||||||
|
class CommandBuffer {
|
||||||
|
public:
|
||||||
|
using Command = std::function<void(const VkCommandBuffer& buffer)>;
|
||||||
|
|
||||||
|
CommandBuffer(const CommandBuffer&) = delete;
|
||||||
|
CommandBuffer& operator=(const CommandBuffer&) = delete;
|
||||||
|
|
||||||
|
virtual ~CommandBuffer() {
|
||||||
|
vkDestroyCommandPool(dev->logical, pool, nullptr);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
CommandBuffer();
|
||||||
|
|
||||||
|
void setPool(const VkCommandPool& newPool) { pool = newPool; }
|
||||||
|
VulkanDevice* dev;
|
||||||
|
private:
|
||||||
|
VkCommandPool pool;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A command buffer that will be immediately executed.
|
||||||
|
class ImmediateCommand : public CommandBuffer {
|
||||||
|
public:
|
||||||
|
ImmediateCommand(Queue queue);
|
||||||
|
|
||||||
|
ImmediateCommand(const ImmediateCommand&) = delete;
|
||||||
|
ImmediateCommand& operator=(const ImmediateCommand&) = delete;
|
||||||
|
|
||||||
|
void run(const Command& cmd);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Queue queue;
|
||||||
|
VkCommandBuffer commands;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A command buffer that will be reused every frame.
|
||||||
|
class RenderCommand : public CommandBuffer {
|
||||||
|
public:
|
||||||
|
using Command = std::function<void(const VkCommandBuffer& commands, uint32_t framebuffer)>;
|
||||||
|
using Update = std::function<void(int frame)>;
|
||||||
|
|
||||||
|
~RenderCommand() override {
|
||||||
|
// Destroy our own data
|
||||||
|
vkDestroySemaphore(dev->logical, renderDoneSem, nullptr);
|
||||||
|
vkDestroySemaphore(dev->logical, newImageSem, nullptr);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
vkDestroyFence(dev->logical, inFlight[i], nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCommand(int frames);
|
||||||
|
|
||||||
|
RenderCommand(const RenderCommand&) = delete;
|
||||||
|
RenderCommand& operator=(const RenderCommand&) = delete;
|
||||||
|
|
||||||
|
uint32_t getFrame() { return imageIndex; }
|
||||||
|
|
||||||
|
void nextFrame() { imageIndex = (imageIndex + 1) % 2; }
|
||||||
|
|
||||||
|
std::optional<VkResult> execute(int frame, const VkSwapchainKHR& swapchain, const Update& update, const Command& cmd);
|
||||||
|
// Renders a single frame out, no semaphores or fences.
|
||||||
|
std::optional<VkResult> executeSimple(int frame, const Update& update, const Command& cmd);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<VkCommandBuffer> commands;
|
||||||
|
// Raised when a new image is available
|
||||||
|
VkSemaphore newImageSem;
|
||||||
|
// Raised when a render is finished
|
||||||
|
VkSemaphore renderDoneSem;
|
||||||
|
// Stores fences for frames that are currently "in flight".
|
||||||
|
std::vector<VkFence> inFlight;
|
||||||
|
|
||||||
|
// The index of the texture that is currently being used by the GPU.
|
||||||
|
uint32_t imageIndex = 0;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <temp/model/Loader.h>
|
||||||
|
#include <vlkx/vulkan/VulkanModule.h>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
class Descriptor {
|
||||||
|
public:
|
||||||
|
using TextureType = vlkxtemp::ModelLoader::TextureType;
|
||||||
|
using BufferInfos = std::map<uint32_t, std::vector<VkDescriptorBufferInfo>>;
|
||||||
|
using ImageInfos = std::map<uint32_t, std::vector<VkDescriptorImageInfo>>;
|
||||||
|
|
||||||
|
struct Meta {
|
||||||
|
struct Binding {
|
||||||
|
uint32_t bindPoint;
|
||||||
|
uint32_t length;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDescriptorType type;
|
||||||
|
VkShaderStageFlags stage;
|
||||||
|
std::vector<Binding> bindings;
|
||||||
|
};
|
||||||
|
|
||||||
|
Descriptor(const Descriptor&) = delete;
|
||||||
|
Descriptor& operator=(const Descriptor&) = delete;
|
||||||
|
|
||||||
|
virtual ~Descriptor() {
|
||||||
|
vkDestroyDescriptorSetLayout(VulkanModule::getInstance()->getDevice()->logical, layout, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkDescriptorSetLayout& getLayout() const { return layout; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
explicit Descriptor() = default;
|
||||||
|
|
||||||
|
void setLayout(const VkDescriptorSetLayout& newLayout) { layout = newLayout; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkDescriptorSetLayout layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StaticDescriptor : public Descriptor {
|
||||||
|
public:
|
||||||
|
|
||||||
|
StaticDescriptor(std::vector<Meta> metas);
|
||||||
|
StaticDescriptor(const StaticDescriptor&) = delete;
|
||||||
|
StaticDescriptor& operator=(const StaticDescriptor&) = delete;
|
||||||
|
|
||||||
|
~StaticDescriptor() override {
|
||||||
|
vkDestroyDescriptorPool(VulkanModule::getInstance()->getDevice()->logical, pool, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const StaticDescriptor& buffers(VkDescriptorType type, const BufferInfos& infos) const;
|
||||||
|
const StaticDescriptor& images(VkDescriptorType type, const ImageInfos& infos) const;
|
||||||
|
|
||||||
|
void bind(const VkCommandBuffer& commands, const VkPipelineLayout& layout, VkPipelineBindPoint bindPoint) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const StaticDescriptor& updateSet(const std::vector<VkWriteDescriptorSet>& write) const;
|
||||||
|
|
||||||
|
VkDescriptorPool pool;
|
||||||
|
VkDescriptorSet set;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: dynamic sets
|
||||||
|
}
|
|
@ -0,0 +1,331 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "vlkx/vulkan/Tools.h"
|
||||||
|
#include "ImageUsage.h"
|
||||||
|
#include <shadow/util/RefCounter.h>
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
// Describes an image without initializing or storing any heavy data.
|
||||||
|
class ImageDescriptor {
|
||||||
|
public:
|
||||||
|
enum class Type { Single, Cubemap };
|
||||||
|
struct Dimension {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t channels;
|
||||||
|
|
||||||
|
VkExtent2D getExtent() const { return { width, height }; }
|
||||||
|
size_t getSize() const { return width * height * channels; }
|
||||||
|
};
|
||||||
|
|
||||||
|
Type getType() const { return type; }
|
||||||
|
VkExtent2D getExtent() const { return dimensions.getExtent(); }
|
||||||
|
uint32_t getWidth() const { return dimensions.width; }
|
||||||
|
uint32_t getHeight() const { return dimensions.height; }
|
||||||
|
uint32_t getChannels() const { return dimensions.channels; }
|
||||||
|
|
||||||
|
std::vector<void*> getData() const {
|
||||||
|
if (type == Type::Single) return { (void*) data };
|
||||||
|
std::vector<void*> dataPtrs;
|
||||||
|
dataPtrs.reserve(6);
|
||||||
|
|
||||||
|
size_t offset = 0;
|
||||||
|
for (size_t i = 0; i < 6; i++) {
|
||||||
|
dataPtrs.emplace_back((char*) data + offset);
|
||||||
|
offset += dimensions.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataPtrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getLayers() const { return type == Type::Single ? 1 : 6; }
|
||||||
|
|
||||||
|
ImageDescriptor(Type t, const Dimension& d, const void* ptr) : type(t), dimensions(d), data(ptr) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Type type;
|
||||||
|
Dimension dimensions;
|
||||||
|
const void* data;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// A staging buffer specialized for uploading images.
|
||||||
|
class ImageStagingBuffer : public StagingBuffer {
|
||||||
|
public:
|
||||||
|
using StagingBuffer::StagingBuffer;
|
||||||
|
|
||||||
|
ImageStagingBuffer(const ImageStagingBuffer&) = delete;
|
||||||
|
ImageStagingBuffer& operator=(const ImageStagingBuffer&) = delete;
|
||||||
|
|
||||||
|
void copy(const VkImage& target, const VkExtent3D& extent, uint32_t layers) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Root class that stores image data on GPU buffers
|
||||||
|
class ImageBuffer : public Buffer {
|
||||||
|
public:
|
||||||
|
ImageBuffer(const ImageBuffer&) = delete;
|
||||||
|
ImageBuffer& operator=(const ImageBuffer&) = delete;
|
||||||
|
|
||||||
|
~ImageBuffer() override {
|
||||||
|
vmaDestroyImage(VkTools::allocator, image.image, image.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const { return image; }
|
||||||
|
const VkImage& getImage() const { return image.image; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using Buffer::Buffer;
|
||||||
|
|
||||||
|
void setImage(const VkTools::ManagedImage newImg) { image = newImg; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkTools::ManagedImage image;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base class of all images; stores the common data.
|
||||||
|
class Image {
|
||||||
|
public:
|
||||||
|
Image(const Image&) = delete;
|
||||||
|
Image& operator=(const Image&) = delete;
|
||||||
|
|
||||||
|
virtual ~Image() {
|
||||||
|
vkDestroyImageView(dev->logical, view, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VkDescriptorType getSampleType() { return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; }
|
||||||
|
static VkDescriptorType getLinearType() { return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; }
|
||||||
|
|
||||||
|
static ImageDescriptor loadSingleFromDisk(std::string path, bool flipY);
|
||||||
|
// The following are left unimplemented intentionally.
|
||||||
|
//static ImageDescriptor loadSingleFromVFS(std::string path, bool flipY);
|
||||||
|
static ImageDescriptor loadCubeFromDisk(const std::string& directory, const std::array<std::string, 6>& files, bool flipY);
|
||||||
|
//static ImageDescriptor loadCubeFromVFS(std::string directory, const std::array<std::string, 6>& files, bool flipY);
|
||||||
|
|
||||||
|
virtual ImageUsage getUsage() const { return ImageUsage {}; }
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& operator*() const { return get(); }
|
||||||
|
|
||||||
|
virtual const VkTools::ManagedImage& get() const = 0;
|
||||||
|
virtual const VkImage& getImage() const = 0;
|
||||||
|
|
||||||
|
const VkImageView& getView() const { return view; }
|
||||||
|
const VkExtent2D& getExtent() const { return extent; }
|
||||||
|
VkFormat getFormat() const { return format; }
|
||||||
|
virtual VkSampleCountFlagBits getSamples() const { return VK_SAMPLE_COUNT_1_BIT; }
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Image(const VkExtent2D& ext, VkFormat form);
|
||||||
|
|
||||||
|
void setView(const VkImageView& imgView) { view = imgView; }
|
||||||
|
|
||||||
|
VulkanDevice* dev;
|
||||||
|
VkImageView view;
|
||||||
|
VkExtent2D extent;
|
||||||
|
VkFormat format;
|
||||||
|
VkSampleCountFlagBits sampleCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configures image sampling in a sensible and extensible way
|
||||||
|
class ImageSampler {
|
||||||
|
public:
|
||||||
|
struct Config {
|
||||||
|
explicit Config(VkFilter filter = VK_FILTER_LINEAR, VkSamplerAddressMode mode = VK_SAMPLER_ADDRESS_MODE_REPEAT) : filter(filter), mode(mode) {}
|
||||||
|
|
||||||
|
VkFilter filter;
|
||||||
|
VkSamplerAddressMode mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageSampler(int mipLevels, const Config& config);
|
||||||
|
|
||||||
|
ImageSampler(const ImageSampler&) = delete;
|
||||||
|
ImageSampler& operator=(const ImageSampler&) = delete;
|
||||||
|
|
||||||
|
~ImageSampler() {
|
||||||
|
vkDestroySampler(dev->logical, sampler, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkSampler& operator*() const { return sampler; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkSampler sampler;
|
||||||
|
VulkanDevice* dev;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Root of images which can be sampled.
|
||||||
|
class SamplableImage {
|
||||||
|
public:
|
||||||
|
virtual ~SamplableImage() = default;
|
||||||
|
|
||||||
|
// Return a VkDescriptorImageInfo we can use to update sets.
|
||||||
|
virtual VkDescriptorImageInfo getInfo(VkImageLayout layout) const = 0;
|
||||||
|
VkDescriptorImageInfo getInfoForSampling() const { return getInfo(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); }
|
||||||
|
VkDescriptorImageInfo getInfoForLinear() const { return getInfo(VK_IMAGE_LAYOUT_GENERAL); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// A samplable image that lives on the GPU, with optional mipmapping.
|
||||||
|
// Use a RefCountedTexture when loading from files.
|
||||||
|
class TextureImage : public Image, public SamplableImage {
|
||||||
|
public:
|
||||||
|
// Image metadata
|
||||||
|
struct Meta {
|
||||||
|
VkExtent2D getExtent() const { return { width, height }; }
|
||||||
|
VkExtent3D get3DExtent() const { return { width, height, channels }; }
|
||||||
|
|
||||||
|
Buffer::BulkCopyMeta getCopyMeta() const;
|
||||||
|
|
||||||
|
std::vector<void*> data;
|
||||||
|
std::vector<ImageUsage> usages;
|
||||||
|
VkFormat format;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t channels;
|
||||||
|
};
|
||||||
|
|
||||||
|
TextureImage(bool mipmapping, const ImageSampler::Config& samplerConfig, const Meta& meta);
|
||||||
|
TextureImage(bool mipmapping, const ImageDescriptor& image, const std::vector<ImageUsage>& usages, const ImageSampler::Config& config);
|
||||||
|
|
||||||
|
TextureImage(const TextureImage&) = delete;
|
||||||
|
TextureImage& operator=(const TextureImage&) = delete;
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const override { return buffer.get(); }
|
||||||
|
const VkImage& getImage() const override { return buffer.getImage(); }
|
||||||
|
|
||||||
|
VkDescriptorImageInfo getInfo(VkImageLayout layout) const override {
|
||||||
|
return { *sampler, getView(), layout };
|
||||||
|
}
|
||||||
|
// Textures are sampled in fragment shaders.
|
||||||
|
ImageUsage getUsage() const override {
|
||||||
|
return ImageUsage::sampledFragment();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
class TextureBuffer : public ImageBuffer {
|
||||||
|
public:
|
||||||
|
TextureBuffer(bool mipmaps, const Meta& meta);
|
||||||
|
|
||||||
|
TextureBuffer(const TextureBuffer&) = delete;
|
||||||
|
TextureBuffer& operator=(const TextureBuffer&) = delete;
|
||||||
|
|
||||||
|
int getMipping() const { return mipLevels; }
|
||||||
|
private:
|
||||||
|
int mipLevels = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TextureBuffer buffer;
|
||||||
|
const ImageSampler sampler;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A texture image that lives on GPU that is reference counted.
|
||||||
|
// This allows it to be reused multiple times without reading the file in more than once.
|
||||||
|
// It also allows the texture to be destructed automatically once nothing in the scene uses it, for eg. level changes.
|
||||||
|
class RefCountedTexture : public SamplableImage {
|
||||||
|
public:
|
||||||
|
// Cubemaps are simply 6 textures, so we include them here for easy instantiation.
|
||||||
|
struct CubemapLocation {
|
||||||
|
std::string directory;
|
||||||
|
std::array<std::string, 6> files;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reference Counting works on both individual files and cubemaps, so we put them together.
|
||||||
|
using ImageLocation = std::variant<std::string, CubemapLocation>;
|
||||||
|
|
||||||
|
RefCountedTexture(const ImageLocation& location, std::vector<ImageUsage> usages, const ImageSampler::Config& config)
|
||||||
|
: texture(get(location, std::move(usages), config)) {}
|
||||||
|
|
||||||
|
RefCountedTexture(RefCountedTexture&&) noexcept = default;
|
||||||
|
RefCountedTexture& operator=(RefCountedTexture&&) noexcept = default;
|
||||||
|
|
||||||
|
VkDescriptorImageInfo getInfo(VkImageLayout layout) const override {
|
||||||
|
return texture->getInfo(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Image* operator->() const { return texture.operator->(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
using ReferenceCounter = shadowutil::RefCounter<TextureImage>;
|
||||||
|
// Get or load the specified image.
|
||||||
|
static ReferenceCounter get(const ImageLocation& location, const std::vector<ImageUsage>& usages, const ImageSampler::Config& config);
|
||||||
|
|
||||||
|
ReferenceCounter texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: unowned, offscreen images.
|
||||||
|
|
||||||
|
// Image that can be used as a depth / stencil buffer attachment.
|
||||||
|
class DepthStencilImage : public Image {
|
||||||
|
public:
|
||||||
|
DepthStencilImage(const DepthStencilImage&) = delete;
|
||||||
|
DepthStencilImage& operator=(const DepthStencilImage&) = delete;
|
||||||
|
|
||||||
|
DepthStencilImage(const VkExtent2D& extent);
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const override { return buffer.get(); }
|
||||||
|
const VkImage& getImage() const override { return buffer.getImage(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
class DepthStencilBuffer : public ImageBuffer {
|
||||||
|
public:
|
||||||
|
DepthStencilBuffer(const VkExtent2D& extent, VkFormat format);
|
||||||
|
DepthStencilBuffer(const DepthStencilBuffer&) = delete;
|
||||||
|
DepthStencilBuffer& operator=(const DepthStencilBuffer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DepthStencilBuffer buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Image that references an existing image on the swapchain
|
||||||
|
class SwapchainImage : public Image {
|
||||||
|
public:
|
||||||
|
SwapchainImage(const SwapchainImage&) = delete;
|
||||||
|
SwapchainImage& operator=(const SwapchainImage&) = delete;
|
||||||
|
|
||||||
|
SwapchainImage(const VkImage& image, const VkExtent2D& extent, VkFormat format);
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const override { return managed; }
|
||||||
|
const VkImage& getImage() const override { return image; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkImage image;
|
||||||
|
VkTools::ManagedImage managed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MultisampleImage : public Image {
|
||||||
|
public:
|
||||||
|
enum class Mode {
|
||||||
|
MostEfficient,
|
||||||
|
Highest
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unique_ptr<Image> createColor(const Image& targetImage, Mode mode);
|
||||||
|
static std::unique_ptr<Image> createDepthStencilMS(const VkExtent2D& extent, Mode mode);
|
||||||
|
static std::unique_ptr<Image> createDepthStencil(VkExtent2D& extent, std::optional<Mode> mode);
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const override { return buffer.get(); }
|
||||||
|
const VkImage& getImage() const override { return buffer.getImage(); }
|
||||||
|
|
||||||
|
VkSampleCountFlagBits getSamples() const override { return samples; }
|
||||||
|
private:
|
||||||
|
class MultisampleBuffer : public ImageBuffer {
|
||||||
|
public:
|
||||||
|
enum class Type { Color, DepthStencil };
|
||||||
|
|
||||||
|
MultisampleBuffer(Type type, const VkExtent2D& extent, VkFormat format, VkSampleCountFlagBits samples);
|
||||||
|
MultisampleBuffer(const MultisampleBuffer&) = delete;
|
||||||
|
MultisampleBuffer& operator=(const MultisampleBuffer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
MultisampleImage(const VkExtent2D& extent, VkFormat format, Mode mode, MultisampleBuffer::Type type);
|
||||||
|
|
||||||
|
VkSampleCountFlagBits chooseSamples(Mode mode);
|
||||||
|
|
||||||
|
const VkSampleCountFlagBits samples;
|
||||||
|
const MultisampleBuffer buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,297 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
/**
|
||||||
|
* Describes how an image (collection of color data) will be used in the GPU.
|
||||||
|
* Has three parts; an overall description, access methods, and location of access.
|
||||||
|
* Use the static methods to create an instance, or the blank initializer and set fields as required.
|
||||||
|
*/
|
||||||
|
class ImageUsage {
|
||||||
|
public:
|
||||||
|
enum class Type {
|
||||||
|
DontCare, // Image is unused
|
||||||
|
RenderTarget, // Color attachment is used
|
||||||
|
DepthStencil, // Depth / Stencil buffer is used
|
||||||
|
Multisample, // Resolves to a multisampled image
|
||||||
|
Presentation, // Framebuffer for presentation
|
||||||
|
LinearAccess, // Host-Readable
|
||||||
|
InputAttachment, // Only what we write, is read.
|
||||||
|
Sampled, // Image is sampled (ie. texture)
|
||||||
|
Transfer // Image is used as an intermediate for transfer.
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Access {
|
||||||
|
DontCare, // Image is unused
|
||||||
|
ReadOnly, // Read Only access.
|
||||||
|
WriteOnly, // Write Only access.
|
||||||
|
ReadWrite, // Read/Write access.
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Location {
|
||||||
|
DontCare, // Image is unused
|
||||||
|
Host, // Image only exists on the host and will be transferred.
|
||||||
|
VertexShader, // Image is only used in the VertexAll Shader.
|
||||||
|
FragmentShader, // Image is only used in the Fragment Shader.
|
||||||
|
ComputeShader, // Image is only used in a Compute Shader.
|
||||||
|
Other, // Reserved.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sampled in a fragment shader. Read-Only.
|
||||||
|
static ImageUsage sampledFragment() { return { Type::Sampled, Access::ReadOnly, Location::FragmentShader }; };
|
||||||
|
// Used as a render target (a render pass will output to this image). Read/Write.
|
||||||
|
static ImageUsage renderTarget(int loc) { return { Type::RenderTarget, Access::ReadWrite, Location::Other, loc}; };
|
||||||
|
// Resolves to a multisampled image. Write-Only.
|
||||||
|
static ImageUsage multisample() { return { Type::Multisample, Access::WriteOnly, Location::Other }; };
|
||||||
|
// A depth or stencil buffer. Access is given, but must not be DontCare.
|
||||||
|
static ImageUsage depthStencil(Access acc) { return { Type::DepthStencil, acc, Location::Other}; };
|
||||||
|
// Used to show to the user. Write-Only.
|
||||||
|
static ImageUsage presentation() { return { Type::Presentation, Access::ReadOnly, Location::Other }; };
|
||||||
|
// Input attachment for a fragment shader. Usually a texture. Read-Only.
|
||||||
|
static ImageUsage input() { return { Type::InputAttachment, Access::ReadOnly, Location::FragmentShader }; };
|
||||||
|
// Linearly accessed image. For a compute shader. Access is given, but must not be DontCare.
|
||||||
|
static ImageUsage compute(Access acc) { return { Type::LinearAccess, acc, Location::ComputeShader }; };
|
||||||
|
|
||||||
|
explicit ImageUsage() : ImageUsage(Type::DontCare, Access::DontCare, Location::DontCare) {}
|
||||||
|
|
||||||
|
bool operator==(const ImageUsage& other) const {
|
||||||
|
return type == other.type && access == other.access && location == other.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageUsageFlagBits getUsageFlags() const {
|
||||||
|
switch (type) {
|
||||||
|
case Type::DontCare: throw std::runtime_error("No usage for type DontCare");
|
||||||
|
case Type::RenderTarget:
|
||||||
|
case Type::Multisample:
|
||||||
|
case Type::Presentation:
|
||||||
|
return VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||||
|
case Type::DepthStencil:
|
||||||
|
return VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||||
|
case Type::LinearAccess:
|
||||||
|
return VK_IMAGE_USAGE_STORAGE_BIT;
|
||||||
|
case Type::Sampled:
|
||||||
|
return VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
|
case Type::Transfer:
|
||||||
|
switch (access) {
|
||||||
|
case Access::DontCare: throw std::runtime_error("No access type specified for transfer usage.");
|
||||||
|
case Access::ReadOnly: return VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
|
case Access::WriteOnly: return VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
|
case Access::ReadWrite: throw std::runtime_error("ReadWrite access type for Transfer usage is invalid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static VkImageUsageFlags getFlagsForUsage(const std::vector<ImageUsage>& usages) {
|
||||||
|
auto flags = 0;
|
||||||
|
for (const auto& usage : usages) {
|
||||||
|
if (usage.type != Type::DontCare)
|
||||||
|
flags |= usage.getUsageFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<VkImageUsageFlags>(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageLayout getLayout() const {
|
||||||
|
switch (type) {
|
||||||
|
case Type::DontCare: return VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
case Type::RenderTarget:
|
||||||
|
case Type::Multisample:
|
||||||
|
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
|
case Type::DepthStencil: return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
|
case Type::Presentation: return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||||
|
case Type::LinearAccess: return VK_IMAGE_LAYOUT_GENERAL;
|
||||||
|
case Type::Sampled: return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
case Type::Transfer: return access == Access::ReadOnly ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
case Type::InputAttachment:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPipelineStageFlags getStage() const {
|
||||||
|
switch (type) {
|
||||||
|
case Type::DontCare: return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
|
case Type::RenderTarget:
|
||||||
|
case Type::Multisample:
|
||||||
|
case Type::Presentation:
|
||||||
|
return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
case Type::DepthStencil:
|
||||||
|
return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||||
|
case Type::LinearAccess:
|
||||||
|
case Type::Sampled:
|
||||||
|
switch (location) {
|
||||||
|
case Location::Host: return VK_PIPELINE_STAGE_HOST_BIT;
|
||||||
|
case Location::FragmentShader: return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||||
|
case Location::ComputeShader: return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||||
|
|
||||||
|
case Location::VertexShader:
|
||||||
|
case Location::Other:
|
||||||
|
throw std::runtime_error("Linear or sampled attachments must not be used in VertexAll or Other stages.");
|
||||||
|
case Location::DontCare: throw std::runtime_error("Linear or sampled attachments must have an access.");
|
||||||
|
}
|
||||||
|
|
||||||
|
case Type::Transfer:
|
||||||
|
return VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Access getAccess() const { return access; }
|
||||||
|
|
||||||
|
Type getType() const { return type; }
|
||||||
|
|
||||||
|
VkAccessFlags getAccessFlags() const {
|
||||||
|
switch (type) {
|
||||||
|
case Type::DontCare: return VK_ACCESS_NONE_KHR;
|
||||||
|
case Type::RenderTarget: return getReadOrWrite(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
|
||||||
|
case Type::DepthStencil: return getReadOrWrite(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
|
||||||
|
case Type::Multisample: return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
|
case Type::Presentation: return 0;
|
||||||
|
case Type::LinearAccess:
|
||||||
|
case Type::Sampled:
|
||||||
|
return location == Location::Host ? getReadOrWrite(VK_ACCESS_HOST_READ_BIT, VK_ACCESS_HOST_WRITE_BIT) : getReadOrWrite(VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
|
||||||
|
case Type::Transfer:
|
||||||
|
return getReadOrWrite(VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||||
|
case Type::InputAttachment:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ImageUsage(Type t, Access a, Location l, std::optional<int> att = std::nullopt)
|
||||||
|
: type(t), access(a), location(l), attachment(att) {}
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
Access access;
|
||||||
|
Location location;
|
||||||
|
std::optional<int> attachment;
|
||||||
|
|
||||||
|
inline VkAccessFlags getReadOrWrite(VkAccessFlags read, VkAccessFlags write) const {
|
||||||
|
VkAccessFlags flag = 0;
|
||||||
|
if (access == Access::ReadOnly || access == Access::ReadWrite)
|
||||||
|
flag |= read;
|
||||||
|
if (access == Access::WriteOnly || access == Access::ReadWrite)
|
||||||
|
flag |= write;
|
||||||
|
return flag;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how a single image will be used in each stage of a render pass.
|
||||||
|
* Allows a single image to be written to during one pass, read in a second, and presented in the final, and tracked.
|
||||||
|
* Helps figure out the optimizations that Vulkan can do to this image and the render passes that use it.
|
||||||
|
*/
|
||||||
|
class UsageTracker {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit UsageTracker(const ImageUsage& initial) : initialUsage(initial) {}
|
||||||
|
explicit UsageTracker() = default;
|
||||||
|
|
||||||
|
UsageTracker(UsageTracker&&) noexcept = default;
|
||||||
|
UsageTracker& operator=(UsageTracker&&) noexcept = default;
|
||||||
|
|
||||||
|
// Fluent API; chain calls in a builder pattern.
|
||||||
|
#define fluent UsageTracker&
|
||||||
|
|
||||||
|
fluent add(int pass, const ImageUsage& usage) {
|
||||||
|
usageAtSubpass.insert( { pass, usage} );
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent add(int start, int end, const ImageUsage& usage) {
|
||||||
|
for (int subpass = start; subpass <= end; ++subpass)
|
||||||
|
add(subpass, usage);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent addMultisample(int pass, std::string_view name) {
|
||||||
|
multisamples.insert( { pass, std::string(name) } );
|
||||||
|
return add(pass, ImageUsage::multisample());
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent setFinal(const ImageUsage& usage) {
|
||||||
|
finalUsage = usage;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<ImageUsage> getUsages() const {
|
||||||
|
size_t count = usageAtSubpass.size() + (finalUsage.has_value() ? 1 : 0) + 1;
|
||||||
|
|
||||||
|
std::vector<ImageUsage> usages;
|
||||||
|
usages.reserve(count);
|
||||||
|
usages.emplace_back(initialUsage);
|
||||||
|
|
||||||
|
for(const auto& pair : usageAtSubpass)
|
||||||
|
usages.emplace_back(pair.second);
|
||||||
|
|
||||||
|
if (finalUsage.has_value())
|
||||||
|
usages.emplace_back(finalUsage.value());
|
||||||
|
|
||||||
|
return usages;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::map<int, ImageUsage>& getUsageMap() const {
|
||||||
|
return usageAtSubpass;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageUsage& getInitialUsage() { return initialUsage; }
|
||||||
|
std::optional<ImageUsage> getFinalUsage() { return finalUsage; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::map<int, ImageUsage> usageAtSubpass;
|
||||||
|
ImageUsage initialUsage;
|
||||||
|
std::optional<ImageUsage> finalUsage;
|
||||||
|
std::map<int, std::string> multisamples;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple wrapper that allows tracking the current usage of multiple images.
|
||||||
|
*/
|
||||||
|
class MultiImageTracker {
|
||||||
|
public:
|
||||||
|
MultiImageTracker() = default;
|
||||||
|
|
||||||
|
MultiImageTracker(const MultiImageTracker&) = delete;
|
||||||
|
MultiImageTracker& operator=(const MultiImageTracker&) = delete;
|
||||||
|
|
||||||
|
// Fluent API; chain calls in a builder pattern
|
||||||
|
#undef fluent
|
||||||
|
#define fluent MultiImageTracker&
|
||||||
|
|
||||||
|
fluent track(std::string&& name, const ImageUsage& usage) {
|
||||||
|
images.insert( { std::move(name), usage } );
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent track(const std::string& name, const ImageUsage& usage) {
|
||||||
|
return track(std::string(name), usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent update(const std::string& name, const ImageUsage& usage) {
|
||||||
|
auto iter = images.find(name);
|
||||||
|
iter->second = usage;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool isTracking(const std::string& image) const {
|
||||||
|
return images.contains(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ImageUsage& get(const std::string& image) const {
|
||||||
|
return images.at(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, ImageUsage> images;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
struct Queue {
|
||||||
|
VkQueue queue;
|
||||||
|
int queueIndex;
|
||||||
|
};
|
||||||
|
}
|
19549
projs/shadow/shadow-engine/shadow-renderer/inc/vulkan/vk_mem_alloc.h
Normal file
19549
projs/shadow/shadow-engine/shadow-renderer/inc/vulkan/vk_mem_alloc.h
Normal file
File diff suppressed because it is too large
Load Diff
129
projs/shadow/shadow-engine/shadow-renderer/src/render/Camera.cpp
Normal file
129
projs/shadow/shadow-engine/shadow-renderer/src/render/Camera.cpp
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
#include <vlkx/render/Camera.h>
|
||||||
|
|
||||||
|
using namespace vlkx;
|
||||||
|
|
||||||
|
Camera& Camera::move(const glm::vec3 &delta) {
|
||||||
|
position += delta;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera &Camera::setPos(const glm::vec3 &pos) {
|
||||||
|
position = pos;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera &Camera::up(const glm::vec3 &up) {
|
||||||
|
upVector = glm::normalize(up);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera &Camera::forward(const glm::vec3 &forward) {
|
||||||
|
frontVector = glm::normalize(forward);
|
||||||
|
rightVector = glm::normalize(glm::cross(frontVector, upVector));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 Camera::getViewMatrix() const {
|
||||||
|
return glm::lookAt(position, position + frontVector, upVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
PerspectiveCamera &PerspectiveCamera::fieldOfView(float newFov) {
|
||||||
|
fov = newFov;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PerspectiveCamera::RT PerspectiveCamera::getRT() const {
|
||||||
|
const glm::vec3 upVec = glm::normalize(glm::cross(getRight(), getForward()));
|
||||||
|
const float fovTan = glm::tan(glm::radians(fov));
|
||||||
|
return { upVec * fovTan, getForward(), getRight() * fovTan * aspectRatio };
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 PerspectiveCamera::getProjMatrix() const {
|
||||||
|
glm::mat4 proj = glm::perspective(glm::radians(fov), aspectRatio, nearPlane, farPlane);
|
||||||
|
proj[1][1] = -proj[1][1];
|
||||||
|
return proj;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthographicCamera &OrthographicCamera::setWidth(float vWidth) {
|
||||||
|
width = vWidth;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 OrthographicCamera::getProjMatrix() const {
|
||||||
|
const float height = width / aspectRatio;
|
||||||
|
const auto halfSize = glm::vec2 { width, height } / 2.0f;
|
||||||
|
return glm::ortho(-halfSize.x, halfSize.x, -halfSize.y, halfSize.y, nearPlane, farPlane);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
void UserCamera<Type>::setInternal(std::function<void(Type *)> op) {
|
||||||
|
op(camera.get());
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
void UserCamera<Type>::move(double x, double y) {
|
||||||
|
if (!isActive) return;
|
||||||
|
|
||||||
|
const auto offsetX = static_cast<float>(x * config.turnSpeed);
|
||||||
|
const auto offsetY = static_cast<float>(y * config.turnSpeed);
|
||||||
|
|
||||||
|
pitch = glm::clamp(pitch - offsetY, glm::radians(-89.9f), glm::radians(89.9f));
|
||||||
|
yaw = glm::mod(yaw - offsetX, glm::radians(360.0f));
|
||||||
|
camera->forward( { glm::cos(pitch) * glm::cos(yaw), glm::sin(pitch), glm::cos(pitch) * glm::sin(yaw) });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
bool UserCamera<Type>::scroll(double delta, double min, double max) {
|
||||||
|
if (!isActive) return false;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<Type, PerspectiveCamera>) {
|
||||||
|
auto newFov = (float) glm::clamp(camera->getFieldOfView() + delta, min, max);
|
||||||
|
if (newFov != camera->getFieldOfView()) {
|
||||||
|
camera->fieldOfView(newFov);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if constexpr (std::is_same_v<Type, OrthographicCamera>) {
|
||||||
|
const auto newWidth = (float) glm::clamp(camera->getWidth() + delta, min, max);
|
||||||
|
if (newWidth != camera->getWidth()) {
|
||||||
|
camera->setWidth(newWidth);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
static_assert("Unhandled Camera Type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
void UserCamera<Type>::press(Camera::Input key, float time) {
|
||||||
|
using Key = Camera::Input;
|
||||||
|
if (!isActive) return;
|
||||||
|
|
||||||
|
if (!config.center.has_value()) {
|
||||||
|
const float distance = time * config.moveSpeed;
|
||||||
|
switch (key) {
|
||||||
|
case Key::Up:
|
||||||
|
camera->move(+camera->getForward() * distance); break;
|
||||||
|
case Key::Down:
|
||||||
|
camera->move(-camera->getForward() * distance); break;
|
||||||
|
case Key::Left:
|
||||||
|
camera->move(-camera->getRight() * distance); break;
|
||||||
|
case Key::Right:
|
||||||
|
camera->move(+camera->getRight() * distance); break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
void UserCamera<Type>::reset() {
|
||||||
|
refForward = camera->getForward();
|
||||||
|
refLeft = -camera->getRight();
|
||||||
|
pitch = yaw = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template class vlkx::UserCamera<PerspectiveCamera>;
|
||||||
|
template class vlkx::UserCamera<OrthographicCamera>;
|
|
@ -0,0 +1,156 @@
|
||||||
|
#include <vlkx/render/Geometry.h>
|
||||||
|
using namespace Geo;
|
||||||
|
|
||||||
|
void Mesh::setTriData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
|
||||||
|
std::vector<VertexAll> Vertices = {
|
||||||
|
{ { 0.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } },
|
||||||
|
{ { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } },
|
||||||
|
{ { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint32_t> Indices = {
|
||||||
|
0, 1, 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
vertices.clear(); indices.clear();
|
||||||
|
|
||||||
|
vertices = Vertices;
|
||||||
|
indices = Indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::setQuadData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
|
||||||
|
|
||||||
|
std::vector<VertexAll> Vertices = {
|
||||||
|
{ { -1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } },
|
||||||
|
{ { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } },
|
||||||
|
{ { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } },
|
||||||
|
{ { 1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint32_t> Indices = {
|
||||||
|
0, 1, 2,
|
||||||
|
0, 2, 3
|
||||||
|
};
|
||||||
|
|
||||||
|
vertices.clear(); indices.clear();
|
||||||
|
vertices = Vertices;
|
||||||
|
indices = Indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::setCubeData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
|
||||||
|
std::vector<VertexAll> Vertices = {
|
||||||
|
// Front
|
||||||
|
{ { -1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 0
|
||||||
|
{ { -1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 1
|
||||||
|
{ { 1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 2
|
||||||
|
{ { 1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 3
|
||||||
|
// Back
|
||||||
|
{ { 1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0, 1.0 } }, // 4
|
||||||
|
{ { 1.0f, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0, 0.0 } }, // 5
|
||||||
|
{ { -1.0, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0, 0.0 } }, // 6
|
||||||
|
{ { -1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0, 1.0 } }, // 7
|
||||||
|
// Left
|
||||||
|
{ { -1.0, -1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 8
|
||||||
|
{ { -1.0f, 1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0, 0.0 } }, // 9
|
||||||
|
{ { -1.0, 1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 1.0, 0.0 } }, // 10
|
||||||
|
{ { -1.0, -1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 1.0, 1.0 } }, // 11
|
||||||
|
// Right
|
||||||
|
{ { 1.0, -1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 12
|
||||||
|
{ { 1.0f, 1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 0.0 } }, // 13
|
||||||
|
{ { 1.0, 1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 1.0, 0.0 } }, // 14
|
||||||
|
{ { 1.0, -1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 1.0, 1.0 } }, // 15
|
||||||
|
// Top
|
||||||
|
{ { -1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0, 1.0 } }, // 16
|
||||||
|
{ { -1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } }, // 17
|
||||||
|
{ { 1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 1.0, 0.0 } }, // 18
|
||||||
|
{ { 1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 1.0, 1.0 } }, // 19
|
||||||
|
// Bottom
|
||||||
|
{ { -1.0f, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0, 1.0 } }, // 20
|
||||||
|
{ { -1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0, 0.0 } }, // 21
|
||||||
|
{ { 1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 1.0, 0.0 } }, // 22
|
||||||
|
{ { 1.0, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 1.0, 1.0 } }, // 23
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint32_t> Indices = {
|
||||||
|
0, 1, 2,
|
||||||
|
2, 3, 0,
|
||||||
|
|
||||||
|
4, 5, 6,
|
||||||
|
4, 6, 7,
|
||||||
|
|
||||||
|
8, 9, 10,
|
||||||
|
8, 10, 11,
|
||||||
|
|
||||||
|
12, 13, 14,
|
||||||
|
12, 14, 15,
|
||||||
|
|
||||||
|
16, 17, 18,
|
||||||
|
16, 18, 19,
|
||||||
|
|
||||||
|
20, 21, 22,
|
||||||
|
20, 22, 23
|
||||||
|
};
|
||||||
|
|
||||||
|
vertices.clear(); indices.clear();
|
||||||
|
vertices = Vertices;
|
||||||
|
indices = Indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Mesh::setSphereData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
|
||||||
|
std::vector<VertexAll> Vertices;
|
||||||
|
std::vector<uint32_t> Indices;
|
||||||
|
|
||||||
|
float latitudeBands = 20.0f;
|
||||||
|
float longitudeBands = 20.0f;
|
||||||
|
float radius = 1.0f;
|
||||||
|
|
||||||
|
for (float latNumber = 0; latNumber <= latitudeBands; latNumber++) {
|
||||||
|
float theta = latNumber * 3.14 / latitudeBands;
|
||||||
|
float sinTheta = sin(theta);
|
||||||
|
float cosTheta = cos(theta);
|
||||||
|
|
||||||
|
for (float longNumber = 0; longNumber <= longitudeBands; longNumber++) {
|
||||||
|
|
||||||
|
float phi = longNumber * 2 * 3.147 / longitudeBands;
|
||||||
|
float sinPhi = sin(phi);
|
||||||
|
float cosPhi = cos(phi);
|
||||||
|
|
||||||
|
VertexAll vs;
|
||||||
|
|
||||||
|
vs.texture.x = (longNumber / longitudeBands); // u
|
||||||
|
vs.texture.y = (latNumber / latitudeBands); // v
|
||||||
|
|
||||||
|
vs.normal.x = cosPhi * sinTheta; // normal x
|
||||||
|
vs.normal.y = cosTheta; // normal y
|
||||||
|
vs.normal.z = sinPhi * sinTheta; // normal z
|
||||||
|
|
||||||
|
vs.position.x = radius * vs.normal.x; // x
|
||||||
|
vs.position.y = radius * vs.normal.y; // y
|
||||||
|
vs.position.z = radius * vs.normal.z; // z
|
||||||
|
|
||||||
|
Vertices.push_back(vs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t latNumber = 0; latNumber < latitudeBands; latNumber++) {
|
||||||
|
for (uint32_t longNumber = 0; longNumber < longitudeBands; longNumber++) {
|
||||||
|
uint32_t first = (latNumber * (longitudeBands + 1)) + longNumber;
|
||||||
|
uint32_t second = first + longitudeBands + 1;
|
||||||
|
|
||||||
|
Indices.push_back(first);
|
||||||
|
Indices.push_back(second);
|
||||||
|
Indices.push_back(first + 1);
|
||||||
|
|
||||||
|
Indices.push_back(second);
|
||||||
|
Indices.push_back(second + 1);
|
||||||
|
Indices.push_back(first + 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices.clear(); indices.clear();
|
||||||
|
vertices = Vertices;
|
||||||
|
indices = Indices;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,312 @@
|
||||||
|
#include "vlkx/render/shader/Pipeline.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
#include "shadow/util/File.h"
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
struct ShaderStage {
|
||||||
|
VkShaderStageFlagBits stage;
|
||||||
|
ShaderModule::CountedShader module;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipelineViewportStateCreateInfo createViewport(const GraphicsPipelineBuilder::Viewport& port) {
|
||||||
|
return {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, 1, &port.viewport, 1, &port.scissor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPipelineColorBlendStateCreateInfo createBlend(const std::vector<VkPipelineColorBlendAttachmentState>& states) {
|
||||||
|
return {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_FALSE, VK_LOGIC_OP_CLEAR, static_cast<uint32_t>(states.size()), states.data(),
|
||||||
|
{ 0, 0, 0, 0 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPipelineVertexInputStateCreateInfo createVertexInput(const std::vector<VkVertexInputBindingDescription>& bindingDescs,
|
||||||
|
const std::vector<VkVertexInputAttributeDescription>& attrDescs) {
|
||||||
|
return {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0,
|
||||||
|
static_cast<uint32_t>(bindingDescs.size()), bindingDescs.data(),
|
||||||
|
static_cast<uint32_t>(attrDescs.size()), attrDescs.data()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ShaderStage> createShader(const std::map<VkShaderStageFlagBits, std::string>& shaderMap) {
|
||||||
|
std::vector<ShaderStage> stages;
|
||||||
|
stages.reserve(shaderMap.size());
|
||||||
|
for (const auto& pair : shaderMap) {
|
||||||
|
stages.push_back({ pair.first, ShaderModule::CountedShader::get(pair.second, pair.second) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return stages;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkPipelineShaderStageCreateInfo> createShaderStage(const std::vector<ShaderStage>& stages) {
|
||||||
|
static constexpr char entryPoint[] = "main";
|
||||||
|
std::vector<VkPipelineShaderStageCreateInfo> infos;
|
||||||
|
infos.reserve(stages.size());
|
||||||
|
for (const auto& stage : stages) {
|
||||||
|
infos.push_back({
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||||
|
nullptr, 0, stage.stage, **stage.module, entryPoint, nullptr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderModule::ShaderModule(const std::string &path) {
|
||||||
|
const shadowutil::FileData* file = shadowutil::loadFile(path);
|
||||||
|
const VkShaderModuleCreateInfo module {
|
||||||
|
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||||
|
nullptr, 0, file->size, reinterpret_cast<const uint32_t*>(file->data.data())
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vkCreateShaderModule(VulkanModule::getInstance()->getDevice()->logical, &module, nullptr, &shader) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create shader module");
|
||||||
|
}
|
||||||
|
|
||||||
|
PipelineBuilder::PipelineBuilder(std::optional<int> maxCache) {
|
||||||
|
const VkPipelineCacheCreateInfo info {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
|
||||||
|
nullptr, 0, static_cast<size_t>(maxCache.value_or(0)), nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vkCreatePipelineCache(VulkanModule::getInstance()->getDevice()->logical, &info, nullptr, &cache) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create pipeline cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineBuilder::setLayout(std::vector<VkDescriptorSetLayout>&& descs,
|
||||||
|
std::vector<VkPushConstantRange>&& pushConstants) {
|
||||||
|
std::vector<int> pushSizes (pushConstants.size());
|
||||||
|
for (size_t i = 0; i < pushConstants.size(); i++)
|
||||||
|
pushSizes[i] = pushConstants[i].size;
|
||||||
|
|
||||||
|
const auto totalSize = std::accumulate(pushSizes.begin(), pushSizes.end(), 0);
|
||||||
|
if (totalSize > 128)
|
||||||
|
throw std::runtime_error("Trying to set push constants of total size " + std::to_string(totalSize) + " into pipeline " + name);
|
||||||
|
|
||||||
|
descLayouts = std::move(descs);
|
||||||
|
constants = std::move(pushConstants);
|
||||||
|
layoutInfo.emplace(VkPipelineLayoutCreateInfo {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||||
|
nullptr, 0, static_cast<uint32_t>(descLayouts.size()), descLayouts.data(),
|
||||||
|
static_cast<uint32_t>(constants.size()), constants.data()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder::GraphicsPipelineBuilder(std::optional<int> maxCache) : PipelineBuilder(maxCache) {
|
||||||
|
assemblyInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
rasterizationInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_FALSE, VK_FALSE, VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||||
|
VK_FALSE, 0, 0, 0, 1
|
||||||
|
};
|
||||||
|
|
||||||
|
multisampleInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_SAMPLE_COUNT_1_BIT, VK_FALSE, 0, nullptr, VK_FALSE, VK_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
depthStencilInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_FALSE, VK_FALSE,
|
||||||
|
VK_COMPARE_OP_LESS_OR_EQUAL, VK_FALSE, VK_FALSE,
|
||||||
|
{}, {}, 0, 1
|
||||||
|
};
|
||||||
|
|
||||||
|
dynamicStateInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, 0, nullptr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::name(std::string &&name) {
|
||||||
|
setName(std::move(name));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::depthTest(bool enable, bool write) {
|
||||||
|
depthStencilInfo.depthTestEnable = enable;
|
||||||
|
depthStencilInfo.depthWriteEnable = write;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::stencilTest(bool enable) {
|
||||||
|
depthStencilInfo.stencilTestEnable = enable;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::multiSample(VkSampleCountFlagBits samples) {
|
||||||
|
multisampleInfo.rasterizationSamples = samples;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::topology(VkPrimitiveTopology topology) {
|
||||||
|
assemblyInfo.topology = topology;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::stencilOp(const VkStencilOpState &state,
|
||||||
|
VkStencilFaceFlags flags) {
|
||||||
|
if (flags & VK_STENCIL_FACE_FRONT_BIT)
|
||||||
|
depthStencilInfo.front = state;
|
||||||
|
if (flags & VK_STENCIL_FACE_BACK_BIT)
|
||||||
|
depthStencilInfo.back = state;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::addVertex(uint32_t bindPoint,
|
||||||
|
VkVertexInputBindingDescription &&desc,
|
||||||
|
std::vector<VkVertexInputAttributeDescription> &&attrs) {
|
||||||
|
desc.binding = bindPoint;
|
||||||
|
for (auto& attr : attrs)
|
||||||
|
attr.binding = bindPoint;
|
||||||
|
bindingDescs.push_back(desc);
|
||||||
|
|
||||||
|
attrDescs.reserve(attrDescs.size() + attrs.size());
|
||||||
|
std::move(attrs.begin(), attrs.end(), std::back_inserter(attrDescs));
|
||||||
|
attrs.clear();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::layout(std::vector<VkDescriptorSetLayout> &&descLayouts,
|
||||||
|
std::vector<VkPushConstantRange> &&constants) {
|
||||||
|
setLayout(std::move(descLayouts), std::move(constants));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::viewport(const vlkx::GraphicsPipelineBuilder::Viewport &port,
|
||||||
|
bool flipY) {
|
||||||
|
viewportMeta.emplace(port);
|
||||||
|
|
||||||
|
if (flipY) {
|
||||||
|
VkViewport& view = viewportMeta.value().viewport;
|
||||||
|
view.y += view.height;
|
||||||
|
view.height *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rasterizationInfo.frontFace = flipY ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::renderPass(const VkRenderPass &pass, uint32_t subpass) {
|
||||||
|
passMeta.emplace(PassInfo { pass, subpass });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::colorBlend(
|
||||||
|
std::vector<VkPipelineColorBlendAttachmentState> &&states) {
|
||||||
|
blendStates = std::move(states);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::shader(VkShaderStageFlagBits stage, std::string &&file) {
|
||||||
|
shaders[stage] = std::move(file);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Pipeline> GraphicsPipelineBuilder::build() const {
|
||||||
|
if (!hasLayout())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no layout set");
|
||||||
|
if (!viewportMeta.has_value())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no viewport set");
|
||||||
|
if (!passMeta.has_value())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no render pass set");
|
||||||
|
if (blendStates.empty())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no color blend states.");
|
||||||
|
if (shaders.empty())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no shaders bound");
|
||||||
|
|
||||||
|
const auto viewportState = createViewport(viewportMeta.value());
|
||||||
|
const auto blendState = createBlend(blendStates);
|
||||||
|
const auto vertexState = createVertexInput(bindingDescs, attrDescs);
|
||||||
|
|
||||||
|
const auto shaderStages = createShader(shaders);
|
||||||
|
const auto shaderStageInfo = createShaderStage(shaderStages);
|
||||||
|
VkPipelineLayout pipelineLayout;
|
||||||
|
if (vkCreatePipelineLayout(VulkanModule::getInstance()->getDevice()->logical, &getLayout(), nullptr, &pipelineLayout) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create layout for pipeline " + getName());
|
||||||
|
|
||||||
|
const VkGraphicsPipelineCreateInfo createInfo {
|
||||||
|
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, nullptr, 0,
|
||||||
|
static_cast<uint32_t>(shaderStageInfo.size()), shaderStageInfo.data(),
|
||||||
|
&vertexState, &assemblyInfo, nullptr, &viewportState,
|
||||||
|
&rasterizationInfo, &multisampleInfo, &depthStencilInfo, &blendState, &dynamicStateInfo,
|
||||||
|
pipelineLayout, passMeta->pass, passMeta->subpass, VK_NULL_HANDLE, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipeline pipeline;
|
||||||
|
if (vkCreateGraphicsPipelines(VulkanModule::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &createInfo, nullptr, &pipeline) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Failed to create pipeline " + getName());
|
||||||
|
|
||||||
|
return std::unique_ptr<Pipeline> {
|
||||||
|
new Pipeline(getName(), pipeline, pipelineLayout, VK_PIPELINE_BIND_POINT_GRAPHICS)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipelineBuilder& ComputePipelineBuilder::name(std::string &&name) {
|
||||||
|
setName(std::move(name));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipelineBuilder& ComputePipelineBuilder::layout(std::vector<VkDescriptorSetLayout> &&descLayouts,
|
||||||
|
std::vector<VkPushConstantRange> &&pushConstants) {
|
||||||
|
setLayout(std::move(descLayouts), std::move(pushConstants));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipelineBuilder& ComputePipelineBuilder::shader(std::string &&path) {
|
||||||
|
shaderPath.emplace(std::move(path));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Pipeline> ComputePipelineBuilder::build() const {
|
||||||
|
if (!hasLayout())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no layout set");
|
||||||
|
if (!shaderPath.has_value())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no shader set");
|
||||||
|
|
||||||
|
const auto shaderStages = createShader({{VK_SHADER_STAGE_COMPUTE_BIT, shaderPath.value()}});
|
||||||
|
const auto shaderStageInfo = createShaderStage(shaderStages);
|
||||||
|
if (shaderStageInfo.size() != 1)
|
||||||
|
throw std::runtime_error("Compute pipeline " + getName() + " must have exactly one shader bound");
|
||||||
|
|
||||||
|
VkPipelineLayout layout;
|
||||||
|
if (vkCreatePipelineLayout(VulkanModule::getInstance()->getDevice()->logical, &getLayout(), nullptr,
|
||||||
|
&layout) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create layout for compute pipeline " + getName());
|
||||||
|
|
||||||
|
const VkComputePipelineCreateInfo createInfo{
|
||||||
|
VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||||
|
nullptr, 0,
|
||||||
|
shaderStageInfo[0], layout, VK_NULL_HANDLE, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipeline pipeline;
|
||||||
|
if (vkCreateComputePipelines(VulkanModule::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &createInfo,
|
||||||
|
nullptr, &pipeline) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create compute pipeline " + getName());
|
||||||
|
|
||||||
|
return std::unique_ptr<Pipeline>{
|
||||||
|
new Pipeline{getName(), pipeline, layout, VK_PIPELINE_BIND_POINT_COMPUTE}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pipeline::bind(const VkCommandBuffer &buffer) const {
|
||||||
|
vkCmdBindPipeline(buffer, bindPoint, pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipeline::~Pipeline() {
|
||||||
|
vkDestroyPipeline(VulkanModule::getInstance()->getDevice()->logical, pipeline, nullptr);
|
||||||
|
vkDestroyPipelineLayout(VulkanModule::getInstance()->getDevice()->logical, layout, nullptr);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,394 @@
|
||||||
|
#include "vlkx/render/render_pass/GPUPass.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
inline bool needsSynchronization(const ImageUsage& prev, const ImageUsage& curr) {
|
||||||
|
if (curr == prev && curr.getAccess() == ImageUsage::Access::ReadOnly)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addUsageToSubpass(const ImageUsage& usage, RenderPassBuilder::SubpassDependency::SubpassMeta* pass) {
|
||||||
|
pass->stage |= usage.getStage();
|
||||||
|
pass->access |= usage.getAccessFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommonPass::addUsage(std::string &&name, UsageTracker &&tracker) {
|
||||||
|
for (const auto& pair : tracker.getUsageMap())
|
||||||
|
validate(pair.first, name, false);
|
||||||
|
|
||||||
|
tracker.add(getVirtualInitial(), tracker.getInitialUsage());
|
||||||
|
if (tracker.getFinalUsage().has_value())
|
||||||
|
tracker.add(getVirtualFinal(), tracker.getFinalUsage().value());
|
||||||
|
|
||||||
|
usageHistory.emplace(std::move(name), std::move(tracker));
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageLayout CommonPass::getInitialLayout(const std::string &name) const {
|
||||||
|
return getHistory(name).getUsageMap().begin()->second.getLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageLayout CommonPass::getFinalLayout(const std::string &name) const {
|
||||||
|
return getHistory(name).getUsageMap().rbegin()->second.getLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageLayout CommonPass::getSubpassLayout(const std::string &name, int subpass) const {
|
||||||
|
validate(subpass, name, false);
|
||||||
|
return getUsage(name, subpass)->getLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommonPass::update(const std::string &name, MultiImageTracker &tracker) const {
|
||||||
|
tracker.update(name, getHistory(name).getUsageMap().rbegin()->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
const UsageTracker& CommonPass::getHistory(const std::string &name) const {
|
||||||
|
return usageHistory.at(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageUsage* CommonPass::getUsage(const std::string &name, int pass) const {
|
||||||
|
validate(pass, name, true);
|
||||||
|
const UsageTracker& history = getHistory(name);
|
||||||
|
const auto iter = history.getUsageMap().find(pass);
|
||||||
|
return iter != history.getUsageMap().end() ? &iter->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CommonPass::Usages> CommonPass::checkForSync(const std::string &name, int pass) const {
|
||||||
|
validate(pass, name, true);
|
||||||
|
const UsageTracker& history = getHistory(name);
|
||||||
|
const auto currIter = history.getUsageMap().find(pass);
|
||||||
|
if (currIter == history.getUsageMap().end())
|
||||||
|
return std::nullopt;
|
||||||
|
const auto prevIter = std::prev(currIter);
|
||||||
|
|
||||||
|
const ImageUsage& prevUsage = prevIter->second;
|
||||||
|
const ImageUsage& currUsage = currIter->second;
|
||||||
|
|
||||||
|
if (!needsSynchronization(prevUsage, currUsage))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
const int prevSubpass = prevIter->first;
|
||||||
|
return CommonPass::Usages { prevSubpass, &prevUsage, &currUsage };
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommonPass::validate(int pass, const std::string &image, bool includeVirtual) const {
|
||||||
|
if (includeVirtual) {
|
||||||
|
if (!(pass >= getVirtualInitial() && pass <= getVirtualFinal()))
|
||||||
|
throw std::runtime_error("Subpass out of range.");
|
||||||
|
} else {
|
||||||
|
if (!(pass >= 0 && pass < numPasses))
|
||||||
|
throw std::runtime_error("nv Subpass out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GraphicsPass::add(const std::string &name, UsageTracker &&history, std::function<int(int)> &&getter,
|
||||||
|
const std::optional<RenderPassBuilder::Attachment::OpsType> ops) {
|
||||||
|
verifyHistory(name, history);
|
||||||
|
|
||||||
|
const std::optional<int> needsGetter = getFirstRenderTarget(history);
|
||||||
|
if (needsGetter.has_value()) {
|
||||||
|
if (getter == nullptr)
|
||||||
|
throw std::runtime_error("Image " + name + " is used as a render target without a location getter.");
|
||||||
|
} else {
|
||||||
|
getter = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int attachmentLocation = static_cast<int>(metas.size());
|
||||||
|
metas.insert(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
AttachmentMeta {
|
||||||
|
attachmentLocation,
|
||||||
|
std::move(getter),
|
||||||
|
getOps(name, history, ops),
|
||||||
|
{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
addUsage(std::string(name), std::move(history));
|
||||||
|
return attachmentLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPass& GraphicsPass::addMultisample(const std::string &source, const std::string &dest, int pass) {
|
||||||
|
validate(pass, source, false);
|
||||||
|
|
||||||
|
const auto source_iter = usageHistory.find(source);
|
||||||
|
if (source_iter == usageHistory.end())
|
||||||
|
throw std::runtime_error("Usage history not found for source image " + source);
|
||||||
|
|
||||||
|
const UsageTracker& source_history = source_iter->second;
|
||||||
|
if (!verifyImageUsage(source_history, pass, ImageUsage::Type::RenderTarget))
|
||||||
|
throw std::runtime_error("Usage type for source image " + source + " must be render target.");
|
||||||
|
|
||||||
|
const auto dest_iter = usageHistory.find(dest);
|
||||||
|
if (dest_iter == usageHistory.end())
|
||||||
|
throw std::runtime_error("Usage history not found for destination image " + dest);
|
||||||
|
|
||||||
|
const UsageTracker& dest_history = dest_iter->second;
|
||||||
|
if (!verifyImageUsage(dest_history, pass, ImageUsage::Type::Multisample))
|
||||||
|
throw std::runtime_error("Usage type for destination image " + dest + " must be multisample");
|
||||||
|
|
||||||
|
auto& targetMap = metas[source].multisample;
|
||||||
|
const bool inserted = targetMap.insert( { pass, dest }).second;
|
||||||
|
|
||||||
|
if (!inserted)
|
||||||
|
throw std::runtime_error("Image " + source + " is already bound to a multisample.");
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPass::setAttachments() {
|
||||||
|
for (const auto& pair : usageHistory) {
|
||||||
|
const std::string& name = pair.first;
|
||||||
|
const AttachmentMeta& meta = metas[name];
|
||||||
|
builder->setAttachment(meta.index, { meta.ops, getInitialLayout(name), getFinalLayout(name) } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPass::setSubpasses() {
|
||||||
|
for (int pass = 0; pass < numPasses; ++pass) {
|
||||||
|
std::vector<RenderPassBuilder::ColorAttachmentMeta> colors;
|
||||||
|
std::vector<RenderPassBuilder::ColorAttachmentMeta> multisamples;
|
||||||
|
VkAttachmentReference dsRef { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED };
|
||||||
|
|
||||||
|
// Verify all images used, the long way around.
|
||||||
|
for (const auto& pair : usageHistory) {
|
||||||
|
const std::string& name = pair.first;
|
||||||
|
const ImageUsage* history = getUsage(name, pass);
|
||||||
|
if (history == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const AttachmentMeta& meta = metas[name];
|
||||||
|
const VkAttachmentReference ref { static_cast<uint32_t>(meta.index), history->getLayout() };
|
||||||
|
|
||||||
|
switch (history->getType()) {
|
||||||
|
// If we're looking at a render target, we need to figure out where it gets its' details from.
|
||||||
|
case ImageUsage::Type::RenderTarget: {
|
||||||
|
const int location = meta.getter(pass);
|
||||||
|
const auto iter = meta.multisample.find(pass);
|
||||||
|
if (iter != meta.multisample.end()) {
|
||||||
|
const std::string& target = iter->second;
|
||||||
|
const ImageUsage* targetUsage = getUsage(target, pass);
|
||||||
|
if (targetUsage == nullptr)
|
||||||
|
throw std::runtime_error("Expected target image to have a usage");
|
||||||
|
multisamples.push_back( { location, metas[target].index, targetUsage->getLayout() });
|
||||||
|
}
|
||||||
|
|
||||||
|
colors.push_back( { location, static_cast<int>(ref.attachment), ref.layout });
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ImageUsage::Type::DepthStencil:
|
||||||
|
if (dsRef.attachment != VK_ATTACHMENT_UNUSED)
|
||||||
|
throw std::runtime_error("Depth stencil used multiple times");
|
||||||
|
dsRef = ref;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ImageUsage::Type::Multisample:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unreachable?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto colorRefs = RenderPassBuilder::parseColorReferences(colors);
|
||||||
|
auto multisampleRefs = RenderPassBuilder::parseMutisampling(colorRefs.size(), multisamples);
|
||||||
|
builder->setSubpass(pass, std::move(colorRefs), std::move(multisampleRefs), dsRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPass::setDependencies() {
|
||||||
|
if (getVirtualFinal() != numPasses)
|
||||||
|
throw std::runtime_error("Virtual subpass mismatch");
|
||||||
|
for (int pass = 0; pass <= numPasses; ++pass) {
|
||||||
|
std::map<int, RenderPassBuilder::SubpassDependency> deps;
|
||||||
|
|
||||||
|
for (const auto& pair : usageHistory) {
|
||||||
|
const auto usage = checkForSync(pair.first, pass);
|
||||||
|
if (!usage.has_value()) continue;
|
||||||
|
|
||||||
|
const ImageUsage& prev = usage.value().lastUsage;
|
||||||
|
const ImageUsage& curr = usage.value().currentUsage;
|
||||||
|
const int sourcePass = usage.value().lastSubpass;
|
||||||
|
|
||||||
|
auto iter = deps.find(sourcePass);
|
||||||
|
if (iter == deps.end()) {
|
||||||
|
const RenderPassBuilder::SubpassDependency defaultDep {
|
||||||
|
{ checkSubpass(sourcePass), VK_PIPELINE_STAGE_NONE_KHR, VK_ACCESS_NONE_KHR },
|
||||||
|
{ checkSubpass(pass), VK_PIPELINE_STAGE_NONE_KHR, VK_ACCESS_NONE_KHR },
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
iter = deps.insert( { sourcePass, defaultDep } ).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
addUsageToSubpass(prev, &iter->second.source);
|
||||||
|
addUsageToSubpass(curr, &iter->second.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& pair : deps) {
|
||||||
|
const RenderPassBuilder::SubpassDependency& dep = pair.second;
|
||||||
|
builder->addDependency(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RenderPassBuilder> GraphicsPass::build(int framebuffers) {
|
||||||
|
builder = std::make_unique<RenderPassBuilder>();
|
||||||
|
builder->setFramebufferCount(framebuffers);
|
||||||
|
|
||||||
|
setAttachments();
|
||||||
|
setSubpasses();
|
||||||
|
setDependencies();
|
||||||
|
return std::move(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> GraphicsPass::getFirstRenderTarget(const UsageTracker &history) const {
|
||||||
|
for (const auto& pair : history.getUsageMap()) {
|
||||||
|
const int pass = pair.first;
|
||||||
|
if (isVirtual(pass)) continue;
|
||||||
|
|
||||||
|
if (pair.second.getType() == ImageUsage::Type::RenderTarget) return pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPassBuilder::Attachment::OpsType GraphicsPass::getOps(const std::string &name, const UsageTracker &history,
|
||||||
|
const std::optional<RenderPassBuilder::Attachment::OpsType> &userOps) const {
|
||||||
|
const ImageUsage::Type type = getUsageType(name, history);
|
||||||
|
|
||||||
|
if (!userOps.has_value()) {
|
||||||
|
switch (type) {
|
||||||
|
case ImageUsage::Type::RenderTarget: return getDefaultOps();
|
||||||
|
case ImageUsage::Type::DepthStencil: return getStencilOps();
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unreachable?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderPassBuilder::Attachment::OpsType& ops = userOps.value();
|
||||||
|
switch (type) {
|
||||||
|
case ImageUsage::Type::RenderTarget:
|
||||||
|
case ImageUsage::Type::DepthStencil:
|
||||||
|
return ops;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unreachable?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageUsage::Type GraphicsPass::getUsageType(const std::string &name, const UsageTracker &history) const {
|
||||||
|
ImageUsage::Type prev = ImageUsage::Type::DontCare;
|
||||||
|
|
||||||
|
for (const auto& pair : history.getUsageMap()) {
|
||||||
|
if (isVirtual(pair.first)) continue;
|
||||||
|
|
||||||
|
ImageUsage::Type type = pair.second.getType();
|
||||||
|
if (type == ImageUsage::Type::Multisample)
|
||||||
|
type = ImageUsage::Type::RenderTarget;
|
||||||
|
|
||||||
|
if (prev == ImageUsage::Type::DontCare) {
|
||||||
|
prev = type;
|
||||||
|
} else if (type != prev) {
|
||||||
|
throw std::runtime_error("Inconsistent usage type specified for " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev == ImageUsage::Type::DontCare)
|
||||||
|
throw std::runtime_error("Image " + name + " has no usages.");
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GraphicsPass::verifyImageUsage(const UsageTracker &history, int subpass, ImageUsage::Type type) const {
|
||||||
|
const auto iter = history.getUsageMap().find(subpass);
|
||||||
|
return iter != history.getUsageMap().end() && iter->second.getType() == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPass::verifyHistory(const std::string &image, const UsageTracker &history) const {
|
||||||
|
for (const auto& pair : history.getUsageMap()) {
|
||||||
|
const ImageUsage::Type type = pair.second.getType();
|
||||||
|
if (type != ImageUsage::Type::RenderTarget && type != ImageUsage::Type::DepthStencil && type != ImageUsage::Type::Multisample)
|
||||||
|
throw std::runtime_error("Invalid usage of " + image + " at subpass " + std::to_string(pair.first));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePass &ComputePass::add(std::string &&name, UsageTracker &&history) {
|
||||||
|
verify(name, history);
|
||||||
|
addUsage(std::move(name), std::move(history));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputePass::execute(const VkCommandBuffer &commands, uint32_t queueFamily,
|
||||||
|
const std::map<std::string, const VkImage*> &images,
|
||||||
|
const std::vector<std::function<void()>>& computeOps) const {
|
||||||
|
|
||||||
|
if (computeOps.size() != numPasses)
|
||||||
|
throw std::runtime_error("Compute shader mismatches ops and passes.");
|
||||||
|
|
||||||
|
if (getVirtualFinal() != numPasses)
|
||||||
|
throw std::runtime_error("Compute shader attempting to run too many subpasses");
|
||||||
|
|
||||||
|
for (int pass = 0; pass < numPasses; ++pass) {
|
||||||
|
for (const auto& pair : usageHistory) {
|
||||||
|
const std::string& image = pair.first;
|
||||||
|
const auto usage = checkForSync(image, pass);
|
||||||
|
if (!usage.has_value()) continue;
|
||||||
|
|
||||||
|
const auto iter = images.find(image);
|
||||||
|
if (iter == images.end())
|
||||||
|
throw std::runtime_error("Image " + image + " not provided");
|
||||||
|
|
||||||
|
barrier(commands, queueFamily, *iter->second, usage.value().lastUsage, usage.value().currentUsage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pass < numPasses)
|
||||||
|
computeOps[pass]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputePass::barrier(const VkCommandBuffer &commands, uint32_t queueFamily, const VkImage &image,
|
||||||
|
const ImageUsage &prev, const ImageUsage ¤t) const {
|
||||||
|
const VkImageMemoryBarrier barrier {
|
||||||
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
|
nullptr,
|
||||||
|
prev.getAccessFlags(),
|
||||||
|
current.getAccessFlags(),
|
||||||
|
prev.getLayout(),
|
||||||
|
current.getLayout(),
|
||||||
|
queueFamily,
|
||||||
|
queueFamily,
|
||||||
|
image,
|
||||||
|
{
|
||||||
|
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdPipelineBarrier(
|
||||||
|
commands,
|
||||||
|
prev.getStage(),
|
||||||
|
current.getStage(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
1,
|
||||||
|
&barrier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputePass::verify(const std::string &name, const UsageTracker &history) const {
|
||||||
|
for (const auto& pair : history.getUsageMap()) {
|
||||||
|
const ImageUsage::Type type = pair.second.getType();
|
||||||
|
if (type != ImageUsage::Type::LinearAccess && type != ImageUsage::Type::Sampled && type != ImageUsage::Type::Transfer)
|
||||||
|
throw std::runtime_error("Compute shader using an attachment that is not guranteed to be readable.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
#include "vlkx/render/render_pass/GenericRenderPass.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the necessary Clear Value struct to erase the given attachment.
|
||||||
|
* @param attachment the attachment metadata
|
||||||
|
* @return the Clear Value that will erase the attachment's data
|
||||||
|
*/
|
||||||
|
VkClearValue createClearFor(const RenderPassBuilder::Attachment& attachment) {
|
||||||
|
|
||||||
|
VkClearValue clear {};
|
||||||
|
if (std::holds_alternative<RenderPassBuilder::Attachment::ColorOps>(attachment.ops))
|
||||||
|
clear.color = { { 0, 0, 0, 0 } };
|
||||||
|
else {
|
||||||
|
clear.depthStencil = { 1.0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return clear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a RenderPassBuilder attachment to a VkAttachmentDescription.
|
||||||
|
* Format will be UNDEFINED.
|
||||||
|
* Sample count will be 1.
|
||||||
|
*/
|
||||||
|
VkAttachmentDescription buildAttachment(const RenderPassBuilder::Attachment& attachment) {
|
||||||
|
VkAttachmentDescription descriptor {
|
||||||
|
{},
|
||||||
|
VK_FORMAT_UNDEFINED,
|
||||||
|
VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||||
|
VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||||
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||||
|
VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||||
|
attachment.layoutInitial,
|
||||||
|
attachment.layoutFinal
|
||||||
|
};
|
||||||
|
|
||||||
|
if (const auto& ops = std::get_if<RenderPassBuilder::Attachment::ColorOps>(&attachment.ops); ops != nullptr) {
|
||||||
|
descriptor.loadOp = ops->LOAD;
|
||||||
|
descriptor.storeOp = ops->STORE;
|
||||||
|
} else if (const auto& ops = std::get_if<RenderPassBuilder::Attachment::StencilDepthOps>(&attachment.ops); ops != nullptr) {
|
||||||
|
descriptor.loadOp = ops->DEPTH_LOAD;
|
||||||
|
descriptor.storeOp = ops->DEPTH_STORE;
|
||||||
|
descriptor.stencilLoadOp = ops->STENCIL_LOAD;
|
||||||
|
descriptor.stencilStoreOp = ops->STENCIL_STORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkSubpassDescription> buildSubpassDescriptors(const std::vector<RenderPassBuilder::SubpassAttachments>& attachments) {
|
||||||
|
std::vector<VkSubpassDescription> descriptors;
|
||||||
|
descriptors.reserve(attachments.size());
|
||||||
|
|
||||||
|
for (const auto& attachment : attachments) {
|
||||||
|
descriptors.emplace_back(VkSubpassDescription {
|
||||||
|
{},
|
||||||
|
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
static_cast<uint32_t>(attachment.colorReferences.size()),
|
||||||
|
attachment.colorReferences.data(),
|
||||||
|
attachment.multisampleReferences.empty() ? nullptr : attachment.multisampleReferences.data(),
|
||||||
|
attachment.stencilDepthReference.has_value() ? &attachment.stencilDepthReference.value() : nullptr,
|
||||||
|
0,
|
||||||
|
nullptr
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSubpassDependency buildSubpassDependency(const RenderPassBuilder::SubpassDependency& dep) {
|
||||||
|
return VkSubpassDependency {
|
||||||
|
dep.source.index,
|
||||||
|
dep.destination.index,
|
||||||
|
dep.source.stage,
|
||||||
|
dep.destination.stage,
|
||||||
|
dep.source.access,
|
||||||
|
dep.destination.access,
|
||||||
|
dep.flags
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> countColorAttachments(const std::vector<RenderPassBuilder::SubpassAttachments>& attachments) {
|
||||||
|
std::vector<int> count;
|
||||||
|
count.reserve(attachments.size());
|
||||||
|
|
||||||
|
for (const auto& attachment : attachments)
|
||||||
|
count.emplace_back(attachment.colorReferences.size());
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkFramebuffer> createFramebuffers(const VkRenderPass& renderPass, const std::vector<std::function<const Image&(int idx)>> getters, int count, const VkExtent2D& extent) {
|
||||||
|
std::vector<VkImageView> views(getters.size());
|
||||||
|
|
||||||
|
VkFramebufferCreateInfo framebufferCreate {
|
||||||
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||||
|
nullptr,
|
||||||
|
{},
|
||||||
|
renderPass,
|
||||||
|
static_cast<uint32_t>(views.size()),
|
||||||
|
views.data(),
|
||||||
|
extent.width,
|
||||||
|
extent.height,
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<VkFramebuffer> framebuffers(count);
|
||||||
|
for (int i = 0; i < framebuffers.size(); ++i) {
|
||||||
|
for (int image = 0; image < getters.size(); image++)
|
||||||
|
views[image] = getters[image](i).getView();
|
||||||
|
|
||||||
|
vkCreateFramebuffer(VulkanModule::getInstance()->getDevice()->logical, &framebufferCreate, nullptr, &framebuffers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return framebuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkAttachmentReference> RenderPassBuilder::parseColorReferences(std::vector<ColorAttachmentMeta> meta) {
|
||||||
|
if (meta.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Search for the highest location index'd attachment reference.
|
||||||
|
// Shaders can define negative locations, so we have to start as low as we can go,
|
||||||
|
// and work our way up until we find the highest.
|
||||||
|
// If we start at 0, we risk missing an all-negative location set.
|
||||||
|
int max = std::numeric_limits<int>::min();
|
||||||
|
for (const auto& attachment : meta)
|
||||||
|
max = std::max(max, attachment.location);
|
||||||
|
|
||||||
|
std::vector<VkAttachmentReference> references(max + 1, { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED });
|
||||||
|
for (const auto& attachment : meta)
|
||||||
|
references[attachment.location] = { static_cast<uint32_t>(attachment.descriptionIdx), attachment.layout };
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkAttachmentReference> RenderPassBuilder::parseMutisampling(int colorReferencesCount, std::vector<ColorAttachmentMeta> meta) {
|
||||||
|
std::vector<VkAttachmentReference> references(colorReferencesCount, { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED });
|
||||||
|
for (const auto& attachment : meta)
|
||||||
|
references[attachment.location] = { static_cast<uint32_t>(attachment.descriptionIdx), attachment.layout };
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::setFramebufferCount(int count) {
|
||||||
|
framebufferCount = count;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::setAttachment(int idx, const Attachment &attachment) {
|
||||||
|
if (idx > clearValues.size())
|
||||||
|
clearValues.resize(idx + 1);
|
||||||
|
clearValues.at(idx) = createClearFor(attachment);
|
||||||
|
|
||||||
|
if (idx > attachmentDescriptors.size())
|
||||||
|
attachmentDescriptors.resize(idx + 1);
|
||||||
|
attachmentDescriptors.at(idx) = buildAttachment(attachment);
|
||||||
|
|
||||||
|
if (attachmentDescriptors.size() > attachmentGetters.size())
|
||||||
|
attachmentGetters.resize(attachmentDescriptors.size());
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::updateAttachmentBacking(int idx,
|
||||||
|
std::function<const Image &(int)> &&getBacking) {
|
||||||
|
const Image& img = getBacking(idx);
|
||||||
|
attachmentDescriptors[idx].format = img.getFormat();
|
||||||
|
attachmentDescriptors[idx].samples = img.getSamples();
|
||||||
|
attachmentGetters.at(idx) = std::move(getBacking);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::setSubpass(int idx, std::vector<VkAttachmentReference> &&color,
|
||||||
|
std::vector<VkAttachmentReference> &&multisample,
|
||||||
|
VkAttachmentReference &depthStencil) {
|
||||||
|
if (multisample.empty())
|
||||||
|
if (multisample.size() != color.size())
|
||||||
|
throw std::runtime_error("Constructing a subpass with mismatched color and multisample attachments");
|
||||||
|
|
||||||
|
SubpassAttachments attachments {
|
||||||
|
std::move(color), std::move(multisample), depthStencil
|
||||||
|
};
|
||||||
|
|
||||||
|
subpassAttachments.emplace_back(attachments);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::addDependency(const SubpassDependency &dep) {
|
||||||
|
subpassDependencies.emplace_back(buildSubpassDependency(dep));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RenderPass> RenderPassBuilder::build() const {
|
||||||
|
if (framebufferCount == 0)
|
||||||
|
throw std::runtime_error("No framebuffers in render pass");
|
||||||
|
for (int i = 0; i < attachmentGetters.size(); i++)
|
||||||
|
if (attachmentGetters[i] == nullptr)
|
||||||
|
throw std::runtime_error("Image " + std::to_string(i) + " is not set in render pass");
|
||||||
|
|
||||||
|
const auto descriptors = buildSubpassDescriptors(subpassAttachments);
|
||||||
|
const VkRenderPassCreateInfo createInfo {
|
||||||
|
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||||
|
nullptr,
|
||||||
|
{},
|
||||||
|
static_cast<uint32_t>(attachmentDescriptors.size()),
|
||||||
|
attachmentDescriptors.data(),
|
||||||
|
static_cast<uint32_t>(descriptors.size()),
|
||||||
|
descriptors.data(),
|
||||||
|
static_cast<uint32_t>(subpassDependencies.size()),
|
||||||
|
subpassDependencies.data()
|
||||||
|
};
|
||||||
|
|
||||||
|
VkRenderPass pass;
|
||||||
|
if (vkCreateRenderPass(VulkanModule::getInstance()->getDevice()->logical, &createInfo, nullptr, &pass) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create render pass");
|
||||||
|
|
||||||
|
const auto framebufferSize = attachmentGetters[0](0).getExtent();
|
||||||
|
|
||||||
|
return std::make_unique<RenderPass> (
|
||||||
|
static_cast<int>(descriptors.size()), pass, clearValues, framebufferSize, createFramebuffers(pass, attachmentGetters, framebufferCount.value(), framebufferSize),
|
||||||
|
countColorAttachments(subpassAttachments)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderPass::execute(const VkCommandBuffer &commands, int imageIndex,
|
||||||
|
std::vector<std::function<void(const VkCommandBuffer &)>> ops) const {
|
||||||
|
const VkRenderPassBeginInfo begin {
|
||||||
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||||
|
nullptr,
|
||||||
|
renderPass,
|
||||||
|
framebuffers[imageIndex],
|
||||||
|
{
|
||||||
|
{0, 0},
|
||||||
|
extent
|
||||||
|
},
|
||||||
|
static_cast<uint32_t>(clearValues.size()),
|
||||||
|
clearValues.data()
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdBeginRenderPass(commands, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
|
for (int i = 0; i < ops.size(); i++) {
|
||||||
|
if (i != 0)
|
||||||
|
vkCmdNextSubpass(commands, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
ops[i](commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkCmdEndRenderPass(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPass::~RenderPass() {
|
||||||
|
for (const auto& fb : framebuffers)
|
||||||
|
vkDestroyFramebuffer(VulkanModule::getInstance()->getDevice()->logical, fb, nullptr);
|
||||||
|
vkDestroyRenderPass(VulkanModule::getInstance()->getDevice()->logical, renderPass, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
#include "vlkx/render/render_pass/ScreenRenderPass.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
#include "vlkx/render/render_pass/GPUPass.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
void checkSubpass(int pass, int high) { if (pass < 1 || pass > high) throw std::runtime_error("Subpass index too high"); }
|
||||||
|
|
||||||
|
void addAttachment(const AttachmentConfig& config, GraphicsPass& pass, MultiImageTracker& tracker, GraphicsPass::LocationGetter&& getter, const std::function<void(UsageTracker&)>& populateHistory) {
|
||||||
|
const std::string& name = config.name;
|
||||||
|
if (!tracker.isTracking(name))
|
||||||
|
throw std::runtime_error("Attempting to add image " + name + " that is not tracked");
|
||||||
|
|
||||||
|
UsageTracker history { tracker.get(name) };
|
||||||
|
populateHistory(history);
|
||||||
|
|
||||||
|
if (config.finalUsage.has_value())
|
||||||
|
history.setFinal(config.finalUsage.value());
|
||||||
|
|
||||||
|
config.index = pass.add(name, std::move(history), std::move(getter), config.loadStoreOps);
|
||||||
|
pass.update(name, tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
RendererConfig::RendererConfig(int passCount, std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen, std::optional<int> firstTransparent, std::optional<int> firstOverlay) : renderImages(destinations) {
|
||||||
|
if (passCount < 1)
|
||||||
|
throw std::runtime_error("Creating a RendererConfig with less than 1 subpass.");
|
||||||
|
|
||||||
|
if (firstTransparent.has_value())
|
||||||
|
checkSubpass(firstTransparent.value(), passCount);
|
||||||
|
if (firstOverlay.has_value())
|
||||||
|
checkSubpass(firstOverlay.value(), passCount);
|
||||||
|
|
||||||
|
if (firstOverlay.has_value())
|
||||||
|
numOverlayPasses = passCount - firstOverlay.value();
|
||||||
|
if (firstTransparent.has_value()) {
|
||||||
|
numOpaquePasses = firstTransparent.value();
|
||||||
|
numTransparentPasses = passCount - numOpaquePasses - numOverlayPasses;
|
||||||
|
} else {
|
||||||
|
numOpaquePasses = passCount - numOverlayPasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
rendersToScreen = toScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RenderPassBuilder> SimpleRenderPass::createBuilder(int framebuffers,
|
||||||
|
const vlkx::RendererConfig &config,
|
||||||
|
const vlkx::AttachmentConfig &color,
|
||||||
|
const vlkx::AttachmentConfig *multisample,
|
||||||
|
const vlkx::AttachmentConfig *depthStencil,
|
||||||
|
vlkx::MultiImageTracker &tracker) {
|
||||||
|
const int passes = config.passes();
|
||||||
|
const int firstPass = 0;
|
||||||
|
const int lastPass = passes - 1;
|
||||||
|
|
||||||
|
const auto getLocation = [](int pass) { return 0; };
|
||||||
|
bool usesMultisample = multisample != nullptr;
|
||||||
|
bool usesDepth = depthStencil != nullptr;
|
||||||
|
|
||||||
|
const bool passesUsingDepth = config.depthPasses() > 0;
|
||||||
|
|
||||||
|
if (usesDepth) {
|
||||||
|
if (passesUsingDepth == 0)
|
||||||
|
throw std::runtime_error("Depth stencil defined, but never used.");
|
||||||
|
} else
|
||||||
|
if (passesUsingDepth > 0)
|
||||||
|
throw std::runtime_error("Depth stencil used, but never defined.");
|
||||||
|
|
||||||
|
GraphicsPass graphicsPass(passes);
|
||||||
|
|
||||||
|
addAttachment(color, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
|
||||||
|
if (usesMultisample)
|
||||||
|
history.add(lastPass, ImageUsage::multisample());
|
||||||
|
else
|
||||||
|
history.add(firstPass, lastPass, ImageUsage::renderTarget(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (usesMultisample) {
|
||||||
|
addAttachment(*multisample, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
|
||||||
|
history.add(firstPass, lastPass, ImageUsage::renderTarget(0));
|
||||||
|
});
|
||||||
|
graphicsPass.addMultisample(multisample->name, color.name, lastPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usesDepth)
|
||||||
|
addAttachment(*depthStencil, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
|
||||||
|
if (config.numOpaquePasses > 0) {
|
||||||
|
const int lastOpaque = config.numOpaquePasses - 1;
|
||||||
|
history.add(firstPass, lastOpaque, ImageUsage::depthStencil(ImageUsage::Access::ReadWrite));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.numTransparentPasses > 0) {
|
||||||
|
const int firstTransparent = config.numOpaquePasses;
|
||||||
|
const int lastTransparent = firstTransparent + config.numTransparentPasses - 1;
|
||||||
|
history.add(firstTransparent, lastTransparent, ImageUsage::depthStencil(ImageUsage::Access::ReadOnly));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return graphicsPass.build(framebuffers);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenRenderPassManager::initializeRenderPass() {
|
||||||
|
if (config.usesDepth()) {
|
||||||
|
depthStencilImage = MultisampleImage::createDepthStencil(VulkanModule::getInstance()->getSwapchain()->extent, std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passBuilder == nullptr)
|
||||||
|
preparePassBuilder();
|
||||||
|
|
||||||
|
passBuilder->updateAttachmentBacking(destinationInfo.index.value(), [this](int index) -> const Image& {
|
||||||
|
return *config.renderImages[index];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (depthStencilImage != nullptr)
|
||||||
|
passBuilder->updateAttachmentBacking(depthStencilInfo.index.value(), [this](int index) -> const Image& {
|
||||||
|
return *depthStencilImage;
|
||||||
|
});
|
||||||
|
|
||||||
|
pass = passBuilder->build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenRenderPassManager::preparePassBuilder() {
|
||||||
|
const bool usesDepth = depthStencilImage != nullptr;
|
||||||
|
const bool usesMultisampling = false;
|
||||||
|
|
||||||
|
MultiImageTracker tracker;
|
||||||
|
destinationInfo.add(tracker, *config.renderImages[0]);
|
||||||
|
if (usesDepth)
|
||||||
|
depthStencilInfo.add(tracker, *depthStencilImage);
|
||||||
|
if (usesMultisampling)
|
||||||
|
// TODO
|
||||||
|
(void) 0;
|
||||||
|
|
||||||
|
auto colorConfig = destinationInfo.makeConfig();
|
||||||
|
if (config.rendersToScreen)
|
||||||
|
colorConfig.setUsage(ImageUsage::presentation());
|
||||||
|
else
|
||||||
|
colorConfig.setUsage(ImageUsage::sampledFragment());
|
||||||
|
const auto multisampleConfig = multisampleInfo.makeConfig();
|
||||||
|
const auto depthConfig = depthStencilInfo.makeConfig();
|
||||||
|
|
||||||
|
passBuilder = SimpleRenderPass::createBuilder(config.renderImages.size(), config, colorConfig, usesMultisampling ? &multisampleConfig : nullptr, usesDepth ? &depthConfig : nullptr, tracker);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
#include "temp/model/Builder.h"
|
||||||
|
|
||||||
|
namespace vlkxtemp {
|
||||||
|
using namespace vlkx;
|
||||||
|
using Geo::VertexAll;
|
||||||
|
using VertexData = PerVertexBuffer::NoShareMeta;
|
||||||
|
|
||||||
|
std::unique_ptr<SamplableImage> createTex(const ModelBuilder::TextureSource& source) {
|
||||||
|
const auto usages = { ImageUsage::sampledFragment() };
|
||||||
|
return std::make_unique<RefCountedTexture>(source, usages, ImageSampler::Config {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillTextureMeta(const ModelBuilder::BindingPoints& binding, const ModelBuilder::TexturePerMesh& textures, const ModelBuilder::TexturePerMesh& sharedTextures, Descriptor::Meta* meta, Descriptor::ImageInfos* infos) {
|
||||||
|
*meta = {Image::getSampleType(), VK_SHADER_STAGE_FRAGMENT_BIT, {} };
|
||||||
|
auto& texBindings = meta->bindings;
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < (size_t) ModelBuilder::TextureType::Count; idx++) {
|
||||||
|
const auto type = (ModelBuilder::TextureType) idx;
|
||||||
|
const size_t numTextures = textures[idx].size() + sharedTextures[idx].size();
|
||||||
|
if (numTextures != 0) {
|
||||||
|
const auto iter = binding.find(type);
|
||||||
|
if (iter == binding.end())
|
||||||
|
throw std::runtime_error("Binding point of texture type " + std::to_string(idx) + " is not set");
|
||||||
|
|
||||||
|
texBindings.push_back({ iter->second, static_cast<uint32_t>(numTextures) });
|
||||||
|
|
||||||
|
auto& metaMap = (*infos)[iter->second];
|
||||||
|
metaMap.reserve(numTextures);
|
||||||
|
for (const auto& texture : textures[idx])
|
||||||
|
metaMap.push_back(texture->getInfoForSampling());
|
||||||
|
for (const auto& texture : sharedTextures[idx])
|
||||||
|
metaMap.push_back(texture->getInfoForSampling());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkPushConstantRange> createRanges(const ModelBuilder::ModelPushConstant& constants) {
|
||||||
|
std::vector<VkPushConstantRange> ranges;
|
||||||
|
ranges.reserve(constants.constants.size());
|
||||||
|
for (const auto& meta : constants.constants)
|
||||||
|
ranges.push_back( { constants.stage, meta.offset, meta.constants->getSize() });
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkVertexInputBindingDescription getBinding(uint32_t stride, bool instancing) {
|
||||||
|
return VkVertexInputBindingDescription{ 0, stride,instancing ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX, };
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVertexInput(const PerVertexBuffer& buffer, const std::vector<PerInstanceVertexBuffer*>& instanceBuffers, GraphicsPipelineBuilder* builder) {
|
||||||
|
uint32_t start = 0;
|
||||||
|
auto attributes = buffer.getAttrs(start);
|
||||||
|
start += attributes.size();
|
||||||
|
|
||||||
|
builder->addVertex(0, Geo::VertexAll::getBindingDesc(), std::move(attributes));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < instanceBuffers.size(); i++) {
|
||||||
|
if (instanceBuffers[i] == nullptr)
|
||||||
|
throw std::runtime_error("PerInstanceVertexBuffer not specified");
|
||||||
|
auto instanceAttrs = instanceBuffers[i]->getAttrs(start);
|
||||||
|
start += instanceAttrs.size();
|
||||||
|
|
||||||
|
auto instanceBinding = getBinding(instanceBuffers[i]->getSize(), true);
|
||||||
|
builder->addVertex(i + 1, std::move(instanceBinding), std::move(instanceAttrs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelBuilder::SingleMeshModel::load(ModelBuilder* builder) const {
|
||||||
|
const Wavefront obj(objFile, objIndexBase);
|
||||||
|
VertexData vertices {
|
||||||
|
{{
|
||||||
|
PerVertexBuffer::VertexDataMeta { obj.indices },
|
||||||
|
PerVertexBuffer::VertexDataMeta { obj.vertices }
|
||||||
|
}}
|
||||||
|
};
|
||||||
|
|
||||||
|
builder->vertexBuffer = std::make_unique<StaticPerVertexBuffer>(std::move(vertices), Geo::VertexAll::getAttributeDesc());
|
||||||
|
|
||||||
|
auto& meshTexs = builder->textures;
|
||||||
|
meshTexs.push_back({});
|
||||||
|
for (const auto& pair : textureSources) {
|
||||||
|
const auto type = (size_t)pair.first;
|
||||||
|
const auto& sources = pair.second;
|
||||||
|
|
||||||
|
meshTexs.back()[type].reserve(sources.size());
|
||||||
|
for (const auto& source : sources)
|
||||||
|
meshTexs.back()[type].push_back({createTex(source)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelBuilder::MultiMeshModel::load(ModelBuilder* builder) const {
|
||||||
|
const ModelLoader loader(models, textures);
|
||||||
|
std::vector<VertexData::PerMesh> data;
|
||||||
|
data.reserve(loader.getMeshes().size());
|
||||||
|
|
||||||
|
for (const auto& mesh : loader.getMeshes())
|
||||||
|
data.push_back({ PerVertexBuffer::VertexDataMeta { mesh.indices }, PerVertexBuffer::VertexDataMeta { mesh.vertices } });
|
||||||
|
|
||||||
|
builder->vertexBuffer = std::make_unique<StaticPerVertexBuffer>(VertexData { std::move(data)}, Geo::VertexAll::getAttributeDesc());
|
||||||
|
|
||||||
|
const auto usages = { ImageUsage::sampledFragment() };
|
||||||
|
auto& meshTexs = builder->textures;
|
||||||
|
meshTexs.reserve(loader.getMeshes().size());
|
||||||
|
for (const auto& mesh : loader.getMeshes()) {
|
||||||
|
meshTexs.push_back({});
|
||||||
|
for (const auto& tex : mesh.textures)
|
||||||
|
meshTexs.back()[(size_t) tex.type].push_back(std::make_unique<RefCountedTexture>(tex.path, usages, ImageSampler::Config {}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder::ModelBuilder(std::string &&name, int frames, float aspect, const ModelResource &resource)
|
||||||
|
: frames(frames), aspectRatio(aspect), uniformBufferMeta(frames), pipelineBuilder(std::make_unique<GraphicsPipelineBuilder>()) {
|
||||||
|
pipelineBuilder->name(std::move(name));
|
||||||
|
resource.load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::texture(TextureType type, const TextureSource &source) {
|
||||||
|
sharedTextures[(size_t) type].push_back(createTex(source));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::bindTextures(TextureType type, uint32_t point) {
|
||||||
|
bindPoints[type] = point;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::instanceBuffer(vlkx::PerInstanceVertexBuffer* buffer) {
|
||||||
|
instanceBuffers.push_back(buffer);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::uniform(VkShaderStageFlags stage, std::vector<vlkx::Descriptor::Meta::Binding> &&bindings) {
|
||||||
|
uniformMeta.push_back({ UniformBuffer::getDescriptorType(), stage, std::move(bindings) });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::uniformBuffer(uint32_t point, const vlkx::UniformBuffer &buffer) {
|
||||||
|
for (size_t frame = 0; frame < frames; frame++) {
|
||||||
|
const int chunk = buffer.isSingle() ? 0 : frame;
|
||||||
|
uniformBufferMeta[frame][point].push_back( buffer.getDescriptorInfo(chunk) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::pushStage(VkShaderStageFlags stage) {
|
||||||
|
if (!pushConstants.has_value())
|
||||||
|
pushConstants.emplace();
|
||||||
|
|
||||||
|
pushConstants->stage = stage;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::pushConstant(const vlkx::PushConstant* constant, uint32_t offset) {
|
||||||
|
if (!pushConstants.has_value())
|
||||||
|
pushConstants.emplace();
|
||||||
|
|
||||||
|
pushConstants.value().constants.push_back( ModelPushConstant::Meta { constant, offset });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::shader(VkShaderStageFlagBits stage, std::string &&file) {
|
||||||
|
pipelineBuilder->shader(stage, std::move(file));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModelBuilder::Descriptors> ModelBuilder::createDescs() const {
|
||||||
|
std::vector<Descriptors> descs(frames);
|
||||||
|
auto infos = uniformMeta;
|
||||||
|
infos.resize(infos.size() + 1);
|
||||||
|
|
||||||
|
for (size_t frame = 0; frame < frames; frame++) {
|
||||||
|
descs[frame].reserve(textures.size());
|
||||||
|
|
||||||
|
for (const auto& tex : textures) {
|
||||||
|
Descriptor::ImageInfos image;
|
||||||
|
fillTextureMeta(bindPoints, tex, sharedTextures, &infos.back(), &image);
|
||||||
|
|
||||||
|
descs[frame].push_back(std::make_unique<StaticDescriptor>(infos));
|
||||||
|
descs[frame].back()->buffers(UniformBuffer::getDescriptorType(), uniformBufferMeta[frame]);
|
||||||
|
descs[frame].back()->images(Image::getSampleType(), image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return descs;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Model> ModelBuilder::build() {
|
||||||
|
if (pushConstants.has_value())
|
||||||
|
if (pushConstants->constants.empty())
|
||||||
|
throw std::runtime_error("Model sets push constant present but no data.");
|
||||||
|
|
||||||
|
auto descs = createDescs();
|
||||||
|
pipelineBuilder->layout(
|
||||||
|
{ descs[0][0]->getLayout() },
|
||||||
|
pushConstants.has_value() ? createRanges(pushConstants.value()) : std::vector<VkPushConstantRange> {}
|
||||||
|
);
|
||||||
|
|
||||||
|
setVertexInput(*vertexBuffer, instanceBuffers, pipelineBuilder.get());
|
||||||
|
|
||||||
|
uniformMeta.clear();
|
||||||
|
uniformBufferMeta.clear();
|
||||||
|
|
||||||
|
return std::unique_ptr<Model> {
|
||||||
|
new Model {
|
||||||
|
aspectRatio, std::move(vertexBuffer), std::move(instanceBuffers), std::move(pushConstants),
|
||||||
|
std::move(sharedTextures), std::move(textures), std::move(descs), std::move(pipelineBuilder)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::update(bool opaque, const VkExtent2D &frame, VkSampleCountFlagBits samples,
|
||||||
|
const vlkx::RenderPass &pass, uint32_t subpass, bool flipY) {
|
||||||
|
|
||||||
|
pipeline = (*pipelineBuilder)
|
||||||
|
.depthTest(true, opaque)
|
||||||
|
.multiSample(samples)
|
||||||
|
.viewport({ { 0, 0, static_cast<float>(frame.width), static_cast<float>(frame.height), 0, 1 }, { { 0, 0 }, frame } })
|
||||||
|
.renderPass(*pass, subpass)
|
||||||
|
.colorBlend(std::vector<VkPipelineColorBlendAttachmentState>(pass.getAttachsInSubpass(subpass), vlkx::Pipeline::getAlphaBlendState(!opaque)))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::draw(const VkCommandBuffer &commands, int frame, uint32_t instances) const {
|
||||||
|
pipeline->bind(commands);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < perInstanceBuffers.size(); i++)
|
||||||
|
perInstanceBuffers[i]->bind(commands, i + 1, 0);
|
||||||
|
|
||||||
|
if (pushConstants.has_value())
|
||||||
|
for (const auto& meta : pushConstants->constants)
|
||||||
|
meta.constants->upload(commands, pipeline->getLayout(), frame, meta.offset, pushConstants->stage);
|
||||||
|
|
||||||
|
for (size_t mesh = 0; mesh < textures.size(); mesh++) {
|
||||||
|
descriptors[frame][mesh]->bind(commands, pipeline->getLayout(), pipeline->getBind());
|
||||||
|
vertexBuffer->draw(commands, 0, mesh, instances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
#include "temp/model/Loader.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace vlkxtemp {
|
||||||
|
|
||||||
|
std::vector<std::string> split (std::string_view s, char delim) {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::stringstream ss ((std::string(s)));
|
||||||
|
std::string item;
|
||||||
|
|
||||||
|
while (std::getline (ss, item, delim)) {
|
||||||
|
result.push_back (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Wavefront::Wavefront(std::string_view path, size_t index_base) {
|
||||||
|
std::ifstream file(std::string(path), std::ios::binary);
|
||||||
|
|
||||||
|
std::vector<glm::vec3> positions;
|
||||||
|
std::vector<glm::vec3> normals;
|
||||||
|
std::vector<glm::vec2> tex_coords;
|
||||||
|
std::map<std::string, uint32_t> loaded_vertices;
|
||||||
|
|
||||||
|
const auto parse_line = [&](std::string_view line) {
|
||||||
|
const size_t non_space = line.find_first_not_of(' ');
|
||||||
|
if (non_space == std::string::npos || line[0] == '#')
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (line[non_space]) {
|
||||||
|
case 'v': {
|
||||||
|
switch (line[non_space + 1]) {
|
||||||
|
case ' ': {
|
||||||
|
const auto nums = split(line.substr(non_space + 2), ' ');
|
||||||
|
positions.emplace_back(stof(nums[0]), stof(nums[1]), stof(nums[2]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'n': {
|
||||||
|
// Normal.
|
||||||
|
const auto nums = split(line.substr(non_space + 3), ' ');
|
||||||
|
normals.emplace_back(glm::vec3{stof(nums[0]), stof(nums[1]), stof(nums[2])});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 't': {
|
||||||
|
// Texture coordinates.
|
||||||
|
const auto nums = split(line.substr(non_space + 3), ' ');
|
||||||
|
tex_coords.emplace_back(glm::vec2{stof(nums[0]), stof(nums[1])});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unexpected symbol " + std::to_string(line[non_space + 1]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'f': {
|
||||||
|
for (const auto& seg : split(line.substr(non_space + 2), ' ')) {
|
||||||
|
const auto iter = loaded_vertices.find(seg);
|
||||||
|
if (iter != loaded_vertices.end()) {
|
||||||
|
indices.push_back(iter->second);
|
||||||
|
} else {
|
||||||
|
indices.push_back(vertices.size());
|
||||||
|
loaded_vertices[seg] = vertices.size();
|
||||||
|
const auto idxs = split(seg, '/');
|
||||||
|
vertices.push_back(Geo::VertexAll {
|
||||||
|
positions.at(stoi(idxs[0]) - index_base),
|
||||||
|
normals.at(stoi(idxs[2]) - index_base),
|
||||||
|
tex_coords.at(stoi(idxs[1]) - index_base),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unexpected symbol in OBJ file: " + std::to_string(line[non_space]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
int line_num = 1;
|
||||||
|
try {
|
||||||
|
for (; std::getline(file, line); ++line_num)
|
||||||
|
parse_line(line);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throw std::runtime_error("Failed to parse obj file, error on line " + std::to_string(line_num) + ": " + line + "; " + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelLoader::ModelLoader(const std::string &model, const std::string &textures) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user