Engineering report  /  11–18 August 2026  /  single workstation, single card

Crimson Desert
on a card that
cannot run it

RX 580 2048SPPolaris 20 · GCN4 · 8 GB
12_0highest feature level the card reports
12_1feature level the game demands
Playableday and night, textured, 39 draws/frame
A black frame with a faint grey wedge: the Compiling Shaders screen, where the game hung indefinitely. Crimson Desert rendering in daylight on the RX 580: terrain, foliage, buildings and character all present and textured. Before · frame 0–∞ After · in game

Left: six days of this. 82 fps, a black frame, and a shader-compile worker that had already died. Right: the same binary after the twelfth root cause. Drag the seam, or use the arrow keys.

ABSTRACT

What this is

Crimson Desert refuses to start on a Radeon RX 580. It queries Direct3D 12 feature level 12_1 and native 16-bit float support; Polaris advertises neither. This is the record of closing that gap — a translation shim, nine patched files in vkd3d-proton, and twelve distinct driver-level faults, each one identified by a measurement rather than a guess.

The card is an RX 580 2048SP: Polaris 20, GCN generation 4, 8 GB of VRAM, in a box with 8 GB of system RAM. It is a 2018 mid-range part. The game is a 2026 open-world title built against a feature level the silicon does not implement.

Nothing here overclocks, flashes firmware, or replaces AMD's driver. The work sits in one layer: a d3d12.dll that the game loads instead of Microsoft's, which answers capability queries honestly enough to get past the gate and then repairs the shader bytecode and command stream on the way down to Vulkan. Everything below it — amdvlk64, the kernel driver, the card — is stock.

The interesting part is not the fixes. It is the ratio. Twelve faults were confirmed. Forty-eight hypotheses were refuted, several of them extremely plausible and held for hours. Forty-four times a measurement lied and had to be re-taken. That ratio is the actual finding, and §4 is where it lives.

Honest status

The game reaches gameplay, renders correctly in daylight and at night, and shows no missing or substituted textures. The title-screen background is still black — diagnosed, not fixed, and §7 explains exactly why it was left alone. Commit headroom is thin on 8 GB. This is a working build, not a finished product.

§ 1

The gap

Four hard requirements, four things the card does not have. Each one had to be answered somewhere in the stack.

The game asks forPolaris / amdvlk64 actually reportsWhere it was answered
D3D12 feature level 12_112_0 — no conservative raster tier 3, no ROVsShim forges the CheckFeatureSupport reply
Native fp16 in shadersshaderFloat16 = false on this driverShim forges the capability; fp16 arithmetic is demoted in the SPIR-V
[WaveSize(32)] compute shaderssubgroup size fixed at 64 (GCN wave64)Shim forges minSubgroupSize to 16 so validation admits 32
Device-generated commands (DGC)extension absentRoot arguments unrolled into a compute pass — see fault 07

A forged capability is a promise. Every one of the four above created a second problem downstream — the thing that was promised then has to actually work. Faults 05, 06, 07 and 08 are all bills that came due for lines in this table.

§ 2

Where the work sits

The signal path from the game's D3D12 call to a lit pixel, and the eight points on it that were changed.

Signal path from CrimsonDesert.exe to Polaris silicon A vertical stack of six layers. CrimsonDesert.exe calls into a replacement d3d12.dll shim, which chains to a patched vkd3d-proton, which uses dxil-spirv to translate shader bytecode, then hands Vulkan work to the stock amdvlk64 driver and the Polaris card. Eight numbered taps mark the modified points: three in the shim, three in vkd3d-proton, one in the SPIR-V post-pass, and one launch-time cache guard. CrimsonDesert.exe Pearl Abyss · GameVersion 1.14.00 · D3D12 renderer LoadLibrary("d3d12.dll") d3d12.dll — the shim (dx12bridge) 11,041 lines of C++ across 38 files · 997-line .ini · no game files touched 1 Capability forgeFL 12_1 · fp16 · subgroup 16..64 2 vkCreateShaderModule hookSPIR-V repaired below vkd3d's cache 3 dxgi.dll shimswapchain E_NOTIMPL RealD3D12 = vkd3d\d3d12.dll ← the chain is declared in one ini line, not hard-coded forwarded D3D12 API vkd3d-proton 3.0.1 — patched, 9 files, +869 / −13 LGPL-2.1-or-later · modified source ships with this package 4 ExecuteIndirect fallbackcommand.c +557 — no DGC needed 5 Root-constant unrollmeta.c +51 · push-UBO mode 6 PSO / cache pathsstate.c +131 · cache.c +29 DXIL VkPipeline / VkCmd* dxil-spirv pinned 62dbb07f · MIT · runtime-identical to upstream 7 SPIR-V post-pass (in the shim, tap 2) helper-invocation collapse · strip dead VRS cap · fp16 demote amdvlk64 — stock unmodified AMD Vulkan driver 8 DxcCache guardlaunch-time, outside the process Polaris 20 silicon 36 CUs · wave64 · 8 GB GDDR5 · no VRS, no DGC, no native fp16
1Answer the gateFeature level, fp16, subgroup range. Every forged answer creates a downstream bill.
2Repair SPIR-V under the cacheHooking vkCreateShaderModule instead of using VKD3D_SHADER_OVERRIDE keeps pipeline caching alive. That distinction is what made a swapchain reachable at all.
3Satisfy the swapchainA separate dxgi.dll answering what the real one returned E_NOTIMPL for.
4Draw indirect without DGCvkd3d returned early when a command signature carried root arguments. 5,281 of 5,284 signatures did.
5Per-command root constantsForce push-UBO mode, then unroll the argument buffer in a compute pass.
6Keep PSOs alivePipeline-state and cache paths, plus the flag accounting that made success counts mean something.
7Make the module legalThree transforms, all in the shim's hook: collapse helper-invocation loads, strip a dead VRS capability, demote fp16.
8Keep commit under the limitThe one intervention deliberately kept outside the process, where it cannot break a working binary.
§ 3

Twelve faults

Each one is stated with the measurement that closed it. The number in the margin is the delta on a real launch of the real game — not a synthetic test.

FAULT 01 · descriptors graphics pipeline failures → 0

Polaris cannot compile a graphics pipeline that uses a mutable descriptor type

vkd3d-proton reaches for VK_EXT_mutable_descriptor_type because it collapses descriptor-heap emulation into one binding. amdvlk64 advertises it. On Polaris the compiler then fails every graphics pipeline that touches it, and D3D12 surfaces that as an opaque VK_ERROR_UNKNOWN.

Disabling both the EXT and VALVE forms took graphics-pipeline creation failures to zero in the live game. This was the first fault found and the one that made every later measurement possible — until it was gone, every log was dominated by it.

Where: device extension filter in the shim.

FAULT 02 · shader compiler 11 of 11 crashers fixed · 0 regressions across 402 modules

A gl_HelperInvocation load storm faults the driver's use-list walk

Some fragment shaders load the HelperInvocation builtin more than a hundred times. Polaris' shader compiler walks the use-list of that value and either takes an access violation that gets swallowed, or never returns at all — the same defect wearing two faces, which is why it read as two unrelated bugs for a day.

The fix is one line of intent: load the builtin once, reuse the result. Collapsing the loads to a single value fixed all eleven known crashers with zero regressions measured over 402 modules.

Getting there required abandoning hypothesis-driven debugging entirely. Every hand-picked theory about those shaders was refuted; the shader's declarations were innocent and its code was guilty. Delta-debugging the module found it in minutes after hours of reasoning had not.

Where: SPIR-V post-pass, vkCreateShaderModule hook.

FAULT 03 · shader compiler second encoding · opcode 5381

The same builtin has a second form, and rewriting it grows the module

OpIsHelperInvocationEXT (opcode 5381) is the other way that value reaches the compiler. Handling it is not symmetric with fault 02: the rewrite needs spare space in the binary, the SPIR-V header length written back afterwards, and an anchor value that resets whenever a demote happens or a call that can reach a demote is made.

A CFG-aware version of that anchor collapse was designed, measured, and deliberately not written: it gained between 0% and 68% depending on the shader, the worst-case shader stayed above the fault threshold anyway, and the demote-dominance const-true rule gained exactly nothing. Measuring a refactor before writing it is cheaper than writing it.

Where: SPIR-V post-pass.

FAULT 04 · pipeline creation refusals 9 → 0 · VkResult −3: 51 → 0 · E_OUTOFMEMORY 16 → 0

A dead FragmentShadingRateKHR declaration was refusing pipelines

Shaders declared variable-rate-shading capability on a card that has no VRS. The capability was never used — the declaration alone was enough for the driver to refuse the pipeline with VkResult = -3, which vkd3d then relabelled as E_OUTOFMEMORY at state.c:5728. That mistranslation sent the investigation after memory pressure for hours. The box did have only 0.3 GB free — true, and irrelevant.

Stripping the unused declaration took all three counters to zero on the same launch.

Four hypotheses died here first: the pipeline layout was not the discriminator (the failing layout was the same object graph as a passing one, and swapping them changed nothing). It was not helper-invocation related (a module collapsed to fanout 27 and accepted by vkCreateShaderModule was still refused, and three of the modules were helper-op-free vertex shaders). It survived dropping the fragment stage entirely. And it was not memory — peak commit 5.6 GB against a 16.4 GB limit.

Where: SPIR-V post-pass.

FAULT 05 · subgroups validation admits WaveSize(32)

[WaveSize(32)] on a wave64 card

Shaders annotated [WaveSize(32)] are rejected outright: GCN4 is wave64 and the driver reports a fixed subgroup size of 64. Vulkan validation only requires min ≤ 32 ≤ max, so forging minSubgroupSize down to 16 satisfies it without lying about anything the shader can observe.

The trap in this fix

vkd3d-proton branches on the executable name CrimsonDesert.exe to apply an RDNA1 compatibility profile — and that branch is gated on minSubgroupSize == 32. So 32 is a uniquely dangerous value to forge here: it silently activates a profile written for different hardware. 16 is correct precisely because it is not 32.

Where: capability forge. Each mode change costs one cold-cache launch to evaluate.

FAULT 06 · fp16 337 fragment modules made invalid

The fp16 graphics demote emitted invalid SPIR-V

Having forged native fp16 support, the fp16 arithmetic then has to go somewhere. The graphics demote path rewrote %v2half to %v2float inside a bitcast, producing an OpBitcast between mismatched widths. 337 fragment modules became invalid SPIR-V. Compute shaders kept fp16 untouched and had zero failures — the split was perfectly clean along the Fragment boundary, which is what identified it.

Worth recording that fp16 was also ruled out as the cause of an earlier failure, twice: fp16 arithmetic and fp16 attachment formats were both validly eliminated. The fp16-versus-int16 distinction mattered, and this build compiles out WARN and TRACE, which is why that took as long as it did.

Where: SPIR-V post-pass. Currently disabled — DXIL_SPIRV_GRAPHICS_DISABLE_NATIVE_FP16 = 0.

FAULT 07 · command stream indirect draws 14 → 255 per frame

Missing DGC silently discarded most indirect draws

This is the fault that was hiding the world. vkd3d's ExecuteIndirect path returns early when a command signature carries root arguments and device-generated commands are unavailable. It returns without drawing and without complaining. Of 5,284 command signatures the engine created, 5,281 carried root arguments.

Falling through to a manual path at command.c:17159 took indirect draws from 14 to 255 per frame. This engine is GPU-driven: its real batches carry roughly 53 draws each, and an earlier reading of maxDrawCount = 1 was survivorship bias — the batches that reached the log were the trivial ones.

Where: vkd3d-proton libs/vkd3d/command.c, +557 / −4.

FAULT 08 · command stream 450,000 calls handled · 0 rejected · 0 refusals

Per-command root constants, emulated without DGC

Fault 07's fallback then needs each unrolled command to see its own root constants. Two changes: force push-UBO mode, and unroll the argument buffer in a compute pass. 450,000 calls were handled with none rejected and no pipeline refusals introduced.

The PSO cache cannot detect this. Changing the root-argument interface changes shader behaviour without changing anything the pipeline cache hashes. A stale cache will serve pipelines built for the old interface. The cache has to be moved aside by hand when this lands — a silent-wrong-answer failure mode, not a crash.

Where: vkd3d-proton meta.c +51, vkd3d_private.h +56.

FAULT 09 · host memory peak commit 30,916 → 8,768 MiB

The endless "Compiling Shaders" screen was a 4 GB AMD DxcCache

The single most user-visible fault, and it was not in any code. AMD's shader cache at %LOCALAPPDATA%\AMD\DxcCache had grown to 4.0 GB. The game then committed 30,916 MB against a 32,318 MB Windows commit limit — 805 MB of headroom. The compile worker died silently and the game sat on its compile screen forever, at 82 fps, showing a black frame.

The tell was equal working set with 2.7× the commit. That signature is memory that is reserved but never touched, which points at a mapped file, not at leaked allocations. Emptying the cache dropped peak commit to 8,768 MiB and the game reached gameplay.

Three other explanations for the same screen were measured and refuted first: the DGC unroll bound, bad shaders, and shader-compile concurrency.

Where: outside the process entirely. Now guarded at launch by Play Crimson Desert (RX 580).cmd, which never touches DxCache — no second 'c' — because the Radeon service holds that one open.

FAULT 10 · process refusals 41 → 0 on revert

A shim build I had made myself was the regression

A compile-screen failure that looked like a game bug was a binary I had built thirteen minutes earlier. Restoring the previous build took refusals from 41 to zero and reached the menu. The cause was building by hand instead of with build_shim.py — the script strips the binary and a hand-rolled g++ line does not. The one time that was done, launch success went from 12/12 to 2/12.

The lasting change is procedural, and it is the most valuable single line in this report:

Hash every file you wrote against its backup before calling a revert complete. A revert that checked three files out of four cost a working build and an evening. Checking three of four is not a revert; it is a coin toss with extra steps.

Where: build procedure. backup-previous\d3d12.dll is the instant escape hatch.

FAULT 11 · capability reporting gap closed

shaderFloat16 reporting, and the denorm modes underneath it

Forging fp16 support is not one boolean. The reported capability, the DenormPreserve / DenormFlushToZero execution modes, and what the shader actually does with a half have to agree, or validation rejects the module while the capability query says everything is fine. Three distinct modes were implemented and each has a different failure surface.

Where: capability forge; vkd3d_shaders.h, include/vkd3d_shader.h.

FAULT 12 · presentation swapchain reached

The DXGI swapchain returned E_NOTIMPL

All the shader work in the world is invisible without a surface to present to. A companion dxgi.dll (1,272 lines) answers the swapchain creation path that was returning E_NOTIMPL. Combined with fault 02's change of substitution strategy — hooking below vkd3d's cache rather than through its override, which forces pipeline_library_ignore_spirv and kills caching — this produced the first swapchain the project ever reached.

Where: src/dxgishim, 6 files.

What it looks like now

Daylight gameplay: terrain, foliage, buildings, character, all textured and lit.
Daylight. Terrain, foliage, buildings and character all present. 0% magenta and 0% flat-grey across four sampled frames — no missing or untextured surfaces.
Night gameplay with local light sources and shadowing.
Night. Local lights and shadowing intact. Zero texture formats rejected or substituted in either log.
A loading transition screen rendering correctly.
Loading transition — reached, rendered, and passed through, which for most of this project's life it was not.
An almost entirely black frame, average luminance near zero.
The state every archived run began in: black from frame 0, average luminance 0.0–0.1.
§ 4

The hypothesis ledger

Twelve confirmed against forty-eight refuted. The refuted column is the more useful one, and it is the part that normally never gets written down. Every entry carries the measurement that decided it — a hypothesis without a deciding measurement is not on this page at all.

Confirmed — 12Refuted — 48 total, 12 shown
01Mutable descriptor type breaks graphics pipelinesDisabled EXT + VALVE forms → failures 0
02gl_HelperInvocation load stormCollapse to one load → 11/11 fixed, 0 regressions / 402 modules
03OpIsHelperInvocationEXT is a second formOpcode 5381 needs slack + length write-back + anchor reset
04Dead FragmentShadingRateKHR declarationStrip unused cap → −3: 51→0, E_OUTOFMEMORY 16→0
05WaveSize(32) needs a forged subgroup minimumminSubgroupSize 16 satisfies min ≤ 32 ≤ max
06fp16 graphics demote emits invalid OpBitcast337 modules, all Fragment; compute kept fp16, 0 failures
07Missing DGC silently drops indirect draws5,281 / 5,284 signatures carry root args → 14 to 255 draws
08Root constants can be unrolled in compute450k calls, 0 rejected, 0 refusals
09A 4 GB DxcCache is a commit-limit wallPeak commit 30,916 → 8,768 MiB
10The 13:41 shim build was the regressionRestore 13:09 build → refusals 41→0, menu reached
11shaderFloat16 needs the denorm modes to agreeThree modes, three distinct failure surfaces
12The swapchain path returned E_NOTIMPLdxgi.dll shim → first swapchain ever reached
×fp16 is the blockerArithmetic and attachment formats both validly eliminated
×The pipeline layout is the refusal discriminatorFailing layout = same object graph as a passing one; swap changed nothing
×The refusals are a helper-invocation problemFanout-27 module accepted by vkCreateShaderModule still refused; 3 offenders were helper-op-free vertex shaders
×Memory pressure is the blockerPeak commit 5.6 GB against a 16.4 GB limit
×E_OUTOFMEMORY means out of memoryhr=0x8007000E is state.c:5728 relabelling a driver refusal
×Failed PSOs are the missing geometryRefusals 5→0 left draw counts bit-identical across 54 windows
×Depth-only PS stripping is a visibility bugNone of them write UAVs or SV_Depth; size was a bad proxy
×The failure is one bad pixel shaderFive offenders in one launch, wearing two different faces
×A CFG-aware anchor collapse is worth writingMeasured 0–68% gain; worst shader stays above the fault floor
×Stubbing the offending shaders is the fix11 stubs met 10 new offenders. Fix the class, not the instance
×The black title screen is a video that fails to decodeZero movie assets, zero video DLLs, zero D3D12 Video interfaces requested
×VKD3D_SHADER_CACHE_PSO_BLOB is the answerLanded, then reverted 13 minutes later: crashes amdvlk64, cut playable time 8×
36 further refutations are recorded in the project notes. The pattern that repeats: a hypothesis survives because the measurement that would kill it was never taken.

The measurement failed 44 times

Refuting a hypothesis requires a measurement you can trust, and on this project measurements lied constantly and quietly. Forty-four distinct instances were recorded. Five representative ones:

  • Pipeline success counts are not evidence. A vkd3d flag makes it ask for pipelines it never compiles. 2,118 of 2,125 apparent "successes" compiled nothing at all. The number that means something is the count of a specific flag value, not the total.
  • Anything that loads the shim truncates both logs. Config load opens them for truncate. One diagnostic ate the very launch it was diagnosing. Archive before every run.
  • A probe that reports n/a is not a negative result. One probe reported n/a for twelve consecutive runs, so the condition it existed to test was never actually tested once.
  • A swap probe is only evidence if the substitute is interface-compatible. Otherwise "swapping them changed nothing" means the swap did not happen.
  • The shell lies about paths. Git Bash eats /F as a path, awk -F: splits on the drive letter, tar reads D: as a remote hostname. An early marker count returned 0 for everything because of a colon.

A patch that used raw getenv in the wrong place turned an entire capture session into a no-op. The user reported "can't see much change than before" — a correct observation of a measurement that had silently stopped measuring. Believing the person at the keyboard over your own instrumentation was, repeatedly, the right call.

§ 5

What it cost

Line counts are exact — counted from the tree that produced the shipped binaries. The experience figure is an estimate, and labelled as one.

25,304lines authored, total
112source files
13upstream files patched
7days elapsed
11–18 Aug 2026
ComponentLinesFilesWhat it is
src/shim9,76932The d3d12.dll the game loads: capability forge, Vulkan hooks, SPIR-V post-pass
tools11,70661Build, launch-capture, log analysis, SPIR-V reduction, frame sampling
src/dxgishim1,2726The companion dxgi.dll — swapchain and adapter
dx12bridge.ini9971Every lever, documented inline, no recompile needed to change one
src/probe4642Standalone capability probe
vkd3d-proton patch+869 / −139ExecuteIndirect fallback, root-constant unroll, PSO and cache paths
dxil-spirv patch+227 / −84fp16 handling. Inert at runtime — the levers are off, so it behaves as pristine upstream
Total authored25,30411523,211 own source + 997 ini + 1,096 patch lines

Where the vkd3d-proton changes landed

FileAddedRemovedWhy
libs/vkd3d/command.c5574ExecuteIndirect fallback — fault 07
libs/vkd3d/state.c1313Pipeline state, refusal reporting
libs/vkd3d/vkd3d_private.h560Structures for the unroll path
libs/vkd3d/meta.c510The compute unroll pass — fault 08
libs/vkd3d/cache.c291Pipeline cache behaviour
libs/vkd3d/device.c294Extension and feature plumbing
include/vkd3d_shader.h90fp16 mode surface
libs/vkd3d-shader/vkd3d_shader_main.c61Mode plumbing
libs/vkd3d/vkd3d_shaders.h10One declaration
9 files86913Verified: the shipped patch reproduces all nine byte-for-byte from clean upstream v3.0.1

How much experience this needs

This is the part of the question that cannot be measured, so here are two honest numbers instead of one confident one.

6–10engineer-months of focused effort to do this work
8–12engineer-years of prior graphics and driver specialisation it rests on
7days it actually took, elapsed

The second number is the real cost and it is not visible in any diff. Nothing in §3 is reachable without already knowing how SPIR-V is laid out in binary, why a use-list walk in a shader compiler is a plausible thing to fault, what VkResult = -3 means before you look it up, that ExecuteIndirect maps onto device-generated commands, and that equal working set with 2.7× the commit means a mapped file. That is graphics-driver knowledge accumulated over years, and every one of the twelve faults consumed some of it.

The first number is what the doing took: twelve faults, forty-eight refutations, and forty-four occasions when the instrumentation had to be repaired before the experiment could run. The seven-day figure is real but it is not a claim about difficulty — it is what happens when the compile-test-measure loop runs continuously and someone is at the keyboard to launch the game every time.

§ 6

What still does not work

Three known defects, stated plainly. Two of them are diagnosed and deliberately not fixed, because the build finally works and the risk is not worth it.

OPEN 01 · diagnosed, not fixed background never submitted

The title-screen background is black

The text and UI render. The scene behind them does not. This is not a render failure and not a broken video: there are zero movie assets of any format in the install, no Bink or Media Foundation DLLs in bin64, zero D3D12 Video interfaces requested, and zero E_NOINTERFACE.

The command stream settles it. Across all 100 measured black windows the workload is flat and UI-shaped: draw=360 drawIndexed=4200 indirect=12120 dispatch=35020 per 120 frames — about 3 direct and 30 indexed draws per frame. Once the game is actually lit, indirect draws step to roughly 192,000 per frame. There is no step anywhere inside the black stretch. The compile screen and the title screen submit the same near-empty workload, so the background scene is never handed to the GPU at all.

Suggestive but unproven: about 101 ExecuteIndirect calls per frame fed by about 292 compute dispatches is this engine's GPU-driven culling path, and a background submitted that way goes black if the argument-generating compute yields zero counts. Chasing that means touching the indirect path that fault 07 just fixed. Not worth it.

OPEN 02 · load-bearing workaround turning it off → 36 refusals, death

Depth-only pixel-shader stripping cannot be turned off

The shim deletes pixel shaders from depth-only pipelines. That workaround is holding the build up: disabling it produces 36 pipeline refusals and the game dies. The shaders it deletes are exactly the ones Polaris refuses, which makes fixing them properly the path to whatever foliage is still missing — and also the reason the workaround exists.

It was checked for correctness: none of the stripped shaders write UAVs or SV_Depth. The only side effect at risk is an undetectable discard, which is why the current mode is the conservative one.

OPEN 03 · environmental 3,103 MB available at peak

Commit headroom is thin on 8 GB

Mid-gameplay peak commit is 11,773 MiB with about 3,103 MB still available. That works, but it is not comfortable, and DxcCache regrows on every launch — so fault 09's wall can come back. The launcher script guards against it. More RAM buys load speed and a usable debug loop; it does not fix anything in this report, and that was measured, not assumed.

Foliage smearing during motion was investigated and is attributed to frame rate rather than to a rendering defect. It is not in this list.

§ 7

Installing it

Five files into one folder. Nothing is written to your system, nothing is registered, and reverting is deleting what you copied.

Before anything else, back up the folder you are about to copy into. If a file with the same name already exists there, keep the original — that is your revert. This report contains an entire section (fault 10) about what happens when a revert is incomplete.

  1. Check you are on the right hardware

    This targets AMD Polaris — RX 470/480/570/580/590, including the 2048SP. On RDNA or newer you do not need it, and it will make things worse. It has been tested on exactly one machine.

  2. Find your game folder

    The folder that contains CrimsonDesert.exe, usually ...\Crimson Desert\bin64\.

  3. Copy the contents of fix\ into it

    That is d3d12.dll, dxgi.dll, dx12bridge.ini, and the vkd3d\ subfolder with two DLLs inside it. Keep the vkd3d\ folder structure — dx12bridge.ini line 35 points at vkd3d\d3d12.dll and the chain breaks if it moves.

  4. Empty your AMD shader cache once

    Delete the contents of %LOCALAPPDATA%\AMD\DxcCache. Not the folder — what is inside it. This is fault 09 and it is the difference between reaching the game and staring at "Compiling Shaders" forever. Never touch DxCache without the second 'c'; the Radeon service holds it open.

  5. Launch with Play Crimson Desert (RX 580).cmd

    Optional but recommended. It checks the two cache folders before every launch, empties them only if they are over budget, prints your commit headroom, and then starts the game. It is a plain text file — open it and read it before you run it. You can also just start the game normally.

  6. Expect the first launch to be slow

    Every shader compiles from scratch. Subsequent launches are much faster. If it hangs on "Compiling Shaders" for more than about twenty minutes, empty DxcCache again and check how much commit you have available.

Package contents

PathBytesWhat
fix/d3d12.dll3,601,660The shim. This is the project.
fix/vkd3d/d3d12core.dll8,847,126Patched vkd3d-proton core
fix/vkd3d/d3d12.dll267,221vkd3d-proton loader
fix/dxgi.dll191,287Swapchain shim — fault 12
fix/dx12bridge.ini56,376997 lines of documented configuration
fix/Play Crimson Desert (RX 580).cmd12,050Cache guard and launcher — fault 09
fix/licenses/LGPL-2.1 and MIT texts for the upstream projects
source/…-rx580-polaris.patch51,894The vkd3d-proton modifications, as required by the LGPL

Every binary in this package is checksummed in README.md. Verify them before you copy anything into a game folder — including if you got this from anywhere other than the person who built it.

§ 8

Credits

Almost none of the hard parts of this stack were written here. A shim is a small correction applied to an enormous amount of other people's work, and the proportions deserve to be stated accurately.

Józef Kucia1985–2020 · original author of vkd3d

Wrote the Direct3D-12-to-Vulkan translation layer that everything on this page is built on, and led it until his death. There is no version of this project that does not begin with his work.

Hans-Kristian Arntzenvkd3d-proton · dxil-spirv

vkd3d-proton is the layer being patched here, and dxil-spirv — the DXIL-to-SPIR-V translator, MIT-licensed, "Copyright (c) 2019–2022 Hans-Kristian Arntzen for Valve Corporation" — is what turns the game's shaders into something a Vulkan driver will accept.

Philip Rebohle · Joshua Ashtonvkd3d-proton

Core maintainers of the project this fix modifies.

vkd3d-proton contributorsfull list in fix/licenses/vkd3d-proton-AUTHORS.txt

Including Alexander Gabello, Alexandre Julliard and Andrew Eikum. The AUTHORS file ships in this package unedited.

Valve Corporationsponsor

Funds the work that makes Direct3D 12 games run on Vulkan drivers at all.

Baldur Karlsson · The Khronos Groupvendored, MIT

bc-decoder and glslang, bundled inside dxil-spirv. Their notices ship with it.

SPIRV-Cross · SPIRV-Tools · SPIRV-Headersbyte-clean, unmodified

Used exactly as upstream ships them. Verified: not one byte differs.

AMDamdvlk64, Radeon Software

The Vulkan driver underneath all of this is stock and unmodified. The palette of this document is borrowed from Radeon Software Crimson Edition, which is what this card's driver suite was called — an accident of naming too good to pass up.

Pearl AbyssCrimson Desert

Whose game this is. No game file is modified, patched, or redistributed by this fix.

The person at the keyboardevery launch, every screenshot, every correction

Ran every one of the dozens of cold-cache launches this required, on the one machine that has the hardware, and reported back each time. Three interventions changed the direction of the work outright: "the game is using 5.4 GB of VRAM, it means things are loading, just not showing up" — which reframed the whole problem from a load failure into a submission failure and led directly to fault 07; "why are you disabling frame generation, it doesn't even relate to my problem" — which stopped a wrong turn cold; and "this is an in-game effect" — correcting a frame I had mislabelled as a cutscene. Also "stop testing, just launch and fix the game", which was the right instruction at the right moment.

Licences

vkd3d-proton is LGPL-2.1-or-later. The d3d12core.dll in this package is a modified build, so sections 4 and 6 require that the corresponding modified source ship with it. It does: source/vkd3d-proton-3.0.1-rx580-polaris.patch, verified two ways — patch -p1 --dry-run exits clean against a fresh upstream v3.0.1 tree, and after applying it, all nine modified files reproduce byte-for-byte.

dxil-spirv is MIT, pinned at 62dbb07f771534c8ce924479efdc6c8fa510361d. Four files carry local changes (+227 / −8), all fp16-related, all inert at runtime because dx12bridge.ini line 371 sets DXIL_SPIRV_GRAPHICS_DISABLE_NATIVE_FP16=0 and the other two levers are unset. Functionally, the shipped build behaves as pristine upstream.

Full licence texts are in fix/licenses/.