Windows下编译skia的安卓版本
最近有对安卓上的自渲染感兴趣,然而前几天由于新买的显卡是N卡,Linux下的兼容实在太差,于是只得换回Windows。因此这篇文章尝试在Windows下编译Android版本的Skia。

Skia是一个开源2D图形库,提供可跨硬件和软件平台工作的通用API,同时是Google、Chrome、Android、Flutter等多个产品的图形引擎。 然而有点幽默的是,虽然安卓内置了Skia,我们想在ndk下却不能使用。 因为Skia不提供稳定API,因此你要么用JNI方法调用java层封装好的Canvas API,要么就只能自己内置一个。

前几天由于新买的显卡是N卡,Linux下的兼容实在太差,于是只得换回Windows。因此这篇文章尝试在Windows下编译Android版本的Skia。

学习参考

安装工具

首先安装我们编译过程中需要的git、python、ninja等工具。现在Windows下也有包管理器了,比如我们可以这样安装git:

winget install Git.Git

然而我实际体验感觉比Linux还是差很多的,例如Git安装后并没有配置好环境变量

配置环境变量一般倒是难不倒我们程序员,但Python安装首先恶心了我一下。当我在Windows下的PowerShell窗口下打出python,回车后是打开微软应用商店安装Python的页面。这是因为Windows搞了事情,可以在设置项中找到

设置中的应用程序别名

理论上可以在Windows应用商店直接安装Python,但隐约记得看过一篇文章说Windows应用商店安装的Python联网会有问题,因此这里就谢绝微软的好意了。

使用winget安装Python并设置环境变量后,在命令行还是找不到python3,这是因为Windows下的Python安装后文件夹中只有一个python.exe,即使你安装的是Python3。解决方法也是简单粗暴——直接复制一份。

直接将Python程序复制一份

拉取源码

唯一注意的是设置网络环境,现在Windows 11默认使用的是powershell,设置代理方式是不一样的。

CMD:

set http_proxy=http://127.0.0.1:7890
set https_proxy=http://127.0.0.1:7890

PowerShell:

$env:HTTP_PROXY="http://127.0.0.1:7890"
$env:HTTPS_PROXY="http://127.0.0.1:7890"

接下来拉取并配置环境。

git clone https://skia.googlesource.com/skia.git
cd skia
python3 tools/git-sync-deps

然后使用默认参数编译

bin/gn gen out/Debug
ninja -C out/Debug

编译通过。

传递更多参数

上面编译成功了Windows版本,但当我尝试编译安卓版本遇到了困难。

PS C:\Users\yqmai\Project\skia> bin/gn gen out/arm64 --args='ndk="/tmp/ndk" target_cpu="arm64"'
ERROR at the command-line "--args":1:5: Invalid token.
ndk=/tmp/ndk target_cpu=arm64
    ^
I have no idea what this is.

这里信息只能隐约看出是参数传递有问题,尝试数次无果。参考这篇文章,使用python脚本可以方便地传递参数,不用担心引号、斜线等的转义问题。

import os
from os.path import join
from subprocess import check_call
SKIA_SOURCE_PATH = "C:\\Users\\yqmai\\Project\\skia"

args = [
    'ndk="C:\\Users\\yqmai\\AppData\\Local\\Android\\Sdk\\ndk\\27.0.11902837"',
    'target_cpu="arm64"',
    'ndk_api=27',
    'is_component_build=true'
]
check_call(
    [join(SKIA_SOURCE_PATH, 'bin\\gn'), 'gen', 'out/Android', '--args=' + ' '.join(args)],
    shell=True,
    cwd=SKIA_SOURCE_PATH,
)

check_call(
    ['ninja', '-C', join(SKIA_SOURCE_PATH, 'out/Android')],
    shell=True,
)

如果你尝试编译一个Release包,你会发现缺好多头文件,因为这些库在系统中没找到。看上去很难解决,所以如果有需要的话还是在Linux/macOS下折腾吧 :{

Yeah, it’s fairly uncommon to have any of that on Windows.

安卓demo

将skia的include拷贝到cpp模块下的skia文件夹下,将libskia.so拷贝到jniLibs/arm64-v8a下,

在CMakeLists.txt添加依赖

# 宿主需要依赖 skia 的头文件
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ./skia)

# 申请 skia 动态库
add_library( skia
        SHARED
        IMPORTED )
set_target_properties( skia
        PROPERTIES IMPORTED_LOCATION
        ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libskia.so)
# 依赖 skia
target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        log
        skia)

在自定义SurfaceView中把holder传到底层

class SkiaSurfaceView @JvmOverloads constructor(context: Context, defStyleAttr: AttributeSet? = null, defStyleRes: Int = 0) : SurfaceView(context, defStyleAttr, defStyleRes) {
    private val surfaceCallback = object : SurfaceHolder.Callback {
        override fun surfaceCreated(holder: SurfaceHolder) {
            nativeSurfaceCreated(holder.surface)
        }

        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
            nativeSurfaceChanged()
        }

        override fun surfaceDestroyed(holder: SurfaceHolder) {
            nativeSurfaceDestroyed()
        }
    }

    init {
        holder.addCallback(surfaceCallback)
    }

    private external fun nativeSurfaceCreated(surface: Surface)

    private external fun nativeSurfaceChanged()

    private external fun nativeSurfaceDestroyed()
}

在底层绘制三角形

#include <jni.h>
#include <string>
#include "android/native_window.h"
#include "android/native_window_jni.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPath.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkAlphaType.h"
#include "include/core/SkColor.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_skiademo1_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_skiademo1_SkiaSurfaceView_nativeSurfaceCreated(JNIEnv *env, jobject thiz,
                                                                jobject surface) {
    // 将 Surface 对象转换为 ANativeWindow 对象
    auto nativeWindow = ANativeWindow_fromSurface(env, surface);
    // 设置 ANativeWindow 宽度、高度和像素格式
    ANativeWindow_setBuffersGeometry(
            nativeWindow, 400, 400, WINDOW_FORMAT_RGBA_8888);
    ANativeWindow_Buffer *buffer = new ANativeWindow_Buffer();
    // 锁定 ANativeWindow 的缓冲区,准备开始修改缓冲区的像素数据
    ANativeWindow_lock(nativeWindow, buffer, 0);
    int bpr = buffer->stride * 4;
    // 实际像素对象
    SkBitmap bitmap;
    // 生成一份位图描述属性
    SkImageInfo image_info = SkImageInfo::MakeS32(
            buffer->width, buffer->height, SkAlphaType::kPremul_SkAlphaType);
    bitmap.setInfo(image_info, bpr);
    bitmap.setPixels(buffer->bits);
    // 构造一个 canvas 对象,将 canvas 画布和 bitmap 关联上
    SkCanvas surfaceCanvas{bitmap};
    // 创建一个红色的画刷
    SkPaint paint;
    paint.setColor(SK_ColorRED);
    paint.setStyle(SkPaint::kFill_Style);
    // 创建一个绘制路径
    SkPath path;
    path.moveTo(100.0f, 0.0f);
    path.lineTo(0.0f, 100);
    path.lineTo(200, 100);
    path.close();
    // 使用画刷绘制路径
    surfaceCanvas.drawPath(path, paint);
    // 解锁并提交缓冲
    ANativeWindow_unlockAndPost(nativeWindow);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_skiademo1_SkiaSurfaceView_nativeSurfaceChanged(JNIEnv *env, jobject thiz) {
    // TODO: implement nativeSurfaceChanged()

}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_skiademo1_SkiaSurfaceView_nativeSurfaceDestroyed(JNIEnv *env, jobject thiz) {
    // TODO: implement nativeSurfaceDestroyed()
}

画一个三角形

恢复环境变量

完成上文后发现一些java程序无法执行,报错 Exception in thread “main” java.lang.UnsupportedClassVersionError: MainKt has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0

显然这是使用了错误的java版本。经检查发现skia编译过程中会修改系统环境变量中的Path和JAVA_HOME,删除EMSDK开头的几项后问题解决。


最后修改于 2024-07-03