通過出色的Sokol GFX庫,使用現代圖形API在Pure C中的有效跨平台2D圖形畫家。
Sokol GP或簡稱SGP代表Sokol圖形畫家。
Sokol GFX是使用現代圖形卡的未修復管道渲染的絕佳庫,但是它太複雜了,無法用於簡單的2D繪圖,並且它的API過於通用,並且專門用於3D渲染。要繪製2D內容,程序員通常需要在使用Sokol GFX時設置自定義著色器,或者使用其Sokol GL Extra Library,但是Sokol GL也有一個帶有3D設計的API,這會產生一些成本和限制。
該庫的創建是為了輕鬆地通過Sokol GFX繪製2D原始圖,並且通過不考慮3D用法僅針對2D渲染進行了優化,此外,它具有自動批處理優化器,將在下面描述更多詳細信息。
當繪製庫時,創建一個尚待繪製的所有原始詞的繪製命令隊列時,每次添加新的draw命令時,批處理優化器都會返回到最近的8個最近的繪製命令(這是可調節的),並嘗試重新安排和合併繪圖命令,如果它找到符合以下標準的先前繪製命令:
通過這樣做,批處理優化器即使與其他中介機構之間的不同紋理繪製,也可以合併紋理的繪製呼叫。繪製時的效果是更效率,因為較少的撥打電話將被派往GPU,
該庫可以避免通過在運行時自動合併繪製呼叫來製作高效的2D繪圖批處理系統的大量工作,因此程序員無需手動管理批處理的繪製呼叫,也不需要對批處理的紋理繪製呼叫進行分類,該庫將在幕後無縫地進行此操作。
批處理算法很快,但是添加了每個新的draw命令的O(n) CPU複雜性,其中n是SGP_BATCH_OPTIMIZER_DEPTH配置。在實驗中,使用8作為默認值是一個很好的默認值,但是您可能需要根據情況嘗試不同的值。不建議使用太高的值,因為該算法可能需要太長時間掃描以前的繪製命令,並且可能會消耗更多的CPU資源。
可以通過將SGP_BATCH_OPTIMIZER_DEPTH設置為0來禁用批處理優化器,您可以使用它來測量其影響。
在此存儲庫的樣本目錄中,有一個基準示例,該示例在啟用浴缸優化器/禁用的情況下測試繪圖。在我的計算機上,基準能夠在啟用2.2倍因子中提高性能。在某些私人遊戲項目中,僅通過使用此庫來代替後端,批處理優化器的收益將FPS性能提高到1.5倍以上,而遊戲本身沒有內部更改。
圖書館有一些設計選擇,並在此處簡要討論。
像Sokol GFX一樣,Sokol GP將永遠不會在Draw循環中進行任何分配,因此,初始化時,必須事先配置Draw命令隊列緩衝區和頂點緩衝區的最大尺寸。
所有2D空間轉換(如sgp_rotate之類的函數)均由CPU而不是由GPU完成,這是故意避免在GPU中添加額外的開銷,因為通常2D應用程序的頂點的數量不是很大,並且更有效地使用CPU進行了更大的效率,而不是在使用額外的CPU上使用額外的gpu,而不是GPU,則可以使用額外的gpu to gpu to the GPU。 CPU <-> GPU總線。相比之下,3D應用程序通常使用頂點著色器將頂點轉換轉換為GPU,它們之所以這樣做,是因為3D對象的頂點的數量可能非常大,並且通常是最佳選擇,但對於2D渲染而言,這是不正確的。
在繪製原始繪製之前,許多可以改變2D空間的API,例如翻譯,旋轉和比例。它們的使用方式與3D圖形API中可用的使用方式相似,但是僅用於2D,例如,當使用2D時,我們不需要使用4x4或3x3矩陣來執行頂點轉換,而是代碼專門用於2D,並且可以使用2x3矩陣,可以使用2x3 matrix,節省超級CPU浮動計算。
即使繪製非紋理原始圖,所有管道都始終使用與之關聯的紋理,因為在混合紋理呼叫和非紋理調用時,將圖形管道最小化,從而提高效率。
該庫以Sokol GFX標頭的樣式進行編碼,從那裡重複使用許多宏,您可以更改其某些語義,例如自定義分配器,自定義日誌功能和其他一些詳細信息,請閱讀sokol_gfx.h文檔,以了解更多信息。
將sokol_gp.h與其他Sokol標頭一起復製到同一文件夾。設置Sokol GFX和通常的設置,然後在SG_SETUP sgp_setup(desc) sg_setup(desc) ,然後在sg_shutdown sgp_shutdown() sg_shutdown() 。請注意,通常應該檢查SGP使用sgp_is_valid()創建其在其創建後是否有效,並以錯誤優雅地退出。
在您的框架繪製功能中,添加sgp_begin(width, height)在調用任何SGP Draw函數之前,然後繪製您的原始功能。在框架的末端(或FrameBuffer),您應始終在Sokol GFX開始/結束渲染通行證之間調用sgp_flush() , sgp_flush()將向Sokol GFX派遣所有繪製命令。然後立即致電sgp_end()丟棄draw命令隊列。
此設置的實際示例將如下顯示。
以下是有關如何使用Sokol GFX和Sokol App到此庫的快速示例:
// 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 ,
};
}要運行此示例,請首先將sokol_gp.h標頭與其他Sokol標頭一起復製到同一文件夾,然後使用適當的鏈接標誌與任何C編譯器編譯(read sokol_gfx.h )。
在文件夾samples中,您可以找到以下完整的示例,涵蓋了庫的所有API:
sgp_begin() 。這些示例用作圖書館的測試套件,您可以通過鍵入make來構建它們。
在多次繪製呼叫後,命令或頂點緩衝區可能會溢出後,在這種情況下,庫將設置錯誤狀態並將繼續正常運行,但是當使用sgp_flush()刷新繪圖命令隊列時,都不會派遣drawd命令。之所以發生這種情況,是因為庫使用預分配的緩衝區,在這種情況下,可以通過增加前綴的命令隊列緩衝區和頂點緩衝區來解決問題sgp_setup()
sgp_push_transform()和sgp_pop_transform()或嵌套太多的sgp_begin()和sgp_end()和sgp_end()的sgp_push_transform()和sgp_begin()和sgp_pop_transform()和sgp_end()也可能導致錯誤,這是一個用法錯誤。
在這種情況下,您可以啟用SOKOL_DEBUG宏來調試,或在調用sgp_end()後通過讀取sgp_get_last_error()來編程錯誤處理錯誤。還建議在使用Sokol開發時離開SOKOL_DEBUG啟用Sokol_debug,因此您可以儘早發現錯誤。
該庫支持2D中使用的最常見的混合模式,這是以下內容:
SGP_BLENDMODE_NONE無混合( dstRGBA = srcRGBA )。SGP_BLENDMODE_BLEND alpha Blending( dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))和dstA = srcA + (dstA * (1-srcA)) )SGP_BLENDMODE_BLEND_PREMULTIPLIED前alpha混合( dstRGBA = srcRGBA + (dstRGBA * (1-srcA)) )SGP_BLENDMODE_ADD加法混合( dstRGB = (srcRGB * srcA) + dstRGB和dstA = dstA )SGP_BLENDMODE_ADD_PREMULTIPLIED預淘汰加性混合( dstRGB = srcRGB + dstRGB和dstA = dstA )SGP_BLENDMODE_MOD顏色調製( dstRGB = srcRGB * dstRGB和dstA = dstA )SGP_BLENDMODE_MUL顏色乘數( dstRGB = (srcRGB * dstRGB) + (dstRGB * (1-srcA))和dstA = (srcA * dstA) + (dstA * (1-srcA)) ) 您可以通過調用sgp_viewport(x, y, width, height)來更改屏幕區域以繪製。您可以使用它來調用sgp_project(left, right, top, bottom)來更改2D空間的坐標系。
您可以通過使用庫提供的轉換功能(例如sgp_translate(x, y) , sgp_rotate(theta)等)來翻譯,旋轉或縮放2D空間。
要保存和還原轉換狀態,您應該調用sgp_push_transform() ,然後調用sgp_pop_transform() 。
該庫為所有基本原始詞提供了繪圖功能,即對於點,三角形和矩形,例如sgp_draw_line()和sgp_draw_filled_rect() 。檢查備忘單或標題以獲取更多信息。所有人都有分批的變化。
要繪製紋理矩形,您可以使用sgp_set_image(0, img) ,然後使用sgp_draw_filled_rect() ,這將使整個紋理繪製到矩形中。稍後,您應該使用sgp_reset_image(0)重置圖像以將綁定的圖像還原為默認的白色映像,否則在繪製純色時,您將出現故障。
如果您想從紋理中繪製特定的來源,則應改用sgp_draw_textured_rect() 。
默認情況下,使用簡單的最近過濾器採樣器繪製紋理,您可以使用sgp_set_sampler(0, smp)更改採樣器,然後在繪製紋理之前,建議使用SGP_RESET_SAMPLER(0)使用sgp_reset_sampler(0)恢復默認採樣器。
所有常見的管道都有顏色調製,您可以通過使用sgp_set_color(r,g,b,a)設置當前狀態來調節顏色,稍後您應使用sgp_reset_color()將顏色重置為默認值(White)。
使用自定義著色器時,必須使用sgp_make_pipeline(desc) ,使用著色器,混合模式和與之相關的Draw原始內容為其創建管道。然後,您應該在著色器繪製呼叫之前調用sgp_set_pipeline() 。您負責使用相同的混合模式並繪製原始管道與創建的管道。
可以使用sgp_set_uniform(vs_data, vs_size, fs_data, fs_size)將自定義制服傳遞給著色器,在此,您應始終將指針傳遞到與頂點和碎片夾板中定義的架構完全相同的架構和大小的結構的指針。
儘管您可以手動為每個圖形後端創建自定義著色器,但建議使用Sokol Shader Compiler SHDC,因為它可以從單個.glsl文件中為多個後端生成著色器,並且通常效果很好。
默認情況下,每次繪製呼叫的庫統一緩衝區僅具有8個浮點套制( SGP_UNIFORM_CONTENT_SLOTS配置),並且可能太低,無法與自定義著色器一起使用。這是默認值,因為通常新移民可能不想使用自定義2D著色器,而增加更大的值則意味著更多的開銷。如果您使用的是自定義著色器,請增加此值以足夠大,以保持最大的著色器的製服數量。
可以在更改庫行為之前定義以下宏:
SGP_BATCH_OPTIMIZER_DEPTH批次優化器回頭看的繪製命令數。默認值為8。SGP_UNIFORM_CONTENT_SLOTS可以在每個繪製呼叫統一緩衝區中存儲的浮子的最大數量。默認值為8。SGP_TEXTURE_SLOTS每個繪製呼叫可以綁定的紋理數量的最大數量。默認值為4。 麻省理工學院,請參閱許可證文件或sokol_gp.h文件的末尾。
這是所有庫功能的快速列表,以供快速參考:
/* 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. */ 自2020年以來,該圖書館已在私人項目中進行了測試,並被證明是穩定的。
該圖書館最初是由MMORPG Game Medivia Online贊助的,我要感謝他們支持我的工作。
感謝@kkukshtel贊助具有不同顏色功能的批處理撥打電話。
請確保結帳@floooh的出色Sokol Project,它具有許多用質量製成的有用的單頭C庫,可用於遊戲開發。
您可能還想檢查我的其他單個標頭C庫Minicoro,它為C帶來了巨大的Coroutines,對於簡化遊戲開發中有限的狀態機非常有用。
sgp_set_uniform API的破壞變化,添加了新的預塑性混合模式。這是samples目錄中所有樣品的一些屏幕截圖。單擊任何圖像以查看其實際上是在瀏覽器中實時渲染的。
原始樣本樣本:
混合模式樣本:
框架緩衝區樣本:
矩形樣本:
效應樣本:
SDF樣本:
我是一名全日制開源開發人員,通過我的Github進行的任何捐贈都將受到讚賞,並可以鼓勵我繼續支持該和其他開源項目。我可能會接受一次性贊助,以供小型功能或與項目目標保持一致的次要增強功能,在這種情況下,請與我聯繫。