CSharpGL(49)試水OpenGL軟實現

CSharpGL(49)試水OpenGL軟實現

CSharpGL迎來了第49篇。本篇內容是用C#編寫一個OpenGL的軟實現。暫且將其命名為SoftGL。

目前已經實現了由Vertex Shader和Fragment Shader組成的Pipeline,其效果與顯卡支持的OpenGL實現幾乎相同。下圖左是常規OpenGL渲染的結果,右是SoftGL渲染的結果。

下載

CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL)

SoftGL也已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/SoftGL)

從使用者的角度開始

OpenGL的使用者就是OpenGL應用程序開發者(Dev)。

下面按其被執行的先后順序陳列OpenGL相關的命令(只陳列最基本的命令):

創建Device Context

用一個System.Windows.Forms.Control類型的對象即可。

最后會發現,這個Device Context的作用之一是為創建Render Context提供參數。目前在SoftGL中不需要這個參數。

創建Render Context

Render Context包含OpenGL的所有狀態字段。例如,當Dev調用glLineWidth(float width);時,Render Context會記下這一width值,從而使之長期有效(直到下次調用glLineWidth(float width);來修改它)。

可能同時存在多個Render Context,每個都保存自己的lineWidth等字段。當使用靜態的OpenGL函數static void glLineWidth(float width);時,它會首先找到當前的Render Context對象(詳見后面的MakeCurrent(..)),然后讓此對象執行真正的glLineWidth(float width);函數。

可見Render Context就是一個典型的class,其偽代碼如下:

 1 partial class SoftGLRenderContext:
 2 {
 3     private float lineWidth;
 4     // .. other fields.
 5     
 6     public static void glLineWidth(float width)
 7     {
 8         SoftGLRenderContext obj = SoftGLRenderContext .GetCurrentContext();
 9         if (obj != null) { obj.LineWidth(width); }
10     }
11     
12     private void LineWidth(float width)
13     {
14         this.lineWidth = width;
15     }
16     
17     // .. other OpenGL functions.
18 }

MakeCurrent(IntPtr dc, IntPtr rc);

函數static void MakeCurrent(IntPtr dc, IntPtr rc);不是OpenGL函數。它的作用是指定當前線程(Thread)與哪個Render Context對應,即在Dictionary<Thread, RenderContext>這一字典類型中記錄下Thread與Render Context的對應關系。

當然,如果rc為IntPtr.Zero,就是要解除當前Thread與其Render Context的對應關系。

偽代碼如下:

 1 partial class SoftGLRenderContext
 2 {
 3     // Thread -> Binding Render Context Object.
 4     static Dictionary<Thread, SoftGLRenderContext> threadContextDict = new Dictionary<Thread, SoftGLRenderContext>();
 5 
 6     // Make specified renderContext the current one of current thread.
 7     public static void MakeCurrent(IntPtr deviceContext, IntPtr renderContext)
 8     {
 9         var threadContextDict = SoftGLRenderContext.threadContextDict;
10         if (renderContext == IntPtr.Zero) // cancel current render context to current thread.
11         {
12             SoftGLRenderContext context = null;
13 
14             Thread thread = System.Threading.Thread.CurrentThread;
15             if (threadContextDict.TryGetValue(thread, out context))
16             {
17                 threadContextDict.Remove(thread);
18             }
19         }
20         else // change current render context to current thread.
21         {
22             SoftGLRenderContext context = GetContextObj(renderContext);
23             if (context != null)
24             {
25                 SoftGLRenderContext oldContext = GetCurrentContextObj();
26                 if (oldContext != context)
27                 {
28                     Thread thread = Thread.CurrentThread;
29                     if (oldContext != null) { threadContextDict.Remove(thread); }
30                     threadContextDict.Add(thread, context);
31                     context.DeviceContextHandle = deviceContext;
32                 }
33             }
34         }
35     }
36 }

獲取OpenGL函數指針

在CSharpGL.Windows項目中,我們可以通過Win32 API找到在opengl32.dll中的OpenGL函數指針,并將其轉換為C#中的函數委托(Delegate),從而可以像使用普通函數一樣使用OpenGL函數。其偽代碼如下:

 1 public partial class WinGL : CSharpGL.GL
 2 {
 3     public override Delegate GetDelegateFor(string functionName, Type functionDeclaration)
 4     {
 5         Delegate del = null;
 6         if (!extensionFunctions.TryGetValue(functionName, out del))
 7         {
 8             IntPtr proc = Win32.wglGetProcAddress(name);
 9             if (proc != IntPtr.Zero)
10             {
11                 // Get the delegate for the function pointer.
12                 del = Marshal.GetDelegateForFunctionPointer(proc, functionDeclaration);
13 
14                 // Add to the dictionary.
15                 extensionFunctions.Add(functionName, del);
16             }
17         }
18 
19         return del;
20 }
21 
22     // Gets a proc address.
23     [DllImport("opengl32.dll", SetLastError = true)]
24 internal static extern IntPtr wglGetProcAddress(string name);
25 
26     // The set of extension functions.
27     static Dictionary<string, Delegate> extensionFunctions = new Dictionary<string, Delegate>();
28 }

此時我們想使用SoftGL,那么要相應地為其編寫一個SoftGL.Windows項目。這個項目通過在類似opengl32.dll的SoftOpengl32項目(或者SoftOpengl32.dll)中查找函數的方式來找到我們自己實現的OpenGL函數。其偽代碼如下:

 1 partial class WinSoftGL : CSharpGL.GL
 2 {
 3     private static readonly Type thisType = typeof(SoftOpengl32.StaticCalls);
 4     public override Delegate GetDelegateFor(string functionName, Type functionDeclaration)
 5     {
 6         Delegate result = null;
 7         if (!extensionFunctions.TryGetValue(functionName, out result))
 8         {
 9             MethodInfo methodInfo = thisType.GetMethod(functionName, BindingFlags.Static | BindingFlags.Public);
10             if (methodInfo != null)
11             {
12                 result = System.Delegate.CreateDelegate(functionDeclaration, methodInfo);
13             }
14 
15             if (result != null)
16             {
17                 //  Add to the dictionary.
18                 extensionFunctions.Add(functionName, result);
19             }
20         }
21 
22         return result;
23     }
24 
25     // The set of extension functions.
26     static Dictionary<string, Delegate> extensionFunctions = new Dictionary<string, Delegate>();
27 }

可見只需通過C#和.NET提供的反射機制即可實現。在找到System.Delegate.CreateDelegate(..)這個方法時,我感覺到一種“完美”。

此時,我們應當注意到另一個涉及大局的問題,就是整個SoftGL的框架結構。

SoftGL項目本身的作用與顯卡驅動中的OpenGL實現相同。操作系統(例如Windows)提供了一個opengl32.dll之類的方式來讓Dev找到OpenGL函數指針,從而使用OpenGL。CSharpGL項目是對OpenGL的封裝,具體地講,是對OpenGL的函數聲明的封裝,它不包含對OpenGL的實現、初始化等功能。這些功能是在CSharpGL.Windows中實現的。Dev通過引用CSharpGL項目和CSharpGL.Windows項目就可以直接使用OpenGL了。

如果不使用顯卡中的OpenGL實現,而是換做SoftGL,那么這一切就要相應地變化。SoftOpengl32項目代替操作系統的opengl32.dll。CSharpGL保持不變。SoftGL.Windows代替CSharpGL.Windows。Dev通過引用CSharpGL項目和SoftGL.Windows項目就可以直接使用軟實現的OpenGL了。

最重要的是,這樣保證了應用程序的代碼不需任何改變,應用程序只需將對CSharpGL.Windows的引用修改為對SoftGL.Windows的引用即可。真的。

創建ShaderProgram和Shader

根據OpenGL命令,可以推測一種可能的創建和刪除ShaderProgram對象的方式,偽代碼如下:

 1 partial class SoftGLRenderContext
 2 {
 3     private uint nextShaderProgramName = 1;
 4 
 5     // name -> ShaderProgram object
 6     Dictionary<uint, ShaderProgram> nameShaderProgramDict = new Dictionary<uint, ShaderProgram>();
 7 
 8     private ShaderProgram currentShaderProgram = null;
 9 
10     public static uint glCreateProgram() // OpenGL functions.
11     {
12         uint id = 0;
13         SoftGLRenderContext context = ContextManager.GetCurrentContextObj();
14         if (context != null)
15         {
16             id = context.CreateProgram();
17         }
18 
19         return id;
20     }
21 
22     private uint CreateProgram()
23     {
24         uint name = nextShaderProgramName;
25         var program = new ShaderProgram(name); //create object.
26         this.nameShaderProgramDict.Add(name, program); // bind name and object.
27         nextShaderProgramName++; // prepare for next name.
28 
29         return name;
30 }
31 
32     public static void glDeleteProgram(uint program)
33     {
34         SoftGLRenderContext context = ContextManager.GetCurrentContextObj();
35         if (context != null)
36         {
37             context.DeleteProgram(program);
38         }
39     }
40     
41     private void DeleteProgram(uint program)
42     {
43         Dictionary<uint, ShaderProgram> dict = this.nameShaderProgramDict;
44         if (!dict.ContainsKey(program)) { SetLastError(ErrorCode.InvalidValue); return; }
45 
46         dict.Remove(program);
47     }
48 }

創建ShaderProgram對象的邏輯很簡單,首先找到當前的Render Context對象,然后讓它創建一個ShaderProgram對象,并使之與一個name綁定(記錄到一個Dictionary<uint, ShaderProgram>字典類型的字段中)。刪除ShaderProgram對象的邏輯也很簡單,首先判斷參數是否合法,然后將字典中的ShaderProgram對象刪除即可。

OpenGL中的很多對象都遵循這樣的創建模式,例如Shader、Buffer、VertexArrayObject、Framebuffer、Renderbuffer、Texture等。

ShaderProgram是一個大塊頭的類型,它要處理很多和GLSL Shader相關的東西。到時候再具體說。

創建VertexBuffer、IndexBuffer和VertexArrayObject

參見創建ShaderProgram對象的方式。要注意的是,這些類型的創建分2步。第一步是調用glGen*(int count, uint[] names);,此時只為其分配了name,沒有創建對象。第二步是首次調用glBind*(uint target, uint name);,此時才會真正創建對象。我猜這是早期的函數接口,所以用了這么啰嗦的方式。

對頂點屬性進行設置

一個頂點緩存對象(GLBuffer)實際上是一個字節數組(byte[])。它里面保存的,可能是頂點的位置屬性(vec3[]),可能是頂點的紋理坐標屬性(vec2[]),可能是頂點的密度屬性(float[]),可能是頂點的法線屬性(vec3[]),還可能是這些屬性的某種組合(如一個位置屬性+一個紋理坐標屬性這樣的輪流出現)。OpenGL函數glVertexAttribPointer(uint index, int size, uint type, bool normalized, int stride, IntPtr pointer)的作用就是描述頂點緩存對象保存的是什么,是如何保存的。

glClear(uint mask)

每次渲染場景前,都應清空畫布,即用glClear(uint mask);清空指定的緩存。

OpenGL函數glClearColor(float r, float g, float b, float a);用于指定將畫布清空為什么顏色。這是十分簡單的,只需設置Render Context中的一個字段即可。

需要清空顏色緩存(GL_COLOR_BUFFER_BIT)時,實際上是將當前Framebuffer對象上的顏色緩存設置為指定的顏色。需要清空深度緩存(GL_DEPTH_BUFFER_BIT)或模板緩存(GL_STENCIL_BUFFER_BIT)時,實際上也是將當前Framebuffer對象上的深度緩存或模板緩存設置為指定的值。

所以,為了實現glClear(uint mask)函數,必須將Framebuffer和各類緩存都準備好。

Framebuffer中的各種緩存都可以簡單的用一個Renderbuffer對象充當。一個Renderbuffer對象實際上也是一個字節數組(byte[]),只不過它還用額外的字段記錄了自己的數據格式(GL_RGBA等)等信息。紋理(Texture)對象里的各個Image也可以充當Framebuffer中的各種緩存。所以Image是和Renderbuffer類似的東西,或者說,它們支持同一個接口IAttachable。

1 interface IAttachable
2 {
3     uint Format { get; }  // buffer’s format
4     int Width { get; } // buffer’s width.
5     int Height { get; } // buffer’s height.
6     byte[] DataStore { get; } // buffer data.
7 }

這里就涉及到對與byte[]這樣的數組與各種其他類型的數組(例如描述位置的vec3[])相互賦值的問題。一般,可以用下面的方式解決:

1 byte[] bytes = ...
2 this.pin = GCHandle.Alloc(bytes, GCHandleType.Pinned);
3 IntPtr pointer = this.pin.AddrOfPinnedObject();
4 var array = (vec3*)pointer.ToPointer();
5 for (in i = 0; i< ...; i++) {
6     array[i] = ...
7 }

只要能將數組轉換為?void*?類型,就沒有什么做不到的了。

?

glGetIntegerv(uint target, int[] values)

這個十分簡單。一個大大的switch語句。

?

設置Viewport

設置viewport本身是十分簡單的,與設置lineWidth類似。但是,在一個Render Context對象被首次MakeCurrent()到一個線程時,要將Device Context的Width和Height賦值給viewport。這個有點麻煩。

更新uniform變量的值

?

glDrawElements(..)

?

總結

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/537578.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/537578.shtml
英文地址,請注明出處:http://en.pswp.cn/news/537578.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SonarQube結合IDEA實現代碼檢測

環境準備 1.SonarQube下載&#xff1a;https://www.sonarqube.org/downloads/ 建議用最新版本&#xff0c;SonarQube與idea的結合 需要SonarQube很多插件&#xff0c;需要借助idea的SonarLint 插件。 不同的SonarQube版本&#xff0c;有不同的插件版本 idea的SonarLint 插件…

二維小波變換_【外文文獻速讀】實時二維水波模擬

題目&#xff1a;Water surface wavelets 作者&#xff1a;Stefan Jeschke&#xff0c; Tom?Sk?ivan&#xff0c; MatthiasMller-Fischer&#xff0c; Nuttapong Chentanez&#xff0c; Miles Macklin&#xff0c; Chris Wojtan

技術開發(委托)合同怎么寫?

一直基于寧波市科技局備案合同模板簽訂合同&#xff0c;并完成科技局備案工作&#xff0c;成功了N次&#xff0c;直接分享模板&#xff0c;該模板通過了法務審核&#xff0c;財務審核&#xff0c;只需要批示修改相關內容即可&#xff0c;一份技術開發委托合同&#xff0c;十幾分…

最常用的15個前端表單驗證JS正則表達式

2019獨角獸企業重金招聘Python工程師標準>>> 在表單驗證中&#xff0c;使用正則表達式來驗證正確與否是一個很頻繁的操作&#xff0c;本文收集整理了15個常用的JavaScript正則表達式&#xff0c;其中包括用戶名、密碼強度、整數、數字、電子郵件地址&#xff08;Ema…

程序員個人外包合同怎么寫?

分享一份工作上經常用到的個人外包合同協議&#xff0c;該協議通過了法務與財務審核&#xff0c;兼顧甲乙雙方利益&#xff0c;程序員接私活必備&#xff01;&#xff01;&#xff01;&#xff01; ---需要電子word版&#xff0c;請關注--------- 回復&#xff1a;個人外包合同…

rocketmq新擴容的broker沒有tps_深入研究RocketMQ消費者是如何獲取消息的

前言小伙伴們&#xff0c;國慶都過的開心嗎&#xff1f;國慶后的第一個工作日是不是很多小伙伴還沉浸在假期的心情中&#xff0c;沒有工作狀態呢&#xff1f;那王子今天和大家聊一聊RocketMQ的消費者是如何獲取消息的&#xff0c;通過學習知識來找回狀態吧。廢話不多說&#xf…

蘇寧 11.11:倉庫內多 AGV 協作的全局路徑規劃算法研究

本文為『InfoQ x 蘇寧 2018雙十一』技術特別策劃系列文章之一。 1. 背景 隨著物聯網和人工智能的發展&#xff0c;越來越多的任務漸漸的被機器人取代&#xff0c;機器人逐漸在發展中慢慢進入物流領域&#xff0c;“智能叉車”&#xff0c;AGV&#xff08;Automated Guided Vehi…

老板思維:工作負責人是首問責任制

工作負責人包括部門領導&#xff0c;項目經理等負責人。以項目經理為例&#xff0c;解釋這種思維。 分好幾種情況&#xff1a; &#xff08;1&#xff09;當公司&#xff08;老板&#xff0c;領導&#xff0c;甲方&#xff09;將事情交給你的時候&#xff0c;這件事情就由你負…

用python繪制玫瑰花的代碼_python也能玩出玫瑰花!程序員的表白代碼

有些情侶是異地戀&#xff0c;情人節想送朵玫瑰花給女朋友都困難。別擔心&#xff0c;用Python就好了&#xff0c;互聯網時代的戀愛神器&#xff01;接下來就讓我們一起來看看如何用Python變出玫瑰花的。 1、首先我們導入畫圖工具turtle&#xff0c;即import turtle 2、導入畫圖…

Springboot 整合 swagger

版權聲明&#xff1a;本文為博主原創文章&#xff0c;未經博主允許不得轉載。 https://blog.csdn.net/weixin_40254498/article/details/83622098 swagger 主要是為后端服務的接口文檔&#xff0c;懶人必備&#xff0c;swagger就是一款讓你更好的書寫API文檔的框架。 其他的框架…

Project為項目設置預算

假設項目預算10萬元&#xff0c;如果項目完成后&#xff0c;花費沒有超過10萬元&#xff0c;則成本管理是成功的&#xff0c;如果花費了11萬&#xff0c;則超過了預算。 預算是10萬&#xff0c;一般目標成本設得比預算成本低&#xff0c;比如9.5萬。在項目實施過程中&#xff…

activiti7流程設計器_變頻空調器通信電路

通信電路由室內機和室外機主板兩個部分單元電路組成&#xff0c;并且在實際維修中該電路的故障率比較高&#xff0c;因此單設--節進行詳細說明。第三章變頻空調器單元電路對比和通信電路第二節通信電路通信電路由室內機和室外機主板兩個部分單元電路組成&#xff0c;并且在實際…

PyCharm 中為 Python 項目添加.gitignore文件

文章目錄 1.安裝.ignore插件 2.在項目中添加.ignore文件 1.安裝.ignore插件 在pycharm編譯器中&#xff0c;依次點擊File->Setting 在跳出Setting的頁面中&#xff0c;執行如下操作&#xff1a; 點擊左側的Plugins&#xff0c; 在搜索框中輸入.ignore 點擊右側的install 點…

mysql的分頁查詢

為什么80%的碼農都做不了架構師&#xff1f;>>> order by case when 的用法&#xff08;實現特殊情況的排序&#xff0c;如leader1的排最前面&#xff09;&#xff1a; select * from m_worker_project order by CASE WHEN leader 1 THEN 100 ELSE 1000 END 項目中…

.describe() python_python的apply應用:一般性的“拆分-應用-合并”,附加詳細講解

跟aggregate一樣&#xff0c;transform也是一個有著嚴格條件的特殊函數&#xff1a;傳入的函數只能產生兩種結果&#xff0c;要么產生一個可以傳播的標量值(如np.mean)&#xff0c;要么產生一個相同大小的結果數組。最一般化的GroupBy方法是apply&#xff0c;apply會將待處理的…

DNS服務(4)Slave DNS及高級特性

為了簡化運維人員的負擔&#xff0c;使用Master/Slave DNS架構的情況比較好&#xff0c;現在我們來簡單敘述一下Master/Slaver DNS的特點主DNS服務器&#xff1a;維護所負責解析的域內解析庫服務器&#xff1b;解析庫由管理員維護&#xff1b;從DNS服務器:從主DNS服務器或其它的…

python運算符_Python運算符總結

建議&#xff1a;字符串拼接操作盡量多用join&#xff0c;而減少用”“ join操作時會先計算字符操作所用到的空間總和大小&#xff0c;然后申請內存。然后進行字符串連接操作。所以join的時間復雜的近似O(n)。 操作符連接操作符時&#xff0c;由于字符串是不可變對象&#xff0…

jupyter notebook常用快捷鍵

Jupyter Notebook 有兩種鍵盤輸入模式。編輯模式&#xff0c;允許你往單元中鍵入代碼或文本&#xff1b;這時的單元框線是綠色的。命令模式&#xff0c;鍵盤輸入運行程序命令&#xff1b;這時的單元框線是灰色。 命令模式 (按鍵 Esc 開啟) Enter : 轉入編輯模式Shift-Enter : …

Eclipse安裝試用Hanlp

【1】確定正確安裝配置Java和Eclipse 【2】下載HanLp的各種東西 hanlp.linrunsoft.com/services.ht… 下載這四個文件到本地&#xff0c;我是放在桌面的一個文件夾了。【3】 把jar包導入到Eclipse 在Eclipse先新建一個項目File——New——Java Project--[名字&#xff1a;Hanlp…

升級pip最新版本

python很多庫對pip版本有要求&#xff0c;升級命令為&#xff1a; python -m pip install --upgrade pip windows在cmd下&#xff0c;輸入以上命令