зеркало из https://github.com/AvaloniaUI/angle.git
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:
Родитель
9ec3f51d11
Коммит
7151fe54fe
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче