Replace the renderer with a modern, module version. (#7)
* New Vulkan Renderer Module & Associated Fixups
This commit is contained in:
parent
71b95e1ccf
commit
a3e89b4e8f
41
imgui.ini
Normal file
41
imgui.ini
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
[Window][Debug##Default]
|
||||||
|
Pos=876,8
|
||||||
|
Size=315,195
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000008,0
|
||||||
|
|
||||||
|
[Window][Dear ImGui Demo]
|
||||||
|
Pos=1193,178
|
||||||
|
Size=62,25
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000007,0
|
||||||
|
|
||||||
|
[Window][Game module window]
|
||||||
|
Pos=1193,8
|
||||||
|
Size=62,50
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000004,0
|
||||||
|
|
||||||
|
[Window][Time]
|
||||||
|
Pos=1193,60
|
||||||
|
Size=62,49
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000005,0
|
||||||
|
|
||||||
|
[Window][Active Modules]
|
||||||
|
Pos=1193,111
|
||||||
|
Size=62,65
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000006,0
|
||||||
|
|
||||||
|
[Docking][Data]
|
||||||
|
DockNode ID=0x00000001 Pos=1196,188 Size=379,195 Split=X
|
||||||
|
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=652,419 HiddenTabBar=1 Selected=0x55954704
|
||||||
|
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=129,419 Split=Y
|
||||||
|
DockNode ID=0x00000002 Parent=0x00000009 SizeRef=219,34 Split=Y Selected=0xFC1D20C0
|
||||||
|
DockNode ID=0x00000004 Parent=0x00000002 SizeRef=219,64 Selected=0xFC1D20C0
|
||||||
|
DockNode ID=0x00000005 Parent=0x00000002 SizeRef=219,62 Selected=0xE75A179E
|
||||||
|
DockNode ID=0x00000003 Parent=0x00000009 SizeRef=219,31 Split=Y Selected=0xEE305C78
|
||||||
|
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=219,142 Selected=0xEE305C78
|
||||||
|
DockNode ID=0x00000007 Parent=0x00000003 SizeRef=219,55 Selected=0xE87781F4
|
||||||
|
|
|
@ -9,11 +9,13 @@ FILE(GLOB_RECURSE SOURCES
|
||||||
core/src/*.cpp
|
core/src/*.cpp
|
||||||
shadow-renderer/src/*.cpp
|
shadow-renderer/src/*.cpp
|
||||||
shadow-reflection/src/*.cpp
|
shadow-reflection/src/*.cpp
|
||||||
|
shadow-utility/src/*.cpp
|
||||||
)
|
)
|
||||||
FILE(GLOB_RECURSE HEADERS
|
FILE(GLOB_RECURSE HEADERS
|
||||||
core/inc/*.h
|
core/inc/*.h
|
||||||
shadow-renderer/inc/*.h
|
shadow-renderer/inc/*.h
|
||||||
shadow-reflection/inc/*.h
|
shadow-reflection/inc/*.h
|
||||||
|
shadow-utility/inc/*.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(shadow-engine SHARED ${SOURCES} $<TARGET_OBJECTS:imgui>)
|
add_library(shadow-engine SHARED ${SOURCES} $<TARGET_OBJECTS:imgui>)
|
||||||
|
@ -24,6 +26,7 @@ target_include_directories(shadow-engine
|
||||||
core/inc
|
core/inc
|
||||||
shadow-renderer/inc
|
shadow-renderer/inc
|
||||||
shadow-reflection/inc
|
shadow-reflection/inc
|
||||||
|
shadow-utility/inc
|
||||||
${glm_SOURCE_DIR}
|
${glm_SOURCE_DIR}
|
||||||
INTERFACE
|
INTERFACE
|
||||||
${imgui_SOURCE_DIR}
|
${imgui_SOURCE_DIR}
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
#define UMBRA_MODULE_H
|
#define UMBRA_MODULE_H
|
||||||
|
|
||||||
#include "SHObject.h"
|
#include "SHObject.h"
|
||||||
#include <SDL_events.h>
|
#include "SDL_events.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Commands.h"
|
||||||
|
|
||||||
namespace ShadowEngine {
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
@ -29,13 +30,17 @@ namespace ShadowEngine {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// update is called each frame
|
/// update is called each frame
|
||||||
/// </summary>
|
/// </summary>
|
||||||
virtual void Update() = 0;
|
virtual void Update(int frame) = 0;
|
||||||
|
|
||||||
|
virtual void Recreate() = 0;
|
||||||
|
|
||||||
virtual void PreRender() = 0;
|
virtual void PreRender() = 0;
|
||||||
|
|
||||||
virtual void Render() = 0;
|
virtual void Render(VkCommandBuffer& commands, int frame) = 0;
|
||||||
|
|
||||||
virtual void LateRender() = 0;
|
virtual void LateRender(VkCommandBuffer& commands, int frame) = 0;
|
||||||
|
|
||||||
|
virtual void OverlayRender() = 0;
|
||||||
|
|
||||||
virtual void AfterFrameEnd() = 0;
|
virtual void AfterFrameEnd() = 0;
|
||||||
|
|
||||||
|
@ -52,6 +57,21 @@ namespace ShadowEngine {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
} // ShadowEngine
|
||||||
|
|
||||||
#endif //UMBRA_MODULE_H
|
#endif //UMBRA_MODULE_H
|
||||||
|
|
|
@ -11,22 +11,29 @@ namespace ShadowEngine {
|
||||||
public:
|
public:
|
||||||
std::shared_ptr<Module> module;
|
std::shared_ptr<Module> module;
|
||||||
std::string domain;
|
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 {
|
class ModuleManager {
|
||||||
public:
|
public:
|
||||||
static ModuleManager *instance;
|
static API ModuleManager *instance;
|
||||||
|
static ModuleManager* getInstance() { return instance; }
|
||||||
|
|
||||||
std::list<ModuleRef> modules;
|
std::list<ModuleRef> modules;
|
||||||
|
ModuleRef renderer;
|
||||||
|
|
||||||
ModuleManager();
|
ModuleManager();
|
||||||
|
|
||||||
~ModuleManager();
|
~ModuleManager();
|
||||||
|
|
||||||
void PushModule(std::shared_ptr<Module> module, std::string domain);
|
void PushModule(const std::shared_ptr<Module>& module, const std::string& domain);
|
||||||
|
|
||||||
Module &GetModule(std::string name);
|
Module &GetModule(const std::string& name);
|
||||||
|
|
||||||
|
/*
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T *GetModuleByType() {
|
T *GetModuleByType() {
|
||||||
for (auto &module: modules) {
|
for (auto &module: modules) {
|
||||||
|
@ -35,15 +42,19 @@ namespace ShadowEngine {
|
||||||
}
|
}
|
||||||
//SH_CORE_ERROR("Can't find the module {0}", T::Type());
|
//SH_CORE_ERROR("Can't find the module {0}", T::Type());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
} */
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
void Update();
|
void Update(int frame);
|
||||||
|
|
||||||
void LateRender();
|
void LateRender(VkCommandBuffer& commands, int frame);
|
||||||
|
|
||||||
void Render();
|
void OverlayRender();
|
||||||
|
|
||||||
|
void Recreate();
|
||||||
|
|
||||||
|
void Render(VkCommandBuffer& commands, int frame);
|
||||||
|
|
||||||
void PreRender();
|
void PreRender();
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include "Module.h"
|
#include "Module.h"
|
||||||
#include "ShadowWindow.h"
|
#include "ShadowWindow.h"
|
||||||
|
|
||||||
#include <SDL.h>
|
#include "SDL.h"
|
||||||
|
|
||||||
namespace ShadowEngine {
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
@ -23,11 +23,15 @@ namespace ShadowEngine {
|
||||||
|
|
||||||
void PreInit() override;
|
void PreInit() override;
|
||||||
|
|
||||||
void Update() override;
|
void Update(int frame) override;
|
||||||
|
|
||||||
void Render() override;
|
void Recreate() override;
|
||||||
|
|
||||||
void LateRender() override;
|
void Render(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
|
void OverlayRender() override;
|
||||||
|
|
||||||
|
void LateRender(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
std::string GetName() override;
|
std::string GetName() override;
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,6 @@ namespace ShadowEngine {
|
||||||
void Init();
|
void Init();
|
||||||
void Start();
|
void Start();
|
||||||
|
|
||||||
|
void PollEvents();
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL.h>
|
#include "SDL.h"
|
||||||
|
|
||||||
namespace ShadowEngine {
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "exports.h"
|
||||||
|
|
||||||
class Time
|
class Time
|
||||||
{
|
{
|
||||||
static int NOW;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static int LAST;
|
static API int NOW;
|
||||||
|
static API int LAST;
|
||||||
|
|
||||||
static double deltaTime;
|
static API double deltaTime;
|
||||||
static double deltaTime_ms;
|
static API double deltaTime_ms;
|
||||||
|
|
||||||
|
static API double timeSinceStart;
|
||||||
|
static API double startTime;
|
||||||
|
|
||||||
static void UpdateTime();
|
static void UpdateTime();
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
#ifndef UMBRA_DEBUGMODULE_H
|
#ifndef UMBRA_DEBUGMODULE_H
|
||||||
#define UMBRA_DEBUGMODULE_H
|
#define UMBRA_DEBUGMODULE_H
|
||||||
|
|
||||||
#include <SDL_events.h>
|
#include "SDL_events.h"
|
||||||
#include "core/Module.h"
|
#include "core/Module.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
namespace ShadowEngine::Debug {
|
namespace ShadowEngine::Debug {
|
||||||
|
|
||||||
|
@ -17,15 +18,19 @@ namespace ShadowEngine::Debug {
|
||||||
bool active;
|
bool active;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void Render() override;
|
void Render(VkCommandBuffer& commands, int frame) override {};
|
||||||
|
|
||||||
void PreInit() override { };
|
void PreInit() override { };
|
||||||
|
|
||||||
void Init() override { };
|
void Init() override { };
|
||||||
|
|
||||||
void Update() override { };
|
void Recreate() override {};
|
||||||
|
|
||||||
void LateRender() override { };
|
void OverlayRender() override;
|
||||||
|
|
||||||
|
void Update(int frame) override { };
|
||||||
|
|
||||||
|
void LateRender(VkCommandBuffer& commands, int frame) override { };
|
||||||
|
|
||||||
void AfterFrameEnd() override { };
|
void AfterFrameEnd() override { };
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
# if defined(EXPORTING_SH_ENGINE)
|
# if defined(EXPORTING_SH_ENGINE)
|
||||||
# define SH_EXPORT __declspec(dllexport)
|
# define API __declspec(dllexport)
|
||||||
# else
|
# else
|
||||||
# define SH_EXPORT __declspec(dllimport)
|
# define API __declspec(dllimport)
|
||||||
# endif
|
# endif
|
||||||
#else // non windows
|
#else // non windows
|
||||||
# define SH_EXPORT
|
# define SH_EXPORT
|
||||||
|
|
348
projs/shadow/shadow-engine/core/src/core/App2.txt
Normal file
348
projs/shadow/shadow-engine/core/src/core/App2.txt
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
#include "ShadowApplication.h"
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
|
#include "time.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui_impl_vulkan.h"
|
||||||
|
#include "imgui_impl_sdl.h"
|
||||||
|
#include <vlkx/vulkan/VulkanManager.h>
|
||||||
|
#include "vlkx/render/Camera.h"
|
||||||
|
#include "vlkx/render/render_pass/ScreenRenderPass.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Commands.h"
|
||||||
|
#include "vlkx/render/Geometry.h"
|
||||||
|
#include "temp/model/Builder.h"
|
||||||
|
|
||||||
|
#define CATCH(x) \
|
||||||
|
try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); }
|
||||||
|
|
||||||
|
namespace ShadowEngine {
|
||||||
|
|
||||||
|
struct SkyboxTransform {
|
||||||
|
alignas(sizeof(glm::mat4)) glm::mat4 value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlanetTransform {
|
||||||
|
alignas(sizeof(glm::mat4)) glm::mat4 model;
|
||||||
|
alignas(sizeof(glm::mat4)) glm::mat4 projection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Light {
|
||||||
|
alignas(sizeof(glm::vec4)) glm::vec4 dirTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::ScreenRenderPassManager> passManager;
|
||||||
|
std::unique_ptr<vlkx::RenderCommand> renderCommands;
|
||||||
|
std::unique_ptr<vlkx::UserPerspectiveCamera> camera;
|
||||||
|
std::unique_ptr<vlkx::UniformBuffer> light;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::PushConstant> skyboxConstant;
|
||||||
|
std::unique_ptr<vlkx::PushConstant> planetConstant;
|
||||||
|
std::unique_ptr<vlkxtemp::Model> skyboxModel;
|
||||||
|
std::unique_ptr<vlkxtemp::Model> planetModel;
|
||||||
|
std::unique_ptr<vlkxtemp::Model> asteroidModel;
|
||||||
|
|
||||||
|
float aspectRatio;
|
||||||
|
|
||||||
|
ShadowApplication* ShadowApplication::instance = nullptr;
|
||||||
|
|
||||||
|
ShadowApplication::ShadowApplication(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
if(argc > 1)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
{
|
||||||
|
std::string param(argv[i]);
|
||||||
|
if(param == "-no-gui")
|
||||||
|
{
|
||||||
|
this->no_gui = true;
|
||||||
|
}
|
||||||
|
if(param == "-game")
|
||||||
|
{
|
||||||
|
this->game = argv[i+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//game = _setupFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ShadowApplication::~ShadowApplication()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::Init()
|
||||||
|
{
|
||||||
|
// Initialize SDL. SDL_Init will return -1 if it fails.
|
||||||
|
if ( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) {
|
||||||
|
//std::cout << "Error initializing SDL: " << SDL_GetError() << std::endl;
|
||||||
|
//system("pause");
|
||||||
|
// End the program
|
||||||
|
//return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = new ShadowWindow(800,800);
|
||||||
|
|
||||||
|
CATCH(VulkanManager::getInstance()->initVulkan(window_->sdlWindowPtr);)
|
||||||
|
|
||||||
|
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
|
||||||
|
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
ImGui::CreateContext();
|
||||||
|
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||||
|
|
||||||
|
// Setup Dear ImGui style
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
|
||||||
|
VkDescriptorPool imGuiPool;
|
||||||
|
VulkanManager* vk = VulkanManager::getInstance();
|
||||||
|
VkDescriptorPoolSize pool_sizes[] =
|
||||||
|
{
|
||||||
|
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
|
||||||
|
{ VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDescriptorPoolCreateInfo pool_info = {};
|
||||||
|
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||||
|
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
||||||
|
pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes);
|
||||||
|
pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes);
|
||||||
|
pool_info.pPoolSizes = pool_sizes;
|
||||||
|
vkCreateDescriptorPool(vk->getDevice()->logical, &pool_info, VK_NULL_HANDLE, &imGuiPool);
|
||||||
|
|
||||||
|
// Setup Platform/Renderer backends
|
||||||
|
ImGui_ImplSDL2_InitForVulkan(window_->sdlWindowPtr);
|
||||||
|
ImGui_ImplVulkan_InitInfo init_info = {};
|
||||||
|
init_info.Instance = vk->getVulkan();
|
||||||
|
init_info.PhysicalDevice = vk->getDevice()->physical;
|
||||||
|
init_info.Device = vk->getDevice()->logical;
|
||||||
|
init_info.QueueFamily = vk->getDevice()->queueData.graphics;
|
||||||
|
init_info.Queue = vk->getDevice()->graphicsQueue;
|
||||||
|
init_info.PipelineCache = VK_NULL_HANDLE;
|
||||||
|
init_info.DescriptorPool = imGuiPool;
|
||||||
|
init_info.Subpass = 1;
|
||||||
|
init_info.MinImageCount = vk->getSwapchain()->images.size();
|
||||||
|
init_info.ImageCount = vk->getSwapchain()->images.size();
|
||||||
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
init_info.Allocator = VK_NULL_HANDLE;
|
||||||
|
init_info.CheckVkResultFn = nullptr;
|
||||||
|
|
||||||
|
vlkxtemp::ModelBuilder::ShaderPool pool;
|
||||||
|
|
||||||
|
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
|
||||||
|
skyboxConstant = std::make_unique<vlkx::PushConstant>(sizeof(SkyboxTransform), 2);
|
||||||
|
planetConstant = std::make_unique<vlkx::PushConstant>(sizeof(PlanetTransform), 2);
|
||||||
|
light = std::make_unique<vlkx::UniformBuffer>(sizeof(Light), 2);
|
||||||
|
|
||||||
|
aspectRatio = (float) window_->Width / window_->Height;
|
||||||
|
|
||||||
|
vlkx::Camera::Config conf {};
|
||||||
|
camera = vlkx::UserPerspectiveCamera::create( {}, {}, { 110, aspectRatio });
|
||||||
|
|
||||||
|
using vlkxtemp::ModelBuilder;
|
||||||
|
|
||||||
|
|
||||||
|
const vlkx::RefCountedTexture::CubemapLocation skybox {
|
||||||
|
"resources/planets/bg",
|
||||||
|
{
|
||||||
|
"left.png", "right.png",
|
||||||
|
"top.png", "bottom.png",
|
||||||
|
"front.png", "back.png"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
skyboxModel = ModelBuilder {
|
||||||
|
"Skybox", 2, aspectRatio,
|
||||||
|
ModelBuilder::SingleMeshModel { "resources/planets/skybox.obj", 1,
|
||||||
|
{ { ModelBuilder::TextureType::Cubemap, { { skybox } } } }
|
||||||
|
}}
|
||||||
|
.bindTextures(ModelBuilder::TextureType::Cubemap, 1)
|
||||||
|
.pushStage(VK_SHADER_STAGE_VERTEX_BIT)
|
||||||
|
.pushConstant(skyboxConstant.get(), 0)
|
||||||
|
.shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/planets/skybox.vert.spv")
|
||||||
|
.shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/planets/skybox.frag.spv")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
planetModel = ModelBuilder {
|
||||||
|
"Walrus", 2, aspectRatio,
|
||||||
|
ModelBuilder::SingleMeshModel {"resources/walrus/walrus.obj", 1,
|
||||||
|
{{ ModelBuilder::TextureType::Diffuse, { { "resources/walrus/texture.png" } } } }
|
||||||
|
}}
|
||||||
|
.bindTextures(ModelBuilder::TextureType::Diffuse, 2)
|
||||||
|
.uniform(VK_SHADER_STAGE_FRAGMENT_BIT, {{1, 1}})
|
||||||
|
.uniformBuffer(1, *light)
|
||||||
|
.pushStage(VK_SHADER_STAGE_VERTEX_BIT)
|
||||||
|
.pushConstant(planetConstant.get(), 0)
|
||||||
|
.shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/walrus/walrus.vert.spv")
|
||||||
|
.shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/walrus/walrus.frag.spv")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
passManager = std::make_unique<vlkx::ScreenRenderPassManager>(vlkx::RendererConfig { 2 });
|
||||||
|
|
||||||
|
passManager->initializeRenderPass();
|
||||||
|
|
||||||
|
skyboxModel->update(true, VulkanManager::getInstance()->getSwapchain()->extent, VK_SAMPLE_COUNT_1_BIT, *passManager->getPass(), 0);
|
||||||
|
int cursorX, cursorY;
|
||||||
|
SDL_GetMouseState(&cursorX, &cursorY);
|
||||||
|
camera->setPos({ cursorX, cursorY });
|
||||||
|
|
||||||
|
planetModel->update(true, VulkanManager::getInstance()->getSwapchain()->extent, VK_SAMPLE_COUNT_1_BIT, *passManager->getPass(), 0);
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_Init(&init_info, **passManager->getPass());
|
||||||
|
// Upload Fonts
|
||||||
|
VkTools::immediateExecute([](const VkCommandBuffer& commands) { ImGui_ImplVulkan_CreateFontsTexture(commands); }, VulkanManager::getInstance()->getDevice());
|
||||||
|
|
||||||
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printMatrix(glm::mat4 mat) {
|
||||||
|
for (size_t i = 0; i < 4; i++) {
|
||||||
|
for (size_t j = 0; j < 4; j++) {
|
||||||
|
std::cout << mat[i][j] << " ";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateData(int frame) {
|
||||||
|
const float elapsed_time = Time::timeSinceStart;
|
||||||
|
|
||||||
|
const glm::vec3 lightDir{glm::sin(elapsed_time * 0.0006f), -0.3f,
|
||||||
|
glm::cos(elapsed_time * 0.0006f)};
|
||||||
|
*light->getData<Light>(frame) =
|
||||||
|
{glm::vec4{lightDir, elapsed_time}};
|
||||||
|
light->upload(frame);
|
||||||
|
|
||||||
|
glm::mat4 modelMatrix { 1 };
|
||||||
|
modelMatrix = glm::rotate(modelMatrix, elapsed_time * glm::radians(0.0005f), glm::vec3 { 0, 1, 0 });
|
||||||
|
const vlkx::Camera& cam = camera->getCamera();
|
||||||
|
|
||||||
|
const glm::mat4 view = glm::lookAt(glm::vec3{3.0f}, glm::vec3{0.0f},
|
||||||
|
glm::vec3{0.0f, 1.0f, 0.0f});
|
||||||
|
const glm::mat4 proj = glm::perspective(
|
||||||
|
glm::radians(45.0f), aspectRatio,
|
||||||
|
0.1f, 100.0f);
|
||||||
|
|
||||||
|
glm::mat4 planetProjection = cam.getProjMatrix() * cam.getViewMatrix();
|
||||||
|
*planetConstant->getData<PlanetTransform>(frame) = { modelMatrix, planetProjection };
|
||||||
|
glm::mat4 skyboxMat = cam.getProjMatrix() * cam.getSkyboxView();
|
||||||
|
skyboxConstant->getData<SkyboxTransform>(frame)->value = skyboxMat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void imGuiStartDraw() {
|
||||||
|
ImGui_ImplVulkan_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
}
|
||||||
|
void imGuiEndDraw(const VkCommandBuffer& commands) {
|
||||||
|
ImGui::Render();
|
||||||
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showDebugWindow(std::unique_ptr<vlkx::UserPerspectiveCamera>* cam) {
|
||||||
|
#define camPos cam->get()->getCamera().getPosition()
|
||||||
|
#define camFwd cam->get()->getCamera().getForward()
|
||||||
|
ImGui::Begin("Camera Debug");
|
||||||
|
ImGui::Text("Camera position: (x %f, y %f, z %f)", camPos.x, camPos.y, camPos.z );
|
||||||
|
ImGui::Text("Camera target: (x %f, y %f, z %f)", camPos.x + camFwd.x, camPos.y + camFwd.y, camPos.z + camFwd.z);
|
||||||
|
ImGui::Text("Camera FOV: %f", cam->get()->getCamera().getFieldOfView());
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::PollEvents() {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) { // poll until all events are handled!
|
||||||
|
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||||
|
|
||||||
|
switch(event.type) {
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_ESCAPE:
|
||||||
|
camera->reset(); break;
|
||||||
|
case SDLK_w:
|
||||||
|
camera->press(vlkx::Camera::Input::Up, Time::deltaTime); break;
|
||||||
|
case SDLK_s:
|
||||||
|
camera->press(vlkx::Camera::Input::Down, Time::deltaTime); break;
|
||||||
|
case SDLK_a:
|
||||||
|
camera->press(vlkx::Camera::Input::Left, Time::deltaTime); break;
|
||||||
|
case SDLK_d:
|
||||||
|
camera->press(vlkx::Camera::Input::Right, Time::deltaTime); break;
|
||||||
|
} break;
|
||||||
|
case SDL_MOUSEMOTION:
|
||||||
|
camera->move(-event.motion.xrel, -event.motion.yrel); break;
|
||||||
|
|
||||||
|
case SDL_MOUSEWHEEL:
|
||||||
|
camera->scroll(event.wheel.y, 1, 170); break;
|
||||||
|
case SDL_QUIT:
|
||||||
|
running = false; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowApplication::Start()
|
||||||
|
{
|
||||||
|
const auto update = [](const int frame) { updateData(frame); };
|
||||||
|
|
||||||
|
while (running)
|
||||||
|
{
|
||||||
|
PollEvents();
|
||||||
|
|
||||||
|
const auto result = renderCommands->execute(renderCommands->getFrame(), VulkanManager::getInstance()->getSwapchain()->swapChain, update,
|
||||||
|
[](const VkCommandBuffer& buffer, uint32_t frame) {
|
||||||
|
passManager->getPass()->execute(buffer, frame, {
|
||||||
|
// Render our model
|
||||||
|
[&frame](const VkCommandBuffer& commands) {
|
||||||
|
planetModel->draw(commands, frame, 1);
|
||||||
|
skyboxModel->draw(commands, frame, 1);
|
||||||
|
},
|
||||||
|
// Render ImGUI
|
||||||
|
[&](const VkCommandBuffer& commands) {
|
||||||
|
imGuiStartDraw();
|
||||||
|
|
||||||
|
bool showDemo = true;
|
||||||
|
//if (showDemo)
|
||||||
|
// ImGui::ShowDemoWindow(&showDemo);
|
||||||
|
|
||||||
|
showDebugWindow(&camera);
|
||||||
|
|
||||||
|
imGuiEndDraw(commands);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.has_value())
|
||||||
|
throw std::runtime_error("Resizing is not implemented");
|
||||||
|
|
||||||
|
renderCommands->nextFrame();
|
||||||
|
|
||||||
|
Time::UpdateTime();
|
||||||
|
|
||||||
|
camera->active(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDeviceWaitIdle(VulkanManager::getInstance()->getDevice()->logical);
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_Shutdown();
|
||||||
|
ImGui_ImplSDL2_Shutdown();
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
|
||||||
|
SDL_DestroyWindow(window_->sdlWindowPtr);
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,17 +18,18 @@ ShadowEngine::ModuleManager::ModuleManager()
|
||||||
}
|
}
|
||||||
|
|
||||||
ShadowEngine::ModuleManager::~ModuleManager()
|
ShadowEngine::ModuleManager::~ModuleManager()
|
||||||
{
|
= default;
|
||||||
}
|
|
||||||
|
|
||||||
void ShadowEngine::ModuleManager::PushModule(std::shared_ptr<Module> module, const std::string domain)
|
void ShadowEngine::ModuleManager::PushModule(const std::shared_ptr<Module>& module, const std::string& domain)
|
||||||
{
|
{
|
||||||
ModuleRef r = {module, domain};
|
ModuleRef r = {module, domain};
|
||||||
modules.emplace_back(r);
|
modules.emplace_back(r);
|
||||||
|
if (domain == "renderer")
|
||||||
|
renderer = r;
|
||||||
module->PreInit();
|
module->PreInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShadowEngine::Module& ShadowEngine::ModuleManager::GetModule(std::string name)
|
ShadowEngine::Module& ShadowEngine::ModuleManager::GetModule(const std::string& name)
|
||||||
{
|
{
|
||||||
for (auto& module : modules)
|
for (auto& module : modules)
|
||||||
{
|
{
|
||||||
|
@ -72,30 +73,47 @@ void ShadowEngine::ModuleManager::Event(SDL_Event* evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShadowEngine::ModuleManager::Update()
|
void ShadowEngine::ModuleManager::Update(int frame)
|
||||||
{
|
{
|
||||||
for (auto& module : modules)
|
for (auto& module : modules)
|
||||||
{
|
{
|
||||||
module.module->Update();
|
module.module->Update(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShadowEngine::ModuleManager::LateRender()
|
void ShadowEngine::ModuleManager::LateRender(VkCommandBuffer& commands, int frame)
|
||||||
{
|
{
|
||||||
for (auto& module : modules)
|
for (auto& module : modules)
|
||||||
{
|
{
|
||||||
module.module->LateRender();
|
module.module->LateRender(commands, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShadowEngine::ModuleManager::Render()
|
void ShadowEngine::ModuleManager::Render(VkCommandBuffer& commands, int frame)
|
||||||
{
|
{
|
||||||
for (auto& module : modules)
|
for (auto& module : modules)
|
||||||
{
|
{
|
||||||
module.module->Render();
|
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()
|
void ShadowEngine::ModuleManager::AfterFrameEnd()
|
||||||
{
|
{
|
||||||
for (auto& module : modules)
|
for (auto& module : modules)
|
||||||
|
|
|
@ -21,20 +21,20 @@ void ShadowEngine::SDL2Module::PreInit() {
|
||||||
//return 1;
|
//return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_window = new ShadowWindow(800,450);
|
_window = new ShadowWindow(1280,720);
|
||||||
|
SDL_SetWindowResizable(_window->sdlWindowPtr, SDL_TRUE);
|
||||||
|
//SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShadowEngine::SDL2Module::Update() {
|
void ShadowEngine::SDL2Module::Update(int frame) {}
|
||||||
|
|
||||||
}
|
void ShadowEngine::SDL2Module::Recreate() {}
|
||||||
|
|
||||||
void ShadowEngine::SDL2Module::Render() {
|
void ShadowEngine::SDL2Module::Render(VkCommandBuffer& commands, int frame) {}
|
||||||
|
|
||||||
}
|
void ShadowEngine::SDL2Module::OverlayRender() {}
|
||||||
|
|
||||||
void ShadowEngine::SDL2Module::LateRender() {
|
void ShadowEngine::SDL2Module::LateRender(VkCommandBuffer& commands, int frame) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ShadowEngine::SDL2Module::GetName() {
|
std::string ShadowEngine::SDL2Module::GetName() {
|
||||||
return this->GetType();
|
return this->GetType();
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
#include "core/ShadowApplication.h"
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include "core/ShadowApplication.h"
|
||||||
#include "core/Time.h"
|
#include "core/Time.h"
|
||||||
#include "core/SDL2Module.h"
|
#include "core/SDL2Module.h"
|
||||||
#include "debug/DebugModule.h"
|
#include "debug/DebugModule.h"
|
||||||
#include "dylib.hpp"
|
#include "dylib.hpp"
|
||||||
|
#include "vlkx/vulkan/abstraction/Commands.h"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_impl_vulkan.h>
|
|
||||||
#include <imgui_impl_sdl.h>
|
#include <imgui_impl_sdl.h>
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
#include <vlkx/vulkan/VulkanModule.h>
|
||||||
#include <vlkx/render/Camera.h>
|
|
||||||
#include <vlkx/render/geometry/SingleRenderer.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
#define CATCH(x) \
|
#define CATCH(x) \
|
||||||
|
@ -23,6 +20,8 @@ namespace ShadowEngine {
|
||||||
|
|
||||||
ShadowApplication* ShadowApplication::instance = nullptr;
|
ShadowApplication* ShadowApplication::instance = nullptr;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::RenderCommand> renderCommands;
|
||||||
|
|
||||||
ShadowApplication::ShadowApplication(int argc, char* argv[])
|
ShadowApplication::ShadowApplication(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
instance = this;
|
instance = this;
|
||||||
|
@ -70,23 +69,17 @@ namespace ShadowEngine {
|
||||||
|
|
||||||
void ShadowApplication::Init()
|
void ShadowApplication::Init()
|
||||||
{
|
{
|
||||||
spdlog::info("Starting Shadow Engine!");
|
moduleManager.PushModule(std::make_shared<SDL2Module>(),"core");
|
||||||
|
auto renderer = std::make_shared<VulkanModule>();
|
||||||
|
renderer->EnableEditor();
|
||||||
|
moduleManager.PushModule(renderer, "renderer");
|
||||||
|
|
||||||
loadGame();
|
loadGame();
|
||||||
|
|
||||||
printf("exe side: %p \n", VulkanManager::getInstance());
|
|
||||||
printf("exe next ID: %llu \n", ShadowEngine::SHObject::GenerateId());
|
|
||||||
|
|
||||||
moduleManager.PushModule(std::make_shared<SDL2Module>(),"core");
|
|
||||||
moduleManager.PushModule(std::make_shared<Debug::DebugModule>(), "core");
|
moduleManager.PushModule(std::make_shared<Debug::DebugModule>(), "core");
|
||||||
|
|
||||||
moduleManager.Init();
|
moduleManager.Init();
|
||||||
|
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
|
||||||
auto sdl2module = moduleManager.GetModuleByType<SDL2Module>();
|
|
||||||
|
|
||||||
window_ = sdl2module->_window;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShadowApplication::Start()
|
void ShadowApplication::Start()
|
||||||
|
@ -100,16 +93,14 @@ namespace ShadowEngine {
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleManager.Update();
|
|
||||||
moduleManager.PreRender();
|
moduleManager.PreRender();
|
||||||
|
|
||||||
VulkanManager::getInstance()->startDraw();
|
moduleManager.renderer->BeginRenderPass(renderCommands);
|
||||||
moduleManager.Render();
|
|
||||||
|
|
||||||
moduleManager.LateRender();
|
|
||||||
VulkanManager::getInstance()->endDraw();
|
|
||||||
|
|
||||||
moduleManager.AfterFrameEnd();
|
moduleManager.AfterFrameEnd();
|
||||||
|
|
||||||
|
renderCommands->nextFrame();
|
||||||
|
Time::UpdateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleManager.Destroy();
|
moduleManager.Destroy();
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
#include "core/Time.h"
|
#include "core/Time.h"
|
||||||
//#include <SDL_hints.h>
|
#include <chrono>
|
||||||
//#include <SDL.h>
|
|
||||||
|
|
||||||
int Time::NOW = 0;//SDL_GetPerformanceCounter();
|
API int Time::NOW = 0;//SDL_GetPerformanceCounter();
|
||||||
int Time::LAST = 0;
|
API int Time::LAST = 0;
|
||||||
double Time::deltaTime_ms = 0;
|
API double lastFrame = 0;
|
||||||
double Time::deltaTime = 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()
|
void Time::UpdateTime()
|
||||||
{
|
{
|
||||||
/*
|
using namespace std::chrono;
|
||||||
NOW = SDL_GetTicks();
|
auto now = system_clock::now();
|
||||||
deltaTime_ms = LAST > 0 ? (NOW - LAST) *10 : (1.0f / 60.0f);
|
auto now_ms = time_point_cast<milliseconds>(now);
|
||||||
deltaTime_ms = deltaTime_ms == 0 ? (1.0f / 60.0f) : deltaTime_ms;
|
|
||||||
|
|
||||||
LAST = NOW;
|
auto value = now_ms.time_since_epoch();
|
||||||
deltaTime = deltaTime_ms * 0.001;
|
double duration = value.count();
|
||||||
*/
|
|
||||||
|
deltaTime = duration - lastFrame;
|
||||||
|
if (startTime == 0)
|
||||||
|
startTime = duration;
|
||||||
|
timeSinceStart = duration - startTime;
|
||||||
|
|
||||||
|
lastFrame = duration;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,29 +9,26 @@
|
||||||
|
|
||||||
SHObject_Base_Impl(ShadowEngine::Debug::DebugModule)
|
SHObject_Base_Impl(ShadowEngine::Debug::DebugModule)
|
||||||
|
|
||||||
void ShadowEngine::Debug::DebugModule::Render() {
|
void ShadowEngine::Debug::DebugModule::OverlayRender() {
|
||||||
|
|
||||||
ImGui::Begin("Time", &active, ImGuiWindowFlags_MenuBar);
|
if (ImGui::Begin("Time", &active, ImGuiWindowFlags_MenuBar)) {
|
||||||
|
ImGui::Text("delta time in ms: %lf", Time::deltaTime_ms);
|
||||||
ImGui::Text("delta time in ms: %lf", Time::deltaTime_ms);
|
ImGui::Text("delta time in s: %lf", Time::deltaTime);
|
||||||
ImGui::Text("delta time in s: %lf", Time::deltaTime);
|
ImGui::Text("LAST time in: %d", Time::LAST);
|
||||||
ImGui::Text("LAST time in: %ld", Time::LAST);
|
}
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
ImGui::Begin("Active Modules", &active, ImGuiWindowFlags_MenuBar);
|
if (ImGui::Begin("Active Modules", &active, ImGuiWindowFlags_MenuBar)) {
|
||||||
|
|
||||||
ShadowEngine::ModuleManager* m = ShadowEngine::ModuleManager::instance;
|
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::TextColored(ImVec4(1.0f, 1.0f, 0.4f, 1.0f), "Active Modules:");
|
ImGui::Text("%s", module.module->GetName().c_str());
|
||||||
for (auto& module : m->modules)
|
}
|
||||||
{
|
|
||||||
ImGui::Text("%s", module.module->GetName().c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include "SFFParser.h"
|
#include "SFFParser.h"
|
||||||
#include "../../shadow-utility/src/string-helpers.h"
|
#include "string-helpers.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#define CATCH_CONFIG_MAIN
|
#define CATCH_CONFIG_MAIN
|
||||||
#include <catch2/catch.hpp>
|
#include "catch2/catch.hpp"
|
||||||
|
|
||||||
TEST_CASE("15 is less than 20", "[numbers]") {
|
TEST_CASE("15 is less than 20", "[numbers]") {
|
||||||
REQUIRE(15 < 20);
|
REQUIRE(15 < 20);
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace ShadowEngine {
|
||||||
* \brief Generates a new UID for each call
|
* \brief Generates a new UID for each call
|
||||||
* \return the next Unique ID that was just generated
|
* \return the next Unique ID that was just generated
|
||||||
*/
|
*/
|
||||||
SH_EXPORT static uint64_t GenerateId() noexcept;
|
API static uint64_t GenerateId() noexcept;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
=====
|
||||||
|
|
||||||
|
This folder exists to store VLKX renderer objects that are TEMPORARY.
|
||||||
|
It currently contains:
|
||||||
|
|
||||||
|
=====
|
||||||
|
|
||||||
|
Model Loader system
|
||||||
|
|
||||||
|
The Model Loader is temporarily implemented as a raw file reader and OBJ parser.
|
||||||
|
It should be removed when the File Format system is able to parse model and texture files.
|
||||||
|
|
||||||
|
====
|
||||||
|
|
||||||
|
Model Abstraction system
|
||||||
|
|
||||||
|
The Model Abstraction allows you to create a model with:
|
||||||
|
- A mesh
|
||||||
|
- An arbitrary number of textures
|
||||||
|
- A push constant
|
||||||
|
- An arbitrarily large uniform buffer
|
||||||
|
- A fragment and vertex shader
|
||||||
|
|
||||||
|
In all, it contains a custom Render Pipeline that will be used to draw the model.
|
||||||
|
This allows for drastic and visually appealing effects.
|
||||||
|
|
||||||
|
It should be maintained and moved into the appropriate Shadow module once ready.
|
||||||
|
|
||||||
|
====
|
||||||
|
|
||||||
|
Model Builder system
|
||||||
|
|
||||||
|
The model Builder allows for simple construction of Models using the Model Abstraction system above.
|
||||||
|
It consumes the Model Loader and returns a Model Abstraction.
|
||||||
|
|
||||||
|
It should be MOVED into the File Format parsing system once it is ready.
|
|
@ -0,0 +1,137 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "vlkx/render/shader/Pipeline.h"
|
||||||
|
#include "Loader.h"
|
||||||
|
#include "vlkx/render/render_pass/GenericRenderPass.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Descriptor.h"
|
||||||
|
|
||||||
|
namespace vlkxtemp {
|
||||||
|
|
||||||
|
class Model;
|
||||||
|
|
||||||
|
class ModelBuilder {
|
||||||
|
public:
|
||||||
|
|
||||||
|
using ShaderPool = vlkx::ShaderModule::ReleasePool;
|
||||||
|
using TextureType = ModelLoader::TextureType;
|
||||||
|
using TexturePerMesh = std::array<std::vector<std::unique_ptr<vlkx::SamplableImage>>, static_cast<int>(TextureType::Count)>;
|
||||||
|
using BindingPoints = std::map<TextureType, uint32_t>;
|
||||||
|
using TextureSource = vlkx::RefCountedTexture::ImageLocation;
|
||||||
|
using TextureSources = std::map<TextureType, std::vector<TextureSource>>;
|
||||||
|
|
||||||
|
class ModelResource {
|
||||||
|
public:
|
||||||
|
virtual ~ModelResource() = default;
|
||||||
|
virtual void load(ModelBuilder* builder) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SingleMeshModel : public ModelResource {
|
||||||
|
public:
|
||||||
|
SingleMeshModel(std::string&& path, int indexBase, TextureSources&& sources)
|
||||||
|
: objFile(std::move(path)), objIndexBase(indexBase), textureSources(std::move(sources)) {}
|
||||||
|
|
||||||
|
void load(ModelBuilder* builder) const override;
|
||||||
|
private:
|
||||||
|
const std::string objFile;
|
||||||
|
const int objIndexBase;
|
||||||
|
const TextureSources textureSources;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MultiMeshModel : public ModelResource {
|
||||||
|
public:
|
||||||
|
MultiMeshModel(std::string&& modelDir, std::string&& textureDir)
|
||||||
|
: models(std::move(modelDir)), textures(std::move(textureDir)) {}
|
||||||
|
|
||||||
|
void load(ModelBuilder* builder) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string models;
|
||||||
|
const std::string textures;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModelPushConstant {
|
||||||
|
struct Meta {
|
||||||
|
const vlkx::PushConstant* constants;
|
||||||
|
uint32_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkShaderStageFlags stage;
|
||||||
|
std::vector<Meta> constants;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Descriptors = std::vector<std::unique_ptr<vlkx::StaticDescriptor>>;
|
||||||
|
|
||||||
|
ModelBuilder(std::string&& name, int frames, float aspect, const ModelResource& resource);
|
||||||
|
|
||||||
|
ModelBuilder(const ModelBuilder&) = delete;
|
||||||
|
ModelBuilder& operator=(const ModelBuilder&) = delete;
|
||||||
|
|
||||||
|
ModelBuilder& texture(TextureType type, const TextureSource& source);
|
||||||
|
ModelBuilder& bindTextures(TextureType type, uint32_t point);
|
||||||
|
ModelBuilder& instanceBuffer(vlkx::PerInstanceVertexBuffer* buffer);
|
||||||
|
ModelBuilder& uniform(VkShaderStageFlags stage, std::vector<vlkx::Descriptor::Meta::Binding>&& bindings);
|
||||||
|
ModelBuilder& uniformBuffer(uint32_t point, const vlkx::UniformBuffer& buffer);
|
||||||
|
ModelBuilder& pushStage(VkShaderStageFlags stage);
|
||||||
|
ModelBuilder& pushConstant(const vlkx::PushConstant* constant, uint32_t offset);
|
||||||
|
ModelBuilder& shader(VkShaderStageFlagBits stage, std::string&& file);
|
||||||
|
|
||||||
|
std::unique_ptr<Model> build();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Descriptors> createDescs() const;
|
||||||
|
|
||||||
|
const int frames;
|
||||||
|
const float aspectRatio;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::StaticPerVertexBuffer> vertexBuffer;
|
||||||
|
std::vector<TexturePerMesh> textures;
|
||||||
|
TexturePerMesh sharedTextures;
|
||||||
|
BindingPoints bindPoints;
|
||||||
|
|
||||||
|
std::vector<vlkx::PerInstanceVertexBuffer*> instanceBuffers;
|
||||||
|
std::vector<vlkx::Descriptor::Meta> uniformMeta;
|
||||||
|
std::vector<vlkx::Descriptor::BufferInfos> uniformBufferMeta;
|
||||||
|
|
||||||
|
std::optional<ModelPushConstant> pushConstants;
|
||||||
|
std::unique_ptr<vlkx::GraphicsPipelineBuilder> pipelineBuilder;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Model {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Model(const Model&) = delete;
|
||||||
|
Model& operator=(const Model&) = delete;
|
||||||
|
|
||||||
|
void update(bool opaque, const VkExtent2D& frame, VkSampleCountFlagBits samples, const vlkx::RenderPass& pass, uint32_t subpass, bool flipY = true);
|
||||||
|
void draw(const VkCommandBuffer& commands, int frame, uint32_t instances) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend std::unique_ptr<Model> ModelBuilder::build();
|
||||||
|
using Descriptors = ModelBuilder::Descriptors;
|
||||||
|
using ModelPushConstant = ModelBuilder::ModelPushConstant;
|
||||||
|
using TexturePerMesh = ModelBuilder::TexturePerMesh;
|
||||||
|
|
||||||
|
Model(float aspectRatio,
|
||||||
|
std::unique_ptr<vlkx::StaticPerVertexBuffer>&& vertexBuffer,
|
||||||
|
std::vector<vlkx::PerInstanceVertexBuffer*>&& perInstanceBuffers,
|
||||||
|
std::optional<ModelPushConstant>&& pushConstants,
|
||||||
|
TexturePerMesh&& sharedTextures,
|
||||||
|
std::vector<TexturePerMesh>&& textures,
|
||||||
|
std::vector<Descriptors>&& descriptors,
|
||||||
|
std::unique_ptr<vlkx::GraphicsPipelineBuilder>&& pipelineBuilder)
|
||||||
|
: aspectRatio(aspectRatio), vertexBuffer(std::move(vertexBuffer)), perInstanceBuffers(std::move(perInstanceBuffers)),
|
||||||
|
pushConstants(std::move(pushConstants)), sharedTextures(std::move(sharedTextures)), textures(std::move(textures)),
|
||||||
|
descriptors(std::move(descriptors)), pipelineBuilder(std::move(pipelineBuilder)) {}
|
||||||
|
|
||||||
|
const float aspectRatio;
|
||||||
|
const std::unique_ptr<vlkx::StaticPerVertexBuffer> vertexBuffer;
|
||||||
|
const std::vector<vlkx::PerInstanceVertexBuffer*> perInstanceBuffers;
|
||||||
|
const std::optional<ModelPushConstant> pushConstants;
|
||||||
|
const TexturePerMesh sharedTextures;
|
||||||
|
const std::vector<TexturePerMesh> textures;
|
||||||
|
const std::vector<Descriptors> descriptors;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::GraphicsPipelineBuilder> pipelineBuilder;
|
||||||
|
std::unique_ptr<vlkx::Pipeline> pipeline;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "vlkx/render/Geometry.h"
|
||||||
|
|
||||||
|
namespace vlkxtemp {
|
||||||
|
|
||||||
|
struct Wavefront {
|
||||||
|
Wavefront(std::string_view path, size_t base);
|
||||||
|
Wavefront(const Wavefront&) = delete;
|
||||||
|
|
||||||
|
Wavefront& operator=(const Wavefront&) = delete;
|
||||||
|
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
std::vector<Geo::VertexAll> vertices;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModelLoader {
|
||||||
|
public:
|
||||||
|
enum class TextureType {
|
||||||
|
Diffuse,
|
||||||
|
Specular,
|
||||||
|
Reflection,
|
||||||
|
Cubemap,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextureData {
|
||||||
|
TextureData(TextureData&&) noexcept = default;
|
||||||
|
TextureData& operator=(TextureData&&) noexcept = default;
|
||||||
|
|
||||||
|
std::string path;
|
||||||
|
TextureType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MeshData {
|
||||||
|
MeshData() = default;
|
||||||
|
MeshData(MeshData&&) noexcept = default;
|
||||||
|
MeshData& operator=(MeshData&&) noexcept = default;
|
||||||
|
|
||||||
|
std::vector<Geo::VertexAll> vertices;
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
std::vector<TextureData> textures;
|
||||||
|
};
|
||||||
|
|
||||||
|
ModelLoader(const std::string& model, const std::string& textures);
|
||||||
|
|
||||||
|
ModelLoader(const ModelLoader&) = delete;
|
||||||
|
ModelLoader& operator=(const ModelLoader&) = delete;
|
||||||
|
|
||||||
|
const std::vector<MeshData>& getMeshes() const { return meshes; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::vector<MeshData> meshes;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,23 +1,213 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#define GLM_FORCE_RADIAN
|
#define GLM_FORCE_RADIAN
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
class Camera {
|
namespace vlkx {
|
||||||
public:
|
class Camera {
|
||||||
|
public:
|
||||||
|
|
||||||
// Set up the camera.
|
enum class Input {
|
||||||
void init(float fov, float width, float height, float near, float far);
|
Up, Down, Left, Right
|
||||||
|
};
|
||||||
|
struct Movement {
|
||||||
|
float moveSpeed = 10;
|
||||||
|
float turnSpeed = 0.0005f;
|
||||||
|
std::optional<glm::vec3> center;
|
||||||
|
};
|
||||||
|
|
||||||
// Move the camera.
|
/**
|
||||||
void setPosition(glm::vec3 positionIn);
|
* 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
|
||||||
|
};
|
||||||
|
|
||||||
glm::mat4 getViewMatrix() { return viewMatrix; };
|
Camera(const Camera &) = delete;
|
||||||
glm::mat4 getProjectionMatrix() { return projectionMatrix; }
|
|
||||||
|
|
||||||
private:
|
Camera &operator=(const Camera &) = delete;
|
||||||
glm::mat4 projectionMatrix;
|
|
||||||
glm::mat4 viewMatrix;
|
virtual ~Camera() = default;
|
||||||
glm::vec3 position;
|
|
||||||
};
|
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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -28,65 +28,66 @@ namespace Geo {
|
||||||
};
|
};
|
||||||
|
|
||||||
// All of the metadata involved with a vertex.
|
// All of the metadata involved with a vertex.
|
||||||
struct Vertex {
|
struct VertexAll {
|
||||||
glm::vec3 position; // XYZ coordinates of the vertex's position.
|
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::vec3 normal; // Unit vector pointing away from the outer surface of the vertex.
|
||||||
glm::vec3 color; // The color of the vertex.
|
|
||||||
glm::vec2 texture; // The u/v coordinates of this vertex in the bound texture.
|
glm::vec2 texture; // The u/v coordinates of this vertex in the bound texture.
|
||||||
|
|
||||||
// How fast should vertex data be read from RAM?
|
// How fast should vertex data be read from RAM?
|
||||||
static VkVertexInputBindingDescription getBindingDesc() {
|
static VkVertexInputBindingDescription getBindingDesc() {
|
||||||
VkVertexInputBindingDescription desc = {};
|
VkVertexInputBindingDescription desc = {};
|
||||||
desc.binding = 0;
|
desc.binding = 0;
|
||||||
desc.stride = sizeof(Vertex);
|
desc.stride = sizeof(VertexAll);
|
||||||
desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||||
|
|
||||||
return desc;
|
return desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// How should vertexes be handled?
|
// How should vertexes be handled?
|
||||||
static std::array<VkVertexInputAttributeDescription, 4> getAttributeDesc() {
|
static std::vector<VkVertexInputAttributeDescription> getAttributeDesc() {
|
||||||
std::array<VkVertexInputAttributeDescription, 4> descs = {};
|
return {
|
||||||
|
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, position)) },
|
||||||
// Attribute 0; position. Location 0, 3x 32-bit float.
|
{ 0, 1, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, normal)) },
|
||||||
descs[0].binding = 0;
|
{ 0, 2, VK_FORMAT_R32G32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, texture)) }
|
||||||
descs[0].location = 0;
|
};
|
||||||
descs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
|
||||||
descs[0].offset = offsetof(Vertex, position);
|
|
||||||
|
|
||||||
// Attribute 1; normal. Location 1, 3x 32-bit float.
|
|
||||||
descs[1].binding = 0;
|
|
||||||
descs[1].location = 1;
|
|
||||||
descs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
|
|
||||||
descs[1].offset = offsetof(Vertex, normal);
|
|
||||||
|
|
||||||
// Attribute 2; color. Location 2, 3x 32-bit float.
|
|
||||||
descs[2].binding = 0;
|
|
||||||
descs[2].location = 2;
|
|
||||||
descs[2].format = VK_FORMAT_R32G32B32_SFLOAT;
|
|
||||||
descs[2].offset = offsetof(Vertex, color);
|
|
||||||
|
|
||||||
// Attribute 3; texture. Location 3, 2x 32-bit float.
|
|
||||||
descs[3].binding = 0;
|
|
||||||
descs[3].location = 3;
|
|
||||||
descs[3].format = VK_FORMAT_R32G32_SFLOAT;
|
|
||||||
descs[3].offset = offsetof(Vertex, texture);
|
|
||||||
|
|
||||||
return descs;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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.
|
// Contains data about a given Mesh.
|
||||||
class Mesh {
|
class Mesh {
|
||||||
public:
|
public:
|
||||||
// Pre-load the data for a triangle into the given buffers.
|
// Pre-load the data for a triangle into the given buffers.
|
||||||
static void setTriData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
|
static void setTriData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
|
||||||
// Pre-load the data for a quad into the given buffers.
|
// Pre-load the data for a quad into the given buffers.
|
||||||
static void setQuadData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
|
static void setQuadData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
|
||||||
// Pre-load the data for a cube into the given buffers.
|
// Pre-load the data for a cube into the given buffers.
|
||||||
static void setCubeData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
|
static void setCubeData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
|
||||||
// Pre-load the data for a sphere into the given buffers.
|
// Pre-load the data for a sphere into the given buffers.
|
||||||
static void setSphereData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
|
static void setSphereData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RenderPass {
|
|
||||||
public:
|
|
||||||
RenderPass();
|
|
||||||
~RenderPass();
|
|
||||||
|
|
||||||
VkRenderPass pass;
|
|
||||||
|
|
||||||
void createVertexRenderPass(VkFormat format);
|
|
||||||
void createRTRenderPass(VkFormat format);
|
|
||||||
void createRTPhysicsPass(VkFormat format);
|
|
||||||
|
|
||||||
void beginRenderPass(std::vector<VkClearValue> clearValues, VkCommandBuffer commands, VkFramebuffer framebuffer, VkExtent2D extent);
|
|
||||||
void endRenderPass(VkCommandBuffer commands);
|
|
||||||
|
|
||||||
void destroy();
|
|
||||||
};
|
|
|
@ -1,35 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vlkx/render/shader/Pipeline.h>
|
|
||||||
#include <vlkx/render/shader/GeoBuffers.h>
|
|
||||||
#include <vlkx/render/shader/Descriptor.h>
|
|
||||||
#include <vlkx/render/Camera.h>
|
|
||||||
|
|
||||||
// Renders a single object.
|
|
||||||
class SingleRenderer {
|
|
||||||
public:
|
|
||||||
|
|
||||||
void createSingleRenderer(Geo::MeshType type, glm::vec3 posIn, glm::vec3 scaleIn);
|
|
||||||
|
|
||||||
void updateUniforms(Camera camera);
|
|
||||||
|
|
||||||
void draw();
|
|
||||||
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
void setPosition(glm::vec3 newPos) { position = newPos; }
|
|
||||||
void setRotation(glm::mat4 newRot) { rotation = newRot; }
|
|
||||||
|
|
||||||
Descriptor getDescriptor() { return descriptor; }
|
|
||||||
|
|
||||||
glm::mat4 getRotation() { return rotation; }
|
|
||||||
private:
|
|
||||||
|
|
||||||
Pipeline pipeline;
|
|
||||||
GeoBuffers buffers;
|
|
||||||
Descriptor descriptor;
|
|
||||||
|
|
||||||
glm::vec3 position;
|
|
||||||
glm::vec3 scale;
|
|
||||||
glm::mat4 rotation;
|
|
||||||
};
|
|
|
@ -0,0 +1,242 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include "vlkx/vulkan/abstraction/ImageUsage.h"
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include "GenericRenderPass.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The common base class for rendering and computing passes that run on the GPU.
|
||||||
|
* Provides some utility methods for handling attachment metadata between subpasses.
|
||||||
|
*/
|
||||||
|
class CommonPass {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit CommonPass(int passes) : numPasses(passes) {}
|
||||||
|
|
||||||
|
// Delete the copy and move constructors
|
||||||
|
CommonPass(const CommonPass&) = delete;
|
||||||
|
CommonPass& operator=(const CommonPass&) = delete;
|
||||||
|
virtual ~CommonPass() = default;
|
||||||
|
|
||||||
|
// Get the image layout of the given image at the start of this pass
|
||||||
|
VkImageLayout getInitialLayout(const std::string& name) const;
|
||||||
|
// Get the image layout of the given image at the end of this pass
|
||||||
|
VkImageLayout getFinalLayout(const std::string& name) const;
|
||||||
|
// Get the image layout of the given image before the given subpass starts
|
||||||
|
VkImageLayout getSubpassLayout(const std::string& name, int subpass) const;
|
||||||
|
|
||||||
|
// Update the state of the given image's usage tracker.
|
||||||
|
void update(const std::string& name, MultiImageTracker& tracker) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Some metadata about the usage of an image between subpasses.
|
||||||
|
*/
|
||||||
|
struct Usages {
|
||||||
|
Usages(const int last, const ImageUsage* prev, const ImageUsage* curr) : lastSubpass(last), lastUsage(*prev), currentUsage(*curr) {}
|
||||||
|
const int lastSubpass;
|
||||||
|
const ImageUsage& lastUsage;
|
||||||
|
const ImageUsage& currentUsage;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the usage of an image in the pass to its' tracker.
|
||||||
|
void addUsage(std::string&& name, UsageTracker&& tracker);
|
||||||
|
|
||||||
|
// Get the full history of the image's usages up to this rendering pass.
|
||||||
|
const UsageTracker& getHistory(const std::string& name) const;
|
||||||
|
|
||||||
|
// Get the usage of an image at the start of the given pass.
|
||||||
|
const ImageUsage* getUsage(const std::string& name, int pass) const;
|
||||||
|
|
||||||
|
// Retrieve image usage data, but only if the image is barriered at the given pass.
|
||||||
|
std::optional<Usages> checkForSync(const std::string& name, int pass) const;
|
||||||
|
|
||||||
|
// Validate that the subpass is valid for the given image.
|
||||||
|
// The meaning of includeVirtual is defined by the child implementation.
|
||||||
|
void validate(int pass, const std::string& image, bool includeVirtual) const;
|
||||||
|
|
||||||
|
int getVirtualInitial() const { return -1; }
|
||||||
|
int getVirtualFinal() const { return numPasses; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::map<std::string, UsageTracker> usageHistory;
|
||||||
|
const int numPasses;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Common Pass implementation for Graphics passes; that is, render passes that output to color buffers
|
||||||
|
* for presentation to the screen, or to be used as textures in such.
|
||||||
|
* The instance of the GraphicsPass can be stored and reused to create multiple RenderPassBuilders.
|
||||||
|
* In this way it is essentially a RenderPassBuilderFactory.
|
||||||
|
*/
|
||||||
|
class GraphicsPass : public CommonPass {
|
||||||
|
public:
|
||||||
|
|
||||||
|
using LocationGetter = std::function<int(int pass)>;
|
||||||
|
|
||||||
|
explicit GraphicsPass(int passes) : CommonPass {passes} {}
|
||||||
|
|
||||||
|
GraphicsPass(const GraphicsPass&) = delete;
|
||||||
|
GraphicsPass& operator=(const GraphicsPass&) = delete;
|
||||||
|
|
||||||
|
// Get the default render ops for a color buffer.
|
||||||
|
static RenderPassBuilder::Attachment::OpsType getDefaultOps() {
|
||||||
|
return RenderPassBuilder::Attachment::ColorOps { VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the default render ops for a stencil buffer.
|
||||||
|
static RenderPassBuilder::Attachment::OpsType getStencilOps() {
|
||||||
|
return RenderPassBuilder::Attachment::StencilDepthOps { VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an image reference that is used in this render pass.
|
||||||
|
* @param name the name of the image used
|
||||||
|
* @param history the usage history of the image, for tracking purposes
|
||||||
|
* @param getter a function to get the location of the image, only if the image is used as a render target.
|
||||||
|
* @param ops optional; uses the static defaults if not present.
|
||||||
|
* @return the index into the VkAttachmentDescriptions.
|
||||||
|
*/
|
||||||
|
int add(const std::string& name, UsageTracker&& history, LocationGetter&& getter, const std::optional<RenderPassBuilder::Attachment::OpsType> ops = std::nullopt);
|
||||||
|
|
||||||
|
#define fluent GraphicsPass&
|
||||||
|
|
||||||
|
// Specifies that the source image will be resolved to the single destination at the given pass.
|
||||||
|
fluent addMultisample(const std::string& source, const std::string& dest, int pass);
|
||||||
|
|
||||||
|
// Build a RenderPassBuilder with the information provided so far.
|
||||||
|
std::unique_ptr<RenderPassBuilder> build(int framebuffers);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct AttachmentMeta {
|
||||||
|
int index;
|
||||||
|
LocationGetter getter;
|
||||||
|
vlkx::RenderPassBuilder::Attachment::OpsType ops;
|
||||||
|
std::map<int, std::string> multisample;
|
||||||
|
};
|
||||||
|
|
||||||
|
void setAttachments();
|
||||||
|
void setSubpasses();
|
||||||
|
void setDependencies();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first subpass where the given image is used as a render target.
|
||||||
|
* @param history the usage history of the image; what it was used at at each subpass.
|
||||||
|
* @return nullopt if the image was not used as a render target, the index of the subpass where it was, if not.
|
||||||
|
*/
|
||||||
|
std::optional<int> getFirstRenderTarget(const UsageTracker& history) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the operations that should be used for the given image attachment.
|
||||||
|
* If the user specified ops, it will be checekd against the history.
|
||||||
|
* @param name the name of the image to use as the attachment
|
||||||
|
* @param history the usage history of the attachment, for internal checks
|
||||||
|
* @param userOps operations to use for the image, as an optional override.
|
||||||
|
* @return the ColorOps to use for the given attachment.
|
||||||
|
*/
|
||||||
|
RenderPassBuilder::Attachment::OpsType getOps(const std::string& name, const UsageTracker& history, const std::optional<RenderPassBuilder::Attachment::OpsType>& userOps) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the usage type of the image.
|
||||||
|
* Assumption: an image is only ever used as a color OR depth stencil. Never both.
|
||||||
|
* Assumption: Multisample == RenderTarget
|
||||||
|
* @param name the name of the image to check
|
||||||
|
* @param history the history of the image's usages in the GPU.
|
||||||
|
* @return whether the image is a RenderTarget or a DepthStencil buffer.
|
||||||
|
*/
|
||||||
|
ImageUsage::Type getUsageType(const std::string& name, const UsageTracker& history) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the image is used as type at subpass in its' history.
|
||||||
|
*/
|
||||||
|
bool verifyImageUsage(const UsageTracker& history, int subpass, ImageUsage::Type type) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the subpass is virtual.
|
||||||
|
* For a Render Pass, virtual means it is a preprocessing step.
|
||||||
|
*/
|
||||||
|
bool isVirtual(int subpass) const {
|
||||||
|
return subpass == getVirtualInitial() || subpass == getVirtualFinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the subpass index; for virtual passes, it uses an EXTERNAL subpass.
|
||||||
|
*/
|
||||||
|
uint32_t checkSubpass(int subpass) const {
|
||||||
|
return isVirtual(subpass) ? VK_SUBPASS_EXTERNAL : (uint32_t) subpass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the image's usages are compatible with a render pass.
|
||||||
|
* For example, compute shader linear buffers cannot be used as render targets, etc.
|
||||||
|
*/
|
||||||
|
void verifyHistory(const std::string& image, const UsageTracker& history) const;
|
||||||
|
|
||||||
|
std::map<std::string, AttachmentMeta> metas;
|
||||||
|
std::unique_ptr<vlkx::RenderPassBuilder> builder;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Common Pass implementation for Compute Shaders.
|
||||||
|
* That is, shaders that do not write to color buffers.
|
||||||
|
* A subpass can execute multiple compute shaders unbarriered, which increases efficiency.
|
||||||
|
* We still need to transition images between passes when necessary, hence the wrapper.
|
||||||
|
*/
|
||||||
|
class ComputePass : public CommonPass {
|
||||||
|
public:
|
||||||
|
|
||||||
|
ComputePass(const ComputePass&) = delete;
|
||||||
|
ComputePass& operator=(const ComputePass&) = delete;
|
||||||
|
|
||||||
|
#define fluent ComputePass&
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the given image as an attachment to the compute shader pass.
|
||||||
|
* @param name the name of the image
|
||||||
|
* @param history the usage history of the image
|
||||||
|
* @return the ComputePass instance, for chaining.
|
||||||
|
*/
|
||||||
|
fluent add(std::string&& name, UsageTracker&& history);
|
||||||
|
fluent add(const std::string& name, UsageTracker&& history) {
|
||||||
|
return add(std::string(name), std::move(history));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run computeOps, insert memory barriers to transition used images into the appropriate format.
|
||||||
|
* Images must be a superset of all images that were called with add().
|
||||||
|
* Compute_ops must be equal to the number of subpasses.
|
||||||
|
* Commands must be recording.
|
||||||
|
* @param commands the command buffer to write into.
|
||||||
|
* @param queueFamily the family to use for inserting barriers
|
||||||
|
* @param images the list of images that were used in the compute pass
|
||||||
|
* @param computeOps the compute functions to upload to the GPU
|
||||||
|
*/
|
||||||
|
void execute(const VkCommandBuffer& commands, uint32_t queueFamily, const std::map<std::string, const VkImage*>& images, const std::vector<std::function<void()>>& computeOps) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a memory barrier, to transition the layout of the image from the previous to the curent.
|
||||||
|
* The barrier is performed using the given queue family.
|
||||||
|
* @param commands the command buffer to write into.
|
||||||
|
* @param queueFamily the family to use for inserting barriers.
|
||||||
|
* @param image the list of images that were used in the compute pass
|
||||||
|
* @param prev the previous usage of the image; the state being transitioned from
|
||||||
|
* @param current the new usage of the image; the state being transitioned to.
|
||||||
|
*/
|
||||||
|
void barrier(const VkCommandBuffer& commands, uint32_t queueFamily, const VkImage& image, const ImageUsage& prev, const ImageUsage& current) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify whether the previous usages of the given image in its' history is compatible with a compute shader.
|
||||||
|
* For example, a fragment shader output image is not compatible.
|
||||||
|
* @param name the name of the image being checked
|
||||||
|
* @param history the usage history of the image/
|
||||||
|
*/
|
||||||
|
void verify(const std::string& name, const UsageTracker& history) const;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <variant>
|
||||||
|
#include <optional>
|
||||||
|
#include "vlkx/vulkan/Tools.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gathers the operations that the GPU should perform when rendering to a framebuffer.
|
||||||
|
* Subpasses and dependencies are to be configured automatically using the builder below.
|
||||||
|
* RenderPass objects are disposable, and should be discarded when the framebuffer changes.
|
||||||
|
*/
|
||||||
|
class RenderPass {
|
||||||
|
public:
|
||||||
|
using RenderFunc = std::function<void(const VkCommandBuffer& buffer)>;
|
||||||
|
|
||||||
|
// Delete copy and move constructors to prevent the GPU getting confused with what we're trying to do
|
||||||
|
RenderPass(const RenderPass&) = delete;
|
||||||
|
RenderPass& operator=(const RenderPass&) = delete;
|
||||||
|
~RenderPass();
|
||||||
|
|
||||||
|
RenderPass(int subpasses, VkRenderPass pass, std::vector<VkClearValue> clear, VkExtent2D ext, std::vector<VkFramebuffer> fbs, std::vector<int> attachs)
|
||||||
|
: subpassCount(subpasses), renderPass(pass), clearValues(std::move(clear)), extent(ext), framebuffers(std::move(fbs)), attachments(std::move(attachs)) {}
|
||||||
|
|
||||||
|
const VkRenderPass& operator*() const { return renderPass; }
|
||||||
|
|
||||||
|
int getAttachsInSubpass(int subpass) const {
|
||||||
|
return attachments[subpass];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload all of the subpass render commands to the command buffer.
|
||||||
|
* The size of ops must be equal to the number of subpasses in this render pass.
|
||||||
|
* @param commands the command buffer to execute on; must be recording
|
||||||
|
* @param imageIndex the index of the image on the swapchain that we're rendering to; the target framebuffer.
|
||||||
|
* @param ops the render operations to add onto the command buffer.
|
||||||
|
*/
|
||||||
|
void execute(const VkCommandBuffer& commands, int imageIndex, std::vector<RenderFunc> ops) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The number of sub-render-passes in this pass.
|
||||||
|
const int subpassCount;
|
||||||
|
// The VkRenderPass that this class wraps.
|
||||||
|
VkRenderPass renderPass;
|
||||||
|
// The clear values that will wipe all framebuffers to their empty states.
|
||||||
|
const std::vector<VkClearValue> clearValues;
|
||||||
|
// The size of the framebuffers (all are the same size)
|
||||||
|
const VkExtent2D extent;
|
||||||
|
// The framebuffers that we can render to
|
||||||
|
const std::vector<VkFramebuffer> framebuffers;
|
||||||
|
// The number of color attachments (sampled color images) in each subpass, by subpass index.
|
||||||
|
const std::vector<int> attachments;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stateful, fluent way to create Render Passes.
|
||||||
|
* This object can be stored and reused; when the window size changes, simply set the extent and
|
||||||
|
* export a new RenderPass to be used in the pipeline.
|
||||||
|
*
|
||||||
|
* Allows setting sub-passes, sub-pass dependencies, operations to read and write them, etc.
|
||||||
|
*/
|
||||||
|
class RenderPassBuilder {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information required to define an attachment to be used in a render pass.
|
||||||
|
* Contains information on the layout, the operations to use on read and write, etc.
|
||||||
|
*/
|
||||||
|
struct Attachment {
|
||||||
|
// Operations to use on color attachments.
|
||||||
|
struct ColorOps {
|
||||||
|
VkAttachmentLoadOp LOAD; // Load data in the color attachment
|
||||||
|
VkAttachmentStoreOp STORE; // Store data into the color attachment
|
||||||
|
};
|
||||||
|
|
||||||
|
// Operations to use on depth and stencil buffers.
|
||||||
|
struct StencilDepthOps {
|
||||||
|
VkAttachmentLoadOp DEPTH_LOAD; // Load data in the depth attachment
|
||||||
|
VkAttachmentStoreOp DEPTH_STORE; // Store data in the depth attachment
|
||||||
|
VkAttachmentLoadOp STENCIL_LOAD; // Load data in the stencil attachment
|
||||||
|
VkAttachmentStoreOp STENCIL_STORE; // Store data in the stencil attachment
|
||||||
|
};
|
||||||
|
|
||||||
|
using OpsType = std::variant<ColorOps, StencilDepthOps>;
|
||||||
|
|
||||||
|
// The operations that can be performed on this attachment
|
||||||
|
OpsType ops;
|
||||||
|
// The initial layout of an image in this attachment (pre-GPU upload)
|
||||||
|
VkImageLayout layoutInitial;
|
||||||
|
// The final layout of an image in this attachment (as seen by the shader)
|
||||||
|
VkImageLayout layoutFinal;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes attachments used in each subpass, in terms of Vulkan data.
|
||||||
|
* Attachment here is used in the conceptual sense, not referring to the Attachment struct above.
|
||||||
|
*
|
||||||
|
* If multisampleReferences is non-zero, its' size must be equal to colorReferences' size.
|
||||||
|
* Each index of multisampleReferences refers to the same-index colorReferences entry.
|
||||||
|
*
|
||||||
|
* If stencilDepthReference is non-zero, it is shared between all subpasses.
|
||||||
|
*/
|
||||||
|
struct SubpassAttachments {
|
||||||
|
std::vector<VkAttachmentReference> colorReferences;
|
||||||
|
std::vector<VkAttachmentReference> multisampleReferences;
|
||||||
|
std::optional<VkAttachmentReference> stencilDepthReference;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the dependencies between each sub-pass.
|
||||||
|
* It describes how each subpass will read or modify the data written by the last subpass, if at all.
|
||||||
|
* This dependency information can allow the GPU to run some passes in parallel, and enforce the
|
||||||
|
* strict ordering of those that require it.
|
||||||
|
*/
|
||||||
|
struct SubpassDependency {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines some metadata about the subpass.
|
||||||
|
* Contains the index of the subpass, how it will use data from the last pass, and what exactly it will do.
|
||||||
|
*/
|
||||||
|
struct SubpassMeta {
|
||||||
|
/**
|
||||||
|
* Index of the subpass.
|
||||||
|
*/
|
||||||
|
uint32_t index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how we want to modify the data passed to us from the last subpass.
|
||||||
|
* Will change how the next subpass will wait for the completion of this subpass, if at all.
|
||||||
|
*
|
||||||
|
* VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT:
|
||||||
|
* read/write to the color attachment
|
||||||
|
*
|
||||||
|
* VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT:
|
||||||
|
* VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT:
|
||||||
|
* read/write to the depth or stencil attachment
|
||||||
|
*
|
||||||
|
* VK_PIPELINE_STAGE_VERTEX_SHADER_BIT:
|
||||||
|
* VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT:
|
||||||
|
* read all attachments
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
VkPipelineStageFlags stage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how we want to synchronize with the subpass after this one.
|
||||||
|
*
|
||||||
|
* VK_ACCESS_SHADER_READ_BIT:
|
||||||
|
* VK_ACCESS_SHADER_WRITE_BIT:
|
||||||
|
* read a texture or write to a color buffer
|
||||||
|
*
|
||||||
|
* VK_ACCESS_COLOR_ATTACHMENT_READ_BIT:
|
||||||
|
* VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT:
|
||||||
|
* VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT:
|
||||||
|
* VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT:
|
||||||
|
* read/write to an attachment.
|
||||||
|
*
|
||||||
|
* VK_ACCESS_INPUT_ATTACHMENT_READ_BIT:
|
||||||
|
* for accessing the inputAttachment of a subpass.
|
||||||
|
*
|
||||||
|
* 0:
|
||||||
|
* do not write, but the next subpass will.
|
||||||
|
* Will automatically barrier the render pass.
|
||||||
|
*/
|
||||||
|
VkAccessFlags access;
|
||||||
|
};
|
||||||
|
|
||||||
|
SubpassMeta source; // The source subpass of this dependency (will take effect after this pass completes)
|
||||||
|
SubpassMeta destination; // The destination subpass of this dependency (will take effect before this pass)
|
||||||
|
VkDependencyFlags flags; // Other information that Vulkan needs to know about this dependency; for example, if we use an inputAttachment.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a color attachment.
|
||||||
|
* Effectively RGB images that live entirely on the GPU.
|
||||||
|
*
|
||||||
|
* Describes the resolution mechanics of a multisampled image.
|
||||||
|
*/
|
||||||
|
struct ColorAttachmentMeta {
|
||||||
|
int location; // Where the GPU shaders expect this attachment to be available.
|
||||||
|
int descriptionIdx; // Index of this attachment in the VkAttachmentDescription data.
|
||||||
|
VkImageLayout layout; // Vulkan image layout. Shader optimized or host readable.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a list of VkAttachmentReference that describes the attachments used per subpass.
|
||||||
|
*/
|
||||||
|
static std::vector<VkAttachmentReference> parseColorReferences(std::vector<ColorAttachmentMeta> meta);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a list of VkAttachmentReference that describes the multisampling setup.
|
||||||
|
*/
|
||||||
|
static std::vector<VkAttachmentReference> parseMutisampling(int colorReferencesCount, std::vector<ColorAttachmentMeta> meta);
|
||||||
|
|
||||||
|
RenderPassBuilder(const RenderPassBuilder&) = delete;
|
||||||
|
RenderPassBuilder& operator=(const RenderPassBuilder&) = delete;
|
||||||
|
~RenderPassBuilder() = default;
|
||||||
|
RenderPassBuilder() = default;
|
||||||
|
|
||||||
|
/** Fluent API Features; chain calls to set data on the render pass.*/
|
||||||
|
#define fluent RenderPassBuilder&
|
||||||
|
|
||||||
|
// Set the number of framebuffers in the render pass
|
||||||
|
fluent setFramebufferCount(int count);
|
||||||
|
// Set an attachment description in the render pass
|
||||||
|
fluent setAttachment(int idx, const Attachment& attachment);
|
||||||
|
// Update the image backing an attachment. The function must be executable during execute() later on.
|
||||||
|
fluent updateAttachmentBacking(int idx, std::function<const Image&(int idx)>&& getBacking);
|
||||||
|
// Set a specific subpass. Use the static parse methods to create these vectors.
|
||||||
|
fluent setSubpass(int idx, std::vector<VkAttachmentReference>&& color, std::vector<VkAttachmentReference>&& multisample, VkAttachmentReference& depthStencil);
|
||||||
|
// Add a dependency between two subpasses.
|
||||||
|
fluent addDependency(const SubpassDependency& dep);
|
||||||
|
|
||||||
|
// Build the Render Pass with all the information given.
|
||||||
|
// Can be called multiple times with the same Builder.
|
||||||
|
[[nodiscard]] std::unique_ptr<vlkx::RenderPass> build() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Number of framebuffers in the render pass
|
||||||
|
std::optional<int> framebufferCount;
|
||||||
|
// Descriptions of used attachments
|
||||||
|
std::vector<VkAttachmentDescription> attachmentDescriptors;
|
||||||
|
// Functions to return attachment images.
|
||||||
|
std::vector<std::function<const Image&(int idx)>> attachmentGetters;
|
||||||
|
// Values to clear all attachments
|
||||||
|
std::vector<VkClearValue> clearValues;
|
||||||
|
// Descriptions of subpasses.
|
||||||
|
std::vector<SubpassAttachments> subpassAttachments;
|
||||||
|
// Descriptions of subpass dependencies.
|
||||||
|
std::vector<VkSubpassDependency> subpassDependencies;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <memory>
|
||||||
|
#include "GenericRenderPass.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/ImageUsage.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
// A simple and versatile way to configure render passes.
|
||||||
|
// Intended to be used with the SimpleRenderPass and the ScreenRenderPass.
|
||||||
|
class RendererConfig {
|
||||||
|
public:
|
||||||
|
RendererConfig(std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen = true) : renderImages(destinations) {
|
||||||
|
numOpaquePasses = 1;
|
||||||
|
rendersToScreen = toScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
RendererConfig(int passCount, std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen = true, std::optional<int> firstTransparent = std::nullopt, std::optional<int> firstOverlay = std::nullopt);
|
||||||
|
|
||||||
|
// Get the number of passes that use the depth buffer.
|
||||||
|
int depthPasses() const {
|
||||||
|
return numOpaquePasses + numTransparentPasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the total number of passes.
|
||||||
|
int passes() const {
|
||||||
|
return depthPasses() + numOverlayPasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get whether any passes use the depth buffer.
|
||||||
|
bool usesDepth() const {
|
||||||
|
return depthPasses() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the render pass builder. Can be called multiple times.
|
||||||
|
void build();
|
||||||
|
|
||||||
|
RendererConfig(RendererConfig&) noexcept = default;
|
||||||
|
RendererConfig(const RendererConfig&) = default;
|
||||||
|
|
||||||
|
int numOpaquePasses = 0;
|
||||||
|
int numTransparentPasses = 0;
|
||||||
|
std::vector<std::unique_ptr<vlkx::Image>>& renderImages;
|
||||||
|
bool rendersToScreen;
|
||||||
|
private:
|
||||||
|
int numOverlayPasses = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all of the information required to use an attachment.
|
||||||
|
* This is heavy, so is only used when the image is being finalized.
|
||||||
|
*/
|
||||||
|
struct AttachmentConfig {
|
||||||
|
AttachmentConfig(std::string_view name, std::optional<int>* index)
|
||||||
|
: name(name), index(*index) {}
|
||||||
|
|
||||||
|
AttachmentConfig& setOps(const RenderPassBuilder::Attachment::OpsType& ops) {
|
||||||
|
loadStoreOps = ops;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentConfig& setUsage(const ImageUsage& final) {
|
||||||
|
finalUsage = final;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
std::optional<int>& index;
|
||||||
|
std::optional<RenderPassBuilder::Attachment::OpsType> loadStoreOps;
|
||||||
|
std::optional<ImageUsage> finalUsage;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lighter version of the Attachment, used primarily in the On-Screen Pass Manager.
|
||||||
|
*/
|
||||||
|
struct Attachment {
|
||||||
|
explicit Attachment(std::string_view image) : name(image) {}
|
||||||
|
|
||||||
|
// Adds the image to the tracker and initializes state.
|
||||||
|
void add(MultiImageTracker& tracker, const Image& image) {
|
||||||
|
tracker.track(name, image.getUsage());
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentConfig makeConfig() { return { name, &index }; }
|
||||||
|
|
||||||
|
const std::string name;
|
||||||
|
std::optional<int> index;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Manages Render Passes that will output to the screen.
|
||||||
|
// This is necessarily exclusively a graphical pass.
|
||||||
|
// If necessary, a depth and stencil buffer will be maintained.
|
||||||
|
// The color buffer is automatically assumed to be the swapchain.
|
||||||
|
class ScreenRenderPassManager {
|
||||||
|
public:
|
||||||
|
explicit ScreenRenderPassManager(RendererConfig renderConfig) : config(renderConfig) {}
|
||||||
|
|
||||||
|
ScreenRenderPassManager(const ScreenRenderPassManager&) = delete;
|
||||||
|
ScreenRenderPassManager& operator=(const ScreenRenderPassManager&) = delete;
|
||||||
|
|
||||||
|
// Initialize the render pass we're managing.
|
||||||
|
void initializeRenderPass();
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::RenderPass>& getPass() { return pass; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Prepare the Render Pass builder.
|
||||||
|
void preparePassBuilder();
|
||||||
|
|
||||||
|
const RendererConfig config;
|
||||||
|
|
||||||
|
Attachment destinationInfo { "Destination" };
|
||||||
|
Attachment multisampleInfo { "Multisample" };
|
||||||
|
Attachment depthStencilInfo { "Depth-Stencil" };
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::Image> depthStencilImage;
|
||||||
|
std::unique_ptr<vlkx::RenderPassBuilder> passBuilder;
|
||||||
|
std::unique_ptr<vlkx::RenderPass> pass;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility namespace used to house a constructor for creating a "simple" render pass with all the defaults.
|
||||||
|
*/
|
||||||
|
namespace SimpleRenderPass {
|
||||||
|
static std::unique_ptr<RenderPassBuilder> createBuilder(int framebuffers, const RendererConfig& config, const AttachmentConfig& color, const AttachmentConfig* multisample, const AttachmentConfig* depthStencil, MultiImageTracker& tracker);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class Descriptor {
|
|
||||||
public:
|
|
||||||
Descriptor();
|
|
||||||
~Descriptor();
|
|
||||||
|
|
||||||
// Global descriptor bindings
|
|
||||||
VkDescriptorSetLayout layout;
|
|
||||||
VkDescriptorPool pool;
|
|
||||||
VkDescriptorSet set;
|
|
||||||
|
|
||||||
// Allocate and prepare the descriptors for a simple (uniform buffer only) descriptor.
|
|
||||||
void createAndAllocateSimple(uint32_t imageCount);
|
|
||||||
|
|
||||||
// Fill the Descriptors with the given uniforms
|
|
||||||
void populate(uint32_t imageCount, VkBuffer uniforms, size_t bufferSize);
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
// Set up the layout for a single UBO
|
|
||||||
void createSimpleLayout();
|
|
||||||
// Setup the pool for a single UBO
|
|
||||||
void createSimplePool(uint32_t imageCount);
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,37 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <vlkx/render/Geometry.h>
|
|
||||||
#include <vlkx/vulkan/Tools.h>
|
|
||||||
|
|
||||||
// Contains memory and objects required to store information about geometry.
|
|
||||||
class GeoBuffers {
|
|
||||||
public:
|
|
||||||
|
|
||||||
GeoBuffers();
|
|
||||||
~GeoBuffers();
|
|
||||||
|
|
||||||
// Program and virtual memory for vertex data.
|
|
||||||
std::vector<Geo::Vertex> vertices;
|
|
||||||
VkTools::ManagedBuffer vertexBuffer;
|
|
||||||
|
|
||||||
// Program and virtual memory for indices data.
|
|
||||||
std::vector<uint32_t> indices;
|
|
||||||
VkTools::ManagedBuffer indexBuffer;
|
|
||||||
|
|
||||||
// Virtual memory for uniforms - translation matrices, etc.
|
|
||||||
VkTools::ManagedBuffer uniformBuffer;
|
|
||||||
|
|
||||||
void createBuffers(Geo::MeshType type);
|
|
||||||
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
void createVertexBuffer();
|
|
||||||
void createIndexBuffer();
|
|
||||||
void createUniformBuffer();
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,31 +1,205 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include "shadow/util/RefCounter.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
|
||||||
class Pipeline {
|
namespace vlkx {
|
||||||
public:
|
class Pipeline;
|
||||||
Pipeline();
|
|
||||||
~Pipeline();
|
|
||||||
|
|
||||||
// The active Graphics Pipeline layout.
|
// A simple wrapper for the shader module used by the pipeline.
|
||||||
VkPipelineLayout layout;
|
class ShaderModule {
|
||||||
// The active Graphics Pipeline instance.
|
public:
|
||||||
VkPipeline pipeline;
|
using CountedShader = shadowutil::RefCounter<ShaderModule>;
|
||||||
|
using ReleasePool = CountedShader::AutoRelease;
|
||||||
|
|
||||||
// Create the layout and pipeline for a vertex renderer
|
ShaderModule(const std::string& path);
|
||||||
void create(VkExtent2D extent, VkDescriptorSetLayout set, VkRenderPass renderPass);
|
|
||||||
|
|
||||||
void destroy();
|
ShaderModule(const ShaderModule&) = delete;
|
||||||
|
ShaderModule& operator=(const ShaderModule&) = delete;
|
||||||
|
|
||||||
private:
|
~ShaderModule() {
|
||||||
|
vkDestroyShaderModule(VulkanModule::getInstance()->getDevice()->logical, shader, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<char> readFile(const std::string& filename);
|
const VkShaderModule& operator*() const { return shader; }
|
||||||
VkShaderModule createShaderModule(const std::vector<char>& code);
|
|
||||||
|
|
||||||
void createPipelineLayout(VkDescriptorSetLayout set);
|
private:
|
||||||
|
VkShaderModule shader;
|
||||||
// For rendering objects that use traditional vertex-based mesh geometry.
|
};
|
||||||
// See Geo::Vertex for the uniform bindings.
|
|
||||||
void createVertexPipeline(VkExtent2D extent, VkRenderPass renderPass);
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
|
||||||
#include <array>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RenderTexture {
|
|
||||||
public:
|
|
||||||
// Create all image views and all framebuffers from a set of images, a format, a size, and the current rendering pass.
|
|
||||||
virtual void createViewsAndFramebuffer(std::vector<VkImage> images, VkFormat format, VkExtent2D extent, VkRenderPass pass) = 0;
|
|
||||||
// Create views for swapChainImages with the given format.
|
|
||||||
virtual void createViews(VkFormat format) = 0;
|
|
||||||
// Create a framebuffer for the swapChainImages, for the given pass.
|
|
||||||
virtual void createFramebuffer(VkExtent2D extent, VkRenderPass pass) = 0;
|
|
||||||
|
|
||||||
virtual VkFramebuffer getFramebuffer(int ID) = 0;
|
|
||||||
|
|
||||||
virtual void destroy() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleRenderTexture : public RenderTexture {
|
|
||||||
public:
|
|
||||||
SingleRenderTexture();
|
|
||||||
~SingleRenderTexture();
|
|
||||||
|
|
||||||
// A list of all images involved with this texture (color, normals, etc)
|
|
||||||
std::vector<VkImage> swapChainImages;
|
|
||||||
// The sizes of all attached images.
|
|
||||||
VkExtent2D swapChainImageExtent;
|
|
||||||
|
|
||||||
// Views - mipmaps, portions, crops, etc of the attached images.
|
|
||||||
std::vector<VkImageView> swapChainImageViews;
|
|
||||||
// Framebuffers containing the images that can be bound and rendered from.
|
|
||||||
std::vector<VkFramebuffer> swapChainFramebuffers;
|
|
||||||
|
|
||||||
VkFramebuffer getFramebuffer(int ID) override { return swapChainFramebuffers.at(ID); }
|
|
||||||
|
|
||||||
// Create all image views and all framebuffers from a set of images, a format, a size, and the current rendering pass.
|
|
||||||
void createViewsAndFramebuffer(std::vector<VkImage> images, VkFormat format, VkExtent2D extent, VkRenderPass pass) override;
|
|
||||||
// Create views for swapChainImages with the given format.
|
|
||||||
void createViews(VkFormat format) override;
|
|
||||||
// Create a framebuffer for the swapChainImages, for the given pass.
|
|
||||||
void createFramebuffer(VkExtent2D extent, VkRenderPass pass) override;
|
|
||||||
|
|
||||||
void destroy() override;
|
|
||||||
};
|
|
|
@ -1,23 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class CommandBuffer {
|
|
||||||
public:
|
|
||||||
|
|
||||||
CommandBuffer();
|
|
||||||
~CommandBuffer();
|
|
||||||
|
|
||||||
VkCommandPool commands;
|
|
||||||
std::vector<VkCommandBuffer> buffers;
|
|
||||||
|
|
||||||
void createCommandPoolAndBuffers(size_t images);
|
|
||||||
void beginCommandBuffer(VkCommandBuffer buffer);
|
|
||||||
void endCommandBuffer(VkCommandBuffer buffer);
|
|
||||||
|
|
||||||
void createCommandPool();
|
|
||||||
void allocateCommandBuffers(size_t size);
|
|
||||||
|
|
||||||
void destroy();
|
|
||||||
};
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
|
||||||
class SwapChain {
|
class SwapChain {
|
||||||
public:
|
public:
|
||||||
|
@ -14,7 +15,8 @@ public:
|
||||||
VkFormat format;
|
VkFormat format;
|
||||||
VkExtent2D extent;
|
VkExtent2D extent;
|
||||||
|
|
||||||
std::vector<VkImage> images;
|
std::vector<std::unique_ptr<vlkx::Image>> images;
|
||||||
|
std::unique_ptr<vlkx::Image> multisampleImg;
|
||||||
|
|
||||||
VkSurfaceFormatKHR chooseFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
|
VkSurfaceFormatKHR chooseFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
|
||||||
VkPresentModeKHR chooseMode(const std::vector<VkPresentModeKHR>& availableModes);
|
VkPresentModeKHR chooseMode(const std::vector<VkPresentModeKHR>& availableModes);
|
||||||
|
|
|
@ -4,19 +4,12 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
|
||||||
|
|
||||||
#include <vulkan/vk_mem_alloc.h>
|
#include <vulkan/vk_mem_alloc.h>
|
||||||
|
#include "VulkanDevice.h"
|
||||||
|
#include "exports.h"
|
||||||
|
|
||||||
|
|
||||||
namespace VkTools {
|
namespace VkTools {
|
||||||
extern VmaAllocator g_allocator;
|
|
||||||
extern VkInstance g_Instance;
|
|
||||||
extern VkPhysicalDevice g_PhysicalDevice;
|
|
||||||
extern VkDevice g_Device;
|
|
||||||
extern uint32_t g_QueueFamily;
|
|
||||||
extern VkQueue g_Queue;
|
|
||||||
extern VkDebugReportCallbackEXT g_DebugReport;
|
|
||||||
|
|
||||||
struct ManagedImage {
|
struct ManagedImage {
|
||||||
VkImage image;
|
VkImage image;
|
||||||
VmaAllocation allocation;
|
VmaAllocation allocation;
|
||||||
|
@ -27,194 +20,20 @@ namespace VkTools {
|
||||||
VmaAllocation allocation;
|
VmaAllocation allocation;
|
||||||
};
|
};
|
||||||
|
|
||||||
ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent, VkDevice device);
|
extern API VmaAllocator allocator;
|
||||||
VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode);
|
|
||||||
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, VkDevice device);
|
|
||||||
ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible = true);
|
|
||||||
uint32_t findMemoryIndex(uint32_t type, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice);
|
|
||||||
VkCommandBuffer createTempCommandBuffer(VkCommandPool pool, VkDevice logical);
|
|
||||||
void executeAndDeleteTempBuffer(VkCommandBuffer buffer, VkCommandPool pool, VkQueue queue, VkDevice logicalDevice);
|
|
||||||
void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VkDevice logical, VkQueue graphicsQueue, uint32_t queueIndex);
|
|
||||||
|
|
||||||
|
ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent);
|
||||||
|
|
||||||
#ifdef VKTOOLS_IMPLEMENTATION
|
VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode, uint32_t mipping, VkDevice device);
|
||||||
ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent, VkDevice device) {
|
|
||||||
// Set up image metadata
|
|
||||||
VkImageCreateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
||||||
info.pNext = nullptr;
|
|
||||||
info.format = format;
|
|
||||||
info.extent = extent;
|
|
||||||
info.mipLevels = 1;
|
|
||||||
info.arrayLayers = 1;
|
|
||||||
info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
||||||
info.usage = flags;
|
|
||||||
|
|
||||||
// Prepare the managed image
|
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipping, uint32_t layers,
|
||||||
ManagedImage image {};
|
VkDevice device);
|
||||||
|
|
||||||
// Set up image allocation
|
ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
|
||||||
VmaAllocationCreateInfo allocateInfo = {};
|
VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible = true);
|
||||||
allocateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
|
||||||
|
|
||||||
// Allocate + create the image
|
void immediateExecute(const std::function<void(const VkCommandBuffer &)> &execute, VulkanDevice *dev);
|
||||||
vmaCreateImage(g_allocator, &info, &allocateInfo, &image.image, &image.allocation, nullptr);
|
|
||||||
|
|
||||||
return image;
|
void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VulkanDevice *dev);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode) {
|
|
||||||
VkSamplerCreateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
|
||||||
info.pNext = nullptr;
|
|
||||||
info.magFilter = filters;
|
|
||||||
info.minFilter = filters;
|
|
||||||
info.addressModeU = mode;
|
|
||||||
info.addressModeV = mode;
|
|
||||||
info.addressModeW = mode;
|
|
||||||
|
|
||||||
VkSampler sampler;
|
|
||||||
vkCreateSampler(g_Device, &info, nullptr, &sampler);
|
|
||||||
|
|
||||||
return sampler;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, VkDevice device) {
|
|
||||||
// Raw information about the image
|
|
||||||
VkImageViewCreateInfo viewInfo = {};
|
|
||||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
||||||
viewInfo.image = image;
|
|
||||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
||||||
viewInfo.format = format;
|
|
||||||
|
|
||||||
// Information about the things we want to create - size, mip levels.
|
|
||||||
viewInfo.subresourceRange.aspectMask = flags;
|
|
||||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
|
||||||
viewInfo.subresourceRange.levelCount = 1;
|
|
||||||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
|
||||||
viewInfo.subresourceRange.layerCount = 1;
|
|
||||||
|
|
||||||
VkImageView imageView;
|
|
||||||
if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Failed to create texture image view.");
|
|
||||||
|
|
||||||
return imageView;
|
|
||||||
}
|
|
||||||
|
|
||||||
ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible) {
|
|
||||||
// Prepare for creation of a buffer
|
|
||||||
VkBufferCreateInfo bufferInfo = {};
|
|
||||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
||||||
bufferInfo.size = size;
|
|
||||||
bufferInfo.usage = usage;
|
|
||||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
||||||
|
|
||||||
ManagedBuffer buffer;
|
|
||||||
|
|
||||||
VmaAllocationCreateInfo vmaInfo = {};
|
|
||||||
vmaInfo.usage = hostVisible ? VMA_MEMORY_USAGE_CPU_ONLY : VMA_MEMORY_USAGE_GPU_ONLY;
|
|
||||||
vmaInfo.requiredFlags = properties;
|
|
||||||
|
|
||||||
// Create the buffer.
|
|
||||||
if (vmaCreateBuffer(g_allocator, &bufferInfo, &vmaInfo, &buffer.buffer, &buffer.allocation, nullptr) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create GPU buffer");
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t findMemoryIndex(uint32_t type, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice) {
|
|
||||||
// Get the physical properties of the device.
|
|
||||||
VkPhysicalDeviceMemoryProperties physProperties;
|
|
||||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &physProperties);
|
|
||||||
|
|
||||||
// Iterate the device and search for a suitable index
|
|
||||||
for (uint32_t i = 0; i < physProperties.memoryTypeCount; i++)
|
|
||||||
// If the type matches, and the properties are what we desire, then ship it.
|
|
||||||
if ((type & (1 << i)) && ((physProperties.memoryTypes[i].propertyFlags & properties) == properties))
|
|
||||||
return i;
|
|
||||||
|
|
||||||
throw std::runtime_error("Unable to find a suitable memory type on the physical device.");
|
|
||||||
}
|
|
||||||
|
|
||||||
VkCommandBuffer createTempCommandBuffer(VkCommandPool pool, VkDevice logical) {
|
|
||||||
// Prepare to allocate a command buffer
|
|
||||||
VkCommandBufferAllocateInfo allocateInfo = {};
|
|
||||||
allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
||||||
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
||||||
allocateInfo.commandPool = pool;
|
|
||||||
allocateInfo.commandBufferCount = 1;
|
|
||||||
|
|
||||||
// Allocate the buffer
|
|
||||||
VkCommandBuffer buffer;
|
|
||||||
vkAllocateCommandBuffers(logical, &allocateInfo, &buffer);
|
|
||||||
|
|
||||||
// Prepare to begin the new buffer.
|
|
||||||
VkCommandBufferBeginInfo beginInfo = {};
|
|
||||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
||||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
||||||
|
|
||||||
// Begin listening on the new buffer.
|
|
||||||
vkBeginCommandBuffer(buffer, &beginInfo);
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void executeAndDeleteTempBuffer(VkCommandBuffer buffer, VkCommandPool pool, VkQueue queue, VkDevice logicalDevice) {
|
|
||||||
// Stop listening on the buffer
|
|
||||||
vkEndCommandBuffer(buffer);
|
|
||||||
|
|
||||||
// Prepare to execute the commands in the buffer
|
|
||||||
VkSubmitInfo submitInfo = {};
|
|
||||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
||||||
submitInfo.commandBufferCount = 1;
|
|
||||||
submitInfo.pCommandBuffers = &buffer;
|
|
||||||
|
|
||||||
// Submit the commands to be executed
|
|
||||||
vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
|
|
||||||
|
|
||||||
// Wait for the GPU to finish executing
|
|
||||||
vkQueueWaitIdle(queue);
|
|
||||||
|
|
||||||
// Delete the now unusable buffers
|
|
||||||
vkFreeCommandBuffers(logicalDevice, pool, 1, &buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VkDevice logical, VkQueue graphicsQueue, uint32_t queueIndex) {
|
|
||||||
|
|
||||||
// Prepare to create a temporary command pool.
|
|
||||||
VkCommandPool pool;
|
|
||||||
VkCommandPoolCreateInfo poolCreateInfo = {};
|
|
||||||
poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
||||||
poolCreateInfo.queueFamilyIndex = queueIndex;
|
|
||||||
poolCreateInfo.flags = 0;
|
|
||||||
|
|
||||||
// Create the pool
|
|
||||||
if (vkCreateCommandPool(logical, &poolCreateInfo, nullptr, &pool) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to allocate a temporary command pool");
|
|
||||||
|
|
||||||
// Allocate a buffer
|
|
||||||
VkCommandBuffer commands = createTempCommandBuffer(pool, logical);
|
|
||||||
|
|
||||||
// ------ Commands are saved into the commands field ------ //
|
|
||||||
|
|
||||||
// Prepare to copy the data between buffers
|
|
||||||
VkBufferCopy copyInfo = {};
|
|
||||||
copyInfo.srcOffset = 0;
|
|
||||||
copyInfo.dstOffset = 0;
|
|
||||||
copyInfo.size = length;
|
|
||||||
|
|
||||||
// Copy the data.
|
|
||||||
vkCmdCopyBuffer(commands, source, dest, 1, ©Info);
|
|
||||||
|
|
||||||
// ------ Commands are no longer saved into the commands field ------ //
|
|
||||||
|
|
||||||
executeAndDeleteTempBuffer(commands, pool, graphicsQueue, logical);
|
|
||||||
|
|
||||||
// Cleanup the temporary buffer and pool we created
|
|
||||||
vkDestroyCommandPool(logical, pool, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
}
|
|
@ -11,7 +11,8 @@ public:
|
||||||
~ValidationAndExtension();
|
~ValidationAndExtension();
|
||||||
|
|
||||||
const std::vector<const char*> requiredValidations = {
|
const std::vector<const char*> requiredValidations = {
|
||||||
"VK_LAYER_KHRONOS_validation"
|
"VK_LAYER_KHRONOS_validation",
|
||||||
|
//"VK_LAYER_LUNARG_api_dump"
|
||||||
};
|
};
|
||||||
|
|
||||||
VkDebugReportCallbackEXT callback;
|
VkDebugReportCallbackEXT callback;
|
||||||
|
|
|
@ -30,6 +30,7 @@ public:
|
||||||
|
|
||||||
/** Physical Devices **/
|
/** Physical Devices **/
|
||||||
VkPhysicalDevice physical;
|
VkPhysicalDevice physical;
|
||||||
|
VkPhysicalDeviceLimits limits;
|
||||||
SwapChainMeta swapChain;
|
SwapChainMeta swapChain;
|
||||||
QueueFamilies queueData;
|
QueueFamilies queueData;
|
||||||
|
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vlkx/vulkan/ValidationAndExtension.h>
|
|
||||||
#include <vlkx/vulkan/VulkanDevice.h>
|
|
||||||
#include <vlkx/vulkan/SwapChain.h>
|
|
||||||
#include <vlkx/render/framebuffer/RenderPass.h>
|
|
||||||
#include <vlkx/render/texture/RenderTexture.h>
|
|
||||||
#include <vlkx/vulkan/CommandBuffer.h>
|
|
||||||
|
|
||||||
#include <vulkan/vk_mem_alloc.h>
|
|
||||||
#include <vulkan/vulkan.h>
|
|
||||||
|
|
||||||
#include <vlkx/vulkan/Tools.h>
|
|
||||||
|
|
||||||
#include <SDL_vulkan.h>
|
|
||||||
|
|
||||||
class VulkanManager {
|
|
||||||
public:
|
|
||||||
VulkanManager();
|
|
||||||
~VulkanManager();
|
|
||||||
|
|
||||||
#ifdef _DEBUG
|
|
||||||
static const bool validationRequired = true;
|
|
||||||
#else
|
|
||||||
static const bool validationRequired = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// VulkanManager is a singleton class.
|
|
||||||
static VulkanManager* instance;
|
|
||||||
static VulkanManager* 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();
|
|
||||||
|
|
||||||
void useRayTrace() { rayTraceMode = true; }
|
|
||||||
|
|
||||||
VkInstance getVulkan() { return vulkan; }
|
|
||||||
VulkanDevice* getDevice() { return device; }
|
|
||||||
SwapChain* getSwapchain() { return swapchain; }
|
|
||||||
RenderPass* getRenderPass() { return renderPass; }
|
|
||||||
VkCommandBuffer getCurrentCommandBuffer() { return currentCommandBuffer; }
|
|
||||||
VmaAllocator getAllocator() { return allocator; }
|
|
||||||
RenderTexture* getRenderTarget() { return renderTexture; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// To keep track of the window during... stuff
|
|
||||||
SDL_Window* wnd;
|
|
||||||
|
|
||||||
// To handle the validation of Vulkan API usage (because fuck you, it's your problem now)
|
|
||||||
ValidationAndExtension* validators{};
|
|
||||||
// To manage interaction with the hardware
|
|
||||||
VulkanDevice* device{};
|
|
||||||
// To handle the framebuffers
|
|
||||||
SwapChain* swapchain{};
|
|
||||||
// To handle the timing of rendering
|
|
||||||
RenderPass* renderPass{};
|
|
||||||
|
|
||||||
// To handle automatic management of memory.
|
|
||||||
VmaAllocator allocator{};
|
|
||||||
|
|
||||||
// The default RenderTexture, mirroring the SwapChain to the viewport.
|
|
||||||
RenderTexture* renderTexture;
|
|
||||||
// The command buffers used when telling the firmware to do things for us.
|
|
||||||
CommandBuffer* buffers{};
|
|
||||||
|
|
||||||
// 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{};
|
|
||||||
|
|
||||||
// The index of the texture that is currently being used by the GPU.
|
|
||||||
uint32_t imageIndex = 0;
|
|
||||||
// The command buffer currently being used by the GPU.
|
|
||||||
VkCommandBuffer currentCommandBuffer{};
|
|
||||||
|
|
||||||
// The maximum number of frames that can be dealt with at a time.
|
|
||||||
const int MAX_FRAMES = 2; // Double-buffering requires two frames in memory
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
bool rayTraceMode;
|
|
||||||
|
|
||||||
};
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vlkx/vulkan/ValidationAndExtension.h>
|
||||||
|
#include <vlkx/vulkan/VulkanDevice.h>
|
||||||
|
|
||||||
|
#include <vulkan/vk_mem_alloc.h>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include <SDL_vulkan.h>
|
||||||
|
#include <core/Module.h>
|
||||||
|
#include "SwapChain.h"
|
||||||
|
|
||||||
|
namespace vlkx { class ScreenRenderPassManager; }
|
||||||
|
|
||||||
|
class VulkanModule : public ShadowEngine::RendererModule {
|
||||||
|
SHObject_Base(VulkanModule);
|
||||||
|
public:
|
||||||
|
|
||||||
|
VulkanModule();
|
||||||
|
~VulkanModule() override;
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
static const bool validationRequired = true;
|
||||||
|
#else
|
||||||
|
static const bool validationRequired = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void PreInit() override;
|
||||||
|
|
||||||
|
void Init() override;
|
||||||
|
|
||||||
|
void Recreate() override;
|
||||||
|
|
||||||
|
void Update(int frame) override;
|
||||||
|
|
||||||
|
void PreRender() override;
|
||||||
|
|
||||||
|
void Render(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
|
void OverlayRender() override;
|
||||||
|
|
||||||
|
void LateRender(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
|
void AfterFrameEnd() override;
|
||||||
|
|
||||||
|
void Destroy() override;
|
||||||
|
|
||||||
|
void Event(SDL_Event* e) override;
|
||||||
|
|
||||||
|
void BeginRenderPass(const std::unique_ptr<vlkx::RenderCommand>& commands) override;
|
||||||
|
|
||||||
|
void EnableEditor() override;
|
||||||
|
|
||||||
|
VkExtent2D GetRenderExtent() override;
|
||||||
|
|
||||||
|
// VulkanModule is a singleton class.
|
||||||
|
static VulkanModule* instance;
|
||||||
|
static VulkanModule* getInstance();
|
||||||
|
|
||||||
|
// Initialize all Vulkan context and prepare validations in debug mode.
|
||||||
|
void initVulkan(SDL_Window* window);
|
||||||
|
void createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations);
|
||||||
|
|
||||||
|
// Start and end a frame render.
|
||||||
|
void startDraw();
|
||||||
|
void endDraw();
|
||||||
|
|
||||||
|
// Cleanup after the application has closed.
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
VkInstance getVulkan() { return vulkan; }
|
||||||
|
VulkanDevice* getDevice() { return device; }
|
||||||
|
SwapChain* getSwapchain() { return swapchain; }
|
||||||
|
VmaAllocator getAllocator() { return allocator; }
|
||||||
|
SDL_Window* getWind() { return wnd; }
|
||||||
|
const std::unique_ptr<vlkx::ScreenRenderPassManager>& getRenderPass();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool editorEnabled = false;
|
||||||
|
std::vector<VkDescriptorSet> editorRenderPlanes;
|
||||||
|
std::vector<std::unique_ptr<vlkx::Image>> editorContentFrames;
|
||||||
|
|
||||||
|
// The SDL Window contains the size of the drawable area.
|
||||||
|
SDL_Window* wnd;
|
||||||
|
// To handle the validation of Vulkan API usage
|
||||||
|
ValidationAndExtension* validators{};
|
||||||
|
// To manage interaction with the hardware
|
||||||
|
VulkanDevice* device{};
|
||||||
|
// To handle the framebuffers
|
||||||
|
SwapChain* swapchain{};
|
||||||
|
// To handle automatic management of memory.
|
||||||
|
VmaAllocator allocator{};
|
||||||
|
// To manage the Vulkan context that was passed to us by the API
|
||||||
|
VkInstance vulkan{};
|
||||||
|
// To manage the canvas that was given to us by GLFW
|
||||||
|
VkSurfaceKHR surface{};
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,412 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vlkx/vulkan/VulkanDevice.h>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
#include "vlkx/vulkan/Tools.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
// Root buffer class.
|
||||||
|
// Used to store & move data between the CPU and GPU.
|
||||||
|
// Basically; hotspot!
|
||||||
|
// Utilities and subclasses exist to optimise and speed up transfer and management of data in bulk.
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
// Metadata of CPU->GPU data copying.
|
||||||
|
struct CopyMeta {
|
||||||
|
const void* data; // The start of data in RAM
|
||||||
|
VkDeviceSize length; // The amount of data to move
|
||||||
|
VkDeviceSize start; // The start (destination) in GPU memory
|
||||||
|
};
|
||||||
|
|
||||||
|
// Metadata of bulk CPU->GPU data copying.
|
||||||
|
struct BulkCopyMeta {
|
||||||
|
VkDeviceSize length; // The total data size of all transfers.
|
||||||
|
std::vector<CopyMeta> metas;
|
||||||
|
};
|
||||||
|
|
||||||
|
Buffer(const Buffer&) = delete;
|
||||||
|
Buffer& operator=(const Buffer&) = delete;
|
||||||
|
|
||||||
|
Buffer();
|
||||||
|
|
||||||
|
virtual ~Buffer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** START OF DATA BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// A buffer that stores data on GPU.
|
||||||
|
// Usage of the data is determined by child classes.
|
||||||
|
class DataBuffer : public Buffer {
|
||||||
|
public:
|
||||||
|
DataBuffer(const DataBuffer&) = delete;
|
||||||
|
DataBuffer& operator=(const DataBuffer&) = delete;
|
||||||
|
|
||||||
|
DataBuffer() = default;
|
||||||
|
|
||||||
|
~DataBuffer() override {
|
||||||
|
vmaDestroyBuffer(VkTools::allocator, managed.buffer, managed.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using Buffer::Buffer;
|
||||||
|
|
||||||
|
void setBuffer(const VkTools::ManagedBuffer& buffer) { managed = buffer; }
|
||||||
|
VkTools::ManagedBuffer get() const { return managed; }
|
||||||
|
const VkBuffer& getBuffer() const { return managed.buffer; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkTools::ManagedBuffer managed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A buffer visible to both GPU and CPU.
|
||||||
|
// Useful for uploading data to GPU for format conversions.
|
||||||
|
class StagingBuffer : public DataBuffer {
|
||||||
|
public:
|
||||||
|
StagingBuffer(const BulkCopyMeta& copyMeta);
|
||||||
|
|
||||||
|
StagingBuffer(const StagingBuffer&) = delete;
|
||||||
|
StagingBuffer& operator=(const StagingBuffer&) = delete;
|
||||||
|
|
||||||
|
void copy(const VkBuffer& target) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const VkDeviceSize dataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Root class of vertex buffers.
|
||||||
|
// Provides utilities for subclasses.
|
||||||
|
class VertexBuffer : public DataBuffer {
|
||||||
|
public:
|
||||||
|
VertexBuffer(const VertexBuffer&) = delete;
|
||||||
|
VertexBuffer& operator=(const VertexBuffer&) = delete;
|
||||||
|
|
||||||
|
// Get attributes of vertexes in the buffer
|
||||||
|
// Location will start from "start"
|
||||||
|
// Binding will not be set
|
||||||
|
std::vector<VkVertexInputAttributeDescription> getAttrs(uint32_t start) const;
|
||||||
|
|
||||||
|
// Draw these vertexes without a buffer per vertex.
|
||||||
|
static void draw(const VkCommandBuffer& buffer, uint32_t verts, uint32_t instances);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class DynamicBuffer;
|
||||||
|
|
||||||
|
explicit VertexBuffer(std::vector<VkVertexInputAttributeDescription>&& attrs) : DataBuffer(), attributes(attrs) {}
|
||||||
|
|
||||||
|
// Initialize device memory and the managed buffer.
|
||||||
|
// indices and vertexes are put in the same buffer.
|
||||||
|
// if dynamic, the buffer will be host visible, this allows dynamic text.
|
||||||
|
// otherwise, the buffer is device local.
|
||||||
|
void create(VkDeviceSize totalSize, bool dynamic, bool indexes);
|
||||||
|
|
||||||
|
const std::vector<VkVertexInputAttributeDescription> attributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** END OF DATA BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// A simple plugin to allow the vertex buffer to be widened when reserved with a larger size.
|
||||||
|
class DynamicBuffer {
|
||||||
|
public:
|
||||||
|
DynamicBuffer(const DynamicBuffer&) = delete;
|
||||||
|
DynamicBuffer& operator=(const DynamicBuffer&) = delete;
|
||||||
|
|
||||||
|
~DynamicBuffer() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
DynamicBuffer(size_t size, bool hasIndices, VertexBuffer* buffer);
|
||||||
|
|
||||||
|
// Reallocate the vertex buffer if the given pSize is larger than the available space
|
||||||
|
void resize(size_t pSize);
|
||||||
|
|
||||||
|
VkDeviceSize bufferSize() const { return size; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const bool hasIndices;
|
||||||
|
VertexBuffer* vertexBuffer;
|
||||||
|
VkDeviceSize size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** START OF PER-VERTEX BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// Root class of buffers that store per-vertex data.
|
||||||
|
// eg. shader data
|
||||||
|
class PerVertexBuffer : public VertexBuffer {
|
||||||
|
public:
|
||||||
|
// Interprets the layout of data in containers (vector, etc)
|
||||||
|
struct VertexDataMeta {
|
||||||
|
template <typename C>
|
||||||
|
VertexDataMeta(const C& cont, int unitsPerMesh) : data(cont.data()), unitsPerMesh(unitsPerMesh), sizePerMesh(sizeof(cont[0]) * unitsPerMesh) {}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
VertexDataMeta(const C& cont) : VertexDataMeta(cont, static_cast<int>(cont.size())) {}
|
||||||
|
|
||||||
|
const void* data;
|
||||||
|
int unitsPerMesh;
|
||||||
|
size_t sizePerMesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interface for buffer data.
|
||||||
|
class BufferDataMeta {
|
||||||
|
public:
|
||||||
|
virtual ~BufferDataMeta() = default;
|
||||||
|
// Populate info and return a bulk copy meta for copying the data to device
|
||||||
|
virtual BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const = 0;
|
||||||
|
// Indicate whether the buffer contains index data too
|
||||||
|
virtual bool hasIndices() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Meshes do not share indices, with variable vertices.
|
||||||
|
class NoIndexBufferMeta : public BufferDataMeta {
|
||||||
|
public:
|
||||||
|
explicit NoIndexBufferMeta(std::vector<VertexDataMeta>&& perVertex) : perMeshVertices(perVertex) {}
|
||||||
|
|
||||||
|
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
|
||||||
|
bool hasIndices() const override { return false; };
|
||||||
|
private:
|
||||||
|
const std::vector<VertexDataMeta> perMeshVertices;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Meshes share indices, with static vertices.
|
||||||
|
class SharedIndexMeta : public BufferDataMeta {
|
||||||
|
public:
|
||||||
|
SharedIndexMeta(int meshes, const VertexDataMeta& perVertex, const VertexDataMeta& sharedIndices) : meshes(meshes),
|
||||||
|
perMeshVertex(perVertex),
|
||||||
|
sharedIndices(sharedIndices) {}
|
||||||
|
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
|
||||||
|
bool hasIndices() const override { return true; };
|
||||||
|
private:
|
||||||
|
const int meshes;
|
||||||
|
const VertexDataMeta perMeshVertex;
|
||||||
|
const VertexDataMeta sharedIndices;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Meshes do not share indexes, with variable indices and vertices.
|
||||||
|
class NoShareMeta : public BufferDataMeta {
|
||||||
|
public:
|
||||||
|
struct PerMesh {
|
||||||
|
VertexDataMeta indices;
|
||||||
|
VertexDataMeta vertices;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit NoShareMeta(std::vector<PerMesh>&& perMesh) : perMeshMeta(std::move(perMesh)) {}
|
||||||
|
|
||||||
|
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
|
||||||
|
bool hasIndices() const override { return true; };
|
||||||
|
private:
|
||||||
|
const std::vector<PerMesh> perMeshMeta;
|
||||||
|
};
|
||||||
|
|
||||||
|
PerVertexBuffer(const PerVertexBuffer&) = delete;
|
||||||
|
PerVertexBuffer& operator=(const PerVertexBuffer&) = delete;
|
||||||
|
|
||||||
|
// Render mesh a given number of times, into a recording buffer.
|
||||||
|
void draw(const VkCommandBuffer& buffer, uint32_t bind, int index, uint32_t instances) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using VertexBuffer::VertexBuffer;
|
||||||
|
|
||||||
|
// Stores vertex data for buffers without indices
|
||||||
|
struct MeshDataNoIndex {
|
||||||
|
struct Info {
|
||||||
|
uint32_t vertexCount;
|
||||||
|
VkDeviceSize vertexStart;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Info> info;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores vertex and index data for buffers with both
|
||||||
|
struct MeshDataIndex {
|
||||||
|
struct Info {
|
||||||
|
uint32_t indexCount;
|
||||||
|
VkDeviceSize indexStart;
|
||||||
|
VkDeviceSize vertexStart;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Info> info;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::variant<MeshDataNoIndex, MeshDataIndex>* getInfo() { return &meshDataInfo; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::variant<MeshDataNoIndex, MeshDataIndex> meshDataInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores static data for one-time upload.
|
||||||
|
class StaticPerVertexBuffer : public PerVertexBuffer {
|
||||||
|
public:
|
||||||
|
StaticPerVertexBuffer(const BufferDataMeta& info, std::vector<VkVertexInputAttributeDescription>&& attrs);
|
||||||
|
|
||||||
|
StaticPerVertexBuffer(const StaticPerVertexBuffer&) = delete;
|
||||||
|
StaticPerVertexBuffer& operator=(const StaticPerVertexBuffer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores host-visible data that can be reallocated.
|
||||||
|
class DynamicPerVertexBuffer : public PerVertexBuffer, public DynamicBuffer {
|
||||||
|
public:
|
||||||
|
DynamicPerVertexBuffer(size_t size, std::vector<VkVertexInputAttributeDescription>&& attrs) : PerVertexBuffer(std::move(attrs)), DynamicBuffer(size, true, this) {}
|
||||||
|
|
||||||
|
DynamicPerVertexBuffer(const DynamicPerVertexBuffer&) = delete;
|
||||||
|
DynamicPerVertexBuffer& operator=(const DynamicPerVertexBuffer&) = delete;
|
||||||
|
|
||||||
|
void copyToDevice(const BufferDataMeta& meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** END OF PER-VERTEX BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// Root class of buffers that store vertex data per instance of a mesh.
|
||||||
|
class PerInstanceVertexBuffer : public VertexBuffer {
|
||||||
|
public:
|
||||||
|
PerInstanceVertexBuffer(const PerInstanceVertexBuffer&) = delete;
|
||||||
|
PerInstanceVertexBuffer& operator=(const PerInstanceVertexBuffer&) = delete;
|
||||||
|
|
||||||
|
void bind(const VkCommandBuffer& commands, uint32_t bindPoint, int offset) const;
|
||||||
|
|
||||||
|
uint32_t getSize() const { return sizePerInstance; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PerInstanceVertexBuffer(uint32_t size, std::vector<VkVertexInputAttributeDescription>&& attrs) : VertexBuffer(std::move(attrs)), sizePerInstance(size) {}
|
||||||
|
private:
|
||||||
|
const uint32_t sizePerInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores vertices that are static per instance of the mesh.
|
||||||
|
class StaticPerInstanceBuffer : public PerInstanceVertexBuffer {
|
||||||
|
public:
|
||||||
|
StaticPerInstanceBuffer(uint32_t size, const void* data, uint32_t instances, std::vector<VkVertexInputAttributeDescription>&& attrs);
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
StaticPerInstanceBuffer(const C& cont, std::vector<VkVertexInputAttributeDescription>&& attrs) : StaticPerInstanceBuffer(sizeof(cont[0]), cont.data(), CONTAINER_SIZE(cont), std::move(attrs)) {}
|
||||||
|
|
||||||
|
StaticPerInstanceBuffer(const StaticPerInstanceBuffer&) = delete;
|
||||||
|
StaticPerInstanceBuffer& operator=(const StaticPerInstanceBuffer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stores vertices of meshes that are dynamic (ie. text, shifting meshes
|
||||||
|
class DynamicPerInstanceBuffer : public PerInstanceVertexBuffer, public DynamicBuffer {
|
||||||
|
public:
|
||||||
|
DynamicPerInstanceBuffer(uint32_t size, size_t maxInstances, std::vector<VkVertexInputAttributeDescription>&& attrs) : PerInstanceVertexBuffer(size, std::move(attrs)), DynamicBuffer(size * maxInstances, false, this) {}
|
||||||
|
|
||||||
|
DynamicPerInstanceBuffer(const DynamicPerInstanceBuffer&) = delete;
|
||||||
|
DynamicPerInstanceBuffer& operator=(const DynamicPerInstanceBuffer*) = delete;
|
||||||
|
|
||||||
|
void copyToDevice(const void* data, uint32_t instances);
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
void copyToDevice(const C& cont) {
|
||||||
|
copyToDevice(cont.data(), CONTAINER_SIZE(cont));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************/
|
||||||
|
/** */
|
||||||
|
/** END OF PER-INSTANCE BUFFERS */
|
||||||
|
/** */
|
||||||
|
/*******************************************/
|
||||||
|
|
||||||
|
// Holds uniform data on host and device.
|
||||||
|
// Supports superallocating (allocating more than one "set" of uniforms at once)
|
||||||
|
// Data is stored on host and device simultaneously, so set the host data and flush it to the device.
|
||||||
|
class UniformBuffer : public DataBuffer {
|
||||||
|
public:
|
||||||
|
UniformBuffer(size_t chunkSize, int chunks);
|
||||||
|
|
||||||
|
UniformBuffer(const UniformBuffer&) = delete;
|
||||||
|
UniformBuffer& operator=(const UniformBuffer&) = delete;
|
||||||
|
|
||||||
|
~UniformBuffer() override { delete data; }
|
||||||
|
|
||||||
|
// Whether this buffer holds a single chunk (is not superallocated).
|
||||||
|
// Simplifies certain algorithms significantly if you know this beforehand
|
||||||
|
bool isSingle() const { return numChunks == 1; }
|
||||||
|
|
||||||
|
// Get the data in the buffer, casted to the given type
|
||||||
|
template <typename DataType>
|
||||||
|
DataType* getData(int index) const {
|
||||||
|
checkIndex(index);
|
||||||
|
return reinterpret_cast<DataType*>(data + chunkSize * index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload (flush) the uniform to the GPU
|
||||||
|
void upload(int index) const;
|
||||||
|
void upload(int index, VkDeviceSize dataSize, VkDeviceSize start) const;
|
||||||
|
|
||||||
|
static VkDescriptorType getDescriptorType() { return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; }
|
||||||
|
|
||||||
|
VkDescriptorBufferInfo getDescriptorInfo(int index) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkIndex(int index) const;
|
||||||
|
|
||||||
|
char* data;
|
||||||
|
const size_t chunkSize;
|
||||||
|
const int numChunks;
|
||||||
|
|
||||||
|
size_t chunkLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A small, highly efficient buffer much like the Uniform Buffer.
|
||||||
|
class PushConstant {
|
||||||
|
public:
|
||||||
|
// Size must be < 128.
|
||||||
|
PushConstant(size_t size, int numFrames);
|
||||||
|
|
||||||
|
PushConstant(const PushConstant&) = delete;
|
||||||
|
PushConstant& operator=(const PushConstant&) = delete;
|
||||||
|
|
||||||
|
~PushConstant() { delete[] data; }
|
||||||
|
|
||||||
|
// Whether this buffer holds a single chunk (is not superallocated).
|
||||||
|
// Simplifies certain algorithms significantly if you know this beforehand
|
||||||
|
bool isSingle() const { return numFrames == 1; }
|
||||||
|
|
||||||
|
uint32_t getSize() const { return sizePerFrame; }
|
||||||
|
|
||||||
|
// Get the data in the buffer, casted to the given type
|
||||||
|
template <typename DataType>
|
||||||
|
DataType* getData(int frame) const {
|
||||||
|
checkIndex(frame);
|
||||||
|
return reinterpret_cast<DataType*>(data + (sizePerFrame * frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPushConstantRange makeRange(VkShaderStageFlags stage) {
|
||||||
|
return VkPushConstantRange { stage, 0, sizePerFrame };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload (flush) the uniform to the GPU
|
||||||
|
void upload(const VkCommandBuffer& commands, const VkPipelineLayout& pipelineLayout, int frame, uint32_t offset, VkShaderStageFlags stage) const;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkIndex(int index) const;
|
||||||
|
|
||||||
|
char* data;
|
||||||
|
const uint32_t sizePerFrame;
|
||||||
|
const int numFrames;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include "Queue.h"
|
||||||
|
#include "vlkx/vulkan/VulkanDevice.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
// Root class of VkCommandBuffer wrappers.
|
||||||
|
class CommandBuffer {
|
||||||
|
public:
|
||||||
|
using Command = std::function<void(const VkCommandBuffer& buffer)>;
|
||||||
|
|
||||||
|
CommandBuffer(const CommandBuffer&) = delete;
|
||||||
|
CommandBuffer& operator=(const CommandBuffer&) = delete;
|
||||||
|
|
||||||
|
virtual ~CommandBuffer() {
|
||||||
|
vkDestroyCommandPool(dev->logical, pool, nullptr);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
CommandBuffer();
|
||||||
|
|
||||||
|
void setPool(const VkCommandPool& newPool) { pool = newPool; }
|
||||||
|
VulkanDevice* dev;
|
||||||
|
private:
|
||||||
|
VkCommandPool pool;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A command buffer that will be immediately executed.
|
||||||
|
class ImmediateCommand : public CommandBuffer {
|
||||||
|
public:
|
||||||
|
ImmediateCommand(Queue queue);
|
||||||
|
|
||||||
|
ImmediateCommand(const ImmediateCommand&) = delete;
|
||||||
|
ImmediateCommand& operator=(const ImmediateCommand&) = delete;
|
||||||
|
|
||||||
|
void run(const Command& cmd);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Queue queue;
|
||||||
|
VkCommandBuffer commands;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A command buffer that will be reused every frame.
|
||||||
|
class RenderCommand : public CommandBuffer {
|
||||||
|
public:
|
||||||
|
using Command = std::function<void(const VkCommandBuffer& commands, uint32_t framebuffer)>;
|
||||||
|
using Update = std::function<void(int frame)>;
|
||||||
|
|
||||||
|
~RenderCommand() override {
|
||||||
|
// Destroy our own data
|
||||||
|
vkDestroySemaphore(dev->logical, renderDoneSem, nullptr);
|
||||||
|
vkDestroySemaphore(dev->logical, newImageSem, nullptr);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
vkDestroyFence(dev->logical, inFlight[i], nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCommand(int frames);
|
||||||
|
|
||||||
|
RenderCommand(const RenderCommand&) = delete;
|
||||||
|
RenderCommand& operator=(const RenderCommand&) = delete;
|
||||||
|
|
||||||
|
uint32_t getFrame() { return imageIndex; }
|
||||||
|
|
||||||
|
void nextFrame() { imageIndex = (imageIndex + 1) % 2; }
|
||||||
|
|
||||||
|
std::optional<VkResult> execute(int frame, const VkSwapchainKHR& swapchain, const Update& update, const Command& cmd);
|
||||||
|
// Renders a single frame out, no semaphores or fences.
|
||||||
|
std::optional<VkResult> executeSimple(int frame, const Update& update, const Command& cmd);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<VkCommandBuffer> commands;
|
||||||
|
// Raised when a new image is available
|
||||||
|
VkSemaphore newImageSem;
|
||||||
|
// Raised when a render is finished
|
||||||
|
VkSemaphore renderDoneSem;
|
||||||
|
// Stores fences for frames that are currently "in flight".
|
||||||
|
std::vector<VkFence> inFlight;
|
||||||
|
|
||||||
|
// The index of the texture that is currently being used by the GPU.
|
||||||
|
uint32_t imageIndex = 0;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <temp/model/Loader.h>
|
||||||
|
#include <vlkx/vulkan/VulkanModule.h>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
class Descriptor {
|
||||||
|
public:
|
||||||
|
using TextureType = vlkxtemp::ModelLoader::TextureType;
|
||||||
|
using BufferInfos = std::map<uint32_t, std::vector<VkDescriptorBufferInfo>>;
|
||||||
|
using ImageInfos = std::map<uint32_t, std::vector<VkDescriptorImageInfo>>;
|
||||||
|
|
||||||
|
struct Meta {
|
||||||
|
struct Binding {
|
||||||
|
uint32_t bindPoint;
|
||||||
|
uint32_t length;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDescriptorType type;
|
||||||
|
VkShaderStageFlags stage;
|
||||||
|
std::vector<Binding> bindings;
|
||||||
|
};
|
||||||
|
|
||||||
|
Descriptor(const Descriptor&) = delete;
|
||||||
|
Descriptor& operator=(const Descriptor&) = delete;
|
||||||
|
|
||||||
|
virtual ~Descriptor() {
|
||||||
|
vkDestroyDescriptorSetLayout(VulkanModule::getInstance()->getDevice()->logical, layout, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkDescriptorSetLayout& getLayout() const { return layout; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
explicit Descriptor() = default;
|
||||||
|
|
||||||
|
void setLayout(const VkDescriptorSetLayout& newLayout) { layout = newLayout; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkDescriptorSetLayout layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StaticDescriptor : public Descriptor {
|
||||||
|
public:
|
||||||
|
|
||||||
|
StaticDescriptor(std::vector<Meta> metas);
|
||||||
|
StaticDescriptor(const StaticDescriptor&) = delete;
|
||||||
|
StaticDescriptor& operator=(const StaticDescriptor&) = delete;
|
||||||
|
|
||||||
|
~StaticDescriptor() override {
|
||||||
|
vkDestroyDescriptorPool(VulkanModule::getInstance()->getDevice()->logical, pool, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const StaticDescriptor& buffers(VkDescriptorType type, const BufferInfos& infos) const;
|
||||||
|
const StaticDescriptor& images(VkDescriptorType type, const ImageInfos& infos) const;
|
||||||
|
|
||||||
|
void bind(const VkCommandBuffer& commands, const VkPipelineLayout& layout, VkPipelineBindPoint bindPoint) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
const StaticDescriptor& updateSet(const std::vector<VkWriteDescriptorSet>& write) const;
|
||||||
|
|
||||||
|
VkDescriptorPool pool;
|
||||||
|
VkDescriptorSet set;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: dynamic sets
|
||||||
|
}
|
|
@ -0,0 +1,331 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "vlkx/vulkan/Tools.h"
|
||||||
|
#include "ImageUsage.h"
|
||||||
|
#include <shadow/util/RefCounter.h>
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
// Describes an image without initializing or storing any heavy data.
|
||||||
|
class ImageDescriptor {
|
||||||
|
public:
|
||||||
|
enum class Type { Single, Cubemap };
|
||||||
|
struct Dimension {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t channels;
|
||||||
|
|
||||||
|
VkExtent2D getExtent() const { return { width, height }; }
|
||||||
|
size_t getSize() const { return width * height * channels; }
|
||||||
|
};
|
||||||
|
|
||||||
|
Type getType() const { return type; }
|
||||||
|
VkExtent2D getExtent() const { return dimensions.getExtent(); }
|
||||||
|
uint32_t getWidth() const { return dimensions.width; }
|
||||||
|
uint32_t getHeight() const { return dimensions.height; }
|
||||||
|
uint32_t getChannels() const { return dimensions.channels; }
|
||||||
|
|
||||||
|
std::vector<void*> getData() const {
|
||||||
|
if (type == Type::Single) return { (void*) data };
|
||||||
|
std::vector<void*> dataPtrs;
|
||||||
|
dataPtrs.reserve(6);
|
||||||
|
|
||||||
|
size_t offset = 0;
|
||||||
|
for (size_t i = 0; i < 6; i++) {
|
||||||
|
dataPtrs.emplace_back((char*) data + offset);
|
||||||
|
offset += dimensions.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataPtrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getLayers() const { return type == Type::Single ? 1 : 6; }
|
||||||
|
|
||||||
|
ImageDescriptor(Type t, const Dimension& d, const void* ptr) : type(t), dimensions(d), data(ptr) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Type type;
|
||||||
|
Dimension dimensions;
|
||||||
|
const void* data;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// A staging buffer specialized for uploading images.
|
||||||
|
class ImageStagingBuffer : public StagingBuffer {
|
||||||
|
public:
|
||||||
|
using StagingBuffer::StagingBuffer;
|
||||||
|
|
||||||
|
ImageStagingBuffer(const ImageStagingBuffer&) = delete;
|
||||||
|
ImageStagingBuffer& operator=(const ImageStagingBuffer&) = delete;
|
||||||
|
|
||||||
|
void copy(const VkImage& target, const VkExtent3D& extent, uint32_t layers) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Root class that stores image data on GPU buffers
|
||||||
|
class ImageBuffer : public Buffer {
|
||||||
|
public:
|
||||||
|
ImageBuffer(const ImageBuffer&) = delete;
|
||||||
|
ImageBuffer& operator=(const ImageBuffer&) = delete;
|
||||||
|
|
||||||
|
~ImageBuffer() override {
|
||||||
|
vmaDestroyImage(VkTools::allocator, image.image, image.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const { return image; }
|
||||||
|
const VkImage& getImage() const { return image.image; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using Buffer::Buffer;
|
||||||
|
|
||||||
|
void setImage(const VkTools::ManagedImage newImg) { image = newImg; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkTools::ManagedImage image;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base class of all images; stores the common data.
|
||||||
|
class Image {
|
||||||
|
public:
|
||||||
|
Image(const Image&) = delete;
|
||||||
|
Image& operator=(const Image&) = delete;
|
||||||
|
|
||||||
|
virtual ~Image() {
|
||||||
|
vkDestroyImageView(dev->logical, view, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VkDescriptorType getSampleType() { return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; }
|
||||||
|
static VkDescriptorType getLinearType() { return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; }
|
||||||
|
|
||||||
|
static ImageDescriptor loadSingleFromDisk(std::string path, bool flipY);
|
||||||
|
// The following are left unimplemented intentionally.
|
||||||
|
//static ImageDescriptor loadSingleFromVFS(std::string path, bool flipY);
|
||||||
|
static ImageDescriptor loadCubeFromDisk(const std::string& directory, const std::array<std::string, 6>& files, bool flipY);
|
||||||
|
//static ImageDescriptor loadCubeFromVFS(std::string directory, const std::array<std::string, 6>& files, bool flipY);
|
||||||
|
|
||||||
|
virtual ImageUsage getUsage() const { return ImageUsage {}; }
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& operator*() const { return get(); }
|
||||||
|
|
||||||
|
virtual const VkTools::ManagedImage& get() const = 0;
|
||||||
|
virtual const VkImage& getImage() const = 0;
|
||||||
|
|
||||||
|
const VkImageView& getView() const { return view; }
|
||||||
|
const VkExtent2D& getExtent() const { return extent; }
|
||||||
|
VkFormat getFormat() const { return format; }
|
||||||
|
virtual VkSampleCountFlagBits getSamples() const { return VK_SAMPLE_COUNT_1_BIT; }
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Image(const VkExtent2D& ext, VkFormat form);
|
||||||
|
|
||||||
|
void setView(const VkImageView& imgView) { view = imgView; }
|
||||||
|
|
||||||
|
VulkanDevice* dev;
|
||||||
|
VkImageView view;
|
||||||
|
VkExtent2D extent;
|
||||||
|
VkFormat format;
|
||||||
|
VkSampleCountFlagBits sampleCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configures image sampling in a sensible and extensible way
|
||||||
|
class ImageSampler {
|
||||||
|
public:
|
||||||
|
struct Config {
|
||||||
|
explicit Config(VkFilter filter = VK_FILTER_LINEAR, VkSamplerAddressMode mode = VK_SAMPLER_ADDRESS_MODE_REPEAT) : filter(filter), mode(mode) {}
|
||||||
|
|
||||||
|
VkFilter filter;
|
||||||
|
VkSamplerAddressMode mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageSampler(int mipLevels, const Config& config);
|
||||||
|
|
||||||
|
ImageSampler(const ImageSampler&) = delete;
|
||||||
|
ImageSampler& operator=(const ImageSampler&) = delete;
|
||||||
|
|
||||||
|
~ImageSampler() {
|
||||||
|
vkDestroySampler(dev->logical, sampler, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkSampler& operator*() const { return sampler; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkSampler sampler;
|
||||||
|
VulkanDevice* dev;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Root of images which can be sampled.
|
||||||
|
class SamplableImage {
|
||||||
|
public:
|
||||||
|
virtual ~SamplableImage() = default;
|
||||||
|
|
||||||
|
// Return a VkDescriptorImageInfo we can use to update sets.
|
||||||
|
virtual VkDescriptorImageInfo getInfo(VkImageLayout layout) const = 0;
|
||||||
|
VkDescriptorImageInfo getInfoForSampling() const { return getInfo(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); }
|
||||||
|
VkDescriptorImageInfo getInfoForLinear() const { return getInfo(VK_IMAGE_LAYOUT_GENERAL); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// A samplable image that lives on the GPU, with optional mipmapping.
|
||||||
|
// Use a RefCountedTexture when loading from files.
|
||||||
|
class TextureImage : public Image, public SamplableImage {
|
||||||
|
public:
|
||||||
|
// Image metadata
|
||||||
|
struct Meta {
|
||||||
|
VkExtent2D getExtent() const { return { width, height }; }
|
||||||
|
VkExtent3D get3DExtent() const { return { width, height, channels }; }
|
||||||
|
|
||||||
|
Buffer::BulkCopyMeta getCopyMeta() const;
|
||||||
|
|
||||||
|
std::vector<void*> data;
|
||||||
|
std::vector<ImageUsage> usages;
|
||||||
|
VkFormat format;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t channels;
|
||||||
|
};
|
||||||
|
|
||||||
|
TextureImage(bool mipmapping, const ImageSampler::Config& samplerConfig, const Meta& meta);
|
||||||
|
TextureImage(bool mipmapping, const ImageDescriptor& image, const std::vector<ImageUsage>& usages, const ImageSampler::Config& config);
|
||||||
|
|
||||||
|
TextureImage(const TextureImage&) = delete;
|
||||||
|
TextureImage& operator=(const TextureImage&) = delete;
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const override { return buffer.get(); }
|
||||||
|
const VkImage& getImage() const override { return buffer.getImage(); }
|
||||||
|
|
||||||
|
VkDescriptorImageInfo getInfo(VkImageLayout layout) const override {
|
||||||
|
return { *sampler, getView(), layout };
|
||||||
|
}
|
||||||
|
// Textures are sampled in fragment shaders.
|
||||||
|
ImageUsage getUsage() const override {
|
||||||
|
return ImageUsage::sampledFragment();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
class TextureBuffer : public ImageBuffer {
|
||||||
|
public:
|
||||||
|
TextureBuffer(bool mipmaps, const Meta& meta);
|
||||||
|
|
||||||
|
TextureBuffer(const TextureBuffer&) = delete;
|
||||||
|
TextureBuffer& operator=(const TextureBuffer&) = delete;
|
||||||
|
|
||||||
|
int getMipping() const { return mipLevels; }
|
||||||
|
private:
|
||||||
|
int mipLevels = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TextureBuffer buffer;
|
||||||
|
const ImageSampler sampler;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A texture image that lives on GPU that is reference counted.
|
||||||
|
// This allows it to be reused multiple times without reading the file in more than once.
|
||||||
|
// It also allows the texture to be destructed automatically once nothing in the scene uses it, for eg. level changes.
|
||||||
|
class RefCountedTexture : public SamplableImage {
|
||||||
|
public:
|
||||||
|
// Cubemaps are simply 6 textures, so we include them here for easy instantiation.
|
||||||
|
struct CubemapLocation {
|
||||||
|
std::string directory;
|
||||||
|
std::array<std::string, 6> files;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reference Counting works on both individual files and cubemaps, so we put them together.
|
||||||
|
using ImageLocation = std::variant<std::string, CubemapLocation>;
|
||||||
|
|
||||||
|
RefCountedTexture(const ImageLocation& location, std::vector<ImageUsage> usages, const ImageSampler::Config& config)
|
||||||
|
: texture(get(location, std::move(usages), config)) {}
|
||||||
|
|
||||||
|
RefCountedTexture(RefCountedTexture&&) noexcept = default;
|
||||||
|
RefCountedTexture& operator=(RefCountedTexture&&) noexcept = default;
|
||||||
|
|
||||||
|
VkDescriptorImageInfo getInfo(VkImageLayout layout) const override {
|
||||||
|
return texture->getInfo(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Image* operator->() const { return texture.operator->(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
using ReferenceCounter = shadowutil::RefCounter<TextureImage>;
|
||||||
|
// Get or load the specified image.
|
||||||
|
static ReferenceCounter get(const ImageLocation& location, const std::vector<ImageUsage>& usages, const ImageSampler::Config& config);
|
||||||
|
|
||||||
|
ReferenceCounter texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: unowned, offscreen images.
|
||||||
|
|
||||||
|
// Image that can be used as a depth / stencil buffer attachment.
|
||||||
|
class DepthStencilImage : public Image {
|
||||||
|
public:
|
||||||
|
DepthStencilImage(const DepthStencilImage&) = delete;
|
||||||
|
DepthStencilImage& operator=(const DepthStencilImage&) = delete;
|
||||||
|
|
||||||
|
DepthStencilImage(const VkExtent2D& extent);
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const override { return buffer.get(); }
|
||||||
|
const VkImage& getImage() const override { return buffer.getImage(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
class DepthStencilBuffer : public ImageBuffer {
|
||||||
|
public:
|
||||||
|
DepthStencilBuffer(const VkExtent2D& extent, VkFormat format);
|
||||||
|
DepthStencilBuffer(const DepthStencilBuffer&) = delete;
|
||||||
|
DepthStencilBuffer& operator=(const DepthStencilBuffer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DepthStencilBuffer buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Image that references an existing image on the swapchain
|
||||||
|
class SwapchainImage : public Image {
|
||||||
|
public:
|
||||||
|
SwapchainImage(const SwapchainImage&) = delete;
|
||||||
|
SwapchainImage& operator=(const SwapchainImage&) = delete;
|
||||||
|
|
||||||
|
SwapchainImage(const VkImage& image, const VkExtent2D& extent, VkFormat format);
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const override { return managed; }
|
||||||
|
const VkImage& getImage() const override { return image; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkImage image;
|
||||||
|
VkTools::ManagedImage managed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MultisampleImage : public Image {
|
||||||
|
public:
|
||||||
|
enum class Mode {
|
||||||
|
MostEfficient,
|
||||||
|
Highest
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unique_ptr<Image> createColor(const Image& targetImage, Mode mode);
|
||||||
|
static std::unique_ptr<Image> createDepthStencilMS(const VkExtent2D& extent, Mode mode);
|
||||||
|
static std::unique_ptr<Image> createDepthStencil(VkExtent2D& extent, std::optional<Mode> mode);
|
||||||
|
|
||||||
|
const VkTools::ManagedImage& get() const override { return buffer.get(); }
|
||||||
|
const VkImage& getImage() const override { return buffer.getImage(); }
|
||||||
|
|
||||||
|
VkSampleCountFlagBits getSamples() const override { return samples; }
|
||||||
|
private:
|
||||||
|
class MultisampleBuffer : public ImageBuffer {
|
||||||
|
public:
|
||||||
|
enum class Type { Color, DepthStencil };
|
||||||
|
|
||||||
|
MultisampleBuffer(Type type, const VkExtent2D& extent, VkFormat format, VkSampleCountFlagBits samples);
|
||||||
|
MultisampleBuffer(const MultisampleBuffer&) = delete;
|
||||||
|
MultisampleBuffer& operator=(const MultisampleBuffer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
MultisampleImage(const VkExtent2D& extent, VkFormat format, Mode mode, MultisampleBuffer::Type type);
|
||||||
|
|
||||||
|
VkSampleCountFlagBits chooseSamples(Mode mode);
|
||||||
|
|
||||||
|
const VkSampleCountFlagBits samples;
|
||||||
|
const MultisampleBuffer buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,297 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
/**
|
||||||
|
* Describes how an image (collection of color data) will be used in the GPU.
|
||||||
|
* Has three parts; an overall description, access methods, and location of access.
|
||||||
|
* Use the static methods to create an instance, or the blank initializer and set fields as required.
|
||||||
|
*/
|
||||||
|
class ImageUsage {
|
||||||
|
public:
|
||||||
|
enum class Type {
|
||||||
|
DontCare, // Image is unused
|
||||||
|
RenderTarget, // Color attachment is used
|
||||||
|
DepthStencil, // Depth / Stencil buffer is used
|
||||||
|
Multisample, // Resolves to a multisampled image
|
||||||
|
Presentation, // Framebuffer for presentation
|
||||||
|
LinearAccess, // Host-Readable
|
||||||
|
InputAttachment, // Only what we write, is read.
|
||||||
|
Sampled, // Image is sampled (ie. texture)
|
||||||
|
Transfer // Image is used as an intermediate for transfer.
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Access {
|
||||||
|
DontCare, // Image is unused
|
||||||
|
ReadOnly, // Read Only access.
|
||||||
|
WriteOnly, // Write Only access.
|
||||||
|
ReadWrite, // Read/Write access.
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Location {
|
||||||
|
DontCare, // Image is unused
|
||||||
|
Host, // Image only exists on the host and will be transferred.
|
||||||
|
VertexShader, // Image is only used in the VertexAll Shader.
|
||||||
|
FragmentShader, // Image is only used in the Fragment Shader.
|
||||||
|
ComputeShader, // Image is only used in a Compute Shader.
|
||||||
|
Other, // Reserved.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sampled in a fragment shader. Read-Only.
|
||||||
|
static ImageUsage sampledFragment() { return { Type::Sampled, Access::ReadOnly, Location::FragmentShader }; };
|
||||||
|
// Used as a render target (a render pass will output to this image). Read/Write.
|
||||||
|
static ImageUsage renderTarget(int loc) { return { Type::RenderTarget, Access::ReadWrite, Location::Other, loc}; };
|
||||||
|
// Resolves to a multisampled image. Write-Only.
|
||||||
|
static ImageUsage multisample() { return { Type::Multisample, Access::WriteOnly, Location::Other }; };
|
||||||
|
// A depth or stencil buffer. Access is given, but must not be DontCare.
|
||||||
|
static ImageUsage depthStencil(Access acc) { return { Type::DepthStencil, acc, Location::Other}; };
|
||||||
|
// Used to show to the user. Write-Only.
|
||||||
|
static ImageUsage presentation() { return { Type::Presentation, Access::ReadOnly, Location::Other }; };
|
||||||
|
// Input attachment for a fragment shader. Usually a texture. Read-Only.
|
||||||
|
static ImageUsage input() { return { Type::InputAttachment, Access::ReadOnly, Location::FragmentShader }; };
|
||||||
|
// Linearly accessed image. For a compute shader. Access is given, but must not be DontCare.
|
||||||
|
static ImageUsage compute(Access acc) { return { Type::LinearAccess, acc, Location::ComputeShader }; };
|
||||||
|
|
||||||
|
explicit ImageUsage() : ImageUsage(Type::DontCare, Access::DontCare, Location::DontCare) {}
|
||||||
|
|
||||||
|
bool operator==(const ImageUsage& other) const {
|
||||||
|
return type == other.type && access == other.access && location == other.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageUsageFlagBits getUsageFlags() const {
|
||||||
|
switch (type) {
|
||||||
|
case Type::DontCare: throw std::runtime_error("No usage for type DontCare");
|
||||||
|
case Type::RenderTarget:
|
||||||
|
case Type::Multisample:
|
||||||
|
case Type::Presentation:
|
||||||
|
return VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||||
|
case Type::DepthStencil:
|
||||||
|
return VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||||
|
case Type::LinearAccess:
|
||||||
|
return VK_IMAGE_USAGE_STORAGE_BIT;
|
||||||
|
case Type::Sampled:
|
||||||
|
return VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
|
case Type::Transfer:
|
||||||
|
switch (access) {
|
||||||
|
case Access::DontCare: throw std::runtime_error("No access type specified for transfer usage.");
|
||||||
|
case Access::ReadOnly: return VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
|
case Access::WriteOnly: return VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
|
case Access::ReadWrite: throw std::runtime_error("ReadWrite access type for Transfer usage is invalid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static VkImageUsageFlags getFlagsForUsage(const std::vector<ImageUsage>& usages) {
|
||||||
|
auto flags = 0;
|
||||||
|
for (const auto& usage : usages) {
|
||||||
|
if (usage.type != Type::DontCare)
|
||||||
|
flags |= usage.getUsageFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<VkImageUsageFlags>(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageLayout getLayout() const {
|
||||||
|
switch (type) {
|
||||||
|
case Type::DontCare: return VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
case Type::RenderTarget:
|
||||||
|
case Type::Multisample:
|
||||||
|
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
|
case Type::DepthStencil: return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
|
case Type::Presentation: return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||||
|
case Type::LinearAccess: return VK_IMAGE_LAYOUT_GENERAL;
|
||||||
|
case Type::Sampled: return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
case Type::Transfer: return access == Access::ReadOnly ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
case Type::InputAttachment:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPipelineStageFlags getStage() const {
|
||||||
|
switch (type) {
|
||||||
|
case Type::DontCare: return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
|
case Type::RenderTarget:
|
||||||
|
case Type::Multisample:
|
||||||
|
case Type::Presentation:
|
||||||
|
return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
case Type::DepthStencil:
|
||||||
|
return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||||
|
case Type::LinearAccess:
|
||||||
|
case Type::Sampled:
|
||||||
|
switch (location) {
|
||||||
|
case Location::Host: return VK_PIPELINE_STAGE_HOST_BIT;
|
||||||
|
case Location::FragmentShader: return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||||
|
case Location::ComputeShader: return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||||
|
|
||||||
|
case Location::VertexShader:
|
||||||
|
case Location::Other:
|
||||||
|
throw std::runtime_error("Linear or sampled attachments must not be used in VertexAll or Other stages.");
|
||||||
|
case Location::DontCare: throw std::runtime_error("Linear or sampled attachments must have an access.");
|
||||||
|
}
|
||||||
|
|
||||||
|
case Type::Transfer:
|
||||||
|
return VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Access getAccess() const { return access; }
|
||||||
|
|
||||||
|
Type getType() const { return type; }
|
||||||
|
|
||||||
|
VkAccessFlags getAccessFlags() const {
|
||||||
|
switch (type) {
|
||||||
|
case Type::DontCare: return VK_ACCESS_NONE_KHR;
|
||||||
|
case Type::RenderTarget: return getReadOrWrite(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
|
||||||
|
case Type::DepthStencil: return getReadOrWrite(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
|
||||||
|
case Type::Multisample: return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
|
case Type::Presentation: return 0;
|
||||||
|
case Type::LinearAccess:
|
||||||
|
case Type::Sampled:
|
||||||
|
return location == Location::Host ? getReadOrWrite(VK_ACCESS_HOST_READ_BIT, VK_ACCESS_HOST_WRITE_BIT) : getReadOrWrite(VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
|
||||||
|
case Type::Transfer:
|
||||||
|
return getReadOrWrite(VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||||
|
case Type::InputAttachment:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ImageUsage(Type t, Access a, Location l, std::optional<int> att = std::nullopt)
|
||||||
|
: type(t), access(a), location(l), attachment(att) {}
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
Access access;
|
||||||
|
Location location;
|
||||||
|
std::optional<int> attachment;
|
||||||
|
|
||||||
|
inline VkAccessFlags getReadOrWrite(VkAccessFlags read, VkAccessFlags write) const {
|
||||||
|
VkAccessFlags flag = 0;
|
||||||
|
if (access == Access::ReadOnly || access == Access::ReadWrite)
|
||||||
|
flag |= read;
|
||||||
|
if (access == Access::WriteOnly || access == Access::ReadWrite)
|
||||||
|
flag |= write;
|
||||||
|
return flag;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how a single image will be used in each stage of a render pass.
|
||||||
|
* Allows a single image to be written to during one pass, read in a second, and presented in the final, and tracked.
|
||||||
|
* Helps figure out the optimizations that Vulkan can do to this image and the render passes that use it.
|
||||||
|
*/
|
||||||
|
class UsageTracker {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit UsageTracker(const ImageUsage& initial) : initialUsage(initial) {}
|
||||||
|
explicit UsageTracker() = default;
|
||||||
|
|
||||||
|
UsageTracker(UsageTracker&&) noexcept = default;
|
||||||
|
UsageTracker& operator=(UsageTracker&&) noexcept = default;
|
||||||
|
|
||||||
|
// Fluent API; chain calls in a builder pattern.
|
||||||
|
#define fluent UsageTracker&
|
||||||
|
|
||||||
|
fluent add(int pass, const ImageUsage& usage) {
|
||||||
|
usageAtSubpass.insert( { pass, usage} );
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent add(int start, int end, const ImageUsage& usage) {
|
||||||
|
for (int subpass = start; subpass <= end; ++subpass)
|
||||||
|
add(subpass, usage);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent addMultisample(int pass, std::string_view name) {
|
||||||
|
multisamples.insert( { pass, std::string(name) } );
|
||||||
|
return add(pass, ImageUsage::multisample());
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent setFinal(const ImageUsage& usage) {
|
||||||
|
finalUsage = usage;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<ImageUsage> getUsages() const {
|
||||||
|
size_t count = usageAtSubpass.size() + (finalUsage.has_value() ? 1 : 0) + 1;
|
||||||
|
|
||||||
|
std::vector<ImageUsage> usages;
|
||||||
|
usages.reserve(count);
|
||||||
|
usages.emplace_back(initialUsage);
|
||||||
|
|
||||||
|
for(const auto& pair : usageAtSubpass)
|
||||||
|
usages.emplace_back(pair.second);
|
||||||
|
|
||||||
|
if (finalUsage.has_value())
|
||||||
|
usages.emplace_back(finalUsage.value());
|
||||||
|
|
||||||
|
return usages;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::map<int, ImageUsage>& getUsageMap() const {
|
||||||
|
return usageAtSubpass;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageUsage& getInitialUsage() { return initialUsage; }
|
||||||
|
std::optional<ImageUsage> getFinalUsage() { return finalUsage; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::map<int, ImageUsage> usageAtSubpass;
|
||||||
|
ImageUsage initialUsage;
|
||||||
|
std::optional<ImageUsage> finalUsage;
|
||||||
|
std::map<int, std::string> multisamples;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple wrapper that allows tracking the current usage of multiple images.
|
||||||
|
*/
|
||||||
|
class MultiImageTracker {
|
||||||
|
public:
|
||||||
|
MultiImageTracker() = default;
|
||||||
|
|
||||||
|
MultiImageTracker(const MultiImageTracker&) = delete;
|
||||||
|
MultiImageTracker& operator=(const MultiImageTracker&) = delete;
|
||||||
|
|
||||||
|
// Fluent API; chain calls in a builder pattern
|
||||||
|
#undef fluent
|
||||||
|
#define fluent MultiImageTracker&
|
||||||
|
|
||||||
|
fluent track(std::string&& name, const ImageUsage& usage) {
|
||||||
|
images.insert( { std::move(name), usage } );
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent track(const std::string& name, const ImageUsage& usage) {
|
||||||
|
return track(std::string(name), usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent update(const std::string& name, const ImageUsage& usage) {
|
||||||
|
auto iter = images.find(name);
|
||||||
|
iter->second = usage;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool isTracking(const std::string& image) const {
|
||||||
|
return images.contains(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ImageUsage& get(const std::string& image) const {
|
||||||
|
return images.at(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, ImageUsage> images;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
struct Queue {
|
||||||
|
VkQueue queue;
|
||||||
|
int queueIndex;
|
||||||
|
};
|
||||||
|
}
|
|
@ -5489,7 +5489,6 @@ public:
|
||||||
// Posts next part of an open string.
|
// Posts next part of an open string.
|
||||||
void ContinueString(const char* pStr);
|
void ContinueString(const char* pStr);
|
||||||
// Posts next part of an open string. The number is converted to decimal characters.
|
// Posts next part of an open string. The number is converted to decimal characters.
|
||||||
void ContinueString(uint32_t n);
|
|
||||||
void ContinueString(uint64_t n);
|
void ContinueString(uint64_t n);
|
||||||
// Posts next part of an open string. Pointer value is converted to characters
|
// Posts next part of an open string. Pointer value is converted to characters
|
||||||
// using "%p" formatting - shown as hexadecimal number, e.g.: 000000081276Ad00
|
// using "%p" formatting - shown as hexadecimal number, e.g.: 000000081276Ad00
|
||||||
|
@ -5498,7 +5497,6 @@ public:
|
||||||
void EndString(const char* pStr = VMA_NULL);
|
void EndString(const char* pStr = VMA_NULL);
|
||||||
|
|
||||||
// Writes a number value.
|
// Writes a number value.
|
||||||
void WriteNumber(uint32_t n);
|
|
||||||
void WriteNumber(uint64_t n);
|
void WriteNumber(uint64_t n);
|
||||||
// Writes a boolean value - false or true.
|
// Writes a boolean value - false or true.
|
||||||
void WriteBool(bool b);
|
void WriteBool(bool b);
|
||||||
|
@ -5654,12 +5652,6 @@ void VmaJsonWriter::ContinueString(const char* pStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VmaJsonWriter::ContinueString(uint32_t n)
|
|
||||||
{
|
|
||||||
VMA_ASSERT(m_InsideString);
|
|
||||||
m_SB.AddNumber(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VmaJsonWriter::ContinueString(uint64_t n)
|
void VmaJsonWriter::ContinueString(uint64_t n)
|
||||||
{
|
{
|
||||||
VMA_ASSERT(m_InsideString);
|
VMA_ASSERT(m_InsideString);
|
||||||
|
@ -5683,13 +5675,6 @@ void VmaJsonWriter::EndString(const char* pStr)
|
||||||
m_InsideString = false;
|
m_InsideString = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VmaJsonWriter::WriteNumber(uint32_t n)
|
|
||||||
{
|
|
||||||
VMA_ASSERT(!m_InsideString);
|
|
||||||
BeginValue(false);
|
|
||||||
m_SB.AddNumber(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VmaJsonWriter::WriteNumber(uint64_t n)
|
void VmaJsonWriter::WriteNumber(uint64_t n)
|
||||||
{
|
{
|
||||||
VMA_ASSERT(!m_InsideString);
|
VMA_ASSERT(!m_InsideString);
|
||||||
|
|
|
@ -1,20 +1,129 @@
|
||||||
#include <vlkx/render/Camera.h>
|
#include <vlkx/render/Camera.h>
|
||||||
|
|
||||||
void Camera::init(float fov, float width, float height, float near, float far) {
|
using namespace vlkx;
|
||||||
|
|
||||||
// Initialise members
|
Camera& Camera::move(const glm::vec3 &delta) {
|
||||||
position = glm::vec3(0, 0, 4);
|
position += delta;
|
||||||
|
return *this;
|
||||||
glm::vec3 cameraFront = glm::vec3(0, 0, 0);
|
|
||||||
glm::vec3 cameraUp = glm::vec3(0, 1, 0);
|
|
||||||
|
|
||||||
viewMatrix = glm::mat4(1);
|
|
||||||
projectionMatrix = glm::mat4(1);
|
|
||||||
|
|
||||||
projectionMatrix = glm::perspective(fov, width / height, near, far);
|
|
||||||
viewMatrix = glm::lookAt(position, cameraFront, cameraUp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::setPosition(glm::vec3 newPosition) {
|
Camera &Camera::setPos(const glm::vec3 &pos) {
|
||||||
position = newPosition;
|
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>;
|
|
@ -1,11 +1,11 @@
|
||||||
#include <vlkx/render/Geometry.h>
|
#include <vlkx/render/Geometry.h>
|
||||||
using namespace Geo;
|
using namespace Geo;
|
||||||
|
|
||||||
void Mesh::setTriData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
|
void Mesh::setTriData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
|
||||||
std::vector<Vertex> Vertices = {
|
std::vector<VertexAll> Vertices = {
|
||||||
{ { 0.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } },
|
{ { 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.0f, 1.0f, 0.0 },{ 0.0, 0.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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } },
|
{ { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<uint32_t> Indices = {
|
std::vector<uint32_t> Indices = {
|
||||||
|
@ -18,13 +18,13 @@ void Mesh::setTriData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indi
|
||||||
indices = Indices;
|
indices = Indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::setQuadData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
|
void Mesh::setQuadData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
|
||||||
|
|
||||||
std::vector<Vertex> Vertices = {
|
std::vector<VertexAll> Vertices = {
|
||||||
{ { -1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } },
|
{ { -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.0f, 1.0f, 0.0 },{ 0.0, 0.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 },{ 0.0f, 0.0f, 1.0 },{ 1.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.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }
|
{ { 1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<uint32_t> Indices = {
|
std::vector<uint32_t> Indices = {
|
||||||
|
@ -37,38 +37,38 @@ void Mesh::setQuadData(std::vector<Vertex>& vertices, std::vector<uint32_t>& ind
|
||||||
indices = Indices;
|
indices = Indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::setCubeData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
|
void Mesh::setCubeData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
|
||||||
std::vector<Vertex> Vertices = {
|
std::vector<VertexAll> Vertices = {
|
||||||
// Front
|
// Front
|
||||||
{ { -1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 0
|
{ { -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.0f, 1.0f, 0.0 },{ 0.0, 0.0 } }, // 1
|
{ { -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 },{ 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, 0.0 } }, // 2
|
||||||
{ { 1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 3
|
{ { 1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 3
|
||||||
// Back
|
// Back
|
||||||
{ { 1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 4
|
{ { 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.0f, 1.0f, 1.0 },{ 0.0, 0.0 } }, // 5
|
{ { 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 },{ 0.0f, 1.0f, 1.0 },{ 1.0, 0.0 } }, // 6
|
{ { -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.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 7
|
{ { -1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0, 1.0 } }, // 7
|
||||||
// Left
|
// Left
|
||||||
{ { -1.0, -1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 8
|
{ { -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.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 9
|
{ { -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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 10
|
{ { -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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 11
|
{ { -1.0, -1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 1.0, 1.0 } }, // 11
|
||||||
// Right
|
// Right
|
||||||
{ { 1.0, -1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 12
|
{ { 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.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 13
|
{ { 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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 14
|
{ { 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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 15
|
{ { 1.0, -1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 1.0, 1.0 } }, // 15
|
||||||
// Top
|
// Top
|
||||||
{ { -1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 16
|
{ { -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.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 17
|
{ { -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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 18
|
{ { 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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 19
|
{ { 1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 1.0, 1.0 } }, // 19
|
||||||
// Bottom
|
// Bottom
|
||||||
{ { -1.0f, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } }, // 20
|
{ { -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.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 21
|
{ { -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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 22
|
{ { 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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 23
|
{ { 1.0, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 1.0, 1.0 } }, // 23
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<uint32_t> Indices = {
|
std::vector<uint32_t> Indices = {
|
||||||
|
@ -97,8 +97,8 @@ void Mesh::setCubeData(std::vector<Vertex>& vertices, std::vector<uint32_t>& ind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Mesh::setSphereData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
|
void Mesh::setSphereData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
|
||||||
std::vector<Vertex> Vertices;
|
std::vector<VertexAll> Vertices;
|
||||||
std::vector<uint32_t> Indices;
|
std::vector<uint32_t> Indices;
|
||||||
|
|
||||||
float latitudeBands = 20.0f;
|
float latitudeBands = 20.0f;
|
||||||
|
@ -116,7 +116,7 @@ void Mesh::setSphereData(std::vector<Vertex>& vertices, std::vector<uint32_t>& i
|
||||||
float sinPhi = sin(phi);
|
float sinPhi = sin(phi);
|
||||||
float cosPhi = cos(phi);
|
float cosPhi = cos(phi);
|
||||||
|
|
||||||
Vertex vs;
|
VertexAll vs;
|
||||||
|
|
||||||
vs.texture.x = (longNumber / longitudeBands); // u
|
vs.texture.x = (longNumber / longitudeBands); // u
|
||||||
vs.texture.y = (latNumber / latitudeBands); // v
|
vs.texture.y = (latNumber / latitudeBands); // v
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
#include <vlkx/render/framebuffer/RenderPass.h>
|
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
|
||||||
|
|
||||||
RenderPass::RenderPass() {}
|
|
||||||
RenderPass::~RenderPass() {}
|
|
||||||
|
|
||||||
|
|
||||||
void RenderPass::createVertexRenderPass(VkFormat format) {
|
|
||||||
// Set up color metadata
|
|
||||||
VkAttachmentDescription color = {};
|
|
||||||
color.format = format;
|
|
||||||
color.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
||||||
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
||||||
color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
||||||
color.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
||||||
color.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
color.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
||||||
|
|
||||||
// Set up subattachments
|
|
||||||
VkAttachmentReference colorReference = {};
|
|
||||||
colorReference.attachment = 0;
|
|
||||||
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
||||||
|
|
||||||
VkSubpassDescription subpass = {};
|
|
||||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
||||||
subpass.colorAttachmentCount = 1;
|
|
||||||
subpass.pColorAttachments = &colorReference;
|
|
||||||
|
|
||||||
// Prepare the Render Pass for creation
|
|
||||||
VkSubpassDependency dependency = {};
|
|
||||||
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
||||||
dependency.dstSubpass = 0;
|
|
||||||
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
||||||
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
||||||
dependency.srcAccessMask = 0;
|
|
||||||
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
||||||
|
|
||||||
std::array<VkAttachmentDescription, 1> attachments = { color };
|
|
||||||
VkRenderPassCreateInfo createInfo = {};
|
|
||||||
createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
||||||
createInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
|
|
||||||
createInfo.pAttachments = attachments.data();
|
|
||||||
createInfo.subpassCount = 1;
|
|
||||||
createInfo.dependencyCount = 1;
|
|
||||||
createInfo.pDependencies = &dependency;
|
|
||||||
createInfo.pSubpasses = &subpass;
|
|
||||||
|
|
||||||
// Create the Render Pass
|
|
||||||
if (vkCreateRenderPass(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pass))
|
|
||||||
throw std::runtime_error("Unable to create Render Pass 1");
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderPass::beginRenderPass(std::vector<VkClearValue> clearValues, VkCommandBuffer commands, VkFramebuffer framebuffer, VkExtent2D extent) {
|
|
||||||
// Prepare the Render Pass for start
|
|
||||||
VkRenderPassBeginInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
|
||||||
info.renderPass = pass;
|
|
||||||
info.framebuffer = framebuffer;
|
|
||||||
|
|
||||||
info.renderArea.offset = { 0, 0 };
|
|
||||||
info.renderArea.extent = extent;
|
|
||||||
|
|
||||||
info.pClearValues = clearValues.data();
|
|
||||||
info.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
|
||||||
|
|
||||||
// Attempt to begin the pass
|
|
||||||
vkCmdBeginRenderPass(commands, &info, VK_SUBPASS_CONTENTS_INLINE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderPass::endRenderPass(VkCommandBuffer commands) {
|
|
||||||
vkCmdEndRenderPass(commands);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void RenderPass::destroy() {
|
|
||||||
vkDestroyRenderPass(VulkanManager::getInstance()->getDevice()->logical, pass, nullptr);
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
#include <vlkx/render/geometry/SingleRenderer.h>
|
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
|
||||||
|
|
||||||
#include <glm/gtc/quaternion.hpp>
|
|
||||||
|
|
||||||
void SingleRenderer::createSingleRenderer(Geo::MeshType type, glm::vec3 posIn, glm::vec3 scaleIn) {
|
|
||||||
|
|
||||||
// Collect metadata about the swap chain
|
|
||||||
uint32_t imageCount = VulkanManager::getInstance()->getSwapchain()->images.size();
|
|
||||||
VkExtent2D imageExtent = VulkanManager::getInstance()->getSwapchain()->extent;
|
|
||||||
|
|
||||||
// Create the buffers
|
|
||||||
buffers.createBuffers(type);
|
|
||||||
|
|
||||||
// Create the descriptor layout
|
|
||||||
descriptor.createAndAllocateSimple(imageCount);
|
|
||||||
descriptor.populate(imageCount, buffers.uniformBuffer.buffer, sizeof(Geo::UniformBufferObject));
|
|
||||||
|
|
||||||
// Create the pipeline
|
|
||||||
pipeline.create(imageExtent, descriptor.layout, VulkanManager::getInstance()->getRenderPass()->pass);
|
|
||||||
|
|
||||||
// Set locals
|
|
||||||
position = posIn;
|
|
||||||
scale = scaleIn;
|
|
||||||
rotation = glm::mat4_cast(glm::quat(0, 0, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleRenderer::updateUniforms(Camera camera) {
|
|
||||||
|
|
||||||
// Prepare data to go into the buffers
|
|
||||||
Geo::UniformBufferObject ubo = {};
|
|
||||||
|
|
||||||
glm::mat4 scaleMatrix = glm::mat4(1.0f);
|
|
||||||
glm::mat4 rotationMatrix = getRotation();
|
|
||||||
glm::mat4 translationMatrix = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
scaleMatrix = glm::scale(scaleMatrix, scale);
|
|
||||||
translationMatrix = glm::translate(translationMatrix, position);
|
|
||||||
|
|
||||||
ubo.model = translationMatrix * rotationMatrix * scaleMatrix;
|
|
||||||
ubo.view = camera.getViewMatrix();
|
|
||||||
ubo.proj = camera.getProjectionMatrix();
|
|
||||||
|
|
||||||
ubo.proj[1][1] *= -1;
|
|
||||||
|
|
||||||
// Copy the buffers into the GPU
|
|
||||||
void* data;
|
|
||||||
vmaMapMemory(VulkanManager::getInstance()->getAllocator(), buffers.uniformBuffer.allocation, &data);
|
|
||||||
memcpy(data, &ubo, sizeof(ubo));
|
|
||||||
vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), buffers.uniformBuffer.allocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleRenderer::draw() {
|
|
||||||
// Fetch the buffer we're going to insert commands to
|
|
||||||
VkCommandBuffer commands = VulkanManager::getInstance()->getCurrentCommandBuffer();
|
|
||||||
|
|
||||||
// Bind our pipeline
|
|
||||||
vkCmdBindPipeline(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
|
|
||||||
|
|
||||||
// Bind the vertex buffer
|
|
||||||
VkBuffer vertexBuffers[] = {
|
|
||||||
buffers.vertexBuffer.buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
VkDeviceSize offsets[] = {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
vkCmdBindVertexBuffers(commands, 0, 1, vertexBuffers, offsets);
|
|
||||||
|
|
||||||
// Bind the index buffer
|
|
||||||
vkCmdBindIndexBuffer(commands, buffers.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
|
|
||||||
|
|
||||||
// Bind the uniform buffer
|
|
||||||
vkCmdBindDescriptorSets(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, &descriptor.set, 0, nullptr);
|
|
||||||
|
|
||||||
// Draw the buffered geometry
|
|
||||||
vkCmdDrawIndexed(commands, static_cast<uint32_t>(buffers.indices.size()), 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleRenderer::destroy() {
|
|
||||||
pipeline.destroy();
|
|
||||||
descriptor.destroy();
|
|
||||||
buffers.destroy();
|
|
||||||
}
|
|
|
@ -0,0 +1,312 @@
|
||||||
|
#include "vlkx/render/shader/Pipeline.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
#include "shadow/util/File.h"
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
struct ShaderStage {
|
||||||
|
VkShaderStageFlagBits stage;
|
||||||
|
ShaderModule::CountedShader module;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipelineViewportStateCreateInfo createViewport(const GraphicsPipelineBuilder::Viewport& port) {
|
||||||
|
return {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, 1, &port.viewport, 1, &port.scissor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPipelineColorBlendStateCreateInfo createBlend(const std::vector<VkPipelineColorBlendAttachmentState>& states) {
|
||||||
|
return {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_FALSE, VK_LOGIC_OP_CLEAR, static_cast<uint32_t>(states.size()), states.data(),
|
||||||
|
{ 0, 0, 0, 0 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPipelineVertexInputStateCreateInfo createVertexInput(const std::vector<VkVertexInputBindingDescription>& bindingDescs,
|
||||||
|
const std::vector<VkVertexInputAttributeDescription>& attrDescs) {
|
||||||
|
return {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0,
|
||||||
|
static_cast<uint32_t>(bindingDescs.size()), bindingDescs.data(),
|
||||||
|
static_cast<uint32_t>(attrDescs.size()), attrDescs.data()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ShaderStage> createShader(const std::map<VkShaderStageFlagBits, std::string>& shaderMap) {
|
||||||
|
std::vector<ShaderStage> stages;
|
||||||
|
stages.reserve(shaderMap.size());
|
||||||
|
for (const auto& pair : shaderMap) {
|
||||||
|
stages.push_back({ pair.first, ShaderModule::CountedShader::get(pair.second, pair.second) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return stages;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkPipelineShaderStageCreateInfo> createShaderStage(const std::vector<ShaderStage>& stages) {
|
||||||
|
static constexpr char entryPoint[] = "main";
|
||||||
|
std::vector<VkPipelineShaderStageCreateInfo> infos;
|
||||||
|
infos.reserve(stages.size());
|
||||||
|
for (const auto& stage : stages) {
|
||||||
|
infos.push_back({
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||||
|
nullptr, 0, stage.stage, **stage.module, entryPoint, nullptr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderModule::ShaderModule(const std::string &path) {
|
||||||
|
const shadowutil::FileData* file = shadowutil::loadFile(path);
|
||||||
|
const VkShaderModuleCreateInfo module {
|
||||||
|
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||||
|
nullptr, 0, file->size, reinterpret_cast<const uint32_t*>(file->data.data())
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vkCreateShaderModule(VulkanModule::getInstance()->getDevice()->logical, &module, nullptr, &shader) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create shader module");
|
||||||
|
}
|
||||||
|
|
||||||
|
PipelineBuilder::PipelineBuilder(std::optional<int> maxCache) {
|
||||||
|
const VkPipelineCacheCreateInfo info {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
|
||||||
|
nullptr, 0, static_cast<size_t>(maxCache.value_or(0)), nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vkCreatePipelineCache(VulkanModule::getInstance()->getDevice()->logical, &info, nullptr, &cache) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create pipeline cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineBuilder::setLayout(std::vector<VkDescriptorSetLayout>&& descs,
|
||||||
|
std::vector<VkPushConstantRange>&& pushConstants) {
|
||||||
|
std::vector<int> pushSizes (pushConstants.size());
|
||||||
|
for (size_t i = 0; i < pushConstants.size(); i++)
|
||||||
|
pushSizes[i] = pushConstants[i].size;
|
||||||
|
|
||||||
|
const auto totalSize = std::accumulate(pushSizes.begin(), pushSizes.end(), 0);
|
||||||
|
if (totalSize > 128)
|
||||||
|
throw std::runtime_error("Trying to set push constants of total size " + std::to_string(totalSize) + " into pipeline " + name);
|
||||||
|
|
||||||
|
descLayouts = std::move(descs);
|
||||||
|
constants = std::move(pushConstants);
|
||||||
|
layoutInfo.emplace(VkPipelineLayoutCreateInfo {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||||
|
nullptr, 0, static_cast<uint32_t>(descLayouts.size()), descLayouts.data(),
|
||||||
|
static_cast<uint32_t>(constants.size()), constants.data()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder::GraphicsPipelineBuilder(std::optional<int> maxCache) : PipelineBuilder(maxCache) {
|
||||||
|
assemblyInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
rasterizationInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_FALSE, VK_FALSE, VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||||
|
VK_FALSE, 0, 0, 0, 1
|
||||||
|
};
|
||||||
|
|
||||||
|
multisampleInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_SAMPLE_COUNT_1_BIT, VK_FALSE, 0, nullptr, VK_FALSE, VK_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
depthStencilInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, VK_FALSE, VK_FALSE,
|
||||||
|
VK_COMPARE_OP_LESS_OR_EQUAL, VK_FALSE, VK_FALSE,
|
||||||
|
{}, {}, 0, 1
|
||||||
|
};
|
||||||
|
|
||||||
|
dynamicStateInfo = {
|
||||||
|
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||||
|
nullptr, 0, 0, nullptr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::name(std::string &&name) {
|
||||||
|
setName(std::move(name));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::depthTest(bool enable, bool write) {
|
||||||
|
depthStencilInfo.depthTestEnable = enable;
|
||||||
|
depthStencilInfo.depthWriteEnable = write;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::stencilTest(bool enable) {
|
||||||
|
depthStencilInfo.stencilTestEnable = enable;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::multiSample(VkSampleCountFlagBits samples) {
|
||||||
|
multisampleInfo.rasterizationSamples = samples;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::topology(VkPrimitiveTopology topology) {
|
||||||
|
assemblyInfo.topology = topology;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::stencilOp(const VkStencilOpState &state,
|
||||||
|
VkStencilFaceFlags flags) {
|
||||||
|
if (flags & VK_STENCIL_FACE_FRONT_BIT)
|
||||||
|
depthStencilInfo.front = state;
|
||||||
|
if (flags & VK_STENCIL_FACE_BACK_BIT)
|
||||||
|
depthStencilInfo.back = state;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::addVertex(uint32_t bindPoint,
|
||||||
|
VkVertexInputBindingDescription &&desc,
|
||||||
|
std::vector<VkVertexInputAttributeDescription> &&attrs) {
|
||||||
|
desc.binding = bindPoint;
|
||||||
|
for (auto& attr : attrs)
|
||||||
|
attr.binding = bindPoint;
|
||||||
|
bindingDescs.push_back(desc);
|
||||||
|
|
||||||
|
attrDescs.reserve(attrDescs.size() + attrs.size());
|
||||||
|
std::move(attrs.begin(), attrs.end(), std::back_inserter(attrDescs));
|
||||||
|
attrs.clear();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::layout(std::vector<VkDescriptorSetLayout> &&descLayouts,
|
||||||
|
std::vector<VkPushConstantRange> &&constants) {
|
||||||
|
setLayout(std::move(descLayouts), std::move(constants));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::viewport(const vlkx::GraphicsPipelineBuilder::Viewport &port,
|
||||||
|
bool flipY) {
|
||||||
|
viewportMeta.emplace(port);
|
||||||
|
|
||||||
|
if (flipY) {
|
||||||
|
VkViewport& view = viewportMeta.value().viewport;
|
||||||
|
view.y += view.height;
|
||||||
|
view.height *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rasterizationInfo.frontFace = flipY ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::renderPass(const VkRenderPass &pass, uint32_t subpass) {
|
||||||
|
passMeta.emplace(PassInfo { pass, subpass });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::colorBlend(
|
||||||
|
std::vector<VkPipelineColorBlendAttachmentState> &&states) {
|
||||||
|
blendStates = std::move(states);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineBuilder& GraphicsPipelineBuilder::shader(VkShaderStageFlagBits stage, std::string &&file) {
|
||||||
|
shaders[stage] = std::move(file);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Pipeline> GraphicsPipelineBuilder::build() const {
|
||||||
|
if (!hasLayout())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no layout set");
|
||||||
|
if (!viewportMeta.has_value())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no viewport set");
|
||||||
|
if (!passMeta.has_value())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no render pass set");
|
||||||
|
if (blendStates.empty())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no color blend states.");
|
||||||
|
if (shaders.empty())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no shaders bound");
|
||||||
|
|
||||||
|
const auto viewportState = createViewport(viewportMeta.value());
|
||||||
|
const auto blendState = createBlend(blendStates);
|
||||||
|
const auto vertexState = createVertexInput(bindingDescs, attrDescs);
|
||||||
|
|
||||||
|
const auto shaderStages = createShader(shaders);
|
||||||
|
const auto shaderStageInfo = createShaderStage(shaderStages);
|
||||||
|
VkPipelineLayout pipelineLayout;
|
||||||
|
if (vkCreatePipelineLayout(VulkanModule::getInstance()->getDevice()->logical, &getLayout(), nullptr, &pipelineLayout) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create layout for pipeline " + getName());
|
||||||
|
|
||||||
|
const VkGraphicsPipelineCreateInfo createInfo {
|
||||||
|
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, nullptr, 0,
|
||||||
|
static_cast<uint32_t>(shaderStageInfo.size()), shaderStageInfo.data(),
|
||||||
|
&vertexState, &assemblyInfo, nullptr, &viewportState,
|
||||||
|
&rasterizationInfo, &multisampleInfo, &depthStencilInfo, &blendState, &dynamicStateInfo,
|
||||||
|
pipelineLayout, passMeta->pass, passMeta->subpass, VK_NULL_HANDLE, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipeline pipeline;
|
||||||
|
if (vkCreateGraphicsPipelines(VulkanModule::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &createInfo, nullptr, &pipeline) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Failed to create pipeline " + getName());
|
||||||
|
|
||||||
|
return std::unique_ptr<Pipeline> {
|
||||||
|
new Pipeline(getName(), pipeline, pipelineLayout, VK_PIPELINE_BIND_POINT_GRAPHICS)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipelineBuilder& ComputePipelineBuilder::name(std::string &&name) {
|
||||||
|
setName(std::move(name));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipelineBuilder& ComputePipelineBuilder::layout(std::vector<VkDescriptorSetLayout> &&descLayouts,
|
||||||
|
std::vector<VkPushConstantRange> &&pushConstants) {
|
||||||
|
setLayout(std::move(descLayouts), std::move(pushConstants));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipelineBuilder& ComputePipelineBuilder::shader(std::string &&path) {
|
||||||
|
shaderPath.emplace(std::move(path));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Pipeline> ComputePipelineBuilder::build() const {
|
||||||
|
if (!hasLayout())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no layout set");
|
||||||
|
if (!shaderPath.has_value())
|
||||||
|
throw std::runtime_error("Pipeline " + getName() + " has no shader set");
|
||||||
|
|
||||||
|
const auto shaderStages = createShader({{VK_SHADER_STAGE_COMPUTE_BIT, shaderPath.value()}});
|
||||||
|
const auto shaderStageInfo = createShaderStage(shaderStages);
|
||||||
|
if (shaderStageInfo.size() != 1)
|
||||||
|
throw std::runtime_error("Compute pipeline " + getName() + " must have exactly one shader bound");
|
||||||
|
|
||||||
|
VkPipelineLayout layout;
|
||||||
|
if (vkCreatePipelineLayout(VulkanModule::getInstance()->getDevice()->logical, &getLayout(), nullptr,
|
||||||
|
&layout) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create layout for compute pipeline " + getName());
|
||||||
|
|
||||||
|
const VkComputePipelineCreateInfo createInfo{
|
||||||
|
VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||||
|
nullptr, 0,
|
||||||
|
shaderStageInfo[0], layout, VK_NULL_HANDLE, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipeline pipeline;
|
||||||
|
if (vkCreateComputePipelines(VulkanModule::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &createInfo,
|
||||||
|
nullptr, &pipeline) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create compute pipeline " + getName());
|
||||||
|
|
||||||
|
return std::unique_ptr<Pipeline>{
|
||||||
|
new Pipeline{getName(), pipeline, layout, VK_PIPELINE_BIND_POINT_COMPUTE}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pipeline::bind(const VkCommandBuffer &buffer) const {
|
||||||
|
vkCmdBindPipeline(buffer, bindPoint, pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipeline::~Pipeline() {
|
||||||
|
vkDestroyPipeline(VulkanModule::getInstance()->getDevice()->logical, pipeline, nullptr);
|
||||||
|
vkDestroyPipelineLayout(VulkanModule::getInstance()->getDevice()->logical, layout, nullptr);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,394 @@
|
||||||
|
#include "vlkx/render/render_pass/GPUPass.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
inline bool needsSynchronization(const ImageUsage& prev, const ImageUsage& curr) {
|
||||||
|
if (curr == prev && curr.getAccess() == ImageUsage::Access::ReadOnly)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addUsageToSubpass(const ImageUsage& usage, RenderPassBuilder::SubpassDependency::SubpassMeta* pass) {
|
||||||
|
pass->stage |= usage.getStage();
|
||||||
|
pass->access |= usage.getAccessFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommonPass::addUsage(std::string &&name, UsageTracker &&tracker) {
|
||||||
|
for (const auto& pair : tracker.getUsageMap())
|
||||||
|
validate(pair.first, name, false);
|
||||||
|
|
||||||
|
tracker.add(getVirtualInitial(), tracker.getInitialUsage());
|
||||||
|
if (tracker.getFinalUsage().has_value())
|
||||||
|
tracker.add(getVirtualFinal(), tracker.getFinalUsage().value());
|
||||||
|
|
||||||
|
usageHistory.emplace(std::move(name), std::move(tracker));
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageLayout CommonPass::getInitialLayout(const std::string &name) const {
|
||||||
|
return getHistory(name).getUsageMap().begin()->second.getLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageLayout CommonPass::getFinalLayout(const std::string &name) const {
|
||||||
|
return getHistory(name).getUsageMap().rbegin()->second.getLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageLayout CommonPass::getSubpassLayout(const std::string &name, int subpass) const {
|
||||||
|
validate(subpass, name, false);
|
||||||
|
return getUsage(name, subpass)->getLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommonPass::update(const std::string &name, MultiImageTracker &tracker) const {
|
||||||
|
tracker.update(name, getHistory(name).getUsageMap().rbegin()->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
const UsageTracker& CommonPass::getHistory(const std::string &name) const {
|
||||||
|
return usageHistory.at(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageUsage* CommonPass::getUsage(const std::string &name, int pass) const {
|
||||||
|
validate(pass, name, true);
|
||||||
|
const UsageTracker& history = getHistory(name);
|
||||||
|
const auto iter = history.getUsageMap().find(pass);
|
||||||
|
return iter != history.getUsageMap().end() ? &iter->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CommonPass::Usages> CommonPass::checkForSync(const std::string &name, int pass) const {
|
||||||
|
validate(pass, name, true);
|
||||||
|
const UsageTracker& history = getHistory(name);
|
||||||
|
const auto currIter = history.getUsageMap().find(pass);
|
||||||
|
if (currIter == history.getUsageMap().end())
|
||||||
|
return std::nullopt;
|
||||||
|
const auto prevIter = std::prev(currIter);
|
||||||
|
|
||||||
|
const ImageUsage& prevUsage = prevIter->second;
|
||||||
|
const ImageUsage& currUsage = currIter->second;
|
||||||
|
|
||||||
|
if (!needsSynchronization(prevUsage, currUsage))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
const int prevSubpass = prevIter->first;
|
||||||
|
return CommonPass::Usages { prevSubpass, &prevUsage, &currUsage };
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommonPass::validate(int pass, const std::string &image, bool includeVirtual) const {
|
||||||
|
if (includeVirtual) {
|
||||||
|
if (!(pass >= getVirtualInitial() && pass <= getVirtualFinal()))
|
||||||
|
throw std::runtime_error("Subpass out of range.");
|
||||||
|
} else {
|
||||||
|
if (!(pass >= 0 && pass < numPasses))
|
||||||
|
throw std::runtime_error("nv Subpass out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GraphicsPass::add(const std::string &name, UsageTracker &&history, std::function<int(int)> &&getter,
|
||||||
|
const std::optional<RenderPassBuilder::Attachment::OpsType> ops) {
|
||||||
|
verifyHistory(name, history);
|
||||||
|
|
||||||
|
const std::optional<int> needsGetter = getFirstRenderTarget(history);
|
||||||
|
if (needsGetter.has_value()) {
|
||||||
|
if (getter == nullptr)
|
||||||
|
throw std::runtime_error("Image " + name + " is used as a render target without a location getter.");
|
||||||
|
} else {
|
||||||
|
getter = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int attachmentLocation = static_cast<int>(metas.size());
|
||||||
|
metas.insert(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
AttachmentMeta {
|
||||||
|
attachmentLocation,
|
||||||
|
std::move(getter),
|
||||||
|
getOps(name, history, ops),
|
||||||
|
{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
addUsage(std::string(name), std::move(history));
|
||||||
|
return attachmentLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPass& GraphicsPass::addMultisample(const std::string &source, const std::string &dest, int pass) {
|
||||||
|
validate(pass, source, false);
|
||||||
|
|
||||||
|
const auto source_iter = usageHistory.find(source);
|
||||||
|
if (source_iter == usageHistory.end())
|
||||||
|
throw std::runtime_error("Usage history not found for source image " + source);
|
||||||
|
|
||||||
|
const UsageTracker& source_history = source_iter->second;
|
||||||
|
if (!verifyImageUsage(source_history, pass, ImageUsage::Type::RenderTarget))
|
||||||
|
throw std::runtime_error("Usage type for source image " + source + " must be render target.");
|
||||||
|
|
||||||
|
const auto dest_iter = usageHistory.find(dest);
|
||||||
|
if (dest_iter == usageHistory.end())
|
||||||
|
throw std::runtime_error("Usage history not found for destination image " + dest);
|
||||||
|
|
||||||
|
const UsageTracker& dest_history = dest_iter->second;
|
||||||
|
if (!verifyImageUsage(dest_history, pass, ImageUsage::Type::Multisample))
|
||||||
|
throw std::runtime_error("Usage type for destination image " + dest + " must be multisample");
|
||||||
|
|
||||||
|
auto& targetMap = metas[source].multisample;
|
||||||
|
const bool inserted = targetMap.insert( { pass, dest }).second;
|
||||||
|
|
||||||
|
if (!inserted)
|
||||||
|
throw std::runtime_error("Image " + source + " is already bound to a multisample.");
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPass::setAttachments() {
|
||||||
|
for (const auto& pair : usageHistory) {
|
||||||
|
const std::string& name = pair.first;
|
||||||
|
const AttachmentMeta& meta = metas[name];
|
||||||
|
builder->setAttachment(meta.index, { meta.ops, getInitialLayout(name), getFinalLayout(name) } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPass::setSubpasses() {
|
||||||
|
for (int pass = 0; pass < numPasses; ++pass) {
|
||||||
|
std::vector<RenderPassBuilder::ColorAttachmentMeta> colors;
|
||||||
|
std::vector<RenderPassBuilder::ColorAttachmentMeta> multisamples;
|
||||||
|
VkAttachmentReference dsRef { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED };
|
||||||
|
|
||||||
|
// Verify all images used, the long way around.
|
||||||
|
for (const auto& pair : usageHistory) {
|
||||||
|
const std::string& name = pair.first;
|
||||||
|
const ImageUsage* history = getUsage(name, pass);
|
||||||
|
if (history == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const AttachmentMeta& meta = metas[name];
|
||||||
|
const VkAttachmentReference ref { static_cast<uint32_t>(meta.index), history->getLayout() };
|
||||||
|
|
||||||
|
switch (history->getType()) {
|
||||||
|
// If we're looking at a render target, we need to figure out where it gets its' details from.
|
||||||
|
case ImageUsage::Type::RenderTarget: {
|
||||||
|
const int location = meta.getter(pass);
|
||||||
|
const auto iter = meta.multisample.find(pass);
|
||||||
|
if (iter != meta.multisample.end()) {
|
||||||
|
const std::string& target = iter->second;
|
||||||
|
const ImageUsage* targetUsage = getUsage(target, pass);
|
||||||
|
if (targetUsage == nullptr)
|
||||||
|
throw std::runtime_error("Expected target image to have a usage");
|
||||||
|
multisamples.push_back( { location, metas[target].index, targetUsage->getLayout() });
|
||||||
|
}
|
||||||
|
|
||||||
|
colors.push_back( { location, static_cast<int>(ref.attachment), ref.layout });
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ImageUsage::Type::DepthStencil:
|
||||||
|
if (dsRef.attachment != VK_ATTACHMENT_UNUSED)
|
||||||
|
throw std::runtime_error("Depth stencil used multiple times");
|
||||||
|
dsRef = ref;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ImageUsage::Type::Multisample:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unreachable?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto colorRefs = RenderPassBuilder::parseColorReferences(colors);
|
||||||
|
auto multisampleRefs = RenderPassBuilder::parseMutisampling(colorRefs.size(), multisamples);
|
||||||
|
builder->setSubpass(pass, std::move(colorRefs), std::move(multisampleRefs), dsRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPass::setDependencies() {
|
||||||
|
if (getVirtualFinal() != numPasses)
|
||||||
|
throw std::runtime_error("Virtual subpass mismatch");
|
||||||
|
for (int pass = 0; pass <= numPasses; ++pass) {
|
||||||
|
std::map<int, RenderPassBuilder::SubpassDependency> deps;
|
||||||
|
|
||||||
|
for (const auto& pair : usageHistory) {
|
||||||
|
const auto usage = checkForSync(pair.first, pass);
|
||||||
|
if (!usage.has_value()) continue;
|
||||||
|
|
||||||
|
const ImageUsage& prev = usage.value().lastUsage;
|
||||||
|
const ImageUsage& curr = usage.value().currentUsage;
|
||||||
|
const int sourcePass = usage.value().lastSubpass;
|
||||||
|
|
||||||
|
auto iter = deps.find(sourcePass);
|
||||||
|
if (iter == deps.end()) {
|
||||||
|
const RenderPassBuilder::SubpassDependency defaultDep {
|
||||||
|
{ checkSubpass(sourcePass), VK_PIPELINE_STAGE_NONE_KHR, VK_ACCESS_NONE_KHR },
|
||||||
|
{ checkSubpass(pass), VK_PIPELINE_STAGE_NONE_KHR, VK_ACCESS_NONE_KHR },
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
iter = deps.insert( { sourcePass, defaultDep } ).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
addUsageToSubpass(prev, &iter->second.source);
|
||||||
|
addUsageToSubpass(curr, &iter->second.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& pair : deps) {
|
||||||
|
const RenderPassBuilder::SubpassDependency& dep = pair.second;
|
||||||
|
builder->addDependency(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RenderPassBuilder> GraphicsPass::build(int framebuffers) {
|
||||||
|
builder = std::make_unique<RenderPassBuilder>();
|
||||||
|
builder->setFramebufferCount(framebuffers);
|
||||||
|
|
||||||
|
setAttachments();
|
||||||
|
setSubpasses();
|
||||||
|
setDependencies();
|
||||||
|
return std::move(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> GraphicsPass::getFirstRenderTarget(const UsageTracker &history) const {
|
||||||
|
for (const auto& pair : history.getUsageMap()) {
|
||||||
|
const int pass = pair.first;
|
||||||
|
if (isVirtual(pass)) continue;
|
||||||
|
|
||||||
|
if (pair.second.getType() == ImageUsage::Type::RenderTarget) return pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPassBuilder::Attachment::OpsType GraphicsPass::getOps(const std::string &name, const UsageTracker &history,
|
||||||
|
const std::optional<RenderPassBuilder::Attachment::OpsType> &userOps) const {
|
||||||
|
const ImageUsage::Type type = getUsageType(name, history);
|
||||||
|
|
||||||
|
if (!userOps.has_value()) {
|
||||||
|
switch (type) {
|
||||||
|
case ImageUsage::Type::RenderTarget: return getDefaultOps();
|
||||||
|
case ImageUsage::Type::DepthStencil: return getStencilOps();
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unreachable?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderPassBuilder::Attachment::OpsType& ops = userOps.value();
|
||||||
|
switch (type) {
|
||||||
|
case ImageUsage::Type::RenderTarget:
|
||||||
|
case ImageUsage::Type::DepthStencil:
|
||||||
|
return ops;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unreachable?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageUsage::Type GraphicsPass::getUsageType(const std::string &name, const UsageTracker &history) const {
|
||||||
|
ImageUsage::Type prev = ImageUsage::Type::DontCare;
|
||||||
|
|
||||||
|
for (const auto& pair : history.getUsageMap()) {
|
||||||
|
if (isVirtual(pair.first)) continue;
|
||||||
|
|
||||||
|
ImageUsage::Type type = pair.second.getType();
|
||||||
|
if (type == ImageUsage::Type::Multisample)
|
||||||
|
type = ImageUsage::Type::RenderTarget;
|
||||||
|
|
||||||
|
if (prev == ImageUsage::Type::DontCare) {
|
||||||
|
prev = type;
|
||||||
|
} else if (type != prev) {
|
||||||
|
throw std::runtime_error("Inconsistent usage type specified for " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev == ImageUsage::Type::DontCare)
|
||||||
|
throw std::runtime_error("Image " + name + " has no usages.");
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GraphicsPass::verifyImageUsage(const UsageTracker &history, int subpass, ImageUsage::Type type) const {
|
||||||
|
const auto iter = history.getUsageMap().find(subpass);
|
||||||
|
return iter != history.getUsageMap().end() && iter->second.getType() == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPass::verifyHistory(const std::string &image, const UsageTracker &history) const {
|
||||||
|
for (const auto& pair : history.getUsageMap()) {
|
||||||
|
const ImageUsage::Type type = pair.second.getType();
|
||||||
|
if (type != ImageUsage::Type::RenderTarget && type != ImageUsage::Type::DepthStencil && type != ImageUsage::Type::Multisample)
|
||||||
|
throw std::runtime_error("Invalid usage of " + image + " at subpass " + std::to_string(pair.first));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePass &ComputePass::add(std::string &&name, UsageTracker &&history) {
|
||||||
|
verify(name, history);
|
||||||
|
addUsage(std::move(name), std::move(history));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputePass::execute(const VkCommandBuffer &commands, uint32_t queueFamily,
|
||||||
|
const std::map<std::string, const VkImage*> &images,
|
||||||
|
const std::vector<std::function<void()>>& computeOps) const {
|
||||||
|
|
||||||
|
if (computeOps.size() != numPasses)
|
||||||
|
throw std::runtime_error("Compute shader mismatches ops and passes.");
|
||||||
|
|
||||||
|
if (getVirtualFinal() != numPasses)
|
||||||
|
throw std::runtime_error("Compute shader attempting to run too many subpasses");
|
||||||
|
|
||||||
|
for (int pass = 0; pass < numPasses; ++pass) {
|
||||||
|
for (const auto& pair : usageHistory) {
|
||||||
|
const std::string& image = pair.first;
|
||||||
|
const auto usage = checkForSync(image, pass);
|
||||||
|
if (!usage.has_value()) continue;
|
||||||
|
|
||||||
|
const auto iter = images.find(image);
|
||||||
|
if (iter == images.end())
|
||||||
|
throw std::runtime_error("Image " + image + " not provided");
|
||||||
|
|
||||||
|
barrier(commands, queueFamily, *iter->second, usage.value().lastUsage, usage.value().currentUsage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pass < numPasses)
|
||||||
|
computeOps[pass]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputePass::barrier(const VkCommandBuffer &commands, uint32_t queueFamily, const VkImage &image,
|
||||||
|
const ImageUsage &prev, const ImageUsage ¤t) const {
|
||||||
|
const VkImageMemoryBarrier barrier {
|
||||||
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
|
nullptr,
|
||||||
|
prev.getAccessFlags(),
|
||||||
|
current.getAccessFlags(),
|
||||||
|
prev.getLayout(),
|
||||||
|
current.getLayout(),
|
||||||
|
queueFamily,
|
||||||
|
queueFamily,
|
||||||
|
image,
|
||||||
|
{
|
||||||
|
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdPipelineBarrier(
|
||||||
|
commands,
|
||||||
|
prev.getStage(),
|
||||||
|
current.getStage(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
1,
|
||||||
|
&barrier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComputePass::verify(const std::string &name, const UsageTracker &history) const {
|
||||||
|
for (const auto& pair : history.getUsageMap()) {
|
||||||
|
const ImageUsage::Type type = pair.second.getType();
|
||||||
|
if (type != ImageUsage::Type::LinearAccess && type != ImageUsage::Type::Sampled && type != ImageUsage::Type::Transfer)
|
||||||
|
throw std::runtime_error("Compute shader using an attachment that is not guranteed to be readable.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
#include "vlkx/render/render_pass/GenericRenderPass.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the necessary Clear Value struct to erase the given attachment.
|
||||||
|
* @param attachment the attachment metadata
|
||||||
|
* @return the Clear Value that will erase the attachment's data
|
||||||
|
*/
|
||||||
|
VkClearValue createClearFor(const RenderPassBuilder::Attachment& attachment) {
|
||||||
|
|
||||||
|
VkClearValue clear {};
|
||||||
|
if (std::holds_alternative<RenderPassBuilder::Attachment::ColorOps>(attachment.ops))
|
||||||
|
clear.color = { { 0, 0, 0, 0 } };
|
||||||
|
else {
|
||||||
|
clear.depthStencil = { 1.0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return clear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a RenderPassBuilder attachment to a VkAttachmentDescription.
|
||||||
|
* Format will be UNDEFINED.
|
||||||
|
* Sample count will be 1.
|
||||||
|
*/
|
||||||
|
VkAttachmentDescription buildAttachment(const RenderPassBuilder::Attachment& attachment) {
|
||||||
|
VkAttachmentDescription descriptor {
|
||||||
|
{},
|
||||||
|
VK_FORMAT_UNDEFINED,
|
||||||
|
VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||||
|
VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||||
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||||
|
VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||||
|
attachment.layoutInitial,
|
||||||
|
attachment.layoutFinal
|
||||||
|
};
|
||||||
|
|
||||||
|
if (const auto& ops = std::get_if<RenderPassBuilder::Attachment::ColorOps>(&attachment.ops); ops != nullptr) {
|
||||||
|
descriptor.loadOp = ops->LOAD;
|
||||||
|
descriptor.storeOp = ops->STORE;
|
||||||
|
} else if (const auto& ops = std::get_if<RenderPassBuilder::Attachment::StencilDepthOps>(&attachment.ops); ops != nullptr) {
|
||||||
|
descriptor.loadOp = ops->DEPTH_LOAD;
|
||||||
|
descriptor.storeOp = ops->DEPTH_STORE;
|
||||||
|
descriptor.stencilLoadOp = ops->STENCIL_LOAD;
|
||||||
|
descriptor.stencilStoreOp = ops->STENCIL_STORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkSubpassDescription> buildSubpassDescriptors(const std::vector<RenderPassBuilder::SubpassAttachments>& attachments) {
|
||||||
|
std::vector<VkSubpassDescription> descriptors;
|
||||||
|
descriptors.reserve(attachments.size());
|
||||||
|
|
||||||
|
for (const auto& attachment : attachments) {
|
||||||
|
descriptors.emplace_back(VkSubpassDescription {
|
||||||
|
{},
|
||||||
|
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
static_cast<uint32_t>(attachment.colorReferences.size()),
|
||||||
|
attachment.colorReferences.data(),
|
||||||
|
attachment.multisampleReferences.empty() ? nullptr : attachment.multisampleReferences.data(),
|
||||||
|
attachment.stencilDepthReference.has_value() ? &attachment.stencilDepthReference.value() : nullptr,
|
||||||
|
0,
|
||||||
|
nullptr
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSubpassDependency buildSubpassDependency(const RenderPassBuilder::SubpassDependency& dep) {
|
||||||
|
return VkSubpassDependency {
|
||||||
|
dep.source.index,
|
||||||
|
dep.destination.index,
|
||||||
|
dep.source.stage,
|
||||||
|
dep.destination.stage,
|
||||||
|
dep.source.access,
|
||||||
|
dep.destination.access,
|
||||||
|
dep.flags
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> countColorAttachments(const std::vector<RenderPassBuilder::SubpassAttachments>& attachments) {
|
||||||
|
std::vector<int> count;
|
||||||
|
count.reserve(attachments.size());
|
||||||
|
|
||||||
|
for (const auto& attachment : attachments)
|
||||||
|
count.emplace_back(attachment.colorReferences.size());
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkFramebuffer> createFramebuffers(const VkRenderPass& renderPass, const std::vector<std::function<const Image&(int idx)>> getters, int count, const VkExtent2D& extent) {
|
||||||
|
std::vector<VkImageView> views(getters.size());
|
||||||
|
|
||||||
|
VkFramebufferCreateInfo framebufferCreate {
|
||||||
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||||
|
nullptr,
|
||||||
|
{},
|
||||||
|
renderPass,
|
||||||
|
static_cast<uint32_t>(views.size()),
|
||||||
|
views.data(),
|
||||||
|
extent.width,
|
||||||
|
extent.height,
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<VkFramebuffer> framebuffers(count);
|
||||||
|
for (int i = 0; i < framebuffers.size(); ++i) {
|
||||||
|
for (int image = 0; image < getters.size(); image++)
|
||||||
|
views[image] = getters[image](i).getView();
|
||||||
|
|
||||||
|
vkCreateFramebuffer(VulkanModule::getInstance()->getDevice()->logical, &framebufferCreate, nullptr, &framebuffers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return framebuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkAttachmentReference> RenderPassBuilder::parseColorReferences(std::vector<ColorAttachmentMeta> meta) {
|
||||||
|
if (meta.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Search for the highest location index'd attachment reference.
|
||||||
|
// Shaders can define negative locations, so we have to start as low as we can go,
|
||||||
|
// and work our way up until we find the highest.
|
||||||
|
// If we start at 0, we risk missing an all-negative location set.
|
||||||
|
int max = std::numeric_limits<int>::min();
|
||||||
|
for (const auto& attachment : meta)
|
||||||
|
max = std::max(max, attachment.location);
|
||||||
|
|
||||||
|
std::vector<VkAttachmentReference> references(max + 1, { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED });
|
||||||
|
for (const auto& attachment : meta)
|
||||||
|
references[attachment.location] = { static_cast<uint32_t>(attachment.descriptionIdx), attachment.layout };
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkAttachmentReference> RenderPassBuilder::parseMutisampling(int colorReferencesCount, std::vector<ColorAttachmentMeta> meta) {
|
||||||
|
std::vector<VkAttachmentReference> references(colorReferencesCount, { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED });
|
||||||
|
for (const auto& attachment : meta)
|
||||||
|
references[attachment.location] = { static_cast<uint32_t>(attachment.descriptionIdx), attachment.layout };
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::setFramebufferCount(int count) {
|
||||||
|
framebufferCount = count;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::setAttachment(int idx, const Attachment &attachment) {
|
||||||
|
if (idx > clearValues.size())
|
||||||
|
clearValues.resize(idx + 1);
|
||||||
|
clearValues.at(idx) = createClearFor(attachment);
|
||||||
|
|
||||||
|
if (idx > attachmentDescriptors.size())
|
||||||
|
attachmentDescriptors.resize(idx + 1);
|
||||||
|
attachmentDescriptors.at(idx) = buildAttachment(attachment);
|
||||||
|
|
||||||
|
if (attachmentDescriptors.size() > attachmentGetters.size())
|
||||||
|
attachmentGetters.resize(attachmentDescriptors.size());
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::updateAttachmentBacking(int idx,
|
||||||
|
std::function<const Image &(int)> &&getBacking) {
|
||||||
|
const Image& img = getBacking(idx);
|
||||||
|
attachmentDescriptors[idx].format = img.getFormat();
|
||||||
|
attachmentDescriptors[idx].samples = img.getSamples();
|
||||||
|
attachmentGetters.at(idx) = std::move(getBacking);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::setSubpass(int idx, std::vector<VkAttachmentReference> &&color,
|
||||||
|
std::vector<VkAttachmentReference> &&multisample,
|
||||||
|
VkAttachmentReference &depthStencil) {
|
||||||
|
if (multisample.empty())
|
||||||
|
if (multisample.size() != color.size())
|
||||||
|
throw std::runtime_error("Constructing a subpass with mismatched color and multisample attachments");
|
||||||
|
|
||||||
|
SubpassAttachments attachments {
|
||||||
|
std::move(color), std::move(multisample), depthStencil
|
||||||
|
};
|
||||||
|
|
||||||
|
subpassAttachments.emplace_back(attachments);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent RenderPassBuilder::addDependency(const SubpassDependency &dep) {
|
||||||
|
subpassDependencies.emplace_back(buildSubpassDependency(dep));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RenderPass> RenderPassBuilder::build() const {
|
||||||
|
if (framebufferCount == 0)
|
||||||
|
throw std::runtime_error("No framebuffers in render pass");
|
||||||
|
for (int i = 0; i < attachmentGetters.size(); i++)
|
||||||
|
if (attachmentGetters[i] == nullptr)
|
||||||
|
throw std::runtime_error("Image " + std::to_string(i) + " is not set in render pass");
|
||||||
|
|
||||||
|
const auto descriptors = buildSubpassDescriptors(subpassAttachments);
|
||||||
|
const VkRenderPassCreateInfo createInfo {
|
||||||
|
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||||
|
nullptr,
|
||||||
|
{},
|
||||||
|
static_cast<uint32_t>(attachmentDescriptors.size()),
|
||||||
|
attachmentDescriptors.data(),
|
||||||
|
static_cast<uint32_t>(descriptors.size()),
|
||||||
|
descriptors.data(),
|
||||||
|
static_cast<uint32_t>(subpassDependencies.size()),
|
||||||
|
subpassDependencies.data()
|
||||||
|
};
|
||||||
|
|
||||||
|
VkRenderPass pass;
|
||||||
|
if (vkCreateRenderPass(VulkanModule::getInstance()->getDevice()->logical, &createInfo, nullptr, &pass) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create render pass");
|
||||||
|
|
||||||
|
const auto framebufferSize = attachmentGetters[0](0).getExtent();
|
||||||
|
|
||||||
|
return std::make_unique<RenderPass> (
|
||||||
|
static_cast<int>(descriptors.size()), pass, clearValues, framebufferSize, createFramebuffers(pass, attachmentGetters, framebufferCount.value(), framebufferSize),
|
||||||
|
countColorAttachments(subpassAttachments)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderPass::execute(const VkCommandBuffer &commands, int imageIndex,
|
||||||
|
std::vector<std::function<void(const VkCommandBuffer &)>> ops) const {
|
||||||
|
const VkRenderPassBeginInfo begin {
|
||||||
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||||
|
nullptr,
|
||||||
|
renderPass,
|
||||||
|
framebuffers[imageIndex],
|
||||||
|
{
|
||||||
|
{0, 0},
|
||||||
|
extent
|
||||||
|
},
|
||||||
|
static_cast<uint32_t>(clearValues.size()),
|
||||||
|
clearValues.data()
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdBeginRenderPass(commands, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
|
for (int i = 0; i < ops.size(); i++) {
|
||||||
|
if (i != 0)
|
||||||
|
vkCmdNextSubpass(commands, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
ops[i](commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkCmdEndRenderPass(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPass::~RenderPass() {
|
||||||
|
for (const auto& fb : framebuffers)
|
||||||
|
vkDestroyFramebuffer(VulkanModule::getInstance()->getDevice()->logical, fb, nullptr);
|
||||||
|
vkDestroyRenderPass(VulkanModule::getInstance()->getDevice()->logical, renderPass, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
#include "vlkx/render/render_pass/ScreenRenderPass.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
#include "vlkx/render/render_pass/GPUPass.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
void checkSubpass(int pass, int high) { if (pass < 1 || pass > high) throw std::runtime_error("Subpass index too high"); }
|
||||||
|
|
||||||
|
void addAttachment(const AttachmentConfig& config, GraphicsPass& pass, MultiImageTracker& tracker, GraphicsPass::LocationGetter&& getter, const std::function<void(UsageTracker&)>& populateHistory) {
|
||||||
|
const std::string& name = config.name;
|
||||||
|
if (!tracker.isTracking(name))
|
||||||
|
throw std::runtime_error("Attempting to add image " + name + " that is not tracked");
|
||||||
|
|
||||||
|
UsageTracker history { tracker.get(name) };
|
||||||
|
populateHistory(history);
|
||||||
|
|
||||||
|
if (config.finalUsage.has_value())
|
||||||
|
history.setFinal(config.finalUsage.value());
|
||||||
|
|
||||||
|
config.index = pass.add(name, std::move(history), std::move(getter), config.loadStoreOps);
|
||||||
|
pass.update(name, tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
RendererConfig::RendererConfig(int passCount, std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen, std::optional<int> firstTransparent, std::optional<int> firstOverlay) : renderImages(destinations) {
|
||||||
|
if (passCount < 1)
|
||||||
|
throw std::runtime_error("Creating a RendererConfig with less than 1 subpass.");
|
||||||
|
|
||||||
|
if (firstTransparent.has_value())
|
||||||
|
checkSubpass(firstTransparent.value(), passCount);
|
||||||
|
if (firstOverlay.has_value())
|
||||||
|
checkSubpass(firstOverlay.value(), passCount);
|
||||||
|
|
||||||
|
if (firstOverlay.has_value())
|
||||||
|
numOverlayPasses = passCount - firstOverlay.value();
|
||||||
|
if (firstTransparent.has_value()) {
|
||||||
|
numOpaquePasses = firstTransparent.value();
|
||||||
|
numTransparentPasses = passCount - numOpaquePasses - numOverlayPasses;
|
||||||
|
} else {
|
||||||
|
numOpaquePasses = passCount - numOverlayPasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
rendersToScreen = toScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RenderPassBuilder> SimpleRenderPass::createBuilder(int framebuffers,
|
||||||
|
const vlkx::RendererConfig &config,
|
||||||
|
const vlkx::AttachmentConfig &color,
|
||||||
|
const vlkx::AttachmentConfig *multisample,
|
||||||
|
const vlkx::AttachmentConfig *depthStencil,
|
||||||
|
vlkx::MultiImageTracker &tracker) {
|
||||||
|
const int passes = config.passes();
|
||||||
|
const int firstPass = 0;
|
||||||
|
const int lastPass = passes - 1;
|
||||||
|
|
||||||
|
const auto getLocation = [](int pass) { return 0; };
|
||||||
|
bool usesMultisample = multisample != nullptr;
|
||||||
|
bool usesDepth = depthStencil != nullptr;
|
||||||
|
|
||||||
|
const bool passesUsingDepth = config.depthPasses() > 0;
|
||||||
|
|
||||||
|
if (usesDepth) {
|
||||||
|
if (passesUsingDepth == 0)
|
||||||
|
throw std::runtime_error("Depth stencil defined, but never used.");
|
||||||
|
} else
|
||||||
|
if (passesUsingDepth > 0)
|
||||||
|
throw std::runtime_error("Depth stencil used, but never defined.");
|
||||||
|
|
||||||
|
GraphicsPass graphicsPass(passes);
|
||||||
|
|
||||||
|
addAttachment(color, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
|
||||||
|
if (usesMultisample)
|
||||||
|
history.add(lastPass, ImageUsage::multisample());
|
||||||
|
else
|
||||||
|
history.add(firstPass, lastPass, ImageUsage::renderTarget(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (usesMultisample) {
|
||||||
|
addAttachment(*multisample, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
|
||||||
|
history.add(firstPass, lastPass, ImageUsage::renderTarget(0));
|
||||||
|
});
|
||||||
|
graphicsPass.addMultisample(multisample->name, color.name, lastPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usesDepth)
|
||||||
|
addAttachment(*depthStencil, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
|
||||||
|
if (config.numOpaquePasses > 0) {
|
||||||
|
const int lastOpaque = config.numOpaquePasses - 1;
|
||||||
|
history.add(firstPass, lastOpaque, ImageUsage::depthStencil(ImageUsage::Access::ReadWrite));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.numTransparentPasses > 0) {
|
||||||
|
const int firstTransparent = config.numOpaquePasses;
|
||||||
|
const int lastTransparent = firstTransparent + config.numTransparentPasses - 1;
|
||||||
|
history.add(firstTransparent, lastTransparent, ImageUsage::depthStencil(ImageUsage::Access::ReadOnly));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return graphicsPass.build(framebuffers);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenRenderPassManager::initializeRenderPass() {
|
||||||
|
if (config.usesDepth()) {
|
||||||
|
depthStencilImage = MultisampleImage::createDepthStencil(VulkanModule::getInstance()->getSwapchain()->extent, std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passBuilder == nullptr)
|
||||||
|
preparePassBuilder();
|
||||||
|
|
||||||
|
passBuilder->updateAttachmentBacking(destinationInfo.index.value(), [this](int index) -> const Image& {
|
||||||
|
return *config.renderImages[index];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (depthStencilImage != nullptr)
|
||||||
|
passBuilder->updateAttachmentBacking(depthStencilInfo.index.value(), [this](int index) -> const Image& {
|
||||||
|
return *depthStencilImage;
|
||||||
|
});
|
||||||
|
|
||||||
|
pass = passBuilder->build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenRenderPassManager::preparePassBuilder() {
|
||||||
|
const bool usesDepth = depthStencilImage != nullptr;
|
||||||
|
const bool usesMultisampling = false;
|
||||||
|
|
||||||
|
MultiImageTracker tracker;
|
||||||
|
destinationInfo.add(tracker, *config.renderImages[0]);
|
||||||
|
if (usesDepth)
|
||||||
|
depthStencilInfo.add(tracker, *depthStencilImage);
|
||||||
|
if (usesMultisampling)
|
||||||
|
// TODO
|
||||||
|
(void) 0;
|
||||||
|
|
||||||
|
auto colorConfig = destinationInfo.makeConfig();
|
||||||
|
if (config.rendersToScreen)
|
||||||
|
colorConfig.setUsage(ImageUsage::presentation());
|
||||||
|
else
|
||||||
|
colorConfig.setUsage(ImageUsage::sampledFragment());
|
||||||
|
const auto multisampleConfig = multisampleInfo.makeConfig();
|
||||||
|
const auto depthConfig = depthStencilInfo.makeConfig();
|
||||||
|
|
||||||
|
passBuilder = SimpleRenderPass::createBuilder(config.renderImages.size(), config, colorConfig, usesMultisampling ? &multisampleConfig : nullptr, usesDepth ? &depthConfig : nullptr, tracker);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,137 +0,0 @@
|
||||||
#include <vlkx/render/shader/Descriptor.h>
|
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
|
||||||
#include <vlkx/render/Geometry.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
Descriptor::Descriptor() {}
|
|
||||||
Descriptor::~Descriptor() {}
|
|
||||||
|
|
||||||
void Descriptor::createAndAllocateSimple(uint32_t images) {
|
|
||||||
createSimpleLayout();
|
|
||||||
createSimplePool(images);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Descriptor::createSimpleLayout() {
|
|
||||||
// Set up a binding
|
|
||||||
VkDescriptorSetLayoutBinding binding = {};
|
|
||||||
binding.binding = 0;
|
|
||||||
binding.descriptorCount = 1;
|
|
||||||
binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
||||||
binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
|
||||||
|
|
||||||
// Prepare to create the layout
|
|
||||||
std::array<VkDescriptorSetLayoutBinding, 1> bindings = {
|
|
||||||
binding
|
|
||||||
};
|
|
||||||
|
|
||||||
VkDescriptorSetLayoutCreateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
||||||
info.bindingCount = static_cast<uint32_t>(bindings.size());
|
|
||||||
info.pBindings = bindings.data();
|
|
||||||
|
|
||||||
// Create the layout
|
|
||||||
if (vkCreateDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create Descriptor layout");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Descriptor::createSimplePool(uint32_t images) {
|
|
||||||
// Set the size of the pool we want
|
|
||||||
VkDescriptorPoolSize size = {};
|
|
||||||
size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
||||||
size.descriptorCount = images;
|
|
||||||
|
|
||||||
std::array<VkDescriptorPoolSize, 1> sizes = {
|
|
||||||
size
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prepare to create the pool
|
|
||||||
VkDescriptorPoolCreateInfo createInfo = {};
|
|
||||||
createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
||||||
createInfo.poolSizeCount = static_cast<uint32_t>(sizes.size());
|
|
||||||
createInfo.pPoolSizes = sizes.data();
|
|
||||||
createInfo.maxSets = images;
|
|
||||||
|
|
||||||
// Create the pool
|
|
||||||
if (vkCreateDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pool) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create Descriptor pool");
|
|
||||||
|
|
||||||
// Prepare to allocate the set
|
|
||||||
std::vector<VkDescriptorSetLayout> layouts(images, layout);
|
|
||||||
VkDescriptorSetAllocateInfo allocateInfo = {};
|
|
||||||
allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
||||||
allocateInfo.descriptorPool = pool;
|
|
||||||
allocateInfo.descriptorSetCount = images;
|
|
||||||
allocateInfo.pSetLayouts = layouts.data();
|
|
||||||
|
|
||||||
// Allocate the Set
|
|
||||||
if (vkAllocateDescriptorSets(VulkanManager::getInstance()->getDevice()->logical, &allocateInfo, &set) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to allocate Descriptor set");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Descriptor::populate(uint32_t images, VkBuffer uniforms, size_t bufferSize) {
|
|
||||||
// Iterate images
|
|
||||||
for (size_t i = 0; i < images; i++) {
|
|
||||||
// Set up the uniform buffer
|
|
||||||
VkDescriptorBufferInfo buffer = {};
|
|
||||||
buffer.buffer = uniforms;
|
|
||||||
buffer.offset = 0;
|
|
||||||
buffer.range = bufferSize;
|
|
||||||
|
|
||||||
// Prepare to write the buffer into the Descriptor set
|
|
||||||
VkWriteDescriptorSet write = {};
|
|
||||||
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
||||||
write.pNext = nullptr;
|
|
||||||
write.dstSet = set;
|
|
||||||
write.dstBinding = 0;
|
|
||||||
write.dstArrayElement = 0;
|
|
||||||
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
||||||
write.descriptorCount = 1;
|
|
||||||
write.pBufferInfo = &buffer;
|
|
||||||
write.pImageInfo = nullptr;
|
|
||||||
write.pTexelBufferView = nullptr;
|
|
||||||
|
|
||||||
std::array<VkWriteDescriptorSet, 1> writes = {
|
|
||||||
write
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write the buffer into the descriptor
|
|
||||||
vkUpdateDescriptorSets(VulkanManager::getInstance()->getDevice()->logical, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Descriptor::destroy() {
|
|
||||||
vkDestroyDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, pool, nullptr);
|
|
||||||
vkDestroyDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, layout, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
VkDescriptorSetLayout createBindingWrapper(std::vector<VkDescriptorSetLayoutBinding> bindings) {
|
|
||||||
VkDescriptorSetLayoutCreateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
||||||
info.bindingCount = static_cast<uint32_t>(bindings.size());
|
|
||||||
info.pBindings = bindings.data();
|
|
||||||
|
|
||||||
VkDescriptorSetLayout layout;
|
|
||||||
|
|
||||||
// Create the layout
|
|
||||||
if (vkCreateDescriptorSetLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create Descriptor layout");
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
VkDescriptorPool createPoolWrapper(std::vector<VkDescriptorPoolSize> sizes, uint32_t images) {
|
|
||||||
// Prepare to create the pool
|
|
||||||
VkDescriptorPoolCreateInfo createInfo = {};
|
|
||||||
createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
||||||
createInfo.poolSizeCount = static_cast<uint32_t>(sizes.size());
|
|
||||||
createInfo.pPoolSizes = sizes.data();
|
|
||||||
createInfo.maxSets = images;
|
|
||||||
|
|
||||||
VkDescriptorPool pool;
|
|
||||||
|
|
||||||
// Create the pool
|
|
||||||
if (vkCreateDescriptorPool(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &pool) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create Descriptor pool");
|
|
||||||
|
|
||||||
return pool;
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
#include <vlkx/render/shader/GeoBuffers.h>
|
|
||||||
#include <vlkx/vulkan/Tools.h>
|
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
|
||||||
|
|
||||||
GeoBuffers::GeoBuffers() {}
|
|
||||||
GeoBuffers::~GeoBuffers() {}
|
|
||||||
|
|
||||||
void GeoBuffers::createBuffers(Geo::MeshType type) {
|
|
||||||
// Set vertex and indice data
|
|
||||||
switch (type) {
|
|
||||||
case Geo::MeshType::Triangle:
|
|
||||||
Geo::Mesh::setTriData(vertices, indices);
|
|
||||||
break;
|
|
||||||
case Geo::MeshType::Quad:
|
|
||||||
Geo::Mesh::setQuadData(vertices, indices);
|
|
||||||
break;
|
|
||||||
case Geo::MeshType::Cube:
|
|
||||||
Geo::Mesh::setCubeData(vertices, indices);
|
|
||||||
break;
|
|
||||||
case Geo::MeshType::Sphere:
|
|
||||||
Geo::Mesh::setSphereData(vertices, indices);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create buffers
|
|
||||||
createVertexBuffer();
|
|
||||||
createIndexBuffer();
|
|
||||||
createUniformBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeoBuffers::createVertexBuffer() {
|
|
||||||
// Gather the size of the buffer we need..
|
|
||||||
VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();
|
|
||||||
|
|
||||||
// Create and bind a temporary buffer
|
|
||||||
VkTools::ManagedBuffer staging = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
|
|
||||||
|
|
||||||
// Get ready to write into our GPU buffer
|
|
||||||
void* data;
|
|
||||||
vmaMapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation, &data);
|
|
||||||
|
|
||||||
// Copy vertex data into the buffer
|
|
||||||
memcpy(data, vertices.data(), (size_t)bufferSize);
|
|
||||||
|
|
||||||
// Unmap the buffer from stack space
|
|
||||||
vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation);
|
|
||||||
|
|
||||||
// Prepare an area in the GPU that we cannot see, that the vertex data can live permanently.
|
|
||||||
vertexBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical, false);
|
|
||||||
|
|
||||||
// Copy our data from the staging buffer into the new permanent buffer
|
|
||||||
VkTools::copyGPUBuffer(staging.buffer, vertexBuffer.buffer, bufferSize, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->graphicsQueue, VulkanManager::getInstance()->getDevice()->queueData.graphics);
|
|
||||||
|
|
||||||
// Cleanup the staging area.
|
|
||||||
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), staging.buffer, staging.allocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeoBuffers::createIndexBuffer() {
|
|
||||||
// Gather the size of the buffer we need..
|
|
||||||
VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();
|
|
||||||
|
|
||||||
// Create and bind a temporary buffer
|
|
||||||
VkTools::ManagedBuffer staging = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
|
|
||||||
|
|
||||||
// Get ready to write into our GPU buffer
|
|
||||||
void* data;
|
|
||||||
vmaMapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation, &data);
|
|
||||||
|
|
||||||
// Copy index data into the buffer
|
|
||||||
memcpy(data, indices.data(), (size_t)bufferSize);
|
|
||||||
|
|
||||||
// Unmap the buffer from stack space
|
|
||||||
vmaUnmapMemory(VulkanManager::getInstance()->getAllocator(), staging.allocation);
|
|
||||||
|
|
||||||
// Prepare an area in the GPU that we cannot see, that the vertex data can live permanently.
|
|
||||||
indexBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
|
|
||||||
|
|
||||||
// Copy our data from the staging buffer into the new permanent buffer
|
|
||||||
VkTools::copyGPUBuffer(staging.buffer, indexBuffer.buffer, bufferSize, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->graphicsQueue, VulkanManager::getInstance()->getDevice()->queueData.graphics);
|
|
||||||
|
|
||||||
// Cleanup the staging area.
|
|
||||||
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), staging.buffer, staging.allocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeoBuffers::createUniformBuffer() {
|
|
||||||
// Allocate a buffer of the right size - we don't need to copy uniforms.
|
|
||||||
VkDeviceSize bufferSize = sizeof(Geo::UniformBufferObject);
|
|
||||||
|
|
||||||
uniformBuffer = VkTools::createGPUBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanManager::getInstance()->getDevice()->logical, VulkanManager::getInstance()->getDevice()->physical);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeoBuffers::destroy() {
|
|
||||||
// Cleanup uniform buffers
|
|
||||||
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), uniformBuffer.buffer, uniformBuffer.allocation);
|
|
||||||
|
|
||||||
// Cleanup index buffers
|
|
||||||
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), indexBuffer.buffer, indexBuffer.allocation);
|
|
||||||
|
|
||||||
// Cleanup vertex buffers
|
|
||||||
vmaDestroyBuffer(VulkanManager::getInstance()->getAllocator(), vertexBuffer.buffer, vertexBuffer.allocation);
|
|
||||||
}
|
|
|
@ -1,200 +0,0 @@
|
||||||
#include <vlkx/render/shader/Pipeline.h>
|
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
|
||||||
|
|
||||||
#include <vlkx/render/Geometry.h>
|
|
||||||
#include <string>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
Pipeline::Pipeline() {}
|
|
||||||
Pipeline::~Pipeline() {}
|
|
||||||
|
|
||||||
void Pipeline::create(VkExtent2D extent, VkDescriptorSetLayout set, VkRenderPass renderPass) {
|
|
||||||
createPipelineLayout(set);
|
|
||||||
createVertexPipeline(extent, renderPass);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Pipeline::createPipelineLayout(VkDescriptorSetLayout set) {
|
|
||||||
// Prepare to create the pipeline layout
|
|
||||||
VkPipelineLayoutCreateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
||||||
info.setLayoutCount = 1;
|
|
||||||
info.pSetLayouts = &set;
|
|
||||||
|
|
||||||
// Create the layout
|
|
||||||
if (vkCreatePipelineLayout(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &layout) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create Pipeline Layout.");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<char> Pipeline::readFile(const std::string& filename) {
|
|
||||||
// Prepare the stream for reading the file
|
|
||||||
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
|
||||||
|
|
||||||
if (!file.is_open())
|
|
||||||
throw std::runtime_error(std::string("Unable to open shader file: ").append(filename));
|
|
||||||
|
|
||||||
size_t fileSize = (size_t)file.tellg();
|
|
||||||
|
|
||||||
// Read the file into a vector
|
|
||||||
std::vector<char> buffer(fileSize);
|
|
||||||
file.seekg(0);
|
|
||||||
file.read(buffer.data(), fileSize);
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkShaderModule Pipeline::createShaderModule(const std::vector<char>& code) {
|
|
||||||
// Prepare to create the shader module
|
|
||||||
VkShaderModuleCreateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
||||||
info.codeSize = code.size();
|
|
||||||
info.pCode = reinterpret_cast<const uint32_t*>(code.data());
|
|
||||||
|
|
||||||
// Create the module
|
|
||||||
VkShaderModule shaderModule;
|
|
||||||
if (vkCreateShaderModule(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &shaderModule) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create Shader module from SPIR-V binary");
|
|
||||||
|
|
||||||
return shaderModule;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Pipeline::createVertexPipeline(VkExtent2D extent, VkRenderPass renderPass) {
|
|
||||||
|
|
||||||
// ------- Stage: Shader ------- //
|
|
||||||
|
|
||||||
// Read the vertex shader
|
|
||||||
auto vertexShaderCode = readFile("vlkx-resources/shader/SPIRV/basic.vert.spv");
|
|
||||||
VkShaderModule vertexShaderModule = createShaderModule(vertexShaderCode);
|
|
||||||
|
|
||||||
// Prepare to create the Shader Stage
|
|
||||||
VkPipelineShaderStageCreateInfo vertexShaderCreateInfo = {};
|
|
||||||
vertexShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
||||||
vertexShaderCreateInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
|
|
||||||
vertexShaderCreateInfo.module = vertexShaderModule;
|
|
||||||
vertexShaderCreateInfo.pName = "main";
|
|
||||||
|
|
||||||
|
|
||||||
// Read the fragment shader
|
|
||||||
auto fragmentShaderCode = readFile("vlkx-resources/shader/SPIRV/basic.frag.spv");
|
|
||||||
VkShaderModule fragmentShaderModule = createShaderModule(fragmentShaderCode);
|
|
||||||
|
|
||||||
// Prepare to create the Shader Stage
|
|
||||||
VkPipelineShaderStageCreateInfo fragmentShaderCreateInfo = {};
|
|
||||||
fragmentShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
||||||
fragmentShaderCreateInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
||||||
fragmentShaderCreateInfo.module = fragmentShaderModule;
|
|
||||||
fragmentShaderCreateInfo.pName = "main";
|
|
||||||
|
|
||||||
VkPipelineShaderStageCreateInfo shaderStages[] = {
|
|
||||||
vertexShaderCreateInfo,
|
|
||||||
fragmentShaderCreateInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------- Stage: Shader Input ------- //
|
|
||||||
|
|
||||||
auto bindingDescription = Geo::Vertex::getBindingDesc();
|
|
||||||
auto attributeDescription = Geo::Vertex::getAttributeDesc();
|
|
||||||
|
|
||||||
VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo = {};
|
|
||||||
vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
|
||||||
vertexInputCreateInfo.vertexBindingDescriptionCount = 1;
|
|
||||||
vertexInputCreateInfo.pVertexBindingDescriptions = &bindingDescription;
|
|
||||||
vertexInputCreateInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescription.size());
|
|
||||||
vertexInputCreateInfo.pVertexAttributeDescriptions = attributeDescription.data();
|
|
||||||
|
|
||||||
// ------- Stage: Input Assembly ------- //
|
|
||||||
|
|
||||||
VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo = {};
|
|
||||||
inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
|
||||||
inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
||||||
inputAssemblyCreateInfo.primitiveRestartEnable = VK_FALSE;
|
|
||||||
|
|
||||||
// ------- Stage: Rasterization ------- //
|
|
||||||
|
|
||||||
VkPipelineRasterizationStateCreateInfo rasterCreateInfo = {};
|
|
||||||
rasterCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
|
||||||
rasterCreateInfo.depthClampEnable = VK_FALSE;
|
|
||||||
rasterCreateInfo.rasterizerDiscardEnable = VK_FALSE;
|
|
||||||
rasterCreateInfo.polygonMode = VK_POLYGON_MODE_FILL;
|
|
||||||
rasterCreateInfo.lineWidth = 1.0f;
|
|
||||||
rasterCreateInfo.cullMode = VK_CULL_MODE_BACK_BIT;
|
|
||||||
rasterCreateInfo.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
|
||||||
rasterCreateInfo.depthBiasEnable = VK_FALSE;
|
|
||||||
rasterCreateInfo.depthBiasConstantFactor = 0.0f;
|
|
||||||
rasterCreateInfo.depthBiasClamp = 0.0f;
|
|
||||||
rasterCreateInfo.depthBiasSlopeFactor = 0.0f;
|
|
||||||
|
|
||||||
// ------- Stage: MultiSample ------- //
|
|
||||||
|
|
||||||
VkPipelineMultisampleStateCreateInfo multisampleCreateInfo = {};
|
|
||||||
multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
|
||||||
multisampleCreateInfo.sampleShadingEnable = VK_FALSE;
|
|
||||||
multisampleCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
|
|
||||||
// ------- Stage: Color Blending ------- //
|
|
||||||
|
|
||||||
VkPipelineColorBlendAttachmentState colorBlendAttach = {};
|
|
||||||
colorBlendAttach.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
||||||
colorBlendAttach.blendEnable = VK_FALSE;
|
|
||||||
|
|
||||||
VkPipelineColorBlendStateCreateInfo colorBlendCreateInfo = {};
|
|
||||||
colorBlendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
|
||||||
colorBlendCreateInfo.attachmentCount = 1;
|
|
||||||
colorBlendCreateInfo.pAttachments = &colorBlendAttach;
|
|
||||||
|
|
||||||
// ------- Stage: Viewport ------- //
|
|
||||||
|
|
||||||
VkViewport viewport = {};
|
|
||||||
viewport.x = 0;
|
|
||||||
viewport.y = 0;
|
|
||||||
viewport.width = (float)extent.width;
|
|
||||||
viewport.height = (float)extent.height;
|
|
||||||
viewport.minDepth = 0.0f;
|
|
||||||
viewport.maxDepth = 1.0f;
|
|
||||||
|
|
||||||
VkRect2D scissor = {};
|
|
||||||
scissor.offset = { 0, 0 };
|
|
||||||
scissor.extent = extent;
|
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo viewportCreateInfo = {};
|
|
||||||
viewportCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
|
||||||
viewportCreateInfo.viewportCount = 1;
|
|
||||||
viewportCreateInfo.pViewports = &viewport;
|
|
||||||
viewportCreateInfo.scissorCount = 1;
|
|
||||||
viewportCreateInfo.pScissors = &scissor;
|
|
||||||
|
|
||||||
// ------- Create the Pipeline ------- //
|
|
||||||
|
|
||||||
VkGraphicsPipelineCreateInfo pipelineInfo = {};
|
|
||||||
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
|
||||||
pipelineInfo.stageCount = 2;
|
|
||||||
pipelineInfo.pStages = shaderStages;
|
|
||||||
|
|
||||||
pipelineInfo.pVertexInputState = &vertexInputCreateInfo;
|
|
||||||
pipelineInfo.pInputAssemblyState = &inputAssemblyCreateInfo;
|
|
||||||
pipelineInfo.pRasterizationState = &rasterCreateInfo;
|
|
||||||
pipelineInfo.pMultisampleState = &multisampleCreateInfo;
|
|
||||||
pipelineInfo.pDepthStencilState = nullptr;
|
|
||||||
pipelineInfo.pColorBlendState = &colorBlendCreateInfo;
|
|
||||||
pipelineInfo.pDynamicState = nullptr;
|
|
||||||
pipelineInfo.pViewportState = &viewportCreateInfo;
|
|
||||||
|
|
||||||
pipelineInfo.layout = layout;
|
|
||||||
pipelineInfo.renderPass = renderPass;
|
|
||||||
pipelineInfo.subpass = 0;
|
|
||||||
|
|
||||||
if (vkCreateGraphicsPipelines(VulkanManager::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create a Vertex rendering Pipeline.");
|
|
||||||
|
|
||||||
|
|
||||||
// ------- Cleanup temporary allocations ------- //
|
|
||||||
|
|
||||||
vkDestroyShaderModule(VulkanManager::getInstance()->getDevice()->logical, vertexShaderModule, nullptr);
|
|
||||||
vkDestroyShaderModule(VulkanManager::getInstance()->getDevice()->logical, fragmentShaderModule, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Pipeline::destroy() {
|
|
||||||
vkDestroyPipeline(VulkanManager::getInstance()->getDevice()->logical, pipeline, nullptr);
|
|
||||||
vkDestroyPipelineLayout(VulkanManager::getInstance()->getDevice()->logical, layout, nullptr);
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
#include <vlkx/render/texture/RenderTexture.h>
|
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
|
||||||
#include <vlkx/vulkan/Tools.h>
|
|
||||||
|
|
||||||
SingleRenderTexture::SingleRenderTexture() {}
|
|
||||||
SingleRenderTexture::~SingleRenderTexture() {}
|
|
||||||
|
|
||||||
void SingleRenderTexture::createViewsAndFramebuffer(std::vector<VkImage> images, VkFormat format, VkExtent2D extent, VkRenderPass pass) {
|
|
||||||
// Initialize members
|
|
||||||
this->swapChainImages = images;
|
|
||||||
this->swapChainImageExtent = extent;
|
|
||||||
|
|
||||||
createViews(format);
|
|
||||||
createFramebuffer(extent, pass);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleRenderTexture::createViews(VkFormat format) {
|
|
||||||
// Prepare maximum size
|
|
||||||
swapChainImageViews.resize(swapChainImages.size());
|
|
||||||
|
|
||||||
// Iterate images and create views for each.
|
|
||||||
for (size_t i = 0; i < swapChainImages.size(); i++) {
|
|
||||||
swapChainImageViews[i] = VkTools::createImageView(swapChainImages[i], format, VK_IMAGE_ASPECT_COLOR_BIT, VulkanManager::getInstance()->getDevice()->logical);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleRenderTexture::createFramebuffer(VkExtent2D extent, VkRenderPass pass) {
|
|
||||||
// Prepare maximum size
|
|
||||||
swapChainFramebuffers.resize(swapChainImageViews.size());
|
|
||||||
|
|
||||||
// Iterate views and create a framebuffer for each.
|
|
||||||
for (size_t i = 0; i < swapChainImageViews.size(); i++) {
|
|
||||||
// Create an array with the image view as an attachment.
|
|
||||||
std::array<VkImageView, 1> attachments = {
|
|
||||||
swapChainImageViews[i]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prepare the creation of a new framebuffer.
|
|
||||||
// One attachment, width and height from the extent, and one layer (one viewport)
|
|
||||||
VkFramebufferCreateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
||||||
info.renderPass = pass;
|
|
||||||
info.attachmentCount = static_cast<uint32_t>(attachments.size());
|
|
||||||
info.pAttachments = attachments.data();
|
|
||||||
info.width = extent.width;
|
|
||||||
info.height = extent.height;
|
|
||||||
info.layers = 1;
|
|
||||||
|
|
||||||
// Create the framebuffer
|
|
||||||
if (vkCreateFramebuffer(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create framebuffer for a texture.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleRenderTexture::destroy() {
|
|
||||||
// Destroy Image Views first
|
|
||||||
for (auto imageView : swapChainImageViews) {
|
|
||||||
vkDestroyImageView(VulkanManager::getInstance()->getDevice()->logical, imageView, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Framebuffers
|
|
||||||
for (auto framebuffer : swapChainFramebuffers) {
|
|
||||||
vkDestroyFramebuffer(VulkanManager::getInstance()->getDevice()->logical, framebuffer, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,238 @@
|
||||||
|
#include "temp/model/Builder.h"
|
||||||
|
|
||||||
|
namespace vlkxtemp {
|
||||||
|
using namespace vlkx;
|
||||||
|
using Geo::VertexAll;
|
||||||
|
using VertexData = PerVertexBuffer::NoShareMeta;
|
||||||
|
|
||||||
|
std::unique_ptr<SamplableImage> createTex(const ModelBuilder::TextureSource& source) {
|
||||||
|
const auto usages = { ImageUsage::sampledFragment() };
|
||||||
|
return std::make_unique<RefCountedTexture>(source, usages, ImageSampler::Config {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillTextureMeta(const ModelBuilder::BindingPoints& binding, const ModelBuilder::TexturePerMesh& textures, const ModelBuilder::TexturePerMesh& sharedTextures, Descriptor::Meta* meta, Descriptor::ImageInfos* infos) {
|
||||||
|
*meta = {Image::getSampleType(), VK_SHADER_STAGE_FRAGMENT_BIT, {} };
|
||||||
|
auto& texBindings = meta->bindings;
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < (size_t) ModelBuilder::TextureType::Count; idx++) {
|
||||||
|
const auto type = (ModelBuilder::TextureType) idx;
|
||||||
|
const size_t numTextures = textures[idx].size() + sharedTextures[idx].size();
|
||||||
|
if (numTextures != 0) {
|
||||||
|
const auto iter = binding.find(type);
|
||||||
|
if (iter == binding.end())
|
||||||
|
throw std::runtime_error("Binding point of texture type " + std::to_string(idx) + " is not set");
|
||||||
|
|
||||||
|
texBindings.push_back({ iter->second, static_cast<uint32_t>(numTextures) });
|
||||||
|
|
||||||
|
auto& metaMap = (*infos)[iter->second];
|
||||||
|
metaMap.reserve(numTextures);
|
||||||
|
for (const auto& texture : textures[idx])
|
||||||
|
metaMap.push_back(texture->getInfoForSampling());
|
||||||
|
for (const auto& texture : sharedTextures[idx])
|
||||||
|
metaMap.push_back(texture->getInfoForSampling());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkPushConstantRange> createRanges(const ModelBuilder::ModelPushConstant& constants) {
|
||||||
|
std::vector<VkPushConstantRange> ranges;
|
||||||
|
ranges.reserve(constants.constants.size());
|
||||||
|
for (const auto& meta : constants.constants)
|
||||||
|
ranges.push_back( { constants.stage, meta.offset, meta.constants->getSize() });
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkVertexInputBindingDescription getBinding(uint32_t stride, bool instancing) {
|
||||||
|
return VkVertexInputBindingDescription{ 0, stride,instancing ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX, };
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVertexInput(const PerVertexBuffer& buffer, const std::vector<PerInstanceVertexBuffer*>& instanceBuffers, GraphicsPipelineBuilder* builder) {
|
||||||
|
uint32_t start = 0;
|
||||||
|
auto attributes = buffer.getAttrs(start);
|
||||||
|
start += attributes.size();
|
||||||
|
|
||||||
|
builder->addVertex(0, Geo::VertexAll::getBindingDesc(), std::move(attributes));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < instanceBuffers.size(); i++) {
|
||||||
|
if (instanceBuffers[i] == nullptr)
|
||||||
|
throw std::runtime_error("PerInstanceVertexBuffer not specified");
|
||||||
|
auto instanceAttrs = instanceBuffers[i]->getAttrs(start);
|
||||||
|
start += instanceAttrs.size();
|
||||||
|
|
||||||
|
auto instanceBinding = getBinding(instanceBuffers[i]->getSize(), true);
|
||||||
|
builder->addVertex(i + 1, std::move(instanceBinding), std::move(instanceAttrs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelBuilder::SingleMeshModel::load(ModelBuilder* builder) const {
|
||||||
|
const Wavefront obj(objFile, objIndexBase);
|
||||||
|
VertexData vertices {
|
||||||
|
{{
|
||||||
|
PerVertexBuffer::VertexDataMeta { obj.indices },
|
||||||
|
PerVertexBuffer::VertexDataMeta { obj.vertices }
|
||||||
|
}}
|
||||||
|
};
|
||||||
|
|
||||||
|
builder->vertexBuffer = std::make_unique<StaticPerVertexBuffer>(std::move(vertices), Geo::VertexAll::getAttributeDesc());
|
||||||
|
|
||||||
|
auto& meshTexs = builder->textures;
|
||||||
|
meshTexs.push_back({});
|
||||||
|
for (const auto& pair : textureSources) {
|
||||||
|
const auto type = (size_t)pair.first;
|
||||||
|
const auto& sources = pair.second;
|
||||||
|
|
||||||
|
meshTexs.back()[type].reserve(sources.size());
|
||||||
|
for (const auto& source : sources)
|
||||||
|
meshTexs.back()[type].push_back({createTex(source)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelBuilder::MultiMeshModel::load(ModelBuilder* builder) const {
|
||||||
|
const ModelLoader loader(models, textures);
|
||||||
|
std::vector<VertexData::PerMesh> data;
|
||||||
|
data.reserve(loader.getMeshes().size());
|
||||||
|
|
||||||
|
for (const auto& mesh : loader.getMeshes())
|
||||||
|
data.push_back({ PerVertexBuffer::VertexDataMeta { mesh.indices }, PerVertexBuffer::VertexDataMeta { mesh.vertices } });
|
||||||
|
|
||||||
|
builder->vertexBuffer = std::make_unique<StaticPerVertexBuffer>(VertexData { std::move(data)}, Geo::VertexAll::getAttributeDesc());
|
||||||
|
|
||||||
|
const auto usages = { ImageUsage::sampledFragment() };
|
||||||
|
auto& meshTexs = builder->textures;
|
||||||
|
meshTexs.reserve(loader.getMeshes().size());
|
||||||
|
for (const auto& mesh : loader.getMeshes()) {
|
||||||
|
meshTexs.push_back({});
|
||||||
|
for (const auto& tex : mesh.textures)
|
||||||
|
meshTexs.back()[(size_t) tex.type].push_back(std::make_unique<RefCountedTexture>(tex.path, usages, ImageSampler::Config {}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder::ModelBuilder(std::string &&name, int frames, float aspect, const ModelResource &resource)
|
||||||
|
: frames(frames), aspectRatio(aspect), uniformBufferMeta(frames), pipelineBuilder(std::make_unique<GraphicsPipelineBuilder>()) {
|
||||||
|
pipelineBuilder->name(std::move(name));
|
||||||
|
resource.load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::texture(TextureType type, const TextureSource &source) {
|
||||||
|
sharedTextures[(size_t) type].push_back(createTex(source));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::bindTextures(TextureType type, uint32_t point) {
|
||||||
|
bindPoints[type] = point;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::instanceBuffer(vlkx::PerInstanceVertexBuffer* buffer) {
|
||||||
|
instanceBuffers.push_back(buffer);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::uniform(VkShaderStageFlags stage, std::vector<vlkx::Descriptor::Meta::Binding> &&bindings) {
|
||||||
|
uniformMeta.push_back({ UniformBuffer::getDescriptorType(), stage, std::move(bindings) });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::uniformBuffer(uint32_t point, const vlkx::UniformBuffer &buffer) {
|
||||||
|
for (size_t frame = 0; frame < frames; frame++) {
|
||||||
|
const int chunk = buffer.isSingle() ? 0 : frame;
|
||||||
|
uniformBufferMeta[frame][point].push_back( buffer.getDescriptorInfo(chunk) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::pushStage(VkShaderStageFlags stage) {
|
||||||
|
if (!pushConstants.has_value())
|
||||||
|
pushConstants.emplace();
|
||||||
|
|
||||||
|
pushConstants->stage = stage;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::pushConstant(const vlkx::PushConstant* constant, uint32_t offset) {
|
||||||
|
if (!pushConstants.has_value())
|
||||||
|
pushConstants.emplace();
|
||||||
|
|
||||||
|
pushConstants.value().constants.push_back( ModelPushConstant::Meta { constant, offset });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelBuilder& ModelBuilder::shader(VkShaderStageFlagBits stage, std::string &&file) {
|
||||||
|
pipelineBuilder->shader(stage, std::move(file));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModelBuilder::Descriptors> ModelBuilder::createDescs() const {
|
||||||
|
std::vector<Descriptors> descs(frames);
|
||||||
|
auto infos = uniformMeta;
|
||||||
|
infos.resize(infos.size() + 1);
|
||||||
|
|
||||||
|
for (size_t frame = 0; frame < frames; frame++) {
|
||||||
|
descs[frame].reserve(textures.size());
|
||||||
|
|
||||||
|
for (const auto& tex : textures) {
|
||||||
|
Descriptor::ImageInfos image;
|
||||||
|
fillTextureMeta(bindPoints, tex, sharedTextures, &infos.back(), &image);
|
||||||
|
|
||||||
|
descs[frame].push_back(std::make_unique<StaticDescriptor>(infos));
|
||||||
|
descs[frame].back()->buffers(UniformBuffer::getDescriptorType(), uniformBufferMeta[frame]);
|
||||||
|
descs[frame].back()->images(Image::getSampleType(), image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return descs;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Model> ModelBuilder::build() {
|
||||||
|
if (pushConstants.has_value())
|
||||||
|
if (pushConstants->constants.empty())
|
||||||
|
throw std::runtime_error("Model sets push constant present but no data.");
|
||||||
|
|
||||||
|
auto descs = createDescs();
|
||||||
|
pipelineBuilder->layout(
|
||||||
|
{ descs[0][0]->getLayout() },
|
||||||
|
pushConstants.has_value() ? createRanges(pushConstants.value()) : std::vector<VkPushConstantRange> {}
|
||||||
|
);
|
||||||
|
|
||||||
|
setVertexInput(*vertexBuffer, instanceBuffers, pipelineBuilder.get());
|
||||||
|
|
||||||
|
uniformMeta.clear();
|
||||||
|
uniformBufferMeta.clear();
|
||||||
|
|
||||||
|
return std::unique_ptr<Model> {
|
||||||
|
new Model {
|
||||||
|
aspectRatio, std::move(vertexBuffer), std::move(instanceBuffers), std::move(pushConstants),
|
||||||
|
std::move(sharedTextures), std::move(textures), std::move(descs), std::move(pipelineBuilder)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::update(bool opaque, const VkExtent2D &frame, VkSampleCountFlagBits samples,
|
||||||
|
const vlkx::RenderPass &pass, uint32_t subpass, bool flipY) {
|
||||||
|
|
||||||
|
pipeline = (*pipelineBuilder)
|
||||||
|
.depthTest(true, opaque)
|
||||||
|
.multiSample(samples)
|
||||||
|
.viewport({ { 0, 0, static_cast<float>(frame.width), static_cast<float>(frame.height), 0, 1 }, { { 0, 0 }, frame } })
|
||||||
|
.renderPass(*pass, subpass)
|
||||||
|
.colorBlend(std::vector<VkPipelineColorBlendAttachmentState>(pass.getAttachsInSubpass(subpass), vlkx::Pipeline::getAlphaBlendState(!opaque)))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::draw(const VkCommandBuffer &commands, int frame, uint32_t instances) const {
|
||||||
|
pipeline->bind(commands);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < perInstanceBuffers.size(); i++)
|
||||||
|
perInstanceBuffers[i]->bind(commands, i + 1, 0);
|
||||||
|
|
||||||
|
if (pushConstants.has_value())
|
||||||
|
for (const auto& meta : pushConstants->constants)
|
||||||
|
meta.constants->upload(commands, pipeline->getLayout(), frame, meta.offset, pushConstants->stage);
|
||||||
|
|
||||||
|
for (size_t mesh = 0; mesh < textures.size(); mesh++) {
|
||||||
|
descriptors[frame][mesh]->bind(commands, pipeline->getLayout(), pipeline->getBind());
|
||||||
|
vertexBuffer->draw(commands, 0, mesh, instances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
#include "temp/model/Loader.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace vlkxtemp {
|
||||||
|
|
||||||
|
std::vector<std::string> split (std::string_view s, char delim) {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::stringstream ss ((std::string(s)));
|
||||||
|
std::string item;
|
||||||
|
|
||||||
|
while (std::getline (ss, item, delim)) {
|
||||||
|
result.push_back (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Wavefront::Wavefront(std::string_view path, size_t index_base) {
|
||||||
|
std::ifstream file(std::string(path), std::ios::binary);
|
||||||
|
|
||||||
|
std::vector<glm::vec3> positions;
|
||||||
|
std::vector<glm::vec3> normals;
|
||||||
|
std::vector<glm::vec2> tex_coords;
|
||||||
|
std::map<std::string, uint32_t> loaded_vertices;
|
||||||
|
|
||||||
|
const auto parse_line = [&](std::string_view line) {
|
||||||
|
const size_t non_space = line.find_first_not_of(' ');
|
||||||
|
if (non_space == std::string::npos || line[0] == '#')
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (line[non_space]) {
|
||||||
|
case 'v': {
|
||||||
|
switch (line[non_space + 1]) {
|
||||||
|
case ' ': {
|
||||||
|
const auto nums = split(line.substr(non_space + 2), ' ');
|
||||||
|
positions.emplace_back(stof(nums[0]), stof(nums[1]), stof(nums[2]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'n': {
|
||||||
|
// Normal.
|
||||||
|
const auto nums = split(line.substr(non_space + 3), ' ');
|
||||||
|
normals.emplace_back(glm::vec3{stof(nums[0]), stof(nums[1]), stof(nums[2])});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 't': {
|
||||||
|
// Texture coordinates.
|
||||||
|
const auto nums = split(line.substr(non_space + 3), ' ');
|
||||||
|
tex_coords.emplace_back(glm::vec2{stof(nums[0]), stof(nums[1])});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unexpected symbol " + std::to_string(line[non_space + 1]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'f': {
|
||||||
|
for (const auto& seg : split(line.substr(non_space + 2), ' ')) {
|
||||||
|
const auto iter = loaded_vertices.find(seg);
|
||||||
|
if (iter != loaded_vertices.end()) {
|
||||||
|
indices.push_back(iter->second);
|
||||||
|
} else {
|
||||||
|
indices.push_back(vertices.size());
|
||||||
|
loaded_vertices[seg] = vertices.size();
|
||||||
|
const auto idxs = split(seg, '/');
|
||||||
|
vertices.push_back(Geo::VertexAll {
|
||||||
|
positions.at(stoi(idxs[0]) - index_base),
|
||||||
|
normals.at(stoi(idxs[2]) - index_base),
|
||||||
|
tex_coords.at(stoi(idxs[1]) - index_base),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unexpected symbol in OBJ file: " + std::to_string(line[non_space]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
int line_num = 1;
|
||||||
|
try {
|
||||||
|
for (; std::getline(file, line); ++line_num)
|
||||||
|
parse_line(line);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throw std::runtime_error("Failed to parse obj file, error on line " + std::to_string(line_num) + ": " + line + "; " + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelLoader::ModelLoader(const std::string &model, const std::string &textures) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
#include <vlkx/vulkan/CommandBuffer.h>
|
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
|
||||||
|
|
||||||
CommandBuffer::CommandBuffer() {}
|
|
||||||
CommandBuffer::~CommandBuffer() {}
|
|
||||||
|
|
||||||
void CommandBuffer::createCommandPoolAndBuffers(size_t size) {
|
|
||||||
createCommandPool();
|
|
||||||
allocateCommandBuffers(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandBuffer::createCommandPool() {
|
|
||||||
// Prepare queues
|
|
||||||
QueueFamilies families = VulkanManager::getInstance()->getDevice()->getQueues();
|
|
||||||
|
|
||||||
// Prepare command pool creation data
|
|
||||||
VkCommandPoolCreateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
||||||
info.queueFamilyIndex = families.graphics;
|
|
||||||
info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
||||||
|
|
||||||
// Attempt creation
|
|
||||||
if (vkCreateCommandPool(VulkanManager::getInstance()->getDevice()->logical, &info, nullptr, &commands) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create a command pool.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandBuffer::allocateCommandBuffers(size_t size) {
|
|
||||||
// Prepare allocation info
|
|
||||||
buffers.resize(size);
|
|
||||||
|
|
||||||
VkCommandBufferAllocateInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
||||||
info.commandPool = commands;
|
|
||||||
info.commandBufferCount = (uint32_t)buffers.size();
|
|
||||||
|
|
||||||
// Attempt allocation
|
|
||||||
if (vkAllocateCommandBuffers(VulkanManager::getInstance()->getDevice()->logical, &info, buffers.data()) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to allocate command buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandBuffer::beginCommandBuffer(VkCommandBuffer buffer) {
|
|
||||||
// Prepare to begin listening on this buffer
|
|
||||||
VkCommandBufferBeginInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
||||||
info.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; // Allow writing to this buffer while another is being read.
|
|
||||||
|
|
||||||
// Attempt to begin listening
|
|
||||||
if (vkBeginCommandBuffer(buffer, &info) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to begin a command buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandBuffer::endCommandBuffer(VkCommandBuffer buffer) {
|
|
||||||
// Attempt to end listening
|
|
||||||
if (vkEndCommandBuffer(buffer) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to end listening for a command buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandBuffer::destroy() {
|
|
||||||
vkDestroyCommandPool(VulkanManager::getInstance()->getDevice()->logical, commands, nullptr);
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include <vlkx/vulkan/SwapChain.h>
|
#include <vlkx/vulkan/SwapChain.h>
|
||||||
#include <vlkx/vulkan/VulkanManager.h>
|
#include <vlkx/vulkan/VulkanModule.h>
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
SwapChain::SwapChain() {}
|
SwapChain::SwapChain() {}
|
||||||
|
@ -45,7 +45,7 @@ VkExtent2D SwapChain::chooseExtent(const VkSurfaceCapabilitiesKHR& capabilities)
|
||||||
}
|
}
|
||||||
|
|
||||||
void SwapChain::create(VkSurfaceKHR surface) {
|
void SwapChain::create(VkSurfaceKHR surface) {
|
||||||
SwapChainMeta info = VulkanManager::getInstance()->getDevice()->swapChain;
|
SwapChainMeta info = VulkanModule::getInstance()->getDevice()->swapChain;
|
||||||
|
|
||||||
VkSurfaceFormatKHR chosenFormat = chooseFormat(info.formats);
|
VkSurfaceFormatKHR chosenFormat = chooseFormat(info.formats);
|
||||||
VkPresentModeKHR chosenMode = chooseMode(info.modes);
|
VkPresentModeKHR chosenMode = chooseMode(info.modes);
|
||||||
|
@ -66,7 +66,7 @@ void SwapChain::create(VkSurfaceKHR surface) {
|
||||||
createInfo.imageArrayLayers = 1; // 2 for VR
|
createInfo.imageArrayLayers = 1; // 2 for VR
|
||||||
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||||
|
|
||||||
QueueFamilies queues = VulkanManager::getInstance()->getDevice()->getQueues();
|
QueueFamilies queues = VulkanModule::getInstance()->getDevice()->getQueues();
|
||||||
uint32_t indices[] = { static_cast<uint32_t>(queues.graphics), static_cast<uint32_t>(queues.presentation) };
|
uint32_t indices[] = { static_cast<uint32_t>(queues.graphics), static_cast<uint32_t>(queues.presentation) };
|
||||||
|
|
||||||
if (queues.graphics != queues.presentation) {
|
if (queues.graphics != queues.presentation) {
|
||||||
|
@ -86,19 +86,32 @@ void SwapChain::create(VkSurfaceKHR surface) {
|
||||||
createInfo.oldSwapchain = VK_NULL_HANDLE;
|
createInfo.oldSwapchain = VK_NULL_HANDLE;
|
||||||
|
|
||||||
// Create the swap-chain
|
// Create the swap-chain
|
||||||
if (vkCreateSwapchainKHR(VulkanManager::getInstance()->getDevice()->logical, &createInfo, nullptr, &swapChain))
|
if (vkCreateSwapchainKHR(VulkanModule::getInstance()->getDevice()->logical, &createInfo, nullptr, &swapChain))
|
||||||
throw std::runtime_error("Failed to create swap-chain");
|
throw std::runtime_error("Failed to create swap-chain");
|
||||||
|
|
||||||
// Fetch our images from the swapchain
|
// Set members
|
||||||
vkGetSwapchainImagesKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, &imageCount, nullptr);
|
format = chosenFormat.format;
|
||||||
images.resize(imageCount);
|
extent = chosenExtent;
|
||||||
vkGetSwapchainImagesKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, &imageCount, images.data());
|
|
||||||
|
// Fetch our images from the swapchain
|
||||||
|
|
||||||
|
uint32_t swapchainImgCount = 0;
|
||||||
|
vkGetSwapchainImagesKHR(VulkanModule::getInstance()->getDevice()->logical, swapChain, &swapchainImgCount, nullptr);
|
||||||
|
|
||||||
|
std::vector<VkImage> swapchainImgs(swapchainImgCount);
|
||||||
|
vkGetSwapchainImagesKHR(VulkanModule::getInstance()->getDevice()->logical, swapChain, &swapchainImgCount, swapchainImgs.data());
|
||||||
|
|
||||||
|
images.resize(0);
|
||||||
|
images.reserve(imageCount);
|
||||||
|
for (const auto& img : swapchainImgs) {
|
||||||
|
images.emplace_back(std::make_unique<vlkx::SwapchainImage>(img, extent, format));
|
||||||
|
}
|
||||||
|
|
||||||
// Set gloabls
|
|
||||||
format = chosenFormat.format;
|
|
||||||
extent = chosenExtent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SwapChain::destroy() {
|
void SwapChain::destroy() {
|
||||||
vkDestroySwapchainKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, nullptr);
|
vkDestroySwapchainKHR(VulkanModule::getInstance()->getDevice()->logical, swapChain, nullptr);
|
||||||
|
/*for (auto & image : images)
|
||||||
|
image.reset();
|
||||||
|
multisampleImg.reset(); */
|
||||||
}
|
}
|
123
projs/shadow/shadow-engine/shadow-renderer/src/vulkan/Tools.cpp
Normal file
123
projs/shadow/shadow-engine/shadow-renderer/src/vulkan/Tools.cpp
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
#include <vlkx/vulkan/Tools.h>
|
||||||
|
#include <string>
|
||||||
|
#include "vlkx/vulkan/abstraction/Commands.h"
|
||||||
|
|
||||||
|
API VmaAllocator VkTools::allocator;
|
||||||
|
|
||||||
|
VkTools::ManagedImage VkTools::createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent) {
|
||||||
|
// Set up image metadata
|
||||||
|
VkImageCreateInfo info = {};
|
||||||
|
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||||
|
info.imageType = VK_IMAGE_TYPE_3D;
|
||||||
|
info.pNext = nullptr;
|
||||||
|
info.format = format;
|
||||||
|
info.extent = extent;
|
||||||
|
info.mipLevels = 1;
|
||||||
|
info.arrayLayers = 1;
|
||||||
|
info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||||
|
info.usage = flags;
|
||||||
|
|
||||||
|
// Prepare the managed image
|
||||||
|
ManagedImage image {};
|
||||||
|
|
||||||
|
// Set up image allocation
|
||||||
|
VmaAllocationCreateInfo allocateInfo = {};
|
||||||
|
allocateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||||
|
|
||||||
|
// Allocate + create the image
|
||||||
|
vmaCreateImage(allocator, &info, &allocateInfo, &image.image, &image.allocation, nullptr);
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSampler VkTools::createSampler(VkFilter filters, VkSamplerAddressMode mode, uint32_t mipping, VkDevice dev) {
|
||||||
|
VkSamplerCreateInfo info = {
|
||||||
|
VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||||
|
nullptr, {}, filters, filters,
|
||||||
|
VK_SAMPLER_MIPMAP_MODE_LINEAR, mode, mode, mode,
|
||||||
|
0, VK_TRUE, 16, VK_FALSE,
|
||||||
|
VK_COMPARE_OP_ALWAYS, 0, static_cast<float>(mipping),
|
||||||
|
VK_BORDER_COLOR_INT_OPAQUE_BLACK, VK_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
VkSampler sampler;
|
||||||
|
vkCreateSampler(dev, &info, nullptr, &sampler);
|
||||||
|
|
||||||
|
return sampler;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageView VkTools::createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipping, uint32_t layers, VkDevice device) {
|
||||||
|
// Raw information about the image
|
||||||
|
VkImageViewCreateInfo viewInfo = {};
|
||||||
|
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||||
|
viewInfo.image = image;
|
||||||
|
viewInfo.viewType = layers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_CUBE;
|
||||||
|
viewInfo.format = format;
|
||||||
|
|
||||||
|
// Information about the things we want to create - size, mip levels.
|
||||||
|
viewInfo.subresourceRange.aspectMask = flags;
|
||||||
|
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||||
|
viewInfo.subresourceRange.levelCount = mipping;
|
||||||
|
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||||
|
viewInfo.subresourceRange.layerCount = layers;
|
||||||
|
|
||||||
|
VkImageView imageView;
|
||||||
|
if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Failed to create texture image view.");
|
||||||
|
|
||||||
|
return imageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkTools::ManagedBuffer VkTools::createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible) {
|
||||||
|
// Prepare for creation of a buffer
|
||||||
|
VkBufferCreateInfo bufferInfo = {};
|
||||||
|
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||||
|
bufferInfo.size = size;
|
||||||
|
bufferInfo.usage = usage;
|
||||||
|
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
|
|
||||||
|
ManagedBuffer buffer {};
|
||||||
|
|
||||||
|
VmaAllocationCreateInfo vmaInfo = {};
|
||||||
|
vmaInfo.usage = hostVisible ? VMA_MEMORY_USAGE_CPU_ONLY : VMA_MEMORY_USAGE_GPU_ONLY;
|
||||||
|
vmaInfo.requiredFlags = properties;
|
||||||
|
|
||||||
|
// Create the buffer.
|
||||||
|
if (VkResult status = vmaCreateBuffer(allocator, &bufferInfo, &vmaInfo, &buffer.buffer, &buffer.allocation, nullptr); status != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create GPU buffer: " + std::to_string(status));
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t findMemoryIndex(uint32_t type, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice) {
|
||||||
|
// Get the physical properties of the device.
|
||||||
|
VkPhysicalDeviceMemoryProperties physProperties;
|
||||||
|
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &physProperties);
|
||||||
|
|
||||||
|
// Iterate the device and search for a suitable index
|
||||||
|
for (uint32_t i = 0; i < physProperties.memoryTypeCount; i++)
|
||||||
|
// If the type matches, and the properties are what we desire, then ship it.
|
||||||
|
if ((type & (1 << i)) && ((physProperties.memoryTypes[i].propertyFlags & properties) == properties))
|
||||||
|
return i;
|
||||||
|
|
||||||
|
throw std::runtime_error("Unable to find a suitable memory type on the physical device.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkTools::immediateExecute(const std::function<void(const VkCommandBuffer&)>& execute, VulkanDevice* dev) {
|
||||||
|
vlkx::ImmediateCommand cmd({ dev->graphicsQueue, dev->queueData.graphics });
|
||||||
|
cmd.run(execute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VkTools::copyGPUBuffer(VkBuffer source, VkBuffer dest, VkDeviceSize length, VulkanDevice* dev) {
|
||||||
|
immediateExecute([&](const VkCommandBuffer& commands) {
|
||||||
|
// Prepare to copy the data between buffers
|
||||||
|
VkBufferCopy copyInfo = {};
|
||||||
|
copyInfo.srcOffset = 0;
|
||||||
|
copyInfo.dstOffset = 0;
|
||||||
|
copyInfo.size = length;
|
||||||
|
|
||||||
|
// Copy the data.
|
||||||
|
vkCmdCopyBuffer(commands, source, dest, 1, ©Info);
|
||||||
|
}, dev);
|
||||||
|
}
|
|
@ -41,6 +41,10 @@ std::vector<const char*> ValidationAndExtension::getRequiredExtensions(SDL_Windo
|
||||||
SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr);
|
SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr);
|
||||||
|
|
||||||
std::vector<const char*> extensions = {
|
std::vector<const char*> extensions = {
|
||||||
|
#ifdef __APPLE__
|
||||||
|
"VK_KHR_portability_enumeration",
|
||||||
|
#endif
|
||||||
|
"VK_KHR_get_physical_device_properties2"
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t additional_extension_count = extensions.size();
|
size_t additional_extension_count = extensions.size();
|
||||||
|
@ -57,17 +61,16 @@ std::vector<const char*> ValidationAndExtension::getRequiredExtensions(SDL_Windo
|
||||||
}
|
}
|
||||||
|
|
||||||
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
|
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
|
||||||
VkDebugReportFlagsEXT flags,
|
VkDebugReportFlagsEXT flags,
|
||||||
VkDebugReportObjectTypeEXT objectType,
|
VkDebugReportObjectTypeEXT objExt,
|
||||||
uint64_t object,
|
uint64_t obj,
|
||||||
size_t location,
|
size_t location,
|
||||||
int32_t messageCode,
|
int32_t code,
|
||||||
const char* pLayerPrefix,
|
const char* layer,
|
||||||
const char* pMessage,
|
const char* message,
|
||||||
void* pUserData
|
void* user) {
|
||||||
) {
|
|
||||||
|
|
||||||
std::cerr << "Validation from layer " << pLayerPrefix << ": " << pMessage << std::endl;
|
std::cerr << "Validation from layer " << layer << ": " << message << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ void VulkanDevice::choosePhysicalDevice(VkInstance* vulkan, VkSurfaceKHR surface
|
||||||
for (const auto& device : physicals) {
|
for (const auto& device : physicals) {
|
||||||
VkPhysicalDeviceProperties props;
|
VkPhysicalDeviceProperties props;
|
||||||
vkGetPhysicalDeviceProperties(device, &props);
|
vkGetPhysicalDeviceProperties(device, &props);
|
||||||
|
limits = props.limits;
|
||||||
|
|
||||||
bool dedicated = props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU;
|
bool dedicated = props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU;
|
||||||
spdlog::debug(std::string("Device: ") + props.deviceName + ", discrete: " + (dedicated ? "yes" : "no"));
|
spdlog::debug(std::string("Device: ") + props.deviceName + ", discrete: " + (dedicated ? "yes" : "no"));
|
||||||
|
@ -62,7 +63,7 @@ bool VulkanDevice::isSuitable(VkPhysicalDevice device, VkSurfaceKHR surface) {
|
||||||
|
|
||||||
QueueFamilies VulkanDevice::checkQueues(VkPhysicalDevice device, VkSurfaceKHR surface) {
|
QueueFamilies VulkanDevice::checkQueues(VkPhysicalDevice device, VkSurfaceKHR surface) {
|
||||||
QueueFamilies families;
|
QueueFamilies families;
|
||||||
// Enumerate queueues
|
// Enumerate queues
|
||||||
uint32_t queueCount = 0;
|
uint32_t queueCount = 0;
|
||||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueCount, nullptr);
|
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueCount, nullptr);
|
||||||
|
|
||||||
|
|
|
@ -1,232 +0,0 @@
|
||||||
#define VMA_IMPLEMENTATION
|
|
||||||
|
|
||||||
#include <vulkan/vk_mem_alloc.h>
|
|
||||||
|
|
||||||
#define VKTOOLS_IMPLEMENTATION
|
|
||||||
|
|
||||||
#include <vlkx/vulkan/Tools.h>
|
|
||||||
|
|
||||||
#include <vlkx\vulkan\VulkanManager.h>
|
|
||||||
#include "spdlog/spdlog.h"
|
|
||||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
|
||||||
|
|
||||||
VulkanManager::VulkanManager() { rayTraceMode = false; }
|
|
||||||
|
|
||||||
VulkanManager::~VulkanManager() = default;
|
|
||||||
|
|
||||||
VulkanManager* VulkanManager::instance = nullptr;
|
|
||||||
|
|
||||||
|
|
||||||
VmaAllocator VkTools::g_allocator;
|
|
||||||
VkInstance VkTools::g_Instance = VK_NULL_HANDLE;
|
|
||||||
VkPhysicalDevice VkTools::g_PhysicalDevice = VK_NULL_HANDLE;
|
|
||||||
VkDevice VkTools::g_Device = VK_NULL_HANDLE;
|
|
||||||
uint32_t VkTools::g_QueueFamily = (uint32_t)-1;
|
|
||||||
VkQueue VkTools::g_Queue = VK_NULL_HANDLE;
|
|
||||||
VkDebugReportCallbackEXT VkTools::g_DebugReport = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
VulkanManager* VulkanManager::getInstance() {
|
|
||||||
return VulkanManager::instance != nullptr ? VulkanManager::instance
|
|
||||||
: (VulkanManager::instance = new VulkanManager());
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanManager::createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations) {
|
|
||||||
VkApplicationInfo info = {};
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
||||||
info.pApplicationName = "Sup";
|
|
||||||
info.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
|
|
||||||
info.pEngineName = "Infinity Drive";
|
|
||||||
info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
|
|
||||||
info.apiVersion = VK_API_VERSION_1_0;
|
|
||||||
|
|
||||||
VkInstanceCreateInfo instanceInfo = {};
|
|
||||||
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
||||||
instanceInfo.pApplicationInfo = &info;
|
|
||||||
|
|
||||||
auto extensions = validations->getRequiredExtensions(wnd, true);
|
|
||||||
instanceInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
|
|
||||||
instanceInfo.ppEnabledExtensionNames = &extensions[0];
|
|
||||||
|
|
||||||
auto layers = validations->requiredValidations;
|
|
||||||
if (enableValidation) {
|
|
||||||
instanceInfo.enabledLayerCount = static_cast<uint32_t>(layers.size());
|
|
||||||
instanceInfo.ppEnabledLayerNames = &layers[0];
|
|
||||||
} else {
|
|
||||||
instanceInfo.enabledLayerCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto status = vkCreateInstance(&instanceInfo, nullptr, &vulkan);
|
|
||||||
if (status != VK_SUCCESS) {
|
|
||||||
throw std::runtime_error("Failed to initialize Vulkan: " + std::to_string(status));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanManager::initVulkan(SDL_Window* window) {
|
|
||||||
wnd = window;
|
|
||||||
validators = new ValidationAndExtension();
|
|
||||||
|
|
||||||
spdlog::info("Initializing Infinity Drive rendering engine");
|
|
||||||
spdlog::default_logger()->set_level(spdlog::level::debug);
|
|
||||||
|
|
||||||
if (!validators->checkValidationSupport())
|
|
||||||
throw std::runtime_error("Validation not available");
|
|
||||||
|
|
||||||
createAppAndVulkanInstance(validationRequired, validators);
|
|
||||||
|
|
||||||
validators->setupDebugCallback(validationRequired, vulkan);
|
|
||||||
|
|
||||||
if (SDL_Vulkan_CreateSurface(window, vulkan, &surface) != SDL_TRUE)
|
|
||||||
throw std::runtime_error("Unable to create Vulkan Surface");
|
|
||||||
|
|
||||||
this->device = new VulkanDevice();
|
|
||||||
this->device->choosePhysicalDevice(&vulkan, surface);
|
|
||||||
this->device->createLogicalDevice(surface, validationRequired, validators);
|
|
||||||
|
|
||||||
VmaAllocatorCreateInfo allocatorInfo = {};
|
|
||||||
allocatorInfo.physicalDevice = this->device->physical;
|
|
||||||
allocatorInfo.device = this->device->logical;
|
|
||||||
allocatorInfo.instance = this->vulkan;
|
|
||||||
vmaCreateAllocator(&allocatorInfo, &this->allocator);
|
|
||||||
VkTools::g_allocator = this->allocator;
|
|
||||||
VkTools::g_PhysicalDevice = this->device->physical;
|
|
||||||
VkTools::g_Device = this->device->logical;
|
|
||||||
VkTools::g_Instance = this->vulkan;
|
|
||||||
|
|
||||||
this->swapchain = new SwapChain();
|
|
||||||
this->swapchain->create(surface);
|
|
||||||
|
|
||||||
this->renderPass = new RenderPass();
|
|
||||||
|
|
||||||
// Set up for vertex rendering
|
|
||||||
this->renderPass->createVertexRenderPass(swapchain->format);
|
|
||||||
this->renderTexture = new SingleRenderTexture();
|
|
||||||
|
|
||||||
this->renderTexture->createViewsAndFramebuffer(swapchain->images, swapchain->format,
|
|
||||||
swapchain->extent,renderPass->pass
|
|
||||||
);
|
|
||||||
|
|
||||||
this->buffers = new CommandBuffer();
|
|
||||||
this->buffers->createCommandPoolAndBuffers(swapchain->images.size());
|
|
||||||
|
|
||||||
// Create semaphores for render events
|
|
||||||
VkSemaphoreCreateInfo semaphoreInfo = {};
|
|
||||||
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
|
||||||
vkCreateSemaphore(device->logical, &semaphoreInfo, nullptr, &newImageSem);
|
|
||||||
vkCreateSemaphore(device->logical, &semaphoreInfo, nullptr, &renderDoneSem);
|
|
||||||
|
|
||||||
// Create fences for the frames
|
|
||||||
inFlight.resize(MAX_FRAMES);
|
|
||||||
VkFenceCreateInfo fenceInfo = {};
|
|
||||||
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
|
||||||
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < MAX_FRAMES; i++) {
|
|
||||||
if (vkCreateFence(device->logical, &fenceInfo, nullptr, &inFlight[i]) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to create fence for a frame");
|
|
||||||
}
|
|
||||||
|
|
||||||
spdlog::info("Infinity Drive initialization finished.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanManager::startDraw() {
|
|
||||||
// Prepare for a new frame to start
|
|
||||||
vkAcquireNextImageKHR(device->logical, swapchain->swapChain,
|
|
||||||
std::numeric_limits<uint64_t>::max(), newImageSem,VK_NULL_HANDLE, &imageIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
vkWaitForFences(device->logical, 1, &inFlight[imageIndex], VK_TRUE,
|
|
||||||
std::numeric_limits<uint64_t>::max()
|
|
||||||
);
|
|
||||||
|
|
||||||
vkResetFences(device->logical, 1, &inFlight[imageIndex]);
|
|
||||||
|
|
||||||
// Fetch the next command buffer
|
|
||||||
currentCommandBuffer = buffers->buffers[imageIndex];
|
|
||||||
buffers->beginCommandBuffer(currentCommandBuffer);
|
|
||||||
|
|
||||||
// Setup render pass; setup clear color
|
|
||||||
VkClearValue clearColor = { 1.0f, 0.0f, 0.0f, 1.0f }; // Red
|
|
||||||
|
|
||||||
// Execute render pass
|
|
||||||
renderPass->beginRenderPass({ clearColor }, currentCommandBuffer, dynamic_cast<SingleRenderTexture*>(renderTexture)->swapChainFramebuffers[imageIndex], dynamic_cast<SingleRenderTexture*>(renderTexture)->swapChainImageExtent);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanManager::endDraw() {
|
|
||||||
// End command buffer first
|
|
||||||
renderPass->endRenderPass(currentCommandBuffer);
|
|
||||||
buffers->endCommandBuffer(currentCommandBuffer);
|
|
||||||
|
|
||||||
// Prepare to submit all draw commands to the GPU
|
|
||||||
VkPipelineStageFlags waitStages[] = {
|
|
||||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
|
|
||||||
};
|
|
||||||
|
|
||||||
VkSubmitInfo submitInfo = {};
|
|
||||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
||||||
submitInfo.commandBufferCount = 1;
|
|
||||||
submitInfo.pCommandBuffers = ¤tCommandBuffer;
|
|
||||||
submitInfo.pWaitDstStageMask = waitStages;
|
|
||||||
// Wait for the New Image semaphore, and signal the Render Done semaphore when finished
|
|
||||||
submitInfo.waitSemaphoreCount = 1;
|
|
||||||
submitInfo.pWaitSemaphores = &newImageSem;
|
|
||||||
submitInfo.signalSemaphoreCount = 1;
|
|
||||||
submitInfo.pSignalSemaphores = &renderDoneSem;
|
|
||||||
|
|
||||||
// Submit.
|
|
||||||
vkQueueSubmit(VulkanManager::getInstance()->getDevice()->graphicsQueue, 1, &submitInfo, inFlight[imageIndex]);
|
|
||||||
|
|
||||||
// Prepare to show the drawn frame on the screen.
|
|
||||||
VkPresentInfoKHR presentInfo = {};
|
|
||||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
|
||||||
presentInfo.swapchainCount = 1;
|
|
||||||
presentInfo.pSwapchains = &swapchain->swapChain;
|
|
||||||
presentInfo.pImageIndices = &imageIndex;
|
|
||||||
// Wait until render is finished before presenting.
|
|
||||||
presentInfo.waitSemaphoreCount = 1;
|
|
||||||
presentInfo.pWaitSemaphores = &renderDoneSem;
|
|
||||||
|
|
||||||
// Show.
|
|
||||||
vkQueuePresentKHR(VulkanManager::getInstance()->getDevice()->presentationQueue, &presentInfo);
|
|
||||||
|
|
||||||
// Wait for the GPU to catch up
|
|
||||||
vkQueueWaitIdle(VulkanManager::getInstance()->getDevice()->presentationQueue);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanManager::cleanup() {
|
|
||||||
// Wait for the GPU to not be busy
|
|
||||||
vkDeviceWaitIdle(VulkanManager::getInstance()->getDevice()->logical);
|
|
||||||
|
|
||||||
// Destroy our own data
|
|
||||||
vkDestroySemaphore(device->logical, renderDoneSem, nullptr);
|
|
||||||
vkDestroySemaphore(device->logical, newImageSem, nullptr);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < MAX_FRAMES; i++) {
|
|
||||||
vkDestroyFence(device->logical, inFlight[i], nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffers->destroy();
|
|
||||||
renderTexture->destroy();
|
|
||||||
renderPass->destroy();
|
|
||||||
swapchain->destroy();
|
|
||||||
|
|
||||||
// Destroy the Vulkan Device
|
|
||||||
VulkanManager::getInstance()->getDevice()->destroy();
|
|
||||||
|
|
||||||
// Delete the layer validators.
|
|
||||||
validators->destroy(validationRequired, vulkan);
|
|
||||||
|
|
||||||
// Delete the surface and Vulkan instance.
|
|
||||||
vkDestroySurfaceKHR(vulkan, surface, nullptr);
|
|
||||||
vkDestroyInstance(vulkan, nullptr);
|
|
||||||
|
|
||||||
vmaDestroyAllocator(allocator);
|
|
||||||
|
|
||||||
// Delete allocated memory for our own data.
|
|
||||||
delete buffers;
|
|
||||||
delete renderTexture;
|
|
||||||
delete renderPass;
|
|
||||||
delete swapchain;
|
|
||||||
delete device;
|
|
||||||
delete validators;
|
|
||||||
}
|
|
|
@ -0,0 +1,348 @@
|
||||||
|
#define VMA_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include <vulkan/vk_mem_alloc.h>
|
||||||
|
#include <vlkx/vulkan/Tools.h>
|
||||||
|
|
||||||
|
#include <vlkx\vulkan\VulkanModule.h>
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||||
|
#include "core/ShadowApplication.h"
|
||||||
|
#include "core/SDL2Module.h"
|
||||||
|
#include "vlkx/render/render_pass/ScreenRenderPass.h"
|
||||||
|
#include <vlkx/vulkan/SwapChain.h>
|
||||||
|
|
||||||
|
#define CATCH(x) \
|
||||||
|
try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); }
|
||||||
|
|
||||||
|
SHObject_Base_Impl(VulkanModule)
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::ScreenRenderPassManager> renderPass;
|
||||||
|
bool renderingGeometry;
|
||||||
|
std::unique_ptr<vlkx::ScreenRenderPassManager> editorPass;
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::RenderCommand> editorRenderCommands;
|
||||||
|
|
||||||
|
const std::unique_ptr<vlkx::ScreenRenderPassManager>& VulkanModule::getRenderPass() {
|
||||||
|
return renderPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanModule::VulkanModule() { instance = this; }
|
||||||
|
|
||||||
|
VulkanModule::~VulkanModule() = default;
|
||||||
|
|
||||||
|
VulkanModule* VulkanModule::instance = nullptr;
|
||||||
|
|
||||||
|
VulkanModule* VulkanModule::getInstance() {
|
||||||
|
return VulkanModule::instance != nullptr ? VulkanModule::instance
|
||||||
|
: (VulkanModule::instance = new VulkanModule());
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::EnableEditor() {
|
||||||
|
editorEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkExtent2D VulkanModule::GetRenderExtent() {
|
||||||
|
if (editorEnabled) {
|
||||||
|
if (renderingGeometry)
|
||||||
|
return editorContentFrames[0]->getExtent();
|
||||||
|
else
|
||||||
|
return swapchain->extent;
|
||||||
|
} else
|
||||||
|
return swapchain->extent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::Recreate() {
|
||||||
|
vkDeviceWaitIdle(device->logical);
|
||||||
|
|
||||||
|
device->swapChain = device->checkSwapchain(device->physical, surface);
|
||||||
|
|
||||||
|
if (device->swapChain.capabilities.currentExtent.width == 0 && device->swapChain.capabilities.currentExtent.height == 0) {
|
||||||
|
[]() {
|
||||||
|
SDL_Event event;
|
||||||
|
while (true) {
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_WINDOWEVENT && (event.window.event == SDL_WINDOWEVENT_MAXIMIZED || event.window.event == SDL_WINDOWEVENT_SHOWN || event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED || event.window.event == SDL_WINDOWEVENT_RESTORED))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
device->swapChain = device->checkSwapchain(device->physical, surface);
|
||||||
|
|
||||||
|
swapchain->destroy();
|
||||||
|
swapchain->create(surface);
|
||||||
|
|
||||||
|
renderPass->initializeRenderPass();
|
||||||
|
editorPass->initializeRenderPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::PreInit() {
|
||||||
|
spdlog::info("Vulkan Renderer Module loading..");
|
||||||
|
|
||||||
|
|
||||||
|
auto shApp = ShadowEngine::ShadowApplication::Get();
|
||||||
|
|
||||||
|
ShadowEngine::ModuleManager &moduleManager = shApp.GetModuleManager();
|
||||||
|
|
||||||
|
auto sdl2module = moduleManager.GetModuleByType<ShadowEngine::SDL2Module>();
|
||||||
|
|
||||||
|
CATCH(initVulkan(sdl2module->_window->sdlWindowPtr);)
|
||||||
|
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
ImGui::CreateContext();
|
||||||
|
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
|
||||||
|
|
||||||
|
// Setup Dear ImGui style
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
VkDescriptorPool imGuiPool;
|
||||||
|
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(getDevice()->logical, &pool_info, VK_NULL_HANDLE, &imGuiPool);
|
||||||
|
|
||||||
|
// Setup Platform/Renderer backends
|
||||||
|
ImGui_ImplSDL2_InitForVulkan(wnd);
|
||||||
|
ImGui_ImplVulkan_InitInfo init_info = {};
|
||||||
|
init_info.Instance = getVulkan();
|
||||||
|
init_info.PhysicalDevice = getDevice()->physical;
|
||||||
|
init_info.Device = getDevice()->logical;
|
||||||
|
init_info.QueueFamily = getDevice()->queueData.graphics;
|
||||||
|
init_info.Queue = getDevice()->graphicsQueue;
|
||||||
|
init_info.PipelineCache = VK_NULL_HANDLE;
|
||||||
|
init_info.DescriptorPool = imGuiPool;
|
||||||
|
init_info.Subpass = 1;
|
||||||
|
init_info.MinImageCount = getSwapchain()->images.size();
|
||||||
|
init_info.ImageCount = getSwapchain()->images.size();
|
||||||
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
init_info.Allocator = VK_NULL_HANDLE;
|
||||||
|
init_info.CheckVkResultFn = nullptr;
|
||||||
|
|
||||||
|
|
||||||
|
if (editorEnabled) {
|
||||||
|
editorContentFrames.resize(1);
|
||||||
|
for (size_t i = 0; i < 1; i++) {
|
||||||
|
vlkx::TextureImage::Meta meta {
|
||||||
|
{nullptr}, {vlkx::ImageUsage::renderTarget(0)}, VK_FORMAT_R8G8B8A8_SRGB, 640, 480, 4
|
||||||
|
};
|
||||||
|
editorContentFrames[i] = std::make_unique<vlkx::TextureImage>(0, vlkx::ImageSampler::Config {}, meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
editorRenderCommands = std::make_unique<vlkx::RenderCommand>(editorContentFrames.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPass = std::make_unique<vlkx::ScreenRenderPassManager>(vlkx::RendererConfig { editorEnabled ? 1 : 2, editorEnabled ? editorContentFrames : swapchain->images, !editorEnabled } );
|
||||||
|
renderPass->initializeRenderPass();
|
||||||
|
editorPass = std::make_unique<vlkx::ScreenRenderPassManager>(vlkx::RendererConfig { 2, swapchain->images, true } );
|
||||||
|
editorPass->initializeRenderPass();
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_Init(&init_info, **(editorEnabled ? editorPass : renderPass)->getPass());
|
||||||
|
|
||||||
|
VkTools::immediateExecute([](const VkCommandBuffer& commands) { ImGui_ImplVulkan_CreateFontsTexture(commands); }, getDevice());
|
||||||
|
|
||||||
|
if (editorEnabled) {
|
||||||
|
editorRenderPlanes.resize(editorContentFrames.size());
|
||||||
|
for (size_t i = 0; i < editorContentFrames.size(); i++) {
|
||||||
|
editorRenderPlanes[i] = ImGui_ImplVulkan_AddTexture(VkTools::createSampler(VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, 0, device->logical), editorContentFrames[i]->getView(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderingGeometry = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::Init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::BeginRenderPass(const std::unique_ptr<vlkx::RenderCommand>& commands) {
|
||||||
|
const auto update = !editorEnabled ? [](const int frame) { ShadowEngine::ModuleManager::instance->Update(frame); } : nullptr;
|
||||||
|
|
||||||
|
const auto res = commands->execute(commands->getFrame(), swapchain->swapChain, update,
|
||||||
|
[this](const VkCommandBuffer& buffer, int frame) {
|
||||||
|
(editorEnabled ? editorPass : renderPass)->getPass()->execute(buffer, frame, {
|
||||||
|
// Render our model
|
||||||
|
[&](const VkCommandBuffer &commands) {
|
||||||
|
if (!editorEnabled) {
|
||||||
|
renderingGeometry = true;
|
||||||
|
ShadowEngine::ModuleManager::instance->Render(const_cast<VkCommandBuffer &>(commands), frame);
|
||||||
|
ShadowEngine::ModuleManager::instance->LateRender(const_cast<VkCommandBuffer &>(commands), frame);
|
||||||
|
renderingGeometry = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Render ImGUI
|
||||||
|
[&](const VkCommandBuffer &commands) {
|
||||||
|
ImGui_ImplVulkan_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
if (editorEnabled)
|
||||||
|
ImGui::Image(editorRenderPlanes[0], { 640, 480 });
|
||||||
|
|
||||||
|
ShadowEngine::ModuleManager::instance->OverlayRender();
|
||||||
|
|
||||||
|
ImGui::Render();
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
(void) io;
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commands);
|
||||||
|
|
||||||
|
// Update and Render additional Platform Windows
|
||||||
|
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
||||||
|
ImGui::UpdatePlatformWindows();
|
||||||
|
ImGui::RenderPlatformWindowsDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.has_value())
|
||||||
|
ShadowEngine::ModuleManager::getInstance()->Recreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::PreRender() {
|
||||||
|
if (editorEnabled) {
|
||||||
|
editorRenderCommands->executeSimple(editorRenderCommands->getFrame(), [](const int frame) { ShadowEngine::ModuleManager::instance->Update(frame); },
|
||||||
|
[&](const VkCommandBuffer& buffer, int frame) {
|
||||||
|
renderPass->getPass()->execute(buffer, frame, {
|
||||||
|
[&](const VkCommandBuffer& commands) {
|
||||||
|
renderingGeometry = true;
|
||||||
|
ShadowEngine::ModuleManager::instance->Render(const_cast<VkCommandBuffer &>(commands), frame);
|
||||||
|
ShadowEngine::ModuleManager::instance->LateRender(const_cast<VkCommandBuffer &>(commands), frame);
|
||||||
|
renderingGeometry = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VulkanModule::OverlayRender() {}
|
||||||
|
void VulkanModule::AfterFrameEnd() {}
|
||||||
|
void VulkanModule::Render(VkCommandBuffer& commands, int frame) {}
|
||||||
|
void VulkanModule::Update(int frame) {}
|
||||||
|
void VulkanModule::Event(SDL_Event *e) { (void)e; }
|
||||||
|
|
||||||
|
void VulkanModule::LateRender(VkCommandBuffer& commands, int frame) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::Destroy() {
|
||||||
|
ImGui_ImplVulkan_Shutdown();
|
||||||
|
ImGui_ImplSDL2_Shutdown();
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations) {
|
||||||
|
VkApplicationInfo info = {};
|
||||||
|
info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||||
|
info.pApplicationName = "Sup";
|
||||||
|
info.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
|
||||||
|
info.pEngineName = "Infinity Drive";
|
||||||
|
info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0);
|
||||||
|
info.apiVersion = VK_API_VERSION_1_0;
|
||||||
|
|
||||||
|
VkInstanceCreateInfo instanceInfo = {};
|
||||||
|
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||||
|
#ifdef __APPLE__
|
||||||
|
VkFlags instanceFlag = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||||
|
#else
|
||||||
|
VkFlags instanceFlag = 0;
|
||||||
|
#endif
|
||||||
|
instanceInfo.flags = instanceFlag;
|
||||||
|
instanceInfo.pApplicationInfo = &info;
|
||||||
|
|
||||||
|
auto extensions = validations->getRequiredExtensions(wnd, true);
|
||||||
|
instanceInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
|
||||||
|
instanceInfo.ppEnabledExtensionNames = &extensions[0];
|
||||||
|
|
||||||
|
auto layers = validations->requiredValidations;
|
||||||
|
if (enableValidation) {
|
||||||
|
instanceInfo.enabledLayerCount = static_cast<uint32_t>(layers.size());
|
||||||
|
instanceInfo.ppEnabledLayerNames = &layers[0];
|
||||||
|
} else {
|
||||||
|
instanceInfo.enabledLayerCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto status = vkCreateInstance(&instanceInfo, nullptr, &vulkan);
|
||||||
|
if (status != VK_SUCCESS) {
|
||||||
|
throw std::runtime_error("Failed to initialize Vulkan: " + std::to_string(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::initVulkan(SDL_Window* window) {
|
||||||
|
wnd = window;
|
||||||
|
validators = new ValidationAndExtension();
|
||||||
|
|
||||||
|
spdlog::info("Initializing Infinity Drive rendering engine");
|
||||||
|
spdlog::default_logger()->set_level(spdlog::level::debug);
|
||||||
|
|
||||||
|
if (!validators->checkValidationSupport())
|
||||||
|
throw std::runtime_error("Validation not available");
|
||||||
|
|
||||||
|
createAppAndVulkanInstance(validationRequired, validators);
|
||||||
|
|
||||||
|
validators->setupDebugCallback(validationRequired, vulkan);
|
||||||
|
|
||||||
|
if (SDL_Vulkan_CreateSurface(window, vulkan, &surface) != SDL_TRUE)
|
||||||
|
throw std::runtime_error("Unable to create Vulkan Surface");
|
||||||
|
|
||||||
|
this->device = new VulkanDevice();
|
||||||
|
this->device->choosePhysicalDevice(&vulkan, surface);
|
||||||
|
this->device->createLogicalDevice(surface, validationRequired, validators);
|
||||||
|
|
||||||
|
VmaAllocatorCreateInfo allocatorInfo = {};
|
||||||
|
allocatorInfo.physicalDevice = this->device->physical;
|
||||||
|
allocatorInfo.device = this->device->logical;
|
||||||
|
allocatorInfo.instance = this->vulkan;
|
||||||
|
vmaCreateAllocator(&allocatorInfo, &this->allocator);
|
||||||
|
VkTools::allocator = allocator;
|
||||||
|
|
||||||
|
this->swapchain = new SwapChain();
|
||||||
|
this->swapchain->create(surface);
|
||||||
|
spdlog::info("Infinity Drive initialization finished.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanModule::cleanup() {
|
||||||
|
// Wait for the GPU to not be busy
|
||||||
|
vkDeviceWaitIdle(VulkanModule::getInstance()->getDevice()->logical);
|
||||||
|
|
||||||
|
swapchain->destroy();
|
||||||
|
|
||||||
|
// Destroy the Vulkan Device
|
||||||
|
VulkanModule::getInstance()->getDevice()->destroy();
|
||||||
|
|
||||||
|
// Delete the layer validators.
|
||||||
|
validators->destroy(validationRequired, vulkan);
|
||||||
|
|
||||||
|
// Delete the surface and Vulkan instance.
|
||||||
|
vkDestroySurfaceKHR(vulkan, surface, nullptr);
|
||||||
|
vkDestroyInstance(vulkan, nullptr);
|
||||||
|
|
||||||
|
vmaDestroyAllocator(allocator);
|
||||||
|
|
||||||
|
delete swapchain;
|
||||||
|
delete device;
|
||||||
|
delete validators;
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
#include "vlkx/vulkan/abstraction/Buffer.h"
|
||||||
|
#include "vlkx/vulkan/Tools.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
|
||||||
|
void executeBulkCopy(VkTools::ManagedBuffer buffer, const std::vector<vlkx::Buffer::CopyMeta>& meta) {
|
||||||
|
void* dst;
|
||||||
|
vmaMapMemory(VulkanModule::getInstance()->getAllocator(), buffer.allocation, &dst);
|
||||||
|
// GPU memory accessible through dst pointer
|
||||||
|
|
||||||
|
for (const auto& info : meta) {
|
||||||
|
memcpy(static_cast<char*>(dst) + info.start, info.data, info.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap GPU memory
|
||||||
|
vmaUnmapMemory(VulkanModule::getInstance()->getAllocator(), buffer.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
StagingBuffer::StagingBuffer(const vlkx::Buffer::BulkCopyMeta ©Meta) : dataSize(copyMeta.length) {
|
||||||
|
setBuffer(VkTools::createGPUBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VulkanModule::getInstance()->getDevice()->logical, VulkanModule::getInstance()->getDevice()->physical));
|
||||||
|
|
||||||
|
executeBulkCopy(get(), copyMeta.metas);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StagingBuffer::copy(const VkBuffer &target) const {
|
||||||
|
VkTools::copyGPUBuffer(get().buffer, target, dataSize, VulkanModule::getInstance()->getDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkVertexInputAttributeDescription> VertexBuffer::getAttrs(uint32_t start) const {
|
||||||
|
std::vector<VkVertexInputAttributeDescription> descs;
|
||||||
|
descs.reserve(attributes.size());
|
||||||
|
|
||||||
|
for (const auto& attr : attributes) {
|
||||||
|
descs.push_back(VkVertexInputAttributeDescription {
|
||||||
|
start++, 0, attr.format, attr.offset
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return descs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexBuffer::draw(const VkCommandBuffer &commands, uint32_t verts, uint32_t instances) {
|
||||||
|
vkCmdDraw(commands, verts, instances, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexBuffer::create(VkDeviceSize totalSize, bool dynamic, bool indexes) {
|
||||||
|
VkBufferUsageFlags usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
||||||
|
VkMemoryPropertyFlags props;
|
||||||
|
|
||||||
|
if (dynamic) {
|
||||||
|
props = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||||
|
} else {
|
||||||
|
usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||||
|
props = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexes)
|
||||||
|
usage |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||||
|
|
||||||
|
setBuffer(VkTools::createGPUBuffer(totalSize, usage, props, VulkanModule::getInstance()->getDevice()->logical, VulkanModule::getInstance()->getDevice()->physical, dynamic));
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicBuffer::DynamicBuffer(size_t size, bool hasIndices, vlkx::VertexBuffer *buffer) : hasIndices(hasIndices), vertexBuffer(buffer) {
|
||||||
|
resize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicBuffer::resize(size_t pSize) {
|
||||||
|
if (pSize <= bufferSize()) return;
|
||||||
|
|
||||||
|
if (pSize > 0) {
|
||||||
|
// todo: release the buffer & device memory
|
||||||
|
}
|
||||||
|
|
||||||
|
size = pSize;
|
||||||
|
vertexBuffer->create(size, true, hasIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::BulkCopyMeta PerVertexBuffer::NoIndexBufferMeta::prepareCopy(vlkx::PerVertexBuffer *buffer) const {
|
||||||
|
auto& meshInfos = buffer->meshDataInfo.emplace<MeshDataNoIndex>().info;
|
||||||
|
meshInfos.reserve(perMeshVertices.size());
|
||||||
|
|
||||||
|
std::vector<Buffer::CopyMeta> copyMetas;
|
||||||
|
copyMetas.reserve(perMeshVertices.size());
|
||||||
|
|
||||||
|
VkDeviceSize offset = 0;
|
||||||
|
for (const auto& verts : perMeshVertices) {
|
||||||
|
meshInfos.push_back(MeshDataNoIndex::Info { static_cast<uint32_t>(verts.unitsPerMesh), offset });
|
||||||
|
copyMetas.push_back(Buffer::CopyMeta { verts.data, verts.sizePerMesh, offset });
|
||||||
|
offset += verts.sizePerMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer::BulkCopyMeta { offset, std::move(copyMetas) };
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::BulkCopyMeta PerVertexBuffer::SharedIndexMeta::prepareCopy(vlkx::PerVertexBuffer *buffer) const {
|
||||||
|
auto& meshInfos = buffer->meshDataInfo.emplace<MeshDataIndex>().info;
|
||||||
|
meshInfos.reserve(meshes);
|
||||||
|
|
||||||
|
VkDeviceSize offset = sharedIndices.sizePerMesh;
|
||||||
|
for (int i = 0; i < meshes; ++i) {
|
||||||
|
meshInfos.push_back(MeshDataIndex::Info { static_cast<uint32_t>(sharedIndices.unitsPerMesh), 0, offset });
|
||||||
|
offset += perMeshVertex.sizePerMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer::BulkCopyMeta { offset, { { sharedIndices.data, sharedIndices.sizePerMesh, 0 }, { perMeshVertex.data, perMeshVertex.sizePerMesh * meshes, sharedIndices.sizePerMesh } } };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Buffer::BulkCopyMeta PerVertexBuffer::NoShareMeta::prepareCopy(vlkx::PerVertexBuffer *buffer) const {
|
||||||
|
auto& meshInfos = buffer->meshDataInfo.emplace<MeshDataIndex>().info;
|
||||||
|
meshInfos.reserve(perMeshMeta.size());
|
||||||
|
|
||||||
|
std::vector<Buffer::CopyMeta> copyMetas;
|
||||||
|
copyMetas.reserve(perMeshMeta.size() * 2);
|
||||||
|
|
||||||
|
VkDeviceSize offset = 0;
|
||||||
|
for (const auto& meta : perMeshMeta) {
|
||||||
|
const size_t indicesSize = meta.indices.sizePerMesh;
|
||||||
|
const size_t verticesSize = meta.vertices.sizePerMesh;
|
||||||
|
const VkDeviceSize verticesOffset = offset + indicesSize;
|
||||||
|
|
||||||
|
meshInfos.push_back(MeshDataIndex::Info { static_cast<uint32_t>(meta.indices.unitsPerMesh), offset, verticesOffset });
|
||||||
|
copyMetas.push_back(Buffer::CopyMeta { meta.indices.data, indicesSize, offset });
|
||||||
|
copyMetas.push_back(Buffer::CopyMeta { meta.vertices.data, verticesSize, verticesOffset });
|
||||||
|
|
||||||
|
offset += indicesSize + verticesSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer::BulkCopyMeta { offset, std::move(copyMetas) };
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerVertexBuffer::draw(const VkCommandBuffer &commands, uint32_t bind, int index, uint32_t instances) const {
|
||||||
|
if (const auto* meshNoIndex = std::get_if<MeshDataNoIndex>(&meshDataInfo); meshNoIndex != nullptr) {
|
||||||
|
const auto& meshInfo = meshNoIndex->info[index];
|
||||||
|
vkCmdBindVertexBuffers(commands, bind, 1, &getBuffer(), &meshInfo.vertexStart);
|
||||||
|
vkCmdDraw(commands, meshInfo.vertexCount, instances, 0, 0);
|
||||||
|
} else if (const auto* meshIndex = std::get_if<MeshDataIndex>(&meshDataInfo); meshIndex != nullptr) {
|
||||||
|
const auto& meshInfo = meshIndex->info[index];
|
||||||
|
vkCmdBindIndexBuffer(commands, getBuffer(), meshInfo.indexStart, VK_INDEX_TYPE_UINT32);
|
||||||
|
vkCmdBindVertexBuffers(commands, bind, 1, &getBuffer(), &meshInfo.vertexStart);
|
||||||
|
vkCmdDrawIndexed(commands, meshInfo.indexCount, instances, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticPerVertexBuffer::StaticPerVertexBuffer(const vlkx::PerVertexBuffer::BufferDataMeta &info,
|
||||||
|
std::vector<VkVertexInputAttributeDescription> &&attrs) : PerVertexBuffer(std::move(attrs)) {
|
||||||
|
const BulkCopyMeta copy = info.prepareCopy(this);
|
||||||
|
create(copy.length, false, info.hasIndices());
|
||||||
|
const StagingBuffer staging(copy);
|
||||||
|
staging.copy(getBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicPerVertexBuffer::copyToDevice(const vlkx::PerVertexBuffer::BufferDataMeta &meta) {
|
||||||
|
const BulkCopyMeta copy = meta.prepareCopy(this);
|
||||||
|
resize(copy.length);
|
||||||
|
executeBulkCopy(get(), copy.metas);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerInstanceVertexBuffer::bind(const VkCommandBuffer &commands, uint32_t bindPoint, int offset) const {
|
||||||
|
const VkDeviceSize size = sizePerInstance * offset;
|
||||||
|
vkCmdBindVertexBuffers(commands, bindPoint, 1, &getBuffer(), &size);
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticPerInstanceBuffer::StaticPerInstanceBuffer(uint32_t size, const void *data, uint32_t instances,
|
||||||
|
std::vector<VkVertexInputAttributeDescription> &&attrs) : PerInstanceVertexBuffer(size, std::move(attrs)) {
|
||||||
|
const uint32_t totalSize = size * instances;
|
||||||
|
create(totalSize, false, false);
|
||||||
|
|
||||||
|
const BulkCopyMeta copy { totalSize, { {data, totalSize, 0} } };
|
||||||
|
const StagingBuffer staging(copy);
|
||||||
|
staging.copy(getBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicPerInstanceBuffer::copyToDevice(const void *data, uint32_t instances) {
|
||||||
|
const uint32_t totalSize = getSize() * instances;
|
||||||
|
const BulkCopyMeta copy { totalSize, { { data, totalSize, 0 } } };
|
||||||
|
resize(totalSize);
|
||||||
|
executeBulkCopy(get(), copy.metas);
|
||||||
|
}
|
||||||
|
|
||||||
|
UniformBuffer::UniformBuffer(size_t chunkSize, int chunks) : DataBuffer(), chunkSize(chunkSize), numChunks(chunks) {
|
||||||
|
const VkDeviceSize alignment = VulkanModule::getInstance()->getDevice()->limits.minUniformBufferOffsetAlignment;
|
||||||
|
chunkLength = (chunkSize + alignment - 1) / alignment * alignment;
|
||||||
|
|
||||||
|
data = new char[chunkSize * numChunks];
|
||||||
|
setBuffer(VkTools::createGPUBuffer(chunkLength * numChunks, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VulkanModule::getInstance()->getDevice()->logical, VulkanModule::getInstance()->getDevice()->physical, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UniformBuffer::upload(int index) const {
|
||||||
|
checkIndex(index);
|
||||||
|
const VkDeviceSize srcOffset = chunkSize * index;
|
||||||
|
const VkDeviceSize dstOffset = chunkLength * index;
|
||||||
|
|
||||||
|
// TODO: dstoffset?
|
||||||
|
executeBulkCopy(get(), { { data + srcOffset, chunkSize, 0 } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
void UniformBuffer::upload(int index, VkDeviceSize dataSize, VkDeviceSize start) const {
|
||||||
|
checkIndex(index);
|
||||||
|
const VkDeviceSize srcOffset = chunkSize * index + start;
|
||||||
|
const VkDeviceSize dstOffset = chunkLength * index + start;
|
||||||
|
|
||||||
|
// TODO: dstoffset?
|
||||||
|
executeBulkCopy(get(), { { data + srcOffset, dataSize, 0 } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDescriptorBufferInfo UniformBuffer::getDescriptorInfo(int index) const {
|
||||||
|
checkIndex(index);
|
||||||
|
return VkDescriptorBufferInfo { getBuffer(), chunkLength * index, chunkSize};
|
||||||
|
}
|
||||||
|
|
||||||
|
void UniformBuffer::checkIndex(int index) const {
|
||||||
|
if (index > numChunks)
|
||||||
|
throw std::runtime_error("Attempting to access uniform chunk " + std::to_string(index) + " out of range.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PushConstant::PushConstant(size_t size, int numFrames) : sizePerFrame(static_cast<uint32_t>(size)), numFrames(numFrames) {
|
||||||
|
if (size > 128)
|
||||||
|
throw std::runtime_error("Attempting to push constant of size " + std::to_string(size) + ", max ix 128.");
|
||||||
|
|
||||||
|
data = new char[size * numFrames];
|
||||||
|
}
|
||||||
|
|
||||||
|
void PushConstant::upload(const VkCommandBuffer &commands, const VkPipelineLayout &pipelineLayout, int frame,
|
||||||
|
uint32_t offset, VkShaderStageFlags stage) const {
|
||||||
|
checkIndex(frame);
|
||||||
|
void* data = getData<void>(frame);
|
||||||
|
vkCmdPushConstants(commands, pipelineLayout, stage, offset, sizePerFrame, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PushConstant::checkIndex(int index) const {
|
||||||
|
if (index > numFrames)
|
||||||
|
throw std::runtime_error("Attempting to access push constant for frame " + std::to_string(index) + " out of range.");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
#include "vlkx/vulkan/abstraction/Commands.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
vlkx::CommandBuffer::CommandBuffer() {
|
||||||
|
dev = VulkanModule::getInstance()->getDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandPool createPool(vlkx::Queue queue, bool shortLived) {
|
||||||
|
VkCommandPool pool;
|
||||||
|
VkCommandPoolCreateInfo poolCreateInfo = {};
|
||||||
|
poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||||
|
poolCreateInfo.queueFamilyIndex = queue.queueIndex;
|
||||||
|
poolCreateInfo.flags = shortLived ? VK_COMMAND_POOL_CREATE_TRANSIENT_BIT : VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||||
|
|
||||||
|
// Create the pool
|
||||||
|
if (vkCreateCommandPool(VulkanModule::getInstance()->getDevice()->logical, &poolCreateInfo, nullptr, &pool) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to allocate a temporary command pool");
|
||||||
|
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkCommandBuffer> allocateBuffers(const VkCommandPool& pool, uint32_t amount) {
|
||||||
|
const VkCommandBufferAllocateInfo allocate {
|
||||||
|
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||||
|
nullptr, pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, amount
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<VkCommandBuffer> buffers(amount);
|
||||||
|
if (vkAllocateCommandBuffers(VulkanModule::getInstance()->getDevice()->logical, &allocate, buffers.data()) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to allocate command buffers");
|
||||||
|
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recordCommands(const VkCommandBuffer& commands, VkCommandBufferUsageFlags flags, const vlkx::CommandBuffer::Command& record) {
|
||||||
|
const VkCommandBufferBeginInfo begin {
|
||||||
|
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, flags, nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vkBeginCommandBuffer(commands, &begin) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to begin listening on command buffer");
|
||||||
|
if (record != nullptr)
|
||||||
|
record(commands);
|
||||||
|
if (vkEndCommandBuffer(commands) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to end listening on command buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<VkResult> parseResult(VkResult result) {
|
||||||
|
switch(result) {
|
||||||
|
case VK_ERROR_OUT_OF_DATE_KHR: return result;
|
||||||
|
case VK_SUCCESS:
|
||||||
|
case VK_SUBOPTIMAL_KHR:
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Command buffer returned unknown result " + std::to_string(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vlkx::ImmediateCommand::ImmediateCommand(Queue queue) : queue(queue) {
|
||||||
|
const auto pool = createPool(queue, true);
|
||||||
|
setPool(pool);
|
||||||
|
commands = allocateBuffers(pool, 1)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void vlkx::ImmediateCommand::run(const vlkx::CommandBuffer::Command &cmd) {
|
||||||
|
recordCommands(commands, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, cmd);
|
||||||
|
const VkSubmitInfo submit {
|
||||||
|
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||||
|
nullptr, 0, nullptr, nullptr, 1,
|
||||||
|
&commands, 0, nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
vkQueueSubmit(queue.queue, 1, &submit, VK_NULL_HANDLE);
|
||||||
|
vkQueueWaitIdle(queue.queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
vlkx::RenderCommand::RenderCommand(int frames) {
|
||||||
|
VulkanDevice* dev = VulkanModule::getInstance()->getDevice();
|
||||||
|
|
||||||
|
// Create semaphores for render events
|
||||||
|
VkSemaphoreCreateInfo semaphoreInfo = {};
|
||||||
|
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||||
|
vkCreateSemaphore(dev->logical, &semaphoreInfo, nullptr, &newImageSem);
|
||||||
|
vkCreateSemaphore(dev->logical, &semaphoreInfo, nullptr, &renderDoneSem);
|
||||||
|
|
||||||
|
// Create fences for the frames
|
||||||
|
inFlight.resize(frames);
|
||||||
|
VkFenceCreateInfo fenceInfo = {};
|
||||||
|
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||||
|
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < frames; i++) {
|
||||||
|
if (vkCreateFence(dev->logical, &fenceInfo, nullptr, &inFlight[i]) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create fence for a frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pool = createPool({ dev->graphicsQueue, dev->queueData.graphics }, false);
|
||||||
|
setPool(pool);
|
||||||
|
commands = allocateBuffers(pool, static_cast<uint32_t>(frames));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<VkResult> vlkx::RenderCommand::execute(int frame, const VkSwapchainKHR &swapchain,
|
||||||
|
const vlkx::RenderCommand::Update &update,
|
||||||
|
const vlkx::RenderCommand::Command &cmd) {
|
||||||
|
|
||||||
|
const VkDevice& logical = VulkanModule::getInstance()->getDevice()->logical;
|
||||||
|
|
||||||
|
vkWaitForFences(logical, 1, &inFlight[imageIndex], VK_TRUE,
|
||||||
|
std::numeric_limits<uint64_t>::max()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (update != nullptr)
|
||||||
|
update(frame);
|
||||||
|
|
||||||
|
uint32_t nextFrame;
|
||||||
|
// Prepare for a new frame to start
|
||||||
|
const auto result = parseResult(vkAcquireNextImageKHR(logical, swapchain,
|
||||||
|
std::numeric_limits<uint64_t>::max(), newImageSem, VK_NULL_HANDLE, &nextFrame
|
||||||
|
));
|
||||||
|
|
||||||
|
if (result.has_value())
|
||||||
|
return result;
|
||||||
|
|
||||||
|
recordCommands(commands[imageIndex], VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, [&cmd, nextFrame](const VkCommandBuffer& buffer) {
|
||||||
|
cmd(buffer, nextFrame);
|
||||||
|
});
|
||||||
|
|
||||||
|
constexpr VkPipelineStageFlags stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
const VkSubmitInfo submit {
|
||||||
|
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||||
|
nullptr, 1, &newImageSem, &stage, 1, &commands[imageIndex], 1, &renderDoneSem
|
||||||
|
};
|
||||||
|
|
||||||
|
vkResetFences(logical, 1, &inFlight[imageIndex]);
|
||||||
|
|
||||||
|
if (VkResult res = vkQueueSubmit(VulkanModule::getInstance()->getDevice()->graphicsQueue, 1, &submit, inFlight[imageIndex]); res != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Failed to submit commands: " + std::to_string(res));
|
||||||
|
|
||||||
|
const VkPresentInfoKHR present {
|
||||||
|
VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||||
|
nullptr, 1, &renderDoneSem, 1, &swapchain, &nextFrame, nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
return parseResult(vkQueuePresentKHR(VulkanModule::getInstance()->getDevice()->presentationQueue, &present));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<VkResult> vlkx::RenderCommand::executeSimple(int frame,
|
||||||
|
const vlkx::RenderCommand::Update &update,
|
||||||
|
const vlkx::RenderCommand::Command &cmd) {
|
||||||
|
if (update != nullptr)
|
||||||
|
update(frame);
|
||||||
|
|
||||||
|
const VkDevice& logical = VulkanModule::getInstance()->getDevice()->logical;
|
||||||
|
|
||||||
|
vkWaitForFences(logical, 1, &inFlight[imageIndex], VK_TRUE,
|
||||||
|
std::numeric_limits<uint64_t>::max()
|
||||||
|
);
|
||||||
|
|
||||||
|
recordCommands(commands[imageIndex], VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, [&cmd](const VkCommandBuffer& buffer) {
|
||||||
|
cmd(buffer, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
vkResetFences(logical, 1, &inFlight[imageIndex]);
|
||||||
|
|
||||||
|
constexpr VkPipelineStageFlags stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
const VkSubmitInfo submit {
|
||||||
|
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||||
|
nullptr, 0, VK_NULL_HANDLE, &stage, 1, &commands[imageIndex], 0, VK_NULL_HANDLE
|
||||||
|
};
|
||||||
|
|
||||||
|
return std::make_optional(vkQueueSubmit(VulkanModule::getInstance()->getDevice()->graphicsQueue, 1, &submit, inFlight[imageIndex]));
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
#include "vlkx/vulkan/abstraction/Descriptor.h"
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
using namespace vlkx;
|
||||||
|
// Returns 'pointer', assuming 'ExpectedType' and 'ActualType' are the same.
|
||||||
|
template <typename ExpectedType, typename ActualType>
|
||||||
|
inline const ExpectedType* getPtr(const ActualType* pointer, std::true_type) {
|
||||||
|
return pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns nullptr, assuming 'ExpectedType' and 'ActualType' are different.
|
||||||
|
template <typename ExpectedType, typename ActualType>
|
||||||
|
inline const ExpectedType* getPtr(const ActualType* pointer, std::false_type) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ExpectedType, typename ValueType>
|
||||||
|
const ExpectedType* getPointer(const std::vector<ValueType>& container) {
|
||||||
|
return getPtr<ExpectedType, ValueType>(container.data(), std::is_same<ExpectedType, ValueType>());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VkDescriptorPool createPool(std::vector<Descriptor::Meta> metas) {
|
||||||
|
std::map<VkDescriptorType, uint32_t> sizes;
|
||||||
|
for (const auto& meta : metas) {
|
||||||
|
uint32_t length = 0;
|
||||||
|
for (const auto& binding : meta.bindings)
|
||||||
|
length += binding.length;
|
||||||
|
sizes[meta.type] += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkDescriptorPoolSize> poolSizes;
|
||||||
|
for (const auto& pair : sizes)
|
||||||
|
poolSizes.push_back({ pair.first, pair.second });
|
||||||
|
|
||||||
|
const VkDescriptorPoolCreateInfo create {
|
||||||
|
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||||
|
nullptr, 0,
|
||||||
|
1, static_cast<uint32_t>(poolSizes.size()), poolSizes.data()
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDescriptorPool pool;
|
||||||
|
if (vkCreateDescriptorPool(VulkanModule::getInstance()->getDevice()->logical, &create, nullptr, &pool) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create Descriptor Pool");
|
||||||
|
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDescriptorSetLayout createLayout(std::vector<Descriptor::Meta> metas, bool dynamic) {
|
||||||
|
size_t bindings = 0;
|
||||||
|
for (const auto& meta : metas)
|
||||||
|
bindings += meta.bindings.size();
|
||||||
|
|
||||||
|
std::vector<VkDescriptorSetLayoutBinding> layoutBindings;
|
||||||
|
layoutBindings.reserve(bindings);
|
||||||
|
for (const auto& meta : metas)
|
||||||
|
for (const auto& binding : meta.bindings)
|
||||||
|
layoutBindings.push_back({ binding.bindPoint, meta.type, binding.length, meta.stage, nullptr });
|
||||||
|
|
||||||
|
const VkDescriptorSetLayoutCreateInfo create {
|
||||||
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||||
|
nullptr, static_cast<VkDescriptorSetLayoutCreateFlags>(dynamic ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR : 0),
|
||||||
|
static_cast<uint32_t>(layoutBindings.size()), layoutBindings.data()
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDescriptorSetLayout layout;
|
||||||
|
if (vkCreateDescriptorSetLayout(VulkanModule::getInstance()->getDevice()->logical, &create, nullptr, &layout) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to create Descriptor Set Layout");
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDescriptorSet allocateSet(const VkDescriptorPool& pool, const VkDescriptorSetLayout& layout) {
|
||||||
|
const VkDescriptorSetAllocateInfo allocate {
|
||||||
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||||
|
nullptr, pool, 1, &layout
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDescriptorSet set;
|
||||||
|
if (vkAllocateDescriptorSets(VulkanModule::getInstance()->getDevice()->logical, &allocate, &set) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("Unable to allocate descriptor set");
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
std::vector<VkWriteDescriptorSet> createWrites(const VkDescriptorSet& set, VkDescriptorType type, const std::map<uint32_t, std::vector<Type>>& map) {
|
||||||
|
|
||||||
|
std::vector<VkWriteDescriptorSet> sets;
|
||||||
|
sets.reserve(map.size());
|
||||||
|
|
||||||
|
for (const auto& pair : map) {
|
||||||
|
const auto& info = pair.second;
|
||||||
|
sets.push_back(VkWriteDescriptorSet {
|
||||||
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||||
|
nullptr, set, pair.first, 0,
|
||||||
|
static_cast<uint32_t>(info.size()), type,
|
||||||
|
getPointer<VkDescriptorImageInfo>(info),
|
||||||
|
getPointer<VkDescriptorBufferInfo>(info),
|
||||||
|
getPointer<VkBufferView>(info)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return sets;
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticDescriptor::StaticDescriptor(std::vector<Meta> metas) : Descriptor() {
|
||||||
|
pool = createPool(metas);
|
||||||
|
const auto layout = createLayout(metas, false);
|
||||||
|
setLayout(layout);
|
||||||
|
set = allocateSet(pool, layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
const StaticDescriptor& StaticDescriptor::buffers(VkDescriptorType type, const BufferInfos &infos) const {
|
||||||
|
return updateSet(createWrites(set, type, infos));
|
||||||
|
}
|
||||||
|
|
||||||
|
const StaticDescriptor& StaticDescriptor::images(VkDescriptorType type, const ImageInfos &infos) const {
|
||||||
|
return updateSet(createWrites(set, type, infos));
|
||||||
|
}
|
||||||
|
|
||||||
|
const StaticDescriptor& StaticDescriptor::updateSet(const std::vector<VkWriteDescriptorSet> &write) const {
|
||||||
|
vkUpdateDescriptorSets(VulkanModule::getInstance()->getDevice()->logical, write.size(), write.data(), 0, nullptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticDescriptor::bind(const VkCommandBuffer &commands, const VkPipelineLayout &layout,
|
||||||
|
VkPipelineBindPoint bindPoint) const {
|
||||||
|
vkCmdBindDescriptorSets(commands, bindPoint, layout, 0, 1, &set, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,439 @@
|
||||||
|
#include "vlkx/vulkan/abstraction/Image.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <fstream>
|
||||||
|
#include <utility>
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
#include "shadow/util/File.h"
|
||||||
|
|
||||||
|
namespace vlkx {
|
||||||
|
struct ImageConfig {
|
||||||
|
explicit ImageConfig(bool readable = false) {
|
||||||
|
if (readable) {
|
||||||
|
tiling = VK_IMAGE_TILING_LINEAR;
|
||||||
|
layout = VK_IMAGE_LAYOUT_PREINITIALIZED;
|
||||||
|
} else {
|
||||||
|
tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||||
|
layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t mipping = 1;
|
||||||
|
uint32_t layers = 1;
|
||||||
|
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
VkImageTiling tiling;
|
||||||
|
VkImageLayout layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ImageData {
|
||||||
|
ImageDescriptor::Dimension dimensions;
|
||||||
|
const char* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageData loadImage(const std::string& path, int wantedChannels) {
|
||||||
|
shadowutil::FileData* data = shadowutil::loadFile(path);
|
||||||
|
int width, height, channels;
|
||||||
|
|
||||||
|
stbi_uc* stbData = stbi_load_from_memory(reinterpret_cast<const stbi_uc *>(data->data.data()), data->size, &width, &height, &channels, wantedChannels);
|
||||||
|
if (stbData == nullptr)
|
||||||
|
throw std::runtime_error("Unable to read image file " + std::string(path));
|
||||||
|
|
||||||
|
switch (channels) {
|
||||||
|
case 1:
|
||||||
|
case 4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: {
|
||||||
|
stbi_image_free(stbData);
|
||||||
|
stbData = stbi_load_from_memory(reinterpret_cast<const stbi_uc *>(data->data.data()), data->size, &width, &height, &channels, STBI_rgb_alpha);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Trying to load image with unsupported number of channels: " + std::to_string(channels));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {{ static_cast<uint32_t>(width), static_cast<uint32_t>(height), static_cast<uint32_t>(channels) },reinterpret_cast<const char *>(stbData) };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<VkFormat> findFormatWith(const std::vector<VkFormat>& formats, VkFormatFeatureFlags feature) {
|
||||||
|
for (const auto format : formats) {
|
||||||
|
VkFormatProperties props;
|
||||||
|
vkGetPhysicalDeviceFormatProperties(VulkanModule::getInstance()->getDevice()->physical, format, &props);
|
||||||
|
if ((props.optimalTilingFeatures & feature) == feature) return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFormat findFormatForChannels(int channels, const std::vector<ImageUsage>& usages, bool highPrecision = false) {
|
||||||
|
switch (channels) {
|
||||||
|
case 1: {
|
||||||
|
VkFormat best, alternative;
|
||||||
|
if (highPrecision) {
|
||||||
|
best = VK_FORMAT_R16_SFLOAT;
|
||||||
|
alternative = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||||
|
} else {
|
||||||
|
best = VK_FORMAT_R8_UNORM;
|
||||||
|
alternative = VK_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (findFormatWith({ best }, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT).has_value())
|
||||||
|
return best;
|
||||||
|
else
|
||||||
|
return alternative;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
if (highPrecision)
|
||||||
|
return VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||||
|
else
|
||||||
|
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Attempting to find format for invalid channels (RGB images have 4 channels!)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFormat findFormatForDepthStencil() {
|
||||||
|
const auto format = findFormatWith({ VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
|
||||||
|
if (!format.has_value())
|
||||||
|
throw std::runtime_error("Unable to find a format for a depth stencil image.");
|
||||||
|
return format.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSampleCountFlagBits getMaxSamples(VkSampleCountFlags samples) {
|
||||||
|
for (const auto count : { VK_SAMPLE_COUNT_64_BIT, VK_SAMPLE_COUNT_32_BIT, VK_SAMPLE_COUNT_16_BIT, VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT, VK_SAMPLE_COUNT_1_BIT }) {
|
||||||
|
if (samples & count)
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error("Multisampling isn't supported?");
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::Buffer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDescriptor Image::loadCubeFromDisk(const std::string& directory, const std::array<std::string, 6> &files,
|
||||||
|
bool flipY) {
|
||||||
|
stbi_set_flip_vertically_on_load(flipY);
|
||||||
|
auto firstImage = loadImage(directory + "/" + files[0], STBI_default);
|
||||||
|
const ImageDescriptor::Dimension& dim = firstImage.dimensions;
|
||||||
|
char* data = new char[dim.getSize() * 6]; // TODO: Figure out how to make this delete
|
||||||
|
memcpy(data, firstImage.data, dim.getSize());
|
||||||
|
for (size_t i = 1; i < 6; i++) {
|
||||||
|
auto image = loadImage(directory + "/" + files[1], STBI_default);
|
||||||
|
if (!(image.dimensions.width == dim.width && image.dimensions.height == dim.height && image.dimensions.channels == dim.channels))
|
||||||
|
throw std::runtime_error("Image " + std::to_string(i) + "(" + directory + "/" + files[i] + ") has different dimensions from the first image.");
|
||||||
|
|
||||||
|
memcpy(data + i * dim.getSize(), image.data, dim.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_set_flip_vertically_on_load(false);
|
||||||
|
return { ImageDescriptor::Type::Cubemap, firstImage.dimensions, data };
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDescriptor Image::loadSingleFromDisk(std::string path, bool flipY) {
|
||||||
|
stbi_set_flip_vertically_on_load(flipY);
|
||||||
|
auto image = loadImage(std::move(path), STBI_default);
|
||||||
|
stbi_set_flip_vertically_on_load(false);
|
||||||
|
|
||||||
|
return { ImageDescriptor::Type::Single, image.dimensions, image.data };
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureImage::Meta createTextureMeta(const ImageDescriptor& image, const std::vector<ImageUsage>& usages) {
|
||||||
|
return TextureImage::Meta {
|
||||||
|
image.getData(), usages,
|
||||||
|
findFormatForChannels(image.getChannels(), usages),
|
||||||
|
image.getWidth(), image.getHeight(), image.getChannels(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkTools::ManagedImage createImage(const ImageConfig& config, VkImageCreateFlags flags, VkFormat format, const VkExtent3D& extent, VkImageUsageFlags usage) {
|
||||||
|
const auto device = VulkanModule::getInstance()->getDevice();
|
||||||
|
|
||||||
|
uint32_t graphicsQueue = (uint32_t) device->getQueues().graphics;
|
||||||
|
VkImageCreateInfo info {
|
||||||
|
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, nullptr,
|
||||||
|
flags, VK_IMAGE_TYPE_2D, format,
|
||||||
|
extent, config.mipping, config.layers, config.samples, config.tiling,
|
||||||
|
usage, VK_SHARING_MODE_EXCLUSIVE,
|
||||||
|
1, &graphicsQueue, config.layout
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare the managed image
|
||||||
|
VkTools::ManagedImage image {};
|
||||||
|
|
||||||
|
// Set up image allocation
|
||||||
|
VmaAllocationCreateInfo allocateInfo = {};
|
||||||
|
allocateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||||
|
|
||||||
|
// Allocate + create the image
|
||||||
|
vmaCreateImage(VulkanModule::getInstance()->getAllocator(), &info, &allocateInfo, &image.image, &image.allocation, nullptr);
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForBarrier(const VkCommandBuffer& commands, const VkImageMemoryBarrier& barrier, const std::array<VkPipelineStageFlags, 2>& stages) {
|
||||||
|
vkCmdPipelineBarrier(commands, stages[0], stages[1], 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
void transitionImage(const VkImage& image, const ImageConfig& config, VkImageAspectFlags aspect, const std::array<VkImageLayout, 2>& layouts, const std::array<VkAccessFlags, 2>& access, const std::array<VkPipelineStageFlags, 2>& stages) {
|
||||||
|
VkTools::immediateExecute([&](const VkCommandBuffer& commands) {
|
||||||
|
waitForBarrier(commands,
|
||||||
|
{
|
||||||
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
|
nullptr,
|
||||||
|
access[0], access[1],
|
||||||
|
layouts[0], layouts[1],
|
||||||
|
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
image,
|
||||||
|
{ aspect, 0, config.mipping, 0, config.layers }
|
||||||
|
}, stages);
|
||||||
|
}, VulkanModule::getInstance()->getDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VkOffset3D convertExtent(const VkExtent2D& extent) {
|
||||||
|
return VkOffset3D { static_cast<int32_t>(extent.width), static_cast<int32_t>(extent.height), 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
inline VkExtent3D expandExtent(const VkExtent2D& extent) {
|
||||||
|
return VkExtent3D { extent.width, extent.height, 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkExtent2D> getExtentForMipmaps(const VkExtent3D& extent) {
|
||||||
|
const int largest = std::max(extent.width, extent.height);
|
||||||
|
const int mipping = std::floor(std::log2(largest));
|
||||||
|
std::vector<VkExtent2D> extents(mipping);
|
||||||
|
VkExtent2D imageExt { extent.width, extent.height };
|
||||||
|
for (size_t level = 0; level < mipping; ++level) {
|
||||||
|
imageExt.width = imageExt.width > 1 ? imageExt.width / 2 : 1;
|
||||||
|
imageExt.height = imageExt.height > 1 ? imageExt.height / 2 : 1;
|
||||||
|
extents[level] = imageExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extents;
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateMipmaps(const VkImage& image, VkFormat format, const VkExtent3D& extent, const std::vector<VkExtent2D>& mipExtents) {
|
||||||
|
VkFormatProperties props;
|
||||||
|
vkGetPhysicalDeviceFormatProperties(VulkanModule::getInstance()->getDevice()->physical, format, &props);
|
||||||
|
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT))
|
||||||
|
throw std::runtime_error("Attempting to create Mipmaps for an image format that does not support blitting");
|
||||||
|
|
||||||
|
uint32_t destLevel = 1;
|
||||||
|
VkExtent2D previousExt { extent.width, extent.height };
|
||||||
|
|
||||||
|
VkTools::immediateExecute([&](const VkCommandBuffer& commands) {
|
||||||
|
// Blit the new images into place
|
||||||
|
for (const auto &ext : mipExtents) {
|
||||||
|
const uint32_t sourceLevel = destLevel - 1;
|
||||||
|
VkImageMemoryBarrier barrier {
|
||||||
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
|
nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
|
0, 0, image, { VK_IMAGE_ASPECT_COLOR_BIT, sourceLevel, 1, 0, 1 }
|
||||||
|
};
|
||||||
|
|
||||||
|
waitForBarrier(commands, barrier, {VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT});
|
||||||
|
|
||||||
|
const VkImageBlit blit {
|
||||||
|
{VK_IMAGE_ASPECT_COLOR_BIT, sourceLevel, 0, 1},
|
||||||
|
{{0, 0, 0}, convertExtent(previousExt)},
|
||||||
|
{VK_IMAGE_ASPECT_COLOR_BIT, destLevel, 0, 1},
|
||||||
|
{{0, 0, 0}, convertExtent(ext)}
|
||||||
|
};
|
||||||
|
vkCmdBlitImage(commands, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR);
|
||||||
|
|
||||||
|
++destLevel;
|
||||||
|
previousExt = ext;
|
||||||
|
}
|
||||||
|
}, VulkanModule::getInstance()->getDevice());
|
||||||
|
|
||||||
|
VkTools::immediateExecute([&](const VkCommandBuffer& commands) {
|
||||||
|
// Convert all images to shader read only so we can sample them
|
||||||
|
for (uint32_t level = 0; level < mipExtents.size() + 1; ++level) {
|
||||||
|
VkImageMemoryBarrier barrier {
|
||||||
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
|
nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
||||||
|
level == mipExtents.size() ? VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
0, 0, image, { VK_IMAGE_ASPECT_COLOR_BIT, level, 1, 0, 1 }
|
||||||
|
};
|
||||||
|
|
||||||
|
waitForBarrier(commands, barrier, { VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT });
|
||||||
|
}
|
||||||
|
}, VulkanModule::getInstance()->getDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Image(const VkExtent2D &ext, VkFormat form) : extent(ext), format(form) {
|
||||||
|
dev = VulkanModule::getInstance()->getDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageStagingBuffer::copy(const VkImage &target, const VkExtent3D &extent, uint32_t layers) const {
|
||||||
|
VkTools::immediateExecute([&](const VkCommandBuffer& commands) {
|
||||||
|
const VkBufferImageCopy copyData { 0, 0, 0, { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, layers }, { 0, 0, 0 }, extent };
|
||||||
|
vkCmdCopyBufferToImage(commands, getBuffer(), target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Data);
|
||||||
|
}, VulkanModule::getInstance()->getDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageSampler::ImageSampler(int mipLevels, const vlkx::ImageSampler::Config &config)
|
||||||
|
: sampler(VkTools::createSampler(config.filter, config.mode, mipLevels, VulkanModule::getInstance()->getDevice()->logical)) {
|
||||||
|
dev = VulkanModule::getInstance()->getDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::BulkCopyMeta TextureImage::Meta::getCopyMeta() const {
|
||||||
|
const VkDeviceSize singleSize = width * height * channels;
|
||||||
|
const VkDeviceSize totalSize = singleSize * data.size();
|
||||||
|
std::vector<Buffer::CopyMeta> copy(data.size());
|
||||||
|
|
||||||
|
// If we're making a framebuffer, we have no data to copy in.
|
||||||
|
if (data[0] == nullptr)
|
||||||
|
return { totalSize, {} };
|
||||||
|
|
||||||
|
for (size_t i = 0; i < copy.size(); ++i) {
|
||||||
|
copy[i] = { data[i], singleSize, singleSize * i };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { totalSize, std::move(copy) };
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureImage::TextureImage(bool mipmapping, const ImageSampler::Config &samplerConfig, const vlkx::TextureImage::Meta &meta)
|
||||||
|
: Image(meta.getExtent(), meta.format), buffer(mipmapping, meta), sampler(buffer.getMipping(), samplerConfig) {
|
||||||
|
|
||||||
|
setView(VkTools::createImageView(buffer.getImage(), format, VK_IMAGE_ASPECT_COLOR_BIT, buffer.getMipping(), meta.data.size(), VulkanModule::getInstance()->getDevice()->logical));
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureImage::TextureImage(bool mipmapping, const ImageDescriptor &image, const std::vector<ImageUsage>& usages, const ImageSampler::Config &config)
|
||||||
|
: TextureImage(mipmapping, config, createTextureMeta(image, usages))
|
||||||
|
{}
|
||||||
|
|
||||||
|
TextureImage::TextureBuffer::TextureBuffer(bool mipmaps, const vlkx::TextureImage::Meta &meta) : ImageBuffer() {
|
||||||
|
const VkExtent3D extent = expandExtent(meta.getExtent());
|
||||||
|
const auto layers = meta.data.size();
|
||||||
|
if (layers != 1 && layers != 6)
|
||||||
|
throw std::runtime_error("Attempting to allocate a texture buffer for an invalid number of textures; only single textures and cubemap textures are supported.");
|
||||||
|
|
||||||
|
ImageConfig config;
|
||||||
|
config.layers = meta.data.size();
|
||||||
|
|
||||||
|
std::vector<VkExtent2D> mipExtents;
|
||||||
|
if (mipmaps) {
|
||||||
|
mipExtents = getExtentForMipmaps(extent);
|
||||||
|
mipLevels = mipExtents.size() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.mipping = mipLevels;
|
||||||
|
|
||||||
|
VkImageCreateFlags createFlags {};
|
||||||
|
if (layers == 6)
|
||||||
|
createFlags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
||||||
|
|
||||||
|
auto usage = ImageUsage::getFlagsForUsage(meta.usages);
|
||||||
|
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
|
usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
|
if (mipmaps) usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
|
|
||||||
|
setImage(createImage(config, createFlags, meta.format, extent, usage));
|
||||||
|
|
||||||
|
transitionImage(getImage(), config, VK_IMAGE_ASPECT_COLOR_BIT, { VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL }, { 0, VK_ACCESS_TRANSFER_WRITE_BIT }, { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT });
|
||||||
|
|
||||||
|
const ImageStagingBuffer staging(meta.getCopyMeta());
|
||||||
|
staging.copy(getImage(), extent, config.layers);
|
||||||
|
|
||||||
|
if (mipmaps) {
|
||||||
|
generateMipmaps(getImage(), meta.format, extent, mipExtents);
|
||||||
|
} else {
|
||||||
|
transitionImage(getImage(), config, VK_IMAGE_ASPECT_COLOR_BIT, { VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }, { VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT }, { VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RefCountedTexture::ReferenceCounter RefCountedTexture::get(const vlkx::RefCountedTexture::ImageLocation &location, const std::vector<ImageUsage>& usages, const ImageSampler::Config &config) {
|
||||||
|
bool mips;
|
||||||
|
const std::string* ident;
|
||||||
|
std::unique_ptr<ImageDescriptor> image;
|
||||||
|
|
||||||
|
if (const auto* singleTex = std::get_if<std::string>(&location); singleTex != nullptr) {
|
||||||
|
mips = true;
|
||||||
|
ident = singleTex;
|
||||||
|
image = std::make_unique<ImageDescriptor>(Image::loadSingleFromDisk(*singleTex, false));
|
||||||
|
} else if (const auto* cubeTex = std::get_if<CubemapLocation>(&location); cubeTex != nullptr) {
|
||||||
|
mips = false;
|
||||||
|
ident = &cubeTex->directory;
|
||||||
|
image = std::make_unique<ImageDescriptor>(Image::loadCubeFromDisk(cubeTex->directory, cubeTex->files, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReferenceCounter::get(*ident, mips, config, createTextureMeta(*image, usages));
|
||||||
|
}
|
||||||
|
|
||||||
|
DepthStencilImage::DepthStencilImage(const VkExtent2D &extent) : Image(extent, findFormatForDepthStencil()), buffer(extent, format) {
|
||||||
|
setView(VkTools::createImageView(getImage(), format, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 1, 1, VulkanModule::getInstance()->getDevice()->logical));
|
||||||
|
}
|
||||||
|
|
||||||
|
DepthStencilImage::DepthStencilBuffer::DepthStencilBuffer(const VkExtent2D &extent, VkFormat format) : ImageBuffer() {
|
||||||
|
setImage(createImage(ImageConfig {}, 0, format, expandExtent(extent), VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
SwapchainImage::SwapchainImage(const VkImage &image, const VkExtent2D &extent, VkFormat format) : Image(extent, format), image(image) {
|
||||||
|
setView(VkTools::createImageView(image, format, VK_IMAGE_ASPECT_COLOR_BIT, 1, 1, VulkanModule::getInstance()->getDevice()->logical));
|
||||||
|
managed = { image, nullptr };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Image> MultisampleImage::createColor(const vlkx::Image &targetImage, vlkx::MultisampleImage::Mode mode) {
|
||||||
|
return std::unique_ptr<Image>(new MultisampleImage(targetImage.getExtent(), targetImage.getFormat(), mode, MultisampleBuffer::Type::Color));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Image> MultisampleImage::createDepthStencilMS(const VkExtent2D &extent, vlkx::MultisampleImage::Mode mode) {
|
||||||
|
return std::unique_ptr<Image>(new MultisampleImage(extent, findFormatForDepthStencil(), mode, MultisampleBuffer::Type::DepthStencil));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Image> MultisampleImage::createDepthStencil(VkExtent2D &extent, std::optional<Mode> mode) {
|
||||||
|
if (mode.has_value())
|
||||||
|
return createDepthStencilMS(extent, mode.value());
|
||||||
|
else
|
||||||
|
return std::make_unique<DepthStencilImage>(extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultisampleImage::MultisampleImage(const VkExtent2D &extent, VkFormat format, vlkx::MultisampleImage::Mode mode,MultisampleBuffer::Type type)
|
||||||
|
: Image(extent, format), samples(chooseSamples(mode)), buffer(type, extent, format, samples) {
|
||||||
|
|
||||||
|
VkImageAspectFlags aspect;
|
||||||
|
switch (type) {
|
||||||
|
case MultisampleBuffer::Type::Color: aspect = VK_IMAGE_ASPECT_COLOR_BIT; break;
|
||||||
|
case MultisampleBuffer::Type::DepthStencil: aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; break;
|
||||||
|
}
|
||||||
|
setView(VkTools::createImageView(buffer.getImage(), format, aspect, 1, 1, VulkanModule::getInstance()->getDevice()->logical));
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSampleCountFlagBits MultisampleImage::chooseSamples(vlkx::MultisampleImage::Mode mode) {
|
||||||
|
VkPhysicalDeviceLimits limits;
|
||||||
|
VkPhysicalDeviceProperties props;
|
||||||
|
vkGetPhysicalDeviceProperties(VulkanModule::getInstance()->getDevice()->physical, &props);
|
||||||
|
limits = props.limits;
|
||||||
|
|
||||||
|
const VkSampleCountFlags sampleFlags = std::min({ limits.framebufferColorSampleCounts, limits.framebufferDepthSampleCounts, limits.framebufferStencilSampleCounts });
|
||||||
|
const VkSampleCountFlagBits maxSamples = getMaxSamples(sampleFlags);
|
||||||
|
switch (mode) {
|
||||||
|
case Mode::MostEfficient: return std::min(VK_SAMPLE_COUNT_4_BIT, maxSamples);
|
||||||
|
case Mode::Highest: return maxSamples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MultisampleImage::MultisampleBuffer::MultisampleBuffer(vlkx::MultisampleImage::MultisampleBuffer::Type type, const VkExtent2D &extent, VkFormat format, VkSampleCountFlagBits samples)
|
||||||
|
: ImageBuffer() {
|
||||||
|
|
||||||
|
VkImageUsageFlags usageFlags;
|
||||||
|
switch (type) {
|
||||||
|
case Type::Color: usageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; break;
|
||||||
|
case Type::DepthStencil: usageFlags = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageConfig config;
|
||||||
|
config.samples = samples;
|
||||||
|
|
||||||
|
setImage(createImage(config, 0, format, expandExtent(extent), usageFlags));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
namespace shadowutil {
|
||||||
|
|
||||||
|
struct FileData {
|
||||||
|
size_t size;
|
||||||
|
std::vector<char> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A testing stub; this should be deleted and wired into the asset system once that becomes ready.
|
||||||
|
FileData* loadFile(std::string path);
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace shadowutil {
|
||||||
|
|
||||||
|
// An object wrapper that behaves like smart pointers.
|
||||||
|
// References to this object are counted, and the object will be destructed when there are no references.
|
||||||
|
// This allows for automatic, safe and leak-free handling of all kinds of resources.
|
||||||
|
// The AutoRelease behaviour can be adjusted.
|
||||||
|
template <typename ObjectType>
|
||||||
|
class RefCounter {
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Preserves ObjectType instances in the current scope.
|
||||||
|
// Use like lock_guard; RAII allows precise scoping rules.
|
||||||
|
class AutoRelease {
|
||||||
|
public:
|
||||||
|
explicit AutoRelease() { RefCounter<ObjectType>::registerAutoRelease(); }
|
||||||
|
|
||||||
|
AutoRelease(const AutoRelease&) = delete;
|
||||||
|
AutoRelease& operator=(const AutoRelease&) = delete;
|
||||||
|
|
||||||
|
~AutoRelease() { RefCounter<ObjectType>::unregisterAutoRelease(); }
|
||||||
|
|
||||||
|
// AutoRelease can only exist on the stack.
|
||||||
|
void* operator new(size_t) = delete;
|
||||||
|
void* operator new[](size_t) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use to access a ref counted object.
|
||||||
|
// If exists, the object will be passed and reference counter increased.
|
||||||
|
// Otherwise, args will be used to create.
|
||||||
|
template<typename... Args>
|
||||||
|
static RefCounter get(const std::string& identifier, Args&&... args) {
|
||||||
|
auto iter = getMap().find(identifier);
|
||||||
|
if (iter == getMap().end()) {
|
||||||
|
const auto inserted = getMap().insert(
|
||||||
|
{
|
||||||
|
identifier,
|
||||||
|
typename ObjectPool::CountedObject {
|
||||||
|
std::make_unique<ObjectType>(std::forward<Args>(args)...), 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
iter = inserted.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& object = iter->second;
|
||||||
|
++object.references;
|
||||||
|
return RefCounter { identifier, object.obj.get() };
|
||||||
|
}
|
||||||
|
|
||||||
|
RefCounter(RefCounter&& other) noexcept {
|
||||||
|
identifier = std::move(other.identifier);
|
||||||
|
objectPtr = other.objectPtr;
|
||||||
|
other.identifier.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefCounter& operator=(RefCounter&& other) noexcept {
|
||||||
|
std::swap(identifier, other.identifier);
|
||||||
|
std::swap(objectPtr, other.objectPtr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we reach 0 references and there's no auto release system set up, destroy the object.
|
||||||
|
~RefCounter() {
|
||||||
|
if (identifier.empty()) return;
|
||||||
|
|
||||||
|
const auto iter = getMap().find(identifier);
|
||||||
|
if (--iter->second.references == 0 && objectPool.activePools == 0)
|
||||||
|
getMap().erase(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smart pointer emulation overloads.
|
||||||
|
const ObjectType* operator->() const { return objectPtr; }
|
||||||
|
const ObjectType& operator*() const { return *objectPtr; }
|
||||||
|
|
||||||
|
static bool hasAutoRelease() { return objectPool.activePools != 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The object pool that handles managing and counting objects.
|
||||||
|
struct ObjectPool {
|
||||||
|
struct CountedObject {
|
||||||
|
std::unique_ptr<ObjectType> obj;
|
||||||
|
size_t references;
|
||||||
|
};
|
||||||
|
|
||||||
|
using RefCountMap = std::map<std::string, CountedObject>;
|
||||||
|
|
||||||
|
RefCountMap refCountMap;
|
||||||
|
size_t activePools = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
RefCounter(std::string identifier, const ObjectType* obj) : identifier(std::move(identifier)), objectPtr(obj) {}
|
||||||
|
|
||||||
|
static void unregisterAutoRelease() {
|
||||||
|
if (--objectPool.activePools == 0) {
|
||||||
|
using CountedObject = typename ObjectPool::CountedObject;
|
||||||
|
static const auto removeZeroRef = [](const std::pair<const std::string, CountedObject> &pair) {
|
||||||
|
return pair.second.references == 0;
|
||||||
|
};
|
||||||
|
std::erase_if(getMap(), removeZeroRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerAutoRelease() { ++objectPool.activePools; }
|
||||||
|
|
||||||
|
static typename ObjectPool::RefCountMap& getMap() { return objectPool.refCountMap; }
|
||||||
|
|
||||||
|
static ObjectPool objectPool;
|
||||||
|
|
||||||
|
std::string identifier;
|
||||||
|
const ObjectType* objectPtr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ObjectType>
|
||||||
|
typename RefCounter<ObjectType>::ObjectPool RefCounter<ObjectType>::objectPool {};
|
||||||
|
}
|
7897
projs/shadow/shadow-engine/shadow-utility/inc/stb_image.h
Normal file
7897
projs/shadow/shadow-engine/shadow-utility/inc/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
26
projs/shadow/shadow-engine/shadow-utility/src/File.cpp
Normal file
26
projs/shadow/shadow-engine/shadow-utility/src/File.cpp
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#include <shadow/util/File.h>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
namespace shadowutil {
|
||||||
|
FileData* loadFile(std::string path) {
|
||||||
|
// Verify the file
|
||||||
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||||
|
|
||||||
|
if (!file.is_open())
|
||||||
|
throw std::runtime_error("Unable to open specified file: " + std::string(path));
|
||||||
|
|
||||||
|
auto* data = new FileData {};
|
||||||
|
|
||||||
|
// Read the file's size (opened with At The End, so just tellg the size)
|
||||||
|
size_t size = file.tellg();
|
||||||
|
data->data.resize(size);
|
||||||
|
data->size = size;
|
||||||
|
// Go to the front of the file
|
||||||
|
file.seekg(0);
|
||||||
|
// Read the file into the buffer
|
||||||
|
file.read(data->data.data(), size);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
#include "string-helpers.h"
|
#include "../inc/string-helpers.h"
|
||||||
|
|
||||||
std::vector<std::string> explode(const std::string& s, const char& c)
|
std::vector<std::string> explode(const std::string& s, const char& c)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,6 @@ add_executable(shadow-runtime ${SOURCES})
|
||||||
target_include_directories(shadow-runtime PRIVATE ${SDL2_INCLUDE_DIRS})
|
target_include_directories(shadow-runtime PRIVATE ${SDL2_INCLUDE_DIRS})
|
||||||
target_link_libraries(shadow-runtime PRIVATE SDL2::SDL2main PUBLIC shadow-engine)
|
target_link_libraries(shadow-runtime PRIVATE SDL2::SDL2main PUBLIC shadow-engine)
|
||||||
|
|
||||||
|
|
||||||
add_custom_command(TARGET shadow-runtime POST_BUILD
|
add_custom_command(TARGET shadow-runtime POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:shadow-runtime> $<TARGET_FILE_DIR:shadow-runtime>
|
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:shadow-runtime> $<TARGET_FILE_DIR:shadow-runtime>
|
||||||
COMMAND_EXPAND_LISTS
|
COMMAND_EXPAND_LISTS
|
||||||
|
|
|
@ -13,13 +13,17 @@ public:
|
||||||
|
|
||||||
void Init() override;
|
void Init() override;
|
||||||
|
|
||||||
void Update() override;
|
void Update(int frame) override;
|
||||||
|
|
||||||
void PreRender() override;
|
void PreRender() override;
|
||||||
|
|
||||||
void Render() override;
|
void Recreate() override;
|
||||||
|
|
||||||
void LateRender() override;
|
void OverlayRender() override;
|
||||||
|
|
||||||
|
void Render(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
|
void LateRender(VkCommandBuffer& commands, int frame) override;
|
||||||
|
|
||||||
void AfterFrameEnd() override;
|
void AfterFrameEnd() override;
|
||||||
|
|
||||||
|
|
|
@ -1,171 +1,92 @@
|
||||||
#include <GameModule.h>
|
#include <GameModule.h>
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include "vlkx/render/geometry/SingleRenderer.h"
|
|
||||||
#include "imgui_impl_vulkan.h"
|
#include "imgui_impl_vulkan.h"
|
||||||
#include "core/ShadowApplication.h"
|
|
||||||
#include "core/SDL2Module.h"
|
|
||||||
#include "imgui_impl_sdl.h"
|
#include "imgui_impl_sdl.h"
|
||||||
#include "core/Time.h"
|
#include "core/Time.h"
|
||||||
|
#include "vlkx/render/Camera.h"
|
||||||
|
#include "vlkx/vulkan/abstraction/Buffer.h"
|
||||||
|
#include "vlkx/render/render_pass/ScreenRenderPass.h"
|
||||||
|
#include "temp/model/Builder.h"
|
||||||
|
#include "core/ModuleManager.h"
|
||||||
|
|
||||||
#define CATCH(x) \
|
#define CATCH(x) \
|
||||||
try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); }
|
try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); }
|
||||||
|
|
||||||
// Create the renderer
|
|
||||||
SingleRenderer object;
|
|
||||||
|
|
||||||
// Create the camera
|
|
||||||
Camera camera;
|
|
||||||
|
|
||||||
SHObject_Base_Impl(GameModule)
|
SHObject_Base_Impl(GameModule)
|
||||||
|
|
||||||
void GameModule::PreInit() { spdlog::info("Game Module loading.."); }
|
struct Transformation {
|
||||||
|
alignas(sizeof(glm::mat4)) glm::mat4 proj_view_model;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<vlkx::PushConstant> trans_constant_;
|
||||||
|
std::unique_ptr<vlkxtemp::Model> cube_model_;
|
||||||
|
float aspectRatio;
|
||||||
|
|
||||||
|
void GameModule::PreInit() { }
|
||||||
|
|
||||||
void GameModule::Init() {
|
void GameModule::Init() {
|
||||||
|
spdlog::info("Game Module loading level..");
|
||||||
|
trans_constant_ = std::make_unique<vlkx::PushConstant>(
|
||||||
|
sizeof(Transformation), 2);
|
||||||
|
|
||||||
auto shApp = ShadowEngine::ShadowApplication::Get();
|
auto extent = ShadowEngine::ModuleManager::getInstance()->renderer->GetRenderExtent();
|
||||||
|
aspectRatio = (float) extent.width / extent.height;
|
||||||
|
|
||||||
ShadowEngine::ModuleManager &moduleManager = shApp.GetModuleManager();
|
/* Model */
|
||||||
|
cube_model_ = vlkxtemp::ModelBuilder {
|
||||||
|
"Walrus", 2, aspectRatio,
|
||||||
|
vlkxtemp::ModelBuilder::SingleMeshModel {"resources/walrus/walrus.obj", 1,
|
||||||
|
{{ vlkxtemp::ModelBuilder::TextureType::Diffuse, { { "resources/walrus/texture.png" } } } }
|
||||||
|
}}
|
||||||
|
.bindTextures(vlkxtemp::ModelBuilder::TextureType::Diffuse, 1)
|
||||||
|
.pushStage(VK_SHADER_STAGE_VERTEX_BIT)
|
||||||
|
.pushConstant(trans_constant_.get(), 0)
|
||||||
|
.shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/walrus/cube.vert.spv")
|
||||||
|
.shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/walrus/cube.frag.spv")
|
||||||
|
.build();
|
||||||
|
|
||||||
auto sdl2module = moduleManager.GetModuleByType<ShadowEngine::SDL2Module>();
|
Recreate();
|
||||||
|
|
||||||
CATCH(VulkanManager::getInstance()->initVulkan(sdl2module->_window->sdlWindowPtr);)
|
|
||||||
|
|
||||||
IMGUI_CHECKVERSION();
|
|
||||||
ImGui::CreateContext();
|
|
||||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
|
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
|
|
||||||
|
|
||||||
// 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(sdl2module->_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 = 0;
|
|
||||||
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;
|
|
||||||
ImGui_ImplVulkan_Init(&init_info, vk->getRenderPass()->pass);
|
|
||||||
|
|
||||||
// Upload Fonts
|
|
||||||
{
|
|
||||||
// Prepare to create a temporary command pool.
|
|
||||||
VkCommandPool pool;
|
|
||||||
VkCommandPoolCreateInfo poolCreateInfo = {};
|
|
||||||
poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
||||||
poolCreateInfo.queueFamilyIndex = vk->getDevice()->queueData.graphics;
|
|
||||||
poolCreateInfo.flags = 0;
|
|
||||||
|
|
||||||
// Create the pool
|
|
||||||
if (vkCreateCommandPool(vk->getDevice()->logical, &poolCreateInfo, nullptr, &pool) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("Unable to allocate a temporary command pool");
|
|
||||||
|
|
||||||
VkCommandBuffer buffer = VkTools::createTempCommandBuffer(pool, vk->getDevice()->logical);
|
|
||||||
|
|
||||||
ImGui_ImplVulkan_CreateFontsTexture(buffer);
|
|
||||||
|
|
||||||
VkSubmitInfo end_info = {};
|
|
||||||
end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
||||||
end_info.commandBufferCount = 1;
|
|
||||||
end_info.pCommandBuffers = &buffer;
|
|
||||||
|
|
||||||
VkTools::executeAndDeleteTempBuffer(buffer, pool, vk->getDevice()->graphicsQueue, vk->getDevice()->logical);
|
|
||||||
|
|
||||||
ImGui_ImplVulkan_DestroyFontUploadObjects();
|
|
||||||
}
|
|
||||||
|
|
||||||
CATCH(object.createSingleRenderer(Geo::MeshType::Cube, glm::vec3(-1, 0, -1), glm::vec3(0.5));)
|
|
||||||
camera.init(45, 1280, 720, 0.1, 10000);
|
|
||||||
camera.setPosition(glm::vec3(0, 0, 4));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameModule::Update() {
|
void GameModule::Recreate() {
|
||||||
object.setRotation(glm::rotate(object.getRotation(), (float) 0.5, glm::vec3(1, 0, 0)));
|
auto extent = ShadowEngine::ModuleManager::getInstance()->renderer->GetRenderExtent();
|
||||||
|
cube_model_->update(true, extent, VK_SAMPLE_COUNT_1_BIT, *VulkanModule::getInstance()->getRenderPass()->getPass(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameModule::PreRender() {
|
void GameModule::Update(int frame) {
|
||||||
ImGui_ImplVulkan_NewFrame();
|
const float elapsed_time = Time::timeSinceStart;
|
||||||
ImGui_ImplSDL2_NewFrame();
|
const glm::mat4 model = glm::rotate(glm::mat4{1.0f},
|
||||||
ImGui::NewFrame();
|
(elapsed_time / 1000 / 2) * glm::radians(90.0f),
|
||||||
|
glm::vec3{1.0f, 1.0f, 0.0f});
|
||||||
|
const glm::mat4 view = glm::lookAt(glm::vec3{3.0f}, glm::vec3{0.0f},
|
||||||
|
glm::vec3{0.0f, 0.0f, 1.0f});
|
||||||
|
const glm::mat4 proj = glm::perspective(
|
||||||
|
glm::radians(45.0f), aspectRatio,
|
||||||
|
0.1f, 100.0f);
|
||||||
|
*trans_constant_->getData<Transformation>(frame) = {proj * view * model};
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameModule::Render() {
|
void GameModule::Render(VkCommandBuffer& commands, int frame) {
|
||||||
|
cube_model_->draw(commands, frame, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameModule::OverlayRender() {
|
||||||
bool active = true;
|
bool active = true;
|
||||||
ImGui::Begin("Game module window", &active, ImGuiWindowFlags_MenuBar);
|
if (ImGui::Begin("Game module window", &active, ImGuiWindowFlags_MenuBar))
|
||||||
|
ImGui::Text("Such text from curle's branch");
|
||||||
ImGui::Text("Such teext from curle's branch");
|
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
CATCH(object.updateUniforms(camera);)
|
bool open = false;
|
||||||
CATCH(object.draw();)
|
ImGui::ShowDemoWindow(&open);
|
||||||
|
|
||||||
bool showDemo = true;
|
|
||||||
if (showDemo)
|
|
||||||
ImGui::ShowDemoWindow(&showDemo);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameModule::LateRender() {
|
|
||||||
ImGui::Render();
|
|
||||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
|
||||||
|
|
||||||
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), VulkanManager::getInstance()->getCurrentCommandBuffer());
|
|
||||||
|
|
||||||
// Update and Render additional Platform Windows
|
|
||||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
|
||||||
{
|
|
||||||
ImGui::UpdatePlatformWindows();
|
|
||||||
ImGui::RenderPlatformWindowsDefault();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameModule::AfterFrameEnd() {
|
void GameModule::AfterFrameEnd() {
|
||||||
Time::UpdateTime();
|
Time::UpdateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameModule::Destroy() {
|
void GameModule::LateRender(VkCommandBuffer& commands, int frame) {}
|
||||||
ImGui_ImplVulkan_Shutdown();
|
void GameModule::PreRender() {}
|
||||||
ImGui_ImplSDL2_Shutdown();
|
|
||||||
ImGui::DestroyContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameModule::Event(SDL_Event *) {
|
void GameModule::Destroy() {}
|
||||||
|
void GameModule::Event(SDL_Event *) {}
|
||||||
}
|
|
|
@ -7,19 +7,8 @@
|
||||||
#include "core/ShadowApplication.h"
|
#include "core/ShadowApplication.h"
|
||||||
#include "GameModule.h"
|
#include "GameModule.h"
|
||||||
#include "core/ShadowApplication.h"
|
#include "core/ShadowApplication.h"
|
||||||
#include "vlkx/vulkan/VulkanManager.h"
|
#include "vlkx/vulkan/VulkanModule.h"
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) void shadow_main(ShadowEngine::ShadowApplication* app) {
|
extern "C" __declspec(dllexport) void shadow_main(ShadowEngine::ShadowApplication* app) {
|
||||||
|
|
||||||
std::cout << "HIIII from a loaded dll weeeeeee!!!" << std::endl;
|
|
||||||
|
|
||||||
app->GetModuleManager().PushModule(std::make_shared<GameModule>(), "game");
|
app->GetModuleManager().PushModule(std::make_shared<GameModule>(), "game");
|
||||||
|
|
||||||
if(app == &ShadowEngine::ShadowApplication::Get()){
|
|
||||||
std::cout << "They are the same!!!" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("dll side: %p \n", VulkanManager::getInstance());
|
|
||||||
printf("dll next ID: %llu \n", ShadowEngine::SHObject::GenerateId());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
10
resources/planets/skybox.frag
Normal file
10
resources/planets/skybox.frag
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#version 460 core
|
||||||
|
|
||||||
|
layout(binding = 1) uniform samplerCube tex;
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 tex_coord;
|
||||||
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
frag_color = texture(tex, tex_coord);
|
||||||
|
}
|
18
resources/planets/skybox.vert
Normal file
18
resources/planets/skybox.vert
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(std140, push_constant) uniform Transform {
|
||||||
|
mat4 matrix;
|
||||||
|
} transf;
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 in_pos;
|
||||||
|
layout(location = 1) in vec3 in_normal;
|
||||||
|
layout(location = 2) in vec2 in_uv;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 texCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
gl_Position = transf.matrix * vec4(in_pos, 1.0);
|
||||||
|
gl_Position.zw = vec2(1.0);
|
||||||
|
texCoord = in_pos;
|
||||||
|
}
|
13
resources/tri/tri.frag
Normal file
13
resources/tri/tri.frag
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(push_constant) uniform Alpha {
|
||||||
|
float value;
|
||||||
|
} alpha;
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 color;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
frag_color = vec4(color, alpha.value);
|
||||||
|
}
|
BIN
resources/tri/tri.frag.spv
Normal file
BIN
resources/tri/tri.frag.spv
Normal file
Binary file not shown.
11
resources/tri/tri.vert
Normal file
11
resources/tri/tri.vert
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 in_pos;
|
||||||
|
layout(location = 1) in vec3 in_color;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(in_pos, 1.0);
|
||||||
|
color = in_color;
|
||||||
|
}
|
BIN
resources/tri/tri.vert.spv
Normal file
BIN
resources/tri/tri.vert.spv
Normal file
Binary file not shown.
10
resources/walrus/cube.frag
Normal file
10
resources/walrus/cube.frag
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(binding = 1) uniform sampler2D tex;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 tex_coord;
|
||||||
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
frag_color = texture(tex, tex_coord);
|
||||||
|
}
|
BIN
resources/walrus/cube.frag.spv
Normal file
BIN
resources/walrus/cube.frag.spv
Normal file
Binary file not shown.
16
resources/walrus/cube.vert
Normal file
16
resources/walrus/cube.vert
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(std140, push_constant) uniform Transform {
|
||||||
|
mat4 matrix;
|
||||||
|
} transf;
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 in_pos;
|
||||||
|
layout(location = 1) in vec3 in_normal;
|
||||||
|
layout(location = 2) in vec2 in_uv;
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 texCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = transf.matrix * vec4(in_pos, 1.0);
|
||||||
|
texCoord = in_uv;
|
||||||
|
}
|
BIN
resources/walrus/cube.vert.spv
Normal file
BIN
resources/walrus/cube.vert.spv
Normal file
Binary file not shown.
BIN
resources/walrus/texture.png
Normal file
BIN
resources/walrus/texture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 487 KiB |
12745
resources/walrus/walrus.obj
Normal file
12745
resources/walrus/walrus.obj
Normal file
File diff suppressed because it is too large
Load Diff
BIN
vlkx-resources/pkg/01 - Copy.vxp
Normal file
BIN
vlkx-resources/pkg/01 - Copy.vxp
Normal file
Binary file not shown.
BIN
vlkx-resources/pkg/01.vxp
Normal file
BIN
vlkx-resources/pkg/01.vxp
Normal file
Binary file not shown.
1
vlkx-resources/pkg/index.vxi
Normal file
1
vlkx-resources/pkg/index.vxi
Normal file
|
@ -0,0 +1 @@
|
||||||
|
01.vxp
|
BIN
vlkx-resources/shader/SPIRV/basic.frag.spv
Normal file
BIN
vlkx-resources/shader/SPIRV/basic.frag.spv
Normal file
Binary file not shown.
BIN
vlkx-resources/shader/SPIRV/basic.vert.spv
Normal file
BIN
vlkx-resources/shader/SPIRV/basic.vert.spv
Normal file
Binary file not shown.
BIN
vlkx-resources/shader/SPIRV/rt.stage1.frag.spv
Normal file
BIN
vlkx-resources/shader/SPIRV/rt.stage1.frag.spv
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user