mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (Espryt): copy only the damaged rectangles into shared images again
This commit is contained in:
@@ -18655,6 +18655,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
EglImageRef Image;
|
||||
GLuint Renderbuffer = 0;
|
||||
Uint64 LastUse = 0;
|
||||
// A present has written the image through this target (its content is a frame).
|
||||
Bool Written = false;
|
||||
};
|
||||
struct PresentTargets {
|
||||
std::mutex Mutex;
|
||||
@@ -18926,6 +18928,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
it = targets.ById.emplace(view.Id, std::move(target)).first;
|
||||
}
|
||||
it->second.LastUse = ++targets.UseClock;
|
||||
// A target written before keeps what it was given; one new to this process is
|
||||
// copied whole whatever the client asked (it cannot know the image's history here).
|
||||
const Bool wholeTarget = !it->second.Written;
|
||||
it->second.Written = true;
|
||||
|
||||
GLuint& framebuffer = g_presentFramebuffers.Current();
|
||||
if (framebuffer == 0) g_GLESFuncs.glGenFramebuffers(1, &framebuffer);
|
||||
@@ -18956,17 +18962,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// TOP ROW FIRST: GL's top row (y = height - 1) lands in the image's first memory
|
||||
// row, which an AHardwareBuffer-backed renderbuffer addresses as y = 0.
|
||||
//
|
||||
// ALWAYS THE WHOLE FRAME, whatever `region` says. A blit of only the damaged
|
||||
// rectangles into the image's renderbuffer was measured to lose texels around
|
||||
// them on a tiler: the driver does not load the image's other content into the
|
||||
// pass that blits a part of it, and what it stores back there is stale tile memory
|
||||
// (old frames' pixels around the copied rectangles). GL has no way to ask for the
|
||||
// load, so the copy stays whole here; the region still narrows what the client
|
||||
// damages on its surface, which is what the compositor repaints.
|
||||
(void)region;
|
||||
g_GLESFuncs.glBlitFramebuffer(0, 0, sourceWidth, sourceHeight, 0, static_cast<GLint>(view.Height),
|
||||
static_cast<GLint>(view.Width), 0, GL_COLOR_BUFFER_BIT,
|
||||
sameExtent ? GL_NEAREST : GL_LINEAR);
|
||||
// Only where the image differs from the frame: the client keeps, per image, the
|
||||
// damage it missed (BufferDamageTracker), and a partial blit keeps the rest of
|
||||
// the renderbuffer as GL promises (checked texel for texel against the frame on
|
||||
// Adreno). What once left old texels around the rectangles was the scissor
|
||||
// guard above trusting another session's shadow: the copy was clipped, and
|
||||
// the missed-damage bookkeeping then never brought those texels over again.
|
||||
if (sameExtent && !wholeTarget && !region.IsFull()) {
|
||||
// Each rectangle flipped the same way (an empty region copies nothing: the
|
||||
// image already is the frame).
|
||||
for (const MG_Util::Damage::FlippedCopy& copy :
|
||||
MG_Util::Damage::FlippedCopies(region, sourceWidth, sourceHeight)) {
|
||||
const MG_Util::Damage::Rect& from = copy.Source;
|
||||
const MG_Util::Damage::Rect& to = copy.Destination;
|
||||
g_GLESFuncs.glBlitFramebuffer(from.X, from.Y, from.X + from.Width, from.Y + from.Height,
|
||||
to.X, to.Y + to.Height, to.X + to.Width, to.Y,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
} else {
|
||||
g_GLESFuncs.glBlitFramebuffer(0, 0, sourceWidth, sourceHeight, 0,
|
||||
static_cast<GLint>(view.Height), static_cast<GLint>(view.Width),
|
||||
0, GL_COLOR_BUFFER_BIT, sameExtent ? GL_NEAREST : GL_LINEAR);
|
||||
}
|
||||
const GLenum error = g_GLESFuncs.glGetError();
|
||||
copied = error == GL_NO_ERROR;
|
||||
if (readBuffer != GL_BACK) g_GLESFuncs.glReadBuffer(static_cast<GLenum>(readBuffer));
|
||||
|
||||
@@ -269,3 +269,61 @@ TEST(Damage, SwapchainAgeIsUnknownWhenContentWasNotKeptOrTheChainChanged) {
|
||||
EXPECT_EQ(ages.AgeOf(1), 0);
|
||||
EXPECT_EQ(ages.AgeOf(7), 0) << "an index the chain does not have";
|
||||
}
|
||||
|
||||
// A partial copy into a shared image: the frame's rows run bottom-up, the image's top-down. Run
|
||||
// the blits the way glBlitFramebuffer does (a destination named with its Y bounds swapped takes
|
||||
// the source rows in reverse) over a frame of known texels: every pixel of the region, and only
|
||||
// those, must land where a whole flipped copy would have put it - edges included.
|
||||
TEST(Damage, FlippedCopiesLandEachDamagedTexelWhereAWholeFlipWouldAndNothingElse) {
|
||||
constexpr Int32 W = 37, H = 23;
|
||||
const auto texel = [](Int32 x, Int32 y) { return y * 1000 + x; };
|
||||
const auto run = [&](const Region& region) {
|
||||
Vector<Int32> image(static_cast<SizeT>(W) * H, -1);
|
||||
for (const FlippedCopy& copy : FlippedCopies(region, W, H)) {
|
||||
const Rect& s = copy.Source;
|
||||
const Rect& d = copy.Destination;
|
||||
EXPECT_EQ(s.Width, d.Width);
|
||||
EXPECT_EQ(s.Height, d.Height);
|
||||
EXPECT_TRUE(Contains({0, 0, W, H}, s));
|
||||
EXPECT_TRUE(Contains({0, 0, W, H}, d));
|
||||
for (Int32 i = 0; i < d.Height; ++i) {
|
||||
for (Int32 x = 0; x < d.Width; ++x) {
|
||||
image[static_cast<SizeT>(d.Y + i) * W + d.X + x] = texel(s.X + x, s.Y + s.Height - 1 - i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
};
|
||||
const auto inRegion = [](const Region& region, Int32 x, Int32 y) {
|
||||
if (region.IsFull()) return true;
|
||||
for (const Rect& r : region.Rects()) {
|
||||
if (x >= r.X && x < r.X + r.Width && y >= r.Y && y < r.Y + r.Height) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const Region cases[] = {
|
||||
Of({{0, 0, 5, 1}}), // the bottom row
|
||||
Of({{30, H - 1, 7, 1}}), // the top row, the right edge
|
||||
Of({{3, 4, 6, 5}, {5, 6, 10, 10}}), // overlapping
|
||||
Of({{-4, -4, 9, 9}, {W - 2, H - 3, 9, 9}}), // past the edges
|
||||
Of({{0, 0, W, H}}), // all of it
|
||||
Region::Full(),
|
||||
};
|
||||
for (const Region& region : cases) {
|
||||
Region clipped = region;
|
||||
clipped.Normalize(W, H);
|
||||
const Vector<Int32> image = run(region);
|
||||
for (Int32 row = 0; row < H; ++row) {
|
||||
for (Int32 x = 0; x < W; ++x) {
|
||||
const Int32 y = H - 1 - row; // the frame row that belongs in image row `row`
|
||||
const Int32 got = image[static_cast<SizeT>(row) * W + x];
|
||||
if (inRegion(clipped, x, y)) {
|
||||
EXPECT_EQ(got, texel(x, y)) << "frame texel " << x << "," << y << " misplaced";
|
||||
} else {
|
||||
EXPECT_EQ(got, -1) << "image texel " << x << "," << row << " written outside the region";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(FlippedCopies(Region{}, W, H).empty()) << "an empty region copies nothing";
|
||||
}
|
||||
|
||||
@@ -283,6 +283,30 @@ namespace MobileGL::MG_Util::Damage {
|
||||
return extBufferAge || (partialUpdate && damageRegionFollows);
|
||||
}
|
||||
|
||||
// One blit of a copy from a frame whose rows run bottom-up (a GL default framebuffer) into an
|
||||
// image of the same size addressed top row first (a shared image's renderbuffer): `Source` in
|
||||
// the frame's coordinates, `Destination` the same pixels in the image's. The blit names the
|
||||
// destination's Y bounds swapped (Y + Height, then Y), which is what turns the rows over.
|
||||
struct FlippedCopy {
|
||||
Rect Source;
|
||||
Rect Destination;
|
||||
};
|
||||
|
||||
// The blits that bring `region` (the frame's coordinates) of a `width` x `height` frame into
|
||||
// the image: clipped to the frame, none for an empty region (the image already is the frame),
|
||||
// and one whole-frame blit for a region that covers it.
|
||||
inline Vector<FlippedCopy> FlippedCopies(const Region& region, Int32 width, Int32 height) {
|
||||
if (width <= 0 || height <= 0) return {};
|
||||
Region clipped = region;
|
||||
clipped.Normalize(width, height);
|
||||
const Rect whole{0, 0, width, height};
|
||||
if (clipped.IsFull()) return {FlippedCopy{whole, whole}};
|
||||
Vector<FlippedCopy> copies;
|
||||
copies.reserve(clipped.Rects().size());
|
||||
for (const Rect& rect : clipped.Rects()) copies.push_back({rect, FlipY(rect, height)});
|
||||
return copies;
|
||||
}
|
||||
|
||||
// Per buffer of a rotating set: the damage each has missed since it was last written.
|
||||
class BufferDamageTracker {
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user