Implement MSAA to Array colour copies. Closes #1023

This commit is contained in:
baldurk
2018-10-05 14:09:30 +01:00
parent b6894185a9
commit 159a67b853
14 changed files with 1041 additions and 104 deletions
+4 -4
View File
@@ -172,7 +172,7 @@ float RENDERDOC_DepthCopyMSToArray(float4 pos : SV_Position) : SV_Depth
return 0;
}
Texture2DArray<uint4> sourceArray : register(t0);
Texture2DArray<uint4> sourceArray : register(t1);
uint4 RENDERDOC_CopyArrayToMS(float4 pos : SV_Position, uint curSample : SV_SampleIndex) : SV_Target0
{
@@ -181,7 +181,7 @@ uint4 RENDERDOC_CopyArrayToMS(float4 pos : SV_Position, uint curSample : SV_Samp
return sourceArray.Load(srcCoord);
}
Texture2DArray<float4> sourceFloatArray : register(t0);
Texture2DArray<float4> sourceFloatArray : register(t1);
float4 RENDERDOC_FloatCopyArrayToMS(float4 pos : SV_Position, uint curSample : SV_SampleIndex) : SV_Target0
{
@@ -190,8 +190,8 @@ float4 RENDERDOC_FloatCopyArrayToMS(float4 pos : SV_Position, uint curSample : S
return sourceFloatArray.Load(srcCoord);
}
Texture2DArray<float2> sourceDepthArray : register(t0);
Texture2DArray<uint2> sourceStencilArray : register(t1);
Texture2DArray<float2> sourceDepthArray : register(t1);
Texture2DArray<uint2> sourceStencilArray : register(t11);
float RENDERDOC_DepthCopyArrayToMS(float4 pos : SV_Position, uint curSample : SV_SampleIndex) : SV_Depth
{
@@ -56,8 +56,8 @@ struct Tex2DMSToArrayStateTracker
// only need to save/restore constant buffer 0
PS.ConstantBuffers[0] = UNWRAP(WrappedID3D11Buffer, PS.ConstantBuffers[0]);
// same for the first 8 SRVs
for(int i = 0; i < 8; i++)
// same for the first 16 SRVs
for(int i = 0; i < 16; i++)
PS.SRVs[i] = UNWRAP(WrappedID3D11ShaderResourceView1, PS.SRVs[i]);
for(int i = 0; i < D3D11_SHADER_MAX_INTERFACES; i++)
@@ -88,7 +88,7 @@ struct Tex2DMSToArrayStateTracker
context->IASetInputLayout(Layout);
context->VSSetShader((ID3D11VertexShader *)VS.Shader, VS.Instances, VS.NumInstances);
context->PSSetShaderResources(0, 8, PS.SRVs);
context->PSSetShaderResources(0, 16, PS.SRVs);
context->PSSetShader((ID3D11PixelShader *)PS.Shader, PS.Instances, PS.NumInstances);
if(m_WrappedContext->IsFL11_1())
@@ -369,7 +369,7 @@ void D3D11DebugManager::CopyArrayToTex2DMS(ID3D11Texture2D *destMS, ID3D11Textur
ID3D11ShaderResourceView *srvs[8] = {NULL};
srvs[0] = srvArray;
m_pImmediateContext->PSSetShaderResources(0, 8, srvs);
m_pImmediateContext->PSSetShaderResources(1, 8, srvs);
// loop over every array slice in MS texture
for(UINT slice = 0; slice < descMS.ArraySize; slice++)
@@ -428,7 +428,7 @@ void D3D11DebugManager::CopyArrayToTex2DMS(ID3D11Texture2D *destMS, ID3D11Textur
return;
}
m_pImmediateContext->PSSetShaderResources(1, 1, &srvArray);
m_pImmediateContext->PSSetShaderResources(11, 1, &srvArray);
D3D11_DEPTH_STENCIL_DESC dsDesc;
ID3D11DepthStencilState *dsState = NULL;
+7 -1
View File
@@ -158,7 +158,13 @@ bool D3D12InitParams::IsSupportedVersion(uint64_t ver)
if(ver == CurrentVersion)
return true;
// see header for explanation of version changes
// 0x5 -> 0x6 - Multiply by number of planes in format when serialising initial states -
// i.e. stencil is saved with depth in initial states.
if(ver == 0x4)
return true;
// 0x4 -> 0x5 - CPU_DESCRIPTOR_HANDLE serialised inline as D3D12Descriptor in appropriate
// list-recording functions
if(ver == 0x4)
return true;
+88 -2
View File
@@ -104,6 +104,8 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
m_pDevice = wrapper;
m_pDevice->InternalRef();
D3D12ResourceManager *rm = wrapper->GetResourceManager();
HRESULT hr = S_OK;
D3D12_DESCRIPTOR_HEAP_DESC desc;
@@ -112,6 +114,8 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
desc.NumDescriptors = 1024;
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
RDCCOMPILE_ASSERT(FIRST_WIN_RTV + 256 < 1024, "Increase size of RTV heap");
hr = m_pDevice->CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap), (void **)&rtvHeap);
if(FAILED(hr))
@@ -119,9 +123,13 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
RDCERR("Couldn't create RTV descriptor heap! HRESULT: %s", ToStr(hr).c_str());
}
desc.NumDescriptors = 16;
rm->SetInternalResource(rtvHeap);
desc.NumDescriptors = 64;
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
RDCCOMPILE_ASSERT(FIRST_WIN_DSV + 32 < 64, "Increase size of DSV heap");
hr = m_pDevice->CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap), (void **)&dsvHeap);
if(FAILED(hr))
@@ -129,9 +137,13 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
RDCERR("Couldn't create DSV descriptor heap! HRESULT: %s", ToStr(hr).c_str());
}
rm->SetInternalResource(dsvHeap);
desc.NumDescriptors = 4096;
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
RDCCOMPILE_ASSERT(MAX_SRV_SLOT < 4096, "Increase size of CBV/SRV/UAV heap");
hr = m_pDevice->CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap), (void **)&uavClearHeap);
if(FAILED(hr))
@@ -139,6 +151,8 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
RDCERR("Couldn't create CBV/SRV descriptor heap! HRESULT: %s", ToStr(hr).c_str());
}
rm->SetInternalResource(uavClearHeap);
desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
hr = m_pDevice->CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap),
@@ -149,6 +163,8 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
RDCERR("Couldn't create CBV/SRV descriptor heap! HRESULT: %s", ToStr(hr).c_str());
}
rm->SetInternalResource(cbvsrvuavHeap);
desc.NumDescriptors = 16;
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
@@ -159,6 +175,8 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
RDCERR("Couldn't create sampler descriptor heap! HRESULT: %s", ToStr(hr).c_str());
}
rm->SetInternalResource(samplerHeap);
// create fixed samplers, point and linear
D3D12_CPU_DESCRIPTOR_HANDLE samp;
samp = samplerHeap->GetCPUDescriptorHandleForHeapStart();
@@ -207,6 +225,28 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
__uuidof(ID3D12RootSignature), (void **)&m_CBOnlyRootSig);
SAFE_RELEASE(root);
rm->SetInternalResource(m_CBOnlyRootSig);
}
{
ID3DBlob *root = shaderCache->MakeRootSig({
// cbuffer
cbvParam(D3D12_SHADER_VISIBILITY_PIXEL, 0, 0),
// normal SRVs (2x, 4x, 8x, 16x, 32x)
tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 1, 5),
// stencil SRVs (2x, 4x, 8x, 16x, 32x)
tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 11, 5),
});
RDCASSERT(root);
hr = m_pDevice->CreateRootSignature(0, root->GetBufferPointer(), root->GetBufferSize(),
__uuidof(ID3D12RootSignature), (void **)&m_ArrayMSAARootSig);
SAFE_RELEASE(root);
rm->SetInternalResource(m_ArrayMSAARootSig);
}
{
@@ -220,6 +260,33 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
"ps_5_0", &m_MeshPS);
}
{
std::string displayhlsl = GetEmbeddedResource(debugcbuffers_h);
displayhlsl += GetEmbeddedResource(debugcommon_hlsl);
displayhlsl += GetEmbeddedResource(debugdisplay_hlsl);
shaderCache->GetShaderBlob(displayhlsl.c_str(), "RENDERDOC_FullscreenVS",
D3DCOMPILE_WARNINGS_ARE_ERRORS, "vs_5_0", &m_FullscreenVS);
}
{
std::string multisamplehlsl = GetEmbeddedResource(multisample_hlsl);
shaderCache->GetShaderBlob(multisamplehlsl.c_str(), "RENDERDOC_CopyMSToArray",
D3DCOMPILE_WARNINGS_ARE_ERRORS, "ps_5_0", &m_IntMS2Array);
shaderCache->GetShaderBlob(multisamplehlsl.c_str(), "RENDERDOC_FloatCopyMSToArray",
D3DCOMPILE_WARNINGS_ARE_ERRORS, "ps_5_0", &m_FloatMS2Array);
shaderCache->GetShaderBlob(multisamplehlsl.c_str(), "RENDERDOC_DepthCopyMSToArray",
D3DCOMPILE_WARNINGS_ARE_ERRORS, "ps_5_0", &m_DepthMS2Array);
shaderCache->GetShaderBlob(multisamplehlsl.c_str(), "RENDERDOC_CopyArrayToMS",
D3DCOMPILE_WARNINGS_ARE_ERRORS, "ps_5_0", &m_IntArray2MS);
shaderCache->GetShaderBlob(multisamplehlsl.c_str(), "RENDERDOC_FloatCopyArrayToMS",
D3DCOMPILE_WARNINGS_ARE_ERRORS, "ps_5_0", &m_FloatArray2MS);
shaderCache->GetShaderBlob(multisamplehlsl.c_str(), "RENDERDOC_DepthCopyArrayToMS",
D3DCOMPILE_WARNINGS_ARE_ERRORS, "ps_5_0", &m_DepthArray2MS);
}
shaderCache->SetCaching(false);
D3D12_RESOURCE_DESC readbackDesc;
@@ -248,6 +315,8 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
m_ReadbackBuffer->SetName(L"m_ReadbackBuffer");
rm->SetInternalResource(m_ReadbackBuffer);
hr = m_pDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
__uuidof(ID3D12CommandAllocator), (void **)&m_DebugAlloc);
@@ -257,6 +326,8 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
return;
}
rm->SetInternalResource(m_DebugAlloc);
ID3D12GraphicsCommandList *list = NULL;
hr = m_pDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_DebugAlloc, NULL,
@@ -271,6 +342,8 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper)
return;
}
rm->SetInternalResource(m_DebugList);
if(m_DebugList)
m_DebugList->Close();
}
@@ -287,10 +360,16 @@ D3D12DebugManager::~D3D12DebugManager()
SAFE_RELEASE(uavClearHeap);
SAFE_RELEASE(samplerHeap);
SAFE_RELEASE(m_CBOnlyRootSig);
SAFE_RELEASE(m_ArrayMSAARootSig);
SAFE_RELEASE(m_RingConstantBuffer);
SAFE_RELEASE(m_TexResource);
SAFE_RELEASE(m_DebugAlloc);
SAFE_RELEASE(m_DebugList);
m_pDevice->InternalRelease();
if(RenderDoc::Inst().GetCrashHandler())
@@ -361,7 +440,9 @@ D3D12_GPU_VIRTUAL_ADDRESS D3D12DebugManager::UploadConstants(const void *data, s
ret += m_RingConstantOffset;
FillBuffer(m_RingConstantBuffer, (size_t)m_RingConstantOffset, data, size);
// passing the unwrapped object here is immaterial as all we do is Map/Unmap, but it means we can
// call this function while capturing without worrying about serialising the map or deadlocking.
FillBuffer(Unwrap(m_RingConstantBuffer), (size_t)m_RingConstantOffset, data, size);
m_RingConstantOffset += size;
m_RingConstantOffset =
@@ -1201,6 +1282,11 @@ void D3D12Replay::TextureRendering::Init(WrappedID3D12Device *device, D3D12Debug
hr = device->CreateRootSignature(0, root->GetBufferPointer(), root->GetBufferSize(),
__uuidof(ID3D12RootSignature), (void **)&RootSig);
if(FAILED(hr))
{
RDCERR("Couldn't create tex display RootSig! HRESULT: %s", ToStr(hr).c_str());
}
SAFE_RELEASE(root);
}
+30
View File
@@ -56,6 +56,19 @@ enum CBVUAVSRVSlot
PICK_RESULT_CLEAR_UAV,
TMP_UAV,
MSAA_SRV2x,
MSAA_SRV4x,
MSAA_SRV8x,
MSAA_SRV16x,
MSAA_SRV32x,
STENCIL_MSAA_SRV2x,
STENCIL_MSAA_SRV4x,
STENCIL_MSAA_SRV8x,
STENCIL_MSAA_SRV16x,
STENCIL_MSAA_SRV32x,
MAX_SRV_SLOT,
};
enum RTVSlot
@@ -64,6 +77,7 @@ enum RTVSlot
CUSTOM_SHADER_RTV,
OVERLAY_RTV,
GET_TEX_RTV,
MSAA_RTV,
FIRST_TMP_RTV,
LAST_TMP_RTV = FIRST_TMP_RTV + 16,
FIRST_WIN_RTV,
@@ -79,6 +93,7 @@ enum SamplerSlot
enum DSVSlot
{
OVERLAY_DSV,
MSAA_DSV,
TMP_DSV,
FIRST_WIN_DSV,
};
@@ -139,6 +154,9 @@ public:
MeshDisplayPipelines CacheMeshDisplayPipelines(const MeshFormat &primary,
const MeshFormat &secondary);
void CopyTex2DMSToArray(ID3D12Resource *destArray, ID3D12Resource *srcMS);
void CopyArrayToTex2DMS(ID3D12Resource *destMS, ID3D12Resource *srcArray, UINT selectedSlice);
private:
WrappedID3D12Device *m_pDevice = NULL;
@@ -168,6 +186,18 @@ private:
ID3D12Resource *m_ReadbackBuffer = NULL;
// Array <-> MSAA copying
ID3D12RootSignature *m_ArrayMSAARootSig = NULL;
ID3DBlob *m_FullscreenVS = NULL;
ID3DBlob *m_IntMS2Array = NULL;
ID3DBlob *m_FloatMS2Array = NULL;
ID3DBlob *m_DepthMS2Array = NULL;
ID3DBlob *m_IntArray2MS = NULL;
ID3DBlob *m_FloatArray2MS = NULL;
ID3DBlob *m_DepthArray2MS = NULL;
// Debug lists
ID3D12GraphicsCommandList2 *m_DebugList = NULL;
ID3D12CommandAllocator *m_DebugAlloc = NULL;
+1 -2
View File
@@ -2295,8 +2295,7 @@ void WrappedID3D12Device::CreateInternalResources()
if(m_TextRenderer == NULL)
m_TextRenderer = new D3D12TextRenderer(this);
if(IsReplayMode(m_State))
m_Replay.CreateResources();
m_Replay.CreateResources();
WrappedID3D12Shader::InternalResources(false);
}
+3 -4
View File
@@ -46,10 +46,7 @@ struct D3D12InitParams
D3D_FEATURE_LEVEL MinimumFeatureLevel;
// check if a frame capture section version is supported
static const uint64_t CurrentVersion = 0x5;
// 0x4 -> 0x5 - CPU_DESCRIPTOR_HANDLE serialised inline as D3D12Descriptor in appropriate
// list-recording functions
static const uint64_t CurrentVersion = 0x6;
static bool IsSupportedVersion(uint64_t ver);
};
@@ -61,6 +58,7 @@ class WrappedID3D12Resource;
class D3D12TextRenderer;
class D3D12ShaderCache;
class D3D12DebugManager;
// give every impression of working but do nothing.
// Just allow the user to call functions so that they don't
@@ -405,6 +403,7 @@ public:
static std::string GetChunkName(uint32_t idx);
D3D12ResourceManager *GetResourceManager() { return m_ResourceManager; }
D3D12ShaderCache *GetShaderCache() { return m_ShaderCache; }
D3D12DebugManager *GetDebugManager() { return m_Replay.GetDebugManager(); }
ResourceId GetResourceID() { return m_ResourceID; }
Threading::RWLock &GetCapTransitionLock() { return m_CapTransitionLock; }
void ReleaseSwapchainResources(IDXGISwapChain *swap, IUnknown **backbuffers, int numBackbuffers);
+286 -78
View File
@@ -25,6 +25,7 @@
#include "driver/dxgi/dxgi_common.h"
#include "d3d12_command_list.h"
#include "d3d12_command_queue.h"
#include "d3d12_debug.h"
#include "d3d12_device.h"
#include "d3d12_manager.h"
#include "d3d12_resources.h"
@@ -67,14 +68,7 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res)
D3D12_RESOURCE_DESC desc = r->GetDesc();
if(desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && desc.SampleDesc.Count > 1)
{
D3D12NOTIMP("Multisampled initial contents");
SetInitialContents(GetResID(r), D3D12InitialContents(D3D12InitialContents::Multisampled));
return true;
}
else if(desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER)
if(desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER)
{
D3D12_HEAP_PROPERTIES heapProps;
r->GetHeapProperties(&heapProps, NULL);
@@ -128,6 +122,99 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res)
}
else
{
if(nonresident)
m_Device->MakeResident(1, &pageable);
ID3D12Resource *arrayTexture = NULL;
D3D12_RESOURCE_STATES destState = D3D12_RESOURCE_STATE_COPY_SOURCE;
ID3D12Resource *unwrappedCopySource = r->GetReal();
if(desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && desc.SampleDesc.Count > 1)
{
bool isDepth = IsDepthFormat(desc.Format);
desc.Alignment = 0;
desc.DepthOrArraySize *= (UINT16)desc.SampleDesc.Count;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
if(isDepth)
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
else
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
D3D12_HEAP_PROPERTIES defaultHeap;
defaultHeap.Type = D3D12_HEAP_TYPE_DEFAULT;
defaultHeap.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
defaultHeap.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
defaultHeap.CreationNodeMask = 1;
defaultHeap.VisibleNodeMask = 1;
// we don't want to serialise this resource's creation, so wrap it manually
HRESULT hr = m_Device->GetReal()->CreateCommittedResource(
&defaultHeap, D3D12_HEAP_FLAG_NONE, &desc,
isDepth ? D3D12_RESOURCE_STATE_DEPTH_WRITE : D3D12_RESOURCE_STATE_RENDER_TARGET, NULL,
__uuidof(ID3D12Resource), (void **)&arrayTexture);
RDCASSERTEQUAL(hr, S_OK);
destState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
}
ID3D12GraphicsCommandList *list = Unwrap(m_Device->GetInitialStateList());
vector<D3D12_RESOURCE_BARRIER> barriers;
{
const vector<D3D12_RESOURCE_STATES> &states = m_Device->GetSubresourceStates(GetResID(r));
barriers.reserve(states.size());
for(size_t i = 0; i < states.size(); i++)
{
if(states[i] & D3D12_RESOURCE_STATE_COPY_SOURCE)
continue;
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = r->GetReal();
barrier.Transition.Subresource = (UINT)i;
barrier.Transition.StateBefore = states[i];
barrier.Transition.StateAfter = destState;
barriers.push_back(barrier);
}
// transition to copy dest
if(!barriers.empty())
list->ResourceBarrier((UINT)barriers.size(), &barriers[0]);
}
if(arrayTexture)
{
// execute the above barriers
m_Device->CloseInitialStateList();
m_Device->ExecuteLists(NULL, true);
m_Device->FlushLists();
// expand multisamples out to array
m_Device->GetDebugManager()->CopyTex2DMSToArray(arrayTexture, r->GetReal());
// open the initial state list again for the remainder of the work
list = Unwrap(m_Device->GetInitialStateList());
D3D12_RESOURCE_BARRIER b = {};
b.Transition.pResource = arrayTexture;
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
b.Transition.StateBefore = IsDepthFormat(desc.Format) ? D3D12_RESOURCE_STATE_DEPTH_WRITE
: D3D12_RESOURCE_STATE_RENDER_TARGET;
b.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
list->ResourceBarrier(1, &b);
unwrappedCopySource = arrayTexture;
}
D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_READBACK;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
@@ -153,6 +240,17 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res)
if(desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE3D)
numSubresources *= desc.DepthOrArraySize;
// account for multiple planes (i.e. depth and stencil)
{
D3D12_FEATURE_DATA_FORMAT_INFO formatInfo = {};
formatInfo.Format = desc.Format;
m_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_INFO, &formatInfo, sizeof(formatInfo));
UINT planes = RDCMAX((UINT8)1, formatInfo.PlaneCount);
numSubresources *= planes;
}
D3D12_PLACED_SUBRESOURCE_FOOTPRINT *layouts =
new D3D12_PLACED_SUBRESOURCE_FOOTPRINT[numSubresources];
@@ -164,45 +262,14 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res)
&heapProps, D3D12_HEAP_FLAG_NONE, &bufDesc, D3D12_RESOURCE_STATE_COPY_DEST, NULL,
__uuidof(ID3D12Resource), (void **)&copyDst);
if(nonresident)
m_Device->MakeResident(1, &pageable);
if(SUCCEEDED(hr))
{
ID3D12GraphicsCommandList *list = Unwrap(m_Device->GetInitialStateList());
vector<D3D12_RESOURCE_BARRIER> barriers;
const vector<D3D12_RESOURCE_STATES> &states = m_Device->GetSubresourceStates(GetResID(r));
barriers.reserve(states.size());
for(size_t i = 0; i < states.size(); i++)
{
if(states[i] & D3D12_RESOURCE_STATE_COPY_SOURCE)
continue;
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = r->GetReal();
barrier.Transition.Subresource = (UINT)i;
barrier.Transition.StateBefore = states[i];
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
barriers.push_back(barrier);
}
// transition to copy dest
if(!barriers.empty())
list->ResourceBarrier((UINT)barriers.size(), &barriers[0]);
for(UINT i = 0; i < numSubresources; i++)
{
D3D12_TEXTURE_COPY_LOCATION dst, src;
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
src.pResource = r->GetReal();
src.pResource = unwrappedCopySource;
src.SubresourceIndex = i;
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
@@ -211,29 +278,31 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res)
list->CopyTextureRegion(&dst, 0, 0, 0, &src, NULL);
}
// transition back
for(size_t i = 0; i < barriers.size(); i++)
std::swap(barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter);
if(!barriers.empty())
list->ResourceBarrier((UINT)barriers.size(), &barriers[0]);
}
else
{
RDCERR("Couldn't create readback buffer: HRESULT: %s", ToStr(hr).c_str());
}
if(nonresident)
// transition back
for(size_t i = 0; i < barriers.size(); i++)
std::swap(barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter);
if(!barriers.empty())
list->ResourceBarrier((UINT)barriers.size(), &barriers[0]);
if(nonresident || arrayTexture)
{
m_Device->CloseInitialStateList();
m_Device->ExecuteLists(NULL, true);
m_Device->FlushLists();
m_Device->Evict(1, &pageable);
if(nonresident)
m_Device->Evict(1, &pageable);
}
SAFE_RELEASE(arrayTexture);
SAFE_DELETE_ARRAY(layouts);
SetInitialContents(GetResID(r), D3D12InitialContents(copyDst));
@@ -269,11 +338,6 @@ uint32_t D3D12ResourceManager::GetSize_InitialState(ResourceId id, ID3D12DeviceC
{
buf = (ID3D12Resource *)res;
}
else if(initContents.tag == D3D12InitialContents::Multisampled)
{
D3D12NOTIMP("Multisampled initial contents");
buf = NULL;
}
return (uint32_t)WriteSerialiser::GetChunkAlignment() + 16 +
uint32_t(buf ? buf->GetDesc().Width : 0);
@@ -383,11 +447,6 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI
{
mappedBuffer = (ID3D12Resource *)liveRes;
}
else if(initContents.tag == D3D12InitialContents::Multisampled)
{
D3D12NOTIMP("Multisampled initial contents");
mappedBuffer = NULL;
}
if(mappedBuffer)
{
@@ -417,12 +476,7 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI
D3D12_HEAP_PROPERTIES heapProps = {};
((ID3D12Resource *)liveRes)->GetHeapProperties(&heapProps, NULL);
if(resDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && resDesc.SampleDesc.Count > 1)
{
initContents.tag = D3D12InitialContents::Multisampled;
D3D12NOTIMP("Multisampled initial contents");
}
else if(heapProps.Type == D3D12_HEAP_TYPE_UPLOAD)
if(heapProps.Type == D3D12_HEAP_TYPE_UPLOAD)
{
// if destination is on the upload heap, it's impossible to copy via the device,
// so we have to CPU copy. To save time and make a more optimal copy, we just keep the data
@@ -457,9 +511,9 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI
desc.Width = RDCMAX(ContentsLength, 64ULL);
ID3D12Resource *copySrc = NULL;
HRESULT hr = m_Device->GetReal()->CreateCommittedResource(
&heapProps, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL,
__uuidof(ID3D12Resource), (void **)&copySrc);
HRESULT hr = m_Device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &desc,
D3D12_RESOURCE_STATE_GENERIC_READ, NULL,
__uuidof(ID3D12Resource), (void **)&copySrc);
if(SUCCEEDED(hr))
{
@@ -506,10 +560,149 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI
{
initContents.resourceType = Resource_Resource;
initContents.resource = mappedBuffer;
SetInitialContents(id, initContents);
}
return true;
D3D12_RESOURCE_DESC resDesc = ((ID3D12Resource *)liveRes)->GetDesc();
// for MSAA textures we upload to an MSAA texture here so we're ready to copy the image in
// Apply_InitState
if(resDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && resDesc.SampleDesc.Count > 1)
{
if(ContentsLength == 0)
{
// backwards compatibility - older captures will have no data for MSAA textures.
initContents.resource = NULL;
SAFE_RELEASE(mappedBuffer);
}
else
{
D3D12_HEAP_PROPERTIES heapProps = {};
((ID3D12Resource *)liveRes)->GetHeapProperties(&heapProps, NULL);
ID3D12GraphicsCommandList *list = Unwrap(m_Device->GetInitialStateList());
D3D12_RESOURCE_DESC arrayDesc = resDesc;
arrayDesc.Alignment = 0;
arrayDesc.DepthOrArraySize *= (UINT16)arrayDesc.SampleDesc.Count;
arrayDesc.SampleDesc.Count = 1;
arrayDesc.SampleDesc.Quality = 0;
arrayDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
bool isDepth = IsDepthFormat(resDesc.Format);
D3D12_RESOURCE_DESC msaaDesc = resDesc;
arrayDesc.Alignment = 0;
arrayDesc.Flags = isDepth ? D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
ID3D12Resource *arrayTex = NULL;
HRESULT hr = m_Device->CreateCommittedResource(
&heapProps, D3D12_HEAP_FLAG_NONE, &arrayDesc, D3D12_RESOURCE_STATE_COPY_DEST, NULL,
__uuidof(ID3D12Resource), (void **)&arrayTex);
if(FAILED(hr))
{
RDCERR("Couldn't create temporary array texture: %s", ToStr(hr).c_str());
ret = false;
}
ID3D12Resource *msaaTex = NULL;
hr = m_Device->CreateCommittedResource(
&heapProps, D3D12_HEAP_FLAG_NONE, &msaaDesc,
isDepth ? D3D12_RESOURCE_STATE_DEPTH_WRITE : D3D12_RESOURCE_STATE_RENDER_TARGET, NULL,
__uuidof(ID3D12Resource), (void **)&msaaTex);
RDCASSERTEQUAL(hr, S_OK);
if(FAILED(hr))
{
RDCERR("Couldn't create init state MSAA texture: %s", ToStr(hr).c_str());
ret = false;
}
// copy buffer to array texture
if(arrayTex)
{
uint32_t numSubresources = arrayDesc.DepthOrArraySize;
{
D3D12_FEATURE_DATA_FORMAT_INFO formatInfo = {};
formatInfo.Format = arrayDesc.Format;
m_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_INFO, &formatInfo,
sizeof(formatInfo));
UINT planes = RDCMAX((UINT8)1, formatInfo.PlaneCount);
numSubresources *= planes;
}
D3D12_PLACED_SUBRESOURCE_FOOTPRINT *layouts =
new D3D12_PLACED_SUBRESOURCE_FOOTPRINT[numSubresources];
m_Device->GetCopyableFootprints(&arrayDesc, 0, numSubresources, 0, layouts, NULL, NULL,
NULL);
for(UINT i = 0; i < numSubresources; i++)
{
D3D12_TEXTURE_COPY_LOCATION dst, src;
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.pResource = Unwrap(arrayTex);
dst.SubresourceIndex = i;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.pResource = Unwrap(mappedBuffer);
src.PlacedFootprint = layouts[i];
// copy buffer into this array slice
list->CopyTextureRegion(&dst, 0, 0, 0, &src, NULL);
// this slice now needs to be in shader-read to copy to the MSAA texture
D3D12_RESOURCE_BARRIER b = {};
b.Transition.pResource = Unwrap(arrayTex);
b.Transition.Subresource = i;
b.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
b.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
list->ResourceBarrier(1, &b);
}
delete[] layouts;
}
m_Device->CloseInitialStateList();
m_Device->ExecuteLists(NULL, true);
m_Device->FlushLists(true);
// compact array into MSAA texture
if(msaaTex && arrayTex)
m_Device->GetDebugManager()->CopyArrayToTex2DMS(msaaTex, arrayTex, ~0U);
// move MSAA texture permanently to copy source state
if(msaaTex)
{
list = Unwrap(m_Device->GetInitialStateList());
D3D12_RESOURCE_BARRIER b = {};
b.Transition.pResource = Unwrap(msaaTex);
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
b.Transition.StateBefore =
isDepth ? D3D12_RESOURCE_STATE_DEPTH_WRITE : D3D12_RESOURCE_STATE_RENDER_TARGET;
b.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
list->ResourceBarrier(1, &b);
m_Device->CloseInitialStateList();
m_Device->ExecuteLists(NULL, true);
m_Device->FlushLists(true);
}
// subsequent copy comes from msaa texture
initContents.resource = msaaTex;
// we can release the buffer now, and the temporary array texture
SAFE_RELEASE(mappedBuffer);
SAFE_RELEASE(arrayTex);
}
}
if(initContents.resource)
SetInitialContents(id, initContents);
}
}
else
{
@@ -566,11 +759,7 @@ void D3D12ResourceManager::Apply_InitialState(ID3D12DeviceChild *live, D3D12Init
}
else if(type == Resource_Resource)
{
if(data.tag == D3D12InitialContents::Multisampled)
{
D3D12NOTIMP("Multisampled initial contents");
}
else if(data.tag == D3D12InitialContents::Copy)
if(data.tag == D3D12InitialContents::Copy)
{
ID3D12Resource *copyDst = Unwrap((ID3D12Resource *)live);
@@ -709,7 +898,13 @@ void D3D12ResourceManager::Apply_InitialState(ID3D12DeviceChild *live, D3D12Init
D3D12_RESOURCE_DESC srcDesc = copySrc->GetDesc();
D3D12_RESOURCE_DESC dstDesc = copyDst->GetDesc();
list->CopyBufferRegion(copyDst, 0, copySrc, 0, RDCMIN(srcDesc.Width, dstDesc.Width));
list->CopyBufferRegion(copyDst, 0, Unwrap(copySrc), 0,
RDCMIN(srcDesc.Width, dstDesc.Width));
}
else if(copyDst->GetDesc().SampleDesc.Count > 1)
{
// MSAA texture was pre-uploaded and decoded, just copy the texture
list->CopyResource(copyDst, Unwrap(copySrc));
}
else
{
@@ -719,6 +914,19 @@ void D3D12ResourceManager::Apply_InitialState(ID3D12DeviceChild *live, D3D12Init
if(desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE3D)
numSubresources *= desc.DepthOrArraySize;
// we only accounted for planes in version 0x6, before then we only copied the first plane
// so the buffer won't have enough data
if(m_Device->GetLogVersion() >= 0x6)
{
D3D12_FEATURE_DATA_FORMAT_INFO formatInfo = {};
formatInfo.Format = desc.Format;
m_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_INFO, &formatInfo, sizeof(formatInfo));
UINT planes = RDCMAX((UINT8)1, formatInfo.PlaneCount);
numSubresources *= planes;
}
D3D12_PLACED_SUBRESOURCE_FOOTPRINT *layouts =
new D3D12_PLACED_SUBRESOURCE_FOOTPRINT[numSubresources];
@@ -733,7 +941,7 @@ void D3D12ResourceManager::Apply_InitialState(ID3D12DeviceChild *live, D3D12Init
dst.SubresourceIndex = i;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.pResource = copySrc;
src.pResource = Unwrap(copySrc);
src.PlacedFootprint = layouts[i];
list->CopyTextureRegion(&dst, 0, 0, 0, &src, NULL);
+1 -1
View File
@@ -696,7 +696,7 @@ template void D3D12ResourceManager::SerialiseResourceStates(
void D3D12ResourceManager::SetInternalResource(ID3D12DeviceChild *res)
{
if(!RenderDoc::Inst().IsReplayApp())
if(!RenderDoc::Inst().IsReplayApp() && res)
{
D3D12ResourceRecord *record = GetResourceRecord(GetResID(res));
if(record)
-1
View File
@@ -593,7 +593,6 @@ struct D3D12InitialContents
// this is only valid during capture - it indicates we didn't create a staging texture, we're
// going to read directly from the resource (only valid for resources that are already READBACK)
MapDirect,
Multisampled,
};
D3D12InitialContents(D3D12Descriptor *d, uint32_t n)
: tag(Copy),
@@ -0,0 +1,541 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2018 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "driver/dx/official/d3dcompiler.h"
#include "driver/dxgi/dxgi_common.h"
#include "d3d12_command_list.h"
#include "d3d12_command_queue.h"
#include "d3d12_debug.h"
#include "d3d12_device.h"
void D3D12DebugManager::CopyTex2DMSToArray(ID3D12Resource *destArray, ID3D12Resource *srcMS)
{
// this function operates during capture so we work on unwrapped objects
D3D12_RESOURCE_DESC descMS = srcMS->GetDesc();
D3D12_RESOURCE_DESC descArr = destArray->GetDesc();
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY;
srvDesc.Texture2DMSArray.FirstArraySlice = 0;
srvDesc.Texture2DMSArray.ArraySize = descMS.DepthOrArraySize;
srvDesc.Format = GetTypedFormat(descMS.Format, CompType::UInt);
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Format = srvDesc.Format;
rtvDesc.Texture2DArray.ArraySize = 1;
rtvDesc.Texture2DArray.FirstArraySlice = 0;
rtvDesc.Texture2DArray.MipSlice = 0;
rtvDesc.Texture2DArray.PlaneSlice = 0;
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY;
dsvDesc.Format = srvDesc.Format;
dsvDesc.Texture2DArray.ArraySize = 1;
dsvDesc.Texture2DArray.FirstArraySlice = 0;
dsvDesc.Texture2DArray.MipSlice = 0;
bool depthFormat = IsDepthFormat(rtvDesc.Format);
bool intFormat = IsUIntFormat(rtvDesc.Format) || IsIntFormat(rtvDesc.Format);
bool stencil = false;
DXGI_FORMAT stencilFormat = DXGI_FORMAT_UNKNOWN;
if(depthFormat)
{
switch(descMS.Format)
{
case DXGI_FORMAT_D32_FLOAT:
case DXGI_FORMAT_R32_FLOAT:
case DXGI_FORMAT_R32_TYPELESS: srvDesc.Format = DXGI_FORMAT_R32_FLOAT; break;
case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
case DXGI_FORMAT_R32G8X24_TYPELESS:
case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
srvDesc.Format = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
stencilFormat = DXGI_FORMAT_X32_TYPELESS_G8X24_UINT;
stencil = true;
break;
case DXGI_FORMAT_D24_UNORM_S8_UINT:
case DXGI_FORMAT_R24G8_TYPELESS:
case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
stencilFormat = DXGI_FORMAT_X24_TYPELESS_G8_UINT;
stencil = true;
break;
case DXGI_FORMAT_D16_UNORM:
case DXGI_FORMAT_R16_TYPELESS: srvDesc.Format = DXGI_FORMAT_R16_FLOAT; break;
}
}
for(CBVUAVSRVSlot slot : {MSAA_SRV2x, MSAA_SRV4x, MSAA_SRV8x, MSAA_SRV16x, MSAA_SRV32x})
{
D3D12_CPU_DESCRIPTOR_HANDLE srv = Unwrap(GetCPUHandle(slot));
m_pDevice->GetReal()->CreateShaderResourceView(srcMS, &srvDesc, srv);
}
if(stencil)
{
srvDesc.Format = stencilFormat;
for(CBVUAVSRVSlot slot : {STENCIL_MSAA_SRV2x, STENCIL_MSAA_SRV4x, STENCIL_MSAA_SRV8x,
STENCIL_MSAA_SRV16x, STENCIL_MSAA_SRV32x})
{
D3D12_CPU_DESCRIPTOR_HANDLE srv = Unwrap(GetCPUHandle(slot));
m_pDevice->GetReal()->CreateShaderResourceView(srcMS, &srvDesc, srv);
}
}
D3D12_CPU_DESCRIPTOR_HANDLE rtv = Unwrap(GetCPUHandle(MSAA_RTV));
D3D12_CPU_DESCRIPTOR_HANDLE dsv = Unwrap(GetCPUHandle(MSAA_DSV));
D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeDesc = {};
pipeDesc.pRootSignature = Unwrap(m_ArrayMSAARootSig);
pipeDesc.VS.BytecodeLength = m_FullscreenVS->GetBufferSize();
pipeDesc.VS.pShaderBytecode = m_FullscreenVS->GetBufferPointer();
pipeDesc.PS.BytecodeLength = m_FloatMS2Array->GetBufferSize();
pipeDesc.PS.pShaderBytecode = m_FloatMS2Array->GetBufferPointer();
pipeDesc.NumRenderTargets = 1;
pipeDesc.RTVFormats[0] = rtvDesc.Format;
pipeDesc.DSVFormat = DXGI_FORMAT_UNKNOWN;
if(depthFormat)
{
pipeDesc.PS.BytecodeLength = m_DepthMS2Array->GetBufferSize();
pipeDesc.PS.pShaderBytecode = m_DepthMS2Array->GetBufferPointer();
pipeDesc.NumRenderTargets = 0;
pipeDesc.RTVFormats[0] = DXGI_FORMAT_UNKNOWN;
pipeDesc.DSVFormat = rtvDesc.Format;
pipeDesc.DepthStencilState.DepthEnable = TRUE;
pipeDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
pipeDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS;
}
else if(intFormat)
{
pipeDesc.PS.BytecodeLength = m_IntMS2Array->GetBufferSize();
pipeDesc.PS.pShaderBytecode = m_IntMS2Array->GetBufferPointer();
}
pipeDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
pipeDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
pipeDesc.SampleMask = 0xFFFFFFFF;
pipeDesc.SampleDesc.Count = 1;
pipeDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED;
pipeDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
pipeDesc.BlendState.RenderTarget[0].BlendEnable = FALSE;
pipeDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA;
pipeDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
pipeDesc.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;
pipeDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_SRC_ALPHA;
pipeDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
pipeDesc.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;
pipeDesc.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
ID3D12PipelineState *pso = NULL, *psoStencil = NULL;
HRESULT hr = m_pDevice->GetReal()->CreateGraphicsPipelineState(
&pipeDesc, __uuidof(ID3D12PipelineState), (void **)&pso);
if(FAILED(hr))
{
RDCERR("Couldn't create MSAA conversion pipeline! HRESULT: %s", ToStr(hr).c_str());
return;
}
ID3D12GraphicsCommandList *list = Unwrap(m_DebugList);
list->Reset(Unwrap(m_DebugAlloc), NULL);
D3D12_VIEWPORT viewport = {0, 0, (float)descArr.Width, (float)descArr.Height, 0.0f, 1.0f};
D3D12_RECT scissor = {0, 0, (LONG)descArr.Width, (LONG)descArr.Height};
list->RSSetViewports(1, &viewport);
list->RSSetScissorRects(1, &scissor);
list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
ID3D12DescriptorHeap *heap = Unwrap(cbvsrvuavHeap);
list->SetDescriptorHeaps(1, &heap);
list->SetPipelineState(pso);
list->SetGraphicsRootSignature(Unwrap(m_ArrayMSAARootSig));
list->SetGraphicsRootDescriptorTable(1, Unwrap(GetGPUHandle(MSAA_SRV2x)));
if(stencil)
list->SetGraphicsRootDescriptorTable(2, Unwrap(GetGPUHandle(STENCIL_MSAA_SRV2x)));
// loop over every array slice in MS texture
for(UINT slice = 0; slice < descMS.DepthOrArraySize; slice++)
{
// loop over every multi sample
for(UINT sample = 0; sample < descMS.SampleDesc.Count; sample++)
{
uint32_t cdata[4] = {descMS.SampleDesc.Count, 1000, sample, slice};
list->SetGraphicsRootConstantBufferView(0, UploadConstants(cdata, sizeof(cdata)));
dsvDesc.Texture2DArray.FirstArraySlice = slice * descMS.SampleDesc.Count + sample;
rtvDesc.Texture2DArray.FirstArraySlice = slice * descMS.SampleDesc.Count + sample;
if(depthFormat)
{
m_pDevice->GetReal()->CreateDepthStencilView(destArray, &dsvDesc, dsv);
list->OMSetRenderTargets(0, NULL, FALSE, &dsv);
}
else
{
m_pDevice->GetReal()->CreateRenderTargetView(destArray, &rtvDesc, rtv);
list->OMSetRenderTargets(1, &rtv, FALSE, NULL);
}
list->DrawInstanced(3, 1, 0, 0);
}
}
if(stencil)
{
pipeDesc.DepthStencilState.DepthEnable = FALSE;
pipeDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
pipeDesc.DepthStencilState.StencilEnable = TRUE;
pipeDesc.DepthStencilState.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
pipeDesc.DepthStencilState.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE;
pipeDesc.DepthStencilState.FrontFace.StencilFailOp = D3D12_STENCIL_OP_REPLACE;
pipeDesc.DepthStencilState.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_REPLACE;
pipeDesc.DepthStencilState.BackFace = pipeDesc.DepthStencilState.FrontFace;
pipeDesc.DepthStencilState.StencilReadMask = 0xff;
pipeDesc.DepthStencilState.StencilWriteMask = 0xff;
hr = m_pDevice->GetReal()->CreateGraphicsPipelineState(&pipeDesc, __uuidof(ID3D12PipelineState),
(void **)&psoStencil);
RDCASSERTEQUAL(hr, S_OK);
list->SetPipelineState(psoStencil);
dsvDesc.Flags = D3D12_DSV_FLAG_READ_ONLY_DEPTH;
dsvDesc.Texture2DArray.ArraySize = 1;
// loop over every array slice in MS texture
for(UINT slice = 0; slice < descMS.DepthOrArraySize; slice++)
{
// loop over every multi sample
for(UINT sample = 0; sample < descMS.SampleDesc.Count; sample++)
{
dsvDesc.Texture2DArray.FirstArraySlice = slice * descMS.SampleDesc.Count + sample;
m_pDevice->GetReal()->CreateDepthStencilView(destArray, &dsvDesc, dsv);
list->OMSetRenderTargets(0, NULL, FALSE, &dsv);
// loop over every stencil value (in theory we could use SV_StencilRef, but it's optional
// and would mean a different shader)
for(UINT stencilval = 0; stencilval < 256; stencilval++)
{
uint32_t cdata[4] = {descMS.SampleDesc.Count, stencilval, sample, slice};
list->SetGraphicsRootConstantBufferView(0, UploadConstants(cdata, sizeof(cdata)));
list->OMSetStencilRef(stencilval);
list->DrawInstanced(3, 1, 0, 0);
}
}
}
}
list->Close();
ID3D12Fence *tmpFence = NULL;
m_pDevice->GetReal()->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence),
(void **)&tmpFence);
ID3D12CommandList *l = list;
m_pDevice->GetQueue()->GetReal()->ExecuteCommandLists(1, &l);
m_pDevice->GPUSync(m_pDevice->GetQueue()->GetReal(), tmpFence);
m_DebugAlloc->Reset();
SAFE_RELEASE(tmpFence);
SAFE_RELEASE(pso);
SAFE_RELEASE(psoStencil);
}
void D3D12DebugManager::CopyArrayToTex2DMS(ID3D12Resource *destMS, ID3D12Resource *srcArray,
UINT selectedSlice)
{
bool singleSliceMode = (selectedSlice != ~0U);
D3D12_RESOURCE_DESC descArr = srcArray->GetDesc();
D3D12_RESOURCE_DESC descMS = destMS->GetDesc();
UINT sampleMask = ~0U;
if(singleSliceMode)
{
sampleMask = 1U << (selectedSlice % descMS.SampleDesc.Count);
selectedSlice /= descMS.SampleDesc.Count;
}
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
srvDesc.Texture2DArray.MipLevels = 1;
srvDesc.Texture2DArray.ArraySize = descArr.DepthOrArraySize;
srvDesc.Format = GetTypedFormat(descMS.Format, CompType::UInt);
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
rtvDesc.Format = srvDesc.Format;
rtvDesc.Texture2DMSArray.ArraySize = 1;
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
dsvDesc.Format = srvDesc.Format;
dsvDesc.Texture2DMSArray.ArraySize = 1;
bool depthFormat = IsDepthFormat(rtvDesc.Format);
bool intFormat = IsUIntFormat(rtvDesc.Format) || IsIntFormat(rtvDesc.Format);
bool stencil = false;
DXGI_FORMAT stencilFormat = DXGI_FORMAT_UNKNOWN;
if(depthFormat)
{
switch(descMS.Format)
{
case DXGI_FORMAT_D32_FLOAT:
case DXGI_FORMAT_R32_FLOAT:
case DXGI_FORMAT_R32_TYPELESS: srvDesc.Format = DXGI_FORMAT_R32_FLOAT; break;
case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
case DXGI_FORMAT_R32G8X24_TYPELESS:
case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
srvDesc.Format = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
stencilFormat = DXGI_FORMAT_X32_TYPELESS_G8X24_UINT;
stencil = true;
break;
case DXGI_FORMAT_D24_UNORM_S8_UINT:
case DXGI_FORMAT_R24G8_TYPELESS:
case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
stencilFormat = DXGI_FORMAT_X24_TYPELESS_G8_UINT;
stencil = true;
break;
case DXGI_FORMAT_D16_UNORM:
case DXGI_FORMAT_R16_TYPELESS: srvDesc.Format = DXGI_FORMAT_R16_FLOAT; break;
}
}
for(CBVUAVSRVSlot slot : {MSAA_SRV2x, MSAA_SRV4x, MSAA_SRV8x, MSAA_SRV16x, MSAA_SRV32x})
{
D3D12_CPU_DESCRIPTOR_HANDLE srv = GetCPUHandle(slot);
m_pDevice->CreateShaderResourceView(srcArray, &srvDesc, srv);
}
if(stencil)
{
srvDesc.Format = stencilFormat;
{
D3D12_FEATURE_DATA_FORMAT_INFO formatInfo = {};
formatInfo.Format = srvDesc.Format;
m_pDevice->CheckFeatureSupport(D3D12_FEATURE_FORMAT_INFO, &formatInfo, sizeof(formatInfo));
UINT planes = RDCMAX((UINT8)1, formatInfo.PlaneCount);
if(planes > 1)
srvDesc.Texture2DArray.PlaneSlice = 1;
}
for(CBVUAVSRVSlot slot : {STENCIL_MSAA_SRV2x, STENCIL_MSAA_SRV4x, STENCIL_MSAA_SRV8x,
STENCIL_MSAA_SRV16x, STENCIL_MSAA_SRV32x})
{
D3D12_CPU_DESCRIPTOR_HANDLE srv = GetCPUHandle(slot);
m_pDevice->CreateShaderResourceView(srcArray, &srvDesc, srv);
}
}
D3D12_CPU_DESCRIPTOR_HANDLE rtv = GetCPUHandle(MSAA_RTV);
D3D12_CPU_DESCRIPTOR_HANDLE dsv = GetCPUHandle(MSAA_DSV);
D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeDesc = {};
pipeDesc.pRootSignature = m_ArrayMSAARootSig;
pipeDesc.VS.BytecodeLength = m_FullscreenVS->GetBufferSize();
pipeDesc.VS.pShaderBytecode = m_FullscreenVS->GetBufferPointer();
pipeDesc.PS.BytecodeLength = m_FloatArray2MS->GetBufferSize();
pipeDesc.PS.pShaderBytecode = m_FloatArray2MS->GetBufferPointer();
pipeDesc.NumRenderTargets = 1;
pipeDesc.RTVFormats[0] = rtvDesc.Format;
pipeDesc.DSVFormat = DXGI_FORMAT_UNKNOWN;
if(depthFormat)
{
pipeDesc.PS.BytecodeLength = m_DepthArray2MS->GetBufferSize();
pipeDesc.PS.pShaderBytecode = m_DepthArray2MS->GetBufferPointer();
pipeDesc.NumRenderTargets = 0;
pipeDesc.RTVFormats[0] = DXGI_FORMAT_UNKNOWN;
pipeDesc.DSVFormat = rtvDesc.Format;
pipeDesc.DepthStencilState.DepthEnable = TRUE;
pipeDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
pipeDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS;
}
else if(intFormat)
{
pipeDesc.PS.BytecodeLength = m_IntArray2MS->GetBufferSize();
pipeDesc.PS.pShaderBytecode = m_IntArray2MS->GetBufferPointer();
}
pipeDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
pipeDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
pipeDesc.SampleMask = sampleMask;
pipeDesc.SampleDesc = descMS.SampleDesc;
pipeDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED;
pipeDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
pipeDesc.BlendState.RenderTarget[0].BlendEnable = FALSE;
pipeDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA;
pipeDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
pipeDesc.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;
pipeDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_SRC_ALPHA;
pipeDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
pipeDesc.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;
pipeDesc.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
ID3D12PipelineState *pso = NULL, *psoStencil = NULL;
HRESULT hr = m_pDevice->CreateGraphicsPipelineState(&pipeDesc, __uuidof(ID3D12PipelineState),
(void **)&pso);
if(FAILED(hr))
{
RDCERR("Couldn't create MSAA conversion pipeline! HRESULT: %s", ToStr(hr).c_str());
return;
}
ID3D12GraphicsCommandList *list = m_DebugList;
list->Reset(m_DebugAlloc, NULL);
D3D12_VIEWPORT viewport = {0, 0, (float)descArr.Width, (float)descArr.Height, 0.0f, 1.0f};
D3D12_RECT scissor = {0, 0, (LONG)descArr.Width, (LONG)descArr.Height};
list->RSSetViewports(1, &viewport);
list->RSSetScissorRects(1, &scissor);
list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
list->SetDescriptorHeaps(1, &cbvsrvuavHeap);
list->SetPipelineState(pso);
list->SetGraphicsRootSignature(m_ArrayMSAARootSig);
list->SetGraphicsRootDescriptorTable(1, GetGPUHandle(MSAA_SRV2x));
if(stencil)
list->SetGraphicsRootDescriptorTable(2, GetGPUHandle(STENCIL_MSAA_SRV2x));
// loop over every array slice in MS texture
for(UINT slice = 0; slice < descMS.DepthOrArraySize; slice++)
{
if(singleSliceMode)
slice = selectedSlice;
uint32_t cdata[4] = {descMS.SampleDesc.Count, 1000, 0, slice};
list->SetGraphicsRootConstantBufferView(0, UploadConstants(cdata, sizeof(cdata)));
rtvDesc.Texture2DMSArray.FirstArraySlice = slice;
dsvDesc.Texture2DMSArray.FirstArraySlice = slice;
if(depthFormat)
{
m_pDevice->CreateDepthStencilView(destMS, &dsvDesc, dsv);
list->OMSetRenderTargets(0, NULL, FALSE, &dsv);
}
else
{
m_pDevice->CreateRenderTargetView(destMS, &rtvDesc, rtv);
list->OMSetRenderTargets(1, &rtv, FALSE, NULL);
}
list->DrawInstanced(3, 1, 0, 0);
if(singleSliceMode)
break;
}
if(stencil)
{
pipeDesc.DepthStencilState.DepthEnable = FALSE;
pipeDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
pipeDesc.DepthStencilState.StencilEnable = TRUE;
pipeDesc.DepthStencilState.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
pipeDesc.DepthStencilState.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE;
pipeDesc.DepthStencilState.FrontFace.StencilFailOp = D3D12_STENCIL_OP_REPLACE;
pipeDesc.DepthStencilState.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_REPLACE;
pipeDesc.DepthStencilState.BackFace = pipeDesc.DepthStencilState.FrontFace;
pipeDesc.DepthStencilState.StencilReadMask = 0xff;
pipeDesc.DepthStencilState.StencilWriteMask = 0xff;
hr = m_pDevice->CreateGraphicsPipelineState(&pipeDesc, __uuidof(ID3D12PipelineState),
(void **)&psoStencil);
RDCASSERTEQUAL(hr, S_OK);
dsvDesc.Flags = D3D12_DSV_FLAG_READ_ONLY_DEPTH;
dsvDesc.Texture2DMSArray.ArraySize = 1;
m_pDevice->CreateDepthStencilView(destMS, &dsvDesc, dsv);
list->OMSetRenderTargets(0, NULL, FALSE, &dsv);
list->SetPipelineState(psoStencil);
// loop over every array slice in MS texture
for(UINT slice = 0; slice < descMS.DepthOrArraySize; slice++)
{
if(singleSliceMode)
slice = selectedSlice;
dsvDesc.Texture2DArray.FirstArraySlice = slice;
// loop over every stencil value (in theory we could use SV_StencilRef, but it's optional
// and would mean a different shader)
for(UINT stencilval = 0; stencilval < 256; stencilval++)
{
uint32_t cdata[4] = {descMS.SampleDesc.Count, stencilval, 0, slice};
list->SetGraphicsRootConstantBufferView(0, UploadConstants(cdata, sizeof(cdata)));
list->OMSetStencilRef(stencilval);
list->DrawInstanced(3, 1, 0, 0);
}
}
}
list->Close();
ID3D12CommandList *l = m_DebugList;
m_pDevice->GetQueue()->ExecuteCommandLists(1, &l);
m_pDevice->GPUSync();
m_DebugAlloc->Reset();
SAFE_RELEASE(pso);
SAFE_RELEASE(psoStencil);
}
+71 -6
View File
@@ -100,6 +100,8 @@ void D3D12Replay::Shutdown()
void D3D12Replay::CreateResources()
{
m_DebugManager = new D3D12DebugManager(m_pDevice);
if(RenderDoc::Inst().IsReplayApp())
{
typedef HRESULT(WINAPI * PFN_CREATE_DXGI_FACTORY)(REFIID, void **);
@@ -136,8 +138,6 @@ void D3D12Replay::CreateResources()
}
}
m_DebugManager = new D3D12DebugManager(m_pDevice);
CreateSOBuffers();
m_General.Init(m_pDevice, m_DebugManager);
@@ -146,10 +146,7 @@ void D3D12Replay::CreateResources()
m_VertexPick.Init(m_pDevice, m_DebugManager);
m_PixelPick.Init(m_pDevice, m_DebugManager);
m_Histogram.Init(m_pDevice, m_DebugManager);
}
if(RenderDoc::Inst().IsReplayApp())
{
AMDCounters *counters = NULL;
if(m_Vendor == GPUVendor::AMD)
@@ -2926,6 +2923,7 @@ void D3D12Replay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip
copyDesc.DepthOrArraySize *= (UINT16)copyDesc.SampleDesc.Count;
copyDesc.SampleDesc.Count = 1;
copyDesc.SampleDesc.Quality = 0;
copyDesc.MipLevels = 1;
wasms = true;
}
@@ -3082,7 +3080,74 @@ void D3D12Replay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip
else if(wasms)
{
// copy/expand multisampled live texture to array readback texture
RDCUNIMPLEMENTED("CopyTex2DMSToArray on D3D12");
if(isDepth)
copyDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
else
copyDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
copyDesc.Format = GetTypelessFormat(copyDesc.Format);
ID3D12Resource *arrayTexture;
hr = m_pDevice->CreateCommittedResource(
&defaultHeap, D3D12_HEAP_FLAG_NONE, &copyDesc,
isDepth ? D3D12_RESOURCE_STATE_DEPTH_WRITE : D3D12_RESOURCE_STATE_RENDER_TARGET, NULL,
__uuidof(ID3D12Resource), (void **)&arrayTexture);
RDCASSERTEQUAL(hr, S_OK);
list = m_pDevice->GetNewList();
// put source texture into shader read state
const vector<D3D12_RESOURCE_STATES> &states = m_pDevice->GetSubresourceStates(tex);
std::vector<D3D12_RESOURCE_BARRIER> barriers;
barriers.reserve(states.size());
for(size_t i = 0; i < states.size(); i++)
{
D3D12_RESOURCE_BARRIER b;
// skip unneeded barriers
if(states[i] & D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
continue;
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
b.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
b.Transition.pResource = resource;
b.Transition.Subresource = (UINT)i;
b.Transition.StateBefore = states[i];
b.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
barriers.push_back(b);
}
if(!barriers.empty())
list->ResourceBarrier((UINT)barriers.size(), &barriers[0]);
list->Close();
list = NULL;
m_pDevice->ExecuteLists();
m_pDevice->FlushLists();
// expand multisamples out to array
GetDebugManager()->CopyTex2DMSToArray(arrayTexture, srcTexture);
tmpTexture = srcTexture = arrayTexture;
list = m_pDevice->GetNewList();
// real resource back to normal
for(size_t i = 0; i < barriers.size(); i++)
std::swap(barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter);
if(!barriers.empty())
list->ResourceBarrier((UINT)barriers.size(), &barriers[0]);
D3D12_RESOURCE_BARRIER b = {};
b.Transition.pResource = arrayTexture;
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
b.Transition.StateBefore =
isDepth ? D3D12_RESOURCE_STATE_DEPTH_WRITE : D3D12_RESOURCE_STATE_RENDER_TARGET;
b.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
list->ResourceBarrier(1, &b);
}
if(list == NULL)
@@ -110,6 +110,7 @@
<ClCompile Include="d3d12_hooks.cpp" />
<ClCompile Include="d3d12_initstate.cpp" />
<ClCompile Include="d3d12_manager.cpp" />
<ClCompile Include="d3d12_msaa_array_conv.cpp" />
<ClCompile Include="d3d12_outputwindow.cpp" />
<ClCompile Include="d3d12_overlay.cpp" />
<ClCompile Include="d3d12_postvs.cpp" />
@@ -146,5 +146,8 @@
<ClCompile Include="d3d12_command_list1_wrap.cpp">
<Filter>Wrapped</Filter>
</ClCompile>
<ClCompile Include="d3d12_msaa_array_conv.cpp">
<Filter>Replay</Filter>
</ClCompile>
</ItemGroup>
</Project>