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:
parent
492cbe4a43
commit
e9d347e89e
22
next.json
22
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": [
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue