From e9d347e89e1f215bac1f6428793aa1496b0b49d8 Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Mon, 26 Jun 2017 16:23:03 -0400 Subject: [PATCH] Add T->B copies. This implements T->B copies on the Metal backend only and while it adds validation tests, end2end tests will be done in a follow-up commit. --- next.json | 22 +++ src/backend/common/CommandBuffer.cpp | 39 +++++ src/backend/common/CommandBuffer.h | 3 + src/backend/common/Commands.h | 6 + src/backend/d3d12/CommandBufferD3D12.cpp | 7 + src/backend/metal/CommandBufferMTL.mm | 33 +++++ src/backend/opengl/CommandBufferGL.cpp | 7 + .../CopyCommandsValidationTests.cpp | 137 ++++++++++++++++++ 8 files changed, 254 insertions(+) diff --git a/next.json b/next.json index c441745902..b67f27bdee 100644 --- a/next.json +++ b/next.json @@ -295,6 +295,28 @@ {"name": "image height", "type": "uint32_t"} ] }, + { + "name": "copy texture to buffer", + "args": [ + {"name": "texture", "type": "texture"}, + {"name": "x", "type": "uint32_t"}, + {"name": "y", "type": "uint32_t"}, + {"name": "z", "type": "uint32_t"}, + {"name": "width", "type": "uint32_t"}, + {"name": "height", "type": "uint32_t"}, + {"name": "depth", "type": "uint32_t"}, + {"name": "level", "type": "uint32_t"}, + {"name": "buffer", "type": "buffer"}, + {"name": "buffer offset", "type": "uint32_t"} + ], + "TODO": [ + "Make pretty with Offset and Extents structures", + "Allow choosing the aspect (depth vs. stencil)?", + "Add these arguments too", + {"name": "row length", "type": "uint32_t"}, + {"name": "image height", "type": "uint32_t"} + ] + }, { "name": "dispatch", "args": [ diff --git a/src/backend/common/CommandBuffer.cpp b/src/backend/common/CommandBuffer.cpp index d21eeca135..4ed0695b8f 100644 --- a/src/backend/common/CommandBuffer.cpp +++ b/src/backend/common/CommandBuffer.cpp @@ -129,6 +129,12 @@ namespace backend { copy->~CopyBufferToTextureCmd(); } break; + case Command::CopyTextureToBuffer: + { + CopyTextureToBufferCmd* copy = commands->NextCommand(); + copy->~CopyTextureToBufferCmd(); + } + break; case Command::Dispatch: { DispatchCmd* dispatch = commands->NextCommand(); @@ -286,6 +292,22 @@ namespace backend { } break; + case Command::CopyTextureToBuffer: + { + CopyTextureToBufferCmd* copy = iterator.NextCommand(); + + uint32_t bufferCopySize = 0; + if (!ComputeTextureCopyBufferSize(this, copy->source, &bufferCopySize) || + !ValidateCopyLocationFitsInTexture(this, copy->source) || + !ValidateCopySizeFitsInBuffer(this, copy->destination, bufferCopySize) || + !state->ValidateCanCopy() || + !state->ValidateCanUseTextureAs(copy->source.texture.Get(), nxt::TextureUsageBit::TransferSrc) || + !state->ValidateCanUseBufferAs(copy->destination.buffer.Get(), nxt::BufferUsageBit::TransferDst)) { + return false; + } + } + break; + case Command::Dispatch: { iterator.NextCommand(); @@ -460,6 +482,23 @@ namespace backend { copy->destination.level = level; } + void CommandBufferBuilder::CopyTextureToBuffer(TextureBase* texture, uint32_t x, uint32_t y, uint32_t z, + uint32_t width, uint32_t height, uint32_t depth, uint32_t level, + BufferBase* buffer, uint32_t bufferOffset) { + CopyTextureToBufferCmd* copy = allocator.Allocate(Command::CopyTextureToBuffer); + new(copy) CopyTextureToBufferCmd; + copy->source.texture = texture; + copy->source.x = x; + copy->source.y = y; + copy->source.z = z; + copy->source.width = width; + copy->source.height = height; + copy->source.depth = depth; + copy->source.level = level; + copy->destination.buffer = buffer; + copy->destination.offset = bufferOffset; + } + void CommandBufferBuilder::Dispatch(uint32_t x, uint32_t y, uint32_t z) { DispatchCmd* dispatch = allocator.Allocate(Command::Dispatch); new(dispatch) DispatchCmd; diff --git a/src/backend/common/CommandBuffer.h b/src/backend/common/CommandBuffer.h index 8c85ec37ac..2f62614ac2 100644 --- a/src/backend/common/CommandBuffer.h +++ b/src/backend/common/CommandBuffer.h @@ -65,6 +65,9 @@ namespace backend { void CopyBufferToTexture(BufferBase* buffer, uint32_t bufferOffset, TextureBase* texture, uint32_t x, uint32_t y, uint32_t z, uint32_t width, uint32_t height, uint32_t depth, uint32_t level); + void CopyTextureToBuffer(TextureBase* texture, uint32_t x, uint32_t y, uint32_t z, + uint32_t width, uint32_t height, uint32_t depth, uint32_t level, + BufferBase* buffer, uint32_t bufferOffset); void Dispatch(uint32_t x, uint32_t y, uint32_t z); void DrawArrays(uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance); void DrawElements(uint32_t vertexCount, uint32_t instanceCount, uint32_t firstIndex, uint32_t firstInstance); diff --git a/src/backend/common/Commands.h b/src/backend/common/Commands.h index 9809412b4c..8ec86c2c5b 100644 --- a/src/backend/common/Commands.h +++ b/src/backend/common/Commands.h @@ -32,6 +32,7 @@ namespace backend { BeginRenderPass, CopyBufferToBuffer, CopyBufferToTexture, + CopyTextureToBuffer, Dispatch, DrawArrays, DrawElements, @@ -77,6 +78,11 @@ namespace backend { TextureCopyLocation destination; }; + struct CopyTextureToBufferCmd { + TextureCopyLocation source; + BufferCopyLocation destination; + }; + struct DispatchCmd { uint32_t x; uint32_t y; diff --git a/src/backend/d3d12/CommandBufferD3D12.cpp b/src/backend/d3d12/CommandBufferD3D12.cpp index 1d5ebd83e9..17b38f892c 100644 --- a/src/backend/d3d12/CommandBufferD3D12.cpp +++ b/src/backend/d3d12/CommandBufferD3D12.cpp @@ -73,6 +73,7 @@ namespace d3d12 { commandList->OMSetRenderTargets(1, &device->GetCurrentRenderTargetDescriptor(), FALSE, nullptr); } break; + case Command::CopyBufferToBuffer: { CopyBufferToBufferCmd* copy = commands.NextCommand(); @@ -85,6 +86,12 @@ namespace d3d12 { } break; + case Command::CopyTextureToBuffer: + { + CopyTextureToBufferCmd* copy = commands.NextCommand(); + } + break; + case Command::Dispatch: { DispatchCmd* dispatch = commands.NextCommand(); diff --git a/src/backend/metal/CommandBufferMTL.mm b/src/backend/metal/CommandBufferMTL.mm index 77c45569f3..2a0ab0412f 100644 --- a/src/backend/metal/CommandBufferMTL.mm +++ b/src/backend/metal/CommandBufferMTL.mm @@ -209,6 +209,39 @@ namespace metal { } break; + case Command::CopyTextureToBuffer: + { + CopyTextureToBufferCmd* copy = commands.NextCommand(); + auto& src = copy->source; + auto& dst = copy->destination; + Texture* texture = ToBackend(src.texture.Get()); + Buffer* buffer = ToBackend(dst.buffer.Get()); + + unsigned rowSize = src.width * TextureFormatPixelSize(texture->GetFormat()); + MTLOrigin origin; + origin.x = src.x; + origin.y = src.y; + origin.z = src.z; + + MTLSize size; + size.width = src.width; + size.height = src.height; + size.depth = src.depth; + + encoders.EnsureBlit(commandBuffer); + [encoders.blit + copyFromTexture:texture->GetMTLTexture() + sourceSlice:0 + sourceLevel:src.level + sourceOrigin:origin + sourceSize:size + toBuffer:buffer->GetMTLBuffer() + destinationOffset:dst.offset + destinationBytesPerRow:rowSize + destinationBytesPerImage:rowSize * src.height]; + } + break; + case Command::Dispatch: { DispatchCmd* dispatch = commands.NextCommand(); diff --git a/src/backend/opengl/CommandBufferGL.cpp b/src/backend/opengl/CommandBufferGL.cpp index d258cf61c2..3b38cf1f4d 100644 --- a/src/backend/opengl/CommandBufferGL.cpp +++ b/src/backend/opengl/CommandBufferGL.cpp @@ -114,6 +114,13 @@ namespace opengl { } break; + case Command::CopyTextureToBuffer: + { + CopyTextureToBufferCmd* copy = commands.NextCommand(); + // TODO(cwallez@chromium.org): implement using a temporary FBO and ReadPixels + } + break; + case Command::Dispatch: { DispatchCmd* dispatch = commands.NextCommand(); diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp index 3aeac67d95..bda37fce0c 100644 --- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp +++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp @@ -245,3 +245,140 @@ TEST_F(CopyCommandTest_B2T, IncorrectUsage) { .GetResult(); } } + +class CopyCommandTest_T2B : public CopyCommandTest { +}; + +// Test a successfull T2B copy +TEST_F(CopyCommandTest_T2B, Success) { + nxt::Texture source = CreateFrozen2DTexture(16, 16, 5, nxt::TextureFormat::R8G8B8A8Unorm, + nxt::TextureUsageBit::TransferSrc); + nxt::Buffer destination = CreateFrozenBuffer(16 * 4, nxt::BufferUsageBit::TransferDst); + + // Different copies, including some that touch the OOB condition + { + nxt::CommandBuffer commands = AssertWillBeSuccess(device.CreateCommandBufferBuilder()) + // Copy from 4x4 block in corner of first mip. + .CopyTextureToBuffer(source, 0, 0, 0, 4, 4, 1, 0, destination, 0) + // Copy from 4x4 block in opposite corner of first mip. + .CopyTextureToBuffer(source, 12, 12, 0, 4, 4, 1, 0, destination, 0) + // Copy from 4x4 block in the 4x4 mip. + .CopyTextureToBuffer(source, 0, 0, 0, 4, 4, 1, 2, destination, 0) + // Copy with a buffer offset + .CopyTextureToBuffer(source, 0, 0, 0, 1, 1, 1, 4, destination, 15 * 4) + .GetResult(); + } + + // Empty copies are valid + { + nxt::CommandBuffer commands = AssertWillBeSuccess(device.CreateCommandBufferBuilder()) + // An empty copy + .CopyTextureToBuffer(source, 0, 0, 0, 0, 0, 1, 0, destination, 0) + // An empty copy touching the end of the buffer + .CopyTextureToBuffer(source, 0, 0, 0, 0, 0, 1, 0, destination, 16 * 4) + // An empty copy touching the side of the texture + .CopyTextureToBuffer(source, 16, 16, 0, 0, 0, 1, 0, destination, 0) + .GetResult(); + } +} + +// Test OOB conditions on the texture +TEST_F(CopyCommandTest_T2B, OutOfBoundsOnTexture) { + nxt::Texture source = CreateFrozen2DTexture(16, 16, 5, nxt::TextureFormat::R8G8B8A8Unorm, + nxt::TextureUsageBit::TransferSrc); + nxt::Buffer destination = CreateFrozenBuffer(16 * 4, nxt::BufferUsageBit::TransferDst); + + // OOB on the texture because x + width overflows + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 13, 12, 0, 4, 4, 1, 0, destination, 0) + .GetResult(); + } + + // OOB on the texture because y + width overflows + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 12, 13, 0, 4, 4, 1, 0, destination, 0) + .GetResult(); + } + + // OOB on the texture because we overflow a non-zero mip + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 1, 0, 0, 4, 4, 1, 2, destination, 0) + .GetResult(); + } + + // OOB on the texture even on an empty copy when we copy from a non-existent mip. + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 0, 0, 0, 0, 0, 1, 5, destination, 0) + .GetResult(); + } +} + +// Test OOB conditions on the buffer +TEST_F(CopyCommandTest_T2B, OutOfBoundsOnBuffer) { + nxt::Texture source = CreateFrozen2DTexture(16, 16, 5, nxt::TextureFormat::R8G8B8A8Unorm, + nxt::TextureUsageBit::TransferSrc); + nxt::Buffer destination = CreateFrozenBuffer(16 * 4, nxt::BufferUsageBit::TransferDst); + + // OOB on the buffer because we copy too many pixels + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 0, 0, 0, 4, 5, 1, 0, destination, 0) + .GetResult(); + } + + // OOB on the buffer because of the offset + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 0, 0, 0, 4, 4, 1, 0, destination, 1) + .GetResult(); + } +} + +// Test that we force Z=0 and Depth=1 on copies from to 2D textures +TEST_F(CopyCommandTest_T2B, ZDepthConstraintFor2DTextures) { + nxt::Texture source = CreateFrozen2DTexture(16, 16, 5, nxt::TextureFormat::R8G8B8A8Unorm, + nxt::TextureUsageBit::TransferSrc); + nxt::Buffer destination = CreateFrozenBuffer(16 * 4, nxt::BufferUsageBit::TransferDst); + + // Z=1 on an empty copy still errors + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 0, 0, 1, 0, 0, 1, 0, destination, 0) + .GetResult(); + } + + // Depth=0 on an empty copy still errors + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 0, 0, 0, 0, 0, 0, 0, destination, 0) + .GetResult(); + } +} + +// Test B2B copies with incorrect buffer usage +TEST_F(CopyCommandTest_T2B, IncorrectUsage) { + nxt::Texture source = CreateFrozen2DTexture(16, 16, 5, nxt::TextureFormat::R8G8B8A8Unorm, + nxt::TextureUsageBit::TransferSrc); + nxt::Texture sampled = CreateFrozen2DTexture(16, 16, 5, nxt::TextureFormat::R8G8B8A8Unorm, + nxt::TextureUsageBit::Sampled); + nxt::Buffer destination = CreateFrozenBuffer(16 * 4, nxt::BufferUsageBit::TransferDst); + nxt::Buffer vertex = CreateFrozenBuffer(16 * 4, nxt::BufferUsageBit::Vertex); + + // Incorrect source usage + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(sampled, 0, 0, 0, 4, 4, 1, 0, destination, 0) + .GetResult(); + } + + // Incorrect destination usage + { + nxt::CommandBuffer commands = AssertWillBeError(device.CreateCommandBufferBuilder()) + .CopyTextureToBuffer(source, 0, 0, 0, 4, 4, 1, 0, vertex, 0) + .GetResult(); + } +}