[texconv] Implemented alpha coverage preservation option (#82)

This commit is contained in:
Julian McKinlay
2019-01-28 19:57:19 +00:00
committed by Chuck Walbourn
parent a79989a4f0
commit f2c4d94a35
3 changed files with 331 additions and 5 deletions

View File

@@ -534,6 +534,11 @@ namespace DirectX
// levels of '0' indicates a full mipchain, otherwise is generates that number of total levels (including the source base image)
// Defaults to Fant filtering which is equivalent to a box filter
HRESULT __cdecl ScaleMipMapsAlphaForCoverage(
_In_ const Image* srcImages, _In_ const TexMetadata& metadata, _In_ size_t item,
_In_ float alphaReference, _Out_ ScratchImage& mipChain);
enum TEX_PMALPHA_FLAGS
{
TEX_PMALPHA_DEFAULT = 0,

View File

@@ -116,6 +116,211 @@ namespace
return hr;
}
HRESULT ScaleAlpha(
_In_ const Image& srcImage,
_In_ float alphaScale,
_Out_ const Image& destImage)
{
assert(srcImage.width == destImage.width);
assert(srcImage.height == destImage.height);
ScopedAlignedArrayXMVECTOR scanline(reinterpret_cast<XMVECTOR*>(_aligned_malloc((sizeof(XMVECTOR)*srcImage.width), 16)));
if (!scanline)
{
return E_OUTOFMEMORY;
}
const uint8_t* pSrc = srcImage.pixels;
uint8_t* pDest = destImage.pixels;
if (!pSrc || !pDest)
{
return E_POINTER;
}
const XMVECTOR vscale = XMVectorReplicate(alphaScale);
for (size_t h = 0; h < srcImage.height; ++h)
{
if (!_LoadScanline(scanline.get(), srcImage.width, pSrc, srcImage.rowPitch, srcImage.format))
{
return E_FAIL;
}
XMVECTOR* ptr = scanline.get();
for (size_t w = 0; w < srcImage.width; ++w)
{
XMVECTOR v = *ptr;
XMVECTOR alpha = XMVectorMultiply(XMVectorSplatW(v), vscale);
*(ptr++) = XMVectorSelect(alpha, v, g_XMSelect1110);
}
if (!_StoreScanline(pDest, destImage.rowPitch, destImage.format, scanline.get(), srcImage.width))
{
return E_FAIL;
}
pSrc += srcImage.rowPitch;
pDest += destImage.rowPitch;
}
return S_OK;
}
void GenerateAlphaCoverageConvolutionVectors(
_In_ size_t N,
_Out_writes_(N*N) XMVECTOR* vectors)
{
for (size_t sy = 0; sy < N; ++sy)
{
const float fy = (sy + 0.5f) / N;
const float ify = 1.0f - fy;
for (size_t sx = 0; sx < N; ++sx)
{
const float fx = (sx + 0.5f) / N;
const float ifx = 1.0f - fx;
// [0]=(x+0, y+0), [1]=(x+0, y+1), [2]=(x+1, y+0), [3]=(x+1, y+1)
vectors[sy * N + sx] = XMVectorSet(ifx * ify, ifx * fy, fx * ify, fx * fy);
}
}
}
HRESULT CalculateAlphaCoverage(
const Image& srcImage,
float alphaReference,
float alphaScale,
float& coverage)
{
coverage = 0.0f;
ScopedAlignedArrayXMVECTOR row0(reinterpret_cast<XMVECTOR*>(_aligned_malloc((sizeof(XMVECTOR)*srcImage.width), 16)));
if (!row0)
{
return E_OUTOFMEMORY;
}
ScopedAlignedArrayXMVECTOR row1(reinterpret_cast<XMVECTOR*>(_aligned_malloc((sizeof(XMVECTOR)*srcImage.width), 16)));
if (!row1)
{
return E_OUTOFMEMORY;
}
const DWORD flags = 0;
const XMVECTOR scale = XMVectorReplicate(alphaScale);
const uint8_t *pSrcRow0 = srcImage.pixels;
if (!pSrcRow0)
{
return E_POINTER;
}
const size_t N = 8;
XMVECTOR convolution[N * N];
GenerateAlphaCoverageConvolutionVectors(N, convolution);
XMMATRIX alpha;
size_t coverageCount = 0;
for (size_t y = 0; y < srcImage.height - 1; ++y)
{
if (!_LoadScanlineLinear(row0.get(), srcImage.width, pSrcRow0, srcImage.rowPitch, srcImage.format, flags))
{
return E_FAIL;
}
const uint8_t *pSrcRow1 = pSrcRow0 + srcImage.rowPitch;
if (!_LoadScanlineLinear(row1.get(), srcImage.width, pSrcRow1, srcImage.rowPitch, srcImage.format, flags))
{
return E_FAIL;
}
const XMVECTOR* pRow0 = row0.get();
const XMVECTOR* pRow1 = row1.get();
for (size_t x = 0; x < srcImage.width - 1; ++x)
{
// [0]=(x+0, y+0), [1]=(x+0, y+1), [2]=(x+1, y+0), [3]=(x+1, y+1)
alpha.r[0] = XMVectorSaturate(XMVectorMultiply(XMVectorSplatW(*pRow0), scale));
alpha.r[1] = XMVectorSaturate(XMVectorMultiply(XMVectorSplatW(*pRow1), scale));
alpha.r[2] = XMVectorSaturate(XMVectorMultiply(XMVectorSplatW(*(pRow0++)), scale));
alpha.r[3] = XMVectorSaturate(XMVectorMultiply(XMVectorSplatW(*(pRow1++)), scale));
XMVECTOR v = XMVectorSet(XMVectorGetX(alpha.r[0]), XMVectorGetX(alpha.r[1]), XMVectorGetX(alpha.r[2]), XMVectorGetX(alpha.r[3]));
for (size_t sy = 0; sy < N; ++sy)
{
const size_t ry = sy * N;
for (size_t sx = 0; sx < N; ++sx)
{
v = XMVectorSum(XMVectorMultiply(v, convolution[ry + sx]));
if (XMVectorGetX(v) > alphaReference)
{
++coverageCount;
}
}
}
}
pSrcRow0 = pSrcRow1;
}
coverage = static_cast<float>(coverageCount) / static_cast<float>((srcImage.width - 1) * (srcImage.height - 1) * N * N);
return S_OK;
}
HRESULT EstimateAlphaScaleForCoverage(
_In_ const Image& srcImage,
_In_ float alphaReference,
_In_ float targetCoverage,
_Out_ float& alphaScale)
{
float minAlphaScale = 0.0f;
float maxAlphaScale = 4.0f;
float bestAlphaScale = 1.0f;
float bestError = FLT_MAX;
// Determine desired scale using a binary search. Hardcoded to 10 steps max.
alphaScale = 1.0f;
const size_t N = 10;
for (size_t i = 0; i < N; ++i)
{
float currentCoverage = 0.0f;
HRESULT hr = CalculateAlphaCoverage(srcImage, alphaReference, alphaScale, currentCoverage);
if (FAILED(hr))
{
return hr;
}
const float error = fabsf(currentCoverage - targetCoverage);
if (error < bestError)
{
bestError = error;
bestAlphaScale = alphaScale;
}
if (currentCoverage < targetCoverage)
{
minAlphaScale = alphaScale;
}
else if (currentCoverage > targetCoverage)
{
maxAlphaScale = alphaScale;
}
else
{
break;
}
alphaScale = (minAlphaScale + maxAlphaScale) * 0.5f;
}
return S_OK;
}
}
@@ -3179,3 +3384,40 @@ HRESULT DirectX::GenerateMipMaps3D(
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
}
}
_Use_decl_annotations_
HRESULT DirectX::ScaleMipMapsAlphaForCoverage(
const Image* srcImages,
const TexMetadata& metadata,
size_t item,
float alphaReference,
ScratchImage& mipChain)
{
HRESULT hr;
float targetCoverage = 0.0f;
hr = CalculateAlphaCoverage(*srcImages, alphaReference, 1.0f, targetCoverage);
if (FAILED(hr))
{
return hr;
}
const size_t levels = metadata.mipLevels;
for (size_t level = 1; level < levels; ++level)
{
float alphaScale = 0.0f;
hr = EstimateAlphaScaleForCoverage(srcImages[level], alphaReference, targetCoverage, alphaScale);
if (FAILED(hr))
{
return hr;
}
hr = ScaleAlpha(srcImages[level], alphaScale, *mipChain.GetImage(level, item, 0));
if (FAILED(hr))
{
return hr;
}
}
return S_OK;
}