W3C home > Mailing lists > Public > public-gpu@w3.org > November 2018

Re: WHLSL Compatibility with HLSL

From: Dzmitry Malyshau <dmalyshau@mozilla.com>
Date: Wed, 28 Nov 2018 09:25:56 -0500
To: Maciej Stachowiak <mjs@apple.com>
Cc: "Myles C. Maxfield" <mmaxfield@apple.com>, public-gpu@w3.org
Message-ID: <e6132125-14f6-ce18-3095-acc011d72ab0@mozilla.com>
Hi Maciej,

>>>> Reading about the vast field of syntax features (preprocessor, 
>>>> array syntax, call syntax, float literals, casts, etc) that raise 
>>>> questions but ultimately not required to be specially handled by 
>>>> WebGPU implementations only strengthens the point that human 
>>>> writable format has different requirements from 
>>>> implementation-digestable one,
>>>>
>>> I don’t understand this argument. SPIR-V has array syntax, float 
>>> literals, and casts too.
>>
>>
>> Well, the problem with HLSL is that there are different array 
>> syntaxes possible, e.g."float myArray[40]"  versus "float[40] 
>> myArray", while SPIR-V has one and only syntax. Same for casts.
>>
> My understanding of Myles’s analysis is that HLSL has only one way to 
> do these things, and WHLSL has a slightly different way, and we could 
> easily change, because it’s a trivial difference.
>
> But if I’m wrong and vanilla HLSL actually supports both syntaxes, 
> supporting both is trivial and purely a parse time issue. When we 
> explored GLSL issues identified during ANGLE development, it seems the 
> issues were not generally parser issues, but rather mostly issues with 
> working around weird limitations in drivers, or semantic-level issues 
> like variable bindings. There’s really no evidence that supporting two 
> slightly different array declaration syntaxes is in conflict with the 
> needs of an ingestion format.
>
> In addition, SPIR-V also sometimes has multiple different ways to do 
> the same thing. OpAccessChain and OpInBoundsAccessChain are proposed 
> to have the exact same effect in the web syntax. So having multiple 
> syntax variations for the same semantic is not unique to text-based 
> languges

Right. I think the difference with SPIR-V is that we can say only one of 
those is acceptable on an objective basis. In HLSL/WHLSL the choices are 
rather subjective. That is to say, the array example is one of many. 
Consider the time when ISVs request generic type support, then generic 
integers, then lambda syntax. These are all things that SPIR-V doesn't 
care about (since it's lower level and the code is already SSA and 
monomorphised), but we'll have to care about in a text language.

Sorry about re-iterating the point that was brought up multiple times in 
the discussion. I only meant to state that the HLSL compatibility list 
can be seen as a piece of evidence that readable text formats are higher 
level than what the implementation digested format needs to be.


>
>> For float literals, in SPIR-V there isn't a question whether exponent 
>> notation needs to be supported.
>>
>>
>>>> and one way or another we are approaching the point where some 
>>>> build step is required before the actual authored shader source 
>>>> gets to the backend.
>>>>
>>> We’re modifying WHLSL to accept existing HLSL programs. We’re not 
>>> expecting web authors to run a build step to produce WHLSL from 
>>> their HLSL source.
>>
>>
>> On the last call we were elaborating about which side needs to run 
>> the preprocessor. It's effectively a build step.
>>
> We discussed three possibilities:
> (1) Ask providers of shaders that use preprocessor directives to run 
> the preprocessor ahead of time, on the server.
> (2) Add a full preprocessor to the WHLSL spec and implementation.
> (3) Spec a simpler subset preprocessor that hits the 90% case
>
> Only in case (1) would the preprocessor be a “build step”. I suspect 
> it’s not the option we’ll pick given the mention of developers wanting 
> to use the preprocessor to vary based on runtime properties of the 
> client system.

I see. I was just confused by "We’re not expecting web authors to run a 
build step to produce WHLSL from their HLSL source." since it implies 
the choice has been made already.


> Incidentally, how does that work for HLSL compiled to SPIR-V or for 
> that matter DXIL? If native developers are using ifdefs for runtime 
> parameters in HLSL, but also distributing their shaders in a binary IR 
> format, then something doesn’t add up. Preprocessor directives would 
> not be in the binary IR format. The preprocessor would generally run 
> before the compiler proper even touches the file, so I wouldn’t expect 
> it to turn into specialization constants or the like, since that’s 
> just now how #defines work. Are native developers shipping these 
> runtime-parameterized shaders as source, or do they precompile to 
> binary for every possible combination of parameters?

That's a great question for ISVs!

My guess would be that they are using the preprocessor (as a 
specialization mechanism) for cases where the variation space is very 
limited (e.g. defined by the hardware capabilities or game settings). 
For more complex cases they are expected to use the actual 
specialization semantic, like:

[[vk::constant_id(1)]] const bool specConstBool = true;


Thanks,

Dzmitry


>>>>
>>>> > It appears that HLSL allows any arbitrary semantic for stage 
>>>> in/out parameters. Around 30% of the corpus uses a semantic that 
>>>> WHLSL doesn’t currently accept.
>>>>
>>>> In HLSL the semantic name is basically the "port to the outside". 
>>>> If it's built-in, then the "outside" is the graphics/compute 
>>>> pipeline, if it's user-defined, then it's the user code that can 
>>>> query the names. I didn't realize this would be different for WHLSL.
>>>>
>>>>
>>>
>>> I don’t quite understand what you are describing. In any modern 3D 
>>> graphics API, there is linkage between the graphics API and the 
>>> inputs/outputs (MSL calls these[[ attribute(n) ]]) and between the 
>>> vertex & fragment shaders (MSL calls these[[ user(n) ]]). When 
>>> researching HLSL, I thought the list in the docs 
>>> <https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-semantics> was 
>>> exhaustive, but it looks like it isn’t.
>>
>>
>> This link is for DX9 semantics, which is long deprecated. In 
>> non-compat (in the sense of DX9 compatibility) HLSL all the built-in 
>> semantics starts with "SV_", and everything else is user defined. 
>> Does WHLSL not have user-defined semantics?
>>
>>
>>>> > There are a whole collection of variable modifiers that HLSL 
>>>> sources use that WHLSL doesn’t accept. E.g.row_major float4x4 
>>>> mvpMatrix;
>>>>
>>>> Is this on your radar?
>>>>
>>> Not quite sure what this question means. Yes?
>>>>
>>>> > HLSL has some hints about how the compiler should treat branches 
>>>> and loops. They look like[ unroll ] for (…)and[ branch ] if ( …). I 
>>>> don’t think these have semantic meaning so we can probably just 
>>>> swallow them.
>>>>
>>>> I vaguely recall this to affect ERR_GRADIENT_FLOW**errors (aka 
>>>> "Gradient operations can't occur inside loops with divergent flow 
>>>> control."). So it may be more than just a hint.
>>>>
>>> Thanks for the tip; we’ll investigate this.
>>>>
>>>> Cheers,
>>>>
>>>> Dzmitry
>>>>
>>>> On 11/27/18 5:29 PM, Myles C. Maxfield wrote:
>>>>> Over the past few days, I’ve collected a large corpus of HLSL 
>>>>> files so we can determine what we need to do to be 
>>>>> source-compatible with existing HLSL source.
>>>>>
>>>>> *The Corpus*
>>>>>
>>>>> I wrote a GitHub crawler which looked for repositories that had 
>>>>> many HLSL files in them. I looked over the results of this crawler 
>>>>> and hand-picked a few repositories that are from respectable 
>>>>> sources. In total, we ended up with 2099 HLSL files.
>>>>>
>>>>> The list of repositories:
>>>>>
>>>>>   * Microsoft/DirectX-Graphics-Samples
>>>>>   * vvvv/vvvv-sdk
>>>>>       o Of limited use, because most of the source is written in
>>>>>         another language (Effects) which includes HLSL snippets.
>>>>>         GitHub classifies this as HLSL.
>>>>>   * Unity-Technologies/ScriptableRenderPipeline
>>>>>       o Of limited use, because most of the source is written in
>>>>>         another language (Shaderlab) which includes HLSL snippets.
>>>>>         GitHub classifies this as HLSL.
>>>>>   * Microsoft/Windows-universal-samples
>>>>>   * OGRECave/ogre
>>>>>   * EpicGames/UnrealEngine
>>>>>       o Of limited use, because most of the source is written in
>>>>>         another language (Unreal Shader Format) which includes
>>>>>         HLSL snippets. GitHub classifies this as HLSL.
>>>>>   * ConfettiFX/The-Forge
>>>>>   * AtomicGameEngine/AtomicGameEngine
>>>>>   * NVIDIAGameWorks/D3DSamples
>>>>>   * EpicGames/UnrealTournament
>>>>>       o Of limited use, because most of the source is written in
>>>>>         another language (Unreal Shader Format) which includes
>>>>>         HLSL snippets. GitHub classifies this as HLSL.
>>>>>   * urho3d/Urho3D
>>>>>   * NVIDIAGameWorks/HairWorks
>>>>>   * NVIDIAGameWorks/WaveWorks
>>>>>       o Of limited use, because most of the source is written in
>>>>>         another language (Effects) which includes HLSL snippets.
>>>>>         GitHub classifies this as HLSL.
>>>>>   * NVIDIAGameWorks/FleX
>>>>>   * Unity-Technologies/PostProcessing
>>>>>       o Of limited use, because most of the source is written in
>>>>>         another language (Shaderlab) which includes HLSL snippets.
>>>>>         GitHub classifies this as HLSL.
>>>>>   * NVIDIAGameWorks/Falcor
>>>>>   * NVIDIAGameWorks/FaceWorks
>>>>>   * NVIDIAGameWorks/HBAOPlus
>>>>>   * GPUOpen-LibrariesAndSDKs/GPUParticles11
>>>>>   * NVIDIAGameWorks/VolumetricLighting
>>>>>   * GPUOpen-Effects/ShadowFX
>>>>>   * GPUOpen-LibrariesAndSDKs/LiquidVR
>>>>>   * NVIDIAGameWorks/NvCloth
>>>>>   * GPUOpen-Effects/DepthOfFieldFX
>>>>>   * NVIDIAGameWorks/PhysX-3.4
>>>>>   * GPUOpen-Effects/GeometryFX
>>>>>   * GPUOpen-LibrariesAndSDKs/TiledLighting11
>>>>>   * NVIDIAGameWorks/Flow
>>>>>   * GPUOpen-LibrariesAndSDKs/ForwardPlus11
>>>>>   * Microsoft/Win2D
>>>>>   * GPUOpen-LibrariesAndSDKs/SSAA11
>>>>>   * Microsoft/Win2D-Samples
>>>>>   * PixarAnimationStudios/OpenSubdiv
>>>>>
>>>>> We could potentially figure out how to compile Effects, Shaderlab 
>>>>> and Unreal Shader Format to HLSL (because that’s what their 
>>>>> engines do). If we did this, we could grow the repository by 13% + 
>>>>> 8% + 15% (respectively) = 36%. I didn’t want to get bogged down 
>>>>> doing this, though.
>>>>>
>>>>> *Preprocessor*
>>>>>
>>>>> HLSL Source files make heavy use of the preprocessor. Each file 
>>>>> includes an average of 9.61 uses of the preprocessor (lines that 
>>>>> begin with “#”) and the preprocessor is used on average every 
>>>>> 11.68 lines.
>>>>>
>>>>> <Screen Shot 2018-11-13 at 1.03.53 PM.jpeg>
>>>>>
>>>>> As you can see above, most of the users of the preprocessor are 
>>>>> not to include files, but are instead to enable / disable 
>>>>> features. Therefore, this is a situation where compatibility with 
>>>>> existing HLSL source is directly in conflict with simplicity of 
>>>>> the language.
>>>>>
>>>>> I proceeded by running the corpus through the Microsoft HLSL 
>>>>> preprocessor, and investigated the preprocessed files. My analysis 
>>>>> is just based on the parsing stage of the language, not name 
>>>>> resolution or type checking. Out-of-the-box, we parse 5.9% of the 
>>>>> corpus.
>>>>>
>>>>> *Language Features*
>>>>>
>>>>> From investigating the source, I found some language features that 
>>>>> HLSL depends on.
>>>>>
>>>>> In MSL, if you want to pass some data to your shader, you make a 
>>>>> struct, and pass a reference to that struct as an argument of the 
>>>>> main function. Then, in the main function, you reference the data 
>>>>> by saying theReference.field. This approach is possible in HLSL, 
>>>>> but there’s another more common way to do it. Instead of making a 
>>>>> struct, you make a “cbuffer” which lists a set of fields, but 
>>>>> those fields are treated as global variables. The cbuffer is given 
>>>>> a “semantic” so the API can attach memory to back the cbuffer.
>>>>>
>>>>> cbuffer Camera : register(b0) // The API assigns memory to this 
>>>>> block by using the “b0” handle
>>>>> {
>>>>> float4x4 viewProjection;
>>>>> float4x4 projectionInv;
>>>>> float3 viewPos;
>>>>> };
>>>>>
>>>>> Output main() {
>>>>> output.foo = viewProjection; // viewProjection, projectionInv, and 
>>>>> viewPos are in the global scope.
>>>>> return output;
>>>>> }
>>>>>
>>>>> About 1/3 of the files in the corpus use cbuffers.
>>>>>
>>>>> HLSL has two flavors of global variables:
>>>>>
>>>>>  1. Resources, like RWTexture2D<float2> dstTexture :
>>>>>     register(u0);. These work just like entry point parameters,
>>>>>     except they are in the global scope and therefore can be
>>>>>     accessed from any function, without passing around a pointer
>>>>>     to them.
>>>>>  2. Literal data, likestatic const float convolutionWeights[] =
>>>>>     {1, 2, 3};.
>>>>>
>>>>>
>>>>> About 1/5 of the files in the corpus use global variables.
>>>>>
>>>>> HLSL supports default arguments in function parameters and 
>>>>> cbuffers, so you can sayvoid foo(int x = 3);. I would imagine 
>>>>> specifying this would be tricky because we have to mention which 
>>>>> variables and functions the initial value can refer to.
>>>>>
>>>>> Many files in the corpus use HLSL’s syntax for sampler literals, 
>>>>> but those aren’t supported in SPIR-V, so I think we can safely 
>>>>> ignore those. I don’t know what the SPIR-V Cross guys are doing 
>>>>> about that.
>>>>>
>>>>> *New Syntax*
>>>>>
>>>>> There are lots of changes to the syntax of the language that 
>>>>> shouldn’t have much of an effect on the language itself, but are 
>>>>> required if we want to claim compatibility with lots of HLSL sources.
>>>>>
>>>>>   * Removing the entry point keywords (vertex, fragment, compute)
>>>>>     is a requirement for any shader to compile. Instead, we should
>>>>>     require that compilation of a WHLSL file state which function
>>>>>     names are the entry points.
>>>>>   * It appears that HLSL allows any arbitrary semantic for stage
>>>>>     in/out parameters. Around 30% of the corpus uses a semantic
>>>>>     that WHLSL doesn’t currently accept.
>>>>>   * Some functions in the HLSL standard library use
>>>>>     member-function-syntax, liketexture.Sample(sampler,
>>>>>     location)instead ofSample(texture, sampler, location).
>>>>>   * There are a whole collection of variable modifiers that HLSL
>>>>>     sources use that WHLSL doesn’t accept. E.g.row_major float4x4
>>>>>     mvpMatrix;
>>>>>   * HLSL has a few function modifiers like[ RootSignature(…stuff
>>>>>     goes here…) ] void foo(…) { … }that are irrelevant for WebGPU.
>>>>>     This includes information about the D3D root signature, but
>>>>>     also things like how geometry shaders and tessellation work,
>>>>>     which WebGPU doesn’t have.
>>>>>   * HLSL arrays put the brackets after the variable name,
>>>>>     likefloat myArray[40];
>>>>>   * Arrays and structs can be initialized using brackets,
>>>>>     likefloat myArray[3] = { 1.0, 2.0, 3.0 };
>>>>>   * HLSL has some hints about how the compiler should treat
>>>>>     branches and loops. They look like[ unroll ] for (…)and[
>>>>>     branch ] if ( …). I don’t think these have semantic meaning so
>>>>>     we can probably just swallow them.
>>>>>   * HLSL uses C-style casts instead of C++-style casts. So, we
>>>>>     need to support(float)xinstead offloat(x).
>>>>>   * HLSL accepts float literals with exponents, like1e-3.
>>>>>   * Functions can be forward-declared in HLSL.
>>>>>
>>>>>
>>>>> After doing all that, we get up to around 90% compatibility with 
>>>>> parsing (not resolving names nor type checking) the HLSL corpus. 
>>>>> The biggest wins are member-function syntax, allowing every 
>>>>> semantic name, C-style casting, and C-style array syntax.
>>>>>
>>>>> —Myles
>>>
>>
>
Received on Wednesday, 28 November 2018 14:26:21 UTC

This archive was generated by hypermail 2.4.0 : Friday, 17 January 2020 19:52:25 UTC