Bazen yalnızca C veya C++ kodu olarak kullanılabilen bir kitaplığı kullanmak isteyebilirsiniz. Genelde bu aşamada vazgeçersiniz. Ama artık değil çünkü Emscripten ve WebAssembly (ya da Wasm)!
Araç zinciri
Kendimi, mevcut C kodunu derleyerek kendimi Wasm. LLVM'nin Wasm arka ucunda biraz gürültü olduğu için Bu konuyu araştırmaya başladım. Bu sırada derlemesi için basit programlar edinebilirsiniz Bu şekilde, ikinci olarak C'nin standart kitaplığını kullanmak veya dosyası yüklerseniz muhtemelen sorunlarla karşılaşırsınız. Bu şekilde Google’ın aldığım ders:
Emscripten, eskiden C-to-asm.js derleyicisi olarak eskiden olsa da zaman içinde Wasm'i hedefliyor ve geçiş sürecinde dahili olarak resmi LLVM arka ucuna gider. Emscripten ayrıca C'nin standart kitaplığının Wasm ile uyumlu uygulaması. Emscripten'i kullanın. Google birçok gizli çalışma barındırır bir dosya sistemi emüle eder, bellek yönetimi sağlar, OpenGL'i WebGL ile sarmalar. hiçbir şeyi kendiniz geliştirirken yaşamazsınız.
Şişkinlikten endişelenmeniz gerekiyor gibi görünse de. Kesinlikle endişeliyim. — Emscripten derleyicisi, gerekli olmayan her şeyi kaldırır. Şu kılavuzda: elde edilen Wasm modüllerini mantığa uygun şekilde emin olmak için çalışıyor. Emscripten ve WebAssembly ekipleri, daha küçük hale gelecektir.
Emscripten'i, web sitesini ziyaret ederek veya Homebrew kullanarak Şu sitenin hayranıysanız: benim gibi yuvaya yerleştirilmiş komutlar var ve sisteminize bir şey yüklemek istemiyorum. bir WebAssembly deneyimi yaşamak için Kullanabileceğiniz Docker görüntüsü aşağıdaki adımları uygulayabilirsiniz:
$ docker pull trzeci/emscripten
$ docker run --rm -v $(pwd):/src trzeci/emscripten emcc <emcc options here>
Basit bir şeyler derleme
C ile yazılacak fonksiyona ilişkin neredeyse standart bir örneği ele alalım: n'inci fibonacci sayısını hesaplar:
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
int fib(int n) {
if(n <= 0){
return 0;
}
int i, t, a = 0, b = 1;
for (i = 1; i < n; i++) {
t = a + b;
a = b;
b = t;
}
return b;
}
C'yi biliyorsanız, fonksiyonun kendisi çok da şaşırtıcı olmamalıdır. Şu an C'yi bilemedim ama JavaScript'i biliyorum. Umarım bu becerileri, Bunun nasıl bir şey olduğunu anlamalısınız.
emscripten.h
, Emscripten tarafından sağlanan bir başlık dosyasıdır. Bu bilgilere ihtiyaç duyduğumuzda
EMSCRIPTEN_KEEPALIVE
makrosuna erişimi vardır, ancak
çok daha fazla işlev sunar.
Bu makro, derleyiciye, bir işlevi görünse bile kaldırmamasını söyler
kullanılmıyor. Bu makroyu atlarsak derleyici, işlevi optimize ederek
Sonuçta kimse kullanmıyor.
Tüm bunları fib.c
adlı bir dosyaya kaydedelim. Bunu bir .wasm
dosyasına dönüştürmek için
Emscripten'in derleyici komutu emcc
olana dönmem gerekiyor:
$ emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' fib.c
Şimdi bu komutu inceleyelim. emcc
, Emscripten'ın derleyicisidir. fib.c
, C'mizdir
dosyası olarak kaydedebilirsiniz. Şu ana kadar her şey yolunda. -s WASM=1
, Emscripten'den bize bir Wasm dosyası vermesini söylüyor
yerine asm.js dosyası kullanmayın.
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
, derleyiciye
JavaScript dosyasında cwrap()
işlevi mevcut. Bu işlev hakkında daha fazla bilgi
daha sonra. -O3
, derleyiciye agresif bir şekilde optimizasyon yapmasını söyler. Daha düşük bir tutar seçebilirsiniz
sayısını artırır, ancak bu da sonuçta elde edilen paketlerin
daha büyük olduğundan, derleyici kullanılmayan kodu kaldırmayabilir.
Komutu çalıştırdıktan sonra elinizde
a.out.js
ve a.out.wasm
adlı bir WebAssembly dosyası içerir. Wasm dosyası (veya
"modül"), derlenmiş C kodumuzu içerir ve oldukça küçük olmalıdır. İlgili içeriği oluşturmak için kullanılan
JavaScript dosyası, Wasm modülümüzün yüklenmesi ve ilk kullanıma hazırlanmasıyla
ve daha iyi bir API sağlamak. Gerekirse,
genellikle reklam öğesi tarafından sağlanması beklenen diğer işlevlerin
işletim sistemi olarak nitelendirilirler. Dolayısıyla, JavaScript dosyası biraz
daha büyüktür, 19 KB'lık (~5 KB gzip ile sıkıştırılmış) olmalıdır.
Basit bir şey çalıştırma
Modülünüzü yükleyip çalıştırmanın en kolay yolu, oluşturulan JavaScript'i kullanmaktır
dosyası olarak kaydedebilirsiniz. Bu dosyayı yüklediğinizde
Module
genel
arasında yer alır. Tekliflerinizi otomatikleştirmek ve optimize etmek için
cwrap
JavaScript yerel işlevi oluşturmak için
sarmalanmış işlevi çağırmaktır. cwrap
İşlev adı, dönüş türü ve bağımsız değişken türleri aşağıdaki sırayla gösterilmiştir:
<script src="a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
const fib = Module.cwrap('fib', 'number', ['number']);
console.log(fib(12));
};
</script>
Şu durumda: bu kodu çalıştırın "144"ü görmeniz gerekir. bu, 12. Fibonacci numarasıdır.
Kutsal kapı: C kitaplığı derleme
Şimdiye kadar, yazdığımız C kodu Wasm düşünülerek yazılmıştı. Çekirdek kullanım alanı ise mevcut C ekosistemini alıp geliştiricilerin bunları web'de kullanmasını sağlar. Bu kütüphaneler genellikle C'nin standart kitaplığını, işletim sistemini, dosya sistemini ve sahip olmalıyız. Emscripten, bu özelliklerin çoğunu sağlar ancak bazı özellikler sınırlamalara dikkat edin.
Asıl hedefime geri dönelim: WebP'den Wasm'a yönelik bir kodlayıcı derleme. İlgili içeriği oluşturmak için kullanılan WebP codec'inin kaynağı C dilinde yazılmıştır ve GitHub'ın yanı sıra API belgeleri. Bu oldukça iyi bir başlangıç noktasıdır.
$ git clone https://github.com/webmproject/libwebp
Basit bir başlangıç için, size WebPGetEncoderVersion()
verilerini göstermeyi deneyelim
webp.c
adlı bir C dosyası yazarak JavaScript'e encode.h
:
#include "emscripten.h"
#include "src/webp/encode.h"
EMSCRIPTEN_KEEPALIVE
int version() {
return WebPGetEncoderVersion();
}
Bu, libwebp'nin kaynak kodunu alıp alamayacağımızı test etmek için basit bir programdır için hiçbir parametre veya karmaşık veri yapısına ihtiyaç duymadığımızdan, işlevi çağırır.
Bu programı derlemek için, derleyiciye programı nerede bulacağını söylememiz gerekir
libwebp'nin başlık dosyalarını yönetmek için -I
işaretini kullanır ve ayrıca
libwebp'yi inceleyeceğiz. Dürüst olmak gerekirse tüm C
bulduğum her şeyi ayırt etmek için derleyiciye başvurdum.
gerekmez. Mükemmel bir iş çıkarıyordu.
$ emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' \
-I libwebp \
webp.c \
libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c
Şimdi şık, yeni modülümüzü yüklemek için yalnızca bazı HTML ve JavaScript'lere ihtiyacımız var:
<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = async (_) => {
const api = {
version: Module.cwrap('version', 'number', []),
};
console.log(api.version());
};
</script>
Düzeltme sürüm numarasını çıkış:
JavaScript'ten Wasm'a resim alma
Kodlayıcının sürüm numarasını almak harikadır ama gerçek bir kodlayıcı olur, değil mi? Haydi başlayalım.
Cevaplamamız gereken ilk soru şudur: Resmi Wasm arazisine nasıl katarız?
Her bir
encoding API of libwebp varsa
RGB, RGBA, BGR veya BGRA'daki bir bayt dizisi. Neyse ki Canvas API'sinin
getImageData()
Bu da bize
Uint8ClampedArray
resim verilerini içeren RGBA:
async function loadImage(src) {
// Load image
const imgBlob = await fetch(src).then((resp) => resp.blob());
const img = await createImageBitmap(imgBlob);
// Make canvas same size as image
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
// Draw image onto canvas
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, img.width, img.height);
}
Artık "yalnızca" verilerin JavaScript bölgesinden Wasm'a kopyalanması arazi. Bunun için iki ek işlevden yararlanmamız gerekiyor. Ayrılan ve tekrar boşaltan bir başka resim belleği göreceksiniz:
EMSCRIPTEN_KEEPALIVE
uint8_t* create_buffer(int width, int height) {
return malloc(width * height * 4 * sizeof(uint8_t));
}
EMSCRIPTEN_KEEPALIVE
void destroy_buffer(uint8_t* p) {
free(p);
}
create_buffer
, RGBA resmi için bir arabellek (yani piksel başına 4 bayt) ayırır.
malloc()
tarafından döndürülen işaretçi, şunun ilk bellek hücresinin adresidir:
zaman alabilir. İşaretçi JavaScript alanına döndürüldüğünde, şu şekilde kabul edilir:
yeterlidir. cwrap
kullanarak işlevi JavaScript'e gösterdikten sonra,
bu sayıyı arabelleğimizin başlangıcını bulmak ve resim verilerini kopyalamak için kullanırız.
const api = {
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
};
const image = await loadImage('/image.jpg');
const p = api.create_buffer(image.width, image.height);
Module.HEAP8.set(image.data, p);
// ... call encoder ...
api.destroy_buffer(p);
Grand Finale: Resmi kodlayın
Resim artık Wasm arazisinde kullanılabilir. Şimdi, WebP kodlayıcıyı çağırarak
işini yapıyor! Her bir
WebP dokümanları, WebPEncodeRGBA
mükemmel bir seçim gibi görünüyor. Fonksiyon, bir işaretçiyi giriş resmine götürür ve
ve 0 ile 100 arasında bir kalite seçeneğine yer vermelidir. Aynı zamanda
bir çıkış tamponu oluşturduk. Bu çabayı tamamladığımızda WebPFree()
kullanarak
WebP görüntüsüyle yapılıyor.
Kodlama işleminin sonucu, çıktı arabelleği ve uzunluğu olur. Çünkü C işlevindeki işlevler dönüş türü olarak dizi içeremez (bellek ayırmadığı sürece dinamik olarak) statik bir global diziye başvurdum. Biliyorum, temiz C değil (aslında, Bunun nedeni, Wasm işaretçilerinin 32 bit genişliğinde olması gerekir.), ancak verileri koruyarak basit sanırım bu adil bir kısayol.
int result[2];
EMSCRIPTEN_KEEPALIVE
void encode(uint8_t* img_in, int width, int height, float quality) {
uint8_t* img_out;
size_t size;
size = WebPEncodeRGBA(img_in, width, height, width * 4, quality, &img_out);
result[0] = (int)img_out;
result[1] = size;
}
EMSCRIPTEN_KEEPALIVE
void free_result(uint8_t* result) {
WebPFree(result);
}
EMSCRIPTEN_KEEPALIVE
int get_result_pointer() {
return result[0];
}
EMSCRIPTEN_KEEPALIVE
int get_result_size() {
return result[1];
}
Tüm bunları yaptıktan sonra, kodlama işlevini çağırabilir, işaretçi ve resim boyutunuz, kendi JavaScript alanı tamponumıza yerleştirin ve süreçte ayırdığımız tüm Wasm-land tamponlarını serbest bırakın.
api.encode(p, image.width, image.height, 100);
const resultPointer = api.get_result_pointer();
const resultSize = api.get_result_size();
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);
const result = new Uint8Array(resultView);
api.free_result(resultPointer);
Resminizin boyutuna bağlı olarak, Wasm'in , bellek hem giriş hem de çıkış resmini barındıracak kadar büyütülemiyor:
Neyse ki bu sorunun çözümü hata mesajındadır! Yapmamız gereken
-s ALLOW_MEMORY_GROWTH=1
öğesini derleme komutumuza ekleyin.
İşte bu kadar! Bir WebP kodlayıcısı derledik ve bir JPEG görüntüsünün kodunu
WebP İşe yaradığını kanıtlamak için sonuç tamponumuzu bir blob'a dönüştürüp
bir <img>
öğesinde yapalım:
const blob = new Blob([result], { type: 'image/webp' });
const blobURL = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = blobURL;
document.body.appendChild(img);
Yeni bir WebP imajıyla ihtişamını yaşayın.
Sonuç
Tarayıcıda çalışmak için bir C kütüphanesi edinmek için parkta yürümek değil, süreci kavradığınıza göre veri akışının nasıl işlediğini daha kolay hale getirir ve sonuçlar akıllara kıvılcımlar getirebilir.
WebAssembly, web'de işleme, veri işleme ve çıtır çıtır çıtırlar ve oyun oynamak. Wasm'ın herkesin kullanımına açık olması her şeye uygulanır, ancak bu darboğazlardan birine ulaştığınızda Wasm son derece faydalı bir araç.
Bonus içerik: Basit bir şeyi zor yoldan çalıştırmak
Oluşturulan JavaScript dosyasından kaçınmayı denemek isterseniz . Fibonacci örneğine dönelim. Kendimiz yükleyip çalıştırmak için şunları yapın:
<!DOCTYPE html>
<script>
(async function () {
const imports = {
env: {
memory: new WebAssembly.Memory({ initial: 1 }),
STACKTOP: 0,
},
};
const { instance } = await WebAssembly.instantiateStreaming(
fetch('/a.out.wasm'),
imports,
);
console.log(instance.exports._fib(12));
})();
</script>
Emscripten tarafından oluşturulan WebAssembly modüllerinin çalışacak belleği yok
bu verileri aklınızdan çıkarmayın. Wasm modülünü sağlama şeklinizle
herhangi bir şey, imports
nesnesini kullanmaktır. Bu nesne,
instantiateStreaming
işlevi. Wasm modülü, Google Cloud'un içindeki
içe aktarma nesnesi bulunur, ancak bunun dışında başka hiçbir şey yoktur. Kurallara göre modüller
Emscripting tarafından derlendi, yüklenen JavaScript'ten birkaç şey bekler
ortam:
- İlki
env.memory
. Wasm modülü dışarıdan habersiz diğer bir deyişle, üzerinde çalışacak hafızaya ihtiyacı var. GirinWebAssembly.Memory
. Doğrusal belleğin (isteğe bağlı olarak büyütülebilir) bir parçasını temsil eder. Boyutlandırma parametreleri "WebAssembly sayfaları birimleri içinde" içindedir. Bu, yukarıdaki kodun 1 sayfa bellek ayırır ve her sayfa 64 boyutunda olur KiB.maximum
sağlamadan seçeneğinde, bellek büyüme açısından teorik olarak sınırsızdır (Chrome şu anda 2 GB'lık bir kesin sınır). Çoğu WebAssembly modülünün bir daha fazla bilgi edineceksiniz. env.STACKTOP
, yığının büyümeye başlaması gereken yeri tanımlar. Grup fonksiyon çağrıları yapmak ve yerel değişkenlere bellek tahsis etmek için gereklidir. Küçük oyunlarımızda dinamik bellek yönetimi mağduriyeti yapmak için belleğin tamamını bir yığın olarak kullanabiliriz.STACKTOP = 0