[HN Gopher] Ubershaders: A Ridiculous Solution to an Impossible ...
       ___________________________________________________________________
        
       Ubershaders: A Ridiculous Solution to an Impossible Problem (2017)
        
       Author : Grognak
       Score  : 209 points
       Date   : 2024-05-16 15:52 UTC (2 days ago)
        
 (HTM) web link (dolphin-emu.org)
 (TXT) w3m dump (dolphin-emu.org)
        
       | dang wrote:
       | Discussed at the time:
       | 
       |  _Ubershaders: A Ridiculous Solution to an Impossible Problem_ -
       | https://news.ycombinator.com/item?id=14884992 - July 2017 (88
       | comments)
        
       | GaggiX wrote:
       | The shader compilation stutter reminds me of a video I recently
       | saw where a developer solved the problem by running a large
       | portion of his game during its first loading:
       | https://youtu.be/oG-H-IfXUqI
       | 
       | The developer register himself playing the game and during the
       | first loading of the game, the entire gameplay is replayed at
       | high speed in the background on the machine.
        
       | corysama wrote:
       | The pixel shading of the GameCube were slower than that of the OG
       | Xbox. But, it was quite a bit more flexible. Specifically, the
       | GameCube could load a couple textures, do a bit of math, then use
       | that math to load some more texels. The Xbox could only load
       | textures as the starting instructions before doing math and tried
       | to make up for that with a few "do very specific math and load
       | textures in a single instruction" ops.
       | 
       | But, still... Both GPUs were pretty well suited for this
       | ubershader approach because they had a small, fixed limit on the
       | number of instructions they could run. And, very strictly defined
       | functionality for each instruction. They weren't really "shaders"
       | as much as highly flexible fixed function stages that you could
       | reasonably wedge in a text shader compiler as a front end and
       | only get a moderate to high amount of complaints about how strict
       | and limited the rules were for the assembly. I recall that both
       | shading units could reasonably be fully specified as C structs
       | that you manually packed into the GPU registers instead of using
       | a shader compiler at all.
        
         | bitwize wrote:
         | ISTR the GC pipeline being fixed-function while the Xbox had a
         | full-fat GPU (GeForce 3 variant) -- one of the reasons why the
         | Xbox absolutely smoked the other sixth-gen consoles in terms of
         | performance. Was I wrong?
        
           | phire wrote:
           | Extremely common misconception (even among developers on
           | those platforms)
           | 
           | In reality, the OG Xbox and GameCube GPUs are almost
           | identical in pixel shading capabilities (Though the
           | gamecube's vertex shading pipeline is legitimately fixed
           | function, but very flexible).
           | 
           | Despite their roughly equal capabilities, they were exposed
           | with very different APIs. The xbox used the new-fangled
           | "Shader" style API that Microsoft was introducing to the
           | industry at the time, while TEV used a very extended version
           | of the older "Texture Environment" style API that was
           | introduced with DirectX 7 and OpenGL 1.3.
           | 
           | ----------------
           | 
           |  _Edit: Actually, it might be better to explain from the
           | other end:_
           | 
           | In a true fixed function GPU like the Playstation 2 and
           | Dreamcast (or OG Playstation... but not the N64, which is a
           | two stage register combiner) the pixel pipeline is limited to
           | just one basic equation. A single texture is sampled, and
           | that sample is multiplied with a single color interpolated
           | from the vertex colors (which were usually derived from
           | lights). The flexibility is of the equation was limited to
           | replacing each input with a fixed value, and then enabling a
           | few optional post-processing stages like depth based fog,
           | alpha cutout and/or blending with a few fixed blend
           | equations.
           | 
           | But the results from that single texel * vertex_color
           | equation are limiting. A common technique to produce better
           | results on such GPUs was "multi-texturing". Graphics
           | developers of the era would render the same triangles two or
           | more times, but with different textures and vertex colours,
           | blending the result into the frame buffer. This was commonly
           | used to achieve the illusion of more detailed textures, or
           | texture based light-maps. Or the reflections on cars in
           | racing games.
           | 
           | But blending in the frame buffer is expensive as it wastes a
           | lot of memory bandwidth. The PS2 is hyper-optimised for this
           | approach, it has the VUs which can quickly generate multiple
           | draws of the same geometry, and a fast, embedded dram with
           | enough read/write ports that it can do blending "for free".
           | But in the PC world, GPUs started adding features to combine
           | these multiple draw call together and blend the result before
           | writing to the frame buffer. The Voodoo 2 and Nvidia TnT
           | (Twin Texel) from 1998 are examples of GPUs that supported
           | this multi-pass texturing.
           | 
           | DirectX and OpenGL provided the "texture environment" APIs
           | that automatically used these new single-pass multi-texturing
           | features when available, or would fall back to multi-pass
           | rendering on older GPUs.
           | 
           | But the actual hardware was often more flexible than what
           | DirectX/OpenGL exposed, though vendors supplied "Register
           | Combiner" OpenGL extensions that exposed the full
           | functionality (This is why John Carmack used OpenGL, so he
           | could create optimised per-gpu render paths for each GPU).
           | And these Register Combiners could be "programmed" to produce
           | pixel equations that were way more advanced that what could
           | be achieved with multi-pass rendering, as they could pass
           | more than one value between stages. And they started
           | supported 4 or 8 textures plus enough math stages to combine
           | the textures.
           | 
           | Microsoft gave up trying to expose the full capabilities of
           | these register combiners though the older Texture Environment
           | APIs and introduced Pixel Shaders with DirectX 8, but they
           | were just providing a new API for the features GPUs already
           | had. The register combine stages were simply renamed to
           | "instructions".
           | 
           | The Xbox is a register combiner with 4 texture fetch stages
           | and 8 combiner stages and Gamecube has 16 combiner stages and
           | 8 texture fetches (well, it technically supports 16 texture
           | fetches, but there are only 8 sets of UV coords)
        
           | corysama wrote:
           | The Xbox had fairly capable vertex shaders. But, phire's
           | comment does a better job of explaining the pixel
           | capabilities both machines than I did.
        
         | phire wrote:
         | _> The Xbox could only load textures as the starting
         | instructions before doing math and tried to make up for that
         | with a few  "do very specific math and load textures in a
         | single instruction" ops._
         | 
         | If you look closely, the TEV actually shares the same
         | limitation, it's just that the traditional representation
         | interleaves the texture fetch and math instructions (Because
         | the 3rd texture fetch "instruction" always feeds into the 3rd
         | math "instruction", for example). There are two independent
         | execution units, separated by a fifo and no way to backfeed
         | from the math back to texture fetch.
         | 
         | The two GPUs are roughly equivalent. The only reason the OG
         | Xbox is consider to "have pixel shaders" is that they were
         | exposed with a pixel shader API, while TEV was only ever
         | exposed with a "texture environment" based API. They are both
         | clearly register combiners, with no control flow, but they sit
         | right in the middle as GPUs were transitioning from register
         | combiners to "proper" pixel shaders. The team that designed
         | GameCube's GPU went on to develop the first DirectX 9 GPU.
         | 
         | I'm pretty sure the Xbox's pixel pipeline is slightly more
         | capable as TEV doesn't have the Dot3 instruction (and it also
         | has programmable vertex shaders). But developers all abandoned
         | the xbox in 2005. TEV has a much better reputation for being
         | flexible because TEV was used in the Wii all the way to ~2013.
         | And graphics developers who were exposed to much better shaders
         | on the Xbox, PS3 and PC got very good at back porting those
         | modern techniques to the more limited Wii. More than one studio
         | created un-offical shader compilers for the Wii, so they could
         | share the same shaders across PS3/Xbox/Wii/PC.
         | 
         |  _> I recall that both shading units could reasonably be fully
         | specified as C structs that you manually packed into the GPU
         | registers instead of using a shader compiler at all._
         | 
         | Yeah, not that they ever exposed that API.
         | 
         | The GameCube had great support for recording display lists, so
         | you could record a display list while you called the API
         | commands to configure TEV and then call that display list later
         | to quickly load the "shader". Some games even saved those
         | display lists to disc (or maybe generated them from scratch
         | with external tools) as a form of offline shader compilation.
        
           | tom_ wrote:
           | You could definitely define pixel shaders on the Xbox using a
           | combiner type struct. See the D3DPIXELSHADERDEF struct in the
           | docs, from memory the equivalent of one of the NV register
           | combiner extensions from OpenGL with additional access to a
           | secret extra stage ordinarily reserved for some fixed
           | function pipeeline stuff.
        
       | phire wrote:
       | Has it really been 9 years since I started working on
       | Ubershaders?
       | 
       | I'm a little surprised no better solution has come along. Vulkan
       | didn't even exist back then (and DirectX 12 had only just
       | released) but instead of making things better, it digs it's feet
       | even deeper into the assumption that all shaders will be known
       | ahead of time (resulting in long "shader recompilation" dialogs
       | on startup on many games).
       | 
       | I've been tempted to build my own fast shader compiler into
       | Dolphin for many common GPU architectures. Hell, it wouldn't even
       | be a proper compiler, more of a templated emitter as all shaders
       | fit a pattern. Register allocation and scheduling could all be
       | pre-calculated.
       | 
       | But that would be even more insane than ubershaders, as it would
       | be one backend per gpu arch. And some drivers (like Nvidia) don't
       | provide a way to inject pre-compiled shader binaries.
       | 
       | On the positive side, ubershaders do solve the problem, and
       | modern GPU drivers do a much better job at accepting ubershaders
       | than they did 9 years ago. Though that's primarily because (as
       | far as I'm aware) examples of Dolphin's ubershader have made
       | their way into every single shader compiler test suite.
        
         | Miksel12 wrote:
         | Don't you think intermediate representation like SPIR-V would
         | suffice in mostly eliminating stutter? Yuzu used that and
         | shader stutter seemed to be minimal and I can image that the
         | shaders generated by Yuzu are much more complex than Dolphin.
        
           | phire wrote:
           | The only step that SPIR-V replaces is parsing the GLSL to an
           | AST tree, and that's only a small part of the total time to
           | compile a shader. Usually the bottleneck is Register
           | allocation or scheduling.
           | 
           | Back when Vulkan was developed, there were a bunch of OpenGL
           | drivers out there which had random AST parsing bugs (Dolphin
           | even has a bunch of workarounds for them); So a large chunk
           | of the motivation for SPIR-V was avoiding the need for every
           | driver to implement their own GLSL parser and the associated
           | bugs.
           | 
           | The problem for Dolphin is not the complexity of the shader,
           | but the quantity.
           | 
           | Shaders in modern games are usually written manually (or
           | authored in a shader node editor by an artist), so it's rare
           | for a game to have more than a few thousand total. Better
           | games might only have a few dozen for the entire game.
           | 
           | But because gamecube/Wii games configure the TEV pixel
           | pipeline though a dynamic API, some games use that API in a
           | pattern where Dolphin can find itself generating hundreds of
           | shaders per second. Some games even manage to generate new
           | shaders continually as you play, because they append junk
           | state to their pixel pixeline state which dolphin doesn't
           | detect as a duplicate.
        
             | Jasper_ wrote:
             | Shaving off the frontend costs is not going to be
             | _nothing_. I don 't know if Dolphin is still using
             | FXC/D3DCompile or if they've switched to DXC, but FXC is
             | infamously slow, even for very simple shaders. Dolphin's
             | shaders are medium-complexity IIRC, so I'd expect removing
             | the frontend to be a decent win.
             | 
             | The driver PSO compilers aren't _amazing_ but they 're also
             | not terrible. Most games do some form of hash-n-cache for
             | PSO compilation and while stutters are still an issue, it's
             | not the worst in the world. With the frontend gone, I'd
             | expect ~50 shaders per second to be roughly stutter-free.
             | 
             | Being smarter about specialization is probably a good idea
             | -- having a blend between "GPU interpreter" and "full
             | specialized pipeline" is where I think you should head.
             | Several of the weirder TEV features could probably be moved
             | to branching on dynamic buffer contents.
             | 
             | Not to mention using newer features like bindless to merge
             | draw calls. I always wanted to do that but got too busy
             | before I stopped working on Dolphin :)
        
               | phire wrote:
               | _> so I 'd expect removing the frontend to be a decent
               | win_
               | 
               | I did some testing before working on ubershaders, and my
               | modified build which cached the bytecode output of
               | FXC/D3DCompile (whatever dolphin was using at the time)
               | didn't reduce the stuttering by enough to be worth the
               | effort of optimising the frontend.
               | 
               | My conclusion was that it's simply wasn't worth any
               | effort to optimise for slightly smaller stutters, as they
               | were still very perceivable to users. And Hybrid
               | Ubershaders can hide any compile delays without any
               | issue.
               | 
               | And this testing was with FXC/D3DCompile which does a
               | bunch of optimisations. The fact that SPIR-V comes in
               | (potentially) unoptimised means any vulkan compiler has
               | to send it though all optimisation passes. Though I have
               | been very tempted to do dead code removal before
               | submitting the shaders, partly to make the shaders more
               | readable to humans and partly to reduce the amount of
               | code going though the various compiler passes.
               | 
               |  _> Being smarter about specialization is probably a good
               | idea -- having a blend between  "GPU interpreter" and
               | "full specialized pipeline" is where I think you should
               | head._
               | 
               | Yeah, that was always next on the list. Start with just
               | ubershaders and then incrementally specialise on a
               | background thread for the correct balance of shaders.
               | 
               | Dolphin's current specialised shaders are no-where near
               | fully specialized. Need to go further by baking some of
               | the constants and lookup textures into the shader.
        
         | naikrovek wrote:
         | I still don't understand why you didn't use the precompiled
         | shaders packed with the games... you're emulating the GameCube
         | or Wii GPU, and it's never going to change, and the games
         | provide precompiled shaders.
        
           | mappu wrote:
           | They're precompiled for the console GPU architecture, not the
           | PC architecture, so they can't be used directly and still
           | need to be emulated - I think those precompiled shaders are
           | the input to the ubershader.
        
             | naikrovek wrote:
             | The GAMES THEMSELVES are precompiled for the PowerPC
             | architecture, not the PC architecture, though. That didn't
             | stop anyone from creating Dolphin.
             | 
             | GPUs (I'm told) have far fewer instructions to emulate than
             | a CPU, so I'd think that low level emulation of the Flipper
             | shaders would be no trouble. Can't translate or transpile
             | them to PC GPUs though because those instruction sets are
             | somewhat secret, I think.
             | 
             | I know nothing about this stuff but I am a developer so
             | perhaps I know enough to ask the most stupid questions
             | possible.
             | 
             | It's gotta be a performance thing, why they didn't emulate
             | Flipper at a low enough level to use the precompiled
             | shaders directly.
        
               | saagarjha wrote:
               | Yes, that's exactly the point though. This is the same
               | question as why you can't emulate a game by precompiling
               | its code, and this doesn't work because that information
               | isn't available until you try to run the game. That's why
               | Dolphin has an interpreter/JIT.
        
               | HideousKojima wrote:
               | >This is the same question as why you can't emulate a
               | game by precompiling its code, and this doesn't work
               | because that information isn't available until you try to
               | run the game.
               | 
               | I mean technically you can, but it generally requires a
               | bunch of inefficient jump tables, or alternatively a way
               | to fall back to an interpreter or JIT for self modifying
               | code.
        
               | mappu wrote:
               | > because those instruction sets are somewhat secret, I
               | think
               | 
               | The GPU ISAs are known (e.g. the PTX compiler for NVidia
               | is open source and has a backend in LLVM). The main
               | problem is that the GPU ISA changes with every GPU
               | hardware generation and manufacturer, so if you want to
               | support Nvidia 3xxx + 4xxx + AMD VLIW + AMD GCN + ... you
               | have to use the common demoninator
               | GLSL/HLSL/SPIR-V/whatever.
               | 
               | > why they didn't emulate Flipper at a low enough level
               | to use the precompiled shaders directly.
               | 
               | They did. Originally the GPU emulator was done in the
               | CPU, and in 2017, the GPU emulator itself was moved into
               | a shader ("ubershader").
               | 
               | The console game itself does not include shaders in text
               | format like many PC games do.
        
               | mandarax8 wrote:
               | > The GPU ISAs are known (e.g. the PTX compiler for
               | NVidia is open source and has a backend in LLVM)
               | 
               | PTX is only and IR afaik, kinda like SPIRV. It also goes
               | through another compiler in the driver so doesn't really
               | help here
        
               | TomatoCo wrote:
               | The ubershader is the thing that emulates Flipper at a
               | low enough level to use the precompiled "shaders"
               | directly. Prior to that the precompiled "shaders" were
               | examined and recompiled into individual shaders, a
               | process that took time.
               | 
               | (Why "shaders" in quotes? Because they weren't shaders as
               | we know them today but really more like lists of hardware
               | flags for how to flow data through a fixed function
               | pipeline)
        
           | phkahler wrote:
           | Wait, I hought that's what the ubershaders are. What you say
           | is what I kept thinking for much of the article - "just"
           | emulate the GPU, no compiler needed. And then they did.
        
             | naikrovek wrote:
             | Maybe, lol, there were a lot of terms in that article that
             | I didn't understand well enough to claim I understood the
             | article in toto.
        
               | pests wrote:
               | One thing to remember is these older consoles don't have
               | the same concept of a "shader" as we do today.
               | 
               | Go back far enough and you'll find the industry trying to
               | settle on quads or triangles for rendering (and we all
               | knew who won)
               | 
               | The games were given basically an immediate mode API into
               | the graphics card and they could do whatever they wanted,
               | whenever they wanted, without warning.
               | 
               | The stutter happened when they were translating the API
               | mentioned above into modern GPU shaders.
               | 
               | When it was on the CPU - They had to determine the
               | effect, generate and compile the modern shaders, and
               | upload that to the GPU, sometimes hundreds of times a
               | second. Then the GPU would take over and display.
               | 
               | Uber shaders took that entire pipeline and moved it into
               | the GPU.
               | 
               | This was low level emulation, just still hitting limits
               | of modern CPUs.
        
           | phire wrote:
           | First, GameCube/Wii API actually generates the "shaders" at
           | runtime, so there is simply no way to know which vertex/pixel
           | pipeline states the game needs short of playing though the
           | whole game, looking at every single bit of level geometry.
           | 
           | Many games actually dynamically generate new "shaders" on the
           | fly, based on which lights are near an object, and in which
           | order.
           | 
           | Second we can't use those vertex/pixel pipeline states
           | directly on modern GPU, they need to be translated into
           | modern shaders, and then compiled by the driver for your
           | graphics card. It's actually that compile step which causes
           | the stuttering, dolphin's translation is plenty fast enough.
           | 
           | The combination of these two facts means Dolphin can't depend
           | on any pre-computation at all.
        
             | naikrovek wrote:
             | I don't get it (this is not your fault, it's mine) but I
             | believe you.
        
               | rkachowski wrote:
               | if I understand correctly
               | 
               | 1. "shader" is just a metaphor, the actual code running
               | on the gamecube gpu is a custom pipeline that has a
               | dynamic structure and is updated aggressively throughout
               | the lifetime of the app - there is no static "shader"
               | program to run on the host GPU.
               | 
               | 2. The architectures of the gamecube and modern GPUs are
               | so distinct as to require an intricate translation layer
               | in order to map gamecube rendering operations to first
               | class shader operations on a modern GPU. This very
               | process causes the stuttering that starts the issue.
        
               | bonzini wrote:
               | Translation is not intricate, but modern graphics card
               | are not tuned for dynamically setting up shaders.
        
           | ErneX wrote:
           | PC games that have a shader pre compile step usually have to
           | re do it when new drivers come out, pre compiled shaders can
           | be shipped to closed systems such as consoles or even steam
           | deck but not for PC. Each different GPU brand requires
           | different ones and like I said even when you update drivers.
        
           | TomatoCo wrote:
           | That's the trick, they actually don't provide precompiled
           | shaders as you know them. The graphics hardware back then was
           | fixed function pipelines with a tremendous number of options
           | to configure how they work. The downside is that you can't
           | run truly arbitrary code but the upside is that they can
           | instantaneously switch behavior as fast as setting a
           | register.
           | 
           | Prior to ubershaders the emulator took a configuration for
           | the hardware pipeline and turned that into a shader, which
           | took time to compile. Ubershaders work by emulating the
           | entire fixed function pipeline in one glorious shader until
           | the smaller, more efficient shader can be compiled and
           | slipped in.
           | 
           | Basically, the ubershader is the only thing that can actually
           | understand the "shaders" packaged with the game and start
           | using them with zero latency.
           | 
           | Why not just precompile all the possible hardware
           | combinations? There's far more combinations than atoms in the
           | universe. Why not just precompile all the hardware
           | combinations that the game actually uses? There's no way to
           | tell before hand without examining every branch of the game's
           | code which ranges in difficulty from "computationally
           | prohibitive" to "fundamental theorems of how computers work
           | says this is impossible".
           | 
           | The article mentions that some users actually passed around
           | cached shader packs, but that solution was brittle.
        
         | oppositelock wrote:
         | Very cool work!
         | 
         | I had to solve a similar problem years ago, during the
         | transition from fixed function to shaders, when shaders weren't
         | as fast or powerful as today. We started out with an ubershader
         | approximating the DX9/OpenGL 1.2 fixed functions, but that was
         | too slow.
         | 
         | People in those days thought of rendering state being stored in
         | a tree, like the transform hierarchy, and you ended up having
         | unpredictable state at the leaf nodes, sometimes leading to a
         | very high permutation of possible states. At the time, I
         | decomposed all possible pipeline state into atomic pieces, eg,
         | one light, fog function, texenv, etc. These were all annotated
         | with inputs and outputs, and based on the state graph
         | traversal, we'd generate a minimal shader for each particular
         | material automatically, while giving old tools the semblance of
         | being able to compose fixed function states. As for you, doing
         | this on-demand resulted in stuttering, but a single game only
         | has so many possible states - from what I've seen, it's on the
         | order of a few hundred to a few thousand. Once all shaders are
         | generated, you can cache the generated shaders and compile them
         | all at startup time.
         | 
         | I wonder if something like this would work for emulating a
         | Gamecube. You can definitely compute a signature for a game
         | executable, and as you encounter new shaders, you can associate
         | them with the game. Over time, you'll discover all the possible
         | state, and if it's cached, you can compile all the cached
         | shaders at startup.
         | 
         | Anyhow, fun stuff. I used to love work like this. I've
         | implemented 3DFx's Glide API on top of DX ages ago to play
         | Voodoo games on my Nvidia cards, and contributed some code to
         | an N64 emulator named UltraHLE.
        
           | M4v3R wrote:
           | > contributed some code to an N64 emulator named UltraHLE
           | 
           | That's a blast from the past, I distinctly remember reading
           | up about UltraHLE way back when and then trying it our and
           | for the first time being able to play Ocarina of Time on my
           | middle class PC with almost no issues, that was magical.
        
         | Akronymus wrote:
         | >On the positive side, ubershaders do solve the problem, and
         | modern GPU drivers do a much better job at accepting
         | ubershaders than they did 9 years ago. Though that's primarily
         | because (as far as I'm aware) examples of Dolphin's ubershader
         | have made their way into every single shader compiler test
         | suite.
         | 
         | How'd that come to be? Just interesting code for test suites or
         | did you guys advocate for it to be included?
        
       | conorpo wrote:
       | Does anyone know why this isn't an issue for modern games on PC?
       | I assume it's because more uniforms are used, and the amount of
       | shaders that actually need to be compiled at runtime is
       | minimized, not to mention that the Graphics API is optimized to
       | compile the shaders in the format they are provided. So is the
       | issue with Dolphin that GameCube games would compile new shaders
       | for lots of different configurations of effects / stages? Would
       | some sort of preprocessor that converts shader compilations to
       | some mini-ubershader with uniforms that can handle a lot of the
       | different effects be feasible? And then depending on how many
       | completely different shaders there are you would have many
       | different mini-ubershaders?
        
         | rtpg wrote:
         | I think it's because PC games know they need to deal with
         | compilation, so they do it on the load screen or whatever. GC
         | games can pre-compile them and just stuff it on the disk, so
         | there's no compilation cost.
        
           | pjmlp wrote:
           | GC shaders are loosely based on GLSL, and OpenGL compilation
           | model, even though GX(2) isn't OpenGL.
        
             | Jasper_ wrote:
             | No, GameCube's TEVs have nothing to do with GLSL. The
             | reference to GX2 implies you're confusing it with Wii U,
             | inexplicably.
        
               | pjmlp wrote:
               | Yes I mixed them with Wii and Wii U,no need for
               | "inexplicably" remark.
        
         | Jasper_ wrote:
         | It is an issue with some modern games (I recently played a
         | title that had a "Preparing Shaders..." loading screen); the
         | main difference is that those games know the full set of what
         | they need to do and can precompile most of them up-front, while
         | an emulator like Dolphin needs to handle whatever the game
         | throws it on the fly.
         | 
         | Also, games might know what shaders it can skip and what it
         | can't, but Dolphin can't skip shaders if they aren't compiled,
         | because it doesn't know what the game will do with the render
         | (e.g. Miis work by rendering their heads once into a texture,
         | and then reusing that. If it skips the render because the
         | shader isn't ready, the Mii will just be missing forever).
         | 
         | Some emulators handle this by sharing "shader caches" between
         | users so that they have a better idea of what the game will
         | use; Dolphin opted for a different solution here.
        
           | MBCook wrote:
           | It's not a problem for the PS/Xbox/Switch. They have known
           | hardware and it can all be recompiled.
           | 
           | But from what I've heard it's often still an issue on PCs
           | (I'm a Mac guy). I've seen videos of shader compilation
           | stutters, even in games with a precompilation step that's
           | supposed to avoid that.
           | 
           | Digital Foundry has covered this many times. The link in a
           | sibling comment to them on Eurogamer is a great place to
           | start.
        
             | talldayo wrote:
             | Steam works around this by letting users enable shader
             | precompilation in settings. If you _want_ a console-like
             | experience (eg. like Steam Deck) and you don 't care so
             | much about storage space, you can toggle it on and
             | eliminate the stutter before booting up. Most people leave
             | this off, which really ruins the experience on shader-heavy
             | engines like UE4.
             | 
             | This is generally an everyone problem, though. If gaming on
             | Mac was caught-up with where Linux is today, there would
             | probably be a few precompilation steps there too. If you
             | wanted to play Fallout 3 on your Android/iPhone device,
             | it's the same story.
        
         | ruined wrote:
         | gcn games never anticipated shader compilation time because it
         | didn't exist.
         | 
         | shaders weren't a thing when the gcn released. it may be
         | arguable, but nobody even used the word at the time. shader
         | compilation time _is_ an issue for modern games on the PC, and
         | because of this, developers anticipate and work around it.
         | 
         | on the gcn, specialized fixed-function pipelines were
         | available, and could be composed by some limited configuration
         | (literally, 24 instructions). you may think of this as a sort
         | of proto-shader, but significantly, the fixed-function
         | pipelines embody quite a lot of behavior _in specialized and
         | limited hardware_ that is now typically achieved _in software
         | on more versatile hardware_.
         | 
         | so, to replicate that specialized hardware, modern graphics
         | hardware (which exposes its greater capability as simple
         | computational primitives) must compile a shader program and run
         | it. but on gcn, the tiny configuration of static hardware loads
         | near-instantly.
        
           | pjmlp wrote:
           | Shaders as concept exists since 1980's,
           | 
           | https://en.m.wikipedia.org/wiki/TMS34010
           | 
           | https://en.m.wikipedia.org/wiki/RenderMan_Shading_Language
           | 
           | Just two examples, SIGGRAPH has plenty of paper on the
           | subject.
        
           | OCASMv2 wrote:
           | A 2002 postmortem on the tech of Rogue Leader, a Gamecube
           | launch title, already using the term "shaders":
           | 
           | https://www.gamedeveloper.com/programming/shader-
           | integration...
        
             | shzhdbi09gv8ioi wrote:
             | The first video card with a programmable pixel shader was
             | the Nvidia GeForce 3 (NV20), released in 2001. [1]
             | 
             | 1: https://www.techpowerup.com/gpu-specs/nvidia-nv20.g644
        
         | ploxiln wrote:
         | It is an issue for some modern big titles on PC. Trying to find
         | some links that have somewhat general overview ...
         | 
         | https://twistedvoxel.com/unreal-engine-5-pc-stuttering-issue...
         | 
         | https://www.eurogamer.net/digitalfoundry-2022-df-direct-week...
        
         | db48x wrote:
         | The programmer of a modern PC game knows that they will be
         | using shaders, and can arrange for them all to be compiled and
         | sent to the GPU during a loading screen. That eliminates the
         | lag, because there is no delay when choosing which shader to
         | use for the next triangle. On the other hand it makes the
         | loading screen take longer.
         | 
         | Meanwhile the programmer of a console game, not using shaders,
         | could set GPU registers to any configuration they wanted just
         | before rendering the next triangle. You have to actually play
         | the game to find out what configurations it programs into the
         | GPU, because those configurations are not neatly organized into
         | a set of discrete shaders. Even then there is no guarantee that
         | you found all possible configurations used by the game. The
         | videos in the article provide a good example: the player fires
         | a gun with luminous bullets, so on that frame the walls and
         | floors need to be rendered with an extra light source. That
         | requires reconfiguring the GPU to take that light source into
         | account, then changing the configuration to render the weapon
         | itself, then changing it again to render the HUD, and so on.
         | 
         | Now imagine that you go to a place on a different level where
         | the walls are not shiny, and it doesn't bother to render the
         | walls with the extra light source. Or it renders them with
         | extra vertex lighting but not extra specular lighting. Now
         | combine that with every type of wall and floor in the game;
         | they might all need a unique shader to be lit correctly by that
         | one gun. To find all possible GPU configurations you need to
         | fire that gun, and every other, near every single different
         | type of wall and floor texture used in the game. And there are
         | a dozen different guns.
         | 
         | And then you need to do it all again while wearing the night-
         | vision goggles, because that causes everything to be rendered
         | with a different configuration yet again.
         | 
         | Every one of those unique combinations needs to be made into a
         | shader, and there's just no way to be sure that you have
         | actually collected all of them. Or you can write a single
         | Ubershader that can, by using branches, loops, and other
         | advanced tricks, emulate the entire capabilities of the
         | emulated GPU. Then you can program the Ubershader by sending
         | all of the emulated GPUs register values as uniforms.
        
         | ErneX wrote:
         | This a big issue in modern PC games, games that don't do a
         | shader pre compile before starting the game suffer from serious
         | shader compile stutters while playing the 1st time, dropping
         | frames every time a new variation is required for a particular
         | frame, it's actually bad.
        
       | sfink wrote:
       | It's interesting to see the parallels between this and an engine
       | for a dynamic programming language. The one I'm most familiar
       | with is JavaScript.
       | 
       | When you first need to run something, you run it on the
       | interpreter (JS) / ubershader (Dolphin). But once you know it's
       | going to be run repeatedly (rarely for JS, almost always for
       | Dolphin), you kick off an async compilation to produce JIT code
       | (JS) / a specialized shader (Dolphin). You continue running in
       | the expensive mode (interpreter / ubershader) until the
       | compilation is complete, then you switch over seamlessly.
        
         | mst wrote:
         | I might have said "oh, _clever_" out loud when that first
         | clicked as I read through the piece.
        
         | shepherdjerred wrote:
         | While JS can be interpreted, V8 and SpiderMonkey (the two most
         | common JS engines) will _always_ compile before execution -- JS
         | is _never_ directly interpreted these days (aside from more
         | niche engines).
         | 
         | https://v8.dev/blog/ignition-interpreter
         | 
         | https://firefox-source-docs.mozilla.org/js/index.html#javasc...
        
           | sfink wrote:
           | That's not exactly right. I believe you're mixing up bytecode
           | generation with compilation. The source code will indeed be
           | compiled down to bytecode, but that doesn't count since it
           | does not change the generality. As in, bytecode is no more
           | specialized than the original source code. It's the same with
           | shaders -- the ubershader does not interpret the original
           | shader source text.
           | 
           | Both V8 and SM will interpret the bytecode until it warms up
           | enough to be compiled to specialized machine code. ("Warms
           | up" == "is observed to execute enough times".) There are some
           | subtle distinctions about whether the interpreter is
           | implemented in C++ or generated by a variant of the JIT code
           | compiler, but as with the shaders the main point is whether
           | it's executed in a way that works for everything or is
           | specialized to a particular purpose (and varying degrees of
           | specialization are implemented, with various mechanisms for
           | falling back to a more general execution mechanism if the
           | specialization assumptions no longer hold).
           | 
           | Your SpiderMonkey doc link points to a section named
           | "JavaScript Interpreter". The title is correct, that section
           | is indeed about the mechanisms for interpreting JavaScript
           | [bytecode].
           | 
           | The V8 link is a little tricky, since it leads off with "Code
           | is initially compiled by a baseline compiler", but if you
           | read a little further, it says "...the V8 team has built a
           | new JavaScript interpreter, called Ignition, which can
           | replace V8's baseline compiler". Basically, V8 experimented
           | for a while with dropping the interpreter, but for the
           | reasons described well in that document, they went back to
           | initially running in an interpreter. The article is quite
           | nice and describes quite a bit about the tradeoffs involved.
           | It's 8 years old, but I believe the overall picture isn't
           | that different today.
           | 
           | (Source: I am an engineer on the SpiderMonkey team.)
        
             | shepherdjerred wrote:
             | I don't think your reply conflicts with my comment. I was
             | clarifying that modern JS engines do not directly interpret
             | JS. JS is always compiled to bytecode which is then
             | interpreted or compiled to machine code.
        
       | doophus wrote:
       | What was the missing piece for "shader sharing"?
       | 
       | Would it be possible to build a web-hosted database of
       | encountered shader configs against a game id, and have Dolphin
       | fetch that list when a game launches and start doing async
       | compilation?
       | 
       | When Dolphin encounters a new shader that wasn't in the db, it
       | phones home to request it to be added it to the list.
       | 
       | I feel an automated sharing solution would build up coverage
       | pretty quickly, and finding a stutter would eventually be
       | considered an achievement - "no-one's been here before!"
        
         | mandarax8 wrote:
         | Every shader depends on both the driver version and the model
         | of your GPU itself. Which means a lot of shaders. I think Valve
         | had a version of it though but not without issues (GBs of
         | shaders to be downloaded)
        
           | 91edec wrote:
           | The Steamdeck does this thats why it doesn't suffer from
           | stutters.
           | 
           | For normal PC's, realistically Valve/Steam are the only
           | people who could solve or implement this for PC games as they
           | have the tech and platform to distribute it all. Even with
           | all that its a crazy task to try and solve due to all of the
           | variations and new patches for games that require the shaders
           | to be recompiled again.
        
             | maccard wrote:
             | Honestly I think torrents are not a bad idea for this.
        
           | aprilnya wrote:
           | The idea here wasn't to share the compiled shader, but to
           | share the shader configurations that each game uses -- the
           | ones that are used to then compile the actual shader. So you
           | would compile them all at game start from the configurations
           | you downloaded
        
       | popcar2 wrote:
       | This is a really neat article because the Godot engine is adding
       | Ubershaders as well to fix shader compilation stuttering:
       | https://github.com/godotengine/godot/pull/90400
        
         | darzu wrote:
         | Pretty sure these are very different phenomena both called
         | "ubershaders". As far as I understand it, outside of Dolphin,
         | when people say "ubershader" they mostly mean a large material
         | shader that has branches and constants to handle all the
         | material variants used in one game (e.g. wood door, metal
         | shield, shiny forcefield). It's produced by the engine so it's
         | under full control of the engine developers. This means instead
         | of loading separate shaders for the wood door and metal shield
         | you can just tweak pipeline parameters. It's a work around for
         | the various overheads, like compilation, involved in creating
         | specialized shaders.
         | 
         | But Dolphin's "Ubershader" is a different beast. It's about
         | handling all the shader variants for _all_ Dolphin games (which
         | are made in different engines) with one shader, and the variant
         | parameters aren't passed as nice constants (data) but as shader
         | programs (code) that need to be interpreted to be understood.
         | It's more like a meta-shader that takes shaders as input and
         | produces shaders as opposed to a "normal" ubershader which
         | takes configuration to specialize it at runtime.
         | 
         | I think that's right anyway. I haven't worked directly with
         | ubershaders in either variety and my knowledge comes from
         | building my own hobby engine and 3D pipelines.
        
           | 01HNNWZ0MV43FF wrote:
           | It sounded to me like an interpreter that emulates the
           | GameCube / Wii GPU inside a shader, but I didn't read it
           | closely
        
             | darzu wrote:
             | Yup, the whole vertex and pixel stages I believe.
        
       | smallstepforman wrote:
       | I'm suprised to see that Ubershaders still exist, most game
       | engines have settled on a set of fit-for-purpose custom shaders
       | with almost no conditionals (for performance reasons), which is
       | the opposite of UberShaders.
        
         | mort96 wrote:
         | Why would a lack of conditionals remove the need for uber
         | shaders..? Or why would the presence of conditionals cause a
         | need for uber shaders? How are these connected?
         | 
         | It's common to have many shader variants exactly because you
         | want to avoid 'if' statements; conditionals are turned into
         | shader-compile-time options. Uber shaders are about turning
         | those compile-time options back into runtime options to have a
         | fallback shader to run while the right shader variant is being
         | compiled.
        
         | petermcneeley wrote:
         | The wins here are probably not as large as one might think
         | https://advances.realtimerendering.com/s2016/s16_ramy_final....
         | 
         | The fact is that a branch that the whole warp takes or does not
         | take is relatively cheap on modern hw. Even if it is per thread
         | dependent.
        
       | nightowl_games wrote:
       | I've thought about writing a GPU side interpreter for SDF
       | definitions for a while. I made a SDF shader generator that dumps
       | out shaders with hard coded values, but doing it with bytecode
       | would be cool. I'm sure this has been done before..
        
       ___________________________________________________________________
       (page generated 2024-05-18 23:02 UTC)