天天看點

Chapter 5. Data(第五章 資料)

周一到周五,每天一篇,中原標準時間早上7點準時更新~

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

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 types

到現在為止,我們展示的那些寫死的資料也好,從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給這些緩沖區裡塞資料, 你也可以直接擷取到這些緩沖區的位址,然後往裡面寫資料或者從裡面讀資料。

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 time

當你有了緩沖區對象了之後,你就可以将它綁定到目前的OpenGL上下文。這些綁定節點有時候我們也叫它目标。OpenGL裡面有很多綁定節點,盡管你綁定上去的緩沖區都一樣,但每種節點有不同的用處。 比如說,你可以用緩沖區去提供shader的頂點資料,或者是用緩沖區去存儲shader産生的輸出資料。你甚至可以在同一時間将同一個緩沖區用于多個目的。

Creating Buffers and Allocating Memory(建立緩沖區并配置設定記憶體)

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,

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,

const GLvoid data);

void glNamedBufferSubData(GLuint buffer,

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方法,如下所示

};

// 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,

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,

GLsizeiptr length,

GLbitfield access);

void glMapNamedBufferRange(GLuint buffer,

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,因為這樣你能獲得更多的性能方面的提升。

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

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

東漢書院,等你來玩哦

繼續閱讀