Browse Source

Add PSO handling to D3D

Panagiotis Christopoulos Charitos 1 year ago
parent
commit
000f62026d

+ 9 - 2
AnKi/Gr/CommandBuffer.h

@@ -63,6 +63,13 @@ public:
 	ClearValue m_clearValue;
 	ClearValue m_clearValue;
 
 
 	TextureUsageBit m_usage = TextureUsageBit::kFramebufferWrite;
 	TextureUsageBit m_usage = TextureUsageBit::kFramebufferWrite;
+
+	RenderTarget() = default;
+
+	RenderTarget(const TextureView& view)
+		: m_textureView(view)
+	{
+	}
 };
 };
 
 
 /// Command buffer initialization flags.
 /// Command buffer initialization flags.
@@ -179,10 +186,10 @@ public:
 	/// @{
 	/// @{
 
 
 	/// Bind vertex buffer.
 	/// Bind vertex buffer.
-	void bindVertexBuffer(U32 binding, const BufferView& buff, PtrSize stride, VertexStepRate stepRate = VertexStepRate::kVertex);
+	void bindVertexBuffer(U32 binding, const BufferView& buff, U32 stride, VertexStepRate stepRate = VertexStepRate::kVertex);
 
 
 	/// Setup a vertex attribute.
 	/// Setup a vertex attribute.
-	void setVertexAttribute(VertexAttributeSemantic attribute, U32 buffBinding, Format fmt, PtrSize relativeOffset);
+	void setVertexAttribute(VertexAttributeSemantic attribute, U32 buffBinding, Format fmt, U32 relativeOffset);
 
 
 	/// Bind index buffer.
 	/// Bind index buffer.
 	void bindIndexBuffer(const BufferView& buff, IndexType type);
 	void bindIndexBuffer(const BufferView& buff, IndexType type);

+ 7 - 1
AnKi/Gr/Common.h

@@ -300,7 +300,7 @@ enum class PrimitiveTopology : U8
 	kLineStip,
 	kLineStip,
 	kTriangles,
 	kTriangles,
 	kTriangleStrip,
 	kTriangleStrip,
-	kPatchs
+	kPatches
 };
 };
 
 
 enum class FillMode : U8
 enum class FillMode : U8
@@ -1045,6 +1045,12 @@ public:
 		{
 		{
 			ANKI_ASSERT(!m_vertex.m_vertexAttributeMask.get(semantic) || m_vertex.m_vertexAttributeLocations[semantic] != kMaxU8);
 			ANKI_ASSERT(!m_vertex.m_vertexAttributeMask.get(semantic) || m_vertex.m_vertexAttributeLocations[semantic] != kMaxU8);
 		}
 		}
+
+		const U32 attachmentCount = m_fragment.m_colorAttachmentWritemask.getSetBitCount();
+		for(U32 i = 0; i < attachmentCount; ++i)
+		{
+			ANKI_ASSERT(m_fragment.m_colorAttachmentWritemask.get(i) && "Should write to all attachments");
+		}
 	}
 	}
 
 
 	/// Combine shader reflection.
 	/// Combine shader reflection.

+ 120 - 41
AnKi/Gr/D3D/D3DCommandBuffer.cpp

@@ -31,19 +31,45 @@ void CommandBuffer::endRecording()
 	self.m_cmdList->Close();
 	self.m_cmdList->Close();
 }
 }
 
 
-void CommandBuffer::bindVertexBuffer(U32 binding, const BufferView& buff, PtrSize stride, VertexStepRate stepRate)
+void CommandBuffer::bindVertexBuffer(U32 binding, const BufferView& buff, U32 stride, VertexStepRate stepRate)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_ASSERT(stride > 0);
+	ANKI_D3D_SELF(CommandBufferImpl);
+
+	self.commandCommon();
+
+	self.m_graphicsState.bindVertexBuffer(binding, stepRate);
+
+	const BufferImpl& impl = static_cast<const BufferImpl&>(buff.getBuffer());
+
+	const D3D12_VERTEX_BUFFER_VIEW d3dView = {.BufferLocation = impl.getGpuAddress() + buff.getOffset(),
+											  .SizeInBytes = U32(buff.getRange()),
+											  .StrideInBytes = stride};
+
+	self.m_cmdList->IASetVertexBuffers(binding, 1, &d3dView);
 }
 }
 
 
-void CommandBuffer::setVertexAttribute(VertexAttributeSemantic attribute, U32 buffBinding, Format fmt, PtrSize relativeOffset)
+void CommandBuffer::setVertexAttribute(VertexAttributeSemantic attribute, U32 buffBinding, Format fmt, U32 relativeOffset)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_ASSERT(attribute < VertexAttributeSemantic::kCount && fmt != Format::kNone);
+	ANKI_D3D_SELF(CommandBufferImpl);
+
+	self.commandCommon();
+	self.m_graphicsState.setVertexAttribute(attribute, buffBinding, fmt, relativeOffset);
 }
 }
 
 
 void CommandBuffer::bindIndexBuffer(const BufferView& buff, IndexType type)
 void CommandBuffer::bindIndexBuffer(const BufferView& buff, IndexType type)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_ASSERT(type != IndexType::kCount);
+	ANKI_D3D_SELF(CommandBufferImpl);
+
+	const BufferImpl& impl = static_cast<const BufferImpl&>(buff.getBuffer());
+
+	const D3D12_INDEX_BUFFER_VIEW view = {.BufferLocation = impl.getGpuAddress() + buff.getOffset(),
+										  .SizeInBytes = U32(buff.getRange()),
+										  .Format = (type == IndexType::kU16) ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT};
+
+	self.m_cmdList->IASetIndexBuffer(&view);
 }
 }
 
 
 void CommandBuffer::setPrimitiveRestart(Bool enable)
 void CommandBuffer::setPrimitiveRestart(Bool enable)
@@ -53,83 +79,115 @@ void CommandBuffer::setPrimitiveRestart(Bool enable)
 
 
 void CommandBuffer::setViewport(U32 minx, U32 miny, U32 width, U32 height)
 void CommandBuffer::setViewport(U32 minx, U32 miny, U32 width, U32 height)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setViewport(minx, miny, width, height);
 }
 }
 
 
 void CommandBuffer::setScissor(U32 minx, U32 miny, U32 width, U32 height)
 void CommandBuffer::setScissor(U32 minx, U32 miny, U32 width, U32 height)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setScissor(minx, miny, width, height);
 }
 }
 
 
 void CommandBuffer::setFillMode(FillMode mode)
 void CommandBuffer::setFillMode(FillMode mode)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setFillMode(mode);
 }
 }
 
 
 void CommandBuffer::setCullMode(FaceSelectionBit mode)
 void CommandBuffer::setCullMode(FaceSelectionBit mode)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setCullMode(mode);
 }
 }
 
 
 void CommandBuffer::setPolygonOffset(F32 factor, F32 units)
 void CommandBuffer::setPolygonOffset(F32 factor, F32 units)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setPolygonOffset(factor, units);
 }
 }
 
 
 void CommandBuffer::setStencilOperations(FaceSelectionBit face, StencilOperation stencilFail, StencilOperation stencilPassDepthFail,
 void CommandBuffer::setStencilOperations(FaceSelectionBit face, StencilOperation stencilFail, StencilOperation stencilPassDepthFail,
 										 StencilOperation stencilPassDepthPass)
 										 StencilOperation stencilPassDepthPass)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setStencilOperations(face, stencilFail, stencilPassDepthFail, stencilPassDepthPass);
 }
 }
 
 
 void CommandBuffer::setStencilCompareOperation(FaceSelectionBit face, CompareOperation comp)
 void CommandBuffer::setStencilCompareOperation(FaceSelectionBit face, CompareOperation comp)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setStencilCompareOperation(face, comp);
 }
 }
 
 
 void CommandBuffer::setStencilCompareMask(FaceSelectionBit face, U32 mask)
 void CommandBuffer::setStencilCompareMask(FaceSelectionBit face, U32 mask)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setStencilCompareMask(face, mask);
 }
 }
 
 
 void CommandBuffer::setStencilWriteMask(FaceSelectionBit face, U32 mask)
 void CommandBuffer::setStencilWriteMask(FaceSelectionBit face, U32 mask)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setStencilWriteMask(face, mask);
 }
 }
 
 
 void CommandBuffer::setStencilReference(FaceSelectionBit face, U32 ref)
 void CommandBuffer::setStencilReference(FaceSelectionBit face, U32 ref)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setStencilReference(face, ref);
 }
 }
 
 
 void CommandBuffer::setDepthWrite(Bool enable)
 void CommandBuffer::setDepthWrite(Bool enable)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setDepthWrite(enable);
 }
 }
 
 
 void CommandBuffer::setDepthCompareOperation(CompareOperation op)
 void CommandBuffer::setDepthCompareOperation(CompareOperation op)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setDepthCompareOperation(op);
 }
 }
 
 
 void CommandBuffer::setAlphaToCoverage(Bool enable)
 void CommandBuffer::setAlphaToCoverage(Bool enable)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setAlphaToCoverage(enable);
 }
 }
 
 
 void CommandBuffer::setColorChannelWriteMask(U32 attachment, ColorBit mask)
 void CommandBuffer::setColorChannelWriteMask(U32 attachment, ColorBit mask)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setColorChannelWriteMask(attachment, mask);
 }
 }
 
 
 void CommandBuffer::setBlendFactors(U32 attachment, BlendFactor srcRgb, BlendFactor dstRgb, BlendFactor srcA, BlendFactor dstA)
 void CommandBuffer::setBlendFactors(U32 attachment, BlendFactor srcRgb, BlendFactor dstRgb, BlendFactor srcA, BlendFactor dstA)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setBlendFactors(attachment, srcRgb, dstRgb, srcA, dstA);
 }
 }
 
 
 void CommandBuffer::setBlendOperation(U32 attachment, BlendOperation funcRgb, BlendOperation funcA)
 void CommandBuffer::setBlendOperation(U32 attachment, BlendOperation funcRgb, BlendOperation funcA)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.commandCommon();
+	self.m_graphicsState.setBlendOperation(attachment, funcRgb, funcA);
 }
 }
 
 
 void CommandBuffer::bindTexture(Register reg, const TextureView& texView)
 void CommandBuffer::bindTexture(Register reg, const TextureView& texView)
@@ -221,17 +279,17 @@ void CommandBuffer::bindShaderProgram(ShaderProgram* prog)
 	const ShaderProgramImpl& progImpl = static_cast<const ShaderProgramImpl&>(*prog);
 	const ShaderProgramImpl& progImpl = static_cast<const ShaderProgramImpl&>(*prog);
 	const Bool isCompute = !(progImpl.getShaderTypes() & ShaderTypeBit::kAllGraphics);
 	const Bool isCompute = !(progImpl.getShaderTypes() & ShaderTypeBit::kAllGraphics);
 
 
-	self.m_descriptors.bindRootSignature(&progImpl.getRootSignature(), isCompute);
+	self.m_descriptors.bindRootSignature(progImpl.m_rootSignature, isCompute);
 
 
 	self.m_mcmdb->pushObjectRef(prog);
 	self.m_mcmdb->pushObjectRef(prog);
 
 
 	if(isCompute)
 	if(isCompute)
 	{
 	{
-		self.m_cmdList->SetPipelineState(&progImpl.getComputePipelineState());
+		self.m_cmdList->SetPipelineState(progImpl.m_compute.m_pipelineState);
 	}
 	}
 	else
 	else
 	{
 	{
-		ANKI_ASSERT(!"TODO");
+		self.m_graphicsState.bindShaderProgram(&progImpl);
 	}
 	}
 
 
 	// Shader program means descriptors so bind the descriptor heaps
 	// Shader program means descriptors so bind the descriptor heaps
@@ -250,37 +308,54 @@ void CommandBuffer::beginRenderPass(ConstWeakArray<RenderTarget> colorRts, Rende
 	ANKI_D3D_SELF(CommandBufferImpl);
 	ANKI_D3D_SELF(CommandBufferImpl);
 	self.commandCommon();
 	self.commandCommon();
 
 
+	U32 rtWidth = 0;
+	U32 rtHeight = 0;
+
 	Array<D3D12_RENDER_PASS_RENDER_TARGET_DESC, kMaxColorRenderTargets> colorRtDescs;
 	Array<D3D12_RENDER_PASS_RENDER_TARGET_DESC, kMaxColorRenderTargets> colorRtDescs;
+	Array<Format, kMaxColorRenderTargets> colorRtFormats;
 	for(U32 i = 0; i < colorRts.getSize(); ++i)
 	for(U32 i = 0; i < colorRts.getSize(); ++i)
 	{
 	{
 		const RenderTarget& rt = colorRts[i];
 		const RenderTarget& rt = colorRts[i];
 		D3D12_RENDER_PASS_RENDER_TARGET_DESC& desc = colorRtDescs[i];
 		D3D12_RENDER_PASS_RENDER_TARGET_DESC& desc = colorRtDescs[i];
+		const TextureImpl& tex = static_cast<const TextureImpl&>(rt.m_textureView.getTexture());
 
 
 		desc = {};
 		desc = {};
-		desc.cpuDescriptor =
-			static_cast<const TextureImpl&>(rt.m_textureView.getTexture()).getOrCreateRtv(rt.m_textureView.getSubresource()).getCpuOffset();
+		desc.cpuDescriptor = tex.getOrCreateRtv(rt.m_textureView.getSubresource()).getCpuOffset();
 		desc.BeginningAccess.Type = convertLoadOp(rt.m_loadOperation);
 		desc.BeginningAccess.Type = convertLoadOp(rt.m_loadOperation);
 		memcpy(&desc.BeginningAccess.Clear.ClearValue.Color, &rt.m_clearValue.m_colorf[0], sizeof(F32) * 4);
 		memcpy(&desc.BeginningAccess.Clear.ClearValue.Color, &rt.m_clearValue.m_colorf[0], sizeof(F32) * 4);
 		desc.EndingAccess.Type = convertStoreOp(rt.m_storeOperation);
 		desc.EndingAccess.Type = convertStoreOp(rt.m_storeOperation);
+
+		colorRtFormats[i] = tex.getFormat();
+
+		rtWidth = tex.getWidth() >> rt.m_textureView.getFirstMipmap();
+		rtHeight = tex.getHeight() >> rt.m_textureView.getFirstMipmap();
 	}
 	}
 
 
 	D3D12_RENDER_PASS_DEPTH_STENCIL_DESC dsDesc;
 	D3D12_RENDER_PASS_DEPTH_STENCIL_DESC dsDesc;
+	Format dsFormat = Format::kNone;
 	if(depthStencilRt)
 	if(depthStencilRt)
 	{
 	{
+		const TextureImpl& tex = static_cast<const TextureImpl&>(depthStencilRt->m_textureView.getTexture());
+
 		dsDesc = {};
 		dsDesc = {};
-		dsDesc.cpuDescriptor = static_cast<const TextureImpl&>(depthStencilRt->m_textureView.getTexture())
-								   .getOrCreateDsv(depthStencilRt->m_textureView.getSubresource(), depthStencilRt->m_usage)
-								   .getCpuOffset();
+		dsDesc.cpuDescriptor = tex.getOrCreateDsv(depthStencilRt->m_textureView.getSubresource(), depthStencilRt->m_usage).getCpuOffset();
 
 
 		dsDesc.DepthBeginningAccess.Type = convertLoadOp(depthStencilRt->m_loadOperation);
 		dsDesc.DepthBeginningAccess.Type = convertLoadOp(depthStencilRt->m_loadOperation);
 		dsDesc.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth, depthStencilRt->m_clearValue.m_depthStencil.m_depth;
 		dsDesc.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth, depthStencilRt->m_clearValue.m_depthStencil.m_depth;
 		dsDesc.DepthEndingAccess.Type = convertStoreOp(depthStencilRt->m_storeOperation);
 		dsDesc.DepthEndingAccess.Type = convertStoreOp(depthStencilRt->m_storeOperation);
 
 
 		dsDesc.StencilBeginningAccess.Type = convertLoadOp(depthStencilRt->m_stencilLoadOperation);
 		dsDesc.StencilBeginningAccess.Type = convertLoadOp(depthStencilRt->m_stencilLoadOperation);
-		dsDesc.StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil = depthStencilRt->m_clearValue.m_depthStencil.m_stencil;
+		dsDesc.StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil = U8(depthStencilRt->m_clearValue.m_depthStencil.m_stencil);
 		dsDesc.StencilEndingAccess.Type = convertStoreOp(depthStencilRt->m_stencilStoreOperation);
 		dsDesc.StencilEndingAccess.Type = convertStoreOp(depthStencilRt->m_stencilStoreOperation);
+
+		dsFormat = tex.getFormat();
+
+		rtWidth = tex.getWidth() >> depthStencilRt->m_textureView.getFirstMipmap();
+		rtHeight = tex.getHeight() >> depthStencilRt->m_textureView.getFirstMipmap();
 	}
 	}
 
 
+	self.m_graphicsState.beginRenderPass(ConstWeakArray(colorRtFormats.getBegin(), colorRts.getSize()), dsFormat, rtWidth, rtHeight);
+
 	self.m_cmdList->BeginRenderPass(colorRts.getSize(), colorRtDescs.getBegin(), (depthStencilRt) ? &dsDesc : nullptr,
 	self.m_cmdList->BeginRenderPass(colorRts.getSize(), colorRtDescs.getBegin(), (depthStencilRt) ? &dsDesc : nullptr,
 									D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES);
 									D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES);
 }
 }
@@ -299,12 +374,20 @@ void CommandBuffer::setVrsRate(VrsRate rate)
 
 
 void CommandBuffer::drawIndexed(PrimitiveTopology topology, U32 count, U32 instanceCount, U32 firstIndex, U32 baseVertex, U32 baseInstance)
 void CommandBuffer::drawIndexed(PrimitiveTopology topology, U32 count, U32 instanceCount, U32 firstIndex, U32 baseVertex, U32 baseInstance)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.m_graphicsState.setPrimitiveTopology(topology);
+	self.drawcallCommon();
+
+	self.m_cmdList->DrawIndexedInstanced(count, instanceCount, firstIndex, baseVertex, baseInstance);
 }
 }
 
 
 void CommandBuffer::draw(PrimitiveTopology topology, U32 count, U32 instanceCount, U32 first, U32 baseInstance)
 void CommandBuffer::draw(PrimitiveTopology topology, U32 count, U32 instanceCount, U32 first, U32 baseInstance)
 {
 {
-	ANKI_ASSERT(!"TODO");
+	ANKI_D3D_SELF(CommandBufferImpl);
+	self.m_graphicsState.setPrimitiveTopology(topology);
+	self.drawcallCommon();
+
+	self.m_cmdList->DrawInstanced(count, instanceCount, first, baseInstance);
 }
 }
 
 
 void CommandBuffer::drawIndirect(PrimitiveTopology topology, const BufferView& buff, U32 drawCount)
 void CommandBuffer::drawIndirect(PrimitiveTopology topology, const BufferView& buff, U32 drawCount)
@@ -560,16 +643,12 @@ Error CommandBufferImpl::init(const CommandBufferInitInfo& init)
 	return Error::kNone;
 	return Error::kNone;
 }
 }
 
 
-void CommandBufferImpl::commandCommon()
+void CommandBufferImpl::drawcallCommon()
 {
 {
-	++m_commandCount;
-	if(m_commandCount >= kCommandBufferSmallBatchMaxCommands)
-	{
-		if((m_commandCount % 10) == 0) // Change the batch every 10 commands as an optimization
-		{
-			m_mcmdb->setBigBatch();
-		}
-	}
+	commandCommon();
+
+	m_graphicsState.getShaderProgram().m_graphics.m_pipelineFactory->flushState(m_graphicsState, *m_cmdList);
+	m_descriptors.flush(*m_cmdList);
 }
 }
 
 
 } // end namespace anki
 } // end namespace anki

+ 9 - 2
AnKi/Gr/D3D/D3DCommandBuffer.h

@@ -8,6 +8,7 @@
 #include <AnKi/Gr/CommandBuffer.h>
 #include <AnKi/Gr/CommandBuffer.h>
 #include <AnKi/Gr/D3D/D3DCommandBufferFactory.h>
 #include <AnKi/Gr/D3D/D3DCommandBufferFactory.h>
 #include <AnKi/Gr/D3D/D3DDescriptor.h>
 #include <AnKi/Gr/D3D/D3DDescriptor.h>
+#include <AnKi/Gr/D3D/D3DGraphicsState.h>
 
 
 namespace anki {
 namespace anki {
 
 
@@ -35,18 +36,24 @@ public:
 	}
 	}
 
 
 private:
 private:
-	ID3D12GraphicsCommandList6* m_cmdList = nullptr; // Cache it.
+	D3D12GraphicsCommandListX* m_cmdList = nullptr; // Cache it.
 	U32 m_commandCount = 0;
 	U32 m_commandCount = 0;
 
 
 	MicroCommandBufferPtr m_mcmdb;
 	MicroCommandBufferPtr m_mcmdb;
 
 
 	DescriptorState m_descriptors;
 	DescriptorState m_descriptors;
+	GraphicsStateTracker m_graphicsState;
 
 
 	StackMemoryPool* m_fastPool = nullptr; // Cache it.
 	StackMemoryPool* m_fastPool = nullptr; // Cache it.
 
 
 	Bool m_descriptorHeapsBound = false;
 	Bool m_descriptorHeapsBound = false;
 
 
-	void commandCommon();
+	void commandCommon()
+	{
+		++m_commandCount;
+	}
+
+	void drawcallCommon();
 };
 };
 /// @}
 /// @}
 
 

+ 13 - 13
AnKi/Gr/D3D/D3DCommandBufferFactory.cpp

@@ -5,15 +5,20 @@
 
 
 #include <AnKi/Gr/D3D/D3DCommandBufferFactory.h>
 #include <AnKi/Gr/D3D/D3DCommandBufferFactory.h>
 #include <AnKi/Util/Tracer.h>
 #include <AnKi/Util/Tracer.h>
+#include <AnKi/Core/StatsSet.h>
 
 
 namespace anki {
 namespace anki {
 
 
+static StatCounter g_commandBufferCountStatVar(StatCategory::kMisc, "CommandBufferCount", StatFlag::kNone);
+
 MicroCommandBuffer::~MicroCommandBuffer()
 MicroCommandBuffer::~MicroCommandBuffer()
 {
 {
 	m_fastPool.destroy();
 	m_fastPool.destroy();
 
 
 	safeRelease(m_cmdList);
 	safeRelease(m_cmdList);
 	safeRelease(m_cmdAllocator);
 	safeRelease(m_cmdAllocator);
+
+	g_commandBufferCountStatVar.decrement(1);
 }
 }
 
 
 Error MicroCommandBuffer::init(CommandBufferFlag flags)
 Error MicroCommandBuffer::init(CommandBufferFlag flags)
@@ -27,6 +32,8 @@ Error MicroCommandBuffer::init(CommandBufferFlag flags)
 	ANKI_D3D_CHECK(getDevice().CreateCommandList(0, cmdListType, m_cmdAllocator, nullptr, IID_PPV_ARGS(&cmdList)));
 	ANKI_D3D_CHECK(getDevice().CreateCommandList(0, cmdListType, m_cmdAllocator, nullptr, IID_PPV_ARGS(&cmdList)));
 	ANKI_D3D_CHECK(cmdList->QueryInterface(IID_PPV_ARGS(&m_cmdList)));
 	ANKI_D3D_CHECK(cmdList->QueryInterface(IID_PPV_ARGS(&m_cmdList)));
 
 
+	g_commandBufferCountStatVar.increment(1);
+
 	return Error::kNone;
 	return Error::kNone;
 }
 }
 
 
@@ -48,8 +55,6 @@ void MicroCommandBuffer::reset()
 
 
 	m_cmdAllocator->Reset();
 	m_cmdAllocator->Reset();
 	m_cmdList->Reset(m_cmdAllocator, nullptr);
 	m_cmdList->Reset(m_cmdAllocator, nullptr);
-
-	m_isSmallBatch = true;
 }
 }
 
 
 void MicroCommandBufferPtrDeleter::operator()(MicroCommandBuffer* cmdb)
 void MicroCommandBufferPtrDeleter::operator()(MicroCommandBuffer* cmdb)
@@ -62,12 +67,9 @@ void MicroCommandBufferPtrDeleter::operator()(MicroCommandBuffer* cmdb)
 
 
 CommandBufferFactory::~CommandBufferFactory()
 CommandBufferFactory::~CommandBufferFactory()
 {
 {
-	for(U32 smallBatch = 0; smallBatch < 2; ++smallBatch)
+	for(GpuQueueType queue : EnumIterable<GpuQueueType>())
 	{
 	{
-		for(GpuQueueType queue : EnumIterable<GpuQueueType>())
-		{
-			m_recyclers[smallBatch][queue].destroy();
-		}
+		m_recyclers[queue].destroy();
 	}
 	}
 }
 }
 
 
@@ -75,18 +77,16 @@ void CommandBufferFactory::deleteCommandBuffer(MicroCommandBuffer* cmdb)
 {
 {
 	ANKI_ASSERT(cmdb);
 	ANKI_ASSERT(cmdb);
 
 
-	const Bool smallBatch = cmdb->m_isSmallBatch;
-	const GpuQueueType queue = (cmdb->m_cmdList->GetType() == D3D12_COMMAND_LIST_TYPE_DIRECT) ? GpuQueueType::kCompute : GpuQueueType::kCompute;
+	const GpuQueueType queue = (cmdb->m_cmdList->GetType() == D3D12_COMMAND_LIST_TYPE_DIRECT) ? GpuQueueType::kGeneral : GpuQueueType::kCompute;
 
 
-	m_recyclers[smallBatch][queue].recycle(cmdb);
+	m_recyclers[queue].recycle(cmdb);
 }
 }
 
 
 Error CommandBufferFactory::newCommandBuffer(CommandBufferFlag cmdbFlags, MicroCommandBufferPtr& ptr)
 Error CommandBufferFactory::newCommandBuffer(CommandBufferFlag cmdbFlags, MicroCommandBufferPtr& ptr)
 {
 {
-	const Bool smallBatch = !!(cmdbFlags & CommandBufferFlag::kSmallBatch);
-	const GpuQueueType queue = !!(cmdbFlags & CommandBufferFlag::kGeneralWork) ? GpuQueueType::kCompute : GpuQueueType::kCompute;
+	const GpuQueueType queue = !!(cmdbFlags & CommandBufferFlag::kGeneralWork) ? GpuQueueType::kGeneral : GpuQueueType::kCompute;
 
 
-	MicroCommandBuffer* cmdb = m_recyclers[smallBatch][queue].findToReuse();
+	MicroCommandBuffer* cmdb = m_recyclers[queue].findToReuse();
 
 
 	if(cmdb == nullptr)
 	if(cmdb == nullptr)
 	{
 	{

+ 3 - 9
AnKi/Gr/D3D/D3DCommandBufferFactory.h

@@ -76,12 +76,7 @@ public:
 		pushToArray(m_objectRefs[T::kClassType], x);
 		pushToArray(m_objectRefs[T::kClassType], x);
 	}
 	}
 
 
-	void setBigBatch()
-	{
-		m_isSmallBatch = false;
-	}
-
-	ID3D12GraphicsCommandList6& getCmdList() const
+	D3D12GraphicsCommandListX& getCmdList() const
 	{
 	{
 		ANKI_ASSERT(m_cmdList);
 		ANKI_ASSERT(m_cmdList);
 		return *m_cmdList;
 		return *m_cmdList;
@@ -101,7 +96,6 @@ private:
 	static constexpr U32 kMaxRefObjectSearch = 16;
 	static constexpr U32 kMaxRefObjectSearch = 16;
 
 
 	mutable Atomic<I32> m_refcount = {0};
 	mutable Atomic<I32> m_refcount = {0};
-	Bool m_isSmallBatch = true;
 
 
 	MicroFencePtr m_fence;
 	MicroFencePtr m_fence;
 	Array<DynamicArray<GrObjectPtr, MemoryPoolPtrWrapper<StackMemoryPool>>, U(GrObjectType::kCount)> m_objectRefs;
 	Array<DynamicArray<GrObjectPtr, MemoryPoolPtrWrapper<StackMemoryPool>>, U(GrObjectType::kCount)> m_objectRefs;
@@ -109,7 +103,7 @@ private:
 	StackMemoryPool m_fastPool;
 	StackMemoryPool m_fastPool;
 
 
 	ID3D12CommandAllocator* m_cmdAllocator = nullptr;
 	ID3D12CommandAllocator* m_cmdAllocator = nullptr;
-	ID3D12GraphicsCommandList6* m_cmdList = nullptr;
+	D3D12GraphicsCommandListX* m_cmdList = nullptr;
 
 
 	void reset();
 	void reset();
 
 
@@ -165,7 +159,7 @@ public:
 	Error newCommandBuffer(CommandBufferFlag cmdbFlags, MicroCommandBufferPtr& ptr);
 	Error newCommandBuffer(CommandBufferFlag cmdbFlags, MicroCommandBufferPtr& ptr);
 
 
 private:
 private:
-	Array2d<MicroObjectRecycler<MicroCommandBuffer>, 2, U(GpuQueueType::kCount)> m_recyclers;
+	Array<MicroObjectRecycler<MicroCommandBuffer>, U(GpuQueueType::kCount)> m_recyclers;
 
 
 	void deleteCommandBuffer(MicroCommandBuffer* cmdb);
 	void deleteCommandBuffer(MicroCommandBuffer* cmdb);
 };
 };

+ 1 - 1
AnKi/Gr/D3D/D3DCommon.cpp

@@ -8,7 +8,7 @@
 
 
 namespace anki {
 namespace anki {
 
 
-ID3D12Device& getDevice()
+ID3D12DeviceX& getDevice()
 {
 {
 	return static_cast<GrManagerImpl&>(GrManager::getSingleton()).getDevice();
 	return static_cast<GrManagerImpl&>(GrManager::getSingleton()).getDevice();
 }
 }

+ 274 - 1
AnKi/Gr/D3D/D3DCommon.h

@@ -19,6 +19,7 @@
 
 
 #include <windows.h>
 #include <windows.h>
 #include <d3d12.h>
 #include <d3d12.h>
+#include <d3dx12/d3dx12_pipeline_state_stream.h>
 #include <dxgi1_6.h>
 #include <dxgi1_6.h>
 #include <dxgidebug.h>
 #include <dxgidebug.h>
 #include <D3Dcompiler.h>
 #include <D3Dcompiler.h>
@@ -63,6 +64,10 @@ namespace anki {
 		} \
 		} \
 	} while(0)
 	} while(0)
 
 
+// Globaly define the versions of some D3D objects
+using D3D12GraphicsCommandListX = ID3D12GraphicsCommandList9;
+using ID3D12DeviceX = ID3D12Device2;
+
 enum class D3DTextureViewType : U8
 enum class D3DTextureViewType : U8
 {
 {
 	kSrv,
 	kSrv,
@@ -114,7 +119,7 @@ void safeRelease(T*& p)
 	}
 	}
 }
 }
 
 
-ID3D12Device& getDevice();
+ID3D12DeviceX& getDevice();
 
 
 GrManagerImpl& getGrManagerImpl();
 GrManagerImpl& getGrManagerImpl();
 
 
@@ -180,6 +185,7 @@ inline [[nodiscard]] D3D12_TEXTURE_ADDRESS_MODE convertSamplingAddressing(Sampli
 inline [[nodiscard]] D3D12_COMPARISON_FUNC convertComparisonFunc(CompareOperation comp)
 inline [[nodiscard]] D3D12_COMPARISON_FUNC convertComparisonFunc(CompareOperation comp)
 {
 {
 	D3D12_COMPARISON_FUNC out = {};
 	D3D12_COMPARISON_FUNC out = {};
+
 	switch(comp)
 	switch(comp)
 	{
 	{
 	case CompareOperation::kAlways:
 	case CompareOperation::kAlways:
@@ -209,6 +215,273 @@ inline [[nodiscard]] D3D12_COMPARISON_FUNC convertComparisonFunc(CompareOperatio
 	default:
 	default:
 		ANKI_ASSERT(0);
 		ANKI_ASSERT(0);
 	}
 	}
+
+	return out;
+}
+
+inline [[nodiscard]] D3D12_PRIMITIVE_TOPOLOGY_TYPE convertPrimitiveTopology(PrimitiveTopology top)
+{
+	D3D12_PRIMITIVE_TOPOLOGY_TYPE out = D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED;
+
+	switch(top)
+	{
+	case PrimitiveTopology::kPoints:
+		out = D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT;
+		break;
+	case PrimitiveTopology::kLines:
+		out = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
+		break;
+	case PrimitiveTopology::kTriangles:
+		out = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	}
+
+	return out;
+}
+
+inline [[nodiscard]] D3D_PRIMITIVE_TOPOLOGY convertPrimitiveTopology2(PrimitiveTopology top)
+{
+	D3D_PRIMITIVE_TOPOLOGY out = {};
+
+	switch(top)
+	{
+	case PrimitiveTopology::kPoints:
+		out = D3D_PRIMITIVE_TOPOLOGY_POINTLIST;
+		break;
+	case PrimitiveTopology::kLines:
+		out = D3D_PRIMITIVE_TOPOLOGY_LINELIST;
+		break;
+	case PrimitiveTopology::kLineStip:
+		out = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP;
+		break;
+	case PrimitiveTopology::kTriangles:
+		out = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+		break;
+	case PrimitiveTopology::kTriangleStrip:
+		out = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
+		break;
+	case PrimitiveTopology::kPatches:
+		ANKI_ASSERT(!"TODO");
+		break;
+	default:
+		ANKI_ASSERT(0);
+	}
+
+	return out;
+}
+
+inline [[nodiscard]] D3D12_BLEND convertBlendFactor(BlendFactor f)
+{
+	D3D12_BLEND out = {};
+
+	switch(f)
+	{
+	case BlendFactor::kZero:
+		out = D3D12_BLEND_ZERO;
+		break;
+	case BlendFactor::kOne:
+		out = D3D12_BLEND_ONE;
+		break;
+	case BlendFactor::kSrcColor:
+		out = D3D12_BLEND_SRC_COLOR;
+		break;
+	case BlendFactor::kOneMinusSrcColor:
+		out = D3D12_BLEND_INV_SRC_COLOR;
+		break;
+	case BlendFactor::kDstColor:
+		out = D3D12_BLEND_DEST_COLOR;
+		break;
+	case BlendFactor::kOneMinusDstColor:
+		out = D3D12_BLEND_INV_DEST_COLOR;
+		break;
+	case BlendFactor::kSrcAlpha:
+		out = D3D12_BLEND_SRC_ALPHA;
+		break;
+	case BlendFactor::kOneMinusSrcAlpha:
+		out = D3D12_BLEND_INV_SRC_ALPHA;
+		break;
+	case BlendFactor::kDstAlpha:
+		out = D3D12_BLEND_DEST_ALPHA;
+		break;
+	case BlendFactor::kOneMinusDstAlpha:
+		out = D3D12_BLEND_INV_DEST_ALPHA;
+		break;
+	case BlendFactor::kConstantColor:
+		out = D3D12_BLEND_BLEND_FACTOR;
+		break;
+	case BlendFactor::kOneMinusConstantColor:
+		out = D3D12_BLEND_INV_BLEND_FACTOR;
+		break;
+	case BlendFactor::kConstantAlpha:
+		out = D3D12_BLEND_ALPHA_FACTOR;
+		break;
+	case BlendFactor::kOneMinusConstantAlpha:
+		out = D3D12_BLEND_INV_ALPHA_FACTOR;
+		break;
+	case BlendFactor::kSrcAlphaSaturate:
+		out = D3D12_BLEND_SRC_ALPHA_SAT;
+		break;
+	case BlendFactor::kSrc1Color:
+		out = D3D12_BLEND_SRC1_COLOR;
+		break;
+	case BlendFactor::kOneMinusSrc1Color:
+		out = D3D12_BLEND_INV_SRC1_COLOR;
+		break;
+	case BlendFactor::kSrc1Alpha:
+		out = D3D12_BLEND_SRC1_ALPHA;
+		break;
+	case BlendFactor::kOneMinusSrc1Alpha:
+		out = D3D12_BLEND_INV_SRC1_ALPHA;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	}
+
+	return out;
+}
+
+inline [[nodiscard]] D3D12_BLEND_OP convertBlendOperation(BlendOperation b)
+{
+	D3D12_BLEND_OP out = {};
+
+	switch(b)
+	{
+	case BlendOperation::kAdd:
+		out = D3D12_BLEND_OP_ADD;
+		break;
+	case BlendOperation::kSubtract:
+		out = D3D12_BLEND_OP_SUBTRACT;
+		break;
+	case BlendOperation::kReverseSubtract:
+		out = D3D12_BLEND_OP_REV_SUBTRACT;
+		break;
+	case BlendOperation::kMin:
+		out = D3D12_BLEND_OP_MIN;
+		break;
+	case BlendOperation::kMax:
+		out = D3D12_BLEND_OP_MAX;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	}
+
+	return out;
+}
+
+inline [[nodiscard]] D3D12_COMPARISON_FUNC convertCompareOperation(CompareOperation c)
+{
+	D3D12_COMPARISON_FUNC out = {};
+
+	switch(c)
+	{
+	case CompareOperation::kAlways:
+		out = D3D12_COMPARISON_FUNC_ALWAYS;
+		break;
+	case CompareOperation::kLess:
+		out = D3D12_COMPARISON_FUNC_LESS;
+		break;
+	case CompareOperation::kEqual:
+		out = D3D12_COMPARISON_FUNC_EQUAL;
+		break;
+	case CompareOperation::kLessEqual:
+		out = D3D12_COMPARISON_FUNC_LESS_EQUAL;
+		break;
+	case CompareOperation::kGreater:
+		out = D3D12_COMPARISON_FUNC_GREATER;
+		break;
+	case CompareOperation::kGreaterEqual:
+		out = D3D12_COMPARISON_FUNC_GREATER_EQUAL;
+		break;
+	case CompareOperation::kNotEqual:
+		out = D3D12_COMPARISON_FUNC_NOT_EQUAL;
+		break;
+	case CompareOperation::kNever:
+		out = D3D12_COMPARISON_FUNC_NEVER;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	}
+
+	return out;
+}
+
+inline [[nodiscard]] D3D12_STENCIL_OP convertStencilOperation(StencilOperation o)
+{
+	D3D12_STENCIL_OP out = {};
+
+	switch(o)
+	{
+	case StencilOperation::kKeep:
+		out = D3D12_STENCIL_OP_KEEP;
+		break;
+	case StencilOperation::kZero:
+		out = D3D12_STENCIL_OP_ZERO;
+		break;
+	case StencilOperation::kReplace:
+		out = D3D12_STENCIL_OP_REPLACE;
+		break;
+	case StencilOperation::kIncrementAndClamp:
+		out = D3D12_STENCIL_OP_INCR_SAT;
+		break;
+	case StencilOperation::kDecrementAndClamp:
+		out = D3D12_STENCIL_OP_DECR_SAT;
+		break;
+	case StencilOperation::kInvert:
+		out = D3D12_STENCIL_OP_INVERT;
+		break;
+	case StencilOperation::kIncrementAndWrap:
+		out = D3D12_STENCIL_OP_INCR;
+		break;
+	case StencilOperation::kDecrementAndWrap:
+		out = D3D12_STENCIL_OP_DECR;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	}
+
+	return out;
+}
+
+inline [[nodiscard]] D3D12_FILL_MODE convertFillMode(FillMode f)
+{
+	D3D12_FILL_MODE out = {};
+
+	switch(f)
+	{
+	case FillMode::kWireframe:
+		out = D3D12_FILL_MODE_WIREFRAME;
+		break;
+	case FillMode::kSolid:
+		out = D3D12_FILL_MODE_SOLID;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	}
+
+	return out;
+}
+
+inline [[nodiscard]] D3D12_CULL_MODE convertCullMode(FaceSelectionBit c)
+{
+	ANKI_ASSERT(c != FaceSelectionBit::kFrontAndBack);
+	D3D12_CULL_MODE out = {};
+
+	if(!!(c & FaceSelectionBit::kFront))
+	{
+		out = D3D12_CULL_MODE_FRONT;
+	}
+	else if(!!(c & FaceSelectionBit::kBack))
+	{
+		out = D3D12_CULL_MODE_BACK;
+	}
+	else
+	{
+		out = D3D12_CULL_MODE_NONE;
+	}
+
+	return out;
 }
 }
 /// @}
 /// @}
 
 

+ 3 - 1
AnKi/Gr/D3D/D3DGrManager.cpp

@@ -405,7 +405,9 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 	ANKI_D3D_LOGI("Vendor identified as %s", &kGPUVendorStrings[m_capabilities.m_gpuVendor][0]);
 	ANKI_D3D_LOGI("Vendor identified as %s", &kGPUVendorStrings[m_capabilities.m_gpuVendor][0]);
 
 
 	// Create device
 	// Create device
-	ANKI_D3D_CHECK(D3D12CreateDevice(adapters[chosenPhysDevIdx].m_adapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&m_device)));
+	ComPtr<ID3D12Device> dev;
+	ANKI_D3D_CHECK(D3D12CreateDevice(adapters[chosenPhysDevIdx].m_adapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&dev)));
+	ANKI_D3D_CHECK(dev->QueryInterface(IID_PPV_ARGS(&m_device)));
 
 
 	if(g_validationCVar.get())
 	if(g_validationCVar.get())
 	{
 	{

+ 2 - 2
AnKi/Gr/D3D/D3DGrManager.h

@@ -35,7 +35,7 @@ public:
 
 
 	Error initInternal(const GrManagerInitInfo& cfg);
 	Error initInternal(const GrManagerInitInfo& cfg);
 
 
-	ID3D12Device& getDevice()
+	ID3D12DeviceX& getDevice()
 	{
 	{
 		return *m_device;
 		return *m_device;
 	}
 	}
@@ -52,7 +52,7 @@ public:
 	}
 	}
 
 
 private:
 private:
-	ID3D12Device* m_device = nullptr;
+	ID3D12DeviceX* m_device = nullptr;
 	Array<ID3D12CommandQueue*, U32(GpuQueueType::kCount)> m_queues = {};
 	Array<ID3D12CommandQueue*, U32(GpuQueueType::kCount)> m_queues = {};
 
 
 	DWORD m_debugMessageCallbackCookie = 0;
 	DWORD m_debugMessageCallbackCookie = 0;

+ 391 - 0
AnKi/Gr/D3D/D3DGraphicsState.cpp

@@ -0,0 +1,391 @@
+// Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Gr/D3D/D3DGraphicsState.h>
+#include <AnKi/Gr/BackendCommon/Functions.h>
+
+namespace anki {
+
+static void getVertexAttributeSemanticInfo(VertexAttributeSemantic x, const Char*& str, U32& idx)
+{
+	switch(x)
+	{
+	case VertexAttributeSemantic::kPosition:
+		str = "POSITION";
+		idx = 0;
+		break;
+	case VertexAttributeSemantic::kNormal:
+		str = "NORMAL";
+		idx = 0;
+		break;
+	case VertexAttributeSemantic::kTexCoord:
+		str = "TEX_COORD";
+		idx = 0;
+		break;
+	case VertexAttributeSemantic::kColor:
+		str = "COLOR";
+		idx = 0;
+		break;
+	case VertexAttributeSemantic::kMisc0:
+		str = "MISC";
+		idx = 0;
+		break;
+	case VertexAttributeSemantic::kMisc1:
+		str = "MISC";
+		idx = 1;
+		break;
+	case VertexAttributeSemantic::kMisc2:
+		str = "MISC";
+		idx = 2;
+		break;
+	case VertexAttributeSemantic::kMisc3:
+		str = "MISC";
+		idx = 3;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	};
+}
+
+Bool GraphicsStateTracker::updateHashes()
+{
+	if(m_hashes.m_vert == 0)
+	{
+		if(m_state.m_vert.m_activeAttribs.getAnySet())
+		{
+			m_hashes.m_vert = 0xC0FEE;
+
+			for(VertexAttributeSemantic i : EnumIterable<VertexAttributeSemantic>())
+			{
+				if(!m_state.m_vert.m_activeAttribs.get(i))
+				{
+					continue;
+				}
+
+				ANKI_ASSERT(m_state.m_vert.m_attribsSetMask.get(i) && "Forgot to set the vert attribute");
+				m_hashes.m_vert = appendObjectHash(m_state.m_vert.m_attribs[i], m_hashes.m_vert);
+
+				ANKI_ASSERT(m_state.m_vert.m_bindingsSetMask.get(m_state.m_vert.m_attribs[i].m_binding) && "Forgot to inform about the vert binding");
+				m_hashes.m_vert = appendObjectHash(m_state.m_vert.m_bindings[m_state.m_vert.m_attribs[i].m_binding], m_hashes.m_vert);
+			}
+		}
+		else
+		{
+			m_hashes.m_vert = 0xC0FEE;
+		}
+	}
+
+	if(m_hashes.m_rast == 0)
+	{
+		m_hashes.m_rast = computeObjectHash(&m_state.m_rast);
+	}
+
+	if(m_hashes.m_depthStencil == 0)
+	{
+		const Bool hasStencil =
+			m_state.m_misc.m_depthStencilFormat != Format::kNone && getFormatInfo(m_state.m_misc.m_depthStencilFormat).isStencil();
+
+		const Bool hasDepth = m_state.m_misc.m_depthStencilFormat != Format::kNone && getFormatInfo(m_state.m_misc.m_depthStencilFormat).isDepth();
+
+		m_hashes.m_depthStencil = 0xC0FEE;
+
+		if(hasStencil)
+		{
+			m_hashes.m_depthStencil = appendObjectHash(m_state.m_stencil, m_hashes.m_depthStencil);
+		}
+
+		if(hasDepth)
+		{
+			m_hashes.m_depthStencil = appendObjectHash(m_state.m_depth, m_hashes.m_depthStencil);
+		}
+	}
+
+	if(m_hashes.m_blend == 0)
+	{
+		if(m_state.m_misc.m_colorRtMask.getAnySet())
+		{
+			m_hashes.m_blend = m_state.m_blend.m_alphaToCoverage;
+
+			for(U32 i = 0; i < kMaxColorRenderTargets; ++i)
+			{
+				if(m_state.m_misc.m_colorRtMask.get(i))
+				{
+					m_hashes.m_blend = appendObjectHash(m_state.m_blend.m_colorRts[i], m_hashes.m_blend);
+				}
+			}
+		}
+		else
+		{
+			m_hashes.m_blend = 0xC0FFE;
+		}
+	}
+
+	if(m_hashes.m_misc == 0)
+	{
+		Array<U32, kMaxColorRenderTargets + 3> toHash;
+		U32 toHashCount = 0;
+
+		toHash[toHashCount++] = m_state.m_misc.m_colorRtMask.getData()[0];
+		for(U32 i = 0; i < kMaxColorRenderTargets; ++i)
+		{
+			if(m_state.m_misc.m_colorRtMask.get(i))
+			{
+				toHash[toHashCount++] = U32(m_state.m_misc.m_colorRtFormats[i]);
+			}
+		}
+
+		if(m_state.m_misc.m_depthStencilFormat != Format::kNone)
+		{
+			toHash[toHashCount++] = U32(m_state.m_misc.m_depthStencilFormat);
+		}
+
+		toHash[toHashCount++] = U32(m_state.m_misc.m_topology);
+
+		m_hashes.m_misc = computeHash(toHash.getBegin(), sizeof(toHash[0]) * toHashCount);
+	}
+
+	if(m_hashes.m_shaderProg == 0)
+	{
+		m_hashes.m_shaderProg = m_state.m_shaderProg->getUuid();
+	}
+
+	// Compute complete hash
+	const U64 globalHash = computeObjectHash(m_hashes);
+
+	if(globalHash != m_globalHash)
+	{
+		m_globalHash = globalHash;
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+GraphicsPipelineFactory::~GraphicsPipelineFactory()
+{
+	for(auto pso : m_map)
+	{
+		safeRelease(pso);
+	}
+}
+
+void GraphicsPipelineFactory::flushState(GraphicsStateTracker& state, D3D12GraphicsCommandListX& cmdList)
+{
+	// Set dynamic state
+	if(state.m_dynState.m_stencilRefMaskDirty && state.m_state.m_misc.m_depthStencilFormat != Format::kNone
+	   && getFormatInfo(state.m_state.m_misc.m_depthStencilFormat).isStencil())
+	{
+		state.m_dynState.m_stencilRefMaskDirty = false;
+		cmdList.OMSetStencilRef(state.m_dynState.m_stencilRefMask);
+	}
+
+	if(state.m_dynState.m_topologyDirty)
+	{
+		state.m_dynState.m_topologyDirty = false;
+		cmdList.IASetPrimitiveTopology(convertPrimitiveTopology2(state.m_dynState.m_topology));
+	}
+
+	if(state.m_dynState.m_viewportDirty)
+	{
+		state.m_dynState.m_viewportDirty = false;
+		const D3D12_VIEWPORT vp = {.TopLeftX = F32(state.m_dynState.m_viewport[0]),
+								   .TopLeftY = F32(state.m_dynState.m_viewport[1]),
+								   .Width = F32(state.m_dynState.m_viewport[2]),
+								   .Height = F32(state.m_dynState.m_viewport[3]),
+								   .MinDepth = 0.0f,
+								   .MaxDepth = 1.0f};
+		cmdList.RSSetViewports(1, &vp);
+	}
+
+	if(state.m_dynState.m_scissorDirty)
+	{
+		state.m_dynState.m_scissorDirty = false;
+
+		const U32 minx = max(state.m_dynState.m_scissor[0], state.m_dynState.m_viewport[0]);
+		const U32 miny = max(state.m_dynState.m_scissor[1], state.m_dynState.m_viewport[1]);
+		const U32 right =
+			min(state.m_dynState.m_scissor[0] + state.m_dynState.m_scissor[2], state.m_dynState.m_viewport[0] + state.m_dynState.m_viewport[2]);
+		const U32 bottom =
+			min(state.m_dynState.m_scissor[1] + state.m_dynState.m_scissor[3], state.m_dynState.m_viewport[1] + state.m_dynState.m_viewport[3]);
+
+		const D3D12_RECT rect = {.left = I32(minx), .top = I32(miny), .right = I32(right), .bottom = I32(bottom)};
+		cmdList.RSSetScissorRects(1, &rect);
+	}
+
+	const Bool rebindPso = state.updateHashes();
+
+	// Find the PSO
+	ID3D12PipelineState* pso = nullptr;
+	{
+		RLockGuard lock(m_mtx);
+
+		auto it = m_map.find(state.m_globalHash);
+		if(it != m_map.getEnd())
+		{
+			pso = *it;
+		}
+	}
+
+	if(pso) [[likely]]
+	{
+		if(rebindPso)
+		{
+			cmdList.SetPipelineState(pso);
+		}
+
+		return;
+	}
+
+	// PSO not found, proactively create it WITHOUT a lock (we dont't want to serialize pipeline creation)
+
+	const ShaderProgramImpl& prog = *state.m_state.m_shaderProg;
+
+	// Vertex input
+	Array<D3D12_INPUT_ELEMENT_DESC, U32(VertexAttributeSemantic::kCount)> inputElementDescs;
+	U32 inputElementDescCount = 0;
+	for(VertexAttributeSemantic i : EnumIterable<VertexAttributeSemantic>())
+	{
+		if(state.m_state.m_vert.m_activeAttribs.get(i))
+		{
+			D3D12_INPUT_ELEMENT_DESC& elem = inputElementDescs[inputElementDescCount++];
+
+			getVertexAttributeSemanticInfo(i, elem.SemanticName, elem.SemanticIndex);
+			elem.Format = DXGI_FORMAT(state.m_state.m_vert.m_attribs[i].m_fmt);
+			elem.InputSlot = state.m_state.m_vert.m_attribs[i].m_binding;
+			elem.AlignedByteOffset = state.m_state.m_vert.m_attribs[i].m_relativeOffset;
+			elem.InputSlotClass = (state.m_state.m_vert.m_bindings[state.m_state.m_vert.m_attribs[i].m_binding].m_stepRate == VertexStepRate::kVertex)
+									  ? D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA
+									  : D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA;
+			elem.InstanceDataStepRate = (elem.InputSlotClass == D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA) ? 0 : 1;
+		}
+	}
+
+	// Blending
+	D3D12_BLEND_DESC blendDesc = {.AlphaToCoverageEnable = state.m_state.m_blend.m_alphaToCoverage, .IndependentBlendEnable = true};
+	for(U32 i = 0; i < kMaxColorRenderTargets; ++i)
+	{
+		if(state.m_state.m_misc.m_colorRtMask.get(i))
+		{
+			const auto& in = state.m_state.m_blend.m_colorRts[i];
+			D3D12_RENDER_TARGET_BLEND_DESC& out = blendDesc.RenderTarget[i];
+			out.BlendEnable = blendingDisabled(in.m_srcRgb, in.m_dstRgb, in.m_srcA, in.m_dstA, in.m_funcRgb, in.m_funcA);
+			out.SrcBlend = convertBlendFactor(in.m_srcRgb);
+			out.DestBlend = convertBlendFactor(in.m_dstRgb);
+			out.BlendOp = convertBlendOperation(in.m_funcRgb);
+			out.SrcBlendAlpha = convertBlendFactor(in.m_srcA);
+			out.DestBlendAlpha = convertBlendFactor(in.m_dstA);
+			out.BlendOpAlpha = convertBlendOperation(in.m_funcA);
+			out.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
+		}
+	}
+
+	// DS
+	D3D12_DEPTH_STENCIL_DESC2 dsDesc = {};
+	if(state.m_state.m_misc.m_depthStencilFormat != Format::kNone)
+	{
+		const Bool stencilEnabled = !stencilTestDisabled(state.m_state.m_stencil.m_fail[0], state.m_state.m_stencil.m_stencilPassDepthFail[0],
+														 state.m_state.m_stencil.m_stencilPassDepthPass[0], state.m_state.m_stencil.m_compare[0])
+									|| !stencilTestDisabled(state.m_state.m_stencil.m_fail[1], state.m_state.m_stencil.m_stencilPassDepthFail[1],
+															state.m_state.m_stencil.m_stencilPassDepthPass[1], state.m_state.m_stencil.m_compare[1]);
+
+		Array<D3D12_DEPTH_STENCILOP_DESC1, 2> stencilDescs;
+		for(U32 w = 0; w < 2; ++w)
+		{
+			stencilDescs[w].StencilFailOp = convertStencilOperation(state.m_state.m_stencil.m_fail[w]);
+			stencilDescs[w].StencilDepthFailOp = convertStencilOperation(state.m_state.m_stencil.m_stencilPassDepthFail[w]);
+			stencilDescs[w].StencilPassOp = convertStencilOperation(state.m_state.m_stencil.m_stencilPassDepthPass[w]);
+			stencilDescs[w].StencilFunc = convertComparisonFunc(state.m_state.m_stencil.m_compare[w]);
+			stencilDescs[w].StencilReadMask = U8(state.m_state.m_stencil.m_compareMask[w]);
+			stencilDescs[w].StencilWriteMask = U8(state.m_state.m_stencil.m_writeMask[w]);
+		}
+
+		dsDesc = {.DepthEnable = state.m_state.m_depth.m_compare != CompareOperation::kAlways || state.m_state.m_depth.m_writeEnabled,
+				  .DepthWriteMask = state.m_state.m_depth.m_writeEnabled ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO,
+				  .DepthFunc = convertCompareOperation(state.m_state.m_depth.m_compare),
+				  .StencilEnable = stencilEnabled,
+				  .FrontFace = stencilDescs[0],
+				  .BackFace = stencilDescs[1],
+				  .DepthBoundsTestEnable = false};
+	}
+
+	// Rast state
+	const D3D12_RASTERIZER_DESC2 rastDesc = {.FillMode = convertFillMode(state.m_state.m_rast.m_fillMode),
+											 .CullMode = convertCullMode(state.m_state.m_rast.m_cullMode),
+											 .FrontCounterClockwise = true,
+											 .DepthBias = state.m_state.m_rast.m_depthBias,
+											 .DepthBiasClamp = state.m_state.m_rast.m_depthBiasClamp,
+											 .SlopeScaledDepthBias = state.m_state.m_rast.m_slopeScaledDepthBias};
+
+	// Misc
+	D3D12_RT_FORMAT_ARRAY rtFormats = {};
+	for(U32 i = 0; i < kMaxColorRenderTargets; ++i)
+	{
+		if(state.m_state.m_misc.m_colorRtMask.get(i))
+		{
+			rtFormats.RTFormats[i] = DXGI_FORMAT(state.m_state.m_misc.m_colorRtFormats[i]);
+			rtFormats.NumRenderTargets = i + 1;
+		}
+	}
+
+	const DXGI_SAMPLE_DESC sampleDesc = {.Count = 1, .Quality = 0};
+
+	// Main descriptor
+#define ANKI_SET_IR(member, stage) \
+	if(!!(prog.getShaderTypes() & ShaderTypeBit::stage)) \
+	{ \
+		desc.member = prog.m_graphics.m_shaderCreateInfos[ShaderType::stage]; \
+	}
+
+	CD3DX12_PIPELINE_STATE_STREAM5 desc = {};
+	desc.Flags = D3D12_PIPELINE_STATE_FLAG_DYNAMIC_DEPTH_BIAS;
+	desc.pRootSignature = &state.m_state.m_shaderProg->m_rootSignature->getD3DRootSignature();
+	desc.InputLayout = D3D12_INPUT_LAYOUT_DESC{.pInputElementDescs = inputElementDescs.getBegin(), .NumElements = inputElementDescCount};
+	desc.PrimitiveTopologyType = convertPrimitiveTopology(state.m_state.m_misc.m_topology);
+	ANKI_SET_IR(VS, kVertex)
+	ANKI_SET_IR(GS, kGeometry)
+	ANKI_SET_IR(HS, kTessellationControl)
+	ANKI_SET_IR(DS, kTessellationEvaluation)
+	ANKI_SET_IR(PS, kFragment)
+	ANKI_SET_IR(AS, kTask)
+	ANKI_SET_IR(MS, kMesh)
+	desc.BlendState = CD3DX12_BLEND_DESC(blendDesc);
+	desc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC2(dsDesc);
+	desc.DSVFormat = DXGI_FORMAT(state.m_state.m_misc.m_depthStencilFormat);
+	desc.RasterizerState = CD3DX12_RASTERIZER_DESC2(rastDesc);
+	desc.RTVFormats = rtFormats;
+	desc.SampleDesc = sampleDesc;
+	desc.SampleMask = kMaxU32;
+
+#undef ANKI_SET_IR
+
+	// Create the PSO
+	const D3D12_PIPELINE_STATE_STREAM_DESC streamDec = {.SizeInBytes = sizeof(desc), .pPipelineStateSubobjectStream = &desc};
+	ANKI_D3D_CHECKF(getDevice().CreatePipelineState(&streamDec, IID_PPV_ARGS(&pso)));
+
+	// Now try to add the PSO to the hashmap
+	{
+		WLockGuard lock(m_mtx);
+
+		auto it = m_map.find(state.m_globalHash);
+		if(it == m_map.getEnd())
+		{
+			// Not found, add it
+			m_map.emplace(state.m_globalHash, pso);
+		}
+		else
+		{
+			// Found, remove the PSO that was proactively created and use the old one
+			safeRelease(pso);
+			pso = *it;
+		}
+	}
+
+	// Final thing, bind the PSO
+	cmdList.SetPipelineState(pso);
+}
+
+} // end namespace anki

+ 451 - 0
AnKi/Gr/D3D/D3DGraphicsState.h

@@ -0,0 +1,451 @@
+// Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Gr/D3D/D3DCommon.h>
+#include <AnKi/Gr/D3D/D3DShaderProgram.h>
+#include <AnKi/Util/WeakArray.h>
+#include <AnKi/Util/HashMap.h>
+
+namespace anki {
+
+/// @addtogroup d3d
+/// @{
+
+/// This is the way the command buffer sets the graphics state.
+class GraphicsStateTracker
+{
+	friend class GraphicsPipelineFactory;
+
+public:
+	void bindVertexBuffer(U32 binding, VertexStepRate stepRate)
+	{
+		if(!m_state.m_vert.m_bindingsSetMask.get(binding) || m_state.m_vert.m_bindings[binding].m_stepRate != stepRate)
+		{
+			m_state.m_vert.m_bindingsSetMask.set(binding);
+			m_state.m_vert.m_bindings[binding].m_stepRate = stepRate;
+			m_hashes.m_vert = 0;
+		}
+	}
+
+	void setVertexAttribute(VertexAttributeSemantic attribute, U32 buffBinding, Format fmt, U32 relativeOffset)
+	{
+		auto& attr = m_state.m_vert.m_attribs[attribute];
+		if(!m_state.m_vert.m_attribsSetMask.get(attribute) || attr.m_fmt != fmt || attr.m_binding != buffBinding
+		   || attr.m_relativeOffset != relativeOffset)
+		{
+			attr.m_fmt = fmt;
+			attr.m_binding = buffBinding;
+			attr.m_relativeOffset = relativeOffset;
+			m_state.m_vert.m_attribsSetMask.set(attribute);
+			m_hashes.m_vert = 0;
+		}
+	}
+
+	void setFillMode(FillMode mode)
+	{
+		ANKI_ASSERT(mode < FillMode::kCount);
+		if(m_state.m_rast.m_fillMode != mode)
+		{
+			m_state.m_rast.m_fillMode = mode;
+			m_hashes.m_rast = 0;
+		}
+	}
+
+	void setCullMode(FaceSelectionBit mode)
+	{
+		if(m_state.m_rast.m_cullMode != mode)
+		{
+			m_state.m_rast.m_cullMode = mode;
+			m_hashes.m_rast = 0;
+		}
+	}
+
+	void setStencilOperations(FaceSelectionBit face, StencilOperation stencilFail, StencilOperation stencilPassDepthFail,
+							  StencilOperation stencilPassDepthPass)
+	{
+		ANKI_ASSERT(face != FaceSelectionBit::kNone);
+		if(!!(face & FaceSelectionBit::kFront)
+		   && (m_state.m_stencil.m_fail[0] != stencilFail || m_state.m_stencil.m_stencilPassDepthFail[0] != stencilPassDepthFail
+			   || m_state.m_stencil.m_stencilPassDepthPass[0] != stencilPassDepthPass))
+		{
+			m_state.m_stencil.m_fail[0] = stencilFail;
+			m_state.m_stencil.m_stencilPassDepthFail[0] = stencilPassDepthFail;
+			m_state.m_stencil.m_stencilPassDepthPass[0] = stencilPassDepthPass;
+			m_hashes.m_depthStencil = 0;
+		}
+
+		if(!!(face & FaceSelectionBit::kBack)
+		   && (m_state.m_stencil.m_fail[1] != stencilFail || m_state.m_stencil.m_stencilPassDepthFail[1] != stencilPassDepthFail
+			   || m_state.m_stencil.m_stencilPassDepthPass[1] != stencilPassDepthPass))
+		{
+			m_state.m_stencil.m_fail[1] = stencilFail;
+			m_state.m_stencil.m_stencilPassDepthFail[1] = stencilPassDepthFail;
+			m_state.m_stencil.m_stencilPassDepthPass[1] = stencilPassDepthPass;
+			m_hashes.m_depthStencil = 0;
+		}
+	}
+
+	void setStencilCompareOperation(FaceSelectionBit face, CompareOperation comp)
+	{
+		ANKI_ASSERT(face != FaceSelectionBit::kNone);
+		if(!!(face & FaceSelectionBit::kFront) && m_state.m_stencil.m_compare[0] != comp)
+		{
+			m_state.m_stencil.m_compare[0] = comp;
+			m_hashes.m_depthStencil = 0;
+		}
+
+		if(!!(face & FaceSelectionBit::kBack) && m_state.m_stencil.m_compare[1] != comp)
+		{
+			m_state.m_stencil.m_compare[1] = comp;
+			m_hashes.m_depthStencil = 0;
+		}
+	}
+
+	void setStencilCompareMask(FaceSelectionBit face, U32 mask)
+	{
+		ANKI_ASSERT(face != FaceSelectionBit::kNone);
+		if(!!(face & FaceSelectionBit::kFront) && m_state.m_stencil.m_compareMask[0] != mask)
+		{
+			m_state.m_stencil.m_compareMask[0] = mask;
+			m_hashes.m_depthStencil = 0;
+		}
+
+		if(!!(face & FaceSelectionBit::kBack) && m_state.m_stencil.m_compareMask[1] != mask)
+		{
+			m_state.m_stencil.m_compareMask[1] = mask;
+			m_hashes.m_depthStencil = 0;
+		}
+	}
+
+	void setStencilWriteMask(FaceSelectionBit face, U32 mask)
+	{
+		ANKI_ASSERT(face != FaceSelectionBit::kNone);
+		if(!!(face & FaceSelectionBit::kFront) && m_state.m_stencil.m_writeMask[0] != mask)
+		{
+			m_state.m_stencil.m_writeMask[0] = mask;
+			m_hashes.m_depthStencil = 0;
+		}
+
+		if(!!(face & FaceSelectionBit::kBack) && m_state.m_stencil.m_writeMask[1] != mask)
+		{
+			m_state.m_stencil.m_writeMask[1] = mask;
+			m_hashes.m_depthStencil = 0;
+		}
+	}
+
+	void setStencilReference([[maybe_unused]] FaceSelectionBit face, U32 ref)
+	{
+		ANKI_ASSERT(face == FaceSelectionBit::kFrontAndBack && "D3D only supports a single value for both sides");
+		m_dynState.m_stencilRefMask = ref;
+		m_dynState.m_stencilRefMaskDirty = true;
+	}
+
+	void setDepthWrite(Bool enable)
+	{
+		if(m_state.m_depth.m_writeEnabled != enable)
+		{
+			m_state.m_depth.m_writeEnabled = enable;
+			m_hashes.m_depthStencil = 0;
+		}
+	}
+
+	void setDepthCompareOperation(CompareOperation op)
+	{
+		ANKI_ASSERT(op < CompareOperation::kCount);
+		if(m_state.m_depth.m_compare != op)
+		{
+			m_state.m_depth.m_compare = op;
+			m_hashes.m_depthStencil = 0;
+		}
+	}
+
+	void setPolygonOffset(F32 factor, F32 units)
+	{
+		if(m_state.m_rast.m_depthBias != factor || m_state.m_rast.m_slopeScaledDepthBias != factor)
+		{
+			m_state.m_rast.m_depthBias = factor;
+			m_state.m_rast.m_slopeScaledDepthBias = units;
+			m_state.m_rast.m_depthBiasClamp = 0.0f;
+			m_hashes.m_rast = 0;
+		}
+	}
+
+	void setAlphaToCoverage(Bool enable)
+	{
+		if(m_state.m_blend.m_alphaToCoverage != enable)
+		{
+			m_state.m_blend.m_alphaToCoverage = enable;
+			m_hashes.m_blend = 0;
+		}
+	}
+
+	void setColorChannelWriteMask(U32 attachment, ColorBit mask)
+	{
+		if(m_state.m_blend.m_colorRts[attachment].m_colorWritemask != mask)
+		{
+			m_state.m_blend.m_colorRts[attachment].m_colorWritemask = mask;
+			m_hashes.m_blend = 0;
+		}
+	}
+
+	void setBlendFactors(U32 attachment, BlendFactor srcRgb, BlendFactor dstRgb, BlendFactor srcA, BlendFactor dstA)
+	{
+		auto& rt = m_state.m_blend.m_colorRts[attachment];
+		if(rt.m_srcRgb != srcRgb || rt.m_srcA != srcA || rt.m_dstRgb != dstRgb || rt.m_dstA != dstA)
+		{
+			rt.m_srcRgb = srcRgb;
+			rt.m_srcA = srcA;
+			rt.m_dstRgb = dstRgb;
+			rt.m_dstA = dstA;
+			m_hashes.m_blend = 0;
+		}
+	}
+
+	void setBlendOperation(U32 attachment, BlendOperation funcRgb, BlendOperation funcA)
+	{
+		auto& rt = m_state.m_blend.m_colorRts[attachment];
+		if(rt.m_funcRgb != funcRgb || rt.m_funcA != funcA)
+		{
+			rt.m_funcRgb = funcRgb;
+			rt.m_funcA = funcA;
+			m_hashes.m_blend = 0;
+		}
+	}
+
+	void beginRenderPass(ConstWeakArray<Format> colorFormats, Format depthStencilFormat, U32 rtsWidth, U32 rtsHeight)
+	{
+		m_state.m_misc.m_colorRtMask.unsetAll();
+		for(U8 i = 0; i < colorFormats.getSize(); ++i)
+		{
+			ANKI_ASSERT(colorFormats[i] != Format::kNone);
+			m_state.m_misc.m_colorRtFormats[i] = colorFormats[i];
+			m_state.m_misc.m_colorRtMask.set(i);
+		}
+		m_state.m_misc.m_depthStencilFormat = depthStencilFormat;
+		m_hashes.m_misc = 0; // Always mark it dirty because calling beginRenderPass is a rare occurance and we want to avoid extra checks
+
+		m_rtsWidth = rtsWidth;
+		m_rtsHeight = rtsHeight;
+
+		// Scissor needs to be re-adjusted if it's in its default size
+		m_dynState.m_scissorDirty = true;
+	}
+
+	void bindShaderProgram(const ShaderProgramImpl* prog)
+	{
+		ANKI_ASSERT(prog);
+		const ShaderReflection& refl = prog->m_refl;
+		if(m_state.m_vert.m_activeAttribs != refl.m_vertex.m_vertexAttributeMask)
+		{
+			m_state.m_vert.m_activeAttribs = refl.m_vertex.m_vertexAttributeMask;
+			m_hashes.m_vert = 0;
+		}
+
+		if(m_state.m_misc.m_colorRtMask != refl.m_fragment.m_colorAttachmentWritemask)
+		{
+			m_state.m_misc.m_colorRtMask = refl.m_fragment.m_colorAttachmentWritemask;
+			m_hashes.m_misc = 0;
+		}
+
+		if(m_state.m_shaderProg != prog)
+		{
+			m_state.m_shaderProg = prog;
+			m_hashes.m_shaderProg = 0;
+		}
+	}
+
+	void setPrimitiveTopology(PrimitiveTopology topology)
+	{
+		if(m_state.m_misc.m_topology != topology)
+		{
+			m_state.m_misc.m_topology = topology;
+			m_hashes.m_misc = 0;
+			m_dynState.m_topology = topology;
+			m_dynState.m_topologyDirty = true;
+		}
+	}
+
+	void setViewport(U32 minx, U32 miny, U32 width, U32 height)
+	{
+		if(m_dynState.m_viewport[0] != minx || m_dynState.m_viewport[1] != miny || m_dynState.m_viewport[2] != width
+		   || m_dynState.m_viewport[3] != height)
+		{
+			m_dynState.m_viewport[0] = minx;
+			m_dynState.m_viewport[1] = miny;
+			m_dynState.m_viewport[2] = width;
+			m_dynState.m_viewport[3] = height;
+			m_dynState.m_viewportDirty = true;
+		}
+	}
+
+	void setScissor(U32 minx, U32 miny, U32 width, U32 height)
+	{
+		if(m_dynState.m_scissor[0] != minx || m_dynState.m_scissor[1] != miny || m_dynState.m_scissor[2] != width
+		   || m_dynState.m_scissor[3] != height)
+		{
+			m_dynState.m_scissor[0] = minx;
+			m_dynState.m_scissor[1] = miny;
+			m_dynState.m_scissor[2] = width;
+			m_dynState.m_scissor[3] = height;
+			m_dynState.m_scissorDirty = true;
+		}
+	}
+
+	const ShaderProgramImpl& getShaderProgram() const
+	{
+		ANKI_ASSERT(m_state.m_shaderProg);
+		return *m_state.m_shaderProg;
+	}
+
+private:
+	// The state vector
+	ANKI_BEGIN_PACKED_STRUCT
+	class State
+	{
+	public:
+		class
+		{
+		public:
+			class VertBinding
+			{
+			public:
+				VertexStepRate m_stepRate;
+
+				VertBinding()
+				{
+					// No init for opt
+				}
+			};
+
+			class VertAttrib
+			{
+			public:
+				U32 m_relativeOffset;
+				Format m_fmt;
+				U32 m_binding;
+
+				VertAttrib()
+				{
+					// No init for opt
+				}
+			};
+
+			Array<VertBinding, U32(VertexAttributeSemantic::kCount)> m_bindings;
+			Array<VertAttrib, U32(VertexAttributeSemantic::kCount)> m_attribs;
+			BitSet<U32(VertexAttributeSemantic::kCount)> m_activeAttribs = false;
+
+			BitSet<U32(VertexAttributeSemantic::kCount)> m_bindingsSetMask = false;
+			BitSet<U32(VertexAttributeSemantic::kCount)> m_attribsSetMask = false;
+		} m_vert;
+
+		class
+		{
+		public:
+			FillMode m_fillMode = FillMode::kSolid;
+			FaceSelectionBit m_cullMode = FaceSelectionBit::kBack;
+			F32 m_depthBias = 0.0f;
+			F32 m_depthBiasClamp = 0.0f;
+			F32 m_slopeScaledDepthBias = 0.0f;
+		} m_rast;
+
+		class
+		{
+		public:
+			Array<StencilOperation, 2> m_fail = {StencilOperation::kKeep, StencilOperation::kKeep};
+			Array<StencilOperation, 2> m_stencilPassDepthFail = {StencilOperation::kKeep, StencilOperation::kKeep};
+			Array<StencilOperation, 2> m_stencilPassDepthPass = {StencilOperation::kKeep, StencilOperation::kKeep};
+			Array<CompareOperation, 2> m_compare = {CompareOperation::kAlways, CompareOperation::kAlways};
+			Array<U32, 2> m_compareMask = {0x5A5A5A5A, 0x5A5A5A5A}; ///< Use a stupid number to initialize.
+			Array<U32, 2> m_writeMask = {0x5A5A5A5A, 0x5A5A5A5A}; ///< Use a stupid number to initialize.
+		} m_stencil;
+
+		class
+		{
+		public:
+			CompareOperation m_compare = CompareOperation::kLess;
+			Bool m_writeEnabled = true;
+		} m_depth;
+
+		class
+		{
+		public:
+			class Rt
+			{
+			public:
+				ColorBit m_colorWritemask = ColorBit::kAll;
+				BlendFactor m_srcRgb = BlendFactor::kOne;
+				BlendFactor m_dstRgb = BlendFactor::kOne;
+				BlendFactor m_srcA = BlendFactor::kZero;
+				BlendFactor m_dstA = BlendFactor::kZero;
+				BlendOperation m_funcRgb = BlendOperation::kAdd;
+				BlendOperation m_funcA = BlendOperation::kAdd;
+			};
+
+			Array<Rt, kMaxColorRenderTargets> m_colorRts;
+			Bool m_alphaToCoverage = false;
+		} m_blend;
+
+		class
+		{
+		public:
+			Array<Format, kMaxColorRenderTargets> m_colorRtFormats = {};
+			Format m_depthStencilFormat = Format::kNone;
+			BitSet<kMaxColorRenderTargets> m_colorRtMask = {false};
+			PrimitiveTopology m_topology = PrimitiveTopology::kTriangles;
+		} m_misc;
+
+		const ShaderProgramImpl* m_shaderProg = nullptr;
+	} m_state;
+	ANKI_END_PACKED_STRUCT
+
+	class DynamicState
+	{
+	public:
+		U32 m_stencilRefMask = 0x5A5A5A5A; ///< Use a stupid number to initialize.
+		PrimitiveTopology m_topology = PrimitiveTopology::kTriangles; ///< Yes, it's both dynamic and static state
+
+		Array<U32, 4> m_viewport = {};
+		Array<U32, 4> m_scissor = {0, 0, kMaxU32, kMaxU32};
+
+		Bool m_stencilRefMaskDirty : 1 = true;
+		Bool m_topologyDirty : 1 = true;
+		Bool m_viewportDirty : 1 = true;
+		Bool m_scissorDirty : 1 = true;
+	} m_dynState;
+
+	class Hashes
+	{
+	public:
+		U64 m_vert = 0;
+		U64 m_rast = 0;
+		U64 m_depthStencil = 0;
+		U64 m_blend = 0;
+		U64 m_misc = 0;
+		U64 m_shaderProg = 0;
+	} m_hashes;
+
+	U64 m_globalHash = 0;
+	U32 m_rtsWidth = 0;
+	U32 m_rtsHeight = 0;
+
+	Bool updateHashes();
+};
+
+class GraphicsPipelineFactory
+{
+public:
+	~GraphicsPipelineFactory();
+
+	/// Write state to the command buffer.
+	/// @note It's thread-safe.
+	void flushState(GraphicsStateTracker& state, D3D12GraphicsCommandListX& cmdList);
+
+private:
+	GrHashMap<U64, ID3D12PipelineState*> m_map;
+	RWMutex m_mtx;
+};
+/// @}
+
+} // end namespace anki

+ 1 - 1
AnKi/Gr/D3D/D3DPipelineQuery.h

@@ -9,7 +9,7 @@
 
 
 namespace anki {
 namespace anki {
 
 
-/// @addtogroup vulkan
+/// @addtogroup d3d
 /// @{
 /// @{
 
 
 /// Pipeline query.
 /// Pipeline query.

+ 41 - 8
AnKi/Gr/D3D/D3DShaderProgram.cpp

@@ -7,6 +7,7 @@
 #include <AnKi/Gr/D3D/D3DShader.h>
 #include <AnKi/Gr/D3D/D3DShader.h>
 #include <AnKi/Gr/BackendCommon/Functions.h>
 #include <AnKi/Gr/BackendCommon/Functions.h>
 #include <AnKi/Gr/D3D/D3DDescriptor.h>
 #include <AnKi/Gr/D3D/D3DDescriptor.h>
+#include <AnKi/Gr/D3D/D3DGraphicsState.h>
 
 
 namespace anki {
 namespace anki {
 
 
@@ -38,6 +39,8 @@ Buffer& ShaderProgram::getShaderGroupHandlesGpuBuffer() const
 ShaderProgramImpl::~ShaderProgramImpl()
 ShaderProgramImpl::~ShaderProgramImpl()
 {
 {
 	safeRelease(m_compute.m_pipelineState);
 	safeRelease(m_compute.m_pipelineState);
+
+	deleteInstance(GrMemoryPool::getSingleton(), m_graphics.m_pipelineFactory);
 }
 }
 
 
 Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
@@ -45,11 +48,20 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 	ANKI_ASSERT(inf.isValid());
 	ANKI_ASSERT(inf.isValid());
 
 
 	// Create the shader references
 	// Create the shader references
-	//
 	if(inf.m_computeShader)
 	if(inf.m_computeShader)
 	{
 	{
 		m_shaders.emplaceBack(inf.m_computeShader);
 		m_shaders.emplaceBack(inf.m_computeShader);
 	}
 	}
+	else if(inf.m_graphicsShaders[ShaderType::kFragment])
+	{
+		for(Shader* s : inf.m_graphicsShaders)
+		{
+			if(s)
+			{
+				m_shaders.emplaceBack(s);
+			}
+		}
+	}
 	else
 	else
 	{
 	{
 		ANKI_ASSERT(!"TODO");
 		ANKI_ASSERT(!"TODO");
@@ -57,14 +69,20 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 
 
 	ANKI_ASSERT(m_shaders.getSize() > 0);
 	ANKI_ASSERT(m_shaders.getSize() > 0);
 
 
+	for(ShaderPtr& shader : m_shaders)
+	{
+		m_shaderTypes |= ShaderTypeBit(1 << shader->getShaderType());
+	}
+
+	const Bool isGraphicsProg = !!(m_shaderTypes & ShaderTypeBit::kAllGraphics);
+	const Bool isComputeProg = !!(m_shaderTypes & ShaderTypeBit::kCompute);
+	const Bool isRtProg = !!(m_shaderTypes & ShaderTypeBit::kAllRayTracing);
+
 	// Link reflection
 	// Link reflection
-	//
 	ShaderReflection refl;
 	ShaderReflection refl;
 	Bool firstLink = true;
 	Bool firstLink = true;
 	for(ShaderPtr& shader : m_shaders)
 	for(ShaderPtr& shader : m_shaders)
 	{
 	{
-		m_shaderTypes |= ShaderTypeBit(1 << shader->getShaderType());
-
 		const ShaderImpl& simpl = static_cast<const ShaderImpl&>(*shader);
 		const ShaderImpl& simpl = static_cast<const ShaderImpl&>(*shader);
 		if(firstLink)
 		if(firstLink)
 		{
 		{
@@ -80,12 +98,22 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 	}
 	}
 
 
 	// Create root signature
 	// Create root signature
-	//
 	ANKI_CHECK(RootSignatureFactory::getSingleton().getOrCreateRootSignature(refl, m_rootSignature));
 	ANKI_CHECK(RootSignatureFactory::getSingleton().getOrCreateRootSignature(refl, m_rootSignature));
 
 
+	// Init the create infos
+	if(isGraphicsProg)
+	{
+		for(U32 ishader = 0; ishader < m_shaders.getSize(); ++ishader)
+		{
+			const ShaderImpl& shaderImpl = static_cast<const ShaderImpl&>(*m_shaders[ishader]);
+
+			m_graphics.m_shaderCreateInfos[shaderImpl.getShaderType()] = {.pShaderBytecode = shaderImpl.m_binary.getBegin(),
+																		  .BytecodeLength = shaderImpl.m_binary.getSizeInBytes()};
+		}
+	}
+
 	// Create the pipeline if compute
 	// Create the pipeline if compute
-	//
-	if(!!(m_shaderTypes & ShaderTypeBit::kCompute))
+	if(isComputeProg)
 	{
 	{
 		const ShaderImpl& shaderImpl = static_cast<const ShaderImpl&>(*m_shaders[0]);
 		const ShaderImpl& shaderImpl = static_cast<const ShaderImpl&>(*m_shaders[0]);
 
 
@@ -98,7 +126,6 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 	}
 	}
 
 
 	// Get shader sizes and a few other things
 	// Get shader sizes and a few other things
-	//
 	for(const ShaderPtr& s : m_shaders)
 	for(const ShaderPtr& s : m_shaders)
 	{
 	{
 		if(!s.isCreated())
 		if(!s.isCreated())
@@ -117,6 +144,12 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 		}
 		}
 	}
 	}
 
 
+	// Misc
+	if(isGraphicsProg)
+	{
+		m_graphics.m_pipelineFactory = anki::newInstance<GraphicsPipelineFactory>(GrMemoryPool::getSingleton());
+	}
+
 	return Error::kNone;
 	return Error::kNone;
 }
 }
 
 

+ 20 - 20
AnKi/Gr/D3D/D3DShaderProgram.h

@@ -10,6 +10,9 @@
 
 
 namespace anki {
 namespace anki {
 
 
+// Forward
+class GraphicsPipelineFactory;
+
 /// @addtogroup directx
 /// @addtogroup directx
 /// @{
 /// @{
 
 
@@ -17,6 +20,23 @@ namespace anki {
 class ShaderProgramImpl final : public ShaderProgram
 class ShaderProgramImpl final : public ShaderProgram
 {
 {
 public:
 public:
+	RootSignature* m_rootSignature = nullptr;
+
+	ShaderReflection m_refl;
+
+	class
+	{
+	public:
+		GraphicsPipelineFactory* m_pipelineFactory = nullptr;
+		Array<D3D12_SHADER_BYTECODE, U32(ShaderType::kFragment - ShaderType::kVertex) + 1> m_shaderCreateInfos = {};
+	} m_graphics;
+
+	class
+	{
+	public:
+		ID3D12PipelineState* m_pipelineState = nullptr;
+	} m_compute;
+
 	ShaderProgramImpl(CString name)
 	ShaderProgramImpl(CString name)
 		: ShaderProgram(name)
 		: ShaderProgram(name)
 	{
 	{
@@ -26,28 +46,8 @@ public:
 
 
 	Error init(const ShaderProgramInitInfo& inf);
 	Error init(const ShaderProgramInitInfo& inf);
 
 
-	RootSignature& getRootSignature() const
-	{
-		ANKI_ASSERT(m_rootSignature);
-		return *m_rootSignature;
-	}
-
-	ID3D12PipelineState& getComputePipelineState() const
-	{
-		ANKI_ASSERT(m_compute.m_pipelineState);
-		return *m_compute.m_pipelineState;
-	}
-
 private:
 private:
 	GrDynamicArray<ShaderPtr> m_shaders;
 	GrDynamicArray<ShaderPtr> m_shaders;
-
-	RootSignature* m_rootSignature = nullptr;
-
-	class
-	{
-	public:
-		ID3D12PipelineState* m_pipelineState = nullptr;
-	} m_compute;
 };
 };
 /// @}
 /// @}
 
 

+ 2 - 2
AnKi/Gr/Vulkan/VkCommandBuffer.cpp

@@ -41,7 +41,7 @@ void CommandBuffer::endRecording()
 	self.endRecording();
 	self.endRecording();
 }
 }
 
 
-void CommandBuffer::bindVertexBuffer(U32 binding, const BufferView& buff, PtrSize stride, VertexStepRate stepRate)
+void CommandBuffer::bindVertexBuffer(U32 binding, const BufferView& buff, U32 stride, VertexStepRate stepRate)
 {
 {
 	ANKI_ASSERT(buff.isValid());
 	ANKI_ASSERT(buff.isValid());
 
 
@@ -52,7 +52,7 @@ void CommandBuffer::bindVertexBuffer(U32 binding, const BufferView& buff, PtrSiz
 	vkCmdBindVertexBuffers(self.m_handle, binding, 1, &vkbuff, &buff.getOffset());
 	vkCmdBindVertexBuffers(self.m_handle, binding, 1, &vkbuff, &buff.getOffset());
 }
 }
 
 
-void CommandBuffer::setVertexAttribute(VertexAttributeSemantic attribute, U32 buffBinding, Format fmt, PtrSize relativeOffset)
+void CommandBuffer::setVertexAttribute(VertexAttributeSemantic attribute, U32 buffBinding, Format fmt, U32 relativeOffset)
 {
 {
 	ANKI_VK_SELF(CommandBufferImpl);
 	ANKI_VK_SELF(CommandBufferImpl);
 	self.commandCommon();
 	self.commandCommon();

+ 2 - 2
AnKi/Gr/Vulkan/VkPipelineFactory.h

@@ -159,7 +159,7 @@ public:
 
 
 	PipelineStateTracker& operator=(const PipelineStateTracker&) = delete; // Non-copyable
 	PipelineStateTracker& operator=(const PipelineStateTracker&) = delete; // Non-copyable
 
 
-	void bindVertexBuffer(U32 binding, PtrSize stride, VertexStepRate stepRate)
+	void bindVertexBuffer(U32 binding, U32 stride, VertexStepRate stepRate)
 	{
 	{
 		VertexBufferBindingPipelineState b;
 		VertexBufferBindingPipelineState b;
 		ANKI_ASSERT(stride <= kMaxU8);
 		ANKI_ASSERT(stride <= kMaxU8);
@@ -173,7 +173,7 @@ public:
 		m_set.m_vertBindings.set(binding);
 		m_set.m_vertBindings.set(binding);
 	}
 	}
 
 
-	void setVertexAttribute(VertexAttributeSemantic semantic, U32 buffBinding, const Format fmt, PtrSize relativeOffset)
+	void setVertexAttribute(VertexAttributeSemantic semantic, U32 buffBinding, const Format fmt, U32 relativeOffset)
 	{
 	{
 		VertexAttributeBindingPipelineState b;
 		VertexAttributeBindingPipelineState b;
 		b.m_binding = U8(buffBinding);
 		b.m_binding = U8(buffBinding);

+ 0 - 11
AnKi/Gr/Vulkan/VkShaderProgram.cpp

@@ -274,17 +274,6 @@ Error ShaderProgramImpl::init(const ShaderProgramInitInfo& inf)
 	//
 	//
 	ANKI_CHECK(PipelineLayoutFactory2::getSingleton().getOrCreatePipelineLayout(m_refl.m_descriptor, m_pplineLayout));
 	ANKI_CHECK(PipelineLayoutFactory2::getSingleton().getOrCreatePipelineLayout(m_refl.m_descriptor, m_pplineLayout));
 
 
-	// Get some masks
-	//
-	if(graphicsProg)
-	{
-		const U32 attachmentCount = m_refl.m_fragment.m_colorAttachmentWritemask.getSetBitCount();
-		for(U32 i = 0; i < attachmentCount; ++i)
-		{
-			ANKI_ASSERT(m_refl.m_fragment.m_colorAttachmentWritemask.get(i) && "Should write to all attachments");
-		}
-	}
-
 	// Init the create infos
 	// Init the create infos
 	//
 	//
 	if(graphicsProg)
 	if(graphicsProg)

+ 14 - 0
AnKi/Util/Hash.h

@@ -25,6 +25,20 @@ namespace anki {
 /// @param prevHash The hash to append to.
 /// @param prevHash The hash to append to.
 /// @return The new hash.
 /// @return The new hash.
 [[nodiscard]] ANKI_PURE U64 appendHash(const void* buffer, PtrSize bufferSize, U64 prevHash);
 [[nodiscard]] ANKI_PURE U64 appendHash(const void* buffer, PtrSize bufferSize, U64 prevHash);
+
+/// See computeHash.
+template<typename T>
+[[nodiscard]] ANKI_PURE U64 computeObjectHash(const T& obj, U64 seed = 123)
+{
+	return computeHash(&obj, sizeof(obj), seed);
+}
+
+/// See appendHash.
+template<typename T>
+[[nodiscard]] ANKI_PURE U64 appendObjectHash(const T& obj, U64 prevHash)
+{
+	return appendHash(&obj, sizeof(obj), prevHash);
+}
 /// @}
 /// @}
 
 
 } // end namespace anki
 } // end namespace anki

+ 84 - 134
Tests/Gr/Gr.cpp

@@ -422,79 +422,6 @@ void main()
 	commonDestroy();
 	commonDestroy();
 }
 }
 
 
-ANKI_TEST(Gr, SimpleDrawcall)
-{
-#if 0
-	COMMON_BEGIN()
-
-	constexpr const char* kVertSrc = R"(
-out gl_PerVertex
-{
-	vec4 gl_Position;
-};
-
-void main()
-{
-	const vec2 POSITIONS[3] = vec2[](vec2(-1.0, 1.0), vec2(0.0, -1.0), vec2(1.0, 1.0));
-
-	gl_Position = vec4(POSITIONS[gl_VertexID % 3], 0.0, 1.0);
-})";
-
-	constexpr const char* kFragSrc = R"(layout (location = 0) out vec4 out_color;
-
-void main()
-{
-	out_color = vec4(0.5);
-})";
-
-	ANKI_TEST_LOGI("Expect to see a grey triangle");
-	ShaderProgramPtr prog = createProgram(kVertSrc, kFragSrc, *g_gr);
-
-	const U ITERATIONS = 200;
-	for(U i = 0; i < ITERATIONS; ++i)
-	{
-		HighRezTimer timer;
-		timer.start();
-
-		TexturePtr presentTex = g_gr->acquireNextPresentableTexture();
-		FramebufferPtr fb = createColorFb(*g_gr, presentTex);
-
-		CommandBufferInitInfo cinit;
-		cinit.m_flags = CommandBufferFlag::kGeneralWork;
-		CommandBufferPtr cmdb = g_gr->newCommandBuffer(cinit);
-
-		cmdb->setViewport(0, 0, g_win->getWidth(), g_win->getHeight());
-		cmdb->bindShaderProgram(prog.get());
-		presentBarrierA(cmdb, presentTex);
-
-		TextureViewInitInfo init;
-		init.m_texture = presentTex.get();
-		TextureViewPtr view = g_gr->newTextureView(init);
-
-		RenderTarget rt;
-		rt.m_view = view.get();
-		cmdb->beginRenderPass({rt});
-
-		cmdb->draw(PrimitiveTopology::kTriangles, 3);
-		cmdb->endRenderPass();
-		presentBarrierB(cmdb, presentTex);
-		cmdb->endRecording();
-		GrManager::getSingleton().submit(cmdb.get());
-
-		g_gr->swapBuffers();
-
-		timer.stop();
-		const F32 TICK = 1.0f / 30.0f;
-		if(timer.getElapsedTime() < TICK)
-		{
-			HighRezTimer::sleep(TICK - timer.getElapsedTime());
-		}
-	}
-
-	COMMON_END()
-#endif
-}
-
 ANKI_TEST(Gr, ViewportAndScissor)
 ANKI_TEST(Gr, ViewportAndScissor)
 {
 {
 #if 0
 #if 0
@@ -731,34 +658,24 @@ ANKI_TEST(Gr, Buffer)
 #endif
 #endif
 }
 }
 
 
-ANKI_TEST(Gr, DrawWithUniforms)
+ANKI_TEST(Gr, SimpleDrawcall)
 {
 {
-#if 0
-	COMMON_BEGIN()
-
-	// A non-uploaded buffer
-	BufferPtr b = g_gr->newBuffer(BufferInitInfo(sizeof(Vec4) * 3, BufferUsageBit::kAllUniform, BufferMapAccessBit::kWrite));
-
-	Vec4* ptr = static_cast<Vec4*>(b->map(0, sizeof(Vec4) * 3, BufferMapAccessBit::kWrite));
-	ANKI_TEST_EXPECT_NEQ(ptr, nullptr);
-	ptr[0] = Vec4(1.0, 0.0, 0.0, 0.0);
-	ptr[1] = Vec4(0.0, 1.0, 0.0, 0.0);
-	ptr[2] = Vec4(0.0, 0.0, 1.0, 0.0);
-	b->unmap();
+	commonInit();
 
 
-	// Prog
-	constexpr const char* kUboVert = R"(
+	{
+		// Prog
+		constexpr const char* kUboVert = R"(
 struct A
 struct A
 {
 {
 	float4 m_color[3];
 	float4 m_color[3];
-};
-[[vk::binding(0)]] ConstantBuffer<A> g_color;
-
-struct B
-{
 	float4 m_rotation2d;
 	float4 m_rotation2d;
 };
 };
-[[vk::binding(1)]] ConstantBuffer<B> g_rotation2d;
+
+#if defined(__spirv__)
+[[vk::push_constant]] ConstantBuffer<A> g_pushConsts;
+#else
+ConstantBuffer<A> g_pushConsts : register(b0, space3000);
+#endif
 
 
 struct VertOut
 struct VertOut
 {
 {
@@ -769,12 +686,27 @@ struct VertOut
 VertOut main(uint svVertexId : SV_VERTEXID)
 VertOut main(uint svVertexId : SV_VERTEXID)
 {
 {
 	VertOut o;
 	VertOut o;
-	o.m_color = g_color.m_color[svVertexId].xyz;
+
+	// No dynamic branching with push constants
+	float4 color;
+	switch(svVertexId)
+	{
+	case 0:
+		color = g_pushConsts.m_color[0];
+		break;
+	case 1:
+		color = g_pushConsts.m_color[1];
+		break;
+	default:
+		color = g_pushConsts.m_color[2];
+	};
+
+	o.m_color = color.xyz;
 
 
 	const float2 kPositions[3] = {float2(-1.0, 1.0), float2(0.0, -1.0), float2(1.0, 1.0)};
 	const float2 kPositions[3] = {float2(-1.0, 1.0), float2(0.0, -1.0), float2(1.0, 1.0)};
 
 
 	float2x2 rot = float2x2(
 	float2x2 rot = float2x2(
-		g_rotation2d.m_rotation2d.x, g_rotation2d.m_rotation2d.y, g_rotation2d.m_rotation2d.z, g_rotation2d.m_rotation2d.w);
+		g_pushConsts.m_rotation2d.x, g_pushConsts.m_rotation2d.y, g_pushConsts.m_rotation2d.z, g_pushConsts.m_rotation2d.w);
 	float2 pos = mul(rot, kPositions[svVertexId % 3]);
 	float2 pos = mul(rot, kPositions[svVertexId % 3]);
 
 
 	o.m_svPosition = float4(pos, 0.0, 1.0);
 	o.m_svPosition = float4(pos, 0.0, 1.0);
@@ -782,7 +714,7 @@ VertOut main(uint svVertexId : SV_VERTEXID)
 	return o;
 	return o;
 })";
 })";
 
 
-	constexpr const char* kUboFrag = R"(
+		constexpr const char* kUboFrag = R"(
 struct VertOut
 struct VertOut
 {
 {
 	float4 m_svPosition : SV_POSITION;
 	float4 m_svPosition : SV_POSITION;
@@ -794,54 +726,72 @@ float4 main(VertOut i) : SV_TARGET0
 	return float4(i.m_color, 1.0);
 	return float4(i.m_color, 1.0);
 })";
 })";
 
 
-	ShaderProgramPtr prog = createProgram(kUboVert, kUboFrag, *g_gr);
+		ShaderProgramPtr prog = createVertFragProg(kUboVert, kUboFrag);
 
 
-	const U ITERATION_COUNT = 100;
-	U iterations = ITERATION_COUNT;
-	while(iterations--)
-	{
-		HighRezTimer timer;
-		timer.start();
-		TexturePtr presentTex = g_gr->acquireNextPresentableTexture();
-		FramebufferPtr fb = createColorFb(*g_gr, presentTex);
+		const U kIterationCount = 100;
+		U iterations = kIterationCount;
+		while(iterations--)
+		{
+			HighRezTimer timer;
+			timer.start();
 
 
-		CommandBufferInitInfo cinit;
-		cinit.m_flags = CommandBufferFlag::kGeneralWork;
-		CommandBufferPtr cmdb = g_gr->newCommandBuffer(cinit);
+			TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 
 
-		cmdb->setViewport(0, 0, g_win->getWidth(), g_win->getHeight());
-		cmdb->bindShaderProgram(prog.get());
-		presentBarrierA(cmdb, presentTex);
-		cmdb->beginRenderPass(fb.get(), {TextureUsageBit::kFramebufferWrite}, {});
+			CommandBufferInitInfo cinit;
+			cinit.m_flags = CommandBufferFlag::kGeneralWork;
+			CommandBufferPtr cmdb = GrManager::getSingleton().newCommandBuffer(cinit);
 
 
-		cmdb->bindUniformBuffer(0, 0, b.get(), 0, kMaxPtrSize);
+			cmdb->setViewport(0, 0, NativeWindow::getSingleton().getWidth(), NativeWindow::getSingleton().getHeight());
+			cmdb->bindShaderProgram(prog.get());
 
 
-		// Uploaded buffer
-		Vec4* rotMat = SET_UNIFORMS(Vec4*, sizeof(Vec4), cmdb, 0, 1);
-		F32 angle = toRad(360.0f / F32(ITERATION_COUNT) * F32(iterations));
-		(*rotMat)[0] = cos(angle);
-		(*rotMat)[1] = -sin(angle);
-		(*rotMat)[2] = sin(angle);
-		(*rotMat)[3] = cos(angle);
+			const TextureBarrierInfo barrier = {TextureView(presentTex.get(), TextureSubresourceDescriptor::all()), TextureUsageBit::kNone,
+												TextureUsageBit::kFramebufferWrite};
+			cmdb->setPipelineBarrier({&barrier, 1}, {}, {});
 
 
-		cmdb->draw(PrimitiveTopology::kTriangles, 3);
-		cmdb->endRenderPass();
-		presentBarrierB(cmdb, presentTex);
-		cmdb->endRecording();
-		GrManager::getSingleton().submit(cmdb.get());
+			cmdb->beginRenderPass({TextureView(presentTex.get(), TextureSubresourceDescriptor::firstSurface())});
 
 
-		g_gr->swapBuffers();
+			// Set uniforms
+			class A
+			{
+			public:
+				Array<Vec4, 3> m_color;
+				Vec4 m_rotation2d;
+			} pc;
 
 
-		timer.stop();
-		const F32 TICK = 1.0f / 30.0f;
-		if(timer.getElapsedTime() < TICK)
-		{
-			HighRezTimer::sleep(TICK - timer.getElapsedTime());
+			const F32 angle = toRad(360.0f / F32(kIterationCount) * F32(iterations)) / 2.0f;
+			pc.m_rotation2d[0] = cos(angle);
+			pc.m_rotation2d[1] = -sin(angle);
+			pc.m_rotation2d[2] = sin(angle);
+			pc.m_rotation2d[3] = cos(angle);
+
+			pc.m_color[0] = Vec4(1.0, 0.0, 0.0, 0.0);
+			pc.m_color[1] = Vec4(0.0, 1.0, 0.0, 0.0);
+			pc.m_color[2] = Vec4(0.0, 0.0, 1.0, 0.0);
+
+			cmdb->setPushConstants(&pc, sizeof(pc));
+
+			cmdb->draw(PrimitiveTopology::kTriangles, 3);
+			cmdb->endRenderPass();
+
+			const TextureBarrierInfo barrier2 = {TextureView(presentTex.get(), TextureSubresourceDescriptor::all()),
+												 TextureUsageBit::kFramebufferWrite, TextureUsageBit::kPresent};
+			cmdb->setPipelineBarrier({&barrier2, 1}, {}, {});
+
+			cmdb->endRecording();
+			GrManager::getSingleton().submit(cmdb.get());
+
+			GrManager::getSingleton().swapBuffers();
+
+			timer.stop();
+			const Second kTick = 1.0f / 30.0f;
+			if(timer.getElapsedTime() < kTick)
+			{
+				HighRezTimer::sleep(kTick - timer.getElapsedTime());
+			}
 		}
 		}
 	}
 	}
 
 
-	COMMON_END()
-#endif
+	commonDestroy();
 }
 }
 
 
 ANKI_TEST(Gr, DrawWithVertex)
 ANKI_TEST(Gr, DrawWithVertex)

+ 14 - 0
Tests/Gr/GrCommon.h

@@ -44,4 +44,18 @@ inline ShaderPtr createShader(CString src, ShaderType type)
 	return GrManager::getSingleton().newShader(initInf);
 	return GrManager::getSingleton().newShader(initInf);
 }
 }
 
 
+inline ShaderProgramPtr createVertFragProg(CString vert, CString frag)
+{
+	ShaderPtr vertS = createShader(vert, ShaderType::kVertex);
+	ShaderPtr fragS = createShader(frag, ShaderType::kFragment);
+
+	ShaderProgramInitInfo init;
+	init.m_graphicsShaders[ShaderType::kVertex] = vertS.get();
+	init.m_graphicsShaders[ShaderType::kFragment] = fragS.get();
+
+	ShaderProgramPtr prog = GrManager::getSingleton().newShaderProgram(init);
+
+	return prog;
+}
+
 } // end namespace anki
 } // end namespace anki