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.
This commit is contained in:
Corentin Wallez 2017-06-26 16:23:03 -04:00 committed by Corentin Wallez
parent 492cbe4a43
commit e9d347e89e
8 changed files with 254 additions and 0 deletions

View File

@ -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": [

View File

@ -129,6 +129,12 @@ namespace backend {
copy->~CopyBufferToTextureCmd();
}
break;
case Command::CopyTextureToBuffer:
{
CopyTextureToBufferCmd* copy = commands->NextCommand<CopyTextureToBufferCmd>();
copy->~CopyTextureToBufferCmd();
}
break;
case Command::Dispatch:
{
DispatchCmd* dispatch = commands->NextCommand<DispatchCmd>();
@ -286,6 +292,22 @@ namespace backend {
}
break;
case Command::CopyTextureToBuffer:
{
CopyTextureToBufferCmd* copy = iterator.NextCommand<CopyTextureToBufferCmd>();
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<DispatchCmd>();
@ -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<CopyTextureToBufferCmd>(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<DispatchCmd>(Command::Dispatch);
new(dispatch) DispatchCmd;

View File

@ -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);

View File

@ -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;

View File

@ -73,6 +73,7 @@ namespace d3d12 {
commandList->OMSetRenderTargets(1, &device->GetCurrentRenderTargetDescriptor(), FALSE, nullptr);
}
break;
case Command::CopyBufferToBuffer:
{
CopyBufferToBufferCmd* copy = commands.NextCommand<CopyBufferToBufferCmd>();
@ -85,6 +86,12 @@ namespace d3d12 {
}
break;
case Command::CopyTextureToBuffer:
{
CopyTextureToBufferCmd* copy = commands.NextCommand<CopyTextureToBufferCmd>();
}
break;
case Command::Dispatch:
{
DispatchCmd* dispatch = commands.NextCommand<DispatchCmd>();

View File

@ -209,6 +209,39 @@ namespace metal {
}
break;
case Command::CopyTextureToBuffer:
{
CopyTextureToBufferCmd* copy = commands.NextCommand<CopyTextureToBufferCmd>();
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<DispatchCmd>();

View File

@ -114,6 +114,13 @@ namespace opengl {
}
break;
case Command::CopyTextureToBuffer:
{
CopyTextureToBufferCmd* copy = commands.NextCommand<CopyTextureToBufferCmd>();
// TODO(cwallez@chromium.org): implement using a temporary FBO and ReadPixels
}
break;
case Command::Dispatch:
{
DispatchCmd* dispatch = commands.NextCommand<DispatchCmd>();

View File

@ -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();
}
}