Refactor android display backend
Major changes included in this change:
* Eliminate the unnecessary copy of each frame. Now crosvm draws
directly on the buffer returned by surfaceflinger.
* In doing so, crosvm respects stride set by Android.
* The scaling routine is removed as surfaceflinger scales the buffer
when it doesn't match with the surface size. This allows us to drop
the dependency to libyuv.
* AndroidDisplayContext is created in its constructor so that any
failure during the construction doesn't leak memory.
Bug: 331900473
Test: N/A
Change-Id: Icc47f50a62696d3c959e8cb16dc531fe28d25ab5
diff --git a/libs/android_display_backend/Android.bp b/libs/android_display_backend/Android.bp
index 6ad5fab..c65536a 100644
--- a/libs/android_display_backend/Android.bp
+++ b/libs/android_display_backend/Android.bp
@@ -42,7 +42,6 @@
"android.system.virtualizationservice_internal-ndk",
"android.system.virtualizationcommon-ndk",
"android.system.virtualizationservice-ndk",
- "libyuv",
],
shared_libs: [
"libbinder_ndk",
diff --git a/libs/android_display_backend/crosvm_android_display_client.cpp b/libs/android_display_backend/crosvm_android_display_client.cpp
index a16b7f2..66320f3 100644
--- a/libs/android_display_backend/crosvm_android_display_client.cpp
+++ b/libs/android_display_backend/crosvm_android_display_client.cpp
@@ -18,38 +18,27 @@
#include <aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
-#include <android/native_window.h>
-#include <android/native_window_aidl.h>
-#include <libyuv.h>
-#include <stdint.h>
-#include <utils/Errors.h>
+#include <system/graphics.h> // for HAL_PIXEL_FORMAT_*
#include <condition_variable>
#include <memory>
#include <mutex>
-#include <vector>
+using aidl::android::crosvm::BnCrosvmAndroidDisplayService;
using aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal;
-
-#define LIBEXPORT __attribute__((visibility("default"))) extern "C"
-
-typedef void (*android_display_log_callback_type)(const char* message);
-
-static void android_display_log_callback_stub(const char* message) {
- (void)message;
-}
+using aidl::android::view::Surface;
namespace {
-class DisplayService : public aidl::android::crosvm::BnCrosvmAndroidDisplayService {
+class DisplayService : public BnCrosvmAndroidDisplayService {
public:
DisplayService() = default;
virtual ~DisplayService() = default;
- ndk::ScopedAStatus setSurface(aidl::android::view::Surface* surface) override {
+ ndk::ScopedAStatus setSurface(Surface* surface) override {
{
std::lock_guard lk(mSurfaceReadyMutex);
- mSurface = std::make_unique<aidl::android::view::Surface>(surface->release());
+ mSurface = std::make_unique<Surface>(surface->release());
}
mSurfaceReady.notify_one();
return ::ndk::ScopedAStatus::ok();
@@ -64,7 +53,7 @@
return ::ndk::ScopedAStatus::ok();
}
- aidl::android::view::Surface* getSurface() {
+ Surface* getSurface() {
std::unique_lock lk(mSurfaceReadyMutex);
mSurfaceReady.wait(lk, [this] { return mSurface != nullptr; });
return mSurface.get();
@@ -73,214 +62,116 @@
private:
std::condition_variable mSurfaceReady;
std::mutex mSurfaceReadyMutex;
- std::unique_ptr<aidl::android::view::Surface> mSurface;
+ std::unique_ptr<Surface> mSurface;
};
-void ErrorF(android_display_log_callback_type error_callback, const char* format, ...) {
- char buffer[1024];
-
- va_list vararg;
- va_start(vararg, format);
- vsnprintf(buffer, sizeof(buffer), format, vararg);
- va_end(vararg);
-
- error_callback(buffer);
-}
-
} // namespace
-struct android_display_context {
- uint32_t width;
- uint32_t height;
- std::shared_ptr<DisplayService> displayService;
+typedef void (*ErrorCallback)(const char* message);
+
+struct AndroidDisplayContext {
+ std::shared_ptr<IVirtualizationServiceInternal> virt_service;
+ std::shared_ptr<DisplayService> disp_service;
+ ErrorCallback error_callback;
+
+ AndroidDisplayContext(ErrorCallback cb) : error_callback(cb) {
+ auto disp_service = ::ndk::SharedRefBase::make<DisplayService>();
+
+ // Creates DisplayService and register it to the virtualizationservice. This is needed
+ // because this code is executed inside of crosvm which runs as an app. Apps are not allowed
+ // to register a service to the service manager.
+ auto virt_service = IVirtualizationServiceInternal::fromBinder(ndk::SpAIBinder(
+ AServiceManager_waitForService("android.system.virtualizationservice")));
+ if (virt_service == nullptr) {
+ errorf("Failed to find virtualization service");
+ return;
+ }
+ auto status = virt_service->setDisplayService(disp_service->asBinder());
+ if (!status.isOk()) {
+ errorf("Failed to register display service");
+ return;
+ }
+
+ this->virt_service = virt_service;
+ this->disp_service = disp_service;
+ ABinderProcess_startThreadPool();
+ }
+
+ ~AndroidDisplayContext() {
+ if (virt_service == nullptr) {
+ errorf("Not connected to virtualization service");
+ return;
+ }
+ auto status = this->virt_service->clearDisplayService();
+ if (!status.isOk()) {
+ errorf("Failed to clear display service");
+ }
+ }
+
+ void errorf(const char* format, ...) {
+ char buffer[1024];
+
+ va_list vararg;
+ va_start(vararg, format);
+ vsnprintf(buffer, sizeof(buffer), format, vararg);
+ va_end(vararg);
+
+ error_callback(buffer);
+ }
};
-LIBEXPORT
-struct android_display_context* create_android_display_context(
- const char* name, size_t name_len, android_display_log_callback_type error_callback) {
- auto ctx = new android_display_context();
-
- auto service = ::ndk::SharedRefBase::make<DisplayService>();
-
- if (strlen(name) != name_len) {
- ErrorF(error_callback, "Invalid service name length. Expected %u, actual %u", name_len,
- strlen(name));
- return nullptr;
- }
- ::ndk::SpAIBinder binder(
- AServiceManager_waitForService("android.system.virtualizationservice"));
-
- auto virt_service = IVirtualizationServiceInternal::fromBinder(binder);
- if (virt_service == nullptr) {
- ErrorF(error_callback, "Failed to find android.system.virtualizationservice");
- return nullptr;
- }
- auto status = virt_service->setDisplayService(service->asBinder());
- if (!status.isOk()) {
- ErrorF(error_callback, "Failed to register %s",
- aidl::android::crosvm::ICrosvmAndroidDisplayService::descriptor);
- return nullptr;
- }
-
- ABinderProcess_startThreadPool();
-
- auto surface = service->getSurface();
- ctx->width = static_cast<uint32_t>(ANativeWindow_getWidth(surface->get()));
- ctx->height = static_cast<uint32_t>(ANativeWindow_getHeight(surface->get()));
- ctx->displayService = service;
- return ctx;
+extern "C" struct AndroidDisplayContext* create_android_display_context(
+ const char*, ErrorCallback error_callback) {
+ return new AndroidDisplayContext(error_callback);
}
-LIBEXPORT
-void destroy_android_display_context(android_display_log_callback_type error_callback,
- struct android_display_context* ctx) {
- auto service = ::ndk::SharedRefBase::make<DisplayService>();
- ::ndk::SpAIBinder binder(
- AServiceManager_waitForService("android.system.virtualizationservice"));
- auto virt_service = IVirtualizationServiceInternal::fromBinder(binder);
- if (virt_service != nullptr) {
- auto status = virt_service->clearDisplayService();
- } else {
- ErrorF(error_callback, "Failed to find android.system.virtualizationservice");
- }
-
- if (!ctx) {
- ErrorF(error_callback, "Invalid context.");
- return;
- }
-
+extern "C" void destroy_android_display_context(struct AndroidDisplayContext* ctx) {
delete ctx;
}
-LIBEXPORT
-uint32_t get_android_display_width(android_display_log_callback_type error_callback,
- struct android_display_context* ctx) {
- if (!ctx) {
- ErrorF(error_callback, "Invalid context.");
- return -1;
+extern "C" ANativeWindow* create_android_surface(struct AndroidDisplayContext* ctx, uint32_t width,
+ uint32_t height) {
+ if (ctx->disp_service == nullptr) {
+ ctx->errorf("Display service was not created");
+ return nullptr;
}
- if (!ctx->displayService->getSurface()) {
- ErrorF(error_callback, "Invalid context surface for ctx:%p.", ctx);
- return -1;
+ // 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()->get(); // this can block
+ if (ANativeWindow_setBuffersGeometry(surface, width, height, format) != 0) {
+ ctx->errorf("Failed to set buffer gemoetry");
+ return nullptr;
}
- return ctx->width;
+ // 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;
}
-LIBEXPORT
-uint32_t get_android_display_height(android_display_log_callback_type error_callback,
- struct android_display_context* ctx) {
- if (!ctx) {
- ErrorF(error_callback, "Invalid context.");
- return -1;
- }
- if (!ctx->displayService->getSurface()) {
- ErrorF(error_callback, "Invalid context surface for ctx:%p.", ctx);
- return -1;
- }
- return ctx->height;
+extern "C" void destroy_android_surface(struct AndroidDisplayContext*, ANativeWindow*) {
+ // NOT IMPLEMENTED
}
-uint16_t RGBA8888ToRGB565(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
- (void)a;
- return (static_cast<uint16_t>(r >> 3) << 11) | (static_cast<uint16_t>(g >> 2) << 5) |
- (static_cast<uint16_t>(b >> 3) << 0);
+extern "C" bool get_android_surface_buffer(struct AndroidDisplayContext* ctx,
+ ANativeWindow* 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) {
+ ctx->errorf("Failed to lock buffer");
+ return false;
+ }
+ return true;
}
-LIBEXPORT
-void blit_android_display(android_display_log_callback_type error_callback,
- struct android_display_context* ctx, uint32_t width, uint32_t height,
- uint8_t* pixels, size_t pixels_num_bytes) {
- if (!ctx) {
- ErrorF(error_callback, "Invalid context.");
- return;
- }
- if (!ctx->displayService->getSurface()) {
- ErrorF(error_callback, "Invalid context surface.");
- return;
- }
- if (pixels_num_bytes != width * height * 4) {
- ErrorF(error_callback, "Invalid buffer size.");
- return;
- }
- ANativeWindow* anw = ctx->displayService->getSurface()->get();
- if (!anw) {
- ErrorF(error_callback, "Invalid context surface.");
- return;
- }
-
- ANativeWindow_Buffer anwBuffer = {};
- if (ANativeWindow_lock(anw, &anwBuffer, nullptr) != android::OK) {
- ErrorF(error_callback, "Failed to lock ANativeWindow.");
- return;
- }
-
- // Source is always BGRA8888.
- auto* src = reinterpret_cast<uint32_t*>(pixels);
- auto srcWidth = static_cast<uint32_t>(width);
- auto srcHeight = static_cast<uint32_t>(height);
- auto srcStrideBytes = srcWidth * 4;
- auto srcStridePixels = srcWidth;
-
- auto dstWidth = static_cast<uint32_t>(anwBuffer.width);
- auto dstHeight = static_cast<uint32_t>(anwBuffer.height);
-
- // Scale to fit if needed.
- std::vector<uint32_t> scaledSrc;
- if (srcWidth != dstWidth || srcHeight != dstHeight) {
- const float ratioWidth = static_cast<float>(dstWidth) / static_cast<float>(srcWidth);
- const float ratioHeight = static_cast<float>(dstHeight) / static_cast<float>(srcHeight);
- const float ratioUsed = std::min(ratioWidth, ratioHeight);
-
- uint32_t scaledSrcWidth = static_cast<uint32_t>(static_cast<float>(srcWidth) * ratioUsed);
- uint32_t scaledSrcHeight = static_cast<uint32_t>(static_cast<float>(srcHeight) * ratioUsed);
- uint32_t scaledSrcStrideBytes = scaledSrcWidth * 4;
- uint32_t scaledSrcStridePixels = scaledSrcWidth;
-
- scaledSrc.resize(scaledSrcHeight * scaledSrcStridePixels);
-
- libyuv::ARGBScale(reinterpret_cast<uint8_t*>(src), srcStrideBytes, srcWidth, srcHeight,
- reinterpret_cast<uint8_t*>(scaledSrc.data()), scaledSrcStrideBytes,
- scaledSrcWidth, scaledSrcHeight, libyuv::kFilterBilinear);
-
- src = scaledSrc.data();
- srcWidth = scaledSrcWidth;
- srcHeight = scaledSrcHeight;
- srcStrideBytes = scaledSrcStrideBytes;
- srcStridePixels = scaledSrcStridePixels;
- }
-
- if (anwBuffer.format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM) {
- auto* dst = reinterpret_cast<uint32_t*>(anwBuffer.bits);
- auto dstStridePixels = static_cast<uint32_t>(anwBuffer.stride);
-
- for (uint32_t h = 0; h < std::min(srcHeight, dstHeight); h++) {
- for (uint32_t w = 0; w < std::min(srcWidth, dstWidth); w++) {
- dst[(h * dstStridePixels) + w] = src[(h * srcStridePixels) + w];
- }
- }
- } else if (anwBuffer.format == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM) {
- auto* dst = reinterpret_cast<uint16_t*>(anwBuffer.bits);
- auto dstWidth = static_cast<uint32_t>(anwBuffer.width);
- auto dstHeight = static_cast<uint32_t>(anwBuffer.height);
- auto dstStridePixels = static_cast<uint32_t>(anwBuffer.stride);
-
- for (uint32_t h = 0; h < std::min(srcHeight, dstHeight); h++) {
- for (uint32_t w = 0; w < std::min(srcWidth, dstWidth); w++) {
- uint32_t srcPixel = src[(h * srcStridePixels) + w];
- uint8_t* srcPixelBytes = reinterpret_cast<uint8_t*>(&srcPixel);
- uint8_t r = srcPixelBytes[2];
- uint8_t g = srcPixelBytes[1];
- uint8_t b = srcPixelBytes[0];
- uint8_t a = srcPixelBytes[3];
- dst[(h * dstStridePixels) + w] = RGBA8888ToRGB565(r, g, b, a);
- }
- }
- } else {
- ErrorF(error_callback, "Unhandled format: %d", anwBuffer.format);
- }
-
- if (ANativeWindow_unlockAndPost(anw) != android::OK) {
- ErrorF(error_callback, "Failed to unlock and post ANativeWindow.");
+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.");
return;
}
}