diff options
| author | lucy96chen <47800040+lucy96chen@users.noreply.github.com> | 2021-12-16 14:48:04 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-12-16 14:48:04 -0800 |
| commit | ac88374136ae0c9e83d3350ca3113f4bce893911 (patch) | |
| tree | 0e8e839eff6cddae41a8e4af2a013ea7e0cfe5a7 /tools | |
| parent | f024d7299831c0312877e315e8d78b920aaafbaf (diff) | |
gfx: Add tests for instanced, indexed instanced, and indirect draw calls (#2060)
* Implemented instancing for the drawInstanced test and confirmed that test values taken at specific pixel location match expected values
* Removed writeImage() helper function as this is for debugging only
* Factored out shared test code and wrapped tests inside a base class with derived structs for each individual draw call variant; Added a test for indexed, instanced draws but test is currently not working correctly
* Indexed instancing test finish and working; Further refactor to move code that fills the test result array and creates the vertex and color buffers to the base test class
* Commented out image dump helper function at the top as it may still be needed for the remaining draw tests
* Added a new struct derived from the base test class for testing indirect but non-indexed draws; Moved required command signatures for indirect draws into D3D12Device to ensure they continue to exist outside of their respective draw calls; Moved command signature creation into D3D12Device::initialize(); Small consistency cleanup changes
* Added working indexed indirect draw call test
* Moved expectedResult and compareComputeResult() call into getTestResults() and renamed to checkTestResults()
* Rerun tests
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/gfx-unit-test/draw-instanced-test.cpp | 156 | ||||
| -rw-r--r-- | tools/gfx-unit-test/gfx-test-util.cpp | 13 | ||||
| -rw-r--r-- | tools/gfx-unit-test/gfx-test-util.h | 5 | ||||
| -rw-r--r-- | tools/gfx-unit-test/graphics-smoke.slang | 5 | ||||
| -rw-r--r-- | tools/gfx-unit-test/instanced-draw-tests.cpp | 581 | ||||
| -rw-r--r-- | tools/gfx/d3d12/render-d3d12.cpp | 71 |
6 files changed, 637 insertions, 194 deletions
diff --git a/tools/gfx-unit-test/draw-instanced-test.cpp b/tools/gfx-unit-test/draw-instanced-test.cpp deleted file mode 100644 index e15e6beda..000000000 --- a/tools/gfx-unit-test/draw-instanced-test.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include "tools/unit-test/slang-unit-test.h" - -#include "slang-gfx.h" -#include "gfx-test-util.h" -#include "tools/gfx-util/shader-cursor.h" -#include "source/core/slang-basic.h" - -using namespace gfx; - -namespace gfx_test -{ - struct Vertex - { - float position[3]; - float color[3]; - }; - - static const int kVertexCount = 3; - static const Vertex kVertexData[kVertexCount] = - { - { { -1, -1, 0.5 }, { 1, 0, 0 } }, - { { -1, 3, 0.5 }, { 1, 0, 0 } }, - { { 3, -1, 0.5 }, { 1, 0, 0 } }, - }; - - void drawInstancedTestImpl(IDevice* device, UnitTestContext* context) - { - Slang::ComPtr<ITransientResourceHeap> transientHeap; - ITransientResourceHeap::Desc transientHeapDesc = {}; - transientHeapDesc.constantBufferSize = 4096; - GFX_CHECK_CALL_ABORT( - device->createTransientResourceHeap(transientHeapDesc, transientHeap.writeRef())); - - ComPtr<IShaderProgram> shaderProgram; - slang::ProgramLayout* slangReflection; - GFX_CHECK_CALL_ABORT(loadGraphicsProgram(device, shaderProgram, "graphics-smoke", "vertexMain", "fragmentMain", slangReflection)); - - Format format = Format::R32G32B32A32_FLOAT; - - InputElementDesc inputElements[] = { - { "POSITION", 0, Format::R32G32B32_FLOAT, offsetof(Vertex, position) }, - { "COLOR", 0, Format::R32G32B32_FLOAT, offsetof(Vertex, color) }, - }; - ComPtr<gfx::IInputLayout> inputLayout = device->createInputLayout(inputElements, 2); - SLANG_CHECK_ABORT(inputLayout != nullptr); - - IBufferResource::Desc vertexBufferDesc; - vertexBufferDesc.type = IResource::Type::Buffer; - vertexBufferDesc.sizeInBytes = kVertexCount * sizeof(Vertex); - vertexBufferDesc.defaultState = ResourceState::VertexBuffer; - vertexBufferDesc.allowedStates = ResourceState::VertexBuffer; - ComPtr<IBufferResource> vertexBuffer = device->createBufferResource(vertexBufferDesc, &kVertexData[0]); - SLANG_CHECK_ABORT(vertexBuffer != nullptr); - - IFramebufferLayout::AttachmentLayout attachmentLayout; - attachmentLayout.format = format; - attachmentLayout.sampleCount = 1; - - IFramebufferLayout::Desc framebufferLayoutDesc; - framebufferLayoutDesc.renderTargetCount = 1; - framebufferLayoutDesc.renderTargets = &attachmentLayout; - ComPtr<gfx::IFramebufferLayout> framebufferLayout = device->createFramebufferLayout(framebufferLayoutDesc); - - GraphicsPipelineStateDesc pipelineDesc = {}; - pipelineDesc.program = shaderProgram.get(); - pipelineDesc.inputLayout = inputLayout; - pipelineDesc.framebufferLayout = framebufferLayout; - pipelineDesc.depthStencil.depthTestEnable = false; - pipelineDesc.depthStencil.depthWriteEnable = false; - ComPtr<gfx::IPipelineState> pipelineState; - GFX_CHECK_CALL_ABORT( - device->createGraphicsPipelineState(pipelineDesc, pipelineState.writeRef())); - - ICommandQueue::Desc queueDesc = { ICommandQueue::QueueType::Graphics }; - auto queue = device->createCommandQueue(queueDesc); - auto commandBuffer = transientHeap->createCommandBuffer(); - - IRenderPassLayout::Desc renderPassDesc = {}; - renderPassDesc.framebufferLayout = framebufferLayout; - renderPassDesc.renderTargetCount = 1; - IRenderPassLayout::AttachmentAccessDesc renderTargetAccess = {}; - renderTargetAccess.loadOp = IRenderPassLayout::AttachmentLoadOp::Clear; - renderTargetAccess.storeOp = IRenderPassLayout::AttachmentStoreOp::Store; - renderTargetAccess.initialState = ResourceState::Undefined; - renderTargetAccess.finalState = ResourceState::CopySource; - renderPassDesc.renderTargetAccess = &renderTargetAccess; - ComPtr<IRenderPassLayout> renderPass = device->createRenderPassLayout(renderPassDesc); - - const int width = 2; - const int height = 2; - - gfx::ITextureResource::Desc colorBufferDesc; - colorBufferDesc.type = IResource::Type::Texture2D; - colorBufferDesc.size.width = width; - colorBufferDesc.size.height = height; - colorBufferDesc.size.depth = 1; - colorBufferDesc.numMipLevels = 1; - colorBufferDesc.format = format; - colorBufferDesc.defaultState = ResourceState::RenderTarget; - colorBufferDesc.allowedStates = { ResourceState::RenderTarget, ResourceState::CopySource }; - ComPtr<ITextureResource> colorBuffer = device->createTextureResource(colorBufferDesc, nullptr); - - gfx::IResourceView::Desc colorBufferViewDesc; - memset(&colorBufferViewDesc, 0, sizeof(colorBufferViewDesc)); - colorBufferViewDesc.format = format; - colorBufferViewDesc.renderTarget.shape = gfx::IResource::Type::Texture2D; - colorBufferViewDesc.type = gfx::IResourceView::Type::RenderTarget; - ComPtr<gfx::IResourceView> rtv = - device->createTextureView(colorBuffer.get(), colorBufferViewDesc); - - gfx::IFramebuffer::Desc framebufferDesc; - framebufferDesc.renderTargetCount = 1; - framebufferDesc.depthStencilView = nullptr; - framebufferDesc.renderTargetViews = rtv.readRef(); - framebufferDesc.layout = framebufferLayout; - ComPtr<gfx::IFramebuffer> framebuffer = device->createFramebuffer(framebufferDesc); - - auto encoder = commandBuffer->encodeRenderCommands(renderPass, framebuffer); - auto rootObject = encoder->bindPipeline(pipelineState); - - UInt vertexCount = 3; - UInt instanceCount = 1; - UInt startVertex = 0; - UInt startInstanceLocation = 0; - - gfx::Viewport viewport = {}; - viewport.maxZ = 1.0f; - viewport.extentX = width; - viewport.extentY = height; - encoder->setViewportAndScissor(viewport); - - encoder->setVertexBuffer(0, vertexBuffer, sizeof(Vertex)); - encoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); - encoder->drawInstanced(vertexCount, instanceCount, startVertex, startInstanceLocation); - encoder->endEncoding(); - commandBuffer->close(); - queue->executeCommandBuffer(commandBuffer); - queue->waitOnHost(); - - float expectedResult[] = { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f }; - compareComputeResult(device, colorBuffer, ResourceState::CopySource, expectedResult, 32, 2); - } - - SLANG_UNIT_TEST(drawInstancedD3D12) - { - runTestImpl(drawInstancedTestImpl, unitTestContext, Slang::RenderApiFlag::D3D12); - } - -#if 0 - SLANG_UNIT_TEST(drawInstancedVulkan) - { - runTestImpl(drawInstancedTestImpl, unitTestContext, Slang::RenderApiFlag::Vulkan); - } -#endif - } diff --git a/tools/gfx-unit-test/gfx-test-util.cpp b/tools/gfx-unit-test/gfx-test-util.cpp index 619d2feb0..7514f22cf 100644 --- a/tools/gfx-unit-test/gfx-test-util.cpp +++ b/tools/gfx-unit-test/gfx-test-util.cpp @@ -150,6 +150,14 @@ namespace gfx_test SLANG_CHECK(memcmp(resultBlob->getBufferPointer(), expectedResult, expectedBufferSize) == 0); } + void compareComputeResultFuzzy(const float* result, float* expectedResult, size_t expectedBufferSize) + { + for (int i = 0; i < expectedBufferSize / sizeof(float); ++i) + { + SLANG_CHECK(abs(result[i] - expectedResult[i]) <= 0.01); + } + } + void compareComputeResultFuzzy(gfx::IDevice* device, gfx::IBufferResource* buffer, float* expectedResult, size_t expectedBufferSize) { // Read back the results. @@ -159,10 +167,7 @@ namespace gfx_test SLANG_CHECK(resultBlob->getBufferSize() == expectedBufferSize); // Compare results with a tolerance of 0.01. auto result = (float*)resultBlob->getBufferPointer(); - for (int i = 0; i < expectedBufferSize / sizeof(float); ++i) - { - SLANG_CHECK(abs(result[i] - expectedResult[i]) <= 0.01); - } + compareComputeResultFuzzy(result, expectedResult, expectedBufferSize); } Slang::ComPtr<gfx::IDevice> createTestingDevice(UnitTestContext* context, Slang::RenderApiFlag::Enum api) diff --git a/tools/gfx-unit-test/gfx-test-util.h b/tools/gfx-unit-test/gfx-test-util.h index addcb151b..04c602359 100644 --- a/tools/gfx-unit-test/gfx-test-util.h +++ b/tools/gfx-unit-test/gfx-test-util.h @@ -42,6 +42,11 @@ namespace gfx_test size_t expectedResultRowPitch, size_t rowCount); + void compareComputeResultFuzzy( + const float* result, + float* expectedResult, + size_t expectedBufferSize); + /// Reads back the content of `buffer` and compares it against `expectedResult` with a set tolerance. void compareComputeResultFuzzy( gfx::IDevice* device, diff --git a/tools/gfx-unit-test/graphics-smoke.slang b/tools/gfx-unit-test/graphics-smoke.slang index 145b83a62..464d6f723 100644 --- a/tools/gfx-unit-test/graphics-smoke.slang +++ b/tools/gfx-unit-test/graphics-smoke.slang @@ -3,7 +3,8 @@ // Per-vertex attributes to be assembled from bound vertex buffers. struct AssembledVertex { - float3 position : POSITION; + float3 positionA : POSITIONA; + float3 positionB : POSITIONB; float3 color : COLOR; }; @@ -33,7 +34,7 @@ VertexStageOutput vertexMain( { VertexStageOutput output; - float3 position = assembledVertex.position; + float3 position = assembledVertex.positionA + assembledVertex.positionB; float3 color = assembledVertex.color; output.coarseVertex.color = color; diff --git a/tools/gfx-unit-test/instanced-draw-tests.cpp b/tools/gfx-unit-test/instanced-draw-tests.cpp new file mode 100644 index 000000000..3e6f123ee --- /dev/null +++ b/tools/gfx-unit-test/instanced-draw-tests.cpp @@ -0,0 +1,581 @@ +#include "tools/unit-test/slang-unit-test.h" + +#include "slang-gfx.h" +#include "gfx-test-util.h" +#include "tools/gfx-util/shader-cursor.h" +#include "source/core/slang-basic.h" + +#include <stdlib.h> +#include <stdio.h> + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "external/stb/stb_image_write.h" + +using namespace gfx; + +namespace gfx_test +{ + // Testing only code used to dump images to visually confirm correctness. + // Will be removed once all draw tests are complete. +// Slang::Result writeImage( +// const char* filename, +// ISlangBlob* pixels, +// uint32_t width, +// uint32_t height) +// { +// int stbResult = +// stbi_write_hdr(filename, width, height, 4, (float*)pixels->getBufferPointer()); +// +// return stbResult ? SLANG_OK : SLANG_FAIL; +// } + + struct Vertex + { + float position[3]; + }; + + struct Instance + { + float position[3]; + float color[3]; + }; + + static const int kVertexCount = 6; + static const Vertex kVertexData[kVertexCount] = + { + // Triangle 1 + { 0, 0, 0.5 }, + { 1, 0, 0.5 }, + { 0, 1, 0.5 }, + + // Triangle 2 + { -1, 0, 0.5 }, + { 0, 0, 0.5 }, + { -1, 1, 0.5 }, + }; + + static const int kInstanceCount = 2; + static const Instance kInstanceData[kInstanceCount] = + { + { { 0, 0, 0 }, { 1, 0, 0 } }, + { { 0, -1, 0 }, { 0, 0, 1 } }, + }; + + static const int kIndexCount = 6; + static const uint32_t kIndexData[kIndexCount] = + { + 0, 2, 5, + 0, 1, 2, + }; + + const int kWidth = 256; + const int kHeight = 256; + const Format format = Format::R32G32B32A32_FLOAT; + + ComPtr<IBufferResource> createVertexBuffer(IDevice* device) + { + IBufferResource::Desc vertexBufferDesc; + vertexBufferDesc.type = IResource::Type::Buffer; + vertexBufferDesc.sizeInBytes = kVertexCount * sizeof(Vertex); + vertexBufferDesc.defaultState = ResourceState::VertexBuffer; + vertexBufferDesc.allowedStates = ResourceState::VertexBuffer; + ComPtr<IBufferResource> vertexBuffer = device->createBufferResource(vertexBufferDesc, &kVertexData[0]); + SLANG_CHECK_ABORT(vertexBuffer != nullptr); + return vertexBuffer; + } + + ComPtr<IBufferResource> createInstanceBuffer(IDevice* device) + { + IBufferResource::Desc instanceBufferDesc; + instanceBufferDesc.type = IResource::Type::Buffer; + instanceBufferDesc.sizeInBytes = kInstanceCount * sizeof(Instance); + instanceBufferDesc.defaultState = ResourceState::VertexBuffer; + instanceBufferDesc.allowedStates = ResourceState::VertexBuffer; + ComPtr<IBufferResource> instanceBuffer = device->createBufferResource(instanceBufferDesc, &kInstanceData[0]); + SLANG_CHECK_ABORT(instanceBuffer != nullptr); + return instanceBuffer; + } + + ComPtr<IBufferResource> createIndexBuffer(IDevice* device) + { + IBufferResource::Desc indexBufferDesc; + indexBufferDesc.type = IResource::Type::Buffer; + indexBufferDesc.sizeInBytes = kIndexCount * sizeof(uint32_t); + indexBufferDesc.defaultState = ResourceState::IndexBuffer; + indexBufferDesc.allowedStates = ResourceState::IndexBuffer; + ComPtr<IBufferResource> indexBuffer = device->createBufferResource(indexBufferDesc, &kIndexData[0]); + SLANG_CHECK_ABORT(indexBuffer != nullptr); + return indexBuffer; + } + + ComPtr<ITextureResource> createColorBuffer(IDevice* device) + { + gfx::ITextureResource::Desc colorBufferDesc; + colorBufferDesc.type = IResource::Type::Texture2D; + colorBufferDesc.size.width = kWidth; + colorBufferDesc.size.height = kHeight; + colorBufferDesc.size.depth = 1; + colorBufferDesc.numMipLevels = 1; + colorBufferDesc.format = format; + colorBufferDesc.defaultState = ResourceState::RenderTarget; + colorBufferDesc.allowedStates = { ResourceState::RenderTarget, ResourceState::CopySource }; + ComPtr<ITextureResource> colorBuffer = device->createTextureResource(colorBufferDesc, nullptr); + SLANG_CHECK_ABORT(colorBuffer != nullptr); + return colorBuffer; + } + + class BaseDrawTest + { + public: + ComPtr<IDevice> device; + UnitTestContext* context; + + ComPtr<ITransientResourceHeap> transientHeap; + ComPtr<IPipelineState> pipelineState; + ComPtr<IRenderPassLayout> renderPass; + ComPtr<IFramebuffer> framebuffer; + + ComPtr<IBufferResource> vertexBuffer; + ComPtr<IBufferResource> instanceBuffer; + ComPtr<ITextureResource> colorBuffer; + + void init(IDevice* device, UnitTestContext* context) + { + this->device = device; + this->context = context; + } + + void createRequiredResources() + { + InputElementDesc inputElements[] = { + // Vertex buffer data + { "POSITIONA", 0, Format::R32G32B32_FLOAT, offsetof(Vertex, position), InputSlotClass::PerVertex, 0 }, + + // Instance buffer data + { "POSITIONB", 0, Format::R32G32B32_FLOAT, offsetof(Instance, position), InputSlotClass::PerInstance, 1, 1 }, + { "COLOR", 0, Format::R32G32B32_FLOAT, offsetof(Instance, color), InputSlotClass::PerInstance, 1, 1 }, + }; + auto inputLayout = device->createInputLayout(inputElements, SLANG_COUNT_OF(inputElements)); + SLANG_CHECK_ABORT(inputLayout != nullptr); + + vertexBuffer = createVertexBuffer(device); + instanceBuffer = createInstanceBuffer(device); + colorBuffer = createColorBuffer(device); + + ITransientResourceHeap::Desc transientHeapDesc = {}; + transientHeapDesc.constantBufferSize = 4096; + GFX_CHECK_CALL_ABORT( + device->createTransientResourceHeap(transientHeapDesc, transientHeap.writeRef())); + + ComPtr<IShaderProgram> shaderProgram; + slang::ProgramLayout* slangReflection; + GFX_CHECK_CALL_ABORT(loadGraphicsProgram(device, shaderProgram, "graphics-smoke", "vertexMain", "fragmentMain", slangReflection)); + + IFramebufferLayout::AttachmentLayout attachmentLayout; + attachmentLayout.format = format; + attachmentLayout.sampleCount = 1; + + IFramebufferLayout::Desc framebufferLayoutDesc; + framebufferLayoutDesc.renderTargetCount = 1; + framebufferLayoutDesc.renderTargets = &attachmentLayout; + ComPtr<gfx::IFramebufferLayout> framebufferLayout = device->createFramebufferLayout(framebufferLayoutDesc); + SLANG_CHECK_ABORT(framebufferLayout != nullptr); + + GraphicsPipelineStateDesc pipelineDesc = {}; + pipelineDesc.program = shaderProgram.get(); + pipelineDesc.inputLayout = inputLayout; + pipelineDesc.framebufferLayout = framebufferLayout; + pipelineDesc.depthStencil.depthTestEnable = false; + pipelineDesc.depthStencil.depthWriteEnable = false; + GFX_CHECK_CALL_ABORT( + device->createGraphicsPipelineState(pipelineDesc, pipelineState.writeRef())); + + IRenderPassLayout::Desc renderPassDesc = {}; + renderPassDesc.framebufferLayout = framebufferLayout; + renderPassDesc.renderTargetCount = 1; + IRenderPassLayout::AttachmentAccessDesc renderTargetAccess = {}; + renderTargetAccess.loadOp = IRenderPassLayout::AttachmentLoadOp::Clear; + renderTargetAccess.storeOp = IRenderPassLayout::AttachmentStoreOp::Store; + renderTargetAccess.initialState = ResourceState::Undefined; + renderTargetAccess.finalState = ResourceState::CopySource; + renderPassDesc.renderTargetAccess = &renderTargetAccess; + GFX_CHECK_CALL_ABORT(device->createRenderPassLayout(renderPassDesc, renderPass.writeRef())); + + gfx::IResourceView::Desc colorBufferViewDesc; + memset(&colorBufferViewDesc, 0, sizeof(colorBufferViewDesc)); + colorBufferViewDesc.format = format; + colorBufferViewDesc.renderTarget.shape = gfx::IResource::Type::Texture2D; + colorBufferViewDesc.type = gfx::IResourceView::Type::RenderTarget; + auto rtv = device->createTextureView(colorBuffer, colorBufferViewDesc); + + gfx::IFramebuffer::Desc framebufferDesc; + framebufferDesc.renderTargetCount = 1; + framebufferDesc.depthStencilView = nullptr; + framebufferDesc.renderTargetViews = rtv.readRef(); + framebufferDesc.layout = framebufferLayout; + GFX_CHECK_CALL_ABORT(device->createFramebuffer(framebufferDesc, framebuffer.writeRef())); + } + + void checkTestResults(int pixelCount, int channelCount, const int* testXCoords, const int* testYCoords, float* testResults) + { + // Read texture values back from four specific pixels located within the triangles + // and compare against expected values (because testing every single pixel will be too long and tedious + // and requires maintaining reference images). + ComPtr<ISlangBlob> resultBlob; + size_t rowPitch = 0; + size_t pixelSize = 0; + GFX_CHECK_CALL_ABORT(device->readTextureResource( + colorBuffer, ResourceState::CopySource, resultBlob.writeRef(), &rowPitch, &pixelSize)); + auto result = (float*)resultBlob->getBufferPointer(); + + // Dump image to disk so we can visually confirm the output + //writeImage("C:/Users/lucchen/Documents/dump.hdr", resultBlob, kWidth, kHeight); + + int cursor = 0; + for (int i = 0; i < pixelCount; ++i) + { + auto x = testXCoords[i]; + auto y = testYCoords[i]; + auto pixelPtr = result + x * channelCount + y * rowPitch / sizeof(float); + for (int j = 0; j < channelCount; ++j) + { + testResults[cursor] = pixelPtr[j]; + cursor++; + } + } + + float expectedResult[] = { 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f }; + compareComputeResultFuzzy(testResults, expectedResult, sizeof(expectedResult)); + } + }; + + struct DrawInstancedTest : BaseDrawTest + { + void setUpAndDraw() + { + createRequiredResources(); + + ICommandQueue::Desc queueDesc = { ICommandQueue::QueueType::Graphics }; + auto queue = device->createCommandQueue(queueDesc); + auto commandBuffer = transientHeap->createCommandBuffer(); + + auto encoder = commandBuffer->encodeRenderCommands(renderPass, framebuffer); + auto rootObject = encoder->bindPipeline(pipelineState); + + gfx::Viewport viewport = {}; + viewport.maxZ = 1.0f; + viewport.extentX = kWidth; + viewport.extentY = kHeight; + encoder->setViewportAndScissor(viewport); + + uint32_t startVertex = 0; + uint32_t startInstanceLocation = 0; + + encoder->setVertexBuffer(0, vertexBuffer, sizeof(Vertex)); + encoder->setVertexBuffer(1, instanceBuffer, sizeof(Instance)); + encoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + encoder->drawInstanced(kVertexCount, kInstanceCount, startVertex, startInstanceLocation); + encoder->endEncoding(); + commandBuffer->close(); + queue->executeCommandBuffer(commandBuffer); + queue->waitOnHost(); + } + + void run() + { + setUpAndDraw(); + + const int kPixelCount = 4; + const int kChannelCount = 4; + int testXCoords[kPixelCount] = { 64, 192, 64, 192 }; + int testYCoords[kPixelCount] = { 100, 100, 250, 250 }; + float testResults[kPixelCount * kChannelCount]; + + checkTestResults(kPixelCount, kChannelCount, testXCoords, testYCoords, testResults); + } + }; + + struct DrawIndexedInstancedTest : BaseDrawTest + { + ComPtr<IBufferResource> indexBuffer; + + void setUpAndDraw() + { + createRequiredResources(); + + ICommandQueue::Desc queueDesc = { ICommandQueue::QueueType::Graphics }; + auto queue = device->createCommandQueue(queueDesc); + auto commandBuffer = transientHeap->createCommandBuffer(); + + auto encoder = commandBuffer->encodeRenderCommands(renderPass, framebuffer); + auto rootObject = encoder->bindPipeline(pipelineState); + + gfx::Viewport viewport = {}; + viewport.maxZ = 1.0f; + viewport.extentX = kWidth; + viewport.extentY = kHeight; + encoder->setViewportAndScissor(viewport); + + uint32_t startIndex = 0; + int32_t startVertex = 0; + uint32_t startInstanceLocation = 0; + + encoder->setVertexBuffer(0, vertexBuffer, sizeof(Vertex)); + encoder->setVertexBuffer(1, instanceBuffer, sizeof(Instance)); + encoder->setIndexBuffer(indexBuffer, Format::R32_UINT); + encoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + encoder->drawIndexedInstanced(kIndexCount, kInstanceCount, startIndex, startVertex, startInstanceLocation); + encoder->endEncoding(); + commandBuffer->close(); + queue->executeCommandBuffer(commandBuffer); + queue->waitOnHost(); + } + + void run() + { + indexBuffer = createIndexBuffer(device); + + setUpAndDraw(); + + const int kPixelCount = 4; + const int kChannelCount = 4; + int testXCoords[kPixelCount] = { 64, 192, 64, 192 }; + int testYCoords[kPixelCount] = { 32, 100, 150, 250 }; + float testResults[kPixelCount * kChannelCount]; + + checkTestResults(kPixelCount, kChannelCount, testXCoords, testYCoords, testResults); + } + }; + + struct DrawIndirectTest : BaseDrawTest + { + ComPtr<IBufferResource> indirectBuffer; + + struct IndirectArgData + { + float padding; // Ensure args and count don't start at 0 offset for testing purposes + IndirectDrawArguments args; + uint32_t count; + }; + + ComPtr<IBufferResource> createIndirectBuffer(IDevice* device) + { + static const IndirectArgData kIndirectData = + { + 42.0f, // padding + {6, 2, 0, 0}, // args + 1, // count + }; + + IBufferResource::Desc indirectBufferDesc; + indirectBufferDesc.type = IResource::Type::Buffer; + indirectBufferDesc.sizeInBytes = sizeof(IndirectArgData); + indirectBufferDesc.defaultState = ResourceState::IndirectArgument; + indirectBufferDesc.allowedStates = ResourceState::IndirectArgument; + ComPtr<IBufferResource> indexBuffer = device->createBufferResource(indirectBufferDesc, &kIndirectData); + SLANG_CHECK_ABORT(indexBuffer != nullptr); + return indexBuffer; + } + + void setUpAndDraw() + { + createRequiredResources(); + + ICommandQueue::Desc queueDesc = { ICommandQueue::QueueType::Graphics }; + auto queue = device->createCommandQueue(queueDesc); + auto commandBuffer = transientHeap->createCommandBuffer(); + + auto encoder = commandBuffer->encodeRenderCommands(renderPass, framebuffer); + auto rootObject = encoder->bindPipeline(pipelineState); + + gfx::Viewport viewport = {}; + viewport.maxZ = 1.0f; + viewport.extentX = kWidth; + viewport.extentY = kHeight; + encoder->setViewportAndScissor(viewport); + + encoder->setVertexBuffer(0, vertexBuffer, sizeof(Vertex)); + encoder->setVertexBuffer(1, instanceBuffer, sizeof(Instance)); + encoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + uint32_t maxDrawCount = 1; + uint64_t argOffset = offsetof(IndirectArgData, args); + uint64_t countOffset = offsetof(IndirectArgData, count); + + encoder->drawIndirect(maxDrawCount, indirectBuffer, argOffset, indirectBuffer, countOffset); + encoder->endEncoding(); + commandBuffer->close(); + queue->executeCommandBuffer(commandBuffer); + queue->waitOnHost(); + } + + void run() + { + indirectBuffer = createIndirectBuffer(device); + + setUpAndDraw(); + + const int kPixelCount = 4; + const int kChannelCount = 4; + int testXCoords[kPixelCount] = { 64, 192, 64, 192 }; + int testYCoords[kPixelCount] = { 100, 100, 250, 250 }; + float testResults[kPixelCount * kChannelCount]; + + checkTestResults(kPixelCount, kChannelCount, testXCoords, testYCoords, testResults); + } + }; + + struct DrawIndexedIndirectTest : BaseDrawTest + { + ComPtr<IBufferResource> indexBuffer; + ComPtr<IBufferResource> indirectBuffer; + + struct IndexedIndirectArgData + { + float padding; // Ensure args and count don't start at 0 offset for testing purposes + IndirectDrawIndexedArguments args; + uint32_t count; + }; + + ComPtr<IBufferResource> createIndirectBuffer(IDevice* device) + { + static const IndexedIndirectArgData kIndexedIndirectData = + { + 42.0f, // padding + {6, 2, 0, 0, 0}, // args + 1, // count + }; + + IBufferResource::Desc indirectBufferDesc; + indirectBufferDesc.type = IResource::Type::Buffer; + indirectBufferDesc.sizeInBytes = sizeof(IndexedIndirectArgData); + indirectBufferDesc.defaultState = ResourceState::IndirectArgument; + indirectBufferDesc.allowedStates = ResourceState::IndirectArgument; + ComPtr<IBufferResource> indexBuffer = device->createBufferResource(indirectBufferDesc, &kIndexedIndirectData); + SLANG_CHECK_ABORT(indexBuffer != nullptr); + return indexBuffer; + } + + void setUpAndDraw() + { + createRequiredResources(); + + ICommandQueue::Desc queueDesc = { ICommandQueue::QueueType::Graphics }; + auto queue = device->createCommandQueue(queueDesc); + auto commandBuffer = transientHeap->createCommandBuffer(); + + auto encoder = commandBuffer->encodeRenderCommands(renderPass, framebuffer); + auto rootObject = encoder->bindPipeline(pipelineState); + + gfx::Viewport viewport = {}; + viewport.maxZ = 1.0f; + viewport.extentX = kWidth; + viewport.extentY = kHeight; + encoder->setViewportAndScissor(viewport); + + encoder->setVertexBuffer(0, vertexBuffer, sizeof(Vertex)); + encoder->setVertexBuffer(1, instanceBuffer, sizeof(Instance)); + encoder->setIndexBuffer(indexBuffer, Format::R32_UINT); + encoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + uint32_t maxDrawCount = 1; + uint64_t argOffset = offsetof(IndexedIndirectArgData, args); + uint64_t countOffset = offsetof(IndexedIndirectArgData, count); + + encoder->drawIndexedIndirect(maxDrawCount, indirectBuffer, argOffset, indirectBuffer, countOffset); + encoder->endEncoding(); + commandBuffer->close(); + queue->executeCommandBuffer(commandBuffer); + queue->waitOnHost(); + } + + void run() + { + indexBuffer = createIndexBuffer(device); + indirectBuffer = createIndirectBuffer(device); + + setUpAndDraw(); + + const int kPixelCount = 4; + const int kChannelCount = 4; + int testXCoords[kPixelCount] = { 64, 192, 64, 192 }; + int testYCoords[kPixelCount] = { 32, 100, 150, 250 }; + float testResults[kPixelCount * kChannelCount]; + + checkTestResults(kPixelCount, kChannelCount, testXCoords, testYCoords, testResults); + } + }; + + void drawInstancedTestImpl(IDevice* device, UnitTestContext* context) + { + DrawInstancedTest test; + test.init(device, context); + test.run(); + } + + void drawIndexedInstancedTestImpl(IDevice* device, UnitTestContext* context) + { + DrawIndexedInstancedTest test; + test.init(device, context); + test.run(); + } + + void drawIndirectTestImpl(IDevice* device, UnitTestContext* context) + { + DrawIndirectTest test; + test.init(device, context); + test.run(); + } + + void drawIndexedIndirectTestImpl(IDevice* device, UnitTestContext* context) + { + DrawIndexedIndirectTest test; + test.init(device, context); + test.run(); + } + + SLANG_UNIT_TEST(drawInstancedD3D12) + { + runTestImpl(drawInstancedTestImpl, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(drawIndexedInstancedD3D12) + { + runTestImpl(drawIndexedInstancedTestImpl, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(drawIndirectD3D12) + { + runTestImpl(drawIndirectTestImpl, unitTestContext, Slang::RenderApiFlag::D3D12); + } + + SLANG_UNIT_TEST(drawIndexedIndirectD3D12) + { + runTestImpl(drawIndexedIndirectTestImpl, unitTestContext, Slang::RenderApiFlag::D3D12); + } + +#if 0 + SLANG_UNIT_TEST(drawInstancedVulkan) + { + runTestImpl(drawInstancedTestImpl, unitTestContext, Slang::RenderApiFlag::Vulkan); + } + + SLANG_UNIT_TEST(drawIndexedInstancedVulkan) + { + runTestImpl(drawIndexedInstancedTestImpl, unitTestContext, Slang::RenderApiFlag::Vulkan); + } + + SLANG_UNIT_TEST(drawIndirectVulkan) + { + runTestImpl(drawIndirectTestImpl, unitTestContext, Slang::RenderApiFlag::Vulkan); + } + + SLANG_UNIT_TEST(drawIndexedIndirectVulkan) + { + runTestImpl(drawIndexedIndirectTestImpl, unitTestContext, Slang::RenderApiFlag::Vulkan); + } +#endif + } diff --git a/tools/gfx/d3d12/render-d3d12.cpp b/tools/gfx/d3d12/render-d3d12.cpp index 3daddc73f..aab260cd1 100644 --- a/tools/gfx/d3d12/render-d3d12.cpp +++ b/tools/gfx/d3d12/render-d3d12.cpp @@ -3425,26 +3425,15 @@ public: { prepareDraw(); - D3D12_INDIRECT_ARGUMENT_DESC args[1]; - args[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW; - - D3D12_COMMAND_SIGNATURE_DESC desc; - desc.ByteStride = 36; - desc.NumArgumentDescs = 1; - desc.pArgumentDescs = args; - - ComPtr<ID3D12CommandSignature> cmdSignature = nullptr; - if (FAILED(m_device->CreateCommandSignature(&desc, nullptr, IID_PPV_ARGS(cmdSignature.writeRef())))) - { - return; - } + auto argBufferImpl = static_cast<BufferResourceImpl*>(argBuffer); + auto countBufferImpl = static_cast<BufferResourceImpl*>(countBuffer); m_d3dCmdList->ExecuteIndirect( - cmdSignature, + m_renderer->drawIndirectCmdSignature, maxDrawCount, - (ID3D12Resource*)argBuffer, + argBufferImpl->m_resource, argOffset, - (ID3D12Resource*)countBuffer, + countBufferImpl->m_resource, countOffset); } @@ -3457,26 +3446,15 @@ public: { prepareDraw(); - D3D12_INDIRECT_ARGUMENT_DESC args[1]; - args[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED; - - D3D12_COMMAND_SIGNATURE_DESC desc; - desc.ByteStride = 36; - desc.NumArgumentDescs = 1; - desc.pArgumentDescs = args; - - ComPtr<ID3D12CommandSignature> cmdSignature = nullptr; - if (FAILED(m_device->CreateCommandSignature(&desc, nullptr, IID_PPV_ARGS(cmdSignature.writeRef())))) - { - return; - } + auto argBufferImpl = static_cast<BufferResourceImpl*>(argBuffer); + auto countBufferImpl = static_cast<BufferResourceImpl*>(countBuffer); m_d3dCmdList->ExecuteIndirect( - cmdSignature, + m_renderer->drawIndexedIndirectCmdSignature, maxDrawCount, - (ID3D12Resource*)argBuffer, + argBufferImpl->m_resource, argOffset, - (ID3D12Resource*)countBuffer, + countBufferImpl->m_resource, countOffset); } @@ -4549,6 +4527,9 @@ public: PFN_D3D12_SERIALIZE_ROOT_SIGNATURE m_D3D12SerializeRootSignature = nullptr; bool m_nvapi = false; + + ComPtr<ID3D12CommandSignature> drawIndirectCmdSignature; + ComPtr<ID3D12CommandSignature> drawIndexedIndirectCmdSignature; }; SLANG_NO_THROW Result SLANG_MCALL D3D12Device::TransientResourceHeapImpl::synchronizeAndReset() @@ -5328,6 +5309,32 @@ Result D3D12Device::initialize(const Desc& desc) profileName, makeArray(slang::PreprocessorMacroDesc{"__D3D12__", "1"}).getView())); + { + D3D12_INDIRECT_ARGUMENT_DESC args[1]; + args[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW; + + D3D12_COMMAND_SIGNATURE_DESC desc; + desc.ByteStride = 36; + desc.NumArgumentDescs = 1; + desc.pArgumentDescs = args; + desc.NodeMask = 0; + + SLANG_RETURN_ON_FAIL(m_device->CreateCommandSignature(&desc, nullptr, IID_PPV_ARGS(drawIndirectCmdSignature.writeRef()))); + } + + { + D3D12_INDIRECT_ARGUMENT_DESC args[1]; + args[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED; + + D3D12_COMMAND_SIGNATURE_DESC desc; + desc.ByteStride = 36; + desc.NumArgumentDescs = 1; + desc.pArgumentDescs = args; + desc.NodeMask = 0; + + SLANG_RETURN_ON_FAIL(m_device->CreateCommandSignature(&desc, nullptr, IID_PPV_ARGS(drawIndexedIndirectCmdSignature.writeRef()))); + } + m_isInitialized = true; return SLANG_OK; } |
