Minimal Efisien Platform Cross Platform 2D Painter di Pure C Menggunakan API Grafis Modern Melalui Perpustakaan Sokol GFX yang sangat baik.
Sokol GP, atau dalam SGP pendek, singkatan dari Sokol Graphics Painter.
Sokol GFX adalah perpustakaan yang sangat baik untuk rendering menggunakan pipa -pipa yang tidak diungkapkan dari kartu grafis modern, tetapi terlalu rumit untuk digunakan untuk gambar 2D sederhana, dan API terlalu umum dan khusus untuk rendering 3D. Untuk menggambar barang -barang 2D, programmer biasanya perlu mengatur shader khusus saat menggunakan Sokol GFX, atau menggunakan perpustakaan ekstra Sokol GL -nya, tetapi Sokol GL juga memiliki API dengan desain 3D dalam pikiran, yang menimbulkan beberapa biaya dan keterbatasan.
Perpustakaan ini dibuat untuk menggambar primitif 2D melalui Sokol GFX dengan mudah, dan dengan tidak mempertimbangkan penggunaan 3D itu dioptimalkan hanya untuk rendering 2D, lebih jauh fitur pengoptimal batch otomatis , lebih banyak detailnya akan dijelaskan di bawah ini.
Saat menggambar perpustakaan membuat antrian perintah draw dari semua primitif yang belum ditarik, setiap kali perintah draw baru ditambahkan, pengoptimal batch melihat kembali ke 8 perintah draw terakhir terakhir (ini dapat disesuaikan), dan mencoba mengatur ulang dan menggabungkan perintah gambar jika menemukan perintah draw sebelumnya yang memenuhi kriteria berikut:
Dengan melakukan ini, pengoptimal batch dapat misalnya untuk menggabungkan panggilan gambar bertekstur, bahkan jika mereka digambar dengan tekstur perantara lain yang berbeda menggambar di antara mereka. Efeknya lebih efisiensi saat menggambar, karena lebih sedikit panggilan penarikan akan dikirim ke GPU,
Perpustakaan ini dapat menghindari banyak pekerjaan untuk membuat sistem batching gambar 2D yang efisien, dengan secara otomatis menggabungkan panggilan undian di belakang layar saat runtime, sehingga programmer tidak perlu mengelola panggilan undian batched secara manual, atau dia perlu mengurutkan panggilan penarikan tekstur batch, perpustakaan akan melakukan ini dengan mulus di belakang layar.
Algoritma batching cepat, tetapi memiliki kompleksitas CPU O(n) untuk setiap perintah draw baru yang ditambahkan, di mana n adalah konfigurasi SGP_BATCH_OPTIMIZER_DEPTH . Dalam percobaan menggunakan 8 karena default adalah default yang baik, tetapi Anda mungkin ingin mencoba nilai yang berbeda tergantung pada kasus Anda. Menggunakan nilai -nilai yang terlalu tinggi tidak dianjurkan, karena algoritma dapat memakan waktu terlalu lama memindai perintah draw sebelumnya, dan itu dapat mengkonsumsi lebih banyak sumber daya CPU.
Pengoptimal batch dapat dinonaktifkan dengan mengatur SGP_BATCH_OPTIMIZER_DEPTH ke 0, Anda dapat menggunakannya untuk mengukur dampaknya.
Di direktori sampel repositori ini ada contoh tolok ukur yang menguji gambar dengan pengoptimal bath yang diaktifkan/dinonaktifkan. Di mesin saya, patokan itu dapat meningkatkan kinerja dalam faktor 2.2x saat diaktifkan. Dalam beberapa proyek permainan pribadi, keuntungan dari pengoptimal batch terbukti meningkatkan kinerja FPS di atas 1,5x dengan hanya mengganti backend grafis dengan perpustakaan ini, tanpa perubahan internal pada permainan itu sendiri.
Perpustakaan memiliki beberapa pilihan desain dengan kinerja dalam pikiran yang akan dibahas secara singkat di sini.
Seperti Sokol GFX, Sokol GP tidak akan pernah melakukan alokasi apa pun di loop Draw, jadi ketika menginisialisasi Anda harus mengkonfigurasi sebelumnya ukuran maksimum buffer antrian perintah draw dan buffer simpul.
Semua transformasi ruang 2D (fungsi seperti sgp_rotate ) dilakukan oleh CPU dan bukan oleh GPU, ini sengaja untuk menghindari menambahkan overhead tambahan dalam GPU, karena biasanya jumlah pita 2D yang lebih besar, dan lebih efisien untuk melakukan semua transformasi dengan CPU daripada pita yang lebih tinggi, dan lebih efisien untuk melakukan semua transformasi dengan CPU daripada pita yang lebih efisien untuk melakukan semua transformasi dengan CPU daripada pita yang lebih efisien untuk melakukan semua gpu daripada CPU daripada pita yang lebih efisien untuk melakukan semua gpu daripada cpu daripada pita. CPU <-> Bus GPU. Sebaliknya aplikasi 3D biasanya mengirimkan transformasi simpul ke GPU menggunakan vertex shader, mereka melakukan ini karena jumlah simpul objek 3D bisa sangat besar dan biasanya pilihan terbaik, tetapi ini tidak benar untuk rendering 2D.
Banyak API untuk mengubah ruang 2D sebelum menggambar primitif tersedia, seperti Translate, Rotate dan Scale. Mereka dapat digunakan sama seperti yang tersedia dalam API grafik 3D, tetapi mereka dibuat hanya untuk 2D, misalnya saat menggunakan 2D kita tidak perlu menggunakan matriks 4x4 atau 3x3 untuk melakukan transformasi verteks, alih -alih kode ini khusus untuk 2D dan dapat menggunakan matriks 2x3, menyimpan komputasi float cpu ekstra.
Semua pipa selalu menggunakan tekstur yang terkait dengannya, bahkan ketika menggambar primitif yang tidak bertekstur, karena ini meminimalkan perubahan pipa grafis saat mencampur panggilan bertekstur dan panggilan yang tidak bertekstur, meningkatkan efisiensi.
Perpustakaan dikodekan dengan gaya header Sokol GFX, menggunakan kembali banyak makro dari sana, Anda dapat mengubah beberapa semantiknya seperti alokasi khusus, fungsi log khusus, dan beberapa detail lainnya, baca dokumentasi sokol_gfx.h untuk lebih lanjut tentang itu.
Salin sokol_gp.h bersama dengan header Sokol lainnya ke folder yang sama. Siapkan Sokol GFX seperti biasanya, lalu tambahkan panggilan ke sgp_setup(desc) tepat setelah sg_setup(desc) , dan hubungi ke sgp_shutdown() tepat sebelum sg_shutdown() . Perhatikan bahwa Anda biasanya harus memeriksa apakah SGP valid setelah pembuatannya dengan sgp_is_valid() dan keluar dengan anggun dengan kesalahan jika tidak.
Dalam fungsi draw frame Anda, tambahkan sgp_begin(width, height) sebelum memanggil fungsi gambar SGP, lalu gambar primitif Anda. Di akhir bingkai (atau framebuffer) Anda harus selalu memanggil sgp_flush() antara Sokol GFX Begin/End Render Render Pass, sgp_flush() akan mengirimkan semua perintah Draw ke Sokol GFX. Kemudian hubungi sgp_end() segera untuk membuang antrian perintah draw.
Contoh sebenarnya dari pengaturan ini akan ditampilkan di bawah ini.
Berikut ini adalah contoh cepat tentang cara perpustakaan ini dengan aplikasi Sokol GFX dan Sokol:
// This is an example on how to set up and use Sokol GP to draw a filled rectangle.
// Includes Sokol GFX, Sokol GP and Sokol APP, doing all implementations.
#define SOKOL_IMPL
#include "sokol_gfx.h"
#include "sokol_gp.h"
#include "sokol_app.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#include <stdio.h> // for fprintf()
#include <stdlib.h> // for exit()
#include <math.h> // for sinf() and cosf()
// Called on every frame of the application.
static void frame ( void ) {
// Get current window size.
int width = sapp_width (), height = sapp_height ();
float ratio = width /( float ) height ;
// Begin recording draw commands for a frame buffer of size (width, height).
sgp_begin ( width , height );
// Set frame buffer drawing region to (0,0,width,height).
sgp_viewport ( 0 , 0 , width , height );
// Set drawing coordinate space to (left=-ratio, right=ratio, top=1, bottom=-1).
sgp_project ( - ratio , ratio , 1.0f , -1.0f );
// Clear the frame buffer.
sgp_set_color ( 0.1f , 0.1f , 0.1f , 1.0f );
sgp_clear ();
// Draw an animated rectangle that rotates and changes its colors.
float time = sapp_frame_count () * sapp_frame_duration ();
float r = sinf ( time ) * 0.5 + 0.5 , g = cosf ( time ) * 0.5 + 0.5 ;
sgp_set_color ( r , g , 0.3f , 1.0f );
sgp_rotate_at ( time , 0.0f , 0.0f );
sgp_draw_filled_rect ( -0.5f , -0.5f , 1.0f , 1.0f );
// Begin a render pass.
sg_pass pass = {. swapchain = sglue_swapchain ()};
sg_begin_pass ( & pass );
// Dispatch all draw commands to Sokol GFX.
sgp_flush ();
// Finish a draw command queue, clearing it.
sgp_end ();
// End render pass.
sg_end_pass ();
// Commit Sokol render.
sg_commit ();
}
// Called when the application is initializing.
static void init ( void ) {
// Initialize Sokol GFX.
sg_desc sgdesc = {
. environment = sglue_environment (),
. logger . func = slog_func
};
sg_setup ( & sgdesc );
if (! sg_isvalid ()) {
fprintf ( stderr , "Failed to create Sokol GFX context!n" );
exit ( -1 );
}
// Initialize Sokol GP, adjust the size of command buffers for your own use.
sgp_desc sgpdesc = { 0 };
sgp_setup ( & sgpdesc );
if (! sgp_is_valid ()) {
fprintf ( stderr , "Failed to create Sokol GP context: %sn" , sgp_get_error_message ( sgp_get_last_error ()));
exit ( -1 );
}
}
// Called when the application is shutting down.
static void cleanup ( void ) {
// Cleanup Sokol GP and Sokol GFX resources.
sgp_shutdown ();
sg_shutdown ();
}
// Implement application main through Sokol APP.
sapp_desc sokol_main ( int argc , char * argv []) {
( void ) argc ;
( void ) argv ;
return ( sapp_desc ){
. init_cb = init ,
. frame_cb = frame ,
. cleanup_cb = cleanup ,
. window_title = "Rectangle (Sokol GP)" ,
. logger . func = slog_func ,
};
} Untuk menjalankan contoh ini, pertama -tama salin header sokol_gp.h bersama dengan header Sokol lainnya ke folder yang sama, lalu kompilasi dengan kompiler C menggunakan bendera tautan yang tepat (baca sokol_gfx.h ).
Dalam samples folder Anda dapat menemukan contoh lengkap berikut yang mencakup semua API perpustakaan:
sgp_begin() dengan buffer bingkai. Contoh -contoh ini digunakan sebagai suite tes untuk perpustakaan, Anda dapat membangunnya dengan make .
Ada kemungkinan bahwa setelah banyak panggilan draw, command atau vertex buffer dapat meluap, dalam hal ini perpustakaan akan menetapkan keadaan kesalahan kesalahan dan akan terus beroperasi secara normal, tetapi ketika menyiram antrian perintah gambar dengan sgp_flush() tidak ada perintah draw akan dikirim. Ini dapat terjadi karena perpustakaan menggunakan buffer yang dialokasikan sebelumnya, dalam kasus seperti itu masalah dapat diperbaiki dengan meningkatkan buffer antrian perintah prefiks dan buffer simpul saat memanggil sgp_setup() .
Membuat jumlah push/pops yang tidak valid dari sgp_push_transform() dan sgp_pop_transform() , atau bersarang terlalu banyak sgp_begin() dan sgp_end() juga dapat menyebabkan kesalahan, itu adalah kesalahan penggunaan.
Anda dapat mengaktifkan makro SOKOL_DEBUG dalam kasus seperti itu untuk men -debug, atau menangani kesalahan secara terprogram dengan membaca sgp_get_last_error() setelah memanggil sgp_end() . Juga disarankan untuk meninggalkan SOKOL_DEBUG diaktifkan saat berkembang dengan Sokol, sehingga Anda dapat menangkap kesalahan lebih awal.
Perpustakaan mendukung mode campuran paling umum yang digunakan dalam 2D, yang merupakan sebagai berikut:
SGP_BLENDMODE_NONE - no blending ( dstRGBA = srcRGBA ).SGP_BLENDMODE_BLEND -alpha blending ( dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA)) dan dstA = srcA + (dstA * (1-srcA)) )SGP_BLENDMODE_BLEND_PREMULTIPLIED -pencampuran alpha pra-multiplied ( dstRGBA = srcRGBA + (dstRGBA * (1-srcA)) )SGP_BLENDMODE_ADD - aditif blending ( dstRGB = (srcRGB * srcA) + dstRGB dan dstA = dstA )SGP_BLENDMODE_ADD_PREMULTIPLIED - pencampuran aditif pra -multiplied ( dstRGB = srcRGB + dstRGB dan dstA = dstA )SGP_BLENDMODE_MOD - Modulasi warna ( dstRGB = srcRGB * dstRGB dan dstA = dstA )SGP_BLENDMODE_MUL -Color Multiply ( dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA)) dan dstA = (srcA * dstA) + (dstA * (1-srcA)) ) Anda dapat mengubah area layar untuk menggambar dengan memanggil sgp_viewport(x, y, width, height) . Anda dapat mengubah sistem koordinat ruang 2D dengan memanggil sgp_project(left, right, top, bottom) , dengan itu.
Anda dapat menerjemahkan, memutar atau mengukur ruang 2D sebelum panggilan draw, dengan menggunakan fungsi transformasi yang disediakan perpustakaan, seperti sgp_translate(x, y) , sgp_rotate(theta) , dll. Periksa lembar cheat atau header untuk lebih banyak.
Untuk menyimpan dan memulihkan keadaan transformasi, Anda harus memanggil sgp_push_transform() dan nanti sgp_pop_transform() .
Perpustakaan menyediakan fungsi menggambar untuk semua primitif dasar, yaitu, untuk poin, garis, segitiga dan persegi panjang, seperti sgp_draw_line() dan sgp_draw_filled_rect() . Periksa lembar cheat atau header untuk lebih. Semuanya memiliki variasi batch.
Untuk menggambar persegi panjang bertekstur, Anda dapat menggunakan sgp_set_image(0, img) dan kemudian sgp_draw_filled_rect() , ini akan menggambar seluruh tekstur ke dalam persegi panjang. Anda kemudian harus mengatur ulang gambar dengan sgp_reset_image(0) untuk mengembalikan gambar terikat ke gambar putih default, jika tidak Anda akan memiliki gangguan saat menggambar warna solid.
Jika Anda ingin menggambar sumber tertentu dari tekstur, Anda harus menggunakan sgp_draw_textured_rect() sebagai gantinya.
Secara default tekstur digambar menggunakan sampler filter terdekat sederhana, Anda dapat mengubah sampler dengan sgp_set_sampler(0, smp) sebelum menggambar tekstur, disarankan untuk mengembalikan sampler default menggunakan sgp_reset_sampler(0) .
Semua pipa umum memiliki modulasi warna, dan Anda dapat memodulasi warna sebelum undian dengan mengatur warna saat ini dengan sgp_set_color(r,g,b,a) , nanti Anda harus mengatur ulang warna ke default (putih) dengan sgp_reset_color() .
Saat menggunakan shader khusus, Anda harus membuat pipa untuk itu dengan sgp_make_pipeline(desc) , menggunakan shader, mode campuran dan draw primitive yang terkait dengannya. Maka Anda harus menghubungi sgp_set_pipeline() sebelum panggilan draw shader. Anda bertanggung jawab untuk menggunakan mode campuran yang sama dan menggambar primitif seperti pipa yang dibuat.
Seragam khusus dapat diteruskan ke shader dengan sgp_set_uniform(vs_data, vs_size, fs_data, fs_size) , di mana Anda harus selalu meneruskan pointer ke struct dengan skema dan ukuran yang persis sama seperti yang didefinisikan dalam verteks dan shader fragmen.
Meskipun Anda dapat membuat shader khusus untuk setiap backend grafik secara manual, disarankan harus menggunakan Sokol Shader Compiler SHDC, karena dapat menghasilkan shader untuk beberapa backend dari satu file .glsl , dan ini biasanya berfungsi dengan baik.
Secara default buffer seragam pustaka per penarikan panggilan hanya memiliki 8 seragam float (konfigurasi SGP_UNIFORM_CONTENT_SLOTS ), dan itu mungkin terlalu rendah untuk digunakan dengan shader khusus. Ini adalah default karena biasanya pendatang baru mungkin tidak ingin menggunakan shader 2D khusus, dan meningkatkan nilai yang lebih besar berarti lebih banyak overhead. Jika Anda menggunakan shader khusus, tingkatkan nilai ini menjadi cukup besar untuk menahan jumlah seragam shader terbesar Anda.
Makro berikut dapat didefinisikan sebelum termasuk mengubah perilaku perpustakaan:
SGP_BATCH_OPTIMIZER_DEPTH - Jumlah perintah draw yang dilihat oleh pengoptimal batch. Default adalah 8.SGP_UNIFORM_CONTENT_SLOTS - jumlah maksimum pelampung yang dapat disimpan di setiap buffer seragam panggilan. Default adalah 8.SGP_TEXTURE_SLOTS - Jumlah tekstur maksimum yang dapat diikat per panggilan. Default adalah 4. MIT, lihat file lisensi atau akhir dari file sokol_gp.h .
Berikut adalah daftar cepat semua fungsi perpustakaan untuk referensi cepat:
/* Initialization and de-initialization. */
void sgp_setup ( const sgp_desc * desc ); /* Initializes the SGP context, and should be called after `sg_setup`. */
void sgp_shutdown ( void ); /* Destroys the SGP context. */
bool sgp_is_valid ( void ); /* Checks if SGP context is valid, should be checked after `sgp_setup`. */
/* Error handling. */
sgp_error sgp_get_last_error ( void ); /* Returns last SGP error. */
const char * sgp_get_error_message ( sgp_error error ); /* Returns a message with SGP error description. */
/* Custom pipeline creation. */
sg_pipeline sgp_make_pipeline ( const sgp_pipeline_desc * desc ); /* Creates a custom shader pipeline to be used with SGP. */
/* Draw command queue management. */
void sgp_begin ( int width , int height ); /* Begins a new SGP draw command queue. */
void sgp_flush ( void ); /* Dispatch current Sokol GFX draw commands. */
void sgp_end ( void ); /* End current draw command queue, discarding it. */
/* 2D coordinate space projection */
void sgp_project ( float left , float right , float top , float bottom ); /* Set the coordinate space boundary in the current viewport. */
void sgp_reset_project ( void ); /* Resets the coordinate space to default (coordinate of the viewport). */
/* 2D coordinate space transformation. */
void sgp_push_transform ( void ); /* Saves current transform matrix, to be restored later with a pop. */
void sgp_pop_transform ( void ); /* Restore transform matrix to the same value of the last push. */
void sgp_reset_transform ( void ); /* Resets the transform matrix to identity (no transform). */
void sgp_translate ( float x , float y ); /* Translates the 2D coordinate space. */
void sgp_rotate ( float theta ); /* Rotates the 2D coordinate space around the origin. */
void sgp_rotate_at ( float theta , float x , float y ); /* Rotates the 2D coordinate space around a point. */
void sgp_scale ( float sx , float sy ); /* Scales the 2D coordinate space around the origin. */
void sgp_scale_at ( float sx , float sy , float x , float y ); /* Scales the 2D coordinate space around a point. */
/* State change for custom pipelines. */
void sgp_set_pipeline ( sg_pipeline pipeline ); /* Sets current draw pipeline. */
void sgp_reset_pipeline ( void ); /* Resets to the current draw pipeline to default (builtin pipelines). */
void sgp_set_uniform ( const void * vs_data , uint32_t vs_size , const void * fs_data , uint32_t fs_size ); /* Sets uniform buffer for a custom pipeline. */
void sgp_reset_uniform ( void ); /* Resets uniform buffer to default (current state color). */
/* State change functions for the common pipelines. */
void sgp_set_blend_mode ( sgp_blend_mode blend_mode ); /* Sets current blend mode. */
void sgp_reset_blend_mode ( void ); /* Resets current blend mode to default (no blending). */
void sgp_set_color ( float r , float g , float b , float a ); /* Sets current color modulation. */
void sgp_reset_color ( void ); /* Resets current color modulation to default (white). */
void sgp_set_image ( int channel , sg_image image ); /* Sets current bound image in a texture channel. */
void sgp_unset_image ( int channel ); /* Remove current bound image in a texture channel (no texture). */
void sgp_reset_image ( int channel ); /* Resets current bound image in a texture channel to default (white texture). */
void sgp_set_sampler ( int channel , sg_sampler sampler ); /* Sets current bound sampler in a texture channel. */
void sgp_unset_sampler ( int channel ); /* Remove current bound sampler in a texture channel (no sampler). */
void sgp_reset_sampler ( int channel ); /* Resets current bound sampler in a texture channel to default (nearest sampler). */
/* State change functions for all pipelines. */
void sgp_viewport ( int x , int y , int w , int h ); /* Sets the screen area to draw into. */
void sgp_reset_viewport ( void ); /* Reset viewport to default values (0, 0, width, height). */
void sgp_scissor ( int x , int y , int w , int h ); /* Set clip rectangle in the viewport. */
void sgp_reset_scissor ( void ); /* Resets clip rectangle to default (viewport bounds). */
void sgp_reset_state ( void ); /* Reset all state to default values. */
/* Drawing functions. */
void sgp_clear ( void ); /* Clears the current viewport using the current state color. */
void sgp_draw ( sg_primitive_type primitive_type , const sgp_vertex * vertices , uint32_t count ); /* Low level drawing function, capable of drawing any primitive. */
void sgp_draw_points ( const sgp_point * points , uint32_t count ); /* Draws points in a batch. */
void sgp_draw_point ( float x , float y ); /* Draws a single point. */
void sgp_draw_lines ( const sgp_line * lines , uint32_t count ); /* Draws lines in a batch. */
void sgp_draw_line ( float ax , float ay , float bx , float by ); /* Draws a single line. */
void sgp_draw_lines_strip ( const sgp_point * points , uint32_t count ); /* Draws a strip of lines. */
void sgp_draw_filled_triangles ( const sgp_triangle * triangles , uint32_t count ); /* Draws triangles in a batch. */
void sgp_draw_filled_triangle ( float ax , float ay , float bx , float by , float cx , float cy ); /* Draws a single triangle. */
void sgp_draw_filled_triangles_strip ( const sgp_point * points , uint32_t count ); /* Draws strip of triangles. */
void sgp_draw_filled_rects ( const sgp_rect * rects , uint32_t count ); /* Draws a batch of rectangles. */
void sgp_draw_filled_rect ( float x , float y , float w , float h ); /* Draws a single rectangle. */
void sgp_draw_textured_rects ( int channel , const sgp_textured_rect * rects , uint32_t count ); /* Draws a batch textured rectangle, each from a source region. */
void sgp_draw_textured_rect ( int channel , sgp_rect dest_rect , sgp_rect src_rect ); /* Draws a single textured rectangle from a source region. */
/* Querying functions. */
sgp_state * sgp_query_state ( void ); /* Returns the current draw state. */
sgp_desc sgp_query_desc ( void ); /* Returns description of the current SGP context. */ Perpustakaan ini telah diuji sejak tahun 2020 dalam proyek -proyek pribadi, dan telah terbukti stabil.
Perpustakaan ini awalnya disponsori oleh game MMORPG Medivia Online, saya ingin mengucapkan terima kasih kepada mereka karena telah mendukung pekerjaan saya.
Terima kasih @kkukshtel untuk mensponsori panggilan undian batching dengan fitur warna yang berbeda.
Pastikan untuk checkout proyek Sokol yang sangat baik oleh @flooooh, fitur banyak pustaka c header tunggal yang berguna yang dibuat dengan kualitas yang dapat digunakan untuk pengembangan game.
Anda mungkin juga ingin memeriksa minicoro header tunggal saya yang lain, ini membawa stackful coroutine untuk C, sangat berguna untuk menyederhanakan mesin negara terbatas di Game Devlopment.
sgp_set_uniform API, menambahkan mode campuran pra-multiplied baru. Berikut adalah beberapa tangkapan layar dari semua sampel dalam direktori samples . Klik salah satu gambar untuk melihatnya sebenarnya diterjemahkan secara realtime di browser Anda.
Sampel primitif:
Campuran Mode Campuran:
Sampel buffer bingkai:
Sampel persegi panjang:
Sampel efek:
Sampel SDF:
Saya seorang pengembang open source penuh waktu, jumlah donasi apa pun melalui github saya akan dihargai dan dapat memberi saya dorongan untuk terus mendukung ini dan proyek open source lainnya. Saya dapat menerima sponsor satu kali untuk fitur-fitur kecil atau peningkatan kecil yang selaras dengan tujuan proyek, dalam hal ini hubungi saya.