From dcc157bd04fcff56c100d7eef5bb59b85f9837e7 Mon Sep 17 00:00:00 2001 From: dinkc64 Date: Fri, 23 Oct 2026 02:03:45 -0400 Subject: [PATCH] dx11, fix a couple shader bugs --- src/intf/video/win32/vid_d3d11.cpp | 302 ++++++++-- src/intf/video/win32/vid_d3d11_shader.cpp | 685 +++++++++++++++++++++- src/intf/video/win32/vid_d3d11_shader.h | 7 +- 3 files changed, 951 insertions(+), 43 deletions(-) diff --git a/src/intf/video/win32/vid_d3d11.cpp b/src/intf/video/win32/vid_d3d11.cpp index 82f35070e..dbda1d432 100644 --- a/src/intf/video/win32/vid_d3d11.cpp +++ b/src/intf/video/win32/vid_d3d11.cpp @@ -253,10 +253,39 @@ static const char* d3d11ShaderTarget(bool bPixelShader) return bPixelShader ? "ps_4_0_level_9_1" : "vs_4_0_level_9_1"; } +// Log text of any length: FBNeo's dprintf() formats into a 1024 character buffer, so the text goes +// out a line at a time, each line clamped, and always through "%hs" (compiler output may contain '%') +static void d3d11LogText(const char* pText, size_t nLen, int nMaxLines) +{ + size_t nPos = 0; + int nLines = 0; + + while (nPos < nLen && pText[nPos]) { + size_t nEnd = nPos; + while (nEnd < nLen && pText[nEnd] && pText[nEnd] != '\n') nEnd++; + + if (nLines >= nMaxLines) { + dprintf(_T(" (more lines not shown)\n")); + return; + } + + char szLine[800]; + size_t nLineLen = nEnd - nPos; + if (nLineLen > sizeof(szLine) - 1) nLineLen = sizeof(szLine) - 1; + memcpy(szLine, pText + nPos, nLineLen); + szLine[nLineLen] = 0; + if (nLineLen && szLine[nLineLen - 1] == '\r') szLine[nLineLen - 1] = 0; + dprintf(_T("%hs\n"), szLine); + + nLines++; + nPos = nEnd + 1; + } +} + static void d3d11PrintCompilerErrors(ID3DBlob* pErrors) { if (pErrors && pErrors->GetBufferSize()) { - dprintf(_T("%hs\n"), (char*)pErrors->GetBufferPointer()); + d3d11LogText((const char*)pErrors->GetBufferPointer(), pErrors->GetBufferSize(), 40); } } @@ -756,12 +785,15 @@ static HRESULT CreateLayoutForVertexShader(ID3DBlob* pVSCode, ID3D11InputLayout* #define D3D11_MAX_HISTORY 8 // original frames kept for PREV .. PREV6 +enum { D3D11_VAR_FLOAT = 0, D3D11_VAR_INT, D3D11_VAR_UINT, D3D11_VAR_BOOL }; + struct d3d11UniformVar { int nUniform; int nSource; int nIndex; UINT nOffset; UINT nSize; + int nType; // D3D11_VAR_*: the values are converted from float when written }; struct d3d11CBuffer { @@ -800,7 +832,8 @@ struct d3d11ShaderPass { ID3D11Texture2D* pTexture; // render target (unless the pass renders to the screen) ID3D11RenderTargetView* pRTV; ID3D11ShaderResourceView* pSRV; - int nWidth, nHeight; // render target size + int nWidth, nHeight; // size of the image rendered into the target + int nTexWidth, nTexHeight; // size of the render target texture (a power of 2, like RetroArch) bool bToScreen; const char* szMode; }; @@ -988,15 +1021,47 @@ static int d3d11LoadLUT(const D3D11ShaderLUT& Desc, d3d11LUT* pLUT, std::string& td.Usage = D3D11_USAGE_IMMUTABLE; td.BindFlags = D3D11_BIND_SHADER_RESOURCE; - D3D11_SUBRESOURCE_DATA sd; - sd.pSysMem = pPixels; - sd.SysMemPitch = w * 4; - sd.SysMemSlicePitch = 0; + HRESULT hr = E_FAIL; + + if (Desc.bMipmap) { + // "_mipmap = true": a full mip chain, generated by the GPU + td.MipLevels = 0; + td.Usage = D3D11_USAGE_DEFAULT; + td.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + td.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; + hr = pDevice->CreateTexture2D(&td, NULL, &pLUT->pTexture); + if (SUCCEEDED(hr)) { + hr = pDevice->CreateShaderResourceView(pLUT->pTexture, NULL, &pLUT->pSRV); + } + if (SUCCEEDED(hr)) { + pContext->UpdateSubresource(pLUT->pTexture, 0, NULL, pPixels, w * 4, 0); + pContext->GenerateMips(pLUT->pSRV); + } else { + D3D11_RELEASE(pLUT->pSRV); + D3D11_RELEASE(pLUT->pTexture); + td.MipLevels = 1; // fall back to a single level + td.Usage = D3D11_USAGE_IMMUTABLE; + td.BindFlags = D3D11_BIND_SHADER_RESOURCE; + td.MiscFlags = 0; + } + } + + if (pLUT->pTexture == NULL) { + D3D11_SUBRESOURCE_DATA sd; + sd.pSysMem = pPixels; + sd.SysMemPitch = w * 4; + sd.SysMemSlicePitch = 0; + + hr = pDevice->CreateTexture2D(&td, &sd, &pLUT->pTexture); + if (SUCCEEDED(hr)) { + hr = pDevice->CreateShaderResourceView(pLUT->pTexture, NULL, &pLUT->pSRV); + } + } - HRESULT hr = pDevice->CreateTexture2D(&td, &sd, &pLUT->pTexture); free(pPixels); - if (FAILED(hr) || FAILED(pDevice->CreateShaderResourceView(pLUT->pTexture, NULL, &pLUT->pSRV))) { + if (FAILED(hr)) { + D3D11_RELEASE(pLUT->pSRV); D3D11_RELEASE(pLUT->pTexture); szErrors += "Couldn't create texture " + Desc.szPath + "\n"; return 1; @@ -1143,6 +1208,12 @@ static int d3d11ReflectBindings(ID3DBlob* pCode, int nPass, d3d11StageBindings* if (Var.nUniform == D3D11_UNI_NONE) { continue; } + switch (td.Type) { + case D3D_SVT_INT: Var.nType = D3D11_VAR_INT; break; + case D3D_SVT_UINT: Var.nType = D3D11_VAR_UINT; break; + case D3D_SVT_BOOL: Var.nType = D3D11_VAR_BOOL; break; + default: Var.nType = D3D11_VAR_FLOAT; break; + } Var.nOffset = vd.StartOffset; Var.nSize = (vd.Size > 16) ? 16 : vd.Size; CB.Vars.push_back(Var); @@ -1218,6 +1289,7 @@ static void d3d11ReleasePassTarget(d3d11ShaderPass& Pass) D3D11_RELEASE(Pass.pRTV); D3D11_RELEASE(Pass.pTexture); Pass.nWidth = Pass.nHeight = 0; + Pass.nTexWidth = Pass.nTexHeight = 0; } static void d3d11ReleaseShader() @@ -1290,6 +1362,52 @@ static int d3d11CompileFX(const std::string& Source, const std::string& szName, return 0; } +// Direct3D 11 links the vertex shader outputs to the pixel shader inputs by register: check that every +// pixel shader input is a vertex shader output with the same semantic, in the same register +static bool d3d11SignaturesMatch(ID3DBlob* pVSCode, ID3DBlob* pPSCode) +{ + ID3D11ShaderReflection* pVS = NULL; + ID3D11ShaderReflection* pPS = NULL; + bool bMatch = true; + + if (FAILED(d3d11Reflect(pVSCode, &pVS)) || FAILED(d3d11Reflect(pPSCode, &pPS))) { + D3D11_RELEASE(pVS); + D3D11_RELEASE(pPS); + return true; // can't tell + } + + D3D11_SHADER_DESC vd, pd; + memset(&vd, 0, sizeof(vd)); + memset(&pd, 0, sizeof(pd)); + pVS->GetDesc(&vd); + pPS->GetDesc(&pd); + + for (UINT i = 0; i < pd.InputParameters && bMatch; i++) { + D3D11_SIGNATURE_PARAMETER_DESC in; + memset(&in, 0, sizeof(in)); + if (FAILED(pPS->GetInputParameterDesc(i, &in))) continue; + if (in.SystemValueType != D3D_NAME_UNDEFINED && in.SystemValueType != D3D_NAME_POSITION) continue; // generated by the rasterizer + + bool bFound = false; + for (UINT o = 0; o < vd.OutputParameters && !bFound; o++) { + D3D11_SIGNATURE_PARAMETER_DESC out; + memset(&out, 0, sizeof(out)); + if (FAILED(pVS->GetOutputParameterDesc(o, &out))) continue; + if (_stricmp(out.SemanticName, in.SemanticName) == 0 && out.SemanticIndex == in.SemanticIndex && out.Register == in.Register) { + bFound = true; + } + } + if (!bFound) { + bMatch = false; + } + } + + pVS->Release(); + pPS->Release(); + + return bMatch; +} + static int d3d11CompilePass(int nPass, std::string& szErrors) { d3d11ShaderPass& Pass = pShader->Passes[nPass]; @@ -1301,6 +1419,7 @@ static int d3d11CompilePass(int nPass, std::string& szErrors) return 1; } + D3D11ShaderFixMacroNames(Source); // D3DPreprocess() workaround D3D11ShaderExtractParams(Source, pShader->Params); ID3DBlob* pVSCode = NULL; @@ -1317,9 +1436,16 @@ static int d3d11CompilePass(int nPass, std::string& szErrors) if (Source.find("HLSL_4") != std::string::npos) { D3D_SHADER_MACRO Macros[] = { { "HLSL_4", "1" }, { "PARAMETER_UNIFORM", "1" }, { "FBNEO_D3D11", "1" }, { NULL, NULL } }; std::string szErr; - if (SUCCEEDED(d3d11CompileEx(Source, szPath, "main_fragment", true, Macros, 0, &pPSCode, &szErr)) && - (!bHasVS || SUCCEEDED(d3d11CompileEx(Source, szPath, "main_vertex", false, Macros, 0, &pVSCode, &szErr)))) { + // backwards compatibility mode: some of these still use the D3D9 POSITION / COLOR semantics + if (SUCCEEDED(d3d11CompileEx(Source, szPath, "main_fragment", true, Macros, D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY, &pPSCode, &szErr)) && + (!bHasVS || SUCCEEDED(d3d11CompileEx(Source, szPath, "main_vertex", false, Macros, D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY, &pVSCode, &szErr)))) { Pass.szMode = "HLSL_4"; + if (pVSCode && !d3d11SignaturesMatch(pVSCode, pPSCode)) { + // the shader's own D3D11 code doesn't link up: use its Cg code instead + D3D11_RELEASE(pPSCode); + D3D11_RELEASE(pVSCode); + szTried += "[HLSL_4] the vertex and pixel shader signatures don't match\n"; + } } else { D3D11_RELEASE(pPSCode); D3D11_RELEASE(pVSCode); @@ -1342,9 +1468,17 @@ static int d3d11CompilePass(int nPass, std::string& szErrors) } else { D3D11_RELEASE(pPSCode); D3D11_RELEASE(pVSCode); -#ifdef FBNEO_DEBUG - dprintf(_T("--- translated %hs ---\n%hs\n"), szPath.c_str(), Translation.szHLSL.c_str()); -#endif + + // Keep the translated source for reference: config/shaders/.translated.hlsl + CreateDirectoryW(L"config", NULL); + CreateDirectoryW(L"config/shaders", NULL); + std::wstring szDump = L"config/shaders/" + d3d11Widen(d3d11BaseName(szPath)) + L".translated.hlsl"; + FILE* fp = _wfopen(szDump.c_str(), L"wb"); + if (fp) { + fwrite(Translation.szHLSL.data(), 1, Translation.szHLSL.size(), fp); + fclose(fp); + szErr += "(translated source saved as " + d3d11NarrowW(szDump.c_str()) + ")\n"; + } } } } @@ -1360,6 +1494,9 @@ static int d3d11CompilePass(int nPass, std::string& szErrors) if (d3d11CompileFX(Source, szPath, &pVSCode, &pPSCode, szErr) == 0) { Pass.szMode = "FX"; bFX = true; + if (pVSCode && !d3d11SignaturesMatch(pVSCode, pPSCode)) { + D3D11_RELEASE(pVSCode); // use a generated vertex shader that matches the pixel shader + } } else { D3D11_RELEASE(pPSCode); D3D11_RELEASE(pVSCode); @@ -1508,21 +1645,63 @@ static int d3d11SaveParamValues() return 0; } +// FBAPopupAddText() formats into a 1024 character buffer, so long text is added in pieces +static void d3d11PopupText(const std::string& szText) +{ + size_t nPos = 0; + while (nPos < szText.size()) { + size_t nLen = szText.size() - nPos; + if (nLen > 700) { + nLen = 700; + while (nLen > 1 && (szText[nPos + nLen] & 0xC0) == 0x80) nLen--; // don't split a UTF-8 sequence + } + std::string szPiece = szText.substr(nPos, nLen); +#if defined (UNICODE) || defined (_UNICODE) + FBAPopupAddText(PUF_TEXT_NO_TRANSLATE, _T("%s"), d3d11Widen(szPiece).c_str()); +#else + FBAPopupAddText(PUF_TEXT_NO_TRANSLATE, _T("%s"), szPiece.c_str()); +#endif + nPos += nLen; + } +} + static void d3d11ShaderError(const std::string& szPath, const std::string& szErrors) { - std::string szShort = szErrors; - if (szShort.size() > 1500) { - szShort = szShort.substr(0, 1500) + "\n..."; + dprintf(_T(" * Error: Couldn't load shader:\n")); + d3d11LogText(szPath.c_str(), szPath.size(), 1); + d3d11LogText(szErrors.c_str(), szErrors.size(), 200); + + // For the popup: only the error lines (not the warnings), with "C:\long\path\file.cg(12,3)" shortened to "file.cg(12,3)" + std::string szShort; + int nLines = 0; + size_t nPos = 0; + while (nPos < szErrors.size() && nLines < 12) { + size_t nEnd = szErrors.find('\n', nPos); + if (nEnd == std::string::npos) nEnd = szErrors.size(); + std::string szLine = szErrors.substr(nPos, nEnd - nPos); + nPos = nEnd + 1; + + if (szLine.find("warning") != std::string::npos || szLine.find(": W") != std::string::npos) { + continue; + } + size_t nParen = szLine.find('('); + size_t nSlash = szLine.find_last_of("/\\", nParen); + if (nParen != std::string::npos && nSlash != std::string::npos) { + szLine = szLine.substr(nSlash + 1); + } + if (szLine.size() > 200) { + szLine = szLine.substr(0, 200) + "..."; + } + if (!szLine.empty()) { + szShort += szLine + "\n"; + nLines++; + } + } + if (nPos < szErrors.size()) { + szShort += "...\n(the full messages are in the debug log)\n"; } - dprintf(_T(" * Error: Couldn't load shader \"%hs\":\n%hs\n"), szPath.c_str(), szErrors.c_str()); - - std::wstring w = d3d11Widen("Couldn't load shader \"" + szPath + "\".\n\n" + szShort); -#if defined (UNICODE) || defined (_UNICODE) - FBAPopupAddText(PUF_TEXT_NO_TRANSLATE, _T("%s"), w.c_str()); -#else - FBAPopupAddText(PUF_TEXT_NO_TRANSLATE, _T("%s"), ("Couldn't load shader \"" + szPath + "\".\n\n" + szShort).c_str()); -#endif + d3d11PopupText("Couldn't load shader \"" + d3d11BaseName(szPath) + "\".\n\n" + szShort); FBAPopupDisplay(PUF_TYPE_ERROR); } @@ -1562,6 +1741,7 @@ static int d3d11LoadShader() Pass.pRTV = NULL; Pass.pSRV = NULL; Pass.nWidth = Pass.nHeight = 0; + Pass.nTexWidth = Pass.nTexHeight = 0; Pass.bToScreen = (i == nPasses - 1) && Pass.Desc.nScaleTypeX == D3D11_SCALE_UNSET && Pass.Desc.nScaleTypeY == D3D11_SCALE_UNSET; Pass.szMode = ""; pShader->Aliases.push_back(Pass.Desc.szAlias); @@ -1678,7 +1858,20 @@ static void d3d11UpdatePassConstants(d3d11StageBindings& Bind, int nPass, float break; } - memcpy(&CB.Data[Var.nOffset], f, Var.nSize); + if (Var.nType != D3D11_VAR_FLOAT) { + // int / uint / bool variables (e.g. "uniform int Ywidth;") need the value, not the bits of a float + UINT32 n[4]; + for (int k = 0; k < 4; k++) { + switch (Var.nType) { + case D3D11_VAR_INT: n[k] = (UINT32)(INT32)floorf(f[k] + 0.5f); break; + case D3D11_VAR_UINT: n[k] = (f[k] > 0.0f) ? (UINT32)(f[k] + 0.5f) : 0; break; + default: n[k] = (f[k] != 0.0f) ? 1 : 0; break; + } + } + memcpy(&CB.Data[Var.nOffset], n, Var.nSize); + } else { + memcpy(&CB.Data[Var.nOffset], f, Var.nSize); + } } d3d11UpdateBuffer(CB.pBuffer, &CB.Data[0], CB.nSize); @@ -1713,9 +1906,14 @@ static void d3d11BindPassResources(d3d11StageBindings& Bind, int nPass, bool bPi } } -static int d3d11EnsurePassTarget(d3d11ShaderPass& Pass, int nWidth, int nHeight) +static int d3d11EnsurePassTarget(d3d11ShaderPass& Pass, int nWidth, int nHeight, int nTexWidth, int nTexHeight) { - if (Pass.pTexture && Pass.nWidth == nWidth && Pass.nHeight == nHeight) { + if (nTexWidth < nWidth) nTexWidth = nWidth; + if (nTexHeight < nHeight) nTexHeight = nHeight; + + if (Pass.pTexture && Pass.nTexWidth == nTexWidth && Pass.nTexHeight == nTexHeight) { + Pass.nWidth = nWidth; + Pass.nHeight = nHeight; return 0; } @@ -1723,8 +1921,8 @@ static int d3d11EnsurePassTarget(d3d11ShaderPass& Pass, int nWidth, int nHeight) D3D11_TEXTURE2D_DESC td; memset(&td, 0, sizeof(td)); - td.Width = nWidth; - td.Height = nHeight; + td.Width = nTexWidth; + td.Height = nTexHeight; td.MipLevels = 1; td.ArraySize = 1; td.Format = DXGI_FORMAT_B8G8R8A8_UNORM; @@ -1755,6 +1953,8 @@ static int d3d11EnsurePassTarget(d3d11ShaderPass& Pass, int nWidth, int nHeight) Pass.nWidth = nWidth; Pass.nHeight = nHeight; + Pass.nTexWidth = nTexWidth; + Pass.nTexHeight = nTexHeight; return 0; } @@ -1777,8 +1977,10 @@ static void d3d11ShaderSource(int nSource, int nIndex, int nPass, ID3D11ShaderRe } else { const d3d11ShaderPass& p = pShader->Passes[nIndex]; *ppSRV = p.pSRV; - pVideoSize[0] = pTextureSize[0] = (float)p.nWidth; - pVideoSize[1] = pTextureSize[1] = (float)p.nHeight; + pVideoSize[0] = (float)p.nWidth; + pVideoSize[1] = (float)p.nHeight; + pTextureSize[0] = (float)p.nTexWidth; + pTextureSize[1] = (float)p.nTexHeight; return; } } @@ -1823,6 +2025,28 @@ static int d3d11PassSize(int nType, float fScale, int nInput, int nViewport) return (n < 1) ? 1 : (n > nMax) ? nMax : n; } +// Size of a pass's render target texture: like RetroArch, the next power of 2 of the (maximum) +// image size, so the image of a pass and the original frame use the same texture coordinates +// (many Cg shaders sample ORIG / PASSPREVn with the texture coordinates of their input) +static int d3d11PassTextureSize(int nType, float fScale, int nInputTexture, int nSize) +{ + int n = nSize; + if (nType == D3D11_SCALE_SOURCE || nType == D3D11_SCALE_UNSET) { + n = (int)(nInputTexture * fScale + 0.5f); + if (n < nSize) n = nSize; + } + + int nMax = (nFeatureLevel >= D3D_FEATURE_LEVEL_11_0) ? 16384 : (nFeatureLevel >= D3D_FEATURE_LEVEL_10_0) ? 8192 : (nFeatureLevel >= D3D_FEATURE_LEVEL_9_3) ? 4096 : 2048; + int nPow2 = 1; + while (nPow2 < n && nPow2 < nMax) { + nPow2 <<= 1; + } + if (nPow2 < nSize) { + nPow2 = nSize; // too large for a power of 2 texture - use the exact size + } + return nPow2; +} + // Run the shader chain; the last pass (or a final stretch) draws into the image area of the back buffer static void d3d11DrawShader() { @@ -1834,6 +2058,7 @@ static void d3d11DrawShader() d3d11ShaderSource(D3D11_SRC_ORIG, 0, 0, &pDummy, InVideo, InTexture); int nInWidth = (int)InVideo[0], nInHeight = (int)InVideo[1]; + int nInTexWidth = (int)InTexture[0], nInTexHeight = (int)InTexture[1]; size_t nPasses = pShader->Passes.size(); pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); @@ -1844,13 +2069,15 @@ static void d3d11DrawShader() d3d11ShaderPass& Pass = pShader->Passes[i]; int nPass = (int)i; - int nOutWidth, nOutHeight; + int nOutWidth, nOutHeight, nOutTexWidth = 0, nOutTexHeight = 0; if (Pass.bToScreen) { nOutWidth = nImageWidth; nOutHeight = nImageHeight; } else { nOutWidth = d3d11PassSize(Pass.Desc.nScaleTypeX, Pass.Desc.fScaleX, nInWidth, nImageWidth); nOutHeight = d3d11PassSize(Pass.Desc.nScaleTypeY, Pass.Desc.fScaleY, nInHeight, nImageHeight); + nOutTexWidth = d3d11PassTextureSize(Pass.Desc.nScaleTypeX, Pass.Desc.fScaleX, nInTexWidth, nOutWidth); + nOutTexHeight = d3d11PassTextureSize(Pass.Desc.nScaleTypeY, Pass.Desc.fScaleY, nInTexHeight, nOutHeight); } // the input's texture coordinates @@ -1890,7 +2117,7 @@ static void d3d11DrawShader() vp.Width = (float)(Render.right - Render.left); vp.Height = (float)(Render.bottom - Render.top); } else { - if (d3d11EnsurePassTarget(Pass, nOutWidth, nOutHeight)) { + if (d3d11EnsurePassTarget(Pass, nOutWidth, nOutHeight, nOutTexWidth, nOutTexHeight)) { return; } pContext->OMSetRenderTargets(1, &Pass.pRTV, NULL); @@ -1914,6 +2141,8 @@ static void d3d11DrawShader() nInWidth = nOutWidth; nInHeight = nOutHeight; + nInTexWidth = Pass.bToScreen ? nOutWidth : Pass.nTexWidth; + nInTexHeight = Pass.bToScreen ? nOutHeight : Pass.nTexHeight; } pContext->PSSetShaderResources(0, 16, NullSRV); @@ -1926,10 +2155,11 @@ static void d3d11DrawShader() for (int k = 0; k < 4; k++) { Quad[k] = GameQuad[k]; } + float u1 = (float)Last.nWidth / Last.nTexWidth, v1 = (float)Last.nHeight / Last.nTexHeight; Quad[0].u = 0.0f; Quad[0].v = 0.0f; - Quad[1].u = 1.0f; Quad[1].v = 0.0f; - Quad[2].u = 0.0f; Quad[2].v = 1.0f; - Quad[3].u = 1.0f; Quad[3].v = 1.0f; + Quad[1].u = u1; Quad[1].v = 0.0f; + Quad[2].u = 0.0f; Quad[2].v = v1; + Quad[3].u = u1; Quad[3].v = v1; d3d11UpdateBuffer(pPassVB, Quad, sizeof(Quad)); D3D11_VIEWPORT vp; diff --git a/src/intf/video/win32/vid_d3d11_shader.cpp b/src/intf/video/win32/vid_d3d11_shader.cpp index 4286d698c..a1b16158a 100644 --- a/src/intf/video/win32/vid_d3d11_shader.cpp +++ b/src/intf/video/win32/vid_d3d11_shader.cpp @@ -99,7 +99,51 @@ std::string D3D11ShaderJoinPath(const std::string& szDir, const std::string& szR if (szDir.empty()) { return szRelative; } - return szDir + "/" + szRelative; + + // join, and collapse "." / ".." (presets and includes are full of "../../"), so paths stay + // short and readable in error messages +#ifdef _WIN32 + const char cSep = '\\'; +#else + const char cSep = '/'; +#endif + std::string szFull = szDir + "/" + szRelative; + + std::string szPrefix; + size_t nStart = 0; + if (szFull.size() > 1 && szFull[1] == ':') { + nStart = 2; // drive letter + } else if (szFull.size() > 1 && (szFull[0] == '\\' || szFull[0] == '/') && (szFull[1] == '\\' || szFull[1] == '/')) { + nStart = 2; // UNC (\\server\share) + } + szPrefix = szFull.substr(0, nStart); + bool bAbsolute = nStart < szFull.size() && (szFull[nStart] == '/' || szFull[nStart] == '\\'); + + std::vector Parts; + size_t i = nStart; + while (i <= szFull.size()) { + size_t e = szFull.find_first_of("/\\", i); + if (e == std::string::npos) e = szFull.size(); + std::string s = szFull.substr(i, e - i); + if (s.empty() || s == ".") { + // skip + } else if (s == ".." && !Parts.empty() && Parts.back() != "..") { + Parts.pop_back(); + } else if (s == ".." && bAbsolute) { + // can't go above the root + } else { + Parts.push_back(s); + } + i = e + 1; + } + + std::string szOut = szPrefix; + if (bAbsolute) szOut += cSep; + for (size_t p = 0; p < Parts.size(); p++) { + if (p) szOut += cSep; + szOut += Parts[p]; + } + return szOut; } std::string D3D11ShaderExtension(const std::string& szPath) @@ -290,6 +334,7 @@ bool D3D11ShaderLoadPreset(const std::string& szPath, D3D11ShaderReadFileFn pRea LUT.szPath = D3D11ShaderJoinPath(szDir, *v); if ((v = FindValue(*pPreset, szName + "_linear")) != NULL) LUT.bLinear = ParseBool(*v); if ((v = FindValue(*pPreset, szName + "_wrap_mode")) != NULL) LUT.nWrap = ParseWrap(*v); + if ((v = FindValue(*pPreset, szName + "_mipmap")) != NULL) LUT.bMipmap = ParseBool(*v); pPreset->LUTs.push_back(LUT); } } @@ -354,6 +399,163 @@ bool D3D11ShaderInlineIncludes(const std::string& szPath, D3D11ShaderReadFileFn return InlineIncludes(szPath, pRead, Source, pError, 0); } +// ---------------------------------------------------------------------------- +// D3DPreprocess() workaround (see the header) + +namespace { + +struct PPIdent { + size_t nPos, nLen; + size_t nLineStart; // start of the line (for directives) + bool bCall; // followed by '(' +}; + +static bool PPIdentStart(char c) { return isalpha((unsigned char)c) || c == '_'; } +static bool PPIdentChar(char c) { return isalnum((unsigned char)c) || c == '_'; } + +} + +void D3D11ShaderFixMacroNames(std::string& Src) +{ + // Collect the identifiers (outside comments, strings and numbers) + std::vector Idents; + size_t i = 0, n = Src.size(), nLineStart = 0; + + while (i < n) { + char c = Src[i]; + if (c == '\n') { nLineStart = i + 1; i++; continue; } + if (c == '/' && i + 1 < n && Src[i + 1] == '/') { while (i < n && Src[i] != '\n') i++; continue; } + if (c == '/' && i + 1 < n && Src[i + 1] == '*') { + size_t e = Src.find("*/", i + 2); + e = (e == std::string::npos) ? n : e + 2; + for (size_t k = i; k < e; k++) if (Src[k] == '\n') nLineStart = k + 1; + i = e; + continue; + } + if (c == '"') { i++; while (i < n && Src[i] != '"' && Src[i] != '\n') { if (Src[i] == '\\') i++; i++; } i++; continue; } + if (isdigit((unsigned char)c)) { while (i < n && (PPIdentChar(Src[i]) || Src[i] == '.')) i++; continue; } + if (PPIdentStart(c)) { + PPIdent id; + id.nPos = i; + while (i < n && PPIdentChar(Src[i])) i++; + id.nLen = i - id.nPos; + id.nLineStart = nLineStart; + size_t k = i; + while (k < n && (Src[k] == ' ' || Src[k] == '\t' || Src[k] == '\r' || Src[k] == '\n')) k++; + id.bCall = (k < n && Src[k] == '('); + Idents.push_back(id); + continue; + } + i++; + } + + // Function-like macros: "#define NAME(" with no space before the '(' + struct Define { size_t nNameIdent; std::set Params; size_t nLineEnd; }; + std::map FnMacros; // name -> 1 + std::vector Defines; // every #define, for parameter scoping + for (size_t k = 0; k + 1 < Idents.size(); k++) { + const PPIdent& d = Idents[k]; + if (Src.compare(d.nPos, d.nLen, "define") != 0) continue; + // must be "# define" at the start of a line + size_t h = d.nLineStart; + while (h < d.nPos && (Src[h] == ' ' || Src[h] == '\t')) h++; + if (h >= d.nPos || Src[h] != '#') continue; + size_t h2 = h + 1; + while (h2 < d.nPos && (Src[h2] == ' ' || Src[h2] == '\t')) h2++; + if (h2 != d.nPos) continue; + + const PPIdent& nm = Idents[k + 1]; + Define def; + def.nNameIdent = k + 1; + // end of the directive (with line continuations) + size_t e = nm.nPos; + while (e < n && Src[e] != '\n') { if (Src[e] == '\\' && e + 1 < n && Src[e + 1] == '\n') e++; e++; } + def.nLineEnd = e; + std::string szName = Src.substr(nm.nPos, nm.nLen); + if (nm.nPos + nm.nLen < n && Src[nm.nPos + nm.nLen] == '(') { + FnMacros[szName] = 1; + size_t p = nm.nPos + nm.nLen + 1; + size_t q = Src.find(')', p); + if (q != std::string::npos && q < e) { + std::string szParams = Src.substr(p, q - p); + size_t a = 0; + while (a < szParams.size()) { + while (a < szParams.size() && !PPIdentStart(szParams[a])) a++; + size_t b = a; + while (b < szParams.size() && PPIdentChar(szParams[b])) b++; + if (b > a) def.Params.insert(szParams.substr(a, b - a)); + a = b; + } + } + } + Defines.push_back(def); + } + + if (FnMacros.empty()) { + return; + } + + // Which ones are also used as plain identifiers? + std::set NameIdents; + for (size_t d = 0; d < Defines.size(); d++) NameIdents.insert(Defines[d].nNameIdent); + + std::set Ambiguous; + for (size_t k = 0; k < Idents.size(); k++) { + const PPIdent& id = Idents[k]; + if (id.bCall || NameIdents.count(k)) continue; + std::string szName = Src.substr(id.nPos, id.nLen); + if (!FnMacros.count(szName)) continue; + + // "#ifdef X", "#ifndef X", "#undef X", "defined X", "defined(X)" refer to the macro + bool bDirective = false; + if (k > 0) { + const PPIdent& prev = Idents[k - 1]; + std::string szPrev = Src.substr(prev.nPos, prev.nLen); + if (szPrev == "ifdef" || szPrev == "ifndef" || szPrev == "undef" || szPrev == "defined") bDirective = true; + } + if (bDirective) continue; + + // a parameter of a #define with the same name doesn't count + bool bParam = false; + for (size_t d = 0; d < Defines.size() && !bParam; d++) { + size_t nStart = Idents[Defines[d].nNameIdent].nPos; + if (id.nPos > nStart && id.nPos < Defines[d].nLineEnd && Defines[d].Params.count(szName)) bParam = true; + } + if (bParam) continue; + + Ambiguous.insert(szName); + } + + if (Ambiguous.empty()) { + return; + } + + // Rename the macro and its uses as a macro (back to front, so positions stay valid) + for (size_t k = Idents.size(); k > 0; k--) { + const PPIdent& id = Idents[k - 1]; + std::string szName = Src.substr(id.nPos, id.nLen); + if (!Ambiguous.count(szName)) continue; + + bool bRename = NameIdents.count(k - 1) > 0 || id.bCall; + if (!bRename && k > 1) { + const PPIdent& prev = Idents[k - 2]; + std::string szPrev = Src.substr(prev.nPos, prev.nLen); + if (szPrev == "ifdef" || szPrev == "ifndef" || szPrev == "undef" || szPrev == "defined") bRename = true; + } + if (!bRename) continue; + + // not a parameter of an enclosing #define + bool bParam = false; + for (size_t d = 0; d < Defines.size() && !bParam; d++) { + size_t nStart = Idents[Defines[d].nNameIdent].nPos; + if (id.nPos > nStart && id.nPos < Defines[d].nLineEnd && Defines[d].Params.count(szName)) bParam = true; + } + if (bParam) continue; + + Src.replace(id.nPos, id.nLen, "fbneo_fn_" + szName); + } +} + void D3D11ShaderExtractParams(const std::string& Source, std::vector& Params) { size_t nPos = 0; @@ -501,6 +703,8 @@ public: std::set Constructors; std::set MemberNames; // all struct member names std::set UserFunctions; // all function names defined by the shader + std::set ParamTextures; // sampler parameters of helper functions (they get a SamplerState parameter too) + std::map > SamplerParamFuncs; // helper function -> argument positions that are samplers }; static bool IsIdentStart(char c) { return isalpha((unsigned char)c) || c == '_'; } @@ -892,6 +1096,18 @@ std::string Translator::EmitPlain(size_t nFirst, size_t nLast) return s; } +static bool IsScalarOrMatrixType(const std::string& s) +{ + static const char* Bases[] = { "float", "half", "int", "uint", "bool", "fixed", "double", NULL }; + for (int b = 0; Bases[b]; b++) { + size_t l = strlen(Bases[b]); + if (s.compare(0, l, Bases[b]) != 0) continue; + if (s.size() == l) return true; + if (s.size() == l + 3 && s[l] >= '1' && s[l] <= '4' && s[l + 1] == 'x' && s[l + 2] >= '1' && s[l + 2] <= '4') return true; + } + return false; +} + static bool EndsWith(const std::string& s, const char* e) { size_t l = strlen(e); @@ -973,6 +1189,36 @@ std::string Translator::Emit(size_t nFirst, size_t nLast) } } + // a helper function that takes samplers: pass each texture's sampler state along with it + if (SamplerParamFuncs.count(n) && (i == 0 || Toks[i - 1].nType != T_IDENT || Toks[i - 1].szText == "return" || Toks[i - 1].szText == "else" || Toks[i - 1].szText == "do")) { + const std::set& Pos = SamplerParamFuncs[n]; + size_t nOpen = i + 1; + size_t nClose = Match(nOpen); + std::string r = t.szBefore + t.szSpace + n + t.szAfter + Toks[nOpen].szBefore + Toks[nOpen].szSpace + "(" + Toks[nOpen].szAfter; + size_t a = nOpen + 1, nArg = 0; + while (a < nClose) { + size_t e = a; + while (e < nClose && Toks[e].szText != ",") { + if (Toks[e].szText == "(" || Toks[e].szText == "[" || Toks[e].szText == "{") e = Match(e); + e++; + } + std::string szArg = Emit(a, e - 1); + std::string szName = Trim(szArg); + r += szArg; + if (Pos.count(nArg)) { + bool bKnown = GlobalTextures.count(szName) || ParamTextures.count(szName); + r += bKnown ? (", fbneo_smp_" + szName) : std::string(", fbneo_smp"); + } + if (e < nClose) r += Toks[e].szBefore + Toks[e].szSpace + "," + Toks[e].szAfter; + a = e + 1; + nArg++; + } + r += Toks[nClose].szBefore + Toks[nClose].szSpace + ")" + Toks[nClose].szAfter; + Out += r; + i = nClose; + continue; + } + int nTexDim = TextureFunctionDim(n); bool bTex = (nTexDim > 0); bool bCtor = IsVectorType(n); @@ -1004,7 +1250,7 @@ std::string Translator::Emit(size_t nFirst, size_t nLast) r += ")"; } else { std::string szTex = Trim(Args[0]); - std::string szSampler = GlobalTextures.count(szTex) ? ("fbneo_smp_" + szTex) : std::string("fbneo_smp"); + std::string szSampler = (GlobalTextures.count(szTex) || ParamTextures.count(szTex)) ? ("fbneo_smp_" + szTex) : std::string("fbneo_smp"); std::string T = "(" + szTex + ")"; static const char* Coord[4] = { "", ".x", ".xy", ".xyz" }; std::string C = Coord[nTexDim]; @@ -1066,6 +1312,244 @@ static void SetSemantic(Token& t, const std::string& szNew) t.szText = szNew; } +struct EntryParam { + std::string szType, szName, szArray; + std::string szInSem; // semantic as written (upper case) + std::string szSem; // semantic after translation (upper case, "" for struct parameters) + bool bIn, bOut, bStruct; + std::string szText; // the translated parameter declaration +}; + +// Canonical form of a varying semantic: "TEXCOORD" == "TEXCOORD0", all the position semantics == "SV_POSITION" +static std::string NormSem(const std::string& s) +{ + std::string u = Upper(Trim(s)); + if (u == "POSITION" || u == "POSITION0" || u == "POS" || u == "VPOS" || u == "WPOS" || u == "SV_POSITION") { + return "SV_POSITION"; + } + if (!u.empty() && !isdigit((unsigned char)u[u.size() - 1])) { + u += "0"; + } + return u; +} + +// Number of components of a scalar / vector type (0 = something else) +static int VecDim(const std::string& t) +{ + static const char* Bases[] = { "float", "half", "fixed", "int", "uint", "bool", "double", NULL }; + for (int b = 0; Bases[b]; b++) { + size_t l = strlen(Bases[b]); + if (t.compare(0, l, Bases[b]) != 0) continue; + if (t.size() == l) return 1; + if (t.size() == l + 1 && t[l] >= '1' && t[l] <= '4') return t[l] - '0'; + } + return 0; +} + +static std::string Convert(const std::string& szDstType, const std::string& szSrcType, const std::string& szExpr) +{ + int d = VecDim(szDstType), s = VecDim(szSrcType); + if (d == 0 || s == 0 || d == s) { + return szExpr; + } + if (d < s) { + return "(" + szDstType + ")(" + szExpr + ")"; + } + std::string r = szDstType + "(" + szExpr; + for (int i = s; i < d; i++) r += ", 0"; + return r + ")"; +} + +static bool IsIntegerType(const std::string& t) +{ + return t.compare(0, 3, "int") == 0 || t.compare(0, 4, "uint") == 0 || t.compare(0, 4, "bool") == 0; +} + +struct Varying { + std::string szType, szArray, szSem; // szSem normalised + std::string szSource; // expression in the vertex shader wrapper +}; + +static std::string MakeStageWrappers(Translator& x, Function* pVS, Function* pPS, std::vector* EntryParams, + std::map >& MemberSem) +{ + std::vector V; + struct Local { + static int Find(std::vector& V, const std::string& szSem) { + for (size_t i = 0; i < V.size(); i++) if (V[i].szSem == szSem) return (int)i; + return -1; + } + static void Add(std::vector& V, const std::string& szType, const std::string& szArray, const std::string& szSem, const std::string& szSource) { + if (szSem.empty() || Find(V, szSem) >= 0) return; + Varying v; v.szType = szType; v.szArray = szArray; v.szSem = szSem; v.szSource = szSource; + V.push_back(v); + } + }; + + // ---- vertex shader wrapper: collect the outputs + // (the wrapper's own locals are named fbneo_lN: a parameter may have the same name as its type) + std::string szVSParams, szVSLocals, szVSArgs; + for (size_t i = 0; i < EntryParams[0].size(); i++) { + const EntryParam& p = EntryParams[0][i]; + char szLocal[32]; + sprintf(szLocal, "fbneo_l%d", (int)i); + + if (!p.bOut) { + szVSParams += std::string(szVSParams.empty() ? "" : ",") + p.szText; + szVSArgs += std::string(i ? ", " : "") + p.szName; + continue; + } + + szVSArgs += std::string(i ? ", " : "") + szLocal; + + if (p.bIn) { + // inout: input under its original semantic, passed through a local + szVSParams += std::string(szVSParams.empty() ? "" : ",") + " " + p.szType + " fbneo_in_" + p.szName + p.szArray + (p.szInSem.empty() ? "" : " : " + p.szInSem); + szVSLocals += "\t" + p.szType + " " + szLocal + p.szArray + " = fbneo_in_" + p.szName + ";\n"; + } else { + szVSLocals += "\t" + p.szType + " " + szLocal + p.szArray + " = (" + p.szType + ")0;\n"; + } + + if (p.bStruct) { + const Struct* s = x.FindStruct(p.szType); + for (size_t m = 0; m < s->Members.size(); m++) { + const Member& mb = s->Members[m]; + Local::Add(V, mb.szType, mb.szArray, NormSem(MemberSem[s->szName][mb.szName]), std::string(szLocal) + "." + mb.szName); + } + } else { + Local::Add(V, p.szType, p.szArray, NormSem(p.szSem), szLocal); + } + } + + std::string szVSRet = x.Toks[pVS->nReturnType].szText; + std::string szVSCall; + if (szVSRet != "void") { + szVSCall = "\t" + szVSRet + " fbneo_ret = fbneo_main_vertex(" + szVSArgs + ");\n"; + const Struct* s = x.FindStruct(szVSRet); + if (s) { + for (size_t m = 0; m < s->Members.size(); m++) { + const Member& mb = s->Members[m]; + Local::Add(V, mb.szType, mb.szArray, NormSem(MemberSem[s->szName][mb.szName]), "fbneo_ret." + mb.szName); + } + } else if (pVS->nSemantic) { + Local::Add(V, szVSRet, "", NormSem(x.Toks[pVS->nSemantic].szText), "fbneo_ret"); + } + } else { + szVSCall = "\tfbneo_main_vertex(" + szVSArgs + ");\n"; + } + + // ---- pixel shader wrapper: build the inputs from the shared struct + std::string szPSParams = " fbneo_v2f fbneo_in", szPSLocals, szPSArgs; + for (size_t i = 0; i < EntryParams[1].size(); i++) { + const EntryParam& p = EntryParams[1][i]; + char szLocal[32]; + sprintf(szLocal, "fbneo_l%d", (int)i); + + if (p.bOut) { + szPSParams += "," + p.szText; + szPSArgs += std::string(i ? ", " : "") + p.szName; + continue; + } + + std::string szSemCheck = NormSem(p.szSem); + if (!p.bStruct && StartsWith(szSemCheck, "SV_") && szSemCheck != "SV_POSITION") { + szPSParams += "," + p.szText; // SV_IsFrontFace etc. + szPSArgs += std::string(i ? ", " : "") + p.szName; + continue; + } + + szPSArgs += std::string(i ? ", " : "") + szLocal; + + if (p.bStruct) { + const Struct* s = x.FindStruct(p.szType); + szPSLocals += "\t" + p.szType + " " + szLocal + " = (" + p.szType + ")0;\n"; + for (size_t m = 0; m < s->Members.size(); m++) { + const Member& mb = s->Members[m]; + std::string szSem = NormSem(MemberSem[s->szName][mb.szName]); + if (szSem.empty() || szSem == "0") continue; + int n = Local::Find(V, szSem); + if (n < 0) { + Local::Add(V, mb.szType, mb.szArray, szSem, ""); + n = (int)V.size() - 1; + } + char szField[32]; + sprintf(szField, "fbneo_in.v%d", n); + szPSLocals += "\t" + std::string(szLocal) + "." + mb.szName + " = " + (mb.szArray.empty() ? Convert(mb.szType, V[n].szType, szField) : std::string(szField)) + ";\n"; + } + continue; + } + + std::string szSem = szSemCheck; + if (szSem.empty() || szSem == "0") { + szPSLocals += "\t" + p.szType + " " + szLocal + p.szArray + " = (" + p.szType + ")0;\n"; + continue; + } + int n = Local::Find(V, szSem); + if (n < 0) { + Local::Add(V, p.szType, p.szArray, szSem, ""); + n = (int)V.size() - 1; + } + char szField[32]; + sprintf(szField, "fbneo_in.v%d", n); + szPSLocals += "\t" + p.szType + " " + szLocal + p.szArray + " = " + (p.szArray.empty() ? Convert(p.szType, V[n].szType, szField) : std::string(szField)) + ";\n"; + } + + // ---- the shared struct + std::string Out = "\n// Stage linkage generated by FBNeo\nstruct fbneo_v2f {\n"; + bool bHasPosition = false; + for (size_t i = 0; i < V.size(); i++) { + std::string szSem = V[i].szSem; + if (szSem == "SV_POSITION") { + szSem = "SV_Position"; + bHasPosition = true; + } + char szField[32]; + sprintf(szField, "v%d", (int)i); + Out += "\t" + std::string(IsIntegerType(V[i].szType) ? "nointerpolation " : "") + V[i].szType + " " + szField + V[i].szArray + " : " + szSem + ";\n"; + } + if (!bHasPosition) { + Out += "\tfloat4 fbneo_pos : SV_Position;\n"; + } + Out += "};\n\n"; + + // ---- vertex shader wrapper + Out += "fbneo_v2f main_vertex(" + szVSParams + ")\n{\n" + szVSLocals + szVSCall + "\tfbneo_v2f fbneo_out = (fbneo_v2f)0;\n"; + for (size_t i = 0; i < V.size(); i++) { + if (V[i].szSource.empty()) continue; + char szField[32]; + sprintf(szField, "fbneo_out.v%d", (int)i); + Out += std::string("\t") + szField + " = " + V[i].szSource + ";\n"; + } + Out += "\treturn fbneo_out;\n}\n\n"; + + // ---- pixel shader wrapper + std::string szPSRet = x.Toks[pPS->nReturnType].szText; + std::string szPSSem; + if (pPS->nSemantic) { + szPSSem = " : " + x.Toks[pPS->nSemantic].szText; + } + Out += szPSRet + " main_fragment(" + szPSParams + ")" + szPSSem + "\n{\n" + szPSLocals; + Out += std::string(szPSRet == "void" ? "\t" : "\treturn ") + "fbneo_main_fragment(" + szPSArgs + ");\n}\n"; + + // ---- the original functions become ordinary functions + x.Toks[pVS->nName].szText = "fbneo_main_vertex"; + x.Toks[pPS->nName].szText = "fbneo_main_fragment"; + + // An entry point may end a path with 'discard' and no return value, an ordinary function may not + if (szPSRet != "void") { + x.Toks[pPS->nBodyClose].szBefore += "\n\treturn (" + szPSRet + ")0;\n"; + } + Function* Fn[2] = { pVS, pPS }; + for (int f = 0; f < 2; f++) { + if (Fn[f]->nSemantic) { + x.Toks[Fn[f]->nSemantic].szText = ""; + x.Toks[Fn[f]->nSemantic - 1].szText = ""; // the ':' + } + } + + return Out; +} + bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std::string* pError) { Translator x; @@ -1100,21 +1584,85 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: for (size_t k = x.Functions[i].nOpen + 1; k < x.Functions[i].nClose; k++) { if (x.Toks[k].nType == T_IDENT && x.Toks[k].szText == "static") x.Toks[k].szText = ""; } + + // Cg accepts functions where not every path returns a value, HLSL doesn't: add an (unreachable) return + // (the entry points are handled by MakeStageWrappers()) + { + const Function& f = x.Functions[i]; + const std::string& szRet = x.Toks[f.nReturnType].szText; + if (szRet != "void" && f.szName != "main_vertex" && f.szName != "main_fragment" && f.nReturnType < f.nName) { + x.Toks[f.nBodyClose].szBefore += "\n\treturn (" + szRet + ")0;\n"; + } + } + + // Cg doesn't require a function to write all of its 'out' parameters, HLSL does: zero them first + { + const Function& f = x.Functions[i]; + for (size_t pi = 0; pi < f.Params.size(); pi++) { + const Param& p = f.Params[pi]; + if (!p.bOut || p.bIn || p.szName.empty() || !p.szArray.empty() || IsSamplerType(p.szType)) continue; + x.Toks[f.nBodyOpen].szAfter += "\n\t" + p.szName + " = (" + p.szType + ")0;"; + } + } + + // Cg doesn't mind locals being read before they're written, the HLSL compiler may refuse: + // give uninitialised scalar / vector / matrix / struct locals a zero initialiser + { + const Function& f = x.Functions[i]; + for (size_t k = f.nBodyOpen + 1; k + 2 < f.nBodyClose; k++) { + const std::string& szPrev = x.Toks[k - 1].szText; + if (szPrev != ";" && szPrev != "{" && szPrev != "}") continue; + if (x.Toks[k].nType != T_IDENT || x.Toks[k + 1].nType != T_IDENT) continue; + const std::string& szType = x.Toks[k].szText; + if (!IsVectorType(szType) && !IsScalarOrMatrixType(szType) && !x.FindStruct(szType)) continue; + + // declarators up to the ';' + size_t d = k + 1; + while (d < f.nBodyClose && x.Toks[d].nType == T_IDENT) { + size_t nName = d; + d++; + bool bArray = false, bInit = false; + if (x.Toks[d].szText == "[") { d = x.Match(d) + 1; bArray = true; } + if (x.Toks[d].szText == ":") { d += 2; } // semantic + if (x.Toks[d].szText == "=") { + bInit = true; + while (d < f.nBodyClose && x.Toks[d].szText != "," && x.Toks[d].szText != ";") { + if (x.Toks[d].szText == "(" || x.Toks[d].szText == "[" || x.Toks[d].szText == "{") d = x.Match(d); + d++; + } + } + if (!bInit && !bArray && (x.Toks[d].szText == "," || x.Toks[d].szText == ";")) { + x.Toks[nName].szAfter += " = (" + szType + ")0"; + } + if (x.Toks[d].szText != ",") break; + d++; + } + } + } + + // Cg lets 'static const' locals be initialised from parameters; HLSL needs constant + // initialisers for static locals, but plain 'const' locals work the same here + for (size_t k = x.Functions[i].nBodyOpen + 1; k + 1 < x.Functions[i].nBodyClose; k++) { + if (x.Toks[k].nType == T_IDENT && x.Toks[k].szText == "static" && x.Toks[k + 1].szText == "const") x.Toks[k].szText = ""; + } } // Cg lets shaders write to global variables; in HLSL they have to be static for (size_t d = 0; d < x.Decls.size(); d++) { GlobalDecl& g = x.Decls[d]; bool bQualified = false; + size_t nUniform = 0; size_t nType = g.nFirst; while (nType <= g.nLast && x.Toks[nType].nType == T_IDENT && IsQualifier(x.Toks[nType].szText)) { const std::string& q = x.Toks[nType].szText; - if (q == "uniform" || q == "static" || q == "const") bQualified = true; + if (q == "static" || q == "const") bQualified = true; + if (q == "uniform") nUniform = nType; nType++; } if (bQualified || nType > g.nLast || x.Toks[nType].nType != T_IDENT || x.Toks[nType].szText == "struct" || x.Toks[nType].szText == "typedef") continue; + if (nUniform && IsSamplerType(x.Toks[nType].szText)) continue; - bool bStatic = (x.FindStruct(x.Toks[nType].szText) != NULL); + bool bStatic = (x.FindStruct(x.Toks[nType].szText) != NULL) && nUniform == 0; for (size_t f = 0; f < x.Functions.size() && !bStatic; f++) { for (size_t k = x.Functions[f].nBodyOpen + 1; k < x.Functions[f].nBodyClose && !bStatic; k++) { @@ -1137,6 +1685,9 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: } if (bStatic) { + if (nUniform) { + x.Toks[nUniform].szText = ""; + } x.Toks[g.nFirst].szBefore += "static "; } } @@ -1201,6 +1752,48 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: } } + // Pass inputs used without being declared, e.g. "MASK_RESIZE.texture" in a branch that can't be + // taken (crt-royale's integrated graphics versions): Cg tolerates these, HLSL doesn't, so declare + // them as if they were uniform parameters (they're bound by name, like any pass input) + { + const Struct* pPassStruct = NULL; + for (std::set::iterator it = UniformStructs.begin(); it != UniformStructs.end() && pPassStruct == NULL; ++it) { + const Struct* s = x.FindStruct(*it); + bool bTexture = false, bTextureSize = false; + for (size_t m = 0; m < s->Members.size(); m++) { + if (s->Members[m].szName == "texture") bTexture = true; + if (s->Members[m].szName == "texture_size") bTextureSize = true; + } + if (bTexture && bTextureSize) pPassStruct = s; + } + + if (pPassStruct) { + std::set Candidates, DeclaredNames; + for (size_t fi = 0; fi < x.Functions.size(); fi++) { + for (size_t i = 0; i < x.Functions[fi].Params.size(); i++) { + DeclaredNames.insert(x.Functions[fi].Params[i].szName); + } + } + for (size_t i = 0; i + 2 < x.Toks.size(); i++) { + const Token& t = x.Toks[i]; + if (t.nType != T_IDENT) continue; + if (i > 0 && x.Toks[i - 1].nType == T_IDENT) { + DeclaredNames.insert(t.szText); // "type name" + } + if (i > 0 && x.Toks[i - 1].szText == ".") continue; + if (x.Toks[i + 1].szText != ".") continue; + const std::string& m = x.Toks[i + 2].szText; + if (m == "texture" || m == "texture_size" || m == "video_size" || m == "tex_coord") { + Candidates.insert(t.szText); + } + } + for (std::set::iterator it = Candidates.begin(); it != Candidates.end(); ++it) { + if (Flatten.count(*it) || DeclaredNames.count(*it) || x.GlobalNames.count(*it) || x.FindStruct(*it)) continue; + Flatten[*it] = pPassStruct->szName; + } + } + } + // ------------------------------------------------------------- // New global declarations @@ -1340,6 +1933,10 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: Function* f = Entries[e]; if (f == NULL) continue; for (size_t i = 0; i < f->Params.size(); i++) { + // Cg numbers the vertex shader's outputs independently: ignore the vertex attributes, and the + // fragment shader's inputs (they name the vertex outputs they read) + if (f == pVS && !f->Params[i].bOut) continue; + if (f != pVS && pVS && !f->Params[i].bOut) continue; const std::string& sem = f->Params[i].szSemantic; if (StartsWith(sem, "TEXCOORD")) UsedTexCoords.insert(IsNumber(sem.substr(8)) ? atoi(sem.substr(8).c_str()) : 0); } @@ -1351,6 +1948,7 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: // Entry point parameters std::string VSBodyStart, PSBodyStart; + std::vector EntryParams[2]; // the entry points' remaining (varying / output) parameters for (int e = 0; e < 2; e++) { Function* f = Entries[e]; @@ -1387,6 +1985,7 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: while (k < p.nLast && x.Toks[k].szText != p.szName) k++; if (k < p.nLast && x.Toks[k + 1].szText == "[") k = x.Match(k + 1); x.Toks[k].szAfter += szSem; + p.szSemantic = szSem + 3; } // semantics @@ -1399,10 +1998,31 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: } Kept.push_back(x.Emit(p.nFirst, p.nLast)); + + EntryParam ep; + ep.szType = p.szType; + ep.szName = p.szName; + ep.szArray = p.szArray; + ep.szInSem = p.szSemantic; + ep.szSem = p.nSemantic ? Upper(x.Toks[p.nSemantic].szText) : p.szSemantic; + ep.bOut = p.bOut; + ep.bIn = p.bIn || !p.bOut; + ep.bStruct = (x.FindStruct(p.szType) != NULL); + ep.szText = Kept.back(); + EntryParams[e].push_back(ep); } if (bVS && bTexCoordUsedInVS) { Kept.push_back(" float2 fbneo_uv01_in : FBNEOUV"); + EntryParam ep; + ep.szType = "float2"; + ep.szName = "fbneo_uv01_in"; + ep.szInSem = ep.szSem = "FBNEOUV"; + ep.bOut = false; + ep.bIn = true; + ep.bStruct = false; + ep.szText = Kept.back(); + EntryParams[e].push_back(ep); for (std::set::iterator it = TexCoordStructs.begin(); it != TexCoordStructs.end(); ++it) { VSBodyStart += "\n\t" + *it + "_tex_coord = fbneo_uv01_in * " + *it + "_video_size / " + *it + "_texture_size;"; } @@ -1447,9 +2067,16 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: // ------------------------------------------------------------- // Struct definitions + std::map > MemberSem; // varying struct -> member -> semantic + for (size_t si = 0; si < x.Structs.size(); si++) { Struct& s = x.Structs[si]; + // 'uniform' isn't valid on a struct member in HLSL (Cg ignores it) + for (size_t k = s.nOpen + 1; k < s.nClose; k++) { + if (x.Toks[k].nType == T_IDENT && x.Toks[k].szText == "uniform") x.Toks[k].szText = ""; + } + if (UniformStructs.count(s.szName)) { // uniform struct: remove sampler members, 'uniform' qualifiers and TEXUNIT semantics int nRemaining = 0; @@ -1500,12 +2127,14 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: if (bVarying && (sem == "POSITION" || sem == "POSITION0" || sem == "POS" || sem == "VPOS" || sem == "WPOS")) SetSemantic(x.Toks[mb.nSemantic], "SV_Position"); if (bPSOut && StartsWith(sem, "COLOR")) SetSemantic(x.Toks[mb.nSemantic], "SV_Target" + sem.substr(5)); if (bPSOut && sem == "DEPTH") SetSemantic(x.Toks[mb.nSemantic], "SV_Depth"); + if (bVarying) MemberSem[s.szName][mb.szName] = Upper(x.Toks[mb.nSemantic].szText); } else if (bVarying) { // Cg assigns semantics automatically; HLSL needs them spelled out while (Used.count(nNext)) nNext++; char szSem[32]; sprintf(szSem, " : TEXCOORD%d", nNext); Used.insert(nNext); + MemberSem[s.szName][mb.szName] = szSem + 3; // insert after the name (and array) size_t k = mb.nFirst; while (k < mb.nLast && x.Toks[k].szText != mb.szName) k++; @@ -1546,6 +2175,22 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: x.Toks[s.nEnd].szAfter += szMaker; } + // ------------------------------------------------------------- + // Sampler parameters of helper functions: add a SamplerState parameter after each, so lookups use + // the right sampler (filter / wrap mode) for whichever texture is passed in + + for (size_t fi = 0; fi < x.Functions.size(); fi++) { + Function& f = x.Functions[fi]; + if (&f == pVS || &f == pPS) continue; + for (size_t i = 0; i < f.Params.size(); i++) { + const Param& p = f.Params[i]; + if (!IsSamplerType(p.szType) || p.bOut || p.szName.empty() || !p.szArray.empty()) continue; + x.Toks[p.nLast].szAfter += ", SamplerState fbneo_smp_" + p.szName; + x.ParamTextures.insert(p.szName); + x.SamplerParamFuncs[f.szName].insert(i); + } + } + // ------------------------------------------------------------- // Global sampler declarations -> Texture2D + SamplerState @@ -1577,6 +2222,22 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: while (nSemi + 1 < x.Toks.size() && x.Toks[nSemi].szText != ";") nSemi++; x.Toks[nSemi].szAfter += "\nSamplerState fbneo_smp_" + szName + ";"; x.GlobalTextures.insert(szName); + } else if (nDepth > 0 && x.Toks[i + 1].nType == T_IDENT && i + 3 < x.Toks.size() && x.Toks[i + 2].szText == "=") { + // local alias: "sampler2D a = b;" -> the sampler state goes along with it + std::string szName = x.Toks[i + 1].szText; + std::string szSource; + int nTokens = 0; + size_t k = i + 3; + for (; k < x.Toks.size() && x.Toks[k].szText != ";"; k++) { + if (x.Toks[k].szText.empty()) continue; + szSource = x.Toks[k].szText; + nTokens++; + } + if (k < x.Toks.size()) { + bool bKnown = (nTokens == 1) && (x.GlobalTextures.count(szSource) || x.ParamTextures.count(szSource)); + x.Toks[k].szAfter += " SamplerState fbneo_smp_" + szName + " = " + (bKnown ? "fbneo_smp_" + szSource : std::string("fbneo_smp")) + ";"; + x.ParamTextures.insert(szName); + } } if (t.szText == "sampler1D") t.szText = "Texture1D"; @@ -1597,6 +2258,7 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: "#define triangle fbneo_triangle\n#define linear fbneo_linear\n#define pass fbneo_pass\n#define string fbneo_string\n" "#define precise fbneo_precise\n#define shared fbneo_shared\n#define snorm fbneo_snorm\n#define unorm fbneo_unorm\n" "#define technique fbneo_technique\n#define compile fbneo_compile\n#define lineadj fbneo_lineadj\n#define triangleadj fbneo_triangleadj\n" + "#define matrix fbneo_matrix\n#define vector fbneo_vector\n" "#ifdef FBNEO_LEVEL9\n" " #define FBNEO_SAMPLE_LEVEL(t, s, uv, l) t.Sample(s, uv)\n" " #define FBNEO_SAMPLE_BIAS(t, s, uv, b) t.Sample(s, uv)\n" @@ -1614,6 +2276,7 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: { static const struct { const char* szName; const char* szDefine; } Glsl[] = { { "fract", "#define fract frac\n" }, + { "double", "#define double float\n" }, { "double2", "#define double2 float2\n" }, { "double3", "#define double3 float3\n" }, { "double4", "#define double4 float4\n" }, { "mix", "#define mix lerp\n" }, { "mod", "#define mod(x, y) ((x) - (y) * floor((x) / (y)))\n" }, { "inversesqrt", "#define inversesqrt rsqrt\n" }, @@ -1634,7 +2297,7 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: for (size_t i = 1; i < x.Toks.size() && !bLocal; i++) { if (x.Toks[i].szText != Glsl[g].szName || x.Toks[i - 1].nType != T_IDENT) continue; const std::string& p = x.Toks[i - 1].szText; - if (p != "return" && p != "else" && p != "case" && p != "do") bLocal = true; + if (p != "return" && p != "else" && p != "case" && p != "do" && !IsQualifier(p) && p != "inline" && p != "typedef") bLocal = true; } if (!bLocal) { Prelude += Glsl[g].szDefine; @@ -1643,7 +2306,17 @@ bool D3D11TranslateCg(const std::string& Source, D3D11CgTranslation* pOut, std:: } } - pOut->szHLSL = Prelude + Globals + x.Emit(0, x.Toks.size() - 1); + // ------------------------------------------------------------- + // Cg links the vertex shader outputs to the pixel shader inputs by semantic, Direct3D 11 by + // register. So the entry points become wrappers around the original functions, passing one + // struct (built from the vertex shader outputs) between the two stages. + + std::string Wrappers; + if (pVS && pPS) { + Wrappers = MakeStageWrappers(x, pVS, pPS, EntryParams, MemberSem); + } + + pOut->szHLSL = Prelude + Globals + x.Emit(0, x.Toks.size() - 1) + Wrappers; return true; } diff --git a/src/intf/video/win32/vid_d3d11_shader.h b/src/intf/video/win32/vid_d3d11_shader.h index 26e1957b0..7df0a304d 100644 --- a/src/intf/video/win32/vid_d3d11_shader.h +++ b/src/intf/video/win32/vid_d3d11_shader.h @@ -50,9 +50,10 @@ struct D3D11ShaderLUT { std::string szName; std::string szPath; bool bLinear; + bool bMipmap; int nWrap; - D3D11ShaderLUT() : bLinear(true), nWrap(D3D11_WRAP_BORDER) { } + D3D11ShaderLUT() : bLinear(true), bMipmap(false), nWrap(D3D11_WRAP_BORDER) { } }; struct D3D11ShaderPreset { @@ -81,6 +82,10 @@ bool D3D11ShaderLoadPreset(const std::string& szPath, D3D11ShaderReadFileFn pRea // Inline #include "..." directives (recursively, relative to the including file) bool D3D11ShaderInlineIncludes(const std::string& szPath, D3D11ShaderReadFileFn pRead, std::string& Source, std::string* pError); +// Work around a bug in D3DPreprocess(): a function-like macro whose name is also used as a plain +// identifier (e.g. "#define X(i) ..." and a variable X) is renamed, together with its calls +void D3D11ShaderFixMacroNames(std::string& Source); + // Add the "#pragma parameter" declarations found in Source to Params (duplicates are ignored) void D3D11ShaderExtractParams(const std::string& Source, std::vector& Params);