Chapter 5. Data(第五章 數據)

週一到週五,天天一篇,北京時間早上7點準時更新~app

What You’ll Learn in This Chapter(你將會在本章學到啥)less

How to create buffers and textures that you can use to store data that your program can access
How to get OpenGL to supply the values of your vertex attributes automatically
How to access textures and buffers from your shaders
如何建立緩衝區和紋理貼圖
如何讓OpenGL自動設置你的頂點的數據
如何從shader中訪問緩衝區和紋理
In the examples you’ve seen so far, either we have used hard-coded data directly in our shaders, or we have passed values to shaders one at a time. While sufficient to demonstrate the configuration of the OpenGL pipeline, this is hardly representative of modern graphics programming. Recent graphics processors are designed as streaming processors that consume and produce huge amounts of data. Passing a few values to OpenGL at a time is extremely inefficient. To allow data to be stored and accessed by OpenGL, we include two main forms of data storage—buffers and textures. In this chapter, we first introduce buffers, which are linear blocks of untyped data and can be seen as generic memory allocations. Next, we introduce textures, which are normally used to store multidimensional data, such as images or other data typeside

到如今爲止,咱們展現的那些硬編碼的數據也好,從C語言裏向shader中傳數據也好,儘管說明了OpenGL的玩法,可是它實際上僅僅是教學須要。 咱們接下來要學習的纔是高效的使用OpenGL的方式。在OpenGL中主要有兩種形式的數據:存儲通常數據用的緩衝區和紋理緩衝區。在本章節中,咱們 首先來看看存儲通常數據的緩衝區,它是線性的無類型的數據塊。緊接着咱們來介紹紋理,你能夠在裏面存儲多維的數據好比圖片或者什麼的。函數

Buffers(緩衝區)性能

In OpenGL, buffers are linear allocations of memory that can be used for a number of purposes. They are represented by names, which are essentially opaque handles that OpenGL uses to identify them. Before you can start using buffers, you have to ask OpenGL to reserve some names for you and then use them to allocate memory and put data into that memory. The memory allocated for a buffer object is called its data store. The data store of the buffer is where OpenGL stores its data. You can put data into the buffer using OpenGL commands, or you can map the buffer object, which means that you can get a pointer that your application can use to write directly into (or read directly out of) the buffer學習

在OpenGL中,緩衝區是線性存儲的內存塊,它們有各自的名字,這個名字基本上就是一個OpenGL用於區分它們的標記。在你開始使用緩衝區以前, 你須要讓OpenGL給你分配一些名字,這樣一來你就能夠爲這些名字對應的緩衝區分配內存並操做它們。你能夠經過OpenGL的API給這些緩衝區裏塞數據, 你也能夠直接獲取到這些緩衝區的地址,而後往裏面寫數據或者從裏面讀數據。ui

Once you have the name of a buffer, you can attach it to the OpenGL context by binding it to a buffer binding point. Binding points are sometimes referred to as targets; these terms may be used interchangeably. There are a large number of buffer binding points in OpenGL and each has a different use, although the buffers you bind to them are the same. For example, you can use the contents of a buffer to automatically supply the inputs of a vertex shader, to store the values of variables that will be used by your shaders, or as a place for shaders to store the data they produce. You can even use the same buffer for multiple purposes at the same timethis

當你有了緩衝區對象了以後,你就能夠將它綁定到當前的OpenGL上下文。這些綁定節點有時候咱們也叫它目標。OpenGL裏面有不少綁定節點,儘管你綁定上去的緩衝區都同樣,但每種節點有不一樣的用處。 好比說,你能夠用緩衝區去提供shader的頂點數據,或者是用緩衝區去存儲shader產生的輸出數據。你甚至能夠在同一時間將同一個緩衝區用於多個目的。編碼

Creating Buffers and Allocating Memory(建立緩衝區並分配內存)spa

Before you can ask OpenGL to allocate memory, you need to create a buffer object to represent that allocation. Like most objects in OpenGL, buffer objects are represented by a GLuint variable, which is generally called its name. One or more buffer objects can be created using the glCreateBuffers() function, whose prototype is

在你爲緩衝區對象分配內存前,你須要先建立這樣一個緩衝區。跟大部分的OpenGL裏的對象同樣,緩衝區物體的標記是一個GLuint類型的,這就是它的名字。 你可使用glCreateBuffers一次性去建立1個或者多個緩衝區對象

void glCreateBuffers(GLsizei n, GLuint* buffers);
The first parameter to glCreateBuffers(), n, is the number of buffer objects to create. The second parameter, buffers, is the address of the variable or variables that will be used to store the names of the buffer objects. If you need to create only one buffer object, set n to 1 and set buffers to the address of a single GLuint variable. If you need to create more than one buffer at a time, simply set n to that number and point buffers to the beginning of an array of at least n GLuint variables. OpenGL will just trust that the array is big enough and will write that many buffer names to the pointer that you specify

第一個參數是你須要建立多少個緩衝區對象,第二個參數是用於存儲緩衝區對象的地址。須要注意的是,第二個參數須要有足夠多的空間去存儲對象的名字,好比你若是要10個緩衝區對象, 那麼第二個參數指向的地址必需要至少能夠放10個GLuint。

Each of the names you get back from glCreateBuffers() represents a single buffer object. You can bind the buffer objects to the current OpenGL context by calling glBindBuffer(), the prototype of which is

建立好了緩衝區對象以後,你就可使用glBindBuffer去將某個緩衝區對象綁定到當前的OpenGL的上下文了

void glBindBuffer(GLenum target, GLuint buffer);
Before you can actually use the buffer objects, you need to allocate their data stores, which is another term for the memory represented by the buffer object. The functions that are used to allocate memory using a buffer object are glBufferStorage() and glNamedBufferStorage(). Their prototypes are

完成了上面的操做後,你就能夠爲你的緩衝區對象分配內存了,使用glBufferStoerage和glNamedBufferStorage來分配

void glBufferStorage(GLenum target,
GLsizeiptr size,
const void data,
GLbitfield flags);
void glNamedBufferStorage(GLuint buffer,
GLsizeiptr size,
const void
data,
GLbtifield flags);
The first function affects the buffer object bound to the binding point specified by target; the second function directly affects the buffer specified by buffer. The remainder of the parameters serve the same purpose in both functions. The size parameter specifies how big the storage region is to be, in bytes. The data parameteris used to pass a pointer to any data that you want to initialize the buffer with. If this is NULL, then the storage associated with the buffer object will at first be uninitialized. The final parameter, flags, is used to tell OpenGL how you’re planning to use the buffer object.

第一個函數會影響綁定在target節點上的緩衝區對象。第二個函數直接影響buffer指定的那個緩衝區對象。倆函數剩餘的參數的含義是同樣的。 size表示的是緩衝區對象的內存有多少字節,data表示的數據,若是data是空,則緩衝區僅分配內存,不會寫入數據,若是data有數據,則會把data上的數據拷貝到緩衝區對象裏去。 最後的一個參數是一個標誌位,用來告訴OpenGL,咱們的程序後面會如何去使用這個緩衝區對象。

Once storage has been allocated for a buffer object using either glBufferStorage() or glNamedBufferStorage(), it cannot be reallocated or respecified, but is considered immutable. To be clear, the contents of the buffer object’s data store can be changed, but its size or usage flags may not. If you need to resize a buffer, you need to delete it, create a new one, and set up new storage for that

當你爲緩衝區對象分配好了內存後,你能夠改變緩衝區對象裏的內容,可是你不能夠改變它的大小,若是你非得這麼作,你只能先刪除當前的緩衝區對象,而後從新建立一個新的。

The most interesting parameter to these two functions is the flags parameter. This should give OpenGL enough information to allocate memory suitable for your intended purpose and allow it to make an informed decision about the storage requirements of the buffer. flags is a GLbitfield type, which means that it’s a combination of one or more bits. The flags that you can set are shown in Table 5.1

最搞笑的參數是最後的那個參數,那個參數會告訴OpenGL你將如何使用這些緩衝區對象,這樣OpenGL才能將這些緩衝區分配到合適的內存上,最後的參數能夠是表5.1中的多個標誌位採用位運算來進行組合

Chapter 5. Data(第五章 數據)
The flags listed in Table 5.1 may seem a little terse and probably deserve more explanation. In particular, the absence of certain flags can mean something to OpenGL, some flags may be used only in combination with others, and the specification of these flags can have an effect on what you’re allowed to do with the buffer later. We’ll provide a brief explanation of each of these flags here and then dive deeper into some of their meanings as we cover further functionality

表5.1裏的這些標誌位可能須要更多的說明才行,特別是,某些標誌對OpenGL有着特殊的意義,有的標誌只能與特定的標誌進行結合使用。咱們先來進行一些簡短的說明, 後面咱們用到那些功能的時候再進行深刻講解

First, the GL_DYNAMIC_STORAGE_BIT flag is used to tell OpenGL that you mean to update the contents of the buffer directly—perhaps once for every time that you use thedata. If this flag is not set, OpenGL will assume that you’re not likely to need to change the contents of the buffer and might put the data somewhere that is less accessible. If you don’t set this bit, you won’t be able to use commands like glBufferSubData() to update the buffer content, although you will be able to write into it directly from the GPU using other OpenGL commands

首先是GL_DYNAMIC_STORAGE_BIT,這個標記告訴OpenGL,你須要頻繁的去更新緩衝區對象,若是這個標記沒有被設置的話,OpenGL爲你的緩衝區對象分配的內存能夠在遙遠的山區, 你訪問這樣的緩衝區可能會形成內分泌失調等,甚至大小便失禁。若是你不設置這個東西,你可能沒法使用glBufferSubData去更新緩衝區的數據,儘管你能夠經過其餘的OpenGL指令直接從GPU裏往裏面寫數據

The mapping flags GL_MAP_READ_BIT, GL_MAP_WRITE_BIT, GL_MAP_PERSISTENT_BIT, and GL_MAP_COHERENT_BIT tell OpenGL if and how you’re planning to map the buffer’s data store. Mapping is the process of getting a pointer that you can use from your application that represents the underlying data store of the buffer. For example, you may map the buffer for read or write access only if you specify the GL_MAP_READ_BIT or GL_MAP_WRITE_BIT flags, respectively. Of course, you can specify both if you wish to map the buffer for both reading and writing. If you specify GL_MAP_PERSISTENT_BIT, then this flag tells OpenGL that you wish to map the buffer and then leave it mapped while you call other drawing comands. If you don’t set this bit, then OpenGL requires that you don’t have the buffers mapped while you’re using it from drawing commands. Supporting peristent maps might come at the expense of some performance, so it’s best not to set this bit unless you really need to. The final bit, GL_MAP_COHERENT_BIT, goes further and tells OpenGL that you want to be able to share data quite tightly with the GPU. If you don’t set this bit, you need to tell OpenGL when you’ve written data into the buffer, even if you don’t unmap it.

GL_MAP_READ_BIT、GL_MAP_WRITE_BIT、GL_MAP_PERSISTENT_BIT和GL_MAP_COHERENT_BIT告訴OpenGL你將如何操做緩衝區的數據。 好比只有你設置了GL_MAP_READ_BIT或者GL_MAP_WRITE_BIT的時候,你才能夠經過mapping的方式去讀或者寫數據。你能夠對緩衝區對象同時使用讀和寫兩種標誌。 GL_MAP_PERSISTENT_BIT標誌告訴OpenGL,你在調用繪製指令的時候,不會取消mapping狀態,若是你沒有使用這個標誌位,那麼你在繪圖的時候,必需要取消緩衝區對象的mapping狀態。 GL_MAP_PERSISTENT_BIT這個標誌位實際上會帶來很大的系統負擔, 因此最好仍是不要用的比較好哦,除非你真的須要。GL_MAP_COHERENT_BIT這個標誌告訴OpenGL你但願可以 與GPU共享緊湊的數據,若是你不去設置這個標誌位,即便你不去unmap,你也須要告訴OpenGL,你啥時候把數據寫入緩衝區了。

// The type used for names in OpenGL is GLuint
GLuint buffer;
// Create a buffer
glCreateBuffers(1, &buffer);
// Specify the data store parameters for the buffer
glNamedBufferStorage(
buffer, // Name of the buffer
1024 * 1024, // 1 MiB of space
NULL, // No initial data
GL_MAP_WRITE_BIT); // Allow map for writing
// Now bind it to the context using the GL_ARRAY_BUFFER binding point
glBindBuffer(GL_ARRAY_BUFFER, buffer);
Listing 5.1: Creating and initializing a buffer

清單5.1:建立和初始化一個緩衝區對象

After the code in Listing 5.1 has executed, buffer contains the name of a buffer object that has been initialized to represent one megabyte of storage for whatever data we choose. Using the GL_ARRAY_BUFFER target to refer to the buffer object suggests to OpenGL that we’re planning to use this buffer to store vertex data, but we’ll still be able to take that buffer and bind it to some other target later. There are a handful of ways to get data into the buffer object. You may have noticed the NULL pointer that we pass as the third argument to glNamedBufferStorage() in Listing 5.1. Had we instead supplied a pointer to some data, that data would have been used to initialize the buffer object. Using this pointer, however, allows us to set only the initial data to be stored in the buffer.

清單5.1的代碼執行完畢後,你就拿到了一個大小爲1MB的緩衝區對象了。GL_ARRAY_BUFFER告訴OpenGL,咱們將使用這個緩衝區對象存儲頂點數據,可是咱們依然能夠在後面 把它用做其餘用途。咱們有不少種方法給緩衝區對象裏面塞數據,注意到清單5.1的第三個參數,咱們給它傳了個NULL,表示咱們只申請內存,而不傳數據。若是這裏第三個參數是有數據的,那麼這些 數據將會被傳遞到緩衝區對象的內存上去

Another way get data into a buffer is to give the buffer to OpenGL and tell it to copy data there. This allows you to dynamically update the content of a buffer after it has already been initialized. To do this, we call either glBufferSubData() or glNamedBufferSubData(), passing the size of the data we want to put into the buffer, the offset in the buffer where we want it to go, and a pointer to the data in memory that should be put into the buffer. glBufferSubData() and glNamedBufferSubData() are declared as follows:

另外一個寫數據的方法就是在建立緩衝區以後,使用glBufferSubData或者是glNamedBufferSubData函數,這些API讓你能夠動態的去更新緩衝區對象的內容。offset是寫入數據位置的偏移, size是數據的大小,data是咱們要寫入的數據

void glBufferSubData(GLenum target,
GLintptr offset,
GLsizeiptr size,
const GLvoid data);
void glNamedBufferSubData(GLuint buffer,
GLintptr offset,
GLsizeiptr size,
const void
data);
To update a buffer object using glBufferSubData(), you must have told OpenGL that you want to put data into it that way. To do this, include GL_DYNAMIC_STORAGE_BIT in the flags parameter to glBufferStorage() or glNamedBufferStorage(). Like glBufferStorage() and glNamedBufferStorage(), glBufferSubData() affects the buffer bound to the binding point specified by target, and glNamedBufferSubData() affects the buffer object specified by buffer. Listing 5.2 shows how we can put the data originally used in Listing 3.1 into a buffer object, which is the first step in automatically feeding a vertex shader with data.

爲了使用glBufferSubData更新緩衝區對象的內容,你須要在建立它的時候使用GL_DYNAMIC_STORAGE_BIT。 glBufferSubData會影響到綁定節點的緩衝區對象,glNamedBufferSubData會影響由buffer指定的緩衝區對象的數據內容。清單5.2展現了咱們如何把清單3.1中的數據用今天學的方式傳給緩衝區對象

// This is the data that we will place into the buffer object
static const float data[] =
{
0.25, -0.25, 0.5, 1.0,
-0.25, -0.25, 0.5, 1.0,
0.25, 0.25, 0.5, 1.0
};// Put the data into the buffer at offset zero
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(data), data);
Listing 5.2: Updating the content of a buffer with glBufferSubData()

清單5.2,使用glBufferSubData更新緩衝區對象的內容

Another method for getting data into a buffer object is to ask OpenGL for a pointer to the memory that the buffer object represents and then copy the data there yourself. This is known as mapping the buffer. Listing 5.3 shows how to do this using the glMapNamedBuffer() function

另外一個往緩衝區對象裏寫數據的方法是使用mapping方法,以下所示

// This is the data that we will place into the buffer object
static const float data[] =
{
0.25, -0.25, 0.5, 1.0,
-0.25, -0.25, 0.5, 1.0,
0.25, 0.25, 0.5, 1.0
};
// Get a pointer to the buffer's data store
void * ptr = glMapNamedBuffer(buffer, GL_WRITE_ONLY);
// Copy our data into it...
memcpy(ptr, data, sizeof(data));
// Tell OpenGL that we're done with the pointer
glUnmapNamedBuffer(GL_ARRAY_BUFFER);
Listing 5.3: Mapping a buffer’s data store with glMapNamedBuffer()

As with many other buffer functions in OpenGL, there are two versions—one that affects the buffer bound to one of the targets of the current context, and one that operates directly on a buffer whose name you specify. Their prototypes are

跟其餘衆多的緩衝區操做函數同樣,mapping方法有兩個版本,以下所示

void glMapBuffer(GLenum target,
GLenum usage);
void
glMapNamedBuffer(GLuint buffer,
GLenum usage);
To unmap the buffer, we call either glUnmapBuffer() or glUnmapNamedBuffer(), as shown in Listing 5.3. Their prototypes are

unmapping操做使用對應的glUnmapBuffer或者glUnmapNamedBuffer

void glUnmapBuffer(GLenum target);
void glUnmapNamedBuffer(GLuint buffer);
Mapping a buffer is useful if you don’t have all the data handy when you call the function. For example, you might be about to generate the data, or to read it from a file. If you wanted to use glBufferSubData() (or the initial pointer passed to glBufferData()), you’d have to generate or read the data into temporary memory and then get OpenGL to make another copy of the data into the buffer object. If you map a buffer, you can simply read the contents of the file directly into the mapped buffer. When you unmap it, if OpenGL can avoid making a copy of the data, it will. Regardless of whether we used glBufferSubData() or glMapBuffer() and an explicit copy to get data into our buffer object, it now contains a copy of data[] and we can use it as a source of data to feed our vertex shader

mapping的好處在於,你沒必要要在C語言中保存一份數據的拷貝,你能夠直接去操做GPU上的數據。好比,若是你從文件中讀入數據,去更新緩衝區對象的內容, 若是使用glBufferSubData的方式,你必須先從文件裏把內容讀到內存,而後再使用glBufferSubData更新數據,若是你使用mapping的方式,那麼 你能夠直接從文件中讀入數據到緩衝區對象中。

The glMapBuffer() and glMapNamedBuffer() functions can sometimes be a little heavy handed. They map the entire buffer, and do not provide any information about the type of mapping operation to be performed besides the usage parameter. Even that serves only as a hint. A more surgical approach can be taken by calling either glMapBufferRange() or glMapNamedBufferRange(), whose prototypes are

使用glMapBuffer和glMapNamedBuffer有時候會太暴力了,由於它mapping了整個緩衝區,另外一個更推薦的方式是glMapBufferRange和glMapNamedBufferRange

void glMapBufferRange(GLenum target,
GLintptr offset,
GLsizeiptr length,
GLbitfield access);
void
glMapNamedBufferRange(GLuint buffer,
GLintptr offset,
GLsizeiptr length,
GLbitfield access);
As with the glMapBuffer() and glMapNamedBuffer() functions, there are two versions of these functions—one that affects a currently bound buffer and one that affects a directly specified buffer object. These functions, rather than mapping the entire buffer object, map only a specific range of the buffer object. This range is given using the offset and length parameters. The parameter contains flags that tell OpenGL how the mapping should be performed. These flags can be a combination of any of the bits listed in Table 5.2.

跟glMapBuffer和glMapNamedBuffer同樣,有兩個版本-一個影響當前綁定節點的緩衝區對象,一個影響由第一個參數指定的緩衝區對象。 比起mapping整個緩衝區,這倆函數只mapping了一部分。offset指出從哪一個偏移地址開始mapping,length表示要mapping多大的數據塊。 最後的標誌位如表5.2所示。

Chapter 5. Data(第五章 數據)
As with the bits that you can pass to glBufferStorage(), these bits can control some advanced functionality of OpenGL and, in some cases, their correct usage depends on other OpenGL functionality. However, these bits are not hints and OpenGL will enforce their correct usage. You should set GL_MAP_READ_BIT if you plan to read from the buffer and GL_MAP_WRITE_BIT if you plan to write to it. Reading or writing into the mapped range without setting the appropriate bits is an error. The GL_MAP_PERSISTENT_BIT and GL_MAP_COHERENT_BIT flags have similar meanings to their identically named counterparts in glBufferStorage(). All four of these bits are required to match between when you specify storage and when you request a mapping. That is, if you want to map a buffer for reading using the GL_MAP_READ_BIT flag, then you must also specify the GL_MAP_READ_BIT flag when you call glBufferStorage()

與建立緩衝區時那些標誌不一樣的是,這裏的標誌位設置不是提示,而是強制設置,也就是說,若是你mapping的時候設置了讀的標誌位,那麼後面卻往裏面寫數據,就會出錯。 還有一點須要注意的是,爲了這裏可以實現讀寫操做,你須要在建立該緩衝區對象的時候,就設置相應的標誌位

We’ll dig deeper into the remaining flags when we cover synchronization primitives a little later in the book. However, because of the additional control and stronger contract provided by glMapBufferRange() and glMapNamedBufferRange(), it isgenerally preferred to call these functions rather than glMapNamedBuffer() (or glMapBuffer()). You should get into the habbit of using these functions even if you’re not using any of their more advanced features

咱們將在後面的內容中更加深刻的聊其餘的那些標誌位。你應該更多的使用glMapBufferRange的API而不是glMapBuffer,由於這樣你能得到更多的性能方面的提高。

本日的翻譯就到這裏,明天見,拜拜~~

第一時間獲取最新橋段,請關注東漢書院以及圖形之心公衆號

東漢書院,等你來玩哦