Commit a4f706b2 by Le Hoang Quyen Committed by Commit Bot

Metal: Use compute to generate 3D texture's mipmap.

- Metal's built-in blit based mipmap generator doesn't use box filtering. Hence manual generation using compute is needed. - Compute based mipmap gen can generate up to 4 mips per pass if the base level is power of 2. - This approach can be extended to 2D/cube texture's mipmap generation in future. Bug: angleproject:4921 Bug: angleproject:2634 Change-Id: I7f997669fe39afef075b2bca2406e9424cbb3016 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2336120Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarJonah Ryan-Davis <jonahr@google.com> Commit-Queue: Le Hoang Quyen <le.hoang.q@gmail.com>
parent 5d3a4ca4
......@@ -59,6 +59,10 @@ struct FeaturesMtl : FeatureSetBase
Feature allowMultisampleStoreAndResolve = {
"allow_msaa_store_and_resolve", FeatureCategory::MetalFeatures,
"The renderer supports MSAA store and resolve in the same pass", &members};
Feature allowGenMultipleMipsPerPass = {
"gen_multiple_mips_per_pass", FeatureCategory::MetalFeatures,
"The renderer supports generating multiple mipmaps per pass", &members};
};
} // namespace angle
......
......@@ -686,6 +686,7 @@ void DisplayMtl::initializeFeatures()
mFeatures.hasStencilOutput.enabled = false;
mFeatures.hasTextureSwizzle.enabled = false;
mFeatures.allowSeparatedDepthStencilBuffers.enabled = false;
mFeatures.allowGenMultipleMipsPerPass.enabled = true;
ANGLE_FEATURE_CONDITION((&mFeatures), hasDepthTextureFiltering,
TARGET_OS_OSX || TARGET_OS_MACCATALYST);
......
......@@ -180,6 +180,7 @@ class TextureMtl : public TextureImpl
// Ensure all image views at all faces/levels are retained.
void retainImageDefinitions();
mtl::TextureRef createImageViewFromNativeTexture(GLuint cubeFaceOrZero, GLuint nativeLevel);
angle::Result ensureNativeLevelViewsCreated();
angle::Result checkForEmulatedChannels(const gl::Context *context,
const mtl::Format &mtlFormat,
const mtl::TextureRef &texture);
......@@ -314,6 +315,7 @@ class TextureMtl : public TextureImpl
// Stored images array defined by glTexImage/glCopy*.
// Once the images array is complete, they will be transferred to real texture object.
// NOTE:
// - The second dimension is indexed by configured base level + actual native level
// - For Cube map, there will be at most 6 entries in the mTexImageDefs table, one for each
// face. This is because the Cube map's image is defined per face & per level.
// - For other texture types, there will be only one entry in the map table. All other textures
......@@ -324,8 +326,12 @@ class TextureMtl : public TextureImpl
// - For 2D texture: There will be one key entry in the map.
// - For Cube map: There will be at most 6 key entries.
// - For array/3D texture: There will be at most slices/depths number of key entries.
// - The second dimension is indexed by configured base level + actual native level
std::map<int, gl::TexLevelArray<RenderTargetMtl>> mPerLayerRenderTargets;
// Mipmap views are indexed by native level (ignored base level):
gl::TexLevelArray<mtl::TextureRef> mNativeLevelViews;
bool mIsPow2 = false;
};
......
......@@ -430,6 +430,11 @@ void TextureMtl::releaseTexture(bool releaseImages, bool releaseTextureObjectsOn
}
}
for (mtl::TextureRef &view : mNativeLevelViews)
{
view.reset();
}
if (!releaseTextureObjectsOnly)
{
mMetalSamplerState = nil;
......@@ -575,6 +580,31 @@ angle::Result TextureMtl::ensureImageCreated(const gl::Context *context,
return angle::Result::Continue;
}
angle::Result TextureMtl::ensureNativeLevelViewsCreated()
{
ASSERT(mNativeTexture);
const GLuint baseLevel = mState.getEffectiveBaseLevel();
for (uint32_t mip = 0; mip < mNativeTexture->mipmapLevels(); ++mip)
{
if (mNativeLevelViews[mip])
{
continue;
}
if (mNativeTexture->textureType() != MTLTextureTypeCube &&
mTexImageDefs[0][mip + baseLevel].image)
{
// Reuse texture image view.
mNativeLevelViews[mip] = mTexImageDefs[0][mip + baseLevel].image;
}
else
{
mNativeLevelViews[mip] = mNativeTexture->createMipView(mip);
}
}
return angle::Result::Continue;
}
mtl::TextureRef TextureMtl::createImageViewFromNativeTexture(GLuint cubeFaceOrZero,
GLuint nativeLevel)
{
......@@ -586,7 +616,15 @@ mtl::TextureRef TextureMtl::createImageViewFromNativeTexture(GLuint cubeFaceOrZe
}
else
{
image = mNativeTexture->createMipView(nativeLevel);
if (mNativeLevelViews[nativeLevel])
{
// Reuse the native level view
image = mNativeLevelViews[nativeLevel];
}
else
{
image = mNativeTexture->createMipView(nativeLevel);
}
}
return image;
......@@ -927,7 +965,17 @@ angle::Result TextureMtl::generateMipmap(const gl::Context *context)
const mtl::FormatCaps &caps = mFormat.getCaps();
if (caps.filterable && caps.colorRenderable)
if (caps.writable && mState.getType() == gl::TextureType::_3D)
{
// http://anglebug.com/4921.
// Use compute for 3D mipmap generation.
ANGLE_TRY(ensureNativeLevelViewsCreated());
bool sRGB = mFormat.metalFormat == MTLPixelFormatRGBA8Unorm_sRGB ||
mFormat.metalFormat == MTLPixelFormatBGRA8Unorm_sRGB;
ANGLE_TRY(contextMtl->getDisplay()->getUtils().generateMipmapCS(contextMtl, mNativeTexture,
sRGB, &mNativeLevelViews));
}
else if (caps.filterable && caps.colorRenderable)
{
mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
blitEncoder->generateMipmapsForTexture(mNativeTexture);
......
......@@ -126,7 +126,7 @@ struct IndexGenerationParams
};
// Utils class for clear & blitting
class ClearUtils
class ClearUtils final : angle::NonCopyable
{
public:
ClearUtils();
......@@ -154,7 +154,7 @@ class ClearUtils
std::array<RenderPipelineCache, kMaxRenderTargets + 1> mClearRenderPipelineCache;
};
class ColorBlitUtils
class ColorBlitUtils final : angle::NonCopyable
{
public:
ColorBlitUtils();
......@@ -192,7 +192,7 @@ class ColorBlitUtils
ColorBlitRenderPipelineCacheArray mBlitUnmultiplyAlphaRenderPipelineCache;
};
class DepthStencilBlitUtils
class DepthStencilBlitUtils final : angle::NonCopyable
{
public:
void onDestroy();
......@@ -242,7 +242,7 @@ class DepthStencilBlitUtils
};
// util class for generating index buffer
class IndexGeneratorUtils
class IndexGeneratorUtils final : angle::NonCopyable
{
public:
void onDestroy();
......@@ -310,6 +310,25 @@ class IndexGeneratorUtils
AutoObjCPtr<id<MTLComputePipelineState>> mTriFanFromArraysGeneratorPipeline;
};
// Util class for handling mipmap generation
class MipmapUtils final : angle::NonCopyable
{
public:
void onDestroy();
// Compute based mipmap generation. Only possible for 3D texture for now.
angle::Result generateMipmapCS(ContextMtl *contextMtl,
const TextureRef &srcTexture,
bool sRGBMipmap,
gl::TexLevelArray<mtl::TextureRef> *mipmapOutputViews);
private:
void ensure3DMipGeneratorPipelineInitialized(ContextMtl *contextMtl);
// Mipmaps generating compute pipeline:
AutoObjCPtr<id<MTLComputePipelineState>> m3DMipGeneratorPipeline;
};
// RenderUtils: container class of various util classes above
class RenderUtils : public Context, angle::NonCopyable
{
......@@ -356,6 +375,12 @@ class RenderUtils : public Context, angle::NonCopyable
angle::Result generateLineLoopLastSegmentFromElementsArray(ContextMtl *contextMtl,
const IndexGenerationParams &params);
// Compute based mipmap generation. Only possible for 3D texture for now.
angle::Result generateMipmapCS(ContextMtl *contextMtl,
const TextureRef &srcTexture,
bool sRGBMipmap,
gl::TexLevelArray<mtl::TextureRef> *mipmapOutputViews);
private:
// override ErrorHandler
void handleError(GLenum error,
......@@ -371,6 +396,7 @@ class RenderUtils : public Context, angle::NonCopyable
ColorBlitUtils mColorBlitUtils;
DepthStencilBlitUtils mDepthStencilBlitUtils;
IndexGeneratorUtils mIndexUtils;
MipmapUtils mMipmapUtils;
};
} // namespace mtl
......
......@@ -77,6 +77,15 @@ struct IndexConversionUniform
uint32_t padding[2];
};
// See libANGLE/renderer/metal/shaders/gen_mipmap.metal
struct GenerateMipmapUniform
{
uint32_t srcLevel;
uint32_t numMipmapsToGenerate;
uint8_t sRGB;
uint8_t padding[7];
};
void GetBlitTexCoords(uint32_t srcWidth,
uint32_t srcHeight,
const gl::Rectangle &srcRect,
......@@ -570,6 +579,15 @@ angle::Result RenderUtils::generateLineLoopLastSegmentFromElementsArray(
return mIndexUtils.generateLineLoopLastSegmentFromElementsArray(contextMtl, params);
}
// Compute based mipmap generation
angle::Result RenderUtils::generateMipmapCS(ContextMtl *contextMtl,
const TextureRef &srcTexture,
bool sRGBMipmap,
gl::TexLevelArray<mtl::TextureRef> *mipmapOutputViews)
{
return mMipmapUtils.generateMipmapCS(contextMtl, srcTexture, sRGBMipmap, mipmapOutputViews);
}
// ClearUtils implementation
ClearUtils::ClearUtils() = default;
......@@ -1566,5 +1584,110 @@ angle::Result IndexGeneratorUtils::generateLineLoopLastSegmentFromElementsArrayC
return generateLineLoopLastSegment(contextMtl, first, last, params.dstBuffer, params.dstOffset);
}
// MipmapUtils implementation
void MipmapUtils::onDestroy()
{
m3DMipGeneratorPipeline = nil;
}
void MipmapUtils::ensure3DMipGeneratorPipelineInitialized(ContextMtl *contextMtl)
{
EnsureComputePipelineInitialized(contextMtl->getDisplay(), @"generate3DMipmaps",
&m3DMipGeneratorPipeline);
}
angle::Result MipmapUtils::generateMipmapCS(ContextMtl *contextMtl,
const TextureRef &srcTexture,
bool sRGBMipmap,
gl::TexLevelArray<mtl::TextureRef> *mipmapOutputViews)
{
// Only support 3D texture for now.
ASSERT(srcTexture->textureType() == MTLTextureType3D);
MTLSize threadGroupSize;
uint32_t slices = 1;
id<MTLComputePipelineState> computePipeline = nil;
ensure3DMipGeneratorPipelineInitialized(contextMtl);
computePipeline = m3DMipGeneratorPipeline;
threadGroupSize =
MTLSizeMake(kGenerateMipThreadGroupSizePerDim, kGenerateMipThreadGroupSizePerDim,
kGenerateMipThreadGroupSizePerDim);
// The compute shader supports up to 4 mipmaps generated per pass.
// See shaders/gen_mipmap.metal
uint32_t maxMipsPerBatch = 4;
if (threadGroupSize.width * threadGroupSize.height * threadGroupSize.depth >
computePipeline.maxTotalThreadsPerThreadgroup ||
ANGLE_UNLIKELY(
!contextMtl->getDisplay()->getFeatures().allowGenMultipleMipsPerPass.enabled))
{
// Multiple mipmaps generation is not supported due to hardware's thread group size limits.
// Fallback to generate one mip per pass and reduce thread group size.
if (ANGLE_UNLIKELY(threadGroupSize.width * threadGroupSize.height >
computePipeline.maxTotalThreadsPerThreadgroup))
{
// Even with reduced thread group size, we cannot proceed.
// HACK: use blit command encoder to generate mipmaps if it is not possible
// to use compute shader due to hardware limits.
BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
blitEncoder->generateMipmapsForTexture(srcTexture);
return angle::Result::Continue;
}
threadGroupSize.depth = 1;
maxMipsPerBatch = 1;
}
ComputeCommandEncoder *cmdEncoder = contextMtl->getComputeCommandEncoder();
ASSERT(cmdEncoder);
cmdEncoder->setComputePipelineState(computePipeline);
GenerateMipmapUniform options;
uint32_t remainMips = srcTexture->mipmapLevels() - 1;
options.srcLevel = 0;
options.sRGB = sRGBMipmap;
cmdEncoder->setTexture(srcTexture, 0);
cmdEncoder->markResourceBeingWrittenByGPU(srcTexture);
while (remainMips)
{
const TextureRef &firstMipView = mipmapOutputViews->at(options.srcLevel + 1);
gl::Extents size = firstMipView->size();
bool isPow2 = gl::isPow2(size.width) && gl::isPow2(size.height) && gl::isPow2(size.depth);
// Currently multiple mipmaps generation is only supported for power of two base level.
if (isPow2)
{
options.numMipmapsToGenerate = std::min(remainMips, maxMipsPerBatch);
}
else
{
options.numMipmapsToGenerate = 1;
}
cmdEncoder->setData(options, 0);
for (uint32_t i = 1; i <= options.numMipmapsToGenerate; ++i)
{
cmdEncoder->setTexture(mipmapOutputViews->at(options.srcLevel + i), i);
}
uint32_t threadsPerZ = std::max(slices, firstMipView->depth());
DispatchCompute(contextMtl, cmdEncoder,
/** allowNonUniform */ false,
MTLSizeMake(firstMipView->width(), firstMipView->height(), threadsPerZ),
threadGroupSize);
remainMips -= options.numMipmapsToGenerate;
options.srcLevel += options.numMipmapsToGenerate;
}
return angle::Result::Continue;
}
} // namespace mtl
} // namespace rx
......@@ -250,6 +250,10 @@ Texture::Texture(ContextMtl *context,
if (!renderTargetOnly)
{
desc.usage = desc.usage | MTLTextureUsageShaderRead;
if (context->getNativeFormatCaps(desc.pixelFormat).writable)
{
desc.usage = desc.usage | MTLTextureUsageShaderWrite;
}
}
if (allowFormatView)
......
......@@ -214,6 +214,11 @@ std::ostream &operator<<(std::ostream &stream, const PlatformParameters &pp)
stream << "_NoStencilOutput";
}
if (pp.eglParameters.genMultipleMipsPerPassFeature == EGL_FALSE)
{
stream << "_NoGenMultipleMipsPerPass";
}
return stream;
}
......
......@@ -234,6 +234,13 @@ inline PlatformParameters WithNoShaderStencilOutput(const PlatformParameters &pa
return re;
}
inline PlatformParameters WithNoGenMultipleMipsPerPass(const PlatformParameters &params)
{
PlatformParameters re = params;
re.eglParameters.genMultipleMipsPerPassFeature = EGL_FALSE;
return re;
}
inline PlatformParameters WithRobustness(const PlatformParameters &params)
{
PlatformParameters withRobustness = params;
......
......@@ -61,7 +61,8 @@ struct EGLPlatformParameters
return std::tie(renderer, majorVersion, minorVersion, deviceType, presentPath,
debugLayersEnabled, contextVirtualization, transformFeedbackFeature,
allocateNonZeroMemoryFeature, emulateCopyTexImage2DFromRenderbuffers,
shaderStencilOutputFeature, platformMethods, robustness);
shaderStencilOutputFeature, genMultipleMipsPerPassFeature, platformMethods,
robustness);
}
EGLint renderer = EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE;
......@@ -76,6 +77,7 @@ struct EGLPlatformParameters
EGLint allocateNonZeroMemoryFeature = EGL_DONT_CARE;
EGLint emulateCopyTexImage2DFromRenderbuffers = EGL_DONT_CARE;
EGLint shaderStencilOutputFeature = EGL_DONT_CARE;
EGLint genMultipleMipsPerPassFeature = EGL_DONT_CARE;
angle::PlatformMethods *platformMethods = nullptr;
};
......
......@@ -201,6 +201,11 @@ bool EGLWindow::initializeDisplay(OSWindow *osWindow,
disabledFeatureOverrides.push_back("has_shader_stencil_output");
}
if (params.genMultipleMipsPerPassFeature == EGL_FALSE)
{
disabledFeatureOverrides.push_back("gen_multiple_mips_per_pass");
}
if (!disabledFeatureOverrides.empty())
{
if (strstr(extensionString, "EGL_ANGLE_feature_control") == nullptr)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment