diff options
| author | Egor Yusov <egor.yusov@gmail.com> | 2018-09-01 19:02:31 +0000 |
|---|---|---|
| committer | Egor Yusov <egor.yusov@gmail.com> | 2018-09-01 19:02:31 +0000 |
| commit | 590c98bfe02f1758f3828b8e3cc813c0dbee654e (patch) | |
| tree | e5072aad7d2944a6b03aa1141b1a5ced90975959 /Graphics/GraphicsEngineD3D12 | |
| parent | Disabled notification messages in OpenGL (diff) | |
| download | DiligentCore-590c98bfe02f1758f3828b8e3cc813c0dbee654e.tar.gz DiligentCore-590c98bfe02f1758f3828b8e3cc813c0dbee654e.zip | |
Updated texture map/unmap intererface; implemented texture mapping in D3D12
Diffstat (limited to 'Graphics/GraphicsEngineD3D12')
4 files changed, 203 insertions, 60 deletions
diff --git a/Graphics/GraphicsEngineD3D12/include/DeviceContextD3D12Impl.h b/Graphics/GraphicsEngineD3D12/include/DeviceContextD3D12Impl.h index ae006d32..bfde19bb 100644 --- a/Graphics/GraphicsEngineD3D12/include/DeviceContextD3D12Impl.h +++ b/Graphics/GraphicsEngineD3D12/include/DeviceContextD3D12Impl.h @@ -26,15 +26,14 @@ /// \file /// Declaration of Diligent::DeviceContextD3D12Impl class +#include <unordered_map> + #include "DeviceContextD3D12.h" #include "DeviceContextBase.h" #include "GenerateMips.h" #include "BufferD3D12Impl.h" #include "TextureViewD3D12Impl.h" #include "PipelineStateD3D12Impl.h" -#ifdef _DEBUG -# define VERIFY_CONTEXT_BINDINGS -#endif namespace Diligent { @@ -115,7 +114,7 @@ public: Uint32 SrcOffset, Uint32 SrcStride, Uint32 SrcDepthStride, - class TextureD3D12Impl* pTextureD3D12, + class TextureD3D12Impl& TextureD3D12, Uint32 DstSubResIndex, const Box& DstBox); void CopyTextureRegion(ID3D12Resource* pd3d12Buffer, @@ -123,17 +122,29 @@ public: Uint32 SrcStride, Uint32 SrcDepthStride, Uint32 BufferSize, - class TextureD3D12Impl* pTextureD3D12, + class TextureD3D12Impl& TextureD3D12, Uint32 DstSubResIndex, const Box& DstBox); void UpdateTextureRegion(const void* pSrcData, Uint32 SrcStride, Uint32 SrcDepthStride, - class TextureD3D12Impl* pTextureD3D12, + class TextureD3D12Impl& TextureD3D12, Uint32 DstSubResIndex, const Box& DstBox); + void MapTexture( class TextureD3D12Impl& TextureD3D12, + Uint32 MipLevel, + Uint32 ArraySlice, + MAP_TYPE MapType, + Uint32 MapFlags, + const Box& MapRegion, + MappedTextureSubresource& MappedData ); + + void UnmapTexture( class TextureD3D12Impl& TextureD3D12, + Uint32 MipLevel, + Uint32 ArraySlice); + void GenerateMips(class TextureViewD3D12Impl *pTexView); struct DynamicAllocation AllocateDynamicSpace(size_t NumBytes, size_t Alignment); @@ -151,6 +162,20 @@ private: void CommitScissorRects(class GraphicsContext &GraphCtx, bool ScissorEnable); void Flush(bool RequestNewCmdCtx); + struct TextureUploadSpace + { + DynamicAllocation Allocation; + Uint32 AlignedOffset = 0; + Uint32 Stride = 0; + Uint32 DepthStride = 0; + Uint32 RowSize = 0; + Uint32 RowCount = 0; + Box Region; + }; + TextureUploadSpace AllocateTextureUploadSpace(TEXTURE_FORMAT TexFmt, + const Box& Region); + + friend class SwapChainD3D12Impl; inline class CommandContext* RequestCmdContext() { @@ -186,6 +211,26 @@ private: const Uint32 m_ContextId; std::vector<std::pair<Uint64, RefCntAutoPtr<IFence> > > m_PendingFences; + + struct TextureUploadAllocationKey + { + TextureD3D12Impl* const Texture; + UINT const Subresource; + + bool operator == (const TextureUploadAllocationKey& rhs)const + { + return Texture == rhs.Texture && + Subresource == rhs.Subresource; + } + struct Hasher + { + size_t operator()(const TextureUploadAllocationKey& Key)const + { + return ComputeHash(Key.Texture, Key.Subresource); + } + }; + }; + std::unordered_map<TextureUploadAllocationKey, TextureUploadSpace, TextureUploadAllocationKey::Hasher> m_TextureUploadAllocations; }; } diff --git a/Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h b/Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h index 95430ade..3a314a37 100644 --- a/Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h +++ b/Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h @@ -63,8 +63,14 @@ public: virtual void UpdateData( IDeviceContext *pContext, Uint32 MipLevel, Uint32 Slice, const Box &DstBox, const TextureSubResData &SubresData )override final; //virtual void CopyData(CTexture *pSrcTexture, Uint32 SrcOffset, Uint32 DstOffset, Uint32 Size); - virtual void Map( IDeviceContext *pContext, Uint32 Subresource, MAP_TYPE MapType, Uint32 MapFlags, MappedTextureSubresource &MappedData )override final; - virtual void Unmap( IDeviceContext *pContext, Uint32 Subresource, MAP_TYPE MapType, Uint32 MapFlags )override final; + virtual void Map( IDeviceContext* pContext, + Uint32 MipLevel, + Uint32 ArraySlice, + MAP_TYPE MapType, + Uint32 MapFlags, + const Box* pMapRegion, + MappedTextureSubresource& MappedData )override final; + virtual void Unmap( IDeviceContext *pContext, Uint32 MipLevel, Uint32 ArraySlice)override final; virtual ID3D12Resource* GetD3D12Texture(){ return GetD3D12Resource(); } diff --git a/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp index ab1ae905..52898a5d 100644 --- a/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp @@ -759,17 +759,17 @@ namespace Diligent Uint32 SrcStride, Uint32 SrcDepthStride, Uint32 BufferSize, - class TextureD3D12Impl* pTextureD3D12, + class TextureD3D12Impl& TextureD3D12, Uint32 DstSubResIndex, const Box& DstBox) { - const auto& TexDesc = pTextureD3D12->GetDesc(); + const auto& TexDesc = TextureD3D12.GetDesc(); auto *pCmdCtx = RequestCmdContext(); auto *pCmdList = pCmdCtx->GetCommandList(); - auto TextureState = pTextureD3D12->GetState(); + auto TextureState = TextureD3D12.GetState(); D3D12_RESOURCE_BARRIER BarrierDesc; BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - BarrierDesc.Transition.pResource = pTextureD3D12->GetD3D12Resource(); + BarrierDesc.Transition.pResource = TextureD3D12.GetD3D12Resource(); BarrierDesc.Transition.Subresource = DstSubResIndex; BarrierDesc.Transition.StateBefore = TextureState; BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; @@ -780,7 +780,7 @@ namespace Diligent D3D12_TEXTURE_COPY_LOCATION DstLocation; DstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - DstLocation.pResource = pTextureD3D12->GetD3D12Resource(); + DstLocation.pResource = TextureD3D12.GetD3D12Resource(); DstLocation.SubresourceIndex = static_cast<UINT>(DstSubResIndex); D3D12_TEXTURE_COPY_LOCATION SrcLocation; @@ -830,7 +830,7 @@ namespace Diligent Uint32 SrcOffset, Uint32 SrcStride, Uint32 SrcDepthStride, - class TextureD3D12Impl* pTextureD3D12, + class TextureD3D12Impl& TextureD3D12, Uint32 DstSubResIndex, const Box& DstBox) { @@ -841,71 +841,136 @@ namespace Diligent RequestCmdContext()->TransitionResource(pBufferD3D12, D3D12_RESOURCE_STATE_GENERIC_READ, true); size_t DataStartByteOffset = 0; auto* pd3d12Buffer = pBufferD3D12->GetD3D12Buffer(DataStartByteOffset, m_ContextId); - CopyTextureRegion(pd3d12Buffer, static_cast<Uint32>(DataStartByteOffset) + SrcOffset, SrcStride, SrcDepthStride, pBufferD3D12->GetDesc().uiSizeInBytes, pTextureD3D12, DstSubResIndex, DstBox); + CopyTextureRegion(pd3d12Buffer, static_cast<Uint32>(DataStartByteOffset) + SrcOffset, SrcStride, SrcDepthStride, pBufferD3D12->GetDesc().uiSizeInBytes, TextureD3D12, DstSubResIndex, DstBox); } - void DeviceContextD3D12Impl::UpdateTextureRegion(const void* pSrcData, - Uint32 SrcStride, - Uint32 SrcDepthStride, - TextureD3D12Impl* pTextureD3D12, - Uint32 DstSubResIndex, - const Box& DstBox) + DeviceContextD3D12Impl::TextureUploadSpace DeviceContextD3D12Impl::AllocateTextureUploadSpace(TEXTURE_FORMAT TexFmt, + const Box& Region) { - const auto& TexDesc = pTextureD3D12->GetDesc(); - const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); - VERIFY_EXPR(DstBox.MaxX > DstBox.MinX && DstBox.MaxY > DstBox.MinY && DstBox.MaxZ > DstBox.MinZ); - auto UpdateRegionWidth = DstBox.MaxX - DstBox.MinX; - auto UpdateRegionHeight = DstBox.MaxY - DstBox.MinY; - auto UpdateRegionDepth = DstBox.MaxZ - DstBox.MinZ; - Uint32 RowSize = 0; - Uint32 RowCount = 0; + TextureUploadSpace UploadSpace; + VERIFY_EXPR(Region.MaxX > Region.MinX && Region.MaxY > Region.MinY && Region.MaxZ > Region.MinZ); + auto UpdateRegionWidth = Region.MaxX - Region.MinX; + auto UpdateRegionHeight = Region.MaxY - Region.MinY; + auto UpdateRegionDepth = Region.MaxZ - Region.MinZ; + const auto& FmtAttribs = GetTextureFormatAttribs(TexFmt); if(FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED) { // Box must be aligned by the calling function VERIFY_EXPR((UpdateRegionWidth % FmtAttribs.BlockWidth) == 0); VERIFY_EXPR((UpdateRegionHeight % FmtAttribs.BlockHeight) == 0); - RowSize = UpdateRegionWidth / Uint32{FmtAttribs.BlockWidth} * Uint32{FmtAttribs.ComponentSize}; - RowCount = UpdateRegionHeight / FmtAttribs.BlockHeight; + UploadSpace.RowSize = UpdateRegionWidth / Uint32{FmtAttribs.BlockWidth} * Uint32{FmtAttribs.ComponentSize}; + UploadSpace.RowCount = UpdateRegionHeight / FmtAttribs.BlockHeight; } else { - RowSize = UpdateRegionWidth * Uint32{FmtAttribs.ComponentSize} * Uint32{FmtAttribs.NumComponents}; - RowCount = UpdateRegionHeight; + UploadSpace.RowSize = UpdateRegionWidth * Uint32{FmtAttribs.ComponentSize} * Uint32{FmtAttribs.NumComponents}; + UploadSpace.RowCount = UpdateRegionHeight; } + // RowPitch must be a multiple of 256 (aka. D3D12_TEXTURE_DATA_PITCH_ALIGNMENT) + UploadSpace.Stride = (UploadSpace.RowSize + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT-1) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT-1); + UploadSpace.DepthStride = UploadSpace.RowCount * UploadSpace.Stride; + const auto MemorySize = UpdateRegionDepth * UploadSpace.DepthStride; + UploadSpace.Allocation = AllocateDynamicSpace(MemorySize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); + UploadSpace.AlignedOffset = (UploadSpace.Allocation.Offset + (D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT-1)) & ~(D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT-1); + UploadSpace.Region = Region; + + return UploadSpace; + } + + void DeviceContextD3D12Impl::UpdateTextureRegion(const void* pSrcData, + Uint32 SrcStride, + Uint32 SrcDepthStride, + TextureD3D12Impl& TextureD3D12, + Uint32 DstSubResIndex, + const Box& DstBox) + { + const auto& TexDesc = TextureD3D12.GetDesc(); + auto UploadSpace = AllocateTextureUploadSpace(TexDesc.Format, DstBox); + auto UpdateRegionDepth = DstBox.MaxZ-DstBox.MinZ; #ifdef _DEBUG { - VERIFY(SrcStride >= RowSize, "Source data stride (", SrcStride, ") is below the image row size (", RowSize, ")"); - const Uint32 PlaneSize = SrcStride * RowCount; + VERIFY(SrcStride >= UploadSpace.RowSize, "Source data stride (", SrcStride, ") is below the image row size (", UploadSpace.RowSize, ")"); + const Uint32 PlaneSize = SrcStride * UploadSpace.RowCount; VERIFY(UpdateRegionDepth == 1 || SrcDepthStride >= PlaneSize, "Source data depth stride (", SrcDepthStride, ") is below the image plane size (", PlaneSize, ")"); } #endif - // RowPitch must be a multiple of 256 (aka. D3D12_TEXTURE_DATA_PITCH_ALIGNMENT) - const auto AlignedStride = (RowSize + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT-1) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT-1); - const auto AlignedDepthStride = RowCount * AlignedStride; - const auto MemorySize = UpdateRegionDepth * AlignedDepthStride; - const auto UploadSpace = AllocateDynamicSpace(MemorySize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); - const auto AlignedOffset = (UploadSpace.Offset + (D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT-1)) & ~(D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT-1); + const auto AlignedOffset = UploadSpace.AlignedOffset; - for(Uint32 slice = 0; slice < UpdateRegionDepth; ++slice) + for(Uint32 DepthSlice = 0; DepthSlice < UpdateRegionDepth; ++DepthSlice) { - for(Uint32 row = 0; row < RowCount; ++row) + for(Uint32 row = 0; row < UploadSpace.RowCount; ++row) { const auto* pSrcPtr = reinterpret_cast<const Uint8*>(pSrcData) + row * SrcStride - + slice * SrcDepthStride; + + DepthSlice * SrcDepthStride; auto* pDstPtr = - reinterpret_cast<Uint8*>(UploadSpace.CPUAddress) - + (AlignedOffset - UploadSpace.Offset) - + row * AlignedStride - + slice * AlignedDepthStride; + reinterpret_cast<Uint8*>(UploadSpace.Allocation.CPUAddress) + + (AlignedOffset - UploadSpace.Allocation.Offset) + + row * UploadSpace.Stride + + DepthSlice * UploadSpace.DepthStride; - memcpy(pDstPtr, pSrcPtr, RowSize); + memcpy(pDstPtr, pSrcPtr, UploadSpace.RowSize); } } - CopyTextureRegion(UploadSpace.pBuffer, static_cast<Uint32>(AlignedOffset), AlignedStride, AlignedDepthStride, MemorySize, pTextureD3D12, DstSubResIndex, DstBox); + CopyTextureRegion(UploadSpace.Allocation.pBuffer, + static_cast<Uint32>(AlignedOffset), + UploadSpace.Stride, + UploadSpace.DepthStride, + static_cast<Uint32>(UploadSpace.Allocation.Size - (AlignedOffset - UploadSpace.Allocation.Offset)), + TextureD3D12, + DstSubResIndex, + DstBox); + } + + void DeviceContextD3D12Impl::MapTexture( TextureD3D12Impl& TextureD3D12, + Uint32 MipLevel, + Uint32 ArraySlice, + MAP_TYPE MapType, + Uint32 MapFlags, + const Box& MapRegion, + MappedTextureSubresource& MappedData ) + { + const auto& TexDesc = TextureD3D12.GetDesc(); + auto UploadSpace = AllocateTextureUploadSpace(TexDesc.Format, MapRegion); + const auto AlignedOffset = (UploadSpace.Allocation.Offset + (D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT-1)) & ~(D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT-1); + MappedData.pData = reinterpret_cast<Uint8*>(UploadSpace.Allocation.CPUAddress) + (UploadSpace.AlignedOffset - UploadSpace.Allocation.Offset); + MappedData.Stride = UploadSpace.Stride; + MappedData.DepthStride = UploadSpace.DepthStride; + + auto Subres = D3D12CalcSubresource(MipLevel, ArraySlice, 0, TexDesc.MipLevels, TexDesc.ArraySize); + auto it = m_TextureUploadAllocations.emplace(TextureUploadAllocationKey{&TextureD3D12, Subres}, std::move(UploadSpace)); + if(!it.second) + LOG_ERROR_MESSAGE("Mip level ", MipLevel, ", slice ", ArraySlice, " of texture '", TexDesc.Name, "' has already been mapped"); } + void DeviceContextD3D12Impl::UnmapTexture( TextureD3D12Impl& TextureD3D12, + Uint32 MipLevel, + Uint32 ArraySlice) + { + const auto& TexDesc = TextureD3D12.GetDesc(); + auto Subres = D3D12CalcSubresource(MipLevel, ArraySlice, 0, TexDesc.MipLevels, TexDesc.ArraySize); + auto UploadSpaceIt = m_TextureUploadAllocations.find(TextureUploadAllocationKey{&TextureD3D12, Subres}); + if(UploadSpaceIt != m_TextureUploadAllocations.end()) + { + auto& UploadSpace = UploadSpaceIt->second; + CopyTextureRegion(UploadSpace.Allocation.pBuffer, + UploadSpace.AlignedOffset, + UploadSpace.Stride, + UploadSpace.DepthStride, + static_cast<Uint32>(UploadSpace.Allocation.Size - (UploadSpace.AlignedOffset - UploadSpace.Allocation.Offset)), + TextureD3D12, + Subres, + UploadSpace.Region); + m_TextureUploadAllocations.erase(UploadSpaceIt); + } + else + { + LOG_ERROR_MESSAGE("Failed to unmap mip level ", MipLevel, ", slice ", ArraySlice, " of texture '", TexDesc.Name, "'. The texture has either been unmapped already or has not been mapped"); + } + } + + void DeviceContextD3D12Impl::GenerateMips(TextureViewD3D12Impl* pTexView) { auto *pCtx = RequestCmdContext(); diff --git a/Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp index 77d959a6..21eeeca4 100644 --- a/Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp @@ -438,9 +438,9 @@ void TextureD3D12Impl::UpdateData( IDeviceContext* pContext, auto *pCtxD3D12 = ValidatedCast<DeviceContextD3D12Impl>(pContext); auto DstSubResIndex = D3D12CalcSubresource(MipLevel, Slice, 0, m_Desc.MipLevels, m_Desc.ArraySize); if (SubresData.pSrcBuffer == nullptr) - pCtxD3D12->UpdateTextureRegion(SubresData.pData, SubresData.Stride, SubresData.DepthStride, this, DstSubResIndex, *pBox); + pCtxD3D12->UpdateTextureRegion(SubresData.pData, SubresData.Stride, SubresData.DepthStride, *this, DstSubResIndex, *pBox); else - pCtxD3D12->CopyTextureRegion(SubresData.pSrcBuffer, 0, SubresData.Stride, SubresData.DepthStride, this, DstSubResIndex, *pBox); + pCtxD3D12->CopyTextureRegion(SubresData.pSrcBuffer, 0, SubresData.Stride, SubresData.DepthStride, *this, DstSubResIndex, *pBox); } void TextureD3D12Impl :: CopyData(IDeviceContext* pContext, @@ -477,19 +477,46 @@ void TextureD3D12Impl :: CopyData(IDeviceContext* pContext, pCtxD3D12->CopyTextureRegion(pSrcTexD3D12, SrcSubResIndex, pD3D12SrcBox, this, DstSubResIndex, DstX, DstY, DstZ); } -void TextureD3D12Impl :: Map(IDeviceContext* pContext, Uint32 Subresource, MAP_TYPE MapType, Uint32 MapFlags, MappedTextureSubresource &MappedData) +void TextureD3D12Impl :: Map( IDeviceContext* pContext, + Uint32 MipLevel, + Uint32 ArraySlice, + MAP_TYPE MapType, + Uint32 MapFlags, + const Box* pMapRegion, + MappedTextureSubresource& MappedData ) { - TTextureBase::Map( pContext, Subresource, MapType, MapFlags, MappedData ); - UNSUPPORTED("TextureD3D12Impl::Map() is not implemented"); + TTextureBase::Map(pContext, MipLevel, ArraySlice, MapType, MapFlags, pMapRegion, MappedData); - //static char TmpDummyBuffer[1024*1024*64]; - MappedData.pData = nullptr;//TmpDummyBuffer; + auto* pDeviceContextD3D12 = ValidatedCast<DeviceContextD3D12Impl>(pContext); + MappedData = MappedTextureSubresource{}; + + Box FullExtentBox; + if (pMapRegion == nullptr) + { + FullExtentBox.MaxX = std::max(m_Desc.Width >> MipLevel, 1u); + FullExtentBox.MaxY = std::max(m_Desc.Height >> MipLevel, 1u); + if (m_Desc.Type == RESOURCE_DIM_TEX_3D) + FullExtentBox.MaxZ = std::max(m_Desc.Depth >> MipLevel, 1u); + pMapRegion = &FullExtentBox; + } + + if(MapType == MAP_WRITE) + { + if( (MapFlags & (MAP_FLAG_DISCARD | MAP_FLAG_DO_NOT_SYNCHRONIZE)) != 0 ) + LOG_WARNING_MESSAGE_ONCE("Mapping textures with flags MAP_FLAG_DISCARD or MAP_FLAG_DO_NOT_SYNCHRONIZE has no effect in D3D12 backend"); + pDeviceContextD3D12->MapTexture(*this, MipLevel, ArraySlice, MapType, MapFlags, *pMapRegion, MappedData); + } + else + { + LOG_ERROR("Textures can currently only be mapped for writing in D3D12 backend"); + } } -void TextureD3D12Impl::Unmap( IDeviceContext* pContext, Uint32 Subresource, MAP_TYPE MapType, Uint32 MapFlags ) +void TextureD3D12Impl::Unmap(IDeviceContext* pContext, Uint32 MipLevel, Uint32 ArraySlice) { - TTextureBase::Unmap( pContext, Subresource, MapType, MapFlags ); - UNSUPPORTED("TextureD3D12Impl::Unmap() is not implemented"); + TTextureBase::Unmap(pContext, MipLevel, ArraySlice); + auto* pDeviceContextD3D12 = ValidatedCast<DeviceContextD3D12Impl>(pContext); + pDeviceContextD3D12->UnmapTexture(*this, MipLevel, ArraySlice); } |
