您的位置:首页 > 其它

mesa源码阅读笔记(7)_顶点变换流程解析

2012-08-19 10:21 591 查看
好久没有写博客,同时也放下对mesa源码的阅读很久了。这一篇会是我最后的一篇,有不尽到之处也会放下了,毕竟OpenGL不是我的强项,后面可能没有这么多时间来关注了。

这篇文章有点长,而我也不知道自己究竟说清楚了没有,放在这里,一方面是作为自己阅读的一个总结,另一方面也期望它能够对人有所助益。

 

0  数据结构的预备

1) __GLcontextRect

__GLcontextRect 也被定义为 GLcontext

Mesa rendering context,在这个数据结构里几乎包括所有的OpenGL状态

 

struct gl_transform_attrib Transform; 对应Transformation属性

 

struct gl_viewport_attrib Viewport; 对应Viewprt属性,glViewport()负责初始化它

 

struct gl_matrix_stack ModelviewMatrixStack;

struct gl_matrix_stack ProjectionMatrixStack;

struct gl_matrix_stack ColorMatrixStack;

struct gl_matrix_stack TextureMatrixStack[MAX_TEXTURE_UNITS];

struct gl_matrix_stack ProgramMatrixStack[MAX_PROGRAM_MATRICES];

struct gl_matrix_stack *CurrentStack; // 指向上面矩阵堆栈中的一个

 

GLmatrix _ModelProjectMatrix; // 联合模型视图矩阵与投影矩阵

 

2) wmesa_context  与  wmesa_framebuffer

wmesa_context // The Windows Mesa rendering context, derived from GLcontext;

它存储与Windows窗口有关的数据成员,除GLcontext之外,比如HDC, 清除颜色,清除画笔,清除画刷等等

 

wmeas_framebuffer // Windows framebuffer, derived from gl_framebuffer

除gl_framebuffer之外,窗口句柄,像素格式,颜色位数,位图,以及像素指针,下一wmesa_framebuffer*next指针等等

 

真正地像素颜色等数据会存储在这里,因而也就比较与平台相关,而GLcontext则是能够比较独立于平台的,如何做到的呢,依靠这里的数据结构了吧。

 

GLvisual 亦即 __GLcontextModesRes 看起来它是支持很多渲染模式的,比如RGB,累积缓存,深度缓存,各种GLX, ARB扩展等等,这个暂时不管它

 

dd_function_table Mesa内核使用这些函数指针以呼叫设备驱动,许多函数直接对应OpenGL状态命令,Mesa内核会在完成错误检查之后再调用它们,所以驱动不用再检查

 

顶点transformation/clipping/lighting存放入 T&L模块

Rasterization光栅化函数存放入 swrast模块

 

3)  glapi_table

gdi项目下的 mesa.def 对应 OpenGL.dll 输出的API函数列表,该列表与 glapi_table 很能对应上

_glapi_set_dispatch()  是对应的设置捆绑接应函数,比如 对上  __glapi_noop_table 可表征空操作

_mesa_init_exec_table() 是对应的初始化 glBegin()/glEnd() 之间的函数

 

mesa里实现gl*的API函数是比较独特的,通过宏替换的形式来进行。未初始化的时候,对应的都是空的函数体,在wglMakeCurrent()里才会被初始化。

 

1 前面的测试工程,调试后可发现其基本的执行流程如下:

(1) 窗口WM_CREATE之后调用 GLSetupRC(),此时也可以进行那些属于一次性的初始化GLInit()

ChoosePixelFormat()

SetPixelFormat()

wglCreateContext

wglMakeCurrent()

(2) 窗口WM_SIZE消息处调用  GLResize()

glViewport()

glMatrixMode()

glLoadIdentity()

gluPerspective()

glMatrixMode()

glLoadIdentity()

(3) 在消息循环之外调用  GLDrawScene() 绘制

glClear()

glLoadIdentity()

glTranslatef()

glBegin()

    glColor3f()

    glVertex3f()

glEnd()

SwapBuffers()

(4) 退出窗口之前销毁该销毁的

略过

 

2 GLSetupRC() 处的执行流程

函数 wglCreateContext()

1) 若当前无有效Ctx数量,则初始化所有 wgl_ctx[i].ctx 为NULL

2) 循环遍历所有 wgl_ctx[i],发现一个空的,则调用 WMesaCreateContext() 创建,若创建成功,则 ctx_count += 1,然后返回该句柄 HGLRC 形式

WMesaCreateContext()

1) 申请wmesa_context内存空间,然后初始化窗口句柄,像素位数,GLvisual(暂不理会)等

2) 调用 _mesa_init_driver_functions() 初始化驱动函数指针 dd_function_table 类型

3) 调用 _mesa_initialize_context() 初始化 GLcontext(这是一个重要类型)

4) 启用一些扩展,暂时忽略

5) 初始化 software rasterizer and helper module, 四个模块的初始化,重要!

6) 管道初始化,重要,_tnl_run_pipeline

7) 返回

 

_mesa_initialize_context()

1) 赋值初始化一些成员 GLcontext

2) share_list 的申请,(不大明白,暂时忽略处理)

3) 调用 init_attrib_groupd() 重要函数,会初始化很多东西,比如 buffer_objects, color, eval, depth, line, matrix, pixel, point, polygon, program, scissor, transform, viewport 等等

4) 申请内存空间,并初始化 ctx->Exec, ctx->Save,这个与glBegin(), glEnd() 有关

5) 再调用 _mesa_init_exec_vtxfmt() 初始化 Neutral tnl module stuff,与顶点有关的API了

6) 返回

 

wglMakeCurrent()

1) 检查若二者当中有一个为NULL,则调用 WMesaMakeCurrent(NULL, NULL))

2) 否则找到对应的句柄,必须能找到,否则失败,再调用 WMesaMakeCurrent()

 

WMesaMakeCurrent()

1) 如果hDC与hRC二者已经有关联,不用做什么退出即可

2) 寻找对应 hDC 的 WMesaFramebuffer

3) 如果hDC, hRC有效,却没有framebuffer,则进入以下步骤创建

==> 1) 获取窗口大小

==> 2) 调用 wmesa_new_framebuffer() 创建 WMesaFramebuffer

==> 3) 如果为双缓存,还要创建 back buffer

==> 4) 然后make render buffers,在wmesa_set_renderbuffer_funcs()处应该注意,它初始化了读写像素的函数指针,比如rb->PutRow(), rb->PutRowRGB(), rb->PutMonoRow(), rb->PutValues(), rb->PubMonoValues(), rb->GetRow(), rb->GetValues() 可以从后面看出,这些函数指针完成对渲染场景像素的读写

双缓存的时候,会建二个render buffers, 目前还没有精力去关注这一方面的内容

==> 5) _mesa_add_soft_renderbuffers() 添加深度缓存,累积缓存,模板

4) 上一步之后,若hRC与framebuffer有效,则传递相关参数,调用 _mesa_make_current()

5) 若以上均不符合,则判定为取消置为当前,传递NULL,调用 _mesa_make_current()

 

_mesa_make_current()

1) 若传入的为有效值(非空),则检查newCtx与drawBuffer, readBuffer之间的兼容性

2) 若可行,则置全局变量 _glapi_Context 值(该值会到处被用到),否则的话会置空

3) 还有对 _glapi_set_dispatch() (它直接对应OpenGL各API函数的) 的初始化,若有效,置各函数指针 newCtx->CurrentDispatch 指针,最后初始化各种 framebuffer size,主要是drawBuffer, readBuffer这两个

4) 后面还有一些初始化,默认也包括了 _mesa_set_viewport() _mesa_set_scissor 函数等

 

3 GLResize() 处的执行流程

注意:m_matrix.c 文件里的注释

-- # 4x4 变换矩阵以列为优先存储

-- # 点/顶点被认为是列矢量

-- # 点经矩阵的变换是 p' = M * p

glViewport()

1) 经过函数指针分派转至 _mesa_Viewport()

2) 获取当前 context

3) 确认是否在 glBegin(), glEnd() 外部使用

4) 调用 _mesa_set_viewport()

 

_mesa_set_viewport()

1) 在最大可能 MaxViewportWidth, MaxViewportHeigth 与给定值之间取最小值

2) 赋值改变 ctx->Viewport 参数,并置新状态 _NEW_VIEWPORT

3) 调用 _math_matrix_viewport() 初始化 ctx->Viewport._WindowMap

glViewport(0, 0, nWidth, nHeight)转换ctx->Viewport._WindowMap矩阵为

width/2   0             0     width/2 + x

0       height/2        0     height/2 + y

0           0    depthMax*((zFar - zNear)/2)   depthMax*((zFar - zNear)/2 + zNear)

0           0              0           1

其中(x, y, width, height)对应上面函数的参数

zNear/zFar对应ctx->Viewport.Near/Far,初始值为0.0/1.0

depthMax值等于ctx->DrawBuffer->_DepthMaxF,该值初始值为65535.0

320  0    0   320

0   240   0   240

0     0 32767.5 32767.5

0     0     0    1

以列为优先存储的方式,故矩阵 0, 1, 2, 3 4, 5, 6, 7, 8,9,10,11, 12, 13, 14, 15内部值为

{ 320, 0, 0, 0, 0, 240, 0, 0, 0, 0, 32767.5, 0 320, 240, 32767.5, 1 }

4) 若存在驱动.Viewport,则调用它,对应 wmesa_viewport() 它负责resize_buffers()

 

glMatrixMode()

1) 经过函数指针分派转至 _mesa_MatrixMode()

2) 获取当前的 context

3) 确保在Begin与End() 之外使用,这个范围跟 glViewport() 有差别,但具体怎样暂时忽略

4) 如果ctx->Transform.MatrixMode已为给定模式,并且给定模式不等于 GL_TEXTURE,退出,不需要后面的处理

5) 因为 _NEW_TRANSFORM特性,需要完成以前的绘制,如果存在的话,FLUSH_VERTICES

6) 置 ctx->CurrentStack 为对应模式的矩阵堆栈,比如投影矩阵堆栈,模型视图堆栈

7) 改变 ctx->Transform.MatrixMode 为给定模式

8) 返回

 

glLoadIdentity()

1) 经过函数指针分派转至 _mesa_LoadIdentity()

2) 获取当前的 context

3) 确保在Begin, End, Flush之外使用本函数

4) 调用 _math_matrix_set_identity() 置 ctx->CurrentStack->Top 指针为单位矩阵

5) 给 ctx->NewState 添加新状态,后面会根据这个来调用相关的初始化函数

 

gluPerspective()

这个是glu里面的函数,设置透视投影矩阵

double radians = fovy / 2 * __glPi / 180 = 0.392699081698...;

double deltaZ = zFar - zNear = 99.4;

矩阵各数据为:

cos(radians)/sin(radians)/aspect      0                   0                   0

0                                cos(radians)/sin(radians)   0                    0

0                                                 0    -(zFar+zNear)/deltaZ    -2*zNear*zFar/deltaZ

0                                                 0                  -1                    0

其中fovy = 45.0; aspect = 1.3333; zNear = 0.6; zFar = 100.0f;

1.810660171     0             0                     0

0           2.41421356         0                     0

0                      0    -1.0120724350     -1.2072435090

0                      0            -1                     0

1) 调用 __gluMakeIdentityd() 初始化 m[4][4] 为单位矩阵

2) 改变 m[0][0], m[1][1], m[2][2], m[2][3], m[3][2], m[3][3] 的值

注意从这里可以看出 m[4][4] 与 OpenGL内部矩阵的存放次序是相反的, m[2][3] 对应mtx[11]位置,而m[3][2] 对应 mtx[14]位置,这一点很需要注意

{ 1.810660171 0 0 0, 0, 2.41421356 0 0, 0 0 -1.0120724350 -1, 0 0 -1.2072435090, 0 }

3) 调用 glMultMatrixd()

 

glMultMatrixd()

1) 经过函数指针分派转至 _mesa_MultMatrixd

2) 转换数据精度为 float型,调用 _mesa_MultMatrixf()

3) _mesa_MultMatrixf() 获取当前ctx,确保参数有效以及被调用位置

4) 然后调用 _math_matrix_mul_floats() 将矩阵乘至 ctx->CurrentStack->Top()

矩阵相乘,以列为优先,故 A(row, col) = A[col*4 + row] 这一点同 UGOPEN 一致

5) 给 ctx->NewState 添加新状态

 

以上已经可以完成对 GLResize() 的分析

 

4 GLDrawScene() 处场景绘制流程

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

此函数有点复杂,会完成很多事情

1) 经函数指针分派转至 _mesa_Clear()

2) 获取当前 context

3) 确保在Begin, End, Flush() 之外被调用

4) 完成当前场景 FLUSH_CURRENT()

5) 确保 mask 参数正确

6) 如果有新状态ctx->NewState,则调用 _mesa_update_state() 更新

更新过程很多,这个函数复杂也就复杂在这里了,更新完后,ctx->NewState会置为0

7) 如果有DrawBuffer, ctx->DrawBuffer有效,继续,否则退出

8) 如果渲染模式为 GL_RENDER,ctx->RenderMode的值,继续

9) 各种缓存模式,最后调用驱动的Clear函数,对应 wmesa.c 里的clear()函数

这里也有一些复杂,不是十分清楚,它内部具体在做什么,仔细分析,里边应该有一个环节将记录像素颜色的地址指针全部初始化为背景颜色的过程

 

glLoadIdentity() 置当前矩阵堆栈Top为单位矩阵

 

glTranslatef()

1) 经过函数指针分派转至 _mesa_Translatef()

2) 获取当前 context

3) 确保在Begin, End, Flush之外调用

4) 调用 _math_matrix_translate(),它相当于一个矩阵相乘,只是平移变换矩阵只需要更新有关的那三个值而已,可以具体化为矩阵相乘效果的

m[12] = m[0] * x + m[4] * y + m[8] * z + m[12];

m[13] = m[1] * x + m[5] * y + m[9] * z + m[13];

m[14] = m[2] * x + m[6] * y + m[10] * z + m[14];

m[15] = m[3] * x + m[7] * y + m[11] * z + m[15];

从而当前模型视图矩阵变为:

1 0 0 -1.5

0 1 0 0

0 0 1 -6

0 0 0 1

在内部的存储很明显是 { 1 0 0 0, 0 1 0 0, 0 0 1 0, -1.5 0 -6 1 }

5) 变换矩阵状态,给 ctx->NewState添加新状态

 

glBegin()

    glColor3f()

    glVertex3f()

glEnd()

这里的函数会走另一个指派过程,有点复杂,难以理清头绪

小专题1) 这里相关的函数所经过的指派过程,扩而广之,应对里边几乎所有地方都有一个记录吧,还包括所有 OpenGL API 函数的指派位置

1) neutral_**前缀指派

在 vtxfmt.c 内,然后包括 vtxfmt_tmp.h头文件 定义PRE_LOOPBACK()宏,再转而调用当前 GET_DISPATCH() 对应的位置,而在上面的 PRE_LOOPBACK()里有对这个位置的设置与改变的,会进入到 tnl模块

2) vbo_***前缀指派,在 vbo_exec_api.c 内,然后包括 vbo_attrib_tmp.h 暂时忽略 vbo_save_api.c 里的

在第一次执行 neutral_**之后,以后会直接转至 vbo_**来执行

 

glBegin()

1) 经过指派,转至 neutral_Begin() vtxfmt_tmp.h 文件内

2) 再又指派至 vbo_exec_**处执行

在这个函数里主要是记录和模式

 

glEnd()

1) 大体类似 先 neutral_End(),再 vbo_exec_End()

2) 并不会立即进入绘制管道

 

2012/06/05 星期二

初步的认定是 neutral_**() 会去fix顶点数据格式,vbo_**() 会记录数据

glTranslatef()

再次执行一个矩阵变换之后,会因为检测到需要Flush了,从而调用 驱动指针 ctx->Driver.FlushVertices, 对应 vbo_exec_FlushVertices() 函数

 

vbo_exec_FlushVertices()

内部确认参数之后,转而调用 vbo_exec_FlushVertices_internal()

可以这么说,不带 _internal的负责对上下文环境的处理,带_internal的职司 FlushVertices

本函数在后面还负责恢复函数指针,让它们重新首先指向 neutral_**()

 

5 结束场景绘制,准备进入管道

glEnd()之后未必会立即转向实际的绘制,mesa会缓存下前面已发生的指令,当下一步发现必须绘制时(比如在glEnd()之后调用glTranlate()变换视图矩阵了)或者缓冲区已满,才会转入管道准备绘制。

vbo_exec_FlushVertices_internal()

1) 若存在顶点 exec->vtx.vert_count > 0 或强制要求 flush

2) 调用 vbo_exec_vtx_flush() 转到这里去了

3) 再回复 exec内的某些成员,方便接受下一次输入

 

vbo_exec_vtx_flush() // Execute the buffer and save copied verts

1) 若存在 prim_count 以及 vert_count,亦即调用过 glBegin(), glVertex*() 则执行以下语句

==> 调用 vbo_copy_vertices() 获取某个指针

==> 如果与 exec->vtx.vert_count 不等则

==> ==> vbo_exec_bind_arrays()

==> ==> 若 ctx->NewState 有新状态,则调用 _mesa_update_state() 更新之

==> ==> 若 _mesa_is_bufferobj() 则调用 vbo_exec_vtx_unmap() 调试发现这里不执行

==> ==> 调用 vbo_context(ctx)-> draw_prims() 函数,转至 _tnl_vbo_draw_prims() 执行,然后转至 tnl_draw_prims() 完成,开始进入管道了,在 t_draw.c 文件之内

==> 上面完成之后,从调试来看,没再执行什么了

2) 后面恢复数据,比如 exec->vtx.buffer_ptr 指向 exec->vtx.buffer_map,以前验证过 glVertex*()命令所操作对应的位置就是在这里的

通过以上步骤之后,就可以结束 顶点的flush() 了

 

tnl_draw_prims()

进入 软件 tnl 模块的最主要入口

1) 如果min_index不为0,做一些设定,因为总是假定从0序号开始的

2) 如果max_index > max,顶点太多了,需要分割一下再处理

3) 普通情形下,现在只针对这一情形阅读:

==> bind_inputs()

==> bind_indices()

==> bind_prims()

==> TNL_CONTEXT(ctx)->Driver.RunPipeline() 对应 t_pipeline.c 下的 _tnl_run_pipeline()

==> unmap_vbos()

==> free_space()

前面的过程都很明显的在为管道初始化数据地址

中间的函数进入 管道运行 _tnl_run_pipeline()

最后面再清理一些东西

 

6 管道绘制

这一个环节会把glBegin()/glEnd()之间的指令转换成为最终输出上的像素,牵涉到好几个模快,弄懂它们也才能最终明白绘制指令是如何被具体实现的,以前的过程中对这里虽然也花了不少精力,但并没有完全弄明白。

在矩阵变换方面,直线绘制(好像用的是中点绘制算法,但不是非常肯定这一点),超出边界裁剪算法等地方用了不少的宏替换生成方法,如果觉得难以读懂,建议打开VC设定里的存储宏替换之后的.i文件。

 

矩阵变换方面的指针在 m_xform.c 内初始化,会包括好几个文件来构建数组

 

数据结构 SWvertex 在软件光栅化时存储顶点的数据结构,这个很重要,最终绘制时输入就是这个。

wpos = attr[FRAG_ATTRIB_WPOS] 在顶点里必须是第一个值,因为 tnl clipping code的缘故

wpos[0] 和 wpoa[1] 是 SWvertex 的屏幕点

wpos[2] 是 z-buffer 坐标 (如果16-位的Zbuffer,在范围 [0, 65535] 之内)

wpos[3] 是 1/w,其中 w是W坐标的倒数,这是 ndc[XYZ]必须乘以而得到的 clip[XYZ]值

 

管道是什么样的? 

在 t_context.h 头文件内,数据结构 tnl_pipeline_stage 描述单一的管道操作,包括create, destroy, validate, run几个函数指针以及少量的数据成员

数据结构 tnl_pipeline 包括所有管道的数组容器,默认值在 t_pipeline.c 的最后,亦即 _tnl_default_pipeline[] 数组,从中可以看出先做顶点变换,再光照,纹理等,最后是运行渲染,渲染部分与管道部分之间可以再进一步分开

各个管道的初始化,可以在源代码中找到

 

安装管道

由 _tnl_install_pipeline() 来完成,可能在最开始的wglCreateContext()里就调用此函数了,此段代码的执行应该是比较靠前的。

 

管道运行 _tnl_run_pipeline()

1) 如果 tnl->vb.Count 为0,则可退出,对应顶点数量

2) 检查输入变换,校验tnl->pipeline.new_state状态

3) 遍历每一个管道,执行里边的 run函数

 

顶点变换管道 t_vb_vertec.c 内

run_vertex_stage() 函数执行流程

1) 如果使用了 顶点编程,则退出

2) 如果 ctx->_NeedEyeCoords 不为0,则执行xxx (这里不大明白,具体不做什么事情)

3) VB->ClipPtr = TransformRaw() 用 ctx->_ModelProjectMatrix 矩阵变换输入顶点,在 glBegin()与glEnd()之间所用到的值

矩阵里也对应有不少宏指派,这里会转至 m_xform_tmp.h 内的 transform_points3_general

这里所对应的就是用模型投影矩阵变换输入顶点

1.8106601     0            0          -2.7159901

0            2.4142137     0                0

0                  0     -1.0120724    4.8651910

0                  0           -1                 6

(1) 顶点变换结果情况列表如下上一矩阵右乘列向量获得 p' = M * p

(0.0 1.0 0.0) 变换为 (-2.7159901 2.4142137 4.8651910 6)

(-1.0 -1.0 0.0)        (-4.5266504 -2.4142137 4.8651910 6)

(1.0 -1.0 0.0)         (-0.90532994 -2.4142137 4.8651910 6)

上述变换结果点为VB->ClipPtr取值

4) 不管 tnl->NeedNdcCoords 是否需要,都会再进行如下一个转换,具体是 点从

(x, y, z, w) 变换成为 (x/w y/w z/w 1/w) ,赋值给 VB->NdcPtr

从上一步再变换为

(-0.45266503 0.40236896 0.81086516 0.16666667)

(-0.75444174 -0.40236896 0.81086516 0.1666667)

(-0.15088832 -0.40236896 0.81086516 0.1666667)

以上顶点变换过程就完成了。

 

中间的这些管道因为没有被启用,比如光照,纹理,雾等等,故而不需要执行什么,内部会返回GL_TRUE

 

光栅渲染管道 t_vb_render.c 内

run_render()

对于这一过程,我想重要的二个地方在:内部如何建构屏幕点SWvertex;从屏幕点绘制算法;目前对后一问题有所追索,而前一问题总结得还不够,好像层次比较复杂。

1) 调用 tnl->Driver.Render.Start() 函数 对应_swsetup_RenderStart()

==> 这里会在 _swsetup_choose_trifuncs() 初始化 tnl->Driver.Render. 三角形,四边形,直线绘制 swsetup_line(),点绘制函数指针

==> 还有调用 setup_vertex_format() 以明确如何构建 SWvertex

内部通过 tnl_attr_map 的map数组,记录每一个需要拷贝的值,比如总是拷贝第一个位置的 EMIT_ATTR(_TNL_ATTRIB_POS, EMIT_4F_VIEWPORT, attrib[FRAG_ATTRIB_WPOS]),然后如果有颜色设定,则拷贝颜色 EMIT_ATTR(_TNL_ATTRIB_COLOR0, EMIT_4CHAN_4F_RGBA, color);等等,范例只有这2个有设定, 最后调用 _tnl_install_attrs() 设置顶点格式

这个过程有调用 invalidate_funcs() 设置函数指针,比如 emit 指向 choose_emit_func,

2) assert() 确认 Render内的指针有值

3) 调用 tnl->Driver.Render.BuildVertices() 亦即 _tnl_build_vertices(),这里会对顶点有一个变换,但是这个变换却又藏得很深 insert_4f_viewport_4() 函数

关键是在这里基于先前的输入开始构建起后面所需要的顶点,所以它执行了一个系列的转换流程,变换到最终的屏幕点,再最后从屏幕点开始绘制,所以这里的变换过程也很关键的,值得深入探讨一番

==> 获取 tnl_clipspace 指针

==> 调用 update_input_ptrs() 根据被设定的属性数量,更新对应的数据指针地址,然后窗口viewport矩阵设置 vtx->vp_scale vtx->vp_xlate 的几个值

==> 调用 vtx->emit()指针函数,亦即 choose_emit_func(),它会根据 vtx->attr (数据类型 tnl_clipspace_aatr ),设置对应的 a[j].emit 函数指针,a[j].insert[] 而这里的编排可能需要一点时间,对于viewport,设置的是 insert_4f_viewport_4 函数

然后再尽可能重置 vtx->emit, 若最后没有值,则使用 _tnl_generic_emit() 函数,再调用此函数,该函数所完成的工作亦即遍历所有顶点,调用先前在a[j].emit()里设置的函数,因而也就会调用 insert_4f_viewport_4(), 完毕之后,至此,本函数的运行结束了

顶点再一步变换为

(175.14719 336.56854 59337.523 0.1666667)

(78.578644 143.43146 59337.523 0.1666667)

(271.71573 143.43146 59337.523 0.1666667)

4) 获取合适的渲染函数指针地址

5) 遍历所有的prim以及顶点,绘制它们,这里又使用了宏指令来得到不同的函数指针,如果是绘制直线并且带颜色的,会到达 rgba_line() (此函数定义经过宏替换产生),里边的算法很像 中点绘制算法,不过不再严格去比对确认是否真为中点绘制算法了

6) 调用 tnl->Driver.Render.Finish(ctx)

 

以上再次对 OpenGL变换与绘制 又重新有所了解了,我在学习OpenGL的时候,有时候头昏脑涨,搞不清楚最终这些指令会是怎样被执行的,所以过来阅读mesa源代码,应该说有所收获吧,看到管道的一种设计,看到函数的规划与宏替换,真正体会到,C语言的简洁与强大,当然更强的是mesa开发者对内部模型的设计,如果搞清楚了设计模型,里边很多代码就顺理成章了。所以阅读之后,我的感受是开发语言并不重要,项目的规划与设计才是最重要的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: