BlitCommandEncoder copy fails because of a totalBytesUsed conflict

We are working with interlaced HD video. The metal color attachment I use for rendering has the dimensions of one field (1920*540 RGBA).

When I want to copy the two rendered fields into a MTLBuffer that has the size of 1920*1080*4 = 8294400 bytes I use the following code...


            let commandBuffer = commandQueue.makeCommandBuffer()
            let blitEncoder = commandBuffer.makeBlitCommandEncoder()
            blitEncoder.copy(from: attachmentTexture,
                             sourceSlice: 0,
                             sourceLevel: 0,
                             sourceOrigin: MTLOriginMake(0, 0, 0),
                             sourceSize: MTLSizeMake(attachmentTexture.width, attachmentTexture.height, 1),
                             to: destinationBuffer,
                             destinationOffset: 1920*4,
                             destinationBytesPerRow: 1920*4*2,
                             destinationBytesPerImage: destinationBuffer.length)
            blitEncoder.endEncoding()
            commandBuffer.commit()


For the first field where the destination offset is zero the function works well. The destination buffer is filled for every second row.


But when I want to write the second field with the same code into the buffer only with the destinationOffset set to 1920*4 like you see above (to start with the second row in the buffer) then I get an assertion like this:


-[MTLDebugBlitCommandEncoder validateCopyFromTexture:sourceSlice:sourceLevel:sourceOrigin:sourceSize:toBuffer:destinationOffset:destinationBytesPerRow:destinationBytesPerImage:options:]:677: failed assertion `totalBytesUsed(8302080) must be <= [destinationBuffer length](8294400).'


The totalBytesUsed are exactly the destination buffer length in bytes plus the offset. So every offset I use in this function will result in this assertion error.

Can someone explain me what I am doing wrong with this function?

Replies

Hey Arnfried


If I understand well what you want to do here, you're trying to "deinterlace" your video. You have two fields (textures), both 1920x540 RGBA. What you want to do is copy them to buffer with 1920x1080x4 bytes, but so that first 1920x4 bytes come from first texture, second 1920x4 bytes come from second texture and so on. This is what I _understain_ you're trying to do.


Now if it is correct, let's look at what you're really doing here. In essence, you're using Blit function that copies from texture to buffer, instructing it to read 1920 RGBA values from each line of source texture, and to write these into buffer, but using 1920x4x2 bytes! So, basically, you're telling metal "yeah, use 1920x4x2 bytes per each line of 1920 RGBA values". So for the first pass you write 540x1920x4x2=8294400 bytes at offset 0 into buffer that is precisely 8294400 bytes long. And all is good and well. But then your second pass tells metal to write same 8294400 bytes but AT OFFSET 1920x4=7680 into buffer that is again, only 8294400 bytes long. But 8294400+7680=8303080, which is more than buffer size, which is why it is yelling at you and it is correct assertion - because you just told this thing to overrun a buffer.


Short and ugly solution would be to increase buffer size (add 7680 bytes that are missing) BUT, and it is a big but, you still may not get what you want. I don't believe (but haven't checked) that this function is guaranteed to write only what it reads, that is, leave excess row bytes as some kind of "padding". If it does, fine, you'll get your deinterlaced image. If it doesn't, however, you get something like second image shifted one line down and with random garbage in between.


What I'd do is to create a kernel writing to destination buffer (or change that for texture and fragment shader), which, depending on the line it is on, reads alternatively from two given textures. Something like this (pseudocode):

float4 oddPixel = oddTexture.read(uint2(gid.x, gid.y/2)), evenPixel = evenTexture.read(uint2(gid.x,gid.y/2));

return (gid.y&1)?oddPixel:evenPixel;


Hope that helps

Michal