Starting to unhardcode, pipelines are now created from outside
This commit is contained in:
parent
2b33897b4a
commit
dc3ac2fdf5
@ -22,4 +22,6 @@ set(HEADERS
|
|||||||
|
|
||||||
add_library(engine STATIC ${SOURCES})
|
add_library(engine STATIC ${SOURCES})
|
||||||
|
|
||||||
|
add_subdirectory(program)
|
||||||
|
|
||||||
target_include_directories(engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
@ -8,7 +8,8 @@ Engine::Engine::Engine() :
|
|||||||
physicalDevice(nullptr),
|
physicalDevice(nullptr),
|
||||||
logicalDevice(nullptr),
|
logicalDevice(nullptr),
|
||||||
deviceExtensionNames(),
|
deviceExtensionNames(),
|
||||||
deviceExtensions()
|
deviceExtensions(),
|
||||||
|
programs()
|
||||||
{
|
{
|
||||||
addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||||
}
|
}
|
||||||
@ -31,6 +32,13 @@ void Engine::Engine::enableDebug() {
|
|||||||
instance->enableDebug();
|
instance->enableDebug();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::Engine::addProgram(const Program& program) {
|
||||||
|
programs.emplace_back(program);
|
||||||
|
|
||||||
|
if (logicalDevice != nullptr)
|
||||||
|
logicalDevice->addProgram(program);
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::Engine::initVulkan() {
|
void Engine::Engine::initVulkan() {
|
||||||
instance->initialize(window->getRequiredVulkanExtensions());
|
instance->initialize(window->getRequiredVulkanExtensions());
|
||||||
surface = new Surface(instance, window);
|
surface = new Surface(instance, window);
|
||||||
@ -38,7 +46,10 @@ void Engine::Engine::initVulkan() {
|
|||||||
pickPhysicalDevice();
|
pickPhysicalDevice();
|
||||||
logicalDevice = new LogicalDevice(physicalDevice, surface, deviceExtensions, instance->getLayers());
|
logicalDevice = new LogicalDevice(physicalDevice, surface, deviceExtensions, instance->getLayers());
|
||||||
|
|
||||||
logicalDevice->createGraphicsPipeline("shaders/shader.vert.spv", "shaders/shader.frag.spv");
|
//logicalDevice->createGraphicsPipeline("shaders/shader.vert.spv", "shaders/shader.frag.spv");
|
||||||
|
for (const Program& program : programs)
|
||||||
|
logicalDevice->addProgram(program);
|
||||||
|
|
||||||
logicalDevice->createSwapChain();
|
logicalDevice->createSwapChain();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +64,7 @@ void Engine::Engine::cleanup() {
|
|||||||
surface = nullptr;
|
surface = nullptr;
|
||||||
|
|
||||||
instance->deinitialize();
|
instance->deinitialize();
|
||||||
|
programs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::Engine::pickPhysicalDevice() {
|
void Engine::Engine::pickPhysicalDevice() {
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "instance.h"
|
#include "instance.h"
|
||||||
#include "physicaldevice.h"
|
#include "physicaldevice.h"
|
||||||
#include "logicaldevice.h"
|
#include "logicaldevice.h"
|
||||||
|
#include "program/program.h"
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ public:
|
|||||||
void run();
|
void run();
|
||||||
void addDeviceExtension(const std::string& extensionName);
|
void addDeviceExtension(const std::string& extensionName);
|
||||||
void enableDebug();
|
void enableDebug();
|
||||||
|
void addProgram(const Program& program);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initVulkan();
|
void initVulkan();
|
||||||
@ -56,6 +58,7 @@ private:
|
|||||||
LogicalDevice* logicalDevice;
|
LogicalDevice* logicalDevice;
|
||||||
std::set<std::string> deviceExtensionNames;
|
std::set<std::string> deviceExtensionNames;
|
||||||
std::vector<const char*> deviceExtensions;
|
std::vector<const char*> deviceExtensions;
|
||||||
|
std::vector<Program> programs;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,8 +17,6 @@ Engine::LogicalDevice::LogicalDevice(
|
|||||||
graphicsQueue(),
|
graphicsQueue(),
|
||||||
presentQueue(),
|
presentQueue(),
|
||||||
renderPass(),
|
renderPass(),
|
||||||
pipelineLayout(),
|
|
||||||
graphicsPipeline(),
|
|
||||||
commandPool(),
|
commandPool(),
|
||||||
commandBuffers(),
|
commandBuffers(),
|
||||||
imageAvailableSemaphores(),
|
imageAvailableSemaphores(),
|
||||||
@ -26,7 +24,6 @@ Engine::LogicalDevice::LogicalDevice(
|
|||||||
inFlightFences(),
|
inFlightFences(),
|
||||||
currentFrame(0),
|
currentFrame(0),
|
||||||
framebufferResized(false),
|
framebufferResized(false),
|
||||||
hasPipeline(false),
|
|
||||||
surface(surface),
|
surface(surface),
|
||||||
surfaceFormat(surface->chooseSwapSurfaceFormat(phys->swapChainSupport)),
|
surfaceFormat(surface->chooseSwapSurfaceFormat(phys->swapChainSupport)),
|
||||||
swapChain(nullptr)
|
swapChain(nullptr)
|
||||||
@ -45,10 +42,7 @@ Engine::LogicalDevice::LogicalDevice(
|
|||||||
Engine::LogicalDevice::~LogicalDevice() {
|
Engine::LogicalDevice::~LogicalDevice() {
|
||||||
clearSwapChain();
|
clearSwapChain();
|
||||||
|
|
||||||
if (hasPipeline) {
|
pipelines.clear();
|
||||||
vkDestroyPipeline(vk, graphicsPipeline, nullptr);
|
|
||||||
vkDestroyPipelineLayout(vk, pipelineLayout, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
vkDestroyRenderPass(vk, renderPass, nullptr);
|
vkDestroyRenderPass(vk, renderPass, nullptr);
|
||||||
|
|
||||||
@ -64,6 +58,10 @@ Engine::LogicalDevice::~LogicalDevice() {
|
|||||||
vkDestroyDevice(vk, nullptr);
|
vkDestroyDevice(vk, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::LogicalDevice::addProgram(const Program& program) {
|
||||||
|
pipelines.emplace_back(program, this);
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::LogicalDevice::createSwapChain() {
|
void Engine::LogicalDevice::createSwapChain() {
|
||||||
clearSwapChain();
|
clearSwapChain();
|
||||||
swapChain = new SwapChain(this);
|
swapChain = new SwapChain(this);
|
||||||
@ -126,25 +124,6 @@ VkResult Engine::LogicalDevice::queuePresent(const VkSemaphore& signal, uint32_t
|
|||||||
return vkQueuePresentKHR(presentQueue, &presentInfo);
|
return vkQueuePresentKHR(presentQueue, &presentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkShaderModule Engine::LogicalDevice::createShaderModule(const std::string& path) {
|
|
||||||
std::vector<char> code = readFile(path);
|
|
||||||
|
|
||||||
VkShaderModuleCreateInfo createInfo{};
|
|
||||||
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
||||||
createInfo.codeSize = code.size();
|
|
||||||
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
|
|
||||||
|
|
||||||
VkShaderModule shaderModule;
|
|
||||||
if (vkCreateShaderModule(vk, &createInfo, nullptr, &shaderModule) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("failed to create shader module!");
|
|
||||||
|
|
||||||
return shaderModule;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Engine::LogicalDevice::destroyShaderModule(VkShaderModule shaderModule) {
|
|
||||||
vkDestroyShaderModule(vk, shaderModule, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Engine::LogicalDevice::createDevice(
|
void Engine::LogicalDevice::createDevice(
|
||||||
VkPhysicalDevice physicalDevice,
|
VkPhysicalDevice physicalDevice,
|
||||||
const QueueFamilyIndices& indices,
|
const QueueFamilyIndices& indices,
|
||||||
@ -154,7 +133,6 @@ void Engine::LogicalDevice::createDevice(
|
|||||||
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
|
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
|
||||||
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
|
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
|
||||||
|
|
||||||
|
|
||||||
for (uint32_t queueFamily : uniqueQueueFamilies) {
|
for (uint32_t queueFamily : uniqueQueueFamilies) {
|
||||||
VkDeviceQueueCreateInfo queueCreateInfo{};
|
VkDeviceQueueCreateInfo queueCreateInfo{};
|
||||||
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||||
@ -248,8 +226,12 @@ void Engine::LogicalDevice::drawFrame() {
|
|||||||
|
|
||||||
resetFence(inFlightFences[currentFrame]);
|
resetFence(inFlightFences[currentFrame]);
|
||||||
|
|
||||||
vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0);
|
if (!pipelines.empty()) {
|
||||||
recordCommandBuffer(commandBuffers[currentFrame], imageIndex);
|
vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0);
|
||||||
|
recordCommandBuffer(commandBuffers[currentFrame], imageIndex);
|
||||||
|
} else {
|
||||||
|
std::cout << "no pipelines attached, skipping command buffer record" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
if (queueSubmitGraphics(
|
if (queueSubmitGraphics(
|
||||||
imageAvailableSemaphores[currentFrame],
|
imageAvailableSemaphores[currentFrame],
|
||||||
@ -413,114 +395,6 @@ void Engine::LogicalDevice::createRenderPass(VkFormat format) {
|
|||||||
throw std::runtime_error("failed to create render pass!");
|
throw std::runtime_error("failed to create render pass!");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::LogicalDevice::createGraphicsPipeline(const std::string& vertexShaderPath, const std::string& fragmentShaderPath) {
|
|
||||||
if (hasPipeline)
|
|
||||||
throw std::runtime_error("an attempt to create graphics pipeline for the second time");
|
|
||||||
|
|
||||||
VkShaderModule vertShaderModule = createShaderModule(vertexShaderPath);
|
|
||||||
VkShaderModule fragShaderModule = createShaderModule(fragmentShaderPath);
|
|
||||||
|
|
||||||
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
|
|
||||||
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
||||||
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
|
|
||||||
vertShaderStageInfo.module = vertShaderModule;
|
|
||||||
vertShaderStageInfo.pName = "main";
|
|
||||||
|
|
||||||
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
|
|
||||||
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
||||||
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
||||||
fragShaderStageInfo.module = fragShaderModule;
|
|
||||||
fragShaderStageInfo.pName = "main";
|
|
||||||
|
|
||||||
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
|
|
||||||
|
|
||||||
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
|
|
||||||
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
|
||||||
vertexInputInfo.vertexBindingDescriptionCount = 0;
|
|
||||||
vertexInputInfo.vertexAttributeDescriptionCount = 0;
|
|
||||||
|
|
||||||
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
|
|
||||||
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
|
||||||
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
||||||
inputAssembly.primitiveRestartEnable = VK_FALSE;
|
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo viewportState{};
|
|
||||||
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
|
||||||
viewportState.viewportCount = 1;
|
|
||||||
viewportState.scissorCount = 1;
|
|
||||||
|
|
||||||
VkPipelineRasterizationStateCreateInfo rasterizer{};
|
|
||||||
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
|
||||||
rasterizer.depthClampEnable = VK_FALSE;
|
|
||||||
rasterizer.rasterizerDiscardEnable = VK_FALSE;
|
|
||||||
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
|
|
||||||
rasterizer.lineWidth = 1.0f;
|
|
||||||
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
|
|
||||||
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
|
||||||
rasterizer.depthBiasEnable = VK_FALSE;
|
|
||||||
|
|
||||||
VkPipelineMultisampleStateCreateInfo multisampling{};
|
|
||||||
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
|
||||||
multisampling.sampleShadingEnable = VK_FALSE;
|
|
||||||
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
|
|
||||||
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
|
|
||||||
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
||||||
colorBlendAttachment.blendEnable = VK_FALSE;
|
|
||||||
|
|
||||||
VkPipelineColorBlendStateCreateInfo colorBlending{};
|
|
||||||
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
|
||||||
colorBlending.logicOpEnable = VK_FALSE;
|
|
||||||
colorBlending.logicOp = VK_LOGIC_OP_COPY;
|
|
||||||
colorBlending.attachmentCount = 1;
|
|
||||||
colorBlending.pAttachments = &colorBlendAttachment;
|
|
||||||
colorBlending.blendConstants[0] = 0.0f;
|
|
||||||
colorBlending.blendConstants[1] = 0.0f;
|
|
||||||
colorBlending.blendConstants[2] = 0.0f;
|
|
||||||
colorBlending.blendConstants[3] = 0.0f;
|
|
||||||
|
|
||||||
std::vector<VkDynamicState> dynamicStates = {
|
|
||||||
VK_DYNAMIC_STATE_VIEWPORT,
|
|
||||||
VK_DYNAMIC_STATE_SCISSOR
|
|
||||||
};
|
|
||||||
VkPipelineDynamicStateCreateInfo dynamicState{};
|
|
||||||
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
|
||||||
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
|
|
||||||
dynamicState.pDynamicStates = dynamicStates.data();
|
|
||||||
|
|
||||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
|
||||||
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
||||||
pipelineLayoutInfo.setLayoutCount = 0;
|
|
||||||
pipelineLayoutInfo.pushConstantRangeCount = 0;
|
|
||||||
|
|
||||||
if (vkCreatePipelineLayout(vk, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("failed to create pipeline layout!");
|
|
||||||
|
|
||||||
VkGraphicsPipelineCreateInfo pipelineInfo{};
|
|
||||||
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
|
||||||
pipelineInfo.stageCount = 2;
|
|
||||||
pipelineInfo.pStages = shaderStages;
|
|
||||||
pipelineInfo.pVertexInputState = &vertexInputInfo;
|
|
||||||
pipelineInfo.pInputAssemblyState = &inputAssembly;
|
|
||||||
pipelineInfo.pViewportState = &viewportState;
|
|
||||||
pipelineInfo.pRasterizationState = &rasterizer;
|
|
||||||
pipelineInfo.pMultisampleState = &multisampling;
|
|
||||||
pipelineInfo.pColorBlendState = &colorBlending;
|
|
||||||
pipelineInfo.pDynamicState = &dynamicState;
|
|
||||||
pipelineInfo.layout = pipelineLayout;
|
|
||||||
pipelineInfo.renderPass = renderPass;
|
|
||||||
pipelineInfo.subpass = 0;
|
|
||||||
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
if (vkCreateGraphicsPipelines(vk, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS)
|
|
||||||
throw std::runtime_error("failed to create graphics pipeline!");
|
|
||||||
|
|
||||||
destroyShaderModule(fragShaderModule);
|
|
||||||
destroyShaderModule(vertShaderModule);
|
|
||||||
|
|
||||||
hasPipeline = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Engine::LogicalDevice::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
|
void Engine::LogicalDevice::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
|
||||||
VkExtent2D extent = swapChain->getExtent();
|
VkExtent2D extent = swapChain->getExtent();
|
||||||
VkCommandBufferBeginInfo beginInfo{};
|
VkCommandBufferBeginInfo beginInfo{};
|
||||||
@ -542,7 +416,8 @@ void Engine::LogicalDevice::recordCommandBuffer(VkCommandBuffer commandBuffer, u
|
|||||||
|
|
||||||
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
|
for (const Pipeline& pipeline: pipelines) {
|
||||||
|
pipeline.bind(commandBuffer);
|
||||||
|
|
||||||
VkViewport viewport{};
|
VkViewport viewport{};
|
||||||
viewport.x = 0.0f;
|
viewport.x = 0.0f;
|
||||||
@ -559,6 +434,7 @@ void Engine::LogicalDevice::recordCommandBuffer(VkCommandBuffer commandBuffer, u
|
|||||||
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
|
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
|
||||||
|
|
||||||
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
|
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
vkCmdEndRenderPass(commandBuffer);
|
vkCmdEndRenderPass(commandBuffer);
|
||||||
|
|
||||||
@ -572,3 +448,28 @@ void Engine::LogicalDevice::recreateSwapChain() {
|
|||||||
phys->recreateSwapChainSupportDetails(surface);
|
phys->recreateSwapChainSupportDetails(surface);
|
||||||
createSwapChain();
|
createSwapChain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkResult Engine::LogicalDevice::createPipelineLayout(const VkPipelineLayoutCreateInfo& info, VkPipelineLayout& out) {
|
||||||
|
return vkCreatePipelineLayout(vk, &info, nullptr, &out);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkResult Engine::LogicalDevice::createPipeline(VkGraphicsPipelineCreateInfo& info, VkPipeline& out) {
|
||||||
|
info.renderPass = renderPass;
|
||||||
|
return vkCreateGraphicsPipelines(vk, VK_NULL_HANDLE, 1, &info, nullptr, &out);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkResult Engine::LogicalDevice::createShaderModule(const VkShaderModuleCreateInfo& info, VkShaderModule& out) {
|
||||||
|
return vkCreateShaderModule(vk, &info, nullptr, &out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::LogicalDevice::destroyPipeline(VkPipeline& pipline) {
|
||||||
|
vkDestroyPipeline(vk, pipline, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::LogicalDevice::destroyPipelineLayout(VkPipelineLayout& layout) {
|
||||||
|
vkDestroyPipelineLayout(vk, layout, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::LogicalDevice::destroyShaderModule(VkShaderModule& module) {
|
||||||
|
vkDestroyShaderModule(vk, module, nullptr);
|
||||||
|
}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "physicaldevice.h"
|
#include "physicaldevice.h"
|
||||||
#include "surface.h"
|
#include "surface.h"
|
||||||
|
#include "program/pipeline.h"
|
||||||
|
#include "program/program.h"
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
class SwapChain;
|
class SwapChain;
|
||||||
@ -24,9 +26,9 @@ public:
|
|||||||
|
|
||||||
void createSwapChain();
|
void createSwapChain();
|
||||||
void clearSwapChain();
|
void clearSwapChain();
|
||||||
void createGraphicsPipeline(const std::string& vertexShaderPath, const std::string& fragmentShaderPath);
|
|
||||||
void recreateSwapChain();
|
void recreateSwapChain();
|
||||||
void setResized();
|
void setResized();
|
||||||
|
void addProgram(const Program& program);
|
||||||
|
|
||||||
void drawFrame();
|
void drawFrame();
|
||||||
|
|
||||||
@ -42,10 +44,14 @@ public:
|
|||||||
void destroyVkImageView(VkImageView& view);
|
void destroyVkImageView(VkImageView& view);
|
||||||
VkResult createVkFrameBuffer(const VkImageView& imageView, const VkExtent2D& extent, VkFramebuffer& out);
|
VkResult createVkFrameBuffer(const VkImageView& imageView, const VkExtent2D& extent, VkFramebuffer& out);
|
||||||
void destroyVkFrameBuffer(VkFramebuffer& buffer);
|
void destroyVkFrameBuffer(VkFramebuffer& buffer);
|
||||||
|
VkResult createPipeline(VkGraphicsPipelineCreateInfo& info, VkPipeline& out);
|
||||||
|
VkResult createPipelineLayout(const VkPipelineLayoutCreateInfo& info, VkPipelineLayout& out);
|
||||||
|
void destroyPipelineLayout(VkPipelineLayout& layout);
|
||||||
|
void destroyPipeline(VkPipeline& pipline);
|
||||||
|
VkResult createShaderModule(const VkShaderModuleCreateInfo& info, VkShaderModule& out);
|
||||||
|
void destroyShaderModule(VkShaderModule& shaderModule);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkShaderModule createShaderModule(const std::string& path);
|
|
||||||
void destroyShaderModule(VkShaderModule shaderModule);
|
|
||||||
void createDevice(
|
void createDevice(
|
||||||
VkPhysicalDevice physicalDevice,
|
VkPhysicalDevice physicalDevice,
|
||||||
const QueueFamilyIndices& indices,
|
const QueueFamilyIndices& indices,
|
||||||
@ -73,8 +79,7 @@ private:
|
|||||||
VkQueue graphicsQueue;
|
VkQueue graphicsQueue;
|
||||||
VkQueue presentQueue;
|
VkQueue presentQueue;
|
||||||
VkRenderPass renderPass;
|
VkRenderPass renderPass;
|
||||||
VkPipelineLayout pipelineLayout;
|
std::vector<Pipeline> pipelines;
|
||||||
VkPipeline graphicsPipeline;
|
|
||||||
VkCommandPool commandPool;
|
VkCommandPool commandPool;
|
||||||
std::vector<VkCommandBuffer> commandBuffers;
|
std::vector<VkCommandBuffer> commandBuffers;
|
||||||
std::vector<VkSemaphore> imageAvailableSemaphores;
|
std::vector<VkSemaphore> imageAvailableSemaphores;
|
||||||
@ -82,7 +87,6 @@ private:
|
|||||||
std::vector<VkFence> inFlightFences;
|
std::vector<VkFence> inFlightFences;
|
||||||
uint32_t currentFrame;
|
uint32_t currentFrame;
|
||||||
bool framebufferResized;
|
bool framebufferResized;
|
||||||
bool hasPipeline;
|
|
||||||
Surface* surface;
|
Surface* surface;
|
||||||
VkSurfaceFormatKHR surfaceFormat;
|
VkSurfaceFormatKHR surfaceFormat;
|
||||||
SwapChain* swapChain;
|
SwapChain* swapChain;
|
||||||
|
11
engine/program/CMakeLists.txt
Normal file
11
engine/program/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
set(SOURCES
|
||||||
|
pipeline.cpp
|
||||||
|
program.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(HEADERS
|
||||||
|
pipeline.h
|
||||||
|
program.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(engine PRIVATE ${SOURCES})
|
149
engine/program/pipeline.cpp
Normal file
149
engine/program/pipeline.cpp
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#include "pipeline.h"
|
||||||
|
#include "logicaldevice.h"
|
||||||
|
|
||||||
|
Engine::Pipeline::Pipeline(const Program& program, LogicalDevice* device):
|
||||||
|
device(device),
|
||||||
|
program(program),
|
||||||
|
vk(),
|
||||||
|
layout()
|
||||||
|
{
|
||||||
|
createLayout();
|
||||||
|
createPipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine::Pipeline::Pipeline(const Pipeline& other):
|
||||||
|
device(other.device),
|
||||||
|
program(other.program),
|
||||||
|
vk(),
|
||||||
|
layout()
|
||||||
|
{
|
||||||
|
createLayout();
|
||||||
|
createPipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine::Pipeline::~Pipeline() {
|
||||||
|
device->destroyPipeline(vk);
|
||||||
|
device->destroyPipelineLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::Pipeline::createLayout() {
|
||||||
|
VkPipelineLayoutCreateInfo info{};
|
||||||
|
info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||||
|
info.setLayoutCount = 0;
|
||||||
|
info.pushConstantRangeCount = 0;
|
||||||
|
|
||||||
|
if (device->createPipelineLayout(info, layout) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("failed to create pipeline layout!");
|
||||||
|
}
|
||||||
|
|
||||||
|
VkShaderModule Engine::Pipeline::createShaderModule(Program::ShaderType type) {
|
||||||
|
VkShaderModuleCreateInfo info{};
|
||||||
|
info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||||
|
info.codeSize = program.codeSize(type);
|
||||||
|
info.pCode = program.code(type);
|
||||||
|
|
||||||
|
VkShaderModule module;
|
||||||
|
if (device->createShaderModule(info, module) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("failed to create shader module!");
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::Pipeline::createPipeline() {
|
||||||
|
VkShaderModule vertShaderModule = createShaderModule(Program::vertex);
|
||||||
|
VkShaderModule fragShaderModule = createShaderModule(Program::fragment);
|
||||||
|
|
||||||
|
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
|
||||||
|
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||||
|
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||||
|
vertShaderStageInfo.module = vertShaderModule;
|
||||||
|
vertShaderStageInfo.pName = "main";
|
||||||
|
|
||||||
|
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
|
||||||
|
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||||
|
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
fragShaderStageInfo.module = fragShaderModule;
|
||||||
|
fragShaderStageInfo.pName = "main";
|
||||||
|
|
||||||
|
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
|
||||||
|
|
||||||
|
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
|
||||||
|
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||||
|
vertexInputInfo.vertexBindingDescriptionCount = 0;
|
||||||
|
vertexInputInfo.vertexAttributeDescriptionCount = 0;
|
||||||
|
|
||||||
|
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
|
||||||
|
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||||
|
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
|
inputAssembly.primitiveRestartEnable = VK_FALSE;
|
||||||
|
|
||||||
|
VkPipelineViewportStateCreateInfo viewportState{};
|
||||||
|
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||||
|
viewportState.viewportCount = 1;
|
||||||
|
viewportState.scissorCount = 1;
|
||||||
|
|
||||||
|
VkPipelineRasterizationStateCreateInfo rasterizer{};
|
||||||
|
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||||
|
rasterizer.depthClampEnable = VK_FALSE;
|
||||||
|
rasterizer.rasterizerDiscardEnable = VK_FALSE;
|
||||||
|
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
|
||||||
|
rasterizer.lineWidth = 1.0f;
|
||||||
|
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
|
||||||
|
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||||
|
rasterizer.depthBiasEnable = VK_FALSE;
|
||||||
|
|
||||||
|
VkPipelineMultisampleStateCreateInfo multisampling{};
|
||||||
|
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||||
|
multisampling.sampleShadingEnable = VK_FALSE;
|
||||||
|
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
|
||||||
|
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
|
||||||
|
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||||
|
colorBlendAttachment.blendEnable = VK_FALSE;
|
||||||
|
|
||||||
|
VkPipelineColorBlendStateCreateInfo colorBlending{};
|
||||||
|
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||||
|
colorBlending.logicOpEnable = VK_FALSE;
|
||||||
|
colorBlending.logicOp = VK_LOGIC_OP_COPY;
|
||||||
|
colorBlending.attachmentCount = 1;
|
||||||
|
colorBlending.pAttachments = &colorBlendAttachment;
|
||||||
|
colorBlending.blendConstants[0] = 0.0f;
|
||||||
|
colorBlending.blendConstants[1] = 0.0f;
|
||||||
|
colorBlending.blendConstants[2] = 0.0f;
|
||||||
|
colorBlending.blendConstants[3] = 0.0f;
|
||||||
|
|
||||||
|
std::vector<VkDynamicState> dynamicStates = {
|
||||||
|
VK_DYNAMIC_STATE_VIEWPORT,
|
||||||
|
VK_DYNAMIC_STATE_SCISSOR
|
||||||
|
};
|
||||||
|
VkPipelineDynamicStateCreateInfo dynamicState{};
|
||||||
|
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||||
|
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
|
||||||
|
dynamicState.pDynamicStates = dynamicStates.data();
|
||||||
|
|
||||||
|
VkGraphicsPipelineCreateInfo pipelineInfo{};
|
||||||
|
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||||
|
pipelineInfo.stageCount = 2;
|
||||||
|
pipelineInfo.pStages = shaderStages;
|
||||||
|
pipelineInfo.pVertexInputState = &vertexInputInfo;
|
||||||
|
pipelineInfo.pInputAssemblyState = &inputAssembly;
|
||||||
|
pipelineInfo.pViewportState = &viewportState;
|
||||||
|
pipelineInfo.pRasterizationState = &rasterizer;
|
||||||
|
pipelineInfo.pMultisampleState = &multisampling;
|
||||||
|
pipelineInfo.pColorBlendState = &colorBlending;
|
||||||
|
pipelineInfo.pDynamicState = &dynamicState;
|
||||||
|
pipelineInfo.layout = layout;
|
||||||
|
//pipelineInfo.renderPass = renderPass;
|
||||||
|
pipelineInfo.subpass = 0;
|
||||||
|
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
if (device->createPipeline(pipelineInfo, vk) != VK_SUCCESS)
|
||||||
|
throw std::runtime_error("failed to create graphics pipeline!");
|
||||||
|
|
||||||
|
device->destroyShaderModule(fragShaderModule);
|
||||||
|
device->destroyShaderModule(vertShaderModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::Pipeline::bind(const VkCommandBuffer& buffer) const {
|
||||||
|
vkCmdBindPipeline(buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vk);
|
||||||
|
}
|
34
engine/program/pipeline.h
Normal file
34
engine/program/pipeline.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include "program.h"
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
class LogicalDevice;
|
||||||
|
|
||||||
|
class Pipeline {
|
||||||
|
public:
|
||||||
|
Pipeline(const Program& program, LogicalDevice* device);
|
||||||
|
Pipeline(const Pipeline& other);
|
||||||
|
~Pipeline();
|
||||||
|
|
||||||
|
void bind(const VkCommandBuffer& buffer) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createLayout();
|
||||||
|
void createPipeline();
|
||||||
|
VkShaderModule createShaderModule(Program::ShaderType type);
|
||||||
|
|
||||||
|
private:
|
||||||
|
LogicalDevice* device;
|
||||||
|
const Program& program;
|
||||||
|
VkPipeline vk;
|
||||||
|
VkPipelineLayout layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
38
engine/program/program.cpp
Normal file
38
engine/program/program.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#include "program.h"
|
||||||
|
|
||||||
|
Engine::Program::Program():
|
||||||
|
vertexShader(),
|
||||||
|
fragmentShader()
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
std::size_t Engine::Program::codeSize(ShaderType type) const {
|
||||||
|
switch (type) {
|
||||||
|
case vertex:
|
||||||
|
return vertexShader.size();
|
||||||
|
case fragment:
|
||||||
|
return fragmentShader.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t* Engine::Program::code(ShaderType type) const {
|
||||||
|
switch (type) {
|
||||||
|
case vertex:
|
||||||
|
return reinterpret_cast<const uint32_t*>(vertexShader.data());
|
||||||
|
case fragment:
|
||||||
|
return reinterpret_cast<const uint32_t*>(fragmentShader.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::Program::loadSPIRV(const std::string& path, ShaderType type) {
|
||||||
|
switch (type) {
|
||||||
|
case vertex:
|
||||||
|
vertexShader = readFile(path);
|
||||||
|
case fragment:
|
||||||
|
fragmentShader = readFile(path);
|
||||||
|
}
|
||||||
|
}
|
30
engine/program/program.h
Normal file
30
engine/program/program.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
public:
|
||||||
|
enum ShaderType {
|
||||||
|
vertex,
|
||||||
|
fragment
|
||||||
|
};
|
||||||
|
Program();
|
||||||
|
|
||||||
|
void loadSPIRV(const std::string& path, ShaderType type);
|
||||||
|
std::size_t codeSize(ShaderType type) const;
|
||||||
|
const uint32_t* code(ShaderType type) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<char> vertexShader;
|
||||||
|
std::vector<char> fragmentShader;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -13,17 +13,6 @@ Engine::SwapChain::SwapChain(LogicalDevice* logical):
|
|||||||
create();
|
create();
|
||||||
}
|
}
|
||||||
|
|
||||||
Engine::SwapChain::SwapChain(LogicalDevice* logical, const VkExtent2D& extent):
|
|
||||||
logical(logical),
|
|
||||||
vk(),
|
|
||||||
images(),
|
|
||||||
extent(extent),
|
|
||||||
imageViews(),
|
|
||||||
frameBuffers()
|
|
||||||
{
|
|
||||||
create();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Engine::SwapChain::create() {
|
void Engine::SwapChain::create() {
|
||||||
VkResult result = logical->createVkSwapChain(extent, vk);
|
VkResult result = logical->createVkSwapChain(extent, vk);
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ class SwapChain {
|
|||||||
friend class LogicalDevice;
|
friend class LogicalDevice;
|
||||||
public:
|
public:
|
||||||
SwapChain(LogicalDevice* logical);
|
SwapChain(LogicalDevice* logical);
|
||||||
SwapChain(LogicalDevice* logical, const VkExtent2D& extent);
|
|
||||||
~SwapChain();
|
~SwapChain();
|
||||||
|
|
||||||
VkExtent2D getExtent() const;
|
VkExtent2D getExtent() const;
|
||||||
|
@ -153,7 +153,7 @@ std::vector<char> Engine::readFile(const std::string& filename) {
|
|||||||
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
||||||
|
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
throw std::runtime_error("failed to open file!");
|
throw std::runtime_error("failed to open file " + filename);
|
||||||
|
|
||||||
size_t fileSize = (size_t) file.tellg();
|
size_t fileSize = (size_t) file.tellg();
|
||||||
std::vector<char> buffer(fileSize);
|
std::vector<char> buffer(fileSize);
|
||||||
|
15
main.cpp
15
main.cpp
@ -1,9 +1,24 @@
|
|||||||
#include "engine/engine.h"
|
#include "engine/engine.h"
|
||||||
|
#include "engine/program/program.h"
|
||||||
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
Engine::Engine app;
|
Engine::Engine app;
|
||||||
app.enableDebug();
|
app.enableDebug();
|
||||||
|
|
||||||
|
Engine::Program tl;
|
||||||
|
Engine::Program br;
|
||||||
|
|
||||||
|
//logicalDevice->createGraphicsPipeline("shaders/shader.vert.spv", "shaders/shader.frag.spv");
|
||||||
|
tl.loadSPIRV("shaders/tl.vert.spv", Engine::Program::vertex);
|
||||||
|
tl.loadSPIRV("shaders/shader.frag.spv", Engine::Program::fragment);
|
||||||
|
|
||||||
|
br.loadSPIRV("shaders/br.vert.spv", Engine::Program::vertex);
|
||||||
|
br.loadSPIRV("shaders/shader.frag.spv", Engine::Program::fragment);
|
||||||
|
|
||||||
|
app.addProgram(tl);
|
||||||
|
app.addProgram(br);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
app.run();
|
app.run();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
20
shaders/br.vert
Normal file
20
shaders/br.vert
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 fragColor;
|
||||||
|
|
||||||
|
vec2 positions[3] = vec2[](
|
||||||
|
vec2(0.5, -0.5),
|
||||||
|
vec2(0.5, 0.5),
|
||||||
|
vec2(-0.5, -0.5)
|
||||||
|
);
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
20
shaders/tl.vert
Normal file
20
shaders/tl.vert
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 fragColor;
|
||||||
|
|
||||||
|
vec2 positions[3] = vec2[](
|
||||||
|
vec2(0.5, 0.5),
|
||||||
|
vec2(-0.5, 0.5),
|
||||||
|
vec2(-0.5, -0.5)
|
||||||
|
);
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user