当前位置:网站首页>Overview of AOSP ~ WiFi architecture
Overview of AOSP ~ WiFi architecture
2022-06-25 02:47:00 【Nanke is cute】
Android WiFi Architectural Overview
In this paper, Android Source project (AOSP) in WiFi Functional software architecture and each module ( Executable file 、 Dynamic link library ) The interface between .
SDK API
Android SDK For developers WiFi Programming interface (API) , It is very convenient to use .
Related packages :
android.net.wifi( Write App Just import The package , You can use WiFi Related functions )
Main related categories :
- WifiManager WIFI Programming entry ,WIFI Most of the functionality of is provided in the form of methods of this class
- WifiInfo Used to describe WIFI The state of the connection
- ScanResult Used to describe a AP, Such as SSID, Signal strength , Safety mode, etc
Overview

WifiManager It's all about managing Wifi Basic of connection API, Can pass :
android.content.Context.getSystemService(Context.WIFI_SERVICE)
Get an example of it .
Specifically IPC(Inter-Process communication)
App & system_server(WifiManager & WifiService)
if Binder Is the connection App and system_server The bridge , that WifiManager and WiFiService The two ends of the bridge .
framework Code and wifi dependent package be located :
frameworks/base/wifi/java (WIFI Some related packages )
frameworks/base/services/java ( Various Service,WIFI Related packages are :com.android.server.wifi )
frameworks In the code , and wifi The relationships of several related classes are as follows :
- WifiService Inherited from IWifiManager.Stub;
- IWifiManager.Stub And inherit from Binder, At the same time IWifiManager Interface ;
- WifiManager.Stu.proxy It has also been realized. IWifiManager Interface ;
Pictured :
among ,IWifiManager, IWifiManager.Stub, IWifiManager.Stub.Proxy All by IWifiManger.aidl Generate ;
aidl Automatically generate related java Code , Simplify the use of Binder Realization RPC The process of .
IWifiManager.Stub.Proxy,WifiManager,BinberProxy For clients (App process );
and IWifiManager.Stub,WifiService,Binder For the server (SystemServer process ).
App And system_server adopt Binder signal communication , but Binder In itself, it only realizes IPC, It's like socket The ability to communicate . and App Terminal WifiManager and system_server Terminal WifiService And Binder And so on RPC(remote procedure call).
WifiManager Only the system is app Provided interface .Context.getSystemService(Context.WIFI_SERVICE)
The actual object type returned is IWifiManager.Stub.Proxy.
IWifiManager.Stub.Proxy An example of is located at App A proxy on the end , Proxy image IWifiManager.Stub.Proxy
take WifiManager The parameters of the method are serialized to Parcel, Re menstruation Binder Send to system_server process .
system_server Internal WifiService closed App From the WifiManager call , Complete the actual work .
such , The actual work of communicating with the lower layer is done by App Transferred to system_server process (WifiService object ).
WifiStateMachine
in addition , You can see WifiStateMachine yes wifi Functional hub , The control flow of several different modes flows down through it .
When WIFI be in STA Pattern ( or P2P Pattern ) when , adopt WifiNative And wpa_supplicant Interaction .
WifiNative Several definitions Native Method :
public native static boolean setMaxTxPower(int txpower, boolean sapRunning);
public native static boolean loadDriver();
public native static boolean isDriverLoaded();
public native static boolean unloadDriver();
public native static boolean startSupplicant(boolean p2pSupported, int firstScanDelay);
/* Sends a kill signal to supplicant. To be used when we have lost connection or when the supplicant is hung */
public native static boolean killSupplicant(boolean p2pSupported);
private native boolean connectToSupplicantNative();
private native void closeSupplicantConnectionNative();
/** * Wait for the supplicant to send an event, returning the event string. * @return the event string sent by the supplicant. */
private native String waitForEventNative();
private native boolean doBooleanCommandNative(String command);
private native int doIntCommandNative(String command);
private native String doStringCommandNative(String command);
When WIFI be in AP Pattern . adopt NetworkManagementService And netd Interaction , Specifically through LocalSocket(Framework Packaged UNIX Domain socket) And netd Process of communication .
such as ,NetworkManagementService.startAccessPoint Method :
@Override
public void startAccessPoint(
WifiConfiguration wifiConfig, String wlanIface) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
wifiFirmwareReload(wlanIface, "AP");
if (wifiConfig == null) {
mConnector.execute("softap", "set", wlanIface); // towards netd Send control command
} else {
mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
wifiConfig.hiddenSSID ? "hidden" : "broadcast",
"1", getSecurityType(wifiConfig),
new SensitiveArg(wifiConfig.preSharedKey));
}
mConnector.execute("softap", "startap");
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
WifiNative
functionally ,WifiNative yes system_server and wpa_supplicant Dialogue window , In fact, it mainly depends on wpa_supplicant The dynamic library compiled by the project libwpa_client.so.
WifiNative Several native The concrete implementation of the method is in :
frameworks/base/core/jni/android_net_wifi_WifiNative.cpp
WifiNative Of native Method implementation :
static JNINativeMethod gWifiMethods[] = {
/* name, signature, funcPtr */
{
"loadDriver", "()Z", (void *)android_net_wifi_loadDriver },
{
"isDriverLoaded", "()Z", (void *)android_net_wifi_isDriverLoaded },
{
"unloadDriver", "()Z", (void *)android_net_wifi_unloadDriver },
{
"startSupplicant", "(ZI)Z", (void *)android_net_wifi_startSupplicant },
{
"killSupplicant", "(Z)Z", (void *)android_net_wifi_killSupplicant },
{
"connectToSupplicantNative", "()Z", (void *)android_net_wifi_connectToSupplicant },
{
"closeSupplicantConnectionNative", "()V", (void *)android_net_wifi_closeSupplicantConnection },
{
"waitForEventNative", "()Ljava/lang/String;", (void*)android_net_wifi_waitForEvent },
{
"doBooleanCommandNative", "(Ljava/lang/String;)Z", (void*)android_net_wifi_doBooleanCommand },
{
"doIntCommandNative", "(Ljava/lang/String;)I", (void*)android_net_wifi_doIntCommand },
{
"doStringCommandNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*) android_net_wifi_doStringCommand },
{
"setMaxTxPower", "(IZ)Z", (void *)android_net_wifi_setMaxTxPower },
};
android_net_wifi_WifiNative.cpp Not much practical work done , Most of them call directly wifi.h wifi_maxtxpower.h Defined function :
wifi.h The concrete realization of is hardware/libhardware_legacy/wifi/wifi.c ( Will be compiled as libhardware_legacy.so)
wifi_maxtxpower.h The concrete realization of is hardware/qcom/wlan/libmaxtxpower/wifi_maxtxpower.c ( Will be compiled as libmaxtxpower.so)
therefore native Code dependency libhardware_legacy.so modular 、libmaxtxpower.so modular .
WIFI HAL
Realization SystemServer And wpa_supplicant(hostapd) communication , namely Wifi HAL.
Wifi HAL Encapsulates the UNIX Domain socket,SystemServer adopt UNIX Domain socket And wpa_supplicant(hostapd)
signal communication ;SystemServer Messages sent and wpa_supplicant The response messages are all for ASCII character string .
Wifi HAL The code is mainly distributed in :
hardware/libhardware_legacy/wifi
hardware/qcom/wlan/libmaxtxpower
Are compiled as :
hardware/libhardware_legacy/wifi -> libhardware_legacy.so
hardware/qcom/wlan/libmaxtxpower -> libmaxtxpower.so
wifi.h Defined WIFI HAL The interface of , The specific functions are :
// Drive related :
int wifi_load_driver();
int wifi_unload_driver();
int is_wifi_driver_loaded();
// supplicant relevant :
int wifi_start_supplicant(int p2pSupported, int first_scan_delay);
int wifi_stop_supplicant(int p2pSupported);
int wifi_connect_to_supplicant();
void wifi_close_supplicant_connection();
// wait for WIFI What happened , This function blocks the current call , Until there is wifi Event time , Returns a representation of wifi The character of the event
int wifi_wait_for_event(char *buf, size_t len);
// towards wifi Drive sends a life ,( Most functions send commands to the lower layer through this function )
int wifi_command(const char *command, char *reply, size_t *reply_len);
// Initiate one dhcp request
int do_dhcp_request(int *ipaddr, int *gateway, int *mask,
int *dns1, int *dns2, int *server, int *lease);
// Return to one do_dhcp_request() Error string for
const char *get_dhcp_error_string();
#define WIFI_GET_FW_PATH_STA 0
#define WIFI_GET_FW_PATH_AP 1
#define WIFI_GET_FW_PATH_P2P 2
// Return an invitation firmware route
const char *wifi_get_fw_path(int fw_type);
// by wlan Drive change firmware route
int wifi_change_fw_path(const char *fwpath);
#define WIFI_ENTROPY_FILE "/data/misc/wifi/entropy.bin"
int ensure_entropy_file_exists();
wifi_maxtxpower.h Only one function is defined :
int set_max_tx_power(int power, int sap_running);
android_net_wifi_WifiNative.cpp These functions are called .
wifi.c Called wpa_ctrl.h Some functions defined , and wpa_ctrl.h The function in
stay external/wpa_supplicant_8 Project implementation , And compiled as libwpa_client.so,
See external/wpa_supplicant_8/Android.mk.
wpa_supplicant(hostapd)
Code is located :
external/wpa_supplicant_8
The project contains two interrelated open source projects wpa_supplicant and hostapd, They will generate two executables :
wpa_supplicant and hostapd, Respectively STA Patterns and AP Mode daemon .
besides , Also generated for testing wpa_cli,hostapd_cli,
as well as WIFI HAL Rely on the wpa_client.so, The details can be found in Android.mk Find .
wpa_supplicant Source code structure and hostapd similar , I'll just introduce wpa_supplicant.
wpa_ctrl.h Defines and wpa_supplicant( or hostapd) Interface for process communication :
struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path);
// Close a control interface to wpa_supplicant/hostapd
void wpa_ctrl_close(struct wpa_ctrl *ctrl);
// Send a command to wpa_supplicant/hostapd
int wpa_ctrl_request(struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len,
char *reply, size_t *reply_len,
void (*msg_cb)(char *msg, size_t len));
// Register as an event monitor for the control interface
int wpa_ctrl_attach(struct wpa_ctrl *ctrl);
// Unregister event monitor from the control interface
int wpa_ctrl_detach(struct wpa_ctrl *ctrl);
// Receive a pending control interface message
int wpa_ctrl_recv(struct wpa_ctrl *ctrl, char *reply, size_t *reply_len);
// Check whether there are pending event messages
int wpa_ctrl_pending(struct wpa_ctrl *ctrl);
// Get file descriptor used by the control interface
int wpa_ctrl_get_fd(struct wpa_ctrl *ctrl);
char * wpa_ctrl_get_remote_ifname(struct wpa_ctrl *ctrl);
wpa_ctrl_open Create a UNIX Domain socket And wpa_supplicant( or hostapd) Processes are connected ,
wpa_ctrl_close Used to turn off wpa_ctrl_open Created connection ,
wpa_ctrl_request Used to direct to wpa_supplicant/hostapd Send control command , And block ,
until wpa_supplicant/hostapd Return the command response . Control commands and responses are ASCII character string .
wpa_ctrl.h In addition to declaring these functions , Also defined wpa_supplicant/hostapd Some news from , There is no detailed list of .
Source code wpa_supplicant_global_ctrl_iface_receive
Be responsible for dispatching control commands from the upper level , And then call the specific processing function :
"ATTACH" -> wpa_supplicant_ctrl_iface_attach
"DETACH" -> wpa_supplicant_ctrl_iface_detach
else -> wpa_supplicant_global_ctrl_iface_process:
"IFNAME="(prefix) // Most control commands are in the form of IFNAME= start
-> wpas_global_ctrl_iface_ifname
-> wpa_supplicant_ctrl_iface_process # "IFNAME=" Processing of the first command
wpas_global_ctrl_iface_redir
-> wpas_global_ctrl_iface_redir_p2p
-> wpa_supplicant_ctrl_iface_process # "IFNAME=" Processing of the first command
-> wpas_global_ctrl_iface_redir_wfd
-> wpa_supplicant_ctrl_iface_process # "IFNAME=" Processing of the first command
"PING" -> "PONG"
"INTERFACE_ADD" -> wpa_supplicant_global_iface_add
"INTERFACE_REMOVE" -> wpa_supplicant_global_iface_remove
"INTERFACE_LIST" -> wpa_supplicant_global_iface_list
"INTERFACES" -> wpa_supplicant_global_iface_interfaces
"TERMINATE" -> wpa_supplicant_terminate_proc
"SUSPEND" -> wpas_notify_suspend
"RESUME" -> wpas_notify_resume
"SET" -> wpas_global_ctrl_iface_set
"SAVE_CONFIG" -> wpas_global_ctrl_iface_save_config
"STATUS" -> wpas_global_ctrl_iface_status
The function is in wpa_supplicant In the catalog
ctrl_iface_unix.c and ctrl_iface_udp.c It can be realized in , It can be accessed from the android.config Switch
(android.config By Android.mk contain , Specific see Android.mk)
wpa_supplicant Communicate with the kernel
wpa_supplicant The running model of is single process and single thread Reactor(IO multiplexing).wpa_supplicant adopt NETLINK socket Communicate with the kernel .
wpa_supplicant The project supports multiple driver programming interfaces , stay Android It uses nl80211;
nl80211 It's new 802.11netlink Interface common header , And cfg80211 Together form Wireless-Extensions alternatives .
cfg80211 yes Linux 802.11 To configure API, nl80211 Used for configuration cfg80211 equipment , It is also used for kernel to user space communication .
The actual use nl80211 when , Just include the header file in the program
wireless module(in kernel)
Code is located :
kernel/net/wireless
nl80211.c Medium nl80211_init Use genl_register_family_with_ops Registered the response application
struct genl_ops nl80211_ops[], This array defines the response NETLINK Function of message .
and nl80211_init stay cfg80211_init Inside is called ,cfg80211_init Be being subsys_initcall Registered
Subsystem initialization program , Compiled into cfg80211.ko.
nl80211_ops[] excerpts :
static struct genl_ops nl80211_ops[] = {
// ...
{
.cmd = NL80211_CMD_TRIGGER_SCAN,
.doit = nl80211_trigger_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
{
.cmd = NL80211_CMD_GET_SCAN,
.policy = nl80211_policy,
.dumpit = nl80211_dump_scan,
},
{
.cmd = NL80211_CMD_START_SCHED_SCAN,
.doit = nl80211_start_sched_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
{
.cmd = NL80211_CMD_STOP_SCHED_SCAN,
.doit = nl80211_stop_sched_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
// ...
};
wlan driver
Code is located :
vendor/qcom/opensource/wlan/prima
Module initialization (module_init), Module exit (module_exit):
CORE/HDD/src/wlan_hdd_main.c
Model :
Multithreading + queue
Create kernel thread :
CORE/VOSS/src/vos_sched.c Of vos_sched_open()
Thread task (vos_sched.c):
- VosMcThread() - The VOSS Main Controller thread
- VosWdThread() - The VOSS Watchdog thread
- VosTXThread() - The VOSS Main Tx thread
- VosRXThread() - The VOSS Main Rx thread
Thread environment (context) (vos_sched.h):
typedef struct _VosSchedContext
{
/* Place holder to the VOSS Context */
v_PVOID_t pVContext;
/* WDA Message queue on the Main thread*/
VosMqType wdaMcMq;
/* PE Message queue on the Main thread*/
VosMqType peMcMq;
/* SME Message queue on the Main thread*/
VosMqType smeMcMq;
/* TL Message queue on the Main thread */
VosMqType tlMcMq;
/* SYS Message queue on the Main thread */
VosMqType sysMcMq;
/* WDI Message queue on the Main thread*/
VosMqType wdiMcMq;
/* WDI Message queue on the Tx Thread*/
VosMqType wdiTxMq;
/* WDI Message queue on the Rx Thread*/
VosMqType wdiRxMq;
/* TL Message queue on the Tx thread */
VosMqType tlTxMq;
/* TL Message queue on the Rx thread */
VosMqType tlRxMq;
/* SYS Message queue on the Tx thread */
VosMqType sysTxMq;
VosMqType sysRxMq;
// ...
struct task_struct* McThread;
/* TX Thread handle */
struct task_struct* TxThread;
/* RX Thread handle */
struct task_struct* RxThread;
// ...
} VosSchedContext, *pVosSchedContext;
Qualcomm data :
80-Y0513-1_G_QCA_WCN36x0_Software_Architecture.pdf
chapter: Android WLAN Host Software Architecture
SCAN Process tracking
App
WifiManager wifiManager = (WifiManager)Context.getService(Contex.WIFI_SERVICE);
wifiManager.startScan();
//wifiManager --> IWifiManager.Stub.Proxy
IWifiManager.Stub.Proxy implements android.net.wifi.IWifiManager
wifiManager.startScan()
-> IWifiManager.Stub.Proxy.startScan(WorkSource=null);
-> BinderProxy.transact(Stub.TRANSACTION_startScan, _data, _reply, 0);
systerm_server
wifi –> WifiService
WifiService extends IWifiManager.Stub
IWifiManager.Stub extends android.os.Binder
implements android.net.wifi.IWifiManager
-> IWifiManager.Stub.onTransact(int code, Parcel data, Parcel reply, int flags);
case TRANSACTION_startScan:
-> WifiService.startScan(WorkSource workSource);
-> WifiStateMachine.startScan(int callingUid, WorkSource workSource);
-> StateMachine.sendMessage(CMD_START_SCAN, callingUid, 0, workSource);
case CMD_START_SCAN:
-> handleScanRequest(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, message);
-> startScanNative(type, freqs)
-> WifiNative.scan(type, freqs)
-> doBooleanCommand("SCAN ..."); // AF_UNIX socket, send to wpa_supplicant
wpa_supplicant
external/wpa_supplicant_8/wpa_supplicant$ grep -nr "\"SCAN " .
./ChangeLog:197: - "SCAN freq=<freq list>" can be used to specify which channels are
./ChangeLog:199: - "SCAN passive=1" can be used to request a passive scan (no Probe
./ChangeLog:201: - "SCAN use_id" can be used to request a scan id to be returned and
./ChangeLog:203: - "SCAN only_new=1" can be used to request the driver/cfg80211 to
./ctrl_iface.c:6986: } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
./src/drivers/driver_test.c:1289: ret = os_snprintf(pos, end - pos, "SCAN " MACSTR,
./src/drivers/driver_test.c:1994: } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
./ctrl_iface.c:6986: } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
refer to ./ctrl_iface.c:6986
} else if (os_strcmp(buf, "SCAN") == 0) {
wpas_ctrl_scan(wpa_s, NULL, reply, reply_size, &reply_len);
} else if (os_strncmp(buf, "SCAN ", 5) == 0) {
wpas_ctrl_scan(wpa_s, buf + 5, reply, reply_size, &reply_len);
wpas_ctrl_scan -> wpa_supplicant_req_scan
int res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,
NULL);
if (res == 1) {
wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d.%06d sec",
sec, usec);
}
wpa_supplicant_scan -> wpa_supplicant_trigger_scan
-> radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx)
wpas_trigger_scan_cb -> wpa_drv_scan
static inline int wpa_drv_scan(struct wpa_supplicant *wpa_s,
struct wpa_driver_scan_params *params)
{
if (wpa_s->driver->scan2) // callback
return wpa_s->driver->scan2(wpa_s->drv_priv, params);
return -1;
}
grep scan2 callback
external/wpa_supplicant_8/wpa_supplicant$ grep -nr "scan2\s*=" .
./src/drivers/driver_wext.c:2401: .scan2 = wpa_driver_wext_scan,
./src/drivers/driver_privsep.c:726: .scan2 = wpa_driver_privsep_scan,
./src/drivers/driver_test.c:2677: .scan2 = wpa_driver_test_scan,
./src/drivers/driver_bsd.c:1618: .scan2 = wpa_driver_bsd_scan,
./src/drivers/driver_nl80211.c:12612: .scan2 = driver_nl80211_scan2,
./src/drivers/driver_ndis.c:3217: wpa_driver_ndis_ops.scan2 = wpa_driver_ndis_scan;
refer to ./src/drivers/driver_nl80211.c:12612
driver_nl80211_scan2 -> wpa_driver_nl80211_scan
msg = nl80211_scan_common(drv, NL80211_CMD_TRIGGER_SCAN, params,
bss->wdev_id_set ? &bss->wdev_id : NULL);
if (!msg)
return -1;
use NL80211_CMD_TRIGGER_SCAN talk with kernel(cfg80211.ko)
kernel
grep NL80211_CMD_TRIGGER_SCAN in kernel source:
kernel$ cgrep NL80211_CMD_TRIGGER_SCAN
./net/wireless/nl80211.c:9053: .cmd = NL80211_CMD_TRIGGER_SCAN,
./net/wireless/nl80211.c:9605: NL80211_CMD_TRIGGER_SCAN) < 0) {
./include/uapi/linux/nl80211.h:255: * option to specify additional IEs in NL80211_CMD_TRIGGER_SCAN,
./include/uapi/linux/nl80211.h:260: * @NL80211_CMD_TRIGGER_SCAN: trigger a new scan with the given parameters
./include/uapi/linux/nl80211.h:759: NL80211_CMD_TRIGGER_SCAN,
./include/uapi/linux/nl80211.h:1362: * This attribute is used with %NL80211_CMD_TRIGGER_SCAN and
./include/uapi/linux/nl80211.h:3863: * of NL80211_CMD_TRIGGER_SCAN and NL80211_CMD_START_SCHED_SCAN
refer to net/wireless/nl80211.c:9053
static struct genl_ops nl80211_ops[] = {
// ... ...
{
.cmd = NL80211_CMD_TRIGGER_SCAN,
.doit = nl80211_trigger_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
// ... ...
};
nl80211_trigger_scan -> rdev_scan(rdev, request);
static inline int rdev_scan(struct cfg80211_registered_device *rdev,
struct cfg80211_scan_request *request)
{
int ret;
trace_rdev_scan(&rdev->wiphy, request);
ret = rdev->ops->scan(&rdev->wiphy, request); // callback
trace_rdev_return_int(&rdev->wiphy, ret);
return ret;
}
边栏推荐
- Kaggle 专利匹配比赛金牌方案赛后总结
- 数组-一口气冲完快慢指针
- 运行时修改Universal Render Data
- E - average and median
- 高速缓存Cache详解(西电考研向)
- It's 2022, and you still don't know what performance testing is?
- I've been doing software testing for two years. I'd like to give some advice to girls who are still hesitating
- Intranet learning notes (6)
- Go synchronization waiting group
- Resolution of cross reference in IDA
猜你喜欢

转行软件测试2年了,给还在犹豫的女生一点建议

ProcessOn制作ER过程(自定义)

数组-一口气冲完快慢指针

yarn : 无法加载文件 C:\Users\xxx\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁止运行脚本

Insurance can also be bought together? Four risks that individuals can pool enough people to buy Medical Insurance in groups

Unity archive system - file in JSON format

UnityShader入门精要——PBS基于物理的渲染

Enlightenment of using shadergraph to make edge fusion particle shader

Pytorch learning notes (VII) ------------------ vision transformer

AOSP ~ WIFI架构总览
随机推荐
Detailed explanation of cache (for the postgraduate entrance examination of XD)
记一次beego通过go get命令后找不到bee.exe的坑
AOSP ~ WIFI架构总览
Pytorch learning notes (VII) ------------------ vision transformer
Advanced usage of groovy
Kaggle 专利匹配比赛赛后总结
Random list random generation of non repeating numbers
Post competition summary of kaggle patent matching competition
How to uninstall CUDA
Software testing salary in first tier cities - are you dragging your feet
Redis
爱
Rod and Schwartz cooperated with ZhongGuanCun pan Lianyuan Institute to carry out 6G technology research and early verification
The role of software security testing, how to find a software security testing company to issue a report?
Is the compass reliable? Is it safe to open a securities account?
When they are in private, they have a sense of propriety
Talking about the advantages of flying book in development work | community essay solicitation
psql 列转行
Distributed transaction solutions and code implementation
电脑端微信用户图片DAT格式解码为图片(TK版)