Skip to content

Commit

Permalink
[D3D12] Started with refactoring of resource state transitioning.
Browse files Browse the repository at this point in the history
- Upload heap resources must always be initially in D3D12_RESOURCE_STATE_GENERIC_READ (see D3D12SubresourceContext).
- Readback heap resources must always be initially in D3D12_RESOURCE_STATE_COPY_DEST (see D3D12SubresourceContext).
- Use shader visible descriptor heap of command context in D3D12Buffer::ClearSubresourceUInt().
- Add list of references to resource states in D3D12ResourceHeap and D3D12BufferArray.
- Added several resource barriers to D3D12CommandBuffer for SetResource, SetIndexBuffer etc. commands.
- Flush pending resource barriers before each draw/dispatch command to make use of barrier cache for SetResource/SetResourceHeap commands.
- Determine target resource state in D3D12PipelineLayout and store it in D3D12DescriptorHeapLocation and D3D12DescriptorLocation.
- Transition texture resource state back to previous one instead of "usage state" in D3D12MipGenerator.
- Added many more iterations (1000) to "ResourceBinding" test to ensure resource state transitioning works; Produced previously wrong results non-deterministically.

TODO:
  State transitioning is far from finished in LLGL/D3D12.
  The "usageState" should be removed from D3D12Resource and subresource state transitioning is not handled yet either.
  In one way or another, the state transitioning must be exposed to the public interface
  but LLGL should retain the option for automatic state tracking to keep the interface easy to use.
  • Loading branch information
LukasBanana committed Jun 30, 2024
1 parent ff24a98 commit c998e54
Show file tree
Hide file tree
Showing 21 changed files with 246 additions and 87 deletions.
2 changes: 1 addition & 1 deletion include/LLGL/CommandBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Before any command can be encoded, the command buffer must be set into encode mo
\remarks In a multi-threaded environment, all blit commands (e.g. CommandBuffer::UpdateBuffer, CommandBuffer::CopyBuffer etc.) <b>must not</b> be called simultaneously
with the same source and/or destination resources even if their ranges do not collide.
Depending on the backend, those resources might be transitioned into difference states during those commands.
Depending on the backend, those resources might be transitioned into different states during those commands.
The same applies to CommandBuffer::BeginRenderPass where the specified RenderTarget might be transitioned into rendering state.
Binding resources (CommandBuffer::SetResource, CommandBuffer::SetResourceHeap) as well as vertex (CommandBuffer::SetVertexBuffer) and
index streams (CommandBuffer::SetIndexBuffer) can be performed in a multi-threaded fashion with either the same or separate resources.
Expand Down
19 changes: 12 additions & 7 deletions sources/Renderer/Direct3D12/Buffer/D3D12Buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,12 @@ void D3D12Buffer::ClearSubresourceUInt(
CreateIntermediateUAVDescriptorHeap(resource, format, formatStride);

/* Get GPU and CPU descriptor handles for intermediate descriptor heap */
D3D12_GPU_DESCRIPTOR_HANDLE gpuDescHandle = uavIntermediateDescHeap_->GetGPUDescriptorHandleForHeapStart();
D3D12DescriptorHeapSetLayout layout;
layout.numHeapResourceViews = 1;
commandContext.PrepareStagingDescriptorHeaps(layout, {});
//D3D12_GPU_DESCRIPTOR_HANDLE gpuDescHandle = uavIntermediateDescHeap_->GetGPUDescriptorHandleForHeapStart();
D3D12_CPU_DESCRIPTOR_HANDLE cpuDescHandle = uavIntermediateDescHeap_->GetCPUDescriptorHandleForHeapStart();
D3D12_GPU_DESCRIPTOR_HANDLE gpuDescHandle = commandContext.CopyDescriptorsForStaging(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuDescHandle, 0, 1);

if (useIntermediateBuffer)
{
Expand Down Expand Up @@ -258,7 +262,7 @@ void D3D12Buffer::ClearSubresourceUInt(
else
{
/* Clear destination buffer directly with intermediate UAV */
commandContext.TransitionResource(GetResource(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, true);
commandContext.TransitionResource(GetResource(), D3D12_RESOURCE_STATE_COMMON, true);
{
ClearSubresourceWithUAV(
commandList,
Expand Down Expand Up @@ -401,14 +405,17 @@ void D3D12Buffer::CreateGpuBuffer(ID3D12Device* device, const BufferDescriptor&
if ((desc.bindFlags & BindFlags::StreamOutputBuffer) != 0)
internalSize_ += g_soBufferFillSizeLen;

/* Store buffer primary usage stage */
resource_.usageState = GetD3DUsageState(desc.bindFlags);

/* Create generic buffer resource */
const CD3DX12_HEAP_PROPERTIES heapProperties{ D3D12_HEAP_TYPE_DEFAULT };
const CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(GetInternalBufferSize(), GetD3DResourceFlags(desc));
HRESULT hr = device->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
resource_.SetInitialAndUsageStates(D3D12_RESOURCE_STATE_COPY_DEST, GetD3DUsageState(desc.bindFlags)),
D3D12_RESOURCE_STATE_COMMON, // Buffers are effectively created in D3D12_RESOURCE_STATE_COMMON state
nullptr,
IID_PPV_ARGS(resource_.native.ReleaseAndGetAddressOf())
);
Expand Down Expand Up @@ -440,7 +447,7 @@ void D3D12Buffer::CreateCpuAccessBuffer(ID3D12Device* device, long cpuAccessFlag
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
cpuAccessBuffer_.usageState,
D3D12_RESOURCE_STATE_COMMON, // Buffers are effectively created in D3D12_RESOURCE_STATE_COMMON state
nullptr,
IID_PPV_ARGS(cpuAccessBuffer_.native.ReleaseAndGetAddressOf())
);
Expand Down Expand Up @@ -495,13 +502,11 @@ void D3D12Buffer::CreateIntermediateUAVBuffer()
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_COMMON, // Buffers are effectively created in D3D12_RESOURCE_STATE_COMMON state
nullptr,
IID_PPV_ARGS(uavIntermediateBuffer_.native.ReleaseAndGetAddressOf())
);
DXThrowIfCreateFailed(hr, "ID3D12Resource", "for buffer subresource UAV");

uavIntermediateBuffer_.SetInitialState(D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}

void D3D12Buffer::CreateVertexBufferView(const BufferDescriptor& desc)
Expand Down
4 changes: 4 additions & 0 deletions sources/Renderer/Direct3D12/Buffer/D3D12BufferArray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ D3D12BufferArray::D3D12BufferArray(std::uint32_t numBuffers, Buffer* const * buf
{
/* Store the strides and offests of each D3D12VertexBuffer inside the arrays */
vertexBufferViews_.reserve(numBuffers);
resourceRefs_.reserve(numBuffers);
while (D3D12Buffer* next = NextArrayResource<D3D12Buffer>(numBuffers, bufferArray))
{
vertexBufferViews_.push_back(next->GetVertexBufferView());
resourceRefs_.push_back(&(next->GetResource()));
}
}


Expand Down
14 changes: 11 additions & 3 deletions sources/Renderer/Direct3D12/Buffer/D3D12BufferArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@


#include <LLGL/BufferArray.h>
#include <LLGL/Container/SmallVector.h>
#include <d3d12.h>
#include <vector>


namespace LLGL
{


class Buffer;
struct D3D12Resource;

class D3D12BufferArray final : public BufferArray
{
Expand All @@ -28,14 +29,21 @@ class D3D12BufferArray final : public BufferArray
D3D12BufferArray(std::uint32_t numBuffers, Buffer* const * bufferArray);

// Returns the array of vertex buffer views.
inline const std::vector<D3D12_VERTEX_BUFFER_VIEW>& GetVertexBufferViews() const
inline ArrayView<D3D12_VERTEX_BUFFER_VIEW> GetVertexBufferViews() const
{
return vertexBufferViews_;
}

// Returns the array of vertex buffer resource references.
inline ArrayView<D3D12Resource*> GetResourceRefs() const
{
return resourceRefs_;
}

private:

std::vector<D3D12_VERTEX_BUFFER_VIEW> vertexBufferViews_;
SmallVector<D3D12_VERTEX_BUFFER_VIEW, 4> vertexBufferViews_;
SmallVector<D3D12Resource*, 4> resourceRefs_;

};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,12 @@ void D3D12BufferConstantsPool::CreateImmutableBuffer(
const UINT64 bufferSize = data.size() * sizeof(UINT64);
const CD3DX12_HEAP_PROPERTIES heapProperties{ D3D12_HEAP_TYPE_DEFAULT };
const CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
resource_.usageState = D3D12_RESOURCE_STATE_COPY_SOURCE;
HRESULT hr = device->CreateCommittedResource(
&heapProperties,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
resource_.SetInitialAndUsageStates(D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE),
D3D12_RESOURCE_STATE_COMMON, // Buffers are effectively created in D3D12_RESOURCE_STATE_COMMON state
nullptr,
IID_PPV_ARGS(resource_.native.ReleaseAndGetAddressOf())
);
Expand Down
32 changes: 24 additions & 8 deletions sources/Renderer/Direct3D12/Command/D3D12CommandBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ void D3D12CommandBuffer::CopyBufferFromTexture(
const UINT64 alignedBufferSize = GetAlignedImageSize<UINT64>(srcExtent, rowStride, alignedRowStride);
ID3D12Resource* alignedBuffer = commandContext_.AllocIntermediateBuffer(alignedBufferSize, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);

commandContext_.TransitionResource(alignedBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ, true);
commandContext_.TransitionBarrier(alignedBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ, true);

/* Copy entire region from source texture into intermediate buffer */
const D3D12_TEXTURE_COPY_LOCATION dstLocationD3D = srcTextureD3D.CalcCopyLocation(alignedBuffer, 0, srcExtent, alignedRowStride);
Expand All @@ -165,7 +165,7 @@ void D3D12CommandBuffer::CopyBufferFromTexture(
&srcBox // pSrcBox
);

commandContext_.TransitionResource(alignedBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST, true);
commandContext_.TransitionBarrier(alignedBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST, true);

/* Copy each row individually from intermediate buffer into destination buffer due to unalgined row pitch */
UINT64 alignedOffset = 0;
Expand All @@ -179,7 +179,7 @@ void D3D12CommandBuffer::CopyBufferFromTexture(
}
}

commandContext_.TransitionResource(alignedBuffer, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_COPY_SOURCE, true);
commandContext_.TransitionBarrier(alignedBuffer, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_COPY_SOURCE, true);
}
else
{
Expand Down Expand Up @@ -286,7 +286,7 @@ void D3D12CommandBuffer::CopyTextureFromBuffer(
const UINT64 alignedBufferSize = GetAlignedImageSize<UINT64>(dstExtent, rowStride, alignedRowStride);
ID3D12Resource* alignedBuffer = commandContext_.AllocIntermediateBuffer(alignedBufferSize, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);

commandContext_.TransitionResource(alignedBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ, true);
commandContext_.TransitionBarrier(alignedBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ, true);

/* Copy each row individually from intermediate buffer into destination texture due to unalgined row pitch */
UINT64 alignedOffset = 0;
Expand All @@ -300,7 +300,7 @@ void D3D12CommandBuffer::CopyTextureFromBuffer(
}
}

commandContext_.TransitionResource(alignedBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST, true);
commandContext_.TransitionBarrier(alignedBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST, true);

/* Copy entire region from intermediate buffer into destination texture */
const D3D12_TEXTURE_COPY_LOCATION srcLocationD3D = dstTextureD3D.CalcCopyLocation(alignedBuffer, 0, dstExtent, alignedRowStride);
Expand All @@ -313,7 +313,7 @@ void D3D12CommandBuffer::CopyTextureFromBuffer(
&srcBox // pSrcBox
);

commandContext_.TransitionResource(alignedBuffer, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_COPY_SOURCE, true);
commandContext_.TransitionBarrier(alignedBuffer, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_COPY_SOURCE, true);
}
else
{
Expand Down Expand Up @@ -510,12 +510,18 @@ void D3D12CommandBuffer::ClearAttachments(std::uint32_t numAttachments, const At
void D3D12CommandBuffer::SetVertexBuffer(Buffer& buffer)
{
auto& bufferD3D = LLGL_CAST(D3D12Buffer&, buffer);
commandContext_.TransitionResource(bufferD3D.GetResource(), D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, true);
GetNative()->IASetVertexBuffers(0, 1, &(bufferD3D.GetVertexBufferView()));
}

void D3D12CommandBuffer::SetVertexBufferArray(BufferArray& bufferArray)
{
auto& bufferArrayD3D = LLGL_CAST(D3D12BufferArray&, bufferArray);

for (D3D12Resource* resource : bufferArrayD3D.GetResourceRefs())
commandContext_.TransitionResource(*resource, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
commandContext_.FlushResourceBarrieres();

GetNative()->IASetVertexBuffers(
0,
static_cast<UINT>(bufferArrayD3D.GetVertexBufferViews().size()),
Expand All @@ -526,6 +532,7 @@ void D3D12CommandBuffer::SetVertexBufferArray(BufferArray& bufferArray)
void D3D12CommandBuffer::SetIndexBuffer(Buffer& buffer)
{
auto& bufferD3D = LLGL_CAST(D3D12Buffer&, buffer);
commandContext_.TransitionResource(bufferD3D.GetResource(), D3D12_RESOURCE_STATE_INDEX_BUFFER, true);
commandContext_.SetIndexBuffer(bufferD3D.GetIndexBufferView());
}

Expand All @@ -535,6 +542,8 @@ void D3D12CommandBuffer::SetIndexBuffer(Buffer& buffer, const Format format, std
D3D12_INDEX_BUFFER_VIEW indexBufferView = bufferD3D.GetIndexBufferView();
if (indexBufferView.SizeInBytes > offset)
{
commandContext_.TransitionResource(bufferD3D.GetResource(), D3D12_RESOURCE_STATE_INDEX_BUFFER, true);

/* Update buffer location and size by offset, and override format */
indexBufferView.BufferLocation += offset;
indexBufferView.SizeInBytes -= static_cast<UINT>(offset);
Expand Down Expand Up @@ -576,7 +585,8 @@ void D3D12CommandBuffer::SetResourceHeap(ResourceHeap& resourceHeap, std::uint32
}

/* Insert resource barriers for the specified descriptor set */
resourceHeapD3D.InsertResourceBarriers(GetNative(), descriptorSet);
resourceHeapD3D.TransitionResources(commandContext_, descriptorSet);
resourceHeapD3D.InsertUAVBarriers(GetNative(), descriptorSet);
}

/*
Expand Down Expand Up @@ -605,6 +615,11 @@ void D3D12CommandBuffer::SetResource(std::uint32_t descriptor, Resource& resourc
const D3D12DescriptorLocation& rootParameterLocation = boundPipelineLayout_->GetRootParameterMap()[descriptor];
if (rootParameterLocation.type != D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE)
{
/* Transition resource into target state */
//TODO: should this be flushed immediately for root parameters?
commandContext_.TransitionGenericResource(resource, rootParameterLocation.state);

/* Set resource as root parameter */
D3D12_GPU_VIRTUAL_ADDRESS gpuVirtualAddr = GetD3DResourceGPUAddr(resource);
if (gpuVirtualAddr != 0)
{
Expand All @@ -617,8 +632,9 @@ void D3D12CommandBuffer::SetResource(std::uint32_t descriptor, Resource& resourc
}
else
{
/* Bind resource with staging descriptor heap */
/* Transition resource into target state and bind resource with staging descriptor heap */
const D3D12DescriptorHeapLocation& descriptorLocation = boundPipelineLayout_->GetDescriptorMap()[descriptor];
commandContext_.TransitionGenericResource(resource, descriptorLocation.state);
commandContext_.EmplaceDescriptorForStaging(resource, descriptorLocation.index, descriptorLocation.type);
}
}
Expand Down
44 changes: 40 additions & 4 deletions sources/Renderer/Direct3D12/Command/D3D12CommandContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
#include "D3D12CommandQueue.h"
#include "../D3D12Device.h"
#include "../D3D12Resource.h"
#include "../Buffer/D3D12Buffer.h"
#include "../Texture/D3D12Texture.h"
#include "../RenderState/D3D12Fence.h"
#include "../../CheckedCast.h"
#include "../../DXCommon/DXCore.h"
#include "../../../Core/Assertion.h"
#include <LLGL/Utils/ForRange.h>
Expand Down Expand Up @@ -147,15 +150,15 @@ void D3D12CommandContext::FinishAndSync(D3D12CommandQueue& commandQueue)
commandQueue.WaitIdle();
}

void D3D12CommandContext::TransitionResource(ID3D12Resource* resource, D3D12_RESOURCE_STATES newState, D3D12_RESOURCE_STATES oldState, bool flushImmediate)
void D3D12CommandContext::TransitionBarrier(ID3D12Resource* resource, D3D12_RESOURCE_STATES newState, D3D12_RESOURCE_STATES oldState, UINT subresource, bool flushImmediate)
{
D3D12_RESOURCE_BARRIER& barrier = NextResourceBarrier();

/* Initialize resource barrier for resource transition */
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = resource;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.Subresource = subresource;
barrier.Transition.StateBefore = oldState;
barrier.Transition.StateAfter = newState;

Expand All @@ -164,6 +167,11 @@ void D3D12CommandContext::TransitionResource(ID3D12Resource* resource, D3D12_RES
FlushResourceBarrieres();
}

void D3D12CommandContext::TransitionBarrier(ID3D12Resource* resource, D3D12_RESOURCE_STATES newState, D3D12_RESOURCE_STATES oldState, bool flushImmediate)
{
TransitionBarrier(resource, newState, oldState, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, flushImmediate);
}

void D3D12CommandContext::TransitionResource(D3D12Resource& resource, D3D12_RESOURCE_STATES newState, bool flushImmediate)
{
if (resource.currentState != newState)
Expand All @@ -187,13 +195,36 @@ void D3D12CommandContext::TransitionResource(D3D12Resource& resource, D3D12_RESO
FlushResourceBarrieres();
}

void D3D12CommandContext::InsertUAVBarrier(D3D12Resource& resource, bool flushImmediate)
void D3D12CommandContext::TransitionGenericResource(Resource& resource, D3D12_RESOURCE_STATES newState, bool flushImmediate)
{
switch (resource.GetResourceType())
{
case ResourceType::Buffer:
{
auto& bufferD3D = LLGL_CAST(D3D12Buffer&, resource);
TransitionResource(bufferD3D.GetResource(), newState, flushImmediate);
}
break;

case ResourceType::Texture:
{
auto& textureD3D = LLGL_CAST(D3D12Texture&, resource);
TransitionResource(textureD3D.GetResource(), newState, flushImmediate);
}
break;

default:
break;
}
}

void D3D12CommandContext::UAVBarrier(ID3D12Resource* resource, bool flushImmediate)
{
D3D12_RESOURCE_BARRIER& barrier = NextResourceBarrier();

barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.UAV.pResource = resource.native.Get();
barrier.UAV.pResource = resource;

if (flushImmediate)
FlushResourceBarrieres();
Expand Down Expand Up @@ -481,6 +512,7 @@ void D3D12CommandContext::DrawInstanced(
UINT startVertexLocation,
UINT startInstanceLocation)
{
FlushResourceBarrieres();
FlushDeferredPipelineState();
FlushGraphicsStagingDescriptorTables();
commandList_->DrawInstanced(vertexCountPerInstance, instanceCount, startVertexLocation, startInstanceLocation);
Expand All @@ -493,6 +525,7 @@ void D3D12CommandContext::DrawIndexedInstanced(
INT baseVertexLocation,
UINT startInstanceLocation)
{
FlushResourceBarrieres();
FlushDeferredPipelineState();
FlushGraphicsStagingDescriptorTables();
commandList_->DrawIndexedInstanced(indexCountPerInstance, instanceCount, startIndexLocation, baseVertexLocation, startInstanceLocation);
Expand All @@ -506,6 +539,7 @@ void D3D12CommandContext::DrawIndirect(
ID3D12Resource* countBuffer,
UINT64 countBufferOffset)
{
FlushResourceBarrieres();
FlushDeferredPipelineState();
FlushGraphicsStagingDescriptorTables();
commandList_->ExecuteIndirect(commandSignature, maxCommandCount, argumentBuffer, argumentBufferOffset, countBuffer, countBufferOffset);
Expand All @@ -516,6 +550,7 @@ void D3D12CommandContext::Dispatch(
UINT threadGroupCountY,
UINT threadGroupCountZ)
{
FlushResourceBarrieres();
FlushComputeStagingDescriptorTables();
commandList_->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
}
Expand All @@ -528,6 +563,7 @@ void D3D12CommandContext::DispatchIndirect(
ID3D12Resource* countBuffer,
UINT64 countBufferOffset)
{
FlushResourceBarrieres();
FlushComputeStagingDescriptorTables();
commandList_->ExecuteIndirect(commandSignature, maxCommandCount, argumentBuffer, argumentBufferOffset, countBuffer, countBufferOffset);
}
Expand Down
Loading

0 comments on commit c998e54

Please sign in to comment.