Mortal Kombat 11
Has the face morph read its three-float deltas the way D3DMetal and DXMT can, which removes the dark patches, and gives the movies their colours and their frame order back. DirectX 12 and 11 alike.
What you see
Bare faces -- forehead, brows, around the eyes -- turn into smeared dark patches with hard edges that follow the light and change from frame to frame. Clean right after a screen loads, broken once the face animates; barely visible in a fight, glaring in close views, the character select and the fatalities. Masked fighters never show it. It has been there since at least an M1 Pro
Why it happens
The face morph reads its per-vertex deltas from DXGI_FORMAT_R32G32B32_FLOAT buffer views, and D3DMetal serves those as two floats an element with z = 0
What the fix changes
Every such view is created as an R32_FLOAT view three times as long, and the two morph shaders read a delta's three floats one by one
The code
The faces. Every three-float buffer view is created as a one-float view three times as long, which D3DMetal and DXMT both read correctly; the rewritten morph shaders then read a delta's three floats one by one.
fixes/mk11-face-fix.c · hk_CreateShaderResourceView, lines 155–172static void STDMETHODCALLTYPE hk_CreateShaderResourceView(ID3D12Device *dev, ID3D12Resource *res,
const D3D12_SHADER_RESOURCE_VIEW_DESC *desc, D3D12_CPU_DESCRIPTOR_HANDLE h)
{
void (STDMETHODCALLTYPE *orig)(ID3D12Device *, ID3D12Resource *, const D3D12_SHADER_RESOURCE_VIEW_DESC *,
D3D12_CPU_DESCRIPTOR_HANDLE) = orig_fn(dev, DEV_IDX(CreateShaderResourceView));
if (desc && desc->ViewDimension == D3D12_SRV_DIMENSION_BUFFER && desc->Format == DXGI_FORMAT_R32G32B32_FLOAT
&& !(desc->Buffer.Flags & D3D12_BUFFER_SRV_FLAG_RAW))
{
D3D12_SHADER_RESOURCE_VIEW_DESC one = *desc;
one.Format = DXGI_FORMAT_R32_FLOAT;
one.Buffer.FirstElement = desc->Buffer.FirstElement * 3;
one.Buffer.NumElements = desc->Buffer.NumElements * 3;
InterlockedIncrement(&views_made);
orig(dev, res, &one, h);
return;
}
orig(dev, res, desc, h);
}The colours. CrossOver's H.264 decoder leaves NV12 out of the types it offers, so the game took YV12 and drew it as NV12. NV12 is offered again, first.
fixes/mk11-movie.c · movie_GetOutputAvailableType, lines 324–345static HRESULT STDMETHODCALLTYPE movie_GetOutputAvailableType(IMFTransform *t, DWORD stream, DWORD index,
IMFMediaType **type)
{
HRESULT (STDMETHODCALLTYPE *real)(IMFTransform *, DWORD, DWORD, IMFMediaType **)
= orig_fn(t, MFT_IDX(GetOutputAvailableType));
IMFMediaType *first = NULL, *nv12 = NULL;
GUID sub = { 0 };
if (!type || FAILED(real(t, stream, 0, &first)) || !first) return real(t, stream, index, type);
if (SUCCEEDED(IMFMediaType_GetGUID(first, &movie_subtype, &sub)) && IsEqualGUID(&sub, &movie_nv12))
{
IMFMediaType_Release(first);
return real(t, stream, index, type);
}
if (index) { IMFMediaType_Release(first); return real(t, stream, index - 1, type); }
if (FAILED(create_media_type(&nv12)) || !nv12) { *type = first; return S_OK; }
IMFMediaType_CopyAllItems(first, (IMFAttributes *)nv12);
IMFMediaType_SetGUID(nv12, &movie_subtype, &movie_nv12);
IMFMediaType_Release(first);
if (!InterlockedExchange(&nv12_offered, 1)) log_msg("movie: the H.264 decoder offers no NV12; it is offered first");
*type = nv12;
return S_OK;
}The frame order. The decoder hands frames out in decode order; the fix holds them and hands out the one that comes first in display order.
fixes/mk11-movie.c · hand_out, lines 304–321static HRESULT hand_out(movie_decoder *d, MFT_OUTPUT_DATA_BUFFER *out, DWORD *status)
{
int i, best = 0;
BOOL ok;
BYTE *data;
DWORD cap;
for (i = 1; i < d->nheld; i++) if (d->held[i].key < d->held[best].key) best = i;
ok = move_frame(out->pSample, &d->held[best].data, &d->held[best].len, &d->held[best].cap, TRUE);
if (best) d->moved++;
/* The rest stay oldest first; the emptied buffer goes to the end for reuse. */
data = d->held[best].data; cap = d->held[best].cap;
memmove(&d->held[best], &d->held[best + 1], (size_t)(d->nheld - best - 1) * sizeof(d->held[0]));
d->nheld--;
d->held[d->nheld].data = data; d->held[d->nheld].cap = cap; d->held[d->nheld].len = 0;
out->dwStatus = 0;
if (status) *status = 0;
return ok ? S_OK : E_FAIL;
}Thanks to
- Anton PavliukFound and published first that the movie frames come out in decode order, and the reordering (MIT, DirectX 11 only); mk11-movie.c is a separate implementation.
Details
- Needs
- D3DMetal with D3D12 (
MK11_DX12.exe) and DXMT with D3D11 (MK11.exe): one install covers both, whichever Steam starts - Installer
- install-mk11-fix.sh
- Executable
- MK11_DX12.exe
- Built from
- mk11-face-fix.c, sm2fix_core.c, mk11-morph-shaders.h, mk11-movie.c
- Verified on
- Preview 27 through NotProton, Game Porting Toolkit 4.0b3 (2026-10-16 and 17)