一、效果
这是一个比较简单的sample,运行sample可以看到,当红橙色长方形完全覆盖白色正方形时,白色正方形不显示,其他情况,均显示白色正方形。
二、实现
Render主要由三个部分组成
1.Far quad
2.Near quad Far
3.Quad bounding box
2和3都是是无论如何都要画的。在画3的时候,会进行一个Query,看3是不是所有的pixel都没有通过depth test(即是否被2全部覆盖)。根据上一帧Query的结果看是否画1,如果全部覆盖则不画1,否则画1。
程序逻辑如下:
1. 设置FarQuad的CBV(Constant Buffer View),根据上一帧Query的结果来SetPredication,画FarQuad;
2. SetPredication(NULL),设置NearQuad的CBV,画NearQuad;
3. 设置BoundingBox的CBV,为Query设置PSO(Pipeline State Object),BeginQuery – 画BoundingBox – EndQuery,导出Query的结果。
三、代码
首先要介绍一下API:
void SetPredication( [in, optional] ID3D12Resource *pBuffer, [in] UINT64 AlignedBufferOffset, [in] D3D12_PREDICATION_OP Operation );
官方SDK上面也有介绍,这里就简单说下,这个API表示,如果pBuffer里的数据满足Operation(=0或≠0),则后面的Rendering以及Resource操作都不会生效。如果pBuffer = NULL,则Predication被Disable掉(失效)。
Query的用法跟以前类似,需要注意的是,现在的SDK要求,除非Query type为TIMESTAMP,否则BeginQuery和EndQuery必须成对出现。
下面就来看一下代码。和这些samples相关的框架以及一些基本的API就不再赘述了,这里主要就介绍一下和PredicationQueries相关的代码。
D3D12PredicationQueries::LoadPipeline()
创建QueryHeap,类型为OCCLUSION,就是用来Query通过depth test的点的个数。
// Describe and create a heap for occlusion queries. D3D12_QUERY_HEAP_DESC queryHeapDesc = {}; queryHeapDesc.Count = 1; queryHeapDesc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION; ThrowIfFailed(m_device->CreateQueryHeap(&queryHeapDesc, IID_PPV_ARGS(&m_queryHeap)));
void D3D12PredicationQueries::LoadAssets()
为了能看到红橙色长方形下面是否画了白色正方形,必须打开Alpha blend
// Enable alpha blending so we can visualize the occlusion query results. CD3DX12_BLEND_DESC blendDesc(D3D12_DEFAULT); blendDesc.RenderTarget[0] = { TRUE, FALSE, D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, D3D12_LOGIC_OP_NOOP, D3D12_COLOR_WRITE_ENABLE_ALL, };
在画Bounding box的时候,只是为了做Occlusion Query,因此不需要把render结果写到RT(RenderTarget)和DS(Depth Stencil)里面去。
// Disable color writes and depth writes for the occlusion query's state. psoDesc.BlendState.RenderTarget[0].RenderTargetWriteMask = 0; psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; ThrowIfFailed(m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_queryState)));
void D3D12PredicationQueries::OnUpdate()里面主要就是去更新红橙色长方形的CBV(即它的位置),因为它以一个速度从左至右移动。
void D3D12PredicationQueries::PopulateCommandList()
这里就是按照之前介绍的逻辑进行Render。
CD3DX12_GPU_DESCRIPTOR_HANDLE cbvFarQuad(m_cbvHeap->GetGPUDescriptorHandleForHeapStart(), m_frameIndex * CbvCountPerFrame, m_cbvSrvDescriptorSize); CD3DX12_GPU_DESCRIPTOR_HANDLE cbvNearQuad(cbvFarQuad, m_cbvSrvDescriptorSize); m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView); // Draw the far quad conditionally based on the result of the occlusion query // from the previous frame. m_commandList->SetGraphicsRootDescriptorTable(0, cbvFarQuad); m_commandList->SetPredication(m_queryResult.Get(), 0, D3D12_PREDICATION_OP_EQUAL_ZERO); m_commandList->DrawInstanced(4, 1, 0, 0); // Disable predication and always draw the near quad. m_commandList->SetPredication(nullptr, 0, D3D12_PREDICATION_OP_EQUAL_ZERO); m_commandList->SetGraphicsRootDescriptorTable(0, cbvNearQuad); m_commandList->DrawInstanced(4, 1, 4, 0); // Run the occlusion query with the bounding box quad. m_commandList->SetGraphicsRootDescriptorTable(0, cbvFarQuad); m_commandList->SetPipelineState(m_queryState.Get()); m_commandList->BeginQuery(m_queryHeap.Get(), D3D12_QUERY_TYPE_BINARY_OCCLUSION, 0); m_commandList->DrawInstanced(4, 1, 8, 0); m_commandList->EndQuery(m_queryHeap.Get(), D3D12_QUERY_TYPE_BINARY_OCCLUSION, 0); // Resolve the occlusion query and store the results in the query result buffer // to be used on the subsequent frame. m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_queryResult.Get(), D3D12_RESOURCE_STATE_PREDICATION, D3D12_RESOURCE_STATE_COPY_DEST)); m_commandList->ResolveQueryData(m_queryHeap.Get(), D3D12_QUERY_TYPE_BINARY_OCCLUSION, 0, 1, m_queryResult.Get(), 0); m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_queryResult.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D
从上面的代码可以看到,最后一个Draw实际上就是去Query通过Depth test的点的个数,ResolveQueryData会将Query的结果Resolve到一块buffer里面。Query Type为D3D12_QUERY_TYPE_BINARY_OCCLUSION表示Query结果只有两种:0表示点通过depth stencil test,1表示至少有一个点通过。也就是说在下一帧Draw far quad之前,会根据这个buffer里面的结果是否等于0来SetPredication。如果结果为0,表示上一帧的长方形完全覆盖住白色正方形,则后面的Draw失效,不画白色正方形。
话说有个问题就是,为什么不把Draw bounding box放在最前面?那样就可以实时的判断当前帧是否需要画白色正方形。不知道放在后面,让下一帧再去判断是什么意思。