你们好,本文整理了现代图形API的技术要点,重点研究了并行和GPU Driven Render Pipeline相关的知识点,调查了WebGPU的相关支持状况。html
另外,本文对实时光线追踪也进行了简要的分析。这是我很是感兴趣的技术方向,也是图形学的发展方向之一。本系列后续文章会围绕这个方向进行更多的研究和实现相关的Demo。git
上一篇博文:
WebGPU学习(四):Alpha To Coverage程序员
下一篇博文:
WebGPU学习(六):学习“rotatingCube”示例github
现代图形API包括哪些API?
包括DX十二、Vulkan、Metalweb
MVP是什么?
是WebGPU的最小可用版本。
在1.0版本发布前,先发布MVP版本。算法
现代图形API包含下面的技术要点:
chrome
下面依次进行分析:windows
为了提升多核CPU和GPU的利用率,现代图形API充分支持了并行。promise
并行包含下面的技术要点:多线程
为了提升GPU利用率,能够将不一样种类的任务对应的command buffer提交到3种队列中:
graphics queue
copy queue
compute queue
不一样队列的任务可以在GPU中并行执行,从而实现Async Compute,提升利用率。
参考资料
Multi-engine synchronization
根据Multiple Queues skeleton proposal,MVP只支持单队列:
what single queue is exposed in the MVP
有3个技术能够实现CPU与GPU之间以及GPU内部的同步:
我不了解它,它应该是用来同步队列的
它用来避免GPU由于资源依赖关系形成等待,以及避免CPU和GPU之间发生Race Condition。
现代图形API更加底层,之前GPU作的同步工做也交给了图形程序员,更加灵活的同时也加剧了程序员的负担。
它用来在CPU和GPU之间同步。
这3个技术的关系能够参考Vulkan Multi-Threading:
由于目前只支持单队列,因此不须要它
你们都表示memory barrier不容易实现,因此barriers由WebGPU帮咱们作了(参考Memory barriers investigations、Memory Barriers portability、The case for passes -> Synchronization and validation),咱们只须要给WebGPU一些提示(如指定buffer的usage)
支持以计数的方式实现fence。
参考资料
TimelineFences
能够在线程中执行现代图形API相关的渲染任务:
在线程中更新资源
如更新buffer
并行地编译shader
并行地建立pipeline state
在线程中建立command buffer
有两种方法实现多线程:
根据Rendering to OffscreenCanvas on non-yielding workers:
WebGPU支持OffScreenCanvas API,可是目前Chrome不能使用它。
Create a proposal for multi-worker中提出了WebGPU如何在worker中执行渲染任务:
1.Asynchronous texture & buffer uploads
2.Asynchronous shader compilation
3.Asynchronous pipeline state creation
4.Using MTLParallelRenderEncoder
5.Each thread in a thread pool records into its own command buffer
根据Minutes for GPU Web meeting 2019-08-05 -> Multi threading:
其中的1,2,3正在实现中;
4, 5会最终实现(没有说很久实现);
根据我目前的调查:
1.shader编译和建立pipeline state目前是同步的,还不是异步的。
2.在WebGPU 规范中,GPUTexture,GPUBuffer,GPUDevice,GPUComputePipeline,GPURenderPipeline,GPUShaderModule是Serializable的,意味着能够传给worker。
那是否是如今已经能够在worker中使用它们,从而实现1,2,3呢?须要进一步验证!
引擎对于多线程的封装:
Parallelizing the Naughty Dog Engine using Fibers
Destiny’s Multi-threaded Renderer Architecture
与memory barriers相似,现代图形API须要程序员本身管理GPU的资源。
如Memory Management in Vulkan™ and DX12所示:
参考资料
Memory Management in Vulkan™ and DX12
根据WebGPU as low level graphics API:
WebGPU compares closest to Metal (probably since Apple is the one that originally proposed it)--both don't require manual memory management while DX12 and Vulkan do
不须要手动管理memory,WebGPU会帮咱们管理
包括两个步骤:
第一个pass遍历gameObjects,建立gbuffer;
第二个pass遍历lights,使用gbuffer计算光照。
相对于前向渲染,它的优势是只在屏幕上出现的像素中计算SHADING,从而使复杂度由O(M * N)将为O(M) + O(N)
由于支持MRT(多渲染目标),因此支持延迟渲染。
值得一提的是两个优化的方向:
在Investigation: Managing on-chip memory中提到:
第一个pass建立gbuffer后,gbuffer的数据会从on-chip内存移到主内存中;
第二个pass读取gbuffer时,将gbuffer的数据从主内存移到on-chip内存。
gbuffer的数据来回移动,形成了性能损失。
所以在Add render sub-passes中,建议增长render的子pass,在子pass中读取gbuffer,从而实如今建立和读取gbuffer期间,gbuffer的数据一直在on-chip内存中。
Minutes for GPU Web meeting 2019-10-28也讨论了这一点。
WebGPU可能会在extension中支持这个优化。
正如DirectX 11 Rendering in Battlefield 3所说:
Hybrid Graphics/Compute shading pipeline:
› Graphics pipeline rasterizes gbuffers for opaque surfaces
› Compute pipeline uses gbuffers, culls lights, computes lighting &
combines with shading
› Graphics pipeline renders transparent surfaces on top
延迟着色法
Optimizing tile-based light culling
DirectX 11 Rendering in Battlefield 3
在defer shading的第一个pass中,咱们将gameObject的几何数据(如Position, Normal等)和材质贴图数据(如从diffuse map中得到的diffuse)存到gbuffer中。
有了bindless texture的支持,咱们能够对此进行优化:
这样作的优势是:
1.减小了gbuffer的大小
2.只在可见的像素中,采样texture的数据,减小了采样次数
这样作也存在一些问题,不过都是能够解决的:
具体能够参考什么是deferred material shading?是否会在将来流行开来?:
1.多材质如何作deferred shading?总不能每一个像素作动态分支,一个一个判断吧。有人提出了作tile把像素区块合并,而后一次性dispatch,性能会高不少。至于vgpr,sgpr,lds占用率之类须要通盘考虑,偏向一边都会影响性能。
2.结果SSAO,SSR之类的post effect仍是须要用到normal,roughness之类的g-bufffer信息。应用上仍是须要权衡利弊。
以及参考Deferred Texturing:
What about mip levels, or derivatives?
具体能够参考Deferred Texturing -> Defer All The Things:
It stores only primitive IDs in its G-buffer; then in a later pass, it fetches vertex data, re-runs the vertex shader per pixel (!), finds the barycentric coordinates of each fragment within its triangle, interpolates the vertex attributes, then finally samples all the textures and does the shading work.
根据本文后面bindless texture的分析,目前WebGPU不支持bindless texture
或许可用texture 2d array代替bindless texture,从而使用WebGPU实现textureless defer render
Deferred Texturing
什么是deferred material shading?是否会在将来流行开来?
BINDLESS TEXTURING FOR DEFERRED RENDERING AND DECALS
Modern textureless deferred rendering techniques
这个技术应该是在[Siggraph15] GPU-Driven Rendering Pipelines中提出来的。它的思想是把渲染任务从CPU端移到GPU端,减小CPU与GPU的同步和数据传输,实现1个draw call就渲染整个场景,从而提升GPU的利用率。
GPU更细粒度的Visibility
不须要在CPU和GPU之间来回传递数据
绘制大量的静态物体
绘制人群
绘制模块化半自动生成内容
离线处理
1.分解gameObject的mesh为多个cluster
参考GPU Driven Pipeline — 工具链与进阶渲染
CPU
1.对gameObject进行粗粒度的frustum cull
2.使用persistent map buffer,准备GPU的数据
能够按照数据的类型,建立多个mapped buffer(如一个buffer存储人群的数据,另外一个buffer存储全部静态物体的数据)
3.使用virtual texture处理texture
全部的texture数据一次性所有准备好,只绑定一次texture
4.用indirect draw发起multi draw call,提交mapped buffer
WebGPU目前不支持multi draw,所以须要发起多个draw call,每一个draw call使用indirect draw提交对应的mapped buffer
GPU
1.对gameObject进行frustum cull和occlusion cull
2.对gameObject的cluster进行frustum cull和occlusion cull
3.修改index buffer,生成新的indices数据
根据Proposal: Run all index buffers through a compute shader validator:
I'm inclined to propose that WebGPU MVP doesn't support index buffers changed on the GPU, since this is quite a bit of headache, but eventually we can do that.
...
In an actual 1.0 release we'll absolutely need to support GPU-generated indices, there is no question here.
WebGPU MVP不会支持在GPU端修改index buffer,1.0版本会支持。
4.multi draw call
根据ExecuteIndirect investigation:
In order to issue draw calls on the CPU, there must be a synchronization point where the CPU waits for the GPU update to complete. This is particularly devastating for WebGPU, where if the CPU has to wait for the GPU, you miss your implicit present and now you're a frame late. Being able to issue these commands on the GPU directly means the rendering and update steps can be in sync.
在GPU端发起draw call能够去掉“CPU和GPU同步”的开销。
However, making it an extension seems valuable.
可能会在WebGPU extension中支持该特性。
GPU Driven Render Pipeline能够一次性取得全部mesh data,经过virtual texture能够取得全部texture,意味着整个场景只须要一次drawcall
[Siggraph15] GPU-Driven Rendering Pipelines
[GDC16] Optimizing the Graphics Pipeline with Compute
知乎大神MaxwellGeng关于GPU Driven Rendering Pipelines的相关文章1
知乎大神MaxwellGeng关于GPU Driven Rendering Pipelines的相关文章2
如今咱们介绍下GPU Driven Render Pipeline相关的概念和技术要点:
这个概念(简称为AZDO)出自approaching-zero-driver-overhead,它分析了OpenGL如何使用GPU实现CPU端0负载,具体包括下面几个方面:
介绍
该技术是为了在“CPU把数据传输到GPU“时减少数据传输的开销。
它包括下面的步骤:
1.映射GPU的buffer到CPU
2.在CPU端修改这个mapped buffer的数据(由于mapped buffer在shared memory中,CPU和GPU均可以访问它,因此要使用fence同步来确保GPU没操做这个buffer)
3.提交修改buffer数据的command
4.GPU执行该command,更新buffer数据
经过上面的步骤,再也不须要“从CPU传输新buffer的数据到GPU”了,减少了开销
参考资料:
Persistent mapped buffers
Persistent Mapped Buffers in OpenGL
WebGPU支持状况
有两种方式实现“CPU把数据传输到GPU“:
1.调用GPUBuffer->setSubData方法
该方法性能差,须要从CPU传输数据到GPU(WebGPU规范并无定义该方法,可是Chrome的WebGPU实现目前有该方法)
2.使用persistent map buffer技术
对于该方法,有如下的要点要说明:
1)不须要fence
WebGPU提供了GPUBuffer->unmap方法,该方法将buffer设置为unmapped state,使该buffer可以被GPU使用。
WebGPU应该在该方法中帮咱们作了fence同步的工做。
2)如何建立mapped buffer?
有两种方式建立:
a)调用GPUDevice->createBufferMapped方法,建立mapped buffer
Make it easier to upload data into buffers correctly指出:
createBufferMapped建立的buffer会使内存增长,所以须要destory它。
b)调用GPUBuffer->mapReadAsync,mapWriteAsync,将buffer设置为mapped buffer
Make it easier to upload data into buffers correctly指出,使用mapWriteAsync会形成一些问题:
in WebGPU, have an implicit present after rAF() returns
...
Using mapWriteAsync() requires you to wait on a promise, so if you do the naive thing and just wait on the promise inside rAF(), you’ll miss your present
...
Could we replace mapWriteAsync returning a Promise with it taking a callback that is guaranteed to execute before any submitted queue bundles are executed?
其中“rAF”指“requestAnimationFrame”
咱们根据示例代码来讲明下这个问题:
function frame(time){ ... const vertexBuffer = device.createBuffer({ ... usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC, }); vertexBuffer.mapWriteAsync().then((vertexBufferData) => { 设置vertexBufferData vertexBuffer.unmap(); 提交修改buffer数据的command到队列中 ... }); window.requestAnimationFrame(frame); }
由于mapWriteAsync是异步操做,而frame函数是同步操做,因此当执行到unmap时,可能已经执行了好几回frame(过了好几帧)。
在这几帧中,可能提交了其它的command到队列,WebGPU可能会在这几帧之间提交了队列中的command到GPU,GPU可能已经执行了其中的一些command。
执行unmap时,咱们预期GPU尚未执行其它的command,但实际上可能已经执行了。这样会形成不一样步的错误。
为了解决该问题,或许可使用await关键字,将mapWriteAsync变成同步操做。
示例代码以下:
async function frame(time){ ... var vertexBufferData = await vertexBuffer.mapWriteAsync(); 设置vertexBufferData vertexBuffer.unmap(); 提交修改buffer数据的command到队列 ... }
这里给出使用persistent map buffer技术的参考代码(来自Buffer operations)
(参考代码经过“调用GPUDevice->createBufferMapped方法”来建立mapped buffer):
//Updating data to an existing buffer(destBuffer) function bufferSubData(device, destBuffer, destOffset, srcArrayBuffer) { const byteCount = srcArrayBuffer.byteLength; const [srcBuffer, arrayBuffer] = device.createBufferMapped({ size: byteCount, usage: GPUBufferUsage.COPY_SRC }); new Uint8Array(arrayBuffer).set(new Uint8Array(srcArrayBuffer)); // memcpy srcBuffer.unmap(); const encoder = device.createCommandEncoder(); encoder.copyBufferToBuffer(srcBuffer, 0, destBuffer, destOffset, byteCount); const commandBuffer = encoder.finish(); const queue = device.getQueue(); queue.submit([commandBuffer]); srcBuffer.destroy(); }
参考资料
Make it easier to upload data into buffers correctly
Buffer operations
Minutes for GPU Web meeting 2019-10-21
介绍
以WebGPU为例,draw方法须要指定顶点个数、实例个数等数据,每次只能绘制一个gameObject(能够批量绘制多个实例instance):
void draw(unsigned long vertexCount, unsigned long instanceCount, unsigned long firstVertex, unsigned long firstInstance);
而indirect draw可使用buffer进行批量绘制多个gameObject(也能够批量绘制多个实例),这个buffer包含了每一个gameObject的顶点个数等数据:
void drawIndirect(GPUBuffer indirectBuffer, GPUBufferSize indirectOffset);
优势
1.能够在compute shader修改buffer的数据,从而实现gpu cull
2.减小了绘制gameObject的次数
3.减小了CPU和GPU之间的同步开销
WebGPU支持状况
支持Indirect draw/dispatch,相关讨论参考 Indirect draw/dispatch commands investigation
参考资料
What are the advantage of using indirect rendering in OpenGL?
vulkan Indirect drawing
INDIRECT RENDERING : “A WAY TO A MILLION DRAW CALLS”
Surviving without gl_DrawID
bindless texture和virtual texture能够结合使用,实现“只绑定一次texture”。
具体参见本文后面的说明:
其它->Bindless Texture
其它->Virtual Texture
在GPU端实现剔除。
1.建立persistent map buffer,indirect draw该buffer
2.在compute shader进行cull操做,将剩余的gameObject对应的draw call数据(如顶点个数)写到该buffer中
具体可参考GPU Driven Pipeline — 工具链与进阶渲染
经过判断目标是否在主相机的视锥体中,来实现剔除
经过判断目标是否被遮挡,来实现剔除
具体可参考Hi-Z GPU Occlusion Culling
在GPU端实现lod。
这个我没有仔细研究,读者能够参考相关资料:
谷歌搜索结果
GPU based dynamic geometry LOD
之前Ray Tracing只在离线渲染中使用(如制做CG电影,通常会使用path tracing来加快收敛速度),如今随着DXR(DirectX Raytracing)的发布,新增了Ray Tracing管线,提出了专为Ray Tracing设计的shader,再配合上新的降噪方法(如使用SVGF降噪算法或者NVDIA提供的基于AI的降噪SDK),可以实现实时的Ray Tracing!
彻底用Ray Tracing来渲染太耗性能,因此目前业界使用混合方案来实现实时Ray Tracing:
若是支持DXR,可使用“光栅化管线 + Ray Tracing管线”来实现;
若是不支持DXR,可使用“光栅化管线 + Compute管线(即便用compute shader)”来实现。
咱们能够把渲染分解为:
根据Is there some plan for Ray Tracing?:
There are not plan for ray-tracing for the forseeable future because WebGPU is meant to be extremely portable and ray-tracing isn't mature yet and is implemented only by a single hardware vendor for now.
WebGPU目前不支持Ray Tracing管线,所以只能使用“光栅化管线 + Compute管线(即便用compute shader)”来实现混合渲染。
能够按照下面的步骤:
1.普遍收集相关资料,对整个技术体系有初步的了解(读者能够看下面的“学习资料”)
2.参考Ray Tracing in One Weekend、Ray Tracing: The Next Week、对应的详解,使用fragment shader,从0实现Ray Tracing。
目前只须要渲染球体或者立方体就行了,不用渲染模型。
3.使用compute shader实现Ray Tracing
4.使用混合渲染(如使用光栅化实现GBuffer和直接光照,使用Ray Tracing实现阴影和反射)
5.实现降噪算法
直接实现SVGF颇有难度,能够先实现其中的子环节(如temporal anti aliasing、tone map、Edge-Avoiding À-Trous等),而后再把它们组装起来,实现SVGF
6.渲染模型
须要实现BVH
7.进一步研究和实现,探索path tracing、优化采样、优化光线排序和连贯性、支持更多的材质等方向
一篇光线追踪的入门
光线追踪与实时渲染的将来
Introduction to NVIDIA RTX and DirectX Ray Tracing
如何评价微软的 DXR(DirectX Raytracing)?
Daily Pathtracer!安利下不错的Pathtracer学习资料
Ray Tracing in One Weekend
Ray Tracing: The Next Week
Ray Tracing in One Weekend和Ray Tracing: The Next Week的详解
基于OpenGL的GPU光线追踪
Webgl中采用PBR的实时光线追踪
Spatiotemporal Variance-Guided Filter, 向实时光线追踪迈进
系统学习Ray Tracing的资料:Ray Tracing Gems
Investigation: Bindless resources提到:
Currently, in WebGPU, if a draw/dispatch call wants to use a resource, that resource must be part of a pre-baked "bind group" and then associated with the draw call inside the current render/compute pass. This means that all the resources that the draw/dispatch call could possibly access are explicitly listed by the programmer at the draw/dispatch site.
也就是说,咱们须要定义每一个texture在shader的binding,而后在每次提交command时,绑定该texture。
咱们来看具体的textureCube sample:
绑定的texture须要在shader中指定binding:
//在fragment shader中指定binding为2 const fragmentShaderGLSL = `#version 450 ... layout(set = 0, binding = 2) uniform texture2D myTexture;
在BindGroup中,设置binding为2的相关数据:
const bindGroupLayout = device.createBindGroupLayout({ bindings: [ ... { // Texture view binding: 2, visibility: GPUShaderStage.FRAGMENT, type: "sampled-texture" }] }); ... const uniformBindGroup = device.createBindGroup({ layout: bindGroupLayout, bindings: [ ... { binding: 2, resource: cubeTexture.createView(), }], });
把BindGroup设置到Pipeline中:
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }); const pipeline = device.createRenderPipeline({ layout: pipelineLayout, ... });
提交command时,设置该bind group和pipeline:
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); passEncoder.setPipeline(pipeline); passEncoder.setBindGroup(0, uniformBindGroup); ... passEncoder.endPass();
Investigation: Bindless resources提到:
"Bindless" is a model where the programmer doesn't explicitly list all of the available resources at the draw/dispatch site. Instead, a large swath of resources are made available to the GPU ahead of time (e.g. during application launch) and then shaders can access any/all of them at runtime.
能够将全部的texture设置到一个buffer中,将其传给GPU,而后shader能够在运行时操做任意的texture。
这样的好处是咱们不须要在每次提交command时绑定特定的texture,只须要绑定一次。
参考approaching-zero-driver-overhead->36页,咱们可使用texture 2d array代替bindless texture,只须要绑定一次texture 2d array,不须要在每次提交command时绑定特定的texture。
texture 2d array的优势参考:
为何要强调Texture2DArray在地形上的应用?
缺点是texture array中的每一个texture的大小、格式要相同,而bindless texture没有该要求。
为了解决该缺点,咱们能够按照大小和格式,把texture划分为多组,对应多个texture 2d array。
从Minutes for GPU Web meeting 2019-08-12中得知,目前还未决定什么时候实现bindless texture,可能实现为extension,可能在1.0版本后实现。
因此目前可考虑用texture 2d array做为替代品
OPENGL AZDO : BINDLESS TEXTURES : BATCHING PROBLEM SOLVED
把全部要用到的texture拼到一块儿,组成physic texture;
经过索引,只把当前要用到的texture加载到内存中。
1.只绑定一次texture
2.组成physic texture的子纹理的格式和mipmap等能够不同;
3.减少了内存占用(内存中只有当前使用的texture)
由于要不断地在内存中加载/卸载texture,因此增长了IO开销
有人提出了Investigation: Sparse Resources, 但愿WebGPU增长操做堆heap的API。不过目前没有回应。
我目前不清楚WebGPU是否能实现virtual texture
approaching-zero-driver-overhead -> Sparse Texture
知乎->Virtual Texture Tools & Practices
关于对virtual texture的浅显认识
根据Investigation: Tessellation:
Let's wait until after the release of a MVP
WebGPU应该会在MVP后考虑加入Tessellation shader
NVDIA在Turing架构中推出了新的管线,用来替代光栅化管线。新管线只保留了Pixel Shader(即fragment shader),新增了Task Shader和Mesh Shader,以下图所示:
新管线更适合于GPU Driven Render Pipeline的理念,包括如下的特性:
相似于Compute管线(compute shader),具备强大的计算能力;
把Mesh分解为Meshlet(相似于GPU Driven Render Pipeline中提到的Cluster),更好地支持cluster cull。
根据Investigation: Tessellation中的讨论,由于Vulkan和Metal还没支持Mesh Shader,因此WebGPU至少要等它们支持后才会考虑支持。
DX12支持了Mesh Shader
Introduction to Turing Mesh Shaders
怎么评价nvidia 推出mesh shader管线?