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 ...@@ -59,6 +59,10 @@ struct FeaturesMtl : FeatureSetBase
Feature allowMultisampleStoreAndResolve = { Feature allowMultisampleStoreAndResolve = {
"allow_msaa_store_and_resolve", FeatureCategory::MetalFeatures, "allow_msaa_store_and_resolve", FeatureCategory::MetalFeatures,
"The renderer supports MSAA store and resolve in the same pass", &members}; "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 } // namespace angle
......
...@@ -686,6 +686,7 @@ void DisplayMtl::initializeFeatures() ...@@ -686,6 +686,7 @@ void DisplayMtl::initializeFeatures()
mFeatures.hasStencilOutput.enabled = false; mFeatures.hasStencilOutput.enabled = false;
mFeatures.hasTextureSwizzle.enabled = false; mFeatures.hasTextureSwizzle.enabled = false;
mFeatures.allowSeparatedDepthStencilBuffers.enabled = false; mFeatures.allowSeparatedDepthStencilBuffers.enabled = false;
mFeatures.allowGenMultipleMipsPerPass.enabled = true;
ANGLE_FEATURE_CONDITION((&mFeatures), hasDepthTextureFiltering, ANGLE_FEATURE_CONDITION((&mFeatures), hasDepthTextureFiltering,
TARGET_OS_OSX || TARGET_OS_MACCATALYST); TARGET_OS_OSX || TARGET_OS_MACCATALYST);
......
...@@ -180,6 +180,7 @@ class TextureMtl : public TextureImpl ...@@ -180,6 +180,7 @@ class TextureMtl : public TextureImpl
// Ensure all image views at all faces/levels are retained. // Ensure all image views at all faces/levels are retained.
void retainImageDefinitions(); void retainImageDefinitions();
mtl::TextureRef createImageViewFromNativeTexture(GLuint cubeFaceOrZero, GLuint nativeLevel); mtl::TextureRef createImageViewFromNativeTexture(GLuint cubeFaceOrZero, GLuint nativeLevel);
angle::Result ensureNativeLevelViewsCreated();
angle::Result checkForEmulatedChannels(const gl::Context *context, angle::Result checkForEmulatedChannels(const gl::Context *context,
const mtl::Format &mtlFormat, const mtl::Format &mtlFormat,
const mtl::TextureRef &texture); const mtl::TextureRef &texture);
...@@ -314,6 +315,7 @@ class TextureMtl : public TextureImpl ...@@ -314,6 +315,7 @@ class TextureMtl : public TextureImpl
// Stored images array defined by glTexImage/glCopy*. // Stored images array defined by glTexImage/glCopy*.
// Once the images array is complete, they will be transferred to real texture object. // Once the images array is complete, they will be transferred to real texture object.
// NOTE: // 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 // - 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. // 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 // - 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 ...@@ -324,8 +326,12 @@ class TextureMtl : public TextureImpl
// - For 2D texture: There will be one key entry in the map. // - For 2D texture: There will be one key entry in the map.
// - For Cube map: There will be at most 6 key entries. // - 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. // - 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; 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; bool mIsPow2 = false;
}; };
......
...@@ -430,6 +430,11 @@ void TextureMtl::releaseTexture(bool releaseImages, bool releaseTextureObjectsOn ...@@ -430,6 +430,11 @@ void TextureMtl::releaseTexture(bool releaseImages, bool releaseTextureObjectsOn
} }
} }
for (mtl::TextureRef &view : mNativeLevelViews)
{
view.reset();
}
if (!releaseTextureObjectsOnly) if (!releaseTextureObjectsOnly)
{ {
mMetalSamplerState = nil; mMetalSamplerState = nil;
...@@ -575,6 +580,31 @@ angle::Result TextureMtl::ensureImageCreated(const gl::Context *context, ...@@ -575,6 +580,31 @@ angle::Result TextureMtl::ensureImageCreated(const gl::Context *context,
return angle::Result::Continue; 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, mtl::TextureRef TextureMtl::createImageViewFromNativeTexture(GLuint cubeFaceOrZero,
GLuint nativeLevel) GLuint nativeLevel)
{ {
...@@ -586,8 +616,16 @@ mtl::TextureRef TextureMtl::createImageViewFromNativeTexture(GLuint cubeFaceOrZe ...@@ -586,8 +616,16 @@ mtl::TextureRef TextureMtl::createImageViewFromNativeTexture(GLuint cubeFaceOrZe
} }
else else
{ {
if (mNativeLevelViews[nativeLevel])
{
// Reuse the native level view
image = mNativeLevelViews[nativeLevel];
}
else
{
image = mNativeTexture->createMipView(nativeLevel); image = mNativeTexture->createMipView(nativeLevel);
} }
}
return image; return image;
} }
...@@ -927,7 +965,17 @@ angle::Result TextureMtl::generateMipmap(const gl::Context *context) ...@@ -927,7 +965,17 @@ angle::Result TextureMtl::generateMipmap(const gl::Context *context)
const mtl::FormatCaps &caps = mFormat.getCaps(); 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(); mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
blitEncoder->generateMipmapsForTexture(mNativeTexture); blitEncoder->generateMipmapsForTexture(mNativeTexture);
......
...@@ -126,7 +126,7 @@ struct IndexGenerationParams ...@@ -126,7 +126,7 @@ struct IndexGenerationParams
}; };
// Utils class for clear & blitting // Utils class for clear & blitting
class ClearUtils class ClearUtils final : angle::NonCopyable
{ {
public: public:
ClearUtils(); ClearUtils();
...@@ -154,7 +154,7 @@ class ClearUtils ...@@ -154,7 +154,7 @@ class ClearUtils
std::array<RenderPipelineCache, kMaxRenderTargets + 1> mClearRenderPipelineCache; std::array<RenderPipelineCache, kMaxRenderTargets + 1> mClearRenderPipelineCache;
}; };
class ColorBlitUtils class ColorBlitUtils final : angle::NonCopyable
{ {
public: public:
ColorBlitUtils(); ColorBlitUtils();
...@@ -192,7 +192,7 @@ class ColorBlitUtils ...@@ -192,7 +192,7 @@ class ColorBlitUtils
ColorBlitRenderPipelineCacheArray mBlitUnmultiplyAlphaRenderPipelineCache; ColorBlitRenderPipelineCacheArray mBlitUnmultiplyAlphaRenderPipelineCache;
}; };
class DepthStencilBlitUtils class DepthStencilBlitUtils final : angle::NonCopyable
{ {
public: public:
void onDestroy(); void onDestroy();
...@@ -242,7 +242,7 @@ class DepthStencilBlitUtils ...@@ -242,7 +242,7 @@ class DepthStencilBlitUtils
}; };
// util class for generating index buffer // util class for generating index buffer
class IndexGeneratorUtils class IndexGeneratorUtils final : angle::NonCopyable
{ {
public: public:
void onDestroy(); void onDestroy();
...@@ -310,6 +310,25 @@ class IndexGeneratorUtils ...@@ -310,6 +310,25 @@ class IndexGeneratorUtils
AutoObjCPtr<id<MTLComputePipelineState>> mTriFanFromArraysGeneratorPipeline; 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 // RenderUtils: container class of various util classes above
class RenderUtils : public Context, angle::NonCopyable class RenderUtils : public Context, angle::NonCopyable
{ {
...@@ -356,6 +375,12 @@ class RenderUtils : public Context, angle::NonCopyable ...@@ -356,6 +375,12 @@ class RenderUtils : public Context, angle::NonCopyable
angle::Result generateLineLoopLastSegmentFromElementsArray(ContextMtl *contextMtl, angle::Result generateLineLoopLastSegmentFromElementsArray(ContextMtl *contextMtl,
const IndexGenerationParams &params); 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: private:
// override ErrorHandler // override ErrorHandler
void handleError(GLenum error, void handleError(GLenum error,
...@@ -371,6 +396,7 @@ class RenderUtils : public Context, angle::NonCopyable ...@@ -371,6 +396,7 @@ class RenderUtils : public Context, angle::NonCopyable
ColorBlitUtils mColorBlitUtils; ColorBlitUtils mColorBlitUtils;
DepthStencilBlitUtils mDepthStencilBlitUtils; DepthStencilBlitUtils mDepthStencilBlitUtils;
IndexGeneratorUtils mIndexUtils; IndexGeneratorUtils mIndexUtils;
MipmapUtils mMipmapUtils;
}; };
} // namespace mtl } // namespace mtl
......
...@@ -77,6 +77,15 @@ struct IndexConversionUniform ...@@ -77,6 +77,15 @@ struct IndexConversionUniform
uint32_t padding[2]; 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, void GetBlitTexCoords(uint32_t srcWidth,
uint32_t srcHeight, uint32_t srcHeight,
const gl::Rectangle &srcRect, const gl::Rectangle &srcRect,
...@@ -570,6 +579,15 @@ angle::Result RenderUtils::generateLineLoopLastSegmentFromElementsArray( ...@@ -570,6 +579,15 @@ angle::Result RenderUtils::generateLineLoopLastSegmentFromElementsArray(
return mIndexUtils.generateLineLoopLastSegmentFromElementsArray(contextMtl, params); 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 implementation
ClearUtils::ClearUtils() = default; ClearUtils::ClearUtils() = default;
...@@ -1566,5 +1584,110 @@ angle::Result IndexGeneratorUtils::generateLineLoopLastSegmentFromElementsArrayC ...@@ -1566,5 +1584,110 @@ angle::Result IndexGeneratorUtils::generateLineLoopLastSegmentFromElementsArrayC
return generateLineLoopLastSegment(contextMtl, first, last, params.dstBuffer, params.dstOffset); 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 mtl
} // namespace rx } // namespace rx
...@@ -250,6 +250,10 @@ Texture::Texture(ContextMtl *context, ...@@ -250,6 +250,10 @@ Texture::Texture(ContextMtl *context,
if (!renderTargetOnly) if (!renderTargetOnly)
{ {
desc.usage = desc.usage | MTLTextureUsageShaderRead; desc.usage = desc.usage | MTLTextureUsageShaderRead;
if (context->getNativeFormatCaps(desc.pixelFormat).writable)
{
desc.usage = desc.usage | MTLTextureUsageShaderWrite;
}
} }
if (allowFormatView) if (allowFormatView)
......
...@@ -214,6 +214,11 @@ std::ostream &operator<<(std::ostream &stream, const PlatformParameters &pp) ...@@ -214,6 +214,11 @@ std::ostream &operator<<(std::ostream &stream, const PlatformParameters &pp)
stream << "_NoStencilOutput"; stream << "_NoStencilOutput";
} }
if (pp.eglParameters.genMultipleMipsPerPassFeature == EGL_FALSE)
{
stream << "_NoGenMultipleMipsPerPass";
}
return stream; return stream;
} }
......
...@@ -234,6 +234,13 @@ inline PlatformParameters WithNoShaderStencilOutput(const PlatformParameters &pa ...@@ -234,6 +234,13 @@ inline PlatformParameters WithNoShaderStencilOutput(const PlatformParameters &pa
return re; return re;
} }
inline PlatformParameters WithNoGenMultipleMipsPerPass(const PlatformParameters &params)
{
PlatformParameters re = params;
re.eglParameters.genMultipleMipsPerPassFeature = EGL_FALSE;
return re;
}
inline PlatformParameters WithRobustness(const PlatformParameters &params) inline PlatformParameters WithRobustness(const PlatformParameters &params)
{ {
PlatformParameters withRobustness = params; PlatformParameters withRobustness = params;
......
...@@ -61,7 +61,8 @@ struct EGLPlatformParameters ...@@ -61,7 +61,8 @@ struct EGLPlatformParameters
return std::tie(renderer, majorVersion, minorVersion, deviceType, presentPath, return std::tie(renderer, majorVersion, minorVersion, deviceType, presentPath,
debugLayersEnabled, contextVirtualization, transformFeedbackFeature, debugLayersEnabled, contextVirtualization, transformFeedbackFeature,
allocateNonZeroMemoryFeature, emulateCopyTexImage2DFromRenderbuffers, allocateNonZeroMemoryFeature, emulateCopyTexImage2DFromRenderbuffers,
shaderStencilOutputFeature, platformMethods, robustness); shaderStencilOutputFeature, genMultipleMipsPerPassFeature, platformMethods,
robustness);
} }
EGLint renderer = EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE; EGLint renderer = EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE;
...@@ -76,6 +77,7 @@ struct EGLPlatformParameters ...@@ -76,6 +77,7 @@ struct EGLPlatformParameters
EGLint allocateNonZeroMemoryFeature = EGL_DONT_CARE; EGLint allocateNonZeroMemoryFeature = EGL_DONT_CARE;
EGLint emulateCopyTexImage2DFromRenderbuffers = EGL_DONT_CARE; EGLint emulateCopyTexImage2DFromRenderbuffers = EGL_DONT_CARE;
EGLint shaderStencilOutputFeature = EGL_DONT_CARE; EGLint shaderStencilOutputFeature = EGL_DONT_CARE;
EGLint genMultipleMipsPerPassFeature = EGL_DONT_CARE;
angle::PlatformMethods *platformMethods = nullptr; angle::PlatformMethods *platformMethods = nullptr;
}; };
......
...@@ -201,6 +201,11 @@ bool EGLWindow::initializeDisplay(OSWindow *osWindow, ...@@ -201,6 +201,11 @@ bool EGLWindow::initializeDisplay(OSWindow *osWindow,
disabledFeatureOverrides.push_back("has_shader_stencil_output"); disabledFeatureOverrides.push_back("has_shader_stencil_output");
} }
if (params.genMultipleMipsPerPassFeature == EGL_FALSE)
{
disabledFeatureOverrides.push_back("gen_multiple_mips_per_pass");
}
if (!disabledFeatureOverrides.empty()) if (!disabledFeatureOverrides.empty())
{ {
if (strstr(extensionString, "EGL_ANGLE_feature_control") == nullptr) 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