13
$\begingroup$

No language that I know of directly has a mechanism for embedding GPU shaders in the code. Third party libraries such as OpenGL are required, and they usually have the shader source code as just a string, and is compiled at runtime, making the system much less efficient.

I would expect some languages out there to have native support for GPU execution, so that the compiler can identify shaders and compile time accordingly, instead of compiling them at runtime.

The only language that half-does this is Metal shading language, which is loaded by the Metal library on the Swift side post-compilation. But that is still not an intrinsic capability of the Swift language.

What would supporting GPU execution in a language take and why is this never seen?

$\endgroup$
6
  • $\begingroup$ Is Metal really a Swift thing? I was under the impression (and I could be wrong) that Metal is an Apple Objective-C thing, and Swift can use it because Swift can use Apple Objective-C. $\endgroup$
    – Bbrk24
    Commented Jul 3, 2023 at 18:32
  • $\begingroup$ @Bbrk24 It's a Swift & Objective-C thing. There are some discrepancies, such as in swift there is a method are called makeCommandBuffer and in Objective-C newCommandBuffer. $\endgroup$
    – CPlus
    Commented Jul 3, 2023 at 18:35
  • $\begingroup$ That makes sense. I wonder if the Objective-C one is just annotated NS_SWIFT_NAME("makeCommandBuffer") or some equivalent, but I suppose that's neither here nor there. $\endgroup$
    – Bbrk24
    Commented Jul 3, 2023 at 18:36
  • $\begingroup$ Julia does it in quite a transparent way, Mojo is apparently going to offload code implicitly to GPUs. $\endgroup$
    – SK-logic
    Commented Jul 4, 2023 at 8:42
  • 1
    $\begingroup$ @TomTsagk there is at least SPIR-V and PTX, so you can compile your kernels into an intermediate representation which is portable across GPUs and is still usable on a CPU as well. $\endgroup$
    – SK-logic
    Commented Jul 4, 2023 at 10:14

3 Answers 3

6
$\begingroup$

It is sometimes seen ─ shaders written in HLSL (the shader language used by DirectX) can also be precompiled. But I think in practice this is usually only done when targeting games consoles, where you know in advance exactly what graphics hardware the shaders will run on. Optimising for the specific hardware will have a bigger impact than the short amount of time it takes on initial load to compile a shader from source.

There is a possible middle-ground, which is compiling shaders to a bytecode language which can then be compiled again at runtime by a JIT for the specific graphics hardware. The bytecode language would have to be standardised so that it could be supported by device drivers which contain optimising compilers for the specific hardware.

In practice, such a bytecode language might not exist for the graphics API you're using*, and I doubt that it would be much faster than compiling from source code as a string. Shader languages are pretty simple compared to general-purpose languages, and the optimisations that can be done platform-independently (at your compile-time) are probably very cheap compared to a lot of other things that would still need to be done at runtime. Bytecode would also take up less space, but the size of shader source code is negligible compared to other assets anyway.

* There are intermediate bytecode languages for DirectX and Vulkan, but I don't think there is one for OpenGL.


All of this explains why shaders are typically distributed as source code. That said, it would still be possible for a compiler to allow shaders to be written in the same language as normal code: you would just need to be able to transpile from that language to the shader language. But this feature would not be as useful as it sounds.

It's not quite true that shaders are typically distributed as source code ─ in most large applications, the source code for a shader is not just compiled at runtime, it is created at runtime, either by string concatenation or something more advanced. This is done because applications want to detect the capabilities of the hardware, and offer the user the option to disable certain features for performance reasons, so there might be thousands or millions of combinations of which parts of a shader need to be used, and the correct combination will only be known at runtime.

So it's not enough to just be able to transpile your language to a shader language ─ you need to transpile it to fragments (no pun intended) of shader language source code, to be concatenated at runtime based on the appropriate conditions.

$\endgroup$
0
3
$\begingroup$

How could a language support GPU execution cleanly?

There are several aspects or interpretations to this question, so maybe the question should be split into narrower ones...

First, there's a question "GPUs (and similar hardware) have particular execution and memory model that's rather different from common CPUs — how can a programming language support it?"

There are quite a number of languages — both standalone and (embedded) DSLs — aimed specifically at (GP)GPU execution: various shader languages (as you've mentioned), CUDA, OpenCL, Halide, ISPC, etc.

Though in my opinion, the cleanest GPGPU language is Futhark. From my view it fully leverages non-Turing-completeness of GPU computations and provides a sweet spot of high-level features for automatic parallelism:

  • arrays-oriented total functional language
  • limited easy to use form of dependent types for static dimensionality checking
  • limited form of linear types for locally mutable computations
  • some other quality-of-life things

Second, there's a question "how can we embed computation offloading to GPU (and other accelerators) in a general-purpose language?"

I haven't seen a really clean and neat solution in this space, though Julia GPU ecosystem supports a huge chunk of the language completely seamlessly (including GC support for GPU). There are a number of Python libraries/extensions supporting various subsets of the language and providing automated GPU/TPU/etc offloading.

AnyDSL presents arguably one of the cleanest approaches through seamless integration of several DSLs on top of the common highly optimized intermediate representation, but that's a very much research project.

Third, there's a question "how can we support graphics programming in a general-purpose language?" Preferably in a safe fashion.

I don't have much knowledge and experience on this front, but from what I've heard you can't precompile shaders to actual machine code because GPUs use completely different proprietary ISAs. The only common API is some common standardized API — OpenGL/Vulcan + shaders/SPIR-V. Thus you can't statically check anything beyond the API/shader model either.

That being said, from practical standpoint many Rust game developers care a lot about statically checking as much as possible, thus there are several projects integrating shaders into a graphics framework through procedural macros and checking statically whatever they can.

Also WebGPU project explicitly builds a safe API, at least in a sense you can't corrupt GPU memory or somehow "brick" it or anything. And they strive to check as much as possible statically and as little as necessary dynamically.

I hope this overview will help clarifying and narrowing down follow-up questions...

$\endgroup$
1
$\begingroup$

Most programming languages can not be cross-compiled to shader code because the structures and capabilities of GPU code are just too different from those of CPU code.

However, embedding shader code would be an option. Embedding one programming language in another is hardly uncharted territory. For example, C and C++ have the asm keyword for ages.

Why does no programming language do that for shader code, though?

  • There are just too many shader languages to choose from. GLSL, HLSL, PSSL, MSL, WGSL... So either you choose exactly one to support (greatly narrowing the applicability of your language), try to support as many as possible (which is a ton of work) or you invent yet another shading language representing the smallest common denominator between them all (which will only support the most basic functionality).
  • CPU and GPU generally communicate "at arms length" with each other through a graphics API like OpenGL, DirectX, Vulkan or Metal. Abstracting that away is often not desired or useful.
  • Pre-compiling shaders is already possible. But it is often not desired, because they are indeed created at runtime based on the configuration of the system they run on.
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .