Metal: fix blitting when render target is smaller than texture (#5616)

This commit is contained in:
Ben Doherty
2022-06-01 14:47:57 -07:00
committed by GitHub
parent 140ebbf10d
commit 2a84fe052c
4 changed files with 55 additions and 63 deletions
+1
View File
@@ -7,6 +7,7 @@ A new header is inserted each time a *tag* is created.
- gltfio: support skinning with bones that do not belong to any scene
- gltfio: add attachSkin / detachSkin method to FilamentAsset
- Metal: fix issues seen with dynamic resolution on M1 Macs
- engine: add a "global" mode for render primitive's `blendOrder`
- engine: remove `RenderManager::setGeometryAt(index, count)` [⚠️ **API Change**]
+12 -42
View File
@@ -1073,33 +1073,13 @@ void MetalDriver::blit(TargetBufferFlags buffers,
dstRect.left >= 0 && dstRect.bottom >= 0,
"Source and destination rects must be positive.");
// Metal's texture coordinates have (0, 0) at the top-left of the texture, but Filament's
// coordinates have (0, 0) at bottom-left.
const NSInteger srcHeight =
srcTarget->isDefaultRenderTarget() ?
mContext->currentReadSwapChain->getSurfaceHeight() : srcTarget->height;
MTLRegion srcRegion = MTLRegionMake2D(
(NSUInteger) srcRect.left,
srcHeight - (NSUInteger) srcRect.bottom - srcRect.height,
srcRect.width, srcRect.height);
const NSInteger dstHeight =
dstTarget->isDefaultRenderTarget() ?
mContext->currentDrawSwapChain->getSurfaceHeight() : dstTarget->height;
MTLRegion dstRegion = MTLRegionMake2D(
(NSUInteger) dstRect.left,
dstHeight - (NSUInteger) dstRect.bottom - dstRect.height,
dstRect.width, dstRect.height);
auto isBlitableTextureType = [](MTLTextureType t) {
return t == MTLTextureType2D || t == MTLTextureType2DMultisample ||
t == MTLTextureType2DArray;
};
MetalBlitter::BlitArgs args;
args.filter = filter;
args.source.region = srcRegion;
args.destination.region = dstRegion;
// MetalBlitter supports blitting color and depth simultaneously, but for simplicitly we'll blit
// them separately. In practice, Filament only ever blits a single buffer at a time anyway.
if (any(buffers & TargetBufferFlags::COLOR_ALL)) {
// We always blit from/to the COLOR0 attachment.
@@ -1111,12 +1091,17 @@ void MetalDriver::blit(TargetBufferFlags buffers,
isBlitableTextureType(dstColorAttachment.getTexture().textureType),
"Metal does not support blitting to/from non-2D textures.");
MetalBlitter::BlitArgs args;
args.filter = filter;
args.source.region = srcColorAttachment.getRegionFromClientRect(srcRect);
args.destination.region = dstColorAttachment.getRegionFromClientRect(dstRect);
args.source.color = srcColorAttachment.getTexture();
args.destination.color = dstColorAttachment.getTexture();
args.source.level = srcColorAttachment.level;
args.destination.level = dstColorAttachment.level;
args.source.slice = srcColorAttachment.layer;
args.destination.slice = dstColorAttachment.layer;
mContext->blitter->blit(getPendingCommandBuffer(mContext), args);
}
}
@@ -1129,34 +1114,19 @@ void MetalDriver::blit(TargetBufferFlags buffers,
isBlitableTextureType(dstDepthAttachment.getTexture().textureType),
"Metal does not support blitting to/from non-2D textures.");
MetalBlitter::BlitArgs args;
args.filter = filter;
args.source.region = srcDepthAttachment.getRegionFromClientRect(srcRect);
args.destination.region = dstDepthAttachment.getRegionFromClientRect(dstRect);
args.source.depth = srcDepthAttachment.getTexture();
args.destination.depth = dstDepthAttachment.getTexture();
if (args.blitColor()) {
// If blitting color, we've already set the source and destination levels and slices.
// Check that they match the requested depth levels/slices.
ASSERT_PRECONDITION(args.source.level == srcDepthAttachment.level,
"Color and depth source LOD must match. (%d != %d)",
args.source.level, srcDepthAttachment.level);
ASSERT_PRECONDITION(args.destination.level == dstDepthAttachment.level,
"Color and depth destination LOD must match. (%d != %d)",
args.destination.level, dstDepthAttachment.level);
ASSERT_PRECONDITION(args.source.slice == srcDepthAttachment.layer,
"Color and depth source layer must match. (%d != %d)",
args.source.slice, srcDepthAttachment.layer);
ASSERT_PRECONDITION(args.destination.slice == dstDepthAttachment.layer,
"Color and depth destination layer must match. (%d != %d)",
args.destination.slice, dstDepthAttachment.layer);
}
args.source.level = srcDepthAttachment.level;
args.destination.level = dstDepthAttachment.level;
args.source.slice = srcDepthAttachment.layer;
args.destination.slice = dstDepthAttachment.layer;
mContext->blitter->blit(getPendingCommandBuffer(mContext), args);
}
}
mContext->blitter->blit(getPendingCommandBuffer(mContext), args);
}
void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t instanceCount) {
+10
View File
@@ -257,6 +257,16 @@ public:
return texture ? texture.pixelFormat : MTLPixelFormatInvalid;
}
MTLRegion getRegionFromClientRect(Viewport rect) {
// Convert the Filament rect into Metal texture coordinates, taking into account Metal's
// inverted texture space and the mip level. Note that the underlying Texture could be
// larger than the RenderTarget. Metal's texture coordinates have (0, 0) at the top-left
// of the texture, but Filament's coordinates have (0, 0) at bottom-left.
const auto mipheight = texture.height >> level;
return MTLRegionMake2D((NSUInteger)rect.left,
mipheight - (NSUInteger)rect.bottom - rect.height, rect.width, rect.height);
}
explicit operator bool() const {
return texture != nil;
}
+32 -21
View File
@@ -876,6 +876,8 @@ TEST_F(BackendTest, BlitRegion) {
constexpr int kDstTexHeight = 384;
constexpr auto kDstTexFormat = TextureFormat::RGBA8;
constexpr int kNumLevels = 3;
constexpr int kSrcLevel = 1;
constexpr int kDstLevel = 0;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
@@ -894,23 +896,12 @@ TEST_F(BackendTest, BlitRegion) {
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
srcRenderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, { srcTexture, level, 0 }, {}, {});
dstRenderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR,
kDstTexWidth >> level, kDstTexHeight >> level, 1, { dstTexture, level, 0 }, {}, {});
}
// Blit one-quarter of src level 1 to dst level 0.
const int srcLevel = 1;
Viewport srcRect = {
.left = (kSrcTexWidth >> srcLevel) / 2,
.bottom = (kSrcTexHeight >> srcLevel) / 2,
.width = (kSrcTexWidth >> srcLevel) / 2,
.height = (kSrcTexHeight >> srcLevel) / 2,
.left = 0,
.bottom = 0,
.width = (kSrcTexWidth >> kSrcLevel) / 2,
.height = (kSrcTexHeight >> kSrcLevel) / 2,
};
Viewport dstRect = {
.left = 10,
@@ -919,9 +910,18 @@ TEST_F(BackendTest, BlitRegion) {
.height = kDstTexHeight - 10,
};
api.blit(TargetBufferFlags::COLOR0, dstRenderTargets[0],
dstRect, srcRenderTargets[srcLevel],
srcRect, SamplerMagFilter::LINEAR);
// Create a RenderTarget for each texture's miplevel.
// We purposely set the render target width and height to smaller than the texture, to check
// that this case is handled correctly.
Handle<HwRenderTarget> srcRenderTarget =
api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width,
srcRect.height, 1, {srcTexture, kSrcLevel, 0}, {}, {});
Handle<HwRenderTarget> dstRenderTarget =
api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel,
kDstTexHeight >> kDstLevel, 1, {dstTexture, kDstLevel, 0}, {}, {});
api.blit(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget, srcRect,
SamplerMagFilter::LINEAR);
// Push through an empty frame to allow the texture to upload and the blit to execute.
api.beginFrame(0, 0);
@@ -931,7 +931,7 @@ TEST_F(BackendTest, BlitRegion) {
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegion.png" };
api.beginFrame(0, 0);
dumpScreenshot(api, dstRenderTargets[0], &params);
dumpScreenshot(api, dstRenderTarget, &params);
api.commit(swapChain);
api.endFrame(0);
@@ -940,12 +940,23 @@ TEST_F(BackendTest, BlitRegion) {
executeCommands();
getDriver().purge();
// Check if the image matches perfectly to our golden run.
//
// TODO: for some reason, this test has very, very slight (as in one pixel) differences between
// OpenGL and Metal. So disable golden checking for now.
// Use the compare tool from ImageMagick to see visual differences:
// compare -verbose -metric mae BlitRegion_Metal.png BlitRegion_OpenGL.png difference.png
//
// const uint32_t expected = 0x74fa34ed;
// printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
// EXPECT_TRUE(params.pixelHashResult == expected);
// Cleanup.
api.destroyTexture(srcTexture);
api.destroyTexture(dstTexture);
api.destroySwapChain(swapChain);
for (auto rt : srcRenderTargets) api.destroyRenderTarget(rt);
for (auto rt : dstRenderTargets) api.destroyRenderTarget(rt);
api.destroyRenderTarget(srcRenderTarget);
api.destroyRenderTarget(dstRenderTarget);
executeCommands();
}