Compare commits

...

10 Commits

Author SHA1 Message Date
387bc84985 UUID & start on IDs 2023-03-11 07:14:36 +00:00
1297af3db1
Bugfix/merge fixes (#11) 2022-11-29 17:52:54 +00:00
Curle
a3e89b4e8f
Replace the renderer with a modern, module version. (#7)
* New Vulkan Renderer Module & Associated Fixups
2022-11-26 16:44:16 +00:00
71b95e1ccf
Base of the Module system (#8)
Co-authored-by: Curle <curle@gemwire.uk>
2022-11-26 13:22:36 +00:00
Curle
6dbb04ea83
Add SpdLog and ImGui to project. (#3)
* Implement spdlog logging

* Implement ImGui

* Fix crashes on dedicated GPUs

* Add some error detection

* Move error logging to spdlog
2022-07-28 21:13:52 +02:00
7a712522d6
Add missing folder to gitignore (#4) 2022-07-26 19:37:05 +02:00
Curle
a370f28f14
Implement a Vulkan Renderer module (#2)
* Starting work on the Vlkx renderer

* Fix renderer implementation

* Move GLM to FetchContent
2022-07-19 20:41:44 +02:00
Curle
3bf44e8985
CMake Build System (#4) and some code form the old codebase
* added test game, moved engine projects under "shadow" folder

* More work on build files

* fix git ignore

* working game folder setup

* Work on game rule

* [SYNC] work on the shadow engine rule

* [SYNC] Added wrapper c sript

* Work on wrapper

* Working wrapper

* Add Vulkan sdk rules

* First working build using the CMake toolchain

* updates

* fix tests

* remove .clwb

Co-authored-by: dpeter99 <dpeter99@gmail.com>
2022-07-19 19:34:21 +02:00
66d8618e4e [DOCS] Started core architecture UML plans 2022-06-25 12:04:56 +02:00
26ece035a2 [DOC, THESIS] Start thesis plan 2022-06-24 18:49:11 +02:00
194 changed files with 67096 additions and 571 deletions

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -1 +0,0 @@
umbra

View File

@ -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>

View File

@ -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>

9
.gitignore vendored
View File

@ -4,7 +4,10 @@ obj/
riderModule.iml
/_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/

View File

@ -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

View File

@ -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>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -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
View 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)

View File

@ -1,7 +0,0 @@
workspace(
name = "umbra",
)
load("//vendor:deps.bzl", "deps")
deps()

27
cmake/FindImGui.cmake Normal file
View 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
View 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

View 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

View File

@ -1,14 +1,29 @@
@startuml
[shadow-engine] <<static lib>> as engine
[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
[test-game] <<dll>> as game
engine <-editor
runner -> engine
shengine <- editor
runner -> shengine
game ..> engine
game ..> shengine : uses
runner --> game : loads
editor --> game : loads

View 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

View File

@ -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

View File

@ -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"],
)

View File

@ -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;
}

View File

@ -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();
}
}
}

View File

@ -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()
{
}

View File

@ -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();
};

View File

@ -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;
*/
}

View File

@ -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();
};

View File

@ -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"
],
)

View File

@ -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"
],
)

View File

@ -1,8 +0,0 @@
//
// Created by dpete on 2022-06-20.
//
#ifndef UMBRA_MAIN_H
#define UMBRA_MAIN_H
#endif //UMBRA_MAIN_H

View File

@ -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"],
)

View 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)

View 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

View 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

View 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

View File

@ -1,12 +1,19 @@
#pragma once
#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 {
/// <summary>
/// Represents the application
/// </summary>
class ShadowApplication
class ShadowApplication
{
/// <summary>
/// This is the singleton instance
@ -21,7 +28,7 @@ namespace ShadowEngine {
/// <summary>
/// The module manager instance
/// </summary>
//ShadowEngine::ShadowModuleManager moduleManager;
ModuleManager moduleManager;
/// <summary>
/// Represents the running state.
@ -30,20 +37,24 @@ namespace ShadowEngine {
bool running = true;
bool no_gui = false;
std::string game = "";
void loadGame();
public:
/// <summary>
/// Default constructor
/// </summary>
ShadowApplication(int argc, char* argv[]);
virtual ~ShadowApplication();
virtual ~ShadowApplication();
/// <summary>
/// Static getter for the singleton instance
/// </summary>
/// Use this for accessing the Application
/// <returns>The current application reference</returns>
static ShadowApplication& Get() { return *instance; };
static ShadowApplication& Get();
/// <summary>
/// Returns the active window used for rendering
@ -52,9 +63,11 @@ namespace ShadowEngine {
//ShadowWindow& const GetWindow() const { return window_; };
//void SetWindow(ShadowWindow w) { window_ = w; }
//ShadowEngine::ShadowModuleManager& GetModuleManager() { return moduleManager; };
ShadowEngine::ModuleManager& GetModuleManager() { return moduleManager; };
void Init();
void Start();
void Init();
void Start();
void PollEvents();
};
}

View 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();
};
}

View 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();
};

View 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

View 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

View 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 {};
};
}

View 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();
}
}

View File

@ -0,0 +1,11 @@
//
// Created by dpete on 2022-07-06.
//
#include "core/Module.h"
namespace ShadowEngine {
SHObject_Base_Impl(Module)
} // ShadowEngine

View 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();
}
}

View 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() {
}

View 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; };
}

View 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()
{
}

View 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;
}

View 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();
}

View 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';
}
}
}
}

View 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) {}
};
}

View 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;
}
}

View 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)

View File

@ -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"
)

View 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}")

View File

@ -1,9 +1,8 @@
#include "SFFParser.h"
#include "string-helpers.h"
#include <fstream>
#include "string-helpers.h"
namespace Shadow::SFF {
SFFElement* SFFParser::ReadFromStream(std::istream& stream)

View File

@ -1,4 +1,5 @@
#include <catch2/catch.hpp>
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
TEST_CASE("15 is less than 20", "[numbers]") {
REQUIRE(15 < 20);

File diff suppressed because it is too large Load Diff

View 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; }
}

View File

@ -0,0 +1,6 @@
#include "../inc/SHObject.h"
uint64_t ShadowEngine::SHObject::GenerateId() noexcept {
static uint64_t count = 0;
return ++count;
}

View File

@ -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.

View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -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);
};
}

View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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();
};

View File

@ -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);
}

View File

@ -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);
}
}
};

View File

@ -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();
};

View File

@ -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{};
};

View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -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
}

View File

@ -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;
};
}

View File

@ -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;
};
}

View File

@ -0,0 +1,8 @@
#pragma once
namespace vlkx {
struct Queue {
VkQueue queue;
int queueIndex;
};
}

File diff suppressed because it is too large Load Diff

View 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>;

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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 &current) 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.");
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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