diff --git a/CMakeLists.txt b/CMakeLists.txt index ad30cf3..5906806 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project (EtrediaEmpires) +add_subdirectory(materials) add_subdirectory(utils) add_subdirectory (p7lib) add_subdirectory (ECSAPI) diff --git a/assets/materials/helloworld.mat b/assets/materials/helloworld.mat new file mode 100644 index 0000000..28bae65 --- /dev/null +++ b/assets/materials/helloworld.mat @@ -0,0 +1 @@ + â±x/$W \ No newline at end of file diff --git a/assets/shaders/helloworld.frag.spv b/assets/shaders/helloworld.frag.spv index e14329e..81575e5 100644 Binary files a/assets/shaders/helloworld.frag.spv and b/assets/shaders/helloworld.frag.spv differ diff --git a/assets/shaders/helloworld.vert.spv b/assets/shaders/helloworld.vert.spv index 428182b..3197881 100644 Binary files a/assets/shaders/helloworld.vert.spv and b/assets/shaders/helloworld.vert.spv differ diff --git a/frontend/CMakeLists.txt b/frontend/CMakeLists.txt index ac2a42e..054a023 100644 --- a/frontend/CMakeLists.txt +++ b/frontend/CMakeLists.txt @@ -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} diff --git a/frontend/include/AssetData.hpp b/frontend/include/AssetData.hpp index a6924f6..e353a4c 100644 --- a/frontend/include/AssetData.hpp +++ b/frontend/include/AssetData.hpp @@ -8,9 +8,23 @@ template concept Asset = std::derived_from; 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; \ No newline at end of file + +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; +}; \ No newline at end of file diff --git a/frontend/include/AssetManager.hpp b/frontend/include/AssetManager.hpp index dd4ec23..95d0b5a 100644 --- a/frontend/include/AssetManager.hpp +++ b/frontend/include/AssetManager.hpp @@ -7,8 +7,11 @@ #include #include #include +#include #include +#define INVALID_ASSET_ID UINT32_MAX + struct AssetAddress { std::atomic 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* 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 T& Get(const AssetHandle &handle); void Free(AssetHandle &handle); @@ -99,11 +105,14 @@ private: IFileSystem *diskSystem; std::vector activeAssets; - std::unordered_map assetsMapping; + std::unordered_map assetsIdMapping; + std::unordered_map assetsMapping; std::vector freeAssetSlots; std::vector loadingEntries; std::vector binaryStorage; + std::vector shadersStorage; + std::vector materialsStorage; void Threading(); void Loop(); diff --git a/frontend/include/Engine.hpp b/frontend/include/Engine.hpp new file mode 100644 index 0000000..a7b566b --- /dev/null +++ b/frontend/include/Engine.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "AssetManager.hpp" +#include "Renderer.hpp" + +class Engine +{ +public: + static AssetManager *assetManager; + static Renderer *renderer; + +private: + Engine(); +}; diff --git a/frontend/include/PipelineFactory.hpp b/frontend/include/PipelineFactory.hpp new file mode 100644 index 0000000..395257f --- /dev/null +++ b/frontend/include/PipelineFactory.hpp @@ -0,0 +1,64 @@ +#pragma once +#include "AssetData.hpp" +#include +#include + +#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 colorBlendAttachmentConfigs; + VkPipelineColorBlendStateCreateInfo colorBlendConfig; + VkPipelineDynamicStateCreateInfo dynamicStateConfig; + VkPipelineLayoutCreateInfo pipelineLayoutConfig; + + VkViewport defaultViewport; + VkRect2D defaultScissors; + std::vector dynamicStates; + + std::vector colorAttachmentConfigs; + std::vector colorAttachmentRefs; + std::vector 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 pipelines; + + VkDevice &device; + VkExtent2D &surfaceExtent; + VkFormat &surfaceFormat; + + void Create(const AssetMaterial &material, uint32_t flags); +}; \ No newline at end of file diff --git a/frontend/include/Renderer.hpp b/frontend/include/Renderer.hpp index 15235da..d73f912 100644 --- a/frontend/include/Renderer.hpp +++ b/frontend/include/Renderer.hpp @@ -9,6 +9,8 @@ #include #include +#include "PipelineFactory.hpp" + #ifdef VALIDATION_LAYER_ON #define VALIDATION_LAYER "VK_LAYER_KHRONOS_validation" #else @@ -63,7 +65,7 @@ public: std::atomic isRunning; std::atomic 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; diff --git a/frontend/include/compile_config.hpp b/frontend/include/compile_config.hpp index dc84aae..fbdf8e7 100644 --- a/frontend/include/compile_config.hpp +++ b/frontend/include/compile_config.hpp @@ -10,4 +10,6 @@ #define MAJOR_VERSION 0 #define MINOR_VERSION 0 #define PATCH_VERSION 0 -#endif \ No newline at end of file +#endif + +#define ASSETS_HASH_SPACE 0 \ No newline at end of file diff --git a/frontend/src/AssetData.cpp b/frontend/src/AssetData.cpp index 1cf69a6..40cadbd 100644 --- a/frontend/src/AssetData.cpp +++ b/frontend/src/AssetData.cpp @@ -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); -} \ No newline at end of file +} + +AssetShader::AssetShader(uint8_t* &source, const size_t &sourceSize) +{ + content = reinterpret_cast(source); + size = sourceSize; + source = nullptr; +} + +AssetShader::~AssetShader() +{ + if (content != nullptr) + DeallocateArray(content); +} diff --git a/frontend/src/AssetManager.cpp b/frontend/src/AssetManager.cpp index adb6c62..e19fa19 100644 --- a/frontend/src/AssetManager.cpp +++ b/frontend/src/AssetManager.cpp @@ -1,7 +1,11 @@ +#include "compile_config.hpp" #include "AssetManager.hpp" #include "allocator.hpp" #include "Logger.h" #include +#include +#include + 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(command.out->load()); } +AssetHandle& AssetManager::Find(const uint32_t &id) +{ + std::atomic result; + AssetStreamCommand command { + .type = AssetStreamCommand::FIND, + .targetId = id, + .out = &result + }; + Request(command); + while (command.out->load() == nullptr); + return *reinterpret_cast(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(entry.file->data, entry.file->size); + shadersStorage.push_back(shader); + Redirect(entry.handle, shadersStorage.back()); + break; + } + case AssetInfo::MATERIAL: { + AssetMaterial* material = Allocate(); + 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(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::iterator mapping = assetsMapping.find(command.targetPath); + uint32_t id = command.targetId; + if (id == INVALID_ASSET_ID) + { + std::unordered_map::iterator idMapping = assetsIdMapping.find(command.targetPath); + if (idMapping == assetsIdMapping.end()) + break; + id = idMapping->second; + } + + std::unordered_map::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); } diff --git a/frontend/src/Engine.cpp b/frontend/src/Engine.cpp new file mode 100644 index 0000000..83f9033 --- /dev/null +++ b/frontend/src/Engine.cpp @@ -0,0 +1,4 @@ +#include "Engine.hpp" + +AssetManager* Engine::assetManager; +Renderer* Engine::renderer; \ No newline at end of file diff --git a/frontend/src/FileSystem.cpp b/frontend/src/FileSystem.cpp index df92285..e5d4062 100644 --- a/frontend/src/FileSystem.cpp +++ b/frontend/src/FileSystem.cpp @@ -1,7 +1,6 @@ #include "FileSystem.hpp" #include "Logger.h" #include "allocator.hpp" -#include 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(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; } } diff --git a/frontend/src/PipelineFactory.cpp b/frontend/src/PipelineFactory.cpp new file mode 100644 index 0000000..8399fba --- /dev/null +++ b/frontend/src/PipelineFactory.cpp @@ -0,0 +1,301 @@ +#include "PipelineFactory.hpp" +#include "Engine.hpp" +#include "Logger.h" + +PipelineConfig PipelineConfig::Copy() const +{ + PipelineConfig copy = *this; + + copy.viewportStateConfig.pViewports = ©.defaultViewport; + copy.viewportStateConfig.pScissors = ©.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 = ©.vertexInputConfig; + copy.pipelineConfig.pInputAssemblyState = ©.inputAssemblyConfig; + copy.pipelineConfig.pViewportState = ©.viewportStateConfig; + copy.pipelineConfig.pRasterizationState = ©.rasterizerConfig; + copy.pipelineConfig.pMultisampleState = ©.multisamplingConfig; + copy.pipelineConfig.pDepthStencilState = nullptr; + copy.pipelineConfig.pColorBlendState = ©.colorBlendConfig; + copy.pipelineConfig.pDynamicState = ©.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(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(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(surfaceExtent.width), + .height = static_cast(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(basePipelineConfig.colorBlendAttachmentConfigs.size()), + .pAttachments = basePipelineConfig.colorBlendAttachmentConfigs.data(), + }; + + basePipelineConfig.renderPassConfig = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = nullptr, + .attachmentCount = static_cast(basePipelineConfig.colorAttachmentConfigs.size()), + .pAttachments = basePipelineConfig.colorAttachmentConfigs.data(), + .subpassCount = static_cast(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 stagesConfigs; + + stagesConfigs.reserve(3); + + // Vertex + VkShaderModule vertexModule; + shaderHandle = Engine::assetManager->Find(material.vertexShader); + shader = &Engine::assetManager->Get(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(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(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); + } +} \ No newline at end of file diff --git a/frontend/src/Renderer.cpp b/frontend/src/Renderer.cpp index 80bdca9..b3d239c 100644 --- a/frontend/src/Renderer.cpp +++ b/frontend/src/Renderer.cpp @@ -9,7 +9,9 @@ #include #include -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(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); diff --git a/frontend/src/main.cpp b/frontend/src/main.cpp index 7ebd423..534f787 100644 --- a/frontend/src/main.cpp +++ b/frontend/src/main.cpp @@ -2,8 +2,7 @@ #include #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(baseMaterialHandle)); renderer.Start(); + //Setup Engine + Engine::assetManager = &assetManager; + Engine::renderer = &renderer; + // Process SDL events while(!quit) { diff --git a/materials/CMakeLists.txt b/materials/CMakeLists.txt new file mode 100644 index 0000000..4ac9ed0 --- /dev/null +++ b/materials/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required (VERSION 3.8) + +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$: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} +) diff --git a/materials/_schema.json b/materials/_schema.json new file mode 100644 index 0000000..e69de29 diff --git a/materials/compile_materials.cpp b/materials/compile_materials.cpp new file mode 100644 index 0000000..b88eb0a --- /dev/null +++ b/materials/compile_materials.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#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(); + fout.write(reinterpret_cast(&material), sizeof(material)); + + fin.close(); + fout.close(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + } + + + return 0; +} \ No newline at end of file diff --git a/materials/helloworld.mat.json b/materials/helloworld.mat.json new file mode 100644 index 0000000..451a887 --- /dev/null +++ b/materials/helloworld.mat.json @@ -0,0 +1,4 @@ +{ + "vertexShader": "assets/shaders/helloworld.vert.spv", + "fragmentShader": "assets/shaders/helloworld.frag.spv" +} \ No newline at end of file diff --git a/shaders/helloworld/helloworld.frag b/shaders/helloworld/helloworld.frag index 9e32b50..ff8da04 100644 --- a/shaders/helloworld/helloworld.frag +++ b/shaders/helloworld/helloworld.frag @@ -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); } \ No newline at end of file diff --git a/shaders/helloworld/helloworld.vert b/shaders/helloworld/helloworld.vert index d83cd4c..b2328fc 100644 --- a/shaders/helloworld/helloworld.vert +++ b/shaders/helloworld/helloworld.vert @@ -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]; } \ No newline at end of file diff --git a/utils/include/allocator.hpp b/utils/include/allocator.hpp index 7239548..88ad59c 100644 --- a/utils/include/allocator.hpp +++ b/utils/include/allocator.hpp @@ -1,8 +1,9 @@ #pragma once +#include -template T* Allocate() +template T* Allocate(Args&&... args) { - return new T(); + return new T(std::forward(args)...); } template void Deallocate(T* obj) diff --git a/vcpkg.json b/vcpkg.json index eedad37..169dbb8 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,8 @@ { "dependencies": [ "boost-lockfree", + "xxhash", + "nlohmann-json", "entt", "vulkan", {"name": "sdl2", "features": ["vulkan"]},