From 4898a6ff505f734f98ba277d1951a26fb783cb7b Mon Sep 17 00:00:00 2001
From: Alexander Gauggel <agauggel@uni-koblenz.de>
Date: Fri, 28 May 2021 20:40:43 +0200
Subject: [PATCH] Add rendering of box into first mesh project

---
 include/vkcv/Core.hpp                         |   4 +-
 projects/first_mesh/CMakeLists.txt            |   4 +-
 .../first_mesh/resources/shaders/compile.bat  |   3 +
 .../first_mesh/resources/shaders/frag.spv     | Bin 0 -> 608 bytes
 .../first_mesh/resources/shaders/shader.frag  |   9 ++
 .../first_mesh/resources/shaders/shader.vert  |  27 ++++
 .../first_mesh/resources/shaders/vert.spv     | Bin 0 -> 1844 bytes
 projects/first_mesh/src/main.cpp              | 147 +++++++++++-------
 projects/first_triangle/src/main.cpp          |  11 +-
 src/vkcv/BufferManager.cpp                    |   3 +
 src/vkcv/Core.cpp                             |  12 +-
 11 files changed, 147 insertions(+), 73 deletions(-)
 create mode 100644 projects/first_mesh/resources/shaders/compile.bat
 create mode 100644 projects/first_mesh/resources/shaders/frag.spv
 create mode 100644 projects/first_mesh/resources/shaders/shader.frag
 create mode 100644 projects/first_mesh/resources/shaders/shader.vert
 create mode 100644 projects/first_mesh/resources/shaders/vert.spv

diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 3ff2d925..ab0d2aca 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -186,9 +186,9 @@ namespace vkcv
 		/**
 		 * @brief render a beautiful triangle
 		*/
-		void renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle,
+		void renderMesh(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle,
 			const int width, const int height, const size_t pushConstantSize, const void* pushConstantData, 
-			const BufferHandle vertexBuffer);
+			const BufferHandle vertexBuffer, const BufferHandle indexBuffer, const size_t indexCount);
 
 		/**
 		 * @brief end recording and present image
diff --git a/projects/first_mesh/CMakeLists.txt b/projects/first_mesh/CMakeLists.txt
index ae9c5604..eb0f028d 100644
--- a/projects/first_mesh/CMakeLists.txt
+++ b/projects/first_mesh/CMakeLists.txt
@@ -22,7 +22,7 @@ if(MSVC)
 endif()
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include})
+target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries})
+target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
diff --git a/projects/first_mesh/resources/shaders/compile.bat b/projects/first_mesh/resources/shaders/compile.bat
new file mode 100644
index 00000000..b4521235
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/compile.bat
@@ -0,0 +1,3 @@
+%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
+%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
+pause
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/frag.spv b/projects/first_mesh/resources/shaders/frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cb13e606fc0041e24ff6a63c0ec7dcca466732aa
GIT binary patch
literal 608
zcmZQ(Qf6mhU}WH8;AIeIfB-=TCI&_Z1_o{hHZbk(6YQf`T#}+^Vrl?V!N<T1qQG+e
z4D1Xn3=CkLo0ypglHg=uVBle3U=Uzn28**ZFfcGPFf%YQFfbfq1gYg@aQBIK404Ju
zPAy0*N-Rl@FV09zNiB-cPs&P7E-417;R2~~_xE@Aag9$dD2OjEsmw`@&&kY7jZeue
zN=+`wEK3Ec<3LiEnU|bXnu4r=m4OB92AJDe8CV%W?#nMNan8@lF9Nw2BnEPCT2W#;
zOwgTy1*}dEq=12e0mKKXf$@bIn85x~1pABu%obx{W?*ArU{GLSU=U|uVF39X#0Q0s
z0s{*J2RIxUz~a_WKFBSg&;+UHVqgI42k}Abxfwt)!@vL%2dM|~L2dxWjWz=dSS=se
zeGT?z46I-`fb0SZfz0^9z{J4Hz`$V3zydad7aA{t46I<i{0s~XATf}7n3w=m3}gpP
rOb{vtioXB`cCh<}7#J8p`a$YJ@*sEEF))L}3?%oLfsw(2fsp|KBDy=E

literal 0
HcmV?d00001

diff --git a/projects/first_mesh/resources/shaders/shader.frag b/projects/first_mesh/resources/shaders/shader.frag
new file mode 100644
index 00000000..d26446a7
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/shader.frag
@@ -0,0 +1,9 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 fragColor;
+layout(location = 0) out vec4 outColor;
+
+void main()	{
+	outColor = vec4(fragColor, 1.0);
+}
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/shader.vert b/projects/first_mesh/resources/shaders/shader.vert
new file mode 100644
index 00000000..e6c0e3d6
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/shader.vert
@@ -0,0 +1,27 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 position;
+
+layout(location = 0) out vec3 fragColor;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+    vec3 positions[3] = {
+        vec3(-0.5, 0.5, -1),
+        vec3( 0.5, 0.5, -1),
+        vec3(0, -0.5, -1)
+    };
+    
+    vec3 colors[3] = {
+        vec3(1, 0, 0),
+        vec3(0, 1, 0),
+        vec3(0, 0, 1)
+    };
+
+	gl_Position = mvp * vec4(position, 1.0);
+	fragColor = colors[gl_VertexIndex % 3];
+}
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/vert.spv b/projects/first_mesh/resources/shaders/vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..74722f38ff0584d51d4a966c748b69a4d3f1d3cb
GIT binary patch
literal 1844
zcmZQ(Qf6mhU}WH8;AOC5fB-=TCI&_Z1_o{hHZbk(6YQf`T#}+^Vrl?V!N<T1qQG+e
z44hy#3j+f~ZenI0h@-&3z@W{*z+lS2z+le6z`)GF%)rFJz;K9>fq{jAlfm65-Z982
zzBsiYu_&=5HNH3_F(tJqK0hfdHMyi1q=pNm#@*lF-N!XPxu77vxTG>CH9jXZFEu_T
zvnVyWB(p3Pq>ckgU1nZ#PH76V0#*hV24r`!GO#l6FfcF_<QHd_Waj4;GcYiK)QB=L
zFeK;a<QEl#_-qVv3=9nEIq?ChMPaE$C8-r4Kd~`D#6fJ39MlK~HU@UEdQf<Q#WV9t
zf-|d9LF!;~Ait;Q#5?C?7Pw><mn7yTr^5AtLJ}lbnv(;UV`X4w0EHpQ43L{u!0yWf
z=>!E20~-Si14s-cpIcVI08+08;(-0b3J!aiA3?$(GeB{XR+O0T4Du&f9UC}q(sSY=
zp7hL10r}K}ffei@kUnk@!N9-(69f4XBnF~kVj#bP#6bRliGkQ4F;LjMGcbeA1F7>s
zayKi~T$n$Uk?ewru`@6*2rw`(fXo1ify{7cU;+CN6h|N}AU?=%$b3+m1KF>|z`!8P
zzywZvir`ep0A`CZFf*_*Fo45YoPmV_WDkfBa<?c03j+sK4rIPIln+YfAbA-E76vW`
z2CyEGm;wU}1IR9r86a_xdXQPXAk7R6`(b=O2;UyY=Lho}_UnLC0>qynJs@=;IheR0
z0|Ns{41_`AF#SSc{R|8+b;1k`3?MO(86b5aagcjNpkW9y2c!n3Ukt3j!5*Xz<aTie
z1_qEA$P5sMiAzAug~`FhC86RVwIDf=ILJOJ1_lO@Ss;BNHK6d7W?%q^Hz+-T><5Lf
z3<IdtVPF7-rz}(+Bqj&VXD~e=IS>Ygvpl#gVF0HmYX%ku5FeCJK=A^KH$`Z?fzl_)
z9uQv%>`w*;2?iDhP#Oc73v!1X12Z@efy6*&t1>WvOEOS81DOrtgYpzCj@6;^F!i9c
z1+oVeb|Cd2KB&wCiGj>Bg1QxCABb-P<-^p2_@HzPayv-g3>qFFH-pRr@j>YrWFE{f
zmQXW5>aC$^5M-Vi11mUPgZu^ZF38Lu3``85JO(lgls-Y_9!M>W4=VFOd^-kaaG3~7
zCm=RR92AF+46F<wKWi{BFo41V6ppsgFw|sVU;v4O_#iWMpyhoa11mV5bs0eE666vF
z2AG&0R1D-Fn3z6P43w_}7}yy=VFjvSK>9)ILGqxqHIac8EN95TzyPuvqz2?ZP#7CS
z;~eBZko!S=kUEgtKw)VCt%JH5SQ$WkD+UGzkQgW|L1uu$(T0J60pxFx97rF?PqqvU
W3?OlknV@(A@&7U~GFUJ$G5`QTCya9d

literal 0
HcmV?d00001

diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index b37429e1..2b5c783b 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -1,76 +1,103 @@
 #include <iostream>
-#include <stdio.h>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
 #include <vkcv/asset/asset_loader.hpp>
 
 
 int main(int argc, const char** argv) {
+	const char* applicationName = "First Mesh";
+
+	const int windowWidth = 800;
+	const int windowHeight = 600;
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		false
+	);
+
+	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
+
+	window.initEvents();
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
 	vkcv::asset::Mesh mesh;
-	
-	const char *path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
+
+	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
 	int result = vkcv::asset::loadMesh(path, mesh);
-	
+
 	if (result == 1) {
 		std::cout << "Mesh loading successful!" << std::endl;
-	} else {
+	}
+	else {
 		std::cout << "Mesh loading failed: " << result << std::endl;
 		return 1;
 	}
 
-	/* Demonstration of how to use the vkcv::asset::Mesh struct. */
-	const char *primitive_modes[] = {
-		"points", "lines", "line loop", "line strip", "triangles",
-		"triangle strip", "triangle fan"
-	};
-	const char *primitive_types[] = {
-		"unknown", "position", "normal", "texcoord0"
-	};
-	printf("Mesh %s (%s) has %lu vertex group(s) and %lu material(s):\n",
-			mesh.name.c_str(), path, mesh.vertexGroups.size(),
-			mesh.materials.size());
-	for (size_t i = 0; i < mesh.vertexGroups.size(); i++) {
-		printf("--- vertex group %lu ---\n", i);
-		const auto &vg = mesh.vertexGroups[i];
-		printf("primitive mode: %d (%s)\n", vg.mode,
-				primitive_modes[vg.mode]);
-		printf("index buffer: %lu bytes for %lu indices ",
-				vg.indexBuffer.data.size(), vg.numIndices);
-		const auto itype = vg.indexBuffer.type;
-		printf("(%s @ %p)\n",
-				itype == vkcv::asset::UINT32 ? "UINT32" :
-				itype == vkcv::asset::UINT16 ? "UINT16" :
-				"UINT8", vg.indexBuffer.data.data());
-		printf("\tindices: ");
-		const size_t n = vg.numIndices;
-		if (vg.indexBuffer.type == vkcv::asset::UINT32) {
-			uint32_t *idx = (uint32_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else
-		if (vg.indexBuffer.type == vkcv::asset::UINT16) {
-			uint16_t *idx = (uint16_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else
-		if (vg.indexBuffer.type == vkcv::asset::UINT8) {
-			uint8_t *idx = (uint8_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else {
-			fprintf(stderr, "ERROR Invalid IndexType: %d\n",
-					vg.indexBuffer.type);
-			return 0;
-		}
-		printf("\n");
-		printf("vertex buffer: %lu bytes for %lu vertices with %lu "
-				"attributes (starting at %p)\n",
-				vg.vertexBuffer.data.size(), vg.numVertices,
-				vg.vertexBuffer.attributes.size(),
-				vg.vertexBuffer.data.data());
-		printf("attributes:\toffset\tlength\tstride\tcomponents\n");
-		for (const auto att : vg.vertexBuffer.attributes) {
-			printf("%11s\t%u\t%u\t%u\t%hhux%hu\n",
-					primitive_types[att.type],
-					att.offset, att.length, att.stride,
-					att.componentCount, att.componentType);
-		}
+	assert(mesh.vertexGroups.size() > 0);
+	const size_t vertexBufferSize = mesh.vertexGroups[0].vertexBuffer.data.size();
+	auto vertexBuffer = core.createBuffer<uint8_t>(vkcv::BufferType::VERTEX, vertexBufferSize, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data.data(), vertexBufferSize);
+
+	const size_t indexBufferSize = mesh.vertexGroups[0].indexBuffer.data.size();
+	auto indexBuffer = core.createBuffer<uint8_t>(vkcv::BufferType::INDEX, indexBufferSize, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data.data(), indexBufferSize);
+
+	// an example attachment for passes that output to the window
+	const vkcv::AttachmentDescription present_color_attachment(
+		vkcv::AttachmentLayout::UNDEFINED,
+		vkcv::AttachmentLayout::COLOR_ATTACHMENT,
+		vkcv::AttachmentLayout::PRESENTATION,
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		core.getSwapchainImageFormat());
+
+	vkcv::PassConfig trianglePassDefinition({ present_color_attachment });
+	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
+
+	if (trianglePass.id == 0)
+	{
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram triangleShaderProgram{};
+	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
+	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+
+	const vkcv::PipelineConfig trianglePipelineDefinition(triangleShaderProgram, windowWidth, windowHeight, trianglePass);
+	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
+	if (trianglePipeline.id == 0)
+	{
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	auto start = std::chrono::system_clock::now();
+	while (window.isWindowOpen())
+	{
+		core.beginFrame();
+		window.pollEvents();
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = end - start;
+		start = end;
+		cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
+		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+
+		core.renderMesh(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp, vertexBuffer.getHandle(), indexBuffer.getHandle(), mesh.vertexGroups[0].numIndices);
+		core.endFrame();
 	}
-	
 	return 0;
 }
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 6261b4d8..0e21f472 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -40,16 +40,19 @@ int main(int argc, const char** argv) {
 	
 	const size_t n = 5027;
 	
-	auto vertexBuffer = core.createBuffer<vec3>(vkcv::BufferType::VERTEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	auto testBuffer = core.createBuffer<vec3>(vkcv::BufferType::VERTEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
 	vec3 vec_data[n];
 
 	for (size_t i = 0; i < n; i++) {
 		vec_data[i] = { 42, static_cast<float>(i), 7 };
 	}
 
-	vertexBuffer.fill(vec_data);
+	testBuffer.fill(vec_data);
+
+	auto triangleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	uint16_t indices[3] = { 0, 1, 2 };
+	triangleIndexBuffer.fill(&indices[0], sizeof(indices));
 
-	
 	/*vec3* m = buffer.map();
 	m[0] = { 0, 0, 0 };
 	m[1] = { 0, 0, 0 };
@@ -143,7 +146,7 @@ int main(int argc, const char** argv) {
         cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
 		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
 
-	    core.renderTriangle(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp, vertexBuffer.getHandle());
+	    core.renderMesh(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp, testBuffer.getHandle(), triangleIndexBuffer.getHandle(), 3);
 	    core.endFrame();
 	}
 	return 0;
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index e9b51128..bb3cf069 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -69,6 +69,9 @@ namespace vkcv {
 			case BufferType::STAGING:
 				usageFlags = vk::BufferUsageFlagBits::eTransferSrc;
 				break;
+			case BufferType::INDEX:
+				usageFlags = vk::BufferUsageFlagBits::eIndexBuffer;
+				break;
 			default:
 				// TODO: maybe an issue
 				break;
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index bac12602..13800254 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -162,9 +162,9 @@ namespace vkcv
 		destroyTemporaryFramebuffers();
 	}
 
-	void Core::renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, 
+	void Core::renderMesh(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, 
 		const int width, const int height, const size_t pushConstantSize, const void *pushConstantData,
-		const BufferHandle vertexBuffer) {
+		const BufferHandle vertexBuffer, const BufferHandle indexBuffer, const size_t indexCount) {
 
 		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
 			return;
@@ -175,7 +175,8 @@ namespace vkcv
 		const vk::Pipeline pipeline		= m_PipelineManager->getVkPipeline(pipelineHandle);
         const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
-		const vk::Buffer vulkanVertexBuffer = m_BufferManager->getBuffer(vertexBuffer);
+		const vk::Buffer vulkanVertexBuffer	= m_BufferManager->getBuffer(vertexBuffer);
+		const vk::Buffer vulkanIndexBuffer	= m_BufferManager->getBuffer(indexBuffer);
 
 		const vk::Framebuffer framebuffer = createFramebuffer(m_Context.getDevice(), renderpass, width, height, imageView);
 		m_TemporaryFramebuffers.push_back(framebuffer);
@@ -184,7 +185,7 @@ namespace vkcv
 		submitInfo.queueType = QueueType::Graphics;
 		submitInfo.signalSemaphores = { m_SyncResources.renderFinished };
 		submitCommands(submitInfo, [renderpass, renderArea, imageView, framebuffer, pipeline, pipelineLayout, 
-			pushConstantSize, pushConstantData, vulkanVertexBuffer](const vk::CommandBuffer& cmdBuffer) {
+			pushConstantSize, pushConstantData, vulkanVertexBuffer, indexCount, vulkanIndexBuffer](const vk::CommandBuffer& cmdBuffer) {
 
 			const std::array<float, 4> clearColor = { 0.f, 0.f, 0.f, 1.f };
 			const vk::ClearValue clearValues(clearColor);
@@ -196,8 +197,9 @@ namespace vkcv
 			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 			
 			cmdBuffer.bindVertexBuffers(0, (vulkanVertexBuffer), { 0 });
+			cmdBuffer.bindIndexBuffer(vulkanIndexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
             cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantSize, pushConstantData);
-			cmdBuffer.draw(3, 1, 0, 0, {});
+			cmdBuffer.drawIndexed(indexCount, 1, 0, 0, {});
 			cmdBuffer.endRenderPass();
 		}, nullptr);
 	}
-- 
GitLab