public BufferInfo(IMgThreadPartition partition, MgBufferUsageFlagBits usage, MgMemoryPropertyFlagBits propertyFlags, uint bufferSize) { Debug.Assert(partition != null); mDevice = partition.Device; Debug.Assert(mDevice != null); mUsageFlags = usage; mMemoryPropertyFlags = propertyFlags; var bufferCreateInfo = new MgBufferCreateInfo { Usage = usage, Size = bufferSize, }; IMgBuffer buffer; var device = partition.Device; var result = device.CreateBuffer(bufferCreateInfo, null, out buffer); Debug.Assert(result == Result.SUCCESS); MgMemoryRequirements memReqs; device.GetBufferMemoryRequirements(buffer, out memReqs); mAlignment = memReqs.Alignment; mBufferSize = memReqs.Size; uint memoryTypeIndex; partition.GetMemoryType(memReqs.MemoryTypeBits, mMemoryPropertyFlags, out memoryTypeIndex); var memAlloc = new MgMemoryAllocateInfo { MemoryTypeIndex = memoryTypeIndex, AllocationSize = memReqs.Size, }; IMgDeviceMemory deviceMemory; result = device.AllocateMemory(memAlloc, null, out deviceMemory); Debug.Assert(result == Result.SUCCESS); buffer.BindBufferMemory(device, deviceMemory, 0); mBuffer = buffer; mDeviceMemory = deviceMemory; mDescriptor = new MgDescriptorBufferInfo { Buffer = mBuffer, Offset = 0, Range = mBufferSize, }; }
//private static BufferTarget GetBufferTarget(MgBufferCreateInfo info) //{ // switch(info.Usage) // { // case MgBufferUsageFlagBits.STORAGE_BUFFER_BIT: // return BufferTarget.ShaderStorageBuffer; // case MgBufferUsageFlagBits.INDEX_BUFFER_BIT: // return BufferTarget.ElementArrayBuffer; // case MgBufferUsageFlagBits.VERTEX_BUFFER_BIT: // return BufferTarget.ArrayBuffer; // case MgBufferUsageFlagBits.INDIRECT_BUFFER_BIT: // return BufferTarget.DrawIndirectBuffer; // case MgBufferUsageFlagBits.TRANSFER_SRC_BIT: // return BufferTarget.CopyReadBuffer; // case MgBufferUsageFlagBits.TRANSFER_DST_BIT: // return BufferTarget.CopyWriteBuffer; // default: // throw new NotSupportedException (); // } //} public FullGLBuffer(MgBufferCreateInfo info) { IsBufferType = DetermineBufferType(info); Usage = info.Usage; //Target = GetBufferTarget(info); RequestedSize = info.Size; }
private static bool DetermineBufferType(MgBufferCreateInfo info) { var isBufferFlags = MgBufferUsageFlagBits.STORAGE_BUFFER_BIT | MgBufferUsageFlagBits.TRANSFER_DST_BIT | MgBufferUsageFlagBits.TRANSFER_SRC_BIT | MgBufferUsageFlagBits.INDEX_BUFFER_BIT | MgBufferUsageFlagBits.UNIFORM_BUFFER_BIT | MgBufferUsageFlagBits.VERTEX_BUFFER_BIT; return((info.Usage & isBufferFlags) != 0); }
public AmtBuffer(MgBufferCreateInfo pCreateInfo) { if (pCreateInfo == null) { throw new ArgumentNullException(nameof(pCreateInfo)); } if (pCreateInfo.Size > nuint.MaxValue) { throw new ArgumentOutOfRangeException(nameof(pCreateInfo.Size) + " must be <= nuint.MaxValue"); } Length = (nuint)pCreateInfo.Size; //var options = MTLResourceOptions.CpuCacheModeDefault; //VertexBuffer = mDevice.CreateBuffer(Length, options); Usage = pCreateInfo.Usage; SharingMode = pCreateInfo.SharingMode; }
void PrepareUniformBuffers() { var structSize = (uint)Marshal.SizeOf(typeof(UniformBufferObject)); MgBufferCreateInfo bufferInfo = new MgBufferCreateInfo { Size = structSize, Usage = MgBufferUsageFlagBits.UNIFORM_BUFFER_BIT, }; var err = mConfiguration.Device.CreateBuffer(bufferInfo, null, out uniformDataVS.buffer); Debug.Assert(err == Result.SUCCESS); mConfiguration.Device.GetBufferMemoryRequirements(uniformDataVS.buffer, out MgMemoryRequirements memReqs); var isValid = mConfiguration.Partition.GetMemoryType(memReqs.MemoryTypeBits, MgMemoryPropertyFlagBits.HOST_VISIBLE_BIT | MgMemoryPropertyFlagBits.HOST_COHERENT_BIT, out uint typeIndex); Debug.Assert(isValid); MgMemoryAllocateInfo allocInfo = new MgMemoryAllocateInfo { AllocationSize = memReqs.Size, MemoryTypeIndex = typeIndex, }; err = mConfiguration.Device.AllocateMemory(allocInfo, null, out uniformDataVS.memory); Debug.Assert(err == Result.SUCCESS); err = uniformDataVS.buffer.BindBufferMemory(mConfiguration.Device, uniformDataVS.memory, 0); Debug.Assert(err == Result.SUCCESS); uniformDataVS.descriptor = new MgDescriptorBufferInfo { Buffer = uniformDataVS.buffer, Offset = 0, Range = structSize, }; UpdateUniformBuffers(); }
private static bool DetermineIfIndexBuffer(MgBufferCreateInfo info) { var isIndexFlags = MgBufferUsageFlagBits.INDEX_BUFFER_BIT; return((info.Usage & isIndexFlags) == isIndexFlags); }
private static bool DetrimineIfStorageBuffer(MgBufferCreateInfo info) { var isStorageFlags = MgBufferUsageFlagBits.STORAGE_BUFFER_BIT; return((info.Usage & isStorageFlags) == isStorageFlags); }
private static bool DetrimineIfVertexBuffer(MgBufferCreateInfo info) { var isVertexFlags = MgBufferUsageFlagBits.VERTEX_BUFFER_BIT; return((info.Usage & isVertexFlags) == isVertexFlags); }
public Result CreateBuffer(MgBufferCreateInfo pCreateInfo, IMgAllocationCallbacks allocator, out IMgBuffer pBuffer) { throw new NotImplementedException(); }
// Allocate one region of memory for the uniform buffer private void InitializeUniforms() { MgBufferCreateInfo pCreateInfo = new MgBufferCreateInfo { Usage = MgBufferUsageFlagBits.UNIFORM_BUFFER_BIT, Size = MaxBytesPerFrame, }; IMgBuffer buffer; var err = mConfiguration.Device.CreateBuffer(pCreateInfo, null, out buffer); Debug.Assert(err == Result.SUCCESS); //dynamicConstantBuffer = device.CreateBuffer(MaxBytesPerFrame, (MTLResourceOptions)0); //dynamicConstantBuffer.Label = "UniformBuffer"; MgMemoryRequirements uniformsMemReqs; mConfiguration.Device.GetBufferMemoryRequirements(buffer, out uniformsMemReqs); const MgMemoryPropertyFlagBits uniformPropertyFlags = MgMemoryPropertyFlagBits.HOST_COHERENT_BIT; uint uniformMemoryTypeIndex; mConfiguration.Partition.GetMemoryType( uniformsMemReqs.MemoryTypeBits, uniformPropertyFlags, out uniformMemoryTypeIndex); var uniformMemAlloc = new MgMemoryAllocateInfo { MemoryTypeIndex = uniformMemoryTypeIndex, AllocationSize = uniformsMemReqs.Size, }; IMgDeviceMemory deviceMemory; var result = mConfiguration.Device.AllocateMemory(uniformMemAlloc, null, out deviceMemory); Debug.Assert(result == Result.SUCCESS); buffer.BindBufferMemory(mConfiguration.Device, deviceMemory, 0); mUniforms = new BufferInfo { Buffer = buffer, DeviceMemory = deviceMemory, Offset = 0, Length = MaxBytesPerFrame, }; IMgDescriptorSetLayout pSetLayout; var dslCreateInfo = new MgDescriptorSetLayoutCreateInfo { Bindings = new MgDescriptorSetLayoutBinding[] { new MgDescriptorSetLayoutBinding { Binding = 0, DescriptorCount = 1, DescriptorType = MgDescriptorType.UNIFORM_BUFFER_DYNAMIC, StageFlags = MgShaderStageFlagBits.VERTEX_BIT, }, }, }; err = mConfiguration.Device.CreateDescriptorSetLayout(dslCreateInfo, null, out pSetLayout); var poolCreateInfo = new Magnesium.MgDescriptorPoolCreateInfo { MaxSets = 1, PoolSizes = new MgDescriptorPoolSize[] { new MgDescriptorPoolSize { DescriptorCount = 1, Type = MgDescriptorType.COMBINED_IMAGE_SAMPLER, }, }, }; err = mConfiguration.Device.CreateDescriptorPool(poolCreateInfo, null, out mDescriptorPool); IMgDescriptorSet[] dSets; MgDescriptorSetAllocateInfo pAllocateInfo = new MgDescriptorSetAllocateInfo { DescriptorPool = mDescriptorPool, DescriptorSetCount = 1, SetLayouts = new IMgDescriptorSetLayout[] { pSetLayout, }, }; mConfiguration.Device.AllocateDescriptorSets(pAllocateInfo, out dSets); mUniformDescriptorSet = dSets[0]; MgWriteDescriptorSet[] writes = new MgWriteDescriptorSet[] { new MgWriteDescriptorSet { DescriptorCount = 1, DescriptorType = MgDescriptorType.UNIFORM_BUFFER_DYNAMIC, DstSet = mUniformDescriptorSet, BufferInfo = new MgDescriptorBufferInfo[] { new MgDescriptorBufferInfo { Buffer = mUniforms.Buffer, Offset = mUniforms.Offset, Range = mUniforms.Length, }, }, DstBinding = 0, } }; mConfiguration.Device.UpdateDescriptorSets(writes, null); mSetLayout = pSetLayout; }
public Result CreateBuffer(MgBufferCreateInfo pCreateInfo, IMgAllocationCallbacks allocator, out IMgBuffer pBuffer) { pBuffer = mEntrypoint.Buffers.CreateBuffer(pCreateInfo); return(Result.SUCCESS); }
private bool DoPack(MgStorageBlockInfo sector, MgOptimizedStorageCreateInfo createInfo, out MgStorageBufferInstance instance) { MgBufferUsageFlagBits bufferUsage = 0; MgMemoryPropertyFlagBits deviceMemoryProperties = 0; var sortedList = new SortedDictionary <SortedKey, uint>(); foreach (var attr in sector.Attributes) { bufferUsage |= attr.Usage; deviceMemoryProperties |= createInfo.Allocations[attr.Index].MemoryPropertyFlags; var firstGroup = 0U; for (var i = 0U; i < mPackingOrder.Length; i += 1) { firstGroup = i; var first = (mPackingOrder[i] & createInfo.Allocations[attr.Index].Usage) > 0; if (first) { break; } } Debug.Assert(firstGroup <= mPackingOrder.Length); var key = new SortedKey { Group = firstGroup, SubOrder = attr.Index, }; sortedList.Add(key, attr.Index); } ulong overallSize = 0; var pCreateInfo = new MgBufferCreateInfo { SharingMode = createInfo.SharingMode, QueueFamilyIndices = createInfo.QueueFamilyIndices, Size = 0, Usage = bufferUsage, }; // The alignment member is identical for all VkBuffer objects created with the same combination of values // for the usage and flags members in the VkBufferCreateInfo structure passed to vkCreateBuffer. var err = mConfiguration.Device.CreateBuffer(pCreateInfo, null, out IMgBuffer pZeroBuffer); if (err != Result.SUCCESS) { instance = null; return(false); } mConfiguration.Device.GetBufferMemoryRequirements(pZeroBuffer, out MgMemoryRequirements pZeroMemReqs); pZeroBuffer.DestroyBuffer(mConfiguration.Device, null); var requiredAlignment = pZeroMemReqs.Alignment; var mappings = new List <MgStorageBufferOffset>(); foreach (var element in sortedList.Values) { var attr = createInfo.Allocations[element]; var mapping = new MgStorageBufferOffset { Index = element, Offset = UpperBounded(overallSize, requiredAlignment), Size = attr.Size, Usage = attr.Usage, }; mappings.Add(mapping); overallSize = mapping.Offset + mapping.Size; } // Real memory allocation pCreateInfo.Size = overallSize; err = mConfiguration.Device.CreateBuffer(pCreateInfo, null, out IMgBuffer pBuffer); if (err != Result.SUCCESS) { instance = null; return(false); } mConfiguration.Device.GetBufferMemoryRequirements(pBuffer, out MgMemoryRequirements pMemReqs); if (mConfiguration.MemoryProperties.GetMemoryType(pMemReqs.MemoryTypeBits, deviceMemoryProperties, out uint typeIndex)) { instance = new MgStorageBufferInstance { Buffer = pBuffer, Usage = bufferUsage, MemoryPropertyFlags = deviceMemoryProperties, TypeIndex = typeIndex, AllocationSize = pMemReqs.Size, Mappings = mappings.ToArray(), }; return(true); } else { // CLEAN UP pBuffer.DestroyBuffer(mConfiguration.Device, null); instance = null; return(false); } }
public MockBuffer(MgBufferCreateInfo createInfo) { BufferSize = createInfo.Size; }
void PrepareVertices() { TriangleVertex[] vertexBuffer = { new TriangleVertex { position = new Vector3(1.0f, 1.0f, 0.0f), color = new Vector3(1.0f, 0.0f, 0.0f) }, new TriangleVertex { position = new Vector3(-1.0f, 1.0f, 0.0f), color = new Vector3(0.0f, 1.0f, 0.0f) }, new TriangleVertex { position = new Vector3(0.0f, -1.0f, 0.0f), color = new Vector3(0.0f, 0.0f, 1.0f) }, }; var structSize = Marshal.SizeOf(typeof(TriangleVertex)); var vertexBufferSize = (ulong)(vertexBuffer.Length * structSize); UInt32[] indexBuffer = { 0, 1, 2 }; indices.count = (uint)indexBuffer.Length; var indexBufferSize = indices.count * sizeof(UInt32); var stagingBuffers = new { vertices = new StagingBuffer(), indices = new StagingBuffer(), }; { var vertexBufferInfo = new MgBufferCreateInfo { Size = vertexBufferSize, Usage = MgBufferUsageFlagBits.TRANSFER_SRC_BIT, }; var err = mConfiguration.Device.CreateBuffer(vertexBufferInfo, null, out stagingBuffers.vertices.buffer); Debug.Assert(err == Result.SUCCESS); mConfiguration.Device.GetBufferMemoryRequirements(stagingBuffers.vertices.buffer, out MgMemoryRequirements memReqs); var isValid = mConfiguration.Partition.GetMemoryType(memReqs.MemoryTypeBits, MgMemoryPropertyFlagBits.HOST_VISIBLE_BIT | MgMemoryPropertyFlagBits.HOST_COHERENT_BIT, out uint typeIndex); Debug.Assert(isValid); MgMemoryAllocateInfo memAlloc = new MgMemoryAllocateInfo { AllocationSize = memReqs.Size, MemoryTypeIndex = typeIndex, }; err = mConfiguration.Device.AllocateMemory(memAlloc, null, out stagingBuffers.vertices.memory); Debug.Assert(err == Result.SUCCESS); // Map and copy err = stagingBuffers.vertices.memory.MapMemory(mConfiguration.Device, 0, memAlloc.AllocationSize, 0, out IntPtr data); Debug.Assert(err == Result.SUCCESS); var offset = 0; foreach (var vertex in vertexBuffer) { IntPtr dest = IntPtr.Add(data, offset); Marshal.StructureToPtr(vertex, dest, false); offset += structSize; } stagingBuffers.vertices.memory.UnmapMemory(mConfiguration.Device); stagingBuffers.vertices.buffer.BindBufferMemory(mConfiguration.Device, stagingBuffers.vertices.memory, 0); Debug.Assert(err == Result.SUCCESS); } { var vertexBufferInfo = new MgBufferCreateInfo { Size = vertexBufferSize, Usage = MgBufferUsageFlagBits.VERTEX_BUFFER_BIT | MgBufferUsageFlagBits.TRANSFER_DST_BIT, }; var err = mConfiguration.Device.CreateBuffer(vertexBufferInfo, null, out vertices.buffer); Debug.Assert(err == Result.SUCCESS); mConfiguration.Device.GetBufferMemoryRequirements(vertices.buffer, out MgMemoryRequirements memReqs); var isValid = mConfiguration.Partition.GetMemoryType(memReqs.MemoryTypeBits, MgMemoryPropertyFlagBits.DEVICE_LOCAL_BIT, out uint typeIndex); Debug.Assert(isValid); var memAlloc = new MgMemoryAllocateInfo { AllocationSize = memReqs.Size, MemoryTypeIndex = typeIndex, }; err = mConfiguration.Device.AllocateMemory(memAlloc, null, out vertices.memory); Debug.Assert(err == Result.SUCCESS); err = vertices.buffer.BindBufferMemory(mConfiguration.Device, vertices.memory, 0); Debug.Assert(err == Result.SUCCESS); } { var indexbufferInfo = new MgBufferCreateInfo { Size = indexBufferSize, Usage = MgBufferUsageFlagBits.TRANSFER_SRC_BIT, }; var err = mConfiguration.Device.CreateBuffer(indexbufferInfo, null, out stagingBuffers.indices.buffer); Debug.Assert(err == Result.SUCCESS); mConfiguration.Device.GetBufferMemoryRequirements(stagingBuffers.indices.buffer, out MgMemoryRequirements memReqs); var isValid = mConfiguration.Partition.GetMemoryType(memReqs.MemoryTypeBits, MgMemoryPropertyFlagBits.HOST_VISIBLE_BIT | MgMemoryPropertyFlagBits.HOST_COHERENT_BIT, out uint typeIndex); Debug.Assert(isValid); var memAlloc = new MgMemoryAllocateInfo { AllocationSize = memReqs.Size, MemoryTypeIndex = typeIndex, }; err = mConfiguration.Device.AllocateMemory(memAlloc, null, out stagingBuffers.indices.memory); Debug.Assert(err == Result.SUCCESS); err = stagingBuffers.indices.memory.MapMemory(mConfiguration.Device, 0, indexBufferSize, 0, out IntPtr data); Debug.Assert(err == Result.SUCCESS); var uintBuffer = new byte[indexBufferSize]; var bufferSize = (int)indexBufferSize; Buffer.BlockCopy(indexBuffer, 0, uintBuffer, 0, bufferSize); Marshal.Copy(uintBuffer, 0, data, bufferSize); stagingBuffers.indices.memory.UnmapMemory(mConfiguration.Device); err = stagingBuffers.indices.buffer.BindBufferMemory(mConfiguration.Device, stagingBuffers.indices.memory, 0); Debug.Assert(err == Result.SUCCESS); } { var indexbufferInfo = new MgBufferCreateInfo { Size = indexBufferSize, Usage = MgBufferUsageFlagBits.INDEX_BUFFER_BIT | MgBufferUsageFlagBits.TRANSFER_DST_BIT, }; var err = mConfiguration.Device.CreateBuffer(indexbufferInfo, null, out indices.buffer); Debug.Assert(err == Result.SUCCESS); mConfiguration.Device.GetBufferMemoryRequirements(indices.buffer, out MgMemoryRequirements memReqs); var isValid = mConfiguration.Partition.GetMemoryType(memReqs.MemoryTypeBits, MgMemoryPropertyFlagBits.DEVICE_LOCAL_BIT, out uint typeIndex); Debug.Assert(isValid); var memAlloc = new MgMemoryAllocateInfo { AllocationSize = memReqs.Size, MemoryTypeIndex = typeIndex, }; err = mConfiguration.Device.AllocateMemory(memAlloc, null, out indices.memory); Debug.Assert(err == Result.SUCCESS); err = indices.buffer.BindBufferMemory(mConfiguration.Device, indices.memory, 0); Debug.Assert(err == Result.SUCCESS); } { var cmdBufferBeginInfo = new MgCommandBufferBeginInfo { }; IMgCommandBuffer copyCmd = getCommandBuffer(true); copyCmd.CmdCopyBuffer( stagingBuffers.vertices.buffer, vertices.buffer, new[] { new MgBufferCopy { Size = vertexBufferSize, } } ); copyCmd.CmdCopyBuffer(stagingBuffers.indices.buffer, indices.buffer, new[] { new MgBufferCopy { Size = indexBufferSize, } }); flushCommandBuffer(copyCmd); stagingBuffers.vertices.buffer.DestroyBuffer(mConfiguration.Device, null); stagingBuffers.vertices.memory.FreeMemory(mConfiguration.Device, null); stagingBuffers.indices.buffer.DestroyBuffer(mConfiguration.Device, null); stagingBuffers.indices.memory.FreeMemory(mConfiguration.Device, null); } const uint VERTEX_BUFFER_BIND_ID = 0; vertices.inputBinding = new MgVertexInputBindingDescription { Binding = VERTEX_BUFFER_BIND_ID, Stride = (uint)structSize, InputRate = MgVertexInputRate.VERTEX, }; var vertexSize = (uint)Marshal.SizeOf(typeof(Vector3)); vertices.inputAttributes = new MgVertexInputAttributeDescription[] { new MgVertexInputAttributeDescription { Binding = VERTEX_BUFFER_BIND_ID, Location = 0, Format = MgFormat.R32G32B32_SFLOAT, Offset = 0, }, new MgVertexInputAttributeDescription { Binding = VERTEX_BUFFER_BIND_ID, Location = 1, Format = MgFormat.R32G32B32_SFLOAT, Offset = vertexSize, } }; vertices.inputState = new MgPipelineVertexInputStateCreateInfo { VertexBindingDescriptions = new MgVertexInputBindingDescription[] { vertices.inputBinding, }, VertexAttributeDescriptions = vertices.inputAttributes, }; }
public IGLBuffer CreateBuffer(MgBufferCreateInfo createInfo) { return(new FullGLBuffer(createInfo)); }
public Result CreateBuffer(MgBufferCreateInfo pCreateInfo, IMgAllocationCallbacks allocator, out IMgBuffer pBuffer) { pBuffer = new AmtBuffer(pCreateInfo); return(Result.SUCCESS); }
void InitializeMesh() { // Generate meshes // Mg : Buffer var indicesInBytes = (ulong)(sizeof(uint) * indicesVboData.Length); var vertexStride = Marshal.SizeOf(typeof(Vector3)); var verticesInBytes = (ulong)(vertexStride * positionVboData.Length); var bufferSize = indicesInBytes + verticesInBytes; var bufferCreateInfo = new MgBufferCreateInfo { Usage = MgBufferUsageFlagBits.INDEX_BUFFER_BIT | MgBufferUsageFlagBits.VERTEX_BUFFER_BIT, Size = bufferSize, }; var device = mConfiguration.Device; var result = device.CreateBuffer(bufferCreateInfo, null, out mBuffer); Debug.Assert(result == Result.SUCCESS); MgMemoryRequirements memReqs; device.GetBufferMemoryRequirements(mBuffer, out memReqs); const MgMemoryPropertyFlagBits memoryPropertyFlags = MgMemoryPropertyFlagBits.HOST_COHERENT_BIT; uint memoryTypeIndex; mConfiguration.Partition.GetMemoryType( memReqs.MemoryTypeBits, memoryPropertyFlags, out memoryTypeIndex); var memAlloc = new MgMemoryAllocateInfo { MemoryTypeIndex = memoryTypeIndex, AllocationSize = memReqs.Size, }; result = device.AllocateMemory(memAlloc, null, out mDeviceMemory); Debug.Assert(result == Result.SUCCESS); mBuffer.BindBufferMemory(device, mDeviceMemory, 0); // COPY INDEX DATA IntPtr dest; result = mDeviceMemory.MapMemory(device, 0, bufferSize, 0, out dest); Debug.Assert(result == Result.SUCCESS); var tempIndices = new byte[indicesInBytes]; Buffer.BlockCopy(indicesVboData, 0, tempIndices, 0, (int)indicesInBytes); Marshal.Copy(tempIndices, 0, dest, (int)indicesInBytes); // COPY VERTEX DATA var vertexOffset = indicesInBytes; // Copy the struct to unmanaged memory. int offset = (int)vertexOffset; for (int i = 0; i < positionVboData.Length; ++i) { IntPtr localDest = IntPtr.Add(dest, offset); Marshal.StructureToPtr(positionVboData[i], localDest, false); offset += vertexStride; } mDeviceMemory.UnmapMemory(device); mIndices = new BufferInfo { Buffer = mBuffer, DeviceMemory = mDeviceMemory, Offset = 0, Length = indicesInBytes, }; mVertices = new BufferInfo { Buffer = mBuffer, DeviceMemory = mDeviceMemory, Offset = indicesInBytes, Length = verticesInBytes, }; }