添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

着色器中可以将图像定义为uniform。

采样器类型和图像类型的主要区别是:首先图像类型表达的单一层的纹理,不是完整的mipmap链;其次,图像类型不支持滤波采样操作;还有包括包括深度比较的采样操作。

三种基本的图像类型分别为image*、iimage*和uimage*,分别对应浮点数、有符号整数和无符号整数。

除了与图像变量相关通用数据类型,还需要使用一个format限定符来设置数据在内存中的图像格式:

在使用变量format限定符的时候,必须与图像本身的基本数据类型匹配,例如image2D必须使用浮点数类型的限定符例如r32f而非浮点型的限定符rg8ui是不行的。

限定符的使用:

layout (rgba32f) uniform image2D image1;
layout (rg32i) uniform iimage2D image2;

format类型不一定与图像真实数据格式(internal format)完全一致,只要符合兼容要求就行,总体上说就是两种格式所对应的每个纹素中储存的数据量是相同的,就可以认为是兼容的。

绑定纹理对象的一层到一个图像单元

void glBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format);

将level层的纹理texture绑定到图像单元unit。如果是数组纹理的类型,可以选择将整个数组还是某一层绑定到图像单元上,如果layered为true则绑定整个数组并忽略layer的设置,反之则绑定layer这一层。access可以是GL_READ_ONLY、GL_WRITE_ONLY或者GL_READ_WRITE,format为上表中的值。

GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 512, 512);
glBindTexture(GL_TEXTURE_2D, 0);
glBIndImageTexture(0, tex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);

如果要将一个缓存对象所谓imageBuffer图像的存储空间,需要创建一个缓存纹理并将缓存对象关联到纹理对象上,然后将缓存纹理绑定到图像单元上。

GLuint tex, buf;
glGenBuffers(1, &buf);
glBindBuffer(GL_TEXTURE_BUFFER, buf);
glBufferData(GL_TEXTURE_BUFFER, 4096, NULL, GL_DYNAMIC_COPY);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, buf);
glBIndImageTexture(0, tex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);

图像的读取和写入

图像的读取

gvec4 imageLoad(readonly gimage<T> image, <T> P[, int sample]);

P为坐标,对于多重采样的图像,需要使用sample参数来给定采样数。imageLoad的工作方式与texelFetch类似,但不经过滤波。

gvec4 imageStore(writeonly gimage<T> image, <T> P[, int sample], gvec4 data);

将数据存储到坐标P处。

查询图像大小

T imageSize(gimage<T> image);

返回image的维度大小,对于数组形式的图像,返回值的最后一个分量记录数组大小。Cube形式的图像只会返回一个面的大小;如果是数组形式,那么还有cube map数组中的元素数量。

需要注意的是图像存储的次数是没有限制的,而帧缓存对象关联的附件数量要受到严格的限制,并且每个附件都只能写入一个片元值。

着色器存储缓存对象

为了方便处理大型结构化数据块,可以使用一个buffer缓存变量来储存数据。

layout (std430, binding = 0) buffer BufferObject{
    int mode;
    vec4 points[];

binding = 0表示这个块需要和索引为0的GL_SHADRED_STORAGE_BUFFER相关联。与GL_UNIFORM_BUFFER的最大区别是,着色器存储缓存可以再着色器中读写。

原子操作和同步

为了避免着色器运行过程中,并发执行导致的资源访问错误,OpenGL提供了一系列原子函数,直接对内存进行操作。它们有两个属性可以帮助我们访问和修改共享的内存区域。首先,它们总是在一个单一的时间间隔内进行操作,不会被别的着色器请求打断;其次,图形硬件提供一些机制,来确保多个同时发生的请求即使在同一时刻对同一处内存位置进行原子操作,它们也是会被重新序列化并且依次执行,以产生预期的效果。但并不保证顺序,只保证所有请求操作不会相互影响。

内置原子函数

uint imageAtomicAdd(IMAGE_PARAMS mem, uint data);
int imageAtomicAdd(IMAGE_PARAMS mem, int data);
uint imageAtomicMin(IMAGE_PARAMS mem, uint data);
int imageAtomicMin(IMAGE_PARAMS mem, int data);
uint imageAtomicMax(IMAGE_PARAMS mem, uint data);
int imageAtomicMax(IMAGE_PARAMS mem, int data);
uint imageAtomicAnd(IMAGE_PARAMS mem, uint data);
int imageAtomicAnd(IMAGE_PARAMS mem, int data);
uint imageAtomicOr(IMAGE_PARAMS mem, uint data);
int imageAtomicOr(IMAGE_PARAMS mem, int data);
uint imageAtomicXor(IMAGE_PARAMS mem, uint data);
int imageAtomicXor(IMAGE_PARAMS mem, int data);
uint imageAtomicExchange(IMAGE_PARAMS mem, uint data);
int imageAtomicExchange(IMAGE_PARAMS mem, int data);
uint imageAtomicCompSwap(IMAGE_PARAMS mem, uint compare, uint data);
int imageAtomicCompSwap(IMAGE_PARAMS mem, int compare, int data);

函数的返回值是执行操作之前内存中的值。
imageAtomicExchange试讲data写入给定坐标上,并返回原来的值。
imageAtomicCompSwap可以比较compare与图像中的值,如果相等,则将data写入,并返回原来的值。

IMAGE_PARAMS的宏定义:

#define IMAGE_PARAMS gimage1D image, int P      // or
#define IMAGE_PARAMS gimage2d image, ivec2 P    // or

原子操作只能作用于单一的有符号或者是无符号整数,不支持浮点数的图像或者是任何向量类型的图像。

缓存的原子操作

uint atomicAdd(inout uint mem, uint data);
int atomicAdd(inout int mem, int data);
uint atomicMin(inout uint mem, uint data);
int atomicMin(inout int mem, int data);
uint atomicMax(inout uint mem, uint data);
int atomicMax(inout int mem, int data);
uint atomicAnd(inout uint mem, uint data);
int atomicAnd(inout int mem, int data);
uint atomicOr(inout uint mem, uint data);
int atomicOr(inout int mem, int data);
uint atomicXor(inout uint mem, uint data);
int atomicXor(inout int mem, int data);
uint atomicExchange(inout uint mem, uint data);
int atomicExchange(inout int mem, int data);
uint atomicCompSwap(inout uint mem, uint compare, uint data);
int atomicCompSwap(inout int mem, int compare, int data);

为了保证客户端和服务端也就是CPU和GPU采用同步的方式运行,可以使用同步对象(sync object),也可以成为栅栏,本质上栅栏就是流命令中的一个标记,它可以在GPU绘制或者状态变化命令过程中被发送,栅栏的其实生命是无信号的黄台,而GPU执行过之后变成有信号的状态,因此可以通过栅栏的状态了解GPU是否已经达到过栅栏。

GLsync glFenceSync(GLenum condition, GLbitfield flags);

创建一个新的展览同步对象,并插入OpenGL的命令流当中,并返回栅栏句柄。condiiton的唯一可用值时GL_SYNC_GPUCOMMANDS_COMPLETE。flag只能是0。

判断是否执行过栅栏的命令

void glGetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei * length, GLint * values);

sync是同步对象句柄,从中读取有pname设置的属性值。bufSize设置接收缓存的大小,其中缓存地址由values给出,lenght是一个地址,用来写入values数据byte大小。

如果pname为GL_SYNC_STATUS那么values对应的值可以是GL_SIGNALED或者GL_UNSIGNALED。

同步等待直到有信号的状态

GLenum glClientWaitSync(GLsync sync, GLbitfields flags, GLuint64 timeout);

让客户端一直等待同步对象sync直到有信号状态。timeout为纳秒。如果flags中包含GL_SYNC_FLUSH_COMMNDS_BIT那么函数将会自动将所有当前等待的命令传递到服务端,然后开始等待,以确保栅栏进行服务端。

函数的返回值:

GL_ALREADY_SIGNALED: 调用函数时,同步对象已经有信号
GL_TIMEOUT_EXPIRED:超时
GL_CONDITION_SATISFIED:在等待过程中进入有信号状态
GL_WAIT_FAILED:由于某些原因失败了

删除同步对象

void glDeleteSync(GLsync sync);

使用同步对象的例子:

GLsync s;
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 3000);
s = glFneceSync();  // 创建栅栏,当之前的命令结束后就会变成有信号状态
void * data = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 256, GL_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
do_something_time_consuming();
switch (glClientWaitSync(s, 0, 1000000)));
glDeleteSync(s);
memcpy(data, source_data, source_data_size);
glUnmapBuffer(GL_UNIFORM_BUFFER);

是否是合法同步对象

GLboolean glIsSync(GLsync sync);

如果需要在两个或者多个环境之间共享对象,那么可以再一个环境下等待同步对象获得信号,并作为另一个环境的命令结果。可以在源环境(需要等待的环境)中调用glFenceSync()然后再目标环境(执行等待过程的环境)中调用glWaitSync()。

void glWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);

要求服务器等待sync指定的同步对象变成有信号状态,flags必须为0,timeout必须为GL_TIMEOUT_IGNORED。

图像限定符

如果要编译器不对图像的读取或者存储操作做任何的优化处理,可以使用volatile关键字。volatile关键字可以用于全局的声明,uniform和函数参数或者是局部变量。

第二个关键字是restrict关键字,他负责只是编译器,某个图像中引用的数据并不是其他图像的数据别名(也就是说,不存在引用同一片内存区域的图像),这种情况下,写入一个图像是不会影响到其他图像内容的,因此编译器可以更加积极的执行优化操作而不担心安全性的问题。restrict的应用范围与volatile相同。

第三个关键字是coherent,它负责控制图像的缓存机制。GPU通常包含大规模、多级别的缓存机制,它们可能是完全连续的也可能不是,如果图像中的数据是存放在不连续的缓存中,那么一个客户端对于缓存的修改是不会被另一个客户端所知,除非在内存中显示的将缓存刷新到一个更低的级别。如果数据只需要从内存中读取而不写入,那么可以保存到不连续的缓存当中也就是最接近处理器的最高级缓存中,以提高性能。但是如果发生写入内存操作并且需要传递给其他处理器,那么就必须存放到一个连续的内存位置上。这里有两种选择:第一是直接忽略所有缓存,第二是忽略一级缓存,并且将数据放置到二级缓存中,这样确保所有需要共享的数据的操作罗都是在对应缓存的着色器处理器组中运行,coherent关键字就是用来提出这种需求的,让OpenGL来保证二级缓存的连续性。

最后两个关键字:readonlywriteonly,负责控制图像的访问权限。readonly与const的区别是,const是作用于变量本身的,readonly是应用于底层的图像数据的;writeonly会组织对于声明为writeonly的图像变量的读取操作。

glsl中包含memoryBarrier()函数,以确保对于内存中某个位置的写入操作罗可以按照正确的顺序,被其他着色器请求先后观察到。也可以同过OpenGL中提供的glMemoryBarrier()函数实现一些内存处理的控制和缓存机制。

void glMemoryBarrier(GLbitfield barriers);

barriers可以包括:

GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT:作用于顶点缓存的数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_ELEMEN_ARRAY_BARRIER_BIT:作用于绑定的元素数组缓存的数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_UNIFORM_BARRIER_BIT:作用于uniform缓存对象中的uniform数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_TEXTURE_FETCH_BARRIER_BIT:作用于任何取自于纹理的数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_SHADER_IMAGE_ACCESS_BARRIER_BIT:作用于着色器图像变量中的数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_COMMAND_BARRIER_BIT:作用于glDraw*Indirect系列命令中的命令缓存对象数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_PIXEL_BUFFER_BARRIER_BIT:作用于绑定到GL_PIXEL_UNPACK_BUFFER或者GL_PIXEL_PACK_BUFFER的缓存数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_TEXTURE_UPDATE_BARRIER_BIT:作用于glTexImage*D()、glTexSubImag*D()等命令写入,并通过glGetTexImage()读取纹理数据,屏障命令之后读取的结果必然是屏障之前执行命令对于纹理写入的数据
GL_BUFFER_UPDATE_BARRIER_BIT:作用于glCopyBufferSubData()或者glGetBufferSubData()或者映射获取的缓存对象数据,屏障命令之后读取的结果必然是屏障之前着色器对缓存写入的数据。类似的,通过映射或者glBufferData()或者glBufferSubData(),在屏障之前写入缓存对象也会直接作用于屏障之后的着色器读取的结果。
GL_FRAMEBUFFER_BARRIER_BIT:作用于帧缓存附件中读取或者写入的数据,屏障命令之后读取的结果必然是屏障之前着色器写入缓存对象附件的内容。此外,在屏障之后写入的帧缓存的命令,其执行顺序必然落后于屏障之前的处理命令。
GL_TRANSFORM_FEEDBACK_BARRIER_BIT:作用于transform feedback期间处理的数据,屏障命令之后读取的结果必然来自于屏障之前的transform feedback。类似的屏障命令之后写入transform feedback,其执行孙旭必然落后于屏障之前的处理命令。
GL_ATOMIC_COUNTER_BARRIER_BIT
GL_ALL_BARRIER_BIT包括以上所有标记位

除了缓存特性的控制之外,glMemropyBarrier()还可负责控制顺序,可以控制多次绘制之间的重叠问题。

预先片元测试的优化控制

使用early_fragment_tests将深度测试和模板测试提前到片元着色器执行之前,如果写入了gl_FragDepth好像会失效。

#version 420 core
layout (early_fragment_tests) in;

高性能的原子计数器

在glsl中可以使用atomic_uint来创建原子计数器:

layout (binding = 0, offset = 0) uniform atomic_uint red_texels;

binding为缓存绑定点的索引位置,offset为计数器在缓存中的偏移量(一个缓存中可以有多个计数器)。OpenGL中使用GL_ATOMIC_COUNTER_BUFFER来创建原子计数器缓存。

原子计数器的增加和减少使用:

atomicCounterIncrement(atomic_uint);
atomicCounterDecrement(atomic_uint);
                    第十一章 内存使用纹理储存通用数据着色器中可以将图像定义为uniform。采样器类型和图像类型的主要区别是:首先图像类型表达的单一层的纹理,不是完整的mipmap链;其次,图像类型不支持滤波采样操作;还有包括包括深度比较的采样操作。三种基本的图像类型分别为image*、iimage*和uimage*,分别对应浮点数、有符号整数和无符号整数。除了与图像变量相关通用数据类型,还需要使用一...
    前面两节介绍了向量和矩阵,以及坐标和转换相关的数学,再继续讨论模型变换等其他包含数学内容的部分之前,本节介绍二维纹理映射,为后面学习做一个准备。纹理映射本身也是比较大的主题,本节只限于讨论二维纹理的基本使用,对于纹理映射的其他方法,后面会继续学习。可以从我的github下载本节代码。通过本节可以了解到
纹理映射的概念和原理
二维纹理映射的处理方法
使用纹理增加物体表面细节要使渲染
				
文章目录体素化方法原理展示实现过程使用Buffer着色器读取体素坐标实现代码顶点着色器片元着色器CPU读取效果图 体素化方法 体素化能够对模型进行简化,得到均匀的网格,在求模型的测地线,求交等过程中有较好的应用。个人理解,把体素化分为基于CPU的方法和基于GPU渲染的方法。输入是三角面片,输出体素化格子。 直接使用三角形求交的方法见 3D模型体素化(Voxelization)过程实现与分析(一)
纹理在glsl里是sampler,是只能读不能写的,以前要实现通用计算的数据输出,就得建一个FBO,绑定一张跟源纹理一样大的新纹理,把计算结果画上去。但是写入数据的位置却是不能任意指定的,只能是当前绘制的像素,在许多方面有其不便之处。 Image的引入就使得事情变得比较简单了。Image绑定的纹理可以直接指定Image坐标写入,使得更为复杂的操作成为可能。
文章目录数据块接口uniform 块指定着色器中的 uniform 块uniform 块的布局控制访问 uniform 块中声明的 uniform 变量从应用程序中访问 uniform 块buffer 块in/out 块、位置和分量References 下面直接引用了 OpenGL RedBook 第九版 - 第2章 的内容 数据块接口 着色器与应用程序之间,或者着色器各阶段之间共享的变量可以组织为变量块的形式,并且有的时候必须采用这种形式。uniform 变量可以使用 uniform 块,输入和输出变量
Image与Texture的区别 https://stackoverflow.com/questions/35716267/opengl-glactivetexture-vs-glbindimagetexture取值方式差别:texture是通过映射到0-1范围内的任何浮点数组取值,而Image则是通过像素坐标取值,限定于图像维度之内的有符号整数组取值,如[100,23]。
之前提到过OGL中纹理缓存是作为输入缓存存在的,这使得输入缓存能够读取但是不能够改写。为了突破这一限制,在OGL中提出了Image的概念,这使得程序员能够有效的操作texture缓存——对背后的texture缓存进行读写操作。但是,这样的操作打破了原有的pipeline流水线,使得原本应该被OGL自身进行管理的缓存需要程序员自身来进行处理,因此提供了方便的同时也增加了程序员的编程负担。
在OpenGL中,图像(image)类似于一个矩阵,代表一块内存,我们在着色器中可以对它进行读写操作。它有点像单一层级的纹理,但与纹理不同,它不支持滤波、深度比较等采样操作。 1.用纹理texture作为image的存储空间 Shader中选择 image1D、image2D、image3D。。。 GLuint tex; glGenTextures(1, &amp;amp;amp;amp;amp;tex); glBindText...
4. 执行指令序列,模拟Java程序的执行过程。 在实现过程中,我们需要注意一些细节问题,比如指令的操作数类型、栈帧的管理、异常处理等。此外,我们还需要考虑性能问题,比如如何优化指令的执行速度、如何减少内存占用等。 总之,实现一个Java虚拟机是一项非常复杂的任务,需要深入理解Java语言和计算机系统的原理。但是,通过这个练习,我们可以更好地理解Java程序的执行过程,提高我们的编程能力。 ### 回答2: Java黑皮书第11章11.6主要探讨了Java中的异常处理机制,以及如何自定义异常。本章节的核心知识点包括: 1. 异常的概念:异常是在程序执行期间发生的错误或其他意外情况,它打断了正常的程序执行流程。 2. 异常的分类:Java中将异常分为Checked异常和Unchecked异常。Checked异常在编译期间就必须捕获处理,否则编译器会提示错误。Unchecked异常则不需要在编译期间捕获,但程序在运行时会抛出异常。 3. 异常处理机制:Java提供了try-catch语句用于捕获和处理异常。try块中放置可能会抛出异常的代码,catch块中处理异常的代码。 4. 自定义异常:Java允许我们自定义异常类,继承自Exception或RuntimeException,也可以添加自己的字段、方法等。 这一章节的课后题主要是通过代码实践来加深对异常处理机制的理解,以及练习自定义异常类。有一道比较经典的题目是编写一个自定义异常类,并在程序中抛出这个异常。这个题目的思路可以参考以下步骤: 1. 创建一个自定义异常类,继承自Exception或RuntimeException。 2. 在构造方法中传入异常信息,然后调用父类的构造方法。 3. 在程序的某个地方,使用throw关键字抛出自定义异常。 4. 在主程序中使用try-catch语句捕获自定义异常,在catch块中处理异常。 例如,我们可以创建一个自定义异常类MyException,并在程序的某个地方抛出这个异常: class MyException extends RuntimeException{ public MyException(String message){ super(message); public class Main{ public static void main(String[] args){ throw new MyException("这是一个自定义异常"); }catch(MyException e){ System.out.println("捕获到自定义异常:" + e.getMessage()); 在这个例子中,我们创建了一个自定义异常类MyException,它继承自RuntimeException。在程序的try块中,我们使用throw关键字抛出这个异常。在主程序中,我们使用try-catch语句捕获这个自定义异常,并在catch块中处理异常并输出异常信息。 总的来说,Java黑皮书第11章11.6是一个非常重要的章节,掌握异常处理机制和自定义异常类的知识对于Java程序开发至关重要。在实际的开发中,我们需要根据实际情况进行异常处理,使程序更加健壮和可靠。 ### 回答3: 11.6题是要求实现一个基于协程(Coroutine)的简单HTTP服务器。协程是一种比线程更轻量级的并发机制,可以在单个线程中实现多个协程的交替执行,类似于CPU在操作系统中的任务切换。HTTP服务器是指接受HTTP请求并返回HTTP响应的程序或服务。 在实现基于协程的HTTP服务器时,我们需要使用Java的协程库,比如Quasar,ByteBuddy或Kilim。在这个服务器中,每个HTTP请求都被视为一个协程,并且服务器需要实现以下功能: 1. 监听并接受HTTP请求:服务器需要启动一个监听端口,以接受客户端的HTTP请求,并将其转变为协程来处理。 2. 解析HTTP请求:对于每个接受的HTTP请求,服务器需要解析其请求头和主体,以确定请求类型、请求路径、请求方法等信息。 3. 处理HTTP请求:服务器需要根据请求的类型和路径,决定如何处理每个HTTP请求。常见的处理方式是返回文件内容、执行代码、跳转到其它页面等。在协程处理HTTP请求时,服务器可以暂停当前协程,等待文件读取、代码执行等耗时操作完成后,再恢复当前协程继续执行。 4. 返回HTTP响应:服务器需要将处理结果封装成HTTP响应,包括响应头和响应主体内容,并将其发送回客户端。 在实现这个服务器时,需要注意以下几点: 1. Java协程库的选择:Java提供了不同的协程库,每个库有不同的优缺点。要根据实际需求选择适合的协程库,并掌握其基本使用方法。 2. HTTP请求的解析:需要熟悉HTTP协议的请求格式和规范,以正确解析每个请求,并提取需要的信息。 3. 耗时操作的处理:在处理HTTP请求时,可能会遇到需要,等待文件读取、代码执行等耗时操作。要注意在这些操作上暂停当前协程,并在操作完成后恢复协程的执行。 4. 程序的安全性:在实现HTTP服务器时,要注意相关的安全问题,比如防止跨站脚本(XSS)攻击、拒绝服务(DOS)攻击、SQL注入等。 总之,基于协程实现HTTP服务器是一项复杂的工作,需要综合掌握HTTP协议、Java协程库、耗时操作处理、程序安全等多个方面的知识。如果熟练掌握这些技能,就可以实现高效、安全、稳定的HTTP服务器,满足不同场景下的需求。