通过出色的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项目,它具有许多有用的单个标头C库,可用于游戏开发的质量。
您可能还想检查我的其他单个标头C库Minicoro,它为C带来了巨大的Coroutines,对于简化游戏开发中有限的状态机非常有用。
sgp_set_uniform API的破坏变化,添加了新的预塑性混合模式。这是samples目录中所有样品的一些屏幕截图。单击任何图像以查看其实际上是在浏览器中实时渲染的。
原始样本样本:
混合模式样本:
框架缓冲区样本:
矩形样本:
效应样本:
SDF样本:
我是一名全日制开源开发人员,通过我的Github进行的任何捐赠都将受到赞赏,并可以鼓励我继续支持该和其他开源项目。我可能会接受一次性赞助,以供小型功能或与项目目标保持一致的次要增强功能,在这种情况下,请与我联系。