不管Anddoid
iOS
還是Web前端
隨著UI圖形界面的發(fā)展饰及,或多或少甚至經(jīng)常聽到硬件加速
這個術(shù)語蚓耽。當通過設(shè)置某個參數(shù),界面滑動效率變好了旋炒。我們都會說硬件加速起作用了步悠。但是終究什么是硬件加速,又加速了什么瘫镇?大多數(shù)人估計還是一知半解鼎兽,網(wǎng)上相關(guān)資料也是比較少的,所以我們今天來探討一下神秘的硬件加速
铣除。
1. 計算機成像的原理
計算機和顯示器之間通過特定驅(qū)動協(xié)議通信谚咬,程序需要做的事情只是繪制出需要顯示的位圖
(一般由RGBA三個8位次像素組成二維數(shù)組,格式可以通過協(xié)議互相協(xié)商尚粘。具體看硬件支持什么格式择卦,比如Alpha通道對屏幕顯示就沒什么用),然后通過系統(tǒng)驅(qū)動接口把二進制數(shù)據(jù)發(fā)送給顯示器(比如linux的 /dev/fb0 設(shè)備符號郎嫁,directFB等等)秉继,由顯示器的硬件把顏色顯示到屏幕上去。
所以程序要做的事情就是快速生成當前需要上屏的
位圖
泽铛。那么可能有人要問了 "我玩的3D游戲感覺好像不是這樣的吧尚辑?",實際上現(xiàn)有的3D渲染方式第一步就是把3D位置數(shù)據(jù)轉(zhuǎn)化成2D平面數(shù)據(jù)盔腔。
2. 位圖生成的原理
問題的關(guān)鍵 在于程序如何生成這個位圖
杠茬,主流的生成算法主要有2種:
?1. 基于線性掃描算法(目前基本上和用戶直接有交互的UI系統(tǒng),全是基于這個算法)
?2. 光線跟蹤算法(主要用來渲染真實世界弛随,比如大家看的變形金剛電影就是采用此類算法渲染的瓢喉,渲染單幀的時間可能非常非常長)
這里主要討論線性掃描算法,光線跟蹤算法以后有機會慢慢討論舀透。
比如程序需要繪制上面這樣的三角形
栓票,算法很簡單:
void draw(rgba** buffer, int width, int height) {
for (int i = 0; i < width; ++i) {
for (int j = 0; j < height; ++j) {
if (is_position_in_path_region(i, j)) {
buffer[i][j] = rgba(0, 0, 0, 1);
}
}
}
}
當然這是一個簡單的繪制,那么如果我們要繪制一個半透明的三角形(三角形背后已經(jīng)有一個紅色的圓)應(yīng)該咋辦盐杂?
rgba draw_color = rgba(0, 0, 0, 0.5);
void draw(rgba** buffer, int width, int height) {
float alpha = draw_color.a;
for (int i = 0; i < width; ++i) {
for (int j = 0; j < height; ++j) {
if (is_position_in_path_region(i, j)) {
rgba back_color = get_current_back_color(i, j);
rgba blend_color;
blend_color.r = (1 - alpha) * draw_color.r + alpha * back_color.r;
blend_color.g = (1 - alpha) * draw_color.g + alpha * back_color.g;
blend_color.b = (1 - alpha) * draw_color.b + alpha * back_color.b;
buffer[i][j] = blend_color;
}
}
}
}
上面的算法解釋了AlphaBlend(通過Alpha通道做顏色融合)逗载,上下這2段代碼表達了簡單的像素染色
的算法哆窿。由于此類算法通常都是類似這樣的一個循環(huán),這個過程類似于掃描儀的掃描過程厉斟,所以又叫線掃描像素填充
挚躯。由于位圖主要提供給光柵顯示器顯示使用,所以位圖又叫光柵圖
擦秽,那么生成光柵圖
的算法過程又叫光柵化過程
码荔。綜上所述整個過程又叫做線掃描光柵化
。
Tips: 上面的算法主要是把簡單的圖形光柵化感挥,那么實際情況存在的形狀非常之多缩搅。那么如何能表達那么多復(fù)制的圖形(比如螺旋圓環(huán))。工業(yè)上存在眾多曲線擬合算法触幼,用來擬合各種復(fù)雜曲線硼瓣。其中最好用的數(shù)學(xué)曲線叫做
貝塞爾曲線
(貝塞爾曲線并不是貝塞爾發(fā)明的,只是貝塞爾
首次在論文里面提出使用這個曲線來擬合工業(yè)圖形)置谦。所以常規(guī)我們都喜歡使用貝塞爾曲線
來描述圖形盛撑,描述一個圖形只要存儲該圖形的貝塞爾參數(shù)
就可以了,并且曲線是由通過公式計算出來的巩螃,所以可以無限放縮(所以圖形又叫矢量圖形蜕青,矢量是2D的靈魂钧敞,貝塞爾曲線
又是矢量的靈魂)。
</br>
3. 位圖生成算法性能問題
通過上面的矢量掃描填充算法谅阿,我們很容易的發(fā)現(xiàn)了有2個可能會出現(xiàn)的性能問題:
?1. 大量的循環(huán)處理半哟,每個涉及的像素都要處理一遍(按照現(xiàn)在手機的分辨率,算算一幀有多少要處理的)
?2. 顏色融合其實就是浮點數(shù)插值算法签餐,對每個次像素都要做寓涨,運算量巨大
那么如何解決這2個問題?
?1. 對于像素太多的問題贱田,最好的辦法就是臟區(qū)域渲染
缅茉。用人類話來說就是每次渲染的時候,最大化的在上一幀的基礎(chǔ)上面進行男摧,對本次這幀沒有變化的像素來說直接忽略處理。
?2. 對于浮點數(shù)運算太多译打,可以通過多媒體指令來加速耗拓。比如:ARM Neon
Intel MMX SSE
等等。以ARM為例,ARM有 16個通用計算寄存器奏司,16個Neon指令寄存器乔询,16個VFP高精度浮點數(shù)運算加速器。其中Neon指令可以讓程序在一個指令周期里面計算8個浮點數(shù)的運算韵洋,理想的情況下相當于比傳統(tǒng)的浮點運算性能提升了8倍竿刁。當然這些指令主要就是用來處理多媒體的黄锤,所以又叫多媒體指令
也叫 SIMD
指令(Single Instruction Multiple Data,單指令多數(shù)據(jù)流食拜,Android的底層繪圖庫就有基于Neon的優(yōu)化器鸵熟,我也嘗試過用SSE指令優(yōu)化Windows平臺API AlphaBlend函數(shù))。
那么這些都被叫做傳統(tǒng)方法负甸,那么硬件加速具體用的是什么方式來加速這個過程的流强?
4. 圖形硬件和硬件的渲染方式
傳統(tǒng)的圖形硬件主要指的是GPU,渲染接口主要有5種:
title | 常規(guī) |
---|---|
Opengl | 基本在所有平臺都能用呻待,基于狀態(tài)機的接口設(shè)計也是醉了 |
Metal | 水果公司獨有的打月,其他平臺別指望了 |
Vulkan | 新版的圖形接口,未來可能取代Opengl |
DirectX | Windows平臺特有的圖形接口 |
OpenVG | 專注于矢量加速的硬件接口蚕捉,基本上沒啥人用 |
就拿Android
來說吧奏篙,作為操作系統(tǒng)需要兼容各種硬件。硬件接口比較通用的就是OpenGL(其實早些年SGL在定義硬件的標準的時候迫淹,2D和3D是分開定義报破,2D用OpenVG加速,3D用OpenGL加速千绪。只是后面OpenVG沒有成為事實標準)充易,Windows是基于DX3D接口實現(xiàn)的,但是原理也是類似荸型。
那么硬件究竟是如何利用Opengl接口操作硬件來加速光柵化過程盹靴?這個要看看Opengl定義的規(guī)范了。
5. Opengl的渲染加速的原理
首先Opengl
只能渲染 點
直線
三角形
(之所以硬件指定是三角形瑞妇。 主要是因為光柵化也就是插值的過程, 當多邊形是凸多邊形的時候最容易處理稿静,硬件也只支持凸多邊形的插值。 三角形在三維變換后任然是凸多邊形
, 而四邊形或者更高邊數(shù)的形狀在變換后可能會出現(xiàn)凹多邊形
辕狰。其次其他形狀都可以細分成三角形改备。)。
當程序需要繪制一個多邊形的時候蔓倍,首先需要將這個多邊形剖分成多個三角形
悬钳,這個過程被稱之為三角剖分
(剖分算法分2大類,幾何剖分
和點云剖分
)偶翅。被剖分出來的三角形默勾,提交給顯卡。顯卡對每個三角形的掃描填充和像素融合處理是像素間無關(guān)的所以這類運算完全可以并行處理聚谁。GPU的并行處理每個三角形和三角形的像素染色母剥,每個渲染流水稱之為渲染管線
。CPU在處理的過程中每次最多操作一個像素,而顯卡每次可能有幾千上萬甚至十萬的管線并行計算环疼,其次顯然的浮點數(shù)運算性能比CPU高非常多习霹。這個就是顯卡為什么能夠加速這個過程。
Android的HWUI的源碼中摘抄了一段:
Code Path: android / platform / frameworks / base / master / . / libs / hwui / PathTessellator.h
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HWUI_PATH_TESSELLATOR_H
#define ANDROID_HWUI_PATH_TESSELLATOR_H
#include "Matrix.h"
#include "Rect.h"
#include "Vertex.h"
#include "VertexBuffer.h"
#include <algorithm>
#include <vector>
class SkPath;
class SkPaint;
namespace android {
namespace uirenderer {
/**
* Structure used for threshold values in outline path tessellation.
*
* TODO: PaintInfo should store one of this object, and initialized all values in constructor
* depending on its type (point, line or path).
*/
struct PathApproximationInfo {
PathApproximationInfo(float invScaleX, float invScaleY, float pixelThreshold)
: thresholdSquared(pixelThreshold * pixelThreshold)
, sqrInvScaleX(invScaleX * invScaleX)
, sqrInvScaleY(invScaleY * invScaleY)
, thresholdForConicQuads(pixelThreshold * std::min(invScaleX, invScaleY) / 2.0f) {
};
const float thresholdSquared;
const float sqrInvScaleX;
const float sqrInvScaleY;
const float thresholdForConicQuads;
};
class PathTessellator {
public:
/**
* Populates scaleX and scaleY with the 'tessellation scale' of the transform - the effective X
* and Y scales that tessellation will take into account when generating the 1.0 pixel thick
* ramp.
*
* Two instances of the same shape (size, paint, etc.) will only generate the same vertices if
* their tessellation scales are equal.
*/
static void extractTessellationScales(const Matrix4& transform, float* scaleX, float* scaleY);
/**
* Populates a VertexBuffer with a tessellated approximation of the input convex path, as a single
* triangle strip. Note: joins are not currently supported.
*
* @param path The path to be approximated
* @param paint The paint the path will be drawn with, indicating AA, painting style
* (stroke vs fill), stroke width, stroke cap & join style, etc.
* @param transform The transform the path is to be drawn with, used to drive stretch-aware path
* vertex approximation, and correct AA ramp offsetting.
* @param vertexBuffer The output buffer
*/
static void tessellatePath(const SkPath& path, const SkPaint* paint,
const mat4& transform, VertexBuffer& vertexBuffer);
......
這段代碼就是HWUI
中對路徑做的的三角剖分處理炫隶。
Opengl繪制幾何圖形存在的性能瓶頸 主要有以下3點:
?1. 三角剖分性能淋叶,如果我們需要繪制一個復(fù)雜的多邊形,那么首先需要把這個多邊形剖分成一個個三角形等限。這個剖發(fā)需要耗時
?2. 由于早期的GPU是作為外設(shè)鏈接到CPU上面的爸吮,所以GPU的RAM是和系統(tǒng)的內(nèi)存
是分開的。所以數(shù)據(jù)需要通過系統(tǒng)BUS
發(fā)送給顯卡望门,這過程也占據(jù)了大量的運行時間形娇。
?3. 每次提交一次渲染就做一次DrawCal
,渲染的開始是管線重啟
筹误。由于Opengl的API缺陷桐早,DrawCall
非常貴重,DrawCall
甚至一度被用來衡量軟件渲染的性能好壞厨剪。
Opengl繪制的優(yōu)勢 主要有以下3點:
?1. 硬件插值器實現(xiàn)的光柵化
算法哄酝,性能飛快
?2. GPU天生的浮點數(shù)運算能力,在前后景圖Blend
過程中可以飛快
?3. 硬件天生的并行
運算特性
硬件就一定能加速么祷膳?
如果只是一個很簡單的圖形陶衅,那么CPU直接渲染的速度回更快。也就是:(CPU顏色填充時間 < 三角剖分的時間+數(shù)據(jù)通信的時間+GPU光柵的時間)直晨。比如Android的圖形基礎(chǔ)庫Skia
就有基于Opengl的加速的優(yōu)化模塊搀军,Google給出了基于硬件加速后的API性能和CPU運算下的API的性能比較,你會發(fā)現(xiàn)并不是所有的繪制接口都有速度提升勇皇,甚至有部分API速度慢了十倍
以上罩句。
以上就是硬件加速在傳統(tǒng)圖形界面中的位置,就目前來看硬件的確是加速了圖形的渲染敛摘,但是是不是所有場景都能加速门烂?硬件加速是不是圖形渲染的萬金油?還是要理解其中的原理兄淫,方能善用屯远。
版權(quán)所有,如有轉(zhuǎn)載請聯(lián)系我本人http://www.breakerror.com/archives/63-i.html