我实现了一个CPU路径跟踪器,它可以渲染成一个图像。这个图像稍后会使用DirectX 12在屏幕上进行blit。
代码已经工作了多年,直到我昨天尝试。我突然得到文物。
例如,这里是由路径跟踪生成的浮点图像:
我在屏幕
上进行blitting后获得的内容
我的代码条款,这里我的基本图像类:
class Image
{
public:
Image(const Image& src);
virtual ~Image() {};
nbUint32 getWidth() const;
nbUint32 getHeight() const;
nbUint64 getBytesPerRow() const;
const unsigned char* getRawData() const;
ImageFormat getFormat() const;
nbUint32 getNbPixels() const;
protected:
Image(ImageFormat imFormat);
Image(nbUint32 width, nbUint32 height, ImageFormat imFormat);
cv::Mat m_image;
ImageFormat m_format;
};
这是我的渲染器类,在屏幕上绘制图像。
class DX12Renderer
{
public:
void drawImage(const Texture::Image& image) override;
void createTexture2D(const Texture::Image* image, Dx12TextureHandle& dst);
void releaseTexture(Dx12TextureHandle& textureHandle) const;
//etc....
private:
void prepareViewportRender();
enum class CommandType
{
Direct,
Copy
};
struct CommandBuffer
{
ID3D12CommandAllocator* commandAllocator[SwapChainBufferCount];
ID3D12GraphicsCommandList* commandList;
ID3D12CommandQueue* commandQueue;
ID3D12Fence* fences[SwapChainBufferCount];
HANDLE fenceEvent;
UINT64 fenceValue[SwapChainBufferCount];
nbInt32 frameIndex;
};
std::unordered_map<CommandType, CommandBuffer> m_commandBuffers;
Dx12TextureHandle m_blittingBuffers[SwapChainBufferCount];
ID3D12Resource* m_renderTargets[SwapChainBufferCount];
// Descriptor allocators
std::unique_ptr<Descriptor::CBV_SRV_UAV_DescriptorAllocator> m_cbs_srv_uavAllocator;
std::unique_ptr<Descriptor::RTV_DescriptorAllocator> m_rtvAllocator;
std::unique_ptr<Descriptor::DSV_DescriptorAllocator> m_dsvAllocator;
//etc....
}
inline void DX12Renderer::drawImage(const Texture::Image& image)
{
prepareViewportRender();
auto& commandList = m_commandBuffers[CommandType::Direct].commandList;
const nbInt32 frameIdx = m_commandBuffers[CommandType::Direct].frameIndex;
Dx12TextureHandle& blitBuffer = m_blittingBuffers[frameIdx];
// TODO : Only recreate texture when image size is different.
// Release current blit buffer
if (blitBuffer.buffer)
releaseTexture(blitBuffer);
// Create texture from image
createTexture2D(&image, blitBuffer);
// Now draw
commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[frameIdx], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvDescriptorsHandle.getCpuHandle(),
frameIdx,
m_rtvAllocator->getDescriptorSize());
commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
{
Effect::RenderRectanglePushArgs args = { blitBuffer };
m_renderRectangleEffect->pushDrawCommands(args, commandList, frameIdx);
}
commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[frameIdx], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
}
inline void DX12Renderer::prepareViewportRender()
{
auto& commandList = m_commandBuffers[CommandType::Direct].commandList;
const nbInt32 frameIdx = m_commandBuffers[CommandType::Direct].frameIndex;
// Set the viewport
commandList->RSSetViewports(1, &m_viewport);
// Set the scissor rect
commandList->RSSetScissorRects(1, &m_scissorRect);
// Set the descriptor heaps
ID3D12DescriptorHeap* descriptorHeaps[] = { m_cbs_srv_uavAllocator->getHeap() };
commandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
}
inline void DX12Renderer::createTexture2D(const Texture::Image* image, Dx12TextureHandle& dst)
{
const nbUint32 width = image->getWidth();
const nbUint32 height = image->getHeight();
if (width > D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION)
throw CreateTextureException("Image width is too high.");
if (height > D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION)
throw CreateTextureException("Image height is too high");
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
textureDesc.Alignment = 0; // may be 0, 4KB, 64KB, or 4MB. 0 will let runtime decide between 64KB and 4MB (4MB for multi-sampled textures)
textureDesc.Width = width;
textureDesc.Height = height;
textureDesc.DepthOrArraySize = 1; // if 3d image, depth of 3d image. Otherwise an array of 1D or 2D textures.
textureDesc.MipLevels = 1; // Number of mipmaps. We are not generating mipmaps for this texture, so we have only one level.
const Texture::ImageFormat imageFormat = image->getFormat();
switch (imageFormat)
{
case Texture::ImageFormat::RGBA8:
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
break;
case Texture::ImageFormat::RGB32F:
textureDesc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
break;
case Texture::ImageFormat::RGBA32F:
textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
break;
default:
throw CreateTextureException("Unsupported image format");
}
textureDesc.SampleDesc.Count = 1; // This is the number of samples per pixel, we just want 1 sample
textureDesc.SampleDesc.Quality = 0; // The quality level of the samples. Higher is better quality, but worse performance
textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; // The arrangement of the pixels. Setting to unknown lets the driver choose the most efficient one
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE; // no flags
// Create a default heap.
HRESULT hr = D3D12Device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST, // We will copy the texture from the upload heap to here, so we start it out in a copy dest state
nullptr,
IID_PPV_ARGS(&dst.buffer));
if (FAILED(hr))
{
return;
}
dst.format = textureDesc.Format;
UINT64 textureUploadBufferSize;
D3D12Device->GetCopyableFootprints(&textureDesc, 0, 1, 0, nullptr, nullptr, nullptr, &textureUploadBufferSize);
// Now we create an upload heap to upload our texture to the GPU
ID3D12Resource* textureBufferUploadHeap;
hr = D3D12Device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(textureUploadBufferSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&textureBufferUploadHeap));
if (FAILED(hr))
{
return;
}
// Initialize subresources
D3D12_SUBRESOURCE_DATA subResourceData;
subResourceData.pData = image->getRawData();
subResourceData.RowPitch = image->getBytesPerRow();
subResourceData.SlicePitch = image->getBytesPerRow() * height;
// Now we copy the upload buffer contents to the default heap
UpdateSubresources(m_commandBuffers[CommandType::Direct].commandList, dst.buffer, textureBufferUploadHeap, 0, 0, 1, &subResourceData);
m_commandBuffers[CommandType::Direct].commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(dst.buffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON));
// Create buffer view
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Format = dst.format;
srvDesc.Texture2D.MipLevels = 1;
dst.descriptorHandle = m_cbs_srv_uavAllocator->allocate({ dst.buffer }, srvDesc);
}
inline void releaseTexture(Dx12TextureHandle& textureHandle) const
{
m_cbs_srv_uavAllocator->release(textureHandle.descriptorHandle);
textureHandle.buffer->Release();
textureHandle.buffer = nullptr;
}
下面是渲染矩形效果:
void RenderRectangle::pushDrawCommands(RenderRectanglePushArgs& data, ID3D12GraphicsCommandList* commandList, nbInt32 frameIndex)
{
commandList->SetPipelineState(m_PSO);
commandList->SetGraphicsRootSignature(m_rootSignature);
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
{
PixelShaderCB cb;
cb.color = { data.color.x, data.color.y, data.color.z, data.color.w };
cb.hasTexture = !!data.textureHandle.buffer;
memcpy(m_pixelShaderCBGPUAddress[frameIndex], &cb, sizeof(PixelShaderCB));
}
commandList->SetGraphicsRootConstantBufferView(0, m_pixelShaderCBUploadHeaps[frameIndex]->GetGPUVirtualAddress());
if (data.textureHandle.buffer)
{
commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(data.textureHandle.buffer, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
commandList->SetGraphicsRootDescriptorTable(1, data.textureHandle.descriptorHandle.getGpuHandle());
}
commandList->IASetVertexBuffers(0, 1, &m_vtxBuffer.bufferView);
commandList->IASetIndexBuffer(&m_idxBuffer.bufferView);
commandList->DrawIndexedInstanced(6, 1, 0, 0, 0);
if (data.textureHandle.buffer)
{
commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(data.textureHandle.buffer, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COMMON));
}
}
这里是渲染矩形着色器:
/////////////////// VERTEX SHADER ////////////////////
struct VS_INPUT
{
float4 position : POSITION;
float2 texCoord: TEXCOORD;
};
struct VS_OUTPUT
{
float4 position: SV_POSITION;
float2 texCoord: TEXCOORD;
};
VS_OUTPUT main(VS_INPUT input)
{
VS_OUTPUT output;
output.texCoord = input.texCoord;
output.position = input.position;
return output;
}
/////////////////// PIXEL SHADER ////////////////////
struct VS_OUTPUT
{
float4 position: SV_POSITION;
float2 texCoord: TEXCOORD;
};
cbuffer PixelShaderCB : register(b0)
{
float4 color;
bool hasTexture;
};
Texture2D billboardTex : register(t0);
SamplerState tSampler : register(s0);
float4 main(VS_OUTPUT input) : SV_TARGET
{
if (hasTexture)
return billboardTex.Sample(tSampler, input.texCoord) * color;
return color;
}
我检查了渲染文档来验证输入纹理是否有问题。我得到了以下输入:
这是输出:
输入的纹理明显是错误的。我相信这是由于createTexture2D函数,但我不明白为什么。而且输出的纹理与输入的纹理有惊人的不同。
1条答案
按热度按时间jc3wubiy1#
这里的错误是你假设纹理资源的行间距与源图像相同。这不一定是真的。你需要逐行复制并将两个指针按各自的rowPitch前进:
image->getBytesPerRow()
。D3D12_SUBRESOURCE_INFO.RowPitch
。请参见D3DX12实用工具helper中的MemcpySubresource。