Replace the renderer with a modern, module version. (#7)

* New Vulkan Renderer Module & Associated Fixups
This commit is contained in:
Curle 2022-11-26 16:44:16 +00:00 committed by GitHub
parent 71b95e1ccf
commit a3e89b4e8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
111 changed files with 27268 additions and 1858 deletions

41
imgui.ini Normal file
View File

@ -0,0 +1,41 @@
[Window][Debug##Default]
Pos=876,8
Size=315,195
Collapsed=0
DockId=0x00000008,0
[Window][Dear ImGui Demo]
Pos=1193,178
Size=62,25
Collapsed=0
DockId=0x00000007,0
[Window][Game module window]
Pos=1193,8
Size=62,50
Collapsed=0
DockId=0x00000004,0
[Window][Time]
Pos=1193,60
Size=62,49
Collapsed=0
DockId=0x00000005,0
[Window][Active Modules]
Pos=1193,111
Size=62,65
Collapsed=0
DockId=0x00000006,0
[Docking][Data]
DockNode ID=0x00000001 Pos=1196,188 Size=379,195 Split=X
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=652,419 HiddenTabBar=1 Selected=0x55954704
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=129,419 Split=Y
DockNode ID=0x00000002 Parent=0x00000009 SizeRef=219,34 Split=Y Selected=0xFC1D20C0
DockNode ID=0x00000004 Parent=0x00000002 SizeRef=219,64 Selected=0xFC1D20C0
DockNode ID=0x00000005 Parent=0x00000002 SizeRef=219,62 Selected=0xE75A179E
DockNode ID=0x00000003 Parent=0x00000009 SizeRef=219,31 Split=Y Selected=0xEE305C78
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=219,142 Selected=0xEE305C78
DockNode ID=0x00000007 Parent=0x00000003 SizeRef=219,55 Selected=0xE87781F4

View File

@ -9,11 +9,13 @@ FILE(GLOB_RECURSE SOURCES
core/src/*.cpp
shadow-renderer/src/*.cpp
shadow-reflection/src/*.cpp
shadow-utility/src/*.cpp
)
FILE(GLOB_RECURSE HEADERS
core/inc/*.h
shadow-renderer/inc/*.h
shadow-reflection/inc/*.h
shadow-utility/inc/*.h
)
add_library(shadow-engine SHARED ${SOURCES} $<TARGET_OBJECTS:imgui>)
@ -24,6 +26,7 @@ target_include_directories(shadow-engine
core/inc
shadow-renderer/inc
shadow-reflection/inc
shadow-utility/inc
${glm_SOURCE_DIR}
INTERFACE
${imgui_SOURCE_DIR}

View File

@ -2,7 +2,8 @@
#define UMBRA_MODULE_H
#include "SHObject.h"
#include <SDL_events.h>
#include "SDL_events.h"
#include "vlkx/vulkan/abstraction/Commands.h"
namespace ShadowEngine {
@ -29,13 +30,17 @@ namespace ShadowEngine {
/// <summary>
/// update is called each frame
/// </summary>
virtual void Update() = 0;
virtual void Update(int frame) = 0;
virtual void Recreate() = 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;
@ -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
#endif //UMBRA_MODULE_H

View File

@ -11,22 +11,29 @@ namespace ShadowEngine {
public:
std::shared_ptr<Module> module;
std::string domain;
// Reinterpret this module as if it were a Renderer Module.
// A shortcut for `std::static_pointer_cast<std::shared_ptr<RendererModule>>(ShadowEngine::ModuleManager::instance->GetModule("renderer"))
std::shared_ptr<RendererModule> operator->() const { return std::static_pointer_cast<RendererModule>(module); }
};
class ModuleManager {
public:
static ModuleManager *instance;
static API ModuleManager *instance;
static ModuleManager* getInstance() { return instance; }
std::list<ModuleRef> modules;
ModuleRef renderer;
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>
T *GetModuleByType() {
for (auto &module: modules) {
@ -35,15 +42,19 @@ namespace ShadowEngine {
}
//SH_CORE_ERROR("Can't find the module {0}", T::Type());
return nullptr;
}
} */
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();

View File

@ -8,7 +8,7 @@
#include "Module.h"
#include "ShadowWindow.h"
#include <SDL.h>
#include "SDL.h"
namespace ShadowEngine {
@ -23,11 +23,15 @@ namespace ShadowEngine {
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;

View File

@ -68,6 +68,6 @@ namespace ShadowEngine {
void Init();
void Start();
};
void PollEvents();
};
}

View File

@ -1,6 +1,6 @@
#pragma once
#include <SDL.h>
#include "SDL.h"
namespace ShadowEngine {

View File

@ -1,14 +1,20 @@
#pragma once
#include "exports.h"
class Time
{
static int NOW;
public:
static int LAST;
static API int NOW;
static API int LAST;
static double deltaTime;
static double deltaTime_ms;
static API double deltaTime;
static API double deltaTime_ms;
static API double timeSinceStart;
static API double startTime;
static void UpdateTime();
};

View File

@ -5,8 +5,9 @@
#ifndef UMBRA_DEBUGMODULE_H
#define UMBRA_DEBUGMODULE_H
#include <SDL_events.h>
#include "SDL_events.h"
#include "core/Module.h"
#include "imgui.h"
namespace ShadowEngine::Debug {
@ -17,15 +18,19 @@ namespace ShadowEngine::Debug {
bool active;
public:
void Render() override;
void Render(VkCommandBuffer& commands, int frame) override {};
void PreInit() 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 { };

View File

@ -6,9 +6,9 @@
#if defined(_WIN32)
# if defined(EXPORTING_SH_ENGINE)
# define SH_EXPORT __declspec(dllexport)
# define API __declspec(dllexport)
# else
# define SH_EXPORT __declspec(dllimport)
# define API __declspec(dllimport)
# endif
#else // non windows
# define SH_EXPORT

View File

@ -0,0 +1,348 @@
#include "ShadowApplication.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include "time.h"
#include "imgui.h"
#include "imgui_impl_vulkan.h"
#include "imgui_impl_sdl.h"
#include <vlkx/vulkan/VulkanManager.h>
#include "vlkx/render/Camera.h"
#include "vlkx/render/render_pass/ScreenRenderPass.h"
#include "spdlog/spdlog.h"
#include "vlkx/vulkan/abstraction/Commands.h"
#include "vlkx/render/Geometry.h"
#include "temp/model/Builder.h"
#define CATCH(x) \
try { x } catch (std::exception& e) { spdlog::error(e.what()); exit(0); }
namespace ShadowEngine {
struct SkyboxTransform {
alignas(sizeof(glm::mat4)) glm::mat4 value;
};
struct PlanetTransform {
alignas(sizeof(glm::mat4)) glm::mat4 model;
alignas(sizeof(glm::mat4)) glm::mat4 projection;
};
struct Light {
alignas(sizeof(glm::vec4)) glm::vec4 dirTime;
};
std::unique_ptr<vlkx::ScreenRenderPassManager> passManager;
std::unique_ptr<vlkx::RenderCommand> renderCommands;
std::unique_ptr<vlkx::UserPerspectiveCamera> camera;
std::unique_ptr<vlkx::UniformBuffer> light;
std::unique_ptr<vlkx::PushConstant> skyboxConstant;
std::unique_ptr<vlkx::PushConstant> planetConstant;
std::unique_ptr<vlkxtemp::Model> skyboxModel;
std::unique_ptr<vlkxtemp::Model> planetModel;
std::unique_ptr<vlkxtemp::Model> asteroidModel;
float aspectRatio;
ShadowApplication* ShadowApplication::instance = nullptr;
ShadowApplication::ShadowApplication(int argc, char* argv[])
{
instance = this;
if(argc > 1)
{
for (size_t i = 0; i < argc; i++)
{
std::string param(argv[i]);
if(param == "-no-gui")
{
this->no_gui = true;
}
if(param == "-game")
{
this->game = argv[i+1];
}
}
}
//game = _setupFunc();
}
ShadowApplication::~ShadowApplication()
{
}
void ShadowApplication::Init()
{
// Initialize SDL. SDL_Init will return -1 if it fails.
if ( SDL_Init( SDL_INIT_EVERYTHING ) < 0 ) {
//std::cout << "Error initializing SDL: " << SDL_GetError() << std::endl;
//system("pause");
// End the program
//return 1;
}
window_ = new ShadowWindow(800,800);
CATCH(VulkanManager::getInstance()->initVulkan(window_->sdlWindowPtr);)
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
// Setup Dear ImGui style
ImGui::StyleColorsDark();
VkDescriptorPool imGuiPool;
VulkanManager* vk = VulkanManager::getInstance();
VkDescriptorPoolSize pool_sizes[] =
{
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
{ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
{ VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
{ VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
};
VkDescriptorPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes);
pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes);
pool_info.pPoolSizes = pool_sizes;
vkCreateDescriptorPool(vk->getDevice()->logical, &pool_info, VK_NULL_HANDLE, &imGuiPool);
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForVulkan(window_->sdlWindowPtr);
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance = vk->getVulkan();
init_info.PhysicalDevice = vk->getDevice()->physical;
init_info.Device = vk->getDevice()->logical;
init_info.QueueFamily = vk->getDevice()->queueData.graphics;
init_info.Queue = vk->getDevice()->graphicsQueue;
init_info.PipelineCache = VK_NULL_HANDLE;
init_info.DescriptorPool = imGuiPool;
init_info.Subpass = 1;
init_info.MinImageCount = vk->getSwapchain()->images.size();
init_info.ImageCount = vk->getSwapchain()->images.size();
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
init_info.Allocator = VK_NULL_HANDLE;
init_info.CheckVkResultFn = nullptr;
vlkxtemp::ModelBuilder::ShaderPool pool;
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
skyboxConstant = std::make_unique<vlkx::PushConstant>(sizeof(SkyboxTransform), 2);
planetConstant = std::make_unique<vlkx::PushConstant>(sizeof(PlanetTransform), 2);
light = std::make_unique<vlkx::UniformBuffer>(sizeof(Light), 2);
aspectRatio = (float) window_->Width / window_->Height;
vlkx::Camera::Config conf {};
camera = vlkx::UserPerspectiveCamera::create( {}, {}, { 110, aspectRatio });
using vlkxtemp::ModelBuilder;
const vlkx::RefCountedTexture::CubemapLocation skybox {
"resources/planets/bg",
{
"left.png", "right.png",
"top.png", "bottom.png",
"front.png", "back.png"
}
};
skyboxModel = ModelBuilder {
"Skybox", 2, aspectRatio,
ModelBuilder::SingleMeshModel { "resources/planets/skybox.obj", 1,
{ { ModelBuilder::TextureType::Cubemap, { { skybox } } } }
}}
.bindTextures(ModelBuilder::TextureType::Cubemap, 1)
.pushStage(VK_SHADER_STAGE_VERTEX_BIT)
.pushConstant(skyboxConstant.get(), 0)
.shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/planets/skybox.vert.spv")
.shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/planets/skybox.frag.spv")
.build();
planetModel = ModelBuilder {
"Walrus", 2, aspectRatio,
ModelBuilder::SingleMeshModel {"resources/walrus/walrus.obj", 1,
{{ ModelBuilder::TextureType::Diffuse, { { "resources/walrus/texture.png" } } } }
}}
.bindTextures(ModelBuilder::TextureType::Diffuse, 2)
.uniform(VK_SHADER_STAGE_FRAGMENT_BIT, {{1, 1}})
.uniformBuffer(1, *light)
.pushStage(VK_SHADER_STAGE_VERTEX_BIT)
.pushConstant(planetConstant.get(), 0)
.shader(VK_SHADER_STAGE_VERTEX_BIT, "resources/walrus/walrus.vert.spv")
.shader(VK_SHADER_STAGE_FRAGMENT_BIT, "resources/walrus/walrus.frag.spv")
.build();
passManager = std::make_unique<vlkx::ScreenRenderPassManager>(vlkx::RendererConfig { 2 });
passManager->initializeRenderPass();
skyboxModel->update(true, VulkanManager::getInstance()->getSwapchain()->extent, VK_SAMPLE_COUNT_1_BIT, *passManager->getPass(), 0);
int cursorX, cursorY;
SDL_GetMouseState(&cursorX, &cursorY);
camera->setPos({ cursorX, cursorY });
planetModel->update(true, VulkanManager::getInstance()->getSwapchain()->extent, VK_SAMPLE_COUNT_1_BIT, *passManager->getPass(), 0);
ImGui_ImplVulkan_Init(&init_info, **passManager->getPass());
// Upload Fonts
VkTools::immediateExecute([](const VkCommandBuffer& commands) { ImGui_ImplVulkan_CreateFontsTexture(commands); }, VulkanManager::getInstance()->getDevice());
SDL_SetRelativeMouseMode(SDL_TRUE);
}
void printMatrix(glm::mat4 mat) {
for (size_t i = 0; i < 4; i++) {
for (size_t j = 0; j < 4; j++) {
std::cout << mat[i][j] << " ";
}
std::cout << std::endl;
}
std::cout << std::endl << std::endl;
}
void updateData(int frame) {
const float elapsed_time = Time::timeSinceStart;
const glm::vec3 lightDir{glm::sin(elapsed_time * 0.0006f), -0.3f,
glm::cos(elapsed_time * 0.0006f)};
*light->getData<Light>(frame) =
{glm::vec4{lightDir, elapsed_time}};
light->upload(frame);
glm::mat4 modelMatrix { 1 };
modelMatrix = glm::rotate(modelMatrix, elapsed_time * glm::radians(0.0005f), glm::vec3 { 0, 1, 0 });
const vlkx::Camera& cam = camera->getCamera();
const glm::mat4 view = glm::lookAt(glm::vec3{3.0f}, glm::vec3{0.0f},
glm::vec3{0.0f, 1.0f, 0.0f});
const glm::mat4 proj = glm::perspective(
glm::radians(45.0f), aspectRatio,
0.1f, 100.0f);
glm::mat4 planetProjection = cam.getProjMatrix() * cam.getViewMatrix();
*planetConstant->getData<PlanetTransform>(frame) = { modelMatrix, planetProjection };
glm::mat4 skyboxMat = cam.getProjMatrix() * cam.getSkyboxView();
skyboxConstant->getData<SkyboxTransform>(frame)->value = skyboxMat;
}
void imGuiStartDraw() {
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
}
void imGuiEndDraw(const VkCommandBuffer& commands) {
ImGui::Render();
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commands);
}
void showDebugWindow(std::unique_ptr<vlkx::UserPerspectiveCamera>* cam) {
#define camPos cam->get()->getCamera().getPosition()
#define camFwd cam->get()->getCamera().getForward()
ImGui::Begin("Camera Debug");
ImGui::Text("Camera position: (x %f, y %f, z %f)", camPos.x, camPos.y, camPos.z );
ImGui::Text("Camera target: (x %f, y %f, z %f)", camPos.x + camFwd.x, camPos.y + camFwd.y, camPos.z + camFwd.z);
ImGui::Text("Camera FOV: %f", cam->get()->getCamera().getFieldOfView());
ImGui::End();
}
void ShadowApplication::PollEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) { // poll until all events are handled!
ImGui_ImplSDL2_ProcessEvent(&event);
switch(event.type) {
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
camera->reset(); break;
case SDLK_w:
camera->press(vlkx::Camera::Input::Up, Time::deltaTime); break;
case SDLK_s:
camera->press(vlkx::Camera::Input::Down, Time::deltaTime); break;
case SDLK_a:
camera->press(vlkx::Camera::Input::Left, Time::deltaTime); break;
case SDLK_d:
camera->press(vlkx::Camera::Input::Right, Time::deltaTime); break;
} break;
case SDL_MOUSEMOTION:
camera->move(-event.motion.xrel, -event.motion.yrel); break;
case SDL_MOUSEWHEEL:
camera->scroll(event.wheel.y, 1, 170); break;
case SDL_QUIT:
running = false; break;
}
}
}
void ShadowApplication::Start()
{
const auto update = [](const int frame) { updateData(frame); };
while (running)
{
PollEvents();
const auto result = renderCommands->execute(renderCommands->getFrame(), VulkanManager::getInstance()->getSwapchain()->swapChain, update,
[](const VkCommandBuffer& buffer, uint32_t frame) {
passManager->getPass()->execute(buffer, frame, {
// Render our model
[&frame](const VkCommandBuffer& commands) {
planetModel->draw(commands, frame, 1);
skyboxModel->draw(commands, frame, 1);
},
// Render ImGUI
[&](const VkCommandBuffer& commands) {
imGuiStartDraw();
bool showDemo = true;
//if (showDemo)
// ImGui::ShowDemoWindow(&showDemo);
showDebugWindow(&camera);
imGuiEndDraw(commands);
}
});
});
if (result.has_value())
throw std::runtime_error("Resizing is not implemented");
renderCommands->nextFrame();
Time::UpdateTime();
camera->active(true);
}
vkDeviceWaitIdle(VulkanManager::getInstance()->getDevice()->logical);
ImGui_ImplVulkan_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_DestroyWindow(window_->sdlWindowPtr);
SDL_Quit();
}
}

View File

@ -18,17 +18,18 @@ 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};
modules.emplace_back(r);
if (domain == "renderer")
renderer = r;
module->PreInit();
}
ShadowEngine::Module& ShadowEngine::ModuleManager::GetModule(std::string name)
ShadowEngine::Module& ShadowEngine::ModuleManager::GetModule(const std::string& name)
{
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)
{
module.module->Update();
module.module->Update(frame);
}
}
void ShadowEngine::ModuleManager::LateRender()
void ShadowEngine::ModuleManager::LateRender(VkCommandBuffer& commands, int frame)
{
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)
{
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()
{
for (auto& module : modules)

View File

@ -21,20 +21,20 @@ void ShadowEngine::SDL2Module::PreInit() {
//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() {
return this->GetType();

View File

@ -1,17 +1,14 @@
#include "core/ShadowApplication.h"
#define STB_IMAGE_IMPLEMENTATION
#include "core/ShadowApplication.h"
#include "core/Time.h"
#include "core/SDL2Module.h"
#include "debug/DebugModule.h"
#include "dylib.hpp"
#include "vlkx/vulkan/abstraction/Commands.h"
#include <imgui.h>
#include <imgui_impl_vulkan.h>
#include <imgui_impl_sdl.h>
#include <vlkx/vulkan/VulkanManager.h>
#include <vlkx/render/Camera.h>
#include <vlkx/render/geometry/SingleRenderer.h>
#include <vlkx/vulkan/VulkanModule.h>
#include <spdlog/spdlog.h>
#define CATCH(x) \
@ -23,6 +20,8 @@ namespace ShadowEngine {
ShadowApplication* ShadowApplication::instance = nullptr;
std::unique_ptr<vlkx::RenderCommand> renderCommands;
ShadowApplication::ShadowApplication(int argc, char* argv[])
{
instance = this;
@ -70,23 +69,17 @@ namespace ShadowEngine {
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();
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.Init();
auto sdl2module = moduleManager.GetModuleByType<SDL2Module>();
window_ = sdl2module->_window;
renderCommands = std::make_unique<vlkx::RenderCommand>(2);
}
void ShadowApplication::Start()
@ -100,16 +93,14 @@ namespace ShadowEngine {
running = false;
}
moduleManager.Update();
moduleManager.PreRender();
VulkanManager::getInstance()->startDraw();
moduleManager.Render();
moduleManager.LateRender();
VulkanManager::getInstance()->endDraw();
moduleManager.renderer->BeginRenderPass(renderCommands);
moduleManager.AfterFrameEnd();
renderCommands->nextFrame();
Time::UpdateTime();
}
moduleManager.Destroy();

View File

@ -1,20 +1,27 @@
#include "core/Time.h"
//#include <SDL_hints.h>
//#include <SDL.h>
#include <chrono>
int Time::NOW = 0;//SDL_GetPerformanceCounter();
int Time::LAST = 0;
double Time::deltaTime_ms = 0;
double Time::deltaTime = 0;
API int Time::NOW = 0;//SDL_GetPerformanceCounter();
API int Time::LAST = 0;
API double lastFrame = 0;
API double Time::deltaTime_ms = 0;
API double Time::deltaTime = 0;
API double Time::startTime = 0;
API double Time::timeSinceStart = 0;
void Time::UpdateTime()
{
/*
NOW = SDL_GetTicks();
deltaTime_ms = LAST > 0 ? (NOW - LAST) *10 : (1.0f / 60.0f);
deltaTime_ms = deltaTime_ms == 0 ? (1.0f / 60.0f) : deltaTime_ms;
using namespace std::chrono;
auto now = system_clock::now();
auto now_ms = time_point_cast<milliseconds>(now);
LAST = NOW;
deltaTime = deltaTime_ms * 0.001;
*/
auto value = now_ms.time_since_epoch();
double duration = value.count();
deltaTime = duration - lastFrame;
if (startTime == 0)
startTime = duration;
timeSinceStart = duration - startTime;
lastFrame = duration;
}

View File

@ -9,29 +9,26 @@
SHObject_Base_Impl(ShadowEngine::Debug::DebugModule)
void ShadowEngine::Debug::DebugModule::Render() {
void ShadowEngine::Debug::DebugModule::OverlayRender() {
ImGui::Begin("Time", &active, ImGuiWindowFlags_MenuBar);
ImGui::Text("delta time in ms: %lf", Time::deltaTime_ms);
ImGui::Text("delta time in s: %lf", Time::deltaTime);
ImGui::Text("LAST time in: %ld", Time::LAST);
if (ImGui::Begin("Time", &active, ImGuiWindowFlags_MenuBar)) {
ImGui::Text("delta time in ms: %lf", Time::deltaTime_ms);
ImGui::Text("delta time in s: %lf", Time::deltaTime);
ImGui::Text("LAST time in: %d", Time::LAST);
}
ImGui::End();
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::Text("%s", module.module->GetName().c_str());
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.4f, 1.0f), "Active Modules:");
for (auto &module: m->modules) {
ImGui::Text("%s", module.module->GetName().c_str());
}
}
ImGui::End();
}

View File

@ -1,5 +1,5 @@
#include "SFFParser.h"
#include "../../shadow-utility/src/string-helpers.h"
#include "string-helpers.h"
#include <fstream>

View File

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

View File

@ -25,7 +25,7 @@ namespace ShadowEngine {
* \brief Generates a new UID for each call
* \return the next Unique ID that was just generated
*/
SH_EXPORT static uint64_t GenerateId() noexcept;
API static uint64_t GenerateId() noexcept;
public:
/**

View File

@ -0,0 +1,37 @@
=====
This folder exists to store VLKX renderer objects that are TEMPORARY.
It currently contains:
=====
Model Loader system
The Model Loader is temporarily implemented as a raw file reader and OBJ parser.
It should be removed when the File Format system is able to parse model and texture files.
====
Model Abstraction system
The Model Abstraction allows you to create a model with:
- A mesh
- An arbitrary number of textures
- A push constant
- An arbitrarily large uniform buffer
- A fragment and vertex shader
In all, it contains a custom Render Pipeline that will be used to draw the model.
This allows for drastic and visually appealing effects.
It should be maintained and moved into the appropriate Shadow module once ready.
====
Model Builder system
The model Builder allows for simple construction of Models using the Model Abstraction system above.
It consumes the Model Loader and returns a Model Abstraction.
It should be MOVED into the File Format parsing system once it is ready.

View File

@ -0,0 +1,137 @@
#pragma once
#include "vlkx/render/shader/Pipeline.h"
#include "Loader.h"
#include "vlkx/render/render_pass/GenericRenderPass.h"
#include "vlkx/vulkan/abstraction/Descriptor.h"
namespace vlkxtemp {
class Model;
class ModelBuilder {
public:
using ShaderPool = vlkx::ShaderModule::ReleasePool;
using TextureType = ModelLoader::TextureType;
using TexturePerMesh = std::array<std::vector<std::unique_ptr<vlkx::SamplableImage>>, static_cast<int>(TextureType::Count)>;
using BindingPoints = std::map<TextureType, uint32_t>;
using TextureSource = vlkx::RefCountedTexture::ImageLocation;
using TextureSources = std::map<TextureType, std::vector<TextureSource>>;
class ModelResource {
public:
virtual ~ModelResource() = default;
virtual void load(ModelBuilder* builder) const = 0;
};
class SingleMeshModel : public ModelResource {
public:
SingleMeshModel(std::string&& path, int indexBase, TextureSources&& sources)
: objFile(std::move(path)), objIndexBase(indexBase), textureSources(std::move(sources)) {}
void load(ModelBuilder* builder) const override;
private:
const std::string objFile;
const int objIndexBase;
const TextureSources textureSources;
};
class MultiMeshModel : public ModelResource {
public:
MultiMeshModel(std::string&& modelDir, std::string&& textureDir)
: models(std::move(modelDir)), textures(std::move(textureDir)) {}
void load(ModelBuilder* builder) const override;
private:
const std::string models;
const std::string textures;
};
struct ModelPushConstant {
struct Meta {
const vlkx::PushConstant* constants;
uint32_t offset;
};
VkShaderStageFlags stage;
std::vector<Meta> constants;
};
using Descriptors = std::vector<std::unique_ptr<vlkx::StaticDescriptor>>;
ModelBuilder(std::string&& name, int frames, float aspect, const ModelResource& resource);
ModelBuilder(const ModelBuilder&) = delete;
ModelBuilder& operator=(const ModelBuilder&) = delete;
ModelBuilder& texture(TextureType type, const TextureSource& source);
ModelBuilder& bindTextures(TextureType type, uint32_t point);
ModelBuilder& instanceBuffer(vlkx::PerInstanceVertexBuffer* buffer);
ModelBuilder& uniform(VkShaderStageFlags stage, std::vector<vlkx::Descriptor::Meta::Binding>&& bindings);
ModelBuilder& uniformBuffer(uint32_t point, const vlkx::UniformBuffer& buffer);
ModelBuilder& pushStage(VkShaderStageFlags stage);
ModelBuilder& pushConstant(const vlkx::PushConstant* constant, uint32_t offset);
ModelBuilder& shader(VkShaderStageFlagBits stage, std::string&& file);
std::unique_ptr<Model> build();
private:
std::vector<Descriptors> createDescs() const;
const int frames;
const float aspectRatio;
std::unique_ptr<vlkx::StaticPerVertexBuffer> vertexBuffer;
std::vector<TexturePerMesh> textures;
TexturePerMesh sharedTextures;
BindingPoints bindPoints;
std::vector<vlkx::PerInstanceVertexBuffer*> instanceBuffers;
std::vector<vlkx::Descriptor::Meta> uniformMeta;
std::vector<vlkx::Descriptor::BufferInfos> uniformBufferMeta;
std::optional<ModelPushConstant> pushConstants;
std::unique_ptr<vlkx::GraphicsPipelineBuilder> pipelineBuilder;
};
class Model {
public:
Model(const Model&) = delete;
Model& operator=(const Model&) = delete;
void update(bool opaque, const VkExtent2D& frame, VkSampleCountFlagBits samples, const vlkx::RenderPass& pass, uint32_t subpass, bool flipY = true);
void draw(const VkCommandBuffer& commands, int frame, uint32_t instances) const;
private:
friend std::unique_ptr<Model> ModelBuilder::build();
using Descriptors = ModelBuilder::Descriptors;
using ModelPushConstant = ModelBuilder::ModelPushConstant;
using TexturePerMesh = ModelBuilder::TexturePerMesh;
Model(float aspectRatio,
std::unique_ptr<vlkx::StaticPerVertexBuffer>&& vertexBuffer,
std::vector<vlkx::PerInstanceVertexBuffer*>&& perInstanceBuffers,
std::optional<ModelPushConstant>&& pushConstants,
TexturePerMesh&& sharedTextures,
std::vector<TexturePerMesh>&& textures,
std::vector<Descriptors>&& descriptors,
std::unique_ptr<vlkx::GraphicsPipelineBuilder>&& pipelineBuilder)
: aspectRatio(aspectRatio), vertexBuffer(std::move(vertexBuffer)), perInstanceBuffers(std::move(perInstanceBuffers)),
pushConstants(std::move(pushConstants)), sharedTextures(std::move(sharedTextures)), textures(std::move(textures)),
descriptors(std::move(descriptors)), pipelineBuilder(std::move(pipelineBuilder)) {}
const float aspectRatio;
const std::unique_ptr<vlkx::StaticPerVertexBuffer> vertexBuffer;
const std::vector<vlkx::PerInstanceVertexBuffer*> perInstanceBuffers;
const std::optional<ModelPushConstant> pushConstants;
const TexturePerMesh sharedTextures;
const std::vector<TexturePerMesh> textures;
const std::vector<Descriptors> descriptors;
std::unique_ptr<vlkx::GraphicsPipelineBuilder> pipelineBuilder;
std::unique_ptr<vlkx::Pipeline> pipeline;
};
}

View File

@ -0,0 +1,57 @@
#pragma once
#include <string>
#include <vector>
#include "vlkx/render/Geometry.h"
namespace vlkxtemp {
struct Wavefront {
Wavefront(std::string_view path, size_t base);
Wavefront(const Wavefront&) = delete;
Wavefront& operator=(const Wavefront&) = delete;
std::vector<uint32_t> indices;
std::vector<Geo::VertexAll> vertices;
};
class ModelLoader {
public:
enum class TextureType {
Diffuse,
Specular,
Reflection,
Cubemap,
Count
};
struct TextureData {
TextureData(TextureData&&) noexcept = default;
TextureData& operator=(TextureData&&) noexcept = default;
std::string path;
TextureType type;
};
struct MeshData {
MeshData() = default;
MeshData(MeshData&&) noexcept = default;
MeshData& operator=(MeshData&&) noexcept = default;
std::vector<Geo::VertexAll> vertices;
std::vector<uint32_t> indices;
std::vector<TextureData> textures;
};
ModelLoader(const std::string& model, const std::string& textures);
ModelLoader(const ModelLoader&) = delete;
ModelLoader& operator=(const ModelLoader&) = delete;
const std::vector<MeshData>& getMeshes() const { return meshes; }
private:
std::vector<MeshData> meshes;
};
}

View File

@ -1,23 +1,213 @@
#pragma once
#include <optional>
#define GLM_FORCE_RADIAN
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <functional>
class Camera {
public:
namespace vlkx {
class Camera {
public:
// Set up the camera.
void init(float fov, float width, float height, float near, float far);
enum class Input {
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; };
glm::mat4 getProjectionMatrix() { return projectionMatrix; }
Camera(const Camera &) = delete;
private:
glm::mat4 projectionMatrix;
glm::mat4 viewMatrix;
glm::vec3 position;
};
Camera &operator=(const Camera &) = delete;
virtual ~Camera() = default;
Camera &move(const glm::vec3 &delta);
Camera &setPos(const glm::vec3 &pos);
Camera &up(const glm::vec3 &up);
Camera &forward(const glm::vec3 &forward);
glm::mat4 getViewMatrix() const;
glm::mat4 getSkyboxView() const {
return glm::mat4{glm::mat3{getViewMatrix()}};
}
virtual glm::mat4 getProjMatrix() const = 0;
const glm::vec3& getPosition() const { return position; }
const glm::vec3& getUp() const { return upVector; }
const glm::vec3& getForward() const { return frontVector; }
const glm::vec3& getRight() const { return rightVector; }
protected:
explicit Camera(const Config &conf) : nearPlane(conf.nearPlane), farPlane(conf.farPlane), position(conf.pos),
upVector(glm::normalize(conf.upV)) {
forward(conf.target - position);
}
const float nearPlane;
const float farPlane;
private:
glm::vec3 position;
glm::vec3 upVector;
glm::vec3 frontVector;
glm::vec3 rightVector;
};
class PerspectiveCamera : public Camera {
public:
struct Frustum {
float fov;
float aspect;
};
struct RT {
glm::vec3 up;
glm::vec3 forward;
glm::vec3 right;
};
PerspectiveCamera(const Camera::Config &conf, const Frustum &frus) :
Camera(conf), aspectRatio(frus.aspect), fov(frus.fov) {}
PerspectiveCamera(const PerspectiveCamera &) = delete;
PerspectiveCamera &operator=(const PerspectiveCamera &) = delete;
PerspectiveCamera &fieldOfView(float newFov);
RT getRT() const;
glm::mat4 getProjMatrix() const override;
float getFieldOfView() const { return fov; }
float getAspect() const { return aspectRatio; }
private:
const float aspectRatio;
float fov;
};
class OrthographicCamera : public Camera {
public:
struct OrthoConfig {
float width;
float aspect;
};
static OrthoConfig getFullscreenConfig() {
return {2, 1};
}
OrthographicCamera(const Camera::Config &config, const OrthoConfig &ortho)
: Camera(config), aspectRatio(ortho.aspect), width(ortho.width) {}
OrthographicCamera(const OrthographicCamera &) = delete;
OrthographicCamera &operator=(const OrthographicCamera &) = delete;
OrthographicCamera &setWidth(float width);
glm::mat4 getProjMatrix() const override;
float getWidth() const { return width; }
private:
const float aspectRatio;
float width;
};
template<typename Type>
class UserCamera {
public:
UserCamera(const UserCamera &) = delete;
UserCamera &operator=(const UserCamera &) = delete;
virtual ~UserCamera() = default;
void setInternal(std::function<void(Type *)> op);
void setPos(const glm::dvec2 &pos) { cursorPos = pos; }
void move(double x, double y);
bool scroll(double delta, double min, double max);
void press(Camera::Input key, float time);
void active(bool active) { isActive = active; }
const Type &getCamera() const { return *camera; }
UserCamera(const Camera::Movement &movement, std::unique_ptr<Type> &&cam)
: config(movement), camera(std::move(cam)) {
reset();
}
void reset();
private:
const Camera::Movement config;
bool isActive = false;
std::unique_ptr<Type> camera;
glm::dvec2 cursorPos;
glm::vec3 refForward;
glm::vec3 refLeft;
float pitch;
float yaw;
};
class UserPerspectiveCamera : public UserCamera<PerspectiveCamera> {
public:
static std::unique_ptr<UserPerspectiveCamera>
create(const Camera::Movement &movement, const Camera::Config &config,
const PerspectiveCamera::Frustum &frustum) {
return std::make_unique<UserPerspectiveCamera>(movement,
std::make_unique<PerspectiveCamera>(config, frustum));
}
protected:
using UserCamera<PerspectiveCamera>::UserCamera;
};
class UserOrthoCamera : public UserCamera<OrthographicCamera> {
public:
static std::unique_ptr<UserOrthoCamera> create(const Camera::Movement &movement, const Camera::Config &config,
const OrthographicCamera::OrthoConfig &ortho) {
return std::make_unique<UserOrthoCamera>(movement, std::make_unique<OrthographicCamera>(config, ortho));
}
protected:
using UserCamera<OrthographicCamera>::UserCamera;
};
}

View File

@ -28,65 +28,66 @@ namespace Geo {
};
// All of the metadata involved with a vertex.
struct Vertex {
struct VertexAll {
glm::vec3 position; // XYZ coordinates of the vertex's position.
glm::vec3 normal; // Unit vector pointing away from the outer surface of the vertex.
glm::vec3 color; // The color of the vertex.
glm::vec2 texture; // The u/v coordinates of this vertex in the bound texture.
// How fast should vertex data be read from RAM?
static VkVertexInputBindingDescription getBindingDesc() {
VkVertexInputBindingDescription desc = {};
desc.binding = 0;
desc.stride = sizeof(Vertex);
desc.stride = sizeof(VertexAll);
desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return desc;
}
// How should vertexes be handled?
static std::array<VkVertexInputAttributeDescription, 4> getAttributeDesc() {
std::array<VkVertexInputAttributeDescription, 4> descs = {};
// Attribute 0; position. Location 0, 3x 32-bit float.
descs[0].binding = 0;
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;
static std::vector<VkVertexInputAttributeDescription> getAttributeDesc() {
return {
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, position)) },
{ 0, 1, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, normal)) },
{ 0, 2, VK_FORMAT_R32G32_SFLOAT, static_cast<uint32_t>(offsetof(VertexAll, texture)) }
};
}
};
// All of the metadata involved with a vertex.
struct VertexColor {
glm::vec3 position; // XYZ coordinates of the vertex's position.
glm::vec3 color; // The color of the vertex.
// How fast should vertex data be read from RAM?
static VkVertexInputBindingDescription getBindingDesc() {
VkVertexInputBindingDescription desc = {};
desc.binding = 0;
desc.stride = sizeof(VertexColor);
desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return desc;
}
// How should vertexes be handled?
static std::vector<VkVertexInputAttributeDescription> getAttributeDesc() {
return {
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexColor, position)) },
{ 0, 1, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexColor, color)) }
};
}
};
// Contains data about a given Mesh.
class Mesh {
public:
// Pre-load the data for a triangle into the given buffers.
static void setTriData(std::vector<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.
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.
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.
static void setSphereData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
static void setSphereData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices);
};

View File

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

View File

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

View File

@ -0,0 +1,242 @@
#pragma once
#include <vulkan/vulkan.h>
#include "vlkx/vulkan/abstraction/ImageUsage.h"
#include <string>
#include <functional>
#include <memory>
#include "GenericRenderPass.h"
namespace vlkx {
/**
* The common base class for rendering and computing passes that run on the GPU.
* Provides some utility methods for handling attachment metadata between subpasses.
*/
class CommonPass {
public:
explicit CommonPass(int passes) : numPasses(passes) {}
// Delete the copy and move constructors
CommonPass(const CommonPass&) = delete;
CommonPass& operator=(const CommonPass&) = delete;
virtual ~CommonPass() = default;
// Get the image layout of the given image at the start of this pass
VkImageLayout getInitialLayout(const std::string& name) const;
// Get the image layout of the given image at the end of this pass
VkImageLayout getFinalLayout(const std::string& name) const;
// Get the image layout of the given image before the given subpass starts
VkImageLayout getSubpassLayout(const std::string& name, int subpass) const;
// Update the state of the given image's usage tracker.
void update(const std::string& name, MultiImageTracker& tracker) const;
protected:
/**
* Some metadata about the usage of an image between subpasses.
*/
struct Usages {
Usages(const int last, const ImageUsage* prev, const ImageUsage* curr) : lastSubpass(last), lastUsage(*prev), currentUsage(*curr) {}
const int lastSubpass;
const ImageUsage& lastUsage;
const ImageUsage& currentUsage;
};
// Add the usage of an image in the pass to its' tracker.
void addUsage(std::string&& name, UsageTracker&& tracker);
// Get the full history of the image's usages up to this rendering pass.
const UsageTracker& getHistory(const std::string& name) const;
// Get the usage of an image at the start of the given pass.
const ImageUsage* getUsage(const std::string& name, int pass) const;
// Retrieve image usage data, but only if the image is barriered at the given pass.
std::optional<Usages> checkForSync(const std::string& name, int pass) const;
// Validate that the subpass is valid for the given image.
// The meaning of includeVirtual is defined by the child implementation.
void validate(int pass, const std::string& image, bool includeVirtual) const;
int getVirtualInitial() const { return -1; }
int getVirtualFinal() const { return numPasses; }
protected:
std::map<std::string, UsageTracker> usageHistory;
const int numPasses;
};
/**
* The Common Pass implementation for Graphics passes; that is, render passes that output to color buffers
* for presentation to the screen, or to be used as textures in such.
* The instance of the GraphicsPass can be stored and reused to create multiple RenderPassBuilders.
* In this way it is essentially a RenderPassBuilderFactory.
*/
class GraphicsPass : public CommonPass {
public:
using LocationGetter = std::function<int(int pass)>;
explicit GraphicsPass(int passes) : CommonPass {passes} {}
GraphicsPass(const GraphicsPass&) = delete;
GraphicsPass& operator=(const GraphicsPass&) = delete;
// Get the default render ops for a color buffer.
static RenderPassBuilder::Attachment::OpsType getDefaultOps() {
return RenderPassBuilder::Attachment::ColorOps { VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE };
}
// Get the default render ops for a stencil buffer.
static RenderPassBuilder::Attachment::OpsType getStencilOps() {
return RenderPassBuilder::Attachment::StencilDepthOps { VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE };
}
/**
* Add an image reference that is used in this render pass.
* @param name the name of the image used
* @param history the usage history of the image, for tracking purposes
* @param getter a function to get the location of the image, only if the image is used as a render target.
* @param ops optional; uses the static defaults if not present.
* @return the index into the VkAttachmentDescriptions.
*/
int add(const std::string& name, UsageTracker&& history, LocationGetter&& getter, const std::optional<RenderPassBuilder::Attachment::OpsType> ops = std::nullopt);
#define fluent GraphicsPass&
// Specifies that the source image will be resolved to the single destination at the given pass.
fluent addMultisample(const std::string& source, const std::string& dest, int pass);
// Build a RenderPassBuilder with the information provided so far.
std::unique_ptr<RenderPassBuilder> build(int framebuffers);
private:
struct AttachmentMeta {
int index;
LocationGetter getter;
vlkx::RenderPassBuilder::Attachment::OpsType ops;
std::map<int, std::string> multisample;
};
void setAttachments();
void setSubpasses();
void setDependencies();
/**
* Find the first subpass where the given image is used as a render target.
* @param history the usage history of the image; what it was used at at each subpass.
* @return nullopt if the image was not used as a render target, the index of the subpass where it was, if not.
*/
std::optional<int> getFirstRenderTarget(const UsageTracker& history) const;
/**
* Return the operations that should be used for the given image attachment.
* If the user specified ops, it will be checekd against the history.
* @param name the name of the image to use as the attachment
* @param history the usage history of the attachment, for internal checks
* @param userOps operations to use for the image, as an optional override.
* @return the ColorOps to use for the given attachment.
*/
RenderPassBuilder::Attachment::OpsType getOps(const std::string& name, const UsageTracker& history, const std::optional<RenderPassBuilder::Attachment::OpsType>& userOps) const;
/**
* Get the usage type of the image.
* Assumption: an image is only ever used as a color OR depth stencil. Never both.
* Assumption: Multisample == RenderTarget
* @param name the name of the image to check
* @param history the history of the image's usages in the GPU.
* @return whether the image is a RenderTarget or a DepthStencil buffer.
*/
ImageUsage::Type getUsageType(const std::string& name, const UsageTracker& history) const;
/**
* Ensure that the image is used as type at subpass in its' history.
*/
bool verifyImageUsage(const UsageTracker& history, int subpass, ImageUsage::Type type) const;
/**
* Return whether the subpass is virtual.
* For a Render Pass, virtual means it is a preprocessing step.
*/
bool isVirtual(int subpass) const {
return subpass == getVirtualInitial() || subpass == getVirtualFinal();
}
/**
* Return the subpass index; for virtual passes, it uses an EXTERNAL subpass.
*/
uint32_t checkSubpass(int subpass) const {
return isVirtual(subpass) ? VK_SUBPASS_EXTERNAL : (uint32_t) subpass;
}
/**
* Ensure that the image's usages are compatible with a render pass.
* For example, compute shader linear buffers cannot be used as render targets, etc.
*/
void verifyHistory(const std::string& image, const UsageTracker& history) const;
std::map<std::string, AttachmentMeta> metas;
std::unique_ptr<vlkx::RenderPassBuilder> builder;
};
/**
* The Common Pass implementation for Compute Shaders.
* That is, shaders that do not write to color buffers.
* A subpass can execute multiple compute shaders unbarriered, which increases efficiency.
* We still need to transition images between passes when necessary, hence the wrapper.
*/
class ComputePass : public CommonPass {
public:
ComputePass(const ComputePass&) = delete;
ComputePass& operator=(const ComputePass&) = delete;
#define fluent ComputePass&
/**
* Add the given image as an attachment to the compute shader pass.
* @param name the name of the image
* @param history the usage history of the image
* @return the ComputePass instance, for chaining.
*/
fluent add(std::string&& name, UsageTracker&& history);
fluent add(const std::string& name, UsageTracker&& history) {
return add(std::string(name), std::move(history));
}
/**
* Run computeOps, insert memory barriers to transition used images into the appropriate format.
* Images must be a superset of all images that were called with add().
* Compute_ops must be equal to the number of subpasses.
* Commands must be recording.
* @param commands the command buffer to write into.
* @param queueFamily the family to use for inserting barriers
* @param images the list of images that were used in the compute pass
* @param computeOps the compute functions to upload to the GPU
*/
void execute(const VkCommandBuffer& commands, uint32_t queueFamily, const std::map<std::string, const VkImage*>& images, const std::vector<std::function<void()>>& computeOps) const;
/**
* Insert a memory barrier, to transition the layout of the image from the previous to the curent.
* The barrier is performed using the given queue family.
* @param commands the command buffer to write into.
* @param queueFamily the family to use for inserting barriers.
* @param image the list of images that were used in the compute pass
* @param prev the previous usage of the image; the state being transitioned from
* @param current the new usage of the image; the state being transitioned to.
*/
void barrier(const VkCommandBuffer& commands, uint32_t queueFamily, const VkImage& image, const ImageUsage& prev, const ImageUsage& current) const;
/**
* Verify whether the previous usages of the given image in its' history is compatible with a compute shader.
* For example, a fragment shader output image is not compatible.
* @param name the name of the image being checked
* @param history the usage history of the image/
*/
void verify(const std::string& name, const UsageTracker& history) const;
};
}

View File

@ -0,0 +1,238 @@
#pragma once
#include <vulkan/vulkan.h>
#include <utility>
#include <vector>
#include <functional>
#include <variant>
#include <optional>
#include "vlkx/vulkan/Tools.h"
#include "vlkx/vulkan/abstraction/Image.h"
namespace vlkx {
/**
* Gathers the operations that the GPU should perform when rendering to a framebuffer.
* Subpasses and dependencies are to be configured automatically using the builder below.
* RenderPass objects are disposable, and should be discarded when the framebuffer changes.
*/
class RenderPass {
public:
using RenderFunc = std::function<void(const VkCommandBuffer& buffer)>;
// Delete copy and move constructors to prevent the GPU getting confused with what we're trying to do
RenderPass(const RenderPass&) = delete;
RenderPass& operator=(const RenderPass&) = delete;
~RenderPass();
RenderPass(int subpasses, VkRenderPass pass, std::vector<VkClearValue> clear, VkExtent2D ext, std::vector<VkFramebuffer> fbs, std::vector<int> attachs)
: subpassCount(subpasses), renderPass(pass), clearValues(std::move(clear)), extent(ext), framebuffers(std::move(fbs)), attachments(std::move(attachs)) {}
const VkRenderPass& operator*() const { return renderPass; }
int getAttachsInSubpass(int subpass) const {
return attachments[subpass];
}
/**
* Upload all of the subpass render commands to the command buffer.
* The size of ops must be equal to the number of subpasses in this render pass.
* @param commands the command buffer to execute on; must be recording
* @param imageIndex the index of the image on the swapchain that we're rendering to; the target framebuffer.
* @param ops the render operations to add onto the command buffer.
*/
void execute(const VkCommandBuffer& commands, int imageIndex, std::vector<RenderFunc> ops) const;
private:
// The number of sub-render-passes in this pass.
const int subpassCount;
// The VkRenderPass that this class wraps.
VkRenderPass renderPass;
// The clear values that will wipe all framebuffers to their empty states.
const std::vector<VkClearValue> clearValues;
// The size of the framebuffers (all are the same size)
const VkExtent2D extent;
// The framebuffers that we can render to
const std::vector<VkFramebuffer> framebuffers;
// The number of color attachments (sampled color images) in each subpass, by subpass index.
const std::vector<int> attachments;
};
/**
* A stateful, fluent way to create Render Passes.
* This object can be stored and reused; when the window size changes, simply set the extent and
* export a new RenderPass to be used in the pipeline.
*
* Allows setting sub-passes, sub-pass dependencies, operations to read and write them, etc.
*/
class RenderPassBuilder {
public:
/**
* Information required to define an attachment to be used in a render pass.
* Contains information on the layout, the operations to use on read and write, etc.
*/
struct Attachment {
// Operations to use on color attachments.
struct ColorOps {
VkAttachmentLoadOp LOAD; // Load data in the color attachment
VkAttachmentStoreOp STORE; // Store data into the color attachment
};
// Operations to use on depth and stencil buffers.
struct StencilDepthOps {
VkAttachmentLoadOp DEPTH_LOAD; // Load data in the depth attachment
VkAttachmentStoreOp DEPTH_STORE; // Store data in the depth attachment
VkAttachmentLoadOp STENCIL_LOAD; // Load data in the stencil attachment
VkAttachmentStoreOp STENCIL_STORE; // Store data in the stencil attachment
};
using OpsType = std::variant<ColorOps, StencilDepthOps>;
// The operations that can be performed on this attachment
OpsType ops;
// The initial layout of an image in this attachment (pre-GPU upload)
VkImageLayout layoutInitial;
// The final layout of an image in this attachment (as seen by the shader)
VkImageLayout layoutFinal;
};
/**
* Describes attachments used in each subpass, in terms of Vulkan data.
* Attachment here is used in the conceptual sense, not referring to the Attachment struct above.
*
* If multisampleReferences is non-zero, its' size must be equal to colorReferences' size.
* Each index of multisampleReferences refers to the same-index colorReferences entry.
*
* If stencilDepthReference is non-zero, it is shared between all subpasses.
*/
struct SubpassAttachments {
std::vector<VkAttachmentReference> colorReferences;
std::vector<VkAttachmentReference> multisampleReferences;
std::optional<VkAttachmentReference> stencilDepthReference;
};
/**
* Describes the dependencies between each sub-pass.
* It describes how each subpass will read or modify the data written by the last subpass, if at all.
* This dependency information can allow the GPU to run some passes in parallel, and enforce the
* strict ordering of those that require it.
*/
struct SubpassDependency {
/**
* Defines some metadata about the subpass.
* Contains the index of the subpass, how it will use data from the last pass, and what exactly it will do.
*/
struct SubpassMeta {
/**
* Index of the subpass.
*/
uint32_t index;
/**
* Describes how we want to modify the data passed to us from the last subpass.
* Will change how the next subpass will wait for the completion of this subpass, if at all.
*
* VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT:
* read/write to the color attachment
*
* VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT:
* VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT:
* read/write to the depth or stencil attachment
*
* VK_PIPELINE_STAGE_VERTEX_SHADER_BIT:
* VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT:
* read all attachments
*
*/
VkPipelineStageFlags stage;
/**
* Describes how we want to synchronize with the subpass after this one.
*
* VK_ACCESS_SHADER_READ_BIT:
* VK_ACCESS_SHADER_WRITE_BIT:
* read a texture or write to a color buffer
*
* VK_ACCESS_COLOR_ATTACHMENT_READ_BIT:
* VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT:
* VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT:
* VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT:
* read/write to an attachment.
*
* VK_ACCESS_INPUT_ATTACHMENT_READ_BIT:
* for accessing the inputAttachment of a subpass.
*
* 0:
* do not write, but the next subpass will.
* Will automatically barrier the render pass.
*/
VkAccessFlags access;
};
SubpassMeta source; // The source subpass of this dependency (will take effect after this pass completes)
SubpassMeta destination; // The destination subpass of this dependency (will take effect before this pass)
VkDependencyFlags flags; // Other information that Vulkan needs to know about this dependency; for example, if we use an inputAttachment.
};
/**
* Describes a color attachment.
* Effectively RGB images that live entirely on the GPU.
*
* Describes the resolution mechanics of a multisampled image.
*/
struct ColorAttachmentMeta {
int location; // Where the GPU shaders expect this attachment to be available.
int descriptionIdx; // Index of this attachment in the VkAttachmentDescription data.
VkImageLayout layout; // Vulkan image layout. Shader optimized or host readable.
};
/**
* Create a list of VkAttachmentReference that describes the attachments used per subpass.
*/
static std::vector<VkAttachmentReference> parseColorReferences(std::vector<ColorAttachmentMeta> meta);
/**
* Create a list of VkAttachmentReference that describes the multisampling setup.
*/
static std::vector<VkAttachmentReference> parseMutisampling(int colorReferencesCount, std::vector<ColorAttachmentMeta> meta);
RenderPassBuilder(const RenderPassBuilder&) = delete;
RenderPassBuilder& operator=(const RenderPassBuilder&) = delete;
~RenderPassBuilder() = default;
RenderPassBuilder() = default;
/** Fluent API Features; chain calls to set data on the render pass.*/
#define fluent RenderPassBuilder&
// Set the number of framebuffers in the render pass
fluent setFramebufferCount(int count);
// Set an attachment description in the render pass
fluent setAttachment(int idx, const Attachment& attachment);
// Update the image backing an attachment. The function must be executable during execute() later on.
fluent updateAttachmentBacking(int idx, std::function<const Image&(int idx)>&& getBacking);
// Set a specific subpass. Use the static parse methods to create these vectors.
fluent setSubpass(int idx, std::vector<VkAttachmentReference>&& color, std::vector<VkAttachmentReference>&& multisample, VkAttachmentReference& depthStencil);
// Add a dependency between two subpasses.
fluent addDependency(const SubpassDependency& dep);
// Build the Render Pass with all the information given.
// Can be called multiple times with the same Builder.
[[nodiscard]] std::unique_ptr<vlkx::RenderPass> build() const;
private:
// Number of framebuffers in the render pass
std::optional<int> framebufferCount;
// Descriptions of used attachments
std::vector<VkAttachmentDescription> attachmentDescriptors;
// Functions to return attachment images.
std::vector<std::function<const Image&(int idx)>> attachmentGetters;
// Values to clear all attachments
std::vector<VkClearValue> clearValues;
// Descriptions of subpasses.
std::vector<SubpassAttachments> subpassAttachments;
// Descriptions of subpass dependencies.
std::vector<VkSubpassDependency> subpassDependencies;
};
}

View File

@ -0,0 +1,130 @@
#pragma once
#include <optional>
#include <memory>
#include "GenericRenderPass.h"
#include "vlkx/vulkan/abstraction/ImageUsage.h"
#include "vlkx/vulkan/abstraction/Image.h"
namespace vlkx {
// A simple and versatile way to configure render passes.
// Intended to be used with the SimpleRenderPass and the ScreenRenderPass.
class RendererConfig {
public:
RendererConfig(std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen = true) : renderImages(destinations) {
numOpaquePasses = 1;
rendersToScreen = toScreen;
}
RendererConfig(int passCount, std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen = true, std::optional<int> firstTransparent = std::nullopt, std::optional<int> firstOverlay = std::nullopt);
// Get the number of passes that use the depth buffer.
int depthPasses() const {
return numOpaquePasses + numTransparentPasses;
}
// Get the total number of passes.
int passes() const {
return depthPasses() + numOverlayPasses;
}
// Get whether any passes use the depth buffer.
bool usesDepth() const {
return depthPasses() > 0;
}
// Create the render pass builder. Can be called multiple times.
void build();
RendererConfig(RendererConfig&) noexcept = default;
RendererConfig(const RendererConfig&) = default;
int numOpaquePasses = 0;
int numTransparentPasses = 0;
std::vector<std::unique_ptr<vlkx::Image>>& renderImages;
bool rendersToScreen;
private:
int numOverlayPasses = 0;
};
/**
* Stores all of the information required to use an attachment.
* This is heavy, so is only used when the image is being finalized.
*/
struct AttachmentConfig {
AttachmentConfig(std::string_view name, std::optional<int>* index)
: name(name), index(*index) {}
AttachmentConfig& setOps(const RenderPassBuilder::Attachment::OpsType& ops) {
loadStoreOps = ops;
return *this;
}
AttachmentConfig& setUsage(const ImageUsage& final) {
finalUsage = final;
return *this;
}
std::string name;
std::optional<int>& index;
std::optional<RenderPassBuilder::Attachment::OpsType> loadStoreOps;
std::optional<ImageUsage> finalUsage;
};
/**
* A lighter version of the Attachment, used primarily in the On-Screen Pass Manager.
*/
struct Attachment {
explicit Attachment(std::string_view image) : name(image) {}
// Adds the image to the tracker and initializes state.
void add(MultiImageTracker& tracker, const Image& image) {
tracker.track(name, image.getUsage());
}
AttachmentConfig makeConfig() { return { name, &index }; }
const std::string name;
std::optional<int> index;
};
// Manages Render Passes that will output to the screen.
// This is necessarily exclusively a graphical pass.
// If necessary, a depth and stencil buffer will be maintained.
// The color buffer is automatically assumed to be the swapchain.
class ScreenRenderPassManager {
public:
explicit ScreenRenderPassManager(RendererConfig renderConfig) : config(renderConfig) {}
ScreenRenderPassManager(const ScreenRenderPassManager&) = delete;
ScreenRenderPassManager& operator=(const ScreenRenderPassManager&) = delete;
// Initialize the render pass we're managing.
void initializeRenderPass();
std::unique_ptr<vlkx::RenderPass>& getPass() { return pass; }
private:
// Prepare the Render Pass builder.
void preparePassBuilder();
const RendererConfig config;
Attachment destinationInfo { "Destination" };
Attachment multisampleInfo { "Multisample" };
Attachment depthStencilInfo { "Depth-Stencil" };
std::unique_ptr<vlkx::Image> depthStencilImage;
std::unique_ptr<vlkx::RenderPassBuilder> passBuilder;
std::unique_ptr<vlkx::RenderPass> pass;
};
/**
* A utility namespace used to house a constructor for creating a "simple" render pass with all the defaults.
*/
namespace SimpleRenderPass {
static std::unique_ptr<RenderPassBuilder> createBuilder(int framebuffers, const RendererConfig& config, const AttachmentConfig& color, const AttachmentConfig* multisample, const AttachmentConfig* depthStencil, MultiImageTracker& tracker);
}
}

View File

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

View File

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

View File

@ -1,31 +1,205 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
#include <optional>
#include <fstream>
#include "shadow/util/RefCounter.h"
#include "vlkx/vulkan/VulkanModule.h"
class Pipeline {
public:
Pipeline();
~Pipeline();
namespace vlkx {
class Pipeline;
// The active Graphics Pipeline layout.
VkPipelineLayout layout;
// The active Graphics Pipeline instance.
VkPipeline pipeline;
// A simple wrapper for the shader module used by the pipeline.
class ShaderModule {
public:
using CountedShader = shadowutil::RefCounter<ShaderModule>;
using ReleasePool = CountedShader::AutoRelease;
// Create the layout and pipeline for a vertex renderer
void create(VkExtent2D extent, VkDescriptorSetLayout set, VkRenderPass renderPass);
ShaderModule(const std::string& path);
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);
VkShaderModule createShaderModule(const std::vector<char>& code);
const VkShaderModule& operator*() const { return shader; }
void createPipelineLayout(VkDescriptorSetLayout set);
// For rendering objects that use traditional vertex-based mesh geometry.
// See Geo::Vertex for the uniform bindings.
void createVertexPipeline(VkExtent2D extent, VkRenderPass renderPass);
};
private:
VkShaderModule shader;
};
class PipelineBuilder {
public:
PipelineBuilder(const PipelineBuilder&) = delete;
PipelineBuilder& operator=(const PipelineBuilder&) = delete;
virtual ~PipelineBuilder() {
vkDestroyPipelineCache(VulkanModule::getInstance()->getDevice()->logical, cache, nullptr);
}
virtual std::unique_ptr<Pipeline> build() const = 0;
protected:
PipelineBuilder(std::optional<int> maxCache);
void setName(std::string&& n) { name = std::move(name); }
void setLayout(std::vector<VkDescriptorSetLayout>&& descLayouts, std::vector<VkPushConstantRange>&& constants);
const std::string& getName() const { return name; }
bool hasLayout() const { return layoutInfo.has_value(); }
const VkPipelineLayoutCreateInfo& getLayout() const { return layoutInfo.value(); }
private:
VkPipelineCache cache;
std::string name;
std::optional<VkPipelineLayoutCreateInfo> layoutInfo;
std::vector<VkDescriptorSetLayout> descLayouts;
std::vector<VkPushConstantRange> constants;
};
/**
* Use when creating Graphics pipelines.
* Internal state is preserved so that multiple pipelines can be created with one builder.
* However, shaders are single-usage. Bind a new shader before claling build again.
* See ShaderModule for more information, and how to change this.
*/
class GraphicsPipelineBuilder : public PipelineBuilder {
public:
struct Viewport {
VkViewport viewport;
VkRect2D scissor;
};
explicit GraphicsPipelineBuilder(std::optional<int> maxCache = std::nullopt);
GraphicsPipelineBuilder(const GraphicsPipelineBuilder&) = delete;
GraphicsPipelineBuilder& operator=(const GraphicsPipelineBuilder&) = delete;
#define fluent GraphicsPipelineBuilder&
fluent name(std::string&& name);
fluent depthTest(bool enable, bool write);
fluent stencilTest(bool enable);
fluent multiSample(VkSampleCountFlagBits samples);
fluent topology(VkPrimitiveTopology topology);
fluent stencilOp(const VkStencilOpState& state, VkStencilFaceFlags flags);
fluent addVertex(uint32_t bindPoint, VkVertexInputBindingDescription&& desc, std::vector<VkVertexInputAttributeDescription>&& attrs);
fluent layout(std::vector<VkDescriptorSetLayout>&& descLayouts, std::vector<VkPushConstantRange>&& constants);
fluent viewport(const Viewport& port, bool flipY = true);
fluent renderPass(const VkRenderPass& pass, uint32_t subpass);
fluent colorBlend(std::vector<VkPipelineColorBlendAttachmentState>&& states);
fluent shader(VkShaderStageFlagBits stage, std::string&& file);
std::unique_ptr<Pipeline> build() const override;
private:
struct PassInfo {
VkRenderPass pass;
uint32_t subpass;
};
VkPipelineInputAssemblyStateCreateInfo assemblyInfo;
VkPipelineRasterizationStateCreateInfo rasterizationInfo;
VkPipelineMultisampleStateCreateInfo multisampleInfo;
VkPipelineDepthStencilStateCreateInfo depthStencilInfo;
VkPipelineDynamicStateCreateInfo dynamicStateInfo;
std::vector<VkVertexInputBindingDescription> bindingDescs;
std::vector<VkVertexInputAttributeDescription> attrDescs;
std::optional<Viewport> viewportMeta;
std::optional<PassInfo> passMeta;
std::vector<VkPipelineColorBlendAttachmentState> blendStates;
std::map<VkShaderStageFlagBits, std::string> shaders;
};
/**
* Use when creating Compute Shader pipelines.
* Internal state is preserved so that multiple pipelines can be created with one builder.
* However, shaders are single-usage. Bind a new shader before claling build again.
* See ShaderModule for more information, and how to change this.
*/
class ComputePipelineBuilder : public PipelineBuilder {
public:
explicit ComputePipelineBuilder(std::optional<int> maxCache = std::nullopt) : PipelineBuilder(maxCache) {}
ComputePipelineBuilder(const ComputePipelineBuilder&) = delete;
ComputePipelineBuilder& operator=(const ComputePipelineBuilder&) = delete;
#define fluent ComputePipelineBuilder&
fluent name(std::string&& name);
fluent layout(std::vector<VkDescriptorSetLayout>&& descLayouts, std::vector<VkPushConstantRange>&& pushConstants);
fluent shader(std::string&& path);
std::unique_ptr<Pipeline> build() const override;
private:
std::optional<std::string> shaderPath;
};
/**
* Pipeline configures:
* - Shader Stages
* - Fixed Function stages
* - Vertex input bindings
* - Vertex attributes
* - Assembly
* - Tesselation
* - Viewport and Scissor
* - Rasterization
* - Multisampling
* - Depth testing
* - Stencil testing
* - Color blending
* - Dynamic states
* - Pipeline layout
* - Descriptor set layout
* - Push constant ranges
*
* Create a Pipeline with one of the builders above.
*/
class Pipeline {
public:
Pipeline(const Pipeline&) = delete;
Pipeline& operator=(const Pipeline&) = delete;
~Pipeline();
void bind(const VkCommandBuffer& buffer) const;
const VkPipeline& operator*() const { return pipeline; }
const VkPipelineLayout& getLayout() const { return layout; }
VkPipelineBindPoint getBind() const { return bindPoint; }
static VkPipelineColorBlendAttachmentState getAlphaBlendState(bool blending) {
return {
blending, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
VK_BLEND_OP_ADD, VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
};
}
private:
friend std::unique_ptr<Pipeline> GraphicsPipelineBuilder::build() const;
friend std::unique_ptr<Pipeline> ComputePipelineBuilder::build() const;
Pipeline(std::string name, const VkPipeline& line, const VkPipelineLayout& lay, VkPipelineBindPoint bPoint)
: name(std::move(name)), pipeline(line), layout(lay), bindPoint(bPoint) {}
const std::string name;
// The active Pipeline layout.
const VkPipelineLayout layout;
// The active Pipeline instance.
const VkPipeline pipeline;
// Whether this is a graphics or compute pipeline
const VkPipelineBindPoint bindPoint;
};
}

View File

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

View File

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

View File

@ -4,6 +4,7 @@
#include <vector>
#include <set>
#include <algorithm>
#include "vlkx/vulkan/abstraction/Image.h"
class SwapChain {
public:
@ -14,7 +15,8 @@ public:
VkFormat format;
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);
VkPresentModeKHR chooseMode(const std::vector<VkPresentModeKHR>& availableModes);

View File

@ -4,19 +4,12 @@
#include <functional>
#include <vulkan/vulkan.h>
#include <vlkx/vulkan/VulkanManager.h>
#include <vulkan/vk_mem_alloc.h>
#include "VulkanDevice.h"
#include "exports.h"
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 {
VkImage image;
VmaAllocation allocation;
@ -27,194 +20,20 @@ namespace VkTools {
VmaAllocation allocation;
};
ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent, VkDevice device);
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);
extern API VmaAllocator allocator;
ManagedImage createImage(VkFormat format, VkImageUsageFlags flags, VkExtent3D extent);
#ifdef VKTOOLS_IMPLEMENTATION
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;
VkSampler createSampler(VkFilter filters, VkSamplerAddressMode mode, uint32_t mipping, VkDevice device);
// Prepare the managed image
ManagedImage image {};
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipping, uint32_t layers,
VkDevice device);
// Set up image allocation
VmaAllocationCreateInfo allocateInfo = {};
allocateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
ManagedBuffer createGPUBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
VkDevice logicalDevice, VkPhysicalDevice physicalDevice, bool hostVisible = true);
// Allocate + create the image
vmaCreateImage(g_allocator, &info, &allocateInfo, &image.image, &image.allocation, nullptr);
void immediateExecute(const std::function<void(const VkCommandBuffer &)> &execute, VulkanDevice *dev);
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, &copyInfo);
// ------ 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
}

View File

@ -11,7 +11,8 @@ public:
~ValidationAndExtension();
const std::vector<const char*> requiredValidations = {
"VK_LAYER_KHRONOS_validation"
"VK_LAYER_KHRONOS_validation",
//"VK_LAYER_LUNARG_api_dump"
};
VkDebugReportCallbackEXT callback;

View File

@ -30,6 +30,7 @@ public:
/** Physical Devices **/
VkPhysicalDevice physical;
VkPhysicalDeviceLimits limits;
SwapChainMeta swapChain;
QueueFamilies queueData;

View File

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

View File

@ -0,0 +1,99 @@
#pragma once
#include <vlkx/vulkan/ValidationAndExtension.h>
#include <vlkx/vulkan/VulkanDevice.h>
#include <vulkan/vk_mem_alloc.h>
#include <vulkan/vulkan.h>
#include <SDL_vulkan.h>
#include <core/Module.h>
#include "SwapChain.h"
namespace vlkx { class ScreenRenderPassManager; }
class VulkanModule : public ShadowEngine::RendererModule {
SHObject_Base(VulkanModule);
public:
VulkanModule();
~VulkanModule() override;
#ifdef _DEBUG
static const bool validationRequired = true;
#else
static const bool validationRequired = false;
#endif
void PreInit() override;
void Init() override;
void Recreate() override;
void Update(int frame) override;
void PreRender() override;
void Render(VkCommandBuffer& commands, int frame) override;
void OverlayRender() override;
void LateRender(VkCommandBuffer& commands, int frame) override;
void AfterFrameEnd() override;
void Destroy() override;
void Event(SDL_Event* e) override;
void BeginRenderPass(const std::unique_ptr<vlkx::RenderCommand>& commands) override;
void EnableEditor() override;
VkExtent2D GetRenderExtent() override;
// VulkanModule is a singleton class.
static VulkanModule* instance;
static VulkanModule* getInstance();
// Initialize all Vulkan context and prepare validations in debug mode.
void initVulkan(SDL_Window* window);
void createAppAndVulkanInstance(bool enableValidation, ValidationAndExtension* validations);
// Start and end a frame render.
void startDraw();
void endDraw();
// Cleanup after the application has closed.
void cleanup();
VkInstance getVulkan() { return vulkan; }
VulkanDevice* getDevice() { return device; }
SwapChain* getSwapchain() { return swapchain; }
VmaAllocator getAllocator() { return allocator; }
SDL_Window* getWind() { return wnd; }
const std::unique_ptr<vlkx::ScreenRenderPassManager>& getRenderPass();
private:
bool editorEnabled = false;
std::vector<VkDescriptorSet> editorRenderPlanes;
std::vector<std::unique_ptr<vlkx::Image>> editorContentFrames;
// The SDL Window contains the size of the drawable area.
SDL_Window* wnd;
// To handle the validation of Vulkan API usage
ValidationAndExtension* validators{};
// To manage interaction with the hardware
VulkanDevice* device{};
// To handle the framebuffers
SwapChain* swapchain{};
// To handle automatic management of memory.
VmaAllocator allocator{};
// To manage the Vulkan context that was passed to us by the API
VkInstance vulkan{};
// To manage the canvas that was given to us by GLFW
VkSurfaceKHR surface{};
};

View File

@ -0,0 +1,412 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vlkx/vulkan/VulkanDevice.h>
#include <variant>
#include <vector>
#include "vlkx/vulkan/Tools.h"
namespace vlkx {
// Root buffer class.
// Used to store & move data between the CPU and GPU.
// Basically; hotspot!
// Utilities and subclasses exist to optimise and speed up transfer and management of data in bulk.
class Buffer {
public:
// Metadata of CPU->GPU data copying.
struct CopyMeta {
const void* data; // The start of data in RAM
VkDeviceSize length; // The amount of data to move
VkDeviceSize start; // The start (destination) in GPU memory
};
// Metadata of bulk CPU->GPU data copying.
struct BulkCopyMeta {
VkDeviceSize length; // The total data size of all transfers.
std::vector<CopyMeta> metas;
};
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
Buffer();
virtual ~Buffer() {
}
protected:
};
/*******************************************/
/** */
/** START OF DATA BUFFERS */
/** */
/*******************************************/
// A buffer that stores data on GPU.
// Usage of the data is determined by child classes.
class DataBuffer : public Buffer {
public:
DataBuffer(const DataBuffer&) = delete;
DataBuffer& operator=(const DataBuffer&) = delete;
DataBuffer() = default;
~DataBuffer() override {
vmaDestroyBuffer(VkTools::allocator, managed.buffer, managed.allocation);
}
protected:
using Buffer::Buffer;
void setBuffer(const VkTools::ManagedBuffer& buffer) { managed = buffer; }
VkTools::ManagedBuffer get() const { return managed; }
const VkBuffer& getBuffer() const { return managed.buffer; }
private:
VkTools::ManagedBuffer managed;
};
// A buffer visible to both GPU and CPU.
// Useful for uploading data to GPU for format conversions.
class StagingBuffer : public DataBuffer {
public:
StagingBuffer(const BulkCopyMeta& copyMeta);
StagingBuffer(const StagingBuffer&) = delete;
StagingBuffer& operator=(const StagingBuffer&) = delete;
void copy(const VkBuffer& target) const;
private:
const VkDeviceSize dataSize;
};
// Root class of vertex buffers.
// Provides utilities for subclasses.
class VertexBuffer : public DataBuffer {
public:
VertexBuffer(const VertexBuffer&) = delete;
VertexBuffer& operator=(const VertexBuffer&) = delete;
// Get attributes of vertexes in the buffer
// Location will start from "start"
// Binding will not be set
std::vector<VkVertexInputAttributeDescription> getAttrs(uint32_t start) const;
// Draw these vertexes without a buffer per vertex.
static void draw(const VkCommandBuffer& buffer, uint32_t verts, uint32_t instances);
protected:
friend class DynamicBuffer;
explicit VertexBuffer(std::vector<VkVertexInputAttributeDescription>&& attrs) : DataBuffer(), attributes(attrs) {}
// Initialize device memory and the managed buffer.
// indices and vertexes are put in the same buffer.
// if dynamic, the buffer will be host visible, this allows dynamic text.
// otherwise, the buffer is device local.
void create(VkDeviceSize totalSize, bool dynamic, bool indexes);
const std::vector<VkVertexInputAttributeDescription> attributes;
};
/*******************************************/
/** */
/** END OF DATA BUFFERS */
/** */
/*******************************************/
// A simple plugin to allow the vertex buffer to be widened when reserved with a larger size.
class DynamicBuffer {
public:
DynamicBuffer(const DynamicBuffer&) = delete;
DynamicBuffer& operator=(const DynamicBuffer&) = delete;
~DynamicBuffer() = default;
protected:
DynamicBuffer(size_t size, bool hasIndices, VertexBuffer* buffer);
// Reallocate the vertex buffer if the given pSize is larger than the available space
void resize(size_t pSize);
VkDeviceSize bufferSize() const { return size; }
private:
const bool hasIndices;
VertexBuffer* vertexBuffer;
VkDeviceSize size = 0;
};
/*******************************************/
/** */
/** START OF PER-VERTEX BUFFERS */
/** */
/*******************************************/
// Root class of buffers that store per-vertex data.
// eg. shader data
class PerVertexBuffer : public VertexBuffer {
public:
// Interprets the layout of data in containers (vector, etc)
struct VertexDataMeta {
template <typename C>
VertexDataMeta(const C& cont, int unitsPerMesh) : data(cont.data()), unitsPerMesh(unitsPerMesh), sizePerMesh(sizeof(cont[0]) * unitsPerMesh) {}
template <typename C>
VertexDataMeta(const C& cont) : VertexDataMeta(cont, static_cast<int>(cont.size())) {}
const void* data;
int unitsPerMesh;
size_t sizePerMesh;
};
// Interface for buffer data.
class BufferDataMeta {
public:
virtual ~BufferDataMeta() = default;
// Populate info and return a bulk copy meta for copying the data to device
virtual BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const = 0;
// Indicate whether the buffer contains index data too
virtual bool hasIndices() const = 0;
};
// Meshes do not share indices, with variable vertices.
class NoIndexBufferMeta : public BufferDataMeta {
public:
explicit NoIndexBufferMeta(std::vector<VertexDataMeta>&& perVertex) : perMeshVertices(perVertex) {}
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
bool hasIndices() const override { return false; };
private:
const std::vector<VertexDataMeta> perMeshVertices;
};
// Meshes share indices, with static vertices.
class SharedIndexMeta : public BufferDataMeta {
public:
SharedIndexMeta(int meshes, const VertexDataMeta& perVertex, const VertexDataMeta& sharedIndices) : meshes(meshes),
perMeshVertex(perVertex),
sharedIndices(sharedIndices) {}
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
bool hasIndices() const override { return true; };
private:
const int meshes;
const VertexDataMeta perMeshVertex;
const VertexDataMeta sharedIndices;
};
// Meshes do not share indexes, with variable indices and vertices.
class NoShareMeta : public BufferDataMeta {
public:
struct PerMesh {
VertexDataMeta indices;
VertexDataMeta vertices;
};
explicit NoShareMeta(std::vector<PerMesh>&& perMesh) : perMeshMeta(std::move(perMesh)) {}
BulkCopyMeta prepareCopy(PerVertexBuffer* buffer) const override;
bool hasIndices() const override { return true; };
private:
const std::vector<PerMesh> perMeshMeta;
};
PerVertexBuffer(const PerVertexBuffer&) = delete;
PerVertexBuffer& operator=(const PerVertexBuffer&) = delete;
// Render mesh a given number of times, into a recording buffer.
void draw(const VkCommandBuffer& buffer, uint32_t bind, int index, uint32_t instances) const;
protected:
using VertexBuffer::VertexBuffer;
// Stores vertex data for buffers without indices
struct MeshDataNoIndex {
struct Info {
uint32_t vertexCount;
VkDeviceSize vertexStart;
};
std::vector<Info> info;
};
// Stores vertex and index data for buffers with both
struct MeshDataIndex {
struct Info {
uint32_t indexCount;
VkDeviceSize indexStart;
VkDeviceSize vertexStart;
};
std::vector<Info> info;
};
std::variant<MeshDataNoIndex, MeshDataIndex>* getInfo() { return &meshDataInfo; }
private:
std::variant<MeshDataNoIndex, MeshDataIndex> meshDataInfo;
};
// Stores static data for one-time upload.
class StaticPerVertexBuffer : public PerVertexBuffer {
public:
StaticPerVertexBuffer(const BufferDataMeta& info, std::vector<VkVertexInputAttributeDescription>&& attrs);
StaticPerVertexBuffer(const StaticPerVertexBuffer&) = delete;
StaticPerVertexBuffer& operator=(const StaticPerVertexBuffer&) = delete;
};
// Stores host-visible data that can be reallocated.
class DynamicPerVertexBuffer : public PerVertexBuffer, public DynamicBuffer {
public:
DynamicPerVertexBuffer(size_t size, std::vector<VkVertexInputAttributeDescription>&& attrs) : PerVertexBuffer(std::move(attrs)), DynamicBuffer(size, true, this) {}
DynamicPerVertexBuffer(const DynamicPerVertexBuffer&) = delete;
DynamicPerVertexBuffer& operator=(const DynamicPerVertexBuffer&) = delete;
void copyToDevice(const BufferDataMeta& meta);
};
/*******************************************/
/** */
/** END OF PER-VERTEX BUFFERS */
/** */
/*******************************************/
// Root class of buffers that store vertex data per instance of a mesh.
class PerInstanceVertexBuffer : public VertexBuffer {
public:
PerInstanceVertexBuffer(const PerInstanceVertexBuffer&) = delete;
PerInstanceVertexBuffer& operator=(const PerInstanceVertexBuffer&) = delete;
void bind(const VkCommandBuffer& commands, uint32_t bindPoint, int offset) const;
uint32_t getSize() const { return sizePerInstance; }
protected:
PerInstanceVertexBuffer(uint32_t size, std::vector<VkVertexInputAttributeDescription>&& attrs) : VertexBuffer(std::move(attrs)), sizePerInstance(size) {}
private:
const uint32_t sizePerInstance;
};
// Stores vertices that are static per instance of the mesh.
class StaticPerInstanceBuffer : public PerInstanceVertexBuffer {
public:
StaticPerInstanceBuffer(uint32_t size, const void* data, uint32_t instances, std::vector<VkVertexInputAttributeDescription>&& attrs);
template <typename C>
StaticPerInstanceBuffer(const C& cont, std::vector<VkVertexInputAttributeDescription>&& attrs) : StaticPerInstanceBuffer(sizeof(cont[0]), cont.data(), CONTAINER_SIZE(cont), std::move(attrs)) {}
StaticPerInstanceBuffer(const StaticPerInstanceBuffer&) = delete;
StaticPerInstanceBuffer& operator=(const StaticPerInstanceBuffer&) = delete;
};
// Stores vertices of meshes that are dynamic (ie. text, shifting meshes
class DynamicPerInstanceBuffer : public PerInstanceVertexBuffer, public DynamicBuffer {
public:
DynamicPerInstanceBuffer(uint32_t size, size_t maxInstances, std::vector<VkVertexInputAttributeDescription>&& attrs) : PerInstanceVertexBuffer(size, std::move(attrs)), DynamicBuffer(size * maxInstances, false, this) {}
DynamicPerInstanceBuffer(const DynamicPerInstanceBuffer&) = delete;
DynamicPerInstanceBuffer& operator=(const DynamicPerInstanceBuffer*) = delete;
void copyToDevice(const void* data, uint32_t instances);
template <typename C>
void copyToDevice(const C& cont) {
copyToDevice(cont.data(), CONTAINER_SIZE(cont));
}
};
/*******************************************/
/** */
/** END OF PER-INSTANCE BUFFERS */
/** */
/*******************************************/
// Holds uniform data on host and device.
// Supports superallocating (allocating more than one "set" of uniforms at once)
// Data is stored on host and device simultaneously, so set the host data and flush it to the device.
class UniformBuffer : public DataBuffer {
public:
UniformBuffer(size_t chunkSize, int chunks);
UniformBuffer(const UniformBuffer&) = delete;
UniformBuffer& operator=(const UniformBuffer&) = delete;
~UniformBuffer() override { delete data; }
// Whether this buffer holds a single chunk (is not superallocated).
// Simplifies certain algorithms significantly if you know this beforehand
bool isSingle() const { return numChunks == 1; }
// Get the data in the buffer, casted to the given type
template <typename DataType>
DataType* getData(int index) const {
checkIndex(index);
return reinterpret_cast<DataType*>(data + chunkSize * index);
}
// Upload (flush) the uniform to the GPU
void upload(int index) const;
void upload(int index, VkDeviceSize dataSize, VkDeviceSize start) const;
static VkDescriptorType getDescriptorType() { return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; }
VkDescriptorBufferInfo getDescriptorInfo(int index) const;
private:
void checkIndex(int index) const;
char* data;
const size_t chunkSize;
const int numChunks;
size_t chunkLength;
};
// A small, highly efficient buffer much like the Uniform Buffer.
class PushConstant {
public:
// Size must be < 128.
PushConstant(size_t size, int numFrames);
PushConstant(const PushConstant&) = delete;
PushConstant& operator=(const PushConstant&) = delete;
~PushConstant() { delete[] data; }
// Whether this buffer holds a single chunk (is not superallocated).
// Simplifies certain algorithms significantly if you know this beforehand
bool isSingle() const { return numFrames == 1; }
uint32_t getSize() const { return sizePerFrame; }
// Get the data in the buffer, casted to the given type
template <typename DataType>
DataType* getData(int frame) const {
checkIndex(frame);
return reinterpret_cast<DataType*>(data + (sizePerFrame * frame));
}
VkPushConstantRange makeRange(VkShaderStageFlags stage) {
return VkPushConstantRange { stage, 0, sizePerFrame };
}
// Upload (flush) the uniform to the GPU
void upload(const VkCommandBuffer& commands, const VkPipelineLayout& pipelineLayout, int frame, uint32_t offset, VkShaderStageFlags stage) const;
private:
void checkIndex(int index) const;
char* data;
const uint32_t sizePerFrame;
const int numFrames;
};
}

View File

@ -0,0 +1,87 @@
#pragma once
#include <vulkan/vulkan.h>
#include "Queue.h"
#include "vlkx/vulkan/VulkanDevice.h"
#include <functional>
#include <optional>
namespace vlkx {
// Root class of VkCommandBuffer wrappers.
class CommandBuffer {
public:
using Command = std::function<void(const VkCommandBuffer& buffer)>;
CommandBuffer(const CommandBuffer&) = delete;
CommandBuffer& operator=(const CommandBuffer&) = delete;
virtual ~CommandBuffer() {
vkDestroyCommandPool(dev->logical, pool, nullptr);
}
protected:
CommandBuffer();
void setPool(const VkCommandPool& newPool) { pool = newPool; }
VulkanDevice* dev;
private:
VkCommandPool pool;
};
// A command buffer that will be immediately executed.
class ImmediateCommand : public CommandBuffer {
public:
ImmediateCommand(Queue queue);
ImmediateCommand(const ImmediateCommand&) = delete;
ImmediateCommand& operator=(const ImmediateCommand&) = delete;
void run(const Command& cmd);
private:
const Queue queue;
VkCommandBuffer commands;
};
// A command buffer that will be reused every frame.
class RenderCommand : public CommandBuffer {
public:
using Command = std::function<void(const VkCommandBuffer& commands, uint32_t framebuffer)>;
using Update = std::function<void(int frame)>;
~RenderCommand() override {
// Destroy our own data
vkDestroySemaphore(dev->logical, renderDoneSem, nullptr);
vkDestroySemaphore(dev->logical, newImageSem, nullptr);
for (size_t i = 0; i < 2; i++) {
vkDestroyFence(dev->logical, inFlight[i], nullptr);
}
}
RenderCommand(int frames);
RenderCommand(const RenderCommand&) = delete;
RenderCommand& operator=(const RenderCommand&) = delete;
uint32_t getFrame() { return imageIndex; }
void nextFrame() { imageIndex = (imageIndex + 1) % 2; }
std::optional<VkResult> execute(int frame, const VkSwapchainKHR& swapchain, const Update& update, const Command& cmd);
// Renders a single frame out, no semaphores or fences.
std::optional<VkResult> executeSimple(int frame, const Update& update, const Command& cmd);
private:
std::vector<VkCommandBuffer> commands;
// Raised when a new image is available
VkSemaphore newImageSem;
// Raised when a render is finished
VkSemaphore renderDoneSem;
// Stores fences for frames that are currently "in flight".
std::vector<VkFence> inFlight;
// The index of the texture that is currently being used by the GPU.
uint32_t imageIndex = 0;
};
}

View File

@ -0,0 +1,71 @@
#pragma once
#include <map>
#include <vector>
#include <vulkan/vulkan.h>
#include <temp/model/Loader.h>
#include <vlkx/vulkan/VulkanModule.h>
namespace vlkx {
class Descriptor {
public:
using TextureType = vlkxtemp::ModelLoader::TextureType;
using BufferInfos = std::map<uint32_t, std::vector<VkDescriptorBufferInfo>>;
using ImageInfos = std::map<uint32_t, std::vector<VkDescriptorImageInfo>>;
struct Meta {
struct Binding {
uint32_t bindPoint;
uint32_t length;
};
VkDescriptorType type;
VkShaderStageFlags stage;
std::vector<Binding> bindings;
};
Descriptor(const Descriptor&) = delete;
Descriptor& operator=(const Descriptor&) = delete;
virtual ~Descriptor() {
vkDestroyDescriptorSetLayout(VulkanModule::getInstance()->getDevice()->logical, layout, nullptr);
}
const VkDescriptorSetLayout& getLayout() const { return layout; }
protected:
explicit Descriptor() = default;
void setLayout(const VkDescriptorSetLayout& newLayout) { layout = newLayout; }
private:
VkDescriptorSetLayout layout;
};
class StaticDescriptor : public Descriptor {
public:
StaticDescriptor(std::vector<Meta> metas);
StaticDescriptor(const StaticDescriptor&) = delete;
StaticDescriptor& operator=(const StaticDescriptor&) = delete;
~StaticDescriptor() override {
vkDestroyDescriptorPool(VulkanModule::getInstance()->getDevice()->logical, pool, nullptr);
}
const StaticDescriptor& buffers(VkDescriptorType type, const BufferInfos& infos) const;
const StaticDescriptor& images(VkDescriptorType type, const ImageInfos& infos) const;
void bind(const VkCommandBuffer& commands, const VkPipelineLayout& layout, VkPipelineBindPoint bindPoint) const;
private:
const StaticDescriptor& updateSet(const std::vector<VkWriteDescriptorSet>& write) const;
VkDescriptorPool pool;
VkDescriptorSet set;
};
// TODO: dynamic sets
}

View File

@ -0,0 +1,331 @@
#pragma once
#include "vlkx/vulkan/Tools.h"
#include "ImageUsage.h"
#include <shadow/util/RefCounter.h>
#include "Buffer.h"
#include <array>
#include <utility>
namespace vlkx {
// Describes an image without initializing or storing any heavy data.
class ImageDescriptor {
public:
enum class Type { Single, Cubemap };
struct Dimension {
uint32_t width;
uint32_t height;
uint32_t channels;
VkExtent2D getExtent() const { return { width, height }; }
size_t getSize() const { return width * height * channels; }
};
Type getType() const { return type; }
VkExtent2D getExtent() const { return dimensions.getExtent(); }
uint32_t getWidth() const { return dimensions.width; }
uint32_t getHeight() const { return dimensions.height; }
uint32_t getChannels() const { return dimensions.channels; }
std::vector<void*> getData() const {
if (type == Type::Single) return { (void*) data };
std::vector<void*> dataPtrs;
dataPtrs.reserve(6);
size_t offset = 0;
for (size_t i = 0; i < 6; i++) {
dataPtrs.emplace_back((char*) data + offset);
offset += dimensions.getSize();
}
return dataPtrs;
}
int getLayers() const { return type == Type::Single ? 1 : 6; }
ImageDescriptor(Type t, const Dimension& d, const void* ptr) : type(t), dimensions(d), data(ptr) {}
private:
Type type;
Dimension dimensions;
const void* data;
};
// A staging buffer specialized for uploading images.
class ImageStagingBuffer : public StagingBuffer {
public:
using StagingBuffer::StagingBuffer;
ImageStagingBuffer(const ImageStagingBuffer&) = delete;
ImageStagingBuffer& operator=(const ImageStagingBuffer&) = delete;
void copy(const VkImage& target, const VkExtent3D& extent, uint32_t layers) const;
};
// Root class that stores image data on GPU buffers
class ImageBuffer : public Buffer {
public:
ImageBuffer(const ImageBuffer&) = delete;
ImageBuffer& operator=(const ImageBuffer&) = delete;
~ImageBuffer() override {
vmaDestroyImage(VkTools::allocator, image.image, image.allocation);
}
const VkTools::ManagedImage& get() const { return image; }
const VkImage& getImage() const { return image.image; }
protected:
using Buffer::Buffer;
void setImage(const VkTools::ManagedImage newImg) { image = newImg; }
private:
VkTools::ManagedImage image;
};
// Base class of all images; stores the common data.
class Image {
public:
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;
virtual ~Image() {
vkDestroyImageView(dev->logical, view, nullptr);
}
static VkDescriptorType getSampleType() { return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; }
static VkDescriptorType getLinearType() { return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; }
static ImageDescriptor loadSingleFromDisk(std::string path, bool flipY);
// The following are left unimplemented intentionally.
//static ImageDescriptor loadSingleFromVFS(std::string path, bool flipY);
static ImageDescriptor loadCubeFromDisk(const std::string& directory, const std::array<std::string, 6>& files, bool flipY);
//static ImageDescriptor loadCubeFromVFS(std::string directory, const std::array<std::string, 6>& files, bool flipY);
virtual ImageUsage getUsage() const { return ImageUsage {}; }
const VkTools::ManagedImage& operator*() const { return get(); }
virtual const VkTools::ManagedImage& get() const = 0;
virtual const VkImage& getImage() const = 0;
const VkImageView& getView() const { return view; }
const VkExtent2D& getExtent() const { return extent; }
VkFormat getFormat() const { return format; }
virtual VkSampleCountFlagBits getSamples() const { return VK_SAMPLE_COUNT_1_BIT; }
protected:
Image(const VkExtent2D& ext, VkFormat form);
void setView(const VkImageView& imgView) { view = imgView; }
VulkanDevice* dev;
VkImageView view;
VkExtent2D extent;
VkFormat format;
VkSampleCountFlagBits sampleCount;
};
// Configures image sampling in a sensible and extensible way
class ImageSampler {
public:
struct Config {
explicit Config(VkFilter filter = VK_FILTER_LINEAR, VkSamplerAddressMode mode = VK_SAMPLER_ADDRESS_MODE_REPEAT) : filter(filter), mode(mode) {}
VkFilter filter;
VkSamplerAddressMode mode;
};
ImageSampler(int mipLevels, const Config& config);
ImageSampler(const ImageSampler&) = delete;
ImageSampler& operator=(const ImageSampler&) = delete;
~ImageSampler() {
vkDestroySampler(dev->logical, sampler, nullptr);
}
const VkSampler& operator*() const { return sampler; }
private:
VkSampler sampler;
VulkanDevice* dev;
};
// Root of images which can be sampled.
class SamplableImage {
public:
virtual ~SamplableImage() = default;
// Return a VkDescriptorImageInfo we can use to update sets.
virtual VkDescriptorImageInfo getInfo(VkImageLayout layout) const = 0;
VkDescriptorImageInfo getInfoForSampling() const { return getInfo(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); }
VkDescriptorImageInfo getInfoForLinear() const { return getInfo(VK_IMAGE_LAYOUT_GENERAL); }
};
// A samplable image that lives on the GPU, with optional mipmapping.
// Use a RefCountedTexture when loading from files.
class TextureImage : public Image, public SamplableImage {
public:
// Image metadata
struct Meta {
VkExtent2D getExtent() const { return { width, height }; }
VkExtent3D get3DExtent() const { return { width, height, channels }; }
Buffer::BulkCopyMeta getCopyMeta() const;
std::vector<void*> data;
std::vector<ImageUsage> usages;
VkFormat format;
uint32_t width;
uint32_t height;
uint32_t channels;
};
TextureImage(bool mipmapping, const ImageSampler::Config& samplerConfig, const Meta& meta);
TextureImage(bool mipmapping, const ImageDescriptor& image, const std::vector<ImageUsage>& usages, const ImageSampler::Config& config);
TextureImage(const TextureImage&) = delete;
TextureImage& operator=(const TextureImage&) = delete;
const VkTools::ManagedImage& get() const override { return buffer.get(); }
const VkImage& getImage() const override { return buffer.getImage(); }
VkDescriptorImageInfo getInfo(VkImageLayout layout) const override {
return { *sampler, getView(), layout };
}
// Textures are sampled in fragment shaders.
ImageUsage getUsage() const override {
return ImageUsage::sampledFragment();
}
private:
class TextureBuffer : public ImageBuffer {
public:
TextureBuffer(bool mipmaps, const Meta& meta);
TextureBuffer(const TextureBuffer&) = delete;
TextureBuffer& operator=(const TextureBuffer&) = delete;
int getMipping() const { return mipLevels; }
private:
int mipLevels = 1;
};
const TextureBuffer buffer;
const ImageSampler sampler;
};
// A texture image that lives on GPU that is reference counted.
// This allows it to be reused multiple times without reading the file in more than once.
// It also allows the texture to be destructed automatically once nothing in the scene uses it, for eg. level changes.
class RefCountedTexture : public SamplableImage {
public:
// Cubemaps are simply 6 textures, so we include them here for easy instantiation.
struct CubemapLocation {
std::string directory;
std::array<std::string, 6> files;
};
// Reference Counting works on both individual files and cubemaps, so we put them together.
using ImageLocation = std::variant<std::string, CubemapLocation>;
RefCountedTexture(const ImageLocation& location, std::vector<ImageUsage> usages, const ImageSampler::Config& config)
: texture(get(location, std::move(usages), config)) {}
RefCountedTexture(RefCountedTexture&&) noexcept = default;
RefCountedTexture& operator=(RefCountedTexture&&) noexcept = default;
VkDescriptorImageInfo getInfo(VkImageLayout layout) const override {
return texture->getInfo(layout);
}
const Image* operator->() const { return texture.operator->(); }
private:
using ReferenceCounter = shadowutil::RefCounter<TextureImage>;
// Get or load the specified image.
static ReferenceCounter get(const ImageLocation& location, const std::vector<ImageUsage>& usages, const ImageSampler::Config& config);
ReferenceCounter texture;
};
// TODO: unowned, offscreen images.
// Image that can be used as a depth / stencil buffer attachment.
class DepthStencilImage : public Image {
public:
DepthStencilImage(const DepthStencilImage&) = delete;
DepthStencilImage& operator=(const DepthStencilImage&) = delete;
DepthStencilImage(const VkExtent2D& extent);
const VkTools::ManagedImage& get() const override { return buffer.get(); }
const VkImage& getImage() const override { return buffer.getImage(); }
private:
class DepthStencilBuffer : public ImageBuffer {
public:
DepthStencilBuffer(const VkExtent2D& extent, VkFormat format);
DepthStencilBuffer(const DepthStencilBuffer&) = delete;
DepthStencilBuffer& operator=(const DepthStencilBuffer&) = delete;
};
const DepthStencilBuffer buffer;
};
// Image that references an existing image on the swapchain
class SwapchainImage : public Image {
public:
SwapchainImage(const SwapchainImage&) = delete;
SwapchainImage& operator=(const SwapchainImage&) = delete;
SwapchainImage(const VkImage& image, const VkExtent2D& extent, VkFormat format);
const VkTools::ManagedImage& get() const override { return managed; }
const VkImage& getImage() const override { return image; }
private:
VkImage image;
VkTools::ManagedImage managed;
};
class MultisampleImage : public Image {
public:
enum class Mode {
MostEfficient,
Highest
};
static std::unique_ptr<Image> createColor(const Image& targetImage, Mode mode);
static std::unique_ptr<Image> createDepthStencilMS(const VkExtent2D& extent, Mode mode);
static std::unique_ptr<Image> createDepthStencil(VkExtent2D& extent, std::optional<Mode> mode);
const VkTools::ManagedImage& get() const override { return buffer.get(); }
const VkImage& getImage() const override { return buffer.getImage(); }
VkSampleCountFlagBits getSamples() const override { return samples; }
private:
class MultisampleBuffer : public ImageBuffer {
public:
enum class Type { Color, DepthStencil };
MultisampleBuffer(Type type, const VkExtent2D& extent, VkFormat format, VkSampleCountFlagBits samples);
MultisampleBuffer(const MultisampleBuffer&) = delete;
MultisampleBuffer& operator=(const MultisampleBuffer&) = delete;
};
MultisampleImage(const VkExtent2D& extent, VkFormat format, Mode mode, MultisampleBuffer::Type type);
VkSampleCountFlagBits chooseSamples(Mode mode);
const VkSampleCountFlagBits samples;
const MultisampleBuffer buffer;
};
}

View File

@ -0,0 +1,297 @@
#pragma once
#include <optional>
#include <map>
#include <string>
#include <vector>
#include <stdexcept>
namespace vlkx {
/**
* Describes how an image (collection of color data) will be used in the GPU.
* Has three parts; an overall description, access methods, and location of access.
* Use the static methods to create an instance, or the blank initializer and set fields as required.
*/
class ImageUsage {
public:
enum class Type {
DontCare, // Image is unused
RenderTarget, // Color attachment is used
DepthStencil, // Depth / Stencil buffer is used
Multisample, // Resolves to a multisampled image
Presentation, // Framebuffer for presentation
LinearAccess, // Host-Readable
InputAttachment, // Only what we write, is read.
Sampled, // Image is sampled (ie. texture)
Transfer // Image is used as an intermediate for transfer.
};
enum class Access {
DontCare, // Image is unused
ReadOnly, // Read Only access.
WriteOnly, // Write Only access.
ReadWrite, // Read/Write access.
};
enum class Location {
DontCare, // Image is unused
Host, // Image only exists on the host and will be transferred.
VertexShader, // Image is only used in the VertexAll Shader.
FragmentShader, // Image is only used in the Fragment Shader.
ComputeShader, // Image is only used in a Compute Shader.
Other, // Reserved.
};
// Sampled in a fragment shader. Read-Only.
static ImageUsage sampledFragment() { return { Type::Sampled, Access::ReadOnly, Location::FragmentShader }; };
// Used as a render target (a render pass will output to this image). Read/Write.
static ImageUsage renderTarget(int loc) { return { Type::RenderTarget, Access::ReadWrite, Location::Other, loc}; };
// Resolves to a multisampled image. Write-Only.
static ImageUsage multisample() { return { Type::Multisample, Access::WriteOnly, Location::Other }; };
// A depth or stencil buffer. Access is given, but must not be DontCare.
static ImageUsage depthStencil(Access acc) { return { Type::DepthStencil, acc, Location::Other}; };
// Used to show to the user. Write-Only.
static ImageUsage presentation() { return { Type::Presentation, Access::ReadOnly, Location::Other }; };
// Input attachment for a fragment shader. Usually a texture. Read-Only.
static ImageUsage input() { return { Type::InputAttachment, Access::ReadOnly, Location::FragmentShader }; };
// Linearly accessed image. For a compute shader. Access is given, but must not be DontCare.
static ImageUsage compute(Access acc) { return { Type::LinearAccess, acc, Location::ComputeShader }; };
explicit ImageUsage() : ImageUsage(Type::DontCare, Access::DontCare, Location::DontCare) {}
bool operator==(const ImageUsage& other) const {
return type == other.type && access == other.access && location == other.location;
}
VkImageUsageFlagBits getUsageFlags() const {
switch (type) {
case Type::DontCare: throw std::runtime_error("No usage for type DontCare");
case Type::RenderTarget:
case Type::Multisample:
case Type::Presentation:
return VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
case Type::DepthStencil:
return VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
case Type::LinearAccess:
return VK_IMAGE_USAGE_STORAGE_BIT;
case Type::Sampled:
return VK_IMAGE_USAGE_SAMPLED_BIT;
case Type::Transfer:
switch (access) {
case Access::DontCare: throw std::runtime_error("No access type specified for transfer usage.");
case Access::ReadOnly: return VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
case Access::WriteOnly: return VK_IMAGE_USAGE_TRANSFER_DST_BIT;
case Access::ReadWrite: throw std::runtime_error("ReadWrite access type for Transfer usage is invalid.");
}
}
}
static VkImageUsageFlags getFlagsForUsage(const std::vector<ImageUsage>& usages) {
auto flags = 0;
for (const auto& usage : usages) {
if (usage.type != Type::DontCare)
flags |= usage.getUsageFlags();
}
return static_cast<VkImageUsageFlags>(flags);
}
VkImageLayout getLayout() const {
switch (type) {
case Type::DontCare: return VK_IMAGE_LAYOUT_UNDEFINED;
case Type::RenderTarget:
case Type::Multisample:
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
case Type::DepthStencil: return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
case Type::Presentation: return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
case Type::LinearAccess: return VK_IMAGE_LAYOUT_GENERAL;
case Type::Sampled: return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
case Type::Transfer: return access == Access::ReadOnly ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
case Type::InputAttachment:
break;
}
}
VkPipelineStageFlags getStage() const {
switch (type) {
case Type::DontCare: return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
case Type::RenderTarget:
case Type::Multisample:
case Type::Presentation:
return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
case Type::DepthStencil:
return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
case Type::LinearAccess:
case Type::Sampled:
switch (location) {
case Location::Host: return VK_PIPELINE_STAGE_HOST_BIT;
case Location::FragmentShader: return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
case Location::ComputeShader: return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
case Location::VertexShader:
case Location::Other:
throw std::runtime_error("Linear or sampled attachments must not be used in VertexAll or Other stages.");
case Location::DontCare: throw std::runtime_error("Linear or sampled attachments must have an access.");
}
case Type::Transfer:
return VK_PIPELINE_STAGE_TRANSFER_BIT;
}
}
Access getAccess() const { return access; }
Type getType() const { return type; }
VkAccessFlags getAccessFlags() const {
switch (type) {
case Type::DontCare: return VK_ACCESS_NONE_KHR;
case Type::RenderTarget: return getReadOrWrite(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
case Type::DepthStencil: return getReadOrWrite(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
case Type::Multisample: return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
case Type::Presentation: return 0;
case Type::LinearAccess:
case Type::Sampled:
return location == Location::Host ? getReadOrWrite(VK_ACCESS_HOST_READ_BIT, VK_ACCESS_HOST_WRITE_BIT) : getReadOrWrite(VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
case Type::Transfer:
return getReadOrWrite(VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
case Type::InputAttachment:
return 0;
}
};
private:
ImageUsage(Type t, Access a, Location l, std::optional<int> att = std::nullopt)
: type(t), access(a), location(l), attachment(att) {}
Type type;
Access access;
Location location;
std::optional<int> attachment;
inline VkAccessFlags getReadOrWrite(VkAccessFlags read, VkAccessFlags write) const {
VkAccessFlags flag = 0;
if (access == Access::ReadOnly || access == Access::ReadWrite)
flag |= read;
if (access == Access::WriteOnly || access == Access::ReadWrite)
flag |= write;
return flag;
};
};
/**
* Describes how a single image will be used in each stage of a render pass.
* Allows a single image to be written to during one pass, read in a second, and presented in the final, and tracked.
* Helps figure out the optimizations that Vulkan can do to this image and the render passes that use it.
*/
class UsageTracker {
public:
explicit UsageTracker(const ImageUsage& initial) : initialUsage(initial) {}
explicit UsageTracker() = default;
UsageTracker(UsageTracker&&) noexcept = default;
UsageTracker& operator=(UsageTracker&&) noexcept = default;
// Fluent API; chain calls in a builder pattern.
#define fluent UsageTracker&
fluent add(int pass, const ImageUsage& usage) {
usageAtSubpass.insert( { pass, usage} );
return *this;
}
fluent add(int start, int end, const ImageUsage& usage) {
for (int subpass = start; subpass <= end; ++subpass)
add(subpass, usage);
return *this;
}
fluent addMultisample(int pass, std::string_view name) {
multisamples.insert( { pass, std::string(name) } );
return add(pass, ImageUsage::multisample());
}
fluent setFinal(const ImageUsage& usage) {
finalUsage = usage;
return *this;
}
[[nodiscard]] std::vector<ImageUsage> getUsages() const {
size_t count = usageAtSubpass.size() + (finalUsage.has_value() ? 1 : 0) + 1;
std::vector<ImageUsage> usages;
usages.reserve(count);
usages.emplace_back(initialUsage);
for(const auto& pair : usageAtSubpass)
usages.emplace_back(pair.second);
if (finalUsage.has_value())
usages.emplace_back(finalUsage.value());
return usages;
}
[[nodiscard]] const std::map<int, ImageUsage>& getUsageMap() const {
return usageAtSubpass;
}
ImageUsage& getInitialUsage() { return initialUsage; }
std::optional<ImageUsage> getFinalUsage() { return finalUsage; }
private:
std::map<int, ImageUsage> usageAtSubpass;
ImageUsage initialUsage;
std::optional<ImageUsage> finalUsage;
std::map<int, std::string> multisamples;
};
/**
* A simple wrapper that allows tracking the current usage of multiple images.
*/
class MultiImageTracker {
public:
MultiImageTracker() = default;
MultiImageTracker(const MultiImageTracker&) = delete;
MultiImageTracker& operator=(const MultiImageTracker&) = delete;
// Fluent API; chain calls in a builder pattern
#undef fluent
#define fluent MultiImageTracker&
fluent track(std::string&& name, const ImageUsage& usage) {
images.insert( { std::move(name), usage } );
return *this;
}
fluent track(const std::string& name, const ImageUsage& usage) {
return track(std::string(name), usage);
}
fluent update(const std::string& name, const ImageUsage& usage) {
auto iter = images.find(name);
iter->second = usage;
return *this;
}
[[nodiscard]] bool isTracking(const std::string& image) const {
return images.contains(image);
}
[[nodiscard]] const ImageUsage& get(const std::string& image) const {
return images.at(image);
}
private:
std::map<std::string, ImageUsage> images;
};
}

View File

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

View File

@ -5489,7 +5489,6 @@ public:
// Posts next part of an open string.
void ContinueString(const char* pStr);
// Posts next part of an open string. The number is converted to decimal characters.
void ContinueString(uint32_t n);
void ContinueString(uint64_t n);
// Posts next part of an open string. Pointer value is converted to characters
// using "%p" formatting - shown as hexadecimal number, e.g.: 000000081276Ad00
@ -5498,7 +5497,6 @@ public:
void EndString(const char* pStr = VMA_NULL);
// Writes a number value.
void WriteNumber(uint32_t n);
void WriteNumber(uint64_t n);
// Writes a boolean value - false or true.
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)
{
VMA_ASSERT(m_InsideString);
@ -5683,13 +5675,6 @@ void VmaJsonWriter::EndString(const char* pStr)
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)
{
VMA_ASSERT(!m_InsideString);

View File

@ -1,20 +1,129 @@
#include <vlkx/render/Camera.h>
void Camera::init(float fov, float width, float height, float near, float far) {
using namespace vlkx;
// Initialise members
position = glm::vec3(0, 0, 4);
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);
Camera& Camera::move(const glm::vec3 &delta) {
position += delta;
return *this;
}
void Camera::setPosition(glm::vec3 newPosition) {
position = newPosition;
}
Camera &Camera::setPos(const glm::vec3 &pos) {
position = pos;
return *this;
}
Camera &Camera::up(const glm::vec3 &up) {
upVector = glm::normalize(up);
return *this;
}
Camera &Camera::forward(const glm::vec3 &forward) {
frontVector = glm::normalize(forward);
rightVector = glm::normalize(glm::cross(frontVector, upVector));
return *this;
}
glm::mat4 Camera::getViewMatrix() const {
return glm::lookAt(position, position + frontVector, upVector);
}
PerspectiveCamera &PerspectiveCamera::fieldOfView(float newFov) {
fov = newFov;
return *this;
}
PerspectiveCamera::RT PerspectiveCamera::getRT() const {
const glm::vec3 upVec = glm::normalize(glm::cross(getRight(), getForward()));
const float fovTan = glm::tan(glm::radians(fov));
return { upVec * fovTan, getForward(), getRight() * fovTan * aspectRatio };
}
glm::mat4 PerspectiveCamera::getProjMatrix() const {
glm::mat4 proj = glm::perspective(glm::radians(fov), aspectRatio, nearPlane, farPlane);
proj[1][1] = -proj[1][1];
return proj;
}
OrthographicCamera &OrthographicCamera::setWidth(float vWidth) {
width = vWidth;
return *this;
}
glm::mat4 OrthographicCamera::getProjMatrix() const {
const float height = width / aspectRatio;
const auto halfSize = glm::vec2 { width, height } / 2.0f;
return glm::ortho(-halfSize.x, halfSize.x, -halfSize.y, halfSize.y, nearPlane, farPlane);
}
template <typename Type>
void UserCamera<Type>::setInternal(std::function<void(Type *)> op) {
op(camera.get());
reset();
}
template <typename Type>
void UserCamera<Type>::move(double x, double y) {
if (!isActive) return;
const auto offsetX = static_cast<float>(x * config.turnSpeed);
const auto offsetY = static_cast<float>(y * config.turnSpeed);
pitch = glm::clamp(pitch - offsetY, glm::radians(-89.9f), glm::radians(89.9f));
yaw = glm::mod(yaw - offsetX, glm::radians(360.0f));
camera->forward( { glm::cos(pitch) * glm::cos(yaw), glm::sin(pitch), glm::cos(pitch) * glm::sin(yaw) });
}
template <typename Type>
bool UserCamera<Type>::scroll(double delta, double min, double max) {
if (!isActive) return false;
if constexpr (std::is_same_v<Type, PerspectiveCamera>) {
auto newFov = (float) glm::clamp(camera->getFieldOfView() + delta, min, max);
if (newFov != camera->getFieldOfView()) {
camera->fieldOfView(newFov);
return true;
}
} else if constexpr (std::is_same_v<Type, OrthographicCamera>) {
const auto newWidth = (float) glm::clamp(camera->getWidth() + delta, min, max);
if (newWidth != camera->getWidth()) {
camera->setWidth(newWidth);
return true;
}
} else {
static_assert("Unhandled Camera Type");
}
return false;
}
template <typename Type>
void UserCamera<Type>::press(Camera::Input key, float time) {
using Key = Camera::Input;
if (!isActive) return;
if (!config.center.has_value()) {
const float distance = time * config.moveSpeed;
switch (key) {
case Key::Up:
camera->move(+camera->getForward() * distance); break;
case Key::Down:
camera->move(-camera->getForward() * distance); break;
case Key::Left:
camera->move(-camera->getRight() * distance); break;
case Key::Right:
camera->move(+camera->getRight() * distance); break;
}
} else {
reset();
}
}
template <typename Type>
void UserCamera<Type>::reset() {
refForward = camera->getForward();
refLeft = -camera->getRight();
pitch = yaw = 0;
}
template class vlkx::UserCamera<PerspectiveCamera>;
template class vlkx::UserCamera<OrthographicCamera>;

View File

@ -1,11 +1,11 @@
#include <vlkx/render/Geometry.h>
using namespace Geo;
void Mesh::setTriData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
std::vector<Vertex> Vertices = {
{ { 0.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.0f, 1.0f, 0.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 } },
void Mesh::setTriData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
std::vector<VertexAll> Vertices = {
{ { 0.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } },
{ { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } },
{ { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } },
};
std::vector<uint32_t> Indices = {
@ -18,13 +18,13 @@ void Mesh::setTriData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indi
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 = {
{ { -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.0f, 1.0f, 0.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.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }
std::vector<VertexAll> Vertices = {
{ { -1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 1.0 } },
{ { -1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } },
{ { 1.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } },
{ { 1.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }
};
std::vector<uint32_t> Indices = {
@ -37,38 +37,38 @@ void Mesh::setQuadData(std::vector<Vertex>& vertices, std::vector<uint32_t>& ind
indices = Indices;
}
void Mesh::setCubeData(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices) {
std::vector<Vertex> Vertices = {
void Mesh::setCubeData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
std::vector<VertexAll> Vertices = {
// Front
{ { -1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0f, 0.0f, 0.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.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 },{ 0.0, 1.0 } }, // 0
{ { -1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 0.0, 0.0 } }, // 1
{ { 1.0f, 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 0.0 } }, // 2
{ { 1.0f, -1.0f, 1.0f },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 3
// Back
{ { 1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.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.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.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 7
{ { 1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0, 1.0 } }, // 4
{ { 1.0f, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 0.0, 0.0 } }, // 5
{ { -1.0, 1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0, 0.0 } }, // 6
{ { -1.0, -1.0, -1.0 },{ 0.0f, 0.0f, -1.0 },{ 1.0, 1.0 } }, // 7
// Left
{ { -1.0, -1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.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.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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 11
{ { -1.0, -1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 8
{ { -1.0f, 1.0, -1.0 },{ -1.0f, 0.0f, 0.0 },{ 0.0, 0.0 } }, // 9
{ { -1.0, 1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 1.0, 0.0 } }, // 10
{ { -1.0, -1.0, 1.0 },{ -1.0f, 0.0f, 0.0 },{ 1.0, 1.0 } }, // 11
// Right
{ { 1.0, -1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0f, 0.0f, 1.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.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 },{ 0.0f, 0.0f, 1.0 },{ 1.0, 1.0 } }, // 15
{ { 1.0, -1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 1.0 } }, // 12
{ { 1.0f, 1.0, 1.0 },{ 1.0f, 0.0f, 0.0 },{ 0.0, 0.0 } }, // 13
{ { 1.0, 1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 1.0, 0.0 } }, // 14
{ { 1.0, -1.0, -1.0 },{ 1.0f, 0.0f, 0.0 },{ 1.0, 1.0 } }, // 15
// Top
{ { -1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0f, 0.0f, 1.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.0f, 0.0f, 1.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 },{ 0.0, 1.0 } }, // 16
{ { -1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 0.0, 0.0 } }, // 17
{ { 1.0f, 1.0f, -1.0f },{ 0.0f, 1.0f, 0.0 },{ 1.0, 0.0 } }, // 18
{ { 1.0f, 1.0f, 1.0f },{ 0.0f, 1.0f, 0.0 },{ 1.0, 1.0 } }, // 19
// Bottom
{ { -1.0f, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0f, 0.0f, 1.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.0f, 0.0f, 1.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.0f, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0, 1.0 } }, // 20
{ { -1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 0.0, 0.0 } }, // 21
{ { 1.0, -1.0, 1.0 },{ 0.0f, -1.0f, 0.0 },{ 1.0, 0.0 } }, // 22
{ { 1.0, -1.0, -1.0 },{ 0.0f, -1.0f, 0.0 },{ 1.0, 1.0 } }, // 23
};
std::vector<uint32_t> Indices = {
@ -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) {
std::vector<Vertex> Vertices;
void Mesh::setSphereData(std::vector<VertexAll>& vertices, std::vector<uint32_t>& indices) {
std::vector<VertexAll> Vertices;
std::vector<uint32_t> Indices;
float latitudeBands = 20.0f;
@ -116,7 +116,7 @@ void Mesh::setSphereData(std::vector<Vertex>& vertices, std::vector<uint32_t>& i
float sinPhi = sin(phi);
float cosPhi = cos(phi);
Vertex vs;
VertexAll vs;
vs.texture.x = (longNumber / longitudeBands); // u
vs.texture.y = (latNumber / latitudeBands); // v

View File

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

View File

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

View File

@ -0,0 +1,312 @@
#include "vlkx/render/shader/Pipeline.h"
#include "vlkx/vulkan/VulkanModule.h"
#include "shadow/util/File.h"
#include <numeric>
namespace vlkx {
struct ShaderStage {
VkShaderStageFlagBits stage;
ShaderModule::CountedShader module;
};
VkPipelineViewportStateCreateInfo createViewport(const GraphicsPipelineBuilder::Viewport& port) {
return {
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
nullptr, 0, 1, &port.viewport, 1, &port.scissor
};
}
VkPipelineColorBlendStateCreateInfo createBlend(const std::vector<VkPipelineColorBlendAttachmentState>& states) {
return {
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
nullptr, 0, VK_FALSE, VK_LOGIC_OP_CLEAR, static_cast<uint32_t>(states.size()), states.data(),
{ 0, 0, 0, 0 }
};
}
VkPipelineVertexInputStateCreateInfo createVertexInput(const std::vector<VkVertexInputBindingDescription>& bindingDescs,
const std::vector<VkVertexInputAttributeDescription>& attrDescs) {
return {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
nullptr, 0,
static_cast<uint32_t>(bindingDescs.size()), bindingDescs.data(),
static_cast<uint32_t>(attrDescs.size()), attrDescs.data()
};
}
std::vector<ShaderStage> createShader(const std::map<VkShaderStageFlagBits, std::string>& shaderMap) {
std::vector<ShaderStage> stages;
stages.reserve(shaderMap.size());
for (const auto& pair : shaderMap) {
stages.push_back({ pair.first, ShaderModule::CountedShader::get(pair.second, pair.second) });
}
return stages;
}
std::vector<VkPipelineShaderStageCreateInfo> createShaderStage(const std::vector<ShaderStage>& stages) {
static constexpr char entryPoint[] = "main";
std::vector<VkPipelineShaderStageCreateInfo> infos;
infos.reserve(stages.size());
for (const auto& stage : stages) {
infos.push_back({
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
nullptr, 0, stage.stage, **stage.module, entryPoint, nullptr
});
}
return infos;
}
ShaderModule::ShaderModule(const std::string &path) {
const shadowutil::FileData* file = shadowutil::loadFile(path);
const VkShaderModuleCreateInfo module {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
nullptr, 0, file->size, reinterpret_cast<const uint32_t*>(file->data.data())
};
if (vkCreateShaderModule(VulkanModule::getInstance()->getDevice()->logical, &module, nullptr, &shader) != VK_SUCCESS)
throw std::runtime_error("Unable to create shader module");
}
PipelineBuilder::PipelineBuilder(std::optional<int> maxCache) {
const VkPipelineCacheCreateInfo info {
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
nullptr, 0, static_cast<size_t>(maxCache.value_or(0)), nullptr
};
if (vkCreatePipelineCache(VulkanModule::getInstance()->getDevice()->logical, &info, nullptr, &cache) != VK_SUCCESS)
throw std::runtime_error("Unable to create pipeline cache");
}
void PipelineBuilder::setLayout(std::vector<VkDescriptorSetLayout>&& descs,
std::vector<VkPushConstantRange>&& pushConstants) {
std::vector<int> pushSizes (pushConstants.size());
for (size_t i = 0; i < pushConstants.size(); i++)
pushSizes[i] = pushConstants[i].size;
const auto totalSize = std::accumulate(pushSizes.begin(), pushSizes.end(), 0);
if (totalSize > 128)
throw std::runtime_error("Trying to set push constants of total size " + std::to_string(totalSize) + " into pipeline " + name);
descLayouts = std::move(descs);
constants = std::move(pushConstants);
layoutInfo.emplace(VkPipelineLayoutCreateInfo {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
nullptr, 0, static_cast<uint32_t>(descLayouts.size()), descLayouts.data(),
static_cast<uint32_t>(constants.size()), constants.data()
});
}
GraphicsPipelineBuilder::GraphicsPipelineBuilder(std::optional<int> maxCache) : PipelineBuilder(maxCache) {
assemblyInfo = {
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
nullptr, 0, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_FALSE
};
rasterizationInfo = {
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
nullptr, 0, VK_FALSE, VK_FALSE, VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE,
VK_FALSE, 0, 0, 0, 1
};
multisampleInfo = {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
nullptr, 0, VK_SAMPLE_COUNT_1_BIT, VK_FALSE, 0, nullptr, VK_FALSE, VK_FALSE
};
depthStencilInfo = {
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
nullptr, 0, VK_FALSE, VK_FALSE,
VK_COMPARE_OP_LESS_OR_EQUAL, VK_FALSE, VK_FALSE,
{}, {}, 0, 1
};
dynamicStateInfo = {
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
nullptr, 0, 0, nullptr
};
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::name(std::string &&name) {
setName(std::move(name));
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::depthTest(bool enable, bool write) {
depthStencilInfo.depthTestEnable = enable;
depthStencilInfo.depthWriteEnable = write;
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::stencilTest(bool enable) {
depthStencilInfo.stencilTestEnable = enable;
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::multiSample(VkSampleCountFlagBits samples) {
multisampleInfo.rasterizationSamples = samples;
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::topology(VkPrimitiveTopology topology) {
assemblyInfo.topology = topology;
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::stencilOp(const VkStencilOpState &state,
VkStencilFaceFlags flags) {
if (flags & VK_STENCIL_FACE_FRONT_BIT)
depthStencilInfo.front = state;
if (flags & VK_STENCIL_FACE_BACK_BIT)
depthStencilInfo.back = state;
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::addVertex(uint32_t bindPoint,
VkVertexInputBindingDescription &&desc,
std::vector<VkVertexInputAttributeDescription> &&attrs) {
desc.binding = bindPoint;
for (auto& attr : attrs)
attr.binding = bindPoint;
bindingDescs.push_back(desc);
attrDescs.reserve(attrDescs.size() + attrs.size());
std::move(attrs.begin(), attrs.end(), std::back_inserter(attrDescs));
attrs.clear();
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::layout(std::vector<VkDescriptorSetLayout> &&descLayouts,
std::vector<VkPushConstantRange> &&constants) {
setLayout(std::move(descLayouts), std::move(constants));
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::viewport(const vlkx::GraphicsPipelineBuilder::Viewport &port,
bool flipY) {
viewportMeta.emplace(port);
if (flipY) {
VkViewport& view = viewportMeta.value().viewport;
view.y += view.height;
view.height *= -1;
}
rasterizationInfo.frontFace = flipY ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE;
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::renderPass(const VkRenderPass &pass, uint32_t subpass) {
passMeta.emplace(PassInfo { pass, subpass });
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::colorBlend(
std::vector<VkPipelineColorBlendAttachmentState> &&states) {
blendStates = std::move(states);
return *this;
}
GraphicsPipelineBuilder& GraphicsPipelineBuilder::shader(VkShaderStageFlagBits stage, std::string &&file) {
shaders[stage] = std::move(file);
return *this;
}
std::unique_ptr<Pipeline> GraphicsPipelineBuilder::build() const {
if (!hasLayout())
throw std::runtime_error("Pipeline " + getName() + " has no layout set");
if (!viewportMeta.has_value())
throw std::runtime_error("Pipeline " + getName() + " has no viewport set");
if (!passMeta.has_value())
throw std::runtime_error("Pipeline " + getName() + " has no render pass set");
if (blendStates.empty())
throw std::runtime_error("Pipeline " + getName() + " has no color blend states.");
if (shaders.empty())
throw std::runtime_error("Pipeline " + getName() + " has no shaders bound");
const auto viewportState = createViewport(viewportMeta.value());
const auto blendState = createBlend(blendStates);
const auto vertexState = createVertexInput(bindingDescs, attrDescs);
const auto shaderStages = createShader(shaders);
const auto shaderStageInfo = createShaderStage(shaderStages);
VkPipelineLayout pipelineLayout;
if (vkCreatePipelineLayout(VulkanModule::getInstance()->getDevice()->logical, &getLayout(), nullptr, &pipelineLayout) != VK_SUCCESS)
throw std::runtime_error("Unable to create layout for pipeline " + getName());
const VkGraphicsPipelineCreateInfo createInfo {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, nullptr, 0,
static_cast<uint32_t>(shaderStageInfo.size()), shaderStageInfo.data(),
&vertexState, &assemblyInfo, nullptr, &viewportState,
&rasterizationInfo, &multisampleInfo, &depthStencilInfo, &blendState, &dynamicStateInfo,
pipelineLayout, passMeta->pass, passMeta->subpass, VK_NULL_HANDLE, 0
};
VkPipeline pipeline;
if (vkCreateGraphicsPipelines(VulkanModule::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &createInfo, nullptr, &pipeline) != VK_SUCCESS)
throw std::runtime_error("Failed to create pipeline " + getName());
return std::unique_ptr<Pipeline> {
new Pipeline(getName(), pipeline, pipelineLayout, VK_PIPELINE_BIND_POINT_GRAPHICS)
};
}
ComputePipelineBuilder& ComputePipelineBuilder::name(std::string &&name) {
setName(std::move(name));
return *this;
}
ComputePipelineBuilder& ComputePipelineBuilder::layout(std::vector<VkDescriptorSetLayout> &&descLayouts,
std::vector<VkPushConstantRange> &&pushConstants) {
setLayout(std::move(descLayouts), std::move(pushConstants));
return *this;
}
ComputePipelineBuilder& ComputePipelineBuilder::shader(std::string &&path) {
shaderPath.emplace(std::move(path));
return *this;
}
std::unique_ptr<Pipeline> ComputePipelineBuilder::build() const {
if (!hasLayout())
throw std::runtime_error("Pipeline " + getName() + " has no layout set");
if (!shaderPath.has_value())
throw std::runtime_error("Pipeline " + getName() + " has no shader set");
const auto shaderStages = createShader({{VK_SHADER_STAGE_COMPUTE_BIT, shaderPath.value()}});
const auto shaderStageInfo = createShaderStage(shaderStages);
if (shaderStageInfo.size() != 1)
throw std::runtime_error("Compute pipeline " + getName() + " must have exactly one shader bound");
VkPipelineLayout layout;
if (vkCreatePipelineLayout(VulkanModule::getInstance()->getDevice()->logical, &getLayout(), nullptr,
&layout) != VK_SUCCESS)
throw std::runtime_error("Unable to create layout for compute pipeline " + getName());
const VkComputePipelineCreateInfo createInfo{
VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
nullptr, 0,
shaderStageInfo[0], layout, VK_NULL_HANDLE, 0
};
VkPipeline pipeline;
if (vkCreateComputePipelines(VulkanModule::getInstance()->getDevice()->logical, VK_NULL_HANDLE, 1, &createInfo,
nullptr, &pipeline) != VK_SUCCESS)
throw std::runtime_error("Unable to create compute pipeline " + getName());
return std::unique_ptr<Pipeline>{
new Pipeline{getName(), pipeline, layout, VK_PIPELINE_BIND_POINT_COMPUTE}
};
}
void Pipeline::bind(const VkCommandBuffer &buffer) const {
vkCmdBindPipeline(buffer, bindPoint, pipeline);
}
Pipeline::~Pipeline() {
vkDestroyPipeline(VulkanModule::getInstance()->getDevice()->logical, pipeline, nullptr);
vkDestroyPipelineLayout(VulkanModule::getInstance()->getDevice()->logical, layout, nullptr);
}
}

View File

@ -0,0 +1,394 @@
#include "vlkx/render/render_pass/GPUPass.h"
namespace vlkx {
inline bool needsSynchronization(const ImageUsage& prev, const ImageUsage& curr) {
if (curr == prev && curr.getAccess() == ImageUsage::Access::ReadOnly)
return false;
return true;
}
void addUsageToSubpass(const ImageUsage& usage, RenderPassBuilder::SubpassDependency::SubpassMeta* pass) {
pass->stage |= usage.getStage();
pass->access |= usage.getAccessFlags();
}
void CommonPass::addUsage(std::string &&name, UsageTracker &&tracker) {
for (const auto& pair : tracker.getUsageMap())
validate(pair.first, name, false);
tracker.add(getVirtualInitial(), tracker.getInitialUsage());
if (tracker.getFinalUsage().has_value())
tracker.add(getVirtualFinal(), tracker.getFinalUsage().value());
usageHistory.emplace(std::move(name), std::move(tracker));
}
VkImageLayout CommonPass::getInitialLayout(const std::string &name) const {
return getHistory(name).getUsageMap().begin()->second.getLayout();
}
VkImageLayout CommonPass::getFinalLayout(const std::string &name) const {
return getHistory(name).getUsageMap().rbegin()->second.getLayout();
}
VkImageLayout CommonPass::getSubpassLayout(const std::string &name, int subpass) const {
validate(subpass, name, false);
return getUsage(name, subpass)->getLayout();
}
void CommonPass::update(const std::string &name, MultiImageTracker &tracker) const {
tracker.update(name, getHistory(name).getUsageMap().rbegin()->second);
}
const UsageTracker& CommonPass::getHistory(const std::string &name) const {
return usageHistory.at(name);
}
const ImageUsage* CommonPass::getUsage(const std::string &name, int pass) const {
validate(pass, name, true);
const UsageTracker& history = getHistory(name);
const auto iter = history.getUsageMap().find(pass);
return iter != history.getUsageMap().end() ? &iter->second : nullptr;
}
std::optional<CommonPass::Usages> CommonPass::checkForSync(const std::string &name, int pass) const {
validate(pass, name, true);
const UsageTracker& history = getHistory(name);
const auto currIter = history.getUsageMap().find(pass);
if (currIter == history.getUsageMap().end())
return std::nullopt;
const auto prevIter = std::prev(currIter);
const ImageUsage& prevUsage = prevIter->second;
const ImageUsage& currUsage = currIter->second;
if (!needsSynchronization(prevUsage, currUsage))
return std::nullopt;
const int prevSubpass = prevIter->first;
return CommonPass::Usages { prevSubpass, &prevUsage, &currUsage };
}
void CommonPass::validate(int pass, const std::string &image, bool includeVirtual) const {
if (includeVirtual) {
if (!(pass >= getVirtualInitial() && pass <= getVirtualFinal()))
throw std::runtime_error("Subpass out of range.");
} else {
if (!(pass >= 0 && pass < numPasses))
throw std::runtime_error("nv Subpass out of range.");
}
}
int GraphicsPass::add(const std::string &name, UsageTracker &&history, std::function<int(int)> &&getter,
const std::optional<RenderPassBuilder::Attachment::OpsType> ops) {
verifyHistory(name, history);
const std::optional<int> needsGetter = getFirstRenderTarget(history);
if (needsGetter.has_value()) {
if (getter == nullptr)
throw std::runtime_error("Image " + name + " is used as a render target without a location getter.");
} else {
getter = nullptr;
}
const int attachmentLocation = static_cast<int>(metas.size());
metas.insert(
{
name,
AttachmentMeta {
attachmentLocation,
std::move(getter),
getOps(name, history, ops),
{}
},
}
);
addUsage(std::string(name), std::move(history));
return attachmentLocation;
}
GraphicsPass& GraphicsPass::addMultisample(const std::string &source, const std::string &dest, int pass) {
validate(pass, source, false);
const auto source_iter = usageHistory.find(source);
if (source_iter == usageHistory.end())
throw std::runtime_error("Usage history not found for source image " + source);
const UsageTracker& source_history = source_iter->second;
if (!verifyImageUsage(source_history, pass, ImageUsage::Type::RenderTarget))
throw std::runtime_error("Usage type for source image " + source + " must be render target.");
const auto dest_iter = usageHistory.find(dest);
if (dest_iter == usageHistory.end())
throw std::runtime_error("Usage history not found for destination image " + dest);
const UsageTracker& dest_history = dest_iter->second;
if (!verifyImageUsage(dest_history, pass, ImageUsage::Type::Multisample))
throw std::runtime_error("Usage type for destination image " + dest + " must be multisample");
auto& targetMap = metas[source].multisample;
const bool inserted = targetMap.insert( { pass, dest }).second;
if (!inserted)
throw std::runtime_error("Image " + source + " is already bound to a multisample.");
return *this;
}
void GraphicsPass::setAttachments() {
for (const auto& pair : usageHistory) {
const std::string& name = pair.first;
const AttachmentMeta& meta = metas[name];
builder->setAttachment(meta.index, { meta.ops, getInitialLayout(name), getFinalLayout(name) } );
}
}
void GraphicsPass::setSubpasses() {
for (int pass = 0; pass < numPasses; ++pass) {
std::vector<RenderPassBuilder::ColorAttachmentMeta> colors;
std::vector<RenderPassBuilder::ColorAttachmentMeta> multisamples;
VkAttachmentReference dsRef { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED };
// Verify all images used, the long way around.
for (const auto& pair : usageHistory) {
const std::string& name = pair.first;
const ImageUsage* history = getUsage(name, pass);
if (history == nullptr)
continue;
const AttachmentMeta& meta = metas[name];
const VkAttachmentReference ref { static_cast<uint32_t>(meta.index), history->getLayout() };
switch (history->getType()) {
// If we're looking at a render target, we need to figure out where it gets its' details from.
case ImageUsage::Type::RenderTarget: {
const int location = meta.getter(pass);
const auto iter = meta.multisample.find(pass);
if (iter != meta.multisample.end()) {
const std::string& target = iter->second;
const ImageUsage* targetUsage = getUsage(target, pass);
if (targetUsage == nullptr)
throw std::runtime_error("Expected target image to have a usage");
multisamples.push_back( { location, metas[target].index, targetUsage->getLayout() });
}
colors.push_back( { location, static_cast<int>(ref.attachment), ref.layout });
break;
}
case ImageUsage::Type::DepthStencil:
if (dsRef.attachment != VK_ATTACHMENT_UNUSED)
throw std::runtime_error("Depth stencil used multiple times");
dsRef = ref;
break;
case ImageUsage::Type::Multisample:
break;
default:
throw std::runtime_error("Unreachable?");
}
}
auto colorRefs = RenderPassBuilder::parseColorReferences(colors);
auto multisampleRefs = RenderPassBuilder::parseMutisampling(colorRefs.size(), multisamples);
builder->setSubpass(pass, std::move(colorRefs), std::move(multisampleRefs), dsRef);
}
}
void GraphicsPass::setDependencies() {
if (getVirtualFinal() != numPasses)
throw std::runtime_error("Virtual subpass mismatch");
for (int pass = 0; pass <= numPasses; ++pass) {
std::map<int, RenderPassBuilder::SubpassDependency> deps;
for (const auto& pair : usageHistory) {
const auto usage = checkForSync(pair.first, pass);
if (!usage.has_value()) continue;
const ImageUsage& prev = usage.value().lastUsage;
const ImageUsage& curr = usage.value().currentUsage;
const int sourcePass = usage.value().lastSubpass;
auto iter = deps.find(sourcePass);
if (iter == deps.end()) {
const RenderPassBuilder::SubpassDependency defaultDep {
{ checkSubpass(sourcePass), VK_PIPELINE_STAGE_NONE_KHR, VK_ACCESS_NONE_KHR },
{ checkSubpass(pass), VK_PIPELINE_STAGE_NONE_KHR, VK_ACCESS_NONE_KHR },
0
};
iter = deps.insert( { sourcePass, defaultDep } ).first;
}
addUsageToSubpass(prev, &iter->second.source);
addUsageToSubpass(curr, &iter->second.destination);
}
for (const auto& pair : deps) {
const RenderPassBuilder::SubpassDependency& dep = pair.second;
builder->addDependency(dep);
}
}
}
std::unique_ptr<RenderPassBuilder> GraphicsPass::build(int framebuffers) {
builder = std::make_unique<RenderPassBuilder>();
builder->setFramebufferCount(framebuffers);
setAttachments();
setSubpasses();
setDependencies();
return std::move(builder);
}
std::optional<int> GraphicsPass::getFirstRenderTarget(const UsageTracker &history) const {
for (const auto& pair : history.getUsageMap()) {
const int pass = pair.first;
if (isVirtual(pass)) continue;
if (pair.second.getType() == ImageUsage::Type::RenderTarget) return pass;
}
return std::nullopt;
}
RenderPassBuilder::Attachment::OpsType GraphicsPass::getOps(const std::string &name, const UsageTracker &history,
const std::optional<RenderPassBuilder::Attachment::OpsType> &userOps) const {
const ImageUsage::Type type = getUsageType(name, history);
if (!userOps.has_value()) {
switch (type) {
case ImageUsage::Type::RenderTarget: return getDefaultOps();
case ImageUsage::Type::DepthStencil: return getStencilOps();
default:
throw std::runtime_error("Unreachable?");
}
}
const RenderPassBuilder::Attachment::OpsType& ops = userOps.value();
switch (type) {
case ImageUsage::Type::RenderTarget:
case ImageUsage::Type::DepthStencil:
return ops;
default:
throw std::runtime_error("Unreachable?");
}
}
ImageUsage::Type GraphicsPass::getUsageType(const std::string &name, const UsageTracker &history) const {
ImageUsage::Type prev = ImageUsage::Type::DontCare;
for (const auto& pair : history.getUsageMap()) {
if (isVirtual(pair.first)) continue;
ImageUsage::Type type = pair.second.getType();
if (type == ImageUsage::Type::Multisample)
type = ImageUsage::Type::RenderTarget;
if (prev == ImageUsage::Type::DontCare) {
prev = type;
} else if (type != prev) {
throw std::runtime_error("Inconsistent usage type specified for " + name);
}
}
if (prev == ImageUsage::Type::DontCare)
throw std::runtime_error("Image " + name + " has no usages.");
return prev;
}
bool GraphicsPass::verifyImageUsage(const UsageTracker &history, int subpass, ImageUsage::Type type) const {
const auto iter = history.getUsageMap().find(subpass);
return iter != history.getUsageMap().end() && iter->second.getType() == type;
}
void GraphicsPass::verifyHistory(const std::string &image, const UsageTracker &history) const {
for (const auto& pair : history.getUsageMap()) {
const ImageUsage::Type type = pair.second.getType();
if (type != ImageUsage::Type::RenderTarget && type != ImageUsage::Type::DepthStencil && type != ImageUsage::Type::Multisample)
throw std::runtime_error("Invalid usage of " + image + " at subpass " + std::to_string(pair.first));
}
}
ComputePass &ComputePass::add(std::string &&name, UsageTracker &&history) {
verify(name, history);
addUsage(std::move(name), std::move(history));
return *this;
}
void ComputePass::execute(const VkCommandBuffer &commands, uint32_t queueFamily,
const std::map<std::string, const VkImage*> &images,
const std::vector<std::function<void()>>& computeOps) const {
if (computeOps.size() != numPasses)
throw std::runtime_error("Compute shader mismatches ops and passes.");
if (getVirtualFinal() != numPasses)
throw std::runtime_error("Compute shader attempting to run too many subpasses");
for (int pass = 0; pass < numPasses; ++pass) {
for (const auto& pair : usageHistory) {
const std::string& image = pair.first;
const auto usage = checkForSync(image, pass);
if (!usage.has_value()) continue;
const auto iter = images.find(image);
if (iter == images.end())
throw std::runtime_error("Image " + image + " not provided");
barrier(commands, queueFamily, *iter->second, usage.value().lastUsage, usage.value().currentUsage);
}
if (pass < numPasses)
computeOps[pass]();
}
}
void ComputePass::barrier(const VkCommandBuffer &commands, uint32_t queueFamily, const VkImage &image,
const ImageUsage &prev, const ImageUsage &current) const {
const VkImageMemoryBarrier barrier {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
nullptr,
prev.getAccessFlags(),
current.getAccessFlags(),
prev.getLayout(),
current.getLayout(),
queueFamily,
queueFamily,
image,
{
VK_IMAGE_ASPECT_COLOR_BIT,
0,
1,
0,
1
}
};
vkCmdPipelineBarrier(
commands,
prev.getStage(),
current.getStage(),
0,
0,
nullptr,
0,
nullptr,
1,
&barrier
);
}
void ComputePass::verify(const std::string &name, const UsageTracker &history) const {
for (const auto& pair : history.getUsageMap()) {
const ImageUsage::Type type = pair.second.getType();
if (type != ImageUsage::Type::LinearAccess && type != ImageUsage::Type::Sampled && type != ImageUsage::Type::Transfer)
throw std::runtime_error("Compute shader using an attachment that is not guranteed to be readable.");
}
}
}

View File

@ -0,0 +1,268 @@
#include "vlkx/render/render_pass/GenericRenderPass.h"
#include <memory>
#include <string>
#include "vlkx/vulkan/VulkanModule.h"
#include <limits>
namespace vlkx {
/**
* Creates the necessary Clear Value struct to erase the given attachment.
* @param attachment the attachment metadata
* @return the Clear Value that will erase the attachment's data
*/
VkClearValue createClearFor(const RenderPassBuilder::Attachment& attachment) {
VkClearValue clear {};
if (std::holds_alternative<RenderPassBuilder::Attachment::ColorOps>(attachment.ops))
clear.color = { { 0, 0, 0, 0 } };
else {
clear.depthStencil = { 1.0, 0 };
}
return clear;
}
/**
* Convert a RenderPassBuilder attachment to a VkAttachmentDescription.
* Format will be UNDEFINED.
* Sample count will be 1.
*/
VkAttachmentDescription buildAttachment(const RenderPassBuilder::Attachment& attachment) {
VkAttachmentDescription descriptor {
{},
VK_FORMAT_UNDEFINED,
VK_SAMPLE_COUNT_1_BIT,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
attachment.layoutInitial,
attachment.layoutFinal
};
if (const auto& ops = std::get_if<RenderPassBuilder::Attachment::ColorOps>(&attachment.ops); ops != nullptr) {
descriptor.loadOp = ops->LOAD;
descriptor.storeOp = ops->STORE;
} else if (const auto& ops = std::get_if<RenderPassBuilder::Attachment::StencilDepthOps>(&attachment.ops); ops != nullptr) {
descriptor.loadOp = ops->DEPTH_LOAD;
descriptor.storeOp = ops->DEPTH_STORE;
descriptor.stencilLoadOp = ops->STENCIL_LOAD;
descriptor.stencilStoreOp = ops->STENCIL_STORE;
}
return descriptor;
}
std::vector<VkSubpassDescription> buildSubpassDescriptors(const std::vector<RenderPassBuilder::SubpassAttachments>& attachments) {
std::vector<VkSubpassDescription> descriptors;
descriptors.reserve(attachments.size());
for (const auto& attachment : attachments) {
descriptors.emplace_back(VkSubpassDescription {
{},
VK_PIPELINE_BIND_POINT_GRAPHICS,
0,
nullptr,
static_cast<uint32_t>(attachment.colorReferences.size()),
attachment.colorReferences.data(),
attachment.multisampleReferences.empty() ? nullptr : attachment.multisampleReferences.data(),
attachment.stencilDepthReference.has_value() ? &attachment.stencilDepthReference.value() : nullptr,
0,
nullptr
}
);
}
return descriptors;
}
VkSubpassDependency buildSubpassDependency(const RenderPassBuilder::SubpassDependency& dep) {
return VkSubpassDependency {
dep.source.index,
dep.destination.index,
dep.source.stage,
dep.destination.stage,
dep.source.access,
dep.destination.access,
dep.flags
};
}
std::vector<int> countColorAttachments(const std::vector<RenderPassBuilder::SubpassAttachments>& attachments) {
std::vector<int> count;
count.reserve(attachments.size());
for (const auto& attachment : attachments)
count.emplace_back(attachment.colorReferences.size());
return count;
}
std::vector<VkFramebuffer> createFramebuffers(const VkRenderPass& renderPass, const std::vector<std::function<const Image&(int idx)>> getters, int count, const VkExtent2D& extent) {
std::vector<VkImageView> views(getters.size());
VkFramebufferCreateInfo framebufferCreate {
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
nullptr,
{},
renderPass,
static_cast<uint32_t>(views.size()),
views.data(),
extent.width,
extent.height,
1
};
std::vector<VkFramebuffer> framebuffers(count);
for (int i = 0; i < framebuffers.size(); ++i) {
for (int image = 0; image < getters.size(); image++)
views[image] = getters[image](i).getView();
vkCreateFramebuffer(VulkanModule::getInstance()->getDevice()->logical, &framebufferCreate, nullptr, &framebuffers[i]);
}
return framebuffers;
}
std::vector<VkAttachmentReference> RenderPassBuilder::parseColorReferences(std::vector<ColorAttachmentMeta> meta) {
if (meta.empty())
return {};
// Search for the highest location index'd attachment reference.
// Shaders can define negative locations, so we have to start as low as we can go,
// and work our way up until we find the highest.
// If we start at 0, we risk missing an all-negative location set.
int max = std::numeric_limits<int>::min();
for (const auto& attachment : meta)
max = std::max(max, attachment.location);
std::vector<VkAttachmentReference> references(max + 1, { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED });
for (const auto& attachment : meta)
references[attachment.location] = { static_cast<uint32_t>(attachment.descriptionIdx), attachment.layout };
return references;
}
std::vector<VkAttachmentReference> RenderPassBuilder::parseMutisampling(int colorReferencesCount, std::vector<ColorAttachmentMeta> meta) {
std::vector<VkAttachmentReference> references(colorReferencesCount, { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED });
for (const auto& attachment : meta)
references[attachment.location] = { static_cast<uint32_t>(attachment.descriptionIdx), attachment.layout };
return references;
}
fluent RenderPassBuilder::setFramebufferCount(int count) {
framebufferCount = count;
return *this;
}
fluent RenderPassBuilder::setAttachment(int idx, const Attachment &attachment) {
if (idx > clearValues.size())
clearValues.resize(idx + 1);
clearValues.at(idx) = createClearFor(attachment);
if (idx > attachmentDescriptors.size())
attachmentDescriptors.resize(idx + 1);
attachmentDescriptors.at(idx) = buildAttachment(attachment);
if (attachmentDescriptors.size() > attachmentGetters.size())
attachmentGetters.resize(attachmentDescriptors.size());
return *this;
}
fluent RenderPassBuilder::updateAttachmentBacking(int idx,
std::function<const Image &(int)> &&getBacking) {
const Image& img = getBacking(idx);
attachmentDescriptors[idx].format = img.getFormat();
attachmentDescriptors[idx].samples = img.getSamples();
attachmentGetters.at(idx) = std::move(getBacking);
return *this;
}
fluent RenderPassBuilder::setSubpass(int idx, std::vector<VkAttachmentReference> &&color,
std::vector<VkAttachmentReference> &&multisample,
VkAttachmentReference &depthStencil) {
if (multisample.empty())
if (multisample.size() != color.size())
throw std::runtime_error("Constructing a subpass with mismatched color and multisample attachments");
SubpassAttachments attachments {
std::move(color), std::move(multisample), depthStencil
};
subpassAttachments.emplace_back(attachments);
return *this;
}
fluent RenderPassBuilder::addDependency(const SubpassDependency &dep) {
subpassDependencies.emplace_back(buildSubpassDependency(dep));
return *this;
}
std::unique_ptr<RenderPass> RenderPassBuilder::build() const {
if (framebufferCount == 0)
throw std::runtime_error("No framebuffers in render pass");
for (int i = 0; i < attachmentGetters.size(); i++)
if (attachmentGetters[i] == nullptr)
throw std::runtime_error("Image " + std::to_string(i) + " is not set in render pass");
const auto descriptors = buildSubpassDescriptors(subpassAttachments);
const VkRenderPassCreateInfo createInfo {
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
nullptr,
{},
static_cast<uint32_t>(attachmentDescriptors.size()),
attachmentDescriptors.data(),
static_cast<uint32_t>(descriptors.size()),
descriptors.data(),
static_cast<uint32_t>(subpassDependencies.size()),
subpassDependencies.data()
};
VkRenderPass pass;
if (vkCreateRenderPass(VulkanModule::getInstance()->getDevice()->logical, &createInfo, nullptr, &pass) != VK_SUCCESS)
throw std::runtime_error("Unable to create render pass");
const auto framebufferSize = attachmentGetters[0](0).getExtent();
return std::make_unique<RenderPass> (
static_cast<int>(descriptors.size()), pass, clearValues, framebufferSize, createFramebuffers(pass, attachmentGetters, framebufferCount.value(), framebufferSize),
countColorAttachments(subpassAttachments)
);
}
void RenderPass::execute(const VkCommandBuffer &commands, int imageIndex,
std::vector<std::function<void(const VkCommandBuffer &)>> ops) const {
const VkRenderPassBeginInfo begin {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr,
renderPass,
framebuffers[imageIndex],
{
{0, 0},
extent
},
static_cast<uint32_t>(clearValues.size()),
clearValues.data()
};
vkCmdBeginRenderPass(commands, &begin, VK_SUBPASS_CONTENTS_INLINE);
for (int i = 0; i < ops.size(); i++) {
if (i != 0)
vkCmdNextSubpass(commands, VK_SUBPASS_CONTENTS_INLINE);
ops[i](commands);
}
vkCmdEndRenderPass(commands);
}
RenderPass::~RenderPass() {
for (const auto& fb : framebuffers)
vkDestroyFramebuffer(VulkanModule::getInstance()->getDevice()->logical, fb, nullptr);
vkDestroyRenderPass(VulkanModule::getInstance()->getDevice()->logical, renderPass, nullptr);
}
}

View File

@ -0,0 +1,145 @@
#include "vlkx/render/render_pass/ScreenRenderPass.h"
#include "vlkx/vulkan/abstraction/Image.h"
#include "vlkx/render/render_pass/GPUPass.h"
#include "vlkx/vulkan/VulkanModule.h"
namespace vlkx {
void checkSubpass(int pass, int high) { if (pass < 1 || pass > high) throw std::runtime_error("Subpass index too high"); }
void addAttachment(const AttachmentConfig& config, GraphicsPass& pass, MultiImageTracker& tracker, GraphicsPass::LocationGetter&& getter, const std::function<void(UsageTracker&)>& populateHistory) {
const std::string& name = config.name;
if (!tracker.isTracking(name))
throw std::runtime_error("Attempting to add image " + name + " that is not tracked");
UsageTracker history { tracker.get(name) };
populateHistory(history);
if (config.finalUsage.has_value())
history.setFinal(config.finalUsage.value());
config.index = pass.add(name, std::move(history), std::move(getter), config.loadStoreOps);
pass.update(name, tracker);
}
RendererConfig::RendererConfig(int passCount, std::vector<std::unique_ptr<vlkx::Image>>& destinations, bool toScreen, std::optional<int> firstTransparent, std::optional<int> firstOverlay) : renderImages(destinations) {
if (passCount < 1)
throw std::runtime_error("Creating a RendererConfig with less than 1 subpass.");
if (firstTransparent.has_value())
checkSubpass(firstTransparent.value(), passCount);
if (firstOverlay.has_value())
checkSubpass(firstOverlay.value(), passCount);
if (firstOverlay.has_value())
numOverlayPasses = passCount - firstOverlay.value();
if (firstTransparent.has_value()) {
numOpaquePasses = firstTransparent.value();
numTransparentPasses = passCount - numOpaquePasses - numOverlayPasses;
} else {
numOpaquePasses = passCount - numOverlayPasses;
}
rendersToScreen = toScreen;
}
std::unique_ptr<RenderPassBuilder> SimpleRenderPass::createBuilder(int framebuffers,
const vlkx::RendererConfig &config,
const vlkx::AttachmentConfig &color,
const vlkx::AttachmentConfig *multisample,
const vlkx::AttachmentConfig *depthStencil,
vlkx::MultiImageTracker &tracker) {
const int passes = config.passes();
const int firstPass = 0;
const int lastPass = passes - 1;
const auto getLocation = [](int pass) { return 0; };
bool usesMultisample = multisample != nullptr;
bool usesDepth = depthStencil != nullptr;
const bool passesUsingDepth = config.depthPasses() > 0;
if (usesDepth) {
if (passesUsingDepth == 0)
throw std::runtime_error("Depth stencil defined, but never used.");
} else
if (passesUsingDepth > 0)
throw std::runtime_error("Depth stencil used, but never defined.");
GraphicsPass graphicsPass(passes);
addAttachment(color, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
if (usesMultisample)
history.add(lastPass, ImageUsage::multisample());
else
history.add(firstPass, lastPass, ImageUsage::renderTarget(0));
});
if (usesMultisample) {
addAttachment(*multisample, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
history.add(firstPass, lastPass, ImageUsage::renderTarget(0));
});
graphicsPass.addMultisample(multisample->name, color.name, lastPass);
}
if (usesDepth)
addAttachment(*depthStencil, graphicsPass, tracker, getLocation, [&](UsageTracker& history) {
if (config.numOpaquePasses > 0) {
const int lastOpaque = config.numOpaquePasses - 1;
history.add(firstPass, lastOpaque, ImageUsage::depthStencil(ImageUsage::Access::ReadWrite));
}
if (config.numTransparentPasses > 0) {
const int firstTransparent = config.numOpaquePasses;
const int lastTransparent = firstTransparent + config.numTransparentPasses - 1;
history.add(firstTransparent, lastTransparent, ImageUsage::depthStencil(ImageUsage::Access::ReadOnly));
}
});
return graphicsPass.build(framebuffers);
}
void ScreenRenderPassManager::initializeRenderPass() {
if (config.usesDepth()) {
depthStencilImage = MultisampleImage::createDepthStencil(VulkanModule::getInstance()->getSwapchain()->extent, std::nullopt);
}
if (passBuilder == nullptr)
preparePassBuilder();
passBuilder->updateAttachmentBacking(destinationInfo.index.value(), [this](int index) -> const Image& {
return *config.renderImages[index];
});
if (depthStencilImage != nullptr)
passBuilder->updateAttachmentBacking(depthStencilInfo.index.value(), [this](int index) -> const Image& {
return *depthStencilImage;
});
pass = passBuilder->build();
}
void ScreenRenderPassManager::preparePassBuilder() {
const bool usesDepth = depthStencilImage != nullptr;
const bool usesMultisampling = false;
MultiImageTracker tracker;
destinationInfo.add(tracker, *config.renderImages[0]);
if (usesDepth)
depthStencilInfo.add(tracker, *depthStencilImage);
if (usesMultisampling)
// TODO
(void) 0;
auto colorConfig = destinationInfo.makeConfig();
if (config.rendersToScreen)
colorConfig.setUsage(ImageUsage::presentation());
else
colorConfig.setUsage(ImageUsage::sampledFragment());
const auto multisampleConfig = multisampleInfo.makeConfig();
const auto depthConfig = depthStencilInfo.makeConfig();
passBuilder = SimpleRenderPass::createBuilder(config.renderImages.size(), config, colorConfig, usesMultisampling ? &multisampleConfig : nullptr, usesDepth ? &depthConfig : nullptr, tracker);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,238 @@
#include "temp/model/Builder.h"
namespace vlkxtemp {
using namespace vlkx;
using Geo::VertexAll;
using VertexData = PerVertexBuffer::NoShareMeta;
std::unique_ptr<SamplableImage> createTex(const ModelBuilder::TextureSource& source) {
const auto usages = { ImageUsage::sampledFragment() };
return std::make_unique<RefCountedTexture>(source, usages, ImageSampler::Config {});
}
void fillTextureMeta(const ModelBuilder::BindingPoints& binding, const ModelBuilder::TexturePerMesh& textures, const ModelBuilder::TexturePerMesh& sharedTextures, Descriptor::Meta* meta, Descriptor::ImageInfos* infos) {
*meta = {Image::getSampleType(), VK_SHADER_STAGE_FRAGMENT_BIT, {} };
auto& texBindings = meta->bindings;
for (size_t idx = 0; idx < (size_t) ModelBuilder::TextureType::Count; idx++) {
const auto type = (ModelBuilder::TextureType) idx;
const size_t numTextures = textures[idx].size() + sharedTextures[idx].size();
if (numTextures != 0) {
const auto iter = binding.find(type);
if (iter == binding.end())
throw std::runtime_error("Binding point of texture type " + std::to_string(idx) + " is not set");
texBindings.push_back({ iter->second, static_cast<uint32_t>(numTextures) });
auto& metaMap = (*infos)[iter->second];
metaMap.reserve(numTextures);
for (const auto& texture : textures[idx])
metaMap.push_back(texture->getInfoForSampling());
for (const auto& texture : sharedTextures[idx])
metaMap.push_back(texture->getInfoForSampling());
}
}
}
std::vector<VkPushConstantRange> createRanges(const ModelBuilder::ModelPushConstant& constants) {
std::vector<VkPushConstantRange> ranges;
ranges.reserve(constants.constants.size());
for (const auto& meta : constants.constants)
ranges.push_back( { constants.stage, meta.offset, meta.constants->getSize() });
return ranges;
}
VkVertexInputBindingDescription getBinding(uint32_t stride, bool instancing) {
return VkVertexInputBindingDescription{ 0, stride,instancing ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX, };
}
void setVertexInput(const PerVertexBuffer& buffer, const std::vector<PerInstanceVertexBuffer*>& instanceBuffers, GraphicsPipelineBuilder* builder) {
uint32_t start = 0;
auto attributes = buffer.getAttrs(start);
start += attributes.size();
builder->addVertex(0, Geo::VertexAll::getBindingDesc(), std::move(attributes));
for (size_t i = 0; i < instanceBuffers.size(); i++) {
if (instanceBuffers[i] == nullptr)
throw std::runtime_error("PerInstanceVertexBuffer not specified");
auto instanceAttrs = instanceBuffers[i]->getAttrs(start);
start += instanceAttrs.size();
auto instanceBinding = getBinding(instanceBuffers[i]->getSize(), true);
builder->addVertex(i + 1, std::move(instanceBinding), std::move(instanceAttrs));
}
}
void ModelBuilder::SingleMeshModel::load(ModelBuilder* builder) const {
const Wavefront obj(objFile, objIndexBase);
VertexData vertices {
{{
PerVertexBuffer::VertexDataMeta { obj.indices },
PerVertexBuffer::VertexDataMeta { obj.vertices }
}}
};
builder->vertexBuffer = std::make_unique<StaticPerVertexBuffer>(std::move(vertices), Geo::VertexAll::getAttributeDesc());
auto& meshTexs = builder->textures;
meshTexs.push_back({});
for (const auto& pair : textureSources) {
const auto type = (size_t)pair.first;
const auto& sources = pair.second;
meshTexs.back()[type].reserve(sources.size());
for (const auto& source : sources)
meshTexs.back()[type].push_back({createTex(source)});
}
}
void ModelBuilder::MultiMeshModel::load(ModelBuilder* builder) const {
const ModelLoader loader(models, textures);
std::vector<VertexData::PerMesh> data;
data.reserve(loader.getMeshes().size());
for (const auto& mesh : loader.getMeshes())
data.push_back({ PerVertexBuffer::VertexDataMeta { mesh.indices }, PerVertexBuffer::VertexDataMeta { mesh.vertices } });
builder->vertexBuffer = std::make_unique<StaticPerVertexBuffer>(VertexData { std::move(data)}, Geo::VertexAll::getAttributeDesc());
const auto usages = { ImageUsage::sampledFragment() };
auto& meshTexs = builder->textures;
meshTexs.reserve(loader.getMeshes().size());
for (const auto& mesh : loader.getMeshes()) {
meshTexs.push_back({});
for (const auto& tex : mesh.textures)
meshTexs.back()[(size_t) tex.type].push_back(std::make_unique<RefCountedTexture>(tex.path, usages, ImageSampler::Config {}));
}
}
ModelBuilder::ModelBuilder(std::string &&name, int frames, float aspect, const ModelResource &resource)
: frames(frames), aspectRatio(aspect), uniformBufferMeta(frames), pipelineBuilder(std::make_unique<GraphicsPipelineBuilder>()) {
pipelineBuilder->name(std::move(name));
resource.load(this);
}
ModelBuilder& ModelBuilder::texture(TextureType type, const TextureSource &source) {
sharedTextures[(size_t) type].push_back(createTex(source));
return *this;
}
ModelBuilder& ModelBuilder::bindTextures(TextureType type, uint32_t point) {
bindPoints[type] = point;
return *this;
}
ModelBuilder& ModelBuilder::instanceBuffer(vlkx::PerInstanceVertexBuffer* buffer) {
instanceBuffers.push_back(buffer);
return *this;
}
ModelBuilder& ModelBuilder::uniform(VkShaderStageFlags stage, std::vector<vlkx::Descriptor::Meta::Binding> &&bindings) {
uniformMeta.push_back({ UniformBuffer::getDescriptorType(), stage, std::move(bindings) });
return *this;
}
ModelBuilder& ModelBuilder::uniformBuffer(uint32_t point, const vlkx::UniformBuffer &buffer) {
for (size_t frame = 0; frame < frames; frame++) {
const int chunk = buffer.isSingle() ? 0 : frame;
uniformBufferMeta[frame][point].push_back( buffer.getDescriptorInfo(chunk) );
}
return *this;
}
ModelBuilder& ModelBuilder::pushStage(VkShaderStageFlags stage) {
if (!pushConstants.has_value())
pushConstants.emplace();
pushConstants->stage = stage;
return *this;
}
ModelBuilder& ModelBuilder::pushConstant(const vlkx::PushConstant* constant, uint32_t offset) {
if (!pushConstants.has_value())
pushConstants.emplace();
pushConstants.value().constants.push_back( ModelPushConstant::Meta { constant, offset });
return *this;
}
ModelBuilder& ModelBuilder::shader(VkShaderStageFlagBits stage, std::string &&file) {
pipelineBuilder->shader(stage, std::move(file));
return *this;
}
std::vector<ModelBuilder::Descriptors> ModelBuilder::createDescs() const {
std::vector<Descriptors> descs(frames);
auto infos = uniformMeta;
infos.resize(infos.size() + 1);
for (size_t frame = 0; frame < frames; frame++) {
descs[frame].reserve(textures.size());
for (const auto& tex : textures) {
Descriptor::ImageInfos image;
fillTextureMeta(bindPoints, tex, sharedTextures, &infos.back(), &image);
descs[frame].push_back(std::make_unique<StaticDescriptor>(infos));
descs[frame].back()->buffers(UniformBuffer::getDescriptorType(), uniformBufferMeta[frame]);
descs[frame].back()->images(Image::getSampleType(), image);
}
}
return descs;
}
std::unique_ptr<Model> ModelBuilder::build() {
if (pushConstants.has_value())
if (pushConstants->constants.empty())
throw std::runtime_error("Model sets push constant present but no data.");
auto descs = createDescs();
pipelineBuilder->layout(
{ descs[0][0]->getLayout() },
pushConstants.has_value() ? createRanges(pushConstants.value()) : std::vector<VkPushConstantRange> {}
);
setVertexInput(*vertexBuffer, instanceBuffers, pipelineBuilder.get());
uniformMeta.clear();
uniformBufferMeta.clear();
return std::unique_ptr<Model> {
new Model {
aspectRatio, std::move(vertexBuffer), std::move(instanceBuffers), std::move(pushConstants),
std::move(sharedTextures), std::move(textures), std::move(descs), std::move(pipelineBuilder)
}
};
}
void Model::update(bool opaque, const VkExtent2D &frame, VkSampleCountFlagBits samples,
const vlkx::RenderPass &pass, uint32_t subpass, bool flipY) {
pipeline = (*pipelineBuilder)
.depthTest(true, opaque)
.multiSample(samples)
.viewport({ { 0, 0, static_cast<float>(frame.width), static_cast<float>(frame.height), 0, 1 }, { { 0, 0 }, frame } })
.renderPass(*pass, subpass)
.colorBlend(std::vector<VkPipelineColorBlendAttachmentState>(pass.getAttachsInSubpass(subpass), vlkx::Pipeline::getAlphaBlendState(!opaque)))
.build();
}
void Model::draw(const VkCommandBuffer &commands, int frame, uint32_t instances) const {
pipeline->bind(commands);
for (size_t i = 0; i < perInstanceBuffers.size(); i++)
perInstanceBuffers[i]->bind(commands, i + 1, 0);
if (pushConstants.has_value())
for (const auto& meta : pushConstants->constants)
meta.constants->upload(commands, pipeline->getLayout(), frame, meta.offset, pushConstants->stage);
for (size_t mesh = 0; mesh < textures.size(); mesh++) {
descriptors[frame][mesh]->bind(commands, pipeline->getLayout(), pipeline->getBind());
vertexBuffer->draw(commands, 0, mesh, instances);
}
}
}

View File

@ -0,0 +1,96 @@
#include "temp/model/Loader.h"
#include <fstream>
#include <map>
#include <string>
#include <vector>
#include <sstream>
namespace vlkxtemp {
std::vector<std::string> split (std::string_view s, char delim) {
std::vector<std::string> result;
std::stringstream ss ((std::string(s)));
std::string item;
while (std::getline (ss, item, delim)) {
result.push_back (item);
}
return result;
}
Wavefront::Wavefront(std::string_view path, size_t index_base) {
std::ifstream file(std::string(path), std::ios::binary);
std::vector<glm::vec3> positions;
std::vector<glm::vec3> normals;
std::vector<glm::vec2> tex_coords;
std::map<std::string, uint32_t> loaded_vertices;
const auto parse_line = [&](std::string_view line) {
const size_t non_space = line.find_first_not_of(' ');
if (non_space == std::string::npos || line[0] == '#')
return;
switch (line[non_space]) {
case 'v': {
switch (line[non_space + 1]) {
case ' ': {
const auto nums = split(line.substr(non_space + 2), ' ');
positions.emplace_back(stof(nums[0]), stof(nums[1]), stof(nums[2]));
break;
}
case 'n': {
// Normal.
const auto nums = split(line.substr(non_space + 3), ' ');
normals.emplace_back(glm::vec3{stof(nums[0]), stof(nums[1]), stof(nums[2])});
break;
}
case 't': {
// Texture coordinates.
const auto nums = split(line.substr(non_space + 3), ' ');
tex_coords.emplace_back(glm::vec2{stof(nums[0]), stof(nums[1])});
break;
}
default:
throw std::runtime_error("Unexpected symbol " + std::to_string(line[non_space + 1]));
}
break;
}
case 'f': {
for (const auto& seg : split(line.substr(non_space + 2), ' ')) {
const auto iter = loaded_vertices.find(seg);
if (iter != loaded_vertices.end()) {
indices.push_back(iter->second);
} else {
indices.push_back(vertices.size());
loaded_vertices[seg] = vertices.size();
const auto idxs = split(seg, '/');
vertices.push_back(Geo::VertexAll {
positions.at(stoi(idxs[0]) - index_base),
normals.at(stoi(idxs[2]) - index_base),
tex_coords.at(stoi(idxs[1]) - index_base),
});
}
}
break;
}
default:
throw std::runtime_error("Unexpected symbol in OBJ file: " + std::to_string(line[non_space]));
}
};
std::string line;
int line_num = 1;
try {
for (; std::getline(file, line); ++line_num)
parse_line(line);
} catch (const std::exception& e) {
throw std::runtime_error("Failed to parse obj file, error on line " + std::to_string(line_num) + ": " + line + "; " + e.what());
}
}
ModelLoader::ModelLoader(const std::string &model, const std::string &textures) {
}
}

View File

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

View File

@ -1,5 +1,5 @@
#include <vlkx/vulkan/SwapChain.h>
#include <vlkx/vulkan/VulkanManager.h>
#include <vlkx/vulkan/VulkanModule.h>
#include "spdlog/spdlog.h"
SwapChain::SwapChain() {}
@ -45,7 +45,7 @@ VkExtent2D SwapChain::chooseExtent(const VkSurfaceCapabilitiesKHR& capabilities)
}
void SwapChain::create(VkSurfaceKHR surface) {
SwapChainMeta info = VulkanManager::getInstance()->getDevice()->swapChain;
SwapChainMeta info = VulkanModule::getInstance()->getDevice()->swapChain;
VkSurfaceFormatKHR chosenFormat = chooseFormat(info.formats);
VkPresentModeKHR chosenMode = chooseMode(info.modes);
@ -66,7 +66,7 @@ void SwapChain::create(VkSurfaceKHR surface) {
createInfo.imageArrayLayers = 1; // 2 for VR
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) };
if (queues.graphics != queues.presentation) {
@ -86,19 +86,32 @@ void SwapChain::create(VkSurfaceKHR surface) {
createInfo.oldSwapchain = VK_NULL_HANDLE;
// 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");
// Fetch our images from the swapchain
vkGetSwapchainImagesKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, &imageCount, nullptr);
images.resize(imageCount);
vkGetSwapchainImagesKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, &imageCount, images.data());
// Set members
format = chosenFormat.format;
extent = chosenExtent;
// 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() {
vkDestroySwapchainKHR(VulkanManager::getInstance()->getDevice()->logical, swapChain, nullptr);
vkDestroySwapchainKHR(VulkanModule::getInstance()->getDevice()->logical, swapChain, nullptr);
/*for (auto & image : images)
image.reset();
multisampleImg.reset(); */
}

View 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, &copyInfo);
}, dev);
}

View File

@ -41,6 +41,10 @@ std::vector<const char*> ValidationAndExtension::getRequiredExtensions(SDL_Windo
SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr);
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();
@ -57,17 +61,16 @@ std::vector<const char*> ValidationAndExtension::getRequiredExtensions(SDL_Windo
}
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType,
uint64_t object,
size_t location,
int32_t messageCode,
const char* pLayerPrefix,
const char* pMessage,
void* pUserData
) {
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objExt,
uint64_t obj,
size_t location,
int32_t code,
const char* layer,
const char* message,
void* user) {
std::cerr << "Validation from layer " << pLayerPrefix << ": " << pMessage << std::endl;
std::cerr << "Validation from layer " << layer << ": " << message << std::endl;
return false;
}

View File

@ -26,6 +26,7 @@ void VulkanDevice::choosePhysicalDevice(VkInstance* vulkan, VkSurfaceKHR surface
for (const auto& device : physicals) {
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(device, &props);
limits = props.limits;
bool dedicated = props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU;
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 families;
// Enumerate queueues
// Enumerate queues
uint32_t queueCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueCount, nullptr);

View File

@ -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 = &currentCommandBuffer;
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

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

View File

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

View File

@ -8,7 +8,6 @@ add_executable(shadow-runtime ${SOURCES})
target_include_directories(shadow-runtime PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(shadow-runtime PRIVATE SDL2::SDL2main PUBLIC shadow-engine)
add_custom_command(TARGET shadow-runtime POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:shadow-runtime> $<TARGET_FILE_DIR:shadow-runtime>
COMMAND_EXPAND_LISTS

View File

@ -13,13 +13,17 @@ public:
void Init() override;
void Update() override;
void Update(int frame) 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;

View File

@ -1,171 +1,92 @@
#include <GameModule.h>
#include "imgui.h"
#include "spdlog/spdlog.h"
#include "vlkx/render/geometry/SingleRenderer.h"
#include "imgui_impl_vulkan.h"
#include "core/ShadowApplication.h"
#include "core/SDL2Module.h"
#include "imgui_impl_sdl.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) \
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)
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() {
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>();
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));
Recreate();
}
void GameModule::Update() {
object.setRotation(glm::rotate(object.getRotation(), (float) 0.5, glm::vec3(1, 0, 0)));
void GameModule::Recreate() {
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() {
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
void GameModule::Update(int frame) {
const float elapsed_time = Time::timeSinceStart;
const glm::mat4 model = glm::rotate(glm::mat4{1.0f},
(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;
ImGui::Begin("Game module window", &active, ImGuiWindowFlags_MenuBar);
ImGui::Text("Such teext from curle's branch");
if (ImGui::Begin("Game module window", &active, ImGuiWindowFlags_MenuBar))
ImGui::Text("Such text from curle's branch");
ImGui::End();
CATCH(object.updateUniforms(camera);)
CATCH(object.draw();)
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();
}
bool open = false;
ImGui::ShowDemoWindow(&open);
}
void GameModule::AfterFrameEnd() {
Time::UpdateTime();
}
void GameModule::Destroy() {
ImGui_ImplVulkan_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
}
void GameModule::LateRender(VkCommandBuffer& commands, int frame) {}
void GameModule::PreRender() {}
void GameModule::Event(SDL_Event *) {
}
void GameModule::Destroy() {}
void GameModule::Event(SDL_Event *) {}

View File

@ -7,19 +7,8 @@
#include "core/ShadowApplication.h"
#include "GameModule.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) {
std::cout << "HIIII from a loaded dll weeeeeee!!!" << std::endl;
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());
}

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

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

Binary file not shown.

11
resources/tri/tri.vert Normal file
View 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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 KiB

12745
resources/walrus/walrus.obj Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

BIN
vlkx-resources/pkg/01.vxp Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
01.vxp

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More