Swapchain presentation and GLFW

- Render objects list
- SDL2 replaced with GLFW
- Swapchain synchronization, presentation and recreation on resize
This commit is contained in:
legatt 2025-09-27 12:30:00 +03:00
parent 389ed599c8
commit 1d65039e2f
20 changed files with 579 additions and 186 deletions

View File

@ -10,7 +10,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project (EtrediaEmpires)
add_subdirectory(materials)
add_subdirectory(sdk/materials)
add_subdirectory(utils)
add_subdirectory (p7lib)
add_subdirectory (ECSAPI)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -23,7 +23,7 @@ list(TRANSFORM SOURCES PREPEND "src/")
find_package(Boost REQUIRED COMPONENTS lockfree)
find_package(xxHash CONFIG REQUIRED)
find_package(SDL2 CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
find_package(Vulkan REQUIRED)
find_package(RmlUi CONFIG REQUIRED)
@ -36,12 +36,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC utils)
target_link_libraries(${PROJECT_NAME} PRIVATE Boost::lockfree)
target_link_libraries(${PROJECT_NAME} PRIVATE xxHash::xxhash)
target_link_libraries(${PROJECT_NAME} PRIVATE p7)
target_link_libraries(
${PROJECT_NAME}
PRIVATE
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
)
target_link_libraries(${PROJECT_NAME} PRIVATE glfw)
target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Vulkan)
target_link_libraries(${PROJECT_NAME} PRIVATE RmlUi::RmlUi)

View File

@ -25,6 +25,8 @@ struct AssetShader : AssetData
struct AssetMaterial : AssetData
{
uint32_t vertexShader;
uint32_t fragmentShader;
uint32_t vertexShaderAssetID;
uint32_t fragmentShaderAssetID;
uint32_t renderType;
uint32_t renderStage;
};

View File

@ -85,9 +85,6 @@ public:
void Start();
void Shutdown();
void Request(const AssetStreamCommand &command);
void Request(const AssetStreamCommand::Type &type);
void LoadShaders();
void LoadAsync(AssetInfo::Type type, const std::filesystem::path &path);
@ -115,6 +112,8 @@ private:
std::vector<AssetMaterial*> materialsStorage;
void Threading();
void Request(const AssetStreamCommand &command);
void Request(const AssetStreamCommand::Type &type);
void Loop();
void ProcessCommand(AssetStreamCommand &command);
void Release();

View File

@ -2,6 +2,7 @@
#include "AssetData.hpp"
#include <vulkan/vulkan.hpp>
#include <vector>
#include <unordered_map>
#define PIPELINE_CREATE_NEW 0
#define PIPELINE_CREATE_DERIVED (1 << 0)
@ -25,11 +26,6 @@ struct PipelineConfig
VkRect2D defaultScissors;
std::vector<VkDynamicState> dynamicStates;
std::vector<VkAttachmentDescription> colorAttachmentConfigs;
std::vector<VkAttachmentReference> colorAttachmentRefs;
std::vector<VkSubpassDescription> subpassConfigs;
VkRenderPassCreateInfo renderPassConfig;
VkGraphicsPipelineCreateInfo pipelineConfig;
PipelineConfig Copy() const;
@ -39,26 +35,26 @@ struct Pipeline
{
VkPipeline instance;
VkPipelineLayout layout;
VkRenderPass renderPass;
VkViewport viewport;
VkRect2D scissors;
};
class PipelineFactory
{
public:
PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &device, VkExtent2D &surfaceExtent, VkFormat &surfaceFormat);
PipelineFactory(const AssetMaterial &baseMaterial, const VkRenderPass &baseRenderPass, VkDevice &device, VkExtent2D &surfaceExtent);
~PipelineFactory();
PipelineConfig &Request(const AssetMaterial &material, uint32_t flags);
Pipeline& Get(const AssetMaterial &material);
void Create(const AssetMaterial &material, const VkRenderPass &renderPass, const uint32_t &flags);
private:
PipelineConfig basePipelineConfig;
Pipeline basePipeline;
std::vector<Pipeline> pipelines;
std::unordered_map<const AssetMaterial*, Pipeline> pipelines;
VkDevice &device;
VkExtent2D &surfaceExtent;
VkFormat &surfaceFormat;
void Create(const AssetMaterial &material, uint32_t flags);
};

View File

@ -2,11 +2,12 @@
#include <thread>
#include <atomic>
#include <cstdint>
#include <vector>
#include <set>
#include <Boost/lockfree/spsc_queue.hpp>
#include <SDL2/SDL.h>
#include <SDL2/SDL_vulkan.h>
#include <vulkan/vulkan.hpp>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <RmlUi/Core.h>
#include "PipelineFactory.hpp"
@ -24,16 +25,46 @@
#define QUEUE_FAMILY_NO_VALUE UINT32_MAX
#define RENDER_PASS_BASE 438461662
#define RENDER_PASS_GUI
struct Transform
{
float x = 0.0f, y = 0.0f;
float scaleX = 1.0f, scaleY = 1.0f, scaleZ = 1.0f;
};
struct Mesh
{
uint32_t vertexCount = 0;
};
struct RenderObject
{
// Multiple transforms for batching
std::vector<Transform> transforms;
uint8_t depth = 0;
Mesh mesh; //TODO: mesh (vertex/index buffer)
//TODO: texture
AssetMaterial &material;
//TODO: materialParams (uniforms)
};
bool operator < (const RenderObject& lhs, const RenderObject& rhs);
struct RenderCommand
{
enum Type
{
NONE,
//STORE screenHeight and screenWidth BEFORE request
RESIZE_WINDOW,
REGISTER_OBJECTS,
RESIZE_WINDOW, //STORE screenHeight and screenWidth BEFORE request
SHUTDOWN
} type = NONE;
RenderObject *objects;
size_t objectsSize;
};
struct QueueFamiliesBundle
@ -48,6 +79,44 @@ struct QueuesBundle
VkQueue present;
};
struct DrawConfig
{
VkCommandBufferBeginInfo commandBeginConfig {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.pNext = nullptr,
.flags = 0,
.pInheritanceInfo = nullptr
};
VkClearValue clearColor {{{1.0f, 1.0f, 1.0f, 1.0f}}};
VkRenderPassBeginInfo renderPassBeginConfig {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.pNext = nullptr,
.clearValueCount = 1,
.pClearValues = &clearColor
};
VkPipelineStageFlags waitImageStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submitConfig {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr,
.waitSemaphoreCount = 1,
.pWaitDstStageMask = &waitImageStage,
.commandBufferCount = 1,
.signalSemaphoreCount = 1
};
VkPresentInfoKHR presentConfig {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = nullptr,
.waitSemaphoreCount = 1,
.swapchainCount = 1,
.pResults = nullptr
};
};
struct SurfaceInfo
{
VkSurfaceCapabilitiesKHR capabilities;
@ -65,44 +134,67 @@ public:
std::atomic<bool> isRunning;
std::atomic<int> screenWidth, screenHeight;
Renderer(SDL_Window *window, const AssetMaterial& baseMaterial);
Renderer(GLFWwindow *window, const AssetMaterial& baseMaterial);
~Renderer();
void Start();
void Shutdown();
void Request(const RenderCommand &command);
void Request(const RenderCommand::Type &type);
void RegisterObjects(RenderObject* objects, size_t size);
void ResizeWindow(int width, int height);
private:
std::stop_token *stoken;
std::jthread renderThread;
boost::lockfree::spsc_queue<RenderCommand, boost::lockfree::capacity<512>> commands;
SDL_Window *window;
GLFWwindow *window;
VkInstance vulkan;
VkSurfaceKHR surface;
VkPhysicalDevice gpu;
VkDevice device;
VkSwapchainKHR swapchain;
VkRenderPass baseRenderPass;
VkCommandPool commandPool = nullptr;
std::vector<VkCommandBuffer> commandBuffers;
std::vector<VkImage> frames;
std::vector<VkImageView> frameViews;
std::vector<VkFramebuffer> baseFrameBuffers;
std::vector<VkSemaphore> imageAvailable;
std::vector<VkSemaphore> renderFinished;
std::vector<VkFence> inFlightFences;
std::vector<VkFence> imagesInFlight;
SurfaceInfo surfaceInfo;
QueueFamiliesBundle queueFamilies;
QueuesBundle queues;
const float defaultQueuePriority = 1.0f;
const AssetMaterial &baseMaterial;
DrawConfig drawConfig;
PipelineFactory *pipelineFactory;
size_t currentFrame = 0;
const float DEFAULT_QUEUE_PRIORITY = 1.0f;
const AssetMaterial &baseMaterial;
const size_t MAX_FRAMES_IN_FLIGHT = 2;
std::set<RenderObject> objects;
Rml::Context *uiContext;
void InitVulkan();
void SetupDevice();
void SetupSwapchain();
void RecreateSwapchain();
void ReleaseSwapchain();
void SetupRenderPasses();
void SetupBuffers();
void SetupSync();
void Draw();
void Threading();
void Request(const RenderCommand &command);
void Request(const RenderCommand::Type &type);
void Init();
void Loop();
void ProcessCommand(const RenderCommand &command);
void Release();

View File

@ -10,13 +10,6 @@ PipelineConfig PipelineConfig::Copy() const
copy.viewportStateConfig.pScissors = &copy.defaultScissors;
copy.dynamicStateConfig.pDynamicStates = copy.dynamicStates.data();
copy.colorBlendConfig.pAttachments = copy.colorBlendAttachmentConfigs.data();
copy.renderPassConfig.pAttachments = copy.colorAttachmentConfigs.data();
copy.renderPassConfig.pSubpasses = copy.subpassConfigs.data();
for (VkSubpassDescription &config : copy.subpassConfigs)
{
config.pColorAttachments = copy.colorAttachmentRefs.data();
}
copy.pipelineConfig.pVertexInputState = &copy.vertexInputConfig;
copy.pipelineConfig.pInputAssemblyState = &copy.inputAssemblyConfig;
copy.pipelineConfig.pViewportState = &copy.viewportStateConfig;
@ -29,10 +22,16 @@ PipelineConfig PipelineConfig::Copy() const
return copy;
}
PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &device, VkExtent2D &surfaceExtent, VkFormat &surfaceFormat) :
device(device), surfaceExtent(surfaceExtent), surfaceFormat(surfaceFormat)
PipelineFactory::PipelineFactory(
const AssetMaterial &baseMaterial,
const VkRenderPass &baseRenderPass,
VkDevice &device,
VkExtent2D &surfaceExtent
) :
device(device), surfaceExtent(surfaceExtent)
{
basePipelineConfig.dynamicStates.push_back(VK_DYNAMIC_STATE_VIEWPORT);
basePipelineConfig.dynamicStates.push_back(VK_DYNAMIC_STATE_SCISSOR);
basePipelineConfig.colorBlendAttachmentConfigs.push_back({
.blendEnable = VK_TRUE,
@ -45,27 +44,6 @@ PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &de
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
});
basePipelineConfig.colorAttachmentConfigs.push_back({
.format = surfaceFormat,
.samples = VK_SAMPLE_COUNT_1_BIT, // TODO: Multisampling
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
});
basePipelineConfig.colorAttachmentRefs.push_back({
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
});
basePipelineConfig.subpassConfigs.push_back({
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = static_cast<uint32_t>(basePipelineConfig.colorAttachmentRefs.size()),
.pColorAttachments = basePipelineConfig.colorAttachmentRefs.data()
});
basePipelineConfig.vertexInputConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pNext = nullptr,
@ -152,15 +130,6 @@ PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &de
.pAttachments = basePipelineConfig.colorBlendAttachmentConfigs.data(),
};
basePipelineConfig.renderPassConfig = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = nullptr,
.attachmentCount = static_cast<uint32_t>(basePipelineConfig.colorAttachmentConfigs.size()),
.pAttachments = basePipelineConfig.colorAttachmentConfigs.data(),
.subpassCount = static_cast<uint32_t>(basePipelineConfig.subpassConfigs.size()),
.pSubpasses = basePipelineConfig.subpassConfigs.data()
};
basePipelineConfig.pipelineConfig = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@ -172,17 +141,23 @@ PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &de
.pDepthStencilState = nullptr,
.pColorBlendState = &basePipelineConfig.colorBlendConfig,
.pDynamicState = &basePipelineConfig.dynamicStateConfig,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = -1
};
LOG(INFO, "Creating base material pipeline...");
Create(baseMaterial, PIPELINE_CREATE_NEW);
Create(baseMaterial, baseRenderPass, PIPELINE_CREATE_NEW);
LOG(INFO, "Base material pipeline created");
}
void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
Pipeline& PipelineFactory::Get(const AssetMaterial &material)
{
if (pipelines.contains(&material))
return pipelines[&material];
else return basePipeline;
}
void PipelineFactory::Create(const AssetMaterial &material, const VkRenderPass &renderPass, const uint32_t &flags)
{
Pipeline newPipeline {};
Pipeline *result = &basePipeline;
@ -209,11 +184,6 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
LOG(CRITICAL, "Failed to create pipeline layout");
config->pipelineConfig.layout = result->layout;
vkResult = vkCreateRenderPass(device, &config->renderPassConfig, nullptr, &result->renderPass);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create render pass");
config->pipelineConfig.renderPass = result->renderPass;
// Configure shaders
AssetHandle shaderHandle;
AssetShader *shader;
@ -224,7 +194,7 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
// Vertex
VkShaderModule vertexModule;
shaderHandle = Engine::assetManager->Find(material.vertexShader);
shaderHandle = Engine::assetManager->Find(material.vertexShaderAssetID);
shader = &Engine::assetManager->Get<AssetShader>(shaderHandle);
shaderModuleCreateConfig = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
@ -246,7 +216,7 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
// Fragment
VkShaderModule fragmentModule;
shaderHandle = Engine::assetManager->Find(material.fragmentShader);
shaderHandle = Engine::assetManager->Find(material.fragmentShaderAssetID);
shader = &Engine::assetManager->Get<AssetShader>(shaderHandle);
shaderModuleCreateConfig = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
@ -268,6 +238,8 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
config->pipelineConfig.stageCount = static_cast<uint32_t>(stagesConfigs.size());
config->pipelineConfig.pStages = stagesConfigs.data();
config->pipelineConfig.renderPass = renderPass;
config->pipelineConfig.subpass = material.renderStage;
vkResult = vkCreateGraphicsPipelines(
device,
@ -281,7 +253,10 @@ void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
LOG(CRITICAL, "Failed to create pipeline");
if (flags & PIPELINE_CREATE_DERIVED)
pipelines.push_back(newPipeline);
pipelines[&material] = newPipeline;
result->viewport = config->defaultViewport;
result->scissors = config->defaultScissors;
vkDestroyShaderModule(device, vertexModule, nullptr);
vkDestroyShaderModule(device, fragmentModule, nullptr);
@ -291,11 +266,9 @@ PipelineFactory::~PipelineFactory()
{
vkDestroyPipeline(device, basePipeline.instance, nullptr);
vkDestroyPipelineLayout(device, basePipeline.layout, nullptr);
vkDestroyRenderPass(device, basePipeline.renderPass, nullptr);
for (Pipeline &pipeline : pipelines)
for (auto &item : pipelines)
{
vkDestroyPipeline(device, pipeline.instance, nullptr);
vkDestroyPipelineLayout(device, pipeline.layout, nullptr);
vkDestroyRenderPass(device, pipeline.renderPass, nullptr);
vkDestroyPipeline(device, item.second.instance, nullptr);
vkDestroyPipelineLayout(device, item.second.layout, nullptr);
}
}

View File

@ -9,11 +9,20 @@
#include <set>
#include <stdexcept>
Renderer::Renderer(SDL_Window *window, const AssetMaterial &baseMaterial) :
bool operator < (const RenderObject& lhs, const RenderObject& rhs)
{
return &lhs.material < &rhs.material;
}
Renderer::Renderer(GLFWwindow *window, const AssetMaterial &baseMaterial) :
isRunning(false),
baseMaterial(baseMaterial)
{
this->window = window;
int width = -1, height = -1;
glfwGetFramebufferSize(window, &width, &height);
screenWidth.store(width);
screenHeight.store(height);
}
Renderer::~Renderer()
@ -31,17 +40,10 @@ void Renderer::InitVulkan()
};
// Get Vulkan extensions list
uint32_t extensionsSDLCount = 0;
if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, nullptr) )
LOG(CRITICAL, "Fail to get SDL Vulkan extensions number! %hs", SDL_GetError());
std::vector<const char*> vulkanExtensionsSDL(extensionsSDLCount);
if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, vulkanExtensionsSDL.data()) )
{
LOG(CRITICAL, "Fail to register SDL Vulkan extensions! %hs", SDL_GetError());
vulkanExtensionsSDL.clear();
}
else vulkanExtensions.insert(vulkanExtensions.end(), vulkanExtensionsSDL.begin(), vulkanExtensionsSDL.end());
uint32_t extensionsGLFWCount = 0;
const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&extensionsGLFWCount);
for (size_t i = 0; i < extensionsGLFWCount; i++)
vulkanExtensions.push_back(glfwExtensions[i]);
for (const char *extension : vulkanExtensions)
{
@ -80,9 +82,11 @@ void Renderer::InitVulkan()
else LOG(INFO, "Vulkan is instantiated");
// Create surface
if ( !SDL_Vulkan_CreateSurface(window, vulkan, &surface) )
LOG(CRITICAL, "Failed to create SDL Vulkan surface. %hs", SDL_GetError());
else LOG(INFO, "SDL Vulkan surface created");
vkResult = glfwCreateWindowSurface(vulkan, window, nullptr, &surface);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create GLFW Vulkan surface. Code: %d", vkResult);
else LOG(INFO, "GLFW Vulkan surface created");
}
void Renderer::SetupDevice()
@ -230,10 +234,11 @@ void Renderer::SetupDevice()
if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
priority += GPU_DISCRETE_PRIORITY;
surfaceInfoBuffer.format.format = VK_FORMAT_B8G8R8A8_UNORM;
for (const VkSurfaceFormatKHR &format : surfaceInfoBuffer.availableFormats)
{
surfaceInfoBuffer.format = format;
if (format.format == VK_FORMAT_B8G8R8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
if (format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
{
priority += GPU_B8G8R8_SRGB_PRIORITY;
break;
@ -279,7 +284,7 @@ void Renderer::SetupDevice()
.pNext = nullptr,
.queueFamilyIndex = queueFamily,
.queueCount = 1,
.pQueuePriorities = &defaultQueuePriority
.pQueuePriorities = &DEFAULT_QUEUE_PRIORITY
};
queuesCreateConfigs.push_back(queueCreateConfig);
@ -321,7 +326,7 @@ void Renderer::SetupSwapchain()
LOG(CRITICAL, "Failed to get surface capabilities. Code: %d", vkResult);
int width = 0, height = 0;
SDL_Vulkan_GetDrawableSize(window, &width, &height);
glfwGetFramebufferSize(window, &width, &height);
// Clamp extent
surfaceInfo.extent.width = (std::max)(
@ -410,6 +415,313 @@ void Renderer::SetupSwapchain()
LOG(INFO, "Frame views are ready.");
}
void Renderer::RecreateSwapchain()
{
vkDeviceWaitIdle(device);
ReleaseSwapchain();
SetupSwapchain();
SetupRenderPasses();
pipelineFactory = Allocate<PipelineFactory>(baseMaterial, baseRenderPass, device, surfaceInfo.extent);
SetupBuffers();
}
void Renderer::ReleaseSwapchain()
{
for (VkFramebuffer &frameBuffer : baseFrameBuffers)
{
vkDestroyFramebuffer(device, frameBuffer, nullptr);
}
vkFreeCommandBuffers(
device,
commandPool,
static_cast<uint32_t>(commandBuffers.size()),
commandBuffers.data()
);
vkDestroyRenderPass(device, baseRenderPass, nullptr);
Deallocate(pipelineFactory);
for (VkImageView &frameView : frameViews)
{
vkDestroyImageView(device, frameView, nullptr);
}
vkDestroySwapchainKHR(device, swapchain, nullptr);
}
void Renderer::SetupRenderPasses()
{
VkAttachmentDescription colorAttachmentConfig {
.format = surfaceInfo.format.format,
.samples = VK_SAMPLE_COUNT_1_BIT, // TODO: Multisampling
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
};
VkAttachmentReference colorAttachmentRef {
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
};
VkSubpassDescription subpass {
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = &colorAttachmentRef
};
VkSubpassDependency dependency
{
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
};
VkRenderPassCreateInfo baseRenderPassConfig {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = nullptr,
.attachmentCount = 1,
.pAttachments = &colorAttachmentConfig,
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &dependency
};
VkResult vkResult = vkCreateRenderPass(device, &baseRenderPassConfig, nullptr, &baseRenderPass);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create base render pass. Code: %d", vkResult);
}
void Renderer::SetupBuffers()
{
VkResult vkResult;
baseFrameBuffers.resize(frameViews.size());
size_t i = 0;
for (const VkImageView &frameView : frameViews)
{
VkImageView attachments[] = {frameView};
VkFramebufferCreateInfo frameBufferConfig {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.pNext = nullptr,
.renderPass = baseRenderPass,
.attachmentCount = 1,
.pAttachments = attachments,
.width = surfaceInfo.extent.width,
.height = surfaceInfo.extent.height,
.layers = 1
};
vkResult = vkCreateFramebuffer(device, &frameBufferConfig, nullptr, &baseFrameBuffers[i]);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create framebuffer for base render pass. Code: %d", vkResult);
i++;
}
if (commandPool == nullptr)
{
VkCommandPoolCreateInfo commandPoolConfig {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.pNext = nullptr,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = queueFamilies.graphics,
};
vkResult = vkCreateCommandPool(device, &commandPoolConfig, nullptr, &commandPool);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create command pool. Code: %d", vkResult);
}
commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
VkCommandBufferAllocateInfo allocateConfig {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.pNext = nullptr,
.commandPool = commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, //TODO: reuse some commands using SECONDARY
.commandBufferCount = static_cast<uint32_t>(commandBuffers.size())
};
vkResult = vkAllocateCommandBuffers(device, &allocateConfig, commandBuffers.data());
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to allocate command buffers. Code: %d", vkResult);
}
void Renderer::Draw()
{
VkResult vkResult;
uint32_t imageIndex = 0;
vkResult = vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
if (vkResult != VK_SUCCESS)
LOG(ERROR, "Failed to wait for fence. Code: %d", vkResult);
vkResult = vkAcquireNextImageKHR(
device, swapchain,
UINT64_MAX,
imageAvailable[currentFrame],
VK_NULL_HANDLE,
&imageIndex
);
switch (vkResult)
{
case VK_SUCCESS:
break;
case VK_ERROR_OUT_OF_DATE_KHR:
case VK_SUBOPTIMAL_KHR:
LOG(WARNING, "Swapchain needs to be recreated");
RecreateSwapchain();
break;
default:
LOG(ERROR, "Failed to get next image index. Code: %d", vkResult);
break;
}
if (imagesInFlight[imageIndex] != VK_NULL_HANDLE)
{
vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
}
imagesInFlight[imageIndex] = inFlightFences[currentFrame];
vkResult = vkResetCommandBuffer(commandBuffers[currentFrame], 0);
if (vkResult != VK_SUCCESS)
LOG(ERROR, "Failed to reset command buffer. Code: %d", vkResult);
vkResult = vkBeginCommandBuffer(commandBuffers[currentFrame], &drawConfig.commandBeginConfig);
if (vkResult != VK_SUCCESS)
LOG(ERROR, "Failed to begin command buffer. Code: %d", vkResult);
drawConfig.renderPassBeginConfig.renderPass = baseRenderPass;
drawConfig.renderPassBeginConfig.framebuffer = baseFrameBuffers[imageIndex];
drawConfig.renderPassBeginConfig.renderArea = {
.offset = {.x = 0, .y = 0},
.extent = surfaceInfo.extent
};
vkCmdBeginRenderPass(
commandBuffers[currentFrame],
&drawConfig.renderPassBeginConfig,
VK_SUBPASS_CONTENTS_INLINE
);
Pipeline* lastPipeline = nullptr;
Pipeline* pipeline = nullptr;
for (const RenderObject &object : objects)
{
pipeline = &pipelineFactory->Get(object.material);
if (pipeline != lastPipeline)
{
vkCmdBindPipeline(
commandBuffers[currentFrame],
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipeline->instance
);
vkCmdSetViewport(commandBuffers[currentFrame], 0, 1, &pipeline->viewport);
vkCmdSetScissor(commandBuffers[currentFrame], 0, 1, &pipeline->scissors);
lastPipeline = pipeline;
}
vkCmdDraw(
commandBuffers[currentFrame],
object.mesh.vertexCount,
static_cast<uint32_t>(object.transforms.size()),
0, // TODO: batching
0 // TODO: batching
);
}
vkCmdEndRenderPass(commandBuffers[currentFrame]);
vkResult = vkEndCommandBuffer(commandBuffers[currentFrame]);
if (vkResult != VK_SUCCESS)
LOG(ERROR, "Failed to end command buffer. Code: %d", vkResult);
VkSemaphore waitSemaphores[] = {imageAvailable[currentFrame]};
VkSemaphore signalSemaphores[] = {renderFinished[currentFrame]};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
//drawConfig.submitConfig.pWaitSemaphores = &imageAvailable[currentFrame];
drawConfig.submitConfig.pWaitSemaphores = waitSemaphores;
//drawConfig.submitConfig.pSignalSemaphores = &renderFinished[currentFrame];
drawConfig.submitConfig.pSignalSemaphores =signalSemaphores;
drawConfig.submitConfig.pCommandBuffers = &commandBuffers[currentFrame];
drawConfig.submitConfig.pWaitDstStageMask = waitStages;
vkResult = vkResetFences(device, 1, &inFlightFences[currentFrame]);
if (vkResult != VK_SUCCESS)
LOG(ERROR, "Failed to reset for fence. Code: %d", vkResult);
vkResult = vkQueueSubmit(queues.graphics, 1, &drawConfig.submitConfig, inFlightFences[currentFrame]);
if (vkResult != VK_SUCCESS)
LOG(ERROR, "Failed to submit render commands. Code: %d", vkResult);
//drawConfig.presentConfig.pWaitSemaphores = &renderFinished[currentFrame];
drawConfig.presentConfig.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = {swapchain};
//drawConfig.presentConfig.pSwapchains = &swapchain;
drawConfig.presentConfig.pSwapchains = swapChains;
drawConfig.presentConfig.pImageIndices = &imageIndex;
vkResult = vkQueuePresentKHR(queues.present, &drawConfig.presentConfig);
switch (vkResult)
{
case VK_SUCCESS:
break;
case VK_ERROR_OUT_OF_DATE_KHR:
case VK_SUBOPTIMAL_KHR:
LOG(WARNING, "Swapchain needs to be recreated");
RecreateSwapchain();
break;
default:
LOG(ERROR, "Failed to present swapchain. Code: %d", vkResult);
break;
}
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
void Renderer::SetupSync()
{
VkResult vkResult;
VkSemaphoreCreateInfo semaphoreConfig {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
.pNext = nullptr
};
VkFenceCreateInfo fenceConfig {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = VK_FENCE_CREATE_SIGNALED_BIT
};
imageAvailable.resize(MAX_FRAMES_IN_FLIGHT);
renderFinished.resize(MAX_FRAMES_IN_FLIGHT);
inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
imagesInFlight.resize(frameViews.size(), VK_NULL_HANDLE);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
{
vkResult = vkCreateSemaphore(device, &semaphoreConfig, nullptr, &imageAvailable[i]);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create semaphore. Code: %d", vkResult);
vkResult = vkCreateSemaphore(device, &semaphoreConfig, nullptr, &renderFinished[i]);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create semaphore. Code: %d", vkResult);
vkResult = vkCreateFence(device, &fenceConfig, nullptr, &inFlightFences[i]);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create fence. Code: %d", vkResult);
}
}
void Renderer::Start()
{
if (isRunning)
@ -430,14 +742,22 @@ void Renderer::Threading()
Logger::trace->Register_Thread(L"Renderer", 0);
LOG(INFO, "Renderer thread started");
Init();
Loop();
Release();
}
void Renderer::Init()
{
InitVulkan();
SetupDevice();
SetupSwapchain();
pipelineFactory = Allocate<PipelineFactory>(baseMaterial, device, surfaceInfo.extent, surfaceInfo.format.format);
SetupRenderPasses();
Loop();
pipelineFactory = Allocate<PipelineFactory>(baseMaterial, baseRenderPass, device, surfaceInfo.extent);
Release();
SetupBuffers();
SetupSync();
}
void Renderer::Loop()
@ -450,8 +770,9 @@ void Renderer::Loop()
ProcessCommand(commandBuffer);
}
Draw();
}
vkDeviceWaitIdle(device);
}
void Renderer::ProcessCommand(const RenderCommand &command)
@ -465,6 +786,18 @@ void Renderer::ProcessCommand(const RenderCommand &command)
break;
}
case RenderCommand::REGISTER_OBJECTS:
{
LOG(INFO, "REGISTER_OBJECTS command");
for (size_t i = 0; i < command.objectsSize; i++)
{
objects.insert(command.objects[i]);
}
break;
}
case RenderCommand::RESIZE_WINDOW:
{
LOG(INFO, "RESIZE_WINDOW command");
@ -494,16 +827,38 @@ void Renderer::Request(const RenderCommand::Type &type)
commands.push(RenderCommand {.type = type});
}
void Renderer::RegisterObjects(RenderObject* objects, size_t size)
{
RenderCommand command {
.type = RenderCommand::REGISTER_OBJECTS,
.objects = objects,
.objectsSize = size
};
Request(command);
}
void Renderer::ResizeWindow(int width, int height)
{
screenWidth.store(width);
screenHeight.store(height);
Request(RenderCommand::RESIZE_WINDOW);
}
void Renderer::Release()
{
Deallocate(pipelineFactory);
vkDeviceWaitIdle(device);
for (VkImageView &frameView : frameViews)
ReleaseSwapchain();
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
{
vkDestroyImageView(device, frameView, nullptr);
vkDestroySemaphore(device, renderFinished[i], nullptr);
vkDestroySemaphore(device, imageAvailable[i], nullptr);
vkDestroyFence(device, inFlightFences[i], nullptr);
}
vkDestroySwapchainKHR(device, swapchain, nullptr);
vkDestroyCommandPool(device, commandPool, nullptr);
vkDestroyDevice(device, nullptr);
vkDestroySurfaceKHR(vulkan, surface, nullptr);
vkDestroyInstance(vulkan, nullptr);

View File

@ -1,12 +1,12 @@
#include "Logger.h"
#include <SDL2/SDL.h>
#include <GLFW/glfw3.h>
#include "compile_config.hpp"
#include "Engine.hpp"
IP7_Trace* Logger::trace;
int SDL_main(int argc, char **argv)
int main(int argc, char **argv)
{
// Setup P7 logger
IP7_Client *p7client = P7_Create_Client(P7_SINK);
@ -16,28 +16,15 @@ int SDL_main(int argc, char **argv)
LOG(INFO, "Etredia Empires, v%d.%d.%d", MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION);
// Setup SDL
bool quit = false;
SDL_Event event;
// Setup GLFW
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER);
SDL_Window *window = SDL_CreateWindow(
"Etredia Empires",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
800,
600,
SDL_WINDOW_VULKAN
);
SDL_StopTextInput();
GLFWwindow *window = glfwCreateWindow(800, 600, "Etredia Empires", nullptr, nullptr);
if (!window)
{
quit = true;
LOG(CRITICAL, "SDL Window fails: %hs", SDL_GetError());
}
else LOG(INFO, "SDL Window created");
LOG(CRITICAL, "GLFW Window failed");
else LOG(INFO, "GLFW Window created");
// Setup assets
AssetManager assetManager;
@ -47,51 +34,35 @@ int SDL_main(int argc, char **argv)
// Setup Renderer
assetManager.LoadAsync(AssetInfo::MATERIAL, "assets/materials/helloworld.mat");
AssetHandle baseMaterialHandle = assetManager.Find("assets/materials/helloworld.mat");
Renderer renderer(window, assetManager.Get<AssetMaterial>(baseMaterialHandle));
AssetMaterial baseMaterial = assetManager.Get<AssetMaterial>(baseMaterialHandle);
Renderer renderer(window, baseMaterial);
renderer.Start();
//Setup Engine
Engine::assetManager = &assetManager;
Engine::renderer = &renderer;
// Process SDL events
while(!quit)
// Test render obj
RenderObject triangle {
.transforms = {{.x = 0, .y = 0}},
.mesh = {.vertexCount = 3},
.material = baseMaterial,
};
renderer.RegisterObjects(&triangle, 1);
// Process GLFW events
while(!glfwWindowShouldClose(window))
{
SDL_PollEvent(&event);
switch(event.type)
{
case SDL_QUIT:
{
SDL_RestoreWindow(window);
quit = true;
break;
}
case SDL_WINDOWEVENT:
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_RESIZED:
{
renderer.screenWidth.store(event.window.data1);
renderer.screenHeight.store(event.window.data2);
renderer.Request(RenderCommand::RESIZE_WINDOW);
break;
}
}
break;
}
}
glfwPollEvents();
}
// Free memory
LOG(INFO, "Exit...");
renderer.Shutdown(); while(renderer.isRunning.load());
assetManager.Shutdown(); while(assetManager.isRunning.load());
SDL_DestroyWindow(window);
SDL_Quit();
glfwDestroyWindow(window);
glfwTerminate();
Logger::trace->Unregister_Thread(0);
Logger::trace->Release();
p7client->Release();

View File

@ -1,4 +0,0 @@
{
"vertexShader": "assets/shaders/helloworld.vert.spv",
"fragmentShader": "assets/shaders/helloworld.frag.spv"
}

View File

@ -8,7 +8,13 @@
using json = nlohmann::json;
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AssetMaterial, vertexShader, fragmentShader)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
AssetMaterial,
vertexShaderAssetID,
fragmentShaderAssetID,
renderType,
renderStage
)
int main(int argc, char **argv)
{
@ -32,15 +38,17 @@ int main(int argc, char **argv)
json data = json::parse(fin);
for (const char *link : {
"vertexShader",
"fragmentShader"
})
for (auto &[key, value] : data.items())
{
std::filesystem::path linkPath = data[link];
if (
!key.ends_with("AssetID") &&
!key.ends_with("Type")
) continue;
std::filesystem::path linkPath = data[key];
auto linkPath8 = linkPath.generic_u8string();
uint32_t assetId = XXH32(linkPath8.data(), linkPath8.size(), 0);
data[link] = assetId;
data[key] = assetId;
}
AssetMaterial material = data.get<AssetMaterial>();

View File

@ -0,0 +1,6 @@
{
"vertexShaderAssetID": "assets/shaders/helloworld.vert.spv",
"fragmentShaderAssetID": "assets/shaders/helloworld.frag.spv",
"renderType": "base",
"renderStage": 0
}

View File

@ -1,4 +1,4 @@
#version 330
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;

View File

@ -1,4 +1,4 @@
#version 330
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) out vec3 fragColor;

View File

@ -5,7 +5,7 @@
"nlohmann-json",
"entt",
"vulkan",
{"name": "sdl2", "features": ["vulkan"]},
"glfw3",
"rmlui",
"gtest"
]