Containerized Shader Compilers

When targetting multiple graphics APIs, a large number of compilers and tools are used. Versioning, deploying and updating all these binaries can be a tedious and complex process, especially when using them with multiple platforms.

In previous posts, I discussed containerizing Microsoft DXC (Linux), Microsoft FXC (Windows under Wine), and signing DXIL post compile (custom tool that loads dxil.dll under Wine).

With the goal of having a shader build service containerized within a Docker container, and scaling it out in a Kubernetes cluster (like GKE), having a single image containing the ability to run all necessary compilers and tools is a critical component.

These are the compilers and tools that are of interest:

  • Microsoft DXC
    • Compiles HLSL to DXIL or SPIR-V
    • Supports shader model 6.X
    • Native Linux support (unsigned DXIL)
    • DirectX ray tracing
  • Microsoft FXC
    • Compiles HLSL to DXBC
    • Supports shader model 5.X
    • Windows binary only
  • Glslc (glslang)
    • Compiles HLSL or GLSL to SPIR-V
    • NV mesh shaders (GLSL only, currently)
    • NV Vulkan ray tracing (GLSL only, currently)
  • AMD Radeon Graphics Analyzer (RGA)
    • Compiler and performance analysis tool
    • Generate and analyze GCN ISA disassembly
    • Windows and Linux binaries
    • Windows version has DirectX support
  • Custom DXIL signing tool
    • Needed to sign DXIL post-compile or with Linux
    • Windows binary (compiled from source)
  • SMOL-V
    • SPIR-V size reduction
  • Vulkan SDK
    • spirv-cross
    • spirv-as
    • spirv-dis

Wine Patches

As the container is Linux (not Windows), any of the Windows binaries must be run using Wine:

  • Microsoft FXC
  • AMD RGA (Windows version)
  • Custom DXIL signing tool

Running FXC and RGA with Wine worked out of the box, but in a previous post, I described some fatal errors when dxc.exe or the custom sign tool would call into dxil.dll: Wine DXC Error

I submitted some bug reports to WineHQ and Fabian Maurer graciously debugged and submitted patches to Wine that resolve the dxil.dll issues - (155381, 155382). Wine is currently in code lockdown for a new release, but these patches are expected to reach mainline in a few weeks. For the time being, I applied these patches to a custom fork of Wine for use in this Docker image.

Initial Version

The initial version of the all-inclusive compiler Docker file is shown here - all tools are downloaded, compiled, and installed directly into a single massive image.

FROM ubuntu:bionic

WORKDIR /app/dxc
ENV VULKAN_SDK=1.1.92
RUN apt-get update && \
    apt-get install -y \
      software-properties-common \
      pkg-config \
      build-essential \
      unzip \
      wget \
      curl \
      git \
      cmake \
      ninja-build \
      python \
      flex \
      bison \
      libpng-dev \
    && wget -qO - http://packages.lunarg.com/lunarg-signing-key-pub.asc | apt-key add - \
    && wget -qO /etc/apt/sources.list.d/lunarg-vulkan-${VULKAN_SDK}-bionic.list \
       http://packages.lunarg.com/vulkan/${VULKAN_SDK}/lunarg-vulkan-${VULKAN_SDK}-bionic.list \
    && apt update && apt install -y lunarg-vulkan-sdk

# Download and build DXC
ENV DXC_BRANCH=master
ENV DXC_REPO=https://github.com/Microsoft/DirectXShaderCompiler.git
ENV DXC_COMMIT=cd237f5c3f7e8390fafff122333423afe55bc6c7

RUN git clone --recurse-submodules -b ${DXC_BRANCH} ${DXC_REPO} /app/dxc && \
    git checkout ${DXC_COMMIT} && \
    git reset --hard

RUN mkdir -p /app/dxc/build && cd /app/dxc/build && \
    cmake ../ -GNinja -DCMAKE_BUILD_TYPE=Release $(cat ../utils/cmake-predefined-config-params) && \
    ninja

# Download and build shaderc/glsang
ENV SHADERC_BRANCH=master
ENV SHADERC_REPO=https://github.com/google/shaderc.git
ENV SHADERC_COMMIT=53c776f776821bc037b31b8b3b79db2fa54b4ce7

ENV GOOGLE_TEST_BRANCH=master
ENV GOOGLE_TEST_REPO=https://github.com/google/googletest.git
ENV GOOGLE_TEST_COMMIT=c6cb7e033591528a5fe2c63157a0d8ce927740dc

ENV GLSLANG_BRANCH=master
ENV GLSLANG_REPO=https://github.com/google/glslang.git
ENV GLSLANG_COMMIT=667506a5eae80931290c2f424888cc5f52fec5d1

ENV SPV_TOOLS_BRANCH=master
ENV SPV_TOOLS_REPO=https://github.com/KhronosGroup/SPIRV-Tools.git
ENV SPV_TOOLS_COMMIT=c512c6864080ff617afb422a3d04dd902809a6cf

ENV SPV_HEADERS_BRANCH=master
ENV SPV_HEADERS_REPO=https://github.com/KhronosGroup/SPIRV-Headers.git
ENV SPV_HEADERS_COMMIT=17da9f8231f78cf519b4958c2229463a63ead9e2

ENV RE2_BRANCH=master
ENV RE2_REPO=https://github.com/google/re2.git
ENV RE2_COMMIT=fadc34500b67414df452c54d980372242f3d7f57

ENV EFFCEE_BRANCH=master
ENV EFFCEE_REPO=https://github.com/google/effcee.git
ENV EFFCEE_COMMIT=8f0a61dc95e0df18c18e0ac56d83b3fa9d2fe90b

# Download shaderc repository
WORKDIR /app/shaderc
RUN git clone --recurse-submodules -b ${SHADERC_BRANCH} ${SHADERC_REPO} /app/shaderc && \
    git checkout ${SHADERC_COMMIT} && git reset --hard

# Download shaderc dependencies
WORKDIR /app/shaderc/third_party

RUN git clone --recurse-submodules -b ${GOOGLE_TEST_BRANCH} ${GOOGLE_TEST_REPO} googletest && \
    cd googletest && git checkout ${GOOGLE_TEST_COMMIT} && git reset --hard && cd ..

RUN git clone --recurse-submodules -b ${GLSLANG_BRANCH} ${GLSLANG_REPO} glslang && \
    cd glslang && git checkout ${GLSLANG_COMMIT} && git reset --hard && cd ..

RUN git clone --recurse-submodules -b ${SPV_TOOLS_BRANCH} ${SPV_TOOLS_REPO} spirv-tools && \
    cd spirv-tools && git checkout ${SPV_TOOLS_COMMIT} && git reset --hard && cd ..

RUN git clone --recurse-submodules -b ${SPV_HEADERS_BRANCH} ${SPV_HEADERS_REPO} spirv-headers && \
    cd spirv-headers && git checkout ${SPV_HEADERS_COMMIT} && git reset --hard && cd ..

RUN git clone --recurse-submodules -b ${RE2_BRANCH} ${RE2_REPO} re2 && \
    cd re2 && git checkout ${RE2_COMMIT} && git reset --hard && cd ..

RUN git clone --recurse-submodules -b ${EFFCEE_BRANCH} ${EFFCEE_REPO} effcee && \
    cd effcee && git checkout ${EFFCEE_COMMIT} && git reset --hard && cd ..

# Build shaderc
WORKDIR /app/shaderc/build
RUN cmake -GNinja \
      -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=/usr/local \
      .. \
    && ninja install

# Download and build SMOL-V
ENV SMOLV_BRANCH=master
ENV SMOLV_REPO=https://github.com/aras-p/smol-v.git
ENV SMOLV_COMMIT=9a787d1354a9e43c9ea6027cd310ce2a2fd78901

WORKDIR /app/smol-v
RUN git clone --recurse-submodules -b ${SMOLV_BRANCH} ${SMOLV_REPO} /app/smol-v && \
    git checkout ${SMOLV_COMMIT} && git reset --hard && \
    make -f projects/Makefile -j 4

WORKDIR /app
RUN wget -O signing.zip https://github.com/gwihlidal/dxil-signing/releases/download/0.1.2/dxil-signing-0_1_2.zip
RUN unzip -q signing.zip; exit 0
RUN mv dxil-signing-0_1_2 signing && rm -f signing.zip

# Download and install wine (for running FXC, DXIL signing tool, RGA for Windows)
ENV WINE_BRANCH=dxil
ENV WINE_REPO=https://github.com/gwihlidal/wine.git
ENV WINE_COMMIT=4777a57d8a5fd2c0aa0ba06abb9148f77b9c2ddf
WORKDIR /wine
RUN git clone --recurse-submodules -b ${WINE_BRANCH} ${WINE_REPO} /wine && \
    git checkout ${WINE_COMMIT} && \
    git reset --hard
RUN ./configure --enable-win64 --with-png --without-freetype && \
    make -j8 && \
    make install
ENV WINEARCH=win64
ENV WINEDEBUG=fixme-all
RUN winecfg

# Copy FXC binaries into container
WORKDIR /app/fxc
COPY fxc_bin /app/fxc

# Download Linux and Windows binaries of AMD RGA
WORKDIR /app/rga
RUN wget -O rga_linux.tgz https://github.com/GPUOpen-Tools/RGA/releases/download/2.0.1/rga-linux-2.0.1.tgz && \
    tar zxf rga_linux.tgz && \
    mv rga-2.0.1.* linux && \
    rm rga_linux.tgz
RUN wget -O rga_windows.zip https://github.com/GPUOpen-Tools/RGA/releases/download/2.0.1/rga-windows-x64-2.0.1.zip
RUN unzip -q rga_windows.zip; exit 0
RUN mv bin windows && rm -f /app/rga/rga_windows.zip

# Convenient path variables
ENV DXC_PATH="/app/dxc/build/bin/dxc"
ENV FXC_PATH="/app/fxc/fxc.exe"
ENV SIGN_PATH="/app/signing/dxil-signing.exe"
ENV RGA_WIN_PATH="/app/rga/windows/rga.exe"
ENV RGA_NIX_PATH="/app/rga/linux/rga"
ENV GLSLC_PATH="/app/shaderc/build/glslc/glslc"
ENV SMOLV_PATH="/app/smol-v/smolv"

WORKDIR /app
ENTRYPOINT ["/bin/bash"]

The image successfully builds and works as expected, but listing the image shows it is a hefty 5.13GB!

$ docker images
REPOSITORY                   TAG                 IMAGE ID            SIZE
gwihlidal/docker-shader     unoptimized          10e54d5e5be1        5.13GB

But what is attributing to the size? You can use the docker history command to list all layers in a Docker image, which includes all the intermediate layers from the build process.

$ docker history gwihlidal/docker-shader:unoptimized
IMAGE            CREATED BY                                        SIZE
0a08bd5f62ac     /bin/sh -c #(nop)  ENTRYPOINT ["/bin/bash"]       0B
3322f44b8605     2 hours agoo     /bin/sh -c #(nop) WORKDIR /app   0B
bd89cff6176d     /bin/sh -c #(nop)  ENV SMOLV_PATH=/app/smol-…     0B
29182233f7b4     /bin/sh -c #(nop)  ENV GLSLC_PATH=/app/shade…     0B
ec23246a56ac     /bin/sh -c #(nop)  ENV RGA_NIX_PATH=/app/rga…     0B
5fffc9199d59     /bin/sh -c #(nop)  ENV RGA_WIN_PATH=/app/rga…     0B
9e310abca099     /bin/sh -c #(nop)  ENV SIGN_PATH=/app/signin…     0B
b32801e5b3fb     /bin/sh -c #(nop)  ENV FXC_PATH=/app/fxc/fxc…     0B
f98f96f64aca     /bin/sh -c #(nop)  ENV DXC_PATH=/app/dxc/bui…     0B
a94e7441d45c     /bin/sh -c mv bin windows && rm -f /app/rga/…     141MB
0c7b20423129     /bin/sh -c unzip -q rga_windows.zip; exit 0       141MB
69d110b75297     /bin/sh -c wget -O rga_windows.zip https://g…     58MB
9222b7587f70     /bin/sh -c wget -O rga_linux.tgz https://git…     217MB
819763acda4b     /bin/sh -c #(nop) WORKDIR /app/rga                0B
4d8fd2fdabea     /bin/sh -c #(nop) COPY dir:de73f1abfa6840418…     4.64MB
fe50b9bab19c     /bin/sh -c #(nop) WORKDIR /app/fxc                0B
9509d060a126     /bin/sh -c winecfg                                17MB
f212229e801a     /bin/sh -c #(nop)  ENV WINEDEBUG=fixme-all        0B
98842b9a0b3e     /bin/sh -c #(nop)  ENV WINEARCH=win64             0B
c0f23b1fe3c8     /bin/sh -c ./configure --enable-win64 --with…     2.02GB
5a25b2ca97c8     /bin/sh -c git clone --recurse-submodules -b…     765MB
5fa7d84fdc04     /bin/sh -c #(nop) WORKDIR /wine                   0B
edcf7080c1f8     /bin/sh -c #(nop)  ENV WINE_COMMIT=4777a57d8…     0B
90a2792b5223     /bin/sh -c #(nop)  ENV WINE_REPO=https://git…     0B
85b67cdaa3c0     /bin/sh -c #(nop)  ENV WINE_BRANCH=dxil           0B
3ec6009ec972     /bin/sh -c mv dxil-signing-0_1_2 signing && …     13.7MB
04f6e5347337     /bin/sh -c unzip -q signing.zip; exit 0           13.7MB
cb93ad3461e3     /bin/sh -c wget -O signing.zip https://githu…     6.29MB
53dbaa57d626     /bin/sh -c #(nop) WORKDIR /app                    0B
53ea7074364c     /bin/sh -c git clone --recurse-submodules -b…     9.67MB
6a0f973ee1d1     /bin/sh -c #(nop) WORKDIR /app/smol-v             0B
2055ded059af     /bin/sh -c #(nop)  ENV SMOLV_COMMIT=9a787d13…     0B
0b302e7c271a     /bin/sh -c #(nop)  ENV SMOLV_REPO=https://gi…     0B
fc210db9dadf     /bin/sh -c #(nop)  ENV SMOLV_BRANCH=master        0B
dcbda32f8ee8     /bin/sh -c cmake -GNinja     -DCMAKE_BUILD_T…     415MB
58c76a77f3a8     /bin/sh -c #(nop) WORKDIR /app/shaderc/build      0B
4f1c8e658e81     /bin/sh -c git clone --recurse-submodules -b…     245kB
1120ceea2a0e     /bin/sh -c git clone --recurse-submodules -b…     3.68MB
7574130b55cc     /bin/sh -c git clone --recurse-submodules -b…     2.74MB
dbdc8f92523f     /bin/sh -c git clone --recurse-submodules -b…     21.2MB
68f5c1bffcea     /bin/sh -c git clone --recurse-submodules -b…     70.5MB
eca2ea2ef379     /bin/sh -c git clone --recurse-submodules -b…     11MB
a59b0f628fac     /bin/sh -c #(nop) WORKDIR /app/shaderc/third…     0B
ef053de23a57     /bin/sh -c git clone --recurse-submodules -b…     2.45MB
c5093478f95e     /bin/sh -c #(nop) WORKDIR /app/shaderc            0B
e02f79324bad     /bin/sh -c #(nop)  ENV EFFCEE_COMMIT=8f0a61d…     0B
dbeadcb25487     /bin/sh -c #(nop)  ENV EFFCEE_REPO=https://g…     0B
41ec6c8abe39     /bin/sh -c #(nop)  ENV EFFCEE_BRANCH=master       0B
836b6c4a985c     /bin/sh -c #(nop)  ENV RE2_COMMIT=fadc34500b…     0B
fe62483b6425     /bin/sh -c #(nop)  ENV RE2_REPO=https://gith…     0B
21054004573b     /bin/sh -c #(nop)  ENV RE2_BRANCH=master          0B
2083aba8217a     /bin/sh -c #(nop)  ENV SPV_HEADERS_COMMIT=17…     0B
f45a7205f3c0     /bin/sh -c #(nop)  ENV SPV_HEADERS_REPO=http…     0B
3cc10b40b93b     /bin/sh -c #(nop)  ENV SPV_HEADERS_BRANCH=ma…     0B
10362e8ae16b     /bin/sh -c #(nop)  ENV SPV_TOOLS_COMMIT=c512…     0B
60caf847f487     /bin/sh -c #(nop)  ENV SPV_TOOLS_REPO=https:…     0B
acdfa5abc624     /bin/sh -c #(nop)  ENV SPV_TOOLS_BRANCH=mast…     0B
e2aad7919b4a     /bin/sh -c #(nop)  ENV GLSLANG_COMMIT=667506…     0B
42eaf14e520b     /bin/sh -c #(nop)  ENV GLSLANG_REPO=https://…     0B
7e124fafe40e     /bin/sh -c #(nop)  ENV GLSLANG_BRANCH=master      0B
0fd16826a92a     /bin/sh -c #(nop)  ENV GOOGLE_TEST_COMMIT=c6…     0B
7ebe8cc8e884     /bin/sh -c #(nop)  ENV GOOGLE_TEST_REPO=http…     0B
66f91486db5c     /bin/sh -c #(nop)  ENV GOOGLE_TEST_BRANCH=ma…     0B
2454aefa1e92     /bin/sh -c #(nop)  ENV SHADERC_COMMIT=53c776…     0B
410bf0ecfcaa     /bin/sh -c #(nop)  ENV SHADERC_REPO=https://…     0B
0d8aa769246c     /bin/sh -c #(nop)  ENV SHADERC_BRANCH=master      0B
da5e72628bae     /bin/sh -c mkdir -p /app/dxc/build && cd /ap…     214MB
10d07ad72cc0     /bin/sh -c git clone --recurse-submodules -b…     190MB
0419b46a5774     /bin/sh -c #(nop)  ENV DXC_COMMIT=cd237f5c3f…     0B
6fcc725f1e24     /bin/sh -c #(nop)  ENV DXC_REPO=https://gith…     0B
5661a62bfaef     /bin/sh -c #(nop)  ENV DXC_BRANCH=master          0B
71ac5a1f73e1     /bin/sh -c apt-get update &&  apt-get instal…     711MB
2a91fc85bce9     /bin/sh -c #(nop)  ENV VULKAN_SDK=1.1.92          0B
6ec4d6e0623a     /bin/sh -c #(nop) WORKDIR /app/dxc                0B
93fd78260bd1     /bin/sh -c #(nop)  CMD ["/bin/bash"]              0B
<missing>        /bin/sh -c mkdir -p /run/systemd && echo 'do…     7B
<missing>        /bin/sh -c rm -rf /var/lib/apt/lists/*            0B
<missing>        /bin/sh -c set -xe   && echo '#!/bin/sh' > /…     745B
<missing>        /bin/sh -c #(nop) ADD file:39e5bc157a8be63bb…     86.2MB

Reducing Image Size

A large proportion of the total size comes from all build tools, dependencies, source, and intermediate files for building the compilers. There are some common approaches we can use to shrink the Docker image.

One trick is to the clear the package manager cache. For Ubuntu/Debian systems, this is done by adding the following lines to our apt install chain:

&& apt-get clean \
&& apt-get autoremove

In addition, a package target in Ubuntu has a set of required, recommended, and suggested packages. Installing the required packages is mandatory, since they represent dependencies, but the other two package types can be skipped, since they are not essential to the correct functioning of the package being installed. Because of this, we can add --no-install-recommends to apt install -y in order to disable the recommended and suggested packages during installation, which saves additional space.

Another trick is to reduce the total number of layers in the Docker file. Each line in the Docker file functions as a step in the build process, which takes up space. Multiple RUN commands can be merged into one.

Instead of separate RUN command lines:

RUN git clone --recurse-submodules -b ${GOOGLE_TEST_BRANCH} ${GOOGLE_TEST_REPO} googletest && \
    cd googletest && git checkout ${GOOGLE_TEST_COMMIT} && git reset --hard && cd ..

RUN git clone --recurse-submodules -b ${GLSLANG_BRANCH} ${GLSLANG_REPO} glslang && \
    cd glslang && git checkout ${GLSLANG_COMMIT} && git reset --hard && cd ..

These lines can be merged together into a single RUN command line:

RUN git clone --recurse-submodules -b ${GOOGLE_TEST_BRANCH} ${GOOGLE_TEST_REPO} googletest && \
    cd googletest && git checkout ${GOOGLE_TEST_COMMIT} && git reset --hard && cd .. && \
    git clone --recurse-submodules -b ${GLSLANG_BRANCH} ${GLSLANG_REPO} glslang && \
    cd glslang && git checkout ${GLSLANG_COMMIT} && git reset --hard && cd ..

If running on Google Cloud Platform or AWS, you can use ubuntu-minimal as a base image (~29mb), which use optimized kernels for cloud hypervisors (50% smaller, and boot 40% faster).

When compiling binaries from source which produce lots of intermediate build files, the most important trick is to split the Docker file into a multi-stage build.

Essentially we can have one image that produces the binaries, and then a clean image can copy just the relevant components from the builder image into the final image.

FROM ubuntu:bionic as builder
# ... do stuff that produces /build/my_binary

# Start from a new image
FROM ubuntu:bionic

# Copy `my_binary` from `builder` stage into final stage
WORKDIR /app
COPY --from=builder /build/my_binary /app/my_binary

You can also try linting a Docker file with FromLatest.io, which can offer additional insight for some improvements. Our optimized image looks good, though! Docker Shader Lint

Optimized Version

Here is the optimized version after applying these techniques, which includes an explicit build of the Vulkan SDK from source in order to easily update to the latest versions:

[GitHub] - [DockerHub: gwihlidal/docker-shader]

FROM ubuntu:bionic as builder

ENV DXC_BRANCH=master
ENV DXC_REPO=https://github.com/Microsoft/DirectXShaderCompiler.git
ENV DXC_COMMIT=cd237f5c3f7e8390fafff122333423afe55bc6c7

ENV SHADERC_BRANCH=master
ENV SHADERC_REPO=https://github.com/google/shaderc.git
ENV SHADERC_COMMIT=53c776f776821bc037b31b8b3b79db2fa54b4ce7

ENV GOOGLE_TEST_BRANCH=master
ENV GOOGLE_TEST_REPO=https://github.com/google/googletest.git
ENV GOOGLE_TEST_COMMIT=c6cb7e033591528a5fe2c63157a0d8ce927740dc

ENV GLSLANG_BRANCH=master
ENV GLSLANG_REPO=https://github.com/google/glslang.git
ENV GLSLANG_COMMIT=667506a5eae80931290c2f424888cc5f52fec5d1

ENV SPV_TOOLS_BRANCH=master
ENV SPV_TOOLS_REPO=https://github.com/KhronosGroup/SPIRV-Tools.git
ENV SPV_TOOLS_COMMIT=c512c6864080ff617afb422a3d04dd902809a6cf

ENV SPV_HEADERS_BRANCH=master
ENV SPV_HEADERS_REPO=https://github.com/KhronosGroup/SPIRV-Headers.git
ENV SPV_HEADERS_COMMIT=17da9f8231f78cf519b4958c2229463a63ead9e2

ENV RE2_BRANCH=master
ENV RE2_REPO=https://github.com/google/re2.git
ENV RE2_COMMIT=fadc34500b67414df452c54d980372242f3d7f57

ENV EFFCEE_BRANCH=master
ENV EFFCEE_REPO=https://github.com/google/effcee.git
ENV EFFCEE_COMMIT=8f0a61dc95e0df18c18e0ac56d83b3fa9d2fe90b

ENV WINE_BRANCH=dxil
ENV WINE_REPO=https://github.com/gwihlidal/wine.git
ENV WINE_COMMIT=4777a57d8a5fd2c0aa0ba06abb9148f77b9c2ddf

ENV SMOLV_BRANCH=master
ENV SMOLV_REPO=https://github.com/aras-p/smol-v.git
ENV SMOLV_COMMIT=9a787d1354a9e43c9ea6027cd310ce2a2fd78901

ENV VULKAN_SDK=1.1.92.1

# Prevents annoying debconf errors during builds
ARG DEBIAN_FRONTEND="noninteractive"

# Download libraries and tools
RUN apt-get update && \
    apt-get install -y \
    software-properties-common \
    build-essential \
    git \
    cmake \
    ninja-build \
    python \
    wget \
    unzip \
    flex \
    bison \
    libpng-dev \
    libwayland-dev \
    libx11-dev \
    libxrandr-dev \
  && apt autoremove -y \
    software-properties-common \
  && apt autoclean \
  && apt clean \
  && apt autoremove

# Download and build DXC
RUN git clone --recurse-submodules -b ${DXC_BRANCH} ${DXC_REPO} /dxc && cd /dxc \
    git checkout ${DXC_COMMIT} && \
    git reset --hard && \
    mkdir -p /dxc/build && cd /dxc/build && \
    cmake ../ -GNinja -DCMAKE_BUILD_TYPE=Release $(cat ../utils/cmake-predefined-config-params) && \
    ninja

# Download shaderc repository and dependencies
RUN git clone --recurse-submodules -b ${SHADERC_BRANCH} ${SHADERC_REPO} /shaderc && cd /shaderc \
    git checkout ${SHADERC_COMMIT} && git reset --hard && \
    mkdir -p /shaderc/third_party && cd /shaderc/third_party && \
    git clone --recurse-submodules -b ${GOOGLE_TEST_BRANCH} ${GOOGLE_TEST_REPO} googletest && \
    cd googletest && git checkout ${GOOGLE_TEST_COMMIT} && git reset --hard && cd .. && \
    git clone --recurse-submodules -b ${GLSLANG_BRANCH} ${GLSLANG_REPO} glslang && \
    cd glslang && git checkout ${GLSLANG_COMMIT} && git reset --hard && cd .. && \
    git clone --recurse-submodules -b ${SPV_TOOLS_BRANCH} ${SPV_TOOLS_REPO} spirv-tools && \
    cd spirv-tools && git checkout ${SPV_TOOLS_COMMIT} && git reset --hard && cd .. && \
    git clone --recurse-submodules -b ${SPV_HEADERS_BRANCH} ${SPV_HEADERS_REPO} spirv-headers && \
    cd spirv-headers && git checkout ${SPV_HEADERS_COMMIT} && git reset --hard && cd .. && \
    git clone --recurse-submodules -b ${RE2_BRANCH} ${RE2_REPO} re2 && \
    cd re2 && git checkout ${RE2_COMMIT} && git reset --hard && cd .. && \
    git clone --recurse-submodules -b ${EFFCEE_BRANCH} ${EFFCEE_REPO} effcee && \
    cd effcee && git checkout ${EFFCEE_COMMIT} && git reset --hard && cd ..

# Build shaderc
RUN mkdir -p /shaderc/build && cd /shaderc/build && \
    cmake -GNinja \
      -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=/usr/local \
      .. && \
    ninja install

# Download and build SMOL-V
WORKDIR /smol-v
RUN git clone --recurse-submodules -b ${SMOLV_BRANCH} ${SMOLV_REPO} /app/smol-v && cd /app/smol-v && \
    git checkout ${SMOLV_COMMIT} && git reset --hard && \
    make -f projects/Makefile -j 4

# Download and install Wine (for running FXC, DXIL signing tool, RGA for Windows)
WORKDIR /wine_src
RUN git clone --recurse-submodules -b ${WINE_BRANCH} ${WINE_REPO} /wine_src && \
    git checkout ${WINE_COMMIT} && \
    git reset --hard && \
    ./configure --enable-win64 --with-png --without-freetype --without-x --prefix=/wine && \
    make -j8 && \
    make install

# Download and build Vulkan SDK
WORKDIR /
RUN wget -O vulkan.tgz https://sdk.lunarg.com/sdk/download/${VULKAN_SDK}/linux/vulkansdk-linux-x86_64-${VULKAN_SDK}.tar.gz && \
    tar zxf vulkan.tgz && \
    mv ${VULKAN_SDK} vulkan && \
    rm vulkan.tgz && \
    cd /vulkan && \
    chmod +x setup-env.sh && \
    chmod +x build_tools.sh && \
    ./setup-env.sh && ./build_tools.sh

# Download and extract signing tool
WORKDIR /
RUN wget -O signing.zip https://github.com/gwihlidal/dxil-signing/releases/download/0.1.2/dxil-signing-0_1_2.zip && \
    unzip -q signing.zip; exit 0
RUN mv dxil-signing-0_1_2 signing

# Download and extract Linux and Windows binaries of AMD RGA
WORKDIR /rga

RUN wget -O rga_linux.tgz https://github.com/GPUOpen-Tools/RGA/releases/download/2.0.1/rga-linux-2.0.1.tgz && \
    tar zxf rga_linux.tgz && \
    mv rga-2.0.1.* linux && \
    rm rga_linux.tgz && \
    wget -O rga_windows.zip https://github.com/GPUOpen-Tools/RGA/releases/download/2.0.1/rga-windows-x64-2.0.1.zip && \
    unzip -q rga_windows.zip; exit 0

# Remove RGA GUI binaries
RUN mv bin windows && \
    rm -f /rga/rga_windows.zip && \
    rm -f /rga/windows/Qt* && \
    rm -f /rga/windows/RadeonGPUAnalyzerGUI.exe && \
    rm -fr /rga/windows/iconengines && \
    rm -fr /rga/windows/imageformats && \
    rm -fr /rga/windows/platforms && \
    rm -fr /rga/linux/Qt && \
    rm -fr /rga/linux/Documentation && \
    rm -f /rga/linux/RadeonGPUAnalyzerGUI-bin && \
    rm -f /rga/linux/RadeonGPUAnalyzerGUI

# Start from a new image
FROM ubuntu:bionic

# Install libpng (needed for Wine)
RUN apt update && \
    apt install --no-install-recommends -y  \
      libpng-dev \
 && apt-get clean \
 && apt-get autoremove

# Copy DXC binaries from `builder` stage into final stage
WORKDIR /app/dxc
COPY --from=builder /dxc/build/bin/dxc /app/dxc/bin/dxc
COPY --from=builder /dxc/build/lib/libdxcompiler.so.3.7 /app/dxc/lib/libdxcompiler.so.3.7
RUN ln -s /dxc/lib/libdxcompiler.so.3.7 /app/dxc/lib/libdxcompiler.so

# Copy glslc binary from `builder` stage into final stage
WORKDIR /app/shaderc
COPY --from=builder /shaderc/build/glslc/glslc /app/shaderc/glslc

# Copy SMOL-V binaries from `builder` stage into final stage
WORKDIR /app/smol-v
COPY --from=builder /app/smol-v /app/smol-v

# Copy Vulkan install binaries from `builder` stage into final stage
WORKDIR /app/vulkan
COPY --from=builder /vulkan/x86_64/bin /app/vulkan

# Copy Wine install from `builder` stage into final stage
WORKDIR /app/wine
COPY --from=builder /wine /app/wine

# Copy DXIL signing binaries from `builder` stage into final stage
WORKDIR /app/signing
COPY --from=builder /signing /app/signing

# Copy RGA binaries from `builder` stage into final stage
WORKDIR /app/rga
COPY --from=builder /rga /app/rga

# Copy local FXC binaries into container
WORKDIR /app/fxc
COPY fxc_bin /app/fxc

# Convenient path variables
ENV DXC_PATH="/app/dxc/bin/dxc"
ENV FXC_PATH="/app/fxc/fxc.exe"
ENV SIGN_PATH="/app/signing/dxil-signing.exe"
ENV RGA_WIN_PATH="/app/rga/windows/rga.exe"
ENV RGA_NIX_PATH="/app/rga/linux/rga"
ENV GLSLC_PATH="/app/shaderc/glslc"
ENV SMOLV_PATH="/app/smol-v/smolv"
ENV WINE_PATH="/app/wine/bin/wine64"
ENV VULKAN_PATH="/app/vulkan"

# Configuration of Wine
ENV WINEARCH=win64
ENV WINEDEBUG=fixme-all
RUN /app/wine/bin/winecfg

WORKDIR /app
ENTRYPOINT ["/bin/bash"]

After reworking the Docker file into a multi-stage build, collapsing down multiple layers, and cleaning up temporary files, the optimized size is much better.

$ docker images
REPOSITORY                  TAG                  IMAGE ID            SIZE
gwihlidal/docker-shader     optimized            801b435a1301        1.14GB

Again, we can list the image history to see where the majority of the size is going.

% docker history gwihlidal/docker-shader:optimized

IMAGE            CREATED BY                                        SIZE
801b435a1301     /bin/sh -c #(nop)  ENTRYPOINT ["/bin/bash"]       0B
33dfbc14f72a     /bin/sh -c #(nop) WORKDIR /app                    0B
b0b27a78e23f     /bin/sh -c /app/wine/bin/winecfg                  17MB
7088ed2a829b     /bin/sh -c #(nop)  ENV WINEDEBUG=fixme-all        0B
efbddc393129     /bin/sh -c #(nop)  ENV WINEARCH=win64             0B
d6664f56affc     /bin/sh -c #(nop)  ENV VULKAN_PATH=/app/vulk…     0B
d4f1543c99d6     /bin/sh -c #(nop)  ENV WINE_PATH=/app/wine/b…     0B
524ce0c1987f     /bin/sh -c #(nop)  ENV SMOLV_PATH=/app/smol-…     0B
b9b7524b18bd     /bin/sh -c #(nop)  ENV GLSLC_PATH=/app/shade…     0B
68df16240c76     /bin/sh -c #(nop)  ENV RGA_NIX_PATH=/app/rga…     0B
55f02a3ac8bc     /bin/sh -c #(nop)  ENV RGA_WIN_PATH=/app/rga…     0B
78455cc2174c     /bin/sh -c #(nop)  ENV SIGN_PATH=/app/signin…     0B
dd7b67e869d0     /bin/sh -c #(nop)  ENV FXC_PATH=/app/fxc/fxc…     0B
37bd8ebb3d3f     /bin/sh -c #(nop)  ENV DXC_PATH=/app/dxc/bin…     0B
08d9fee097be     /bin/sh -c #(nop) COPY dir:a11dddb9151b9bd79…     4.64MB
2c11e63f6817     /bin/sh -c #(nop) WORKDIR /app/fxc                0B
25afe82f290b     /bin/sh -c #(nop) COPY dir:0180b95284eb9b27b…     279MB
fcee61d49f65     /bin/sh -c #(nop) WORKDIR /app/rga                0B
f6cf29857fb1     /bin/sh -c #(nop) COPY dir:e9ad49937e85e3ae0…     13.7MB
6900590649b2     /bin/sh -c #(nop) WORKDIR /app/signing            0B
eb0f03625222     /bin/sh -c #(nop) COPY dir:9d900a0720f8b4751…     469MB
555236245873     /bin/sh -c #(nop) WORKDIR /app/wine               0B
c3024a092124     /bin/sh -c #(nop) COPY dir:4e3abc1afe3949db7…     171MB
acd269c9cbd7     /bin/sh -c #(nop) WORKDIR /app/vulkan             0B
3b00d576c493     /bin/sh -c #(nop) COPY dir:5421ce9f0a82643f7…     9.67MB
6aa11e8b27e2     /bin/sh -c #(nop) WORKDIR /app/smol-v             0B
8bb0bb4c9300     /bin/sh -c #(nop) COPY file:f44b94ec0a07bebe…     6.96MB
eeab5ff2535f     /bin/sh -c #(nop) WORKDIR /app/shaderc            0B
280c30a2e7f4     /bin/sh -c ln -s /dxc/lib/libdxcompiler.so.3…     29B
02c8adb3f8a2     /bin/sh -c #(nop) COPY file:02e5ccdde34db51c…     33.6MB
dd1ef5d2f1d2     /bin/sh -c #(nop) COPY file:024bda7e343f7327…     553kB
ea7cfefa9092     /bin/sh -c #(nop) WORKDIR /app/dxc                0B
151cf9b3012b     /bin/sh -c apt update &&     apt install --n…     37.6MB
e9e49a465deb     /bin/sh -c #(nop)  CMD ["bash"]                   0B
<missing>        /bin/sh -c #(nop) ADD file:dab9baf938799c515…     55.3MB

Based on this listing, the remaining size is largely inflated by Wine and extra binaries within RGA and the Vulkan SDK.

  • For Wine, this is currently a full install, but it is possible to remove everything except the specific components that are needed to run FXC, RGA for Windows, and the DXIL signing tool.
  • For RGA, only some of the extra binaries are deleted in this container, but there are likely many more files that could be deleted from the install.
  • For Vulkan, this is another case of extra binaries that need to be removed.

These improvements are left as an exercise to the reader, or a later date when I get around to it :)

Here are the final results:

$ docker images
REPOSITORY                   TAG                 IMAGE ID            SIZE
gwihlidal/docker-shader     optimized            801b435a1301        1.14GB
gwihlidal/docker-shader     unoptimized          10e54d5e5be1        5.13GB

As mentioned, further size reductions could be performed, but 1.14GB isn’t too bad considering all the packages and tooling that are included in this image.

Example Usage

The optimized Docker image has been published as gwihlidal/docker-shader to DockerHub.

A container instance can be easily launched from this image with the following command:

$ docker run --rm -it gwihlidal/docker-shader
root@b31fc74f94b8:/app#

If you want to access local file system resources without needing to copy them into the image, you can bind mount your current working directory like this:

$ docker run --rm -it -v $(pwd):/shaders -w /shaders gwihlidal/docker-shader
root@517a5031d89f:/shaders#

All files in your working directory will be available within the running container at the /shaders mount point.

A collection of environment variables are exported to simplify invoking each compiler or tool:

  • $DXC_PATH
  • $FXC_PATH
  • $SIGN_PATH
  • $RGA_WIN_PATH
  • $RGA_NIX_PATH
  • $GLSLC_PATH
  • $SMOLV_PATH
  • $WINE_PATH
  • $VULKAN_PATH

Microsoft DXC

Invoking DXC:

root@b31fc74f94b8:/app# $DXC_PATH /help

OVERVIEW: HLSL Compiler

Version:

USAGE: dxc.exe [options] <inputs>

Common Options:
  -help              Display available options
  -nologo            Suppress copyright message
  -Qunused-arguments Don't emit warning for unused driver arguments

Compilation Options:
  -all_resources_bound    Enables agressive flattening
  -auto-binding-space <value>
                          Set auto binding space - enables auto resource binding in libraries
  -Cc                     Output color coded assembly listings
  -default-linkage <value>
                          Set default linkage for non-shader functions when compiling or linking to a library target (internal, external)
  -denorm <value>         select denormal value options (any, preserve, ftz). any is the default.
  -D <value>              Define macro
  -enable-16bit-types     Enable 16bit types and disable min precision types. Available in HLSL 2018 and shader model 6.2
  -export-shaders-only    Only export shaders when compiling a library
  -exports <value>        Specify exports when compiling a library: export1[[,export1_clone,...]=internal_name][;...]
  -E <value>              Entry point name
  -Fc <file>              Output assembly code listing file
  -Fd <file>              Write debug information to the given file or directory; trail \ to auto-generate and imply Qstrip_priv
  -Fe <file>              Output warnings and errors to the given file
  -Fh <file>              Output header file containing object code
  -flegacy-macro-expansion
                          Expand the operands before performing token-pasting operation (fxc behavior)
  -flegacy-resource-reservation
                          Reserve unused explicit register assignments for compatibility with shader model 5.0 and below
  -force_rootsig_ver <profile>
                          force root signature version (rootsig_1_1 if omitted)
  -Fo <file>              Output object file
  -Gec                    Enable backward compatibility mode
  -Ges                    Enable strict mode
  -Gfa                    Avoid flow control constructs
  -Gfp                    Prefer flow control constructs
  -Gis                    Force IEEE strictness
  -HV <value>             HLSL version (2016, 2017, 2018). Default is 2018
  -H                      Show header includes and nesting depth
  -ignore-line-directives Ignore line directives
  -I <value>              Add directory to include search path
  -Lx                     Output hexadecimal literals
  -Ni                     Output instruction numbers in assembly listings
  -no-warnings            Suppress warnings
  -not_use_legacy_cbuf_load
                          Do not use legacy cbuffer load
  -No                     Output instruction byte offsets in assembly listings
  -Odump                  Print the optimizer commands.
  -Od                     Disable optimizations
  -pack_optimized         Optimize signature packing assuming identical signature provided for each connecting stage
  -pack_prefix_stable     (default) Pack signatures preserving prefix-stable property - appended elements will not disturb placement of prior elements
  -recompile              recompile from DXIL container with Debug Info or Debug Info bitcode file
  -rootsig-define <value> Read root signature from a #define
  -T <profile>            Set target profile.
        <profile>: ps_6_0, ps_6_1, ps_6_2, ps_6_3, ps_6_4,
                 vs_6_0, vs_6_1, vs_6_2, vs_6_3, vs_6_4,
                 cs_6_0, cs_6_1, cs_6_2, cs_6_3, cs_6_4,
                 gs_6_0, gs_6_1, gs_6_2, gs_6_3, gs_6_4,
                 ds_6_0, ds_6_1, ds_6_2, ds_6_3, ds_6_4,
                 hs_6_0, hs_6_1, hs_6_2, hs_6_3, hs_6_4,
                 lib_6_3, lib_6_4
  -Vd                     Disable validation
  -Vi                     Display details about the include process.
  -Vn <name>              Use <name> as variable name in header file
  -WX                     Treat warnings as errors
  -Zi                     Enable debug information
  -Zpc                    Pack matrices in column-major order
  -Zpr                    Pack matrices in row-major order
  -Zsb                    Build debug name considering only output binary
  -Zss                    Build debug name considering source information

Optimization Options:
  -O0 Optimization Level 0
  -O1 Optimization Level 1
  -O2 Optimization Level 2
  -O3 Optimization Level 3 (Default)

SPIR-V CodeGen Options:
  -fspv-debug=<value>     Specify whitelist of debug info category (file -> source -> line, tool)
  -fspv-extension=<value> Specify SPIR-V extension permitted to use
  -fspv-reflect           Emit additional SPIR-V instructions to aid reflection
  -fspv-target-env=<value>
                          Specify the target environment: vulkan1.0 (default) or vulkan1.1
  -fvk-b-shift <shift> <space>
                          Specify Vulkan binding number shift for b-type register
  -fvk-bind-register <type-number> <space> <binding> <set>
                          Specify Vulkan descriptor set and binding for a specific register
  -fvk-invert-y           Negate SV_Position.y before writing to stage output in VS/DS/GS to accommodate Vulkan's coordinate system
  -fvk-s-shift <shift> <space>
                          Specify Vulkan binding number shift for s-type register
  -fvk-t-shift <shift> <space>
                          Specify Vulkan binding number shift for t-type register
  -fvk-u-shift <shift> <space>
                          Specify Vulkan binding number shift for u-type register
  -fvk-use-dx-layout      Use DirectX memory layout for Vulkan resources
  -fvk-use-dx-position-w  Reciprocate SV_Position.w after reading from stage input in PS to accommodate the difference between Vulkan and DirectX
  -fvk-use-gl-layout      Use strict OpenGL std140/std430 memory layout for Vulkan resources
  -fvk-use-scalar-layout  Use scalar memory layout for Vulkan resources
  -Oconfig=<value>        Specify a comma-separated list of SPIRV-Tools passes to customize optimization configuration (see http://khr.io/hlsl2spirv#optimization)
  -spirv                  Generate SPIR-V code

Utility Options:
  -dumpbin              Load a binary file rather than compiling
  -extractrootsignature Extract root signature from shader bytecode (must be used with /Fo <file>)
  -getprivate <file>    Save private data from shader blob
  -P <value>            Preprocess to file (must be used alone)
  -Qstrip_debug         Strip debug information from 4_0+ shader bytecode  (must be used with /Fo <file>)
  -Qstrip_priv          Strip private data from shader bytecode  (must be used with /Fo <file>)
  -Qstrip_reflect       Strip reflection data from shader bytecode  (must be used with /Fo <file>)
  -Qstrip_rootsignature Strip root signature data from shader bytecode  (must be used with /Fo <file>)
  -setprivate <file>    Private data to add to compiled shader blob
  -setrootsignature <file>
                        Attach root signature to shader bytecode
  -verifyrootsignature <file>
                        Verify shader bytecode with root signature

Microsoft FXC

Invoking FXC:

root@b31fc74f94b8:/app# $WINE_PATH $FXC_PATH /?

Microsoft (R) Direct3D Shader Compiler 10.1
Copyright (C) 2013 Microsoft. All rights reserved.

Usage: fxc <options> <files>

   /?, /help           print this message

   /T <profile>        target profile
   /E <name>           entrypoint name
   /I <include>        additional include path
   /Vi                 display details about the include process

   /Od                 disable optimizations
   /Op                 disable preshaders
   /O{0,1,2,3}         optimization level 0..3.  1 is default
   /WX                 treat warnings as errors
   /Vd                 disable validation
   /Zi                 enable debugging information
   /Zpr                pack matrices in row-major order
   /Zpc                pack matrices in column-major order

   /Gpp                force partial precision
   /Gfa                avoid flow control constructs
   /Gfp                prefer flow control constructs
   /Gdp                disable effect performance mode
   /Ges                enable strict mode
   /Gec                enable backwards compatibility mode
   /Gis                force IEEE strictness
   /Gch                compile as a child effect for FX 4.x targets

   /Fo <file>          output object file
   /Fl <file>          output a library
   /Fc <file>          output assembly code listing file
   /Fx <file>          output assembly code and hex listing file
   /Fh <file>          output header file containing object code
   /Fe <file>          output warnings and errors to a specific file
   /Fd <file>          extract shader PDB and write to given file
   /Vn <name>          use <name> as variable name in header file
   /Cc                 output color coded assembly listings
   /Ni                 output instruction numbers in assembly listings
   /No                 output instruction byte offset in assembly listings
   /Lx                 output hexadecimal literals

   /P <file>           preprocess to file (must be used alone)

   @<file>             options response file
   /dumpbin            load a binary file rather than compiling
   /Qstrip_reflect     strip reflection data from 4_0+ shader bytecode
   /Qstrip_debug       strip debug information from 4_0+ shader bytecode
   /Qstrip_priv        strip private data from 4_0+ shader bytecode
   /Qstrip_rootsignature         strip root signature from shader bytecode

   /setrootsignature <file>      attach root signature to shader bytecode
   /extractrootsignature <file>  extract root signature from shader bytecode
   /verifyrootsignature <file>   verify shader bytecode against root signature

   /compress           compress DX10 shader bytecode from files
   /decompress         decompress bytecode from first file, output files should
                       be listed in the order they were in during compression

   /shtemplate <file>  template shader file for merging/matching resources
   /mergeUAVs          merge UAV slots of template shader and current shader
   /matchUAVs          match template shader UAV slots in current shader
   /res_may_alias      assume that UAVs/SRVs may alias for cs_5_0+
   /enable_unbounded_descriptor_tables  enables unbounded descriptor tables
   /all_resources_bound  enable aggressive flattening in SM5.1+

   /setprivate <file>  private data to add to compiled shader blob
   /getprivate <file>  save private data from shader blob
   /force_rootsig_ver <profile>  force root signature version (rootsig_1_1 if omitted)

   /D <id>=<text>      define macro
   /nologo             suppress copyright message

   <profile>: cs_4_0 cs_4_1 cs_5_0 cs_5_1 ds_5_0 ds_5_1 gs_4_0 gs_4_1 gs_5_0
      gs_5_1 hs_5_0 hs_5_1 lib_4_0 lib_4_1 lib_4_0_level_9_1
      lib_4_0_level_9_1_vs_only lib_4_0_level_9_1_ps_only lib_4_0_level_9_3
      lib_4_0_level_9_3_vs_only lib_4_0_level_9_3_ps_only lib_5_0 ps_2_0
      ps_2_a ps_2_b ps_2_sw ps_3_0 ps_3_sw ps_4_0 ps_4_0_level_9_1
      ps_4_0_level_9_3 ps_4_0_level_9_0 ps_4_1 ps_5_0 ps_5_1 rootsig_1_0
      rootsig_1_1 tx_1_0 vs_1_1 vs_2_0 vs_2_a vs_2_sw vs_3_0 vs_3_sw vs_4_0
      vs_4_0_level_9_1 vs_4_0_level_9_3 vs_4_0_level_9_0 vs_4_1 vs_5_0 vs_5_1

DXIL Signing Tool

Invoking DXIL signing:

root@b31fc74f94b8:/app# $WINE_PATH $SIGN_PATH --help
DXIL Signing Utility
Usage: Z:\app\signing\dxil-signing.exe [OPTIONS]

Options:
  -h,--help                   Print this help message and exit
  -i,--input TEXT REQUIRED    Input unsigned dxil file
  -o,--output TEXT REQUIRED   Output signed dxil file

GLSLc / Glslang

Invoking GLSLc:

root@b31fc74f94b8:/app# $GLSLC_PATH --help
glslc - Compile shaders into SPIR-V

Usage: glslc [options] file...

An input file of - represents standard input.

Options:
  -c                Only run preprocess, compile, and assemble steps.
  -Dmacro[=defn]    Add an implicit macro definition.
  -E                Outputs only the results of the preprocessing step.
                    Output defaults to standard output.
  -fauto-bind-uniforms
                    Automatically assign bindings to uniform variables that
                    don't have an explicit 'binding' layout in the shader
                    source.
  -fauto-map-locations
                    Automatically assign locations to uniform variables that
                    don't have an explicit 'location' layout in the shader
                    source.
  -fentry-point=<name>
                    Specify the entry point name for HLSL compilation, for
                    all subsequent source files.  Default is "main".
  -fhlsl_functionality1, -fhlsl-functionality1
                    Enable extension SPV_GOOGLE_hlsl_functionality1 for HLSL
                    compilation.
  -fhlsl-iomap      Use HLSL IO mappings for bindings.
  -fhlsl-offsets    Use HLSL offset rules for packing members of blocks.
                    Affects only GLSL.  HLSL rules are always used for HLSL.
  -flimit=<settings>
                    Specify resource limits. Each limit is specified by a limit
                    name followed by an integer value.  Tokens should be
                    separated by whitespace.  If the same limit is specified
                    several times, only the last setting takes effect.
  -flimit-file <file>
                    Set limits as specified in the given file.
  -fresource-set-binding [stage] <reg0> <set0> <binding0>
                        [<reg1> <set1> <binding1>...]
                    Explicitly sets the descriptor set and binding for
                    HLSL resources, by register name.  Optionally restrict
                    it to a single stage.
  -fcbuffer-binding-base [stage] <value>
                    Same as -fubo-binding-base.
  -fimage-binding-base [stage] <value>
                    Sets the lowest automatically assigned binding number for
                    images.  Optionally only set it for a single shader stage.
                    For HLSL, the resource register number is added to this
                    base.
  -fsampler-binding-base [stage] <value>
                    Sets the lowest automatically assigned binding number for
                    samplers  Optionally only set it for a single shader stage.
                    For HLSL, the resource register number is added to this
                    base.
  -fssbo-binding-base [stage] <value>
                    Sets the lowest automatically assigned binding number for
                    shader storage buffer objects (SSBO).  Optionally only set
                    it for a single shader stage.  Only affects GLSL.
  -ftexture-binding-base [stage] <value>
                    Sets the lowest automatically assigned binding number for
                    textures.  Optionally only set it for a single shader stage.
                    For HLSL, the resource register number is added to this
                    base.
  -fuav-binding-base [stage] <value>
                    For automatically assigned bindings for unordered access
                    views (UAV), the register number is added to this base to
                    determine the binding number.  Optionally only set it for
                    a single shader stage.  Only affects HLSL.
  -fubo-binding-base [stage] <value>
                    Sets the lowest automatically assigned binding number for
                    uniform buffer objects (UBO).  Optionally only set it for
                    a single shader stage.
                    For HLSL, the resource register number is added to this
                    base.
  -fshader-stage=<stage>
                    Treat subsequent input files as having stage <stage>.
                    Valid stages are vertex, vert, fragment, frag, tesscontrol,
                    tesc, tesseval, tese, geometry, geom, compute, and comp.
  -g                Generate source-level debug information.
                    Currently this option has no effect.
  --help            Display available options.
  -I <value>        Add directory to include search path.
  -mfmt=<format>    Output SPIR-V binary code using the selected format. This
                    option may be specified only when the compilation output is
                    in SPIR-V binary code form. Available options include bin, c
                    and num. By default the binary output format is bin.
  -M                Generate make dependencies. Implies -E and -w.
  -MM               An alias for -M.
  -MD               Generate make dependencies and compile.
  -MF <file>        Write dependency output to the given file.
  -MT <target>      Specify the target of the rule emitted by dependency
                    generation.
  -O                Optimize the generated SPIR-V code for better performance.
  -Os               Optimize the generated SPIR-V code for smaller size.
  -O0               Disable optimization.
  -o <file>         Write output to <file>.
                    A file name of '-' represents standard output.
  -std=<value>      Version and profile for GLSL input files. Possible values
                    are concatenations of version and profile, e.g. 310es,
                    450core, etc.  Ignored for HLSL files.
  -S                Only run preprocess and compilation steps.
  --show-limits     Display available limit names and their default values.
  --target-env=<environment>
                    Set the target client environment, and the semantics
                    of warnings and errors.  An optional suffix can specify
                    the client version.  Values are:
                        vulkan1.0       # The default
                        vulkan1.1
                        vulkan          # Same as vulkan1.0
                        opengl4.5
                        opengl          # Same as opengl4.5
  --version         Display compiler version information.
  -w                Suppresses all warning messages.
  -Werror           Treat all warnings as errors.
  -x <language>     Treat subsequent input files as having type <language>.
                    Valid languages are: glsl, hlsl.
                    For files ending in .hlsl the default is hlsl.
                    Otherwise the default is glsl.

AMD Radeon Graphics Analyzer

Invoking RGA (Linux version):

root@b31fc74f94b8:/app# $RGA_NIX_PATH -h

Radeon GPU Analyzer Version: 2.0.1305.69
Radeon GPU Analyzer is an analysis tool for OpenCL, OpenGL and Vulkan

To view help for ROCm OpenCL: -h -s rocm-cl
To view help for legacy OpenCL: -h -s cl
To view help for OpenGL: -h -s opengl
To view help for Vulkan (GLSL): -h -s vulkan
To view help for Vulkan (SPIR-V binary input): -h -s vulkan-spv
To view help for Vulkan (SPIR-V textual input): -h -s vulkan-spv-txt

Invoking RGA (Windows version):

root@b31fc74f94b8:/app# $WINE_PATH $RGA_WIN_PATH -h

Radeon GPU Analyzer Version: 2.0.1305.268
Radeon GPU Analyzer is an analysis tool for OpenCL, DirectX, OpenGL and Vulkan

To view help for ROCm OpenCL: -h -s rocm-cl
To view help for legacy OpenCL: -h -s cl
To view help for OpenGL: -h -s opengl
To view help for Vulkan (GLSL): -h -s vulkan
To view help for Vulkan (SPIR-V binary input): -h -s vulkan-spv
To view help for Vulkan (SPIR-V textual input): -h -s vulkan-spv-txt
To view help for DirectX: -h -s hlsl
To view help for AMDIL: -h -s amdil

Vulkan Tools

Invoking SPIR-V cross compiler:

root@b31fc74f94b8:/app# $VULKAN_PATH/spirv-cross -h

Usage: spirv-cross
        [--output <output path>]
        [SPIR-V file]
        [--es]
        [--no-es]
        [--version <GLSL version>]
        [--dump-resources]
        [--help]
        [--force-temporary]
        [--vulkan-semantics]
        [--flatten-ubo]
        [--fixup-clipspace]
        [--flip-vert-y]
        [--iterations iter]
        [--cpp]
        [--cpp-interface-name <name>]
        [--msl]
        [--msl-version <MMmmpp>]
        [--msl-swizzle-texture-samples]
        [--msl-ios]
        [--hlsl]
        [--reflect]
        [--shader-model]
        [--hlsl-enable-compat]
        [--separate-shader-objects]
        [--pls-in format input-name]
        [--pls-out format output-name]
        [--remap source_name target_name components]
        [--extension ext]
        [--entry name]
        [--stage <stage (vert, frag, geom, tesc, tese comp)>]
        [--remove-unused-variables]
        [--flatten-multidimensional-arrays]
        [--no-420pack-extension]
        [--remap-variable-type <variable_name> <new_variable_type>]
        [--rename-interface-variable <in|out> <location> <new_variable_name>]
        [--set-hlsl-vertex-input-semantic <location> <semantic>]
        [--rename-entry-point <old> <new> <stage>]
        [--combined-samplers-inherit-bindings]
        [--no-support-nonzero-baseinstance]

Invoking SPIR-V disassembler:

root@b31fc74f94b8:/app# $VULKAN_PATH/spirv-dis -h

/app/vulkan/spirv-dis - Disassemble a SPIR-V binary module

Usage: /app/vulkan/spirv-dis [options] [<filename>]

The SPIR-V binary is read from <filename>. If no file is specified,
or if the filename is "-", then the binary is read from standard input.

Options:

  -h, --help      Print this help.
  --version       Display disassembler version information.

  -o <filename>   Set the output filename.
                  Output goes to standard output if this option is
                  not specified, or if the filename is "-".

  --color         Force color output.  The default when printing to a terminal.
                  Overrides a previous --no-color option.
  --no-color      Don't print in color.  Overrides a previous --color option.
                  The default when output goes to something other than a
                  terminal (e.g. a file, a pipe, or a shell redirection).

  --no-indent     Don't indent instructions.

  --no-header     Don't output the header as leading comments.

  --raw-id        Show raw Id values instead of friendly names.

  --offsets       Show byte offsets for each instruction.

Compile as a Service

In addition to running the image directly (which is great for tests or quick iteration), it wouldn’t be ideal to individually start and stop a container for each shader instance that needs compilation, if there are a large number of shaders. A more effective design is to have a service running within a container that accepts compilation requests, invokes the necessary executables, and responsed to the caller with the appropriate data.

This post won’t go into details behind the shader build service, but here is an example Docker file that inherits from the gwihlidal/docker-shader image, installs the Rust language, compiles a release build of the service (including a trick for fast iteration), and specifies the service binary as the default entry point.

FROM gwihlidal/docker-shader

# Prevents annoying debconf errors during builds
ARG DEBIAN_FRONTEND="noninteractive"

RUN apt update \
 && apt install --no-install-recommends -y \
    build-essential \
    pkg-config \
    curl \
 && apt autoremove -y \
 && apt autoclean \
 && apt clean \
 && apt autoremove

# Install Rust
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

WORKDIR /app

# Avoid having to install/build all dependencies when iterating
# by copying the Cargo files and making dummy main source files
COPY Cargo.* ./
RUN mkdir -p src && echo "fn main() {}" > src/main.rs && \
    echo "fn main() {}" > src/main.rs && \
    cargo build --release

# We need to touch our real source files or
# else Docker will use the cached ones.
COPY src src

RUN touch src/main.rs && \
    cargo build --release --color never

ENTRYPOINT ["./target/release/main"]

With a containerized service, we can now realize the goal of scaling out the shader compilation as a load balancing service in Kubernetes!

Here is an example (basic) Kubernetes manifest for the service replicas and load balancer (GKE):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: shader-build
  labels:
    name: "shader-build"
    keel.sh/policy: force
    keel.sh/trigger: poll
  annotations:
    keel.sh/pollSchedule: "@every 10m"
spec:
  selector:
    matchLabels:
      app: shader-build
  replicas: 4
  template:
    metadata:
      labels:
        app: shader-build
    spec:
      containers:
      - name: shader-build
        image: gwihlidal/shader-build-test
        resources:
          requests:
            memory: 4Gi
            cpu: 4
        ports:
        - containerPort: 63999
---
apiVersion: v1
kind: Service
metadata:
  name: shader-build
  labels:
    app: shader-build
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 63999
    protocol: TCP
    name: http2
  selector:
    app: shader-build

It works!

Shader Build in GKE

Shader Build in GKE


© 2024. All rights reserved.