Windows コンテナで、 GPU ベンダー固有のグラフィックAPIを使うという記事を見かけました。手元では、ちょっとうまくいかない点があったので修正してみました。オフラインレンダリングの記事は Unreal Engine のものですが、ヘッドレスモードで描画をストリーミングできる機構が用意できれば、他のアプリでも活用できるのではと思います。
参考文献
始めにきっかけとなった参考文献を紹介します。
- https://unrealcontainers.com/blog/offscreen-rendering-in-windows-containers/
- https://unrealcontainers.com/blog/enabling-vendor-specific-graphics-apis-in-windows-containers/
これらを読んで、 Windows コンテナでもベンダー固有機能を使って実行ができる!とワクワクしたのが全ての始まりです。
Dockerfile
紹介されていた Dockerfile を読むと Windows クライアントOSから必要な DLL をかき集めて、 Windows Server のコンテナにコピーするという手法をとっていました。これはおそらくコンテナサイズを小さく保つための工夫か、Windows Server 上の Windows コンテナとして動かすために必要なことだろうと考えています。
これに倣って、Dockerfile を構築したものが以下の通りです。元記事に紹介されているものでは、DirectX の スワップチェインの生成で失敗してしまったので修正を加えています。
# escape=`
ARG BASETAG
FROM mcr.microsoft.com/windows:${BASETAG} AS full
# Gather the system DLLs that we need from the full Windows base image
RUN xcopy /y C:\Windows\System32\avicap32.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\avrt.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\d3d10warp.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\D3DSCache.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\dsound.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\dxva2.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\glu32.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\mf.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\mfplat.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\mfplay.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\mfreadwrite.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\msdmo.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\msvfw32.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\opengl32.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\ResourcePolicyClient.dll C:\GatheredDlls\ && `
xcopy /y C:\Windows\System32\dcomp.dll C:\GatheredDlls\
# Retrieve the DirectX runtime files required by the Unreal Engine,
# since even the full Windows base image does not include them
RUN curl --progress -L "https://download.microsoft.com/download/8/4/A/84A35BF1-DAFE-4AE8-82AF-AD2AE20B6B14/directx_Jun2010_redist.exe" --output %TEMP%\directx_redist.exe && `
start /wait %TEMP%\directx_redist.exe /Q /T:%TEMP%\DirectX && `
expand %TEMP%\DirectX\APR2007_xinput_x64.cab -F:xinput1_3.dll C:\GatheredDlls\ && `
expand %TEMP%\DirectX\Jun2010_D3DCompiler_43_x64.cab -F:D3DCompiler_43.dll C:\GatheredDlls\ && `
expand %TEMP%\DirectX\Feb2010_X3DAudio_x64.cab -F:X3DAudio1_7.dll C:\GatheredDlls\ && `
expand %TEMP%\DirectX\Jun2010_XAudio_x64.cab -F:XAPOFX1_5.dll C:\GatheredDlls\ && `
expand %TEMP%\DirectX\Jun2010_XAudio_x64.cab -F:XAudio2_7.dll C:\GatheredDlls\
# Retrieve the DirectX shader compiler files needed for DirectX Raytracing (DXR)
RUN curl --progress -L "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.6.2104/dxc_2021_04-20.zip" --output %TEMP%\dxc.zip && `
powershell -Command "Expand-Archive -Path \"$env:TEMP\dxc.zip\" -DestinationPath $env:TEMP" && `
xcopy /y %TEMP%\bin\x64\dxcompiler.dll C:\GatheredDlls\ && `
xcopy /y %TEMP%\bin\x64\dxil.dll C:\GatheredDlls\
RUN curl --progress -L "https://sdk.lunarg.com/sdk/download/1.2.189.2/windows/VulkanRT-1.2.189.2-Components.zip" --output %TEMP%\VulkanRT.zip && `
powershell -Command "Expand-Archive -Path \"$env:TEMP\VulkanRT.zip\" -DestinationPath $env:TEMP" && `
xcopy /y %TEMP%\VulkanRT-1.2.189.2-Components\x64\vulkan-1.dll C:\GatheredDlls\ && `
xcopy /y %TEMP%\VulkanRT-1.2.189.2-Components\x64\vulkaninfo.exe C:\GatheredDlls\
# Copy the required DLLs from the full Windows base image into a smaller Windows Server Core base image
ARG BASETAG
FROM mcr.microsoft.com/windows/servercore:${BASETAG}
COPY --from=full C:\GatheredDlls\ C:\Windows\System32\
# Install the Visual C++ runtime files using Chocolatey
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))"
RUN choco install -y vcredist-all
# Copy our Unreal Engine application into the container image here
# (This assumes we have a packaged project called "MyProject" with a `-Cmd.exe` suffixed
# version as per the blog post "Offscreen rendering in Windows containers")
# COPY WindowsNoEditor C:\WindowsNoEditor
COPY entrypoint.cmd C:\entrypoint.cmd
COPY enable-graphics-apis.ps1 c:\enable-graphics-apis.ps1
# Set our Unreal Engine application as the container's entrypoint
# ENTRYPOINT ["cmd.exe", "/S", "/C", "C:\\WindowsNoEditor\\MyProject\\Binaries\\Win64\\MyProject-Cmd.exe", "-stdout", "-FullStdOutLogOutput", "-RenderOffscreen", "-unattended", "-ResX=1024", "-ResY=768"]
ENTRYPOINT ["cmd.exe"]
このコードには自分の現在の試行錯誤部分も含まれていますが、そのまま公開します。色々な実験のために、コンテナでシェルを起動する状態に変更しています。
ここで使用している enable-graphics-apis.ps1 ファイルですが、 NVIDIA 環境ということもあり以下のファイル内容としています。
function CopyToSystem32($sourceDirectory, $filenames, $rename)
{
foreach ($filename in $filenames)
{
# Determine whether we are renaming the file when we copy it to the destination
$source = "$sourceDirectory\$filename"
$destination = "C:\Windows\System32\$filename"
if ($rename -and $rename[$filename])
{
$renamed = $rename[$filename]
$destination = "C:\Windows\System32\$renamed"
}
# Perform the copy
Write-Host " Copying $source to $destination"
try {
Copy-Item -Path "$source" -Destination "$destination" -ErrorAction Stop
}
catch {
Write-Host " Warning: failed to copy file $filename" -ForegroundColor Yellow
}
}
}
# Attempt to locate the NVIDIA Display Driver directory in the host system's driver store
$nvidiaSentinelFile = (Get-ChildItem "C:\Windows\System32\HostDriverStore\FileRepository\nv*.inf_amd64_*\nvapi64.dll" -ErrorAction SilentlyContinue)
if ($nvidiaSentinelFile) {
# Retrieve the path to the directory containing the DLL files for NVIDIA graphics APIs
$nvidiaDirectory = $nvidiaSentinelFile[0].VersionInfo.FileName | Split-Path
Write-Host "Found NVIDIA Display Driver directory: $nvidiaDirectory"
# Copy the DLL file for NVAPI to System32
Write-Host "`nEnabling NVIDIA NVAPI support:"
CopyToSystem32 `
-SourceDirectory $nvidiaDirectory `
-Filenames @("nvapi64.dll")
# Copy the DLL files for NVENC to System32
Write-Host "`nEnabling NVIDIA NVENC support:"
CopyToSystem32 `
-SourceDirectory $nvidiaDirectory `
-Filenames @("nvEncodeAPI64.dll", "nvEncMFTH264x.dll", "nvEncMFThevcx.dll")
# Copy the DLL files for NVDEC (formerly known as CUVID) to System32
Write-Host "`nEnabling NVIDIA CUVID/NVDEC support:"
CopyToSystem32 `
-SourceDirectory $nvidiaDirectory `
-Filenames @("nvcuvid64.dll", "nvDecMFTMjpeg.dll", "nvDecMFTMjpegx.dll") `
-Rename @{"nvcuvid64.dll" = "nvcuvid.dll"}
# Copy the DLL files for CUDA to System32
Write-Host "`nEnabling NVIDIA CUDA support:"
CopyToSystem32 `
-SourceDirectory $nvidiaDirectory `
-Filenames @("nvcuda64.dll", "nvcuda_loader64.dll", "nvptxJitCompiler64.dll") `
-Rename @{"nvcuda_loader64.dll" = "nvcuda.dll"}
# Copy the DLL files for OpenGL/Vulkan to System32
Write-Host "`nEnabling NVIDIA Vulkan support:"
CopyToSystem32 `
-SourceDirectory $nvidiaDirectory `
-Filenames @("nvoglv64.dll". "nvrtum64.dll") `
# Print a blank line before any subsequent output
Write-Host ""
}
現状
ここまで作ったこの環境では、まだ簡単な DirectX のアプリしか確認していません。まだ CUDA のアプリや、そもそも UnrealEngine のプロジェクトなどは未検証です。一方で、 Vulkan の情報取得では失敗するようで、 vulkaninfo による情報列挙は叶いませんでした。ここから同様に OpenGL のアプリも期待薄かと思っています。
OpenCL のアプリでは、 OpenCL.dll をコピーしてみても clGetPlatformIDs 関数でエラー (対応デバイス無し) という状態でした。
しかし、 DirectX + ベンダー固有機能ならば、動いた事例があるというのは楽しみなことです。