From: Piotr Byszewski Date: Wed, 6 Jul 2022 09:51:49 +0000 (+0200) Subject: Test NULL set layouts with non-independent sets X-Git-Tag: upstream/1.3.5~214 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0170b4a25e36c25442a40a05a21da8452bdab9b4;p=platform%2Fupstream%2FVK-GL-CTS.git Test NULL set layouts with non-independent sets This change ads a test that uses VK_NULL_HANDLE for descriptor set layout when creating a pipeline layout without independent sets. VK_NULL_HANDLE is also used in vkCmdBindDescriptorSets. Note that this functionality is allowed only when VK_EXT_graphics_pipeline_library is enabled. This change also fixes 3 validation errors in bind_null_descriptor_set group which is also part of misc group. VK-GL-CTS issue: 3681 Components: Vulkan Affects: dEQP-VK.pipeline.pipeline_library.graphics_library.misc.* Change-Id: I466ca645fafd845dc89a2c7980e6958ec27ca05b --- diff --git a/android/cts/main/vk-master-2022-03-01/pipeline.txt b/android/cts/main/vk-master-2022-03-01/pipeline.txt index ea9dc50..24cd048 100644 --- a/android/cts/main/vk-master-2022-03-01/pipeline.txt +++ b/android/cts/main/vk-master-2022-03-01/pipeline.txt @@ -225404,7 +225404,8 @@ dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.101 dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.1010 dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.1001 -dEQP-VK.pipeline.pipeline_library.graphics_library.misc.timing.compare_link_times +dEQP-VK.pipeline.pipeline_library.graphics_library.misc.other.compare_link_times +dEQP-VK.pipeline.pipeline_library.graphics_library.misc.other.null_descriptor_set_in_monolithic_pipeline dEQP-VK.pipeline.monolithic.blend.format.r10x6g10x6b10x6a10x6_unorm_4pack16.states.color_dc_sas_rsub_alpha_1mdc_1msc_sub-color_1msa_1msc_add_alpha_ca_da_min-color_1msc_da_sub_alpha_1mca_ca_sub-color_o_1mda_max_alpha_sa_dc_min dEQP-VK.pipeline.monolithic.blend.format.r10x6g10x6b10x6a10x6_unorm_4pack16.states.color_sas_1mda_rsub_alpha_1mda_1mcc_sub-color_1mda_1mca_min_alpha_o_cc_min-color_1mdc_da_min_alpha_1mda_da_min-color_sas_1msa_max_alpha_sas_o_min dEQP-VK.pipeline.monolithic.blend.format.r10x6g10x6b10x6a10x6_unorm_4pack16.states.color_ca_1mcc_rsub_alpha_sa_1msc_rsub-color_1mca_ca_rsub_alpha_1msc_da_rsub-color_1mcc_1mdc_sub_alpha_z_da_sub-color_sc_dc_add_alpha_1mdc_1msa_min diff --git a/android/cts/main/vk-master/pipeline/pipeline-library.txt b/android/cts/main/vk-master/pipeline/pipeline-library.txt index b4bb462..8358fd3 100644 --- a/android/cts/main/vk-master/pipeline/pipeline-library.txt +++ b/android/cts/main/vk-master/pipeline/pipeline-library.txt @@ -225404,4 +225404,5 @@ dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.101 dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.1010 dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.1001 -dEQP-VK.pipeline.pipeline_library.graphics_library.misc.timing.compare_link_times +dEQP-VK.pipeline.pipeline_library.graphics_library.misc.other.compare_link_times +dEQP-VK.pipeline.pipeline_library.graphics_library.misc.other.null_descriptor_set_in_monolithic_pipeline diff --git a/external/vulkancts/modules/vulkan/pipeline/vktPipelineLibraryTests.cpp b/external/vulkancts/modules/vulkan/pipeline/vktPipelineLibraryTests.cpp index 0e45b89..fe67a51 100644 --- a/external/vulkancts/modules/vulkan/pipeline/vktPipelineLibraryTests.cpp +++ b/external/vulkancts/modules/vulkan/pipeline/vktPipelineLibraryTests.cpp @@ -354,7 +354,8 @@ public: void updateVertexInputInterface (Context& context, GraphicsPipelineCreateInfo& graphicsPipelineCreateInfo, - VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) + VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + deUint32 vertexDescriptionCount = 1u) { DE_UNREF(context); @@ -372,17 +373,17 @@ void updateVertexInputInterface (Context& context, 0u // deUint32 offsetInBytes; }; - const VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = + const VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; 0u, // VkPipelineVertexInputStateCreateFlags flags; - 1u, // deUint32 vertexBindingDescriptionCount; + vertexDescriptionCount, // deUint32 vertexBindingDescriptionCount; &graphicsPipelineCreateInfo.m_vertexInputBindingDescription, // const VkVertexInputBindingDescription* pVertexBindingDescriptions; - 1u, // deUint32 vertexAttributeDescriptionCount; + vertexDescriptionCount, // deUint32 vertexAttributeDescriptionCount; &graphicsPipelineCreateInfo.m_vertexInputAttributeDescription, // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions; }; - const VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo = + const VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, // VkStructureType sType; DE_NULL, // const void* pNext; @@ -1229,6 +1230,7 @@ enum class MiscTestMode INDEPENDENT_PIPELINE_LAYOUT_SETS_FAST_LINKED = 0, INDEPENDENT_PIPELINE_LAYOUT_SETS_WITH_LINK_TIME_OPTIMIZATION_UNION_HANDLE, BIND_NULL_DESCRIPTOR_SET, + BIND_NULL_DESCRIPTOR_SET_IN_MONOLITHIC_PIPELINE, COMPARE_LINK_TIMES }; @@ -1251,9 +1253,10 @@ public: protected: - tcu::TestStatus runNullDescriptorSet (void); - tcu::TestStatus runIndependentPipelineLayoutSets (bool useLinkTimeOptimization = false); - tcu::TestStatus runCompareLinkTimes (void); + tcu::TestStatus runNullDescriptorSet (void); + tcu::TestStatus runNullDescriptorSetInMonolithicPipeline(void); + tcu::TestStatus runIndependentPipelineLayoutSets (bool useLinkTimeOptimization = false); + tcu::TestStatus runCompareLinkTimes (void); struct VerificationData { @@ -1313,6 +1316,8 @@ tcu::TestStatus PipelineLibraryMiscTestInstance::iterate (void) // run selected test if (m_testParams.mode == MiscTestMode::BIND_NULL_DESCRIPTOR_SET) return runNullDescriptorSet(); + else if (m_testParams.mode == MiscTestMode::BIND_NULL_DESCRIPTOR_SET_IN_MONOLITHIC_PIPELINE) + return runNullDescriptorSetInMonolithicPipeline(); else if (m_testParams.mode == MiscTestMode::INDEPENDENT_PIPELINE_LAYOUT_SETS_FAST_LINKED) return runIndependentPipelineLayoutSets(); else if (m_testParams.mode == MiscTestMode::INDEPENDENT_PIPELINE_LAYOUT_SETS_WITH_LINK_TIME_OPTIMIZATION_UNION_HANDLE) @@ -1545,6 +1550,127 @@ tcu::TestStatus PipelineLibraryMiscTestInstance::runNullDescriptorSet(void) return verifyResult(verificationData, colorPixelAccess); } +tcu::TestStatus PipelineLibraryMiscTestInstance::runNullDescriptorSetInMonolithicPipeline() +{ + // VK_NULL_HANDLE can be used for descriptor set layouts when creating a pipeline layout whether independent sets are used or not, + // as long as graphics pipeline libraries are enabled; VK_NULL_HANDLE is also alowed for a descriptor set under the same conditions + // when using vkCmdBindDescriptorSets + + const DeviceInterface& vk = m_context.getDeviceInterface(); + const VkDevice device = m_context.getDevice(); + Allocator& allocator = m_context.getDefaultAllocator(); + + const VkDeviceSize colorBufferDataSize = static_cast(m_renderArea.extent.width * m_renderArea.extent.height * tcu::getPixelSize(mapVkFormat(m_colorFormat))); + const VkBufferCreateInfo colorBufferCreateInfo = makeBufferCreateInfo(colorBufferDataSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + const BufferWithMemory colorBuffer(vk, device, allocator, colorBufferCreateInfo, MemoryRequirement::HostVisible); + + const tcu::Vec4 uniformBuffData { 0.0f, 0.20f, 0.6f, 0.75f }; + VkDeviceSize uniformBufferDataSize = sizeof(tcu::Vec4); + const VkBufferCreateInfo uniformBufferCreateInfo = makeBufferCreateInfo(uniformBufferDataSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + de::MovePtr uniformBuffer = de::MovePtr(new BufferWithMemory(vk, device, allocator, uniformBufferCreateInfo, MemoryRequirement::HostVisible)); + deMemcpy(uniformBuffer->getAllocation().getHostPtr(), uniformBuffData.getPtr(), (size_t)uniformBufferDataSize); + flushAlloc(vk, device, uniformBuffer->getAllocation()); + + // create descriptor set layouts - first unused, second used + Move descriptorSetLayout + { + DescriptorSetLayoutBuilder() + .addSingleBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT) + .build(vk, device) + }; + + Move allDescriptorPool = DescriptorPoolBuilder() + .addType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1) + .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1); + + // create descriptor set + Move descriptorSet = makeDescriptorSet(vk, device, *allDescriptorPool, *descriptorSetLayout); + + // update descriptor with actual buffer + const VkDescriptorBufferInfo shaderBufferInfo = makeDescriptorBufferInfo(**uniformBuffer, 0u, uniformBufferDataSize); + DescriptorSetUpdateBuilder() + .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, &shaderBufferInfo) + .update(vk, device); + + // create a pipeline layout with its first descriptor set layout as VK_NULL_HANDLE + // and a second with a valid descriptor set layout containing a buffer + VkDescriptorSet rawDescriptorSets[] = { DE_NULL, *descriptorSet }; + VkDescriptorSetLayout rawDescriptorSetLayouts[] = { DE_NULL, *descriptorSetLayout }; + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = initVulkanStructure(); + pipelineLayoutCreateInfo.setLayoutCount = 2u; + pipelineLayoutCreateInfo.pSetLayouts = rawDescriptorSetLayouts; + Move pipelineLayout = createPipelineLayout(vk, device, &pipelineLayoutCreateInfo); + + // create monolithic graphics pipeline + GraphicsPipelineCreateInfo pipelineCreateInfo(*pipelineLayout, *m_renderPass, 0, 0u); + updateVertexInputInterface(m_context, pipelineCreateInfo, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 0u); + updatePreRasterization(m_context, pipelineCreateInfo, false); + updatePostRasterization(m_context, pipelineCreateInfo, false); + updateFragmentOutputInterface(m_context, pipelineCreateInfo); + Move pipeline = createGraphicsPipeline(vk, device, DE_NULL, &pipelineCreateInfo); + + vk::beginCommandBuffer(vk, *m_cmdBuffer, 0u); + { + // change color image layout + const VkImageMemoryBarrier initialImageBarrier = makeImageMemoryBarrier( + 0, // VkAccessFlags srcAccessMask; + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, // VkAccessFlags dstAccessMask; + VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout oldLayout; + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // VkImageLayout newLayout; + **m_colorImage, // VkImage image; + { VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u } // VkImageSubresourceRange subresourceRange; + ); + vk.cmdPipelineBarrier(*m_cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, (VkDependencyFlags)0, 0, DE_NULL, 0, DE_NULL, 1, &initialImageBarrier); + + // wait for uniform buffer + const VkBufferMemoryBarrier initialBufferBarrier = makeBufferMemoryBarrier( + VK_ACCESS_HOST_WRITE_BIT, // VkAccessFlags2KHR srcAccessMask + VK_ACCESS_UNIFORM_READ_BIT, // VkAccessFlags2KHR dstAccessMask + uniformBuffer->get(), // VkBuffer buffer + 0u, // VkDeviceSize offset + uniformBufferDataSize // VkDeviceSize size + ); + vk.cmdPipelineBarrier(*m_cmdBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, (VkDependencyFlags)0, 0, DE_NULL, 1, &initialBufferBarrier, 0, DE_NULL); + + beginRenderPass(vk, *m_cmdBuffer, *m_renderPass, *m_framebuffer, m_renderArea, m_colorClearColor); + + vk.cmdBindPipeline(*m_cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); + vk.cmdBindDescriptorSets(*m_cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 2u, rawDescriptorSets, 0u, DE_NULL); + vk.cmdDraw(*m_cmdBuffer, 4, 1u, 0u, 0u); + + endRenderPass(vk, *m_cmdBuffer); + + const tcu::IVec2 size{ (deInt32)m_renderArea.extent.width, (deInt32)m_renderArea.extent.height }; + copyImageToBuffer(vk, *m_cmdBuffer, **m_colorImage, *colorBuffer, size); + } + vk::endCommandBuffer(vk, *m_cmdBuffer); + vk::submitCommandsAndWait(vk, device, m_context.getUniversalQueue(), *m_cmdBuffer); + + vk::invalidateAlloc(vk, device, colorBuffer.getAllocation()); + const tcu::ConstPixelBufferAccess colorPixelAccess(mapVkFormat(m_colorFormat), m_renderArea.extent.width, m_renderArea.extent.height, 1, colorBuffer.getAllocation().getHostPtr()); + + // verify result + deInt32 width = (deInt32)m_renderArea.extent.width; + deInt32 height = (deInt32)m_renderArea.extent.height; + tcu::IVec4 outColor + { + 0, // r is 0 because COLOR_COMPONENTS_NO_RED is used + static_cast(uniformBuffData[1] * 255), + static_cast(uniformBuffData[2] * 255), + static_cast(uniformBuffData[3] * 255) + }; + const std::vector verificationData + { + { { 1, 1 }, outColor }, + { { width / 2, height / 2 }, outColor }, + { { width - 2, height - 2 }, { 0, 0, 0, 255 } } // clear color + }; + + return verifyResult(verificationData, colorPixelAccess); +} + tcu::TestStatus PipelineLibraryMiscTestInstance::runIndependentPipelineLayoutSets (bool useLinkTimeOptimization) { const DeviceInterface& vk = m_context.getDeviceInterface(); @@ -1652,7 +1778,7 @@ tcu::TestStatus PipelineLibraryMiscTestInstance::runIndependentPipelineLayoutSet }; // fill proper portion of pipeline state - updateVertexInputInterface (m_context, partialPipelineCreateInfo[0], VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); + updateVertexInputInterface (m_context, partialPipelineCreateInfo[0], VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 0u); updatePreRasterization (m_context, partialPipelineCreateInfo[1], false); updatePostRasterization (m_context, partialPipelineCreateInfo[2], false); updateFragmentOutputInterface (m_context, partialPipelineCreateInfo[3]); @@ -1696,14 +1822,14 @@ tcu::TestStatus PipelineLibraryMiscTestInstance::runIndependentPipelineLayoutSet // wait for uniform buffers std::vector initialBufferBarriers(3u, makeBufferMemoryBarrier( VK_ACCESS_HOST_WRITE_BIT, // VkAccessFlags2KHR srcAccessMask - VK_ACCESS_TRANSFER_READ_BIT, // VkAccessFlags2KHR dstAccessMask + VK_ACCESS_UNIFORM_READ_BIT, // VkAccessFlags2KHR dstAccessMask uniformBuffer[0]->get(), // VkBuffer buffer 0u, // VkDeviceSize offset uniformBufferDataSize // VkDeviceSize size )); initialBufferBarriers[1].buffer = uniformBuffer[1]->get(); initialBufferBarriers[2].buffer = uniformBuffer[2]->get(); - vk.cmdPipelineBarrier(*m_cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, (VkDependencyFlags)0, 0, DE_NULL, 3, initialBufferBarriers.data(), 0, DE_NULL); + vk.cmdPipelineBarrier(*m_cmdBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, (VkDependencyFlags)0, 0, DE_NULL, 3, initialBufferBarriers.data(), 0, DE_NULL); beginRenderPass(vk, *m_cmdBuffer, *m_renderPass, *m_framebuffer, m_renderArea, m_colorClearColor); @@ -1903,14 +2029,30 @@ void PipelineLibraryMiscTestCase::checkSupport(Context& context) const void PipelineLibraryMiscTestCase::initPrograms(SourceCollections& programCollection) const { - if (m_testParams.mode == MiscTestMode::BIND_NULL_DESCRIPTOR_SET) + if ((m_testParams.mode == MiscTestMode::BIND_NULL_DESCRIPTOR_SET) || + (m_testParams.mode == MiscTestMode::BIND_NULL_DESCRIPTOR_SET_IN_MONOLITHIC_PIPELINE)) { std::string vertDefinition = ""; std::string fragDefinition = ""; std::string vertValue = " vec4 v = vec4(-1.0, 1.0, 2.0, -2.0);\n"; std::string fragValue = " vec4 v = vec4(0.0, 0.2, 0.6, 0.75);\n"; - if (m_testParams.layoutsBits > 0u) + // define lambda that creates proper uniform buffer definition + auto constructBufferDefinition = [](deUint32 setIndex) + { + return std::string("layout(set = ") + std::to_string(setIndex) + ", binding = 0) uniform buf\n" + "{\n" + " vec4 v;\n" + "};\n\n"; + }; + + if (m_testParams.mode == MiscTestMode::BIND_NULL_DESCRIPTOR_SET_IN_MONOLITHIC_PIPELINE) + { + // descriptor set 0 will be DE_NULL, descriptor set 1 will be valid buffer with color + fragDefinition = constructBufferDefinition(1); + fragValue = ""; + } + else if (m_testParams.layoutsBits > 0u) { std::vector bitsThatAreSet; const deUint32 maxBitsCount = 8 * sizeof(m_testParams.layoutsBits); @@ -1925,15 +2067,6 @@ void PipelineLibraryMiscTestCase::initPrograms(SourceCollections& programCollect // there should be 1 or 2 bits set DE_ASSERT((bitsThatAreSet.size() > 0) && (bitsThatAreSet.size() < 3)); - // define lambda that creates proper uniform buffer definition - auto constructBufferDefinition = [](deUint32 setIndex) - { - return std::string("layout(set = ") + std::to_string(setIndex) + ", binding = 0) uniform buf\n" - "{\n" - " vec4 v;\n" - "};\n\n"; - }; - vertDefinition = constructBufferDefinition(bitsThatAreSet[0]); vertValue = ""; @@ -1946,8 +2079,7 @@ void PipelineLibraryMiscTestCase::initPrograms(SourceCollections& programCollect programCollection.glslSources.add("vert") << glu::VertexSource( std::string("#version 450\n" - "precision mediump int; precision highp float;" - "layout(location = 0) in vec4 in_position;\n") + + "precision mediump int;\nprecision highp float;\n") + vertDefinition + "out gl_PerVertex\n" "{\n" @@ -2229,9 +2361,10 @@ tcu::TestCaseGroup* createPipelineLibraryTests(tcu::TestContext& testCtx) } miscTests->addChild(bindNullDescriptorCombinationsTests.release()); - de::MovePtr timingTests(new tcu::TestCaseGroup(testCtx, "timing", "")); - timingTests->addChild(new PipelineLibraryMiscTestCase(testCtx, "compare_link_times", { MiscTestMode::COMPARE_LINK_TIMES, 0u, 0u })); - miscTests->addChild(timingTests.release()); + de::MovePtr otherTests(new tcu::TestCaseGroup(testCtx, "other", "")); + otherTests->addChild(new PipelineLibraryMiscTestCase(testCtx, "compare_link_times", { MiscTestMode::COMPARE_LINK_TIMES, 0u, 0u })); + otherTests->addChild(new PipelineLibraryMiscTestCase(testCtx, "null_descriptor_set_in_monolithic_pipeline", { MiscTestMode::BIND_NULL_DESCRIPTOR_SET_IN_MONOLITHIC_PIPELINE, 0u, 0u })); + miscTests->addChild(otherTests.release()); group->addChild(miscTests.release()); diff --git a/external/vulkancts/mustpass/main/vk-default/pipeline/pipeline-library.txt b/external/vulkancts/mustpass/main/vk-default/pipeline/pipeline-library.txt index f1f5bc6..489c841 100644 --- a/external/vulkancts/mustpass/main/vk-default/pipeline/pipeline-library.txt +++ b/external/vulkancts/mustpass/main/vk-default/pipeline/pipeline-library.txt @@ -225445,4 +225445,5 @@ dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.101 dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.1010 dEQP-VK.pipeline.pipeline_library.graphics_library.misc.bind_null_descriptor_set.1001 -dEQP-VK.pipeline.pipeline_library.graphics_library.misc.timing.compare_link_times +dEQP-VK.pipeline.pipeline_library.graphics_library.misc.other.compare_link_times +dEQP-VK.pipeline.pipeline_library.graphics_library.misc.other.null_descriptor_set_in_monolithic_pipeline