Pipeline factory and materials

- AssetManager uses static assets ID from path hash
- PipelineFactory creates Vulkan pipelines using PipelineConfig structure depending on AssetMaterial and creation flags
- AssetMaterial describes pipelines (e.g. used shaders)
- Materials are described with JSON in sources with path references to other assets
- AssetMaterial is parsed from binary file with asset IDs in references
- compile_config tool compiles material JSON file to binary file with path references replaced with asset IDs
- Allocator supports type's constructors with arguments
- NativeFileSystem: fixed empty bufferSize and buffer trash
This commit is contained in:
legatt 2025-08-29 13:13:11 +03:00
parent b0be28ff2c
commit 389ed599c8
26 changed files with 661 additions and 68 deletions

View File

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

View File

@ -0,0 +1 @@
âąx/$W

Binary file not shown.

Binary file not shown.

View File

@ -13,8 +13,8 @@ project (EEfrontend)
# List sources
set(HEADERS "Renderer.hpp" "FileSystem.hpp" "compile_config.hpp" "AssetManager.hpp" "AssetData.hpp")
set(SOURCES "main.cpp" "Renderer.cpp" "FileSystem.cpp" "AssetManager.cpp" "AssetData.cpp")
set(HEADERS "Engine.hpp" "Renderer.hpp" "FileSystem.hpp" "compile_config.hpp" "AssetManager.hpp" "AssetData.hpp" "PipelineFactory.hpp")
set(SOURCES "main.cpp" "Engine.cpp" "Renderer.cpp" "FileSystem.cpp" "AssetManager.cpp" "AssetData.cpp" "PipelineFactory.cpp")
list(TRANSFORM HEADERS PREPEND "include/")
list(TRANSFORM SOURCES PREPEND "src/")
@ -22,6 +22,7 @@ list(TRANSFORM SOURCES PREPEND "src/")
# Find dependencies
find_package(Boost REQUIRED COMPONENTS lockfree)
find_package(xxHash CONFIG REQUIRED)
find_package(SDL2 CONFIG REQUIRED)
find_package(Vulkan REQUIRED)
find_package(RmlUi CONFIG REQUIRED)
@ -33,6 +34,7 @@ add_executable(${PROJECT_NAME} ${HEADERS} ${SOURCES})
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}

View File

@ -8,9 +8,23 @@ template<typename T> concept Asset = std::derived_from<T, AssetData>;
struct AssetBinary : AssetData
{
uint8_t* content = nullptr;
size_t size= 0;
size_t size = 0;
AssetBinary(uint8_t* &source, size_t sourceSize);
AssetBinary(uint8_t* &source, const size_t &sourceSize);
~AssetBinary();
};
typedef AssetBinary AssetShader;
struct AssetShader : AssetData
{
uint32_t* content = nullptr;
size_t size = 0;
AssetShader(uint8_t* &source, const size_t &sourceSize);
~AssetShader();
};
struct AssetMaterial : AssetData
{
uint32_t vertexShader;
uint32_t fragmentShader;
};

View File

@ -7,8 +7,11 @@
#include <filesystem>
#include <thread>
#include <atomic>
#include <cstdint>
#include <Boost/lockfree/spsc_queue.hpp>
#define INVALID_ASSET_ID UINT32_MAX
struct AssetAddress
{
std::atomic<void*> resource;
@ -16,7 +19,8 @@ struct AssetAddress
struct AssetHandle
{
size_t id = 0;
size_t slot = 0;
uint32_t id = 0;
};
class AssetInfo
@ -64,6 +68,7 @@ struct AssetStreamCommand
} type = NONE;
AssetHandle target;
uint32_t targetId = INVALID_ASSET_ID;
std::filesystem::path targetPath;
AssetInfo::Type targetType = AssetInfo::BINARY;
std::atomic<void*>* out;
@ -87,6 +92,7 @@ public:
void LoadAsync(AssetInfo::Type type, const std::filesystem::path &path);
AssetHandle& Find(const std::filesystem::path &path);
AssetHandle& Find(const uint32_t &id);
void Redirect(AssetHandle &handle, void *resource);
template <Asset T> T& Get(const AssetHandle &handle);
void Free(AssetHandle &handle);
@ -99,11 +105,14 @@ private:
IFileSystem *diskSystem;
std::vector<AssetInfo> activeAssets;
std::unordered_map<std::filesystem::path, AssetHandle*> assetsMapping;
std::unordered_map<std::filesystem::path, uint32_t> assetsIdMapping;
std::unordered_map<uint32_t, AssetHandle*> assetsMapping;
std::vector<size_t> freeAssetSlots;
std::vector<AssetLoadingEntry> loadingEntries;
std::vector<AssetBinary*> binaryStorage;
std::vector<AssetShader*> shadersStorage;
std::vector<AssetMaterial*> materialsStorage;
void Threading();
void Loop();

View File

@ -0,0 +1,13 @@
#pragma once
#include "AssetManager.hpp"
#include "Renderer.hpp"
class Engine
{
public:
static AssetManager *assetManager;
static Renderer *renderer;
private:
Engine();
};

View File

@ -0,0 +1,64 @@
#pragma once
#include "AssetData.hpp"
#include <vulkan/vulkan.hpp>
#include <vector>
#define PIPELINE_CREATE_NEW 0
#define PIPELINE_CREATE_DERIVED (1 << 0)
struct PipelineConfig
{
VkPipelineVertexInputStateCreateInfo vertexInputConfig;
VkPipelineInputAssemblyStateCreateInfo inputAssemblyConfig;
VkPipelineViewportStateCreateInfo viewportStateConfig;
VkPipelineRasterizationStateCreateInfo rasterizerConfig;
VkPipelineMultisampleStateCreateInfo multisamplingConfig;
VkPipelineDepthStencilStateCreateInfo depthStencilConfig;
// SRC - new color, DST - old color
std::vector<VkPipelineColorBlendAttachmentState> colorBlendAttachmentConfigs;
VkPipelineColorBlendStateCreateInfo colorBlendConfig;
VkPipelineDynamicStateCreateInfo dynamicStateConfig;
VkPipelineLayoutCreateInfo pipelineLayoutConfig;
VkViewport defaultViewport;
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;
};
struct Pipeline
{
VkPipeline instance;
VkPipelineLayout layout;
VkRenderPass renderPass;
};
class PipelineFactory
{
public:
PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &device, VkExtent2D &surfaceExtent, VkFormat &surfaceFormat);
~PipelineFactory();
PipelineConfig &Request(const AssetMaterial &material, uint32_t flags);
private:
PipelineConfig basePipelineConfig;
Pipeline basePipeline;
std::vector<Pipeline> pipelines;
VkDevice &device;
VkExtent2D &surfaceExtent;
VkFormat &surfaceFormat;
void Create(const AssetMaterial &material, uint32_t flags);
};

View File

@ -9,6 +9,8 @@
#include <vulkan/vulkan.hpp>
#include <RmlUi/Core.h>
#include "PipelineFactory.hpp"
#ifdef VALIDATION_LAYER_ON
#define VALIDATION_LAYER "VK_LAYER_KHRONOS_validation"
#else
@ -63,7 +65,7 @@ public:
std::atomic<bool> isRunning;
std::atomic<int> screenWidth, screenHeight;
Renderer(SDL_Window *window);
Renderer(SDL_Window *window, const AssetMaterial& baseMaterial);
~Renderer();
void Start();
@ -91,6 +93,8 @@ private:
QueueFamiliesBundle queueFamilies;
QueuesBundle queues;
const float defaultQueuePriority = 1.0f;
const AssetMaterial &baseMaterial;
PipelineFactory *pipelineFactory;
Rml::Context *uiContext;

View File

@ -10,4 +10,6 @@
#define MAJOR_VERSION 0
#define MINOR_VERSION 0
#define PATCH_VERSION 0
#endif
#endif
#define ASSETS_HASH_SPACE 0

View File

@ -1,7 +1,7 @@
#include "AssetData.hpp"
#include "allocator.hpp"
AssetBinary::AssetBinary(uint8_t* &source, size_t sourceSize)
AssetBinary::AssetBinary(uint8_t* &source, const size_t &sourceSize)
{
content = source;
size = sourceSize;
@ -12,4 +12,17 @@ AssetBinary::~AssetBinary()
{
if (content != nullptr)
DeallocateArray(content);
}
}
AssetShader::AssetShader(uint8_t* &source, const size_t &sourceSize)
{
content = reinterpret_cast<uint32_t*>(source);
size = sourceSize;
source = nullptr;
}
AssetShader::~AssetShader()
{
if (content != nullptr)
DeallocateArray(content);
}

View File

@ -1,7 +1,11 @@
#include "compile_config.hpp"
#include "AssetManager.hpp"
#include "allocator.hpp"
#include "Logger.h"
#include <stdexcept>
#include <xxhash.h>
#include <cstring>
AssetInfo::AssetInfo(void *resource)
{
@ -10,6 +14,7 @@ AssetInfo::AssetInfo(void *resource)
}
AssetInfo::AssetInfo(AssetInfo&& other) noexcept :
type(other.type),
resourceAddress(other.resourceAddress)
{
isReady.store(other.isReady.load(std::memory_order_relaxed), std::memory_order_relaxed);
@ -22,6 +27,7 @@ AssetInfo& AssetInfo::operator=(AssetInfo&& other) noexcept
{
if (this != &other)
{
type = other.type;
resourceAddress = other.resourceAddress;
isReady.store(other.isReady.load(std::memory_order_relaxed), std::memory_order_relaxed);
@ -33,7 +39,7 @@ AssetInfo& AssetInfo::operator=(AssetInfo&& other) noexcept
void AssetManager::Redirect(AssetHandle &handle, void* resource)
{
AssetInfo &asset = activeAssets[handle.id];
AssetInfo &asset = activeAssets[handle.slot];
if (asset.resourceAddress == nullptr)
{
LOG(WARNING, "Attempt to redirect empty handle");
@ -52,12 +58,12 @@ void AssetManager::Redirect(AssetHandle &handle, void* resource)
void AssetManager::Free(AssetHandle &handle)
{
AssetInfo &asset = activeAssets[handle.id];
AssetInfo &asset = activeAssets[handle.slot];
Redirect(handle, nullptr);
Deallocate(asset.resourceAddress);
asset.resourceAddress = nullptr;
freeAssetSlots.push_back(handle.id);
freeAssetSlots.push_back(handle.slot);
}
AssetManager::AssetManager()
@ -102,6 +108,18 @@ AssetHandle& AssetManager::Find(const std::filesystem::path &path)
while (command.out->load() == nullptr);
return *reinterpret_cast<AssetHandle*>(command.out->load());
}
AssetHandle& AssetManager::Find(const uint32_t &id)
{
std::atomic<void*> result;
AssetStreamCommand command {
.type = AssetStreamCommand::FIND,
.targetId = id,
.out = &result
};
Request(command);
while (command.out->load() == nullptr);
return *reinterpret_cast<AssetHandle*>(command.out->load());
}
void AssetManager::Start()
{
@ -152,13 +170,26 @@ void AssetManager::Loop()
bool isReady = entry.fileOwner->PollReady(*entry.file);
if (isReady)
{
AssetInfo& asset = activeAssets[entry.handle.id];
AssetInfo& asset = activeAssets[entry.handle.slot];
//TODO: functions for each type
switch (asset.type)
{
case AssetInfo::SHADER:
{
AssetShader* shader = Allocate<AssetShader>(entry.file->data, entry.file->size);
shadersStorage.push_back(shader);
Redirect(entry.handle, shadersStorage.back());
break;
}
case AssetInfo::MATERIAL:
{
AssetMaterial* material = Allocate<AssetMaterial>();
if (entry.file->size < sizeof(AssetMaterial))
LOG(CRITICAL, "File size is less than needed for material");
std::memcpy(material, entry.file->data, sizeof(AssetMaterial));
materialsStorage.push_back(material);
Redirect(entry.handle, materialsStorage.back());
break;
}
@ -167,11 +198,10 @@ void AssetManager::Loop()
break;
}
case AssetInfo::SHADER:
case AssetInfo::BINARY:
default:
{
AssetBinary* data = new AssetBinary(entry.file->data, entry.file->size);
AssetBinary* data = Allocate<AssetBinary>(entry.file->data, entry.file->size);
binaryStorage.push_back(data);
Redirect(entry.handle, binaryStorage.back());
break;
@ -180,8 +210,7 @@ void AssetManager::Loop()
entry.fileOwner->Close(*entry.file);
#ifndef NDEBUG
auto path = entry.file->debugPath.c_str();
LOG(INFO, "Asset loaded: [%s]", path);
LOG(INFO, "Asset loaded: [%hs]", entry.file->debugPath.generic_u8string().c_str());
#endif
entry.file = nullptr;
asset.isReady.store(true);
@ -199,7 +228,7 @@ void AssetManager::ProcessCommand(AssetStreamCommand &command)
{
case AssetStreamCommand::LOAD:
{
LOG(INFO, "LOAD command: [%s]", command.targetPath.c_str());
LOG(INFO, "LOAD command: [%hs]", command.targetPath.generic_u8string().c_str());
/* TODO: process path to select FS */
/* TODO: select between async load and sync job task */
@ -225,8 +254,12 @@ void AssetManager::ProcessCommand(AssetStreamCommand &command)
activeAssets.push_back(std::move(asset));
assetPos = activeAssets.size() - 1;
}
handle->id = assetPos;
assetsMapping[command.targetPath] = handle;
handle->slot = assetPos;
auto assetPath = command.targetPath.generic_u8string();
uint32_t assetId = assetsIdMapping[command.targetPath] =
XXH32(assetPath.data(), assetPath.size(), ASSETS_HASH_SPACE);
handle->id = assetId;
assetsMapping[assetId] = handle;
AssetLoadingEntry loadingEntry {
.handle = *handle,
@ -241,12 +274,21 @@ void AssetManager::ProcessCommand(AssetStreamCommand &command)
case AssetStreamCommand::FIND:
{
std::unordered_map<std::filesystem::path, AssetHandle*>::iterator mapping = assetsMapping.find(command.targetPath);
uint32_t id = command.targetId;
if (id == INVALID_ASSET_ID)
{
std::unordered_map<std::filesystem::path, uint32_t>::iterator idMapping = assetsIdMapping.find(command.targetPath);
if (idMapping == assetsIdMapping.end())
break;
id = idMapping->second;
}
std::unordered_map<uint32_t, AssetHandle*>::iterator assetMapping = assetsMapping.find(id);
if (mapping == assetsMapping.end())
if (assetMapping == assetsMapping.end())
break;
command.out->store(mapping->second);
command.out->store(assetMapping->second);
command.type = AssetStreamCommand::COMPLETED;
break;
@ -254,12 +296,12 @@ void AssetManager::ProcessCommand(AssetStreamCommand &command)
case AssetStreamCommand::GET:
{
if (activeAssets.size() <= command.target.id)
if (activeAssets.size() <= command.target.slot)
break;
if (!activeAssets[command.target.id].isReady)
if (!activeAssets[command.target.slot].isReady)
break;
command.out->store(activeAssets[command.target.id].resourceAddress->resource);
command.out->store(activeAssets[command.target.slot].resourceAddress->resource);
command.type = AssetStreamCommand::COMPLETED;
break;
@ -291,6 +333,20 @@ void AssetManager::Release()
Deallocate(mapping.second);
assetsMapping.clear();
//TODO: ClrearStorage(&storage);
for (AssetBinary* &resource : binaryStorage)
{
if (resource != nullptr)
Deallocate(resource);
}
binaryStorage.clear();
for (AssetShader* &resource : shadersStorage)
{
if (resource != nullptr)
Deallocate(resource);
}
shadersStorage.clear();
LOG(INFO, "Asset Streaming is released");
isRunning.store(false);
}

4
frontend/src/Engine.cpp Normal file
View File

@ -0,0 +1,4 @@
#include "Engine.hpp"
AssetManager* Engine::assetManager;
Renderer* Engine::renderer;

View File

@ -1,7 +1,6 @@
#include "FileSystem.hpp"
#include "Logger.h"
#include "allocator.hpp"
#include <stdexcept>
IFileSystem::~IFileSystem()
{
@ -72,6 +71,7 @@ void NativeFileSystem::ReadAsync(FileHandle &file, const uint32_t bufferSize, co
.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)
};
file.buffer = AllocateArray<uint8_t>(bufferSize);
file.bufferSize = bufferSize;
BOOL isSync = ReadFile(
file.win32Handle,
@ -87,31 +87,6 @@ void NativeFileSystem::ReadAsync(FileHandle &file, const uint32_t bufferSize, co
bool NativeFileSystem::PollReady(FileHandle &file)
{
// DWORD transferredSize = 0L;
// BOOL status = GetOverlappedResult(
// file.win32Handle,
// &file.overlapped,
// &transferredSize,
// FALSE
// );
// DWORD error = GetLastError();
// if (!status && error != ERROR_IO_INCOMPLETE)
// {
// LOG(CRITICAL, "Error in reading file status. Code: %d", error);
// throw std::runtime_error("Error in reading file status");
// }
// if (file.size == transferredSize)
// {
// file.data = file.buffer;
// }
// else
// {
// /* TODO: reserve size for data, shift and move */
// }
// file.buffer = nullptr;
DWORD status = WaitForSingleObject(file.overlapped.hEvent, 0);
switch (status)
@ -121,6 +96,7 @@ bool NativeFileSystem::PollReady(FileHandle &file)
if (file.size == file.bufferSize)
{
file.data = file.buffer;
file.buffer = nullptr;
}
else
{
@ -137,7 +113,7 @@ bool NativeFileSystem::PollReady(FileHandle &file)
default:
{
LOG(CRITICAL, "Failed to get file status. Error: %d", GetLastError());
throw std::runtime_error("Failed to get file status");
return false;
}
}

View File

@ -0,0 +1,301 @@
#include "PipelineFactory.hpp"
#include "Engine.hpp"
#include "Logger.h"
PipelineConfig PipelineConfig::Copy() const
{
PipelineConfig copy = *this;
copy.viewportStateConfig.pViewports = &copy.defaultViewport;
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;
copy.pipelineConfig.pRasterizationState = &copy.rasterizerConfig;
copy.pipelineConfig.pMultisampleState = &copy.multisamplingConfig;
copy.pipelineConfig.pDepthStencilState = nullptr;
copy.pipelineConfig.pColorBlendState = &copy.colorBlendConfig;
copy.pipelineConfig.pDynamicState = &copy.dynamicStateConfig;
return copy;
}
PipelineFactory::PipelineFactory(const AssetMaterial &baseMaterial, VkDevice &device, VkExtent2D &surfaceExtent, VkFormat &surfaceFormat) :
device(device), surfaceExtent(surfaceExtent), surfaceFormat(surfaceFormat)
{
basePipelineConfig.dynamicStates.push_back(VK_DYNAMIC_STATE_VIEWPORT);
basePipelineConfig.colorBlendAttachmentConfigs.push_back({
.blendEnable = VK_TRUE,
.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
.colorBlendOp = VK_BLEND_OP_ADD,
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
.alphaBlendOp = VK_BLEND_OP_ADD,
.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,
.vertexBindingDescriptionCount = 0,
.pVertexBindingDescriptions = nullptr,
.vertexAttributeDescriptionCount = 0,
.pVertexAttributeDescriptions = nullptr
};
basePipelineConfig.inputAssemblyConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.pNext = nullptr,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.primitiveRestartEnable = VK_FALSE
};
basePipelineConfig.rasterizerConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pNext = nullptr,
.depthClampEnable = VK_FALSE,
.rasterizerDiscardEnable = VK_FALSE,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_NONE,
.depthBiasEnable = VK_FALSE,
.lineWidth = 1.0f,
};
basePipelineConfig.multisamplingConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.sampleShadingEnable = VK_FALSE,
};
basePipelineConfig.depthStencilConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.pNext = nullptr,
};
basePipelineConfig.dynamicStateConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.pNext = nullptr,
.dynamicStateCount = static_cast<uint32_t>(basePipelineConfig.dynamicStates.size()),
.pDynamicStates = basePipelineConfig.dynamicStates.data()
};
basePipelineConfig.pipelineLayoutConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.setLayoutCount = 0, //TODO: vector
.pSetLayouts = nullptr,
.pushConstantRangeCount = 0,
.pPushConstantRanges = nullptr
};
basePipelineConfig.defaultViewport = {
.x = 0.0f,
.y = 0.0f,
.width = static_cast<float>(surfaceExtent.width),
.height = static_cast<float>(surfaceExtent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f
};
basePipelineConfig.defaultScissors = {
.offset = {.x = 0, .y = 0},
.extent = surfaceExtent
};
basePipelineConfig.viewportStateConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pNext = nullptr,
.viewportCount = 1,
.pViewports = &basePipelineConfig.defaultViewport,
.scissorCount = 1,
.pScissors = &basePipelineConfig.defaultScissors
};
basePipelineConfig.colorBlendConfig = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.pNext = nullptr,
.logicOpEnable = VK_FALSE,
.attachmentCount = static_cast<uint32_t>(basePipelineConfig.colorBlendAttachmentConfigs.size()),
.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,
.pVertexInputState = &basePipelineConfig.vertexInputConfig,
.pInputAssemblyState = &basePipelineConfig.inputAssemblyConfig,
.pViewportState = &basePipelineConfig.viewportStateConfig,
.pRasterizationState = &basePipelineConfig.rasterizerConfig,
.pMultisampleState = &basePipelineConfig.multisamplingConfig,
.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);
LOG(INFO, "Base material pipeline created");
}
void PipelineFactory::Create(const AssetMaterial &material, uint32_t flags)
{
Pipeline newPipeline {};
Pipeline *result = &basePipeline;
PipelineConfig newConfig;
PipelineConfig *config = &basePipelineConfig;
if (flags & PIPELINE_CREATE_DERIVED)
{
newConfig = basePipelineConfig.Copy();
config = &newConfig;
result = &newPipeline;
config->pipelineConfig.basePipelineHandle = basePipeline.instance;
}
VkResult vkResult = vkCreatePipelineLayout(
device,
&config->pipelineLayoutConfig,
nullptr,
&result->layout
);
if (vkResult != VK_SUCCESS)
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;
VkShaderModuleCreateInfo shaderModuleCreateConfig;
std::vector<VkPipelineShaderStageCreateInfo> stagesConfigs;
stagesConfigs.reserve(3);
// Vertex
VkShaderModule vertexModule;
shaderHandle = Engine::assetManager->Find(material.vertexShader);
shader = &Engine::assetManager->Get<AssetShader>(shaderHandle);
shaderModuleCreateConfig = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.pNext = nullptr,
.codeSize = shader->size,
.pCode = shader->content
};
vkResult = vkCreateShaderModule(device, &shaderModuleCreateConfig, nullptr, &vertexModule);
if (vkResult != VK_SUCCESS)
LOG(ERROR, "Failed to create vertex shader module [Asset ID: %d]. Code: %d", shaderHandle.id, vkResult);
stagesConfigs.push_back({
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = nullptr,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertexModule,
.pName = "main",
.pSpecializationInfo = nullptr
});
// Fragment
VkShaderModule fragmentModule;
shaderHandle = Engine::assetManager->Find(material.fragmentShader);
shader = &Engine::assetManager->Get<AssetShader>(shaderHandle);
shaderModuleCreateConfig = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.pNext = nullptr,
.codeSize = shader->size,
.pCode = shader->content
};
vkResult = vkCreateShaderModule(device, &shaderModuleCreateConfig, nullptr, &fragmentModule);
if (vkResult != VK_SUCCESS)
LOG(ERROR, "Failed to create fragment shader module [Asset ID: %d]. Code: %d", shaderHandle.id, vkResult);
stagesConfigs.push_back({
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = nullptr,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragmentModule,
.pName = "main",
.pSpecializationInfo = nullptr
});
config->pipelineConfig.stageCount = static_cast<uint32_t>(stagesConfigs.size());
config->pipelineConfig.pStages = stagesConfigs.data();
vkResult = vkCreateGraphicsPipelines(
device,
VK_NULL_HANDLE,
1,
&config->pipelineConfig,
nullptr,
&result->instance
);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Failed to create pipeline");
if (flags & PIPELINE_CREATE_DERIVED)
pipelines.push_back(newPipeline);
vkDestroyShaderModule(device, vertexModule, nullptr);
vkDestroyShaderModule(device, fragmentModule, nullptr);
}
PipelineFactory::~PipelineFactory()
{
vkDestroyPipeline(device, basePipeline.instance, nullptr);
vkDestroyPipelineLayout(device, basePipeline.layout, nullptr);
vkDestroyRenderPass(device, basePipeline.renderPass, nullptr);
for (Pipeline &pipeline : pipelines)
{
vkDestroyPipeline(device, pipeline.instance, nullptr);
vkDestroyPipelineLayout(device, pipeline.layout, nullptr);
vkDestroyRenderPass(device, pipeline.renderPass, nullptr);
}
}

View File

@ -9,7 +9,9 @@
#include <set>
#include <stdexcept>
Renderer::Renderer(SDL_Window *window) : isRunning(false)
Renderer::Renderer(SDL_Window *window, const AssetMaterial &baseMaterial) :
isRunning(false),
baseMaterial(baseMaterial)
{
this->window = window;
}
@ -431,6 +433,7 @@ void Renderer::Threading()
InitVulkan();
SetupDevice();
SetupSwapchain();
pipelineFactory = Allocate<PipelineFactory>(baseMaterial, device, surfaceInfo.extent, surfaceInfo.format.format);
Loop();
@ -493,6 +496,8 @@ void Renderer::Request(const RenderCommand::Type &type)
void Renderer::Release()
{
Deallocate(pipelineFactory);
for (VkImageView &frameView : frameViews)
{
vkDestroyImageView(device, frameView, nullptr);

View File

@ -2,8 +2,7 @@
#include <SDL2/SDL.h>
#include "compile_config.hpp"
#include "AssetManager.hpp"
#include "Renderer.hpp"
#include "Engine.hpp"
IP7_Trace* Logger::trace;
@ -46,9 +45,15 @@ int SDL_main(int argc, char **argv)
assetManager.LoadShaders();
// Setup Renderer
Renderer renderer(window);
assetManager.LoadAsync(AssetInfo::MATERIAL, "assets/materials/helloworld.mat");
AssetHandle baseMaterialHandle = assetManager.Find("assets/materials/helloworld.mat");
Renderer renderer(window, assetManager.Get<AssetMaterial>(baseMaterialHandle));
renderer.Start();
//Setup Engine
Engine::assetManager = &assetManager;
Engine::renderer = &renderer;
// Process SDL events
while(!quit)
{

46
materials/CMakeLists.txt Normal file
View File

@ -0,0 +1,46 @@
cmake_minimum_required (VERSION 3.8)
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project (compile_materials)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(xxHash CONFIG REQUIRED)
add_executable(${PROJECT_NAME} compile_materials.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(${PROJECT_NAME} PRIVATE xxHash::xxhash)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/frontend/include/)
if (CMAKE_VERSION VERSION_GREATER 3.12)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
endif()
file(GLOB MATERIAL_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.mat.json")
set(COMPILED_DIR "${CMAKE_SOURCE_DIR}/assets/materials")
foreach(MAT ${MATERIAL_FILES})
get_filename_component(MAT_NAME ${MAT} NAME_WE)
set(OUT_FILE "${COMPILED_DIR}/${MAT_NAME}.mat")
add_custom_command(
OUTPUT ${OUT_FILE}
COMMAND ${PROJECT_NAME} ${MAT} ${OUT_FILE}
DEPENDS ${MAT}
COMMENT "Compiling material ${MAT_NAME}"
VERBATIM
)
list(APPEND COMPILED_BINARIES ${OUT_FILE})
endforeach()
add_custom_target(compile_assets ALL
DEPENDS ${COMPILED_BINARIES}
)

0
materials/_schema.json Normal file
View File

View File

@ -0,0 +1,59 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <xxhash.h>
#include <string>
#include "AssetData.hpp"
using json = nlohmann::json;
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AssetMaterial, vertexShader, fragmentShader)
int main(int argc, char **argv)
{
try
{
std::filesystem::path in = argv[1];
std::filesystem::path out = argv[2];
std::ifstream fin(in);
std::ofstream fout(out);
if (!fin.is_open()) {
std::cout << "Error: Could not open input file." << std::endl;
return 1;
}
if (!fout.is_open()) {
std::cout << "Error: Could not open output file." << std::endl;
return 1;
}
json data = json::parse(fin);
for (const char *link : {
"vertexShader",
"fragmentShader"
})
{
std::filesystem::path linkPath = data[link];
auto linkPath8 = linkPath.generic_u8string();
uint32_t assetId = XXH32(linkPath8.data(), linkPath8.size(), 0);
data[link] = assetId;
}
AssetMaterial material = data.get<AssetMaterial>();
fout.write(reinterpret_cast<const char*>(&material), sizeof(material));
fin.close();
fout.close();
}
catch (const std::exception &e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}

View File

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

View File

@ -1,8 +1,10 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(1.0, 0.0, 0.0, 1.0);
outColor = vec4(fragColor, 1.0);
}

View File

@ -1,12 +1,21 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) out vec3 fragColor;
vec2 positions[3] = vec2[](
vec2(-1.0, 1.0),
vec2(0.0, -1.0),
vec2(1.0, 1.0)
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
void main()
{
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex];
}

View File

@ -1,8 +1,9 @@
#pragma once
#include <utility>
template<typename T> T* Allocate()
template<typename T, typename... Args> T* Allocate(Args&&... args)
{
return new T();
return new T(std::forward<Args>(args)...);
}
template<typename T> void Deallocate(T* obj)

View File

@ -1,6 +1,8 @@
{
"dependencies": [
"boost-lockfree",
"xxhash",
"nlohmann-json",
"entt",
"vulkan",
{"name": "sdl2", "features": ["vulkan"]},