Android啟動時間優化大全

1 修改Android mksh默認的列長度
不修改這個參數,adb shell后,輸入超過80個字符,就不能看到完整的命令行。

external/mksh/src/sh.h
EXTERN mksh_ari_t x_cols E_INIT(80);
EXTERN mksh_ari_t x_lins E_INIT(24);

2 Kernel優化
2.1 內核驅動模塊化
將內核中盡可能多的驅動模塊化,寫一個負責insmod的shell腳本,開機時作為服務運行,可以大大減少內核啟動的時間。同時由于init后是多任務運行,腳本服務對init啟動其它服務的延時可以忽略不計。

注意insmod是阻塞調用,所以直接在init.rc腳本中調用,還是會加長啟動時間,所以需要insmod的模塊統一放到一個腳本服務中。

BoardConfig.mk
BOARD_VENDOR_KERNEL_MODULES
BOARD_RECOVERY_KERNEL_MODULES

2.2 提升console串口的波特率

2.3 printk
2.3.1 實現原理
printk的實現原理很簡單,在有了日志消息后,首先申請控制臺的信號量,如果申請到,則調用控制臺寫方法,寫控制臺。

在內核源碼樹的kernel/printk.c中,使用宏DECLARE_MUTEX聲明了一個互斥鎖console_sem,他用于保護console驅動列表console_drivers及同步對整個console驅動系統的訪問。其中定義了函數acquire_console_sem來獲得互斥鎖console_sem,定義了release_console_sem來釋放互斥鎖console_sem,定義了函數try_acquire_console_sem來盡力得到互斥鎖console_sem。這三個函數實際上是分別對函數down,up和down_trylock的簡單包裝。需要訪問console_drivers驅動列表時就需要使用acquire_console_sem來保護console_drivers列表,當訪問完該列表后,就調用release_console_sem釋放信號量console_sem。函數console_unblank,console_device,console_stop,console_start,register_console 和unregister_console都需要訪問console_drivers,因此他們都使用函數對acquire_console_sem和release_console_sem來對console_drivers進行保護。

調試console_sem時,需要打開宏CONFIG_DEBUG_SPINLOCK以跟蹤owner字段。

關閉Kernel Log,通過bootchart.png可以看到啟動init進程的時間明顯提前,可以加快啟動速度。
kernel/printk.c
int console_printk[4] = {
DEFAULT_CONSOLE_LOGLEVEL,
DEFAULT_MESSAGE_LOGLEVEL,
MINIMUM_CONSOLE_LOGLEVEL,
DEFAULT_CONSOLE_LOGLEVEL,
};
改為
int console_printk[4] = {
0, //DEFAULT_CONSOLE_LOGLEVEL,
0, //DEFAULT_MESSAGE_LOGLEVEL,
0, //MINIMUM_CONSOLE_LOGLEVEL,
0, //DEFAULT_CONSOLE_LOGLEVEL,
};
這四個值對應到路徑proc/sys/kernel/printk,當printk()沒有指定消息級別時,就采用DEFAULT_MESSAGE_LOGLEVEL(對應到KERN_WARNING = 4)。

echo "8 8 8 8" > /proc/sys/kernel/printk
- 第一個“8”表示內核打印函數printk的打印級別

2.3.2 pr_debug動態log
CONFIG_DEBUG_FS=y
CONFIG_DYNAMIC_DEBUG=y

echo "file my_drv.c +p" > \
/sys/kernel/debug/dynamic_debug/control

2.3.3 kernel調試時打開所有log
BOARD_KERNEL_CMDLINE += ignore_loglevel

動態修改:
echo 0 > \
/sys/module/printk/parameters/ignore_loglevel
echo 1 > \
/sys/module/printk/parameters/ignore_loglevel

2.4 調試驅動probe耗時
BoardConfig.mk
BOARD_KERNEL_CMDLINE += \
initcall_debug ignore_loglevel

3 Android init進程
3.1 init
stage1: device tree
stage2: fstab

3.2 Sections Loading Sequence
on early-init
wait_for_coldboot_done()
on init
on early-fs
on fs
on post-fs
on post-fs-data
on early-boot
on boot

3.3 init Log機制
無論init代碼架構如何變化,init進程的log始終是通過/dev/kmsg輸出。
android::base::InitLogging(argv,
&android::base::KernelLogger);

LOG:普通的流式
PLOG:普通的流式,但是可以打印錯誤,類似于Linux的perror()

3.4 自定義kmsg函數
#include <sys/stat.h>
#include <sys/types.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
static void kmsg(const char *fmt, ...)
{
char buf[512] = {0};
va_list ap;
int n, flags;
static int fd = 0;

va_start(ap,fmt);
n = vsnprintf(buf, 511, fmt, ap);
va_end(ap);

if (fd <= 0) {
fd = open("/dev/kmsg", O_RDWR);
if (fd > 0) {
// 必須加,否則init fork zygote后,
// 該描述符會被zygote繼承,
// 導致zygote異常,不斷重啟
flags = fcntl(fd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(fd, F_SETFD, flags);
}
}

if ((fd > 0) && (n > 0)) {
write(fd, buf, n);
}
}

如果往/dev/kmsg中寫log,通過dmesg幾乎看不到log,加上如下的配置可以解決該問題。
BoardConfig.mk
BOARD_KERNEL_CMDLINE += \
printk.devkmsg=on

該配置用在如下的代碼中:
kernel/printk/printk.c
devkmsg_write()

4 IO
4.1 CPU手動調頻
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

Foreground:Tasks run all cores
Background:Tasks run little cores
System-background:Tasks run all little cores for system processes that shouldn’t run on big cores
TOP-APP:Tasks run all cores big cores
?
4.2 eMMC5.1速度調試
Read my blog “Flash閃存技術”。

4.3 IO調度Tunning
IO調度算法種類:cfq、deadline、noop(No Operation,電梯調度算法)

4.3.1 方法1
@ init.rc
on late-fs
# boot time fs tune for UFS, change sda for eMMC
write /sys/block/sda/queue/iostats 0
write /sys/block/sda/queue/read_ahead_kb 2048
write /sys/block/sda/queue/nr_requests 256

write /sys/block/dm-0/queue/read_ahead_kb 2048
write /sys/block/dm-1/queue/read_ahead_kb 2048

on property:sys.boot_completed=1
write /sys/block/dm-0/queue/read_ahead_kb 512
write /sys/block/dm-1/queue/read_ahead_kb 512

write /sys/block/sda/queue/read_ahead_kb 128
write /sys/block/sda/queue/nr_requests 128

更好的方法是用(與上面的方法互斥):ioprio rt <value>
添加方法如下:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
[...]
# nice的取值范圍從-20到+19,-20優先級最高,+19最低
priority -20
# 范圍從0到7,數字越小ioprio real-time優先級越高
ioprio rt 2
[...]
Read my blog “Linux Containers知識點”。

4.3.2 方法2
ionice可以用來調整特定進程的ioprio。

0 - none, 1 - Realtime, 2 - Best-effort, 3 - idle
SYS_ioprio_get
SYS_ioprio_set

5 Framework優化
5.1 設置log等級
/data/local.prop
setprop log.tag.<tagname> VERBOSE
setprop persist.log.tag.<tagname> VERBOSE

5.2 Android虛擬按鍵編碼頭文件
frameworks/native/include/android/keycodes.h

5.3 Zygote
5.3.1 Preface
frameworks/base/cmds/app_process
frameworks/base/core/jni/AndroidRuntime.cpp - LOG_BOOT_PROGRESS_START
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

當64位zygote啟動時,它會從/data/dalvik-cache/arm64/system@framework@boot*.{art,oat,vdex}加載代碼,這些被加載的jar包由環境變量BOOTCLASSPATH指定。

測試zygote c++時間:
#include<utils/SystemClock.h>
uptimeMillis()

5.3.2 preloaded-classes
frameworks/base/preloaded-classes

frameworks/base/tools/preload/WritePreloadedClassFile.java
frameworks/base/tools/preload/Compile.java

以上2個文件的修改,需要重新生成preload.jar
mmm frameworks/base/tools/preload/
產生文件out/host/linux-x86/framework/preload.jar

重新生成preloaded-classes文件
rm frameworks/base/preloaded-classes
rm out/target/product/<ppp>/system/etc/preloaded-classes
java -Xss512M -cp out/host/linux-x86/framework/preload.jar ?WritePreloadedClassFile frameworks/base/tools/preload/20100223.compiled
make systemimage -j8

5.3.3 framework-res.apk
on post-fs-data
# exec [ <seclabel> [ <user> [ <group> ]* ]] -- <command> [ <argument> ]*
exec u:r:init:s0 -- /system/bin/cat /system/framework/framework-res.apk > /dev/null

or

service preload_res /system/bin/cat /system/framework/framework-res.apk > /dev/null
class core
user root
# 加seclabel是為了避免寫cat.te文件,可以快速測試,最終提交版本是需要寫cat.te的
seclabel u:r:init:s0
oneshot
disabled

on post-fs-data
start preload_res

5.3.4 多線程做preload優化
new Thread(new Runnable() {
@Override
public void run() {
// TODO, call preload();
}
}).start();

5.4 system_server
5.4.1 PMS包掃描
scanDirLI() :scanDir Lock mInstallLock

/system/app
/system/framework
/data/app
/data/app-private

Android 8.0后存儲管理類是StorageManagerService.java

5.4.2 Pre-optimization
Android第一次開機后會進行dex2oat操作。

5.4.3 enableScreenAfterBoot
@ ActivityManagerService.java
enableScreenAfterBoot()
- >
@ WindowManagerService.java
enableScreenAfterBoot()
- >
mPolicy.systemBooted() ?- 在PhoneWindowManager.java(PWM)中
->
performEnableScreen()
- >
checkWaitingForWindows() - 系統將檢查目前所有的window是否畫完,如果所有的window(包括keyguard、wallpaper和launcher等)都已經畫好,系統會設置屬性service.bootanim.exit值為1,退出動畫。

查看當前top窗口:
findFocusedWindow()
adb shell dumpsys window windows | grep mCurrent
adb shell pm path <package name>

5.5 查看系統安裝的所有APK
pm list packages

5.6 settings數據庫讀寫
settings list system
settings put system screen_brightness 50

6 進程調試
6.1 Android性能分析工具匯總
top
free -m
procrank
vmstat 1
pidstat -w 1
mpstat -P ALL 3
iostat
logcat -b events | grep am_crash
logcat | grep died

https://github.com/zhenggaobing/pidstat

6.2 busybox
https://busybox.net/downloads/binaries/1.28.1-defconfig-multiarch/

查看進程樹
busybox pstree

show threads
busybox ps -T
busybox top -H

6.3 Linux signal
查看Linux支持的signal:kill -l

6.4 Linux swap分區的使用
/dev/block/zram0
procrank
free -m

6.5 strace zygote
修改init進程
@ system/core/init/service.cpp
static void trace_zygote64(pid_t pid)
{
char pid_str[16] = {0};

if (fork() == 0) {
snprintf(pid_str, 15, "%d", pid);
execl("/system/xbin/strace",
"strace",
"-p", pid_str,
/*"-e", "trace=open,read,write,ioctl",*/
"-o", "/dev/z_tr.log",
"-s", "128",
"-tt", "-T", "-x", NULL);
}
}
將該函數放在創建zygote的地方。

z_tr.log中有具體時間,與logcat -b events | grep boot_progress抓取的log的關鍵事件時間戳進行對比,找出問題。

6.6 strace監視文件讀寫
抓取unix domain socket數據的讀寫 - 類似于tcpdump抓取網絡數據包
strace -e read=7 -e write=7 -p 1
PID為1的進程中,dump出所有對fd = 7的讀寫數據

7 時間測量方法
7.1 bootchart源碼下載編譯
http://www.bootchart.org/download.html

在Linux桌面機器上:
apt-get install ant

解壓下載的bootchart源代碼,在bootchart源代碼目錄下執行ant,結束后,產生bootchart.jar,可以在Linux上分析,也可以將該jar包拷貝到Windows上。

7.2 如何使用bootchart
Android的bootchart時間軸是從kernel啟動的時間點開始計算的,這個可以根據生成的bootchart.png和kernel msg得出結論。

echo 1 > /data/bootchart/enabled
重啟手機

logs under /data/bootchart
cd /data/bootchart
busybox tar zcvf bootchart.tgz header kernel_pacct proc_diskstats.log proc_ps.log proc_stat.log
adb pull /data/bootchart/bootchart.tgz .
java -jar bootchart.jar .\bootchart.tgz

7.3 比較修改
system/core/init/compare-bootcharts.py

將2次生成的bootchart.tgz分別放到old_dir和new_dir中:
python compare-bootcharts.py old_dir new_dir

7.4 perfboot
system/core/init/perfboot.py

將system/core/init/perfboot.py和development/python-packages/adb文件夾拷貝到同一個目錄下。需要注意的是,拷貝的是adb文件夾,不然perfboot.py中的import adb會報錯。生成的.tsv文件使用excel打開。

python perfboot.py \
--iterations=2 \
--interval=30 -v \
--output=D:\data.tsv

等價于如下的命令:
adb logcat -b events | grep boot_progress

7.5 kernel啟動時間分析
packages/services/Car/tools/bootanalyze/bootanalyze.py
packages/services/Car/tools/bootanalyze/config.yaml

7.6 獲取Android各階段的時間消耗
getprop | grep -i boottime
logcat | grep -i Timing

7.7 Android systrace使用
1) atrace.rc
打開默認關閉的trace開關
in frameworks/native/cmds/atrace/atrace.rc?
- write /sys/kernel/debug/tracing/tracing_on 0
+ #write /sys/kernel/debug/tracing/tracing_on 0

2) 附加配置
in device/<OEM>/common/common.mk
PRODUCT_PROPERTY_OVERRIDES += \
debug.atrace.tags.enableflags=802922

in BoardConfig.mk
BOARD_KERNEL_CMDLINE += \
trace_buf_size=64M \
trace_event=sched_wakeup,\
sched_switch,sched_blocked_reason,\
sched_cpu_hotplug,block,ext4

3) 開機完成后結束紀錄
項目的init.<PRODUCT>.rc文件加入如下修改,目的是結束trace記錄。
on property:sys.boot_completed=1
write /d/tracing/tracing_on 0
write /d/tracing/events/ext4/enable 0
write /d/tracing/events/block/enable 0

4) 抓取log并分析
做完上述修改后編譯燒錄鏡像文件,待開機結束后執行:
adb root
adb shell "cat /d/tracing/trace" > boot_trace

然后執行
python external/chromium-trace/systrace.py \
--from-file=boot_trace \
-o boot_trace.html
上述命令可以將trace log轉成html文件,用瀏覽器打開即可。

8 Abbreviations
bzImage:big zImage
vmlinuz:virtual memory
ABS_MT_POSITION_X:Multi Touch
Android PMS LI、LIF、LPw、LPr:要想弄明白方法名中的LI、LIF、LPw、LPr的含義,需要先了解PackageManagerService內部使用的兩個鎖。因為LI、LIF、LPw、LPr中的L,指的是Lock,而后面跟的I和P指的是兩個鎖,I表示mInstallLock同步鎖;P表示mPackages同步鎖。LPw、LPr中的w表示writing,r表示reading。LIF中的F表示Freeze。
avb:Android Verified Boot,用dm-verify驗證system分區的完整性,用在Android 8.0之后的fstab文件中
scanDirLI() :scanDir Lock mInstallLock
APUE:??pju,Advanced Programming in the UNIX Environment
AT_FDCWD:File Descriptor Current Working Directory
bail out:跳傘
BLCR:BerkeleyLab Checkpoint/Restart
FRP:Factory Reset Protection
Intercept:API攔截,通信攔截
Linux dd命令:if表示input file,of表示output file,bs表示block size
Linux EPROTO:表示USB bitstuff出現了錯誤,眼圖有問題
lmkd:Low Memory Killer Daemon
lsof:list open files
MIDR:ARM Main ID Register
MPIDR:ARM MultiProcessor ID Register
PPID:Parent Process ID(Linux ps命令可以看到),MFi:Product Plan ID
PuTTY:?p?ti
RA:Linux blockdev read ahead
Slog.wtf:what a terrible failure

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/90892.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/90892.shtml
英文地址,請注明出處:http://en.pswp.cn/web/90892.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

matplotlib.pyplot: 底層原理簡析與進階技巧

文章目錄 1 底層實現原理 1.1 核心架構 1.1 渲染流程 2 基礎用法 2.1 基本繪圖 2.2 多子圖系統 2.3 高階用法 2.3.1 自定義Artist對象 2.3.2 高級動畫技術 2.3.3 事件處理系統 2.3.4 混合渲染技術 3 性能優化技巧 4 擴展模塊 5 總結 5.1 底層原理關鍵點 5.2 進階技巧 1 底層實現…

深入理解現代前端開發中的 <script type=“module“> 與構建工具實踐

引言&#xff1a;模塊化開發的演進在早期的前端開發中&#xff0c;JavaScript 缺乏原生的模塊化支持&#xff0c;開發者不得不依賴 IIFE&#xff08;立即調用函數表達式&#xff09;或第三方庫&#xff08;如 RequireJS&#xff09;來實現代碼組織。隨著 ES6&#xff08;ES2015…

yolo--qt可視化開發

qt5可能不支持我們的cuda版本&#xff0c;改用qt6 YOLO11QT6OpencvC訓練加載模型全過程講解_yolov11 模型轉換成opencv c模型-CSDN博客 下面是qt5版本的案例&#xff0c;和yolo及cuda有沖突 安裝qt 切換到虛擬環境&#xff0c;例如pyqt&#xff0c;conda activate pyqt pip …

SQL性能優化

show [session|global] status : 查看服務器狀態 show global status like Com_ : 查看各種語句的執行次數 開啟慢查詢: 在 MySQL 配置文件&#xff08;/etc/my.cnf&#xff09;配置: #開啟MySQL慢日志查詢開關 slow_query_log1 #設置慢日志的時間為2秒&#xff0c;SQL語句執…

ctfshow pwn40

目錄 1. 分析程序 2. 漏洞編寫 3. 漏洞驗證 1. 分析程序 首先檢查程序相關保護&#xff0c;發現程序為32位且只開啟了一個NX保護 checksec pwn 使用IDA進行逆向分析代碼&#xff0c;查看漏洞觸發點&#xff1a; 在main函數中&#xff0c;有一個ctfshow函數&#xff0c;這里…

SQL173 店鋪901國慶期間的7日動銷率和滯銷率

SQL173 店鋪901國慶期間的7日動銷率和滯銷率 SQL題解&#xff1a;店鋪動銷率與滯銷率計算 關鍵&#xff1a;只要當天任一店鋪有任何商品的銷量就輸出該天的結果&#xff0c;即使店鋪901當天的動銷率為0。 潛臺詞&#xff1a;?輸出邏輯與店鋪901的銷售情況無關&#xff0c;只取…

PytorchLightning最佳實踐基礎篇

PyTorch Lightning&#xff08;簡稱 PL&#xff09;是一個建立在 PyTorch 之上的高層框架&#xff0c;核心目標是剝離工程代碼與研究邏輯&#xff0c;讓研究者專注于模型設計和實驗思路&#xff0c;而非訓練循環、分布式配置、日志管理等重復性工程工作。本文從基礎到進階&…

Apache Flink 實時流處理性能優化實踐指南

Apache Flink 實時流處理性能優化實踐指南 隨著大數據和實時計算需求不斷增長&#xff0c;Apache Flink 已經成為主流的流處理引擎。然而&#xff0c;在生產環境中&#xff0c;高并發、大吞吐量和低延遲的業務場景對 Flink 作業的性能提出了更高要求。本文將從原理層面深入解析…

ubuntu上將TempMonitor加入開機自動運行的方法

1.新建一個TempMonitor.sh文件&#xff0c;內容如下&#xff1a;#!/bin/bashcd /fjrobot/ ./TempMonitor &2.執行以下命令chmod x TempMonitor chmod x TempMonitor.sh rm -rf /etc/rc2.d/S56TempMonitor rm -rf /etc/init.d/TempMonitor cp /fjrobot/TempMonitor.sh /etc/…

速賣通自養號測評技術解析:IP、瀏覽器與風控規避的實戰方案

一、速賣通的“春天”來了&#xff0c;賣家如何抓住機會&#xff1f;2025年的夏天&#xff0c;速賣通的風頭正勁。從沙特市場躍升為第二大電商平臺&#xff0c;到8月大促返傭力度升級&#xff0c;平臺對優質商家的扶持政策越來越清晰。但與此同時&#xff0c;競爭也愈發激烈——…

adb: CreateProcessW failed: 系統找不到指定的文件

具體錯誤 adb devices * daemon not running; starting now at tcp:5037 adb: CreateProcessW failed: 系統找不到指定的文件。 (2) * failed to start daemon adb.exe: failed to check server version: cannot connect to daemon 下載最新的platform-tools-windows 下載最新…

Centos安裝HAProxy搭建Mysql高可用集群負載均衡

接上文MYSQL高可用集群搭建–docker https://blog.csdn.net/weixin_43914685/article/details/149647589?spm1001.2014.3001.5501 連接到你搭建的 Percona XtraDB Cluster (PXC) 數據庫集群&#xff0c;實現高可用性和負載均衡&#xff0c;建議使用一個中間件來管理這些連接。…

Sql server開掛的OPENJSON

以前一直用sql server2008&#xff0c;自從升級成sql server2019后&#xff0c;用OPENJSON的感覺像開掛&#xff0c;想想以前表作為參數傳輸時的痛苦&#xff0c;不堪回首。一》不堪回首 為了執行效率&#xff0c;很多時候希望將表作為參數傳給數據庫的存儲過程。存儲過程支持自…

【數據結構】隊列和棧練習

1.用隊列實現棧 225. 用隊列實現棧 - 力扣&#xff08;LeetCode&#xff09; typedef int QDatatype; typedef struct QueueNode {struct QueueNode *next;QDatatype data; }QNode;typedef struct Queue {QNode* head;QNode* tail;QDatatype size; }Que;typedef struct {Que…

LabVIEW二維碼實時識別

?LabVIEW通過機器視覺技術&#xff0c;集成適配硬件構建二維碼實時識別系統。通過圖像采集、預處理、定位及識別全流程自動化&#xff0c;解決復雜環境下二維碼識別效率低、準確率不足問題&#xff0c;滿足工業產線追溯、物流分揀等實時識別需求。應用場景適用于工業產線追溯&…

微服務-springcloud-springboot-Skywalking詳解(下載安裝)

一、SkyWalking核心介紹 1. 什么是SkyWalking&#xff1f; Apache SkyWalking是一款國人主導開發的開源APM&#xff08;應用性能管理&#xff09;系統&#xff0c;2015年由吳晟創建&#xff0c;2017年進入Apache孵化器&#xff0c;2019年畢業成為Apache頂級項目。它通過分布式…

Elasticsearch 字段值過長導致索引報錯問題排查與解決經驗總結

在最近使用 Elasticsearch 的過程中&#xff0c;我遇到了一個 字段值過長導致索引失敗 的問題。經過排查和多次嘗試&#xff0c;最終通過設置字段 "index": false 方式解決。本文將從問題現象、排查過程、問題分析、解決方案和建議等方面&#xff0c;詳細記錄這次踩坑…

使用idea 將一個git分支的部分記錄合并到git另一個分支

場景&#xff1a; 有多個版本分支&#xff0c;需要將其中一個分支的某一兩次提交合并到指定分支上 eg&#xff1a; 將v1.0.0分支中指定提交記錄 合并到 v1.0.1分支中 操作&#xff1a; 步驟一 idea切換項目分支到v1.0.1(需要合并到哪個分支就先站到哪個分支上) 步驟二 在ide…

基于深度學習的圖像分類:使用ShuffleNet實現高效分類

前言 圖像分類是計算機視覺領域中的一個基礎任務&#xff0c;其目標是將輸入的圖像分配到預定義的類別中。近年來&#xff0c;深度學習技術&#xff0c;尤其是卷積神經網絡&#xff08;CNN&#xff09;&#xff0c;在圖像分類任務中取得了顯著的進展。ShuffleNet是一種輕量級的…

OpenGL里相機的運動控制

相機的核心構造一個是glm::lookAt函數&#xff0c;一個是glm::perspective函數&#xff0c;本文相機的一切運動都在于如何構建相應的參數傳入上述兩個函數里。glm::mat4 glm::lookAt(glm::vec3 const &eye,//相機所在位置glm::vec3 const &center,//要凝視的點glm::vec…