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>
This commit is contained in:
Jonah Ryan-Davis 2019-07-17 15:15:27 -04:00 коммит произвёл Commit Bot
Родитель 9ec3f51d11
Коммит 7151fe54fe
7 изменённых файлов: 359 добавлений и 5 удалений

Просмотреть файл

@ -323,6 +323,14 @@ struct FeaturesGL : FeatureSetBase
"Limit max 3d texture size and max array texture layers to 1024 to avoid system hang on "
"older Intel Linux",
&members, "http://crbug.com/927470"};
// BlitFramebuffer has issues on some platforms with large source/dest texture sizes. This
// workaround adjusts the destination rectangle source and dest rectangle to fit within maximum
// twice the size of the framebuffer.
Feature adjustSrcDstRegionBlitFramebuffer = {
"adjust_src_dst_region_for_blitframebuffer", FeatureCategory::OpenGLWorkarounds,
"Many platforms have issues with blitFramebuffer when the parameters are large.", &members,
"http://crbug.com/830046"};
};
inline FeaturesGL::FeaturesGL() = default;

Просмотреть файл

@ -89,6 +89,14 @@ class CheckedNumeric
// IsValid() is the public API to test if a CheckedNumeric is currently valid.
bool IsValid() const { return validity() == RANGE_VALID; }
// AssignIfValid(Dst) - Assigns the underlying value if it is currently valid and is within the
// range supported by the destination type. Returns true if successful and false otherwise.
template <typename Dst>
constexpr bool AssignIfValid(Dst *result) const
{
return IsValid() ? ((*result = static_cast<Dst>(state_.value())), true) : false;
}
// ValueOrDie() The primary accessor for the underlying value. If the current
// state is not valid it will CHECK and crash.
T ValueOrDie() const

Просмотреть файл

@ -601,11 +601,16 @@ int FramebufferState::getBaseViewIndex() const
}
Box FramebufferState::getDimensions() const
{
Extents extents = getExtents();
return Box(0, 0, 0, extents.width, extents.height, extents.depth);
}
Extents FramebufferState::getExtents() const
{
ASSERT(attachmentsHaveSameDimensions());
ASSERT(getFirstNonNullAttachment() != nullptr);
Extents extents = getFirstNonNullAttachment()->getSize();
return Box(0, 0, 0, extents.width, extents.height, extents.depth);
return getFirstNonNullAttachment()->getSize();
}
Framebuffer::Framebuffer(const Caps &caps, rx::GLImplFactory *factory, GLuint id)
@ -2285,6 +2290,11 @@ Box Framebuffer::getDimensions() const
return mState.getDimensions();
}
Extents Framebuffer::getExtents() const
{
return mState.getExtents();
}
angle::Result Framebuffer::ensureBufferInitialized(const Context *context,
GLenum bufferType,
GLint bufferIndex)

Просмотреть файл

@ -82,6 +82,7 @@ class FramebufferState final : angle::NonCopyable
bool hasSeparateDepthAndStencilAttachments() const;
bool colorAttachmentsAreUniqueImages() const;
Box getDimensions() const;
Extents getExtents() const;
const FramebufferAttachment *getDrawBuffer(size_t drawBufferIdx) const;
size_t getDrawBufferCount() const;
@ -202,6 +203,7 @@ class Framebuffer final : public angle::ObserverInterface,
bool readDisallowedByMultiview() const;
GLsizei getNumViews() const;
GLint getBaseViewIndex() const;
Extents getExtents() const;
size_t getDrawbufferStateCount() const;
GLenum getDrawBufferState(size_t drawBuffer) const;

Просмотреть файл

@ -506,6 +506,7 @@ angle::Result FramebufferGL::blit(const gl::Context *context,
{
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 @@ angle::Result FramebufferGL::blit(const gl::Context *context,
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;
}

Просмотреть файл

@ -115,6 +115,12 @@ class FramebufferGL : public FramebufferImpl
void maskOutInactiveOutputDrawBuffersImpl(const gl::Context *context,
gl::DrawBufferMask targetAppliedDrawBuffers);
angle::Result adjustSrcDstRegion(const gl::Context *context,
const gl::Rectangle &sourceArea,
const gl::Rectangle &destArea,
gl::Rectangle *newSourceArea,
gl::Rectangle *newDestArea);
GLuint mFramebufferID;
bool mIsDefault;

Просмотреть файл

@ -1531,6 +1531,9 @@ void InitializeFeatures(const FunctionsGL *functions, angle::FeaturesGL *feature
features->clearToZeroOrOneBroken.enabled =
IsApple() && IsIntel(vendor) && GetMacOSVersion() < OSVersion(10, 12, 6);
features->adjustSrcDstRegionBlitFramebuffer.enabled =
IsApple() || IsLinux() || (IsAndroid() && IsNvidia(vendor));
}
void InitializeFrontendFeatures(const FunctionsGL *functions, angle::FrontendFeatures *features)