summaryrefslogtreecommitdiffstats
path: root/Graphics
diff options
context:
space:
mode:
authorEgor Yusov <egor.yusov@gmail.com>2019-04-19 15:11:58 +0000
committerEgor Yusov <egor.yusov@gmail.com>2019-04-19 15:11:58 +0000
commit3f81d1daa7b13e935cf43a6af30367f2b39c09bf (patch)
tree6203a390741d2179acca18397eced96af33167cb /Graphics
parentFixed parameter issue in ALLOCATE macro (diff)
downloadDiligentCore-3f81d1daa7b13e935cf43a6af30367f2b39c09bf.tar.gz
DiligentCore-3f81d1daa7b13e935cf43a6af30367f2b39c09bf.zip
Fixed issues with multi-mip level staging resource in Vk and D3D12 back-ends
Diffstat (limited to 'Graphics')
-rw-r--r--Graphics/GraphicsAccessories/interface/GraphicsAccessories.h13
-rw-r--r--Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp34
-rw-r--r--Graphics/GraphicsEngine/src/Texture.cpp16
-rw-r--r--Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h9
-rw-r--r--Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp68
-rw-r--r--Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp8
-rw-r--r--Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h10
-rw-r--r--Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp21
-rw-r--r--Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp28
9 files changed, 91 insertions, 116 deletions
diff --git a/Graphics/GraphicsAccessories/interface/GraphicsAccessories.h b/Graphics/GraphicsAccessories/interface/GraphicsAccessories.h
index f6a290a3..c5bb97b0 100644
--- a/Graphics/GraphicsAccessories/interface/GraphicsAccessories.h
+++ b/Graphics/GraphicsAccessories/interface/GraphicsAccessories.h
@@ -302,4 +302,17 @@ inline bool IsAnisotropicFilter(FILTER_TYPE FilterType)
bool VerifyResourceStates(RESOURCE_STATE State, bool IsTexture);
+/// Describes the mip level properties
+struct MipLevelProperties
+{
+ Uint32 Width = 0;
+ Uint32 Height = 0;
+ Uint32 Depth = 1;
+ Uint32 RowSize = 0;
+ Uint32 DepthSliceSize = 0;
+ Uint32 MipSize = 0;
+};
+
+MipLevelProperties GetMipLevelProperties(const TextureDesc& TexDesc, Uint32 MipLevel);
+
}
diff --git a/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp b/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp
index 21fe6719..d40114b1 100644
--- a/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp
+++ b/Graphics/GraphicsAccessories/src/GraphicsAccessories.cpp
@@ -21,9 +21,11 @@
* of the possibility of such damages.
*/
+#include <algorithm>
+
#include "GraphicsAccessories.h"
#include "DebugUtilities.h"
-#include <algorithm>
+#include "Align.h"
namespace Diligent
{
@@ -934,4 +936,34 @@ if ( (State & ExclusiveState) != 0 && (State & ~ExclusiveState) != 0 )\
return true;
}
+MipLevelProperties GetMipLevelProperties(const TextureDesc& TexDesc, Uint32 MipLevel)
+{
+ MipLevelProperties MipProps;
+ const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format);
+
+ MipProps.Width = std::max(TexDesc.Width >> MipLevel, 1u);
+ MipProps.Height = std::max(TexDesc.Height >> MipLevel, 1u);
+ MipProps.Depth = (TexDesc.Type == RESOURCE_DIM_TEX_3D) ? std::max(TexDesc.Depth >> MipLevel, 1u) : 1u;
+ if (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED)
+ {
+ VERIFY_EXPR(FmtAttribs.BlockWidth > 1 && FmtAttribs.BlockHeight > 1);
+ VERIFY((FmtAttribs.BlockWidth & (FmtAttribs.BlockWidth-1)) == 0, "Compressed block width is expected to be power of 2");
+ VERIFY((FmtAttribs.BlockHeight & (FmtAttribs.BlockHeight-1)) == 0, "Compressed block height is expected to be power of 2");
+ // For block-compression formats, all parameters are still specified in texels rather than compressed texel blocks (18.4.1)
+ MipProps.Width = Align(MipProps.Width, Uint32{FmtAttribs.BlockWidth});
+ MipProps.Height = Align(MipProps.Height,Uint32{FmtAttribs.BlockHeight});
+ MipProps.RowSize = MipProps.Width / Uint32{FmtAttribs.BlockWidth} * Uint32{FmtAttribs.ComponentSize}; // ComponentSize is the block size
+ MipProps.DepthSliceSize = MipProps.Height / Uint32{FmtAttribs.BlockHeight} * MipProps.RowSize;
+ MipProps.MipSize = MipProps.DepthSliceSize * MipProps.Depth;
+ }
+ else
+ {
+ MipProps.RowSize = MipProps.Width * Uint32{FmtAttribs.ComponentSize} * Uint32{FmtAttribs.NumComponents};
+ MipProps.DepthSliceSize = MipProps.RowSize * MipProps.Height;
+ MipProps.MipSize = MipProps.DepthSliceSize * MipProps.Depth;
+ }
+
+ return MipProps;
+}
+
}
diff --git a/Graphics/GraphicsEngine/src/Texture.cpp b/Graphics/GraphicsEngine/src/Texture.cpp
index d33701ea..57b58279 100644
--- a/Graphics/GraphicsEngine/src/Texture.cpp
+++ b/Graphics/GraphicsEngine/src/Texture.cpp
@@ -238,18 +238,10 @@ void ValidateCopyTextureParams(const CopyTextureAttribs& CopyAttribs )
auto pSrcBox = CopyAttribs.pSrcBox;
if( pSrcBox == nullptr )
{
- SrcBox.MaxX = std::max( SrcTexDesc.Width >> CopyAttribs.SrcMipLevel, 1u );
- if( SrcTexDesc.Type == RESOURCE_DIM_TEX_1D ||
- SrcTexDesc.Type == RESOURCE_DIM_TEX_1D_ARRAY )
- SrcBox.MaxY = 1;
- else
- SrcBox.MaxY = std::max( SrcTexDesc.Height >> CopyAttribs.SrcMipLevel, 1u );
-
- if( SrcTexDesc.Type == RESOURCE_DIM_TEX_3D )
- SrcBox.MaxZ = std::max( SrcTexDesc.Depth >> CopyAttribs.SrcMipLevel, 1u );
- else
- SrcBox.MaxZ = 1;
-
+ auto MipLevelAttribs = GetMipLevelProperties(SrcTexDesc, CopyAttribs.SrcMipLevel);
+ SrcBox.MaxX = MipLevelAttribs.Width;
+ SrcBox.MaxY = MipLevelAttribs.Height;
+ SrcBox.MaxZ = MipLevelAttribs.Depth;
pSrcBox = &SrcBox;
}
ValidateTextureRegion(SrcTexDesc, CopyAttribs.SrcMipLevel, CopyAttribs.SrcSlice, *pSrcBox);
diff --git a/Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h b/Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h
index 552ebeda..0b67b262 100644
--- a/Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h
+++ b/Graphics/GraphicsEngineD3D12/include/TextureD3D12Impl.h
@@ -72,6 +72,13 @@ public:
D3D12_RESOURCE_DESC GetD3D12TextureDesc()const;
+ const D3D12_PLACED_SUBRESOURCE_FOOTPRINT& GetStagingFootprint(Uint32 Subresource)
+ {
+ VERIFY_EXPR(m_StagingFootprints != nullptr);
+ VERIFY_EXPR(Subresource <= (Uint32{m_Desc.MipLevels} * (m_Desc.Type == RESOURCE_DIM_TEX_3D ? 1 : Uint32{m_Desc.ArraySize})));
+ return m_StagingFootprints[Subresource];
+ }
+
protected:
void CreateViewInternal( const struct TextureViewDesc &ViewDesc, ITextureView **ppView, bool bIsDefaultView )override final;
//void PrepareD3D12InitData(const TextureData &InitData, Uint32 NumSubresources, std::vector<D3D12_SUBRESOURCE_DATA> &D3D12InitData);
@@ -81,6 +88,8 @@ protected:
void CreateDSV( TextureViewDesc &DSVDesc, D3D12_CPU_DESCRIPTOR_HANDLE DSVHandle );
void CreateUAV( TextureViewDesc &UAVDesc, D3D12_CPU_DESCRIPTOR_HANDLE UAVHandle );
+ D3D12_PLACED_SUBRESOURCE_FOOTPRINT* m_StagingFootprints = nullptr;
+
friend class RenderDeviceD3D12Impl;
};
diff --git a/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp
index c0866371..e4c04ba4 100644
--- a/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp
+++ b/Graphics/GraphicsEngineD3D12/src/DeviceContextD3D12Impl.cpp
@@ -1139,17 +1139,7 @@ namespace Diligent
if (pSrcTexture->GetDesc().Usage == USAGE_STAGING)
{
SrcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
- auto d3d12TexDesc = pSrcTexture->GetD3D12TextureDesc();
- auto* pd3d12Device = m_pDevice.RawPtr<RenderDeviceD3D12Impl>()->GetD3D12Device();
- pd3d12Device->GetCopyableFootprints(&d3d12TexDesc,
- SrcSubResIndex,
- 1, // Num subresources
- 0, // The offset, in bytes, to the resource.
- &SrcLocation.PlacedFootprint,
- nullptr,
- nullptr,
- nullptr
- );
+ SrcLocation.PlacedFootprint = pSrcTexture->GetStagingFootprint(SrcSubResIndex);
}
else
{
@@ -1161,17 +1151,7 @@ namespace Diligent
if (pDstTexture->GetDesc().Usage == USAGE_STAGING)
{
DstLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
- auto d3d12TexDesc = pDstTexture->GetD3D12TextureDesc();
- auto* pd3d12Device = m_pDevice.RawPtr<RenderDeviceD3D12Impl>()->GetD3D12Device();
- pd3d12Device->GetCopyableFootprints(&d3d12TexDesc,
- DstSubResIndex,
- 1, // Num subresources
- 0, // The offset, in bytes, to the resource.
- &DstLocation.PlacedFootprint,
- nullptr,
- nullptr,
- nullptr
- );
+ DstLocation.PlacedFootprint = pDstTexture->GetStagingFootprint(DstSubResIndex);
}
else
{
@@ -1436,21 +1416,8 @@ namespace Diligent
"access and map texture with MAP_FLAG_DO_NOT_SYNCHRONIZE flag.");
}
- auto d3d12TexDesc = TextureD3D12.GetD3D12TextureDesc();
- auto* pd3d12Device = m_pDevice.RawPtr<RenderDeviceD3D12Impl>()->GetD3D12Device();
- D3D12_PLACED_SUBRESOURCE_FOOTPRINT Footprint = {};
- UINT64 TotalBytes = 0;
- UINT NumRows = 0;
- pd3d12Device->GetCopyableFootprints(&d3d12TexDesc,
- Subres,
- 1, // Num subresources
- 0, // The offset, in bytes, to the resource.
- &Footprint,
- &NumRows,
- nullptr,
- &TotalBytes
- );
-
+ const auto& Footprint = TextureD3D12.GetStagingFootprint(Subres);
+
// It is valid to specify the CPU won't read any data by passing a range where
// End is less than or equal to Begin.
// https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/nf-d3d12-id3d12resource-map
@@ -1460,7 +1427,8 @@ namespace Diligent
DEV_CHECK_ERR((TexDesc.CPUAccessFlags & CPU_ACCESS_READ), "Texture '", TexDesc.Name, "' was not created with CPU_ACCESS_READ flag and can't be mapped for reading");
// Resources on D3D12_HEAP_TYPE_READBACK heaps do not support persistent map.
InvalidateRange.Begin = static_cast<SIZE_T>(Footprint.Offset);
- InvalidateRange.End = static_cast<SIZE_T>(Footprint.Offset + TotalBytes);
+ const auto& NextFootprint = TextureD3D12.GetStagingFootprint(Subres+1);
+ InvalidateRange.End = static_cast<SIZE_T>(NextFootprint.Offset);
}
else if (MapType == MAP_WRITE)
{
@@ -1473,9 +1441,12 @@ namespace Diligent
// Map() invalidates the CPU cache, when necessary, so that CPU reads to this address
// reflect any modifications made by the GPU.
- TextureD3D12.GetD3D12Resource()->Map(0, &InvalidateRange, &MappedData.pData);
+ void* pMappedDataPtr = nullptr;
+ TextureD3D12.GetD3D12Resource()->Map(0, &InvalidateRange, &pMappedDataPtr);
+ MappedData.pData = reinterpret_cast<Uint8*>(pMappedDataPtr) + Footprint.Offset;
MappedData.Stride = static_cast<Uint32>(Footprint.Footprint.RowPitch);
- MappedData.DepthStride = static_cast<Uint32>(Footprint.Footprint.RowPitch * NumRows);
+ const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format);
+ MappedData.DepthStride = static_cast<Uint32>(Footprint.Footprint.Height / FmtAttribs.BlockHeight * Footprint.Footprint.RowPitch);
}
else
{
@@ -1521,21 +1492,10 @@ namespace Diligent
if (TexDesc.CPUAccessFlags == CPU_ACCESS_WRITE)
{
- auto d3d12TexDesc = TextureD3D12.GetD3D12TextureDesc();
- auto* pd3d12Device = m_pDevice.RawPtr<RenderDeviceD3D12Impl>()->GetD3D12Device();
- D3D12_PLACED_SUBRESOURCE_FOOTPRINT Footprint = {};
- UINT64 TotalBytes = 0;
- pd3d12Device->GetCopyableFootprints(&d3d12TexDesc,
- Subres,
- 1, // Num subresources
- 0, // The offset, in bytes, to the resource.
- &Footprint,
- nullptr,
- nullptr,
- &TotalBytes
- );
+ const auto& Footprint = TextureD3D12.GetStagingFootprint(Subres);
+ const auto& NextFootprint = TextureD3D12.GetStagingFootprint(Subres+1);
FlushRange.Begin = static_cast<SIZE_T>(Footprint.Offset);
- FlushRange.End = static_cast<SIZE_T>(Footprint.Offset + TotalBytes);
+ FlushRange.End = static_cast<SIZE_T>(NextFootprint.Offset);
}
// Map and Unmap can be called by multiple threads safely. Nested Map calls are supported
diff --git a/Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp
index 652150ae..56847982 100644
--- a/Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp
+++ b/Graphics/GraphicsEngineD3D12/src/TextureD3D12Impl.cpp
@@ -298,7 +298,9 @@ TextureD3D12Impl :: TextureD3D12Impl(IReferenceCounters* pRefCounters,
UINT64 stagingBufferSize = 0;
Uint32 NumSubresources = Uint32{Desc.MipLevels} * (Desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? 1 : Uint32{Desc.DepthOrArraySize});
- pd3d12Device->GetCopyableFootprints(&Desc, 0, NumSubresources, 0, nullptr, nullptr, nullptr, &stagingBufferSize);
+ m_StagingFootprints = ALLOCATE(GetRawAllocator(), "Memory for staging footprints", D3D12_PLACED_SUBRESOURCE_FOOTPRINT, NumSubresources+1);
+ pd3d12Device->GetCopyableFootprints(&Desc, 0, NumSubresources, 0, m_StagingFootprints, nullptr, nullptr, &stagingBufferSize);
+ m_StagingFootprints[NumSubresources] = D3D12_PLACED_SUBRESOURCE_FOOTPRINT{stagingBufferSize};
D3D12_RESOURCE_DESC BufferDesc;
BufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
@@ -495,6 +497,10 @@ TextureD3D12Impl :: ~TextureD3D12Impl()
// D3D12 object can only be destroyed when it is no longer used by the GPU
auto *pDeviceD3D12Impl = ValidatedCast<RenderDeviceD3D12Impl>(GetDevice());
pDeviceD3D12Impl->SafeReleaseDeviceObject(std::move(m_pd3d12Resource), m_Desc.CommandQueueMask);
+ if (m_StagingFootprints != nullptr)
+ {
+ FREE(GetRawAllocator(), m_StagingFootprints);
+ }
}
void TextureD3D12Impl::CreateSRV( TextureViewDesc& SRVDesc, D3D12_CPU_DESCRIPTOR_HANDLE SRVHandle )
diff --git a/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h b/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h
index d3d0883a..39ec0ea1 100644
--- a/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h
+++ b/Graphics/GraphicsEngineVulkan/include/TextureVkImpl.h
@@ -38,16 +38,6 @@ namespace Diligent
class FixedBlockMemoryAllocator;
-struct MipLevelProperties
-{
- Uint32 Width = 0;
- Uint32 Height = 0;
- Uint32 Depth = 1;
- Uint32 RowSize = 0;
- Uint32 MipSize = 0;
-};
-
-MipLevelProperties GetMipLevelProperties(const TextureDesc& TexDesc, Uint32 MipLevel);
Uint32 GetStagingDataOffset(const TextureDesc& TexDesc, Uint32 ArraySlice, Uint32 MipLevel);
/// Base implementation of the Diligent::ITextureVk interface
diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp
index ee6331fb..865d4177 100644
--- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp
+++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp
@@ -31,6 +31,7 @@
#include "BufferVkImpl.h"
#include "VulkanTypeConversions.h"
#include "CommandListVkImpl.h"
+#include "GraphicsAccessories.h"
namespace Diligent
{
@@ -1331,12 +1332,10 @@ namespace Diligent
Box FullMipBox;
if (pSrcBox == nullptr)
{
- FullMipBox.MaxX = std::max(DstTexDesc.Width >> CopyAttribs.SrcMipLevel, 1u);
- FullMipBox.MaxY = std::max(DstTexDesc.Height >> CopyAttribs.SrcMipLevel, 1u);
- if(DstTexDesc.Type == RESOURCE_DIM_TEX_3D)
- FullMipBox.MaxZ = std::max(DstTexDesc.Depth >> CopyAttribs.SrcMipLevel, 1u);
- else
- FullMipBox.MaxZ = 1;
+ auto MipLevelAttribs = GetMipLevelProperties(SrcTexDesc, CopyAttribs.SrcMipLevel);
+ FullMipBox.MaxX = MipLevelAttribs.Width;
+ FullMipBox.MaxY = MipLevelAttribs.Height;
+ FullMipBox.MaxZ = MipLevelAttribs.Depth;
pSrcBox = &FullMipBox;
}
const auto& DstFmtAttribs = GetTextureFormatAttribs(DstTexDesc.Format);
@@ -1714,10 +1713,10 @@ namespace Diligent
Box FullExtentBox;
if (pMapRegion == nullptr)
{
- FullExtentBox.MaxX = std::max(TexDesc.Width >> MipLevel, 1u);
- FullExtentBox.MaxY = std::max(TexDesc.Height >> MipLevel, 1u);
- if (TexDesc.Type == RESOURCE_DIM_TEX_3D)
- FullExtentBox.MaxZ = std::max(TexDesc.Depth >> MipLevel, 1u);
+ auto MipLevelAttribs = GetMipLevelProperties(TexDesc, MipLevel);
+ FullExtentBox.MaxX = MipLevelAttribs.Width;
+ FullExtentBox.MaxY = MipLevelAttribs.Height;
+ FullExtentBox.MaxZ = MipLevelAttribs.Depth;
pMapRegion = &FullExtentBox;
}
@@ -1774,7 +1773,7 @@ namespace Diligent
MappedData.pData = TextureVk.GetStagingDataCPUAddress() + MapStartOffset;
MappedData.Stride = MipLevelAttribs.RowSize;
- MappedData.DepthStride = MipLevelAttribs.RowSize * MipLevelAttribs.Height;
+ MappedData.DepthStride = MipLevelAttribs.DepthSliceSize;
if (MapType == MAP_READ)
{
diff --git a/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp
index c6f336ee..dfed36b9 100644
--- a/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp
+++ b/Graphics/GraphicsEngineVulkan/src/TextureVkImpl.cpp
@@ -30,37 +30,11 @@
#include "VulkanTypeConversions.h"
#include "EngineMemory.h"
#include "StringTools.h"
+#include "GraphicsAccessories.h"
namespace Diligent
{
-MipLevelProperties GetMipLevelProperties(const TextureDesc& TexDesc, Uint32 MipLevel)
-{
- MipLevelProperties MipProps;
- const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format);
-
- MipProps.Width = std::max(TexDesc.Width >> MipLevel, 1u);
- MipProps.Height = std::max(TexDesc.Height >> MipLevel, 1u);
- MipProps.Depth = (TexDesc.Type == RESOURCE_DIM_TEX_3D) ? std::max(TexDesc.Depth >> MipLevel, 1u) : 1u;
- if (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED)
- {
- VERIFY_EXPR(FmtAttribs.BlockWidth > 1 && FmtAttribs.BlockHeight > 1);
- VERIFY((FmtAttribs.BlockWidth & (FmtAttribs.BlockWidth-1)) == 0, "Compressed block widht is expected to be power of 2");
- VERIFY((FmtAttribs.BlockHeight & (FmtAttribs.BlockHeight-1)) == 0, "Compressed block widht is expected to be power of 2");
- // For block-compression formats, all parameters are still specified in texels rather than compressed texel blocks (18.4.1)
- MipProps.Width = Align(MipProps.Width, Uint32{FmtAttribs.BlockWidth});
- MipProps.Height = Align(MipProps.Height,Uint32{FmtAttribs.BlockHeight});
- MipProps.RowSize = MipProps.Width / Uint32{FmtAttribs.BlockWidth} * Uint32{FmtAttribs.ComponentSize}; // ComponentSize is the block size
- }
- else
- {
- MipProps.RowSize = MipProps.Width * Uint32{FmtAttribs.ComponentSize} * Uint32{FmtAttribs.NumComponents};
- }
- MipProps.MipSize = MipProps.RowSize * MipProps.Height * MipProps.Depth;
-
- return MipProps;
-}
-
TextureVkImpl :: TextureVkImpl(IReferenceCounters* pRefCounters,
FixedBlockMemoryAllocator& TexViewObjAllocator,
RenderDeviceVkImpl* pRenderDeviceVk,