tag:blogger.com,1999:blog-3986825253657787082024-03-12T19:03:56.996-07:00Diary of a Graphics ProgrammerWolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.comBlogger140125tag:blogger.com,1999:blog-398682525365778708.post-4113142262639167832020-11-20T08:48:00.002-08:002020-11-30T19:45:31.100-08:00Catching Up / History of The Forge / GPU Zen / Ray Tracing / Holiday Dinner<p>I just realized I haven't posted here since 2018. There is generally less sharing of information happening now compared to let's say 10 years ago and I seem to have become one of the people who shares less. In my defense, I can bring up good reasons :-) : </p><p>Being part of an ever-growing company with increasing business and HR needs makes it harder for me to focus on the technical aspects of our work in a blog post. Dealing with the development of The Forge, H1-B or O1 Visas, 401k plans, lunch reimbursements, deciding what health insurances to offer, writing contracts and invoices and keeping track of all the money movements, deciding where the holiday dinner should happen, and spending time with interviewing all the new hires, the landlord of our office building, bookkeepers, tax advisors, IP lawyers, and other lawyers leaves much less time for this. Before I can write the blog post I need to decide how we will have a Holiday dinner this year. </p><p>With COVID-19 I also began training in two more Martial Arts after practicing Tang Soo Do for more than 11 years: Gum Do and Tai Chi. When working from home it helps with the stress levels to just step outside and listen to the wooshing sound of a Sword splitting the air. Tai Chi is something I do together with my wife, which makes this activity even more valuable.</p><p>So now that my defense was laid out, let's get back to business.</p><p>One of the interesting things we did was open-source our internal rendering framework. The company is using an internal framework since day 1 of its existence. </p><p>This rendering framework is kind of like the "beehive mind" of the company. Over the years some of the best people in the company were invited to extend this framework and then the whole company benefitted from its existence as a blueprint for typical rendering tasks.</p><p>In 2017 we decided to write a new rendering framework from scratch because we wanted to cover the new Graphics APIs better and needed fundamental changes in the architecture of our old framework. This framework was open-sourced at the beginning of 2018 and since then steadily improved. We named it "The Forge" because with its help we can create new tools, game engines, experiences. It is shipping in AAA custom game engines, smaller things like editors or educational apps, in the future on hundreds of million devices as the foundation of business frameworks. We used it also to write a new game engine for Supergiant that shipped Hades so far on PC, macOS, and Switch.</p><p><a href="https://github.com/ConfettiFX/The-Forge" target="_blank">https://github.com/ConfettiFX/The-Forge</a><br /></p><p>With every release of The Forge, I write release notes in the style of blog posts. I offer an opinion on why we implemented things the way they were or often describe what went wrong and how we had to rewrite the same sub-system 3 or more times. I describe our technical successes and our failures :-)</p><p>I am hoping the release notes are partially making up for the lack of blog posts here. After all, one can look at the source code in its entirety and see what I am talking about. Something my blog posts didn't always offer in the past. Generally, the lack of source code makes a lot of presentations or descriptions of technical implementations less valuable. </p><p>Regarding GPU Zen: helping aspiring and experienced graphics programmers was always a goal of mine. I consider The Forge now more useful than a new edition of GPU Zen. It provides the actual code and you can see it working in the games it shipped with or will be shipping with. Compared to a conference talk or a book chapter, this is really what everyone would want to see. I believe a presentation that outlines a technique accompanied by a math equation doesn't offer that same level of usefulness. Showing source code is the ultimate way to share graphics programming knowledge.<br />So you can think of "The Forge" in GitHub as the next-gen GPU Zen. That doesn't mean we won't do another GPU Zen in the future. It just means we have to think about the value proposition this future book will offer.</p><p>Ray Tracing: my last blog post on this topic made some waves in the industry. At some point, I couldn't really dedicate as much time to this topic as I wanted. It came up in Advisory boards, there were IP related projects we worked on regarding Ray Tracing and we -as an industry- eventually succeeded in getting a more open interface. <br />My company gave a talk on cross-platform Ray Tracing where the macOS / iOS ray tracing run-time was extended to be on par with the DXR / RTX run-time (available in the GitHub repository). This was mostly meant for tool development but can also be used in a cross-platform game engine. I think it also shows a blueprint of how to do Ray Tracing with the newer interfaces on various platforms.</p><p>Holiday dinner: before I wrote this blog post, I organized the first Holiday dinner via Skype. My colleagues in the PST time zone will dial in via Skype. They will get food via DoorDash and the company will reimburse it. This will be a good time to see how everyone's family has grown, what the dogs are doing etc.. :-) I will then repeat a dinner/breakfast with the colleagues in the different time zones we cover.</p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p>Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com2tag:blogger.com,1999:blog-398682525365778708.post-92019359828518665262018-09-07T12:02:00.000-07:002018-09-07T12:02:21.595-07:00Ray Tracing without Ray Tracing API<div dir="ltr" style="text-align: left;" trbidi="on">
<span style="font-family: "arial" , "helvetica" , sans-serif;">Following up on my last blog post, where I stated that a Ray Tracing API is bad for game developers and publishers because of the increase in QA effort that it will bring: based on the last 20+ years of graphics development, it is easy to project that when a large part of the ray tracing codebase is owned by a hardware vendor, there will be various bugs introduced with each driver release. For game developers and game engine middleware providers, it will make it expensive to support such an API.<br /><br />The current main use cases for real-time Ray Tracing in games are Shadows, Reflections, and Ambient Occlusion.<br />To find out how easy it would be to avoid using the Ray Tracing APIs, we wanted to see how fast "native" implementations would be compared to the ones that are using Ray Tracing APIs.<br /><br />At the time, Kostas Anagnostou <a href="https://twitter.com/KostasAAA">@KostasAAA</a> had experimented with getting hybrid Ray Tracing running on lower-end GPUs. I was talking to him because he was supposed to write an article for GPU Zen 2 about a culling system. I asked him if he would be interested in integrating his experiments in the Confetti rendering framework The Forge, so that we can run them on more platforms like Linux (VLKN), macOS / iOS (Metal 2) and then the XBOX One. When he started working on this, he re-visited some of his former ideas and improved the performance substantially. He wrote a blog post here: <a href="https://interplayoflight.wordpress.com/2018/09/04/hybrid-raytraced-shadows-part-2-performance-improvements/">Interplay of Light</a><br /><br />Today a new release of The Forge came out that supported his approach to Hybrid Ray Traced Shadows. It is running on all platforms that The Forge supports and the iOS version is running astonishingly well.</span><br />
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;">We have reason to believe that when it comes to hybrid Ray Traced Shadows, at the moment it is better to not use a Ray Tracing API. When using DXR, we expect Hybrid Ray Traced Shadows to run on all hardware apart from the GeForce RTX with subpar performance. We expect our implementation when compared to a DXR version both running on a GeForce RTX, to perform admirably and most important practically useful in games.</span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<span style="font-family: "arial" , "helvetica" , sans-serif;">Here is a screenshot running on an iPhone 7 with the Sponza scene and iOS 11.4.1 (15G77). Resolution is 1334 x 750.</span><br />
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2cf-UqQJ4XNlG5rApghK8KpebL4vZ6uuA8gv16Db3sH6DET3u7vyQLAkaxuxYx8YqHmFOrFsidoNuOvsonGMe7eB6_bGgiPk65EDelRjZ_IiiFMwjzKZFgsThXqOi4wWkDmIuni8mpNQ/s1600/09a_HRT_Shadows_iOS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="750" data-original-width="1334" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2cf-UqQJ4XNlG5rApghK8KpebL4vZ6uuA8gv16Db3sH6DET3u7vyQLAkaxuxYx8YqHmFOrFsidoNuOvsonGMe7eB6_bGgiPk65EDelRjZ_IiiFMwjzKZFgsThXqOi4wWkDmIuni8mpNQ/s400/09a_HRT_Shadows_iOS.png" width="400" /></a></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;">This approach was developed on a PC running Windows DX12 / VLKN; we just ported the PC version to macOS / iOS and the other target platforms. In case we would spend some time doing iOS specific optimizations there might be opportunities to improve framerate.</span><br />
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;">The next step is to develop a Hybrid Ray Traced Reflection approach.</span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;">The general hope is to make it possible to have Hybrid Ray Traced Shadows, Reflections, and Ambient Occlusion on all GPUs and on all platforms available.</span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;">Let's see how far we can get ...</span><br />
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
<div>
<br /></div>
<div>
<br /></div>
</div>
</div>
</div>
</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com3tag:blogger.com,1999:blog-398682525365778708.post-75652898257569156862018-04-05T08:19:00.000-07:002018-04-07T17:56:13.574-07:00Ray Tracing with the DirectX Ray Tracing API (DXR)<div dir="ltr" style="text-align: left;" trbidi="on">
<span style="font-family: "arial" , "helvetica" , sans-serif;">Experimental DXR support was added to <a href="https://github.com/ConfettiFX/The-Forge" target="_blank">The Forge</a> today. Let's think about this for a second by starting with </span><span style="font-family: "arial" , "helvetica" , sans-serif;">a few quotes from a now famous book, that many people have read in the last couple of days/weeks. The quotes are on the first page of the book "Ray Tracing in One Weekend" by Peter Shirley:</span><br />
<blockquote class="tr_bq">
<span style="font-family: "arial" , "helvetica" , sans-serif;">I've taught many graphics classes over the years. Often I do them in ray tracing, because you are forced to write all the code but you can still get cool images with no API.</span></blockquote>
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;">Later on the same page, he says: </span><br />
<blockquote class="tr_bq">
<span style="font-family: "arial" , "helvetica" , sans-serif;">When somebody says "ray tracing" it could mean many things.</span></blockquote>
<span style="font-family: "arial" , "helvetica" , sans-serif;">If you read the following pages of this book, you can see that writing a ray tracer might have a low level of complexity. This is why computer graphics students learn ray tracing before they are exposed to the world of Graphics APIs.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;">So the first question that goes through one's head when looking at DXR is, why do we need now an API for Ray Tracing? The obvious follow-up is, why is that beneficial for game developers?</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;">I could just finish this blog post now and say "there is no benefit for game developers because it restricts the creative process of making games distinguishable and that is obvious" and it will raise the cost of game development because there is one more API to take care off and move on with my day :-) I could say something along the lines of "requesting an API for ray tracing is like selling refrigerators at Antartica" because in this sentence both comparisons have about the same level of complexity.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;">I could also say something along the lines of "you want to ruin the whole computer graphics experience by letting them learn a ray tracing API first? Do you not have any heart?". </span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;">Instead, let's just ask the question why do we need a ray tracing API?</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;">Hardware vendors will say: because we have to map the complexity of ray tracing to our hardware; we need an API. So far we haven't dedicated any silicon to ray tracing (except PowerVR) because rasterized graphics are commercially at this moment more successful. Just in case this time (compared to the 10+ times before) it catches on, we are promising to add ray tracing silicon as long as you let us hide all this for a while behind an API. </span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;">OS vendors will say: we want to be at the forefront of development and we want to bind ray tracing to our platform, let's define an API that everyone has to use to bind them also to our platform. If they want to develop for a different platform they will have to rewrite all their code or use a different API that is hopefully less powerful than ours and those two facts might keep them from doing it ...</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;">Then there are game development studios and then graphics programmers. Commonly described as wise and matured, drawing pictures of cats or flowers on paper and screens and living out their creative energies by writing ray tracing code at <a href="https://www.shadertoy.com/" target="_blank">ShaderToy</a>. ShaderToy is a showcase of the possibilities of ray tracing and all the opportunities it might have in games. It shows the wide range of approaches with their respective pros and cons and what creative people can really achieve if you offer them the freedom to do it.</span><br />
<br />
<span style="font-family: "arial" , "helvetica" , sans-serif;">Making games distinguishable on a visual level is a common requirement, similar to movies. There is not one ray tracing technique that can take over the role of being generic. There is no way any API could settle on a subset of ray tracing that could be acceptable for a large group of game developers. We *might* all be able to agree on a BVH structure but there is where it ends. Why would any game developer want to black-box ray tracing. This is comparable to say the following: "a game developer only needs one pixel shader for a material like metal, one pixel shader for a material like skin and all the graphics card drivers only need to be optimized for exactly those pixel shaders. That would make life so much easier.". I put this sentence in quotes because this actually happened not long ago and the rationalization was publicly expressed. Game developers were able to prevent this from happening. </span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;">As a game developer and graphics programmer, my interest is always in making commercially successful products that are appealing to gamers and distinguishable from their competitors in appearance and gameplay. Deciding about the fate of ray tracing by creating an API that black boxes parts of it is counterproductive to this effort and not in the interest of small or big game developers like EA, Ubisoft, R* and others.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;">Then there is the cost factor. Supporting another graphics API will be expensive. As usual, the expense is not in the initial implementation but in the maintenance and QA. So in case someone licenses middleware, the cost of maintenance is still there.Treating ray tracing as an additional feature set to the common graphics APIs should be cheaper.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br />DXR is in the proposal stage at the moment. Microsoft expressed interest in getting feedback from developers and they would like to change it. I would like to encourage people and game development companies to raise their concerns. <br />On a technical level, I would prefer to extend the existing APIs "enough" and offer more flexibility through additional features like for example a ray tracing feature level, instead of adding a black box for ray tracing. As soon as the special hardware is available it can be exposed through extensions as well.<br /><br />This will give game developers the creative freedom they need and at the same time offer the opportunity to invest into it easier.</span><br />
<br />
<br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br />Addendum 1:<br />One more thought added: in the moment if you want to ship a game on the PC, there is a high chance you will have to go to a hardware vendor to ask for driver updates or performance improvements. If Ray Tracing (RT) gets its own API, we will have to ask for driver updates for RT as well. Obviously, hardware vendors might want something in return for fixing drivers or making sure your RT code runs fast. This is how it works on PC with Graphics APIs.<br /><br />We work quite often with smaller developers and bigger developers. Bigger developers just go to the hardware vendor and ask for driver updates and dependent on how "important" their game is, they will get them. Smaller developers hardly get noticed in the process. Over the years most of us agreed that the driver / API situation is not good; Now if we add a RT API, we are creating the same ecosystem for a second API on PC ....<br /><br />On an economic level, this is very much noticeable by publishers and therefore we can explain this to Bethesda, EA, R* and others. If a game launches on a "broken" driver, the sales of that game will be lower. A game publisher can predict the amount of money that a RT API will cost them during the launch of a game. If we add a RT driver/API we have two opportunities to tank sales at the beginning of the lifetime of a game. Most of us saw their game launching with "broken" drivers. Now extend the experience to a second API.<br /><br />From that perspective an RT driver is a huge economic factor that is put on the shoulders of the game developers ... you could say whoever wants to add another driver to the PC ecosystem increases the cost of game development on PC substantially ... although it is hard to specify how much.</span> <span style="font-family: "arial" , "helvetica" , sans-serif;"><span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span> <span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span></span> <span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com5tag:blogger.com,1999:blog-398682525365778708.post-6891805168413170742018-03-30T13:13:00.014-07:002021-06-12T10:54:12.910-07:00Triangle Visibility Buffer<div dir="ltr" style="text-align: left;" trbidi="on">
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">A Rendering Architecture for high-resolution Displays and Console Games</span></h2><div>-----------------------------------</div><div><span face=""arial" , "helvetica" , sans-serif">Document History:</span></div><div><span face=""arial" , "helvetica" , sans-serif">- Initial Published March 30th, 2018</span></div><div><span face=""arial" , "helvetica" , sans-serif">- Updated January 22th, 2021</span></div><div><span face=""arial" , "helvetica" , sans-serif">- Updated June 4th, 2021 with a simplified degenerate triangle removal</span></div><div><span face=""arial" , "helvetica" , sans-serif">- Updated June 12th, 2021 links to the new Forge Shader Language shaders should work now</span></div><div><span face=""arial" , "helvetica" , sans-serif">-----------------------------------</span></div><div><span face=""arial" , "helvetica" , sans-serif"><br /></span></div>
<div>
<span face=""arial" , "helvetica" , sans-serif">The "Triangle Visibility Buffer" is a research project at our company since September 2015. This blog entry serves the purpose of outlining the current status.</span></div>
<div>
<span face=""arial" , "helvetica" , sans-serif">We called it Triangle Visibility Buffer because this rendering technique is keeping track of triangles through the whole rendering pipeline and stores visibility of every opaque triangle in the scene in a buffer. The technique is very suitable for target hardware platforms that have only limited amounts of high-speed memory to store render data but need to support large resolutions. It is also most suitable for rendering on high-resolution displays with 4k, 5k, or 8k resolutions.<br />You can find the source code accompanying this blog entry at</span><br />
<div>
<div style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<a href="https://github.com/ConfettiFX" style="text-decoration: none;"><span face=""arial" , "helvetica" , sans-serif" style="background-color: transparent; color: #1155cc; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre;">https://github.com/ConfettiFX</span></a></div>
<br />
<span face=""arial" , "helvetica" , sans-serif">In this repository, there are PC DirectX 12 / Vulkan, Linux Vulkan and macOS / Metal 2 implementations available. On request, there is also source code for various console platforms available. The following text will only refer to the DirectX 12 implementations for consistency. Finding the Vulkan and macOS counterparts in the code base is left to the reader. <br /><br />Following the data flow in the Triangle Visibility Buffer rendering pipeline, the first stage is the triangle removal stage, which culls invisible triangles from the data set.</span><br />
<span face=""arial" , "helvetica" , sans-serif"><br /></span></div>
<div>
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Multi-View Triangle Cluster Culling / Triangle Filtering</span></h2>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">The number of polygons increases every year in games. Hardware can become bottlenecked in the Command Processor, in case empty draw calls are spawned, in the vertex shader with the number of vertices to transform, with backface culling and clipping and or in the rasterizer because small triangles that are not visible make it primitive bound.<br />To reduce the number of triangles that are going into the graphics pipeline in general, a triangle removal stage is added as a first step to the whole rendering system. This way the graphics pipeline can be better utilized with the visible triangles.<br />Triangle removal is not a new concept and was utilized on certain platforms already a decade ago or probably even longer. Due to the triangle complexity of modern games, it was revived in recent years in talks by [Chajdas][Wihlidal] because modern hardware seems to benefit from it. <br />The techniques used in the demo to remove triangles consist of the following consecutive stages:<br />- Cluster culling: cull groups of 256 triangles with a similar orientation on the CPU<br />- Triangle filtering: cull individual triangles in an async compute shader individually<br />- Draw call compaction: remove empty draw calls with no triangles left and order the remaining draw calls sequentially in a compute shader<br /><br />The demo implementation does all the above for the main camera view and the shadow map view at the same time. We call this Multi-View Triangle removal.</span><br />
<span face=""arial" , "helvetica" , sans-serif"><br /></span>
<br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Triangle Cluster Culling on the CPU </span><span face=""arial" , "helvetica" , sans-serif" style="font-size: 16pt; white-space: pre;"> </span></h3>
<span face=""arial" , "helvetica" , sans-serif">Triangle cluster culling -running in this case on the CPU- removes invisible chunks of 256 triangles with similar orientation. This is done by picking the first 256 triangles of a mesh and then testing them against a visibility test cone. In the following Image 1, the triangles and the face normals of those triangles are represented by the orange lines.<br /><br /><br /></span></div>
<span face=""arial" , "helvetica" , sans-serif"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpwUKDIyS-HSsf1BpUIBRnDnenXKGipWvisZFQnkNqFofXkPXd-mAc0ZewRK9z7MP8LIPovcp9VZQYonaG7ag5WwSj7QI3xRW0omz5alL2ixl0HmsIvD468VF9ZTyjfPtx_br2oGiVGBk/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="611" data-original-width="1271" height="193" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpwUKDIyS-HSsf1BpUIBRnDnenXKGipWvisZFQnkNqFofXkPXd-mAc0ZewRK9z7MP8LIPovcp9VZQYonaG7ag5WwSj7QI3xRW0omz5alL2ixl0HmsIvD468VF9ZTyjfPtx_br2oGiVGBk/w400-h193/TriangleCulling_1.PNG" width="400" /></a><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 1 - Triangle Cluster Culling - The Test Cone</span></div></span>
<span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<span face=""arial" , "helvetica" , sans-serif">The light blue triangle is the test cone. In case the eye or the camera is inside this test cone, the triangles are considered not visible. To find the center of the cone, we start out by accumulating the face normals of the triangle cluster negatively as shown in Image 2.</span></div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBcXCdJazUidon1O9GrL-uAoRvmk0JWatmVL20cz0O3U0lXZbAkFES6hQd_dQrxo-nN3Z5wH8zJOaY8F2Kh0gtuXDWiPll8-ecBASRLa9_o-M5XBnJEEcMDH5EFDuIpUvldvcnm-b_TYU/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1032" data-original-width="2048" height="201" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBcXCdJazUidon1O9GrL-uAoRvmk0JWatmVL20cz0O3U0lXZbAkFES6hQd_dQrxo-nN3Z5wH8zJOaY8F2Kh0gtuXDWiPll8-ecBASRLa9_o-M5XBnJEEcMDH5EFDuIpUvldvcnm-b_TYU/w400-h201/TriangleCulling_2.PNG" width="400" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 2 - Triangle Cluster Culling - Negatively accumulating Triangles to find the cone center</span></div><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><br /></div><span face=""arial" , "helvetica" , sans-serif"> </span><br />
<span face=""arial" , "helvetica" , sans-serif">To calculate the cone open angle, the most restrictive triangle planes are taken from the triangle cluster as shown in Image 3.</span> </div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgephlcnsgxUE9Con8JArMpyE258sOMPXNA3I0X2YZhCwaQATs8Ya0HTBa0DyMmqSfwnKKF_DUowN4Yfjl3oh6dNkaEY_YJaI3Pucu66nS1gsdQiefZLOl7iKpqEGYlmOtdbzdYgcBZlq0/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1127" data-original-width="2689" height="168" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgephlcnsgxUE9Con8JArMpyE258sOMPXNA3I0X2YZhCwaQATs8Ya0HTBa0DyMmqSfwnKKF_DUowN4Yfjl3oh6dNkaEY_YJaI3Pucu66nS1gsdQiefZLOl7iKpqEGYlmOtdbzdYgcBZlq0/w400-h168/TriangleCulling_3.PNG" width="400" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 3 - Triangle Cluster Culling - Calculating the Test Cone Planes</span></div>
<span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<span face=""arial" , "helvetica" , sans-serif">If the camera or eye is in the area of the test cone, the triangle cluster is not visible and can, therefore, be removed.<br />The effectivity of this simple cluster culling mechanism depends on the scene data. In case there are a lot of triangles that face in a similar direction, it will be more efficient, in case triangles are facing in different directions, it is lower.<br />The test scene -San Miguel- in the Visibility Buffer demo doesn’t have many clusters of triangles that are facing in a similar direction and therefore triangle cluster culling is not very efficient. This might be different with geometry that is tessellated by hardware. By default, triangle cluster culling is due to poor efficiency switched off in the demo.<br />The code for cluster culling can be found in <a href="https://github.com/ConfettiFX/The-Forge/blob/master/Examples_3/Visibility_Buffer/src/Visibility_Buffer.cpp">Visibility_Buffer.cpp</a>, and therein triangleFilteringPass() and then cullCluster().</span><br />
<span face=""arial" , "helvetica" , sans-serif"><br /></span></div>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Triangle Filtering on the GPU</span></h3>
<div>
<span face=""arial" , "helvetica" , sans-serif">To remove invisible triangles, an async compute shader is executed on each triangle. It runs 256 triangles in one batch and tests if triangles are</span></div>
<div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<ul style="text-align: left;">
<li><span face=""arial" , "helvetica" , sans-serif">Degenerate </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Back-facing </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Clip through the near clipping plane of the view frustum </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Clip through or are outside of the view frustum </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Are too small to cover a pixel center or sampling point </span></li>
</ul>
<span face=""arial" , "helvetica" , sans-serif">Triangles that pass all tests will be appended to an index buffer. This index buffer is then called a filtered index buffer.<br />All the source code for this section can be found in the shader <a href="https://github.com/ConfettiFX/The-Forge/blob/master/Examples_3/Visibility_Buffer/src/Shaders/FSL/triangle_filtering.comp.fsl">triangle_filtering.comp.fsl</a> and in there in the function FilterTriangle().</span><br />
<span face=""arial" , "helvetica" , sans-serif"><br /></span></div>
<h4 style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;">Degenerate and Back-facing Triangles</h4>
Triangles that face away from the viewer are not visible and therefore need to be culled. This implementation uses a technique described by [Olano]. He calculates the determinant of the 3x3 clip-space matrix consisting of the three vertices of the triangle. In case the determinant is larger than 0, it has no inverse and therefore is back-facing and can be culled. </div><div>If the determinant is 0, the triangle is degenerate, or is being viewed edge-on and has zero screen-space area. Degenerate triangles might be introduced in hardware tessellation or in case there is a bug in the art asset pipeline.</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZs_qiX135rvp_398OAxMTMh_ZjqeVdhJ2UfdNYNnFyCJ-9kJKDi33lWZCeI9-ALSkRTV0-tFO_d6Vfp8FPrO4rF09L0beptLTD6MxbRQsYdCJXkO_JBimUbgSTNbwhLw2uwXtLWcfdgw/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="467" data-original-width="1867" height="100" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZs_qiX135rvp_398OAxMTMh_ZjqeVdhJ2UfdNYNnFyCJ-9kJKDi33lWZCeI9-ALSkRTV0-tFO_d6Vfp8FPrO4rF09L0beptLTD6MxbRQsYdCJXkO_JBimUbgSTNbwhLw2uwXtLWcfdgw/w400-h100/Backface+Culling.PNG" width="400" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 3 - Backface Culling</span></div><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><pre class="code highlight" lang="plaintext"><span style="font-family: courier; font-size: x-small;"><span class="line" id="LC1" lang="plaintext">#if ENABLE_CULL_BACKFACE</span>
<span class="line" id="LC2" lang="plaintext"> // Culling in homogenous coordinates</span>
<span class="line" id="LC3" lang="plaintext"> // Read: "Triangle Scan Conversion using 2D Homogeneous Coordinates"</span>
<span class="line" id="LC4" lang="plaintext"> // by Marc Olano, Trey Greer</span>
<span class="line" id="LC5" lang="plaintext"> // http://www.cs.unc.edu/~olano/papers/2dh-tri/2dh-tri.pdf</span>
<span class="line" id="LC6" lang="plaintext"> float3x3 m = float3x3(vertices[0].xyw, vertices[1].xyw, vertices[2].xyw);</span>
<span class="line" id="LC7" lang="plaintext"> if (cullBackFace)</span>
<span class="line" id="LC8" lang="plaintext"> cull = cull || (determinant(m) >= 0);</span>
<span class="line" id="LC9" lang="plaintext">#endif</span></span></pre></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">Back-face culling can potentially remove 50% of the geometry.</span><br />
<span face=""arial" , "helvetica" , sans-serif"><br /></span>
<br />
<h4 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Near Plane Clipping</span></h4>
</div>
<span face=""arial" , "helvetica" , sans-serif">Triangles that are in front of the near clipping plane of the view frustum need to be culled. To check, if the triangle is in front of the near clipping plane, the following code checks if the w component of each vertex is below zero. In case this is true, it flips the w component, to make sure it is not projected on two sides of the screen.</span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">for (uint i = 0; i < 3; i++)</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">{</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> if (vertices[i].w < 0)</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> {</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">++verticesInFrontOfNearPlane;</span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> // Flip the w so that any triangle that straddles the plane </span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> // won't be projected onto two sides of the screen</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> vertices[i].w *= (-1.0);</span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> }</span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">…</span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">}</span></span></div>
<span face=""arial" , "helvetica" , sans-serif">If all three vertices of the triangle are in front of the near clipping plane, the triangle gets culled:</span><br />
<span face=""arial" , "helvetica" , sans-serif"><br /></span>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">if (verticesInFrontOfNearPlane == 3)</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">return true;</span></span></div>
<div style="text-align: left;">
</div>
<h4>
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h4>
<h4>
<span face=""arial" , "helvetica" , sans-serif"><br /></span><span face=""arial" , "helvetica" , sans-serif"> Frustum Culling</span><span face=""arial" , "helvetica" , sans-serif"><br /></span></h4>
<span face=""arial" , "helvetica" , sans-serif">Triangles whose vertices are all on the negative side of the clip-space cube are outside the view frustum and therefore can be culled. </span><br />
<span face=""arial" , "helvetica" , sans-serif"><br />The following image 4 shows the camera situated close to a table in the San Miguel scene and the remaining triangles after frustum culling.</span></div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZX5T8j_6kYHLpZPnCBjvPZuPZJaBJNIvWBvN37B6LvXmzD5cASQWIoiEchCdXqCczYiO7n6k5ANjlr6BkR-w5OXvN-W-R9InSbrkoIZdt_HMWbFVHG_pF0150Y3dbDZ2OlKwpqn_Tf5Y/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="600" data-original-width="882" height="272" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZX5T8j_6kYHLpZPnCBjvPZuPZJaBJNIvWBvN37B6LvXmzD5cASQWIoiEchCdXqCczYiO7n6k5ANjlr6BkR-w5OXvN-W-R9InSbrkoIZdt_HMWbFVHG_pF0150Y3dbDZ2OlKwpqn_Tf5Y/w400-h272/TriangleFrustumCulling.png" width="400" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 4 - Triangle Frustum Culling</span></div><br /><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><div style="text-align: center;"><br /></div></div>
<span face=""arial" , "helvetica" , sans-serif">The demo code in the GitHub repository allows to freeze triangle frustum culling and then to move the camera away to see the results.</span><br />
<span face=""arial" , "helvetica" , sans-serif">To make the comparison against the clip-space cube more efficient, all the vertices of a triangle are transformed into the normalized 0..1 space first.</span>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">...</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">vertices[i].xy /= vertices[i].w * 2;</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">vertices[i].xy += float2(0.5, 0.5);</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">…</span></div>
<span face=""arial" , "helvetica" , sans-serif">If any vertices of a triangle are outside of this 0 .. 1 range, it will be culled.</span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-size: 10pt; white-space: pre;"><span style="font-family: "courier new" , "courier" , monospace;">...</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">float minx = min(min(vertices[0].x, vertices[1].x), vertices[2].x);</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">float miny = min(min(vertices[0].y, vertices[1].y), vertices[2].y);</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">float maxx = max(max(vertices[0].x, vertices[1].x), vertices[2].x);</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">float maxy = max(max(vertices[0].y, vertices[1].y), vertices[2].y);</span></div>
<span style="font-family: "courier new" , "courier" , monospace;"><b style="font-weight: normal;"><br /></b> </span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">if ((maxx < 0) || (maxy < 0) || (minx > 1) || (miny > 1))</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">return true;</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">...</span></div>
<h4 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h4>
<h4 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Small Primitive Culling</span></h4><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<div style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Triangles are considered too small, in case they do not overlap with a pixel center or a sample point after projection. </span></div>
<div style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">The following image shows very small triangles in-between sampling points:</span></div><div style="text-align: left;"><span face=""arial" , "helvetica" , sans-serif"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisG4a3Y45p2TfyxdSt_B6TYbi8q5Brn7b87uszNRegc1ehkluBakU1fRaZZ4H7RsqnAfSoGEkXYrbVvhhW8xk_JpKGStIVvGaA90eqlQfoP9oZfiJOC26Q_fU9Sz3R6y_MPertPDfEefc/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="375" data-original-width="365" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisG4a3Y45p2TfyxdSt_B6TYbi8q5Brn7b87uszNRegc1ehkluBakU1fRaZZ4H7RsqnAfSoGEkXYrbVvhhW8xk_JpKGStIVvGaA90eqlQfoP9oZfiJOC26Q_fU9Sz3R6y_MPertPDfEefc/w390-h400/SmallPrimitives.PNG" width="390" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 5 - Small Primitive Culling</span></div></span></div></div>
<br />
<span face=""arial" , "helvetica" , sans-serif"><div><span face=""arial" , "helvetica" , sans-serif">Although the triangles in image 5 are too small to be visible, the rasterizer might still spend cycles dealing with them. In case the </span>GPU can only deal with one primitive per cycle per tile, the cost of even one invisible triangle can become high.</div><div>This is why small triangles need to be removed before they hit the graphics pipeline.</div><div><br /></div></span></div><div><span face=""arial" , "helvetica" , sans-serif">The following code for the small triangle test generates a bounding box in screen-space around a triangle, then uses the x and y value of this bounding box to see if it overlaps a pixel center or sampling point in the case of MSAA.</span> <br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">// Scale based on distance from center to msaa sample point</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">int2 screenSpacePosition = int2(screenSpacePositionFP * (SUBPIXEL_SAMPLES * samples));</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">minBB = min(screenSpacePosition, minBB);</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">maxBB = max(screenSpacePosition, maxBB);</span></div>
<span style="font-family: "courier new" , "courier" , monospace;"><b style="font-weight: normal;"><br /></b> </span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">if (any(((minBB & SUBPIXEL_MASK) > SUBPIXEL_SAMPLE_CENTER) &&</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">((maxBB - ((minBB & ~SUBPIXEL_MASK) + SUBPIXEL_SAMPLE_CENTER)) < </span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> (SUBPIXEL_SAMPLE_SIZE))))</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">{</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">return true;</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">}</span></div>
<span face=""arial" , "helvetica" , sans-serif"><br /></span>
<br />
<h4 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h4>
<h4 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Multi-View Triangle Removal</span></h4>
</div>
<div>
<span face=""arial" , "helvetica" , sans-serif">Triangle removal comes at the cost of loading for every triangle the index and vertex data, transform vertices, and then, later on, append the triangle data to the filtered index buffer. It appears that the cost of accessing the triangle data seems to be higher than the cost of running the visibility tests.<br />As long as the numbers of triangles in the scene are high, this cost should be offset by the gains on modern GPUs.<br />One way to amortize this cost, even more, is to remove invisible triangles for several views -like a main camera view, a shadow map view, reflective shadow map view, etc.- at the same time.<br />That means if a triangle is visible in the main camera view but not in the shadow map view, it is considered visible in both views. In other words, the remaining set of visible triangles will be the least common denominator between all views; reducing the effectiveness of triangle removal.<br />Although the overall number of triangles that are removed with a multi-view triangle removal stage is smaller compared to just removing triangles for each view separately, huge performance gains are achieved by just loading the triangle data only once in that case.<br />Here is the source code in <a href="https://github.com/ConfettiFX/The-Forge/blob/master/Examples_3/Visibility_Buffer/src/Shaders/FSL/triangle_filtering.comp.fsl">triangle_filtering.comp.fsl</a> that executes FilterTriangle() for several views:</span>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">for (uint i = 0; i < NUM_CULLING_VIEWPORTS; ++i)</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">{</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">float4x4 worldViewProjection = uniforms.transform[i].mvp;</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">float4 vertices[3] =</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">{</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">mul(worldViewProjection, vert[0]),</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">mul(worldViewProjection, vert[1]),</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">mul(worldViewProjection, vert[2])</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">};</span></span></div>
<span style="font-family: "courier new" , "courier" , monospace;"><b style="font-weight: normal;"><br /></b> </span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">CullingViewPort viewport = uniforms.cullingViewports[i];</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">cull[i] = FilterTriangle(indices, vertices, !twoSided, </span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">viewport.windowSize, viewport.sampleCount);</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">if (!cull[i])</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">InterlockedAdd(workGroupIndexCount[i], 3, threadOutputSlot[i]);</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">}</span></div>
<span face=""arial" , "helvetica" , sans-serif"><br /></span>
<br />
<h4 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h4>
<h4 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Multi-View Triangle Removal - Results</span></h4>
</div>
<div>
<span face=""arial" , "helvetica" , sans-serif">The San Miguel scene used in the demo has around 8 million triangles. When the demo starts up in the default camera view - shown in image 6-, after multi-view triangle removal, the filtered index buffer for the shadow map view indexes 1.843 million triangles, while the filtered index buffer for the main view indexes 2.321 million triangles.</span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRFMexDXlS5PnuhCkKALI3sbKd9z0hrFUjYzm_2hfVlONA5aPJTpio6BI-IWAyYMhmRuKgX3CSBAfvBji22Oqldkf-aXbvNn_tUoSJRr20damrt_iG3ERFlUUUt_-Fg9b3zU2OuZCO7LE/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="437" data-original-width="777" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRFMexDXlS5PnuhCkKALI3sbKd9z0hrFUjYzm_2hfVlONA5aPJTpio6BI-IWAyYMhmRuKgX3CSBAfvBji22Oqldkf-aXbvNn_tUoSJRr20damrt_iG3ERFlUUUt_-Fg9b3zU2OuZCO7LE/w400-h225/DefaultStartupView.png" width="400" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 6 - Default start-up view of the Visibility Buffer demo</span></div>
<b style="font-weight: normal;"><span face=""arial" , "helvetica" , sans-serif"><br /></span></b> <span face=""arial" , "helvetica" , sans-serif">Triangle removal as described here or similar approaches are now used by every major developer in next-gen rendering systems and future graphics API design is picking up this idea and might improve geometry handling more.<br /></span><h3 style="text-align: left;"><br /></h3><h3 style="text-align: left;">Draw Call Compaction</h3>
<div style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">The async compute shader for Triangle Filtering runs on batches of 256 triangles as described above. This stage removes all non-visible triangles by appending only visible triangles to the “filtered index buffer”. </span></div><div style="text-align: left;"><span face=""arial" , "helvetica" , sans-serif">This might lead to a situation where the removal of triangles ends up creating an empty draw call:</span></div>
<ol style="text-align: left;">
<li><span face=""arial" , "helvetica" , sans-serif">Batch0 - start index: 0 | num of indices: 12</span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Batch1 - start index: 12 | num of indices: 256 </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Batch2 - start index: 268 | num of indices: 120</span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Batch3 - start index: 388 | num of indices: 0 (empty batch) </span></li>
</ol>
<span face=""arial" , "helvetica" , sans-serif">In this list, Batch3 ends up being empty.</span></div><div><span face=""arial" , "helvetica" , sans-serif">These "holes" impact performance since the GPU command processor has to do all the work setting up data for that draw call, which is wasted work if it is empty.</span></div><div><span face=""arial" , "helvetica" , sans-serif">To fix the "holes", there is another pass called batch compaction in the shader <a href="https://github.com/ConfettiFX/The-Forge/blob/master/Examples_3/Visibility_Buffer/src/Shaders/FSL/batch_compaction.comp.fsl">batch_compaction.comp.fsl</a>.</span></div><div><span face=""arial" , "helvetica" , sans-serif"><br /></span></div><div><span face=""arial" , "helvetica" , sans-serif">This shader removes empty draw calls and aligns the remaining ones so that the ExecuteIndirect call is efficient. </span></div><div>Image 7 shows the flow from triangles that are removed with culling tests to draw calls that need to be compacted.</div><div>
<span face=""arial" , "helvetica" , sans-serif"><br /></span>
<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7yrpbZPsU4mtln86Y27HTNnGvVmBOJujjZQtBWEZ1ETEShgUBm-PJslry9Om2DgnSTBK8GKNKl9cYWjLYyI-3fb9nAR_AVJbBCXrJNWtHZ5P2YnBNfz6AT1AG4-CIW9FXM9zKQNtgqDg/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1122" data-original-width="2803" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7yrpbZPsU4mtln86Y27HTNnGvVmBOJujjZQtBWEZ1ETEShgUBm-PJslry9Om2DgnSTBK8GKNKl9cYWjLYyI-3fb9nAR_AVJbBCXrJNWtHZ5P2YnBNfz6AT1AG4-CIW9FXM9zKQNtgqDg/w640-h256/DrawCallCompaction.PNG" width="640" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 7 - Draw Call Compaction</span></div><span face=""arial" , "helvetica" , sans-serif"><br />The batch compaction compute shader checks if the draw call is empty and then removes those calls by filling a new draw argument buffer with only usable draw call data. This new draw argument buffer is later used by ExecuteIndirect.</span></div><div><span face=""arial" , "helvetica" , sans-serif">This same shader also fills a per draw indirect material buffer which holds the material index for each draw call. It also determines the overall number of draw calls that will be passed as the final draw counter to the ExecuteIndirect call.<br /></span></div><div><span face=""arial" , "helvetica" , sans-serif"><br /></span></div><div><span face=""arial" , "helvetica" , sans-serif">On a high-level view, the Triangle Visibility Buffer rendering system went through the following stages so far:</span></div><div><ul style="text-align: left;"><span face=""arial" , "helvetica" , sans-serif"><li>[CPU] Early discard geometry not visible from any view using cluster culling </li>
<li>[CS] Generate N index and N ExecuteIndirect buffers by culling and filtering triangles against the N views (one triangle per compute shader thread) </li>
<li>[CS] Draw call compaction </li>
<li>For each, i view use ith index buffer and ith indirect argument buffer </li>
</span></ul>
<span face=""arial" , "helvetica" , sans-serif"> With all the draw data optimized for usage, the next stage is filling the actual Visibility Buffer with ExecuteIndirect.</span></div>
<h2 style="text-align: left;">
</h2>
<h2 style="text-align: left;">
Filling the Visibility Buffer - ExecuteIndirect</h2>
<div>
<span face=""arial" , "helvetica" , sans-serif">The Triangle Visibility Buffer will hold indices into triangle data in an 8:8:8:8 render target similar to [Burns][Schied]. The index consists of a packed 32-bit value: </span><br />
<ul style="text-align: left;">
<li><span face=""arial" , "helvetica" , sans-serif">1-bit Alpha-Masked<br />In the demo, one bit holds information on if the geometry requires alpha masking or not. The PC requires a dedicated code path for each with its own ExecuteIndirect.</span></li>
<li><span face=""arial" , "helvetica" , sans-serif">8-bit drawID - indirect draw call id<br />An 8-bit value represents the id of the draw call to which the triangle belongs. In this implementation, there is space for 256 draw calls </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">23-bit triangleID<br />A 23-bit value holds an id that describes the offset of a triangle inside a draw call. In other words, it is relative to the drawID. </span></li>
</ul>
<span face=""arial" , "helvetica" , sans-serif">The render target holding this data is filled with ExecuteIndirect calls in parallel with the Depth Buffer.<br />All ExecuteIndirect calls read vertex buffers, index buffers, and a material buffer, that is used to apply various materials.<br />There are four different vertex buffers:</span><br />
<ul style="text-align: left;">
<li><span face=""arial" , "helvetica" , sans-serif">Position </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Texture coordinates </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Normals </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Tangents </span></li>
</ul>
<span face=""arial" , "helvetica" , sans-serif">Separating vertex data into four buffers (also called non-interleaved vertex data) turned out to be more efficient due to position and texture coordinates being used more often than normals and tangents. <br />Looking at the separate stages:</span></div><div><ul style="text-align: left;"><li><span face=""arial" , "helvetica" , sans-serif">Triangle filtering uses<br /></span></li><ul><li><span face=""arial" , "helvetica" , sans-serif">Position</span></li></ul><li>Filling the Visibility Buffer uses</li><ul><li>Position</li><li>Texture coordinates for alpha testing</li></ul><li>Shading uses</li><ul><li>Position</li><li>Texture coordinates</li><li>Normals</li><li>Tangents</li></ul></ul></div><div><span face=""arial" , "helvetica" , sans-serif">The ExecuteIndirect calls also expect index buffers that are used to index into the vertex buffers. This demo is using six “filtered” index buffers that were generated during triangle removal by appending only visible triangles to them. There are two sets for the camera view and the shadow map view of three index buffers for triple buffering the swap chain. The triple buffer was necessary for the async compute shader used in triangle removal.<br />The ExecuteIndirect calls also expect “filtered” indirect argument buffers that were generated during the draw call compaction stage after triangle removal.<br />The last buffer fed to the ExecuteIndirect calls is the texture id or material buffer (also generated during draw call compaction), which is used to represent a wide range of materials in the scene.<br />All the source code can be found in Visibility_Buffer.cpp and there in <a href="https://github.com/ConfettiFX/The-Forge/blob/81b29058b1b433f581e0e31c633f64118d9ce247/Examples_3/Visibility_Buffer/src/Visibility_Buffer.cpp#L4297">drawVisibilityBufferPass()</a> and in <a href="https://github.com/ConfettiFX/The-Forge/blob/master/Examples_3/Visibility_Buffer/src/Shaders/FSL/visibilityBuffer_pass.frag.fsl">visibilitybuffer_pass.frag.fsl</a>.<br /><br />In the San Miguel test scene, the number of indirect draw calls in each of the four ExecuteIndirect calls are: </span><br />
<div>
<ul style="text-align: left;">
<li><span face=""arial" , "helvetica" , sans-serif">Shadow opaque: 163 </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Shadow alpha masked: 50 </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Main view opaque: 152 </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Main view alpha masked: 50 </span></li>
</ul>
<span face=""arial" , "helvetica" , sans-serif">As soon as these four ExecuteIndirect calls have finished, the Visibility Buffer and the Depth buffer are filled with one layer of triangles and one layer of pixels. In other words overdraw of triangles and pixels is removed for opaque geometry.<br />The demo holds implementations for the described Visibility Buffer approach and a G-Buffer based Deferred Shading approach. The way the G-Buffer is filled resembles the way the Visibility Buffer is filled. The main difference is the memory usage patterns. </span></div>
<div>
<span face=""arial" , "helvetica" , sans-serif"><br /></span></div>
<div>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Memory Usage - Visibility Buffer vs. G-Buffer</span></h3>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">Memory bandwidth is one of the more limiting factors for the performance of games, especially on lower-end platforms or on platforms that need to support 4k and higher resolutions.<br />The increasing size of G-Buffers during the last 10 years makes the commonly used Deferred Shading techniques more bandwidth-hungry. <br />Games use vertex and index buffers, other data like textures, draw arguments, uniforms, descriptors, and then render targets. Render targets scale with screen size and for larger screen resolutions represent a very large part of the memory occupied during rendering. <br />One of the advantages of the Visibility Buffer is that it fits into two 32-bit render targets (Triangle Visibility in 32-bit and depth visibility in 32-bit as well). The following text will compare the memory usage of the demo implementation of the Visibility Buffer and the G-Buffer implementation.<br />The usage of vertex and index buffers to feed the ExecuteIndirect calls are the same in the Visibility Buffer and the G-Buffer implementation as shown in Image 8:</span></div><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKVleg8eZJd7E4rpHAFZ3c8EFd6ygors2xAtWGYTSqGR3soH5fr-GOssVImUNQqsVU2ybBldyS8tbt8dnT6CkVvAGPb4b8ww4vCBp_ETvouWY6VAwpTOua5f1tOPAMZZtU3hFCZAlMoUU/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="638" data-original-width="1634" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKVleg8eZJd7E4rpHAFZ3c8EFd6ygors2xAtWGYTSqGR3soH5fr-GOssVImUNQqsVU2ybBldyS8tbt8dnT6CkVvAGPb4b8ww4vCBp_ETvouWY6VAwpTOua5f1tOPAMZZtU3hFCZAlMoUU/w640-h250/VertexNIndexBufferMemoryUsage.PNG" width="640" /></a><br /><i style="font-size: 12px; white-space: pre;">Image 8 - Memory usage of the Vertex and Index Buffers</i></div><br /></div><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<div style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Additionally, there is data used for textures (roughly 21 MB), draw arguments, uniforms, descriptors etc. (roughly 2 MB). From a memory perspective, the most interesting memory is the one that is used for screen-space render targets. Image 9 shows the render target memory occupied with a resolution of 1080p and various MSAA settings for the Visibility buffer:</span></div><div style="text-align: left;"><span face=""arial" , "helvetica" , sans-serif"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrhP44fdkYbDErkIZLQihXxX-0_sKSBnyE9JCe-JOgJTuEgtQLRwnB5ZooPL6l-DVHVHlVSJXiejjurXxJs19MMzTy3LSXj-_QkA1ZEmp0Mlef-1WScxq8Xo0NiPWttVenPPWDlUTn0fg/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="492" data-original-width="1760" height="178" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrhP44fdkYbDErkIZLQihXxX-0_sKSBnyE9JCe-JOgJTuEgtQLRwnB5ZooPL6l-DVHVHlVSJXiejjurXxJs19MMzTy3LSXj-_QkA1ZEmp0Mlef-1WScxq8Xo0NiPWttVenPPWDlUTn0fg/w640-h178/VisibilityBuffer_RenderTarget_Memory_1080.PNG" width="640" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 9 - Visibility Buffer Memory at 1080p</span></div><br /></span></div></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<br />
<div style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Image 10 shows the render target memory occupied in a resolution of 1080p and various MSAA settings for a G-Buffer:</span></div><div style="text-align: left;"><span face=""arial" , "helvetica" , sans-serif"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbVB6mPGnlMO3_94m_kLCNg6BUkIDcH-tSfaf4s8kFySaVSi8PqyWXcvMtasUKLkdn4CP4bPp31pqtGHY90c1j35iZmUCanLxP6RIl8tP9pr1rgyKT2JsJL72-dQyh7s9841a_FT1-MBA/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="627" data-original-width="1639" height="244" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbVB6mPGnlMO3_94m_kLCNg6BUkIDcH-tSfaf4s8kFySaVSi8PqyWXcvMtasUKLkdn4CP4bPp31pqtGHY90c1j35iZmUCanLxP6RIl8tP9pr1rgyKT2JsJL72-dQyh7s9841a_FT1-MBA/w640-h244/G-Buffer_RenderTarget_Memory_1080.PNG" width="640" /></a><br /><i><span style="font-size: x-small;">Image 10 - G-Buffer Memory at 1080p</span></i></div><br /></span></div>
<div style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Comparing the 1080p memory numbers, the G-Buffer with 2x and 4x MSAA more than doubles in size as expected from going from two 32-bit render targets to five.</span></div>
</div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;">
<br />
<div style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">With a monitor or TV supporting 4k (3840x2160) the delta between the G-Buffer compared to the Visibility Buffer becomes bigger as shown in Image 11 and 12:</span></div><div style="text-align: left;"><span face=""arial" , "helvetica" , sans-serif"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjak1U_uXv0XGXEWSqCkPJsIIfUn3hUvFXIOaedC-wsR6A17kuJk8Uqc8cT6yUNPi43Ah43YYUqLzqIRZetZh5dOFnuJZyTjBiGXZWXNw6MywGLkySgKlnEi-EtyRpJe7RUHRznCmpHmZE/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="574" data-original-width="1939" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjak1U_uXv0XGXEWSqCkPJsIIfUn3hUvFXIOaedC-wsR6A17kuJk8Uqc8cT6yUNPi43Ah43YYUqLzqIRZetZh5dOFnuJZyTjBiGXZWXNw6MywGLkySgKlnEi-EtyRpJe7RUHRznCmpHmZE/w640-h190/VisibilityBuffer_RenderTarget_Memory_4K.PNG" width="640" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 11 - Visibility Buffer Render Target memory at 4k
</span></div><div class="separator" style="clear: both; text-align: center;"><span style="font-size: 12px; font-style: italic; white-space: pre;"><br /></span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMAe-5ulMjZGIgEACh0mstJwdDMxgWXaUsEBy3xwzw-HyGNVn1XgweVvLYnt1f1KModSRQ7MByi4AB5HvhV-OHwv-lzJBr354RFcby4nNovaxyKphlohse97fN-asElNLG_tRjV_1Zgvs/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="637" data-original-width="1819" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMAe-5ulMjZGIgEACh0mstJwdDMxgWXaUsEBy3xwzw-HyGNVn1XgweVvLYnt1f1KModSRQ7MByi4AB5HvhV-OHwv-lzJBr354RFcby4nNovaxyKphlohse97fN-asElNLG_tRjV_1Zgvs/w640-h224/G-Buffer_RenderTarget_Memory_4K.PNG" width="640" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 12 - G-Buffer Render Target memory at 4k</span></div></span></div></div><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">The numbers provided are only estimates on PC because the driver and the way memory is fragmented might change how much one render target actually occupies. <br />These numbers show how filling and reading a G-Buffer with large screen resolutions for Deferred Shading can become a memory bandwidth bottleneck, depending on the memory bandwidth of the used GPU. This becomes even more dramatic with 5k and 8k displays.<br />In other words: one motivation to implement a Visibility Buffer-like approach is to reduce memory bandwidth on high-res displays on platforms that do not have much high-speed memory, like hardware-tiled platforms or some console platforms.</span><br />
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h2>
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Shading</span></h2>
</div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">After the Visibility Buffer is filled with one layer of triangles for the opaque pass, and the depth buffer is filled with one layer of pixels, the scene can be shaded. To prepare for shading the scene, a list of lights per screen-space tile is generated upfront (Tiled Light List).</span><br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Tiled Light List</span></h3>
<span face=""arial" , "helvetica" , sans-serif">To deal with a large number of lights, the demo implementation splits the screen-space into tiles and identifies lights that need to be rendered in those tiles. In the actual shading pass, this light list will be used to do one screen-space lighting pass for all light sources for opaque and transparent objects.<br />To generate this list, a compute shader runs on 64 lights per tile. It compares the bounding volume of the light with its x and y-direction to the x and y-direction of the tile in screen-space, in case it is in the tile, it adds the light to the light cluster and increases the light count for that cluster. There is also an early out for lights that are behind the camera.<br />The source code for generating the list of lights in those tiles can be found at <a href="https://github.com/ConfettiFX/The-Forge/blob/master/Examples_3/Visibility_Buffer/src/Shaders/FSL/cluster_lights.comp.fsl">cluster_lights.comp.fsl</a>.</span><br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Forward++</span></h3>
<span face=""arial" , "helvetica" , sans-serif">Because the lighting technique uses the Visibility Buffer with its one layer of optimized triangle data in one screen-space pass, we call it Forward++ compared to Forward+ that would use several draw calls.</span></div><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span face=""arial" , "helvetica" , sans-serif"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEIcAX_f6vI1Jh1rk-eReBJCvXa59dWs7oGN1OfHy51dYxV7j-DyTRFLMOQg8i4buo7No6ry7M-ysZbpR3TXtdZLo5zQVzLJqVREXYKKfUyJTti5Zi7cd_ZppALOM0urB-ueGP_yNbE1M/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1057" data-original-width="2567" height="264" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEIcAX_f6vI1Jh1rk-eReBJCvXa59dWs7oGN1OfHy51dYxV7j-DyTRFLMOQg8i4buo7No6ry7M-ysZbpR3TXtdZLo5zQVzLJqVREXYKKfUyJTti5Zi7cd_ZppALOM0urB-ueGP_yNbE1M/w640-h264/Forward%252B%252B_Shading.PNG" width="640" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 13 - Shading the Visibility Buffer with Forward++</span></div><br /></span></div><span face=""arial" , "helvetica" , sans-serif">Image 13 shows the Visibility Buffer and the Depth buffer at the top. The various vertex and index buffers used for shading on the right. On the left is the tiled light list that is used to apply a large number of lights per tile. For transparent objects, we still have to use traditional Forward+ by sorting draw calls back-to-front before we execute them.<br /><br />On a high level, the shading algorithm goes through the following steps: </span></div>
<div>
<ul style="text-align: left;">
<li><span face=""arial" , "helvetica" , sans-serif">Get drawID/triangleID at screen-space pixel position </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Load data for the 3 vertices from the IB and then the VB </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Compute the partial derivatives of the barycentric coordinates – triangle gradients </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Interpolate vertex attributes at pixel position using gradients </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Calculate Directional light contribution (in the demo either Blinn-Phong or PBR) </span></li>
<li><span face=""arial" , "helvetica" , sans-serif">Add point light contributions by going through the tiles of the tiled light list </span></li>
</ul>
<span face=""arial" , "helvetica" , sans-serif">The source code for applying the lights is in <a href="https://github.com/ConfettiFX/The-Forge/blob/master/Examples_3/Visibility_Buffer/src/Shaders/FSL/visibilityBuffer_shade.frag.fsl">visibilityBuffer_shade.frag.fsl</a>.<br /><br />To calculate the partial derivatives, we are using the following equation from [Schied] in Appendix A Equation (4):</span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzI5XO1mCC_iZE410gPdl7OVn4LL0y_9fQT5DCIdrNYoZ4w0BE9RW80e7bPVlr21DfsSSH2uPf3QNyHszzbY0Y9ne1CbzRr2z34HFaEOVlcAvEuvMxw9Lgh75OIavyv0hD8LJOJTeqInk/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="65" data-original-width="326" height="80" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzI5XO1mCC_iZE410gPdl7OVn4LL0y_9fQT5DCIdrNYoZ4w0BE9RW80e7bPVlr21DfsSSH2uPf3QNyHszzbY0Y9ne1CbzRr2z34HFaEOVlcAvEuvMxw9Lgh75OIavyv0hD8LJOJTeqInk/w400-h80/PartialDerivatives.png" width="400" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Equation 1 - Partial Derivatives</span></div><span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<span face=""arial" , "helvetica" , sans-serif">The implementation of this equation looks like this:</span><span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">// Computes the partial derivatives of a triangle from the projected </span><br />
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">// screen space vertices</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">DerivativesOutput computePartialDerivatives(float2 v[3])</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">{</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> DerivativesOutput output;</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> float d = 1.0 / determinant(float2x2(v[2] - v[1], v[0] - v[1]));</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> output.db_dx = float3(v[1].y - v[2].y, v[2].y - v[0].y, v[0].y - v[1].y) * d;</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> output.db_dy = float3(v[2].x - v[1].x, v[0].x - v[2].x, v[1].x - v[0].x) * d;</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: "courier new" , "courier" , monospace;"><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> </span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">return output;</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "courier new" , "courier" , monospace; font-size: 10pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">}</span></div>
<span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">The partial derivatives in this code are calculated without intrinsics to preserve as much precision as possible. <br />The actual shading code is rather straightforward. The directional light is applied first and the point lights are applied later in a for loop depending on their visibility in the screen tiles:<br /><br /><span style="font-size: 9pt; white-space: pre;">…</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif" style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">// directional light</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif"><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">shadedColor = calculateIllumination(normal, uniforms.camPos.xyz, uniforms.esmControl, </span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">uniforms.lightDir.xyz, isTwoSided, posLS, position, shadowMap, </span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">diffuseColor.xyz, specularData.xyz, depthSampler);</span></span></div>
<span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif" style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">// point lights</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif" style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">// Find the light cluster for the current pixel</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif"><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">uint2 clusterCoords = uint2(floor((input.screenPos * 0.5 + 0.5) * </span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">float2(LIGHT_CLUSTER_WIDTH, LIGHT_CLUSTER_HEIGHT)));</span></span></div>
<span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif"><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">uint numLightsInCluster = lightClustersCount.Load(LIGHT_CLUSTER_COUNT_POS(clusterCoords.x, </span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">clusterCoords.y) * 4);</span></span></div>
<span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif" style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">// Accumulate light contributions</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif" style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">for (uint i = 0; i < numLightsInCluster; i++)</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif" style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">{</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif"><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> uint lightId = lightClusters.Load(LIGHT_CLUSTER_DATA_POS(i, clusterCoords.x, </span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">clusterCoords.y) * 4);</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif"><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> shadedColor += pointLightShade(lights[lightId].position, lights[lightId].color, </span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">uniforms.camPos.xyz, position, normal,</span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><br class="kix-line-break" /></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">specularData, isTwoSided);</span></span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif" style="background-color: transparent; color: black; font-size: 9pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">}</span></div>
<span face=""arial" , "helvetica" , sans-serif"><b style="font-weight: normal;"><br /></b> </span><br />
<span face=""arial" , "helvetica" , sans-serif">This code and the setup might likely change in future iterations of the demo. Any Ray Tracing code might benefit from the existence of partial derivatives and the fact that the visibility of triangles is optimized in the Visibility Buffer.</span></div>
<div>
<span face=""arial" , "helvetica" , sans-serif"><br /></span>
<br />
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h2>
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Visibility Buffer - Benefits</span></h2>
<span face=""arial" , "helvetica" , sans-serif">Comparing the Visibility Buffer to a Deferred Shading system with a large G-Buffer shows the following benefits.</span></div>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Memory Bandwidth</span></h3>
<div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">Due to the smaller render target memory footprint, the Visibility Buffer offers memory bandwidth benefits compared to a G-Buffer. This becomes eminent in scenarios where the screen resolution is high or where the amount of fast memory is so limited that only two 32-bit render targets or even a tiled region of render targets fit.</span><br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Memory Access Patterns</span></h3>
<span face=""arial" , "helvetica" , sans-serif">When shading happens, triangle data is fetched from the filtered index buffer in the Visibility Buffer. The actual fetch of data from the index/vertex buffers happens similar to a regular draw call but continuously in screen-space once. In other words, the memory access of index and vertex buffers apart from the indirection through the Visibility Buffer is the “optimal” access pattern that the architects of GPUs had in mind. Compared to a regular forward renderer this only happens once for opaque objects in screen-space and not for several draw calls. <br />We see highly coherent cache hit rates of 99% L2 cache hits for textures, vertex, and index buffers. Therefore lighting the triangles appears to be fast.<br />To apply a light in a G-Buffer, a larger memory area has to be accessed due to the more redundant nature of screen-space data.</span></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">These two benefits are underlined by the performance measurements shown below.</span><br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Material Variety</span></h3>
<span face=""arial" , "helvetica" , sans-serif">The Visibility Buffer can represent a much wider range of materials due to the fact that material parameters do not have to be stored per-pixel in a G-Buffer. All the lessons learned from using materials in forward renderers need to be extended by the idea that the Visibility Buffer uses bindless texture arrays, other than that it should be the same.</span><br />
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h2>
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Miscellaneous</span></h2>
<span face=""arial" , "helvetica" , sans-serif">There are several questions that usually come up in discussions about the Triangle Visibility Buffer implementation.</span><br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Why didn’t we implement this earlier?</span></h3>
<span face=""arial" , "helvetica" , sans-serif">What is described here was not a straightforward process of implementing one paper. We started out with [Schied] in September 2015. Christoph Schied came to our office and implemented his approach in our old rendering framework at that point in time in OpenGL. We then simplified everything over the following 2 ½ years to a point our approach transformed into the approach taken by </span>[Burns]. Compared to [Burns], the actual storage of triangles in the Visibility Buffer happens due to the triangle removal and draw compaction step with an optimal “massaged” workload set, with ExecuteIndirect reducing CPU overhead. Because this requires a compute shader, it was not possible at the time.</div><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span face=""arial" , "helvetica" , sans-serif">After the Visibility Buffer is filled with one layer of triangles and the depth buffer holds one layer of pixels, the now one-time screen-space shading can be executed faster compared to Deferred Shading and a Forward Renderer due to better memory access patterns. </span></div><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">[Burns] couldn't use compute shaders and therefore a tiled light list was not possible.<br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">How often do you have to skin animated objects?</span></h3>
<span face=""arial" , "helvetica" , sans-serif">There are three stages that transform vertices for triangle removal, filling the Visibility Buffer and then shading. After the triangle removal stage, the transformation has -hopefully- only to happen on less than half of the triangles compared to triangle removal.</span><br />
<span face=""arial" , "helvetica" , sans-serif">To reduce the number of times that a triangle has to be transformed, in a future iteration of the demo application, pre-transformation of triangles will be implemented.</span><br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">How about Deferred Decals?</span></h3>
<span face=""arial" , "helvetica" , sans-serif">If you still use a Decal system it might be time to switch to an async compute-driven texture synthesis system. Other than that the equivalent of Deferred Decals can be implemented after the Visibility Buffer fill, fetching triangle and normal data from the Visibility Buffer and applying the end result in the back-buffer similarly to a Deferred Decal system.</span><br />
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h3>
<h3 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Performance Numbers</span></h3>
<span face=""arial" , "helvetica" , sans-serif">Over the years, we collected performance numbers on various platforms ranging from console platforms to macOS and now PC with DirectX 12 and Vulkan. The Visibility Buffer demo allows switching between a Deferred Shading implementation with a G-Buffer that resembles what is used in games and the actual Visibility Buffer implementation. </span><br />
<span face=""arial" , "helvetica" , sans-serif">Both are similar when it comes to how the data is set up to be rendered into the Visibility Buffer / Buffer. So both use the ExecuteIndirect setup described above on all platforms. The main difference is the usage of the G-Buffer.</span><br />
<span face=""arial" , "helvetica" , sans-serif">Below are performance numbers for the DirectX 12 implementation running at 4k from an older version of the codebase. </span></div><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span face=""arial" , "helvetica" , sans-serif"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4r4a6afAbR9o8W6xilEoGEFTxCuolvyvCSvyEjeEzdsNuSiuOytGPMpYqhh_xFo1s01JJp3CGaJYedYF8vGTzukkg_4LjJWu2qahVbfRvzyCYiV5yifSoMLp62-j5g5wxBoSS_sj7qes/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1167" data-original-width="2399" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4r4a6afAbR9o8W6xilEoGEFTxCuolvyvCSvyEjeEzdsNuSiuOytGPMpYqhh_xFo1s01JJp3CGaJYedYF8vGTzukkg_4LjJWu2qahVbfRvzyCYiV5yifSoMLp62-j5g5wxBoSS_sj7qes/w640-h312/VisibilityBufferPerformance.PNG" width="640" /></a><br /><span style="font-size: 12px; font-style: italic; white-space: pre;">Image 14 - Visibility Buffer Performance Numbers</span></div><div class="separator" style="clear: both; text-align: center;"><span style="font-size: 12px; white-space: pre;"><i><br /></i></span><span style="font-size: 12px; font-style: italic; white-space: pre;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjv0pFbYyKNODMQPQrDUHv_i_6f2fdmaikzcokONirID8FyaOHCFO-vw5KSwBmrwT1L75bJztJEDmqj_75yi8e5YN3Pa4CGhO-V8UfMnob1PmS31_KsjK9hAtUiCFF_BRXAb2oDpb-kMwE/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1163" data-original-width="2398" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjv0pFbYyKNODMQPQrDUHv_i_6f2fdmaikzcokONirID8FyaOHCFO-vw5KSwBmrwT1L75bJztJEDmqj_75yi8e5YN3Pa4CGhO-V8UfMnob1PmS31_KsjK9hAtUiCFF_BRXAb2oDpb-kMwE/w640-h310/DeferredShadingPerformance.PNG" width="640" /></a>
Image 15 - Deferred Shading Performance Numbers
</div></span></div></span></div><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center;"><br /></div>
<span face=""arial" , "helvetica" , sans-serif">The column that is named “Culling” shows the performance cost of triangle culling and filtering. Most of the other columns are self-explanatory. </span><br />
<span face=""arial" , "helvetica" , sans-serif">With increasing screen resolution, the difference in performance between a G-Buffer and the Visibility Buffer becomes apparent. The difference translates also to console platforms in 1080p and 4k resolutions.</span></div>
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Future</span></h2>
<div>
<span face=""arial" , "helvetica" , sans-serif">For future iterations of the Visibility Buffer, we are looking at Physically Based Materials, Ray Tracing and Object-Space Shading. In case we find any noteworthy results, they will be shared in another blog post.</span></div>
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">Credits</span></h2>
<div>
<span face=""arial" , "helvetica" , sans-serif">Like all the work at our company, a research project like this for such a long time is touched by a large number of people. In no particular order, there was Leroy Sikkes, Jesus Gumbau, Thomas Zeng, Max Oomen, Jordan Logan, Marijn Tamis, David Srour, Manas Kulkarni, Volkan Ilbeyli, Andreas Valencia Telez, Eloy Ribera, Antoine Micaelian who worked at one point or another on this project. In case I forgot someone, I will add the person … let me know :-) </span></div><div><span face=""arial" , "helvetica" , sans-serif"><br /></span></div><div><span face=""arial" , "helvetica" , sans-serif">Update: it is the year 2021: this list can be extended by about 30 more people. Everyone who ever worked at our company worked on this in one or two ways since 2015 ... we also need to thank more companies to support this research: Apple, AMD, INTEL, Google, and I am forgetting probably a few, they span off projects with us over the years. Thanks for all the support!</span></div><div><span face=""arial" , "helvetica" , sans-serif"><br /></span></div>
<span face=""arial" , "helvetica" , sans-serif">We are using GeometryFX and the Vulkan Memory Manager from AMD and many other open-source libraries. We want to thank all the open-source contributors for sharing their code and knowledge. Without these contributions writing your own game engine with a framework like the Forge wouldn’t be as easily possible. We are hoping that this spirit lives on and others are encouraged to do the same.</span></div>
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif"><br /></span></h2>
<h2 style="text-align: left;">
<span face=""arial" , "helvetica" , sans-serif">References </span></h2>
<div>
<span face=""arial" , "helvetica" , sans-serif">[Burns] Christopher A. Burns, Warren A. Hunt “The Visibility Buffer: A Cache-Friendly Approach to Deferred Shading” Journal of Computer Graphics Techniques (JCGT) 2:2 (2013), 55- 69. Available online at<a href="http://jcgt.org/published/0002/02/04"> http://jcgt.org/published/0002/02/04</a> <br />[Chajdas] Matthaeus Chajdas “GeometryFX”<a href="http://gpuopen.com/gaming-product/geometryfx/"> http://gpuopen.com/gaming-product/geometryfx/</a> <br />[Engel2009] Wolfgang Engel, “Light Pre-Pass”, “Advances in Real-Time Rendering in 3D Graphics and Games”, SIGGRAPH 2009,<a href="http://halo.bungie.net/news/content.aspx?link=Siggraph_09"> http://halo.bungie.net/news/content.aspx?link=Siggraph_09</a> <br />[Lagarde] Sebastien Lagarde, Charles de Rousiers, “Moving Frostbite to Physically Based Rendering”, Course notes SIGGRAPH 2014 <br />[Olano] Marc Olano, Trey Greer, “Triangle Scan Conversion using 2D Homogeneous Coordinates”, <a href="https://www.csee.umbc.edu/~olano/papers/2dh-tri/" target="_blank">https://www.csee.umbc.edu/~olano/papers/2dh-tri/ </a><br />[Schied2015] Christoph Schied, Carsten Dachsbacher “Deferred Attribute Interpolation for Memory-Efficient Deferred Shading”,<a href="http://cg.ivd.kit.edu/publications/2015/dais/DAIS.pdf"> http://cg.ivd.kit.edu/publications/2015/dais/DAIS.pdf</a> <br />[Schied2016] Christoph Schied, Carten Dachsbacher “Deferred Attribute Interpolation Shading”, GPU Pro 7, CRC Press <br />[Wihlidal] Graham Wihlidal, “Optimizing the Graphics Pipeline with Compute”, GDC 2016,<a href="http://www.frostbite.com/2016/03/optimizing-the-graphics-pipeline-with-compute/"> http://www.frostbite.com/2016/03/optimizing-the-graphics-pipeline-with-compute/</a> </span></div>
</div>
</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com8tag:blogger.com,1999:blog-398682525365778708.post-85704836549692102622017-04-07T17:20:00.000-07:002017-04-07T17:31:49.016-07:00HDR10 - TV setup<div dir="ltr" style="text-align: left;" trbidi="on">
We have a large amount of HDR (High-Dynamic Range) TVs in the office due to our work on HDR standards. We run HDR capable content on those TVs frequently. <br /><br />I was recently showing one of our non-HDR demos at a conference. The conference organizer was very nice and they provided an HDR10 TV to us. I was grateful for that because that meant we didn't have to ship a large and heavy TV over a large distance.<br />
Usually, I am not the one who sets up our TVs. One of my co-workers with more experience is dealing with this type of work.<br />
<br />
The demo I was going to show didn't have any special HDR treatment. It is not supposed to showcase the advantages of a higher dynamic brightness range. So all the art assets are LDR and there was no effort made in making it look beautiful on an HDR TV. It was demoed on many LDR TVs and monitors before.<br />
<div>
I was stunned to see how ugly this demo looked on this particular HDR10 capable TV. Up until then it didn't occur to me that we would have to make changes to the art assets or add a tone mapper and tune it (the demo didn't have one) to make a LDR demo look good on a HDR10 capable TV. My assumption was that the LDR demo should still look good on an HDR TV after all LDR is a subset of HDR right?</div>
<div>
<div>
Also as a programmer, I consider going from LDR - > HDR a solved problem ... at least it should be documented enough and therefore didn't expect any challenge from just outputting our little demo that was already used on many LDR monitors and TVs before on an HDR10 TV.</div>
<div>
So after my initial shock, a friendly person suggested to go to the following website to tune the TV:<br />
<br />
<a href="http://www.rtings.com/tv/reviews/vizio/m-series-2015/settings">http://www.rtings.com/tv/reviews/vizio/m-series-2015/settings</a><br />
<br />
At that point, it occurred to me that an HDR10 capable TV has dozens of sliders in several sub-menus. I had a hard time to understand what all these sliders are doing and how they interact. There is no way an end-user will go through them and understand 5% of them.</div>
<div>
<br /></div>
</div>
<div>
How do we make sure that our games run on a wide range of HDR10 capable TVs consistently? Will every game come with a handbook explaining the best settings for a few dozen TVs? We can not possibly assume any end-user will go through the exercise of figuring this out. Previous experiences with just adjusting the gamma value indicate a low "adoption rate" of menu options.</div>
<div>
Will we give recommendations like we do for PC Graphics Cards now, saying this game is best used with this display and expect users to upgrade their displays for our game? What happens if two games recommend different displays, will users need to have different displays in their main living room ... obviously, I am exaggerating. </div>
<div>
<br /></div>
<div>
For gamma, we invented our own gamma calibration test screens that worked well. Is there a way to do this for color as well? Maybe color wheels? This way we can guide users that are inclined to work their way through the menu options to a more optimal image?</div>
<div>
<br /></div>
<div>
Anyone has already thought this through?</div>
</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com1tag:blogger.com,1999:blog-398682525365778708.post-71642334372045416162016-08-22T15:48:00.003-07:002016-08-22T15:48:31.269-07:00GDCE 2016 - The filtered and culled Visibility Buffer<div dir="ltr" style="text-align: left;" trbidi="on">
Here is the executive summary: we built a rendering system that<br />
<br />
<ul style="text-align: left;">
<li>Cluster culls and filters triangles for different views like main view, shadow view, reflection view, GI view etc. at the same time</li>
<li>The optimized triangles are used to fill a screen-space Visibility Buffer or more Visibility Buffers for more views</li>
<li>We then render lights, shadows, bounce lights with the optimized geometry based on visibility</li>
<li>We can differ between visibility of geometry and shading frequency</li>
<li>We can light per triangle or in so called object space</li>
</ul>
<br />
<br />
Please download it from here <a href="http://www.confettispecialfx.com/gdce-2016-the-filtered-and-culled-visibility-buffer-2/" target="_blank">http://www.confettispecialfx.com/gdce-2016-the-filtered-and-culled-visibility-buffer-2/</a></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-69580266189551470312015-10-22T14:49:00.005-07:002015-10-22T14:49:36.080-07:00Intel Blog: Performance Considerations for Resource Binding in Microsoft DirectX* 12<div dir="ltr" style="text-align: left;" trbidi="on">
I wrote another blog entry for Intel.<br />
<br />
<a href="https://software.intel.com/en-us/articles/performance-considerations-for-resource-binding-in-microsoft-directx-12" target="_blank">Performance Considerations for Resource Binding in Microsoft DirectX* 12</a><br />
<br />
<br /></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-49224577393880927812015-08-12T16:15:00.001-07:002015-08-12T16:15:20.784-07:00Implementation of the GPU Pro 5 Screen-Space Glossy Reflection Algorithm<div dir="ltr" style="text-align: left;" trbidi="on">
Someone (can't find the name on the website) provided an implementation of a GPU Pro 5 article:<br />
<br />
<a href="http://roar11.com/2015/07/screen-space-glossy-reflections/">http://roar11.com/2015/07/screen-space-glossy-reflections/</a><br />
<br />
Pretty cool!</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-88941694101280909672015-07-01T10:12:00.000-07:002015-07-06T15:38:31.600-07:00MVP Award<div dir="ltr" style="text-align: left;" trbidi="on">
This year I was honored with an MVP award. This is the tenth time in a row and I am very excited about this. I would like to thank everyone for supporting my nominations for the last 10 years.<br />
<br />
Here is my MVP page:<br />
<br />
<a href="http://mvp.microsoft.com/en-us/mvp/Wolfgang%20Engel-35704" target="_blank">http://mvp.microsoft.com/en-us/mvp/Wolfgang%20Engel-35704</a><br />
<br />
A lot of things that I do during the year do not find their way onto this blog. Most of the time I am too busy doing these things, that leaves me with not much time to blog about them. I also consider this blog more an offer to provide advice or insights into things I am working on in my spare time (outside of Confetti). On top of that with Confetti growing more and more over the last more than six years, my programming time including spare time decreased.<br />
<br />
In general I do not give myself much time to reflect what happened during those ten years as a MVP. I am still trying to understand the dimension of being active in a highly volatile industry like the game industry for 10 years. Obviously I am already much longer in the industry.<br />
<br />
10 years ago a new console generation launched with the XBOX 360 / PS3. We considered that launch a major event because these two platforms together with the PC were considered the main gaming devices for the next seven years. Only two years later, mobile games started to take off after Steve Jobs changed his mind about not supporting native programming on the iOS devices.<br />
Today we have devices like the iPad Air 2 and the NVIDIA Shield that offer performance close to the XBOX 360 / PS3 and the big console manufacturers have a serious challenge in competing with the many mobile devices that people already have in their homes. It became so easy for companies to launch their own consoles that now many companies are launching mini consoles that use more advanced mobile parts.<br />
<br />
The production models in the industry are rapidly changing. Similar to the movie industry, parts of the industry move away from the monolithic model of having large dev-teams on games to more flexible strike teams, where they hire companies like Confetti to come in and take care of graphics and tools instead of having a group of people permanently on staff for those tasks for a long time.<br />
This is an exciting development for Confetti and I feel like we are in the middle of it.<br />
It will be interesting where all this will go ... one thing I know is that we will become better every year. We will always strive to make the next year better than the previous year, improve efficiency, learn more.<br />
<br />
With the companies that haven't adjusted to the strike team model, there is the unfortunate development over the last 10 years that they keep flooding the news with large layoffs, they send out press releases saying that they had to reduce workforce out of reasons like "aligning" expectations, budget, lack of success etc.. Many of those press releases express a snide view on the treatment of humans that remind of the darker time of slavery.<br />
<br />
One more unfortunate development with sharing information over the last 10 years is, that most of the information that is shared on conferences now have software patents attached to them. So in case someone wants to implement them (obviously without knowing it: every employee is told that they are not allowed to read patent descriptions) his / her company might have to pay for them in the future. The system of freely sharing information and helping other developers to succeed with the difficult technical implications was turned upside down in favor of companies with large law units. The willingness of developers to help their peers is used by companies to secure future economic advantages.<br />
On top of that middleware companies like Unity and others have a hard time to open-source their engine because they are concerned that they violate various patents and therefore would run into huge economic risks when they share source code.<br />
<br />
Apart from the "strike team" model, the most exciting development is the new breed of developers that adjusted to the new economic pressures of the App store model and make a living from new and innovative games. We had the pleasure of working with some of these and it is an awesome experience to feel the creative and positive energy that is flowing in those companies. They remind me of the development in the middle of the 90's when what we call now the game industry booted into "big" games that reach millions of people. This new generation now reaches hundreds of millions of people. You could say this is the third wave of game developers, being the first wave the developers of the 80's, the second wave the developers of the 90's.<br />
<br />
<br />
<br />
<br />
<br /></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-88455670975580310152015-06-14T13:17:00.002-07:002015-06-14T16:13:46.328-07:00Link Collection<div dir="ltr" style="text-align: left;" trbidi="on">
Here are some links that show some interesting progress:<br />
<div>
<br /></div>
<div>
<a href="http://www.husl-colors.org/" target="_blank">HUSL is a human-friendly alternative to HSL</a></div>
<div>
<br /></div>
<div>
<a href="http://www.catb.org/esr/structure-packing/?src=yc" target="_blank">The Lost Art of C Structure Packing</a></div>
<div>
<br /></div>
<div>
<a href="http://www.lifehack.org/articles/lifestyle/picture-show-you-clearly-the-effects-aperture-shutter-speed-and-iso-images.html?ref=fbp&n=2" target="_blank">A Picture To Show You Clearly The Effects of Aperture, Shutter Speed and ISO On Images</a></div>
<div>
<br /></div>
<div>
<a href="https://developer.nvidia.com/content/constant-buffers-without-constant-pain-0" target="_blank">Constant Buffers without Constant Pain</a></div>
<div>
<br /></div>
<div>
<a href="http://danluu.com/new-cpu-features/" target="_blank">What's New in CPUs Since the 80s and How Does It Affect Programmers?</a></div>
<div>
<br /></div>
<div>
<a href="http://www.gdcvault.com/play/1022094/JPS-Over-100x-Faster-than" target="_blank">JPS+: Over 100x Faster than A*</a><br />
<br />
<a href="http://jcgt.org/published/0003/04/08/paper-lowres.pdf" target="_blank">Adaptive Depth Bias for Shadow Maps</a></div>
</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-48259076405002584172015-05-31T20:23:00.000-07:002015-05-31T20:24:04.398-07:00Multi-GPU Game Engine<div dir="ltr" style="text-align: left;" trbidi="on">
Many high-end rendering solutions for -for example- battlefield simulations can utilize now hardware solutions with multiple consumer GPUs. The idea is to split up computational power in-between 4 - 8 GPUs to increase the level of realism as much as possible. <br />
Now with more modern APIs like DirectX 12 and probably Vulcan and before that CUDA, splitting up the rendering pipeline can happen in the following way:<br />
- GPU0 - fills up the G-Buffer after a Z pre-pass<br />
- GPU1 - Renders Deferred Lights and Shadows<br />
- GPU2 - Renders Particles and Vegetation<br />
- GPU3 - Renders Screen-Space Materials like skin etc. and PostFX<br />
<br />
Now you can use the result of GPU0 and feed it to GPU1 and then feed it to GPU2 and so on. All this will run in parallel but will introduce two or three frames of lag (depending on how you light Particles and Vegetation). As long as the system renders 60 fps or 120 fps this will not be as much noticeable (obviously one of the targets is to have a high framerate to make animations look smooth and then also having 4K resolution rendering). GPU4 and higher can work on Physics, AI and other things.
There is also the opportunity to spread out G-Buffer rendering over several GPUs, like one GPU is doing the Z pre-pass, then another fills up diffuse, normal and probably some geometry data to indentify different objects later or store their edges and another GPU is filling up the terrain data. Vegetation can be rendered on a dedicated GPU etc. etc..
On the CPU side the rule of thumb is that at least 2 cores are needed for one GPU. It is probably better to go for 3 or four. So a four GPU machine should have 8 - 16 CPU cores and a eight GPU machine 16 - 32 CPU cores; which might be split between several physical CPUs. We need at least 2x as much CPU RAM as the GPUs have RAM, so if four GPUs have each 2 GB, we need at least 16 GB Ram, if we have eight GPUs, we need at least 32 GB RAM etc..
<br />
A 4K resolution consists of 3840 × 2160 pixels and it will occupy with four render targets, each 32-bit per pixel (8:8:8:8 or 11:11:10), roughly 126.56 MB. This number goes up with 4x or 8x MSAA and maybe super-sampling. It is probably save to assume that the G-Buffer might occupy between 500 and 1GB.
<br />
Achieving a frametime of 8 - 16ms, means that even a high-end GPU will be quite busy to fill up a G-Buffer this size. So thinking about splitting this between two GPUs might make sense. <br />
A high-end PostFX pipeline is now < 5ms on medium-class GPUs but dedicating a whole high-end GPU means we can finally switch on the movie settings :-)<br />
A GPU particle system can easily saturate a GPU with 16 ms ... especially if it is not rendering in a quarter size resolution.
<br />
For Lights and shadows it depends on the number of lights that should be applied. Caching all the shadow data in partially resident textures or cube maps or any other shadow map technique will hit the memory budget of this card substantially.
<br />
<br />
<br />
<i>Note: I wrote this more than two years ago. At the time a G-Buffer was a valid solution for designing a rendering system. Now with the high-res displays it is not anymore.</i></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com4tag:blogger.com,1999:blog-398682525365778708.post-4743265881384979942015-05-27T10:56:00.000-07:002016-08-24T10:56:49.514-07:00V Buffer - Deferred Lighting Re-Thought<div dir="ltr" style="text-align: left;" trbidi="on">
After eight years I would like to go back to re-design the existing rendering systems, so that they are capable to run more efficiently on high-resolution devices and display more lights with attached shadows.<br />
<br />
Let's first see where we are: the Light Pre-Pass was introduced in March 2008 on this blog. At this point I had it already running in one R* game for a while. It eventually shipped in a large number of games and also outside of R*. The S.T.A.L.K.E.R series and the games developed by Naughty Dog had at the time a similar approach. Since then a number of modifications were proposed.<br />
One modification was to calculate lighting by tiling the G-Buffer, then sorting lights into those tiles and then execute each tile with its light. Johan Andersson covered a practical implementation in "DirectX 11 rendering in Battlefield 3" (<a href="http://www.slideshare.net/DICEStudio/directx-11-rendering-in-battlefield-3">http://www.slideshare.net/DICEStudio/directx-11-rendering-in-battlefield-3</a>). Before Tiled-Deferred, lights were additively blended into a buffer, consuming memory bandwidth with each blit. The Tiled-Deferred approach reduced memory bandwidth consumption substantially by resolving all the lights in one tile.<br />
The drawback of this approach is the higher minimum run-time cost. Sorting the lights into the tiles raised the "resting" workload even when only a few lights were rendered. Compared to the older approaches it didn't break even until one rendered a few dozen lights. Additionally as soon as lights had to be drawn with shadows, the memory bandwidth savings were negligible.<br />
Newer approaches like "Clustered Deferred and Forward Shading" (<a href="http://www.cse.chalmers.se/~uffe/clustered_shading_preprint.pdf">http://www.cse.chalmers.se/~uffe/clustered_shading_preprint.pdf</a>) by Ola Olsson et all. started solving the "light overdraw" problem in even more efficient ways. A practical implementation is shown on Emil Perrson's website (<a href="http://www.humus.name/Articles/PracticalClusteredShading.pdf">http://www.humus.name/Articles/PracticalClusteredShading.pdf</a>) in an example program.<br />
Because transparency solutions with all the approaches mentioned above are inconsistent with the way opaque objects are handled, there was a group of people that wishes to go back to forward rendering. Takahiro Harada described and refined an approach that he called Forward+ (<a href="http://www.slideshare.net/takahiroharada/forward-34779335">http://www.slideshare.net/takahiroharada/forward-34779335</a>). The tiled-based handling of light sources was similar to the Tiled-Deferred approach. The advantage of having a consistent way of lighting transparent and opaque objects was bought by having to re-submit all potentially visible geometry several times.<br />
<br />
Filling a G-Buffer or re-submitting geometry in case of Forward+ is expensive. For the Deferred Lighting implementations, the G-Buffer fill was the stage were also visibility of geometry was solved (there is also the option of making a Z Pre-Pass which means geometry is submitted one more time at least).<br />
With modern 4k displays and high-res devices like Tablets and smart phones, a G-Buffer is not a feasible solution anymore. When the Light Pre-Pass was developed a 1280x720 resolution was considered state of the art. Today 1080p is considered the minimum resolution, iOS and Android devices have resolutions several times this size and even modern PC monitors can have more than 4K resolution. <br />
MSAA increases the size and therefore cost manifold.<br />
<br />
Instead of rendering geometry into three or four render targets with overdraw (or re-submitting after the Z prepass), we need to find a way to store visibility data separate in a much smaller buffer, in a more efficient way.<br />
In other words, if we could capture the full-screen visibility of geometry in as small a footprint as possible, we could significantly reduce the cost of geometry submission and pixel overdraw afterwards.<br />
<br />
A first idea on how this could be done is described in the article "The Visibility Buffer: A Cache-Friendly Approach to Deferred Shading" by Christopher A. Burns et all.. The article outlines the idea to store per triangle visibility data in a visibility buffer.<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br /></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-41747077203427005522015-04-09T15:21:00.003-07:002015-04-09T15:25:53.273-07:00Introduction to Resource Binding in Microsoft DirectX* 12<div dir="ltr" style="text-align: left;" trbidi="on">
I spent some time to write an article that should explain resource binding in DirectX 12. When I looked at this for the first time I had a tough time to get my head around resource binding ... so I am hoping this article makes it for others easier to understand. Let me know in the comments ...<br />
<br />
<a href="https://software.intel.com/en-us/articles/introduction-to-resource-binding-in-microsoft-directx-12" target="_blank">https://software.intel.com/en-us/articles/introduction-to-resource-binding-in-microsoft-directx-12</a></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com1tag:blogger.com,1999:blog-398682525365778708.post-23395528960232110722015-01-12T15:50:00.004-08:002015-02-01T16:51:22.568-08:00Reloaded: Compute Shader Optimizations for AMD GPUs: Parallel Reduction<div dir="ltr" style="text-align: left;" trbidi="on">
After nearly a year, it was time to revisit the last blog entry. The source code of the example implementation was still on one of my hard-drives and needed to be cleaned-up and released, which I had planned for the first quarter of last year.<br />
I also did receive a comment high-lighting a few mistakes I made in the previous blog post and on top of that I wanted to add numbers for other GPUs as well.<br />
<br />
Now while looking at the code the few hours of time I had reserved for the task turned into a day and then a bit more. On top of that getting some time off from my project management duties at Confetti was quite enjoyable :-)<br />
<br />
In the previous blog post I forgot to mention that I used INTEL's GPA to measure all the performance numbers. Several runs of the performance profiler always generated slightly different results but I felt the overall direction is becoming clear.<br />
My current setup uses the currently latest AMD driver 14.12.<br />
<br />
All the source code can be found at<br />
<br />
<a href="https://code.google.com/p/graphicsdemoskeleton/source/browse/#svn%2Ftrunk%2F04_DirectCompute%20Parallel%20Reduction%20Case%20Study">https://code.google.com/p/graphicsdemoskeleton/source/browse/#svn%2Ftrunk%2F04_DirectCompute%20Parallel%20Reduction%20Case%20Study</a><br />
<br />
While comparing the current performance numbers with the previous setup from the previous post, it becomes obvious that not much has changed for the first three rows. Here is the new chart:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPFIx78mCb0WHXoM0hLP79H2dKEgGkIqlR-eKj-AUwge4A3TpW0HQYDxCOwPOkAaTgV4664_ujYHcYlSbzb4UpkTyfyLWEYlPmG2aCjWvv2tG9mq2huZ08xjBj9TVMuEOaE1UcOrYf55E/s1600/Numbers.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPFIx78mCb0WHXoM0hLP79H2dKEgGkIqlR-eKj-AUwge4A3TpW0HQYDxCOwPOkAaTgV4664_ujYHcYlSbzb4UpkTyfyLWEYlPmG2aCjWvv2tG9mq2huZ08xjBj9TVMuEOaE1UcOrYf55E/s1600/Numbers.PNG" height="640" width="458" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<span style="font-size: x-small;"><i>Latest Performance numbers from January 2015</i></span></div>
<br />
In the fourth column ("Pre-fetching two color values into TGSM with 64 threads"), the numbers for the 6770 are nearly cut in half while they stay roughly the same for the other cards; only a slight improvement on the 290X. This is the first shader that fetches two values from device memory, converts them to luminance, stores them into shared memory and then kicks off the Parallel Reduction.<br />
Here is the source code.<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;">StructuredBuffer<float4> Input : register( t0 );</float4></span><br />
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">RWTexture2D<float4> Result : register (u0);</float4></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define THREADX 8</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define THREADY 16</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">cbuffer cbCS : register(b0)</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">{</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>int c_height : packoffset(c0.x);</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>int c_width : packoffset(c0.y);<span class="Apple-tab-span" style="white-space: pre;"> </span>// size view port</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">/*<span class="Apple-tab-span" style="white-space: pre;"> </span></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>This is in the constant buffer as well but not used in this shader, so I just keep it in here as a comment</span></div>
<div style="text-align: left;">
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>float c_epsilon : packoffset(c0.z);<span class="Apple-tab-span" style="white-space: pre;"> </span>// julia detail <span class="Apple-tab-span" style="white-space: pre;"> </span></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>int c_selfShadow : packoffset(c0.w); // selfshadowing on or off </span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>float4 c_diffuse : packoffset(c1);<span class="Apple-tab-span" style="white-space: pre;"> </span>// diffuse shading color</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>float4 c_mu : packoffset(c2);<span class="Apple-tab-span" style="white-space: pre;"> </span>// julia quaternion parameter</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>float4x4 rotation : packoffset(c3);</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>float zoom : packoffset(c7.x);</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">*/</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">};</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// the following shader applies parallel reduction to an image and converts it to luminance</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define groupthreads THREADX * THREADY</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">groupshared float sharedMem[groupthreads];</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;">[numthreads(THREADX, THREADY, 1)]</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">void PostFX( uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint GI : SV_GroupIndex )</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">{</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>const float4 LumVector = float4(0.2125f, 0.7154f, 0.0721f, 0.0f);</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// thread groups in x is 1920 / 16 = 120</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// thread groups in y is 1080 / 16 = 68</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// index in x (1920) goes from 0 to 119 | 120 (thread groups) * 8 (threads) = 960 indices in x</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// index in y (1080) goes from 0 to 67 | 68 (thread groups) * 16 (threads) = 1080 indices in y</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>uint idx = ((DTid.x * 2) + DTid.y * c_width);</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// 1920 * 1080 = 2073600 pixels
<br /> // 120 * 68 * 128(number of threads : 8 * 16) * 2 (number of fetches) = 2088960
</span></div>
<div>
<span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"> float temp = (dot(Input[idx], LumVector) + dot(Input[idx + 1], LumVector));</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] = temp;</span></div>
<div style="text-align: left;">
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// wait until everything is transfered from device memory to shared memory</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// hard-coded for 128 threads</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 64)</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 64];</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 32) sharedMem[GI] += sharedMem[GI + 32];</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 16) sharedMem[GI] += sharedMem[GI + 16];</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 8) sharedMem[GI] += sharedMem[GI + 8];</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 4) sharedMem[GI] += sharedMem[GI + 4];</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 2)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 2];</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 1)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 1];</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// Have the first thread write out to the output</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI == 0)</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>{</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// write out the result for each thread group</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>Result[Gid.xy] = sharedMem[0] / (THREADX * THREADY * 2);</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>}</span></div>
<div style="text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">}</span></div>
<div>
<br /></div>
<div>
The grid size in x and why is 1920 / 16 and 1080 / 16. In other words this is the number of thread groups kicked off by the dispatch call.</div>
<div>
<br /></div>
<div>
The next shader extends the idea to fetching four values. It fetches four instead of two values from device memory.</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// thread groups in x is 1920 / 16 = 120</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// thread groups in y is 1080 / 16 = 68</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// index in x (1920) goes from 0 to 119 | 120 (thread groups) * 4 (threads) = 480 indices in x</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// index in y (1080) goes from 0 to 67 | 68 (thread groups) * 16 (threads) = 1080 indices in y</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">uint idx = ((DTid.x * 4) + DTid.y * c_width);</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// 1920 * 1080 = 2073600 pixels</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// 120 * 68 * 64 (number of threads : 4 * 16) * 4 (number of fetches) = 2088960</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">float temp = (dot(Input[idx], LumVector) + dot(Input[idx + 1], LumVector))</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span> + (dot(Input[idx + 2], LumVector) + dot(Input[idx + 3], LumVector));</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// store in shared memory </span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">sharedMem[IndexOfThreadInGroup] = temp;</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// wait until everything is transfered from device memory to shared memory</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">GroupMemoryBarrierWithGroupSync();</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 32) sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 32];</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 16) sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 16];</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 8) sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 8];</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 4) sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 4];</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 2)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 2];</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 1)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 1];</span></div>
</div>
<br />
Looking at the performance results ("Pre-fetching four color values into TGSM with 64 threads"), the difference between the performance numbers is not significant. This seems to be the first sign that the shader might be read memory bandwidth limited. Just reading the 1080p memory area takes the longest time.<br />
<br />
While all the previous shaders were writing the reduced image into a 120 x 68 area, The following two shaders in the chart are writing into a 60 x 34 area. This is mostly achieved by decreasing the grid size, or in other words running less thread groups. To make up for the decrease in grid size we had to increase the size of each thread group to 256 and then 512.<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define THREADX 8</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define THREADY 32</span><br />
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">... // more code here</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// thread groups in x is 1920 / 32 = 60</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// thread groups in y is 1080 / 32 = 34</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// index in x (1920) goes from 0 to 60 (thread groups) * 8 (threads) = 480 indices in x</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// index in y (1080) goes from 0 to 34 (thread groups) * 32 (threads) = 1088 indices in y</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">uint idx = ((DTid.x * 4) + DTid.y * c_width);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// 1920 * 1080 = 2073600 pixels</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// 60 * 34 * 256 (number of threads : 8 * 32) * 4 (number of fetches) = 2088960</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">float temp = (dot(Input[idx], LumVector) + dot(Input[idx + 1], LumVector))</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span> <span class="Apple-tab-span" style="white-space: pre;"> </span> + (dot(Input[idx + 2], LumVector) + dot(Input[idx + 3], LumVector));</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// store in shared memory </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">sharedMem[IndexOfThreadInGroup] = temp;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// wait until everything is transfered from device memory to shared memory</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// hard-coded for 256 threads</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 128)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 128];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 64)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 64];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 32) sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 32];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 16) sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 16];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 8) sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 8];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 4) sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 4];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 2)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 2];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 1)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 1];</span><br />
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">... // more code here</span></div>
<div>
<br /></div>
<div>
The next shader decreases the grid size even more and increases the number of threads of each thread group to 1024; the current maximum that the Direct3D run-time allows. For both shaders ("Pre-fetching four color values into TGSM with 1024 threads" and then "Pre-fetching four color values into 2x TGSM with 1024 threads"), the performance numbers do not change much compared to the previous shaders, although the reduction has to do more work, because the dimension of the target area halve in each direction. Here is the source code for the second of the two shaders that fetch four color values with 1024 threads per thread group:<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define THREADX 16</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define THREADY 64</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//.. constant buffer code here</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// the following shader applies parallel reduction to an image and converts it to luminance</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define groupthreads THREADX * THREADY</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">groupshared float sharedMem[groupthreads * 2]; // double the number of shared mem slots</span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;">[numthreads(THREADX, THREADY, 1)]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">void PostFX( uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint GI : SV_GroupIndex )</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">{</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>const float4 LumVector = float4(0.2125f, 0.7154f, 0.0721f, 0.0f);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// thread groups in x is 1920 / 64 = 30</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// thread groups in y is 1080 / 64 = 17</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// index in x (1920) goes from 0 to 29 | 30 (thread groups) * 16 (threads) = 480 indices in x</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// index in y (1080) goes from 0 to 16 | 17 (thread groups) * 64 (threads) = 1088 indices in y</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>uint idx = ((DTid.x * 4) + DTid.y * c_width); // index into structured buffer</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// 1920 * 1080 = 2073600 pixels</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// 30 * 17 * 1024 (number of threads : 16 * 64) * 4 (number of fetches) = 2088960</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>uint idSharedMem = GI * 2;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[idSharedMem] = (dot(Input[idx], LumVector) + dot(Input[idx + 1], LumVector));</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[idSharedMem + 1] = (dot(Input[idx + 2], LumVector) + dot(Input[idx + 3], LumVector));</span><br />
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// wait until everything is transfered from device memory to shared memory</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// hard-coded for 1024 threads</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 1024)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 1024];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 512)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 512];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 256)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 256];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 128)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 128];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 64)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 64];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 32) sharedMem[GI] += sharedMem[GI + 32];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 16) sharedMem[GI] += sharedMem[GI + 16];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 8) sharedMem[GI] += sharedMem[GI + 8];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 4) sharedMem[GI] += sharedMem[GI + 4];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 2)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 2];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 1)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 1];</span><br />
<br />
One thing I wanted to try here, is utilize double the amount of shared memory and therefore saturate the 1024 threads more by having the first addition happening in shared memory. At the end that didn't change much because the shader is not utilizing temp registers much, so replacing a temp register with using shared memory didn't increase performance much.<br />
<br />
My last test was aiming at fetching 16 color values while decreasing the 1080p image to 15x9. The result is shown in the last column. This shader also uses 1024 threads and fetches into 2x the shared memory like the previous one. It runs slower than the previous shaders. Here is the source code:<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define THREADX 16</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define THREADY 64</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//.. some constant buffer code here </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// the following shader applies parallel reduction to an image and converts it to luminance</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">//</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">#define groupthreads THREADX * THREADY</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">groupshared float sharedMem[groupthreads * 2]; // double the number of shared mem slots</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: 'Courier New', Courier, monospace; font-size: xx-small;">[numthreads(THREADX, THREADY, 1)]</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">void PostFX( uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint GI : SV_GroupIndex )</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">{</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>const float4 LumVector = float4(0.2125f, 0.7154f, 0.0721f, 0.0f);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// thread groups in x is 1920 / 128 = 15</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// thread groups in y is 1080 / 128 = 9</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> // index in x (1920) goes from 0 to 14 | 15 (thread groups) * 16 (threads) <br /> // = 240 indices in x | need to fetch 8 in x direction</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// index in y (1080) goes from 0 to 8 | 9 (thread groups) * 64 (threads) <br /> // = 576 indices in y | need to fetch 2 in y direction</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>uint idx = ((DTid.x * 8) + (DTid.y * 2) * c_width); // index into structured buffer</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// 1920 * 1080 = 2073600 pixels</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// 15 * 9 * 1024 (number of threads : 16 * 64) * 15 (number of fetches) = 2073600</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>uint idSharedMem = GI * 2;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[idSharedMem] = (dot(Input[idx], LumVector) </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 1], LumVector) </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 2], LumVector) </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 3], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 4], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 5], LumVector) </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 6], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 7], LumVector));</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[idSharedMem + 1] = (dot(Input[idx + 8], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 9], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 10], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 11], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 12], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 13], LumVector)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 14], LumVector) </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>+ dot(Input[idx + 15], LumVector));</span><br />
<span class="Apple-tab-span" style="white-space: pre;"><span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// wait until everything is transfered from device memory to shared memory</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>// hard-coded for 1024 threads</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 1024)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 1024];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 512)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 512];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 256)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 256];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 128)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 128];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 64)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 64];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 32) sharedMem[GI] += sharedMem[GI + 32];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 16) sharedMem[GI] += sharedMem[GI + 16];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 8) sharedMem[GI] += sharedMem[GI + 8];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 4) sharedMem[GI] += sharedMem[GI + 4];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 2)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 2];</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (GI < 1)<span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[GI] += sharedMem[GI + 1];</span></div>
<div>
<br /></div>
<div>
Looking at all those numbers it seems that the performance is mostly limited by the speed on how to read the 1080p source buffer. In the moment I would like to predict that reducing the source resolution to 720p or 480p would lead to a more differentiated view of performance. Maybe something to try in the future ...</div>
<div>
<br /></div>
<br /></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com2tag:blogger.com,1999:blog-398682525365778708.post-3916205053494144162014-03-26T14:19:00.002-07:002014-03-27T11:58:39.273-07:00Compute Shader Optimizations for AMD GPUs: Parallel Reduction<div dir="ltr" style="text-align: left;" trbidi="on">
We recently looked more often into compute shader optimizations on AMD platforms. Additionally I had a UCSD class in Winter that dealt with this topic and a talk at the Sony booth at GDC 2014 that covered the same topic.<br />
This blog post covers a common scenario while implementing a post-processing pipeline: Parallel Reduction. It uses the excellent talk given by <a href="http://developer.download.nvidia.com/assets/cuda/files/reduction.pdf" target="_blank">Mark Harris </a>a few years back as a starting point, enriched with new discoveries, credited to the new hardware platforms and AMD specifics.<br />
<br />
The topics covered are:<br />
<ul>
<li>Sequential Shared Memory (TGSM) Access: utilizing the Memory bank layout </li>
<li>When to Unroll Loops in a compute shader </li>
<ul>
<li>Overhead of address arithmetic and loop instructions </li>
<li>Skipping Memory Barriers: Wavefront </li>
</ul>
<li>Pre-fetching data into Shared Memory </li>
</ul>
<div>
Most of the examples accompanying this blog post are showing a simple parallel reduction going from 1080p to 120x68. While reducing the size of the image, these examples also reduce the color color value to luminance.</div>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP-mKVITjz072v3MjOKkUETl3grkskNXgir_vYFwKmTFVFAHOPzRAOHM1gvoHPIk2Irr8crVrAAINzAouwoXfDSRoX0yVA02mnBtOKq_X00ODEkrboQkDOoG2xl_SnWNSqBVmcc6wvbGE/s1600/ParallelReduction.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP-mKVITjz072v3MjOKkUETl3grkskNXgir_vYFwKmTFVFAHOPzRAOHM1gvoHPIk2Irr8crVrAAINzAouwoXfDSRoX0yVA02mnBtOKq_X00ODEkrboQkDOoG2xl_SnWNSqBVmcc6wvbGE/s1600/ParallelReduction.JPG" height="156" width="400" /></a></div>
<div style="text-align: center;">
<i><span style="font-size: x-small;">Image 1 - Going from 1080p to 120x68 and from Color to Luminance</span></i></div>
</div>
<div>
<br />
On an algorithmic level, Parallel Reduction looks like a tree-based approach:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOly-NXzHQB9uQwW-hFNFdryaba6k_DYh7hy1jvMArv5STH-s1odeBs78tcsfD-oDj-9bahNqBpeUA9VECLsL_edNhldZhPtIjkjtaTjUKrjzHwqUB86oh-X5D2i4bDRhitz2pFvzvFu0/s1600/TreeStructure.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOly-NXzHQB9uQwW-hFNFdryaba6k_DYh7hy1jvMArv5STH-s1odeBs78tcsfD-oDj-9bahNqBpeUA9VECLsL_edNhldZhPtIjkjtaTjUKrjzHwqUB86oh-X5D2i4bDRhitz2pFvzvFu0/s1600/TreeStructure.JPG" height="121" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 2 - Tree-based approach for Parallel Reduction</span></i></div>
<br />
Instead of building a fully recursive kernel, which is not possible on current hardware, the algorithm mimics recursion by using a for loop.<br />
As you will see later on, the fact that each of the invocations utilizes less threads from a pool of threads in a thread group, has some impact on performance. Let's say we allocate 256 threads in a thread pool, only the first iteration of the Parallel Reduction algorithm will use all of them. The second iteration -based on the implementation- might only use half of them and the next one again half of those etc..<br />
<b><br /></b></div>
<div>
<b>TGSM Access: Utilizing the Memory bank layout</b></div>
<div>
One of the first rules of thumb mentioned by <a href="https://twitter.com/nthibieroz" target="_blank">Nicolas Thibieroz</a> is dealing with the access pattern that is used to access TGSM. There is only a limited number of I/O banks and they need to be utilized in the most efficient way. It turns out that AMD and NVIDIA seem to have both 32 banks.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpG5oD0EjdSb0kylbQjjiiQyJo5MpKSefUrYs7APrFQg0eG6PYY98weMt4r2A5eoVqsOPBqrAbOOCNVxNJRGQWbu-DVN5gI6pPlsZPhCMOA1Lu9vjh3e70QeWTEQvDSXVplowZK0e7c08/s1600/SharedMemoryBanks.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpG5oD0EjdSb0kylbQjjiiQyJo5MpKSefUrYs7APrFQg0eG6PYY98weMt4r2A5eoVqsOPBqrAbOOCNVxNJRGQWbu-DVN5gI6pPlsZPhCMOA1Lu9vjh3e70QeWTEQvDSXVplowZK0e7c08/s1600/SharedMemoryBanks.JPG" height="45" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 3 - Memory banks are arranged linearly with addresses</span></i></div>
<br />
Accessing TGSM with addresses that are 32 DWORD apart will lead to a situation where threads will use the same bank. This generates so called bank conflicts. In other words, accessing the same address from multiple threads creates bank conflicts.<br />
The preferred method to access TGSM is to have 32 threads use 32 different banks. Usually the extreme example mentioned is the 2D array example, where you want to access memory by increasing the bank number first -you might consider this moving horizontally- and then increase the vertical direction. This way threads will hit different banks more often.<br />
The more subtle bank conflicts happen when memory banks are accessed in non-sequential patterns. Mark Harris has shown the following example. Here is an image depicting this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0oJHiH6a6KLCuPN__POe314QTJe2kbh7IDyfU95Bb9eCIEX0Kxyy42WwH554Je-lZd91rtV-3tKi5o4IbHkCbGNLPsiLLjxWD_CQ55eEhYoEU6k4rR0lwNfrMBNfNXwgXyyVvAHrcg1M/s1600/InterleavedMemoryAccess.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0oJHiH6a6KLCuPN__POe314QTJe2kbh7IDyfU95Bb9eCIEX0Kxyy42WwH554Je-lZd91rtV-3tKi5o4IbHkCbGNLPsiLLjxWD_CQ55eEhYoEU6k4rR0lwNfrMBNfNXwgXyyVvAHrcg1M/s1600/InterleavedMemoryAccess.JPG" height="207" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 4 - Memory banks accessed interleaved</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"><br /></span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"></span></i></div>
The first example source is showing an implementation of this memory access pattern:<br />
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">//
Example for Interleaved Memory access</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">numthreads</span><span style="font-family: 'Courier New';">(THREADX,
THREADY, 1)]</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">void </span><span style="font-family: 'Courier New';">PostFX</span><span style="font-family: 'Courier New';">(
uint3 </span><span style="font-family: 'Courier New';">Gid</span><span style="font-family: 'Courier New';"> : </span><span style="font-family: 'Courier New';">SV_GroupID</span><span style="font-family: 'Courier New';">,
uint3 </span><span style="font-family: 'Courier New';">DTid</span><span style="font-family: 'Courier New';"> : </span><span style="font-family: 'Courier New';">SV_DispatchThreadID</span><span style="font-family: 'Courier New';">,
uint3 </span><span style="font-family: 'Courier New';">GTid</span><span style="font-family: 'Courier New';"> : </span><span style="font-family: 'Courier New';">SV_GroupThreadID</span><span style="font-family: 'Courier New';">, </span><span style="font-family: 'Courier New';">uint</span><span style="font-family: 'Courier New';"> GI :
</span><span style="font-family: 'Courier New';">SV_GroupIndex</span><span style="font-family: 'Courier New';"> )</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">const</span><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">float4 </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';"> =
float4(0.2125f, 0.7154f, 0.0721f, 0.0f);</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">uint</span><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> = </span><span style="font-family: 'Courier New';">DTid.x</span><span style="font-family: 'Courier New';"> + </span><span style="font-family: 'Courier New';">DTid.y</span><span style="font-family: 'Courier New';"> * </span><span style="font-family: 'Courier New';">c_width</span><span style="font-family: 'Courier New';">; </span><span style="font-family: 'Courier New';">//
read from structured buffer</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI</span><span style="font-family: 'Courier New';">] =
dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';">], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">); </span><span style="font-family: 'Courier New';">//
store in shared memory </span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">GroupMemoryBarrierWithGroupSync</span><span style="font-family: 'Courier New';">();</span><span style="font-family: 'Courier New';"> //
wait until everything is </span><span style="font-family: 'Courier New';">transfered</span><span style="font-family: 'Courier New';"> from
device memory to shared memory</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"><br /></span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">unroll(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';">)]</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">for </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">uint</span><span style="font-family: 'Courier New';"> s = 1; s < </span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';">; s
*= 2</span><span style="font-family: 'Courier New';">) // </span><span style="font-family: 'Courier New';">stride: 1, 2, 4, 8, 16, 32, 64, </span><span style="font-family: 'Courier New';">128</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">int</span><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">index
= 2 * s * GI;</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"><br />
if </span><span style="font-family: 'Courier New';">(index < (</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';">))</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[index</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[index
+ s];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">GroupMemoryBarrierWithGroupSync</span><span style="font-family: 'Courier New';">();</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">}</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">Have the first thread write out to the
output</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(GI == 0)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">write out the result for each thread
group</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">Result[</span><span style="font-family: 'Courier New';">Gid.xy</span><span style="font-family: 'Courier New';">] = </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[0] /
(THREADX * THREADY);</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">}</span></span></div>
<span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">}</span><br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<br /></div>
This code fetches TGSM in its for loop in a pattern as showed in Image 4. The image of a sequential access pattern is supposed to look like this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVutXvgMPLCLmTwhyphenhyphenD-vjurzS-krHIP-ubKKVq-PxTUTsUf_JY67C-Hlf-tvci3vEc4EwPlu4gvb2iFfoIvChN1Q52niQiIFogbR45y6SfAdqgK0mX_G7m4kckyCkuIUiU2i7JwwHDzE8/s1600/SequentialMemoryAccess.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVutXvgMPLCLmTwhyphenhyphenD-vjurzS-krHIP-ubKKVq-PxTUTsUf_JY67C-Hlf-tvci3vEc4EwPlu4gvb2iFfoIvChN1Q52niQiIFogbR45y6SfAdqgK0mX_G7m4kckyCkuIUiU2i7JwwHDzE8/s1600/SequentialMemoryAccess.JPG" height="247" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 5 - Memory banks accessed sequential</span></i></div>
<br />
The source code of the sequential access version looks like this:<br />
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">numthreads</span><span style="font-family: 'Courier New';">(THREADX,
THREADY, 1)]</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">void </span><span style="font-family: 'Courier New';">PostFX</span><span style="font-family: 'Courier New';">(
uint3 </span><span style="font-family: 'Courier New';">Gid</span><span style="font-family: 'Courier New';"> : </span><span style="font-family: 'Courier New';">SV_GroupID</span><span style="font-family: 'Courier New';">,
uint3 </span><span style="font-family: 'Courier New';">DTid</span><span style="font-family: 'Courier New';"> : </span><span style="font-family: 'Courier New';">SV_DispatchThreadID</span><span style="font-family: 'Courier New';">,
uint3 </span><span style="font-family: 'Courier New';">GTid</span><span style="font-family: 'Courier New';"> : </span><span style="font-family: 'Courier New';">SV_GroupThreadID</span><span style="font-family: 'Courier New';">, </span><span style="font-family: 'Courier New';">uint</span><span style="font-family: 'Courier New';"> GI :
</span><span style="font-family: 'Courier New';">SV_GroupIndex</span><span style="font-family: 'Courier New';"> )</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">const</span><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">float4 </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';"> =
float4(0.2125f, 0.7154f, 0.0721f, 0.0f);</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">uint</span><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> = </span><span style="font-family: 'Courier New';">DTid.x</span><span style="font-family: 'Courier New';"> + </span><span style="font-family: 'Courier New';">DTid.y</span><span style="font-family: 'Courier New';"> * </span><span style="font-family: 'Courier New';">c_width</span><span style="font-family: 'Courier New';">; </span><span style="font-family: 'Courier New';">//
read from structured buffer</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI</span><span style="font-family: 'Courier New';">] =
dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';">], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">); </span><span style="font-family: 'Courier New';">//
store in shared memory </span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">GroupMemoryBarrierWithGroupSync</span><span style="font-family: 'Courier New';">();</span><span style="font-family: 'Courier New';"> //
wait until everything is </span><span style="font-family: 'Courier New';">transfered</span><span style="font-family: 'Courier New';"> from
device memory to shared memory</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">[unroll(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> / </span><span style="font-family: 'Courier New';">2</span><span style="font-family: 'Courier New';">)]</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="color: red; font-family: 'Courier New';">for (</span><span style="color: red; font-family: 'Courier New';">uint</span><span style="color: red; font-family: 'Courier New';"> s = </span><span style="color: red; font-family: 'Courier New';">groupthreads</span><span style="color: red; font-family: 'Courier New';"> / 2; s
> 0; s >>= 1)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="color: red; font-family: 'Courier New';"><span style="font-size: xx-small;">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="color: red; font-family: 'Courier New';"> if </span><span style="color: red; font-family: 'Courier New';">(GI < s)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="color: red; font-family: 'Courier New';"> </span><span style="color: red; font-family: 'Courier New';">sharedMem</span><span style="color: red; font-family: 'Courier New';">[GI</span><span style="color: red; font-family: 'Courier New';">] += </span><span style="color: red; font-family: 'Courier New';">sharedMem</span><span style="color: red; font-family: 'Courier New';">[GI +
s];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="color: red; font-family: 'Courier New';"> </span><span style="color: red; font-family: 'Courier New';">GroupMemoryBarrierWithGroupSync</span><span style="color: red; font-family: 'Courier New';">();</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="color: red; font-family: 'Courier New';"><span style="font-size: xx-small;">}</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> // </span><span style="font-family: 'Courier New';">Have the first thread write out to the
output</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(GI == 0)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">write out the result for each thread
group</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">Result[</span><span style="font-family: 'Courier New';">Gid.xy</span><span style="font-family: 'Courier New';">] = </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[0] /
(THREADX * THREADY);</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">}</span></span></div>
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">}</span></span></div>
</div>
<div>
<br />
The changes are marked in red. While on previous hardware generations, this slight change in source code had some impact on the performance, it looks like on modern AMD GPUs it doesn't seem to make a difference anymore. All the measurements were done on an AMD RADEON(TM) HD 6770, an AMD RADEON(TM) HD 7750 and an AMD RADEON(TM) HD 7850:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKiXPQs3uPqBtXolu_1cKiGtzFoX77YvEFymLLz7rHZDIzlrnCoBph_ZXhEjHDy10cI_7J69_V6L4GUzmdA-mrThg-DCtklnQnE_s1kD0RqSgy56IDt0ftW360VAu7h4k0sjGwe21wpp0/s1600/InterleavedMemoryAccessPerformanceImpact.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKiXPQs3uPqBtXolu_1cKiGtzFoX77YvEFymLLz7rHZDIzlrnCoBph_ZXhEjHDy10cI_7J69_V6L4GUzmdA-mrThg-DCtklnQnE_s1kD0RqSgy56IDt0ftW360VAu7h4k0sjGwe21wpp0/s1600/InterleavedMemoryAccessPerformanceImpact.JPG" height="96" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 5 - Peformance of Interleaved / Sequential TGSM access pattern</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"><br /></span></i></div>
</div>
In case of our example program re-arranging the access pattern doesn't make a difference. It might be that the driver re-arranges the code already or the hardware re-directs the accesses.<br />
<br />
<b>Unroll the Loops</b><br />
A likely overhead of the shaders shown above is the instruction overhead of ancillary instructions that are not loads, stores, or arithmetic instructions for core computations. In other words address arithmetic and loop instructions overhead.<br />
Thread groups that access Thread Group Shared Memory are automatically broken down into hardware schedulable groups of threads. In case of NVIDIA, those are called Warps and there are 32 threads in a warp, and in case of AMD they are called a Wavefront and there are 64 threads in a wavefront (there is a finer level of granularity regarding Wavefronts that we won't cover here). Instructions are SIMD synchronous within a Warp or Wavefront. That means as long as the number of threads executed are below 32 for NVIDIA or below 64 on AMD, a memory barrier is not necessary.<br />
In case of the tree like algorithm that is used for Parallel Reduction as shown in Image 2, the number of threads utilized in a loop are decreasing. As soon as they are below 32 or 64, a memory barrier shouldn't be necessary anymore.<br />
This means that unrolling loops not only might save some ancillary instructions but also might reduce the number of memory barriers used in a compute shader. Source code for an unrolled loop might look like this:<br />
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">… //
like the previous </span><span style="font-family: 'Courier New';">shader</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';">
>= 256)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(GI < 128)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
128];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">GroupMemoryBarrierWithGroupSync</span><span style="font-family: 'Courier New';">();</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">}</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">AMD -
64 / NVIDIA - 32</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(GI
< 64)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">{</span></span><br />
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;"></span></span><br />
<span style="font-family: Courier New; font-size: xx-small;"> if (groupthreads >= 64) sharedMem[GI] += sharedMem[GI + 32];</span><br />
<div>
<span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;"> </span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">if </span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">(</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">groupthreads</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">
>= 32) </span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">sharedMem</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">[GI]
+= </span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">sharedMem</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">[GI +
16];</span></div>
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 16)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
8];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 8)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
4];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> </span><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 4)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
2];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 2)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
1];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">}</span></span></div>
<span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">…</span><br />
<br />
The performance numbers for this optimization show that older hardware appreciates the effort of unrolling the loop and decreasing the number of memory barriers more than newer designs:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4ECwdeRDYm01ssAAV5dmJhGpJXlOiy3hZRSpUnWv6_H_v_PkAa51zPisuH41HQRECPtaggCTkao7klp4b-F6_ZmQeFMiFNm2Imrq_XW9j-TIRxfye2VXQa3ykeeBrgJPdQO7YNJYOePw/s1600/UnrollLoopsPerformanceImpact.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4ECwdeRDYm01ssAAV5dmJhGpJXlOiy3hZRSpUnWv6_H_v_PkAa51zPisuH41HQRECPtaggCTkao7klp4b-F6_ZmQeFMiFNm2Imrq_XW9j-TIRxfye2VXQa3ykeeBrgJPdQO7YNJYOePw/s1600/UnrollLoopsPerformanceImpact.JPG" height="127" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 6 - Unrolled Loops / Less Memory Barriers performance Impact</span></i></div>
<br />
<b>Pre-fetching two Color Values into Shared Memory</b><br />
When looking at the previous shader, the only operation that utilizes all 256 threads in the thread group is the first load into shared memory. By fetching two color values from device memory and adding them already at the beginning of the shader, we could utilize the threads better.<br />
To stay consistent with the previous Parallel Reduction shaders and offering the same 1080p to 120x68 reduction, the following shader only uses 64 threads in the thread group.<br />
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">…</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">//
pack two values</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">//
like the previous </span><span style="font-family: 'Courier New';">shader</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">//
store in shared memory </span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">float
temp = (dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> *
2], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">) +
dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> * 2
+ 1], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">));</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
= temp;</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<br /></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">//
AMD - 64 / NVIDIA - 32 </span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">if
(GI < 32)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';">
>= 32) </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
16];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 16)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
8];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 8)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
4];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 4)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
2];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 2)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI]
+= </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[GI +
1];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">}</span></span></div>
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">…</span></span></div>
<br />
Because the number of threads used is 64, memory barriers are not necessary.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh67uHnv_K6CEdGG9lC5msE1g91N_Qnh2xkDzyTxX6E-KCY1qojhd8MxUfGgFl8TLo_aEZSRupxj7XabmChMjEE2xPLzJ0IIRYlr_nIakB1A-jZNZbWMbpdkEAfSIDAuiBU2wZlw25Unoo/s1600/FetchingTwoValuesFromDeviceMemoryPerformanceImpact.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh67uHnv_K6CEdGG9lC5msE1g91N_Qnh2xkDzyTxX6E-KCY1qojhd8MxUfGgFl8TLo_aEZSRupxj7XabmChMjEE2xPLzJ0IIRYlr_nIakB1A-jZNZbWMbpdkEAfSIDAuiBU2wZlw25Unoo/s1600/FetchingTwoValuesFromDeviceMemoryPerformanceImpact.JPG" height="168" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 7 - Pre-fetching two color values</span></i></div>
<br />
It seems that throughout the hardware generations, the performance benefits from fetching two values at the same time, although the number of threads per thread group were reduced to 64 from 256 are appreciated. The reduced number of threads will become a topic later on.<br />
<br />
<b>Pre-fetching four Color Values into Shared Memory</b><br />
With the success story behind fetching two color values into TGSM, the obvious question arises, what would happen if four values would be fetched. To keep the Parallel Reduction algorithm comparable, so that it reduces from 1080p to 120x68, the threads in the thread group are reduced again.<br />
The following shader only uses 16 threads per thread group and is therefore considered not efficient in this respect. The official rule of thumb is using a multiply of 64. On the bright side it doesn't use any memory barriers.<br />
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">…</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">//
pack four values </span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">#define
THREADX 4</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">#define
THREADY 4</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">…</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">like
the previous </span><span style="font-family: 'Courier New';">shader</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">float
</span><span style="font-family: 'Courier New';">temp
= (dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> *
4], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">) +
dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> * 4
+ 1], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">))</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> + </span><span style="font-family: 'Courier New';">(dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> * 4
+ 2], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">) +
dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> * 4
+ 3], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">));</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">store
in shared memory </span><span style="font-family: 'Courier New';">->
no group barrier</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] =
temp;</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">AMD -
64 / NVIDIA - 32 </span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> <
</span><span style="font-family: 'Courier New';">16)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">{</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 16)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> +
8];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 8)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> +
4];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 4)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> +
2];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 2)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> +
1];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">}</span></span></div>
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">…</span></span></div>
<br />
The performance increase compared to the previous shader shows a nearly linear increase:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsSeJg_PJxj_oT_DHRWes4ZzXjkP4aidnxOrt0LwWEeyhmtkwm5Xm05GECbzYRFMTYHQmbeqg5FHc1C9KovaOx7sgKm1h5KZIBr3DzyoubBDwtplaaD9W-_fwojlSVjWfqaDBy1badWcg/s1600/FetchingFourValuesFromDeviceMemoryPerformanceImpact.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsSeJg_PJxj_oT_DHRWes4ZzXjkP4aidnxOrt0LwWEeyhmtkwm5Xm05GECbzYRFMTYHQmbeqg5FHc1C9KovaOx7sgKm1h5KZIBr3DzyoubBDwtplaaD9W-_fwojlSVjWfqaDBy1badWcg/s1600/FetchingFourValuesFromDeviceMemoryPerformanceImpact.JPG" height="212" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 8 - Pre-fetching four color values</span></i></div>
<br />
Looking at the improvements of fetching four instead of two color values brings up the question, how would performance change if the number of threads in a thread group would be increased and the number of thread groups in the dispatch then decreased, which also leads to a higher parallel reduction because the resulting image is smaller.<br />
The next example increases the number of threads from 16 to 64:<br />
<br />
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">…</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">// pack four values</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">#define THREADX 8</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">#define THREADY 8</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">…</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">like the previous </span><span style="font-family: 'Courier New';">shader</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">float </span><span style="font-family: 'Courier New';">temp = (dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> * 4], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">) + dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> * 4 + 1], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">))</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> + </span><span style="font-family: 'Courier New';">(dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> * 4 + 2], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">) + dot(Input[</span><span style="font-family: 'Courier New';">idx</span><span style="font-family: 'Courier New';"> * 4 + 3], </span><span style="font-family: 'Courier New';">LumVector</span><span style="font-family: 'Courier New';">));</span></span><br />
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"><br /></span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">store in shared memory ></span><span style="font-family: 'Courier New';"> no group barrier</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] = temp;</span></span><br />
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"><br /></span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
</div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">// </span><span style="font-family: 'Courier New';">AMD - 64 / NVIDIA - 32</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';">if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> < 64</span><span style="font-family: 'Courier New';">)</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">{</span></span><br />
<span style="font-family: Courier New; font-size: xx-small;"> if (groupthreads >= 64)sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 32];</span><br />
<span style="font-family: Courier New; font-size: xx-small; text-indent: 0in;"> if (groupthreads </span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">></span><span style="font-family: Courier New; font-size: xx-small; text-indent: 0in;">= 32)sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 16];</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;"> if </span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">(</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">groupthreads</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;"> </span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">></span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">= 16)</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">sharedMem</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">[</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">IndexOfThreadInGroup</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">] += </span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">sharedMem</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">[</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">IndexOfThreadInGroup</span><span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;"> + 8];</span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 8)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> + 4];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 4)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> + 2];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-size: xx-small;"><span style="font-family: 'Courier New';"> if </span><span style="font-family: 'Courier New';">(</span><span style="font-family: 'Courier New';">groupthreads</span><span style="font-family: 'Courier New';"> </span></span><span style="font-family: 'Courier New'; font-size: xx-small;">></span><span style="font-size: xx-small;"><span style="font-family: 'Courier New';">= 2)</span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';">] += </span><span style="font-family: 'Courier New';">sharedMem</span><span style="font-family: 'Courier New';">[</span><span style="font-family: 'Courier New';">IndexOfThreadInGroup</span><span style="font-family: 'Courier New';"> + 1];</span></span></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 2.4pt; text-indent: 0in; unicode-bidi: embed; vertical-align: baseline;">
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;">}</span></span></div>
<span style="font-family: 'Courier New'; font-size: xx-small; text-indent: 0in;">…</span><br />
<div>
<span style="font-family: 'Courier New';"><span style="font-size: xx-small;"><br /></span></span></div>
Similar to the previous shader this shader avoids any memory barriers but it runs with 64 instead of 16 threads and it is not executed as often, because the grid size was reduced to 60 x 34.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijMrshsteFmpIjElGVqqCqzXn7DQdWVxtw9FtHP2jkkwtzqh4fI8TRi2vIG7s-74IuvPWn0ZTU0u34XCl28BmIseUlqS74TcNZpHkUmaWatxAcReRk0FUfz0FPBeMUvAbmpXuI7hoFG88/s1600/FetchingFourValuesFromDeviceMemoryPerformanceImpactWith64Threads.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijMrshsteFmpIjElGVqqCqzXn7DQdWVxtw9FtHP2jkkwtzqh4fI8TRi2vIG7s-74IuvPWn0ZTU0u34XCl28BmIseUlqS74TcNZpHkUmaWatxAcReRk0FUfz0FPBeMUvAbmpXuI7hoFG88/s1600/FetchingFourValuesFromDeviceMemoryPerformanceImpactWith64Threads.JPG" height="290" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 9 - Increasing the number of Threads from 16 to 64 and decreasing the size of the result</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"><br /></span></i></div>
<div class="separator" style="clear: both; text-align: left;">
Although the number of threads are increased, the workload of this shader is also increased due to halving the size of the resulting image in each direction. In other words this shader does more work than the previous shaders. This allows the conclusion that this shader runs faster than the previous one.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Following the successful path of increasing the number of threads, the last shader in this blog post will use 256 threads to parallel reduce the image size from 1080p to 30x17.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">... // like the previous shaders</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// store in shared memory </span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">sharedMem[IndexOfThreadInGroup] = temp;</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// wait until everything is transfered from device memory to shared memory</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">GroupMemoryBarrierWithGroupSync();</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (groupthreads >= 256)</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">{</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (IndexOfThreadInGroup < 128)</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 128];</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>GroupMemoryBarrierWithGroupSync();</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">}</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">// AMD - 64 / NVIDIA - 32</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">if (IndexOfThreadInGroup < 64)</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">{</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (groupthreads >= 64)sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 32];</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (groupthreads >= 32)sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 16];</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (groupthreads >= 16)sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 8];</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (groupthreads >= 8)sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 4];</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (groupthreads >= 4)sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 2];</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><span class="Apple-tab-span" style="white-space: pre;"> </span>if (groupthreads >= 2)sharedMem[IndexOfThreadInGroup] += sharedMem[IndexOfThreadInGroup + 1];</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">}</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">...</span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div>
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;">With the increased number of threads we have to add memory barriers again. Nevertheless this shader runs quicker than all the previous shaders while -at the same time- doing more work:</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6OSf5TyemePWGuVoxs1jZYDDy5OD0fnTbCNtiA8CaW6YN4wUHN1ouyzHGhqYBbA8rjsR-mckPGl-N_-31-aqY27vfRGGRQwbIjZU-DvTER3EO6FhpSvCnn3JQ5zWcArl3mPTi_EoQMUc/s1600/FetchingFourValuesFromDeviceMemoryPerformanceImpactWith256Threads.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6OSf5TyemePWGuVoxs1jZYDDy5OD0fnTbCNtiA8CaW6YN4wUHN1ouyzHGhqYBbA8rjsR-mckPGl-N_-31-aqY27vfRGGRQwbIjZU-DvTER3EO6FhpSvCnn3JQ5zWcArl3mPTi_EoQMUc/s1600/FetchingFourValuesFromDeviceMemoryPerformanceImpactWith256Threads.JPG" height="367" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Image 10 - Increasing the number of Threads from 64 to 256 and decreasing the size of the result</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"><br /></span></i></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;">Please note how the older GPU starts beating the newer GPU when the number of threads are increased. Overall for the 6770, we went from roughly 1 ms to close to a tenth of the original time frame. For the 7750 and the 7850 we ended up reducing the frame time to roughly a bit more than a fourth, while increasing the workload in the last two test setups. </span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;"><br /></span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;"><b>Conclusion</b></span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;">Like with most optimization tasks there is always more to consider and more to try out. A list of things that would be worth considering is still short but give some time, it will increase. </span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;">If you -the valued reader of this blog- have anything you want me to try and add to this list, please let me know and I will add it to this blog post.</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;">Overall I believe the case studies shown above should give someone a good starting point to optimize the Parallel Reduction part of a post-processing pipeline.</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;"><br /></span></div>
<div class="separator" style="clear: both; text-align: left;">
One other topic crucial for the performance of a post-processing pipeline is the speed of the blur kernel. Optimizations that lead to the "ultimate" blur kernel will have to wait for a future blog post :-)</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Thanks to Stephan Hodes from AMD for providing feedback.</div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: inherit;"><br /></span></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br /></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-19315624623223061242014-03-24T15:06:00.001-07:002014-03-24T15:06:38.032-07:00DirectX 12 Blog<div dir="ltr" style="text-align: left;" trbidi="on">
Finally information about DirectX 12 is published on Matt Sandy's <a href="http://blogs.msdn.com/b/directx/archive/2014/03/20/directx-12.aspx" target="_blank">blog</a>.<br />
<br />
Today I wear my DirectX 12 T-Shirt to work ... below this shirt I am wearing the Mantle T-Shirt (... I was thinking about the order for a while but only this order can make sense ... right?).<br />
I had the opportunity to test drive DirectX 12 in the last couple of months and it looks already great. Very excited to work with DirectX 12 and Mantle in the near future.<br />
<br />
<br /></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-62846869603926920842014-03-13T06:19:00.000-07:002014-03-13T06:24:36.317-07:00GDC 2014 - Compute Shader Optimizations<div dir="ltr" style="text-align: left;" trbidi="on">
I will be speaking at the Sony booth on Wednesday at 5pm on compute shader optimizations. The 15 minute talk will be broadcast on Twitch.<br />
The talk will cover performance numbers of three different AMD GPUs: RADEON 6770, RADEON 7750 and RADEON 7850.<br />
The main topics are:<br />
<br />
<ul style="text-align: left;">
<li>Sequential Shared Memory (TGSM) Access: utilizing the Memory bank layout </li>
<li>When to Unroll Loops in a compute shader </li>
<ul>
<li>Overhead of address arithmetic and loop instructions </li>
<li>Skipping Memory Barriers: Wavefront </li>
</ul>
<li>Pre-fetching data into Shared Memory </li>
<li>Packing data into Shared Memory</li>
</ul>
<div>
Looking at two different generations of AMD GPUs makes it better visible which one of the ground rules developed for GPU optimizations works on current GPUs, compared to previous generations.</div>
<div>
This is based on some of the optimization work we did on AAA games last year. </div>
<div>
At Confetti we have Aura - our Dynamic Global Illumination System- and PixelPuzzle - our PostFX pipeline - running in compute.</div>
<div>
This talk will deal with how to optimize parts of a PostFX pipeline with Compute. I am also planning to write a blog series about this.</div>
</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com6tag:blogger.com,1999:blog-398682525365778708.post-47444226345151372512014-01-14T14:11:00.000-08:002015-05-31T20:04:08.812-07:00Link Collection<div dir="ltr" style="text-align: left;" trbidi="on">
<div style="text-align: left;">
The book "Is Parallel Programming Hard, And, If So, What Can You Do About It?" can be found at</div>
<div style="text-align: left;">
<a href="https://www.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html" target="_blank">https://www.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html</a></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
An overview on C99 support in Visual Studio 2013 can be found at</div>
<div style="text-align: left;">
<a href="http://blogs.msdn.com/b/vcblog/archive/2013/07/19/c99-library-support-in-visual-studio-2013.aspx">http://blogs.msdn.com/b/vcblog/archive/2013/07/19/c99-library-support-in-visual-studio-2013.aspx</a></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Adaptive Depth Bias for Shadow Maps</div>
<div style="text-align: left;">
<a href="http://jcgt.org/published/0003/04/08/paper-lowres.pdf">http://jcgt.org/published/0003/04/08/paper-lowres.pdf</a></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
FSE decoding : how it works</div>
<div style="text-align: left;">
<a href="http://fastcompression.blogspot.com/2014/01/fse-decoding-how-it-works.html?spref=tw">http://fastcompression.blogspot.com/2014/01/fse-decoding-how-it-works.html?spref=tw</a></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Learning Three.js - WebGL for Dummies</div>
<div style="text-align: left;">
<a href="http://learningthreejs.com/">http://learningthreejs.com/</a></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Fuzebox</div>
<div style="text-align: left;">
<a href="https://www.fuzebox.com/">https://www.fuzebox.com</a></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Blending of Normal Maps: Blending in Detail</div>
<div style="text-align: left;">
<a href="http://blog.selfshadow.com/publications/blending-in-detail/">http://blog.selfshadow.com/publications/blending-in-detail/</a></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
What Every C Programmer Should Know About Undefined Behavior #1/3</div>
<div style="text-align: left;">
<a href="http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html?repost">http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html?repost</a></div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
CUB provides state-of-the-art, reusable software components for every layer of the CUDA programming model</div>
<div style="text-align: left;">
<a href="http://nvlabs.github.io/cub/">http://nvlabs.github.io/cub/</a></div>
<br />
<br />
<br /></div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com3tag:blogger.com,1999:blog-398682525365778708.post-28420091371791882962014-01-10T12:14:00.000-08:002014-01-13T10:07:17.754-08:00Visual Studio 2013 - C99 support<div dir="ltr" style="text-align: left;" trbidi="on">
I think using C99 in game development could be useful for large teams, especially if they are distributed over several locations.<br />
<div>
So I thought I look a little bit closer on the support of C99 in Visual Studio 2013 (we also use VS 2013 with C99 now in my UCSD class).</div>
<div>
The new features that are support in VS 2013 are:</div>
<div>
<br /></div>
<div>
<div>
<b>New features in 2013</b></div>
<div>
- variable decls</div>
<div>
- _Bool</div>
<div>
- compound literals</div>
<div>
- designated initializers</div>
</div>
<div>
<br /></div>
<div>
<div>
<b>Already available:</b></div>
<div>
variadic macros, long long, __pragma, __FUNCTION__, and __restrict</div>
<div>
<br /></div>
<div>
<b>What is missing:</b></div>
<div>
- variable-length arrays (VLAs)</div>
<div>
- Reserved keywords in C99</div>
<div>
C99 has a few reserved keywords that are not recognized by C++:</div>
<div>
restrict</div>
<div>
_Bool -> this is now implemented ... see above</div>
<div>
_Complex</div>
<div>
_Imaginary</div>
<div>
_Pragma</div>
<div>
- restrict keyword</div>
<div>
C99 supports the restrict keyword, which allows for certain optimizations involving pointers. For example:</div>
<div>
<br /></div>
<div>
void copy(int *restrict d, const int *restrict s, int n)</div>
<div>
{</div>
<div>
while (n-- > 0)</div>
<div>
*d++ = *s++;</div>
<div>
} </div>
<div>
C++ does not recognize this keyword.</div>
<div>
A simple work-around for code that is meant to be compiled as either C or C++ is to use a macro for the restrict keyword:</div>
<div>
<br /></div>
<div>
#ifdef __cplusplus</div>
<div>
#define restrict /* nothing */</div>
<div>
#endif </div>
<div>
(This feature is likely to be provided as an extension by many C++ compilers. If it is, it is also likely to be allowed as a reference modifier as well as a pointer modifier.)</div>
</div>
<div>
<br /></div>
<div>
<div>
<b>Don't know if it is in there:</b></div>
<div>
- hexadecimal floating-point literals like </div>
<div>
float pi = 0x3.243F6A88p+03; </div>
<div>
- C99 adds a few header files that are not included as part of the standard C++ library, though:<br />
<complex.h><br>
<fenv.h><br>
<inttypes.h><br>
<stdbool.h><br>
<stdint.h><br>
<tgmath.h><br>
<div>
<br /></div>
<div>
<br /></div>
</div>
<div>
<b>References</b></div>
<div>
<div>
<ul style="text-align: left;">
<li>What was added in June 2013 blog<br /><a href="http://blogs.msdn.com/b/vcblog/archive/2013/06/27/what-s-new-for-visual-c-developers-in-vs2013-preview.aspx">http://blogs.msdn.com/b/vcblog/archive/2013/06/27/what-s-new-for-visual-c-developers-in-vs2013-preview.aspx</a></li>
<li>C99 library support in Visual Studio 2013 <br /><a href="http://blogs.msdn.com/b/vcblog/archive/2013/07/19/c99-library-support-in-visual-studio-2013.aspx">http://blogs.msdn.com/b/vcblog/archive/2013/07/19/c99-library-support-in-visual-studio-2013.aspx</a></li>
<li>Incompatibilities between C99 and C++98<br /><a href="http://david.tribble.com/text/cdiffs.htm">http://david.tribble.com/text/cdiffs.htm</a></li>
</ul>
</div>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
</div>
</div>Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com3tag:blogger.com,1999:blog-398682525365778708.post-9421485105465663912014-01-02T11:40:00.005-08:002014-01-02T11:40:49.881-08:00CSE 190 - GPU Programming UCSD class Winter 2014<div dir="ltr" style="text-align: left;" trbidi="on">
GPU Programming<br />
With the new console generation and the advances in PC hardware, compute support is becoming more important in games. The new course in 2014 will therefore start with compute and we will spend about a 1/3 of the whole course talking about how it is used on next-gen consoles and in next-gen games. We will also look into several case studies and discuss the feasibility to "re-factor" existing game algorithms so that they run in compute. An emphasis is put here on effects that are traditionally used for post-processing effects.<br />
<br />
The remaining 2 / 3 of the course will focus on the DirectX 11.2 graphics API and how it is used in games to create a rendering engine for a next-gen game. We will cover most of the fundamental concepts like the HLSL language, renderer design, lighting in games, how to generate shadows and we also discuss how transparency can be mimicked with techniques other than alpha blending.<br />
The course will end with a survey of different real-time Global Illumination algorithms that are used in different types of games.<br />
<br />
<br />
First Class<br />
Overview<br />
-- DirectX 11.2 Graphics<br />
-- DirectX 11.2 Compute<br />
-- Tools of the Trade - how to setup your development system<br />
Introduction to DirectX 11.2 Compute<br />
-- Advantages<br />
-- Memory Model<br />
-- Threading Model<br />
-- DirectX 10.x support<br />
<br />
Second Class<br />
Simple Compute Case Studies<br />
- PostFX Color Filters<br />
- PostFX Parallel Reduction<br />
- DirectX 11 Mandelbrot<br />
- DirectX 10 Mandelbrot<br />
<br />
Third Class<br />
DirectCompute performance optimization<br />
- Histogram optimization case study<br />
<br />
Fourth Class<br />
Direct3D 11.2 Graphics Pipeline Part 1<br />
- Direct3D 9 vs. Direct3D 11<br />
- Direct3D 11 vs. Direct3D 11.1<br />
- Direct3D 11.1 vs. Direct3D 11.2<br />
- Resources (typeless memory arrays)<br />
- Resource Views<br />
- Resources Access Intention<br />
- State Objects<br />
- Pipeline Stages<br />
-- Input Assembler<br />
-- Vertex Shader<br />
-- Tesselation<br />
-- Geometry Shader<br />
-- Stream Out<br />
-- Setup / Rasterizer<br />
-- Pixel Shader<br />
-- Output Merger<br />
-- Video en- / decoder access<br />
<br />
Fifth Class<br />
Direct3D 11.2 Graphics Pipeline Part 2<br />
-- HLSL<br />
--- Keywords<br />
--- Basic Data Types<br />
--- Vector Data Types<br />
--- Swizzling<br />
--- Write Masks<br />
--- Matrices<br />
--- Type Casting<br />
--- SamplerState<br />
--- Texture Objects<br />
--- Intrinsics<br />
--- Flow Control<br />
-- Case Study: implementing Blinn-Phong lighting with DirectX 11.2<br />
--- Physcially / Observational Lighting Models<br />
--- Local / Global Lighting<br />
--- Lighting Implementation<br />
---- Ambient<br />
---- Diffuse<br />
---- Specular<br />
---- Normal Mapping<br />
---- Self-Shadowing<br />
---- Point Light<br />
---- Spot Light<br />
<br />
Sixth Class<br />
Physically Based Lighting<br />
- Normalized Blinn-Phong Lighting Model<br />
- Cook-Torrance Reflectance Model<br />
<br />
Seventh Class<br />
Deferred Lighting, AA<br />
- Rendering Many Lights History<br />
- Light Pre-Pass (LPP)<br />
- LPP Implementation<br />
- Efficient Light rendering on DX 9, 10, 11<br />
- Balance Quality / Performance<br />
- MSAA Implementation on DX 10.0, 10.1, XBOX 360, 11<br />
Screen-Space Materials<br />
- Skin<br />
<br />
Eigth Class<br />
Shadows<br />
- The Shadow Map Basics<br />
- “Attaching” a Shadow Map frustum around a view frustum<br />
- Multi-Frustum Shadow Maps<br />
- Cascaded Shadow Maps (CSM) : Splitting up the View<br />
- CSM Challenges<br />
- Cube Shadow Maps<br />
- Softening the Penumbra<br />
- Soft Shadow Maps<br />
<br />
Nineth Class<br />
Order-Independent Transparency<br />
- Depth Peeling<br />
- Reverse Depth Peeling<br />
- Per-Pixel Linked Lists<br />
<br />
Tenth Class<br />
Global Illumination Algorithms in Games<br />
- Requirement for Real-Time GI<br />
- Ambient Cubes<br />
- Diffuse Cube Mapping<br />
- Screen-Space Ambient Occlusion<br />
- Screen-Space Global Illumination<br />
- Reflective Shadow Maps<br />
- Splatting Indirect Illumination (SII)<br />
<br />
Prerequisite<br />
Each student should bring a DirectX 11.0 or higher capable notebook with Windows 7 or 8 into class. All the examples accompanying the class are build in C/C++ in Visual Studio 2013. </div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com4tag:blogger.com,1999:blog-398682525365778708.post-44801541243188716012013-11-07T13:11:00.004-08:002013-11-07T13:11:35.665-08:00Visual Studio 2013 / Demo Skeleton Programming<div dir="ltr" style="text-align: left;" trbidi="on">
I updated my demo skeleton in the google code repository. It is using now Visual Studio 2013, that now partially supports C99 and therefore can compile the code.
I updated the compute shader code a bit and I upgraded Crinkler to version 1.4. The compute shader example now also compiles the shader into a header file and then Crinkler compresses this file as part of the data compression. It packs now overall to 2,955 bytes.
<br />
<br />
<a href="https://code.google.com/p/graphicsdemoskeleton/">https://code.google.com/p/graphicsdemoskeleton/</a>
<br />
<br />
If you have fun with this code, let me know ... :-)
</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com2tag:blogger.com,1999:blog-398682525365778708.post-26010344545174286962013-09-30T11:53:00.001-07:002013-09-30T11:58:51.324-07:00Call for a new Post-Processing Pipeline - KGC 2013 talk<div dir="ltr" style="text-align: left;" trbidi="on">
This is the text version of my talk at KGC 2013.<br />
<div>
The main motivation for the talk was the idea of looking for fundamental changes that can bring a modern Post-Processing Pipeline to the next level.<br />
Let's look first into the short history of Post-Processing Pipelines, where we are in the moment and where we might be going in the near future.<br />
<div>
<br /></div>
<div>
<b>History</b></div>
<div>
Probably one of the first Post-Processing Pipelines appeared in the DirectX SDK around 2004. It was a first attempt to implement HDR rendering. I believe from there on we called a collection of image space effects at the end of the rendering pipeline Post-Processing pipeline. </div>
<div>
The idea was to re-use resources like render targets and data with as many image space effects as possible in a Post-Processing Pipeline. </div>
<div>
A typical collection of screen-space effects were</div>
<div>
<ul style="text-align: left;">
<li>Tone-mapping + HDR rendering: the tone-mapper can be considered a dynamic contrast operator </li>
<li>Camera effects like Depth of Field with shaped Bokeh, Motion Blur, lens flare etc..</li>
<li>Full-screen color filters like contrast, saturation, color additions and multiplications etc..</li>
</ul>
<div>
One of the first coverages of a whole collection of effects in a Post-Processing Pipeline running on XBOX 360 / PS3 was done in [Engel2007].</div>
</div>
<div>
Since then numerous new tone mapping operators were introduced [Day2012], new more advanced Depth of Field algorithms with shaped Bokeh were covered but there was no fundamental change to the concept of the pipeline.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<b>Call for a new Post-Processing Pipeline</b></div>
<div>
Let's start with the color space: RGB is not a good color space for a post-processing pipeline. It is well known that luminance variety is more important than color variety, so it makes sense to pick a color space that has luminance in one of the channels. With the 11:11:10 render targets it would be cool to store luminance in one of the 11 bit channels. Having luminance available in the pipeline without having to go through color conversions opens up many new possibilities, from which we will cover a few below.</div>
<div>
<br /></div>
<div>
Global tone mapping operators didn't work out well in practice. We looked at numerous engines in the last four years and a common decision by artists was to limit the luminance values by clamping them. The reasons for this were partially in the fact that the textures didn't provide enough quality to survive a "light adaptation" without blowing out or sometimes most of their resolution was in the low-end greyscale values and there wasn't just enough resolution to mimic light adaptations. </div>
<div>
Another reason for this limitation was that the available resolution in the rendering pipeline with the RGB color space was not enough. Another reason for this limitation is the fact that we limited ourselves to Global tone mapping operators, because local tone mapping operators are considered too expensive.</div>
<div>
<br /></div>
<div>
A fixed global gamma adjustment at the end of the pipeline is partially doing "the same thing" as the tone mapping operator. It applies a contrast and might counteract the activities that the tone-mapper already does. </div>
<div>
So the combination of a tone-mapping operator and then the commonly used hardware gamma correction, which are both global is odd.</div>
<div>
<br /></div>
<div>
On a lighter note, a new Post-Processing Pipeline can add more stages. In the last couple of years, screen-space ambient occlusion, screen-space skin and screen-space reflections for dynamic objects became popular. Adding those to the Post-Processing Pipeline by trying to re-use existing resources need to be considered in the architecture of the pipeline.</div>
<div>
<br /></div>
<div>
Last, one of the best targets for the new compute capabilities of GPUs is the Post-Processing Pipeline. Saving memory bandwidth by merging "render target blits" and re-factoring blur kernels for thread group shared memory or GSM are considerations not further covered in the following text; but most obvious design decisions.</div>
<div>
<br /></div>
<div>
Let's start by looking at the an old Post-Processing Pipeline design. This is an overview I used in 2007:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgo_oSZ_F0-f2IrZ_YNsqix6uX_o1JXkuXN695wt3c9c76xBBxJTp-wgmdPBN394Dg-VbMDlV3lWCKBkMj2idVFIcCNipv4UdAQOPyJLrRxGAKz7ILrQ2gEa3DNAkE9pbhah1FGRiUZo94/s1600/2007Post-Processing+Pipeline.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgo_oSZ_F0-f2IrZ_YNsqix6uX_o1JXkuXN695wt3c9c76xBBxJTp-wgmdPBN394Dg-VbMDlV3lWCKBkMj2idVFIcCNipv4UdAQOPyJLrRxGAKz7ILrQ2gEa3DNAkE9pbhah1FGRiUZo94/s400/2007Post-Processing+Pipeline.JPG" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">A Post-Processing Pipeline Overview from 2007</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"><br /></span></i></div>
<div class="separator" style="clear: both; text-align: left;">
A few notes on this pipeline. The tone mapping operation happens at two places. At the "final" stage for tone-mapping the final result and in the bright-pass filter for tone mapping the values before they can be considered "bright". </div>
<div class="separator" style="clear: both; text-align: left;">
The "right" way to apply tone mapping independent of the tone mapping operator you choose is to convert into a color space that exposes luminance, apply the tone mapper to luminance and then convert back to RGB. In other words: you had to convert between RGB and a different color space back and forth twice. </div>
<div class="separator" style="clear: both; text-align: left;">
In some pipelines, it was decided that this is a bit much and the tone mapper was applied to the RGB value directly. Tone mapping a RGB value with a luminance contrast operator led to "interesting" results.</div>
<div class="separator" style="clear: both; text-align: left;">
Obviously this overview doesn't cover the latest Depth of Field effects with shaped Bokeh and separated near and far field Center of Confusion calculations, nevertheless it shows already a large amount of render-target to render-target blits that can be merged with compute support.</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
All modern rendering pipelines calculate color values in linear space; meaning every texture that is loaded is converted into linear space by the hardware, then all the color operations are applied like lighting and shadowing, post-processing and then at the end the color values are converted back by applying the gamma curve.</div>
<div class="separator" style="clear: both; text-align: left;">
This separate Gamma Control is located at the end of the pipeline, situated after tone mapping and color filters. This is because the GPU hardware can apply a global gamma correction to the image after everything is rendered. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
The following paragraphs will cover some of the ideas we had to improve a Post-Processing Pipeline on a fundamental level. We implemented them into our Post-Processing Pipeline PixelPuzzle. Some of the research activities like finally replacing the "global tone mapping concept" with a better way of calculating contrast and color will have to wait for a future column.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Yxy Color Space</b></div>
<div class="separator" style="clear: both; text-align: left;">
The first step to change a Post-Processing Pipeline in a fundamental way is to switch it to a different color space. Instead of running it in RGB we decided to use CIE Yxy through the whole pipeline. That means we convert RGB into Yxy at the beginning of the pipeline and convert back to RGB at the end. In-between all operations run on Yxy.</div>
<div class="separator" style="clear: both; text-align: left;">
With CIE Yxy, the Y channel holds the luminance value. With a 11:11:10 render target, the Y channel will have 11 bits of resolution.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Instead of converting RGB to Yxy and back each time for the final tone mapping and the bright-pass stage, running the whole pipeline in Yxy means that this conversion might be only done once to Yxy and once or twice back to RGB.</div>
<div class="separator" style="clear: both; text-align: left;">
Tone mapping then still happens with the Y channel in the same way it happened before. Confetti's PostFX pipeline offers eight different tone mapping operators and each of them works well in this setup.</div>
<div class="separator" style="clear: both; text-align: left;">
Now one side effect of using Yxy is also that you can run the bright-pass filter as a one channel operation, which saves on modern scalar GPUs some cycles.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
One other thing that Yxy allows to do is to consider the occlusion term in Screen-Space Ambient Occlusion as a member of the Y channel. So you can mix in this term and use it in interesting ways. Similar ideas apply to any other occlusion term that your pipeline might be able to use.</div>
<div class="separator" style="clear: both; text-align: left;">
The choice of using CIE Yxy as the color space of choice was arbitrary. In 2007 I evaluated several different color spaces and we ended up with Yxy at the time. Here is my old table:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhY2HoOKaG-65J_sQZQbrqyewsQztlDK1SHhiHXmuEOty6_wXBb6zR4UuZFAybQn6T8CJ0Y5bLApanBHssMy8g3E5CPkEpBwaUx8oDqajuRTzE8FP8qwBNK1ljrU8uWBvYI8D40SS6c2WU/s1600/ColorSpaces.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="171" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhY2HoOKaG-65J_sQZQbrqyewsQztlDK1SHhiHXmuEOty6_wXBb6zR4UuZFAybQn6T8CJ0Y5bLApanBHssMy8g3E5CPkEpBwaUx8oDqajuRTzE8FP8qwBNK1ljrU8uWBvYI8D40SS6c2WU/s400/ColorSpaces.JPG" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<span style="font-size: x-small;"><i>Pick a Color Space Table from 2007</i></span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-size: x-small;"><i><br /></i></span></div>
<div class="separator" style="clear: both; text-align: left;">
Compared to CIE Yxy, HSV doesn't allow easily to run a blur filter kernel. The target was to leave the pipeline as unchanged as possible when picking a color space. So with Yxy, all the common Depth of Field algorithms and any other blur kernel runs unchanged in Yxy. HSV conversions also seem to be more expensive compared to RGB -> CIE XYZ -> CIE Yxy and vice versa.</div>
<div class="separator" style="clear: both; text-align: left;">
There might be other color spaces similar tailored to the task.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Dynamic Local Gamma</b></div>
<div class="separator" style="clear: both; text-align: left;">
As mentioned above, the fact that we apply a tone mapping operator and then later on a global gamma operator appears to be a bit odd. Here is what the hardware is supposed to do when it applies the gamma "correction".</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp0HQoMP3eryCu6gn8TuEHu0vFZXgR0_UTkSR9vpkyLuy8I9udReh6m49i_jY-3x-qneUpDJJueqy5OJCpmX5ABHKJHkdoD14RbkYPhrzqY2ycCromyhdpg1dUcZ2Czb2CwXR3QDXyRZE/s1600/GammaCorrection.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="181" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp0HQoMP3eryCu6gn8TuEHu0vFZXgR0_UTkSR9vpkyLuy8I9udReh6m49i_jY-3x-qneUpDJJueqy5OJCpmX5ABHKJHkdoD14RbkYPhrzqY2ycCromyhdpg1dUcZ2Czb2CwXR3QDXyRZE/s400/GammaCorrection.JPG" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Gamma Correction</span></i></div>
<div class="separator" style="clear: both; text-align: left;">
The main take-away from this curve is that the same curve is applied to every pixel on screen. In other words: this curve shows an emphasis on dark areas independently of the pixel being very bright or very dark.</div>
<div class="separator" style="clear: both; text-align: left;">
Whatever curve the tone-mapper will apply, the gamma correction might be counteracting it.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
It appears to be a better idea to move the gamma correction closer to the tone mapper, making it part of the tone mapper and at the same time apply gamma locally per pixel.</div>
<div class="separator" style="clear: both; text-align: left;">
In fact gamma correction is considered depending on the light adaptation level of the human visual system. The "gamma correction" that is applied by the eye changes the perceived luminance based on the eye's adapatation level [Bartleson 1967] [Kwon 2011]. </div>
<div class="separator" style="clear: both; text-align: left;">
When the eye is adapted to dark lighting conditions, the exponent for the gamma correction is supposed to increase. If the eye is adapted to bright lighting conditions, the exponent for the gamma correction is supposed to decrease. This is shown in the following image taken from [Bartleson 1967]:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhnbgkc278GJvrTmfc8-jlPC3O9A5nzLTK5jjs6_osQQlfPNzRZQSaVNAztC-Rb7Ib7EssJ2gZxd2NVC_KuNTya1ehObNaRwaIVG95cNuhPSH6uqztX4_wSde83uYaq2Y2ZNMq8Fn4FA4/s1600/GammaChangesWithAdaptation.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhnbgkc278GJvrTmfc8-jlPC3O9A5nzLTK5jjs6_osQQlfPNzRZQSaVNAztC-Rb7Ib7EssJ2gZxd2NVC_KuNTya1ehObNaRwaIVG95cNuhPSH6uqztX4_wSde83uYaq2Y2ZNMq8Fn4FA4/s320/GammaChangesWithAdaptation.JPG" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Changes in Relative Brightness Contrast [Bartleson 1967]</span></i></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
A local gamma value can vary with the eye's adaptation level. The equation that adjusts the gamma correction following the current adaptation level of the eye can be found in [Kwon 2011].</div>
<div class="separator" style="clear: both; text-align: left;">
<!--[if gte msEquation 12]><m:oMath><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-style:italic;mso-style-textfill-type:solid;mso-style-textfill-fill-themecolor:
text1;mso-style-textfill-fill-color:black;mso-style-textfill-fill-alpha:100.0%'>𝛾</span></m:r><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-weight:normal;font-style:italic;vertical-align:sub;mso-text-raise:
-25%;mso-style-textfill-type:solid;mso-style-textfill-fill-themecolor:text1;
mso-style-textfill-fill-color:black;mso-style-textfill-fill-alpha:100.0%'>𝑣</span></m:r><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-weight:normal;font-style:italic;mso-style-textfill-type:solid;
mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:black;
mso-style-textfill-fill-alpha:100.0%'>=0.444+0.045</span></m:r><m:func><m:funcPr><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-weight:normal;font-style:italic;mso-style-textfill-type:solid;
mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:black;
mso-style-textfill-fill-alpha:100.0%'><m:ctrlPr></m:ctrlPr></span></m:funcPr><m:fName><m:r><m:rPr><m:sty
m:val="p"></m:sty></m:rPr><span style='font-size:32.0pt;font-family:"Cambria Math";
mso-ascii-font-family:"Cambria Math";mso-fareast-font-family:"Cambria Math";
mso-bidi-theme-font:minor-bidi;color:black;mso-color-index:1;mso-font-kerning:
12.0pt;language:en-US;font-weight:normal;font-style:normal;mso-style-textfill-type:
solid;mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:
black;mso-style-textfill-fill-alpha:100.0%'>ln</span></m:r></m:fName><m:e><m:d><m:dPr><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;
language:en-US;font-weight:normal;font-style:italic;mso-style-textfill-type:
solid;mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:
black;mso-style-textfill-fill-alpha:100.0%'><m:ctrlPr></m:ctrlPr></span></m:dPr><m:e><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;
language:en-US;font-weight:normal;font-style:italic;mso-style-textfill-type:
solid;mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:
black;mso-style-textfill-fill-alpha:100.0%'>𝐿</span></m:r><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;
language:en-US;font-weight:normal;font-style:italic;vertical-align:sub;
mso-text-raise:-25%;mso-style-textfill-type:solid;mso-style-textfill-fill-themecolor:
text1;mso-style-textfill-fill-color:black;mso-style-textfill-fill-alpha:
100.0%'>𝑎𝑛</span></m:r><m:r><span style='font-size:32.0pt;
font-family:"Cambria Math";mso-ascii-font-family:"Cambria Math";
mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:minor-bidi;
color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:en-US;
font-weight:normal;font-style:italic;mso-style-textfill-type:solid;
mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:
black;mso-style-textfill-fill-alpha:100.0%'>+0.6034</span></m:r></m:e></m:d></m:e></m:func></m:oMath><![endif]--><!--[if !msEquation]--><span style="font-family: 'Cambria Math';">γ</span><span style="font-family: 'Cambria Math'; vertical-align: sub;">v</span><span style="font-family: 'Cambria Math';">=0.444+0.045 ln(L</span><span style="font-family: 'Cambria Math'; vertical-align: sub;">an</span><span style="font-family: 'Cambria Math';">+0.6034)</span><!--[endif]--></div>
For this presentation, this equation was taken from the paper by Kwon et all. Depending on the type of game there is an opportunity to build your own local gamma operator. </div>
<div>
The input luminance value is generated by the tone mapping operator and then stored in the Y channel of the Yxy color space:</div>
<div>
<!--[if gte msEquation 12]><m:oMath><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-weight:normal;font-style:italic;mso-style-textfill-type:solid;
mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:black;
mso-style-textfill-fill-alpha:100.0%'>𝑌</span></m:r><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-weight:normal;font-style:italic;vertical-align:sub;mso-text-raise:
-25%;mso-style-textfill-type:solid;mso-style-textfill-fill-themecolor:text1;
mso-style-textfill-fill-color:black;mso-style-textfill-fill-alpha:100.0%'>𝑌𝑥𝑦</span></m:r><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-weight:normal;font-style:italic;mso-style-textfill-type:solid;
mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:black;
mso-style-textfill-fill-alpha:100.0%'>=</span></m:r><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-weight:normal;font-style:italic;mso-style-textfill-type:solid;
mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:black;
mso-style-textfill-fill-alpha:100.0%'>𝐿</span></m:r><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-style:italic;vertical-align:super;mso-text-raise:50%;mso-style-textfill-type:
solid;mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:
black;mso-style-textfill-fill-alpha:100.0%'>𝛾</span></m:r><m:r><span
style='font-size:32.0pt;font-family:"Cambria Math";mso-ascii-font-family:
"Cambria Math";mso-fareast-font-family:"Cambria Math";mso-bidi-theme-font:
minor-bidi;color:black;mso-color-index:1;mso-font-kerning:12.0pt;language:
en-US;font-style:italic;vertical-align:super;mso-text-raise:30%;mso-style-textfill-type:
solid;mso-style-textfill-fill-themecolor:text1;mso-style-textfill-fill-color:
black;mso-style-textfill-fill-alpha:100.0%'>𝑣</span></m:r></m:oMath><![endif]--><!--[if !msEquation]--><span style="font-family: 'Cambria Math';">Y</span><span style="font-family: 'Cambria Math'; vertical-align: sub;">Yxy</span><span style="font-family: 'Cambria Math';">=L</span><span style="font-family: 'Cambria Math'; vertical-align: super;">γ</span><span style="font-family: 'Cambria Math'; vertical-align: super;">v</span></div>
<div>
γv changes based on the luminance value of the current pixel. That means each pixels luminance value might be gamma corrected with a different exponent. For the equation above, the exponent value is in the range of 0.421 to 0.465. </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrwFSDS5_z-hA-P-JdtbUrE4GSZfAThwRVmH2XxO5CVdNKKmV5kKQSqf9SEErfNjRrQLxNf24iSXodvQjAFlQZZ9I_w74XnHIQhMIxqfiqJQLojSn251WtG_gcUALFdh5pGrYg5M3HTyQ/s1600/AppliedDynamicGamma.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="222" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrwFSDS5_z-hA-P-JdtbUrE4GSZfAThwRVmH2XxO5CVdNKKmV5kKQSqf9SEErfNjRrQLxNf24iSXodvQjAFlQZZ9I_w74XnHIQhMIxqfiqJQLojSn251WtG_gcUALFdh5pGrYg5M3HTyQ/s400/AppliedDynamicGamma.JPG" width="400" /></a></div>
<div style="direction: ltr; margin-bottom: 0pt; margin-left: 0in; margin-top: 0pt; text-align: center; unicode-bidi: embed; word-break: normal;">
<span style="font-family: Calibri;"><span style="font-size: x-small;"><i>Applied
Gamma Curve per-pixel based on luminance of pixel</i></span></span></div>
<div class="O1" style="direction: ltr; margin-bottom: 0pt; margin-left: 0.81in; margin-top: 0pt; text-align: center; text-indent: -0.31in; unicode-bidi: embed; word-break: normal;">
<span style="font-size: x-small;"><i><span style="font-family: Arial; mso-special-format: bullet;">•</span><span style="font-family: Calibri;">Eye’s adaptation == low - >blue
curve</span></i></span></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="O1" style="direction: ltr; margin-bottom: 0pt; margin-left: 0.81in; margin-top: 0pt; text-align: center; text-indent: -0.31in; unicode-bidi: embed; word-break: normal;">
<span style="font-size: x-small;"><i><span style="font-family: Arial; mso-special-format: bullet;">•</span><span style="font-family: Calibri;">Eye’s adaptation value == high
-> green curve</span></i></span></div>
<div>
<span style="font-family: 'Cambria Math';">L</span><span style="font-family: 'Cambria Math'; vertical-align: super;">γ</span><span style="font-family: 'Cambria Math'; vertical-align: super;">v</span><br />
works with any tone mapping operator. L is the luminance value coming from the tone mapping operator. </div>
<div>
With a dynamic local gamma value, the dynamic lighting and shadowing information that is introduced in the pipeline will be considered for the gamma correction. The changes when going from bright areas to dark areas appear more natural. Textures are holding up better the challenges of light adaptation. Overall lights and shadows look better.<br />
<br />
<br />
<b>Depth of Field</b></div>
<div>
As a proof-of-concept of the usage of Yxy color space and the local dynamic gamma correction, this section is showing screen-shots of a modern Depth of Field implementation with separated near and far field calculations and a shaped Bokeh, implemented in compute.<br />
<br />
Producing an image through a lens leads to a "spot" that will vary in size depending on the position of the original point in the scene:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvWeaztBBERVdXPNuygWSMN534sGEuRH8S2GbabvRlst4bKOsyPaIe4ONwv7zc-LZUajWiXAhPZvmGjwZzIzsAZs6yeeJsCAGgNU3fld8nRpE9qwwtaX6CP2Mw0bfFVhw144gJaOo5Fp0/s1600/Cirles_of_confusion_lens_diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvWeaztBBERVdXPNuygWSMN534sGEuRH8S2GbabvRlst4bKOsyPaIe4ONwv7zc-LZUajWiXAhPZvmGjwZzIzsAZs6yeeJsCAGgNU3fld8nRpE9qwwtaX6CP2Mw0bfFVhw144gJaOo5Fp0/s1600/Cirles_of_confusion_lens_diagram.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Circle of Confusion (image taken from <a href="http://en.wikipedia.org/wiki/Circle_of_confusion" target="_blank">Wikipedia</a>)</span></i> </div>
<br />
<br />
<div class="separator" style="clear: both;">
The Depth of Field is the region, where the CoC is less than the resolution of the human eye (or in our case the resolution of our display medium). The equation on how to calculate the CoC [Potmesil1981] is:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8_Rg7hKla5uJxw-W_ULuWfo3op-tTqItA8iy-nF-KyogDXp6u1zNgZ892CEYUtmJ3-Q3K1VWYgLTva4GLZya2QiKzI3VmLpVjg4NX4SXnlsLULgH4v1eWPUyiuiBw_7KXwXY6IyBIv24/s1600/Potmesil.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="70" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8_Rg7hKla5uJxw-W_ULuWfo3op-tTqItA8iy-nF-KyogDXp6u1zNgZ892CEYUtmJ3-Q3K1VWYgLTva4GLZya2QiKzI3VmLpVjg4NX4SXnlsLULgH4v1eWPUyiuiBw_7KXwXY6IyBIv24/s320/Potmesil.JPG" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Following the variables in this equation, Confetti demonstrated in a demo at GDC 2011 [Alling2011] the following controls:</div>
</div>
<ul style="text-align: left;">
<li>F-stop - ratio of focal length to aperture size</li>
<li>Focal length – distance from lens to image in focus</li>
<li>Focus distance – distance to plane in focus</li>
</ul>
<div>
Because the CoC is negative for far field and positive for near field calculations, separate results are commonly generated for the near field and far field of the effect [Sousa13].<br />
Usually the calculation of the CoC is done for each pixel in a down-sampled buffer or texture. Then the near and far field results are generated. Then, first, the far and focus field results are combined and then this result is combined with the near field, based on a near field coverage value. The following screenshots show the result of those steps, with the first screenshot showing the near and far field calculations:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicjrsBvf0Qqh3xkSKItSA6zYfF8Fb_K3scbwNRAcplxvPOaigM6Q6Gx42m8juPRFhZFepkVe9H0wyeQj4aCOWyUoqMsdFzh-36TFjCEcWeejaZKaSCoW6Zti9Um4o14YBZStjgqlD0HKU/s1600/NearFieldFarField+CoC.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicjrsBvf0Qqh3xkSKItSA6zYfF8Fb_K3scbwNRAcplxvPOaigM6Q6Gx42m8juPRFhZFepkVe9H0wyeQj4aCOWyUoqMsdFzh-36TFjCEcWeejaZKaSCoW6Zti9Um4o14YBZStjgqlD0HKU/s400/NearFieldFarField+CoC.jpg" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Red = max CoC(near field CoC)</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Green = min CoC(far field CoC)</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"><br /></span></i></div>
<div class="separator" style="clear: both; text-align: left;">
Here is a screenshot of the far field result in Yxy:</div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"><br /></span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhykwKo5ei38X2303yS21szfWYS1igKYo8jndcEWEJINazNtj_aWPPFl7g3HeGAABrLrpmFAtWLxP5rKgBnuFuRdgGmO54LJbzd7OJTFwXRwKGS_wTNJt1lTf1eDSB9vk7PJ2T1AiY_-0I/s1600/FarFieldResultsInYxy.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhykwKo5ei38X2303yS21szfWYS1igKYo8jndcEWEJINazNtj_aWPPFl7g3HeGAABrLrpmFAtWLxP5rKgBnuFuRdgGmO54LJbzd7OJTFwXRwKGS_wTNJt1lTf1eDSB9vk7PJ2T1AiY_-0I/s400/FarFieldResultsInYxy.jpg" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<span style="font-size: x-small;"><i>Far field result in Yxy</i></span></div>
<div class="separator" style="clear: both; text-align: center;">
<span style="font-size: x-small;"><i><br /></i></span></div>
<div class="separator" style="clear: both; text-align: left;">
Here is a screenshot of the near field result in Yxy:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqf74DXg42M2E_cJrqD8d2F4q5PHo7blKeY_SYVvuUbj_0B20QIdsaPfIf_w1IjwsdvAceeEISGoowfmdDYp1k2J_-ZnVrgGM22QZH1oOAUg7P4m8CelOsC-lwDHjn_J54VchC3htbHlo/s1600/NearFieldResultsInYxy.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqf74DXg42M2E_cJrqD8d2F4q5PHo7blKeY_SYVvuUbj_0B20QIdsaPfIf_w1IjwsdvAceeEISGoowfmdDYp1k2J_-ZnVrgGM22QZH1oOAUg7P4m8CelOsC-lwDHjn_J54VchC3htbHlo/s400/NearFieldResultsInYxy.jpg" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Near field result in Yxy</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;"><br /></span></i></div>
<div class="separator" style="clear: both; text-align: left;">
Here is a screenshot of resulting image after it was converted back to RGB:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOY80xbyjHc7ii2UjvclIEmmXlRPCrkxrsysIpA5ztxFvyW2t_d1vVjXC8eczCPdi-Z-tRCwvFs4Q9oeUsluxr6j52JNs9cIKX538A01NuxvtOCzAVCgHa3pCp_eWTtwmQMcZuTJNOZ-g/s1600/ResultDOF.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOY80xbyjHc7ii2UjvclIEmmXlRPCrkxrsysIpA5ztxFvyW2t_d1vVjXC8eczCPdi-Z-tRCwvFs4Q9oeUsluxr6j52JNs9cIKX538A01NuxvtOCzAVCgHa3pCp_eWTtwmQMcZuTJNOZ-g/s400/ResultDOF.jpg" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<i><span style="font-size: x-small;">Resulting Image in RGB</span></i></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Conclusion</b></div>
<div class="separator" style="clear: both; text-align: left;">
A modern Post-Processing Pipeline can benefit greatly from being run in a color space that offers a separable luminance channel. This opens up new opportunities for an efficient implementation of many new effects.</div>
<div class="separator" style="clear: both; text-align: left;">
With the long-term goal of removing any global tone mapping from the pipeline, a dynamic local gamma control can offer more intelligent gamma control that is per-pixel and offers a stronger contrast of bright and dark areas, considering all the dynamic additions in the pipeline.</div>
<div class="separator" style="clear: both; text-align: left;">
Any future development in the area of Post-Processing Pipelines can be focused on a more intelligent luminance and color harmonization.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
<div>
<b>References</b><br />
[Alling2011] Michael Alling, "Post-Processing Pipeline", http://www.conffx.com/GDC2011.zip</div>
[Bartleson 1967] C. J. Bartleson and E. J. Breneman, “Brightness function: Effects of adaptation,” J. Opt. Soc. Am., vol. 57, pp. 953-957, 1967.<br />
[Day2012] Mike Day, “An efficient and user-friendly tone mapping operator”, <a href="http://www.insomniacgames.com/mike-day-an-efficient-and-user-friendly-tone-mapping-operator/">http://www.insomniacgames.com/mike-day-an-efficient-and-user-friendly-tone-mapping-operator/</a><br />
[Engel2007] Wolfgang Engel, “Post-Processing Pipeline”, GDC 2007 <a href="http://www.coretechniques.info/index_2007.html">http</a><a href="http://www.coretechniques.info/index_2007.html">://</a><a href="http://www.coretechniques.info/index_2007.html">www.coretechniques.info/index_2007.html</a><br />
[Kwon 2011] Hyuk-Ju Kwon, Sung-Hak Lee, Seok-Min Chae, Kyu-Ik Sohng, “Tone Mapping Algorithm for Luminance Separated HDR Rendering Based on Visual Brightness Function”, online at <a href="http://world-comp.org/p2012/IPC3874.pdf">http://world-comp.org/p2012/IPC3874.pdf</a><br />
[Potmesil1981] Potmesil M., Chakravarty I. “Synthetic Image Generation with a Lens and Aperture Camera Model”, 1981<br />
[Reinhard] Erik Reinhard, Michael Stark, Peter Shirley, James Ferwerda, "Photographic Tone Reproduction for Digital Images", http://www.cs.utah.edu/~reinhard/cdrom/<br />
[Sousa13] Tiago Sousa, "CryEngine 3 Graphics Gems", SIGGRAPH 2013, <a href="http://www.crytek.com/cryengine/presentations/cryengine-3-graphic-gems">http://www.crytek.com/cryengine/presentations/cryengine-3-graphic-gems</a><br />
<div>
<br /></div>
</div>
</div>
</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com9tag:blogger.com,1999:blog-398682525365778708.post-16670950768224655912013-09-13T10:32:00.003-07:002013-09-13T10:33:18.994-07:00KGC 2013<div dir="ltr" style="text-align: left;" trbidi="on">
I will be a speaker on the Korean Game Developer Conference this year. This is my third time and I am very much enjoying it.<br />
This year I want to talk about building a next-gen Post-Processing Pipeline. Most people haven't change their PostFX pipeline algorithms since 6 or 7 years ( ... no re-writing it in compute doesn't count ... also replacing your Reinhard operator with an approx. Hable operator: check out Insomniac's website doesn't count either :-) ).<br />
<br />
Please come by and say hi if you are around.</div>
Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com4tag:blogger.com,1999:blog-398682525365778708.post-71479768835835820452013-07-29T11:48:00.004-07:002013-07-29T11:49:46.431-07:00TressFX - Crystal Dynamics and AMD cover TressFX on SIGGRAPH<div dir="ltr" style="text-align: left;" trbidi="on">
There were more talks about Confetti's work on TressFX on SIGGRAPH:
One talk by Jason Lacroix was: <a href="http://s2013.siggraph.org/attendees/real-time-live/events/adding-more-life-your-characters-tressfx">"Adding More Life to Your Characters With TressFX"</a>.<p>
Activision's head demo uses TressFX as well: <a href="http://s2013.siggraph.org/attendees/real-time-live/events/digital-ira-high-resolution-facial-performance-playback">"Digital Ira: High-Resolution Facial Performance Playback"</a>.<p>
If you are a registered developer and you need XBOX One or PS4 implementations, send me an e-mail.
<br /></div>Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com0tag:blogger.com,1999:blog-398682525365778708.post-17791881448059951542013-07-25T11:07:00.002-07:002013-07-27T11:29:10.104-07:00SIGGRAPH 2013<div dir="ltr" style="text-align: left;" trbidi="on">
I would like to highlight the talk "Crafting a Next-Gen Material Pipeline for The Order: 1886":<p>
<a href="http://blog.selfshadow.com/publications/s2013-shading-course">http://blog.selfshadow.com/publications/s2013-shading-course</a><p>
The 3D Fabric Scanner is a fantastic idea and the results are awesome. Those are next-gen characters. Great work!
<br /></div>Wolfgang Engelhttp://www.blogger.com/profile/11031097395025597662noreply@blogger.com2