Use a wrapper around ANW to allow updating Surface
GpuDisplaySurface are roughly created once per "display in the
guest". Prior to this change, the Surface from the viewer app would
be saved in the AndroidSurface once at initial startup. However,
when the viewer app is moved to the background, the underlying
Surface is destroyed. After this point, the Surface saved in
AndroidSurface is no longer valid. This change updates
AndroidSurface to save a pointer to a ANativeWindowWrapper struct,
which contains the latest Surface, if any, and which can be updated
by DisplayService.
Note: this does not fix the app resuming issue.
Bug: b/334903567
Test: launch ferrochrome,
move to viewer to background,
move viewer back to foreground
Change-Id: I647de0a7735280a757e392e878fce86f559d5a74
diff --git a/libs/android_display_backend/crosvm_android_display_client.cpp b/libs/android_display_backend/crosvm_android_display_client.cpp
index 0557127..6e4a793 100644
--- a/libs/android_display_backend/crosvm_android_display_client.cpp
+++ b/libs/android_display_backend/crosvm_android_display_client.cpp
@@ -30,47 +30,179 @@
namespace {
+class SinkANativeWindow_Buffer {
+public:
+ SinkANativeWindow_Buffer() = default;
+ virtual ~SinkANativeWindow_Buffer() = default;
+
+ bool configure(uint32_t width, uint32_t height, int format) {
+ if (format != HAL_PIXEL_FORMAT_BGRA_8888) {
+ return false;
+ }
+
+ mBufferBits.resize(width * height * 4);
+ mBuffer = ANativeWindow_Buffer{
+ .width = static_cast<int32_t>(width),
+ .height = static_cast<int32_t>(height),
+ .stride = static_cast<int32_t>(width),
+ .format = format,
+ .bits = mBufferBits.data(),
+ };
+ return true;
+ }
+
+ operator ANativeWindow_Buffer&() { return mBuffer; }
+
+private:
+ ANativeWindow_Buffer mBuffer;
+ std::vector<uint8_t> mBufferBits;
+};
+
+// Wrapper which contains the latest available Surface/ANativeWindow
+// from the DisplayService, if available. A Surface/ANativeWindow may
+// not always be available if, for example, the VmLauncherApp on the
+// other end of the DisplayService is not in the foreground / is paused.
+class AndroidDisplaySurface {
+public:
+ AndroidDisplaySurface() = default;
+ virtual ~AndroidDisplaySurface() = default;
+
+ void setSurface(Surface* surface) {
+ {
+ std::lock_guard lk(mSurfaceMutex);
+ mNativeSurface = std::make_unique<Surface>(surface->release());
+ mNativeSurfaceNeedsConfiguring = true;
+ }
+
+ mNativeSurfaceReady.notify_one();
+ }
+
+ void removeSurface() {
+ {
+ std::lock_guard lk(mSurfaceMutex);
+ mNativeSurface = nullptr;
+ }
+ mNativeSurfaceReady.notify_one();
+ }
+
+ Surface* getSurface() {
+ std::unique_lock lk(mSurfaceMutex);
+ return mNativeSurface.get();
+ }
+
+ void configure(uint32_t width, uint32_t height) {
+ std::unique_lock lk(mSurfaceMutex);
+
+ mRequestedSurfaceDimensions = Rect{
+ .width = width,
+ .height = height,
+ };
+
+ mSinkBuffer.configure(width, height, kFormat);
+ }
+
+ void waitForNativeSurface() {
+ std::unique_lock lk(mSurfaceMutex);
+ mNativeSurfaceReady.wait(lk, [this] { return mNativeSurface != nullptr; });
+ }
+
+ int lock(ANativeWindow_Buffer* out_buffer) {
+ std::unique_lock lk(mSurfaceMutex);
+
+ Surface* surface = mNativeSurface.get();
+ if (surface == nullptr) {
+ // Surface not currently available but not necessarily an error
+ // if, for example, the VmLauncherApp is not in the foreground.
+ *out_buffer = mSinkBuffer;
+ return 0;
+ }
+
+ ANativeWindow* anw = surface->get();
+ if (anw == nullptr) {
+ return -1;
+ }
+
+ if (mNativeSurfaceNeedsConfiguring) {
+ if (!mRequestedSurfaceDimensions) {
+ return -1;
+ }
+ const auto& dims = *mRequestedSurfaceDimensions;
+
+ // Ensure locked buffers have our desired format.
+ if (ANativeWindow_setBuffersGeometry(anw, dims.width, dims.height, kFormat) != 0) {
+ return -1;
+ }
+
+ mNativeSurfaceNeedsConfiguring = false;
+ }
+
+ return ANativeWindow_lock(anw, out_buffer, nullptr);
+ }
+
+ int unlockAndPost() {
+ std::unique_lock lk(mSurfaceMutex);
+
+ Surface* surface = mNativeSurface.get();
+ if (surface == nullptr) {
+ // Surface not currently available but not necessarily an error
+ // if, for example, the VmLauncherApp is not in the foreground.
+ return 0;
+ }
+
+ ANativeWindow* anw = surface->get();
+ if (anw == nullptr) {
+ return -1;
+ }
+
+ return ANativeWindow_unlockAndPost(anw);
+ }
+
+private:
+ // Note: crosvm always uses BGRA8888 or BGRX8888. See devices/src/virtio/gpu/mod.rs in
+ // crosvm where the SetScanoutBlob command is handled. Let's use BGRA not BGRX with a hope
+ // that we will need alpha blending for the cursor surface.
+ static constexpr const int kFormat = HAL_PIXEL_FORMAT_BGRA_8888;
+
+ std::mutex mSurfaceMutex;
+ std::unique_ptr<Surface> mNativeSurface;
+ std::condition_variable mNativeSurfaceReady;
+ bool mNativeSurfaceNeedsConfiguring = true;
+
+ SinkANativeWindow_Buffer mSinkBuffer;
+
+ struct Rect {
+ uint32_t width = 0;
+ uint32_t height = 0;
+ };
+ std::optional<Rect> mRequestedSurfaceDimensions;
+};
+
class DisplayService : public BnCrosvmAndroidDisplayService {
public:
DisplayService() = default;
virtual ~DisplayService() = default;
ndk::ScopedAStatus setSurface(Surface* surface, bool forCursor) override {
- {
- std::lock_guard lk(mSurfaceReadyMutex);
- if (forCursor) {
- mCursorSurface = std::make_unique<Surface>(surface->release());
- } else {
- mSurface = std::make_unique<Surface>(surface->release());
- }
+ if (forCursor) {
+ mCursor.setSurface(surface);
+ } else {
+ mScanout.setSurface(surface);
}
- mSurfaceReady.notify_all();
return ::ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus removeSurface(bool forCursor) override {
- {
- std::lock_guard lk(mSurfaceReadyMutex);
- if (forCursor) {
- mCursorSurface = nullptr;
- } else {
- mSurface = nullptr;
- }
+ if (forCursor) {
+ mCursor.removeSurface();
+ } else {
+ mScanout.removeSurface();
}
- mSurfaceReady.notify_all();
return ::ndk::ScopedAStatus::ok();
}
- Surface* getSurface(bool forCursor) {
- std::unique_lock lk(mSurfaceReadyMutex);
- if (forCursor) {
- mSurfaceReady.wait(lk, [this] { return mCursorSurface != nullptr; });
- return mCursorSurface.get();
- } else {
- mSurfaceReady.wait(lk, [this] { return mSurface != nullptr; });
- return mSurface.get();
- }
- }
+ AndroidDisplaySurface* getCursorSurface() { return &mCursor; }
+ AndroidDisplaySurface* getScanoutSurface() { return &mScanout; }
+
ndk::ScopedFileDescriptor& getCursorStream() { return mCursorStream; }
ndk::ScopedAStatus setCursorStream(const ndk::ScopedFileDescriptor& in_stream) {
mCursorStream = ndk::ScopedFileDescriptor(dup(in_stream.get()));
@@ -78,10 +210,8 @@
}
private:
- std::condition_variable mSurfaceReady;
- std::mutex mSurfaceReadyMutex;
- std::unique_ptr<Surface> mSurface;
- std::unique_ptr<Surface> mCursorSurface;
+ AndroidDisplaySurface mScanout;
+ AndroidDisplaySurface mCursor;
ndk::ScopedFileDescriptor mCursorStream;
};
@@ -149,25 +279,29 @@
delete ctx;
}
-extern "C" ANativeWindow* create_android_surface(struct AndroidDisplayContext* ctx, uint32_t width,
- uint32_t height, bool for_cursor) {
+extern "C" AndroidDisplaySurface* create_android_surface(struct AndroidDisplayContext* ctx,
+ uint32_t width, uint32_t height,
+ bool forCursor) {
if (ctx->disp_service == nullptr) {
ctx->errorf("Display service was not created");
return nullptr;
}
- // Note: crosvm always uses BGRA8888 or BGRX8888. See devices/src/virtio/gpu/mod.rs in crosvm
- // where the SetScanoutBlob command is handled. Let's use BGRA not BGRX with a hope that we will
- // need alpha blending for the cursor surface.
- int format = HAL_PIXEL_FORMAT_BGRA_8888;
- ANativeWindow* surface = ctx->disp_service->getSurface(for_cursor)->get(); // this can block
- if (ANativeWindow_setBuffersGeometry(surface, width, height, format) != 0) {
- ctx->errorf("Failed to set buffer gemoetry");
+
+ AndroidDisplaySurface* displaySurface = forCursor ? ctx->disp_service->getCursorSurface()
+ : ctx->disp_service->getScanoutSurface();
+ if (displaySurface == nullptr) {
+ ctx->errorf("AndroidDisplaySurface was not created");
return nullptr;
}
+
+ displaySurface->configure(width, height);
+
+ displaySurface->waitForNativeSurface(); // this can block
+
// TODO(b/332785161): if we know that surface can get destroyed dynamically while VM is running,
// consider calling ANativeWindow_acquire here and _release in destroy_android_surface, so that
// crosvm doesn't hold a dangling pointer.
- return surface;
+ return displaySurface;
}
extern "C" void destroy_android_surface(struct AndroidDisplayContext*, ANativeWindow*) {
@@ -175,16 +309,23 @@
}
extern "C" bool get_android_surface_buffer(struct AndroidDisplayContext* ctx,
- ANativeWindow* surface,
+ AndroidDisplaySurface* surface,
ANativeWindow_Buffer* out_buffer) {
if (out_buffer == nullptr) {
ctx->errorf("out_buffer is null");
return false;
}
- if (ANativeWindow_lock(surface, out_buffer, nullptr) != 0) {
+
+ if (surface == nullptr) {
+ ctx->errorf("Invalid AndroidDisplaySurface provided");
+ return false;
+ }
+
+ if (surface->lock(out_buffer) != 0) {
ctx->errorf("Failed to lock buffer");
return false;
}
+
return true;
}
@@ -204,9 +345,14 @@
}
extern "C" void post_android_surface_buffer(struct AndroidDisplayContext* ctx,
- ANativeWindow* surface) {
- if (ANativeWindow_unlockAndPost(surface) != 0) {
- ctx->errorf("Failed to unlock and post surface.");
+ AndroidDisplaySurface* surface) {
+ if (surface == nullptr) {
+ ctx->errorf("Invalid AndroidDisplaySurface provided");
+ return;
+ }
+
+ if (surface->unlockAndPost() != 0) {
+ ctx->errorf("Failed to unlock and post AndroidDisplaySurface.");
return;
}
}