Port adjust_src_dst_region_for_blitframebuffer workaround to ANGLE.
BlitFramebuffer has issues on some platforms with large source/dest
textures. As per the WebGL2 spec, this was caught with validation for
sizes over 2^32, but there is a specific issue on Linux NVIDIA where it
fails on sizes over 2^16. A better workaround (from chromium), resizes
the blitframebuffer call based on the framebuffer size.
Bug: chromium:830046
Change-Id: Ic6196db6228d0d0ac92b12a68bbced76dcbcdf8c
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1707115
Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index ae5db21..e19bffa 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -506,6 +506,7 @@
{
const FunctionsGL *functions = GetFunctionsGL(context);
StateManagerGL *stateManager = GetStateManagerGL(context);
+ const angle::FeaturesGL &features = GetFeaturesGL(context);
const Framebuffer *sourceFramebuffer = context->getState().getReadFramebuffer();
const Framebuffer *destFramebuffer = context->getState().getDrawFramebuffer();
@@ -583,9 +584,325 @@
stateManager->bindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebufferGL->getFramebufferID());
stateManager->bindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebufferID);
- functions->blitFramebuffer(sourceArea.x, sourceArea.y, sourceArea.x1(), sourceArea.y1(),
- destArea.x, destArea.y, destArea.x1(), destArea.y1(), blitMask,
- filter);
+ if (features.adjustSrcDstRegionBlitFramebuffer.enabled)
+ {
+ gl::Rectangle newSourceArea;
+ gl::Rectangle newDestArea;
+ // This workaround is taken from chromium: http://crbug.com/830046
+ if (adjustSrcDstRegion(context, sourceArea, destArea, &newSourceArea, &newDestArea) ==
+ angle::Result::Continue)
+ {
+ functions->blitFramebuffer(newSourceArea.x, newSourceArea.y, newSourceArea.x1(),
+ newSourceArea.y1(), newDestArea.x, newDestArea.y,
+ newDestArea.x1(), newDestArea.y1(), blitMask, filter);
+ }
+ }
+ else
+ {
+ functions->blitFramebuffer(sourceArea.x, sourceArea.y, sourceArea.x1(), sourceArea.y1(),
+ destArea.x, destArea.y, destArea.x1(), destArea.y1(), blitMask,
+ filter);
+ }
+
+ return angle::Result::Continue;
+}
+
+angle::Result FramebufferGL::adjustSrcDstRegion(const gl::Context *context,
+ const gl::Rectangle &sourceArea,
+ const gl::Rectangle &destArea,
+ gl::Rectangle *newSourceArea,
+ gl::Rectangle *newDestArea)
+{
+ const Framebuffer *sourceFramebuffer = context->getState().getReadFramebuffer();
+ const Framebuffer *destFramebuffer = context->getState().getDrawFramebuffer();
+
+ gl::Extents readSize = sourceFramebuffer->getExtents();
+ gl::Extents drawSize = destFramebuffer->getExtents();
+
+ CheckedNumeric<GLint> sourceWidthTemp = sourceArea.x1();
+ sourceWidthTemp -= sourceArea.x;
+ CheckedNumeric<GLint> sourceHeightTemp = sourceArea.y1();
+ sourceHeightTemp -= sourceArea.y;
+ CheckedNumeric<GLint> destWidthTemp = destArea.x1();
+ destWidthTemp -= destArea.x;
+ CheckedNumeric<GLint> destHeightTemp = destArea.y1();
+ destHeightTemp -= destArea.y;
+
+ GLint sourceX = sourceArea.x1() > sourceArea.x ? sourceArea.x : sourceArea.x1();
+ GLint sourceY = sourceArea.y1() > sourceArea.y ? sourceArea.y : sourceArea.y1();
+ GLuint sourceWidth = angle::base::checked_cast<GLuint>(sourceWidthTemp.Abs().ValueOrDefault(0));
+ GLuint sourceHeight =
+ angle::base::checked_cast<GLuint>(sourceHeightTemp.Abs().ValueOrDefault(0));
+
+ GLint destX = destArea.x1() > destArea.x ? destArea.x : destArea.x1();
+ GLint destY = destArea.y1() > destArea.y ? destArea.y : destArea.y1();
+ GLuint destWidth = angle::base::checked_cast<GLuint>(destWidthTemp.Abs().ValueOrDefault(0));
+ GLuint destHeight = angle::base::checked_cast<GLuint>(destHeightTemp.Abs().ValueOrDefault(0));
+
+ if (destWidth == 0 || sourceWidth == 0 || destHeight == 0 || sourceHeight == 0)
+ {
+ return angle::Result::Stop;
+ }
+
+ gl::Rectangle sourceBounds(0, 0, readSize.width, readSize.height);
+ gl::Rectangle sourceRegion(sourceX, sourceY, sourceWidth, sourceHeight);
+
+ gl::Rectangle destBounds(0, 0, drawSize.width, drawSize.height);
+ gl::Rectangle destRegion(destX, destY, destWidth, destHeight);
+
+ if (!ClipRectangle(destBounds, destRegion, nullptr))
+ {
+ return angle::Result::Stop;
+ }
+
+ bool xFlipped = ((sourceArea.x1() > sourceArea.x) && (destArea.x1() < destArea.x)) ||
+ ((sourceArea.x1() < sourceArea.x) && (destArea.x1() > destArea.x));
+ bool yFlipped = ((sourceArea.y1() > sourceArea.y) && (destArea.y1() < destArea.y)) ||
+ ((sourceArea.y1() < sourceArea.y) && (destArea.y1() > destArea.y));
+
+ if (!destBounds.encloses(destRegion))
+ {
+ // destRegion is not within destBounds. We want to adjust it to a
+ // reasonable size. This is done by halving the destRegion until it is at
+ // most twice the size of the framebuffer. We cut it in half instead
+ // of arbitrarily shrinking it to fit so that we don't end up with
+ // non-power-of-two scale factors which could mess up pixel interpolation.
+ // Naively clipping the dst rect and then proportionally sizing the
+ // src rect yields incorrect results.
+
+ GLuint destXHalvings = 0;
+ GLuint destYHalvings = 0;
+ GLint destOriginX = destX;
+ GLint destOriginY = destY;
+
+ GLint destClippedWidth = destRegion.width;
+ while (destClippedWidth > 2 * destBounds.width)
+ {
+ destClippedWidth = destClippedWidth / 2;
+ destXHalvings++;
+ }
+
+ GLint destClippedHeight = destRegion.height;
+ while (destClippedHeight > 2 * destBounds.height)
+ {
+ destClippedHeight = destClippedHeight / 2;
+ destYHalvings++;
+ }
+
+ // Before this block, we check that the two rectangles intersect.
+ // Now, compute the location of a new region origin such that we use the
+ // scaled dimensions but the new region has the same intersection as the
+ // original region.
+
+ GLint left = destRegion.x0();
+ GLint right = destRegion.x1();
+ GLint top = destRegion.y0();
+ GLint bottom = destRegion.y1();
+
+ GLint extraXOffset = 0;
+ if (left >= 0 && left < destBounds.width)
+ {
+ // Left edge is in-bounds
+ destOriginX = destX;
+ }
+ else if (right > 0 && right <= destBounds.width)
+ {
+ // Right edge is in-bounds
+ destOriginX = right - destClippedWidth;
+ }
+ else
+ {
+ // Region completely spans bounds
+ extraXOffset = (destRegion.width - destClippedWidth) / 2;
+ destOriginX = destX + extraXOffset;
+ }
+
+ GLint extraYOffset = 0;
+ if (top >= 0 && top < destBounds.height)
+ {
+ // Top edge is in-bounds
+ destOriginY = destY;
+ }
+ else if (bottom > 0 && bottom <= destBounds.height)
+ {
+ // Bottom edge is in-bounds
+ destOriginY = bottom - destClippedHeight;
+ }
+ else
+ {
+ // Region completely spans bounds
+ extraYOffset = (destRegion.height - destClippedHeight) / 2;
+ destOriginY = destY + extraYOffset;
+ }
+
+ destRegion = gl::Rectangle(destOriginX, destOriginY, destClippedWidth, destClippedHeight);
+
+ // Offsets from the bottom left corner of the original region to
+ // the bottom left corner of the clipped region.
+ // This value (after it is scaled) is the respective offset we will apply
+ // to the src origin.
+
+ CheckedNumeric<GLuint> checkedXOffset(destRegion.x - destX - extraXOffset / 2);
+ CheckedNumeric<GLuint> checkedYOffset(destRegion.y - destY - extraYOffset / 2);
+
+ // if X/Y is reversed, use the top/right out-of-bounds region to compute
+ // the origin offset instead of the left/bottom out-of-bounds region
+ if (xFlipped)
+ {
+ checkedXOffset = (destX + destWidth - destRegion.x1() + extraXOffset / 2);
+ }
+ if (yFlipped)
+ {
+ checkedYOffset = (destY + destHeight - destRegion.y1() + extraYOffset / 2);
+ }
+
+ // These offsets should never overflow
+ GLuint xOffset, yOffset;
+ if (!checkedXOffset.AssignIfValid(&xOffset) || !checkedYOffset.AssignIfValid(&yOffset))
+ {
+ UNREACHABLE();
+ return angle::Result::Stop;
+ }
+
+ // Adjust the src region by the same factor
+ sourceRegion = gl::Rectangle(
+ sourceX + (xOffset >> destXHalvings), sourceY + (yOffset >> destYHalvings),
+ sourceRegion.width >> destXHalvings, sourceRegion.height >> destYHalvings);
+
+ // if the src was scaled to 0, set it to 1 so the src is non-empty
+ if (sourceRegion.width == 0)
+ {
+ sourceRegion.width = 1;
+ }
+ if (sourceRegion.height == 0)
+ {
+ sourceRegion.height = 1;
+ }
+ }
+
+ if (!sourceBounds.encloses(sourceRegion))
+ {
+ // sourceRegion is not within sourceBounds. We want to adjust it to a
+ // reasonable size. This is done by halving the sourceRegion until it is at
+ // most twice the size of the framebuffer. We cut it in half instead
+ // of arbitrarily shrinking it to fit so that we don't end up with
+ // non-power-of-two scale factors which could mess up pixel interpolation.
+ // Naively clipping the source rect and then proportionally sizing the
+ // dest rect yields incorrect results.
+
+ GLuint sourceXHalvings = 0;
+ GLuint sourceYHalvings = 0;
+ GLint sourceOriginX = sourceX;
+ GLint sourceOriginY = sourceY;
+
+ GLint sourceClippedWidth = sourceRegion.width;
+ while (sourceClippedWidth > 2 * sourceBounds.width)
+ {
+ sourceClippedWidth = sourceClippedWidth / 2;
+ sourceXHalvings++;
+ }
+
+ GLint sourceClippedHeight = sourceRegion.height;
+ while (sourceClippedHeight > 2 * sourceBounds.height)
+ {
+ sourceClippedHeight = sourceClippedHeight / 2;
+ sourceYHalvings++;
+ }
+
+ // Before this block, we check that the two rectangles intersect.
+ // Now, compute the location of a new region origin such that we use the
+ // scaled dimensions but the new region has the same intersection as the
+ // original region.
+
+ GLint left = sourceRegion.x0();
+ GLint right = sourceRegion.x1();
+ GLint top = sourceRegion.y0();
+ GLint bottom = sourceRegion.y1();
+
+ GLint extraXOffset = 0;
+ if (left >= 0 && left < sourceBounds.width)
+ {
+ // Left edge is in-bounds
+ sourceOriginX = sourceX;
+ }
+ else if (right > 0 && right <= sourceBounds.width)
+ {
+ // Right edge is in-bounds
+ sourceOriginX = right - sourceClippedWidth;
+ }
+ else
+ {
+ // Region completely spans bounds
+ extraXOffset = (sourceRegion.width - sourceClippedWidth) / 2;
+ sourceOriginX = sourceX + extraXOffset;
+ }
+
+ GLint extraYOffset = 0;
+ if (top >= 0 && top < sourceBounds.height)
+ {
+ // Top edge is in-bounds
+ sourceOriginY = sourceY;
+ }
+ else if (bottom > 0 && bottom <= sourceBounds.height)
+ {
+ // Bottom edge is in-bounds
+ sourceOriginY = bottom - sourceClippedHeight;
+ }
+ else
+ {
+ // Region completely spans bounds
+ extraYOffset = (sourceRegion.height - sourceClippedHeight) / 2;
+ sourceOriginY = sourceY + extraYOffset;
+ }
+
+ sourceRegion =
+ gl::Rectangle(sourceOriginX, sourceOriginY, sourceClippedWidth, sourceClippedHeight);
+
+ // Offsets from the bottom left corner of the original region to
+ // the bottom left corner of the clipped region.
+ // This value (after it is scaled) is the respective offset we will apply
+ // to the dest origin.
+
+ CheckedNumeric<GLuint> checkedXOffset(sourceRegion.x - sourceX - extraXOffset / 2);
+ CheckedNumeric<GLuint> checkedYOffset(sourceRegion.y - sourceY - extraYOffset / 2);
+
+ // if X/Y is reversed, use the top/right out-of-bounds region to compute
+ // the origin offset instead of the left/bottom out-of-bounds region
+ if (xFlipped)
+ {
+ checkedXOffset = (sourceX + sourceWidth - sourceRegion.x1() + extraXOffset / 2);
+ }
+ if (yFlipped)
+ {
+ checkedYOffset = (sourceY + sourceHeight - sourceRegion.y1() + extraYOffset / 2);
+ }
+
+ // These offsets should never overflow
+ GLuint xOffset, yOffset;
+ if (!checkedXOffset.AssignIfValid(&xOffset) || !checkedYOffset.AssignIfValid(&yOffset))
+ {
+ UNREACHABLE();
+ return angle::Result::Stop;
+ }
+
+ // Adjust the dest region by the same factor
+ destRegion = gl::Rectangle(
+ destX + (xOffset >> sourceXHalvings), destY + (yOffset >> sourceYHalvings),
+ destRegion.width >> sourceXHalvings, destRegion.height >> sourceYHalvings);
+ }
+ // Set the src and dst endpoints. If they were previously flipped,
+ // set them as flipped.
+ *newSourceArea = gl::Rectangle(
+ sourceArea.x0() < sourceArea.x1() ? sourceRegion.x0() : sourceRegion.x1(),
+ sourceArea.y0() < sourceArea.y1() ? sourceRegion.y0() : sourceRegion.y1(),
+ sourceArea.x0() < sourceArea.x1() ? sourceRegion.width : -sourceRegion.width,
+ sourceArea.y0() < sourceArea.y1() ? sourceRegion.height : -sourceRegion.height);
+
+ *newDestArea =
+ gl::Rectangle(destArea.x0() < destArea.x1() ? destRegion.x0() : destRegion.x1(),
+ destArea.y0() < destArea.y1() ? destRegion.y0() : destRegion.y1(),
+ destArea.x0() < destArea.x1() ? destRegion.width : -destRegion.width,
+ destArea.y0() < destArea.y1() ? destRegion.height : -destRegion.height);
return angle::Result::Continue;
}