Commit 5e13757b by Le Hoang Quyen Committed by Commit Bot

Metal: Fix array of structs containing array of samplers bug.

Previously ProgramMtl could try to bind fixed slots to samplers based on layout (set=..., binding=...). However, GLSL layout model is different from Metal slots assignment. For example, The following is valid layout in GLSL: - array samplers A[2] is bound to index 0. - array samplers B[2] is bound to index 1. It is invalid to do so in Metal, since A occupies 2 slots, thus samplers B[2] must be bound to slots starting from 2. New binding method: let spirv-cross auto assigns the texture slots and retrieve them after compilation. Incomplete textures moved to ContextMtl using IncompleteTextureSet. New test added: GLSLTest.ArrayOfStructContainingArrayOfSamplers. Bug: angleproject:2634 Change-Id: Ib0edaaf8b20512e1272c37c1d4b16a88a5b35e75 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2193193 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarJonah Ryan-Davis <jonahr@google.com>
parent 4b2a9cbc
......@@ -240,6 +240,10 @@ class ContextMtl : public ContextImpl, public mtl::Context
const mtl::VertexFormat &getVertexFormat(angle::FormatID angleFormatId,
bool tightlyPacked) const;
angle::Result getIncompleteTexture(const gl::Context *context,
gl::TextureType type,
gl::Texture **textureOut);
// Recommended to call these methods to end encoding instead of invoking the encoder's
// endEncoding() directly.
void endEncoding(mtl::RenderCommandEncoder *encoder);
......@@ -282,6 +286,7 @@ class ContextMtl : public ContextImpl, public mtl::Context
private:
void ensureCommandBufferValid();
angle::Result ensureIncompleteTexturesCreated(const gl::Context *context);
angle::Result setupDraw(const gl::Context *context,
gl::PrimitiveMode mode,
GLint firstVertex,
......@@ -455,6 +460,9 @@ class ContextMtl : public ContextImpl, public mtl::Context
DriverUniforms mDriverUniforms;
DefaultAttribute mDefaultAttributes[mtl::kMaxVertexAttribs];
IncompleteTextureSet mIncompleteTextures;
bool mIncompleteTexturesInitialized = false;
};
} // namespace rx
......
......@@ -121,6 +121,28 @@ void ContextMtl::onDestroy(const gl::Context *context)
{
mTriFanIndexBuffer.destroy(this);
mLineLoopIndexBuffer.destroy(this);
mIncompleteTextures.onDestroy(context);
mIncompleteTexturesInitialized = false;
}
angle::Result ContextMtl::ensureIncompleteTexturesCreated(const gl::Context *context)
{
if (ANGLE_LIKELY(mIncompleteTexturesInitialized))
{
return angle::Result::Continue;
}
constexpr gl::TextureType supportedTextureTypes[] = {gl::TextureType::_2D,
gl::TextureType::CubeMap};
for (gl::TextureType texType : supportedTextureTypes)
{
gl::Texture *texture;
ANGLE_UNUSED_VARIABLE(texture);
ANGLE_TRY(mIncompleteTextures.getIncompleteTexture(context, texType, nullptr, &texture));
}
mIncompleteTexturesInitialized = true;
return angle::Result::Continue;
}
// Flush and finish.
......@@ -519,6 +541,9 @@ angle::Result ContextMtl::syncState(const gl::Context *context,
{
const gl::State &glState = context->getState();
// Initialize incomplete texture set.
ANGLE_TRY(ensureIncompleteTexturesCreated(context));
for (size_t dirtyBit : dirtyBits)
{
switch (dirtyBit)
......@@ -1023,6 +1048,13 @@ const mtl::VertexFormat &ContextMtl::getVertexFormat(angle::FormatID angleFormat
return getDisplay()->getVertexFormat(angleFormatId, tightlyPacked);
}
angle::Result ContextMtl::getIncompleteTexture(const gl::Context *context,
gl::TextureType type,
gl::Texture **textureOut)
{
return mIncompleteTextures.getIncompleteTexture(context, type, nullptr, textureOut);
}
void ContextMtl::endEncoding(mtl::RenderCommandEncoder *encoder)
{
encoder->endEncoding();
......
......@@ -120,8 +120,6 @@ class DisplayMtl : public DisplayImpl
return mStateCache.getSamplerState(getMetalDevice(), desc);
}
const mtl::TextureRef &getNullTexture(const gl::Context *context, gl::TextureType type);
const mtl::Format &getPixelFormat(angle::FormatID angleFormatId) const
{
return mFormatTable.getPixelFormat(angleFormatId);
......@@ -158,8 +156,6 @@ class DisplayMtl : public DisplayImpl
// Built-in Shaders
mtl::AutoObjCPtr<id<MTLLibrary>> mDefaultShaders = nil;
angle::PackedEnumMap<gl::TextureType, mtl::TextureRef> mNullTextures;
mutable bool mCapsInitialized;
mutable gl::TextureCapsMap mNativeTextureCaps;
mutable gl::Extensions mNativeExtensions;
......
......@@ -95,10 +95,6 @@ angle::Result DisplayMtl::initializeImpl(egl::Display *display)
void DisplayMtl::terminate()
{
for (mtl::TextureRef &nullTex : mNullTextures)
{
nullTex.reset();
}
mUtils.onDestroy();
mCmdQueue.reset();
mDefaultShaders = nil;
......@@ -380,45 +376,6 @@ const gl::Extensions &DisplayMtl::getNativeExtensions() const
return mNativeExtensions;
}
const mtl::TextureRef &DisplayMtl::getNullTexture(const gl::Context *context, gl::TextureType type)
{
// TODO(hqle): Use rx::IncompleteTextureSet.
ContextMtl *contextMtl = mtl::GetImpl(context);
if (!mNullTextures[type])
{
// initialize content with zeros
MTLRegion region = MTLRegionMake2D(0, 0, 1, 1);
const uint8_t zeroPixel[4] = {0, 0, 0, 255};
const auto &rgbaFormat = getPixelFormat(angle::FormatID::R8G8B8A8_UNORM);
switch (type)
{
case gl::TextureType::_2D:
(void)(mtl::Texture::Make2DTexture(contextMtl, rgbaFormat, 1, 1, 1, false, false,
&mNullTextures[type]));
mNullTextures[type]->replaceRegion(contextMtl, region, 0, 0, zeroPixel,
sizeof(zeroPixel));
break;
case gl::TextureType::CubeMap:
(void)(mtl::Texture::MakeCubeTexture(contextMtl, rgbaFormat, 1, 1, false, false,
&mNullTextures[type]));
for (int f = 0; f < 6; ++f)
{
mNullTextures[type]->replaceRegion(contextMtl, region, 0, f, zeroPixel,
sizeof(zeroPixel));
}
break;
default:
UNREACHABLE();
// NOTE(hqle): Support more texture types.
}
ASSERT(mNullTextures[type]);
}
return mNullTextures[type];
}
void DisplayMtl::ensureCapsInitialized() const
{
if (mCapsInitialized)
......@@ -521,7 +478,7 @@ void DisplayMtl::ensureCapsInitialized() const
// Note that we currently implement textures as combined image+samplers, so the limit is
// the minimum of supported samplers and sampled images.
mNativeCaps.maxCombinedTextureImageUnits = mtl::kMaxShaderSamplers;
mNativeCaps.maxCombinedTextureImageUnits = mtl::kMaxGLSamplerBindings;
mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Fragment] = mtl::kMaxShaderSamplers;
mNativeCaps.maxShaderTextureImageUnits[gl::ShaderType::Vertex] = mtl::kMaxShaderSamplers;
......
......@@ -19,6 +19,7 @@
#include "libANGLE/renderer/ProgramImpl.h"
#include "libANGLE/renderer/glslang_wrapper_utils.h"
#include "libANGLE/renderer/metal/mtl_command_buffer.h"
#include "libANGLE/renderer/metal/mtl_glslang_utils.h"
#include "libANGLE/renderer/metal/mtl_resources.h"
#include "libANGLE/renderer/metal/mtl_state_cache.h"
......@@ -130,10 +131,6 @@ class ProgramMtl : public ProgramImpl
angle::Result linkImpl(const gl::Context *glContext,
const gl::ProgramLinkedResources &resources,
gl::InfoLog &infoLog);
angle::Result convertToMsl(const gl::Context *glContext,
gl::ShaderType shaderType,
gl::InfoLog &infoLog,
std::vector<uint32_t> *sprivCode);
angle::Result createMslShader(const gl::Context *glContext,
gl::ShaderType shaderType,
......@@ -158,6 +155,10 @@ class ProgramMtl : public ProgramImpl
gl::ShaderBitSet mSamplerBindingsDirty;
gl::ShaderMap<DefaultUniformBlock> mDefaultUniformBlocks;
gl::ShaderMap<std::string> mTranslatedMslShader;
gl::ShaderMap<mtl::TranslatedShaderInfo> mMslShaderTranslateInfo;
mtl::RenderPipelineCache mMetalRenderPipelineCache;
};
......
......@@ -13,8 +13,6 @@
#include <sstream>
#include <spirv_msl.hpp>
#include "common/debug.h"
#include "libANGLE/Context.h"
#include "libANGLE/ProgramLinkedResources.h"
......@@ -33,85 +31,6 @@ namespace
#define SHADER_ENTRY_NAME @"main0"
spv::ExecutionModel ShaderTypeToSpvExecutionModel(gl::ShaderType shaderType)
{
switch (shaderType)
{
case gl::ShaderType::Vertex:
return spv::ExecutionModelVertex;
case gl::ShaderType::Fragment:
return spv::ExecutionModelFragment;
default:
UNREACHABLE();
return spv::ExecutionModelMax;
}
}
// Some GLSL variables need 2 binding points in metal. For example,
// glsl sampler will be converted to 2 metal objects: texture and sampler.
// Thus we need to set 2 binding points for one glsl sampler variable.
using BindingField = uint32_t spirv_cross::MSLResourceBinding::*;
template <BindingField bindingField1, BindingField bindingField2 = bindingField1>
angle::Result BindResources(spirv_cross::CompilerMSL *compiler,
const spirv_cross::SmallVector<spirv_cross::Resource> &resources,
gl::ShaderType shaderType)
{
auto &compilerMsl = *compiler;
for (const spirv_cross::Resource &resource : resources)
{
spirv_cross::MSLResourceBinding resBinding;
resBinding.stage = ShaderTypeToSpvExecutionModel(shaderType);
if (compilerMsl.has_decoration(resource.id, spv::DecorationDescriptorSet))
{
resBinding.desc_set =
compilerMsl.get_decoration(resource.id, spv::DecorationDescriptorSet);
}
if (!compilerMsl.has_decoration(resource.id, spv::DecorationBinding))
{
continue;
}
resBinding.binding = compilerMsl.get_decoration(resource.id, spv::DecorationBinding);
uint32_t bindingPoint;
// NOTE(hqle): We use separate discrete binding point for now, in future, we should use
// one argument buffer for each descriptor set.
switch (resBinding.desc_set)
{
case 0:
// Use resBinding.binding as binding point.
bindingPoint = resBinding.binding;
break;
case mtl::kDriverUniformsBindingIndex:
bindingPoint = mtl::kDriverUniformsBindingIndex;
break;
case mtl::kDefaultUniformsBindingIndex:
// NOTE(hqle): Properly handle transform feedbacks and UBO binding once ES 3.0 is
// implemented.
bindingPoint = mtl::kDefaultUniformsBindingIndex;
break;
default:
// We don't support this descriptor set.
continue;
}
// bindingField can be buffer or texture, which will be translated to [[buffer(d)]] or
// [[texture(d)]] or [[sampler(d)]]
resBinding.*bindingField1 = bindingPoint;
if (bindingField1 != bindingField2)
{
resBinding.*bindingField2 = bindingPoint;
}
compilerMsl.add_msl_resource_binding(resBinding);
}
return angle::Result::Continue;
}
void InitDefaultUniformBlock(const std::vector<sh::Uniform> &uniforms,
gl::Shader *shader,
sh::BlockLayoutMap *blockLayoutMapOut,
......@@ -173,35 +92,6 @@ void UpdateDefaultUniformBlock(GLsizei count,
}
}
void BindNullSampler(const gl::Context *glContext,
mtl::RenderCommandEncoder *encoder,
gl::ShaderType shaderType,
gl::TextureType textureType,
int bindingPoint)
{
ContextMtl *contextMtl = mtl::GetImpl(glContext);
const mtl::TextureRef &nullTex =
contextMtl->getDisplay()->getNullTexture(glContext, textureType);
mtl::AutoObjCPtr<id<MTLSamplerState>> nullSampler =
contextMtl->getDisplay()->getStateCache().getNullSamplerState(contextMtl);
switch (shaderType)
{
case gl::ShaderType::Vertex:
encoder->setVertexTexture(nullTex, bindingPoint);
encoder->setVertexSamplerState(nullSampler, 0, 0, bindingPoint);
break;
case gl::ShaderType::Fragment:
encoder->setFragmentTexture(nullTex, bindingPoint);
encoder->setFragmentSamplerState(nullSampler, 0, 0, bindingPoint);
break;
default:
UNREACHABLE();
}
}
template <typename T>
void ReadFromDefaultUniformBlock(int componentCount,
uint32_t arrayIndex,
......@@ -259,6 +149,15 @@ void ProgramMtl::reset(ContextMtl *context)
block.uniformLayout.clear();
}
for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes())
{
for (mtl::SamplerBinding &binding :
mMslShaderTranslateInfo[shaderType].actualSamplerBindings)
{
binding.textureBinding = mtl::kMaxShaderSamplers;
}
}
mMetalRenderPipelineCache.clear();
}
......@@ -322,10 +221,15 @@ angle::Result ProgramMtl::linkImpl(const gl::Context *glContext,
shaderSources, variableInfoMap, &shaderCodes));
// Convert spirv code to MSL
ANGLE_TRY(convertToMsl(glContext, gl::ShaderType::Vertex, infoLog,
&shaderCodes[gl::ShaderType::Vertex]));
ANGLE_TRY(convertToMsl(glContext, gl::ShaderType::Fragment, infoLog,
&shaderCodes[gl::ShaderType::Fragment]));
ANGLE_TRY(mtl::SpirvCodeToMsl(contextMtl, mState, &shaderCodes, &mMslShaderTranslateInfo,
&mTranslatedMslShader));
for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes())
{
// Create actual Metal shader
ANGLE_TRY(
createMslShader(glContext, shaderType, infoLog, mTranslatedMslShader[shaderType]));
}
return angle::Result::Continue;
}
......@@ -421,59 +325,6 @@ angle::Result ProgramMtl::initDefaultUniformBlocks(const gl::Context *glContext)
return angle::Result::Continue;
}
angle::Result ProgramMtl::convertToMsl(const gl::Context *glContext,
gl::ShaderType shaderType,
gl::InfoLog &infoLog,
std::vector<uint32_t> *sprivCode)
{
ContextMtl *contextMtl = mtl::GetImpl(glContext);
spirv_cross::CompilerMSL compilerMsl(std::move(*sprivCode));
spirv_cross::CompilerMSL::Options compOpt;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
compOpt.platform = spirv_cross::CompilerMSL::Options::macOS;
#else
compOpt.platform = spirv_cross::CompilerMSL::Options::iOS;
#endif
if (ANGLE_APPLE_AVAILABLE_XCI(10.14, 13.0, 12))
{
// Use Metal 2.1
compOpt.set_msl_version(2, 1);
}
else
{
// Always use at least Metal 2.0.
compOpt.set_msl_version(2);
}
compilerMsl.set_msl_options(compOpt);
// Tell spirv-cross to map default & driver uniform blocks & samplers as we want
spirv_cross::ShaderResources mslRes = compilerMsl.get_shader_resources();
ANGLE_TRY(BindResources<&spirv_cross::MSLResourceBinding::msl_buffer>(
&compilerMsl, mslRes.uniform_buffers, shaderType));
ANGLE_TRY((BindResources<&spirv_cross::MSLResourceBinding::msl_sampler,
&spirv_cross::MSLResourceBinding::msl_texture>(
&compilerMsl, mslRes.sampled_images, shaderType)));
// NOTE(hqle): spirv-cross uses exceptions to report error, what should we do here
// in case of error?
std::string translatedMsl = compilerMsl.compile();
if (translatedMsl.size() == 0)
{
ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION);
}
// Create actual Metal shader
ANGLE_TRY(createMslShader(glContext, shaderType, infoLog, translatedMsl));
return angle::Result::Continue;
}
angle::Result ProgramMtl::createMslShader(const gl::Context *glContext,
gl::ShaderType shaderType,
gl::InfoLog &infoLog,
......@@ -868,7 +719,8 @@ angle::Result ProgramMtl::updateTextures(const gl::Context *glContext,
mtl::RenderCommandEncoder *cmdEncoder,
bool forceUpdate)
{
const auto &glState = glContext->getState();
ContextMtl *contextMtl = mtl::GetImpl(glContext);
const auto &glState = glContext->getState();
const gl::ActiveTexturesCache &completeTextures = glState.getActiveTexturesCache();
......@@ -886,18 +738,26 @@ angle::Result ProgramMtl::updateTextures(const gl::Context *glContext,
ASSERT(!samplerBinding.unreferenced);
const mtl::SamplerBinding &mslBinding =
mMslShaderTranslateInfo[shaderType].actualSamplerBindings[textureIndex];
if (mslBinding.textureBinding >= mtl::kMaxShaderSamplers)
{
// No binding assigned
continue;
}
gl::TextureType textureType = samplerBinding.textureType;
for (uint32_t arrayElement = 0; arrayElement < samplerBinding.boundTextureUnits.size();
++arrayElement)
{
GLuint textureUnit = samplerBinding.boundTextureUnits[arrayElement];
gl::Texture *texture = completeTextures[textureUnit];
auto destBindingPoint = textureIndex + arrayElement;
GLuint textureUnit = samplerBinding.boundTextureUnits[arrayElement];
gl::Texture *texture = completeTextures[textureUnit];
uint32_t textureSlot = mslBinding.textureBinding + arrayElement;
uint32_t samplerSlot = mslBinding.samplerBinding + arrayElement;
if (!texture)
{
BindNullSampler(glContext, cmdEncoder, shaderType, samplerBinding.textureType,
destBindingPoint);
continue;
ANGLE_TRY(contextMtl->getIncompleteTexture(glContext, textureType, &texture));
}
TextureMtl *textureMtl = mtl::GetImpl(texture);
......@@ -905,12 +765,12 @@ angle::Result ProgramMtl::updateTextures(const gl::Context *glContext,
switch (shaderType)
{
case gl::ShaderType::Vertex:
ANGLE_TRY(textureMtl->bindVertexShader(glContext, cmdEncoder,
destBindingPoint, destBindingPoint));
ANGLE_TRY(textureMtl->bindVertexShader(glContext, cmdEncoder, textureSlot,
samplerSlot));
break;
case gl::ShaderType::Fragment:
ANGLE_TRY(textureMtl->bindFragmentShader(
glContext, cmdEncoder, destBindingPoint, destBindingPoint));
ANGLE_TRY(textureMtl->bindFragmentShader(glContext, cmdEncoder, textureSlot,
samplerSlot));
break;
default:
UNREACHABLE();
......
......@@ -112,6 +112,9 @@ constexpr uint32_t kUniformBufferSettingOffsetMinAlignment = 4;
#endif
constexpr uint32_t kIndexBufferOffsetAlignment = 4;
// Front end binding limits
constexpr uint32_t kMaxGLSamplerBindings = 2 * kMaxShaderSamplers;
// Binding index start for vertex data buffers:
constexpr uint32_t kVboBindingIndexStart = 0;
......
......@@ -19,6 +19,19 @@ namespace rx
{
namespace mtl
{
struct SamplerBinding
{
uint32_t textureBinding = 0;
uint32_t samplerBinding = 0;
};
struct TranslatedShaderInfo
{
std::array<SamplerBinding, kMaxGLSamplerBindings> actualSamplerBindings;
// NOTE(hqle): UBO, XFB bindings.
};
void GlslangGetShaderSource(const gl::ProgramState &programState,
const gl::ProgramLinkedResources &resources,
gl::ShaderMap<std::string> *shaderSourcesOut,
......@@ -30,6 +43,14 @@ angle::Result GlslangGetShaderSpirvCode(ErrorHandler *context,
const gl::ShaderMap<std::string> &shaderSources,
const ShaderMapInterfaceVariableInfoMap &variableInfoMap,
gl::ShaderMap<std::vector<uint32_t>> *shaderCodeOut);
// Translate from SPIR-V code to Metal shader source code.
angle::Result SpirvCodeToMsl(Context *context,
const gl::ProgramState &programState,
gl::ShaderMap<std::vector<uint32_t>> *sprivShaderCode,
gl::ShaderMap<TranslatedShaderInfo> *mslShaderInfoOut,
gl::ShaderMap<std::string> *mslCodeOut);
} // namespace mtl
} // namespace rx
......
......@@ -8,7 +8,13 @@
#include "libANGLE/renderer/metal/mtl_glslang_utils.h"
#include <regex>
#include <spirv_msl.hpp>
#include "common/apple_platform_utils.h"
#include "libANGLE/renderer/glslang_wrapper_utils.h"
#include "libANGLE/renderer/metal/DisplayMtl.h"
namespace rx
{
......@@ -16,6 +22,17 @@ namespace mtl
{
namespace
{
constexpr uint32_t kGlslangTextureDescSet = 0;
constexpr uint32_t kGlslangDefaultUniformDescSet = 1;
constexpr uint32_t kGlslangDriverUniformsDescSet = 2;
constexpr uint32_t kGlslangShaderResourceDescSet = 3;
// Original mapping of front end from sampler name to multiple sampler slots (in form of
// slot:count pair)
using OriginalSamplerBindingMap =
std::unordered_map<std::string, std::vector<std::pair<uint32_t, uint32_t>>>;
angle::Result HandleError(ErrorHandler *context, GlslangError)
{
ANGLE_MTL_TRY(context, false);
......@@ -24,13 +41,14 @@ angle::Result HandleError(ErrorHandler *context, GlslangError)
void ResetGlslangProgramInterfaceInfo(GlslangProgramInterfaceInfo *programInterfaceInfo)
{
programInterfaceInfo->uniformsAndXfbDescriptorSetIndex = kDefaultUniformsBindingIndex;
programInterfaceInfo->currentUniformBindingIndex = 0;
programInterfaceInfo->textureDescriptorSetIndex = 0;
programInterfaceInfo->currentTextureBindingIndex = 0;
programInterfaceInfo->driverUniformsDescriptorSetIndex = kDriverUniformsBindingIndex;
// NOTE(hqle): Unused for now, until we support ES 3.0
programInterfaceInfo->shaderResourceDescriptorSetIndex = -1;
// These are binding options passed to glslang. The actual binding might be changed later
// by spirv-cross.
programInterfaceInfo->uniformsAndXfbDescriptorSetIndex = kGlslangDefaultUniformDescSet;
programInterfaceInfo->currentUniformBindingIndex = 0;
programInterfaceInfo->textureDescriptorSetIndex = kGlslangTextureDescSet;
programInterfaceInfo->currentTextureBindingIndex = 0;
programInterfaceInfo->driverUniformsDescriptorSetIndex = kGlslangDriverUniformsDescSet;
programInterfaceInfo->shaderResourceDescriptorSetIndex = kGlslangShaderResourceDescSet;
programInterfaceInfo->currentShaderResourceBindingIndex = 0;
programInterfaceInfo->locationsUsedForXfbExtension = 0;
......@@ -43,6 +61,165 @@ GlslangSourceOptions CreateSourceOptions()
GlslangSourceOptions options;
return options;
}
spv::ExecutionModel ShaderTypeToSpvExecutionModel(gl::ShaderType shaderType)
{
switch (shaderType)
{
case gl::ShaderType::Vertex:
return spv::ExecutionModelVertex;
case gl::ShaderType::Fragment:
return spv::ExecutionModelFragment;
default:
UNREACHABLE();
return spv::ExecutionModelMax;
}
}
void BindBuffers(spirv_cross::CompilerMSL *compiler,
const spirv_cross::SmallVector<spirv_cross::Resource> &resources,
gl::ShaderType shaderType)
{
auto &compilerMsl = *compiler;
for (const spirv_cross::Resource &resource : resources)
{
spirv_cross::MSLResourceBinding resBinding;
resBinding.stage = ShaderTypeToSpvExecutionModel(shaderType);
if (compilerMsl.has_decoration(resource.id, spv::DecorationDescriptorSet))
{
resBinding.desc_set =
compilerMsl.get_decoration(resource.id, spv::DecorationDescriptorSet);
}
if (!compilerMsl.has_decoration(resource.id, spv::DecorationBinding))
{
continue;
}
resBinding.binding = compilerMsl.get_decoration(resource.id, spv::DecorationBinding);
uint32_t bindingPoint = 0;
// NOTE(hqle): We use separate discrete binding point for now, in future, we should use
// one argument buffer for each descriptor set.
switch (resBinding.desc_set)
{
case kGlslangTextureDescSet:
// Texture binding point is ignored. We let spirv-cross automatically assign it and
// retrieve it later
continue;
case kGlslangDriverUniformsDescSet:
bindingPoint = mtl::kDriverUniformsBindingIndex;
break;
case kGlslangDefaultUniformDescSet:
// NOTE(hqle): Properly handle transform feedbacks binding.
if (shaderType != gl::ShaderType::Vertex || resBinding.binding == 0)
{
bindingPoint = mtl::kDefaultUniformsBindingIndex;
}
else
{
continue;
}
break;
default:
// We don't support this descriptor set.
continue;
}
resBinding.msl_buffer = bindingPoint;
compilerMsl.add_msl_resource_binding(resBinding);
} // for (resources)
}
void GetAssignedSamplerBindings(const spirv_cross::CompilerMSL &compilerMsl,
const OriginalSamplerBindingMap &originalBindings,
std::array<SamplerBinding, mtl::kMaxGLSamplerBindings> *bindings)
{
for (const spirv_cross::Resource &resource : compilerMsl.get_shader_resources().sampled_images)
{
uint32_t descriptorSet = 0;
if (compilerMsl.has_decoration(resource.id, spv::DecorationDescriptorSet))
{
descriptorSet = compilerMsl.get_decoration(resource.id, spv::DecorationDescriptorSet);
}
// We already assigned descriptor set 0 to textures. Just to double check.
ASSERT(descriptorSet == kGlslangTextureDescSet);
ASSERT(compilerMsl.has_decoration(resource.id, spv::DecorationBinding));
uint32_t actualTextureSlot = compilerMsl.get_automatic_msl_resource_binding(resource.id);
uint32_t actualSamplerSlot =
compilerMsl.get_automatic_msl_resource_binding_secondary(resource.id);
// Assign sequential index for subsequent array elements
const std::vector<std::pair<uint32_t, uint32_t>> &resOrignalBindings =
originalBindings.at(resource.name);
uint32_t currentTextureSlot = actualTextureSlot;
uint32_t currentSamplerSlot = actualSamplerSlot;
for (const std::pair<uint32_t, uint32_t> &originalBindingRange : resOrignalBindings)
{
SamplerBinding &actualBinding = bindings->at(originalBindingRange.first);
actualBinding.textureBinding = currentTextureSlot;
actualBinding.samplerBinding = currentSamplerSlot;
currentTextureSlot += originalBindingRange.second;
currentSamplerSlot += originalBindingRange.second;
}
}
}
// Customized spirv-cross compiler
class SpirvToMslCompiler : public spirv_cross::CompilerMSL
{
public:
SpirvToMslCompiler(std::vector<uint32_t> &&spriv) : spirv_cross::CompilerMSL(spriv) {}
std::string compileEx(gl::ShaderType shaderType,
const OriginalSamplerBindingMap &originalSamplerBindings,
TranslatedShaderInfo *mslShaderInfoOut)
{
spirv_cross::CompilerMSL::Options compOpt;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
compOpt.platform = spirv_cross::CompilerMSL::Options::macOS;
#else
compOpt.platform = spirv_cross::CompilerMSL::Options::iOS;
#endif
if (ANGLE_APPLE_AVAILABLE_XCI(10.14, 13.0, 12))
{
// Use Metal 2.1
compOpt.set_msl_version(2, 1);
}
else
{
// Always use at least Metal 2.0.
compOpt.set_msl_version(2);
}
compOpt.pad_fragment_output_components = true;
// Tell spirv-cross to map default & driver uniform blocks as we want
spirv_cross::ShaderResources mslRes = spirv_cross::CompilerMSL::get_shader_resources();
BindBuffers(this, mslRes.uniform_buffers, shaderType);
spirv_cross::CompilerMSL::set_msl_options(compOpt);
// Actual compilation
std::string translatedMsl = spirv_cross::CompilerMSL::compile();
// Retrieve automatic texture slot assignments
GetAssignedSamplerBindings(*this, originalSamplerBindings,
&mslShaderInfoOut->actualSamplerBindings);
return translatedMsl;
}
};
} // namespace
void GlslangGetShaderSource(const gl::ProgramState &programState,
......@@ -69,5 +246,48 @@ angle::Result GlslangGetShaderSpirvCode(ErrorHandler *context,
[context](GlslangError error) { return HandleError(context, error); }, linkedShaderStages,
glCaps, shaderSources, variableInfoMap, shaderCodeOut);
}
angle::Result SpirvCodeToMsl(Context *context,
const gl::ProgramState &programState,
gl::ShaderMap<std::vector<uint32_t>> *sprivShaderCode,
gl::ShaderMap<TranslatedShaderInfo> *mslShaderInfoOut,
gl::ShaderMap<std::string> *mslCodeOut)
{
// Retrieve original sampler bindings produced by front end.
OriginalSamplerBindingMap originalSamplerBindings;
const std::vector<gl::SamplerBinding> &samplerBindings = programState.getSamplerBindings();
const std::vector<gl::LinkedUniform> &uniforms = programState.getUniforms();
for (uint32_t textureIndex = 0; textureIndex < samplerBindings.size(); ++textureIndex)
{
const gl::SamplerBinding &samplerBinding = samplerBindings[textureIndex];
uint32_t uniformIndex = programState.getUniformIndexFromSamplerIndex(textureIndex);
const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
std::string mappedSamplerName = GlslangGetMappedSamplerName(samplerUniform.name);
originalSamplerBindings[mappedSamplerName].push_back(
{textureIndex, static_cast<uint32_t>(samplerBinding.boundTextureUnits.size())});
}
// Do the actual translation
for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes())
{
std::vector<uint32_t> &sprivCode = sprivShaderCode->at(shaderType);
SpirvToMslCompiler compilerMsl(std::move(sprivCode));
// NOTE(hqle): spirv-cross uses exceptions to report error, what should we do here
// in case of error?
std::string translatedMsl = compilerMsl.compileEx(shaderType, originalSamplerBindings,
&mslShaderInfoOut->at(shaderType));
if (translatedMsl.size() == 0)
{
ANGLE_MTL_CHECK(context, false, GL_INVALID_OPERATION);
}
mslCodeOut->at(shaderType) = std::move(translatedMsl);
} // for (gl::ShaderType shaderType
return angle::Result::Continue;
}
} // namespace mtl
} // namespace rx
......@@ -2276,6 +2276,47 @@ void main()
EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::red);
}
// Test that array of structs containing array of samplers work as expected.
TEST_P(GLSLTest, ArrayOfStructContainingArrayOfSamplers)
{
constexpr char kFS[] =
"precision mediump float;\n"
"struct Data { mediump sampler2D data[2]; };\n"
"uniform Data test[2];\n"
"void main() {\n"
" gl_FragColor = vec4(texture2D(test[1].data[1], vec2(0.0, 0.0)).r,\n"
" texture2D(test[1].data[0], vec2(0.0, 0.0)).r,\n"
" texture2D(test[0].data[1], vec2(0.0, 0.0)).r,\n"
" texture2D(test[0].data[0], vec2(0.0, 0.0)).r);\n"
"}\n";
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
glUseProgram(program.get());
GLTexture textures[4];
GLColor expected = MakeGLColor(32, 64, 96, 255);
GLubyte data[8] = {}; // 4 bytes of padding, so that texture can be initialized with 4 bytes
memcpy(data, expected.data(), sizeof(expected));
for (int i = 0; i < 4; i++)
{
int outerIdx = i % 2;
int innerIdx = i / 2;
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, textures[i]);
// Each element provides two components.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data + i);
std::stringstream uniformName;
uniformName << "test[" << innerIdx << "].data[" << outerIdx << "]";
// Then send it as a uniform
GLint uniformLocation = glGetUniformLocation(program.get(), uniformName.str().c_str());
// The uniform should be active.
EXPECT_NE(uniformLocation, -1);
glUniform1i(uniformLocation, 3 - i);
}
drawQuad(program.get(), essl1_shaders::PositionAttrib(), 0.5f);
EXPECT_PIXEL_COLOR_EQ(0, 0, expected);
}
// Test that two constructors which have vec4 and mat2 parameters get disambiguated (issue in
// HLSL).
TEST_P(GLSLTest_ES3, AmbiguousConstructorCall2x2)
......
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